mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 01:00:14 +02:00
Implement retired swapchain management for OpenXR backends: add logic to handle swapchain lifecycle and ensure proper resource cleanup
This commit is contained in:
1 parent
e89988f92e
commit
5af0bdbee6
7 files changed
+188
-50
No files matched your search
@@ -140,18 +140,26 @@ the left eye's size now and, while they differ, the size the slider's value give
|
|||||||
|
|
||||||
A new scale never interrupts the picture. Each backend keeps two swapchain pairs, one on display and
|
A new scale never interrupts the picture. Each backend keeps two swapchain pairs, one on display and
|
||||||
one Aurora writes next, and rebuilds only the second, at the start of the frame that writes it; the
|
one Aurora writes next, and rebuilds only the second, at the start of the frame that writes it; the
|
||||||
other follows a frame later, once it is the one written. The new
|
other follows a frame later, once it is the one written. The new swapchains are created before the
|
||||||
swapchains are created before the old ones go, and Aurora first forgets the old images: the D3D12
|
old ones go, and a replaced pair is not destroyed at once: the compositor may still be consuming the
|
||||||
bridge waits for any copy still writing them, the Windows Vulkan bridge drains Dawn's queue and drops
|
layer that last showed it (the spec lets a runtime use the images after `xrDestroySwapchain`, and
|
||||||
its wraps of the old `VkImage`s (the runtime may hand the same handles to the new swapchains), and
|
destroying a pair straight after its last frame lost the Vulkan device on a PC runtime), so it lives
|
||||||
the Quest also replaces its shared eye buffers, whose Dawn imports are released
|
on for `kOpenXRRetiredSwapchainCycles` (8) more pacing cycles, each of which ends another compositor
|
||||||
(`aurora_vulkan_forget_stereo_buffers`). Those buffers are kept as large as both pairs, growing with
|
frame, and goes with the session if it is still waiting then. Before a pair is destroyed Aurora
|
||||||
the first pair rebuilt larger and shrinking once the second has followed it down, and each copy moves
|
forgets its images: the D3D12 bridge waits for any copy still writing them, and the Windows Vulkan
|
||||||
only what fits the image it writes. If the runtime cannot allocate a size, the log says so, the eyes
|
bridge drains Dawn's queue and drops its wraps of the old `VkImage`s (the runtime may hand the same
|
||||||
keep the size they had (a pair already rebuilt goes back to it), and that size is not tried again
|
handles to the new swapchains); on the Quest the backend's own copy device is idled. The Windows
|
||||||
until the scale changes; the saved value is still what the next launch asks for.
|
Vulkan binding also takes Dawn's device guard around `xrCreateSwapchain` and `xrDestroySwapchain`,
|
||||||
`mkw_openxr_replay_tests` and `mkw_openxr_vulkan_replay_tests` cover the rebuild, the display pair
|
since mid-session Dawn's worker is submitting on the shared queue. The Quest also replaces its
|
||||||
left alone, the images forgotten before their swapchains are destroyed, and both kinds of refusal.
|
shared eye buffers, whose Dawn imports are released (`aurora_vulkan_forget_stereo_buffers`). Those
|
||||||
|
buffers are kept as large as both pairs, growing with the first pair rebuilt larger and shrinking
|
||||||
|
once the second has followed it down, and each copy moves only what fits the image it writes. If the
|
||||||
|
runtime cannot allocate a size, the log says so, the eyes keep the size they had (a pair already
|
||||||
|
rebuilt goes back to it), and that size is not tried again until the scale changes; the saved value
|
||||||
|
is still what the next launch asks for. `mkw_openxr_replay_tests` and
|
||||||
|
`mkw_openxr_vulkan_replay_tests` cover the rebuild, the display pair left alone, the replaced pairs
|
||||||
|
outliving their last frame by exactly that many cycles and going with the session otherwise, the
|
||||||
|
images forgotten before their swapchains are destroyed, and both kinds of refusal.
|
||||||
`world_units_per_meter` controls the scale of headset translation in the game world.
|
`world_units_per_meter` controls the scale of headset translation in the game world.
|
||||||
`hud_distance_meters` and `hud_width_meters` place and size the virtual screen. They are read at
|
`hud_distance_meters` and `hud_width_meters` place and size the virtual screen. They are read at
|
||||||
launch and govern both the menu screen and the in-race 2D screen, so 2D content keeps its place
|
launch and govern both the menu screen and the in-race 2D screen, so 2D content keeps its place
|
||||||
@@ -336,7 +344,11 @@ of you.
|
|||||||
|
|
||||||
How it is drawn: `settings_overlay.cpp` builds the panel with a second Dear ImGui context of its own,
|
How it is drawn: `settings_overlay.cpp` builds the panel with a second Dear ImGui context of its own,
|
||||||
a 1440 × 1080 canvas at twice the desktop menu's scale with its own font atlas, fed by the pointer
|
a 1440 × 1080 canvas at twice the desktop menu's scale with its own font atlas, fed by the pointer
|
||||||
that `openxr_input.cpp` publishes through `vr/openxr_settings_panel.h`. Aurora renders that draw data
|
that `openxr_input.cpp` publishes through `vr/openxr_settings_panel.h`. That context shares the
|
||||||
|
desktop context's renderer backend data: ImGui's current context is one process-wide pointer, and
|
||||||
|
Aurora's frame worker renders draw data while the game thread may have switched to the panel
|
||||||
|
context, so the WebGPU backend has to find its device objects through either (without this, a
|
||||||
|
worker render during the switch crashed on a null backend, seen while dragging the panel's sliders). Aurora renders that draw data
|
||||||
into a panel texture once per sealed frame (`aurora-main/lib/stereo_overlay.cpp`). The ImGui backend
|
into a panel texture once per sealed frame (`aurora-main/lib/stereo_overlay.cpp`). The ImGui backend
|
||||||
keeps a single projection uniform, so the panel's pass is submitted on its own command buffer before
|
keeps a single projection uniform, so the panel's pass is submitted on its own command buffer before
|
||||||
the desktop's ImGui pass of the same frame is recorded.
|
the desktop's ImGui pass of the same frame is recorded.
|
||||||
|
|||||||
@@ -65,6 +65,16 @@ struct OpenXRPanelLayer {
|
|||||||
inline constexpr uint32_t kOpenXRPanelLayerWidth = static_cast<uint32_t>(kSettingsPanelWidthPixels);
|
inline constexpr uint32_t kOpenXRPanelLayerWidth = static_cast<uint32_t>(kSettingsPanelWidthPixels);
|
||||||
inline constexpr uint32_t kOpenXRPanelLayerHeight = static_cast<uint32_t>(kSettingsPanelHeightPixels);
|
inline constexpr uint32_t kOpenXRPanelLayerHeight = static_cast<uint32_t>(kSettingsPanelHeightPixels);
|
||||||
|
|
||||||
|
// How many more pacing cycles an eye swapchain pair replaced by a new render
|
||||||
|
// resolution lives on before it is destroyed. The pair was last shown by the
|
||||||
|
// xrEndFrame of the cycle before its replacement, and the compositor keeps
|
||||||
|
// reading that layer's images until a later one lands and its own GPU work on
|
||||||
|
// them is done; the spec allows a runtime to use the images after
|
||||||
|
// xrDestroySwapchain, but destroying a pair the compositor was still consuming
|
||||||
|
// lost the Vulkan device on a PC runtime. Each cycle ends a compositor frame,
|
||||||
|
// so this many later frames have replaced the pair's picture by then.
|
||||||
|
inline constexpr uint32_t kOpenXRRetiredSwapchainCycles = 8;
|
||||||
|
|
||||||
// The panel's layer, submitted after (so over) the scene's.
|
// The panel's layer, submitted after (so over) the scene's.
|
||||||
inline XrCompositionLayerQuad OpenXRPanelQuadLayer(const OpenXRPanelLayer& panel, XrSpace space,
|
inline XrCompositionLayerQuad OpenXRPanelQuadLayer(const OpenXRPanelLayer& panel, XrSpace space,
|
||||||
XrSwapchain swapchain) noexcept {
|
XrSwapchain swapchain) noexcept {
|
||||||
|
|||||||
@@ -2261,11 +2261,20 @@ VrSettingsPanel g_vrSettingsPanel;
|
|||||||
|
|
||||||
ImGuiContext* CreateVrSettingsPanelContext() {
|
ImGuiContext* CreateVrSettingsPanelContext() {
|
||||||
ImGuiContext* const desktop = ImGui::GetCurrentContext();
|
ImGuiContext* const desktop = ImGui::GetCurrentContext();
|
||||||
|
const ImGuiIO& desktopIo = ImGui::GetIO();
|
||||||
ImGuiContext* const context = ImGui::CreateContext();
|
ImGuiContext* const context = ImGui::CreateContext();
|
||||||
ImGui::SetCurrentContext(context);
|
ImGui::SetCurrentContext(context);
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
io.IniFilename = nullptr;
|
io.IniFilename = nullptr;
|
||||||
io.LogFilename = nullptr;
|
io.LogFilename = nullptr;
|
||||||
|
// ImGui's current context is one process-wide pointer, and Aurora's frame
|
||||||
|
// worker renders draw data (the desktop overlay, this panel) while this
|
||||||
|
// thread may have switched to this context to draw the panel. The WebGPU
|
||||||
|
// backend finds its device objects through the current context's IO, so
|
||||||
|
// this context carries the desktop's: without them, a render on the worker
|
||||||
|
// during the switch reads through a null backend.
|
||||||
|
io.BackendRendererUserData = desktopIo.BackendRendererUserData;
|
||||||
|
io.BackendRendererName = desktopIo.BackendRendererName;
|
||||||
// No platform backend draws a cursor for it, and nothing else shows where
|
// No platform backend draws a cursor for it, and nothing else shows where
|
||||||
// the controller is aiming.
|
// the controller is aiming.
|
||||||
io.MouseDrawCursor = true;
|
io.MouseDrawCursor = true;
|
||||||
|
|||||||
@@ -851,6 +851,7 @@ private:
|
|||||||
// before the old ones go, so a size the runtime cannot allocate leaves the pair as it was.
|
// before the old ones go, so a size the runtime cannot allocate leaves the pair as it was.
|
||||||
// False only on a failure that ends the session.
|
// False only on a failure that ends the session.
|
||||||
bool ResizeWritablePair() {
|
bool ResizeWritablePair() {
|
||||||
|
ReapRetiredPairs(false);
|
||||||
std::array<OpenXREyeSize, kOpenXREyeCount> current{};
|
std::array<OpenXREyeSize, kOpenXREyeCount> current{};
|
||||||
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
if (eye_swapchains_[eye].acquired) {
|
if (eye_swapchains_[eye].acquired) {
|
||||||
@@ -874,17 +875,8 @@ private:
|
|||||||
eye_size_ = current;
|
eye_size_ = current;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
std::vector<void*> retired;
|
// The compositor may still be reading the old pair (kOpenXRRetiredSwapchainCycles).
|
||||||
for (const EyeSwapchain& swapchain : eye_swapchains_) {
|
retired_pairs_.push_back({std::move(eye_swapchains_), kOpenXRRetiredSwapchainCycles});
|
||||||
for (const auto& image : swapchain.images) {
|
|
||||||
retired.push_back(image.texture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!aurora_d3d12_forget_stereo_targets(retired.data(), static_cast<uint32_t>(retired.size()))) {
|
|
||||||
DestroySwapchainPair(replacement);
|
|
||||||
return Fail("Aurora could not retire its copies into the old D3D12 eye swapchains");
|
|
||||||
}
|
|
||||||
DestroySwapchainPair(eye_swapchains_);
|
|
||||||
eye_swapchains_ = std::move(replacement);
|
eye_swapchains_ = std::move(replacement);
|
||||||
std::ostringstream message;
|
std::ostringstream message;
|
||||||
message << "OpenXR D3D12 eyes resized: " << eye_size_[0].width << 'x' << eye_size_[0].height << " / "
|
message << "OpenXR D3D12 eyes resized: " << eye_size_[0].width << 'x' << eye_size_[0].height << " / "
|
||||||
@@ -893,6 +885,32 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroys the retired pairs whose time is up (every one of them at shutdown), once Aurora's
|
||||||
|
// copies into their images are known complete. A pair Aurora cannot vouch for is left to
|
||||||
|
// xrDestroySession rather than destroyed under a possibly live copy.
|
||||||
|
void ReapRetiredPairs(bool all) {
|
||||||
|
for (auto it = retired_pairs_.begin(); it != retired_pairs_.end();) {
|
||||||
|
if (!all && --it->cycles_left != 0) {
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::vector<void*> images;
|
||||||
|
for (const EyeSwapchain& swapchain : it->swapchains) {
|
||||||
|
for (const auto& image : swapchain.images) {
|
||||||
|
images.push_back(image.texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (aurora_d3d12_forget_stereo_targets(images.data(), static_cast<uint32_t>(images.size()))) {
|
||||||
|
DestroySwapchainPair(it->swapchains);
|
||||||
|
} else {
|
||||||
|
Log(OpenXRLogLevel::Warning,
|
||||||
|
"Aurora could not retire its copies into a replaced D3D12 eye swapchain pair; "
|
||||||
|
"deferring its destruction to xrDestroySession");
|
||||||
|
}
|
||||||
|
it = retired_pairs_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool CreateSwapchain(EyeSwapchain& swapchain, uint32_t width, uint32_t height, const char* what) {
|
bool CreateSwapchain(EyeSwapchain& swapchain, uint32_t width, uint32_t height, const char* what) {
|
||||||
swapchain.width = width;
|
swapchain.width = width;
|
||||||
swapchain.height = height;
|
swapchain.height = height;
|
||||||
@@ -1058,6 +1076,7 @@ private:
|
|||||||
|
|
||||||
void DestroySwapchains() {
|
void DestroySwapchains() {
|
||||||
DestroyPanelSwapchains();
|
DestroyPanelSwapchains();
|
||||||
|
ReapRetiredPairs(true);
|
||||||
DestroySwapchainPair(eye_swapchains_);
|
DestroySwapchainPair(eye_swapchains_);
|
||||||
DestroySwapchainPair(retained_swapchains_);
|
DestroySwapchainPair(retained_swapchains_);
|
||||||
have_retained_frame_ = false;
|
have_retained_frame_ = false;
|
||||||
@@ -1137,6 +1156,12 @@ private:
|
|||||||
// for, which differ while a size the runtime refused is being kept.
|
// for, which differ while a size the runtime refused is being kept.
|
||||||
std::array<OpenXREyeSize, kOpenXREyeCount> eye_size_{};
|
std::array<OpenXREyeSize, kOpenXREyeCount> eye_size_{};
|
||||||
std::array<OpenXREyeSize, kOpenXREyeCount> requested_eye_size_{};
|
std::array<OpenXREyeSize, kOpenXREyeCount> requested_eye_size_{};
|
||||||
|
// Pairs replaced by a new size, destroyed once the compositor has moved on from them.
|
||||||
|
struct RetiredPair {
|
||||||
|
std::array<EyeSwapchain, kOpenXREyeCount> swapchains;
|
||||||
|
uint32_t cycles_left;
|
||||||
|
};
|
||||||
|
std::vector<RetiredPair> retired_pairs_;
|
||||||
// The settings panel's layer: written like the eyes into panel_swapchain_,
|
// The settings panel's layer: written like the eyes into panel_swapchain_,
|
||||||
// shown from retained_panel_swapchain_ (see FinishFrame).
|
// shown from retained_panel_swapchain_ (see FinishFrame).
|
||||||
EyeSwapchain panel_swapchain_{};
|
EyeSwapchain panel_swapchain_{};
|
||||||
|
|||||||
@@ -1848,6 +1848,7 @@ private:
|
|||||||
// writes). Everything new is allocated before anything old goes, so a size that cannot be had
|
// writes). Everything new is allocated before anything old goes, so a size that cannot be had
|
||||||
// leaves the eyes as they were.
|
// leaves the eyes as they were.
|
||||||
bool ResizeWritablePair() {
|
bool ResizeWritablePair() {
|
||||||
|
ReapRetiredPairs(false);
|
||||||
std::array<OpenXREyeSize, kOpenXREyeCount> current{};
|
std::array<OpenXREyeSize, kOpenXREyeCount> current{};
|
||||||
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
if (eye_swapchains_[eye].acquired) {
|
if (eye_swapchains_[eye].acquired) {
|
||||||
@@ -1945,7 +1946,8 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DestroySwapchainPair(eye_swapchains_);
|
// The compositor may still be reading the old pair (kOpenXRRetiredSwapchainCycles).
|
||||||
|
retired_pairs_.push_back({std::move(eye_swapchains_), kOpenXRRetiredSwapchainCycles});
|
||||||
eye_swapchains_ = std::move(replacement);
|
eye_swapchains_ = std::move(replacement);
|
||||||
std::ostringstream message;
|
std::ostringstream message;
|
||||||
message << "OpenXR Vulkan eyes resized: " << eye_size_[0].width << 'x' << eye_size_[0].height << " / "
|
message << "OpenXR Vulkan eyes resized: " << eye_size_[0].width << 'x' << eye_size_[0].height << " / "
|
||||||
@@ -1957,6 +1959,31 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// As the D3D12 backend's. Only this device's copies write the images, so its idle is the
|
||||||
|
// completion the spec asks for before xrDestroySwapchain.
|
||||||
|
void ReapRetiredPairs(bool all) {
|
||||||
|
bool waited = false;
|
||||||
|
for (auto it = retired_pairs_.begin(); it != retired_pairs_.end();) {
|
||||||
|
if (!all && --it->cycles_left != 0) {
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!waited && vk_device_ != VK_NULL_HANDLE) {
|
||||||
|
std::lock_guard lock(vk_mutex_);
|
||||||
|
waited = vkDeviceWaitIdle(vk_device_) == VK_SUCCESS;
|
||||||
|
if (!waited) {
|
||||||
|
Log(OpenXRLogLevel::Warning,
|
||||||
|
"vkDeviceWaitIdle failed before destroying replaced Vulkan eye swapchains; "
|
||||||
|
"deferring their destruction to xrDestroySession");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (waited || vk_device_ == VK_NULL_HANDLE) {
|
||||||
|
DestroySwapchainPair(it->swapchains);
|
||||||
|
}
|
||||||
|
it = retired_pairs_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool CreateSwapchain(EyeSwapchain& swapchain, uint32_t width, uint32_t height, const char* what) {
|
bool CreateSwapchain(EyeSwapchain& swapchain, uint32_t width, uint32_t height, const char* what) {
|
||||||
swapchain.width = width;
|
swapchain.width = width;
|
||||||
swapchain.height = height;
|
swapchain.height = height;
|
||||||
@@ -2115,6 +2142,7 @@ private:
|
|||||||
|
|
||||||
void DestroySwapchains() {
|
void DestroySwapchains() {
|
||||||
DestroyPanelSwapchains();
|
DestroyPanelSwapchains();
|
||||||
|
ReapRetiredPairs(true);
|
||||||
DestroySwapchainPair(eye_swapchains_);
|
DestroySwapchainPair(eye_swapchains_);
|
||||||
DestroySwapchainPair(retained_swapchains_);
|
DestroySwapchainPair(retained_swapchains_);
|
||||||
have_retained_frame_ = false;
|
have_retained_frame_ = false;
|
||||||
@@ -2177,6 +2205,11 @@ private:
|
|||||||
// As the D3D12 backend's.
|
// As the D3D12 backend's.
|
||||||
std::array<OpenXREyeSize, kOpenXREyeCount> eye_size_{};
|
std::array<OpenXREyeSize, kOpenXREyeCount> eye_size_{};
|
||||||
std::array<OpenXREyeSize, kOpenXREyeCount> requested_eye_size_{};
|
std::array<OpenXREyeSize, kOpenXREyeCount> requested_eye_size_{};
|
||||||
|
struct RetiredPair {
|
||||||
|
std::array<EyeSwapchain, kOpenXREyeCount> swapchains;
|
||||||
|
uint32_t cycles_left;
|
||||||
|
};
|
||||||
|
std::vector<RetiredPair> retired_pairs_;
|
||||||
// The settings panel's layer: written like the eyes into panel_swapchain_,
|
// The settings panel's layer: written like the eyes into panel_swapchain_,
|
||||||
// shown from retained_panel_swapchain_ (see FinishFrame).
|
// shown from retained_panel_swapchain_ (see FinishFrame).
|
||||||
EyeSwapchain panel_swapchain_{};
|
EyeSwapchain panel_swapchain_{};
|
||||||
|
|||||||
@@ -876,6 +876,7 @@ private:
|
|||||||
// As the D3D12 backend's. Aurora's bridge wraps each swapchain VkImage for Dawn, and the
|
// As the D3D12 backend's. Aurora's bridge wraps each swapchain VkImage for Dawn, and the
|
||||||
// runtime may hand the old handles out again, so the wraps go with the swapchains.
|
// runtime may hand the old handles out again, so the wraps go with the swapchains.
|
||||||
bool ResizeWritablePair() {
|
bool ResizeWritablePair() {
|
||||||
|
ReapRetiredPairs(false);
|
||||||
std::array<OpenXREyeSize, kOpenXREyeCount> current{};
|
std::array<OpenXREyeSize, kOpenXREyeCount> current{};
|
||||||
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
if (eye_swapchains_[eye].acquired) {
|
if (eye_swapchains_[eye].acquired) {
|
||||||
@@ -899,17 +900,8 @@ private:
|
|||||||
eye_size_ = current;
|
eye_size_ = current;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
std::vector<void*> retired;
|
// The compositor may still be reading the old pair (kOpenXRRetiredSwapchainCycles).
|
||||||
for (const EyeSwapchain& swapchain : eye_swapchains_) {
|
retired_pairs_.push_back({std::move(eye_swapchains_), kOpenXRRetiredSwapchainCycles});
|
||||||
for (const auto& image : swapchain.images) {
|
|
||||||
retired.push_back(reinterpret_cast<void*>(image.image));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!aurora_vulkan_win32_forget_targets(retired.data(), static_cast<uint32_t>(retired.size()))) {
|
|
||||||
DestroySwapchainPair(replacement);
|
|
||||||
return Fail("Aurora could not retire its copies into the old Vulkan eye swapchains");
|
|
||||||
}
|
|
||||||
DestroySwapchainPair(eye_swapchains_);
|
|
||||||
eye_swapchains_ = std::move(replacement);
|
eye_swapchains_ = std::move(replacement);
|
||||||
std::ostringstream message;
|
std::ostringstream message;
|
||||||
message << "OpenXR Vulkan eyes resized: " << eye_size_[0].width << 'x' << eye_size_[0].height << " / "
|
message << "OpenXR Vulkan eyes resized: " << eye_size_[0].width << 'x' << eye_size_[0].height << " / "
|
||||||
@@ -918,6 +910,31 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// As the D3D12 backend's: Aurora drains Dawn's queue and drops its wraps of the images (which
|
||||||
|
// it does under Dawn's device guard, so before the guard is taken to destroy them).
|
||||||
|
void ReapRetiredPairs(bool all) {
|
||||||
|
for (auto it = retired_pairs_.begin(); it != retired_pairs_.end();) {
|
||||||
|
if (!all && --it->cycles_left != 0) {
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::vector<void*> images;
|
||||||
|
for (const EyeSwapchain& swapchain : it->swapchains) {
|
||||||
|
for (const auto& image : swapchain.images) {
|
||||||
|
images.push_back(reinterpret_cast<void*>(image.image));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (aurora_vulkan_win32_forget_targets(images.data(), static_cast<uint32_t>(images.size()))) {
|
||||||
|
DestroySwapchainPair(it->swapchains);
|
||||||
|
} else {
|
||||||
|
Log(OpenXRLogLevel::Warning,
|
||||||
|
"Aurora could not retire its copies into a replaced Vulkan eye swapchain pair; "
|
||||||
|
"deferring its destruction to xrDestroySession");
|
||||||
|
}
|
||||||
|
it = retired_pairs_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool CreateSwapchain(EyeSwapchain& swapchain, uint32_t width, uint32_t height, const char* what) {
|
bool CreateSwapchain(EyeSwapchain& swapchain, uint32_t width, uint32_t height, const char* what) {
|
||||||
swapchain.width = width;
|
swapchain.width = width;
|
||||||
swapchain.height = height;
|
swapchain.height = height;
|
||||||
@@ -936,7 +953,14 @@ private:
|
|||||||
create.faceCount = 1;
|
create.faceCount = 1;
|
||||||
create.arraySize = 1;
|
create.arraySize = 1;
|
||||||
create.mipCount = 1;
|
create.mipCount = 1;
|
||||||
XrResult result = xrCreateSwapchain(runtime_->Session(), &create, &swapchain.handle);
|
XrResult result;
|
||||||
|
{
|
||||||
|
// The spec keeps the runtime off the VkQueue here, but mid-session (the panel's
|
||||||
|
// swapchains, a new render resolution) Dawn's worker is submitting on it, and a
|
||||||
|
// runtime that transitions its new images would race that; the guard costs nothing.
|
||||||
|
const auto queue_guard = runtime_->LockGraphicsQueue();
|
||||||
|
result = xrCreateSwapchain(runtime_->Session(), &create, &swapchain.handle);
|
||||||
|
}
|
||||||
ObserveResult(result);
|
ObserveResult(result);
|
||||||
if (XR_FAILED(result)) {
|
if (XR_FAILED(result)) {
|
||||||
std::ostringstream message;
|
std::ostringstream message;
|
||||||
@@ -1075,6 +1099,7 @@ private:
|
|||||||
void DestroyPanelSwapchains() {
|
void DestroyPanelSwapchains() {
|
||||||
for (auto* swapchain : {&panel_swapchain_, &retained_panel_swapchain_}) {
|
for (auto* swapchain : {&panel_swapchain_, &retained_panel_swapchain_}) {
|
||||||
if (swapchain->handle != XR_NULL_HANDLE && !swapchain->acquired) {
|
if (swapchain->handle != XR_NULL_HANDLE && !swapchain->acquired) {
|
||||||
|
const auto queue_guard = runtime_ ? runtime_->LockGraphicsQueue() : OpenXRRuntime::GraphicsQueueGuard{nullptr, nullptr};
|
||||||
xrDestroySwapchain(swapchain->handle);
|
xrDestroySwapchain(swapchain->handle);
|
||||||
} else if (swapchain->acquired) {
|
} else if (swapchain->acquired) {
|
||||||
Log(OpenXRLogLevel::Warning,
|
Log(OpenXRLogLevel::Warning,
|
||||||
@@ -1088,6 +1113,7 @@ private:
|
|||||||
|
|
||||||
void DestroySwapchains() {
|
void DestroySwapchains() {
|
||||||
DestroyPanelSwapchains();
|
DestroyPanelSwapchains();
|
||||||
|
ReapRetiredPairs(true);
|
||||||
DestroySwapchainPair(eye_swapchains_);
|
DestroySwapchainPair(eye_swapchains_);
|
||||||
DestroySwapchainPair(retained_swapchains_);
|
DestroySwapchainPair(retained_swapchains_);
|
||||||
have_retained_frame_ = false;
|
have_retained_frame_ = false;
|
||||||
@@ -1098,6 +1124,7 @@ private:
|
|||||||
void DestroySwapchainPair(std::array<EyeSwapchain, kOpenXREyeCount>& pair) {
|
void DestroySwapchainPair(std::array<EyeSwapchain, kOpenXREyeCount>& pair) {
|
||||||
for (auto& swapchain : pair) {
|
for (auto& swapchain : pair) {
|
||||||
if (swapchain.handle != XR_NULL_HANDLE && !swapchain.acquired) {
|
if (swapchain.handle != XR_NULL_HANDLE && !swapchain.acquired) {
|
||||||
|
const auto queue_guard = runtime_ ? runtime_->LockGraphicsQueue() : OpenXRRuntime::GraphicsQueueGuard{nullptr, nullptr};
|
||||||
xrDestroySwapchain(swapchain.handle);
|
xrDestroySwapchain(swapchain.handle);
|
||||||
} else if (swapchain.acquired) {
|
} else if (swapchain.acquired) {
|
||||||
Log(OpenXRLogLevel::Warning,
|
Log(OpenXRLogLevel::Warning,
|
||||||
@@ -1173,6 +1200,11 @@ private:
|
|||||||
// As the D3D12 backend's.
|
// As the D3D12 backend's.
|
||||||
std::array<OpenXREyeSize, kOpenXREyeCount> eye_size_{};
|
std::array<OpenXREyeSize, kOpenXREyeCount> eye_size_{};
|
||||||
std::array<OpenXREyeSize, kOpenXREyeCount> requested_eye_size_{};
|
std::array<OpenXREyeSize, kOpenXREyeCount> requested_eye_size_{};
|
||||||
|
struct RetiredPair {
|
||||||
|
std::array<EyeSwapchain, kOpenXREyeCount> swapchains;
|
||||||
|
uint32_t cycles_left;
|
||||||
|
};
|
||||||
|
std::vector<RetiredPair> retired_pairs_;
|
||||||
// The settings panel's layer: written like the eyes into panel_swapchain_,
|
// The settings panel's layer: written like the eyes into panel_swapchain_,
|
||||||
// shown from retained_panel_swapchain_ (see FinishFrame).
|
// shown from retained_panel_swapchain_ (see FinishFrame).
|
||||||
EyeSwapchain panel_swapchain_{};
|
EyeSwapchain panel_swapchain_{};
|
||||||
|
|||||||
@@ -635,7 +635,6 @@ void TestRenderScale() {
|
|||||||
Require(backend.BeginFrame(presentation, frame) == OpenXRBeginStatus::Ready);
|
Require(backend.BeginFrame(presentation, frame) == OpenXRBeginStatus::Ready);
|
||||||
Require(frame.render_width[0] == width && frame.render_height[1] == height && targets.size() == 2);
|
Require(frame.render_width[0] == width && frame.render_height[1] == height && targets.size() == 2);
|
||||||
for (const auto& target : targets) Require(target.width == width && target.height == height);
|
for (const auto& target : targets) Require(target.width == width && target.height == height);
|
||||||
Require(live_swapchains == 4);
|
|
||||||
};
|
};
|
||||||
const auto finish = [&] {
|
const auto finish = [&] {
|
||||||
Complete();
|
Complete();
|
||||||
@@ -643,35 +642,49 @@ void TestRenderScale() {
|
|||||||
Require(backend.FinishFrame(frame, true));
|
Require(backend.FinishFrame(frame, true));
|
||||||
Require(layer_count == 1 && displayed_content == frame.xr_frame.serial);
|
Require(layer_count == 1 && displayed_content == frame.xr_frame.serial);
|
||||||
};
|
};
|
||||||
|
// Enough cycles at one size for every replaced pair to be destroyed.
|
||||||
|
const auto settle = [&](uint32_t width, uint32_t height) {
|
||||||
|
for (uint32_t i = 0; i <= kOpenXRRetiredSwapchainCycles; ++i) {
|
||||||
|
begin(width, height);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
Require(live_swapchains == 4);
|
||||||
|
};
|
||||||
|
|
||||||
backend.SetRenderScale(1.0f); // The session's own scale rebuilds nothing.
|
backend.SetRenderScale(1.0f); // The session's own scale rebuilds nothing.
|
||||||
begin(100, 80);
|
begin(100, 80);
|
||||||
finish();
|
finish();
|
||||||
Require(forgotten.empty() && shown_width == 100);
|
Require(forgotten.empty() && live_swapchains == 4 && shown_width == 100);
|
||||||
|
|
||||||
backend.SetRenderScale(1.5f);
|
backend.SetRenderScale(1.5f);
|
||||||
begin(150, 120); // The pair written next, while the one on display still shows the last frame.
|
begin(150, 120); // The pair written next, while the one on display still shows the last frame.
|
||||||
Require(forgotten.size() == 2);
|
Require(live_swapchains == 6 && forgotten.empty()); // The old pair lives on for the compositor.
|
||||||
Require(backend.RepeatFrame(frame) && layer_count == 1 && shown_width == 100);
|
Require(backend.RepeatFrame(frame) && layer_count == 1 && shown_width == 100);
|
||||||
finish();
|
finish();
|
||||||
Require(shown_width == 150);
|
Require(shown_width == 150);
|
||||||
begin(150, 120); // Then the other pair, once it is the one written.
|
begin(150, 120); // Then the other pair, once it is the one written.
|
||||||
Require(forgotten.size() == 4);
|
Require(live_swapchains == 8 && forgotten.empty());
|
||||||
finish();
|
finish();
|
||||||
begin(150, 120); // Both are the new size: nothing more is rebuilt.
|
// Each replaced pair is forgotten by Aurora, then destroyed, kOpenXRRetiredSwapchainCycles
|
||||||
|
// cycles after the one that replaced it: the first pair one cycle before the second.
|
||||||
|
for (uint32_t i = 1; i < kOpenXRRetiredSwapchainCycles; ++i) {
|
||||||
|
begin(150, 120); // Both are the new size: nothing more is rebuilt.
|
||||||
|
Require(live_swapchains == (i < kOpenXRRetiredSwapchainCycles - 1 ? 8u : 6u));
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
Require(forgotten.size() == 2);
|
||||||
|
begin(150, 120);
|
||||||
|
Require(live_swapchains == 4 && forgotten.size() == 4);
|
||||||
finish();
|
finish();
|
||||||
Require(forgotten.size() == 4);
|
|
||||||
|
|
||||||
backend.SetRenderScale(5.0f); // Clamped to the runtime's maximum.
|
backend.SetRenderScale(5.0f); // Clamped to the runtime's maximum.
|
||||||
begin(400, 320);
|
begin(400, 320);
|
||||||
finish();
|
finish();
|
||||||
begin(400, 320);
|
settle(400, 320);
|
||||||
finish();
|
|
||||||
backend.SetRenderScale(1.5f);
|
backend.SetRenderScale(1.5f);
|
||||||
begin(150, 120);
|
begin(150, 120);
|
||||||
finish();
|
finish();
|
||||||
begin(150, 120);
|
settle(150, 120);
|
||||||
finish();
|
|
||||||
const size_t forgotten_before_refusal = forgotten.size();
|
const size_t forgotten_before_refusal = forgotten.size();
|
||||||
|
|
||||||
// A size the runtime cannot make keeps the eyes, and is not retried while it is still asked for.
|
// A size the runtime cannot make keeps the eyes, and is not retried while it is still asked for.
|
||||||
@@ -683,13 +696,14 @@ void TestRenderScale() {
|
|||||||
backend.SetRenderScale(3.0f);
|
backend.SetRenderScale(3.0f);
|
||||||
begin(150, 120);
|
begin(150, 120);
|
||||||
finish();
|
finish();
|
||||||
Require(create_attempts == attempts + 2 && forgotten.size() == forgotten_before_refusal);
|
Require(create_attempts == attempts + 2 && live_swapchains == 4);
|
||||||
|
settle(150, 120);
|
||||||
|
Require(forgotten.size() == forgotten_before_refusal);
|
||||||
creates_before_failure = -1;
|
creates_before_failure = -1;
|
||||||
backend.SetRenderScale(0.5f); // A new size is tried again.
|
backend.SetRenderScale(0.5f); // A new size is tried again.
|
||||||
begin(50, 40);
|
begin(50, 40);
|
||||||
finish();
|
finish();
|
||||||
begin(50, 40);
|
settle(50, 40);
|
||||||
finish();
|
|
||||||
|
|
||||||
// Refused for the second pair only: the first follows it back to the size it kept.
|
// Refused for the second pair only: the first follows it back to the size it kept.
|
||||||
creates_before_failure = 2;
|
creates_before_failure = 2;
|
||||||
@@ -703,6 +717,7 @@ void TestRenderScale() {
|
|||||||
creates_before_failure = -1;
|
creates_before_failure = -1;
|
||||||
begin(50, 40);
|
begin(50, 40);
|
||||||
finish();
|
finish();
|
||||||
|
settle(50, 40);
|
||||||
const size_t forgotten_settled = forgotten.size();
|
const size_t forgotten_settled = forgotten.size();
|
||||||
begin(50, 40);
|
begin(50, 40);
|
||||||
finish();
|
finish();
|
||||||
@@ -721,6 +736,8 @@ void TestRenderScale() {
|
|||||||
Require(backend.FinishFrame(frame, true));
|
Require(backend.FinishFrame(frame, true));
|
||||||
Require(layer_count == 1 && shown_width == 100);
|
Require(layer_count == 1 && shown_width == 100);
|
||||||
expect_render_first = false;
|
expect_render_first = false;
|
||||||
|
// A pair still retired at shutdown goes with the rest, forgotten first.
|
||||||
|
Require(live_swapchains == 6);
|
||||||
Require(backend.Shutdown() && live_swapchains == 0);
|
Require(backend.Shutdown() && live_swapchains == 0);
|
||||||
display_time = 0;
|
display_time = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user