mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 08:00:25 +02:00
runtime: integrate OpenXR presentation for Mario Kart Wii
This commit is contained in:
1 parent
d8788de919
commit
2156617cd5
17 files changed
+3052
-42
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user