Skip to content

Commit 2912d17

Browse files
authored
Merge pull request #430 from gpx1000/OpenXR-lluid-removal
Remove incorrect guidance about looking for the LLUID.
2 parents eff887d + f83d2f5 commit 2912d17

10 files changed

Lines changed: 128 additions & 79 deletions

File tree

antora/modules/ROOT/nav.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@
438438
** The OpenXR-Vulkan 1.3 Handshake
439439
*** xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/01_introduction.adoc[Introduction]
440440
*** xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/02_system_integration.adoc[System Integration]
441-
*** xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/03_hardware_alignment_luid.adoc[Hardware Alignment (LUID)]
441+
*** xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/03_hardware_alignment_luid.adoc[Hardware Alignment]
442442
*** xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/04_vulkan_1_3_feature_requirements.adoc[Vulkan 1.3 Feature Requirements]
443443
*** xref:OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/05_incorporating_into_the_engine.adoc[Incorporating into the Engine]
444444
** Runtime-Owned Swapchains

attachments/openxr_engine/renderer_core.cpp

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,9 @@ bool Renderer::Initialize(const std::string& appName, bool enableValidationLayer
154154
return false;
155155
}
156156

157-
// In XR mode pick the device BEFORE creating the surface. The OpenXR runtime
158-
// identifies the correct GPU via LUID/UUID, so no surface is needed for selection.
157+
// In XR mode pick the device BEFORE creating the surface. The OpenXR runtime hands us
158+
// the exact VkPhysicalDevice it requires (it enforces this — it's tied to whatever GPU
159+
// the headset's display is wired to), so no surface is needed for selection.
159160
// Creating the GLFW surface first causes the XR runtime's Vulkan layer to intercept
160161
// vkCreateXxxSurfaceKHR before the validation layer sees it, producing an untracked
161162
// surface handle that crashes vkGetPhysicalDeviceSurfaceSupportKHR.
@@ -753,14 +754,17 @@ bool Renderer::pickPhysicalDevice() {
753754
<< " (Type: " << vk::to_string(deviceProperties.deviceType) << ")" << std::endl;
754755

755756
if (xrMode) {
756-
// Match the LUID provided by OpenXR
757-
auto props2 = _device.getProperties2<vk::PhysicalDeviceProperties2, vk::PhysicalDeviceIDProperties>();
758-
const auto& idProps = props2.get<vk::PhysicalDeviceIDProperties>();
759-
760-
const uint8_t* requiredLuid = xrContext.getRequiredLUID();
761-
if (requiredLuid && std::memcmp(idProps.deviceLUID, requiredLuid, VK_LUID_SIZE) != 0) {
762-
std::cout << " - LUID mismatch for OpenXR" << std::endl;
763-
continue; // Not the right GPU for XR!
757+
// The OpenXR runtime enforces which GPU we must use — it already handed us the
758+
// exact VkPhysicalDevice via xrGetVulkanGraphicsDevice2KHR. Skip anything else;
759+
// there is no matching or scoring left to do, the runtime made the choice for us.
760+
vk::PhysicalDevice required = xrContext.getRequiredPhysicalDevice();
761+
if (static_cast<VkPhysicalDevice>(required) == VK_NULL_HANDLE) {
762+
std::cerr << " - Could not obtain required physical device from OpenXR runtime" << std::endl;
763+
continue;
764+
}
765+
if (*_device != required) {
766+
std::cout << " - Not the GPU OpenXR requires" << std::endl;
767+
continue;
764768
}
765769
}
766770

attachments/openxr_engine/xr_context.cpp

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ bool XrContext::createInstance(const std::string& appName) {
105105
}
106106

107107
// Load Vulkan extension functions
108+
xrGetInstanceProcAddr(instance, "xrGetVulkanInstanceExtensionsKHR", (PFN_xrVoidFunction*)&pfnGetVulkanInstanceExtensionsKHR);
109+
xrGetInstanceProcAddr(instance, "xrGetVulkanDeviceExtensionsKHR", (PFN_xrVoidFunction*)&pfnGetVulkanDeviceExtensionsKHR);
108110
xrGetInstanceProcAddr(instance, "xrGetVulkanGraphicsRequirements2KHR", (PFN_xrVoidFunction*)&pfnGetVulkanGraphicsRequirements2KHR);
109111
xrGetInstanceProcAddr(instance, "xrGetVulkanGraphicsDevice2KHR", (PFN_xrVoidFunction*)&pfnGetVulkanGraphicsDevice2KHR);
110112

@@ -302,15 +304,61 @@ void XrContext::cleanup() {
302304
}
303305
}
304306

305-
const uint8_t* XrContext::getRequiredLUID() {
306-
if (!luidValid && vkInstance && instance != XR_NULL_HANDLE && systemId != XR_NULL_SYSTEM_ID) {
307+
std::vector<const char*> XrContext::getVulkanInstanceExtensions() {
308+
if (instance == XR_NULL_HANDLE) return { "XR_KHR_vulkan_enable2" };
309+
uint32_t size = 0;
310+
if (!pfnGetVulkanInstanceExtensionsKHR) return {};
311+
pfnGetVulkanInstanceExtensionsKHR(instance, systemId, 0, &size, nullptr);
312+
std::vector<char> buffer(size);
313+
pfnGetVulkanInstanceExtensionsKHR(instance, systemId, size, &size, buffer.data());
314+
315+
static std::vector<std::string> extStrings;
316+
extStrings.clear();
317+
std::string extensions(buffer.data());
318+
std::istringstream iss(extensions);
319+
std::string ext;
320+
while (iss >> ext) extStrings.push_back(ext);
321+
322+
static std::vector<const char*> extPtrs;
323+
extPtrs.clear();
324+
for (const auto& s : extStrings) extPtrs.push_back(s.c_str());
325+
return extPtrs;
326+
}
327+
328+
std::vector<const char*> XrContext::getVulkanDeviceExtensions(vk::PhysicalDevice physicalDevice) {
329+
if (instance == XR_NULL_HANDLE) return { "VK_KHR_external_memory", "VK_KHR_external_semaphore" };
330+
uint32_t size = 0;
331+
if (!pfnGetVulkanDeviceExtensionsKHR) return {};
332+
pfnGetVulkanDeviceExtensionsKHR(instance, systemId, 0, &size, nullptr);
333+
std::vector<char> buffer(size);
334+
pfnGetVulkanDeviceExtensionsKHR(instance, systemId, size, &size, buffer.data());
335+
336+
static std::vector<std::string> devExtStrings;
337+
devExtStrings.clear();
338+
std::string extensions(buffer.data());
339+
std::istringstream iss(extensions);
340+
std::string ext;
341+
while (iss >> ext) devExtStrings.push_back(ext);
342+
343+
static std::vector<const char*> devExtPtrs;
344+
devExtPtrs.clear();
345+
for (const auto& s : devExtStrings) devExtPtrs.push_back(s.c_str());
346+
return devExtPtrs;
347+
}
348+
349+
vk::PhysicalDevice XrContext::getRequiredPhysicalDevice() {
350+
if (!requiredPhysicalDeviceQueried && vkInstance && instance != XR_NULL_HANDLE && systemId != XR_NULL_SYSTEM_ID) {
351+
requiredPhysicalDeviceQueried = true;
352+
307353
// Step 1: Call graphics requirements as mandated by spec before getting graphics device
308354
XrGraphicsRequirementsVulkanKHR graphicsRequirements{XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR};
309355
if (pfnGetVulkanGraphicsRequirements2KHR) {
310356
pfnGetVulkanGraphicsRequirements2KHR(instance, systemId, &graphicsRequirements);
311357
}
312358

313-
// Step 2: Get the physical device from OpenXR
359+
// Step 2: Ask the runtime which physical device to use. The runtime enforces this
360+
// choice (it is tied to the display the headset is connected to), so the returned
361+
// handle IS the device to use — there is nothing left to match or search for.
314362
VkPhysicalDevice vkPhysicalDevice = VK_NULL_HANDLE;
315363
XrResult result = XR_ERROR_FUNCTION_UNSUPPORTED;
316364

@@ -322,30 +370,13 @@ const uint8_t* XrContext::getRequiredLUID() {
322370
}
323371

324372
if (result == XR_SUCCESS && vkPhysicalDevice != VK_NULL_HANDLE) {
325-
// Step 3: Extract LUID from the physical device
326-
VkPhysicalDeviceIDProperties idProps{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES};
327-
idProps.pNext = nullptr;
328-
VkPhysicalDeviceProperties2 props2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
329-
props2.pNext = &idProps;
330-
331-
auto pfnGetPhysicalDeviceProperties2 = (PFN_vkGetPhysicalDeviceProperties2)vkGetInstanceProcAddr((VkInstance)vkInstance, "vkGetPhysicalDeviceProperties2");
332-
if (pfnGetPhysicalDeviceProperties2) {
333-
pfnGetPhysicalDeviceProperties2(vkPhysicalDevice, &props2);
334-
if (idProps.deviceLUIDValid) {
335-
std::memcpy(requiredLuid, idProps.deviceLUID, VK_LUID_SIZE);
336-
luidValid = true;
337-
std::cout << "XrContext: Required LUID found and stored." << std::endl;
338-
} else {
339-
std::cout << "XrContext: Physical device LUID is not valid." << std::endl;
340-
}
341-
} else {
342-
std::cerr << "XrContext: Failed to load vkGetPhysicalDeviceProperties2" << std::endl;
343-
}
373+
requiredPhysicalDevice = vkPhysicalDevice;
374+
std::cout << "XrContext: Required Vulkan physical device obtained from OpenXR runtime." << std::endl;
344375
} else {
345376
std::cerr << "XrContext: Failed to get Vulkan graphics device from OpenXR (XrResult=" << result << ")" << std::endl;
346377
}
347378
}
348-
return luidValid ? requiredLuid : nullptr;
379+
return vk::PhysicalDevice(requiredPhysicalDevice);
349380
}
350381

351382
vk::Extent2D XrContext::getRecommendedExtent() const {

attachments/openxr_engine/xr_context.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ class XrContext {
4848
#endif
4949

5050
// Core Handshake (Chapter 2)
51-
const uint8_t* getRequiredLUID();
51+
std::vector<const char*> getVulkanInstanceExtensions();
52+
std::vector<const char*> getVulkanDeviceExtensions(vk::PhysicalDevice physicalDevice);
53+
// The runtime hands back the exact VkPhysicalDevice it wants us to use — no LUID
54+
// matching required. Returns VK_NULL_HANDLE if the query fails.
55+
vk::PhysicalDevice getRequiredPhysicalDevice();
5256

5357
// Swapchain Management (Chapter 3 & 8)
5458
vk::Extent2D getRecommendedExtent() const;
@@ -116,6 +120,8 @@ class XrContext {
116120
static bool checkRuntimeAvailable();
117121

118122
private:
123+
PFN_xrGetVulkanInstanceExtensionsKHR pfnGetVulkanInstanceExtensionsKHR = nullptr;
124+
PFN_xrGetVulkanDeviceExtensionsKHR pfnGetVulkanDeviceExtensionsKHR = nullptr;
119125
PFN_xrGetVulkanGraphicsRequirements2KHR pfnGetVulkanGraphicsRequirements2KHR = nullptr;
120126
PFN_xrGetVulkanGraphicsDevice2KHR pfnGetVulkanGraphicsDevice2KHR = nullptr;
121127

@@ -127,8 +133,8 @@ class XrContext {
127133
bool sessionBegun = false;
128134
XrReferenceSpaceType referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
129135

130-
uint8_t requiredLuid[VK_LUID_SIZE] = {0};
131-
bool luidValid = false;
136+
VkPhysicalDevice requiredPhysicalDevice = VK_NULL_HANDLE;
137+
bool requiredPhysicalDeviceQueried = false;
132138

133139
#if defined(PLATFORM_ANDROID)
134140
struct android_app* androidApp = nullptr;

en/OpenXR_Vulkan_Spatial_Computing/02_OpenXR_Vulkan_Handshake/01_introduction.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Before we can render a single pixel to an XR headset, we must first establish a
66
In this chapter, we are going to look at the three pillars of a successful spatial handshake:
77

88
1. **System Integration**: How to extend our engine's `VulkanContext` to support the mandatory OpenXR extensions, specifically `XR_KHR_vulkan_enable2`.
9-
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.
9+
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.
1010
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.
1111
1212
== The Concept of the Handshake
Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,78 @@
11
:pp: {plus}{plus}
2-
= Hardware Alignment (LUID)
2+
= Hardware Alignment
33

44
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.
55

66
== Ecosystem Perspective
77

88
This chapter falls under the category: **Using Vulkan with an OpenXR Runtime**.
99

10-
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.
10+
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.
1111

1212
== The "PCIe Tax": Why Alignment is Mandatory
1313

14-
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).
14+
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).
1515

1616
* **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.
1717
* **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.
1818
* **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.
1919

20-
== What is an LUID?
21-
22-
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.
23-
24-
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.
20+
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.
2521

2622
== Querying the XR Device
2723

28-
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`.
24+
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`).
2925

3026
[source,cpp]
3127
----
3228
VkPhysicalDevice xrRequiredDevice;
33-
xrGetVulkanGraphicsDevice2KHR(xrInstance, systemId, *instance, &xrRequiredDevice);
29+
30+
XrVulkanGraphicsDeviceGetInfoKHR getInfo{XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR};
31+
getInfo.systemId = systemId;
32+
getInfo.vulkanInstance = instance;
33+
34+
xrGetVulkanGraphicsDevice2KHR(xrInstance, &getInfo, &xrRequiredDevice);
3435
----
3536

36-
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.
37+
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.
38+
3739

38-
== Matching the LUID in Vulkan
40+
== Using the Handle Directly
3941

40-
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.
42+
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:
4143

4244
[source,cpp]
4345
----
44-
// Iterating through physical devices to find a match using vulkan-hpp RAII
45-
for (const auto& physicalDevice : instance.enumeratePhysicalDevices()) {
46-
auto props2 = physicalDevice.getProperties2<vk::PhysicalDeviceProperties2, vk::PhysicalDeviceIDProperties>();
47-
const auto& idProps = props2.get<vk::PhysicalDeviceIDProperties>();
48-
49-
if (idProps.deviceLUIDValid) {
50-
// Compare the 8-byte LUID against the target from OpenXR
51-
if (std::memcmp(idProps.deviceLUID, targetLUID, VK_LUID_SIZE) == 0) {
52-
return physicalDevice;
53-
}
46+
// The runtime enforces this choice; we don't search for it, we're told it.
47+
vk::PhysicalDevice requiredDevice(xrRequiredDevice);
48+
----
49+
50+
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:
51+
52+
[source,cpp]
53+
----
54+
for (auto& physicalDevice : instance.enumeratePhysicalDevices()) {
55+
if (*physicalDevice == requiredDevice) {
56+
return physicalDevice;
5457
}
5558
}
5659
----
5760

61+
This is a simple handle comparison, not a hardware "fingerprint" match—the runtime already told us which device this is.
62+
5863
== Cross-Process Memory Visibility
5964

60-
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.
65+
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.
6166

62-
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.
67+
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.
6368

6469
== Advanced: Multi-GPU Load Balancing and Device Groups
6570

6671
While an OpenXR session is typically tied to the single GPU connected to the display, Vulkan allows us to go further:
6772

6873
* **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.
69-
* **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.
74+
* **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.
7075

71-
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].
76+
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].
7277

7378
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]

0 commit comments

Comments
 (0)