runtime: integrate OpenXR presentation for Mario Kart Wii

This commit is contained in:
iChris4 committed 2026-09-03 04:00:24 +02:00
1 parent d8788de919
commit 2156617cd5
17 files changed
+3052 -42

No files matched your search

+731
View File
@@ -0,0 +1,731 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#if defined(_WIN32) && !defined(NOMINMAX)
#define NOMINMAX
#endif
#include "vr/openxr_integration.h"
#include "runtime_config.h"
#include "runtime_log.h"
#include "vr/mkw_vr_policy.h"
#include "vr/mkw_vr_instrumentation.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <cmath>
#include <condition_variable>
#include <cstdint>
#include <cstring>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#if defined(MKW_ENABLE_OPENXR)
#include "vr/openxr_runtime.h"
#if defined(_WIN32)
#include "vr/openxr_d3d12.h"
#else
#include "vr/openxr_vulkan_backend.h"
#endif
#endif
namespace mkw::vr {
namespace {
void ConfigurePolicy(bool enabled) noexcept {
MkwVRPolicyReset();
MkwVRPolicyConfig config{};
config.enabled = enabled;
config.immersive_races = true;
config.world_units_per_meter = RuntimeConfigFile::VrWorldUnitsPerMeter(500.0f);
config.hud_distance_meters = RuntimeConfigFile::VrHudDistanceMeters(2.0f);
MkwVRPolicyConfigure(config);
MkwVRInstrumentationInitialize();
}
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
struct Quaternion {
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
float w = 1.0f;
};
struct Pose {
Quaternion orientation{};
std::array<float, 3> position{};
};
Quaternion Normalize(Quaternion value) noexcept {
const float length_squared = value.x * value.x + value.y * value.y +
value.z * value.z + value.w * value.w;
if (!(length_squared > 1.0e-12f)) {
return {};
}
const float inverse_length = 1.0f / std::sqrt(length_squared);
value.x *= inverse_length;
value.y *= inverse_length;
value.z *= inverse_length;
value.w *= inverse_length;
return value;
}
Quaternion Conjugate(Quaternion value) noexcept {
return {-value.x, -value.y, -value.z, value.w};
}
Quaternion Multiply(const Quaternion& left, const Quaternion& right) noexcept {
return Normalize({
left.w * right.x + left.x * right.w + left.y * right.z - left.z * right.y,
left.w * right.y - left.x * right.z + left.y * right.w + left.z * right.x,
left.w * right.z + left.x * right.y - left.y * right.x + left.z * right.w,
left.w * right.w - left.x * right.x - left.y * right.y - left.z * right.z,
});
}
std::array<float, 3> Rotate(const Quaternion& q, const std::array<float, 3>& value) noexcept {
// Expanded q * [v,0] * conjugate(q), avoiding two temporary normalizations.
const float tx = 2.0f * (q.y * value[2] - q.z * value[1]);
const float ty = 2.0f * (q.z * value[0] - q.x * value[2]);
const float tz = 2.0f * (q.x * value[1] - q.y * value[0]);
return {
value[0] + q.w * tx + (q.y * tz - q.z * ty),
value[1] + q.w * ty + (q.z * tx - q.x * tz),
value[2] + q.w * tz + (q.x * ty - q.y * tx),
};
}
void RotationMatrix(const Quaternion& value, float matrix[9]) noexcept {
const Quaternion q = Normalize(value);
const float xx = q.x * q.x;
const float yy = q.y * q.y;
const float zz = q.z * q.z;
const float xy = q.x * q.y;
const float xz = q.x * q.z;
const float yz = q.y * q.z;
const float wx = q.w * q.x;
const float wy = q.w * q.y;
const float wz = q.w * q.z;
matrix[0] = 1.0f - 2.0f * (yy + zz);
matrix[1] = 2.0f * (xy - wz);
matrix[2] = 2.0f * (xz + wy);
matrix[3] = 2.0f * (xy + wz);
matrix[4] = 1.0f - 2.0f * (xx + zz);
matrix[5] = 2.0f * (yz - wx);
matrix[6] = 2.0f * (xz - wy);
matrix[7] = 2.0f * (yz + wx);
matrix[8] = 1.0f - 2.0f * (xx + yy);
}
Pose CenterPose(const OpenXRFrame& frame, bool position_valid) noexcept {
const auto& left = frame.views[0].pose;
const auto& right = frame.views[1].pose;
Quaternion l{left.orientation.x, left.orientation.y, left.orientation.z,
left.orientation.w};
Quaternion r{right.orientation.x, right.orientation.y, right.orientation.z,
right.orientation.w};
l = Normalize(l);
r = Normalize(r);
const float dot = l.x * r.x + l.y * r.y + l.z * r.z + l.w * r.w;
if (dot < 0.0f) {
r = {-r.x, -r.y, -r.z, -r.w};
}
Pose center;
center.orientation = Normalize({l.x + r.x, l.y + r.y, l.z + r.z, l.w + r.w});
if (position_valid) {
center.position = {
(left.position.x + right.position.x) * 0.5f,
(left.position.y + right.position.y) * 0.5f,
(left.position.z + right.position.z) * 0.5f,
};
}
return center;
}
void IdentityEye(AuroraStereoEye& eye) noexcept {
std::fill(std::begin(eye.projection), std::end(eye.projection), 0.0f);
eye.projection[0] = 1.0f;
eye.projection[5] = 1.0f;
eye.projection[10] = 1.0f;
eye.projection[15] = 1.0f;
std::fill(std::begin(eye.viewFromCenter), std::end(eye.viewFromCenter), 0.0f);
eye.viewFromCenter[0] = 1.0f;
eye.viewFromCenter[5] = 1.0f;
eye.viewFromCenter[10] = 1.0f;
}
void ProjectionFromFov(const XrFovf& fov, float output[16]) noexcept {
const float left = std::tan(fov.angleLeft);
const float right = std::tan(fov.angleRight);
const float down = std::tan(fov.angleDown);
const float up = std::tan(fov.angleUp);
const float inverse_width = 1.0f / (right - left);
const float inverse_height = 1.0f / (up - down);
std::fill(output, output + 16, 0.0f);
output[0] = 2.0f * inverse_width;
output[2] = (right + left) * inverse_width;
output[5] = 2.0f * inverse_height;
output[6] = (up + down) * inverse_height;
}
void ViewFromBase(const XrPosef& eye_pose, const Pose& base, bool position_valid,
float units_per_meter, float output[12]) noexcept {
const Quaternion eye = Normalize({eye_pose.orientation.x, eye_pose.orientation.y,
eye_pose.orientation.z, eye_pose.orientation.w});
const Quaternion inverse_eye = Conjugate(eye);
const Quaternion delta = Multiply(inverse_eye, base.orientation);
float rotation[9];
RotationMatrix(delta, rotation);
std::array<float, 3> translation{};
if (position_valid) {
const std::array<float, 3> base_to_eye{
base.position[0] - eye_pose.position.x,
base.position[1] - eye_pose.position.y,
base.position[2] - eye_pose.position.z,
};
translation = Rotate(inverse_eye, base_to_eye);
}
output[0] = rotation[0];
output[1] = rotation[1];
output[2] = rotation[2];
output[3] = translation[0] * units_per_meter;
output[4] = rotation[3];
output[5] = rotation[4];
output[6] = rotation[5];
output[7] = translation[1] * units_per_meter;
output[8] = rotation[6];
output[9] = rotation[7];
output[10] = rotation[8];
output[11] = translation[2] * units_per_meter;
}
class OpenXRIntegration final {
public:
static OpenXRIntegration& Get() {
static OpenXRIntegration integration;
return integration;
}
OpenXRStartupResult Prepare(AuroraConfig& aurora_config) {
Shutdown();
{
std::lock_guard lock(error_mutex_);
last_error_.clear();
}
if (graphics_retained_) {
SetError("OpenXR cannot be restarted after an unfenceable D3D12 submission");
return OpenXRStartupResult::Unavailable;
}
requested_ = RuntimeConfigFile::VrEnabled(false);
ConfigurePolicy(requested_);
if (!requested_) {
return OpenXRStartupResult::Disabled;
}
if (aurora_config.desiredBackend != BACKEND_AUTO &&
aurora_config.desiredBackend != BACKEND_D3D12) {
SetError("OpenXR currently requires the D3D12 graphics backend on Windows");
return OpenXRStartupResult::Unavailable;
}
logger_ = [](OpenXRLogLevel level, std::string_view message) {
const char* name = level == OpenXRLogLevel::Error ? "error" :
level == OpenXRLogLevel::Warning ? "warning" : "info";
RT_LOG(RT_TAG_RUNTIME) << "[openxr::" << name << "] " << message << std::endl;
};
runtime_ = std::make_unique<OpenXRRuntime>(logger_);
backend_ = std::make_unique<OpenXRD3D12Backend>(logger_);
OpenXRConfig config{};
config.application_name = aurora_config.appName != nullptr ? aurora_config.appName
: "WiiCompiled";
config.engine_name = "Aurora";
config.resolution_scale = RuntimeConfigFile::VrRenderScale(1.0f);
config.required_extensions = {"XR_KHR_D3D12_enable"};
if (!runtime_->Initialize(config)) {
SetError("OpenXR instance initialization failed: " + runtime_->LastError().message);
ResetPreparedObjects();
return OpenXRStartupResult::Unavailable;
}
if (!backend_->QueryGraphicsRequirements(*runtime_)) {
SetError(backend_->LastError());
ResetPreparedObjects();
return OpenXRStartupResult::Unavailable;
}
const auto& requirements = backend_->GraphicsRequirements();
aurora_config.desiredBackend = BACKEND_D3D12;
aurora_config.xrInterop = true;
aurora_config.hasD3D12AdapterLuid = true;
aurora_config.d3d12AdapterLuidLow = requirements.adapter_luid_low;
aurora_config.d3d12AdapterLuidHigh = requirements.adapter_luid_high;
prepared_ = true;
return OpenXRStartupResult::Prepared;
}
bool Start(AuroraBackend active_backend) {
if (!prepared_ || runtime_ == nullptr || backend_ == nullptr) {
return !requested_;
}
if (active_backend != BACKEND_D3D12) {
SetError("Aurora could not create the OpenXR-required D3D12 backend");
ResetPreparedObjects();
return false;
}
if (!backend_->BindAurora(*runtime_)) {
SetError(backend_->LastError());
ResetPreparedObjects();
return false;
}
stop_.store(false, std::memory_order_release);
teardown_requested_.store(false, std::memory_order_release);
WithdrawPublishedFrame();
aurora_set_stereo_frame_provider(&OpenXRIntegration::ProvideStereoFrame, this);
provider_registered_ = true;
running_.store(true, std::memory_order_release);
try {
pacing_thread_ = std::thread([this] { PacingThread(); });
} catch (const std::exception& exception) {
running_.store(false, std::memory_order_release);
aurora_set_stereo_frame_provider(nullptr, nullptr);
provider_registered_ = false;
SetError(std::string("could not start the OpenXR pacing thread: ") + exception.what());
ResetPreparedObjects();
return false;
}
RT_LOG(RT_TAG_RUNTIME) << "OpenXR asynchronous D3D12 presentation started" << std::endl;
return true;
}
void Shutdown() noexcept {
teardown_requested_.store(false, std::memory_order_release);
if (pacing_thread_.joinable()) {
// Registration changes are only safe while no sealed frame is in
// flight. The caller invokes us before Aurora teardown.
aurora_quiesce_frame_worker();
aurora_set_stereo_frame_provider(nullptr, nullptr);
provider_registered_ = false;
WithdrawPublishedFrame();
{
// Pair the predicate update with the wait mutex. Otherwise a
// terminal pacing thread can observe false, miss the notify,
// and make join wait forever.
std::lock_guard lock(stop_mutex_);
stop_.store(true, std::memory_order_release);
}
stop_cv_.notify_all();
pacing_thread_.join();
} else {
if (provider_registered_) {
aurora_quiesce_frame_worker();
aurora_set_stereo_frame_provider(nullptr, nullptr);
provider_registered_ = false;
}
ShutdownOrRetainGraphicsObjects();
}
running_.store(false, std::memory_order_release);
MkwVRPolicySetSessionActive(false);
backend_.reset();
runtime_.reset();
prepared_ = false;
ResetTrackingOrigin();
applied_session_run_serial_ = 0;
session_was_active_ = false;
}
bool IsRunning() const noexcept { return running_.load(std::memory_order_acquire); }
void ServiceProducerFrameBoundary() noexcept {
if (teardown_requested_.load(std::memory_order_acquire)) {
Shutdown();
}
}
std::string LastError() const {
std::lock_guard lock(error_mutex_);
return last_error_;
}
private:
struct PublishedFrame {
AuroraStereoFrame frame{};
};
void ResetPreparedObjects() {
ShutdownOrRetainGraphicsObjects();
backend_.reset();
runtime_.reset();
prepared_ = false;
}
bool ShutdownOrRetainGraphicsObjects() noexcept {
if (backend_ != nullptr && !backend_->Shutdown()) {
RT_LOG(RT_TAG_RUNTIME)
<< "OpenXR D3D12 queue completion is unknown; retaining the backend, "
"runtime, session, and graphics resources until process exit"
<< std::endl;
(void)backend_.release();
(void)runtime_.release();
graphics_retained_ = true;
return false;
}
if (runtime_ != nullptr) {
runtime_->Shutdown();
}
return true;
}
static bool ProvideStereoFrame(uint32_t, AuroraStereoFrame* output, void* userdata) {
auto* self = static_cast<OpenXRIntegration*>(userdata);
if (self == nullptr || output == nullptr) {
return false;
}
// The packet storage is reused by the XR thread. Claim and copy it
// under one short lock so cancellation cannot begin the next packet
// while this callback is preempted between exchange and copy.
std::lock_guard lock(self->published_mutex_);
PublishedFrame* frame = self->published_.exchange(nullptr, std::memory_order_acq_rel);
if (frame == nullptr) {
return false;
}
*output = frame->frame;
return true;
}
void PacingThread() noexcept {
bool fatal = false;
bool presentation_logged = false;
VRPresentationMode logged_presentation = VRPresentationMode::Desktop;
uint32_t presentation_log_count = 0;
bool immersive_submission_logged = false;
while (!stop_.load(std::memory_order_acquire) && !fatal) {
const OpenXREventStatus events = runtime_->PollEvents();
const bool session_active = runtime_->IsSessionRunning();
MkwVRPolicySetSessionActive(session_active);
const uint64_t session_run_serial = runtime_->SessionRunSerial();
if (session_run_serial != applied_session_run_serial_) {
applied_session_run_serial_ = session_run_serial;
ResetTrackingOrigin();
}
if (session_active != session_was_active_) {
session_was_active_ = session_active;
if (!session_active) {
ResetTrackingOrigin();
}
}
if (events == OpenXREventStatus::ExitRequested) {
SetError("OpenXR runtime requested session exit; continuing on the desktop mirror");
break;
}
if (events == OpenXREventStatus::Error) {
SetError("OpenXR event processing failed: " + runtime_->LastError().message);
break;
}
if (!session_active) {
WaitForStopOrDelay(std::chrono::milliseconds(5));
continue;
}
const MkwVRPolicySnapshot policy = MkwVRPolicyGetSnapshot();
if ((!presentation_logged || policy.presentation != logged_presentation) &&
presentation_log_count < 16) {
presentation_logged = true;
logged_presentation = policy.presentation;
++presentation_log_count;
RT_LOG(RT_TAG_RUNTIME)
<< "[mkw-vr] presentation="
<< (policy.presentation == VRPresentationMode::ImmersiveRace
? "immersive-race"
: policy.presentation == VRPresentationMode::VirtualScreen
? "virtual-screen"
: "desktop")
<< ", scene=" << static_cast<unsigned>(policy.scene.mode)
<< ", screens=" << policy.scene.local_player_count
<< ", camera-valid=" << policy.camera.valid
<< ", scene-frame=" << policy.scene.guest_frame_index
<< ", camera-frame=" << policy.camera.guest_frame_index
<< ", bindings=0x" << std::hex << policy.available_bindings
<< std::dec << std::endl;
}
OpenXRD3D12Presentation presentation{};
const bool immersive = policy.presentation == VRPresentationMode::ImmersiveRace;
presentation.mode = immersive ? OpenXRD3D12FrameMode::ImmersiveProjection
: OpenXRD3D12FrameMode::VirtualScreen;
presentation.quad_distance_meters = policy.config.hud_distance_meters;
OpenXRD3D12Frame frame{};
const OpenXRD3D12BeginStatus begin = backend_->BeginFrame(presentation, frame);
if (begin == OpenXRD3D12BeginStatus::SessionNotRunning) {
MkwVRPolicySetSessionActive(false);
continue;
}
if (begin == OpenXRD3D12BeginStatus::ExitRequested) {
SetError("OpenXR runtime requested session exit; continuing on the desktop mirror");
break;
}
if (begin == OpenXRD3D12BeginStatus::Error) {
SetError(backend_->LastError());
fatal = true;
break;
}
if (!frame.expects_gpu_submission) {
if (!backend_->FinishFrame(frame, false)) {
SetError(backend_->LastError());
fatal = true;
}
continue;
}
{
std::lock_guard lock(published_mutex_);
BuildPublishedFrame(frame, immersive, policy.config.world_units_per_meter,
policy.content_tag);
published_.store(&published_frame_, std::memory_order_release);
}
OpenXRD3D12SubmissionStatus submission = OpenXRD3D12SubmissionStatus::Timeout;
bool canceled_before_encode = false;
bool submission_stalled = false;
const auto submission_deadline =
std::chrono::steady_clock::now() + std::chrono::milliseconds(250);
while (!stop_.load(std::memory_order_acquire) &&
submission == OpenXRD3D12SubmissionStatus::Timeout) {
submission = backend_->WaitForSubmission(frame, 50);
if (submission == OpenXRD3D12SubmissionStatus::Timeout) {
// A pause, minimized window, or guest stall may leave no GX
// frame to consume this packet. Withdraw it, then cancel the
// matching bridge target only if Encode has not taken ownership.
WithdrawPublishedFrame();
canceled_before_encode = backend_->TryCancelPendingFrame(frame);
if (canceled_before_encode) {
break;
}
if (std::chrono::steady_clock::now() >= submission_deadline) {
// Cancellation may lose a race to completion. Recheck
// the callback-published predicate under the backend
// mutex before declaring a stall at the deadline.
submission = backend_->WaitForSubmission(frame, 0);
submission_stalled =
submission == OpenXRD3D12SubmissionStatus::Timeout;
break;
}
}
}
WithdrawPublishedFrame();
if (stop_.load(std::memory_order_acquire)) {
// Aurora has been drained by Shutdown(); backend shutdown below
// cancels its pending target, then either safely releases the
// XR image or retains the entire graph if GPU completion is unknown.
break;
}
if (canceled_before_encode) {
if (!backend_->FinishFrame(frame, false)) {
SetError(backend_->LastError());
fatal = true;
}
continue;
}
if (submission_stalled) {
SetError("Aurora did not complete the OpenXR stereo submission; "
"requesting a safe desktop fallback");
fatal = true;
break;
}
const bool submit = submission == OpenXRD3D12SubmissionStatus::Success;
if (!backend_->FinishFrame(frame, submit)) {
SetError(backend_->LastError());
fatal = true;
} else if (!submit) {
SetError("Aurora's D3D12 stereo copy failed; continuing on the desktop mirror");
fatal = true;
} else if (immersive && !immersive_submission_logged) {
immersive_submission_logged = true;
RT_LOG(RT_TAG_RUNTIME)
<< "[mkw-vr] first immersive packet consumed and submitted as "
"an OpenXR projection layer"
<< std::endl;
}
}
running_.store(false, std::memory_order_release);
MkwVRPolicySetSessionActive(false);
if (!stop_.load(std::memory_order_acquire)) {
// A runtime/backend failure can happen while Aurora is submitting.
// Ask the producer to reach a safe frame boundary, drain Aurora,
// and unregister the provider before this XR owner destroys state.
teardown_requested_.store(true, std::memory_order_release);
std::unique_lock lock(stop_mutex_);
stop_cv_.wait(lock, [this] { return stop_.load(std::memory_order_acquire); });
}
ShutdownOrRetainGraphicsObjects();
}
void BuildPublishedFrame(const OpenXRD3D12Frame& source, bool immersive,
float units_per_meter, uint64_t content_tag) noexcept {
ApplyPendingReferenceSpaceChange(source.xr_frame);
auto& destination = published_frame_.frame;
destination = {};
destination.frameToken = source.xr_frame.serial;
destination.contentTag = content_tag;
destination.mode = immersive ? AURORA_STEREO_FRAME_IMMERSIVE_REPLAY
: AURORA_STEREO_FRAME_VIRTUAL_SCREEN;
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
destination.eyes[eye].width = source.render_width[eye];
destination.eyes[eye].height = source.render_height[eye];
IdentityEye(destination.eyes[eye]);
}
if (!immersive) {
last_immersive_ = false;
return;
}
const bool position_valid =
(source.xr_frame.view_state_flags & XR_VIEW_STATE_POSITION_VALID_BIT) != 0;
if (!base_pose_valid_ || !last_immersive_) {
base_pose_ = CenterPose(source.xr_frame, position_valid);
base_pose_valid_ = true;
base_position_valid_ = position_valid;
} else if (position_valid && !base_position_valid_) {
base_pose_.position = CenterPose(source.xr_frame, true).position;
base_position_valid_ = true;
}
last_immersive_ = true;
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
ProjectionFromFov(source.xr_frame.views[eye].fov,
destination.eyes[eye].projection);
ViewFromBase(source.xr_frame.views[eye].pose, base_pose_,
position_valid && base_position_valid_,
units_per_meter, destination.eyes[eye].viewFromCenter);
}
}
void ApplyPendingReferenceSpaceChange(const OpenXRFrame& frame) noexcept {
if (runtime_->ConsumeAppSpaceChangesThrough(frame.predicted_display_time)) {
ResetTrackingOrigin();
}
}
void ResetTrackingOrigin() noexcept {
base_pose_ = {};
base_pose_valid_ = false;
base_position_valid_ = false;
last_immersive_ = false;
}
void WaitForStopOrDelay(std::chrono::milliseconds delay) {
std::unique_lock lock(stop_mutex_);
stop_cv_.wait_for(lock, delay,
[this] { return stop_.load(std::memory_order_acquire); });
}
void WithdrawPublishedFrame() noexcept {
std::lock_guard lock(published_mutex_);
published_.store(nullptr, std::memory_order_release);
}
void SetError(std::string message) {
{
std::lock_guard lock(error_mutex_);
last_error_ = std::move(message);
}
RT_LOG(RT_TAG_RUNTIME) << "OpenXR: " << LastError() << std::endl;
}
OpenXRLogCallback logger_;
std::unique_ptr<OpenXRRuntime> runtime_;
std::unique_ptr<OpenXRD3D12Backend> backend_;
std::thread pacing_thread_;
std::atomic_bool stop_{false};
std::atomic_bool running_{false};
std::atomic_bool teardown_requested_{false};
std::atomic<PublishedFrame*> published_{nullptr};
PublishedFrame published_frame_{};
std::mutex published_mutex_;
std::mutex stop_mutex_;
std::condition_variable stop_cv_;
mutable std::mutex error_mutex_;
std::string last_error_;
Pose base_pose_{};
bool base_pose_valid_ = false;
bool base_position_valid_ = false;
bool last_immersive_ = false;
uint64_t applied_session_run_serial_ = 0;
bool session_was_active_ = false;
bool requested_ = false;
bool prepared_ = false;
bool provider_registered_ = false;
bool graphics_retained_ = false;
};
#endif // defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
} // namespace
OpenXRStartupResult OpenXRPrepareAurora(AuroraConfig& config) {
#if !defined(MKW_ENABLE_OPENXR)
(void)config;
ConfigurePolicy(false);
return RuntimeConfigFile::VrEnabled(false) ? OpenXRStartupResult::Unavailable
: OpenXRStartupResult::Disabled;
#elif defined(_WIN32)
return OpenXRIntegration::Get().Prepare(config);
#else
ConfigurePolicy(RuntimeConfigFile::VrEnabled(false));
if (!RuntimeConfigFile::VrEnabled(false)) {
return OpenXRStartupResult::Disabled;
}
const OpenXRVulkanCapabilityInfo capability = OpenXRVulkanBackend::DawnInteropCapability();
RT_LOG(RT_TAG_RUNTIME) << "OpenXR Vulkan unavailable: " << capability.reason << std::endl;
return OpenXRStartupResult::Unavailable;
#endif
}
bool OpenXRStartAfterAurora(AuroraBackend active_backend) {
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
return OpenXRIntegration::Get().Start(active_backend);
#else
(void)active_backend;
return !RuntimeConfigFile::VrEnabled(false);
#endif
}
void OpenXRShutdownBeforeAurora() noexcept {
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
OpenXRIntegration::Get().Shutdown();
#else
MkwVRPolicySetSessionActive(false);
#endif
}
void OpenXRServiceProducerFrameBoundary() noexcept {
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
OpenXRIntegration::Get().ServiceProducerFrameBoundary();
#endif
}
bool OpenXRIsRunning() noexcept {
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
return OpenXRIntegration::Get().IsRunning();
#else
return false;
#endif
}
std::string OpenXRLastError() {
#if !defined(MKW_ENABLE_OPENXR)
return "this build was compiled without OpenXR support";
#elif defined(_WIN32)
return OpenXRIntegration::Get().LastError();
#else
return OpenXRVulkanBackend::DawnInteropCapability().reason;
#endif
}
} // namespace mkw::vr