// SPDX-License-Identifier: Unlicense

#include "openxr.h"

#ifdef SHR3D_OPENXR_PCVR

#include "camera.h"
#include "data.h"
#include "global.h"
#include "glsl.h"
#include "highway.h"
#include "highway2.h"
#include "hud.h"
#include "opengl.h"
#include "shader.h"
#include "stage.h"
#include "ui.h"
#include "texture.h"
#include "xr.h"

#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#include <openxr/openxr_reflection.h>

#ifdef SHR3D_WINDOW_SDL
#include <SDL_syswm.h>
#endif // SHR3D_WINDOW_SDL

#include <algorithm>
#include <array>
#include <list>
#include <locale>
#include <map>
#include <memory>
#include <stdarg.h>
#include <string>
#include <vector>

static XrInstance m_instance{ XR_NULL_HANDLE };
static XrSession m_session{ XR_NULL_HANDLE };
static XrSpace m_appSpace{ XR_NULL_HANDLE };
static XrSpace m_stageSpace{ XR_NULL_HANDLE };
static XrSystemId m_systemId{ XR_NULL_SYSTEM_ID };
static std::vector<XrViewConfigurationView> m_configViews;
struct Swapchain {
  XrSwapchain handle;
  int32_t width;
  int32_t height;
};
static std::vector<Swapchain> m_swapchains;
static std::map<XrSwapchain, std::vector<XrSwapchainImageBaseHeader*>> m_swapchainImages;
static std::vector<XrView> m_views;

static std::vector<XrSpace> m_visualizedSpaces;

namespace Side {
  const int LEFT = 0;
  const int RIGHT = 1;
  const int COUNT = 2;
}

struct InputState {
  XrActionSet actionSet{ XR_NULL_HANDLE };
  XrAction grabAction{ XR_NULL_HANDLE };
  //XrAction poseAction{ XR_NULL_HANDLE };
  XrAction vibrateAction{ XR_NULL_HANDLE };
  XrAction quitAction{ XR_NULL_HANDLE };
  std::array<XrPath, Side::COUNT> handSubactionPath;
  std::array<XrSpace, Side::COUNT> handSpace;
  std::array<XrSpace, Side::COUNT> aimSpace;

  XrAction gripPoseAction{ XR_NULL_HANDLE };
  XrAction aimPoseAction{ XR_NULL_HANDLE };
  XrAction hapticAction{ XR_NULL_HANDLE };

  XrAction thumbstickValueAction{ XR_NULL_HANDLE };
  XrAction thumbstickClickAction{ XR_NULL_HANDLE };
  XrAction thumbstickTouchAction{ XR_NULL_HANDLE };
  XrAction triggerValueAction{ XR_NULL_HANDLE };
  //XrAction triggerClickAction{ XR_NULL_HANDLE }; // (does not exist outside VDXR Runtime)
  XrAction triggerTouchAction{ XR_NULL_HANDLE };
  XrAction squeezeValueAction{ XR_NULL_HANDLE };
  //XrAction squeezeClickAction{ XR_NULL_HANDLE }; // (does not exist outside VDXR Runtime)

  XrAction AAction{ XR_NULL_HANDLE };
  XrAction BAction{ XR_NULL_HANDLE };
  XrAction XAction{ XR_NULL_HANDLE };
  XrAction YAction{ XR_NULL_HANDLE };
  XrAction ATouchAction{ XR_NULL_HANDLE };
  XrAction BTouchAction{ XR_NULL_HANDLE };
  XrAction XTouchAction{ XR_NULL_HANDLE };
  XrAction YTouchAction{ XR_NULL_HANDLE };
  XrAction menuAction{ XR_NULL_HANDLE };

  //eye tracking
  XrAction gazeAction{ XR_NULL_HANDLE };
  XrSpace  gazeActionSpace;
  XrBool32 gazeActive;
};

// Application's current lifecycle state according to the runtime
XrSessionState m_sessionState{ XR_SESSION_STATE_UNKNOWN };
static bool m_sessionRunning{ false };

static XrEventDataBuffer m_eventDataBuffer;
static InputState m_input;

#ifdef SHR3D_OPENXR_PCVR
typedef struct XrGraphicsBindingOpenGLWin32KHR {
  XrStructureType             type;
  const void* XR_MAY_ALIAS    next;
  HDC                         hDC;
  HGLRC                       hGLRC;
} XrGraphicsBindingOpenGLWin32KHR;
static XrGraphicsBindingOpenGLWin32KHR m_graphicsBinding{ XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR };
static std::list<std::vector<XrSwapchainImageOpenGLKHR>> m_swapchainImageBuffers;
#else // SHR3D_OPENXR_PCVR
#endif // SHR3D_OPENXR_PCVR

//static XrPosef mControllerPose[2];
static GLuint m_swapchainFramebuffer{ 0 };

static GLint texture_location{ 0 };

// Map color buffer to associated depth buffer. This map is populated on demand.
static std::map<uint32_t, uint32_t> m_colorToDepthMap;

#define ENUM_CASE_STR(name, val) case name: return #name;
#define MAKE_TO_STRING_FUNC(enumType)                  \
    inline const char* to_string(enumType e) {         \
        switch (e) {                                   \
            XR_LIST_ENUM_##enumType(ENUM_CASE_STR)     \
            default: return "Unknown " #enumType;      \
        }                                              \
    }
// clang-format on

MAKE_TO_STRING_FUNC(XrReferenceSpaceType);
MAKE_TO_STRING_FUNC(XrViewConfigurationType);
MAKE_TO_STRING_FUNC(XrEnvironmentBlendMode);
MAKE_TO_STRING_FUNC(XrSessionState);
MAKE_TO_STRING_FUNC(XrResult);
MAKE_TO_STRING_FUNC(XrFormFactor);

inline std::string Fmt(const char* fmt, ...) {
  va_list vl;
  va_start(vl, fmt);
  int size = std::vsnprintf(nullptr, 0, fmt, vl);
  va_end(vl);

  if (size != -1) {
    std::unique_ptr<char[]> buffer(new char[size + 1]);

    va_start(vl, fmt);
    size = std::vsnprintf(buffer.get(), size + 1, fmt, vl);
    va_end(vl);
    if (size != -1) {
      return std::string(buffer.get(), size);
    }
  }

  throw std::runtime_error("Unexpected vsnprintf failure");
}

[[noreturn]] inline void Throw(std::string failureMessage, const char* originator = nullptr, const char* sourceLocation = nullptr) {
  if (originator != nullptr) {
    failureMessage += Fmt("\n    Origin: %s", originator);
  }
  if (sourceLocation != nullptr) {
    failureMessage += Fmt("\n    Source: %s", sourceLocation);
  }

  throw std::logic_error(failureMessage);
}

#define THROW(msg) Throw(msg, nullptr, FILE_AND_LINE);
#define CHECK(exp)                                      \
    {                                                   \
        if (!(exp)) {                                   \
            Throw("Check failed", #exp, FILE_AND_LINE); \
        }                                               \
    }
#define CHECK_MSG(exp, msg)                  \
    {                                        \
        if (!(exp)) {                        \
            Throw(msg, #exp, FILE_AND_LINE); \
        }                                    \
    }

[[noreturn]] inline void ThrowXrResult(XrResult res, const char* originator = nullptr, const char* sourceLocation = nullptr) {
  Throw(Fmt("XrResult failure [%s]", to_string(res)), originator, sourceLocation);
}

inline XrResult CheckXrResult(XrResult res, const char* originator = nullptr, const char* sourceLocation = nullptr) {
  if (XR_FAILED(res)) {
    ThrowXrResult(res, originator, sourceLocation);
  }

  return res;
}

#define CHK_STRINGIFY(x) #x
#define TOSTRING(x) CHK_STRINGIFY(x)
#define FILE_AND_LINE __FILE__ ":" TOSTRING(__LINE__)

#define THROW_XR(xr, cmd) ThrowXrResult(xr, #cmd, FILE_AND_LINE);
#define OXR(cmd) CheckXrResult(cmd, #cmd, FILE_AND_LINE);
#define CHECK_XRRESULT(res, cmdStr) CheckXrResult(res, cmdStr, FILE_AND_LINE);

#ifdef SHR3D_OPENXR_PCVR
static std::vector<XrSwapchainImageBaseHeader*> AllocateSwapchainImageStructs(uint32_t capacity, const XrSwapchainCreateInfo& /*swapchainCreateInfo*/) {
  // Allocate and initialize the buffer of image structs (must be sequential in memory for xrEnumerateSwapchainImages).
  // Return back an array of pointers to each swapchain image struct so the consumer doesn't need to know the type/size.
  std::vector<XrSwapchainImageOpenGLKHR> swapchainImageBuffer(capacity);
  std::vector<XrSwapchainImageBaseHeader*> swapchainImageBase;
  for (XrSwapchainImageOpenGLKHR& image : swapchainImageBuffer) {
    image.type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR;
    swapchainImageBase.push_back(reinterpret_cast<XrSwapchainImageBaseHeader*>(&image));
  }

  // Keep the buffer alive by moving it into the list of buffers.
  m_swapchainImageBuffers.push_back(std::move(swapchainImageBuffer));

  return swapchainImageBase;
}
#endif // SHR3D_OPENXR_PCVR

static void CreateSwapchains() {
  CHECK(m_session != XR_NULL_HANDLE);
  CHECK(m_swapchains.empty());
  CHECK(m_configViews.empty());

  // Read graphics properties for preferred swapchain length and logging.
  XrSystemProperties systemProperties{ XR_TYPE_SYSTEM_PROPERTIES };
  OXR(xrGetSystemProperties(m_instance, m_systemId, &systemProperties));

  uint32_t viewCount;
  OXR(xrEnumerateViewConfigurationViews(m_instance, m_systemId, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &viewCount, nullptr));
  m_configViews.resize(viewCount, { XR_TYPE_VIEW_CONFIGURATION_VIEW });
  OXR(xrEnumerateViewConfigurationViews(m_instance, m_systemId, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, viewCount, &viewCount, m_configViews.data()));

  ASSERT(viewCount == 2);
  ASSERT(m_configViews[0].recommendedImageRectWidth == m_configViews[1].recommendedImageRectWidth);
  ASSERT(m_configViews[0].recommendedImageRectHeight == m_configViews[1].recommendedImageRectHeight);
  Global::xrResolutionWidth = m_configViews[0].recommendedImageRectWidth;
  Global::xrResolutionHeight = m_configViews[0].recommendedImageRectHeight;

  m_views.resize(viewCount, { XR_TYPE_VIEW });

  // Create the swapchain and get the images.
  if (viewCount > 0) {
    // Select a swapchain format.
    uint32_t swapchainFormatCount;
    OXR(xrEnumerateSwapchainFormats(m_session, 0, &swapchainFormatCount, nullptr));
    std::vector<int64_t> swapchainFormats(swapchainFormatCount);
    OXR(xrEnumerateSwapchainFormats(m_session, (uint32_t)swapchainFormats.size(), &swapchainFormatCount,
      swapchainFormats.data()));
    CHECK(swapchainFormatCount == swapchainFormats.size());
    const int64_t colorSwapchainFormat = GL_SRGB8_ALPHA8;
    ASSERT(std::find(swapchainFormats.begin(), swapchainFormats.end(), colorSwapchainFormat) != swapchainFormats.end());

    // Create a swapchain for each view.
    for (uint32_t i = 0; i < viewCount; ++i)
    {
      const XrViewConfigurationView& vp = m_configViews[i];

      // Create the swapchain.
      XrSwapchainCreateInfo swapchainCreateInfo{ XR_TYPE_SWAPCHAIN_CREATE_INFO };
      swapchainCreateInfo.arraySize = 1;
      swapchainCreateInfo.format = colorSwapchainFormat;
      swapchainCreateInfo.width = vp.recommendedImageRectWidth;
      swapchainCreateInfo.height = vp.recommendedImageRectHeight;
      swapchainCreateInfo.mipCount = 1;
      swapchainCreateInfo.faceCount = 1;
#if defined(PLATFORM_OPENXR_ANDROID) && defined(SHR3D_GRAPHICS_MSAA)
      if (const GLsizei msaa = 1 << Settings::graphicsMSAA; msaa >= 2)
      {
        swapchainCreateInfo.sampleCount = 2;
      }
      else
#endif // PLATFORM_OPENXR_ANDROID && SHR3D_GRAPHICS_MSAA
      {
        swapchainCreateInfo.sampleCount = vp.recommendedSwapchainSampleCount;
        ASSERT(swapchainCreateInfo.sampleCount == 1);
      }
      swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
      Swapchain swapchain;
      swapchain.width = swapchainCreateInfo.width;
      swapchain.height = swapchainCreateInfo.height;
      OXR(xrCreateSwapchain(m_session, &swapchainCreateInfo, &swapchain.handle));

      m_swapchains.push_back(swapchain);

      uint32_t imageCount;
      OXR(xrEnumerateSwapchainImages(swapchain.handle, 0, &imageCount, nullptr));
      // XXX This should really just return XrSwapchainImageBaseHeader*

#ifdef SHR3D_OPENXR_PCVR
      std::vector<XrSwapchainImageBaseHeader*> swapchainImages = AllocateSwapchainImageStructs(imageCount, swapchainCreateInfo);
      OXR(xrEnumerateSwapchainImages(swapchain.handle, imageCount, &imageCount, swapchainImages[0]));

      m_swapchainImages.insert(std::make_pair(swapchain.handle, std::move(swapchainImages)));
#endif // SHR3D_OPENXR_PCVR
    }
  }
}

void OpenXr::init()
{
#ifdef SHR3D_OPENXR_PCVR
  ASSERT(Settings::xrEnabled);
#else // SHR3D_OPENXR_PCVR
#endif // SHR3D_OPENXR_PCVR

  if (m_instance == XR_NULL_HANDLE)
  {
    XrInstanceCreateInfo createInfo{ XR_TYPE_INSTANCE_CREATE_INFO };
    createInfo.next = nullptr;

    std::vector<const char*> extensions = {
#ifdef SHR3D_OPENXR_PCVR
      XR_KHR_OPENGL_ENABLE_EXTENSION_NAME
#else // SHR3D_OPENXR_PCVR
      XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME,
      XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME,
      XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME,
      XR_FB_PASSTHROUGH_EXTENSION_NAME,
      XR_FB_TRIANGLE_MESH_EXTENSION_NAME
#endif // SHR3D_OPENXR_PCVR
    };

    createInfo.enabledExtensionCount = (uint32_t)extensions.size();
    createInfo.enabledExtensionNames = extensions.data();

    strcpy(createInfo.applicationInfo.applicationName, "Shr3D");
    createInfo.applicationInfo.apiVersion = XR_API_VERSION_1_0;
    OXR(xrCreateInstance(&createInfo, &m_instance));

    ASSERT(m_instance != XR_NULL_HANDLE);
  }

  {
    XrInstanceProperties instanceProperties{ XR_TYPE_INSTANCE_PROPERTIES };
    xrGetInstanceProperties(m_instance, &instanceProperties);
  }

  {
    XrSystemGetInfo systemInfo{ XR_TYPE_SYSTEM_GET_INFO };
    systemInfo.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
    const XrResult result = xrGetSystem(m_instance, &systemInfo, &m_systemId); // will fail if no headset present

#ifdef SHR3D_OPENXR_PCVR
    Global::xrInitialized = result == XrResult::XR_SUCCESS;
#endif // SHR3D_OPENXR_PCVR
  }

#ifdef SHR3D_OPENXR_PCVR
  if (Global::xrInitialized)
#endif // SHR3D_OPENXR_PCVR
  {
    {
      uint32_t count;
      OXR(xrEnumerateEnvironmentBlendModes(m_instance, m_systemId, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &count, nullptr));
      CHECK(count > 0);
      {
        std::vector<XrEnvironmentBlendMode> blendModes(count);
        OXR(xrEnumerateEnvironmentBlendModes(m_instance, m_systemId, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, count, &count, blendModes.data()));
      }
    }

    {
      uint32_t viewConfigTypeCount;
      OXR(xrEnumerateViewConfigurations(m_instance, m_systemId, 0, &viewConfigTypeCount, nullptr));
      std::vector<XrViewConfigurationType> viewConfigTypes(viewConfigTypeCount);
      OXR(xrEnumerateViewConfigurations(m_instance, m_systemId, viewConfigTypeCount, &viewConfigTypeCount,
        viewConfigTypes.data()));
      CHECK((uint32_t)viewConfigTypes.size() == viewConfigTypeCount);


      for (XrViewConfigurationType viewConfigType : viewConfigTypes)
      {
        XrViewConfigurationProperties viewConfigProperties{ XR_TYPE_VIEW_CONFIGURATION_PROPERTIES };
        OXR(xrGetViewConfigurationProperties(m_instance, m_systemId, viewConfigType, &viewConfigProperties));

        uint32_t viewCount;
        OXR(xrEnumerateViewConfigurationViews(m_instance, m_systemId, viewConfigType, 0, &viewCount, nullptr));
        if (viewCount > 0)
        {
          std::vector<XrViewConfigurationView> views(viewCount, { XR_TYPE_VIEW_CONFIGURATION_VIEW });
          OXR(xrEnumerateViewConfigurationViews(m_instance, m_systemId, viewConfigType, viewCount, &viewCount, views.data()));
        }
        else
        {
          ASSERT(false);
        }
      }
    }

#ifdef SHR3D_OPENXR_PCVR
    {
      PFN_xrGetOpenGLGraphicsRequirementsKHR pfnGetOpenGLGraphicsRequirementsKHR = nullptr;
      OXR(xrGetInstanceProcAddr(m_instance, "xrGetOpenGLGraphicsRequirementsKHR", reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetOpenGLGraphicsRequirementsKHR)));

      XrGraphicsRequirementsOpenGLKHR graphicsRequirements{ XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR };
      OXR(pfnGetOpenGLGraphicsRequirementsKHR(m_instance, m_systemId, &graphicsRequirements));
    }
#endif // SHR3D_OPENXR_PCVR
  }
}

//static bool EqualsIgnoreCase(const std::string& s1, const std::string& s2, const std::locale& loc = std::locale()) {
//  const std::ctype<char>& ctype = std::use_facet<std::ctype<char>>(loc);
//  const auto compareCharLower = [&](char c1, char c2) { return ctype.tolower(c1) == ctype.tolower(c2); };
//  return s1.size() == s2.size() && std::equal(s1.begin(), s1.end(), s2.begin(), compareCharLower);
//}

//namespace Math {
//  namespace Pose {
//    XrPosef Identity() {
//      XrPosef t{};
//      t.orientation.w = 1;
//      return t;
//    }
//
//    XrPosef Translation(const XrVector3f& translation) {
//      XrPosef t = Identity();
//      t.position = translation;
//      return t;
//    }
//
//    XrPosef RotateCCWAboutYAxis(float radians, XrVector3f translation) {
//      XrPosef t = Identity();
//      t.orientation.x = 0.f;
//      t.orientation.y = std::sin(radians * 0.5f);
//      t.orientation.z = 0.f;
//      t.orientation.w = std::cos(radians * 0.5f);
//      t.position = translation;
//      return t;
//    }
//  }
//}

//static XrReferenceSpaceCreateInfo GetXrReferenceSpaceCreateInfo(const std::string& referenceSpaceTypeStr)
//{
//  XrReferenceSpaceCreateInfo referenceSpaceCreateInfo{ XR_TYPE_REFERENCE_SPACE_CREATE_INFO };
//  referenceSpaceCreateInfo.poseInReferenceSpace = Math::Pose::Identity();
//  if (referenceSpaceTypeStr == "View") {
//    referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
//  }
//  else if (referenceSpaceTypeStr == "ViewFront") {
//    // Render head-locked 2m in front of device.
//    referenceSpaceCreateInfo.poseInReferenceSpace = Math::Pose::Translation({ 0.f, 0.f, -2.f }),
//      referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
//  }
//  else if (referenceSpaceTypeStr == "Local") {
//    referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
//  }
//  else if (referenceSpaceTypeStr == "Stage") {
//    referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
//  }
//  else if (referenceSpaceTypeStr == "StageLeft") {
//    referenceSpaceCreateInfo.poseInReferenceSpace = Math::Pose::RotateCCWAboutYAxis(0.f, { -2.f, 0.f, -2.f });
//    referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
//  }
//  else if (referenceSpaceTypeStr == "StageRight") {
//    referenceSpaceCreateInfo.poseInReferenceSpace = Math::Pose::RotateCCWAboutYAxis(0.f, { 2.f, 0.f, -2.f });
//    referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
//  }
//  else if (referenceSpaceTypeStr == "StageLeftRotated") {
//    referenceSpaceCreateInfo.poseInReferenceSpace = Math::Pose::RotateCCWAboutYAxis(3.14f / 3.f, { -2.f, 0.5f, -2.f });
//    referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
//  }
//  else if (referenceSpaceTypeStr == "StageRightRotated") {
//    referenceSpaceCreateInfo.poseInReferenceSpace = Math::Pose::RotateCCWAboutYAxis(-3.14f / 3.f, { 2.f, 0.5f, -2.f });
//    referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
//  }
//  else {
//    unreachable();
//    //throw std::invalid_argument(Fmt("Unknown reference space type '%s'", referenceSpaceTypeStr.c_str()));
//  }
//  return referenceSpaceCreateInfo;
//}

static const XrEventDataBaseHeader* TryReadNextEvent() {
  // It is sufficient to clear the just the XrEventDataBuffer header to
  // XR_TYPE_EVENT_DATA_BUFFER
  XrEventDataBaseHeader* baseHeader = reinterpret_cast<XrEventDataBaseHeader*>(&m_eventDataBuffer);
  *baseHeader = { XR_TYPE_EVENT_DATA_BUFFER };
  const XrResult xr = xrPollEvent(m_instance, &m_eventDataBuffer);
  if (xr == XR_SUCCESS) {
    if (baseHeader->type == XR_TYPE_EVENT_DATA_EVENTS_LOST) {
      //const XrEventDataEventsLost* const eventsLost = reinterpret_cast<const XrEventDataEventsLost*>(baseHeader);
      //Log::Write(Log::Level::Warning, Fmt("%d events lost", eventsLost));
    }

    return baseHeader;
  }
  if (xr == XR_EVENT_UNAVAILABLE) {
    return nullptr;
  }
  //THROW_XR(xr, "xrPollEvent");
  unreachable(); // xrPollEvent
}

static void HandleSessionStateChangedEvent(const XrEventDataSessionStateChanged& stateChangedEvent, bool* exitRenderLoop,
  bool* requestRestart) {
  //const XrSessionState oldState = m_sessionState;
  m_sessionState = stateChangedEvent.state;

  //Log::Write(Log::Level::Info, Fmt("XrEventDataSessionStateChanged: state %s->%s session=%lld time=%lld", to_string(oldState), to_string(m_sessionState), stateChangedEvent.session, stateChangedEvent.time));

  if ((stateChangedEvent.session != XR_NULL_HANDLE) && (stateChangedEvent.session != m_session)) {
    //Log::Write(Log::Level::Error, "XrEventDataSessionStateChanged for unknown session");
    return;
  }

  switch (m_sessionState) {
  case XR_SESSION_STATE_READY: {
    CHECK(m_session != XR_NULL_HANDLE);
    XrSessionBeginInfo sessionBeginInfo{ XR_TYPE_SESSION_BEGIN_INFO };
    sessionBeginInfo.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
    OXR(xrBeginSession(m_session, &sessionBeginInfo));
    m_sessionRunning = true;
    break;
  }
  case XR_SESSION_STATE_STOPPING: {
    CHECK(m_session != XR_NULL_HANDLE);
    m_sessionRunning = false;
    OXR(xrEndSession(m_session))
      break;
  }
  case XR_SESSION_STATE_EXITING: {
    *exitRenderLoop = true;
    // Do not attempt to restart because user closed this session.
    *requestRestart = false;
    break;
  }
  case XR_SESSION_STATE_LOSS_PENDING: {
    *exitRenderLoop = true;
    // Poll for a new instance.
    *requestRestart = true;
    break;
  }
  default:
    break;
  }
}

//static void LogActionSourceName(XrAction action, const std::string& actionName) {
//  XrBoundSourcesForActionEnumerateInfo getInfo = { XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO };
//  getInfo.action = action;
//  uint32_t pathCount = 0;
//  OXR(xrEnumerateBoundSourcesForAction(m_session, &getInfo, 0, &pathCount, nullptr));
//  std::vector<XrPath> paths(pathCount);
//  OXR(xrEnumerateBoundSourcesForAction(m_session, &getInfo, uint32_t(paths.size()), &pathCount, paths.data()));
//
//  std::string sourceName;
//  for (uint32_t i = 0; i < pathCount; ++i) {
//    constexpr XrInputSourceLocalizedNameFlags all = XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT |
//      XR_INPUT_SOURCE_LOCALIZED_NAME_INTERACTION_PROFILE_BIT |
//      XR_INPUT_SOURCE_LOCALIZED_NAME_COMPONENT_BIT;
//
//    XrInputSourceLocalizedNameGetInfo nameInfo = { XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO };
//    nameInfo.sourcePath = paths[i];
//    nameInfo.whichComponents = all;
//
//    uint32_t size = 0;
//    OXR(xrGetInputSourceLocalizedName(m_session, &nameInfo, 0, &size, nullptr));
//    if (size < 1) {
//      continue;
//    }
//    std::vector<char> grabSource(size);
//    OXR(xrGetInputSourceLocalizedName(m_session, &nameInfo, uint32_t(grabSource.size()), &size, grabSource.data()));
//    if (!sourceName.empty()) {
//      sourceName += " and ";
//    }
//    sourceName += "'";
//    sourceName += std::string(grabSource.data(), size - 1);
//    sourceName += "'";
//  }
//
//  //Log::Write(Log::Level::Info, Fmt("%s action is bound to %s", actionName.c_str(), ((!sourceName.empty()) ? sourceName.c_str() : "nothing")));
//}

static void PollEvents(bool* exitRenderLoop, bool* requestRestart)
{
  *exitRenderLoop = *requestRestart = false;

  // Process all pending messages.
  while (const XrEventDataBaseHeader* event = TryReadNextEvent()) {
    switch (event->type) {
    case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: {
      //const auto& instanceLossPending = *reinterpret_cast<const XrEventDataInstanceLossPending*>(event);
      //Log::Write(Log::Level::Warning, Fmt("XrEventDataInstanceLossPending by %lld", instanceLossPending.lossTime));
      *exitRenderLoop = true;
      *requestRestart = true;
      return;
    }
    case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
      auto sessionStateChangedEvent = *reinterpret_cast<const XrEventDataSessionStateChanged*>(event);
      HandleSessionStateChangedEvent(sessionStateChangedEvent, exitRenderLoop, requestRestart);
      break;
    }
    case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED:
      //LogActionSourceName(m_input.grabAction, "Grab");
      //LogActionSourceName(m_input.quitAction, "Quit");
      //LogActionSourceName(m_input.poseAction, "Pose");
      //LogActionSourceName(m_input.vibrateAction, "Vibrate");
      break;
    case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING:
    default:
      break;
    }
  }
}

void OpenXr::init2()
{
#ifdef SHR3D_OPENXR_PCVR
  if (!Global::xrInitialized)
    return;
#endif // SHR3D_OPENXR_PCVR

#ifdef SHR3D_OPENXR_PCVR
#ifdef SHR3D_WINDOW_SDL
  SDL_SysWMinfo wmInfo;
  SDL_VERSION(&wmInfo.version);
  SDL_GetWindowWMInfo(Global::window, &wmInfo);
  HDC hDC = (HDC)wmInfo.info.win.hdc;
#else
  HDC hDC = GetDC(Global::window);
#endif // SHR3D_WINDOW_SDL

  m_graphicsBinding.hDC = hDC;
  m_graphicsBinding.hGLRC = Global::glContext;
#endif // SHR3D_OPENXR_PCVR

  {
    GL(glGenFramebuffers(1, &m_swapchainFramebuffer));

    GL(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), nullptr)); // vertex coords
    GL(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void*)(3 * sizeof(GLfloat)))); // uv coords
    GL(glEnableVertexAttribArray(0));
    GL(glEnableVertexAttribArray(1));
  }

#ifdef SHR3D_OPENXR_PCVR
  //  XrGraphicsBindingOpenGLWin32KHR graphicsBinding = {XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR};
  //    graphicsBinding.hDC = app.egl.hDC;
  //    graphicsBinding.hGLRC = app.egl.hGLRC;
#else // SHR3D_OPENXR_PCVR
#if defined(PLATFORM_OPENXR_ANDROID)
  XrGraphicsBindingOpenGLESAndroidKHR graphicsBinding = { XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR };
  //  graphicsBinding.display = app.egl.Display;
  //  graphicsBinding.config = app.egl.Config;
  //  graphicsBinding.context = app.egl.Context;
#elif defined(XR_USE_GRAPHICS_API_OPENGL)

#endif
#endif // SHR3D_OPENXR_PCVR

  { // Creating Session
    XrSessionCreateInfo sessionCreateInfo{ XR_TYPE_SESSION_CREATE_INFO };
#ifdef SHR3D_OPENXR_PCVR
    sessionCreateInfo.next = reinterpret_cast<const XrBaseInStructure*>(&m_graphicsBinding);
#else // SHR3D_OPENXR_PCVR
    sessionCreateInfo.next = &graphicsBinding;
#endif // SHR3D_OPENXR_PCVR
    sessionCreateInfo.createFlags = 0;
    sessionCreateInfo.systemId = m_systemId;
    OXR(xrCreateSession(m_instance, &sessionCreateInfo, &m_session));
  }

  {
    uint32_t spaceCount;
    OXR(xrEnumerateReferenceSpaces(m_session, 0, &spaceCount, nullptr));
    std::vector<XrReferenceSpaceType> spaces(spaceCount);
    OXR(xrEnumerateReferenceSpaces(m_session, spaceCount, &spaceCount, spaces.data()));
  }

  { // init Actions
    // Create an action set.
    {
      XrActionSetCreateInfo actionSetInfo{ XR_TYPE_ACTION_SET_CREATE_INFO };
      strcpy(actionSetInfo.actionSetName, "gameplay");
      strcpy(actionSetInfo.localizedActionSetName, "Gameplay");
      actionSetInfo.priority = 0;
      OXR(xrCreateActionSet(m_instance, &actionSetInfo, &m_input.actionSet));
    }

    { // Get the XrPath for the left and right hands - we will use them as subaction paths.
      OXR(xrStringToPath(m_instance, "/user/hand/left", &m_input.handSubactionPath[Side::LEFT]));
      OXR(xrStringToPath(m_instance, "/user/hand/right", &m_input.handSubactionPath[Side::RIGHT]));
    }

    { // Create actions.
      // Create an input action for grabbing objects with the left and right hands.
      XrActionCreateInfo actionInfo{ XR_TYPE_ACTION_CREATE_INFO };
      actionInfo.actionType = XR_ACTION_TYPE_FLOAT_INPUT;
      strcpy(actionInfo.actionName, "grab_object");
      strcpy(actionInfo.localizedActionName, "Grab Object");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.grabAction));

      // Create output actions for vibrating the left and right controller.
      actionInfo.actionType = XR_ACTION_TYPE_VIBRATION_OUTPUT;
      strcpy(actionInfo.actionName, "vibrate_hand");
      strcpy(actionInfo.localizedActionName, "Vibrate Hand");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.vibrateAction));

      // Create input actions for quitting the session using the left and right controller.
      // Since it doesn't matter which hand did this, we do not specify subaction paths for it.
      // We will just suggest bindings for both hands, where possible.
      actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
      strcpy(actionInfo.actionName, "quit_session");
      strcpy(actionInfo.localizedActionName, "Quit Session");
      actionInfo.countSubactionPaths = 0;
      actionInfo.subactionPaths = nullptr;
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.quitAction));




      actionInfo.actionType = XR_ACTION_TYPE_POSE_INPUT;
      strcpy(actionInfo.actionName, "grip_pose");
      strcpy(actionInfo.localizedActionName, "Grip_pose");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.gripPoseAction));

      actionInfo.actionType = XR_ACTION_TYPE_POSE_INPUT;
      strcpy(actionInfo.actionName, "aim_pose");
      strcpy(actionInfo.localizedActionName, "Aim_pose");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.aimPoseAction));

      // Create output actions for vibrating the left and right controller.
      actionInfo.actionType = XR_ACTION_TYPE_VIBRATION_OUTPUT;
      strcpy(actionInfo.actionName, "haptic");
      strcpy(actionInfo.localizedActionName, "Haptic");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.hapticAction));

      actionInfo.actionType = XR_ACTION_TYPE_VECTOR2F_INPUT;
      strcpy(actionInfo.actionName, "thumbstick_value");
      strcpy(actionInfo.localizedActionName, "Thumbstick_value");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.thumbstickValueAction));

      actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
      strcpy(actionInfo.actionName, "thumbstick_click");
      strcpy(actionInfo.localizedActionName, "Thumbstick_click");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.thumbstickClickAction));

      actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
      strcpy(actionInfo.actionName, "thumbstick_touch");
      strcpy(actionInfo.localizedActionName, "Thumbstick_touch");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.thumbstickTouchAction));

      actionInfo.actionType = XR_ACTION_TYPE_FLOAT_INPUT;
      strcpy(actionInfo.actionName, "trigger_value");
      strcpy(actionInfo.localizedActionName, "Trigger_value");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.triggerValueAction));

      //actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT; // (does not exist outside VDXR Runtime)
      //strcpy(actionInfo.actionName, "trigger_click");
      //strcpy(actionInfo.localizedActionName, "Trigger_click");
      //actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      //actionInfo.subactionPaths = m_input.handSubactionPath.data();
      //OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.triggerClickAction));

      actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
      strcpy(actionInfo.actionName, "trigger_touch");
      strcpy(actionInfo.localizedActionName, "Trigger_touch");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.triggerTouchAction));

      actionInfo.actionType = XR_ACTION_TYPE_FLOAT_INPUT;
      strcpy(actionInfo.actionName, "squeeze_value");
      strcpy(actionInfo.localizedActionName, "Squeeze_value");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.squeezeValueAction));

      //actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT; // (does not exist outside VDXR Runtime)
      //strcpy(actionInfo.actionName, "squeeze_click");
      //strcpy(actionInfo.localizedActionName, "Squeeze_click");
      //actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      //actionInfo.subactionPaths = m_input.handSubactionPath.data();
      //OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.squeezeClickAction));

      actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
      strcpy(actionInfo.actionName, "akey");
      strcpy(actionInfo.localizedActionName, "Akey");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.AAction));

      actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
      strcpy(actionInfo.actionName, "bkey");
      strcpy(actionInfo.localizedActionName, "Bkey");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.BAction));

      actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
      strcpy(actionInfo.actionName, "xkey");
      strcpy(actionInfo.localizedActionName, "Xkey");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.XAction));

      actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
      strcpy(actionInfo.actionName, "ykey");
      strcpy(actionInfo.localizedActionName, "Ykey");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.YAction));

      actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
      strcpy(actionInfo.actionName, "akey_touch");
      strcpy(actionInfo.localizedActionName, "Akey_touch");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.ATouchAction));

      actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
      strcpy(actionInfo.actionName, "bkey_touch");
      strcpy(actionInfo.localizedActionName, "Bkey_touch");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.BTouchAction));

      actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
      strcpy(actionInfo.actionName, "xkey_touch");
      strcpy(actionInfo.localizedActionName, "Xkey_touch");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.XTouchAction));

      actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
      strcpy(actionInfo.actionName, "ykey_touch");
      strcpy(actionInfo.localizedActionName, "Ykey_touch");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.YTouchAction));

      actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
      strcpy(actionInfo.actionName, "menukey");
      strcpy(actionInfo.localizedActionName, "Menukey");
      actionInfo.countSubactionPaths = uint32_t(m_input.handSubactionPath.size());
      actionInfo.subactionPaths = m_input.handSubactionPath.data();
      OXR(xrCreateAction(m_input.actionSet, &actionInfo, &m_input.menuAction));
    }

    std::array<XrPath, Side::COUNT> selectPath;
    std::array<XrPath, Side::COUNT> squeezeForcePath;
    std::array<XrPath, Side::COUNT> squeezeClickPath;
    std::array<XrPath, Side::COUNT> hapticPath;
    std::array<XrPath, Side::COUNT> triggerValuePath;

    std::array<XrPath, Side::COUNT> gripPosePath;
    std::array<XrPath, Side::COUNT> aimPosePath;
    std::array<XrPath, Side::COUNT> thumbstickValuePath;
    std::array<XrPath, Side::COUNT> thumbstickClickPath;
    std::array<XrPath, Side::COUNT> thumbstickTouchPath;
    std::array<XrPath, Side::COUNT> squeezeValuePath;
    std::array<XrPath, Side::COUNT> triggerClickPath;
    std::array<XrPath, Side::COUNT> triggerTouchPath;
    std::array<XrPath, Side::COUNT> AClickPath;
    std::array<XrPath, Side::COUNT> BClickPath;
    std::array<XrPath, Side::COUNT> XClickPath;
    std::array<XrPath, Side::COUNT> YClickPath;
    std::array<XrPath, Side::COUNT> ATouchPath;
    std::array<XrPath, Side::COUNT> BTouchPath;
    std::array<XrPath, Side::COUNT> XTouchPath;
    std::array<XrPath, Side::COUNT> YTouchPath;
    std::array<XrPath, Side::COUNT> menuClickPath;

    OXR(xrStringToPath(m_instance, "/user/hand/left/input/select/click", &selectPath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/select/click", &selectPath[Side::RIGHT]));
    OXR(xrStringToPath(m_instance, "/user/hand/left/input/squeeze/force", &squeezeForcePath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/squeeze/force", &squeezeForcePath[Side::RIGHT]));


    OXR(xrStringToPath(m_instance, "/user/hand/left/input/grip/pose", &gripPosePath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/grip/pose", &gripPosePath[Side::RIGHT]));
    OXR(xrStringToPath(m_instance, "/user/hand/left/input/aim/pose", &aimPosePath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/aim/pose", &aimPosePath[Side::RIGHT]));
    OXR(xrStringToPath(m_instance, "/user/hand/left/output/haptic", &hapticPath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/output/haptic", &hapticPath[Side::RIGHT]));

    OXR(xrStringToPath(m_instance, "/user/hand/left/input/thumbstick", &thumbstickValuePath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/thumbstick", &thumbstickValuePath[Side::RIGHT]));
    OXR(xrStringToPath(m_instance, "/user/hand/left/input/thumbstick/click", &thumbstickClickPath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/thumbstick/click", &thumbstickClickPath[Side::RIGHT]));
    OXR(xrStringToPath(m_instance, "/user/hand/left/input/thumbstick/touch", &thumbstickTouchPath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/thumbstick/touch", &thumbstickTouchPath[Side::RIGHT]));

    OXR(xrStringToPath(m_instance, "/user/hand/left/input/trigger/value", &triggerValuePath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/trigger/value", &triggerValuePath[Side::RIGHT]));
    OXR(xrStringToPath(m_instance, "/user/hand/left/input/trigger/click", &triggerClickPath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/trigger/click", &triggerClickPath[Side::RIGHT]));
    OXR(xrStringToPath(m_instance, "/user/hand/left/input/trigger/touch", &triggerTouchPath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/trigger/touch", &triggerTouchPath[Side::RIGHT]));

    OXR(xrStringToPath(m_instance, "/user/hand/left/input/squeeze/value", &squeezeValuePath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/squeeze/value", &squeezeValuePath[Side::RIGHT]));
    OXR(xrStringToPath(m_instance, "/user/hand/left/input/squeeze/click", &squeezeClickPath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/squeeze/click", &squeezeClickPath[Side::RIGHT]));

    OXR(xrStringToPath(m_instance, "/user/hand/right/input/a/click", &AClickPath[Side::RIGHT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/b/click", &BClickPath[Side::RIGHT]));
    OXR(xrStringToPath(m_instance, "/user/hand/left/input/x/click", &XClickPath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/left/input/y/click", &YClickPath[Side::LEFT]));

    OXR(xrStringToPath(m_instance, "/user/hand/right/input/a/touch", &ATouchPath[Side::RIGHT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/b/touch", &BTouchPath[Side::RIGHT]));
    OXR(xrStringToPath(m_instance, "/user/hand/left/input/x/touch", &XTouchPath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/left/input/y/touch", &YTouchPath[Side::LEFT]));

    OXR(xrStringToPath(m_instance, "/user/hand/left/input/menu/click", &menuClickPath[Side::LEFT]));
    OXR(xrStringToPath(m_instance, "/user/hand/right/input/menu/click", &menuClickPath[Side::RIGHT]));

    { // Suggest bindings for KHR Simple.
      XrPath khrSimpleInteractionProfilePath;
      OXR(xrStringToPath(m_instance, "/interaction_profiles/khr/simple_controller", &khrSimpleInteractionProfilePath));
      std::vector<XrActionSuggestedBinding> bindings{ {// Fall back to a click input for the grab action.
                                                      {m_input.grabAction, selectPath[Side::LEFT]},
                                                      {m_input.grabAction, selectPath[Side::RIGHT]},
                                                      {m_input.gripPoseAction, gripPosePath[Side::LEFT]},
                                                      {m_input.gripPoseAction, gripPosePath[Side::RIGHT]},
                                                      {m_input.quitAction, menuClickPath[Side::LEFT]},
                                                      {m_input.quitAction, menuClickPath[Side::RIGHT]},
                                                      {m_input.vibrateAction, hapticPath[Side::LEFT]},
                                                      {m_input.vibrateAction, hapticPath[Side::RIGHT]},

                                                      //{m_input.AAction, AClickPath[Side::RIGHT]},
                                                      //{m_input.BAction, BClickPath[Side::RIGHT]},
                                                      //{m_input.XAction, XClickPath[Side::LEFT]},
                                                      //{m_input.YAction, YClickPath[Side::LEFT]},

} };
      XrInteractionProfileSuggestedBinding suggestedBindings{ XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING };
      suggestedBindings.interactionProfile = khrSimpleInteractionProfilePath;
      suggestedBindings.suggestedBindings = bindings.data();
      suggestedBindings.countSuggestedBindings = (uint32_t)bindings.size();
      OXR(xrSuggestInteractionProfileBindings(m_instance, &suggestedBindings));
    }

    { // Suggest bindings for the Oculus Touch.
      XrPath oculusTouchInteractionProfilePath;
      OXR(xrStringToPath(m_instance, "/interaction_profiles/oculus/touch_controller", &oculusTouchInteractionProfilePath));
      std::vector<XrActionSuggestedBinding> bindings{ {{m_input.gripPoseAction, gripPosePath[Side::LEFT]},
                                                      {m_input.gripPoseAction, gripPosePath[Side::RIGHT]},
                                                      {m_input.aimPoseAction, aimPosePath[Side::LEFT]},
                                                      {m_input.aimPoseAction, aimPosePath[Side::RIGHT]},
                                                      {m_input.hapticAction, hapticPath[Side::LEFT]},
                                                      {m_input.hapticAction, hapticPath[Side::RIGHT]},

                                                      {m_input.thumbstickValueAction, thumbstickValuePath[Side::LEFT]},
                                                      {m_input.thumbstickValueAction, thumbstickValuePath[Side::RIGHT]},
                                                      {m_input.thumbstickClickAction, thumbstickClickPath[Side::LEFT]},
                                                      {m_input.thumbstickClickAction, thumbstickClickPath[Side::RIGHT]},
                                                      {m_input.thumbstickTouchAction, thumbstickTouchPath[Side::LEFT]},
                                                      {m_input.thumbstickTouchAction, thumbstickTouchPath[Side::RIGHT]},

                                                      {m_input.triggerValueAction, triggerValuePath[Side::LEFT]},
                                                      {m_input.triggerValueAction, triggerValuePath[Side::RIGHT]},
                                                      //{m_input.triggerClickAction, triggerClickPath[Side::LEFT]}, // (does not exist outside VDXR Runtime)
                                                      //{m_input.triggerClickAction, triggerClickPath[Side::RIGHT]}, // (does not exist outside VDXR Runtime)
                                                      {m_input.triggerTouchAction, triggerTouchPath[Side::LEFT]},
                                                      {m_input.triggerTouchAction, triggerTouchPath[Side::RIGHT]},

                                                      //{m_input.squeezeClickAction, squeezeClickPath[Side::LEFT]}, // (does not exist outside VDXR Runtime)
                                                      //{m_input.squeezeClickAction, squeezeClickPath[Side::RIGHT]}, // (does not exist outside VDXR Runtime)
                                                      {m_input.squeezeValueAction, squeezeValuePath[Side::LEFT]},
                                                      {m_input.squeezeValueAction, squeezeValuePath[Side::RIGHT]},

                                                      {m_input.AAction, AClickPath[Side::RIGHT]},
                                                      {m_input.BAction, BClickPath[Side::RIGHT]},
                                                      {m_input.XAction, XClickPath[Side::LEFT]},
                                                      {m_input.YAction, YClickPath[Side::LEFT]},

                                                      {m_input.ATouchAction, ATouchPath[Side::RIGHT]},
                                                      {m_input.BTouchAction, BTouchPath[Side::RIGHT]},
                                                      {m_input.XTouchAction, XTouchPath[Side::LEFT]},
                                                      {m_input.YTouchAction, YTouchPath[Side::LEFT]},

                                                      {m_input.menuAction, menuClickPath[Side::LEFT]}} };
      XrInteractionProfileSuggestedBinding suggestedBindings{ XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING };
      suggestedBindings.interactionProfile = oculusTouchInteractionProfilePath;
      suggestedBindings.suggestedBindings = bindings.data();
      suggestedBindings.countSuggestedBindings = (uint32_t)bindings.size();
      OXR(xrSuggestInteractionProfileBindings(m_instance, &suggestedBindings));
    }

    //{ // Suggest bindings for the PICO Controller.
    //  //see https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_BD_controller_interaction
    //  const char* interactionProfilePath = "/interaction_profiles/bytedance/pico4_controller";
    //  XrPath picoMixedRealityInteractionProfilePath;
    //  OXR(xrStringToPath(m_instance, interactionProfilePath, &picoMixedRealityInteractionProfilePath));
    //  std::vector<XrActionSuggestedBinding> bindings{ {{m_input.gripPoseAction, gripPosePath[Side::LEFT]},
    //                                                  {m_input.gripPoseAction, gripPosePath[Side::RIGHT]},
    //                                                  {m_input.aimPoseAction, aimPosePath[Side::LEFT]},
    //                                                  {m_input.aimPoseAction, aimPosePath[Side::RIGHT]},
    //                                                  {m_input.hapticAction, hapticPath[Side::LEFT]},
    //                                                  {m_input.hapticAction, hapticPath[Side::RIGHT]},

    //                                                  {m_input.thumbstickValueAction, thumbstickValuePath[Side::LEFT]},
    //                                                  {m_input.thumbstickValueAction, thumbstickValuePath[Side::RIGHT]},
    //                                                  {m_input.thumbstickClickAction, thumbstickClickPath[Side::LEFT]},
    //                                                  {m_input.thumbstickClickAction, thumbstickClickPath[Side::RIGHT]},
    //                                                  {m_input.thumbstickTouchAction, thumbstickTouchPath[Side::LEFT]},
    //                                                  {m_input.thumbstickTouchAction, thumbstickTouchPath[Side::RIGHT]},

    //                                                  {m_input.triggerValueAction, triggerValuePath[Side::LEFT]},
    //                                                  {m_input.triggerValueAction, triggerValuePath[Side::RIGHT]},
    //                                                  {m_input.triggerClickAction, triggerClickPath[Side::LEFT]},
    //                                                  {m_input.triggerClickAction, triggerClickPath[Side::RIGHT]},
    //                                                  {m_input.triggerTouchAction, triggerTouchPath[Side::LEFT]},
    //                                                  {m_input.triggerTouchAction, triggerTouchPath[Side::RIGHT]},

    //                                                  {m_input.squeezeClickAction, squeezeClickPath[Side::LEFT]},
    //                                                  {m_input.squeezeClickAction, squeezeClickPath[Side::RIGHT]},
    //                                                  {m_input.squeezeValueAction, squeezeValuePath[Side::LEFT]},
    //                                                  {m_input.squeezeValueAction, squeezeValuePath[Side::RIGHT]},

    //                                                  {m_input.AAction, AClickPath[Side::RIGHT]},
    //                                                  {m_input.BAction, BClickPath[Side::RIGHT]},
    //                                                  {m_input.XAction, XClickPath[Side::LEFT]},
    //                                                  {m_input.YAction, YClickPath[Side::LEFT]},

    //                                                  {m_input.ATouchAction, ATouchPath[Side::RIGHT]},
    //                                                  {m_input.BTouchAction, BTouchPath[Side::RIGHT]},
    //                                                  {m_input.XTouchAction, XTouchPath[Side::LEFT]},
    //                                                  {m_input.YTouchAction, YTouchPath[Side::LEFT]},

    //                                                  {m_input.menuAction, menuClickPath[Side::LEFT]}} };

    //  XrInteractionProfileSuggestedBinding suggestedBindings{ XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING };
    //  suggestedBindings.interactionProfile = picoMixedRealityInteractionProfilePath;
    //  suggestedBindings.suggestedBindings = bindings.data();
    //  suggestedBindings.countSuggestedBindings = (uint32_t)bindings.size();
    //  OXR(xrSuggestInteractionProfileBindings(m_instance, &suggestedBindings));
    //}

    {
      XrActionSpaceCreateInfo actionSpaceInfo{ XR_TYPE_ACTION_SPACE_CREATE_INFO };
      actionSpaceInfo.action = m_input.gripPoseAction;
      actionSpaceInfo.poseInActionSpace.orientation.w = 1.0f;
      actionSpaceInfo.subactionPath = m_input.handSubactionPath[Side::LEFT];
      OXR(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_input.handSpace[Side::LEFT]));
      actionSpaceInfo.subactionPath = m_input.handSubactionPath[Side::RIGHT];
      OXR(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_input.handSpace[Side::RIGHT]));
    }

    {
      XrActionSpaceCreateInfo actionSpaceInfo{ XR_TYPE_ACTION_SPACE_CREATE_INFO };
      actionSpaceInfo.action = m_input.aimPoseAction;
      actionSpaceInfo.poseInActionSpace.orientation.w = 1.0f;
      actionSpaceInfo.subactionPath = m_input.handSubactionPath[Side::LEFT];
      OXR(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_input.aimSpace[Side::LEFT]));
      actionSpaceInfo.subactionPath = m_input.handSubactionPath[Side::RIGHT];
      OXR(xrCreateActionSpace(m_session, &actionSpaceInfo, &m_input.aimSpace[Side::RIGHT]));
    }

    {
      XrSessionActionSetsAttachInfo attachInfo{ XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO };
      attachInfo.countActionSets = 1;
      attachInfo.actionSets = &m_input.actionSet;
      OXR(xrAttachSessionActionSets(m_session, &attachInfo));
    }
  }

  {
    XrReferenceSpaceCreateInfo referenceSpaceCreateInfo{ XR_TYPE_REFERENCE_SPACE_CREATE_INFO };
    referenceSpaceCreateInfo.poseInReferenceSpace.orientation.w = 1.0f;
    referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
    OXR(xrCreateReferenceSpace(m_session, &referenceSpaceCreateInfo, &m_appSpace));
  }
  {
    XrReferenceSpaceCreateInfo referenceSpaceCreateInfo{ XR_TYPE_REFERENCE_SPACE_CREATE_INFO };
    referenceSpaceCreateInfo.poseInReferenceSpace.orientation.w = 1.0f;
    referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
    OXR(xrCreateReferenceSpace(m_session, &referenceSpaceCreateInfo, &m_stageSpace));
  }

  CreateSwapchains();
}

void OpenXr::tick()
{
#ifdef SHR3D_OPENXR_PCVR
  ASSERT(Settings::xrEnabled);
  if (!Global::xrInitialized)
    return;
#else // SHR3D_OPENXR_PCVR
#endif // SHR3D_OPENXR_PCVR

  bool exitRenderLoop = false;
  bool requestRestart = false;
  PollEvents(&exitRenderLoop, &requestRestart);



  // Sync actions
  const XrActiveActionSet activeActionSet{ m_input.actionSet, XR_NULL_PATH };
  XrActionsSyncInfo syncInfo{ XR_TYPE_ACTIONS_SYNC_INFO };
  syncInfo.countActiveActionSets = 1;
  syncInfo.activeActionSets = &activeActionSet;
  OXR(xrSyncActions(m_session, &syncInfo));

  // Get pose and grab action state and start haptic vibrate when hand is 90% squeezed.
  //for (auto hand : { Side::LEFT, Side::RIGHT }) {
  //  XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
  //  getInfo.action = m_input.grabAction;
  //  getInfo.subactionPath = m_input.handSubactionPath[hand];

  //  XrActionStateFloat grabValue{ XR_TYPE_ACTION_STATE_FLOAT };
  //  OXR(xrGetActionStateFloat(m_session, &getInfo, &grabValue));
  //  if (grabValue.isActive == XR_TRUE) {
  //    // Scale the rendered hand by 1.0f (open) to 0.5f (fully squeezed).
  //    if (grabValue.currentState > 0.9f) {
  //      XrHapticVibration vibration{ XR_TYPE_HAPTIC_VIBRATION };
  //      vibration.amplitude = 0.5;
  //      vibration.duration = XR_MIN_HAPTIC_DURATION;
  //      vibration.frequency = XR_FREQUENCY_UNSPECIFIED;

  //      XrHapticActionInfo hapticActionInfo{ XR_TYPE_HAPTIC_ACTION_INFO };
  //      hapticActionInfo.action = m_input.vibrateAction;
  //      hapticActionInfo.subactionPath = m_input.handSubactionPath[hand];
  //      OXR(xrApplyHapticFeedback(m_session, &hapticActionInfo, (XrHapticBaseHeader*)&vibration));
  //    }
  //  }

  //  getInfo.action = m_input.poseAction;
  //  XrActionStatePose poseState{ XR_TYPE_ACTION_STATE_POSE };
  //  OXR(xrGetActionStatePose(m_session, &getInfo, &poseState));
  ////  poseState.isActive;
  //}

  //// There were no subaction paths specified for the quit action, because we don't care which hand did it.
  //XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO, nullptr, m_input.quitAction, XR_NULL_PATH };
  //XrActionStateBoolean quitValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
  //OXR(xrGetActionStateBoolean(m_session, &getInfo, &quitValue));
  //if ((quitValue.isActive == XR_TRUE) && (quitValue.changedSinceLastSync == XR_TRUE) && (quitValue.currentState == XR_TRUE)) {
  //  OXR(xrRequestExitSession(m_session));
  //}


  static float joystick_x[Side::COUNT] = { 0 };
  static float joystick_y[Side::COUNT] = { 0 };
  static float trigger[Side::COUNT] = { 0 };
  static float squeeze[Side::COUNT] = { 0 };

  // Get pose and grab action state and start haptic vibrate when hand is 90% squeezed.
  for (u8 i = 0; i < 2; ++i)
  {
    XrControllerEvent& applicationEvent = Global::inputXRControllerEvent[i];
    applicationEvent.controllerEventBit = ControllerEventBit::none;

    XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
    getInfo.subactionPath = m_input.handSubactionPath[i];

    //menu click
    getInfo.action = m_input.menuAction;
    XrActionStateBoolean homeValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(m_session, &getInfo, &homeValue));
    if (homeValue.isActive == XR_TRUE)
    {
      if (homeValue.changedSinceLastSync == XR_TRUE)
      {
        applicationEvent.controllerEventBit |= ControllerEventBit::click_menu;
        if (homeValue.currentState == XR_TRUE)
        {
          applicationEvent.click_menu = true;
        }
        else
        {
          applicationEvent.click_menu = false;
        }
      }
    }

    //thumbstick value x/y
    getInfo.action = m_input.thumbstickValueAction;
    XrActionStateVector2f thumbstickValue{ XR_TYPE_ACTION_STATE_VECTOR2F };
    OXR(xrGetActionStateVector2f(m_session, &getInfo, &thumbstickValue));
    if (thumbstickValue.isActive == XR_TRUE)
    {
      if (thumbstickValue.currentState.x == 0.0f && thumbstickValue.currentState.y == 0.0f && joystick_x[i] == 0.0f && joystick_y[i] == 0.0f)
      {
      }
      else
      {
        applicationEvent.controllerEventBit |= ControllerEventBit::value_thumbstick;
        applicationEvent.thumbstick_x = thumbstickValue.currentState.x;
        applicationEvent.thumbstick_y = thumbstickValue.currentState.y;
      }
      joystick_x[i] = thumbstickValue.currentState.x;
      joystick_y[i] = thumbstickValue.currentState.y;
    }
    // thumbstick click
    getInfo.action = m_input.thumbstickClickAction;
    XrActionStateBoolean thumbstickClick{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(m_session, &getInfo, &thumbstickClick));
    if ((thumbstickClick.isActive == XR_TRUE) && (thumbstickClick.changedSinceLastSync == XR_TRUE))
    {
      applicationEvent.controllerEventBit |= ControllerEventBit::click_thumbstick;
      if (thumbstickClick.currentState == XR_TRUE)
      {
        applicationEvent.click_thumbstick = true;
      }
      else
      {
        applicationEvent.click_thumbstick = false;
      }
    }

    // thumbstick touch
    getInfo.action = m_input.thumbstickTouchAction;
    XrActionStateBoolean thumbstickTouch{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(m_session, &getInfo, &thumbstickTouch));
    if (thumbstickTouch.isActive == XR_TRUE)
    {
      if (thumbstickTouch.changedSinceLastSync == XR_TRUE)
      {
        applicationEvent.controllerEventBit |= ControllerEventBit::touch_thumbstick;
        if (thumbstickTouch.currentState == XR_TRUE)
        {
          applicationEvent.touch_thumbstick = true;
        }
        else
        {
          applicationEvent.touch_thumbstick = false;
        }
      }
    }

    //trigger value
    getInfo.action = m_input.triggerValueAction;
    XrActionStateFloat triggerValue{ XR_TYPE_ACTION_STATE_FLOAT };
    OXR(xrGetActionStateFloat(m_session, &getInfo, &triggerValue));
    if (triggerValue.isActive == XR_TRUE)
    {
      if (triggerValue.currentState == 0.0f && trigger[i] == 0.0f)
      {
      }
      else
      {
        applicationEvent.controllerEventBit |= ControllerEventBit::value_trigger;
        applicationEvent.trigger = triggerValue.currentState;

        if (!applicationEvent.click_trigger && triggerValue.currentState != 0.0f
          || applicationEvent.click_trigger && triggerValue.currentState == 0.0f)
          applicationEvent.controllerEventBit |= ControllerEventBit::click_trigger;
        applicationEvent.click_trigger = triggerValue.currentState != 0.0f;
      }
      trigger[i] = triggerValue.currentState;
    }

    // trigger click (does not exist outside VDXR Runtime)
    //getInfo.action = m_input.triggerClickAction;
    //XrActionStateBoolean TriggerClick{ XR_TYPE_ACTION_STATE_BOOLEAN };
    //OXR(xrGetActionStateBoolean(m_session, &getInfo, &TriggerClick));
    //if (TriggerClick.isActive == XR_TRUE) {
    //  if (TriggerClick.changedSinceLastSync == XR_TRUE) {
    //    applicationEvent.controllerEventBit |= ControllerEventBit::click_trigger;
    //    if (TriggerClick.currentState == XR_TRUE) {
    //      applicationEvent.click_trigger = true;
    //    }
    //    else {
    //      applicationEvent.click_trigger = false;
    //    }
    //  }
    //}

    // trigger touch
    getInfo.action = m_input.triggerTouchAction;
    XrActionStateBoolean TriggerTouch{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(m_session, &getInfo, &TriggerTouch));
    if (TriggerTouch.isActive == XR_TRUE)
    {
      if (TriggerTouch.changedSinceLastSync == XR_TRUE)
      {
        applicationEvent.controllerEventBit |= ControllerEventBit::touch_trigger;
        if (TriggerTouch.currentState == XR_TRUE)
        {
          applicationEvent.touch_trigger = true;
        }
        else
        {
          applicationEvent.touch_trigger = false;
        }
      }
    }

    // squeeze value
    getInfo.action = m_input.squeezeValueAction;
    XrActionStateFloat squeezeValue{ XR_TYPE_ACTION_STATE_FLOAT };
    OXR(xrGetActionStateFloat(m_session, &getInfo, &squeezeValue));
    if (squeezeValue.isActive == XR_TRUE)
    {
      if (squeezeValue.currentState == 0.0f && squeeze[i] == 0.0f)
      {
      }
      else
      {
        applicationEvent.controllerEventBit |= ControllerEventBit::value_squeeze;
        applicationEvent.squeeze = squeezeValue.currentState;

        if (!applicationEvent.click_squeeze && squeezeValue.currentState != 0.0f
          || applicationEvent.click_squeeze && squeezeValue.currentState == 0.0f)
          applicationEvent.controllerEventBit |= ControllerEventBit::click_squeeze;
        applicationEvent.click_squeeze = squeezeValue.currentState != 0.0f;
      }
      squeeze[i] = squeezeValue.currentState;
    }

    // squeeze click (does not exist outside VDXR Runtime)
    //getInfo.action = m_input.squeezeClickAction;
    //XrActionStateBoolean squeezeClick{ XR_TYPE_ACTION_STATE_BOOLEAN };
    //OXR(xrGetActionStateBoolean(m_session, &getInfo, &squeezeClick));
    //if ((squeezeClick.isActive == XR_TRUE) && (squeezeClick.changedSinceLastSync == XR_TRUE))
    //{
    //  applicationEvent.controllerEventBit |= ControllerEventBit::click_squeeze;
    //  if (squeezeClick.currentState == XR_TRUE)
    //  {
    //    applicationEvent.click_squeeze = true;
    //  }
    //  else
    //  {
    //    applicationEvent.click_squeeze = false;
    //  }
    //}

    // A button
    getInfo.action = m_input.AAction;
    XrActionStateBoolean AValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(m_session, &getInfo, &AValue));
    if ((AValue.isActive == XR_TRUE) && (AValue.changedSinceLastSync == XR_TRUE))
    {
      applicationEvent.controllerEventBit |= ControllerEventBit::click_a;
      if (AValue.currentState == XR_TRUE)
      {
        applicationEvent.click_a = true;
      }
      else
      {
        applicationEvent.click_a = false;
      }
    }

    // B button
    getInfo.action = m_input.BAction;
    XrActionStateBoolean BValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(m_session, &getInfo, &BValue));
    if ((BValue.isActive == XR_TRUE) && (BValue.changedSinceLastSync == XR_TRUE))
    {
      applicationEvent.controllerEventBit |= ControllerEventBit::click_b;
      if (BValue.currentState == XR_TRUE)
      {
        applicationEvent.click_b = true;
      }
      else {
        applicationEvent.click_b = false;
      }
    }

    // X button
    getInfo.action = m_input.XAction;
    XrActionStateBoolean XValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(m_session, &getInfo, &XValue));
    if ((XValue.isActive == XR_TRUE) && (XValue.changedSinceLastSync == XR_TRUE))
    {
      applicationEvent.controllerEventBit |= ControllerEventBit::click_x;
      if (XValue.currentState == XR_TRUE)
      {
        applicationEvent.click_x = true;
      }
      else
      {
        applicationEvent.click_x = false;
      }
    }

    // Y button
    getInfo.action = m_input.YAction;
    XrActionStateBoolean YValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(m_session, &getInfo, &YValue));
    if ((YValue.isActive == XR_TRUE) && (YValue.changedSinceLastSync == XR_TRUE))
    {
      applicationEvent.controllerEventBit |= ControllerEventBit::click_y;
      if (YValue.currentState == XR_TRUE)
      {
        applicationEvent.click_y = true;
      }
      else
      {
        applicationEvent.click_y = false;
      }
    }

    // A button touch
    getInfo.action = m_input.ATouchAction;
    XrActionStateBoolean ATouchValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(m_session, &getInfo, &ATouchValue));
    if ((ATouchValue.isActive == XR_TRUE) && (ATouchValue.changedSinceLastSync == XR_TRUE))
    {
      applicationEvent.controllerEventBit |= ControllerEventBit::touch_a;
      if (ATouchValue.currentState == XR_TRUE)
      {
        applicationEvent.touch_a = true;
      }
      else
      {
        applicationEvent.touch_a = false;
      }
    }

    // B button touch
    getInfo.action = m_input.BTouchAction;
    XrActionStateBoolean BTouchValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(m_session, &getInfo, &BTouchValue));
    if ((BTouchValue.isActive == XR_TRUE) && (BTouchValue.changedSinceLastSync == XR_TRUE)) {
      applicationEvent.controllerEventBit |= ControllerEventBit::touch_b;
      if (BTouchValue.currentState == XR_TRUE)
      {
        applicationEvent.touch_b = true;
      }
      else
      {
        applicationEvent.touch_b = false;
      }
    }

    // X button touch
    getInfo.action = m_input.XTouchAction;
    XrActionStateBoolean XTouchValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(m_session, &getInfo, &XTouchValue));
    if ((XTouchValue.isActive == XR_TRUE) && (XTouchValue.changedSinceLastSync == XR_TRUE)) {
      applicationEvent.controllerEventBit |= ControllerEventBit::touch_x;
      if (XTouchValue.currentState == XR_TRUE)
      {
        applicationEvent.touch_x = true;
      }
      else
      {
        applicationEvent.touch_x = false;
      }
    }

    // Y button touch
    getInfo.action = m_input.YTouchAction;
    XrActionStateBoolean YTouchValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
    OXR(xrGetActionStateBoolean(m_session, &getInfo, &YTouchValue));
    if ((YTouchValue.isActive == XR_TRUE) && (YTouchValue.changedSinceLastSync == XR_TRUE)) {
      applicationEvent.controllerEventBit |= ControllerEventBit::touch_y;
      if (YTouchValue.currentState == XR_TRUE)
      {
        applicationEvent.touch_y = true;
      }
      else
      {
        applicationEvent.touch_y = false;
      }
    }
  }

  //if (m_extentions.isSupportEyeTracking && m_extentions.activeEyeTracking) {
  //  XrActionStatePose actionStatePose{ XR_TYPE_ACTION_STATE_POSE };
  //  XrActionStateGetInfo getActionStateInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
  //  getActionStateInfo.action = m_input.gazeAction;
  //  OXR(xrGetActionStatePose(m_session, &getActionStateInfo, &actionStatePose));
  //  m_input.gazeActive = actionStatePose.isActive;
  //}

  {
    if (Global::inputXRControllerEvent[0].click_trigger)
      Global::inputXRActiveController = XrActiveController::left;
    else if (Global::inputXRControllerEvent[1].click_trigger)
      Global::inputXRActiveController = XrActiveController::right;
    Xr::handleXrControllerInput();
  }

#ifdef SHR3D_OPENXR_PCVR
  if (!Global::inputHideMenu.toggled && Global::inputHideMenu.heldDownLastFrame)
  {
    Global::inputCursorPosXrX = Global::xrResolutionWidth / 2;
    Global::inputCursorPosXrY = Global::xrResolutionHeight / 2;
  }
#endif // SHR3D_OPENXR_PCVR
}

static bool RenderLayer(XrTime predictedDisplayTime, std::vector<XrCompositionLayerProjectionView>& projectionLayerViews, XrCompositionLayerProjection& layer)
{
  XrResult res;

  XrViewState viewState{ XR_TYPE_VIEW_STATE };
  uint32_t viewCapacityInput = (uint32_t)m_views.size();
  uint32_t viewCountOutput;

  XrViewLocateInfo viewLocateInfo{ XR_TYPE_VIEW_LOCATE_INFO };
  viewLocateInfo.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
  viewLocateInfo.displayTime = predictedDisplayTime;
  viewLocateInfo.space = m_appSpace;

  res = xrLocateViews(m_session, &viewLocateInfo, &viewState, viewCapacityInput, &viewCountOutput, m_views.data());
  CHECK_XRRESULT(res, "xrLocateViews");
  if ((viewState.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) == 0 ||
    (viewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) == 0) {
    return false;  // There is no valid tracking poses for the views.
  }

  CHECK(viewCountOutput == viewCapacityInput);
  CHECK(viewCountOutput == m_configViews.size());
  CHECK(viewCountOutput == m_swapchains.size());

  projectionLayerViews.resize(viewCountOutput);

  XrActionStateGetInfo actionStateGetInfo{ XR_TYPE_ACTION_STATE_GET_INFO };

  actionStateGetInfo.action = m_input.gripPoseAction;

  for (u8 i = 0; i < 2; ++i)
  {
    actionStateGetInfo.subactionPath = m_input.handSubactionPath[i];
    res = xrGetActionStatePose(m_session, &actionStateGetInfo, &Global::mControllerPoseState[i]);
    CHECK_XRRESULT(res, "xrGetActionStatePose");

    XrSpaceLocation spaceLocation{ XR_TYPE_SPACE_LOCATION };
    res = xrLocateSpace(m_input.handSpace[i], m_appSpace, predictedDisplayTime, &spaceLocation);
    CHECK_XRRESULT(res, "xrLocateSpace");
    if (XR_UNQUALIFIED_SUCCESS(res) &&
      (spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0 &&
      (spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0)
    {
      Global::mControllerModel[i] = Xr::mat4_CreateTranslationRotation(spaceLocation.pose.position, spaceLocation.pose.orientation);
      //mControllerPose[i] = spaceLocation.pose;
    }
    else
    {
      Global::mControllerPoseState[i].isActive = false;
    }
  }

  for (u8 i = 0; i < 2; ++i)
  {
    XrSpaceLocation spaceLocation{ XR_TYPE_SPACE_LOCATION };
    res = xrLocateSpace(m_input.aimSpace[i], m_appSpace, predictedDisplayTime, &spaceLocation);
    CHECK_XRRESULT(res, "xrLocateSpace");
    if (XR_UNQUALIFIED_SUCCESS(res)) {
      if ((spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0 &&
        (spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0) {
        Global::mAimModel[i] = Xr::mat4_CreateTranslationRotation(spaceLocation.pose.position, spaceLocation.pose.orientation);
        Global::mAimPose[i] = spaceLocation.pose;
      }
    }
  }

  //stage
  {
    XrSpaceLocation spaceLocation{ XR_TYPE_SPACE_LOCATION };
    res = xrLocateSpace(m_stageSpace, m_appSpace, predictedDisplayTime, &spaceLocation);
    CHECK_XRRESULT(res, "xrLocateSpace");
    if (XR_UNQUALIFIED_SUCCESS(res)) {
      if ((spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0 &&
        (spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0) {
        Global::mStageModel = Xr::mat4_CreateTranslationRotation(spaceLocation.pose.position, spaceLocation.pose.orientation);
        Global::mStagePose = spaceLocation.pose;
      }
    }
  }

  // Render view to the appropriate part of the swapchain image.
  for (uint32_t i = 0; i < viewCountOutput; i++) {
    // Each view has a separate swapchain which is acquired, rendered to, and released.
    const Swapchain viewSwapchain = m_swapchains[i];
    XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
    uint32_t swapchainImageIndex;
    OXR(xrAcquireSwapchainImage(viewSwapchain.handle, &acquireInfo, &swapchainImageIndex));

    XrSwapchainImageWaitInfo waitInfo{ XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO };
    waitInfo.timeout = XR_INFINITE_DURATION;
    OXR(xrWaitSwapchainImage(viewSwapchain.handle, &waitInfo));

    projectionLayerViews[i] = { XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW };
    projectionLayerViews[i].pose = m_views[i].pose;
    projectionLayerViews[i].fov = m_views[i].fov;
    projectionLayerViews[i].subImage.swapchain = viewSwapchain.handle;
    projectionLayerViews[i].subImage.imageRect.offset = { 0, 0 };
    projectionLayerViews[i].subImage.imageRect.extent = { viewSwapchain.width, viewSwapchain.height };

    ASSERT(projectionLayerViews[i].subImage.imageArrayIndex == 0);  // Texture arrays not supported.
    GL(glBindFramebuffer(GL_FRAMEBUFFER, m_swapchainFramebuffer));

#ifdef SHR3D_OPENXR_PCVR
    {
      const XrSwapchainImageBaseHeader* const swapchainImage = m_swapchainImages[viewSwapchain.handle][swapchainImageIndex];
      const GLuint colorTexture = reinterpret_cast<const XrSwapchainImageOpenGLKHR*>(swapchainImage)->image;
      const GLuint depthTexture = Xr::GetDepthTexture(colorTexture);
      Xr::RenderView(projectionLayerViews[i], colorTexture, depthTexture, Global::mControllerPoseState, Global::mAimPose, Global::mControllerModel, Global::mAimModel, Global::mStageModel);
    }
#else // SHR3D_OPENXR_PCVR
#endif // SHR3D_OPENXR_PCVR

    XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
    OXR(xrReleaseSwapchainImage(viewSwapchain.handle, &releaseInfo));
  }

  Ui::renderClear();

  layer.space = m_appSpace;
  layer.layerFlags = 0;
  layer.viewCount = (uint32_t)projectionLayerViews.size();
  layer.views = projectionLayerViews.data();

  return true;
}

void OpenXr::render()
{
#ifdef SHR3D_OPENXR_PCVR
  ASSERT(Settings::xrEnabled);
  if (!Global::xrInitialized)
    return;
#else // SHR3D_OPENXR_PCVR
#endif // SHR3D_OPENXR_PCVR

  CHECK(m_session != XR_NULL_HANDLE);

  XrFrameWaitInfo frameWaitInfo{ XR_TYPE_FRAME_WAIT_INFO };
  XrFrameState frameState{ XR_TYPE_FRAME_STATE };
  OXR(xrWaitFrame(m_session, &frameWaitInfo, &frameState));

  XrFrameBeginInfo frameBeginInfo{ XR_TYPE_FRAME_BEGIN_INFO };
  OXR(xrBeginFrame(m_session, &frameBeginInfo));

  std::vector<XrCompositionLayerBaseHeader*> layers;
  XrCompositionLayerProjection layer{ XR_TYPE_COMPOSITION_LAYER_PROJECTION };
  std::vector<XrCompositionLayerProjectionView> projectionLayerViews;

  //if (frameState.shouldRender == XR_TRUE) {
  if (RenderLayer(frameState.predictedDisplayTime, projectionLayerViews, layer)) {
    layers.push_back(reinterpret_cast<XrCompositionLayerBaseHeader*>(&layer));
  }
  //}

  XrFrameEndInfo frameEndInfo{ XR_TYPE_FRAME_END_INFO };
  frameEndInfo.displayTime = frameState.predictedDisplayTime;
  frameEndInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
  frameEndInfo.layerCount = (uint32_t)layers.size();
  frameEndInfo.layers = layers.data();
  OXR(xrEndFrame(m_session, &frameEndInfo));
}

void OpenXr::fini()
{
  //for (u64 i = 0; i < m_swapchains.size(); ++i)
  //  xrDestroySwapchain(m_swapchains[i].handle);

  //xrDestroySpace(m_appSpace);
  //xrDestroySpace(m_stageSpace);
  //xrDestroySession(m_session);
  xrDestroyInstance(m_instance);
}

#endif // SHR3D_OPENXR_PCVR
