/* * Copyright (c) 2026 Mykhailo Mamedov. All rights reserved. * * RESEARCH PREVIEW / REFERENCE ONLY: * This source code is provided solely for the purpose of reviewing * the author's research methods and implementation. * * NO LICENSE GRANTED: * This code is NOT for distribution, modification, or use in any * project (commercial or otherwise). Unauthorized copying or * use of this code is strictly prohibited. * * For inquiries regarding use or licensing, contact: ua.modin@gmail.com * * Description: * * The OpenXR Interface serves as an application-to-hardware bridge through OpenXR, * currently optimized for integration with VR systems like SteamVR. * * Current Iteration: * - Connection via OpenXR, basic session, instance. * - Provides HMD transforms and hand tracking data. * * Future Improvements: * - Structure changes. * - Device/API compatibility checking improvements. * */ #ifndef OPENXR_HEADER #define OPENXR_HEADER #define XR_USE_PLATFORM_WIN32 #define XR_USE_GRAPHICS_API_VULKAN #include #include #include #include #include namespace cfgXr { ; } namespace M_CORE { struct sem; struct mDevice; struct PhyDevice; } struct SwapchainResources { XrSwapchain handle = nullptr; std::vector images; std::vector imageViews; std::vector m_depthImages; VkFormat format = VkFormat::VK_FORMAT_UNDEFINED; }; enum HandIndex { HAND_LEFT = 0, HAND_RIGHT = 1, HAND_COUNT = 2 }; struct HandTrackingState { XrHandJointLocationEXT jointLocations[2][XR_HAND_JOINT_COUNT_EXT]; XrHandJointLocationsEXT handLocations[2]; }; /** * CB signature : It passes the views(for camera transforms) * and the index the swapchain wants app to render to. */ using RenderCallback = std::function& projectionViews, uint32_t imageIndex )>; class OpenXrInterface { public: void SetRenderCallback(RenderCallback cb) { m_renderCallback = cb; } void CreateXrSession(VkInstance vulkanInstance, M_CORE::mDevice& m_device, u32 queueFamilyIndex, RENDERDOC_API_1_6_0* rdoc_api); bool checkDeviceCompatible(M_CORE::mDevice& m_device); void PrepareSwapchainImageViews(M_CORE::mCore& core, M_CORE::mDevice& device, SwapchainResources& resources); void CreateXrSwapchain(M_CORE::mCore& core, M_CORE::mDevice& device); void RenderXrScene(); OpenXrInterface() { PrintInstanceExtensions(); m_currentInstance = createXrInstance(); XrSystemGetInfo info{ XR_TYPE_SYSTEM_GET_INFO }; info.formFactor = XrFormFactor::XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; XrResult res = xrGetSystem(m_currentInstance, &info, &m_xrSystemId); assert(!XR_FAILED(res) && "xrGetSystem failed!"); SetRequiredInstanceExtensions(); SetVulkanXrDeviceExtensions(); getVulkanGfxRequirement(); GetViewConfigViews(m_currentInstance, m_xrSystemId); SetEnvironmentBlendModes(); XrResult result = xrGetInstanceProcAddr( m_currentInstance, "xrLocateHandJointsEXT", (PFN_xrVoidFunction*)&pfnxrLocateHandJointsEXT); assert(!XR_FAILED(result)); } // Example: Get configuration for the primary stereo view void GetViewConfigViews(XrInstance instance, XrSystemId systemId) { uint32_t viewCount = 0; // Get the required count XrResult result = xrEnumerateViewConfigurationViews( instance, systemId, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &viewCount, nullptr); assert(!XR_FAILED(result)); // Allocate and get data m_views.resize(viewCount, { XR_TYPE_VIEW_CONFIGURATION_VIEW }); result = xrEnumerateViewConfigurationViews( instance, systemId, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, viewCount, &viewCount, m_views.data()); assert(!XR_FAILED(result)); // Set defaults: assert(m_views.size()); m_samples = m_views[0].recommendedSwapchainSampleCount; m_width = m_views[0].recommendedImageRectWidth; m_height = m_views[0].recommendedImageRectHeight; } XrInstance GetXrInstance() { assert(m_currentInstance); return m_currentInstance; } void BeginXrSession() { XrSessionBeginInfo beginInfo{ XR_TYPE_SESSION_BEGIN_INFO }; beginInfo.primaryViewConfigurationType = XrViewConfigurationType::XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; beginInfo.next = nullptr; XrResult result = xrBeginSession(m_xrSession, &beginInfo); assert(XR_SUCCEEDED(result)); m_sessionLaunched = true; } XrInstance createXrInstance() { XrInstanceCreateInfo instanceInfo{ XR_TYPE_INSTANCE_CREATE_INFO }; instanceInfo.applicationInfo = { .applicationName = "TheApp", .applicationVersion = 1, .engineName = "TheEngine", .engineVersion = 1, .apiVersion = XR_API_VERSION_1_0 // XR_CURRENT_API_VERSION // Forward compat. }; std::vector enabledExtensions = { XR_KHR_VULKAN_ENABLE_EXTENSION_NAME, XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME, XR_EXT_HAND_TRACKING_EXTENSION_NAME }; // XR_ERROR_EXTENSION_NOT_PRESENT TODO. instanceInfo.enabledExtensionCount = enabledExtensions.size(); instanceInfo.enabledExtensionNames = enabledExtensions.data(); XrInstance instance; XrResult result = xrCreateInstance(&instanceInfo, &instance); assert(!XR_FAILED(result) && "OpenXR Runtime not found or failed!"); result = xrGetInstanceProperties(instance, &m_instanceProps); assert(!XR_FAILED(result)); std::cout << "OpenXR Instance created successfully!" << std::endl; std::cout << "Runtime Name: " << m_instanceProps.runtimeName << std::endl; std::cout << "Runtime Version: " << XR_VERSION_MAJOR(m_instanceProps.runtimeVersion) << "." << XR_VERSION_MINOR(m_instanceProps.runtimeVersion) << "." << XR_VERSION_PATCH(m_instanceProps.runtimeVersion) << std::endl; return instance; } void PrintInstanceExtensions() { uint32_t extensionCount = 0; xrEnumerateInstanceExtensionProperties(nullptr, 0, &extensionCount, nullptr); std::vector extensionProperties(extensionCount, { XR_TYPE_EXTENSION_PROPERTIES }); xrEnumerateInstanceExtensionProperties(nullptr, extensionCount, &extensionCount, extensionProperties.data()); std::cout << "Xr Extensions:" << std::endl; for (const auto& ext : extensionProperties) { std::cout << std::string(ext.extensionName) << std::endl; } } /** * @brief Retrieves hand tracking information from the specified context. * * This function retrieves details about hand tracking, such as joint locations, */ void getHandTrackingInfo(XrHandTrackerEXT hand_tracker, const std::vector& views, XrTime predictedDisplayTime, HandIndex hand_idx) { m_handTrack.handLocations[hand_idx] = { XR_TYPE_HAND_JOINT_LOCATIONS_EXT }; m_handTrack.handLocations[hand_idx].jointCount = XR_HAND_JOINT_COUNT_EXT; m_handTrack.handLocations[hand_idx].jointLocations = m_handTrack.jointLocations[hand_idx]; XrHandJointsLocateInfoEXT locateInfo{ XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT }; locateInfo.baseSpace = m_appSpace; locateInfo.time = predictedDisplayTime; XrResult result = pfnxrLocateHandJointsEXT(hand_tracker, &locateInfo, &m_handTrack.handLocations[hand_idx]); assert(XR_SUCCEEDED(result)); /* if (m_jointLocationsInfo.isActive) { std::cout << "\n=== Hand Tracking Frame Data ===" << std::endl; std::cout << std::fixed << std::setprecision(4); for (uint32_t i = 0; i < XR_HAND_JOINT_COUNT_EXT; ++i) { constexpr XrSpaceLocationFlags validFlags = XR_SPACE_LOCATION_POSITION_VALID_BIT | XR_SPACE_LOCATION_ORIENTATION_VALID_BIT; if ((m_jointLocations[i].locationFlags & validFlags) == validFlags) { const XrVector3f& pos = m_jointLocations[i].pose.position; const XrQuaternionf& rot = m_jointLocations[i].pose.orientation; float radius = m_jointLocations[i].radius; std::cout << "Joint [" << std::setw(2) << i << "] " << "Pos: (" << std::setw(7) << pos.x << ", " << std::setw(7) << pos.y << ", " << std::setw(7) << pos.z << ") | " << "Rot: (" << std::setw(7) << rot.x << ", " << std::setw(7) << rot.y << ", " << std::setw(7) << rot.z << ", " << std::setw(7) << rot.w << ") | " << "Rad: " << radius << "\n"; } else { std::cout << "Joint [" << std::setw(2) << i << "] Not tracked (invalid flags)\n"; } } std::cout << "================================\n" << std::endl; } else { std::cout << "Hand tracker is currently INACTIVE." << std::endl; } */ } /** * @brief Creates and initializes hand trackers. * * This function creates instances of XrHandTrackerEXT, * It sets up the necessary configurations to start tracking both left and right hands in the current session. * The created trackers are stored internally within this class instance. * */ void CreateHandTrackers() { XrHandTrackerCreateInfoEXT createInfo = { XrStructureType::XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT }; createInfo.hand = XrHandEXT::XR_HAND_LEFT_EXT; createInfo.handJointSet = XrHandJointSetEXT::XR_HAND_JOINT_SET_DEFAULT_EXT; PFN_xrCreateHandTrackerEXT pfnxrCreateHandTrackerEXT = nullptr; XrResult result = xrGetInstanceProcAddr(m_currentInstance, "xrCreateHandTrackerEXT", (PFN_xrVoidFunction*)&pfnxrCreateHandTrackerEXT); assert(!XR_FAILED(result)); result = pfnxrCreateHandTrackerEXT(m_xrSession, &createInfo, &m_leftHandTracker); assert(!XR_FAILED(result)); createInfo.hand = XR_HAND_RIGHT_EXT; result = pfnxrCreateHandTrackerEXT(m_xrSession, &createInfo, &m_rightHandTracker); assert(!XR_FAILED(result)); } void SetEnvironmentBlendModes() { uint32_t count = 0; XrViewConfigurationType viewType = XrViewConfigurationType::XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; // First call to get the required count XrResult result = xrEnumerateEnvironmentBlendModes(m_currentInstance, m_xrSystemId, viewType, 0, &count, nullptr); assert(XR_SUCCEEDED(result)); m_blendModes.resize(count); // Second call to get the actual data result = xrEnumerateEnvironmentBlendModes(m_currentInstance, m_xrSystemId, viewType, count, &count, m_blendModes.data()); assert(XR_SUCCEEDED(result)); } void SetRequiredInstanceExtensions() { PFN_xrGetVulkanInstanceExtensionsKHR pfnGetVulkanInstanceExtensionsKHR = nullptr; XrResult result = xrGetInstanceProcAddr(m_currentInstance, "xrGetVulkanInstanceExtensionsKHR", (PFN_xrVoidFunction*)&pfnGetVulkanInstanceExtensionsKHR); assert(!XR_FAILED(result)); uint32_t length = 0; result = pfnGetVulkanInstanceExtensionsKHR(m_currentInstance, m_xrSystemId, 0, &length, nullptr); assert(!XR_FAILED(result)); if (length == 0) return; std::string extensions(length, ' '); result = pfnGetVulkanInstanceExtensionsKHR(m_currentInstance, m_xrSystemId, length, &length, extensions.data()); assert(!XR_FAILED(result)); // Parse the space-separated string std::stringstream ss(extensions); std::string ext; while (ss >> ext) { m_requiredVulkanInstanceExtensions.push_back(ext); } } void SetVulkanXrDeviceExtensions() { PFN_xrGetVulkanDeviceExtensionsKHR pfnGetVulkanDeviceExtensionsKHR = nullptr; XrResult result = xrGetInstanceProcAddr(m_currentInstance, "xrGetVulkanDeviceExtensionsKHR", (PFN_xrVoidFunction*)&pfnGetVulkanDeviceExtensionsKHR); assert(!XR_FAILED(result)); uint32_t count = 0; // Get the required buffer size result = pfnGetVulkanDeviceExtensionsKHR(m_currentInstance, m_xrSystemId, 0, &count, nullptr); assert(!XR_FAILED(result)); if (count == 0) return; // Allocate buffer std::string buffer(count, ' '); // Populate buffer result = pfnGetVulkanDeviceExtensionsKHR(m_currentInstance, m_xrSystemId, count, &count, &buffer[0]); assert(!XR_FAILED(result)); // Parse just like the instance extensions std::stringstream ss(buffer); std::string ext; while (ss >> ext) { m_requiredVulkanDeviceExtensions.push_back(ext); } } VkPhysicalDevice GetVulkanPhysicalDevice(VkInstance vkInstance) { assert(m_currentInstance && m_xrSystemId && "Wrong order of execution."); PFN_xrGetVulkanGraphicsDevice2KHR pfnGetVulkanGraphicsDevice2KHR = nullptr; XrResult res = xrGetInstanceProcAddr(m_currentInstance, "xrGetVulkanGraphicsDevice2KHR", (PFN_xrVoidFunction*)&pfnGetVulkanGraphicsDevice2KHR); assert(!XR_FAILED(res) && "xrGetInstanceProcAddr failed!"); VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; XrVulkanGraphicsDeviceGetInfoKHR getInfo{ XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR }; getInfo.vulkanInstance = vkInstance; getInfo.systemId = m_xrSystemId; res = pfnGetVulkanGraphicsDevice2KHR(m_currentInstance, &getInfo, &physicalDevice); assert(!XR_FAILED(res) && "pfnGetVulkanGraphicsDevice2 failed!"); return physicalDevice; } void getVulkanGfxRequirement(){ PFN_xrGetVulkanGraphicsRequirementsKHR pfnGetVulkanGraphicsRequirementsKHR = nullptr; XrResult result = xrGetInstanceProcAddr(m_currentInstance, "xrGetVulkanGraphicsRequirementsKHR", (PFN_xrVoidFunction*)&pfnGetVulkanGraphicsRequirementsKHR); assert(!XR_FAILED(result) && "xrGetInstanceProcAddr failed!"); assert(pfnGetVulkanGraphicsRequirementsKHR && "Extension (xrGetVulkanGraphicsRequirementsKHR) not supported by runtime!"); // (must be done before xrCreateSession) result = pfnGetVulkanGraphicsRequirementsKHR(m_currentInstance, m_xrSystemId, &m_graphicsRequirements); assert(!XR_FAILED(result) && "pfnGetVulkanGraphicsRequirementsKHR failed!"); } void ResetState(XrEventDataBuffer& eventData) { std::memset(&eventData, 0, sizeof(XrEventDataBuffer)); eventData.type = XR_TYPE_EVENT_DATA_BUFFER; eventData.next = nullptr; } bool IsInstanceStateInvalid() { XrInstanceProperties instanceProperties{ XR_TYPE_INSTANCE_PROPERTIES }; if (!m_currentInstance) return true; XrResult result = xrGetInstanceProperties(m_currentInstance, &instanceProperties); assert(XR_SUCCEEDED(result)); if (result == XR_ERROR_HANDLE_INVALID) { return true; } return false; } void HandshakeSimulatorSwapchains() { // TODO: Check if necessary. XrSwapchain sw_chain = swapChainResources->handle; assert(sw_chain); uint32_t imageIndex = 0; XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO }; XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO }; // Check out the texture array from the runtime XrResult res = xrAcquireSwapchainImage(sw_chain, &acquireInfo, &imageIndex); if (res == XR_SUCCESS) { XrSwapchainImageWaitInfo waitInfo{ XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO }; waitInfo.timeout = XR_INFINITE_DURATION; // Let it block until ready res = xrWaitSwapchainImage(sw_chain, &waitInfo); assert(XR_SUCCEEDED(res)); // Immediately hand it right back to unblock the compositor queue res = xrReleaseSwapchainImage(sw_chain, &releaseInfo); assert(XR_SUCCEEDED(res)); } } void SetupXrSpace() { XrReferenceSpaceCreateInfo spaceInfo = { XR_TYPE_REFERENCE_SPACE_CREATE_INFO }; spaceInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE; // XR_REFERENCE_SPACE_TYPE_LOCAL spaceInfo.poseInReferenceSpace = { {0,0,0,1}, {0,0,0} }; assert(xrCreateReferenceSpace(m_xrSession, &spaceInfo, &m_appSpace) == XR_SUCCESS); } ~OpenXrInterface() { if(m_currentInstance) xrDestroyInstance(m_currentInstance); if (m_xrSession) xrDestroySession(m_xrSession); } u64 m_scheduledWorkloadCounter = 0; u32 m_previousIndex = 20; u32 m_width = 0; u32 m_height = 0; u32 m_samples = 0; u32 m_arraySizeInitial = 2; u32 m_frame = 0; bool m_sessionLaunched = false; bool r_running = true; bool r_ready = false; bool r_idle = false; bool r_isSessionRunning = false; bool capture_xr = false; u32 m_captured_frames = 0; M_CORE::mDevice* m_deviceGfx = nullptr; RENDERDOC_API_1_6_0* m_rdoc_api; XrSessionState r_latestState = XR_SESSION_STATE_UNKNOWN; XrSession m_xrSession = nullptr; XrSystemId m_xrSystemId = 0; XrSpace m_appSpace; XrInstance m_currentInstance = nullptr; XrInstanceProperties m_instanceProps{ XR_TYPE_INSTANCE_PROPERTIES }; XrHandTrackerEXT m_leftHandTracker; XrHandTrackerEXT m_rightHandTracker; XrGraphicsRequirementsVulkanKHR m_graphicsRequirements = { XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR }; XrActionSetCreateInfo m_actionSetInfo{ XR_TYPE_ACTION_SET_CREATE_INFO }; XrSessionActionSetsAttachInfo m_attachInfo{ XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO }; PFN_xrLocateHandJointsEXT pfnxrLocateHandJointsEXT = nullptr; std::vector m_blendModes; std::vector m_views; std::vector m_requiredVulkanInstanceExtensions; std::vector m_requiredVulkanDeviceExtensions; SwapchainResources* swapChainResources = nullptr; VkCommandPool xrCommandPool = nullptr; RenderCallback m_renderCallback; HandTrackingState m_handTrack{}; }; #endif // OPENXR_HEADER #ifdef OPENXR_COMPONENT void OpenXrInterface::CreateXrSession(VkInstance vulkanInstance, M_CORE::mDevice& m_device, u32 queueFamilyIndex, RENDERDOC_API_1_6_0* rdoc_api) { XrGraphicsBindingVulkanKHR graphicsBinding = { XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR }; graphicsBinding.instance = vulkanInstance; graphicsBinding.physicalDevice = m_device.phyDeviceData.m_physDevice; graphicsBinding.device = m_device.virtualDevice; graphicsBinding.queueFamilyIndex = queueFamilyIndex; graphicsBinding.queueIndex = 0; // ? XrSessionCreateInfo sessionCreateInfo = { XR_TYPE_SESSION_CREATE_INFO }; sessionCreateInfo.next = &graphicsBinding; sessionCreateInfo.systemId = m_xrSystemId; XrResult res = xrCreateSession(m_currentInstance, &sessionCreateInfo, &m_xrSession); assert(res == XR_SUCCESS && m_xrSession); SetupXrSpace(); // Create action set (TEST) strcpy(m_actionSetInfo.actionSetName, "gameplay"); strcpy(m_actionSetInfo.localizedActionSetName, "Gameplay"); XrActionSet actionSet; res = xrCreateActionSet(m_currentInstance, &m_actionSetInfo, &actionSet); assert(XR_SUCCEEDED(res)); // Attach to the session m_attachInfo.countActionSets = 1; m_attachInfo.actionSets = &actionSet; res = xrAttachSessionActionSets(m_xrSession, &m_attachInfo); assert(XR_SUCCEEDED(res)); m_deviceGfx = &m_device; m_rdoc_api = rdoc_api; } bool OpenXrInterface::checkDeviceCompatible(M_CORE::mDevice& m_device) { // Ensure the device supports at least the minimum version uint32_t xrMinMajor = (m_graphicsRequirements.minApiVersionSupported >> 48) & 0xFFFF; uint32_t xrMinMinor = (m_graphicsRequirements.minApiVersionSupported >> 32) & 0xFFFF; uint32_t vkMajor = (m_device.phyDeviceData.m_devProps.apiVersion >> 22) & 0x3FF; uint32_t vkMinor = (m_device.phyDeviceData.m_devProps.apiVersion >> 12) & 0x3FF; bool versionOk = vkMajor >= xrMinMajor && vkMinor >= xrMinMinor; if (!versionOk) { auto err_string = std::format("Api version {} not supported on {}, required {}", m_device.phyDeviceData.m_devProps.apiVersion, m_device.phyDeviceData.m_devProps.deviceName, m_graphicsRequirements.minApiVersionSupported ); assert(versionOk && err_string.c_str()); } return true; } /** * Create XR swap-chain. */ void OpenXrInterface::CreateXrSwapchain(M_CORE::mCore& core, M_CORE::mDevice& device) { assert(m_currentInstance && m_xrSession); // Create Swapchain swapChainResources = new SwapchainResources(); swapChainResources->format = VK_FORMAT_R8G8B8A8_SRGB; XrSwapchainCreateInfo swapchainInfo = { XR_TYPE_SWAPCHAIN_CREATE_INFO }; swapchainInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_SAMPLED_BIT; // Adding XR_SWAPCHAIN_USAGE_SAMPLED_BIT swapchainInfo.format = swapChainResources->format; swapchainInfo.sampleCount = 1; // m_samples swapchainInfo.width = m_width; swapchainInfo.height = m_height; swapchainInfo.faceCount = 1; swapchainInfo.arraySize = m_arraySizeInitial; // Stereo swapchainInfo.mipCount = 1; swapchainInfo.createFlags = 0; XrResult res = xrCreateSwapchain(m_xrSession, &swapchainInfo, &swapChainResources->handle); assert(XR_SUCCEEDED(res)); // Prepare views PrepareSwapchainImageViews(core, device, *swapChainResources); } /** * Function to prepare Vulkan Image and Depth Views. */ void OpenXrInterface::PrepareSwapchainImageViews(M_CORE::mCore& core, M_CORE::mDevice& device, SwapchainResources& resources) { u32 imageCount = 0; XrResult res = xrEnumerateSwapchainImages(resources.handle, 0, &imageCount, nullptr); assert(XR_SUCCEEDED(res)); std::vector swapchainImages(imageCount, { XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR }); res = xrEnumerateSwapchainImages( resources.handle, imageCount, &imageCount, reinterpret_cast(swapchainImages.data())); assert(XR_SUCCEEDED(res)); resources.images.clear(); resources.imageViews.clear(); resources.m_depthImages.resize(imageCount); u32 counter = 0; for (int i = 0; i < imageCount; i++) { auto& image = swapchainImages[i]; resources.images.push_back(image.image); VkImageViewCreateInfo viewInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; viewInfo.image = image.image; viewInfo.viewType = VkImageViewType::VK_IMAGE_VIEW_TYPE_2D_ARRAY; viewInfo.format = resources.format; viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; viewInfo.subresourceRange.baseMipLevel = 0; viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = m_arraySizeInitial; VkImageView imageView; if (vkCreateImageView(device.virtualDevice, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { throw std::runtime_error("failed to create image view!"); } core.setObjectName(device.virtualDevice, imageView, std::format("xrImage_{}", counter).c_str()); resources.imageViews.push_back(imageView); VkImageLayout OldLayout = VK_IMAGE_LAYOUT_UNDEFINED; VkImageLayout NewLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; core.TransitionImageLayout(viewInfo.image, resources.format, OldLayout, NewLayout, 1); vkDeviceWaitIdle(device.virtualDevice); counter++; } // Depth resources maintained by us, not XR api. VkFormat DepthFormat = device.phyDeviceData.m_depthFormat; for (int i = 0; i < imageCount; i++) { M_CORE::ImageParams parms{ .device = device, .Tex = resources.m_depthImages[i], .ImageWidth = m_width, .ImageHeight = m_height, .TexFormat = DepthFormat, .UsageFlags = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, .PropertyFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, .ImageDepth = 1, .name = std::format("xrDepthImage_{}", i).c_str(), .shareMode = VK_SHARING_MODE_EXCLUSIVE, .sharedQueueFamilies = {0, 0}, .IsCubemap = false, .Is3dImage = false, .IsStereo = true }; core.CreateImage(parms); VkImageLayout OldLayout = VK_IMAGE_LAYOUT_UNDEFINED; VkImageLayout NewLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; M_CORE::ImageViewParams params{}; params.Device = device.virtualDevice; params.Image = resources.m_depthImages[i].m_image; params.Format = DepthFormat; params.AspectFlags = VK_IMAGE_ASPECT_DEPTH_BIT; params.IsStereo = true; resources.m_depthImages[i].m_view = core.CreateImageView(params); core.setObjectName(device.virtualDevice, resources.m_depthImages[i].m_view, std::format("xrDepthImage_{}", i).c_str()); core.TransitionImageLayout(resources.m_depthImages[i].m_image, DepthFormat, OldLayout, NewLayout, 1); } } /** * @brief RenderXrScene is the function responsible for rendering scenes within an OpenXR application context, * facilitating interaction between VR and graphics pipelines. * * @details It processes: * - Camera transforms to ensure accurate alignment of virtual content with user's field of view. * - Hand tracking data for realistic interactions in VR scenarios. * - Swapchain images captured during each frame cycle to provide real-time visual feedback. * This function integrates hardware inputs like camera movements, hand positions, * orientations, and joint locations into the rendering process. */ void OpenXrInterface::RenderXrScene() { XrResult result = XrResult::XR_SUCCESS; assert(swapChainResources); if (!r_running) return; while (true) { if (IsInstanceStateInvalid()) { r_running = false; break; } // --- Event Handling --- XrEventDataBuffer eventData = { XR_TYPE_EVENT_DATA_BUFFER, nullptr }; while (true) { result = xrPollEvent(m_currentInstance, &eventData); if (result != XR_SUCCESS) { // XR_ERROR_VALIDATION_FAILURE if (result == XR_EVENT_UNAVAILABLE) { ResetState(eventData); std::this_thread::sleep_for(std::chrono::milliseconds(1)); break; } char errorString[XR_MAX_RESULT_STRING_SIZE]; XrResult result2 = xrResultToString(m_currentInstance, result, errorString); assert(XR_SUCCEEDED(result2)); printf(std::format("xrPollEvent throws: {}\n", errorString).c_str()); ResetState(eventData); break; } if (eventData.type == XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED) { auto* stateChanged = reinterpret_cast(&eventData); r_latestState = stateChanged->state; switch (stateChanged->state) { case XR_SESSION_STATE_READY: { BeginXrSession(); HandshakeSimulatorSwapchains(); ResetState(eventData); break; } case XR_SESSION_STATE_SYNCHRONIZED: { printf(std::format("XR_SESSION_STATE_SYNCHRONIZED\n").c_str()); r_isSessionRunning = true; ResetState(eventData); break; } case XR_SESSION_STATE_VISIBLE: { printf(std::format("XR_SESSION_STATE_VISIBLE\n").c_str()); r_isSessionRunning = true; ResetState(eventData); break; } case XR_SESSION_STATE_FOCUSED: { r_isSessionRunning = true; ResetState(eventData); break; } case XR_SESSION_STATE_IDLE: { std::this_thread::sleep_for(std::chrono::milliseconds(1)); ResetState(eventData); break; } case XR_SESSION_STATE_STOPPING: { r_isSessionRunning = false; m_sessionLaunched = false; if (stateChanged->state == XR_SESSION_STATE_STOPPING) { xrEndSession(m_xrSession); // Clean up if required by spec } break; } } } else { printf(std::format("Unhandled other event: {}\n", (int)eventData.type).c_str()); } } if (!m_sessionLaunched) { break; } if (m_rdoc_api && capture_xr) m_rdoc_api->StartFrameCapture(m_deviceGfx->virtualDevice, nullptr); // --- Frame Synchronization --- XrFrameWaitInfo waitInfo = { XR_TYPE_FRAME_WAIT_INFO }; XrFrameState frameState = { XR_TYPE_FRAME_STATE }; result = xrWaitFrame(m_xrSession, &waitInfo, &frameState); assert(!XR_FAILED(result)); XrFrameBeginInfo beginInfo = { XR_TYPE_FRAME_BEGIN_INFO }; result = xrBeginFrame(m_xrSession, &beginInfo); assert(!XR_FAILED(result)); // --- Camera Pose Location --- XrViewLocateInfo viewLocateInfo = { XR_TYPE_VIEW_LOCATE_INFO }; viewLocateInfo.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; // Assuming Stereo viewLocateInfo.displayTime = frameState.predictedDisplayTime; viewLocateInfo.space = m_appSpace; XrViewState viewState = { XR_TYPE_VIEW_STATE }; u32 viewCount{ 0 }; std::vector views; result = xrLocateViews(m_xrSession, &viewLocateInfo, &viewState, viewCount, &viewCount, nullptr); assert(!XR_FAILED(result)); assert(viewCount==2); views.resize(viewCount, { XR_TYPE_VIEW }); result = xrLocateViews(m_xrSession, &viewLocateInfo, &viewState, viewCount, &viewCount, views.data()); assert(!XR_FAILED(result)); bool invalid_view = false; if ((viewState.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) == 0) { invalid_view = true; // Pose is invalid, do not submit this layer } if ((viewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) == 0) { invalid_view = true; } // --- Pull hand tracking. getHandTrackingInfo(m_leftHandTracker, views, frameState.predictedDisplayTime, HAND_LEFT); getHandTrackingInfo(m_rightHandTracker, views, frameState.predictedDisplayTime, HAND_RIGHT); // --- Rendering --- XrCompositionLayerProjection projectionLayer = { XR_TYPE_COMPOSITION_LAYER_PROJECTION }; std::vector projectionViews(viewCount, { XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW }); if (frameState.shouldRender) { XrRect2Di singleEyeRect; singleEyeRect.offset.x = 0; singleEyeRect.offset.y = 0; singleEyeRect.extent.width = m_width; singleEyeRect.extent.height = m_height; if(true) { u32 imageIndex; XrSwapchainImageAcquireInfo acquireInfo = { XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO }; result = xrAcquireSwapchainImage(swapChainResources->handle, &acquireInfo, &imageIndex); assert(!XR_FAILED(result)); XrSwapchainImageWaitInfo waitInfo = { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO }; waitInfo.timeout = XR_INFINITE_DURATION; if (xrWaitSwapchainImage(swapChainResources->handle, &waitInfo) == XR_SUCCESS) { // Prepare projection views. for (uint32_t i = 0; i < viewCount; ++i) { projectionViews[i].pose = views[i].pose; projectionViews[i].fov = views[i].fov; projectionViews[i].subImage.swapchain = swapChainResources->handle; projectionViews[i].subImage.imageArrayIndex = i; projectionViews[i].subImage.imageRect = singleEyeRect; } // Use registered CB, inherited from the parent instance. // See who called SetRenderCallback() declared above. assert(m_renderCallback); m_renderCallback(projectionViews, 0); m_frame++; } else { throw std::runtime_error("xrWaitSwapchainImage failed!"); // TODO Check handling. } XrSwapchainImageReleaseInfo releaseInfo = { XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO }; result = xrReleaseSwapchainImage(swapChainResources->handle, &releaseInfo); assert(!XR_FAILED(result)); } // End Frame projectionLayer.space = m_appSpace; projectionLayer.viewCount = viewCount; projectionLayer.views = projectionViews.data(); const XrCompositionLayerBaseHeader* layers[] = { (XrCompositionLayerBaseHeader*)&projectionLayer }; XrFrameEndInfo endInfo = { XR_TYPE_FRAME_END_INFO }; endInfo.displayTime = frameState.predictedDisplayTime; endInfo.layerCount = 1; endInfo.layers = layers; endInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; // XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND; assert(!not_contains(endInfo.environmentBlendMode, m_blendModes)); if (invalid_view || !r_isSessionRunning || !frameState.shouldRender) { endInfo.layers = nullptr; endInfo.layerCount = 0; } result = xrEndFrame(m_xrSession, &endInfo); if (m_rdoc_api && capture_xr) m_rdoc_api->EndFrameCapture(m_deviceGfx->virtualDevice, nullptr); m_captured_frames++; if (m_captured_frames > 3) { m_captured_frames = 0; capture_xr = false; } if (result == XR_SESSION_LOSS_PENDING) { printf(std::format("Session loss pending\n").c_str()); r_running = false; } if (result == XR_ERROR_SESSION_NOT_RUNNING) { printf(std::format("Session is not running. Pausing frame loop.\n").c_str()); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } if (XR_FAILED(result)) { char errorString[XR_MAX_RESULT_STRING_SIZE]; result = xrResultToString(m_currentInstance, result, errorString); assert(XR_SUCCEEDED(result)); printf(std::format("xrEndFrame throws: {}\n", errorString).c_str()); } } else { printf(std::format("Skipping render.\n").c_str()); } break; // Not a loop. } // --- Cleanup --- // Destroy resources here as this is the end of the thread - TODO } #endif