diff --git a/antora/modules/ROOT/nav.adoc b/antora/modules/ROOT/nav.adoc index f709366d..23b622c4 100644 --- a/antora/modules/ROOT/nav.adoc +++ b/antora/modules/ROOT/nav.adoc @@ -438,7 +438,7 @@ ** The OpenXR-Vulkan 1.3 Handshake *** xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/01_introduction.adoc[Introduction] *** xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/02_system_integration.adoc[System Integration] -*** xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/03_hardware_alignment_luid.adoc[Hardware Alignment (LUID)] +*** xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/03_hardware_alignment_luid.adoc[Hardware Alignment] *** xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/04_vulkan_1_3_feature_requirements.adoc[Vulkan 1.3 Feature Requirements] *** xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/05_incorporating_into_the_engine.adoc[Incorporating into the Engine] ** Runtime-Owned Swapchains diff --git a/attachments/openxr_engine/renderer_core.cpp b/attachments/openxr_engine/renderer_core.cpp index e831c897..ffe79092 100644 --- a/attachments/openxr_engine/renderer_core.cpp +++ b/attachments/openxr_engine/renderer_core.cpp @@ -154,8 +154,9 @@ bool Renderer::Initialize(const std::string& appName, bool enableValidationLayer return false; } - // In XR mode pick the device BEFORE creating the surface. The OpenXR runtime - // identifies the correct GPU via LUID/UUID, so no surface is needed for selection. + // In XR mode pick the device BEFORE creating the surface. The OpenXR runtime hands us + // the exact VkPhysicalDevice it requires (it enforces this — it's tied to whatever GPU + // the headset's display is wired to), so no surface is needed for selection. // Creating the GLFW surface first causes the XR runtime's Vulkan layer to intercept // vkCreateXxxSurfaceKHR before the validation layer sees it, producing an untracked // surface handle that crashes vkGetPhysicalDeviceSurfaceSupportKHR. @@ -753,14 +754,17 @@ bool Renderer::pickPhysicalDevice() { << " (Type: " << vk::to_string(deviceProperties.deviceType) << ")" << std::endl; if (xrMode) { - // Match the LUID provided by OpenXR - auto props2 = _device.getProperties2(); - const auto& idProps = props2.get(); - - const uint8_t* requiredLuid = xrContext.getRequiredLUID(); - if (requiredLuid && std::memcmp(idProps.deviceLUID, requiredLuid, VK_LUID_SIZE) != 0) { - std::cout << " - LUID mismatch for OpenXR" << std::endl; - continue; // Not the right GPU for XR! + // The OpenXR runtime enforces which GPU we must use — it already handed us the + // exact VkPhysicalDevice via xrGetVulkanGraphicsDevice2KHR. Skip anything else; + // there is no matching or scoring left to do, the runtime made the choice for us. + vk::PhysicalDevice required = xrContext.getRequiredPhysicalDevice(); + if (static_cast(required) == VK_NULL_HANDLE) { + std::cerr << " - Could not obtain required physical device from OpenXR runtime" << std::endl; + continue; + } + if (*_device != required) { + std::cout << " - Not the GPU OpenXR requires" << std::endl; + continue; } } diff --git a/attachments/openxr_engine/xr_context.cpp b/attachments/openxr_engine/xr_context.cpp index b39b0e89..6c8e8c2e 100644 --- a/attachments/openxr_engine/xr_context.cpp +++ b/attachments/openxr_engine/xr_context.cpp @@ -105,6 +105,8 @@ bool XrContext::createInstance(const std::string& appName) { } // Load Vulkan extension functions + xrGetInstanceProcAddr(instance, "xrGetVulkanInstanceExtensionsKHR", (PFN_xrVoidFunction*)&pfnGetVulkanInstanceExtensionsKHR); + xrGetInstanceProcAddr(instance, "xrGetVulkanDeviceExtensionsKHR", (PFN_xrVoidFunction*)&pfnGetVulkanDeviceExtensionsKHR); xrGetInstanceProcAddr(instance, "xrGetVulkanGraphicsRequirements2KHR", (PFN_xrVoidFunction*)&pfnGetVulkanGraphicsRequirements2KHR); xrGetInstanceProcAddr(instance, "xrGetVulkanGraphicsDevice2KHR", (PFN_xrVoidFunction*)&pfnGetVulkanGraphicsDevice2KHR); @@ -302,15 +304,61 @@ void XrContext::cleanup() { } } -const uint8_t* XrContext::getRequiredLUID() { - if (!luidValid && vkInstance && instance != XR_NULL_HANDLE && systemId != XR_NULL_SYSTEM_ID) { +std::vector XrContext::getVulkanInstanceExtensions() { + if (instance == XR_NULL_HANDLE) return { "XR_KHR_vulkan_enable2" }; + uint32_t size = 0; + if (!pfnGetVulkanInstanceExtensionsKHR) return {}; + pfnGetVulkanInstanceExtensionsKHR(instance, systemId, 0, &size, nullptr); + std::vector buffer(size); + pfnGetVulkanInstanceExtensionsKHR(instance, systemId, size, &size, buffer.data()); + + static std::vector extStrings; + extStrings.clear(); + std::string extensions(buffer.data()); + std::istringstream iss(extensions); + std::string ext; + while (iss >> ext) extStrings.push_back(ext); + + static std::vector extPtrs; + extPtrs.clear(); + for (const auto& s : extStrings) extPtrs.push_back(s.c_str()); + return extPtrs; +} + +std::vector XrContext::getVulkanDeviceExtensions(vk::PhysicalDevice physicalDevice) { + if (instance == XR_NULL_HANDLE) return { "VK_KHR_external_memory", "VK_KHR_external_semaphore" }; + uint32_t size = 0; + if (!pfnGetVulkanDeviceExtensionsKHR) return {}; + pfnGetVulkanDeviceExtensionsKHR(instance, systemId, 0, &size, nullptr); + std::vector buffer(size); + pfnGetVulkanDeviceExtensionsKHR(instance, systemId, size, &size, buffer.data()); + + static std::vector devExtStrings; + devExtStrings.clear(); + std::string extensions(buffer.data()); + std::istringstream iss(extensions); + std::string ext; + while (iss >> ext) devExtStrings.push_back(ext); + + static std::vector devExtPtrs; + devExtPtrs.clear(); + for (const auto& s : devExtStrings) devExtPtrs.push_back(s.c_str()); + return devExtPtrs; +} + +vk::PhysicalDevice XrContext::getRequiredPhysicalDevice() { + if (!requiredPhysicalDeviceQueried && vkInstance && instance != XR_NULL_HANDLE && systemId != XR_NULL_SYSTEM_ID) { + requiredPhysicalDeviceQueried = true; + // Step 1: Call graphics requirements as mandated by spec before getting graphics device XrGraphicsRequirementsVulkanKHR graphicsRequirements{XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR}; if (pfnGetVulkanGraphicsRequirements2KHR) { pfnGetVulkanGraphicsRequirements2KHR(instance, systemId, &graphicsRequirements); } - // Step 2: Get the physical device from OpenXR + // Step 2: Ask the runtime which physical device to use. The runtime enforces this + // choice (it is tied to the display the headset is connected to), so the returned + // handle IS the device to use — there is nothing left to match or search for. VkPhysicalDevice vkPhysicalDevice = VK_NULL_HANDLE; XrResult result = XR_ERROR_FUNCTION_UNSUPPORTED; @@ -322,30 +370,13 @@ const uint8_t* XrContext::getRequiredLUID() { } if (result == XR_SUCCESS && vkPhysicalDevice != VK_NULL_HANDLE) { - // Step 3: Extract LUID from the physical device - VkPhysicalDeviceIDProperties idProps{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES}; - idProps.pNext = nullptr; - VkPhysicalDeviceProperties2 props2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2}; - props2.pNext = &idProps; - - auto pfnGetPhysicalDeviceProperties2 = (PFN_vkGetPhysicalDeviceProperties2)vkGetInstanceProcAddr((VkInstance)vkInstance, "vkGetPhysicalDeviceProperties2"); - if (pfnGetPhysicalDeviceProperties2) { - pfnGetPhysicalDeviceProperties2(vkPhysicalDevice, &props2); - if (idProps.deviceLUIDValid) { - std::memcpy(requiredLuid, idProps.deviceLUID, VK_LUID_SIZE); - luidValid = true; - std::cout << "XrContext: Required LUID found and stored." << std::endl; - } else { - std::cout << "XrContext: Physical device LUID is not valid." << std::endl; - } - } else { - std::cerr << "XrContext: Failed to load vkGetPhysicalDeviceProperties2" << std::endl; - } + requiredPhysicalDevice = vkPhysicalDevice; + std::cout << "XrContext: Required Vulkan physical device obtained from OpenXR runtime." << std::endl; } else { std::cerr << "XrContext: Failed to get Vulkan graphics device from OpenXR (XrResult=" << result << ")" << std::endl; } } - return luidValid ? requiredLuid : nullptr; + return vk::PhysicalDevice(requiredPhysicalDevice); } vk::Extent2D XrContext::getRecommendedExtent() const { diff --git a/attachments/openxr_engine/xr_context.h b/attachments/openxr_engine/xr_context.h index e2095f3e..1f7f4033 100644 --- a/attachments/openxr_engine/xr_context.h +++ b/attachments/openxr_engine/xr_context.h @@ -48,7 +48,11 @@ class XrContext { #endif // Core Handshake (Chapter 2) - const uint8_t* getRequiredLUID(); + std::vector getVulkanInstanceExtensions(); + std::vector getVulkanDeviceExtensions(vk::PhysicalDevice physicalDevice); + // The runtime hands back the exact VkPhysicalDevice it wants us to use — no LUID + // matching required. Returns VK_NULL_HANDLE if the query fails. + vk::PhysicalDevice getRequiredPhysicalDevice(); // Swapchain Management (Chapter 3 & 8) vk::Extent2D getRecommendedExtent() const; @@ -116,6 +120,8 @@ class XrContext { static bool checkRuntimeAvailable(); private: + PFN_xrGetVulkanInstanceExtensionsKHR pfnGetVulkanInstanceExtensionsKHR = nullptr; + PFN_xrGetVulkanDeviceExtensionsKHR pfnGetVulkanDeviceExtensionsKHR = nullptr; PFN_xrGetVulkanGraphicsRequirements2KHR pfnGetVulkanGraphicsRequirements2KHR = nullptr; PFN_xrGetVulkanGraphicsDevice2KHR pfnGetVulkanGraphicsDevice2KHR = nullptr; @@ -127,8 +133,8 @@ class XrContext { bool sessionBegun = false; XrReferenceSpaceType referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE; - uint8_t requiredLuid[VK_LUID_SIZE] = {0}; - bool luidValid = false; + VkPhysicalDevice requiredPhysicalDevice = VK_NULL_HANDLE; + bool requiredPhysicalDeviceQueried = false; #if defined(PLATFORM_ANDROID) struct android_app* androidApp = nullptr; diff --git a/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/01_introduction.adoc b/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/01_introduction.adoc index 543068e7..54b9d991 100644 --- a/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/01_introduction.adoc +++ b/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/01_introduction.adoc @@ -6,7 +6,7 @@ Before we can render a single pixel to an XR headset, we must first establish a In this chapter, we are going to look at the three pillars of a successful spatial handshake: 1. **System Integration**: How to extend our engine's `VulkanContext` to support the mandatory OpenXR extensions, specifically `XR_KHR_vulkan_enable2`. -2. **Hardware Alignment**: Utilizing the Locally Unique Identifier (**LUID**) to ensure that both OpenXR and Vulkan are talking to the exact same physical GPU. This is critical for cross-process memory visibility and performance. +2. **Hardware Alignment**: Using the exact `VkPhysicalDevice` handle the OpenXR runtime hands back from `xrGetVulkanGraphicsDevice2KHR` to ensure both OpenXR and Vulkan are talking to the same physical GPU. The runtime enforces this choice, so there is no matching to do—just use the handle it gives you. This is critical for cross-process memory visibility and performance. 3. **Vulkan 1.3 Requirements**: Activating the modern features—like Timeline Semaphores, Dynamic Rendering, and Synchronization 2—that allow our spatial pipeline to operate with minimal latency. == The Concept of the Handshake diff --git a/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/03_hardware_alignment_luid.adoc b/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/03_hardware_alignment_luid.adoc index eee1c87a..2f8e2bc5 100644 --- a/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/03_hardware_alignment_luid.adoc +++ b/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/03_hardware_alignment_luid.adoc @@ -1,5 +1,5 @@ :pp: {plus}{plus} -= Hardware Alignment (LUID) += Hardware Alignment In many modern computing environments, especially high-end gaming desktops, it is common to have multiple GPUs—such as an integrated GPU on the processor and a dedicated high-performance card. For spatial computing, it is absolutely critical that both our application and the OpenXR runtime are using the exact same physical hardware. @@ -7,67 +7,72 @@ In many modern computing environments, especially high-end gaming desktops, it i This chapter falls under the category: **Using Vulkan with an OpenXR Runtime**. -Aligning your Vulkan physical device with the XR runtime's preferred hardware is a mandatory step to ensure high-performance resource sharing. Using the LUID (Locally Unique Identifier) ensures that both systems are talking to the same physical silicon. +Aligning your Vulkan physical device with the XR runtime's preferred hardware is a mandatory step to ensure high-performance resource sharing. Critically, this is not a negotiation: the OpenXR runtime *enforces* which GPU you must use, and it hands you that exact device directly. There is no matching or searching to do. == The "PCIe Tax": Why Alignment is Mandatory -To understand why we care about the LUID, we must consider the cost of data movement. The PCIe bus is the highway between your CPU, system RAM, and your GPUs. While it is very fast, it is still orders of magnitude slower than the internal memory bus of a modern GPU (VRAM). +To understand why we care about hardware alignment, we must consider the cost of data movement. The PCIe bus is the highway between your CPU, system RAM, and your GPUs. While it is very fast, it is still orders of magnitude slower than the internal memory bus of a modern GPU (VRAM). * **Display Discovery**: The runtime has negotiated with the operating system (e.g., via DXGI on Windows) to identify which physical GPU is electrically connected to the headset's display. * **Avoiding Cross-GPU Copies**: If our engine renders a frame on GPU A, but the headset is connected to GPU B, the OS would be forced to copy that image across the **PCIe** bus. This adds several milliseconds of latency—potentially half of your frame budget for a 90Hz headset. * **Internal VRAM Efficiency**: Moving an image within the same GPU is almost instantaneous. By pinning the application to the same GPU, the runtime ensures your frames stay within high-speed VRAM at all times. -== What is an LUID? - -An **LUID** (Locally Unique Identifier) is a 64-bit value guaranteed to be unique on the local machine until the next reboot. Unlike a **UUID**, which is persistent across machines, the LUID is a transient hardware handle provided by the operating system. - -In the context of the OpenXR-Vulkan handshake, the LUID serves as the hardware "fingerprint" of the GPU. OpenXR tells us: "I am currently talking to the GPU with this specific LUID," and we must search through our available `VkPhysicalDevice` handles until we find the one that matches. +Because getting this wrong silently costs performance (or breaks resource sharing outright), OpenXR does not leave device selection up to the application at all—it tells you exactly which `VkPhysicalDevice` to use. == Querying the XR Device -Once we have initialized our `xr::Instance`, we can query the specific hardware identity that the runtime expects us to use. We do this by calling `xrGetVulkanGraphicsDevice2KHR`. +Once we have initialized our `xr::Instance`, we can query the specific `VkPhysicalDevice` that the runtime requires us to use. We do this by calling `xrGetVulkanGraphicsDevice2KHR` (or the older `xrGetVulkanGraphicsDeviceKHR`). [source,cpp] ---- VkPhysicalDevice xrRequiredDevice; -xrGetVulkanGraphicsDevice2KHR(xrInstance, systemId, *instance, &xrRequiredDevice); + +XrVulkanGraphicsDeviceGetInfoKHR getInfo{XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR}; +getInfo.systemId = systemId; +getInfo.vulkanInstance = instance; + +xrGetVulkanGraphicsDevice2KHR(xrInstance, &getInfo, &xrRequiredDevice); ---- -While we can get the raw handle directly from the runtime, matching via LUID is often more robust, especially when our engine's architecture abstracts physical device selection or when multiple Vulkan instances are present. +This handle is the answer. The runtime has already resolved display topology, driver adapter enumeration, and cross-process sharing requirements on your behalf—that is precisely what this call is for. Your job is simply to use `xrRequiredDevice` as your `VkPhysicalDevice`, not to re-derive it by some other means. + -== Matching the LUID in Vulkan +== Using the Handle Directly -To find the LUID of a Vulkan physical device, we query the `VkPhysicalDeviceIDProperties` structure. In the `vulkan-hpp` RAII world, we can retrieve this by chaining structures in a `getProperties2` call. +In the `vulkan-hpp` RAII world, you don't need to enumerate physical devices at all in XR mode—you already have the handle the runtime requires. Wrap it directly: [source,cpp] ---- -// Iterating through physical devices to find a match using vulkan-hpp RAII -for (const auto& physicalDevice : instance.enumeratePhysicalDevices()) { - auto props2 = physicalDevice.getProperties2(); - const auto& idProps = props2.get(); - - if (idProps.deviceLUIDValid) { - // Compare the 8-byte LUID against the target from OpenXR - if (std::memcmp(idProps.deviceLUID, targetLUID, VK_LUID_SIZE) == 0) { - return physicalDevice; - } +// The runtime enforces this choice; we don't search for it, we're told it. +vk::PhysicalDevice requiredDevice(xrRequiredDevice); +---- + +If you still want a `vk::raii::PhysicalDevice` (for example, to reuse suitability-checking code that expects one), find the matching entry by comparing the raw handle—not any derived identifier: + +[source,cpp] +---- +for (auto& physicalDevice : instance.enumeratePhysicalDevices()) { + if (*physicalDevice == requiredDevice) { + return physicalDevice; } } ---- +This is a simple handle comparison, not a hardware "fingerprint" match—the runtime already told us which device this is. + == Cross-Process Memory Visibility -The LUID isn't just for selection; it is the foundation of **Cross-Process Memory Visibility**. Because the XR runtime usually lives in a separate process from our engine, the images we render must be shared across process boundaries. +Using the runtime-provided device is the foundation of **Cross-Process Memory Visibility**. Because the XR runtime usually lives in a separate process from our engine, the images we render must be shared across process boundaries. -By aligning our hardware selection via the LUID, we guarantee that the "Wait-Acquire-Release" cycle can happen entirely on-device, without expensive CPU-side synchronization or system memory copies. +By using the exact `VkPhysicalDevice` the runtime requires, we guarantee that the "Wait-Acquire-Release" cycle can happen entirely on-device, without expensive CPU-side synchronization or system memory copies. == Advanced: Multi-GPU Load Balancing and Device Groups While an OpenXR session is typically tied to the single GPU connected to the display, Vulkan allows us to go further: * **Vulkan Device Groups**: You can use `VK_KHR_device_group` to treat multiple GPUs as a single logical device. This allows you to use a secondary GPU for heavy compute tasks (like physics or scene decomposition) and aggregate the results on the primary GPU before handing the final frame to the XR compositor. -* **Handling Hardware Changes**: If the hardware configuration changes (e.g., an external GPU is disconnected), you can use Vulkan's hardware abstraction to detect these changes and prompt the user for a clean engine restart, ensuring that the hardware LUID handshake remains valid. +* **Handling Hardware Changes**: If the hardware configuration changes (e.g., an external GPU is disconnected), you can use Vulkan's hardware abstraction to detect these changes and prompt the user for a clean engine restart. On restart, simply re-query `xrGetVulkanGraphicsDevice2KHR`—the runtime will hand you whatever device is now correct. -TIP: For more information on hardware alignment, check out the official link:https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#VkPhysicalDeviceIDProperties[Vulkan Specification on Device IDs], the link:https://docs.vulkan.org/guide/latest/index.html[Vulkan Guide], and our link:00_introduction.adoc[main tutorial series]. +TIP: For more information on hardware alignment, check out the official link:https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#xrGetVulkanGraphicsDevice2KHR[OpenXR Specification on xrGetVulkanGraphicsDevice2KHR], the link:https://docs.vulkan.org/guide/latest/index.html[Vulkan Guide], and our link:00_introduction.adoc[main tutorial series]. xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/02_system_integration.adoc[Previous] | xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/04_vulkan_1_3_feature_requirements.adoc[Next] diff --git a/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/04_vulkan_1_3_feature_requirements.adoc b/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/04_vulkan_1_3_feature_requirements.adoc index 1ebd1ff6..83e5e545 100644 --- a/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/04_vulkan_1_3_feature_requirements.adoc +++ b/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/04_vulkan_1_3_feature_requirements.adoc @@ -74,7 +74,7 @@ TIP: For more information on Vulkan 1.3 and core features, check out the officia We have now successfully: 1. Extended our `VulkanContext` to negotiate extensions with OpenXR. -2. Aligned our `VkPhysicalDevice` selection using the hardware LUID. +2. Aligned our `VkPhysicalDevice` selection with the device the OpenXR runtime requires. 3. Enabled the modern Vulkan 1.3 features required for low-latency spatial rendering. With the handshake complete, we are ready to tackle the most significant architectural change in an XR engine: moving from engine-owned swapchains to **Runtime-Owned Swapchains**. diff --git a/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/05_incorporating_into_the_engine.adoc b/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/05_incorporating_into_the_engine.adoc index a455fa7e..359fbf91 100644 --- a/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/05_incorporating_into_the_engine.adoc +++ b/en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/05_incorporating_into_the_engine.adoc @@ -45,7 +45,8 @@ class XrContext { public: // Core Handshake (Chapter 2) std::vector getVulkanInstanceExtensions(); - const uint8_t* getRequiredLUID(); + // The runtime hands back the exact VkPhysicalDevice it requires — no matching needed. + vk::PhysicalDevice getRequiredPhysicalDevice(); // Swapchain Management (Chapter 3) void createSwapchains(vk::Device device, vk::Format format, vk::Extent2D extent); @@ -121,9 +122,9 @@ bool Renderer::createInstance(const std::string& appName, bool enableValidationL } ---- -=== 2. Hardware Alignment (LUID Matching) +=== 2. Hardware Alignment (Runtime-Enforced Device) -In `Renderer::pickPhysicalDevice`, we must ensure we select the GPU that OpenXR is actually using. We do this by matching the LUID: +In `Renderer::pickPhysicalDevice`, we must ensure we select the GPU that OpenXR requires. The runtime does not merely suggest a device—it enforces one, and hands it back to us directly from `xrGetVulkanGraphicsDevice2KHR`. We just compare handles: [source,cpp] ---- @@ -133,13 +134,13 @@ bool Renderer::pickPhysicalDevice() { for (auto& _device : devices) { if (xrMode) { - // Match the LUID provided by OpenXR - auto props2 = _device.getProperties2(); - const auto& idProps = props2.get(); - - const uint8_t* requiredLuid = xrContext.getRequiredLUID(); - if (requiredLuid && std::memcmp(idProps.deviceLUID, requiredLuid, VK_LUID_SIZE) != 0) { - continue; // Not the right GPU for XR! + // The OpenXR runtime already told us which VkPhysicalDevice to use. + vk::PhysicalDevice required = xrContext.getRequiredPhysicalDevice(); + if (static_cast(required) == VK_NULL_HANDLE) { + continue; // Could not query the runtime's required device. + } + if (*_device != required) { + continue; // Not the GPU OpenXR requires. } } // ... rest of suitability scoring ... @@ -147,6 +148,8 @@ bool Renderer::pickPhysicalDevice() { } ---- +NOTE: Earlier versions of this tutorial matched devices by comparing their LUID (`VkPhysicalDeviceIDProperties::deviceLUID`) against a LUID extracted from the runtime's device. That extra step was unnecessary—`xrGetVulkanGraphicsDevice2KHR` already gives you the `VkPhysicalDevice` handle itself—and unreliable on runtimes/drivers where `deviceLUIDValid` isn't set. Compare handles directly instead. + === 3. Enabling Vulkan 1.3 Features In `Renderer::createLogicalDevice`, we must explicitly enable the Vulkan 1.3 features required for spatial computing, particularly **Dynamic Rendering** and **Synchronization 2**: @@ -175,6 +178,6 @@ bool Renderer::createLogicalDevice(bool enableValidationLayers) { == Why These Changes? -By referencing the specific locations in `renderer_core.cpp`, we can see that OpenXR isn't just an "add-on"—it's a **collaborator** in the Vulkan lifecycle. Without the LUID matching in `pickPhysicalDevice`, our engine might initialize on a discrete GPU while the VR headset is tethered to an integrated one, leading to a complete failure to display. Similarly, the extension negotiation ensures that the XR compositor and our engine speak the same "dialect" of Vulkan. +By referencing the specific locations in `renderer_core.cpp`, we can see that OpenXR isn't just an "add-on"—it's a **collaborator** in the Vulkan lifecycle. Without honoring the runtime-enforced device in `pickPhysicalDevice`, our engine might initialize on a discrete GPU while the VR headset is tethered to an integrated one, leading to a complete failure to display. Similarly, the extension negotiation ensures that the XR compositor and our engine speak the same "dialect" of Vulkan. xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/04_vulkan_1_3_feature_requirements.adoc[Previous] | xref:OpenXR_Vulkan_Spatial_Computing/03_Runtime_Owned_Swapchains/01_introduction.adoc[Next] diff --git a/en/OpenXR_Vulkan_Spatial_Computing/19_Platform_Divergence/04_incorporating_into_the_engine.adoc b/en/OpenXR_Vulkan_Spatial_Computing/19_Platform_Divergence/04_incorporating_into_the_engine.adoc index 56fd08fd..374cfd7b 100644 --- a/en/OpenXR_Vulkan_Spatial_Computing/19_Platform_Divergence/04_incorporating_into_the_engine.adoc +++ b/en/OpenXR_Vulkan_Spatial_Computing/19_Platform_Divergence/04_incorporating_into_the_engine.adoc @@ -30,7 +30,7 @@ void Renderer::applySpatialPlatformPresets() { // Enable high-bandwidth features like Ray Query vk::PhysicalDeviceRayQueryFeaturesKHR rayQueryFeatures{}; - // ... request features from the LUID-matched physical device ... + // ... request features from the runtime-required physical device ... } } ---- diff --git a/en/OpenXR_Vulkan_Spatial_Computing/introduction.adoc b/en/OpenXR_Vulkan_Spatial_Computing/introduction.adoc index 430e98ff..d92014e2 100644 --- a/en/OpenXR_Vulkan_Spatial_Computing/introduction.adoc +++ b/en/OpenXR_Vulkan_Spatial_Computing/introduction.adoc @@ -44,7 +44,7 @@ Vulkan 1.3 brings several critical features to the table that are particularly p Throughout this series, we will cover the entire lifecycle of an XR frame: -1. **The Handshake**: Connecting OpenXR to our Vulkan context using LUID matching and mandatory extensions. +1. **The Handshake**: Connecting OpenXR to our Vulkan context using the runtime-enforced physical device and mandatory extensions. 2. **Resource Management**: Wrapping runtime-owned images into our RAII-based engine abstractions. 3. **The Predictive Loop**: Mastering frame timing to ensure that what the user sees matches exactly where their head is located at the moment of display. 4. **Spatial Shaders**: Using Slang to author efficient multiview and foveated rendering shaders.