mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 07:00:30 +02:00
732 lines
28 KiB
C++
732 lines
28 KiB
C++
// 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
|