Files
mitch030504--Wiicompiled_VR…/runtime/include/vr/openxr_vulkan.h
T
iChris4andClaude Opus 5 54c07c982b Show the headset settings panel as its own OpenXR quad layer
The panel was drawn into the eye images, so below a 1.00x OpenXR
resolution its 1440x1080 canvas was minified into a small eye region and
the text became hard to read. It is now submitted as a quad layer of its
own over the scene's projection or menu quad, which the compositor samples
directly at any render scale.

Each backend (D3D12, Windows Vulkan, Quest) makes the panel's swapchain
pair (plus two shared buffers on the Quest) the first time the panel
opens. While it is open, the frame hands Aurora one more stereo target
after the eyes, which the bridge fills with the panel texture or a
transparent image. The panel image follows the eyes' displayed/retained
pairing, so a cancelled frame never shows an unwritten panel. The quad
hangs where the pointer's hits are tested. Aurora leaves the panel out of
the eyes in layer mode, and a backend that cannot make the layer falls
back to drawing it into the eyes.

On the Quest, debug.wiicompiled.panel_layer 0 selects the old path at
run time. Measured there at a race start (render_scale 0.8), the layer
costs nothing while the panel is closed; while it is open, app GPU time is
10.5 ms against 9.7 ms drawn into the eyes, with unchanged frame rates.

The replay tests cover lazy creation, cancelled frames, closed and
unplaced panels, and render-first pacing on D3D12 and Windows Vulkan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 16:26:28 +02:00

96 lines
4.4 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#if defined(MKW_ENABLE_OPENXR) && defined(__ANDROID__)
#include "vr/openxr_backend.h"
#include "vr/openxr_runtime.h"
#include <cstdint>
#include <memory>
#include <string>
namespace mkw::vr {
struct OpenXRVulkanGraphicsRequirements {
// XR_MAKE_VERSION-encoded bounds reported by xrGetVulkanGraphicsRequirements2KHR.
XrVersion min_api_version = 0;
XrVersion max_api_version = 0;
// Which binding extension was negotiated: XR_KHR_vulkan_enable2 when the
// runtime offers it, otherwise the original XR_KHR_vulkan_enable.
bool uses_enable2 = false;
};
// Two-device Dawn/OpenXR Vulkan backend for Android (Meta Quest).
//
// Method surface and threading rules are identical to OpenXRD3D12Backend so the
// pacing thread in openxr_integration.cpp is shared. The difference is behind
// BindAurora: instead of binding Dawn's device to the session, this backend
// creates its own VkInstance/VkDevice through the OpenXR runtime, owns a small
// ring of AHardwareBuffers per eye, and hands them to Aurora's Vulkan stereo
// bridge (aurora/vulkan_interop.h). When Aurora reports a completed copy, the
// backend copies the buffer into the acquired XrSwapchain image on the
// session's queue, so the compositor sees ordinary same-queue rendering.
//
// QueryGraphicsRequirements() runs after OpenXRRuntime::Initialize() and before
// aurora_initialize(); BindAurora() runs afterwards, while Aurora's frame worker
// is idle. Everything from BeginFrame() through FinishFrame() belongs to one XR
// pacing thread; the Aurora callback only records a small copy on this
// backend's private queue and publishes a token.
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;
bool QueryGraphicsRequirements(OpenXRRuntime& runtime);
bool BindAurora(OpenXRRuntime& runtime);
OpenXRBeginStatus BeginFrame(const OpenXRPresentation& presentation, OpenXRBackendFrame& frame);
OpenXRSubmissionStatus WaitForSubmission(const OpenXRBackendFrame& frame,
uint32_t timeout_ms = UINT32_MAX);
bool TryCancelPendingFrame(OpenXRBackendFrame& frame);
bool RepeatFrame(const OpenXRBackendFrame& frame);
bool FinishFrame(OpenXRBackendFrame& frame, bool submit_layer);
// Render-first pacing, used while VR interpolation is off. A packet is prepared with the
// views located for an estimated display time and handed to Aurora without a compositor
// frame open; once Aurora has rendered the eyes into the shared buffers, the compositor frame
// is begun, the eyes are copied into its swapchain images and it is ended at once. A headset
// frame therefore never waits for a game frame: it stays open for the copy alone.
OpenXRBeginStatus PreparePacket(const OpenXRPresentation& presentation, OpenXRBackendFrame& packet);
bool TryCancelPendingPacket(OpenXRBackendFrame& packet);
OpenXRBeginStatus BeginFrameForPacket(const OpenXRBackendFrame& packet, OpenXRBackendFrame& frame);
OpenXRSubmissionStatus CopyRenderedEyes(const OpenXRBackendFrame& frame);
// One compositor cycle that resubmits the retained layer (or nothing), with no frame left
// active: keeps the runtime fed while the eyes are still being rendered and learns the
// display timing the next packet is located for.
OpenXRBeginStatus KeepAliveCycle();
// Drains this backend's own queue before tearing down. Returns false only
// when the private device could not be waited on, in which case the caller
// retains the backend and runtime for the process lifetime.
bool Shutdown();
bool IsBound() const;
// False once the settings panel's own layer could not be set up; the panel
// is then drawn into the eyes again.
bool PanelLayerAvailable() const;
const OpenXRVulkanGraphicsRequirements& GraphicsRequirements() const;
int64_t SwapchainFormat() const;
const std::string& LastError() const;
private:
class Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace mkw::vr
#endif // defined(MKW_ENABLE_OPENXR) && defined(__ANDROID__)