mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 01:00:14 +02:00
Implement render-first pacing for D3D12 and Vulkan backends; enhance packet management and diagnostics
This commit is contained in:
1 parent
787dbcf7d2
commit
fbc9ce7217
5 files changed
+377
-26
No files matched your search
@@ -320,18 +320,31 @@ short-lived immutable stereo packet. Each sealed GX frame and immersive packet c
|
||||
policy-generation tag; a mismatch is rendered in mono and the acquired XR frame is canceled, so an
|
||||
asynchronous menu/race transition cannot replay race transforms over unsafe content.
|
||||
|
||||
With interpolation off, the standalone (Vulkan) backend paces render-first: the pacing thread
|
||||
locates the views for an estimated display time (two periods past the last one the compositor
|
||||
predicted), hands Aurora the packet and its shared-buffer targets with no compositor frame open,
|
||||
and waits for Aurora to render the eyes at its next seal, repeating the retained layer if that
|
||||
takes more than 50 ms. Only then does it call xrWaitFrame and xrBeginFrame, copy the eyes into the
|
||||
freshly acquired swapchain images and end the frame with the packet's render poses. A headset
|
||||
frame therefore stays open for the copy alone instead of for the next 60 Hz game frame plus the
|
||||
whole encode, which on a 72 or 90 Hz display used to make every second cycle span two display
|
||||
slots (about 45 headset frames per second while the game rendered 60). The compositor reprojects
|
||||
the rendered pose to the frame it lands in. The D3D12 backend keeps the frame-first order below,
|
||||
as does interpolation on either backend, since interpolation renders for the frame's own
|
||||
predicted display time.
|
||||
With interpolation off, both PC (D3D12) and standalone (Vulkan) pace render-first:
|
||||
the pacing thread locates views for an estimated display time (two periods past the last
|
||||
prediction), hands Aurora a packet without leaving a compositor frame open, and waits for
|
||||
rendering. A 50 ms stall repeats the retained layer; cancellation also advances a keep-alive
|
||||
cycle to refresh timing. Once rendering is submitted, the thread calls xrWaitFrame and
|
||||
xrBeginFrame, completes backend-specific copy/release work, and ends the frame using the
|
||||
packet's original render poses with the current compositor display time.
|
||||
|
||||
Vulkan renders into shared buffers and copies them into newly acquired XR images afterward.
|
||||
D3D12 acquires images from its non-retained swapchain pair before rendering; Aurora queues the
|
||||
copy on the session's D3D12 queue before reporting completion. PC therefore needs no additional
|
||||
copy in the short compositor cycle. Pending images remain acquired and separate from the
|
||||
retained pair until completion or confirmed cancellation before encoding. GPU failure still
|
||||
requires the existing queue-drain teardown. Rendered poses keep the session/reference-space
|
||||
serials recorded when the packet was prepared, so changes during rendering invalidate them.
|
||||
|
||||
VR interpolation keeps the frame-first order on both backends because it renders for the
|
||||
frame's own predicted display time. The log announces `OpenXR D3D12 pacing: render-first` or
|
||||
`frame-first (VR interpolation)` on each transition. For PC testing, disable **VR** frame
|
||||
interpolation for a race capture; changing desktop interpolation alone does not select this
|
||||
path. Menus use render-first even when VR interpolation is configured for races. Compare the
|
||||
new diagnostic `open`, `end-gap`, `late`, and stage timings against a frame-first capture on
|
||||
the same course and settings. Shorter `open` alone does not prove fewer black frames: rendering
|
||||
and xrWaitFrame still take time outside that interval. Hardware testing is needed to measure
|
||||
latency, runtime throttling and visible blackouts.
|
||||
|
||||
With VR interpolation enabled, Aurora retains each sealed race's command stream and matched
|
||||
previous/current transform uniforms. New OpenXR packets wake the frame worker between game
|
||||
|
||||
@@ -81,6 +81,16 @@ public:
|
||||
// available, resubmits the retained layer using the current display time.
|
||||
bool FinishFrame(OpenXRD3D12Frame& frame, bool submit_layer);
|
||||
|
||||
// Render-first path when interpolation is off. Aurora renders into acquired
|
||||
// non-retained XR images while no compositor frame is open. BeginFrameForPacket
|
||||
// accepts only a completed packet; CopyRenderedEyes verifies the already queued
|
||||
// bridge copy. FinishFrame releases the images and submits their original poses.
|
||||
OpenXRBeginStatus PreparePacket(const OpenXRPresentation& presentation, OpenXRBackendFrame& packet);
|
||||
bool TryCancelPendingPacket(OpenXRBackendFrame& packet);
|
||||
OpenXRBeginStatus BeginFrameForPacket(const OpenXRBackendFrame& packet, OpenXRBackendFrame& frame);
|
||||
OpenXRSubmissionStatus CopyRenderedEyes(const OpenXRBackendFrame& frame);
|
||||
OpenXRBeginStatus KeepAliveCycle();
|
||||
|
||||
// Call on the XR owner thread after Aurora's worker is idle and before
|
||||
// aurora_shutdown(). Safe to repeat. False means a submitted D3D12 command
|
||||
// could not be fenced; the caller must retain this backend and its runtime
|
||||
|
||||
@@ -234,7 +234,7 @@ public:
|
||||
Fail("BeginFrame called before the D3D12 backend was bound");
|
||||
return OpenXRD3D12BeginStatus::Error;
|
||||
}
|
||||
if (frame_active_) {
|
||||
if (frame_active_ || pending_packet_serial_ != 0) {
|
||||
Fail("BeginFrame called while another OpenXR frame is active");
|
||||
return OpenXRD3D12BeginStatus::Error;
|
||||
}
|
||||
@@ -255,6 +255,7 @@ public:
|
||||
break;
|
||||
}
|
||||
}
|
||||
NoteDisplayTiming(frame.xr_frame);
|
||||
if (!runtime_->BeginFrame(frame.xr_frame)) {
|
||||
Fail("xrBeginFrame failed");
|
||||
return OpenXRD3D12BeginStatus::Error;
|
||||
@@ -283,8 +284,13 @@ public:
|
||||
return OpenXRD3D12BeginStatus::Ready;
|
||||
}
|
||||
|
||||
return PrepareTargets(frame);
|
||||
}
|
||||
|
||||
// Both pacing paths retain ownership until completion or cancellation.
|
||||
OpenXRD3D12BeginStatus PrepareTargets(OpenXRD3D12Frame& frame) {
|
||||
const uint32_t target_count =
|
||||
presentation.mode == OpenXRD3D12FrameMode::VirtualScreen ? 1u : kOpenXREyeCount;
|
||||
frame.presentation.mode == OpenXRD3D12FrameMode::VirtualScreen ? 1u : kOpenXREyeCount;
|
||||
if (target_count == 1) {
|
||||
frame.render_width[1] = frame.render_width[0];
|
||||
frame.render_height[1] = frame.render_height[0];
|
||||
@@ -332,6 +338,151 @@ public:
|
||||
return OpenXRD3D12BeginStatus::Ready;
|
||||
}
|
||||
|
||||
// D3D12 renders straight into the non-retained XR swapchain pair. Acquiring
|
||||
// images is independent of the compositor cycle; keep them acquired while
|
||||
// Aurora owns them, and never expose them through the retained pair early.
|
||||
OpenXRBeginStatus PreparePacket(const OpenXRPresentation& presentation, OpenXRBackendFrame& packet) {
|
||||
packet = {};
|
||||
packet.presentation = presentation;
|
||||
if (!bound_ || runtime_ == nullptr || frame_active_ || pending_packet_serial_ != 0) {
|
||||
Fail("PreparePacket called before binding or with work pending");
|
||||
return OpenXRBeginStatus::Error;
|
||||
}
|
||||
if (runtime_->ShouldExit()) return OpenXRBeginStatus::ExitRequested;
|
||||
if (!runtime_->IsSessionRunning()) return OpenXRBeginStatus::SessionNotRunning;
|
||||
if (timing_session_serial_ != runtime_->SessionRunSerial() || last_display_period_ <= 0) {
|
||||
const auto status = KeepAliveCycle();
|
||||
if (status != OpenXRBeginStatus::Ready) return status;
|
||||
}
|
||||
packet.xr_frame.serial = next_packet_serial_++;
|
||||
packet.xr_frame.predicted_display_time = last_display_time_ + 2 * last_display_period_;
|
||||
packet.xr_frame.predicted_display_period = last_display_period_;
|
||||
packet.xr_frame.should_render = last_should_render_;
|
||||
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||
packet.render_width[eye] = eye_swapchains_[eye].width;
|
||||
packet.render_height[eye] = eye_swapchains_[eye].height;
|
||||
}
|
||||
if (!packet.xr_frame.should_render) return OpenXRBeginStatus::Ready;
|
||||
if (!runtime_->LocateViewsAt(packet.xr_frame.predicted_display_time, packet.xr_frame)) {
|
||||
Fail("xrLocateViews failed for a D3D12 packet");
|
||||
return OpenXRBeginStatus::Error;
|
||||
}
|
||||
if (!packet.xr_frame.views_valid) return OpenXRBeginStatus::Ready;
|
||||
render_session_serial_ = runtime_->SessionRunSerial();
|
||||
render_space_serial_ = runtime_->LastReferenceSpaceChange().serial;
|
||||
const auto status = PrepareTargets(packet);
|
||||
if (status == OpenXRBeginStatus::Ready) pending_packet_serial_ = packet.xr_frame.serial;
|
||||
return status;
|
||||
}
|
||||
|
||||
bool TryCancelPendingPacket(OpenXRBackendFrame& packet) {
|
||||
if (frame_active_ || pending_packet_serial_ == 0 ||
|
||||
packet.xr_frame.serial != pending_packet_serial_ || !packet.expects_gpu_submission ||
|
||||
!aurora_d3d12_cancel_stereo_targets(packet.xr_frame.serial)) return false;
|
||||
{
|
||||
std::lock_guard lock(submission_mutex_);
|
||||
awaiting_token_ = submitted_token_ = 0;
|
||||
submission_arrived_ = submission_success_ = submission_unsafe_ = false;
|
||||
}
|
||||
packet.expects_gpu_submission = false;
|
||||
pending_packet_serial_ = 0;
|
||||
const diagnostics::Stopwatch release_timer;
|
||||
const bool released = ReleaseAcquiredSwapchains();
|
||||
diagnostics::OnSwapchainRelease(release_timer);
|
||||
// A release error is fatal; keep it visible to the next prepare rather
|
||||
// than letting it register new targets over still-acquired images.
|
||||
if (!released) pending_packet_serial_ = packet.xr_frame.serial;
|
||||
return true; // Encoding was canceled; a release error blocks the next prepare.
|
||||
}
|
||||
|
||||
void NoteDisplayTiming(const OpenXRFrame& frame) {
|
||||
last_display_time_ = frame.predicted_display_time;
|
||||
last_display_period_ = frame.predicted_display_period;
|
||||
last_should_render_ = frame.should_render;
|
||||
timing_session_serial_ = runtime_->SessionRunSerial();
|
||||
}
|
||||
|
||||
OpenXRBeginStatus BeginCompositorCycle() {
|
||||
const auto status = runtime_->WaitFrame(active_frame_);
|
||||
if (status != OpenXRFrameStatus::Ready) {
|
||||
if (status == OpenXRFrameStatus::Error) Fail(BeginStatusOperation(status));
|
||||
return status == OpenXRFrameStatus::SessionNotRunning ? OpenXRBeginStatus::SessionNotRunning
|
||||
: status == OpenXRFrameStatus::ExitRequested ? OpenXRBeginStatus::ExitRequested
|
||||
: OpenXRBeginStatus::Error;
|
||||
}
|
||||
NoteDisplayTiming(active_frame_);
|
||||
if (!runtime_->BeginFrame(active_frame_)) {
|
||||
Fail("xrBeginFrame failed for a D3D12 compositor cycle");
|
||||
return OpenXRBeginStatus::Error;
|
||||
}
|
||||
frame_active_ = true;
|
||||
return OpenXRBeginStatus::Ready;
|
||||
}
|
||||
|
||||
OpenXRBeginStatus KeepAliveCycle() {
|
||||
if (!bound_ || runtime_ == nullptr || frame_active_) {
|
||||
Fail("KeepAliveCycle called before binding or with an active frame");
|
||||
return OpenXRBeginStatus::Error;
|
||||
}
|
||||
const auto status = BeginCompositorCycle();
|
||||
if (status != OpenXRBeginStatus::Ready) return status;
|
||||
const bool ended = EndRetainedFrame(false);
|
||||
frame_active_ = false;
|
||||
active_frame_ = {};
|
||||
if (!ended) {
|
||||
Fail("OpenXR could not resubmit the retained D3D12 frame");
|
||||
return OpenXRBeginStatus::Error;
|
||||
}
|
||||
return OpenXRBeginStatus::Ready;
|
||||
}
|
||||
|
||||
OpenXRBeginStatus BeginFrameForPacket(const OpenXRBackendFrame& packet, OpenXRBackendFrame& frame) {
|
||||
frame = {};
|
||||
if (!bound_ || runtime_ == nullptr || frame_active_ || pending_packet_serial_ == 0 ||
|
||||
packet.xr_frame.serial != pending_packet_serial_ || !packet.expects_gpu_submission ||
|
||||
WaitForSubmission(packet, 0) != OpenXRSubmissionStatus::Success) {
|
||||
Fail("BeginFrameForPacket requires the completed current D3D12 packet");
|
||||
return OpenXRBeginStatus::Error;
|
||||
}
|
||||
const auto status = BeginCompositorCycle();
|
||||
if (status != OpenXRBeginStatus::Ready) {
|
||||
// Completion was already confirmed. A stopped session must not
|
||||
// strand a packet and block preparation after the next READY event.
|
||||
if (status == OpenXRBeginStatus::SessionNotRunning) {
|
||||
const bool released = ReleaseAcquiredSwapchains();
|
||||
pending_packet_serial_ = 0;
|
||||
std::lock_guard lock(submission_mutex_);
|
||||
awaiting_token_ = submitted_token_ = 0;
|
||||
submission_arrived_ = submission_success_ = submission_unsafe_ = false;
|
||||
if (!released) return OpenXRBeginStatus::Error;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
frame = packet;
|
||||
// Use the current compositor token/time but the original render poses.
|
||||
frame.xr_frame.serial = active_frame_.serial;
|
||||
frame.xr_frame.predicted_display_time = active_frame_.predicted_display_time;
|
||||
frame.xr_frame.predicted_display_period = active_frame_.predicted_display_period;
|
||||
frame.xr_frame.should_render = active_frame_.should_render;
|
||||
active_frame_serial_ = frame.xr_frame.serial;
|
||||
{
|
||||
std::lock_guard lock(submission_mutex_);
|
||||
awaiting_token_ = submitted_token_ = frame.xr_frame.serial;
|
||||
}
|
||||
pending_packet_serial_ = 0;
|
||||
return OpenXRBeginStatus::Ready;
|
||||
}
|
||||
|
||||
OpenXRSubmissionStatus CopyRenderedEyes(const OpenXRBackendFrame& frame) {
|
||||
if (!frame_active_ || frame.xr_frame.serial != active_frame_serial_) {
|
||||
Fail("CopyRenderedEyes received a stale D3D12 frame");
|
||||
return OpenXRSubmissionStatus::Failed;
|
||||
}
|
||||
// The native bridge already queued the copy on the session's D3D12
|
||||
// queue before publishing completion. There is no second copy on PC.
|
||||
return WaitForSubmission(frame, 0);
|
||||
}
|
||||
|
||||
OpenXRD3D12SubmissionStatus WaitForSubmission(const OpenXRD3D12Frame& frame,
|
||||
uint32_t timeout_ms) {
|
||||
if (!frame.expects_gpu_submission) {
|
||||
@@ -456,6 +607,7 @@ public:
|
||||
!runtime_->BeginFrame(active_frame_)) {
|
||||
return Fail("OpenXR could not start a retained-frame compositor cycle");
|
||||
}
|
||||
NoteDisplayTiming(active_frame_);
|
||||
frame_active_ = true;
|
||||
return true;
|
||||
}
|
||||
@@ -577,6 +729,8 @@ public:
|
||||
active_frame_ = {};
|
||||
}
|
||||
DestroySwapchains();
|
||||
pending_packet_serial_ = 0;
|
||||
last_display_period_ = 0;
|
||||
if (owns_session_ && runtime_ != nullptr) {
|
||||
runtime_->DestroySession();
|
||||
owns_session_ = false;
|
||||
@@ -779,7 +933,7 @@ private:
|
||||
}
|
||||
|
||||
void EndActiveFrameWithoutLayers(const OpenXRFrame& frame) {
|
||||
if (runtime_ != nullptr && runtime_->IsSessionRunning()) {
|
||||
if (frame_active_ && runtime_ != nullptr && runtime_->IsSessionRunning()) {
|
||||
runtime_->EndFrameWithoutLayers(frame);
|
||||
}
|
||||
frame_active_ = false;
|
||||
@@ -851,6 +1005,12 @@ private:
|
||||
bool submission_unsafe_ = false;
|
||||
bool shutting_down_ = false;
|
||||
|
||||
uint64_t pending_packet_serial_ = 0;
|
||||
uint64_t next_packet_serial_ = 1ull << 40;
|
||||
uint64_t timing_session_serial_ = 0;
|
||||
XrTime last_display_time_ = 0;
|
||||
XrDuration last_display_period_ = 0;
|
||||
bool last_should_render_ = false;
|
||||
uint64_t active_frame_serial_ = 0;
|
||||
uint64_t render_session_serial_ = 0;
|
||||
uint64_t render_space_serial_ = 0;
|
||||
@@ -898,6 +1058,20 @@ bool OpenXRD3D12Backend::RepeatFrame(const OpenXRD3D12Frame& frame) {
|
||||
return m_impl->RepeatFrame(frame);
|
||||
}
|
||||
|
||||
OpenXRBeginStatus OpenXRD3D12Backend::PreparePacket(const OpenXRPresentation& presentation, OpenXRBackendFrame& packet) {
|
||||
return m_impl->PreparePacket(presentation, packet);
|
||||
}
|
||||
bool OpenXRD3D12Backend::TryCancelPendingPacket(OpenXRBackendFrame& packet) {
|
||||
return m_impl->TryCancelPendingPacket(packet);
|
||||
}
|
||||
OpenXRBeginStatus OpenXRD3D12Backend::BeginFrameForPacket(const OpenXRBackendFrame& packet, OpenXRBackendFrame& frame) {
|
||||
return m_impl->BeginFrameForPacket(packet, frame);
|
||||
}
|
||||
OpenXRSubmissionStatus OpenXRD3D12Backend::CopyRenderedEyes(const OpenXRBackendFrame& frame) {
|
||||
return m_impl->CopyRenderedEyes(frame);
|
||||
}
|
||||
OpenXRBeginStatus OpenXRD3D12Backend::KeepAliveCycle() { return m_impl->KeepAliveCycle(); }
|
||||
|
||||
bool OpenXRD3D12Backend::Shutdown() { return m_impl->Shutdown(); }
|
||||
|
||||
bool OpenXRD3D12Backend::IsBound() const { return m_impl->IsBound(); }
|
||||
|
||||
@@ -727,6 +727,7 @@ private:
|
||||
VRPresentationMode logged_presentation = VRPresentationMode::Desktop;
|
||||
uint32_t presentation_log_count = 0;
|
||||
bool immersive_submission_logged = false;
|
||||
int last_pacing_mode = -1;
|
||||
while (!stop_.load(std::memory_order_acquire) && !fatal) {
|
||||
#if defined(__ANDROID__)
|
||||
if (!worker_registered) {
|
||||
@@ -823,19 +824,23 @@ private:
|
||||
const uint32_t interpolation_target = frame_interpolation_fps_.load(std::memory_order_relaxed);
|
||||
SetInterpolationActive(immersive && FrameInterpolationAvailable() && interpolation_target != 0);
|
||||
|
||||
#if !defined(_WIN32)
|
||||
// Standalone (Vulkan) backend: with interpolation off, the eyes are rendered before
|
||||
// With interpolation off, the eyes are rendered before
|
||||
// the compositor frame that shows them is begun, so that frame never waits for a
|
||||
// game frame. Interpolation keeps the frame-first order below: it renders for the
|
||||
// frame's own predicted display time.
|
||||
if (!aurora_get_stereo_frame_interpolation()) {
|
||||
const bool render_first = !aurora_get_stereo_frame_interpolation();
|
||||
if (last_pacing_mode != static_cast<int>(render_first)) {
|
||||
last_pacing_mode = static_cast<int>(render_first);
|
||||
RT_LOG(RT_TAG_RUNTIME) << "OpenXR " << kGraphicsBackendName << " pacing: "
|
||||
<< (render_first ? "render-first" : "frame-first (VR interpolation)") << std::endl;
|
||||
}
|
||||
if (render_first) {
|
||||
if (!RenderFirstCycle(presentation, policy, immersive, consecutive_skips,
|
||||
immersive_submission_logged)) {
|
||||
fatal = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
OpenXRBackendFrame frame{};
|
||||
const OpenXRBeginStatus begin = backend_->BeginFrame(presentation, frame);
|
||||
if (begin == OpenXRBeginStatus::SessionNotRunning) {
|
||||
@@ -1003,7 +1008,6 @@ private:
|
||||
ShutdownOrRetainGraphicsObjects();
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
// One compositor cycle on the retained layer, with no frame left active. False on a fatal
|
||||
// backend or runtime failure (the error is recorded).
|
||||
bool KeepAlive() {
|
||||
@@ -1023,7 +1027,7 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
// Render-first pacing (see OpenXRVulkanBackend::PreparePacket). Returns false on a fatal
|
||||
// Render-first pacing (see each backend's PreparePacket). Returns false on a fatal
|
||||
// failure; a cycle that ends without a layer returns true and the loop tries again.
|
||||
bool RenderFirstCycle(OpenXRPresentation presentation, const MkwVRPolicySnapshot& policy, bool immersive,
|
||||
uint32_t& consecutive_skips, bool& immersive_submission_logged) {
|
||||
@@ -1089,10 +1093,15 @@ private:
|
||||
}
|
||||
}
|
||||
diagnostics::Measure(diagnostics::Stage::Withdraw, [&] { WithdrawPublishedFrame(); });
|
||||
if (stop_.load(std::memory_order_acquire) || canceled_before_encode ||
|
||||
if (stop_.load(std::memory_order_acquire) ||
|
||||
submission == OpenXRSubmissionStatus::ShuttingDown) {
|
||||
return true;
|
||||
}
|
||||
if (canceled_before_encode) {
|
||||
// Refresh display timing after a canceled packet as well, so the
|
||||
// next estimate cannot remain stuck in the past during a game stall.
|
||||
return KeepAlive();
|
||||
}
|
||||
if (submission != OpenXRSubmissionStatus::Success) {
|
||||
// Nothing reached the shared buffers (Skipped) or Aurora failed after queuing GPU
|
||||
// work (Failed): same accounting as the frame-first path, on a keep-alive cycle.
|
||||
@@ -1115,7 +1124,7 @@ private:
|
||||
return KeepAlive();
|
||||
}
|
||||
|
||||
// The eyes are in the shared buffers: begin the compositor frame, copy, end.
|
||||
// The eyes are rendered: begin the compositor frame, complete the backend copy, end.
|
||||
OpenXRBackendFrame frame{};
|
||||
const OpenXRBeginStatus begin = backend_->BeginFrameForPacket(packet, frame);
|
||||
if (begin == OpenXRBeginStatus::SessionNotRunning) {
|
||||
@@ -1165,7 +1174,6 @@ private:
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void BuildPublishedFrame(const OpenXRBackendFrame& source, bool immersive,
|
||||
float units_per_meter, uint64_t content_tag) noexcept {
|
||||
|
||||
@@ -37,6 +37,8 @@ AuroraD3D12StereoSubmittedCallback callback = nullptr;
|
||||
void* callback_data = nullptr;
|
||||
uint64_t pending_token = 0;
|
||||
bool encoded = false;
|
||||
bool compositor_open = false;
|
||||
bool expect_render_first = false;
|
||||
bool should_render = true;
|
||||
bool tracking_valid = true;
|
||||
bool change_space = false;
|
||||
@@ -53,6 +55,7 @@ XrPosef quad_pose{};
|
||||
|
||||
void Complete(bool success = true) {
|
||||
Require(pending_token != 0);
|
||||
if (expect_render_first) Require(!compositor_open);
|
||||
if (success) {
|
||||
for (const auto& target : targets)
|
||||
reinterpret_cast<Image*>(target.resource)->content = pending_token;
|
||||
@@ -172,16 +175,17 @@ bool OpenXRRuntime::CreateSession(const void*) {
|
||||
++m_session_run_serial;
|
||||
return true;
|
||||
}
|
||||
void OpenXRRuntime::DestroySession() { m_session_running = false; }
|
||||
void OpenXRRuntime::DestroySession() { m_session_running = false; compositor_open = false; }
|
||||
void OpenXRRuntime::ObserveResult(XrResult) noexcept {}
|
||||
OpenXREventStatus OpenXRRuntime::PollEvents() {
|
||||
if (change_space) { ++m_reference_space_change.serial; change_space = false; }
|
||||
if (restart_session) { ++m_session_run_serial; restart_session = false; }
|
||||
if (restart_session) { ++m_session_run_serial; m_session_running = true; restart_session = false; }
|
||||
if (stop_session) { m_session_running = false; stop_session = false; }
|
||||
return OpenXREventStatus::Continue;
|
||||
}
|
||||
OpenXRFrameStatus OpenXRRuntime::WaitFrame(OpenXRFrame& frame) {
|
||||
Require(m_frame_phase == FramePhase::Idle);
|
||||
if (!m_session_running) return OpenXRFrameStatus::SessionNotRunning;
|
||||
frame = {};
|
||||
frame.serial = m_next_frame_serial++;
|
||||
frame.predicted_display_time = frame.serial * 11'111'111;
|
||||
@@ -194,6 +198,7 @@ OpenXRFrameStatus OpenXRRuntime::WaitFrame(OpenXRFrame& frame) {
|
||||
bool OpenXRRuntime::BeginFrame(const OpenXRFrame& frame) {
|
||||
Require(m_frame_phase == FramePhase::Waited && frame.serial == m_active_frame_serial);
|
||||
m_frame_phase = FramePhase::Begun;
|
||||
compositor_open = true;
|
||||
return true;
|
||||
}
|
||||
bool OpenXRRuntime::LocateViews(OpenXRFrame& frame) {
|
||||
@@ -206,6 +211,11 @@ bool OpenXRRuntime::LocateViews(OpenXRFrame& frame) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool OpenXRRuntime::LocateViewsAt(XrTime time, OpenXRFrame& frame) {
|
||||
Require(!compositor_open);
|
||||
frame.predicted_display_time = time;
|
||||
return LocateViews(frame);
|
||||
}
|
||||
bool OpenXRRuntime::EndFrame(const OpenXRFrame& frame,
|
||||
const XrCompositionLayerBaseHeader* const* layers, uint32_t count) {
|
||||
Require(m_frame_phase == FramePhase::Begun && frame.serial == m_active_frame_serial);
|
||||
@@ -238,6 +248,7 @@ bool OpenXRRuntime::EndFrame(const OpenXRFrame& frame,
|
||||
}
|
||||
}
|
||||
m_frame_phase = FramePhase::Idle;
|
||||
compositor_open = false;
|
||||
if (fail_end) {
|
||||
fail_end = false;
|
||||
return false;
|
||||
@@ -249,7 +260,142 @@ bool OpenXRRuntime::EndFrameWithoutLayers(const OpenXRFrame& frame) {
|
||||
}
|
||||
}
|
||||
|
||||
void TestRenderFirst() {
|
||||
display_time = 0;
|
||||
expect_render_first = true;
|
||||
OpenXRRuntime runtime;
|
||||
OpenXRD3D12Backend backend;
|
||||
Require(backend.QueryGraphicsRequirements(runtime) && backend.BindAurora(runtime));
|
||||
OpenXRPresentation presentation;
|
||||
OpenXRBackendFrame packet, frame;
|
||||
const auto prepare = [&] {
|
||||
Require(backend.PreparePacket(presentation, packet) == OpenXRBeginStatus::Ready);
|
||||
Require(!compositor_open && packet.expects_gpu_submission);
|
||||
};
|
||||
const auto submit = [&] {
|
||||
Complete();
|
||||
Require(backend.WaitForSubmission(packet, 0) == OpenXRSubmissionStatus::Success);
|
||||
Require(backend.BeginFrameForPacket(packet, frame) == OpenXRBeginStatus::Ready);
|
||||
Require(compositor_open && frame.xr_frame.serial != packet.xr_frame.serial);
|
||||
Require(backend.CopyRenderedEyes(frame) == OpenXRSubmissionStatus::Success);
|
||||
Require(backend.FinishFrame(frame, true));
|
||||
Require(!compositor_open);
|
||||
};
|
||||
prepare();
|
||||
Require(backend.BeginFrameForPacket(packet, frame) == OpenXRBeginStatus::Error);
|
||||
submit();
|
||||
const auto first = displayed_content;
|
||||
Require(first == packet.xr_frame.serial && layer_count == 1);
|
||||
prepare();
|
||||
const auto before_stall = releases;
|
||||
encoded = true;
|
||||
for (int i = 0; i < 300; ++i) {
|
||||
Require(backend.WaitForSubmission(packet, 0) == OpenXRSubmissionStatus::Timeout);
|
||||
Require(!backend.TryCancelPendingPacket(packet));
|
||||
Require(backend.KeepAliveCycle() == OpenXRBeginStatus::Ready);
|
||||
Require(!compositor_open && layer_count == 1 && displayed_content == first);
|
||||
Require(releases == before_stall);
|
||||
}
|
||||
submit(); // Must preserve original poses across independently advancing cycles.
|
||||
Require(displayed_content == packet.xr_frame.serial);
|
||||
const auto second = displayed_content;
|
||||
prepare();
|
||||
auto stale = packet;
|
||||
++stale.xr_frame.serial;
|
||||
Require(!backend.TryCancelPendingPacket(stale));
|
||||
Require(backend.BeginFrameForPacket(stale, frame) == OpenXRBeginStatus::Error);
|
||||
Require(backend.TryCancelPendingPacket(packet));
|
||||
Require(!compositor_open && releases == before_stall + 4);
|
||||
Require(backend.KeepAliveCycle() == OpenXRBeginStatus::Ready);
|
||||
Require(displayed_content == second);
|
||||
|
||||
// Original frame-first mode still works after switching interpolation on.
|
||||
expect_render_first = false;
|
||||
Require(backend.BeginFrame(presentation, frame) == OpenXRBeginStatus::Ready);
|
||||
Complete();
|
||||
Require(backend.FinishFrame(frame, true));
|
||||
expect_render_first = true;
|
||||
prepare();
|
||||
Require(packet.xr_frame.predicted_display_time > display_time);
|
||||
Require(backend.TryCancelPendingPacket(packet));
|
||||
presentation.mode = OpenXRFrameMode::VirtualScreen;
|
||||
presentation.quad_anchored = true;
|
||||
presentation.quad_pose.position.z = -4;
|
||||
prepare();
|
||||
Require(targets.size() == 1);
|
||||
submit();
|
||||
Require(layer_type == XR_TYPE_COMPOSITION_LAYER_QUAD && quad_pose.position.z == -4);
|
||||
|
||||
presentation.mode = OpenXRFrameMode::ImmersiveProjection;
|
||||
prepare();
|
||||
should_render = false; // Visibility can change while rendering.
|
||||
submit();
|
||||
Require(layer_count == 0);
|
||||
Require(backend.PreparePacket(presentation, packet) == OpenXRBeginStatus::Ready);
|
||||
Require(!packet.expects_gpu_submission);
|
||||
should_render = true;
|
||||
Require(backend.KeepAliveCycle() == OpenXRBeginStatus::Ready);
|
||||
tracking_valid = false;
|
||||
Require(backend.PreparePacket(presentation, packet) == OpenXRBeginStatus::Ready);
|
||||
Require(!packet.expects_gpu_submission);
|
||||
tracking_valid = true;
|
||||
|
||||
for (bool session_change : {false, true}) {
|
||||
prepare();
|
||||
change_space = !session_change;
|
||||
restart_session = session_change;
|
||||
runtime.PollEvents();
|
||||
submit();
|
||||
Require(layer_count == 0); // Do not relabel old images with new space/session serials.
|
||||
prepare();
|
||||
submit();
|
||||
Require(layer_count == 1);
|
||||
}
|
||||
prepare();
|
||||
Complete();
|
||||
stop_session = true;
|
||||
runtime.PollEvents();
|
||||
Require(backend.BeginFrameForPacket(packet, frame) == OpenXRBeginStatus::SessionNotRunning);
|
||||
restart_session = true;
|
||||
runtime.PollEvents();
|
||||
prepare();
|
||||
submit();
|
||||
Require(layer_count == 1);
|
||||
|
||||
prepare();
|
||||
const auto before_failure = releases;
|
||||
Complete(false);
|
||||
Require(backend.WaitForSubmission(packet, 0) == OpenXRSubmissionStatus::Failed);
|
||||
Require(backend.BeginFrameForPacket(packet, frame) == OpenXRBeginStatus::Error);
|
||||
Require(releases == before_failure);
|
||||
Require(backend.Shutdown() && live_swapchains == 0);
|
||||
// Shutdown must also drain packets that never entered a compositor frame,
|
||||
// including a runtime end failure or session stop during a keep-alive.
|
||||
for (int scenario = 0; scenario < 3; ++scenario) {
|
||||
display_time = 0;
|
||||
OpenXRRuntime next_runtime;
|
||||
OpenXRD3D12Backend next_backend;
|
||||
Require(next_backend.QueryGraphicsRequirements(next_runtime));
|
||||
Require(next_backend.BindAurora(next_runtime));
|
||||
Require(next_backend.PreparePacket(presentation, packet) == OpenXRBeginStatus::Ready);
|
||||
encoded = true;
|
||||
if (scenario == 1) {
|
||||
fail_end = true;
|
||||
Require(next_backend.KeepAliveCycle() == OpenXRBeginStatus::Error);
|
||||
} else if (scenario == 2) {
|
||||
stop_session = true;
|
||||
next_runtime.PollEvents();
|
||||
Require(next_backend.KeepAliveCycle() == OpenXRBeginStatus::SessionNotRunning);
|
||||
}
|
||||
Require(!compositor_open);
|
||||
Require(next_backend.Shutdown() && live_swapchains == 0);
|
||||
}
|
||||
expect_render_first = false;
|
||||
display_time = 0;
|
||||
}
|
||||
|
||||
int main() {
|
||||
TestRenderFirst();
|
||||
OpenXRRuntime runtime;
|
||||
OpenXRD3D12Backend backend;
|
||||
Require(backend.QueryGraphicsRequirements(runtime) && backend.BindAurora(runtime));
|
||||
|
||||
Reference in new issue
Block a user