diff --git a/include/mocktail/vr/openxr_backend.h b/include/mocktail/vr/openxr_backend.h index 6408ffa..c3df817 100644 --- a/include/mocktail/vr/openxr_backend.h +++ b/include/mocktail/vr/openxr_backend.h @@ -350,8 +350,21 @@ class OpenXrBackend final { VkCommandBuffer command = VK_NULL_HANDLE; VkFence fence = VK_NULL_HANDLE; bool pending = false; + std::uint64_t serial = 0; // bumped per submission std::vector images; }; + // The shared-memory mirror's readback from the previous frame copy. It is + // published at the next copy, when the GPU has long finished it, so neither + // the headset nor the mirror makes the CPU wait for the frame. + struct MirrorReadback { + AsyncSubmission* slot = nullptr; + std::uint64_t serial = 0; + std::uint64_t frame = 0; + VkFormat format = VK_FORMAT_UNDEFINED; + std::uint32_t width = 0; + std::uint32_t height = 0; + }; + void PublishMirrorReadbackLocked(); // The next slot, recorded-ready, or nullptr when its previous submission // did not finish in time (the device is hung or lost). AsyncSubmission* NextAsyncSubmissionLocked(); @@ -454,6 +467,7 @@ class OpenXrBackend final { static constexpr std::size_t kAsyncSubmissions = 6; std::array async_{}; std::size_t next_async_ = 0; + MirrorReadback mirror_readback_{}; // The mirror's signal for this present, which PrepareHostPresent passes on. VkSemaphore mirror_ready_ = VK_NULL_HANDLE; // Semaphores of destroyed desktop swapchains. A present may still wait on diff --git a/src/vr/openxr_backend.cc b/src/vr/openxr_backend.cc index 9b5f8a0..f2f118c 100644 --- a/src/vr/openxr_backend.cc +++ b/src/vr/openxr_backend.cc @@ -3262,6 +3262,7 @@ bool OpenXrBackend::EnsureCopyResources(VkDevice device, void OpenXrBackend::DestroyCopyResources() { mirror_ready_ = VK_NULL_HANDLE; + mirror_readback_ = {}; next_async_ = 0; if (vk_ == nullptr || vk_device_ == VK_NULL_HANDLE) { copy_pool_ = VK_NULL_HANDLE; @@ -3319,6 +3320,7 @@ VkResult OpenXrBackend::SubmitAsyncLocked(VkQueue queue, AsyncSubmission *slot, slot->fence); } perf::ProcessCollector().Count(perf::Counter::kSubmits); + ++slot->serial; slot->pending = result == VK_SUCCESS; if (!slot->pending) slot->images.clear(); @@ -3406,6 +3408,8 @@ bool OpenXrBackend::CopyEyesIntoSwapchains(VkQueue queue) { const VkImage destination[2] = { eye_swapchains_[0].images[eye_swapchains_[0].acquired_index], eye_swapchains_[1].images[eye_swapchains_[1].acquired_index]}; + // Publish the last readback before its staging is resized or overwritten. + PublishMirrorReadbackLocked(); EnsureMirrorForExtent(eyes_[0].width, eyes_[0].height); AsyncSubmission *const slot = NextAsyncSubmissionLocked(); @@ -3538,29 +3542,32 @@ bool OpenXrBackend::CopyEyesIntoSwapchains(VkQueue queue) { if (SubmitAsyncLocked(queue, slot, submit) != VK_SUCCESS) { return false; } - if (mirror_.enabled()) { - // The shared-memory readback must land before it is published. - VkResult fence_result = VK_SUCCESS; - { - const ScopedStageTimer fence_timer(perf::Stage::kCopyFenceWait); - fence_result = vk_->WaitForFences(vk_device_, 1, &slot->fence, VK_TRUE, - 2'000'000'000ULL); - } - perf::ProcessCollector().Count(perf::Counter::kFenceWaits); - if (fence_result != VK_SUCCESS) { - (void)IdleHostDevice(vk_->DeviceWaitIdle, vk_device_); - throw std::runtime_error("XR copy fence wait failed"); - } - slot->pending = false; - slot->images.clear(); - const ScopedStageTimer publish_timer(perf::Stage::kMirrorPublish); - mirror_.Publish(published_pose_.frame, eyes_[0].format, eyes_[0].width, - eyes_[0].height, perf::NowNs()); - perf::ProcessCollector().Count(perf::Counter::kMirrorPairs); - } + if (mirror_.enabled()) + mirror_readback_ = {slot, slot->serial, published_pose_.frame, + eyes_[0].format, eyes_[0].width, eyes_[0].height}; return true; } +void OpenXrBackend::PublishMirrorReadbackLocked() { + const MirrorReadback readback = std::exchange(mirror_readback_, {}); + if (readback.slot == nullptr || !mirror_.enabled()) + return; + // A reused slot was waited for before reuse, so its readback landed. + if (readback.slot->serial == readback.serial && readback.slot->pending) { + const ScopedStageTimer fence_timer(perf::Stage::kCopyFenceWait); + perf::ProcessCollector().Count(perf::Counter::kFenceWaits); + if (vk_->WaitForFences(vk_device_, 1, &readback.slot->fence, VK_TRUE, + 2'000'000'000ULL) != VK_SUCCESS) { + perf::ProcessCollector().Count(perf::Counter::kMirrorDrops); + return; + } + } + const ScopedStageTimer publish_timer(perf::Stage::kMirrorPublish); + mirror_.Publish(readback.frame, readback.format, readback.width, + readback.height, perf::NowNs()); + perf::ProcessCollector().Count(perf::Counter::kMirrorPairs); +} + void OpenXrBackend::EnsureMirrorForExtent(std::uint32_t width, std::uint32_t height) { static const std::string name = EnvString("MOCKTAIL_VR_MIRROR_SHM", "");