From 49b860254391cce6a191d16f0174f16e96a9278c Mon Sep 17 00:00:00 2001 From: iChris4 Date: Wed, 16 Sep 2026 22:04:30 +0200 Subject: [PATCH] Remove OpenXR Vulkan backend implementation files --- runtime/include/vr/openxr_vulkan_backend.h | 186 ------ runtime/src/vr/openxr_vulkan_backend.cpp | 630 --------------------- 2 files changed, 816 deletions(-) delete mode 100644 runtime/include/vr/openxr_vulkan_backend.h delete mode 100644 runtime/src/vr/openxr_vulkan_backend.cpp diff --git a/runtime/include/vr/openxr_vulkan_backend.h b/runtime/include/vr/openxr_vulkan_backend.h deleted file mode 100644 index 52eaf1b..0000000 --- a/runtime/include/vr/openxr_vulkan_backend.h +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#if defined(MKW_ENABLE_OPENXR) - -#include "vr/openxr_runtime.h" - -#include -#include -#include -#include -#include -#include - -namespace mkw::vr { - -// Keep Vulkan implementation types out of the runtime/Aurora boundary. Vulkan -// dispatchable and non-dispatchable handles both fit in 64 bits on every target -// supported by this project. The backend converts these values to the Vulkan -// header's actual handle types only in its implementation file. -using OpenXRVulkanHandle = uint64_t; - -inline constexpr uint32_t kOpenXRVulkanNativeContextVersion = 1; - -enum OpenXRVulkanNativeContextFlagBits : uint32_t { - // The handles are the exact objects used by Aurora's Dawn device. A second - // Vulkan device is not a valid substitute: it cannot present Dawn's work to - // an OpenXR swapchain without an explicit external-memory bridge. - OpenXRVulkanNativeContextDawnOwnedBit = 1u << 0, - - // The provider has queried the queue from device/queue_family_index/ - // queue_index and verified that it equals queue. Dawn's package API does not - // currently expose enough information for this backend to do that itself. - OpenXRVulkanNativeContextQueueVerifiedBit = 1u << 1, -}; - -using OpenXRVulkanQueueLockCallback = bool (*)(void* userdata); -using OpenXRVulkanQueueUnlockCallback = void (*)(void* userdata); - -// ABI-stable context a Dawn-native shim must provide. All handles are borrowed -// and must outlive OpenXRVulkanBackend. The paired callbacks externally -// synchronize Vulkan queue access with Dawn; they are mandatory because a -// Vulkan queue is not internally synchronized and XR may use the graphics queue -// during image hand-off. -struct OpenXRVulkanNativeContext { - uint32_t version = kOpenXRVulkanNativeContextVersion; - uint32_t struct_size = sizeof(OpenXRVulkanNativeContext); - uint32_t flags = 0; - - OpenXRVulkanHandle instance = 0; - OpenXRVulkanHandle physical_device = 0; - OpenXRVulkanHandle device = 0; - OpenXRVulkanHandle queue = 0; - uint32_t queue_family_index = 0; - uint32_t queue_index = 0; - - // A VK_MAKE_API_VERSION encoded value for the Vulkan instance/device. - uint32_t api_version = 0; - - OpenXRVulkanQueueLockCallback lock_queue = nullptr; - OpenXRVulkanQueueUnlockCallback unlock_queue = nullptr; - void* queue_userdata = nullptr; -}; - -enum class OpenXRVulkanCapability { - Available, - VulkanHeadersUnavailable, - DawnNativeHandlesUnavailable, - InvalidNativeContext, - MissingEnable2Extension, - RuntimeRejectedGraphicsDevice, - RuntimeApiVersionMismatch, -}; - -struct OpenXRVulkanCapabilityInfo { - OpenXRVulkanCapability capability = OpenXRVulkanCapability::Available; - std::string reason; - - explicit operator bool() const { - return capability == OpenXRVulkanCapability::Available; - } -}; - -struct OpenXRVulkanBackendConfig { - // Rendering directly into an XR image needs COLOR_ATTACHMENT. Aurora's - // intended zero-copy bridge renders to an imported/application-owned image - // and performs a GPU copy, which additionally requires TRANSFER_DST. - bool require_transfer_destination = true; - XrDuration image_wait_timeout = XR_INFINITE_DURATION; - XrCompositionLayerFlags projection_layer_flags = 0; -}; - -struct OpenXRVulkanEyeImage { - uint32_t eye = 0; - uint32_t image_index = 0; - uint32_t width = 0; - uint32_t height = 0; - int64_t format = 0; - OpenXRVulkanHandle image = 0; -}; - -// Owns the Vulkan-bound session and its per-eye OpenXR swapchains. It does not -// record Vulkan commands: Aurora's backend bridge is responsible for rendering -// or copying into the borrowed VkImage on the supplied Dawn queue, submitting -// that GPU work, then calling ReleaseEyeImage(). No CPU/readback fallback exists. -// -// All methods must be called on the XR owner thread. Shutdown() must run before -// the native Vulkan device is destroyed. -class OpenXRVulkanBackend final { -public: - explicit OpenXRVulkanBackend(OpenXRLogCallback logger = {}); - ~OpenXRVulkanBackend(); - - OpenXRVulkanBackend(const OpenXRVulkanBackend&) = delete; - OpenXRVulkanBackend& operator=(const OpenXRVulkanBackend&) = delete; - OpenXRVulkanBackend(OpenXRVulkanBackend&&) = delete; - OpenXRVulkanBackend& operator=(OpenXRVulkanBackend&&) = delete; - - // Reports whether this build's Aurora/Dawn provider has the native Vulkan - // handle shim required by the backend. The pinned prebuilt Dawn package does - // not expose VkPhysicalDevice, VkDevice, VkQueue, or the queue-family index, - // so this intentionally reports DawnNativeHandlesUnavailable until such a - // shim is compiled in. - static OpenXRVulkanCapabilityInfo DawnInteropCapability(); - - // runtime must already be initialized with XR_KHR_vulkan_enable2 in its - // required extension list and must not yet own a session. - bool Initialize(OpenXRRuntime& runtime, - const OpenXRVulkanNativeContext& native_context, - const OpenXRVulkanBackendConfig& config = {}); - void Shutdown(); - - bool AcquireEyeImage(uint32_t eye, OpenXRVulkanEyeImage& image); - bool ReleaseEyeImage(uint32_t eye); - - // Ends a begun OpenXR frame with a stereo projection layer. The pose/FOV - // come from the same OpenXRFrame token used for rendering. When - // should_render is false or views are invalid, the frame is ended without a - // layer as required by the OpenXR frame protocol. - bool SubmitProjection(const OpenXRFrame& frame); - - bool IsInitialized() const { return m_runtime != nullptr; } - int64_t SwapchainFormat() const { return m_swapchain_format; } - const std::array& - ViewConfiguration() const; - const OpenXRError& LastError() const { return m_last_error; } - -private: - struct EyeSwapchain { - XrSwapchain handle = XR_NULL_HANDLE; - uint32_t width = 0; - uint32_t height = 0; - std::vector images; - uint32_t acquired_index = 0; - bool acquired = false; - bool waited = false; - }; - - bool ValidateNativeContext(const OpenXRVulkanNativeContext& native_context); - bool ValidateRuntimeDevice(); - bool CreateSwapchains(); - bool CreateEyeSwapchain(uint32_t eye); - bool SelectSwapchainFormat(); - void DestroySwapchains(); - - bool LockQueue(); - void UnlockQueue(); - bool Check(XrResult result, std::string_view operation); - bool Fail(XrResult result, std::string_view operation, std::string_view detail); - void Log(OpenXRLogLevel level, std::string_view message) const noexcept; - std::string ResultString(XrResult result) const; - - OpenXRRuntime* m_runtime = nullptr; - OpenXRLogCallback m_logger; - OpenXRError m_last_error; - OpenXRVulkanNativeContext m_native_context{}; - OpenXRVulkanBackendConfig m_config{}; - std::array m_eye_swapchains{}; - int64_t m_swapchain_format = 0; - bool m_owns_session = false; -}; - -} // namespace mkw::vr - -#endif // defined(MKW_ENABLE_OPENXR) diff --git a/runtime/src/vr/openxr_vulkan_backend.cpp b/runtime/src/vr/openxr_vulkan_backend.cpp deleted file mode 100644 index a652bce..0000000 --- a/runtime/src/vr/openxr_vulkan_backend.cpp +++ /dev/null @@ -1,630 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#if defined(MKW_ENABLE_OPENXR) - -#include "vr/openxr_vulkan_backend.h" - -#include -#include -#include -#include -#include -#include - -#if __has_include() -#define MKW_OPENXR_VULKAN_HEADERS_AVAILABLE 1 -#include -#define XR_USE_GRAPHICS_API_VULKAN -#include -#else -#define MKW_OPENXR_VULKAN_HEADERS_AVAILABLE 0 -#endif - -namespace mkw::vr { -namespace { - -constexpr std::array - kEmptyViewConfiguration{}; -constexpr std::string_view kVulkanEnable2Extension = - "XR_KHR_vulkan_enable2"; - -bool HasExtension(const OpenXRRuntime& runtime, std::string_view extension) { - const auto& extensions = runtime.EnabledExtensions(); - return std::find(extensions.begin(), extensions.end(), extension) != - extensions.end(); -} - -#if MKW_OPENXR_VULKAN_HEADERS_AVAILABLE -template -VulkanHandle FromOpaqueHandle(OpenXRVulkanHandle handle) { - if constexpr (std::is_pointer_v) { - return reinterpret_cast(static_cast(handle)); - } else { - return static_cast(handle); - } -} - -template -OpenXRVulkanHandle ToOpaqueHandle(VulkanHandle handle) { - if constexpr (std::is_pointer_v) { - return static_cast(reinterpret_cast(handle)); - } else { - return static_cast(handle); - } -} - -XrVersion VulkanApiVersionAsXrVersion(uint32_t version) { - return XR_MAKE_VERSION(VK_API_VERSION_MAJOR(version), - VK_API_VERSION_MINOR(version), - VK_API_VERSION_PATCH(version)); -} -#endif - -} // namespace - -OpenXRVulkanBackend::OpenXRVulkanBackend(OpenXRLogCallback logger) - : m_logger(std::move(logger)) {} - -OpenXRVulkanBackend::~OpenXRVulkanBackend() { - Shutdown(); -} - -OpenXRVulkanCapabilityInfo OpenXRVulkanBackend::DawnInteropCapability() { -#if !MKW_OPENXR_VULKAN_HEADERS_AVAILABLE - return {OpenXRVulkanCapability::VulkanHeadersUnavailable, - "Vulkan headers were not available when the runtime was built"}; -#elif !defined(MKW_AURORA_DAWN_VULKAN_NATIVE_HANDLES) - return { - OpenXRVulkanCapability::DawnNativeHandlesUnavailable, - "the pinned Dawn package exposes VkInstance only; same-device OpenXR " - "also requires Aurora to expose Dawn's VkPhysicalDevice, VkDevice, " - "VkQueue, queue-family index, and queue synchronization", - }; -#else - return {OpenXRVulkanCapability::Available, {}}; -#endif -} - -bool OpenXRVulkanBackend::Initialize( - OpenXRRuntime& runtime, - const OpenXRVulkanNativeContext& native_context, - const OpenXRVulkanBackendConfig& config) { - m_last_error = {}; - if (IsInitialized()) { - return Fail(XR_ERROR_CALL_ORDER_INVALID, "Initialize", - "the Vulkan OpenXR backend is already initialized"); - } - if (!runtime.IsInitialized()) { - return Fail(XR_ERROR_HANDLE_INVALID, "Initialize", - "OpenXRRuntime must be initialized first"); - } - if (runtime.HasSession()) { - return Fail(XR_ERROR_CALL_ORDER_INVALID, "Initialize", - "OpenXRRuntime already owns a graphics session"); - } - if (!HasExtension(runtime, kVulkanEnable2Extension)) { - return Fail(XR_ERROR_EXTENSION_NOT_PRESENT, "Initialize", - "XR_KHR_vulkan_enable2 was not enabled on the instance"); - } -#if !MKW_OPENXR_VULKAN_HEADERS_AVAILABLE - (void)native_context; - (void)config; - return Fail(XR_ERROR_GRAPHICS_DEVICE_INVALID, "Initialize", - "this build has no Vulkan headers"); -#else - m_runtime = &runtime; - m_config = config; - m_native_context = native_context; - - if (!ValidateNativeContext(native_context) || !ValidateRuntimeDevice()) { - m_runtime = nullptr; - return false; - } - - const XrGraphicsBindingVulkan2KHR graphics_binding{ - XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR, - nullptr, - FromOpaqueHandle(native_context.instance), - FromOpaqueHandle(native_context.physical_device), - FromOpaqueHandle(native_context.device), - native_context.queue_family_index, - native_context.queue_index, - }; - if (!runtime.CreateSession(&graphics_binding)) { - m_last_error = runtime.LastError(); - m_runtime = nullptr; - return false; - } - m_owns_session = true; - - if (!SelectSwapchainFormat() || !CreateSwapchains()) { - DestroySwapchains(); - runtime.DestroySession(); - m_owns_session = false; - m_runtime = nullptr; - return false; - } - - std::ostringstream message; - message << "OpenXR Vulkan swapchains ready: format " << m_swapchain_format - << ", left " << m_eye_swapchains[0].width << 'x' - << m_eye_swapchains[0].height << ", right " - << m_eye_swapchains[1].width << 'x' - << m_eye_swapchains[1].height; - Log(OpenXRLogLevel::Info, message.str()); - return true; -#endif -} - -bool OpenXRVulkanBackend::ValidateNativeContext( - const OpenXRVulkanNativeContext& native_context) { - if (native_context.version != kOpenXRVulkanNativeContextVersion || - native_context.struct_size < sizeof(OpenXRVulkanNativeContext)) { - return Fail(XR_ERROR_VALIDATION_FAILURE, "ValidateNativeContext", - "unsupported native context ABI version or size"); - } - if (native_context.instance == 0 || native_context.physical_device == 0 || - native_context.device == 0 || native_context.queue == 0 || - native_context.api_version == 0) { - return Fail(XR_ERROR_GRAPHICS_DEVICE_INVALID, "ValidateNativeContext", - "instance, physical device, device, queue, and API version " - "must all be supplied"); - } - constexpr uint32_t required_flags = - OpenXRVulkanNativeContextDawnOwnedBit | - OpenXRVulkanNativeContextQueueVerifiedBit; - if ((native_context.flags & required_flags) != required_flags) { - return Fail(XR_ERROR_GRAPHICS_DEVICE_INVALID, "ValidateNativeContext", - "native handles must be the verified Dawn device and queue"); - } - if (native_context.lock_queue == nullptr || - native_context.unlock_queue == nullptr) { - return Fail(XR_ERROR_VALIDATION_FAILURE, "ValidateNativeContext", - "paired Dawn graphics-queue lock callbacks are required"); - } - return true; -} - -bool OpenXRVulkanBackend::ValidateRuntimeDevice() { -#if !MKW_OPENXR_VULKAN_HEADERS_AVAILABLE - return false; -#else - PFN_xrGetVulkanGraphicsRequirements2KHR get_requirements = nullptr; - PFN_xrGetVulkanGraphicsDevice2KHR get_graphics_device = nullptr; - if (!m_runtime->LoadFunction("xrGetVulkanGraphicsRequirements2KHR", - &get_requirements) || - !m_runtime->LoadFunction("xrGetVulkanGraphicsDevice2KHR", - &get_graphics_device)) { - m_last_error = m_runtime->LastError(); - return false; - } - - XrGraphicsRequirementsVulkan2KHR requirements{ - XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN2_KHR}; - if (!Check(get_requirements(m_runtime->Instance(), m_runtime->SystemId(), - &requirements), - "xrGetVulkanGraphicsRequirements2KHR")) { - return false; - } - - const XrVersion supplied_version = - VulkanApiVersionAsXrVersion(m_native_context.api_version); - if (supplied_version < requirements.minApiVersionSupported || - supplied_version > requirements.maxApiVersionSupported) { - std::ostringstream detail; - detail << "Dawn Vulkan version " - << VK_API_VERSION_MAJOR(m_native_context.api_version) << '.' - << VK_API_VERSION_MINOR(m_native_context.api_version) << '.' - << VK_API_VERSION_PATCH(m_native_context.api_version) - << " is outside the OpenXR runtime range " - << XR_VERSION_MAJOR(requirements.minApiVersionSupported) << '.' - << XR_VERSION_MINOR(requirements.minApiVersionSupported) << '-' - << XR_VERSION_MAJOR(requirements.maxApiVersionSupported) << '.' - << XR_VERSION_MINOR(requirements.maxApiVersionSupported); - return Fail(XR_ERROR_GRAPHICS_DEVICE_INVALID, - "xrGetVulkanGraphicsRequirements2KHR", detail.str()); - } - - XrVulkanGraphicsDeviceGetInfoKHR device_get_info{ - XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR}; - device_get_info.systemId = m_runtime->SystemId(); - device_get_info.vulkanInstance = - FromOpaqueHandle(m_native_context.instance); - - VkPhysicalDevice runtime_physical_device = VK_NULL_HANDLE; - if (!Check(get_graphics_device(m_runtime->Instance(), &device_get_info, - &runtime_physical_device), - "xrGetVulkanGraphicsDevice2KHR")) { - return false; - } - if (ToOpaqueHandle(runtime_physical_device) != - m_native_context.physical_device) { - return Fail(XR_ERROR_GRAPHICS_DEVICE_INVALID, - "xrGetVulkanGraphicsDevice2KHR", - "OpenXR requires a different physical device than Dawn selected"); - } - return true; -#endif -} - -bool OpenXRVulkanBackend::SelectSwapchainFormat() { -#if !MKW_OPENXR_VULKAN_HEADERS_AVAILABLE - return false; -#else - const auto& formats = m_runtime->SwapchainFormats(); - if (formats.empty()) { - return Fail(XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED, - "xrEnumerateSwapchainFormats", - "the runtime reported no Vulkan swapchain formats"); - } - - constexpr std::array preferred_formats{ - VK_FORMAT_R8G8B8A8_SRGB, - VK_FORMAT_B8G8R8A8_SRGB, - VK_FORMAT_R8G8B8A8_UNORM, - VK_FORMAT_B8G8R8A8_UNORM, - VK_FORMAT_A2B10G10R10_UNORM_PACK32, - VK_FORMAT_R16G16B16A16_SFLOAT, - }; - for (VkFormat preferred : preferred_formats) { - const int64_t candidate = static_cast(preferred); - if (std::find(formats.begin(), formats.end(), candidate) != formats.end()) { - m_swapchain_format = candidate; - return true; - } - } - - // A caller-provided GPU bridge can still support a format outside our - // preference list, so keep the runtime's first valid choice visible. - m_swapchain_format = formats.front(); - Log(OpenXRLogLevel::Warning, - "OpenXR Vulkan runtime offered no preferred RGBA/BGRA format; using " - "its first advertised format"); - return true; -#endif -} - -bool OpenXRVulkanBackend::CreateSwapchains() { - for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) { - if (!CreateEyeSwapchain(eye)) { - return false; - } - } - return true; -} - -bool OpenXRVulkanBackend::CreateEyeSwapchain(uint32_t eye) { -#if !MKW_OPENXR_VULKAN_HEADERS_AVAILABLE - (void)eye; - return false; -#else - const auto& view_configuration = m_runtime->ViewConfiguration(); - EyeSwapchain& swapchain = m_eye_swapchains[eye]; - swapchain.width = view_configuration[eye].render_width; - swapchain.height = view_configuration[eye].render_height; - - XrSwapchainUsageFlags usage = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT; - if (m_config.require_transfer_destination) { - usage |= XR_SWAPCHAIN_USAGE_TRANSFER_DST_BIT; - } - const XrSwapchainCreateInfo create_info{ - XR_TYPE_SWAPCHAIN_CREATE_INFO, - nullptr, - 0, - usage, - m_swapchain_format, - 1, - swapchain.width, - swapchain.height, - 1, - 1, - 1, - }; - if (!Check(xrCreateSwapchain(m_runtime->Session(), &create_info, - &swapchain.handle), - eye == 0 ? "xrCreateSwapchain(left)" - : "xrCreateSwapchain(right)")) { - return false; - } - - uint32_t image_count = 0; - if (!Check(xrEnumerateSwapchainImages(swapchain.handle, 0, &image_count, - nullptr), - "xrEnumerateSwapchainImages(count)")) { - return false; - } - if (image_count == 0) { - return Fail(XR_ERROR_RUNTIME_FAILURE, "xrEnumerateSwapchainImages", - "the runtime returned an empty Vulkan swapchain"); - } - - std::vector images( - image_count, - XrSwapchainImageVulkan2KHR{XR_TYPE_SWAPCHAIN_IMAGE_VULKAN2_KHR}); - if (!Check(xrEnumerateSwapchainImages( - swapchain.handle, image_count, &image_count, - reinterpret_cast(images.data())), - "xrEnumerateSwapchainImages")) { - return false; - } - - swapchain.images.reserve(image_count); - for (const XrSwapchainImageVulkan2KHR& image : images) { - swapchain.images.push_back(ToOpaqueHandle(image.image)); - } - return true; -#endif -} - -bool OpenXRVulkanBackend::AcquireEyeImage(uint32_t eye, - OpenXRVulkanEyeImage& image) { - if (!IsInitialized() || eye >= kOpenXREyeCount) { - return Fail(XR_ERROR_VALIDATION_FAILURE, "AcquireEyeImage", - "backend is not initialized or eye index is invalid"); - } - EyeSwapchain& swapchain = m_eye_swapchains[eye]; - if (swapchain.acquired && swapchain.waited) { - return Fail(XR_ERROR_CALL_ORDER_INVALID, "AcquireEyeImage", - "this eye already has an acquired image"); - } - - if (!swapchain.acquired) { - XrSwapchainImageAcquireInfo acquire_info{ - XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO}; - if (!LockQueue()) { - return false; - } - const XrResult acquire_result = - xrAcquireSwapchainImage(swapchain.handle, &acquire_info, - &swapchain.acquired_index); - UnlockQueue(); - if (!Check(acquire_result, "xrAcquireSwapchainImage")) { - return false; - } - swapchain.acquired = true; - } - - const XrSwapchainImageWaitInfo wait_info{ - XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, nullptr, - m_config.image_wait_timeout}; - const XrResult wait_result = xrWaitSwapchainImage(swapchain.handle, &wait_info); - if (wait_result == XR_TIMEOUT_EXPIRED) { - // XR_TIMEOUT_EXPIRED is a positive XrResult, but the image has not - // completed its wait and therefore cannot be used or released yet. - return Fail(wait_result, "xrWaitSwapchainImage", - "the image wait timed out; retry acquisition to resume the wait"); - } - if (!Check(wait_result, "xrWaitSwapchainImage")) { - // A timeout leaves the image acquired but not waited. The caller may - // retry AcquireEyeImage(), which resumes at xrWaitSwapchainImage rather - // than illegally acquiring a second image or releasing an unwaited one. - return false; - } - swapchain.waited = true; - if (swapchain.acquired_index >= swapchain.images.size()) { - ReleaseEyeImage(eye); - return Fail(XR_ERROR_RUNTIME_FAILURE, "xrAcquireSwapchainImage", - "the runtime returned an out-of-range image index"); - } - - image = { - eye, - swapchain.acquired_index, - swapchain.width, - swapchain.height, - m_swapchain_format, - swapchain.images[swapchain.acquired_index], - }; - return true; -} - -bool OpenXRVulkanBackend::ReleaseEyeImage(uint32_t eye) { - if (!IsInitialized() || eye >= kOpenXREyeCount) { - return Fail(XR_ERROR_VALIDATION_FAILURE, "ReleaseEyeImage", - "backend is not initialized or eye index is invalid"); - } - EyeSwapchain& swapchain = m_eye_swapchains[eye]; - if (!swapchain.acquired) { - return true; - } - if (!swapchain.waited) { - return Fail(XR_ERROR_CALL_ORDER_INVALID, "ReleaseEyeImage", - "xrWaitSwapchainImage has not completed for this image"); - } - - XrSwapchainImageReleaseInfo release_info{ - XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO}; - if (!LockQueue()) { - return false; - } - const XrResult release_result = - xrReleaseSwapchainImage(swapchain.handle, &release_info); - UnlockQueue(); - if (!Check(release_result, "xrReleaseSwapchainImage")) { - return false; - } - swapchain.acquired = false; - swapchain.waited = false; - return true; -} - -bool OpenXRVulkanBackend::SubmitProjection(const OpenXRFrame& frame) { - if (!IsInitialized()) { - return Fail(XR_ERROR_HANDLE_INVALID, "SubmitProjection", - "backend is not initialized"); - } - for (const EyeSwapchain& swapchain : m_eye_swapchains) { - if (swapchain.acquired) { - return Fail(XR_ERROR_CALL_ORDER_INVALID, "SubmitProjection", - "all eye images must be released before xrEndFrame"); - } - } - const bool position_valid = - (frame.view_state_flags & XR_VIEW_STATE_POSITION_VALID_BIT) != 0; - if (!frame.should_render || !frame.views_valid || !position_valid) { - return m_runtime->EndFrameWithoutLayers(frame); - } - - std::array views{}; - for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) { - views[eye] = {XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW}; - views[eye].pose = frame.views[eye].pose; - views[eye].fov = frame.views[eye].fov; - views[eye].subImage.swapchain = m_eye_swapchains[eye].handle; - views[eye].subImage.imageRect = { - {0, 0}, - {static_cast(m_eye_swapchains[eye].width), - static_cast(m_eye_swapchains[eye].height)}, - }; - views[eye].subImage.imageArrayIndex = 0; - } - - const XrCompositionLayerProjection layer{ - XR_TYPE_COMPOSITION_LAYER_PROJECTION, - nullptr, - m_config.projection_layer_flags, - m_runtime->AppSpace(), - kOpenXREyeCount, - views.data(), - }; - const XrCompositionLayerBaseHeader* layers[] = { - reinterpret_cast(&layer), - }; - if (!m_runtime->EndFrame(frame, layers, 1)) { - m_last_error = m_runtime->LastError(); - return false; - } - return true; -} - -void OpenXRVulkanBackend::DestroySwapchains() { - for (EyeSwapchain& swapchain : m_eye_swapchains) { - bool may_destroy_swapchain = true; - if (swapchain.acquired && swapchain.waited && - swapchain.handle != XR_NULL_HANDLE) { - XrSwapchainImageReleaseInfo release_info{ - XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO}; - if (LockQueue()) { - const XrResult result = - xrReleaseSwapchainImage(swapchain.handle, &release_info); - UnlockQueue(); - m_runtime->ObserveResult(result); - if (XR_FAILED(result)) { - may_destroy_swapchain = false; - Log(OpenXRLogLevel::Warning, - "xrReleaseSwapchainImage failed during Vulkan shutdown"); - } else { - swapchain.acquired = false; - swapchain.waited = false; - } - } else { - may_destroy_swapchain = false; - } - } else if (swapchain.acquired) { - // OpenXR does not permit releasing before a successful wait. Leave - // this child to xrDestroySession instead of violating the image - // call order with a best-effort release or destroy. - may_destroy_swapchain = false; - Log(OpenXRLogLevel::Warning, - "Vulkan swapchain still has an unwaited image; deferring its " - "destruction to xrDestroySession"); - } - if (swapchain.handle != XR_NULL_HANDLE && m_runtime != nullptr && - m_runtime->HasSession() && may_destroy_swapchain) { - const XrResult result = xrDestroySwapchain(swapchain.handle); - m_runtime->ObserveResult(result); - if (XR_FAILED(result)) { - Log(OpenXRLogLevel::Warning, - "xrDestroySwapchain failed during Vulkan shutdown"); - } - } - swapchain = {}; - } - m_swapchain_format = 0; -} - -void OpenXRVulkanBackend::Shutdown() { - if (m_runtime == nullptr) { - return; - } - DestroySwapchains(); - if (m_owns_session && m_runtime->HasSession()) { - m_runtime->DestroySession(); - } - m_owns_session = false; - m_runtime = nullptr; - m_native_context = {}; -} - -const std::array& -OpenXRVulkanBackend::ViewConfiguration() const { - return m_runtime != nullptr ? m_runtime->ViewConfiguration() - : kEmptyViewConfiguration; -} - -bool OpenXRVulkanBackend::LockQueue() { - if (m_native_context.lock_queue == nullptr) { - return Fail(XR_ERROR_VALIDATION_FAILURE, "LockQueue", - "no Vulkan queue lock callback is installed"); - } - if (!m_native_context.lock_queue(m_native_context.queue_userdata)) { - return Fail(XR_ERROR_RUNTIME_FAILURE, "LockQueue", - "Aurora refused the Vulkan graphics queue lock"); - } - return true; -} - -void OpenXRVulkanBackend::UnlockQueue() { - m_native_context.unlock_queue(m_native_context.queue_userdata); -} - -bool OpenXRVulkanBackend::Check(XrResult result, std::string_view operation) { - if (m_runtime != nullptr) { - m_runtime->ObserveResult(result); - } - if (XR_SUCCEEDED(result)) { - return true; - } - return Fail(result, operation, {}); -} - -bool OpenXRVulkanBackend::Fail(XrResult result, std::string_view operation, - std::string_view detail) { - m_last_error.result = result; - m_last_error.operation.assign(operation); - m_last_error.message = ResultString(result); - if (!detail.empty()) { - m_last_error.message.append(": "); - m_last_error.message.append(detail); - } - std::string message = m_last_error.operation; - message.append(" failed: "); - message.append(m_last_error.message); - Log(OpenXRLogLevel::Error, message); - return false; -} - -void OpenXRVulkanBackend::Log(OpenXRLogLevel level, - std::string_view message) const noexcept { - if (!m_logger) { - return; - } - try { - m_logger(level, message); - } catch (...) { - // Diagnostics must not escape a graphics/session teardown path. - } -} - -std::string OpenXRVulkanBackend::ResultString(XrResult result) const { - char buffer[XR_MAX_RESULT_STRING_SIZE]{}; - if (m_runtime != nullptr && m_runtime->IsInitialized() && - XR_SUCCEEDED(xrResultToString(m_runtime->Instance(), result, buffer))) { - return buffer; - } - return std::to_string(static_cast(result)); -} - -} // namespace mkw::vr - -#endif // defined(MKW_ENABLE_OPENXR)