Run the same-device Vulkan OpenXR backend on desktop Linux

Lepton's Vulkan driver has no external memory or sync fd extensions,
so the Steam Frame gets a native SteamOS build instead. Its backend is
the PC's: OpenXR creates Dawn's own Vulkan instance and device, and the
eyes are copied into the swapchain on Dawn's queue, with nothing shared
between devices.

- openxr_vulkan_win32 and Aurora's vulkan_win32_interop now compile on
  desktop Linux. Dawn links statically there, so the bridge calls the
  patched package's hooks directly, and exists only when the package's
  aurora-dawn.json declares the Vulkan hook ABI (AuroraDawnProvider
  turns AuroraVulkanAbi into AURORA_DAWN_VULKAN_HOOKS); a stock Dawn
  keeps the stubs and VR falls back to the desktop.
- openxr_integration.cpp gains a Linux branch: XR_KHR_vulkan_enable2,
  the timespec clock, refresh rate, performance level, the Frame
  controllers and eye gaze; fragment density maps are requested on
  Linux as on the Quest (fdm.cpp and gpu.cpp, AURORA_FDM=0/1 overrides).
- Controller motion uses XR_KHR_convert_timespec_time on any Linux.
- CMake: Vulkan headers are fetched for Linux OpenXR builds, the
  headset option is MKW_HEADSET on Android and Linux, and Linux aarch64
  builds take MKW_LINUX_CPU (cortex-x4 for the Steam Frame, else native).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HBRGKTE1GnN2ah8gcZKr3
This commit is contained in:
Claude committed 2026-10-04 09:48:48 +00:00
1 parent da0d13bdd2
commit 28a25108c6
13 files changed
+137 -36

No files matched your search

+19 -16
View File
@@ -37,18 +37,19 @@ option(MKW_BUILD_PRODUCTS "Build translated WiiCompiled product targets" ON)
# aurora-main and its third-party tree stay unaffected.
set(MKW_PROJECT_COMPILE_DEFINITIONS NOMINMAX)
# The Android headset family the build is for (the Gradle flavour passes it). The
# Steam Frame build runs under SteamVR's Android runtime rather than Horizon OS and
# takes its own defaults (runtime_config.h) through MKW_HEADSET_STEAM_FRAME. The
# definition reaches every target below, so the game kit's recorded compile flags
# carry it too.
if(MKW_PLATFORM_ANDROID)
set(MKW_ANDROID_HEADSET "quest" CACHE STRING "Android headset family: quest (Meta Quest, any flavour) or steam_frame")
set_property(CACHE MKW_ANDROID_HEADSET PROPERTY STRINGS quest steam_frame)
if(MKW_ANDROID_HEADSET STREQUAL "steam_frame")
# The headset a standalone build is for: the Android app's Gradle flavour passes
# it, and so does a native SteamOS build for the Steam Frame
# (Launcher/local-build.sh --headset steam_frame). The Steam Frame takes its own
# defaults (runtime_config.h) through MKW_HEADSET_STEAM_FRAME. The definition
# reaches every target below, so the Quest game kit's recorded compile flags carry
# it too.
if(MKW_PLATFORM_ANDROID OR MKW_PLATFORM_LINUX)
set(MKW_HEADSET "" CACHE STRING "Headset the build targets: empty (a Meta Quest on Android, any PC headset elsewhere) or steam_frame")
set_property(CACHE MKW_HEADSET PROPERTY STRINGS "" steam_frame)
if(MKW_HEADSET STREQUAL "steam_frame")
list(APPEND MKW_PROJECT_COMPILE_DEFINITIONS MKW_HEADSET_STEAM_FRAME=1)
elseif(NOT MKW_ANDROID_HEADSET STREQUAL "quest")
message(FATAL_ERROR "MKW_ANDROID_HEADSET must be quest or steam_frame, not '${MKW_ANDROID_HEADSET}'")
elseif(NOT MKW_HEADSET STREQUAL "")
message(FATAL_ERROR "MKW_HEADSET must be empty or steam_frame, not '${MKW_HEADSET}'")
endif()
endif()
@@ -148,10 +149,10 @@ set(MKW_TRANSLATED_COMPILE_JOBS 0 CACHE STRING
Scheduling only - never affects output bytes, so it is deliberately outside the canonical flag fingerprint.")
set(MKW_CPPWINRT_INCLUDE_DIR "" CACHE PATH "Optional self-contained C++/WinRT include directory")
# The functional backend is currently Windows D3D12, so only Windows defaults
# to carrying the loader. Linux keeps the capability-gated Vulkan scaffold
# available to explicit developer builds without making a nonfunctional SDK
# fetch part of every normal build.
# Windows and Android carry the loader by default. Desktop Linux has the
# same-device Vulkan backend too, but it needs a Dawn built with Aurora's patches
# (Launcher/build-dawn-linux.sh), so it stays opt-in: Launcher/local-build.sh
# --openxr, as the Steam Frame's native SteamOS build uses it.
set(MKW_ENABLE_OPENXR_DEFAULT OFF)
if(MKW_PLATFORM_WINDOWS OR MKW_PLATFORM_ANDROID)
set(MKW_ENABLE_OPENXR_DEFAULT ON)
@@ -301,7 +302,9 @@ if(MKW_PROJECT_COMPILE_DEFINITIONS)
add_compile_definitions(${MKW_PROJECT_COMPILE_DEFINITIONS})
endif()
if(MKW_PLATFORM_WINDOWS AND MKW_ENABLE_OPENXR)
# The same-device Vulkan backend (openxr_vulkan_win32.cpp, on Windows and desktop
# Linux) includes vulkan.h; the pin keeps it independent of the build machine.
if((MKW_PLATFORM_WINDOWS OR MKW_PLATFORM_LINUX) AND MKW_ENABLE_OPENXR)
include(FetchContent)
FetchContent_Declare(vulkan_headers
URL "https://github.com/KhronosGroup/Vulkan-Headers/archive/015e25c3c91b70eb1a754d36fb14c4ba6ad9b0b9.tar.gz"
+8 -1
View File
@@ -452,7 +452,14 @@ elseif(MKW_PLATFORM_ANDROID)
set(MKW_ANDROID_CPU "cortex-a77" CACHE STRING "AArch64 -mcpu target for the Android products")
set(MKW_BASELINE_ARCH_FLAG -mcpu=${MKW_ANDROID_CPU})
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
set(MKW_BASELINE_ARCH_FLAG -mcpu=native)
# A cross-build names its CPU; the Steam Frame's Snapdragon 8 Gen 3 exposes SVE (cortex-x4).
if(MKW_HEADSET STREQUAL "steam_frame")
set(_mkw_linux_cpu_default "cortex-x4")
else()
set(_mkw_linux_cpu_default "native")
endif()
set(MKW_LINUX_CPU "${_mkw_linux_cpu_default}" CACHE STRING "AArch64 -mcpu target for the Linux products")
set(MKW_BASELINE_ARCH_FLAG -mcpu=${MKW_LINUX_CPU})
else()
set(MKW_BASELINE_ARCH_FLAG "")
endif()
+4 -2
View File
@@ -2,7 +2,9 @@
#pragma once
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
// Despite the name, also the desktop Linux backend (SteamOS on the Steam Frame): nothing in it is
// Windows-specific. The Quest has its own two-device backend (openxr_vulkan.h).
#if defined(MKW_ENABLE_OPENXR) && (defined(_WIN32) || (defined(__linux__) && !defined(__ANDROID__)))
#include "vr/openxr_backend.h"
#include "vr/openxr_runtime.h"
@@ -112,4 +114,4 @@ private:
} // namespace mkw::vr
#endif // defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
#endif // defined(MKW_ENABLE_OPENXR) && (defined(_WIN32) || (defined(__linux__) && !defined(__ANDROID__)))
+7 -2
View File
@@ -35,6 +35,8 @@
#if defined(__ANDROID__)
#include <sys/system_properties.h>
#endif
#if defined(__linux__)
#include <time.h>
#endif
@@ -275,6 +277,9 @@ bool Injected(const char*) { return false; }
#if defined(_WIN32)
using ConvertNowToXrTime = XrResult(XRAPI_PTR*)(XrInstance, const LARGE_INTEGER*, XrTime*);
#elif defined(__linux__)
// Desktop Linux (SteamOS on the Steam Frame); Android declares it with its injection above.
using ConvertNowToXrTime = XrResult(XRAPI_PTR*)(XrInstance, const struct timespec*, XrTime*);
#endif
#endif
@@ -666,7 +671,7 @@ void OpenXRInput::LoadInputClock() {
if (enabled("XR_KHR_win32_convert_performance_counter_time")) {
m_runtime->GetInstanceProcAddress("xrConvertWin32PerformanceCounterToTimeKHR", &function);
}
#elif defined(__ANDROID__)
#elif defined(__linux__)
if (enabled("XR_KHR_convert_timespec_time")) {
m_runtime->GetInstanceProcAddress("xrConvertTimespecTimeToTimeKHR", &function);
}
@@ -692,7 +697,7 @@ XrTime OpenXRInput::InputSampleTime(XrTime predicted_display_time) const {
&counter, &now))) {
return predicted_display_time;
}
#elif defined(__ANDROID__)
#elif defined(__linux__)
timespec spec{};
if (clock_gettime(CLOCK_MONOTONIC, &spec) != 0 ||
XR_FAILED(reinterpret_cast<ConvertNowToXrTime>(m_convert_now_to_xr_time)(m_runtime->Instance(),
+26 -4
View File
@@ -48,6 +48,14 @@
#define XR_USE_TIMESPEC
#include <openxr/openxr_platform.h>
#define MKW_OPENXR_GRAPHICS_BACKEND 1
#elif defined(__linux__)
// Desktop Linux, SteamOS on the Steam Frame above all: the PC's same-device Vulkan backend, with
// Dawn's own device bound to the session.
#include "vr/openxr_vulkan_win32.h"
#include <time.h>
#define XR_USE_TIMESPEC
#include <openxr/openxr_platform.h>
#define MKW_OPENXR_GRAPHICS_BACKEND 1
#else
#define MKW_OPENXR_GRAPHICS_BACKEND 0
#endif
@@ -85,9 +93,12 @@ void ConfigurePolicy(bool enabled) noexcept {
#if defined(_WIN32)
using GraphicsBackend = OpenXRWindowsBackend;
#else
#elif defined(__ANDROID__)
using GraphicsBackend = OpenXRVulkanBackend;
inline constexpr const char* kGraphicsBackendName = "Vulkan";
#else
using GraphicsBackend = OpenXRWindowsVulkanBackend;
inline constexpr const char* kGraphicsBackendName = "Vulkan";
#endif
// Whether the immersive window's eyes can be aimed through the window, so that only the window is
@@ -412,6 +423,16 @@ public:
"XR_FB_display_refresh_rate", "XR_EXT_performance_settings",
"XR_VALVE_frame_controller_interaction"};
AddHandMeshExtensions(config);
#elif defined(__linux__) && !defined(__ANDROID__)
// As the PC's Vulkan binding, plus the clock conversion Linux uses and the Steam Frame's
// eye gaze for eye-tracked foveation.
config.required_extensions = {"XR_KHR_vulkan_enable2"};
config.optional_extensions = {"XR_KHR_convert_timespec_time", "XR_FB_display_refresh_rate",
"XR_EXT_performance_settings", "XR_VALVE_frame_controller_interaction"};
if (RuntimeConfigFile::VrEyeTrackedFoveation()) {
config.optional_extensions.push_back("XR_EXT_eye_gaze_interaction");
}
AddHandMeshExtensions(config);
#else
// Either Vulkan binding extension is acceptable; the backend picks
// whichever the runtime enabled, preferring enable2.
@@ -718,9 +739,10 @@ private:
void ApplyGraphicsRequirements(AuroraConfig& aurora_config) {
aurora_config.desiredBackend = kRequiredAuroraBackend;
aurora_config.xrInterop = true;
#if defined(__ANDROID__)
// Foveated rendering: fragment density maps are decided with the device. They put a flag on
// every render pipeline, so a session launched with foveation off does without them.
#if !defined(_WIN32)
// Foveated rendering (the Quest's and desktop Linux's patched Dawn): fragment density maps
// are decided with the device. They put a flag on every render pipeline, so a session
// launched with foveation off does without them.
aurora_config.xrFragmentDensityMap = RuntimeConfigFile::VrFoveation() != "off";
#endif
#if defined(_WIN32)
+6 -2
View File
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
#if defined(MKW_ENABLE_OPENXR) && (defined(_WIN32) || (defined(__linux__) && !defined(__ANDROID__)))
// OpenXR's Vulkan structures are selected when openxr_platform.h is parsed.
#define XR_USE_GRAPHICS_API_VULKAN
@@ -180,7 +180,11 @@ public:
if (XR_FAILED(get(runtime.Instance(), runtime.SystemId(), &requirements))) return Fail("Vulkan requirements query failed");
requirements_ = {requirements.minApiVersionSupported, requirements.maxApiVersionSupported};
AuroraDawnVulkanHooks hooks{this, CreateInstance, CreateDevice, GetPhysical};
#if defined(_WIN32)
if (!aurora_vulkan_win32_configure(&hooks)) return Fail("PC Vulkan OpenXR requires the custom Dawn library with Aurora Vulkan ABI 1; rebuild/install it with Launcher/Build-DawnVulkan.ps1");
#else
if (!aurora_vulkan_win32_configure(&hooks)) return Fail("Linux Vulkan OpenXR requires a Dawn built with Aurora's patches (Aurora Vulkan ABI 1); build it with Launcher/build-dawn-linux.sh");
#endif
hooks_installed_ = true;
{
std::lock_guard lock(submission_mutex_);
@@ -1315,4 +1319,4 @@ const std::string& OpenXRWindowsVulkanBackend::LastError() const { return m_impl
} // namespace mkw::vr
#endif // defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
#endif // defined(MKW_ENABLE_OPENXR) && (defined(_WIN32) || (defined(__linux__) && !defined(__ANDROID__)))