mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 05:00:27 +02:00
Implement render scale adjustments for OpenXR D3D12 and Vulkan backends
- Added SetRenderScale method to both D3D12 and Vulkan backends to adjust the render scale dynamically. - Updated eye size calculations to reflect the new render scale in both backends. - Implemented ResizeWritablePair method to handle resizing of swapchains based on the requested eye sizes. - Modified BeginFrame methods to ensure swapchains are resized appropriately before rendering. - Enhanced error handling to maintain current eye sizes when the requested sizes cannot be allocated. - Added tests to validate render scale functionality, ensuring correct behavior when scaling up and down, including handling of refused sizes.
This commit is contained in:
1 parent
f013f9d2f7
commit
e4d198ee13
22 files changed
+779
-48
No files matched your search
@@ -135,8 +135,26 @@ The old Eager Frame Heartbeat option has been removed and existing `eager_frame_
|
|||||||
settings are ignored. Completed rendering wakes the XR thread immediately. A 50 ms keep-alive
|
settings are ignored. Completed rendering wakes the XR thread immediately. A 50 ms keep-alive
|
||||||
still protects pauses and window dragging without eager repeats during rendering.
|
still protects pauses and window dragging without eager repeats during rendering.
|
||||||
|
|
||||||
`render_scale` scales the per-eye size recommended by the OpenXR runtime. It defaults to 1.0 on PC
|
`render_scale` scales the per-eye size recommended by the OpenXR runtime (0.25 to 2, never above the
|
||||||
and 0.8 on the Quest, whose mobile GPU needs the headroom.
|
runtime's maximum). It defaults to 1.0 on PC and 0.8 on the Quest, whose mobile GPU needs the
|
||||||
|
headroom. It is live: **F10 → VR → Render resolution** (also on the headset panel's VR tab) sets it
|
||||||
|
in percent, applies it when the slider is let go, and saves it. Below the slider, *Each eye* gives
|
||||||
|
the left eye's size now and, while they differ, the size the slider's value gives.
|
||||||
|
|
||||||
|
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
|
||||||
|
other follows a frame later, once it is the one written. The new
|
||||||
|
swapchains are created before the old ones go, and Aurora first forgets the old images: the D3D12
|
||||||
|
bridge waits for any copy still writing them, the Windows Vulkan bridge drains Dawn's queue and drops
|
||||||
|
its wraps of the old `VkImage`s (the runtime may hand the same handles to the new swapchains), and
|
||||||
|
the Quest also replaces its 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 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
|
||||||
|
|||||||
@@ -84,6 +84,15 @@ bool aurora_d3d12_set_stereo_targets_with_panel(uint64_t frameToken,
|
|||||||
*/
|
*/
|
||||||
bool aurora_d3d12_cancel_stereo_targets(uint64_t frameToken);
|
bool aurora_d3d12_cancel_stereo_targets(uint64_t frameToken);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The host is about to destroy these target resources (the eye swapchains, when
|
||||||
|
* the render resolution changes): returns once no copy the bridge queued can
|
||||||
|
* still write them, waiting for the queue if it has to. Call with no frame
|
||||||
|
* token pending. False when the queue could not be fenced, in which case the
|
||||||
|
* resources must be kept.
|
||||||
|
*/
|
||||||
|
bool aurora_d3d12_forget_stereo_targets(void* const* resources, uint32_t count);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the sink and drains bridge resources. The worker must be idle. Returns
|
* Removes the sink and drains bridge resources. The worker must be idle. Returns
|
||||||
* false when queued work cannot be fenced; in that case the bridge is retained
|
* false when queued work cannot be fenced; in that case the bridge is retained
|
||||||
|
|||||||
@@ -132,6 +132,15 @@ bool aurora_vulkan_set_stereo_targets_with_panel(uint64_t frameToken,
|
|||||||
*/
|
*/
|
||||||
bool aurora_vulkan_cancel_stereo_targets(uint64_t frameToken);
|
bool aurora_vulkan_cancel_stereo_targets(uint64_t frameToken);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases Dawn's imports of buffers the caller is about to free (the eyes'
|
||||||
|
* ring, when the render resolution changes), which would otherwise keep every
|
||||||
|
* old buffer alive for the bridge's lifetime. Dawn's own reference to each
|
||||||
|
* buffer lasts until its queue is done with it, so the caller need not wait for
|
||||||
|
* Dawn. Call with no frame token pending; false if one is, releasing nothing.
|
||||||
|
*/
|
||||||
|
bool aurora_vulkan_forget_stereo_buffers(struct AHardwareBuffer* const* buffers, uint32_t count);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the sink and releases the cached Dawn imports. The worker must be
|
* Removes the sink and releases the cached Dawn imports. The worker must be
|
||||||
* idle. Returns false only when Dawn could not be drained, in which case the
|
* idle. Returns false only when Dawn could not be drained, in which case the
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ bool aurora_vulkan_win32_set_targets(uint64_t token, const AuroraD3D12StereoTarg
|
|||||||
bool aurora_vulkan_win32_set_targets_with_panel(uint64_t token, const AuroraD3D12StereoTarget* targets, uint32_t count,
|
bool aurora_vulkan_win32_set_targets_with_panel(uint64_t token, const AuroraD3D12StereoTarget* targets, uint32_t count,
|
||||||
const AuroraD3D12StereoTarget* panel);
|
const AuroraD3D12StereoTarget* panel);
|
||||||
bool aurora_vulkan_win32_cancel(uint64_t token);
|
bool aurora_vulkan_win32_cancel(uint64_t token);
|
||||||
|
// As aurora_d3d12_forget_stereo_targets, for VkImages: waits for Dawn's queue,
|
||||||
|
// then drops the textures wrapping them, since the runtime may hand the same
|
||||||
|
// handles out again for the swapchains that replace them.
|
||||||
|
bool aurora_vulkan_win32_forget_targets(void* const* images, uint32_t count);
|
||||||
bool aurora_vulkan_win32_disable();
|
bool aurora_vulkan_win32_disable();
|
||||||
void* aurora_vulkan_win32_lock_queue();
|
void* aurora_vulkan_win32_lock_queue();
|
||||||
void aurora_vulkan_win32_unlock_queue(void* guard);
|
void aurora_vulkan_win32_unlock_queue(void* guard);
|
||||||
|
|||||||
@@ -319,6 +319,26 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The command references keep a destroyed swapchain's resources alive, but
|
||||||
|
// not necessarily the runtime's memory behind them, so a copy still writing
|
||||||
|
// one is waited for. Usually the last one finished frames ago.
|
||||||
|
bool ForgetTargets(void* const* resources, uint32_t count) noexcept {
|
||||||
|
std::lock_guard lock(m_mutex);
|
||||||
|
if (m_framePending) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CollectCompletedCommandsLocked();
|
||||||
|
const auto writesOne = [&](const InFlightCommand& command) {
|
||||||
|
return std::any_of(command.destinations.begin(), command.destinations.end(), [&](const auto& destination) {
|
||||||
|
return destination && std::find(resources, resources + count, destination.Get()) != resources + count;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (std::none_of(m_commands.begin(), m_commands.end(), writesOne)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return WaitForGpuLocked();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool EnsureIntermediate(uint32_t eye, const stereo::EyeImage& source) noexcept {
|
bool EnsureIntermediate(uint32_t eye, const stereo::EyeImage& source) noexcept {
|
||||||
auto& intermediate = m_intermediates[eye];
|
auto& intermediate = m_intermediates[eye];
|
||||||
@@ -756,6 +776,14 @@ bool aurora_d3d12_cancel_stereo_targets(uint64_t frameToken) {
|
|||||||
return g_bridge && g_bridge->CancelBeforeEncode(frameToken);
|
return g_bridge && g_bridge->CancelBeforeEncode(frameToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool aurora_d3d12_forget_stereo_targets(void* const* resources, uint32_t count) {
|
||||||
|
using namespace aurora::d3d12_interop;
|
||||||
|
if (!g_bridge) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return resources != nullptr && g_bridge->ForgetTargets(resources, count);
|
||||||
|
}
|
||||||
|
|
||||||
bool aurora_d3d12_disable_stereo_bridge() {
|
bool aurora_d3d12_disable_stereo_bridge() {
|
||||||
using namespace aurora::d3d12_interop;
|
using namespace aurora::d3d12_interop;
|
||||||
if (!g_bridge) {
|
if (!g_bridge) {
|
||||||
@@ -798,6 +826,8 @@ bool aurora_d3d12_set_stereo_targets_with_panel(uint64_t, const AuroraD3D12Stere
|
|||||||
|
|
||||||
bool aurora_d3d12_cancel_stereo_targets(uint64_t) { return false; }
|
bool aurora_d3d12_cancel_stereo_targets(uint64_t) { return false; }
|
||||||
|
|
||||||
|
bool aurora_d3d12_forget_stereo_targets(void* const*, uint32_t) { return true; }
|
||||||
|
|
||||||
bool aurora_d3d12_disable_stereo_bridge() { return true; }
|
bool aurora_d3d12_disable_stereo_bridge() { return true; }
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -93,8 +93,9 @@ bool device_supports_bridge() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// One AHardwareBuffer imported into Dawn. The import is created on first use
|
// One AHardwareBuffer imported into Dawn. The import is created on first use
|
||||||
// and kept until the bridge is disabled; the ring the OpenXR side recycles is
|
// and kept until the bridge is disabled or the OpenXR side replaces the buffer
|
||||||
// tiny (two buffers per eye), so this never grows beyond a handful of entries.
|
// (a new render resolution); the ring it recycles is tiny (two buffers per
|
||||||
|
// eye), so this never grows beyond a handful of entries.
|
||||||
struct Import {
|
struct Import {
|
||||||
AHardwareBuffer* buffer = nullptr;
|
AHardwareBuffer* buffer = nullptr;
|
||||||
wgpu::SharedTextureMemory memory;
|
wgpu::SharedTextureMemory memory;
|
||||||
@@ -254,6 +255,30 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ForgetBuffers(AHardwareBuffer* const* buffers, uint32_t count) noexcept {
|
||||||
|
std::lock_guard lock(m_mutex);
|
||||||
|
if (m_framePending) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (uint32_t i = 0; i < count; ++i) {
|
||||||
|
const auto found = m_imports.find(buffers[i]);
|
||||||
|
if (found == m_imports.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto& import = found->second;
|
||||||
|
if (import.accessBegun) {
|
||||||
|
// Only an abandoned frame leaves one open (see PrepareForDestruction).
|
||||||
|
wgpu::SharedTextureMemoryEndAccessState end{};
|
||||||
|
import.memory.EndAccess(import.texture, &end);
|
||||||
|
}
|
||||||
|
import.texture = nullptr;
|
||||||
|
import.memory = nullptr;
|
||||||
|
AHardwareBuffer_release(import.buffer);
|
||||||
|
m_imports.erase(found);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// An eye may be smaller than its buffer (the immersive window's eyes are the window only): it is
|
// An eye may be smaller than its buffer (the immersive window's eyes are the window only): it is
|
||||||
// copied into the buffer's top-left corner, and the OpenXR side shows just that rectangle.
|
// copied into the buffer's top-left corner, and the OpenXR side shows just that rectangle.
|
||||||
@@ -629,6 +654,14 @@ bool aurora_vulkan_cancel_stereo_targets(uint64_t frameToken) {
|
|||||||
return g_bridge && g_bridge->CancelBeforeEncode(frameToken);
|
return g_bridge && g_bridge->CancelBeforeEncode(frameToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool aurora_vulkan_forget_stereo_buffers(AHardwareBuffer* const* buffers, uint32_t count) {
|
||||||
|
using namespace aurora::vulkan_interop;
|
||||||
|
if (!g_bridge) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return buffers != nullptr && g_bridge->ForgetBuffers(buffers, count);
|
||||||
|
}
|
||||||
|
|
||||||
bool aurora_vulkan_disable_stereo_bridge() {
|
bool aurora_vulkan_disable_stereo_bridge() {
|
||||||
using namespace aurora::vulkan_interop;
|
using namespace aurora::vulkan_interop;
|
||||||
if (!g_bridge) {
|
if (!g_bridge) {
|
||||||
@@ -668,6 +701,8 @@ bool aurora_vulkan_set_stereo_targets_with_panel(uint64_t, const AuroraVulkanSte
|
|||||||
|
|
||||||
bool aurora_vulkan_cancel_stereo_targets(uint64_t) { return false; }
|
bool aurora_vulkan_cancel_stereo_targets(uint64_t) { return false; }
|
||||||
|
|
||||||
|
bool aurora_vulkan_forget_stereo_buffers(struct AHardwareBuffer* const*, uint32_t) { return true; }
|
||||||
|
|
||||||
bool aurora_vulkan_disable_stereo_bridge() { return true; }
|
bool aurora_vulkan_disable_stereo_bridge() { return true; }
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -92,6 +92,22 @@ public:
|
|||||||
if (!guard.owns_lock() || encoded || !token || token != wanted) return false;
|
if (!guard.owns_lock() || encoded || !token || token != wanted) return false;
|
||||||
token = 0; return true;
|
token = 0; return true;
|
||||||
}
|
}
|
||||||
|
// Before the runtime destroys these images: the drain retires every copy into
|
||||||
|
// them, and their wraps go because a new swapchain may get the same handles.
|
||||||
|
bool Forget(void* const* images, uint32_t n) {
|
||||||
|
{
|
||||||
|
std::lock_guard guard(mutex);
|
||||||
|
if (token) return false;
|
||||||
|
}
|
||||||
|
if (!api.drain(webgpu::g_device.Get())) return false;
|
||||||
|
std::lock_guard guard(mutex);
|
||||||
|
std::erase_if(imports, [&](const Import& entry) {
|
||||||
|
return std::find(images, images + n, reinterpret_cast<void*>(static_cast<uintptr_t>(entry.image))) !=
|
||||||
|
images + n;
|
||||||
|
});
|
||||||
|
active = {};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
bool Encode(wgpu::CommandEncoder& encoder, const stereo::SinkFrame& frame) {
|
bool Encode(wgpu::CommandEncoder& encoder, const stereo::SinkFrame& frame) {
|
||||||
std::lock_guard guard(mutex);
|
std::lock_guard guard(mutex);
|
||||||
if (!token || encoded || frame.frameToken != token) return false;
|
if (!token || encoded || frame.frameToken != token) return false;
|
||||||
@@ -187,6 +203,11 @@ bool aurora_vulkan_win32_cancel(uint64_t token) {
|
|||||||
using namespace aurora::vulkan_win32;
|
using namespace aurora::vulkan_win32;
|
||||||
return bridge && bridge->Cancel(token);
|
return bridge && bridge->Cancel(token);
|
||||||
}
|
}
|
||||||
|
bool aurora_vulkan_win32_forget_targets(void* const* images, uint32_t count) {
|
||||||
|
using namespace aurora::vulkan_win32;
|
||||||
|
if (!bridge) return true;
|
||||||
|
return images && bridge->Forget(images, count);
|
||||||
|
}
|
||||||
bool aurora_vulkan_win32_disable() {
|
bool aurora_vulkan_win32_disable() {
|
||||||
using namespace aurora::vulkan_win32;
|
using namespace aurora::vulkan_win32;
|
||||||
if (!bridge) return true;
|
if (!bridge) return true;
|
||||||
@@ -214,6 +235,7 @@ bool aurora_vulkan_win32_set_targets(uint64_t, const AuroraD3D12StereoTarget*, u
|
|||||||
bool aurora_vulkan_win32_set_targets_with_panel(uint64_t, const AuroraD3D12StereoTarget*, uint32_t,
|
bool aurora_vulkan_win32_set_targets_with_panel(uint64_t, const AuroraD3D12StereoTarget*, uint32_t,
|
||||||
const AuroraD3D12StereoTarget*) { return false; }
|
const AuroraD3D12StereoTarget*) { return false; }
|
||||||
bool aurora_vulkan_win32_cancel(uint64_t) { return false; }
|
bool aurora_vulkan_win32_cancel(uint64_t) { return false; }
|
||||||
|
bool aurora_vulkan_win32_forget_targets(void* const*, uint32_t) { return true; }
|
||||||
bool aurora_vulkan_win32_disable() { return true; }
|
bool aurora_vulkan_win32_disable() { return true; }
|
||||||
void* aurora_vulkan_win32_lock_queue() { return nullptr; }
|
void* aurora_vulkan_win32_lock_queue() { return nullptr; }
|
||||||
void aurora_vulkan_win32_unlock_queue(void*) {}
|
void aurora_vulkan_win32_unlock_queue(void*) {}
|
||||||
|
|||||||
+5
-3
@@ -294,8 +294,9 @@ seat, hand steering, tracked hands, lean back, render scale, VR interpolation,
|
|||||||
virtual screen size and distance),
|
virtual screen size and distance),
|
||||||
Graphics (resolution, widescreen, bloom, shader stutter), Controls (controller
|
Graphics (resolution, widescreen, bloom, shader stutter), Controls (controller
|
||||||
mode, vibration, the Wii Remote mapping), Audio, and About (paths, OpenXR
|
mode, vibration, the Wii Remote mapping), Audio, and About (paths, OpenXR
|
||||||
logging). The launch-time geometry (`render_scale`, `hud_distance_meters`,
|
logging). The launch-time geometry (`hud_distance_meters`, `hud_width_meters`)
|
||||||
`hud_width_meters`) is only reachable here, not from the in-headset panel.
|
is only reachable here, not from the in-headset panel; `render_scale` is also the
|
||||||
|
panel's live **VR → Render resolution** slider (OPENXR.md).
|
||||||
|
|
||||||
**Patches**, between the two, is the PC launcher's mods page (`PatchesPage`,
|
**Patches**, between the two, is the PC launcher's mods page (`PatchesPage`,
|
||||||
`ModLibrary`). Import takes one or more picked files, asks for a name and makes
|
`ModLibrary`). Import takes one or more picked files, asks for a name and makes
|
||||||
@@ -1113,7 +1114,8 @@ or `EndAccess` errors); a black mirror too points at Aurora itself.
|
|||||||
profiled on the XR2. The first run compiles every bundled pipeline recipe
|
profiled on the XR2. The first run compiles every bundled pipeline recipe
|
||||||
(about half a minute); later runs load Dawn's pipeline cache from `Cache/`
|
(about half a minute); later runs load Dawn's pipeline cache from `Cache/`
|
||||||
next to `DATA`. `render_scale` defaults to 0.8 here (1.0 on
|
next to `DATA`. `render_scale` defaults to 0.8 here (1.0 on
|
||||||
PC); lower it further if the compositor reports missed frames.
|
PC); lower it further if the compositor reports missed frames. It can be
|
||||||
|
changed during a race from the headset panel (VR → Render resolution).
|
||||||
Foveated rendering (above) is off by default: at `render_scale` 0.8 it saves
|
Foveated rendering (above) is off by default: at `render_scale` 0.8 it saves
|
||||||
nothing measurable, above that 8 to 22% of the eyes' GPU time.
|
nothing measurable, above that 8 to 22% of the eyes' GPU time.
|
||||||
- **Lifecycle.** Backgrounding (the Quest menu, guardian) pauses the session
|
- **Lifecycle.** Backgrounding (the Quest menu, guardian) pauses the session
|
||||||
|
|||||||
@@ -195,6 +195,10 @@ inline constexpr float kVrRenderScaleDefault = 0.8f;
|
|||||||
inline constexpr float kVrRenderScaleDefault = 1.0f;
|
inline constexpr float kVrRenderScaleDefault = 1.0f;
|
||||||
#define MKW_VR_RENDER_SCALE_DEFAULT_TEXT "1.0"
|
#define MKW_VR_RENDER_SCALE_DEFAULT_TEXT "1.0"
|
||||||
#endif
|
#endif
|
||||||
|
// The range the file accepts and the F10 bar's slider offers; the Quest
|
||||||
|
// launcher's settings page (SettingsPage.kt) repeats it.
|
||||||
|
inline constexpr float kVrRenderScaleMin = 0.25f;
|
||||||
|
inline constexpr float kVrRenderScaleMax = 2.0f;
|
||||||
|
|
||||||
// First-person camera defaults and the range its head offsets accept, in one
|
// First-person camera defaults and the range its head offsets accept, in one
|
||||||
// place: the config getters, the on-disk template and the F10 bar's reset all
|
// place: the config getters, the on-disk template and the F10 bar's reset all
|
||||||
@@ -773,7 +777,7 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
|
|||||||
config.vrEnabled = FindConfigValue<bool>(document, "vr", "enabled");
|
config.vrEnabled = FindConfigValue<bool>(document, "vr", "enabled");
|
||||||
config.vrRequired = FindConfigValue<bool>(document, "vr", "required");
|
config.vrRequired = FindConfigValue<bool>(document, "vr", "required");
|
||||||
if (auto value = FindConfigFloat(document, "vr", "render_scale");
|
if (auto value = FindConfigFloat(document, "vr", "render_scale");
|
||||||
value && *value >= 0.25f && *value <= 2.0f) {
|
value && *value >= kVrRenderScaleMin && *value <= kVrRenderScaleMax) {
|
||||||
config.vrRenderScale = *value;
|
config.vrRenderScale = *value;
|
||||||
}
|
}
|
||||||
if (auto value = FindConfigFloat(document, "vr", "world_units_per_meter");
|
if (auto value = FindConfigFloat(document, "vr", "world_units_per_meter");
|
||||||
@@ -1180,6 +1184,14 @@ inline bool SetVrRecenterKey(std::string value) {
|
|||||||
return WriteSetting("vr", "recenter_key", FormatString(value));
|
return WriteSetting("vr", "recenter_key", FormatString(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool SetVrRenderScale(float value) {
|
||||||
|
value = std::clamp(value, kVrRenderScaleMin, kVrRenderScaleMax);
|
||||||
|
Mutable().vrRenderScale = value;
|
||||||
|
std::ostringstream formatted;
|
||||||
|
formatted << value;
|
||||||
|
return WriteSetting("vr", "render_scale", formatted.str());
|
||||||
|
}
|
||||||
|
|
||||||
inline bool SetVrLeanBackDegrees(float value) {
|
inline bool SetVrLeanBackDegrees(float value) {
|
||||||
value = std::clamp(value, -kVrLeanBackDegreesLimit, kVrLeanBackDegreesLimit);
|
value = std::clamp(value, -kVrLeanBackDegreesLimit, kVrLeanBackDegreesLimit);
|
||||||
Mutable().vrLeanBackDegrees = value;
|
Mutable().vrLeanBackDegrees = value;
|
||||||
@@ -1578,7 +1590,7 @@ inline bool VrRequired(bool fallback = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline float VrRenderScale(float fallback = kVrRenderScaleDefault) {
|
inline float VrRenderScale(float fallback = kVrRenderScaleDefault) {
|
||||||
return std::clamp(Get().vrRenderScale.value_or(fallback), 0.25f, 2.0f);
|
return std::clamp(Get().vrRenderScale.value_or(fallback), kVrRenderScaleMin, kVrRenderScaleMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline float VrWorldUnitsPerMeter(float fallback = 500.0f) {
|
inline float VrWorldUnitsPerMeter(float fallback = 500.0f) {
|
||||||
|
|||||||
@@ -54,6 +54,14 @@ public:
|
|||||||
bool QueryGraphicsRequirements(OpenXRRuntime& runtime);
|
bool QueryGraphicsRequirements(OpenXRRuntime& runtime);
|
||||||
bool BindAurora(OpenXRRuntime& runtime);
|
bool BindAurora(OpenXRRuntime& runtime);
|
||||||
|
|
||||||
|
// The eyes' resolution, as a scale of the size the runtime recommends (the
|
||||||
|
// session starts at OpenXRConfig::resolution_scale). A new one applies from
|
||||||
|
// the next BeginFrame or PreparePacket: each swapchain pair is rebuilt at it
|
||||||
|
// the next time it is the pair Aurora writes, so the pair on display is
|
||||||
|
// never touched. A size the runtime cannot allocate keeps the one before,
|
||||||
|
// until the scale changes again.
|
||||||
|
void SetRenderScale(float scale);
|
||||||
|
|
||||||
OpenXRD3D12BeginStatus BeginFrame(const OpenXRD3D12Presentation& presentation,
|
OpenXRD3D12BeginStatus BeginFrame(const OpenXRD3D12Presentation& presentation,
|
||||||
OpenXRD3D12Frame& frame);
|
OpenXRD3D12Frame& frame);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <aurora/aurora.h>
|
#include <aurora/aurora.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace mkw::vr {
|
namespace mkw::vr {
|
||||||
@@ -74,6 +75,24 @@ void OpenXRSetPassthrough(bool enabled) noexcept;
|
|||||||
// altogether, makes it moot.
|
// altogether, makes it moot.
|
||||||
void OpenXRSetImmersiveWindow(bool enabled) noexcept;
|
void OpenXRSetImmersiveWindow(bool enabled) noexcept;
|
||||||
|
|
||||||
|
// The headset's eye resolution, as a scale of the size the OpenXR runtime
|
||||||
|
// recommends (clamped to RuntimeConfigFile::kVrRenderScaleMin..Max). Callable
|
||||||
|
// from any thread. The pacing thread rebuilds the eye swapchains at it over the
|
||||||
|
// next two frames, never touching the one on display; a size the runtime
|
||||||
|
// cannot allocate keeps the eyes as they are.
|
||||||
|
void OpenXRSetRenderScale(float scale) noexcept;
|
||||||
|
|
||||||
|
// The left eye's image size: width x height is what the headset is shown now,
|
||||||
|
// scaled_width x scaled_height what `scale` gives on this headset. All zero
|
||||||
|
// while no OpenXR session runs.
|
||||||
|
struct OpenXREyeResolution {
|
||||||
|
uint32_t width = 0;
|
||||||
|
uint32_t height = 0;
|
||||||
|
uint32_t scaled_width = 0;
|
||||||
|
uint32_t scaled_height = 0;
|
||||||
|
};
|
||||||
|
OpenXREyeResolution OpenXRGetEyeResolution(float scale) noexcept;
|
||||||
|
|
||||||
// Live scene interpolation at the headset's own display deadlines.
|
// Live scene interpolation at the headset's own display deadlines.
|
||||||
// 0 = Off, 1 = Auto, otherwise 72/90/120 as a rendering-rate ceiling.
|
// 0 = Off, 1 = Auto, otherwise 72/90/120 as a rendering-rate ceiling.
|
||||||
void OpenXRSetFrameInterpolationFps(uint32_t target) noexcept;
|
void OpenXRSetFrameInterpolationFps(uint32_t target) noexcept;
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
|
|
||||||
#include "vr/openxr_config.h"
|
#include "vr/openxr_config.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -41,6 +43,28 @@ struct OpenXRViewConfiguration {
|
|||||||
uint32_t render_height = 0;
|
uint32_t render_height = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct OpenXREyeSize {
|
||||||
|
uint32_t width = 0;
|
||||||
|
uint32_t height = 0;
|
||||||
|
|
||||||
|
bool operator==(const OpenXREyeSize& other) const noexcept {
|
||||||
|
return width == other.width && height == other.height;
|
||||||
|
}
|
||||||
|
bool operator!=(const OpenXREyeSize& other) const noexcept { return !(*this == other); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// A view's image at `scale` of the size the runtime recommends, never below one
|
||||||
|
// pixel or above the runtime's maximum. The size the session starts at, and the
|
||||||
|
// one a backend rebuilds its eyes at when the render scale changes live.
|
||||||
|
inline OpenXREyeSize OpenXRScaledEyeSize(const XrViewConfigurationView& view, float scale) noexcept {
|
||||||
|
const auto scaled = [scale](uint32_t recommended, uint32_t maximum) {
|
||||||
|
const double value = std::round(static_cast<double>(recommended) * static_cast<double>(scale));
|
||||||
|
return static_cast<uint32_t>(std::clamp(value, 1.0, static_cast<double>(std::max(maximum, 1u))));
|
||||||
|
};
|
||||||
|
return {scaled(view.recommendedImageRectWidth, view.maxImageRectWidth),
|
||||||
|
scaled(view.recommendedImageRectHeight, view.maxImageRectHeight)};
|
||||||
|
}
|
||||||
|
|
||||||
struct OpenXRRuntimeInfo {
|
struct OpenXRRuntimeInfo {
|
||||||
std::string runtime_name;
|
std::string runtime_name;
|
||||||
XrVersion runtime_version = 0;
|
XrVersion runtime_version = 0;
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ public:
|
|||||||
bool QueryGraphicsRequirements(OpenXRRuntime& runtime);
|
bool QueryGraphicsRequirements(OpenXRRuntime& runtime);
|
||||||
bool BindAurora(OpenXRRuntime& runtime);
|
bool BindAurora(OpenXRRuntime& runtime);
|
||||||
|
|
||||||
|
// As OpenXRD3D12Backend::SetRenderScale. The shared eye buffers are
|
||||||
|
// reallocated along with the first pair rebuilt at the new size.
|
||||||
|
void SetRenderScale(float scale);
|
||||||
|
|
||||||
OpenXRBeginStatus BeginFrame(const OpenXRPresentation& presentation, OpenXRBackendFrame& frame);
|
OpenXRBeginStatus BeginFrame(const OpenXRPresentation& presentation, OpenXRBackendFrame& frame);
|
||||||
OpenXRSubmissionStatus WaitForSubmission(const OpenXRBackendFrame& frame,
|
OpenXRSubmissionStatus WaitForSubmission(const OpenXRBackendFrame& frame,
|
||||||
uint32_t timeout_ms = UINT32_MAX);
|
uint32_t timeout_ms = UINT32_MAX);
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ public:
|
|||||||
bool QueryGraphicsRequirements(OpenXRRuntime& runtime);
|
bool QueryGraphicsRequirements(OpenXRRuntime& runtime);
|
||||||
bool BindAurora(OpenXRRuntime& runtime);
|
bool BindAurora(OpenXRRuntime& runtime);
|
||||||
|
|
||||||
|
// As OpenXRD3D12Backend::SetRenderScale.
|
||||||
|
void SetRenderScale(float scale);
|
||||||
|
|
||||||
OpenXRWindowsVulkanBeginStatus BeginFrame(const OpenXRWindowsVulkanPresentation& presentation,
|
OpenXRWindowsVulkanBeginStatus BeginFrame(const OpenXRWindowsVulkanPresentation& presentation,
|
||||||
OpenXRWindowsVulkanFrame& frame);
|
OpenXRWindowsVulkanFrame& frame);
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ public:
|
|||||||
bool BindAurora(OpenXRRuntime& runtime) {
|
bool BindAurora(OpenXRRuntime& runtime) {
|
||||||
return std::visit([&](auto& backend) -> bool { return backend->BindAurora(runtime); }, backend_);
|
return std::visit([&](auto& backend) -> bool { return backend->BindAurora(runtime); }, backend_);
|
||||||
}
|
}
|
||||||
|
void SetRenderScale(float scale) {
|
||||||
|
std::visit([&](auto& backend) { backend->SetRenderScale(scale); }, backend_);
|
||||||
|
}
|
||||||
OpenXRBeginStatus BeginFrame(const OpenXRPresentation& presentation, OpenXRBackendFrame& frame) {
|
OpenXRBeginStatus BeginFrame(const OpenXRPresentation& presentation, OpenXRBackendFrame& frame) {
|
||||||
return std::visit([&](auto& backend) -> OpenXRBeginStatus { return backend->BeginFrame(presentation, frame); }, backend_);
|
return std::visit([&](auto& backend) -> OpenXRBeginStatus { return backend->BeginFrame(presentation, frame); }, backend_);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,6 +238,8 @@ SDL_Scancode g_vrRecenterScancode = [] {
|
|||||||
}();
|
}();
|
||||||
bool g_vrRecenterRebinding = false;
|
bool g_vrRecenterRebinding = false;
|
||||||
float g_vrLeanBackDegrees = RuntimeConfigFile::VrLeanBackDegrees();
|
float g_vrLeanBackDegrees = RuntimeConfigFile::VrLeanBackDegrees();
|
||||||
|
// [vr] render_scale as the slider shows it, in percent of the headset's recommended size.
|
||||||
|
int g_vrRenderScalePercent = static_cast<int>(std::lround(RuntimeConfigFile::VrRenderScale() * 100.0f));
|
||||||
uint32_t g_disabledPostProcessingPaths = RuntimeConfigFile::DisabledPostProcessingPaths();
|
uint32_t g_disabledPostProcessingPaths = RuntimeConfigFile::DisabledPostProcessingPaths();
|
||||||
std::array<int32_t, PAD_MAX_CONTROLLERS> g_configuredControllerIndices = [] {
|
std::array<int32_t, PAD_MAX_CONTROLLERS> g_configuredControllerIndices = [] {
|
||||||
std::array<int32_t, PAD_MAX_CONTROLLERS> indices{};
|
std::array<int32_t, PAD_MAX_CONTROLLERS> indices{};
|
||||||
@@ -1419,6 +1421,35 @@ void DrawVrSettings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Live, and usable without a session like the mirror above. It applies when the slider is let
|
||||||
|
// go rather than at every step of a drag, since each new size means new eye swapchains.
|
||||||
|
ImGui::SliderInt("Render resolution", &g_vrRenderScalePercent,
|
||||||
|
static_cast<int>(RuntimeConfigFile::kVrRenderScaleMin * 100.0f),
|
||||||
|
static_cast<int>(RuntimeConfigFile::kVrRenderScaleMax * 100.0f), "%d%%",
|
||||||
|
ImGuiSliderFlags_AlwaysClamp);
|
||||||
|
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||||
|
const float scale = static_cast<float>(g_vrRenderScalePercent) / 100.0f;
|
||||||
|
mkw::vr::OpenXRSetRenderScale(scale);
|
||||||
|
RuntimeConfigFile::SetVrRenderScale(scale);
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip(
|
||||||
|
"How many pixels each eye is rendered with, as a share of what your headset's OpenXR "
|
||||||
|
"runtime recommends. Lower is faster, higher is sharper, and the GPU's work grows with the "
|
||||||
|
"pixel count: 150%% draws about 2.25 times as many as 100%%.\n"
|
||||||
|
"Applies when you let go of the slider, without restarting; the headset keeps showing "
|
||||||
|
"the game while the eyes change size.");
|
||||||
|
}
|
||||||
|
if (const auto eyes = mkw::vr::OpenXRGetEyeResolution(static_cast<float>(g_vrRenderScalePercent) / 100.0f);
|
||||||
|
eyes.width != 0) {
|
||||||
|
if (eyes.width == eyes.scaled_width && eyes.height == eyes.scaled_height) {
|
||||||
|
ImGui::TextDisabled("Each eye: %u x %u", eyes.width, eyes.height);
|
||||||
|
} else {
|
||||||
|
ImGui::TextDisabled("Each eye: %u x %u now, %u x %u at %d%%", eyes.width, eyes.height, eyes.scaled_width,
|
||||||
|
eyes.scaled_height, g_vrRenderScalePercent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui::Combo("VR frame interpolation (experimental)", &g_vrFrameInterpolationMode,
|
if (ImGui::Combo("VR frame interpolation (experimental)", &g_vrFrameInterpolationMode,
|
||||||
kVrInterpolationLabels.data(), static_cast<int>(kVrInterpolationLabels.size()))) {
|
kVrInterpolationLabels.data(), static_cast<int>(kVrInterpolationLabels.size()))) {
|
||||||
const auto target = kVrInterpolationFps[static_cast<size_t>(g_vrFrameInterpolationMode)];
|
const auto target = kVrInterpolationFps[static_cast<size_t>(g_vrFrameInterpolationMode)];
|
||||||
|
|||||||
@@ -195,6 +195,11 @@ public:
|
|||||||
}
|
}
|
||||||
owns_session_ = true;
|
owns_session_ = true;
|
||||||
aurora_format_ = static_cast<DXGI_FORMAT>(handles.colorDxgiFormat);
|
aurora_format_ = static_cast<DXGI_FORMAT>(handles.colorDxgiFormat);
|
||||||
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
|
const auto& view = runtime.ViewConfiguration()[eye];
|
||||||
|
eye_size_[eye] = {view.render_width, view.render_height};
|
||||||
|
}
|
||||||
|
requested_eye_size_ = eye_size_;
|
||||||
|
|
||||||
if (!SelectSwapchainFormat() || !CreateSwapchains()) {
|
if (!SelectSwapchainFormat() || !CreateSwapchains()) {
|
||||||
DestroySwapchains();
|
DestroySwapchains();
|
||||||
@@ -226,6 +231,21 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetRenderScale(float scale) {
|
||||||
|
if (runtime_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::array<OpenXREyeSize, kOpenXREyeCount> requested{};
|
||||||
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
|
requested[eye] = OpenXRScaledEyeSize(runtime_->ViewConfiguration()[eye].properties, scale);
|
||||||
|
}
|
||||||
|
// Only a change: a refused size stays refused while it is still the one asked for.
|
||||||
|
if (requested != requested_eye_size_) {
|
||||||
|
requested_eye_size_ = requested;
|
||||||
|
eye_size_ = requested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OpenXRD3D12BeginStatus BeginFrame(const OpenXRD3D12Presentation& presentation,
|
OpenXRD3D12BeginStatus BeginFrame(const OpenXRD3D12Presentation& presentation,
|
||||||
OpenXRD3D12Frame& frame) {
|
OpenXRD3D12Frame& frame) {
|
||||||
frame = {};
|
frame = {};
|
||||||
@@ -238,6 +258,9 @@ public:
|
|||||||
Fail("BeginFrame called while another OpenXR frame is active");
|
Fail("BeginFrame called while another OpenXR frame is active");
|
||||||
return OpenXRD3D12BeginStatus::Error;
|
return OpenXRD3D12BeginStatus::Error;
|
||||||
}
|
}
|
||||||
|
if (!ResizeWritablePair()) {
|
||||||
|
return OpenXRD3D12BeginStatus::Error;
|
||||||
|
}
|
||||||
|
|
||||||
const OpenXRFrameStatus status = runtime_->WaitFrame(frame.xr_frame);
|
const OpenXRFrameStatus status = runtime_->WaitFrame(frame.xr_frame);
|
||||||
if (status != OpenXRFrameStatus::Ready) {
|
if (status != OpenXRFrameStatus::Ready) {
|
||||||
@@ -372,6 +395,7 @@ public:
|
|||||||
const auto status = KeepAliveCycle();
|
const auto status = KeepAliveCycle();
|
||||||
if (status != OpenXRBeginStatus::Ready) return status;
|
if (status != OpenXRBeginStatus::Ready) return status;
|
||||||
}
|
}
|
||||||
|
if (!ResizeWritablePair()) return OpenXRBeginStatus::Error;
|
||||||
packet.xr_frame.serial = next_packet_serial_++;
|
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_time = last_display_time_ + 2 * last_display_period_;
|
||||||
packet.xr_frame.predicted_display_period = last_display_period_;
|
packet.xr_frame.predicted_display_period = last_display_period_;
|
||||||
@@ -814,8 +838,7 @@ private:
|
|||||||
|
|
||||||
bool CreateSwapchainPair(std::array<EyeSwapchain, kOpenXREyeCount>& pair) {
|
bool CreateSwapchainPair(std::array<EyeSwapchain, kOpenXREyeCount>& pair) {
|
||||||
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
const auto& view = runtime_->ViewConfiguration()[eye];
|
if (!CreateSwapchain(pair[eye], eye_size_[eye].width, eye_size_[eye].height,
|
||||||
if (!CreateSwapchain(pair[eye], view.render_width, view.render_height,
|
|
||||||
eye == 0 ? "left eye" : "right eye")) {
|
eye == 0 ? "left eye" : "right eye")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -823,6 +846,53 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rebuilds the pair Aurora writes next at eye_size_ when it is another size. Only that pair,
|
||||||
|
// with none of its images acquired, never the one on display. The new swapchains are made
|
||||||
|
// 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.
|
||||||
|
bool ResizeWritablePair() {
|
||||||
|
std::array<OpenXREyeSize, kOpenXREyeCount> current{};
|
||||||
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
|
if (eye_swapchains_[eye].acquired) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
current[eye] = {eye_swapchains_[eye].width, eye_swapchains_[eye].height};
|
||||||
|
}
|
||||||
|
if (current == eye_size_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::array<EyeSwapchain, kOpenXREyeCount> replacement{};
|
||||||
|
if (!CreateSwapchainPair(replacement)) {
|
||||||
|
DestroySwapchainPair(replacement);
|
||||||
|
std::ostringstream message;
|
||||||
|
message << "OpenXR D3D12 eyes stay " << current[0].width << 'x' << current[0].height
|
||||||
|
<< ": the runtime could not make " << eye_size_[0].width << 'x' << eye_size_[0].height
|
||||||
|
<< " swapchains (" << last_error_ << ')';
|
||||||
|
Log(OpenXRLogLevel::Warning, message.str());
|
||||||
|
ClearError();
|
||||||
|
// Back to this pair's size, which also returns the other pair to it if it was rebuilt.
|
||||||
|
eye_size_ = current;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::vector<void*> retired;
|
||||||
|
for (const EyeSwapchain& swapchain : eye_swapchains_) {
|
||||||
|
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);
|
||||||
|
std::ostringstream message;
|
||||||
|
message << "OpenXR D3D12 eyes resized: " << eye_size_[0].width << 'x' << eye_size_[0].height << " / "
|
||||||
|
<< eye_size_[1].width << 'x' << eye_size_[1].height;
|
||||||
|
Log(OpenXRLogLevel::Info, message.str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -1063,6 +1133,10 @@ private:
|
|||||||
OpenXRD3D12GraphicsRequirements requirements_{};
|
OpenXRD3D12GraphicsRequirements requirements_{};
|
||||||
std::array<EyeSwapchain, kOpenXREyeCount> eye_swapchains_{};
|
std::array<EyeSwapchain, kOpenXREyeCount> eye_swapchains_{};
|
||||||
std::array<EyeSwapchain, kOpenXREyeCount> retained_swapchains_{};
|
std::array<EyeSwapchain, kOpenXREyeCount> retained_swapchains_{};
|
||||||
|
// The size a rebuilt eye pair gets, and the one the render scale last asked
|
||||||
|
// for, which differ while a size the runtime refused is being kept.
|
||||||
|
std::array<OpenXREyeSize, kOpenXREyeCount> eye_size_{};
|
||||||
|
std::array<OpenXREyeSize, kOpenXREyeCount> requested_eye_size_{};
|
||||||
// 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_{};
|
||||||
@@ -1119,6 +1193,8 @@ bool OpenXRD3D12Backend::BindAurora(OpenXRRuntime& runtime) {
|
|||||||
return m_impl->BindAurora(runtime);
|
return m_impl->BindAurora(runtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OpenXRD3D12Backend::SetRenderScale(float scale) { m_impl->SetRenderScale(scale); }
|
||||||
|
|
||||||
OpenXRD3D12BeginStatus OpenXRD3D12Backend::BeginFrame(
|
OpenXRD3D12BeginStatus OpenXRD3D12Backend::BeginFrame(
|
||||||
const OpenXRD3D12Presentation& presentation, OpenXRD3D12Frame& frame) {
|
const OpenXRD3D12Presentation& presentation, OpenXRD3D12Frame& frame) {
|
||||||
return m_impl->BeginFrame(presentation, frame);
|
return m_impl->BeginFrame(presentation, frame);
|
||||||
|
|||||||
@@ -474,6 +474,13 @@ public:
|
|||||||
ResetPreparedObjects();
|
ResetPreparedObjects();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
const OpenXRViewConfiguration& left = runtime_->ViewConfiguration()[0];
|
||||||
|
std::lock_guard lock(eye_view_mutex_);
|
||||||
|
eye_view_ = left.properties;
|
||||||
|
eye_width_.store(left.render_width, std::memory_order_relaxed);
|
||||||
|
eye_height_.store(left.render_height, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
input_ = std::make_unique<OpenXRInput>(logger_);
|
input_ = std::make_unique<OpenXRInput>(logger_);
|
||||||
if (!input_->Create(*runtime_)) {
|
if (!input_->Create(*runtime_)) {
|
||||||
RT_LOG(RT_TAG_RUNTIME) << "OpenXR controller input unavailable: " << input_->LastError()
|
RT_LOG(RT_TAG_RUNTIME) << "OpenXR controller input unavailable: " << input_->LastError()
|
||||||
@@ -563,6 +570,12 @@ public:
|
|||||||
rendered_fps_.store(0, std::memory_order_relaxed);
|
rendered_fps_.store(0, std::memory_order_relaxed);
|
||||||
interpolation_available_.store(false, std::memory_order_release);
|
interpolation_available_.store(false, std::memory_order_release);
|
||||||
hand_tracking_available_.store(false, std::memory_order_release);
|
hand_tracking_available_.store(false, std::memory_order_release);
|
||||||
|
{
|
||||||
|
std::lock_guard lock(eye_view_mutex_);
|
||||||
|
eye_view_ = {XR_TYPE_VIEW_CONFIGURATION_VIEW};
|
||||||
|
eye_width_.store(0, std::memory_order_relaxed);
|
||||||
|
eye_height_.store(0, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
ResetTrackingOrigin();
|
ResetTrackingOrigin();
|
||||||
applied_session_run_serial_ = 0;
|
applied_session_run_serial_ = 0;
|
||||||
session_was_active_ = false;
|
session_was_active_ = false;
|
||||||
@@ -605,6 +618,23 @@ public:
|
|||||||
std::memory_order_relaxed);
|
std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetRenderScale(float scale) noexcept {
|
||||||
|
render_scale_.store(ClampRenderScale(scale), std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenXREyeResolution EyeResolution(float scale) const noexcept {
|
||||||
|
OpenXREyeResolution resolution{};
|
||||||
|
std::lock_guard lock(eye_view_mutex_);
|
||||||
|
resolution.width = eye_width_.load(std::memory_order_relaxed);
|
||||||
|
resolution.height = eye_height_.load(std::memory_order_relaxed);
|
||||||
|
if (resolution.width != 0) {
|
||||||
|
const OpenXREyeSize scaled = OpenXRScaledEyeSize(eye_view_, ClampRenderScale(scale));
|
||||||
|
resolution.scaled_width = scaled.width;
|
||||||
|
resolution.scaled_height = scaled.height;
|
||||||
|
}
|
||||||
|
return resolution;
|
||||||
|
}
|
||||||
|
|
||||||
void ServiceProducerFrameBoundary() noexcept {
|
void ServiceProducerFrameBoundary() noexcept {
|
||||||
if (teardown_requested_.load(std::memory_order_acquire)) {
|
if (teardown_requested_.load(std::memory_order_acquire)) {
|
||||||
Shutdown();
|
Shutdown();
|
||||||
@@ -631,6 +661,17 @@ private:
|
|||||||
// at the headset's refresh rate.
|
// at the headset's refresh rate.
|
||||||
static constexpr uint32_t kMaxConsecutiveSkips = 300;
|
static constexpr uint32_t kMaxConsecutiveSkips = 300;
|
||||||
|
|
||||||
|
static float ClampRenderScale(float scale) noexcept {
|
||||||
|
return std::clamp(scale, RuntimeConfigFile::kVrRenderScaleMin, RuntimeConfigFile::kVrRenderScaleMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The size the eyes are rendered at now, for the settings: the pair being written, before the
|
||||||
|
// immersive window may aim its eyes through the window.
|
||||||
|
void NoteEyeSize(const OpenXRBackendFrame& frame) noexcept {
|
||||||
|
eye_width_.store(frame.render_width[0], std::memory_order_relaxed);
|
||||||
|
eye_height_.store(frame.render_height[0], std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
bool BackendMatchesConfiguredGraphicsApi(const AuroraConfig& aurora_config) {
|
bool BackendMatchesConfiguredGraphicsApi(const AuroraConfig& aurora_config) {
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
kRequiredAuroraBackend = aurora_config.desiredBackend == BACKEND_VULKAN ? BACKEND_VULKAN : BACKEND_D3D12;
|
kRequiredAuroraBackend = aurora_config.desiredBackend == BACKEND_VULKAN ? BACKEND_VULKAN : BACKEND_D3D12;
|
||||||
@@ -927,6 +968,8 @@ private:
|
|||||||
RT_LOG(RT_TAG_RUNTIME) << "OpenXR " << kGraphicsBackendName << " pacing: "
|
RT_LOG(RT_TAG_RUNTIME) << "OpenXR " << kGraphicsBackendName << " pacing: "
|
||||||
<< (render_first ? "render-first" : "frame-first (VR interpolation)") << std::endl;
|
<< (render_first ? "render-first" : "frame-first (VR interpolation)") << std::endl;
|
||||||
}
|
}
|
||||||
|
// A new scale rebuilds the eyes as the backend next prepares them.
|
||||||
|
backend_->SetRenderScale(render_scale_.load(std::memory_order_relaxed));
|
||||||
if (render_first) {
|
if (render_first) {
|
||||||
if (!RenderFirstCycle(presentation, policy, immersive, consecutive_skips,
|
if (!RenderFirstCycle(presentation, policy, immersive, consecutive_skips,
|
||||||
immersive_submission_logged)) {
|
immersive_submission_logged)) {
|
||||||
@@ -949,6 +992,7 @@ private:
|
|||||||
fatal = true;
|
fatal = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
NoteEyeSize(frame);
|
||||||
|
|
||||||
UpdateFrameTiming(frame.xr_frame);
|
UpdateFrameTiming(frame.xr_frame);
|
||||||
if (diagnostics::Enabled()) {
|
if (diagnostics::Enabled()) {
|
||||||
@@ -1140,6 +1184,7 @@ private:
|
|||||||
SetError(backend_->LastError());
|
SetError(backend_->LastError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
NoteEyeSize(packet);
|
||||||
// The head pose this packet was located with places the screens and aims the pointer.
|
// The head pose this packet was located with places the screens and aims the pointer.
|
||||||
ServiceRecenterRequest();
|
ServiceRecenterRequest();
|
||||||
UpdateVirtualScreenPose(packet);
|
UpdateVirtualScreenPose(packet);
|
||||||
@@ -1917,6 +1962,13 @@ private:
|
|||||||
std::atomic_bool teardown_requested_{false};
|
std::atomic_bool teardown_requested_{false};
|
||||||
std::atomic_bool recenter_requested_{false};
|
std::atomic_bool recenter_requested_{false};
|
||||||
std::atomic<float> lean_back_degrees_{RuntimeConfigFile::VrLeanBackDegrees()};
|
std::atomic<float> lean_back_degrees_{RuntimeConfigFile::VrLeanBackDegrees()};
|
||||||
|
std::atomic<float> render_scale_{RuntimeConfigFile::VrRenderScale()};
|
||||||
|
// The left eye for OpenXRGetEyeResolution: the runtime's description of it, set while a
|
||||||
|
// session runs, and the size it is rendered at now (0 without a session).
|
||||||
|
mutable std::mutex eye_view_mutex_;
|
||||||
|
XrViewConfigurationView eye_view_{XR_TYPE_VIEW_CONFIGURATION_VIEW};
|
||||||
|
std::atomic_uint32_t eye_width_{0};
|
||||||
|
std::atomic_uint32_t eye_height_{0};
|
||||||
std::atomic_bool passthrough_{RuntimeConfigFile::VrPassthrough()};
|
std::atomic_bool passthrough_{RuntimeConfigFile::VrPassthrough()};
|
||||||
std::atomic_bool immersive_window_{RuntimeConfigFile::VrImmersiveWindow()};
|
std::atomic_bool immersive_window_{RuntimeConfigFile::VrImmersiveWindow()};
|
||||||
std::atomic_uint32_t frame_interpolation_fps_{RuntimeConfigFile::VrFrameInterpolationFps()};
|
std::atomic_uint32_t frame_interpolation_fps_{RuntimeConfigFile::VrFrameInterpolationFps()};
|
||||||
@@ -2049,6 +2101,23 @@ void OpenXRSetImmersiveWindow(bool enabled) noexcept {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OpenXRSetRenderScale(float scale) noexcept {
|
||||||
|
#if MKW_OPENXR_GRAPHICS_BACKEND
|
||||||
|
OpenXRIntegration::Get().SetRenderScale(scale);
|
||||||
|
#else
|
||||||
|
(void)scale;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenXREyeResolution OpenXRGetEyeResolution(float scale) noexcept {
|
||||||
|
#if MKW_OPENXR_GRAPHICS_BACKEND
|
||||||
|
return OpenXRIntegration::Get().EyeResolution(scale);
|
||||||
|
#else
|
||||||
|
(void)scale;
|
||||||
|
return {};
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void OpenXRSetFrameInterpolationFps(uint32_t target) noexcept {
|
void OpenXRSetFrameInterpolationFps(uint32_t target) noexcept {
|
||||||
#if MKW_OPENXR_GRAPHICS_BACKEND
|
#if MKW_OPENXR_GRAPHICS_BACKEND
|
||||||
OpenXRIntegration::Get().SetFrameInterpolationFps(target);
|
OpenXRIntegration::Get().SetFrameInterpolationFps(target);
|
||||||
|
|||||||
@@ -48,14 +48,6 @@ bool IsFinitePositive(float value) noexcept {
|
|||||||
(bits & 0x7f800000u) != 0x7f800000u;
|
(bits & 0x7f800000u) != 0x7f800000u;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t ScaledDimension(uint32_t recommended, uint32_t maximum, float scale) {
|
|
||||||
const double scaled = std::round(static_cast<double>(recommended) *
|
|
||||||
static_cast<double>(scale));
|
|
||||||
const double clamped = std::clamp(
|
|
||||||
scaled, 1.0, static_cast<double>(std::max(maximum, 1u)));
|
|
||||||
return static_cast<uint32_t>(clamped);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* ReferenceSpaceName(XrReferenceSpaceType type) {
|
const char* ReferenceSpaceName(XrReferenceSpaceType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case XR_REFERENCE_SPACE_TYPE_VIEW:
|
case XR_REFERENCE_SPACE_TYPE_VIEW:
|
||||||
@@ -309,12 +301,9 @@ bool OpenXRRuntime::EnumerateViewConfiguration() {
|
|||||||
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
OpenXRViewConfiguration& destination = m_view_configuration[eye];
|
OpenXRViewConfiguration& destination = m_view_configuration[eye];
|
||||||
destination.properties = properties[eye];
|
destination.properties = properties[eye];
|
||||||
destination.render_width = ScaledDimension(
|
const OpenXREyeSize size = OpenXRScaledEyeSize(properties[eye], m_config.resolution_scale);
|
||||||
properties[eye].recommendedImageRectWidth,
|
destination.render_width = size.width;
|
||||||
properties[eye].maxImageRectWidth, m_config.resolution_scale);
|
destination.render_height = size.height;
|
||||||
destination.render_height = ScaledDimension(
|
|
||||||
properties[eye].recommendedImageRectHeight,
|
|
||||||
properties[eye].maxImageRectHeight, m_config.resolution_scale);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,6 +181,9 @@ public:
|
|||||||
// The settings panel's layer image follows the eyes.
|
// The settings panel's layer image follows the eyes.
|
||||||
bool panel = false;
|
bool panel = false;
|
||||||
std::array<VkImage, kMaxCopies> swapchain_images{};
|
std::array<VkImage, kMaxCopies> swapchain_images{};
|
||||||
|
// Each image's size. A shared buffer can be larger than the image while
|
||||||
|
// the render resolution is changing, and a copy moves only what fits.
|
||||||
|
std::array<VkExtent2D, kMaxCopies> swapchain_extents{};
|
||||||
|
|
||||||
uint32_t Count() const noexcept { return target_count + (panel ? 1u : 0u); }
|
uint32_t Count() const noexcept { return target_count + (panel ? 1u : 0u); }
|
||||||
};
|
};
|
||||||
@@ -291,6 +294,11 @@ public:
|
|||||||
return Fail("OpenXR rejected the Vulkan device binding");
|
return Fail("OpenXR rejected the Vulkan device binding");
|
||||||
}
|
}
|
||||||
owns_session_ = true;
|
owns_session_ = true;
|
||||||
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
|
const auto& view = runtime.ViewConfiguration()[eye];
|
||||||
|
eye_size_[eye] = {view.render_width, view.render_height};
|
||||||
|
}
|
||||||
|
requested_eye_size_ = eye_size_;
|
||||||
|
|
||||||
if (!SelectSwapchainFormat() || !CreateSwapchains() || !AllocateSlots()) {
|
if (!SelectSwapchainFormat() || !CreateSwapchains() || !AllocateSlots()) {
|
||||||
DestroySwapchains();
|
DestroySwapchains();
|
||||||
@@ -329,6 +337,21 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetRenderScale(float scale) {
|
||||||
|
if (runtime_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::array<OpenXREyeSize, kOpenXREyeCount> requested{};
|
||||||
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
|
requested[eye] = OpenXRScaledEyeSize(runtime_->ViewConfiguration()[eye].properties, scale);
|
||||||
|
}
|
||||||
|
// Only a change: a refused size stays refused while it is still the one asked for.
|
||||||
|
if (requested != requested_eye_size_) {
|
||||||
|
requested_eye_size_ = requested;
|
||||||
|
eye_size_ = requested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OpenXRBeginStatus BeginFrame(const OpenXRPresentation& presentation, OpenXRBackendFrame& frame) {
|
OpenXRBeginStatus BeginFrame(const OpenXRPresentation& presentation, OpenXRBackendFrame& frame) {
|
||||||
frame = {};
|
frame = {};
|
||||||
frame.presentation = presentation;
|
frame.presentation = presentation;
|
||||||
@@ -340,6 +363,9 @@ public:
|
|||||||
Fail("BeginFrame called while another OpenXR frame is active");
|
Fail("BeginFrame called while another OpenXR frame is active");
|
||||||
return OpenXRBeginStatus::Error;
|
return OpenXRBeginStatus::Error;
|
||||||
}
|
}
|
||||||
|
if (!ResizeWritablePair()) {
|
||||||
|
return OpenXRBeginStatus::Error;
|
||||||
|
}
|
||||||
|
|
||||||
const OpenXRFrameStatus status = runtime_->WaitFrame(frame.xr_frame);
|
const OpenXRFrameStatus status = runtime_->WaitFrame(frame.xr_frame);
|
||||||
if (status != OpenXRFrameStatus::Ready) {
|
if (status != OpenXRFrameStatus::Ready) {
|
||||||
@@ -423,14 +449,7 @@ public:
|
|||||||
panel_target = SlotTargetLocked(panel_slots_[slot]);
|
panel_target = SlotTargetLocked(panel_slots_[slot]);
|
||||||
}
|
}
|
||||||
pending_copy_ = {frame.xr_frame.serial, slot, target_count, panel, {}};
|
pending_copy_ = {frame.xr_frame.serial, slot, target_count, panel, {}};
|
||||||
for (uint32_t eye = 0; eye < target_count; ++eye) {
|
RecordCopyImagesLocked(target_count, panel);
|
||||||
pending_copy_.swapchain_images[eye] =
|
|
||||||
eye_swapchains_[eye].images[eye_swapchains_[eye].acquired_index].image;
|
|
||||||
}
|
|
||||||
if (panel) {
|
|
||||||
pending_copy_.swapchain_images[target_count] =
|
|
||||||
panel_swapchain_.images[panel_swapchain_.acquired_index].image;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::lock_guard lock(submission_mutex_);
|
std::lock_guard lock(submission_mutex_);
|
||||||
@@ -543,6 +562,9 @@ public:
|
|||||||
return primed;
|
return primed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!ResizeWritablePair()) {
|
||||||
|
return OpenXRBeginStatus::Error;
|
||||||
|
}
|
||||||
packet.xr_frame.serial = next_packet_serial_++;
|
packet.xr_frame.serial = next_packet_serial_++;
|
||||||
// The eyes are ready after at most one game frame plus the encode and show at the first
|
// The eyes are ready after at most one game frame plus the encode and show at the first
|
||||||
// display slot after that: two periods past the last predicted display time.
|
// display slot after that: two periods past the last predicted display time.
|
||||||
@@ -774,14 +796,7 @@ public:
|
|||||||
diagnostics::OnSwapchainAcquire(acquire_timer);
|
diagnostics::OnSwapchainAcquire(acquire_timer);
|
||||||
{
|
{
|
||||||
std::lock_guard lock(vk_mutex_);
|
std::lock_guard lock(vk_mutex_);
|
||||||
for (uint32_t eye = 0; eye < target_count; ++eye) {
|
RecordCopyImagesLocked(target_count, panel);
|
||||||
pending_copy_.swapchain_images[eye] =
|
|
||||||
eye_swapchains_[eye].images[eye_swapchains_[eye].acquired_index].image;
|
|
||||||
}
|
|
||||||
if (panel) {
|
|
||||||
pending_copy_.swapchain_images[target_count] =
|
|
||||||
panel_swapchain_.images[panel_swapchain_.acquired_index].image;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return OpenXRBeginStatus::Ready;
|
return OpenXRBeginStatus::Ready;
|
||||||
}
|
}
|
||||||
@@ -1568,6 +1583,20 @@ private:
|
|||||||
self->submission_cv_.notify_all();
|
self->submission_cv_.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The acquired compositor images the pending copy writes: the eyes', then the panel's.
|
||||||
|
void RecordCopyImagesLocked(uint32_t target_count, bool panel) noexcept {
|
||||||
|
const auto record = [this](uint32_t n, const EyeSwapchain& swapchain) {
|
||||||
|
pending_copy_.swapchain_images[n] = swapchain.images[swapchain.acquired_index].image;
|
||||||
|
pending_copy_.swapchain_extents[n] = {swapchain.width, swapchain.height};
|
||||||
|
};
|
||||||
|
for (uint32_t eye = 0; eye < target_count; ++eye) {
|
||||||
|
record(eye, eye_swapchains_[eye]);
|
||||||
|
}
|
||||||
|
if (panel) {
|
||||||
|
record(target_count, panel_swapchain_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The slot image `n` of a copy reads: an eye's, or after the eyes the panel's.
|
// The slot image `n` of a copy reads: an eye's, or after the eyes the panel's.
|
||||||
EyeSlot& CopySlotLocked(const PendingCopy& copy, uint32_t n) noexcept {
|
EyeSlot& CopySlotLocked(const PendingCopy& copy, uint32_t n) noexcept {
|
||||||
return n < copy.target_count ? slots_[n][copy.slot] : panel_slots_[copy.slot];
|
return n < copy.target_count ? slots_[n][copy.slot] : panel_slots_[copy.slot];
|
||||||
@@ -1670,7 +1699,8 @@ private:
|
|||||||
VkImageCopy region{};
|
VkImageCopy region{};
|
||||||
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||||
region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||||
region.extent = {slot.width, slot.height, 1};
|
region.extent = {std::min(slot.width, copy.swapchain_extents[n].width),
|
||||||
|
std::min(slot.height, copy.swapchain_extents[n].height), 1};
|
||||||
vkCmdCopyImage(cmd, slot.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, copy.swapchain_images[n],
|
vkCmdCopyImage(cmd, slot.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, copy.swapchain_images[n],
|
||||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||||
|
|
||||||
@@ -1804,8 +1834,7 @@ private:
|
|||||||
|
|
||||||
bool CreateSwapchainPair(std::array<EyeSwapchain, kOpenXREyeCount>& pair) {
|
bool CreateSwapchainPair(std::array<EyeSwapchain, kOpenXREyeCount>& pair) {
|
||||||
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
const auto& view = runtime_->ViewConfiguration()[eye];
|
if (!CreateSwapchain(pair[eye], eye_size_[eye].width, eye_size_[eye].height,
|
||||||
if (!CreateSwapchain(pair[eye], view.render_width, view.render_height,
|
|
||||||
eye == 0 ? "left eye" : "right eye")) {
|
eye == 0 ? "left eye" : "right eye")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1813,6 +1842,121 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// As the D3D12 backend's, with the shared eye buffers, which hold whichever pair is written
|
||||||
|
// and so are kept as large as both pairs: they grow with the first pair rebuilt larger and
|
||||||
|
// shrink once the second has followed it down (each copy moves only what fits the image it
|
||||||
|
// writes). Everything new is allocated before anything old goes, so a size that cannot be had
|
||||||
|
// leaves the eyes as they were.
|
||||||
|
bool ResizeWritablePair() {
|
||||||
|
std::array<OpenXREyeSize, kOpenXREyeCount> current{};
|
||||||
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
|
if (eye_swapchains_[eye].acquired) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
current[eye] = {eye_swapchains_[eye].width, eye_swapchains_[eye].height};
|
||||||
|
}
|
||||||
|
if (current == eye_size_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto keep_current = [&](const char* what) {
|
||||||
|
std::ostringstream message;
|
||||||
|
message << "OpenXR Vulkan eyes stay " << current[0].width << 'x' << current[0].height
|
||||||
|
<< ": could not make " << eye_size_[0].width << 'x' << eye_size_[0].height << ' ' << what
|
||||||
|
<< " (" << last_error_ << ')';
|
||||||
|
Log(OpenXRLogLevel::Warning, message.str());
|
||||||
|
ClearError();
|
||||||
|
// Back to this pair's size, which also returns the other pair to it if it was rebuilt.
|
||||||
|
eye_size_ = current;
|
||||||
|
};
|
||||||
|
std::array<EyeSwapchain, kOpenXREyeCount> replacement{};
|
||||||
|
if (!CreateSwapchainPair(replacement)) {
|
||||||
|
DestroySwapchainPair(replacement);
|
||||||
|
keep_current("swapchains");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard lock(vk_mutex_);
|
||||||
|
std::array<OpenXREyeSize, kOpenXREyeCount> needed{};
|
||||||
|
std::array<bool, kOpenXREyeCount> new_slots{};
|
||||||
|
bool must_grow = false;
|
||||||
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
|
needed[eye] = {std::max(eye_size_[eye].width, retained_swapchains_[eye].width),
|
||||||
|
std::max(eye_size_[eye].height, retained_swapchains_[eye].height)};
|
||||||
|
const OpenXREyeSize held{slots_[eye][0].width, slots_[eye][0].height};
|
||||||
|
new_slots[eye] = needed[eye] != held;
|
||||||
|
must_grow |= needed[eye].width > held.width || needed[eye].height > held.height;
|
||||||
|
}
|
||||||
|
decltype(slots_) slots{};
|
||||||
|
const auto discard_new_slots = [&] {
|
||||||
|
for (auto& eye : slots) {
|
||||||
|
for (EyeSlot& slot : eye) {
|
||||||
|
DestroySlotLocked(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_slots = {};
|
||||||
|
};
|
||||||
|
bool allocated = true;
|
||||||
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
|
if (new_slots[eye]) {
|
||||||
|
for (EyeSlot& slot : slots[eye]) {
|
||||||
|
allocated = allocated && AllocateSlot(slot, needed[eye].width, needed[eye].height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!allocated) {
|
||||||
|
discard_new_slots();
|
||||||
|
if (must_grow) {
|
||||||
|
DestroySwapchainPair(replacement);
|
||||||
|
keep_current("shared buffers");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Smaller buffers would only give memory back; the larger ones still hold both pairs.
|
||||||
|
Log(OpenXRLogLevel::Warning, "OpenXR Vulkan keeps its larger shared eye buffers: " + last_error_);
|
||||||
|
ClearError();
|
||||||
|
}
|
||||||
|
// This device's copies out of the old buffers and into the old images are done after this.
|
||||||
|
const VkResult idle = vkDeviceWaitIdle(vk_device_);
|
||||||
|
if (idle != VK_SUCCESS) {
|
||||||
|
discard_new_slots();
|
||||||
|
DestroySwapchainPair(replacement);
|
||||||
|
return Fail(VkFailure("vkDeviceWaitIdle failed before resizing the eyes", idle));
|
||||||
|
}
|
||||||
|
std::vector<AHardwareBuffer*> retired;
|
||||||
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
|
if (new_slots[eye]) {
|
||||||
|
for (EyeSlot& slot : slots_[eye]) {
|
||||||
|
retired.push_back(slot.buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!retired.empty()) {
|
||||||
|
if (!aurora_vulkan_forget_stereo_buffers(retired.data(), static_cast<uint32_t>(retired.size()))) {
|
||||||
|
discard_new_slots();
|
||||||
|
DestroySwapchainPair(replacement);
|
||||||
|
return Fail("Aurora could not release the old shared eye buffers");
|
||||||
|
}
|
||||||
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
|
if (!new_slots[eye]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (uint32_t slot = 0; slot < kSlotCount; ++slot) {
|
||||||
|
DestroySlotLocked(slots_[eye][slot]);
|
||||||
|
slots_[eye][slot] = slots[eye][slot];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DestroySwapchainPair(eye_swapchains_);
|
||||||
|
eye_swapchains_ = std::move(replacement);
|
||||||
|
std::ostringstream message;
|
||||||
|
message << "OpenXR Vulkan eyes resized: " << eye_size_[0].width << 'x' << eye_size_[0].height << " / "
|
||||||
|
<< eye_size_[1].width << 'x' << eye_size_[1].height;
|
||||||
|
if (!retired.empty()) {
|
||||||
|
message << ", shared buffers " << slots_[0][0].width << 'x' << slots_[0][0].height;
|
||||||
|
}
|
||||||
|
Log(OpenXRLogLevel::Info, message.str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -2030,6 +2174,9 @@ private:
|
|||||||
OpenXRVulkanGraphicsRequirements requirements_{};
|
OpenXRVulkanGraphicsRequirements requirements_{};
|
||||||
std::array<EyeSwapchain, kOpenXREyeCount> eye_swapchains_{};
|
std::array<EyeSwapchain, kOpenXREyeCount> eye_swapchains_{};
|
||||||
std::array<EyeSwapchain, kOpenXREyeCount> retained_swapchains_{};
|
std::array<EyeSwapchain, kOpenXREyeCount> retained_swapchains_{};
|
||||||
|
// As the D3D12 backend's.
|
||||||
|
std::array<OpenXREyeSize, kOpenXREyeCount> eye_size_{};
|
||||||
|
std::array<OpenXREyeSize, kOpenXREyeCount> requested_eye_size_{};
|
||||||
// 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_{};
|
||||||
@@ -2110,6 +2257,8 @@ bool OpenXRVulkanBackend::BindAurora(OpenXRRuntime& runtime) {
|
|||||||
return m_impl->BindAurora(runtime);
|
return m_impl->BindAurora(runtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OpenXRVulkanBackend::SetRenderScale(float scale) { m_impl->SetRenderScale(scale); }
|
||||||
|
|
||||||
OpenXRBeginStatus OpenXRVulkanBackend::BeginFrame(const OpenXRPresentation& presentation,
|
OpenXRBeginStatus OpenXRVulkanBackend::BeginFrame(const OpenXRPresentation& presentation,
|
||||||
OpenXRBackendFrame& frame) {
|
OpenXRBackendFrame& frame) {
|
||||||
return m_impl->BeginFrame(presentation, frame);
|
return m_impl->BeginFrame(presentation, frame);
|
||||||
|
|||||||
@@ -216,6 +216,11 @@ public:
|
|||||||
if (!runtime.CreateSession(&binding)) return Fail("OpenXR rejected Dawn's Vulkan device binding");
|
if (!runtime.CreateSession(&binding)) return Fail("OpenXR rejected Dawn's Vulkan device binding");
|
||||||
owns_session_ = true;
|
owns_session_ = true;
|
||||||
aurora_format_ = static_cast<VkFormat>(format);
|
aurora_format_ = static_cast<VkFormat>(format);
|
||||||
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
|
const auto& view = runtime.ViewConfiguration()[eye];
|
||||||
|
eye_size_[eye] = {view.render_width, view.render_height};
|
||||||
|
}
|
||||||
|
requested_eye_size_ = eye_size_;
|
||||||
const auto abandon_session = [&] {
|
const auto abandon_session = [&] {
|
||||||
DestroySwapchains();
|
DestroySwapchains();
|
||||||
runtime.DestroySession();
|
runtime.DestroySession();
|
||||||
@@ -248,6 +253,21 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetRenderScale(float scale) {
|
||||||
|
if (runtime_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::array<OpenXREyeSize, kOpenXREyeCount> requested{};
|
||||||
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
|
requested[eye] = OpenXRScaledEyeSize(runtime_->ViewConfiguration()[eye].properties, scale);
|
||||||
|
}
|
||||||
|
// Only a change: a refused size stays refused while it is still the one asked for.
|
||||||
|
if (requested != requested_eye_size_) {
|
||||||
|
requested_eye_size_ = requested;
|
||||||
|
eye_size_ = requested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OpenXRWindowsVulkanBeginStatus BeginFrame(const OpenXRWindowsVulkanPresentation& presentation,
|
OpenXRWindowsVulkanBeginStatus BeginFrame(const OpenXRWindowsVulkanPresentation& presentation,
|
||||||
OpenXRWindowsVulkanFrame& frame) {
|
OpenXRWindowsVulkanFrame& frame) {
|
||||||
frame = {};
|
frame = {};
|
||||||
@@ -260,6 +280,9 @@ public:
|
|||||||
Fail("BeginFrame called while another OpenXR frame is active");
|
Fail("BeginFrame called while another OpenXR frame is active");
|
||||||
return OpenXRWindowsVulkanBeginStatus::Error;
|
return OpenXRWindowsVulkanBeginStatus::Error;
|
||||||
}
|
}
|
||||||
|
if (!ResizeWritablePair()) {
|
||||||
|
return OpenXRWindowsVulkanBeginStatus::Error;
|
||||||
|
}
|
||||||
|
|
||||||
const OpenXRFrameStatus status = runtime_->WaitFrame(frame.xr_frame);
|
const OpenXRFrameStatus status = runtime_->WaitFrame(frame.xr_frame);
|
||||||
if (status != OpenXRFrameStatus::Ready) {
|
if (status != OpenXRFrameStatus::Ready) {
|
||||||
@@ -394,6 +417,7 @@ public:
|
|||||||
const auto status = KeepAliveCycle();
|
const auto status = KeepAliveCycle();
|
||||||
if (status != OpenXRBeginStatus::Ready) return status;
|
if (status != OpenXRBeginStatus::Ready) return status;
|
||||||
}
|
}
|
||||||
|
if (!ResizeWritablePair()) return OpenXRBeginStatus::Error;
|
||||||
packet.xr_frame.serial = next_packet_serial_++;
|
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_time = last_display_time_ + 2 * last_display_period_;
|
||||||
packet.xr_frame.predicted_display_period = last_display_period_;
|
packet.xr_frame.predicted_display_period = last_display_period_;
|
||||||
@@ -841,8 +865,7 @@ private:
|
|||||||
|
|
||||||
bool CreateSwapchainPair(std::array<EyeSwapchain, kOpenXREyeCount>& pair) {
|
bool CreateSwapchainPair(std::array<EyeSwapchain, kOpenXREyeCount>& pair) {
|
||||||
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
const auto& view = runtime_->ViewConfiguration()[eye];
|
if (!CreateSwapchain(pair[eye], eye_size_[eye].width, eye_size_[eye].height,
|
||||||
if (!CreateSwapchain(pair[eye], view.render_width, view.render_height,
|
|
||||||
eye == 0 ? "left eye" : "right eye")) {
|
eye == 0 ? "left eye" : "right eye")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -850,6 +873,51 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
bool ResizeWritablePair() {
|
||||||
|
std::array<OpenXREyeSize, kOpenXREyeCount> current{};
|
||||||
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
|
if (eye_swapchains_[eye].acquired) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
current[eye] = {eye_swapchains_[eye].width, eye_swapchains_[eye].height};
|
||||||
|
}
|
||||||
|
if (current == eye_size_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::array<EyeSwapchain, kOpenXREyeCount> replacement{};
|
||||||
|
if (!CreateSwapchainPair(replacement)) {
|
||||||
|
DestroySwapchainPair(replacement);
|
||||||
|
std::ostringstream message;
|
||||||
|
message << "OpenXR Vulkan eyes stay " << current[0].width << 'x' << current[0].height
|
||||||
|
<< ": the runtime could not make " << eye_size_[0].width << 'x' << eye_size_[0].height
|
||||||
|
<< " swapchains (" << last_error_ << ')';
|
||||||
|
Log(OpenXRLogLevel::Warning, message.str());
|
||||||
|
ClearError();
|
||||||
|
// Back to this pair's size, which also returns the other pair to it if it was rebuilt.
|
||||||
|
eye_size_ = current;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::vector<void*> retired;
|
||||||
|
for (const EyeSwapchain& swapchain : eye_swapchains_) {
|
||||||
|
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);
|
||||||
|
std::ostringstream message;
|
||||||
|
message << "OpenXR Vulkan eyes resized: " << eye_size_[0].width << 'x' << eye_size_[0].height << " / "
|
||||||
|
<< eye_size_[1].width << 'x' << eye_size_[1].height;
|
||||||
|
Log(OpenXRLogLevel::Info, message.str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -1102,6 +1170,9 @@ private:
|
|||||||
OpenXRWindowsVulkanGraphicsRequirements requirements_{};
|
OpenXRWindowsVulkanGraphicsRequirements requirements_{};
|
||||||
std::array<EyeSwapchain, kOpenXREyeCount> eye_swapchains_{};
|
std::array<EyeSwapchain, kOpenXREyeCount> eye_swapchains_{};
|
||||||
std::array<EyeSwapchain, kOpenXREyeCount> retained_swapchains_{};
|
std::array<EyeSwapchain, kOpenXREyeCount> retained_swapchains_{};
|
||||||
|
// As the D3D12 backend's.
|
||||||
|
std::array<OpenXREyeSize, kOpenXREyeCount> eye_size_{};
|
||||||
|
std::array<OpenXREyeSize, kOpenXREyeCount> requested_eye_size_{};
|
||||||
// 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_{};
|
||||||
@@ -1158,6 +1229,8 @@ bool OpenXRWindowsVulkanBackend::BindAurora(OpenXRRuntime& runtime) {
|
|||||||
return m_impl->BindAurora(runtime);
|
return m_impl->BindAurora(runtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OpenXRWindowsVulkanBackend::SetRenderScale(float scale) { m_impl->SetRenderScale(scale); }
|
||||||
|
|
||||||
OpenXRWindowsVulkanBeginStatus OpenXRWindowsVulkanBackend::BeginFrame(
|
OpenXRWindowsVulkanBeginStatus OpenXRWindowsVulkanBackend::BeginFrame(
|
||||||
const OpenXRWindowsVulkanPresentation& presentation, OpenXRWindowsVulkanFrame& frame) {
|
const OpenXRWindowsVulkanPresentation& presentation, OpenXRWindowsVulkanFrame& frame) {
|
||||||
return m_impl->BeginFrame(presentation, frame);
|
return m_impl->BeginFrame(presentation, frame);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#define aurora_d3d12_set_stereo_targets aurora_vulkan_win32_set_targets
|
#define aurora_d3d12_set_stereo_targets aurora_vulkan_win32_set_targets
|
||||||
#define aurora_d3d12_set_stereo_targets_with_panel aurora_vulkan_win32_set_targets_with_panel
|
#define aurora_d3d12_set_stereo_targets_with_panel aurora_vulkan_win32_set_targets_with_panel
|
||||||
#define aurora_d3d12_cancel_stereo_targets aurora_vulkan_win32_cancel
|
#define aurora_d3d12_cancel_stereo_targets aurora_vulkan_win32_cancel
|
||||||
|
#define aurora_d3d12_forget_stereo_targets aurora_vulkan_win32_forget_targets
|
||||||
#define aurora_d3d12_disable_stereo_bridge aurora_vulkan_win32_disable
|
#define aurora_d3d12_disable_stereo_bridge aurora_vulkan_win32_disable
|
||||||
#else
|
#else
|
||||||
#define CINTERFACE
|
#define CINTERFACE
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
|
|
||||||
#include "vr/openxr_wii_remote.h"
|
#include "vr/openxr_wii_remote.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -51,6 +53,8 @@ void RequireAt(bool condition, int line, const char* expression) {
|
|||||||
struct Image { uint64_t content = 0; };
|
struct Image { uint64_t content = 0; };
|
||||||
struct Swapchain {
|
struct Swapchain {
|
||||||
Image image;
|
Image image;
|
||||||
|
uint32_t width = 0;
|
||||||
|
uint32_t height = 0;
|
||||||
bool acquired = false;
|
bool acquired = false;
|
||||||
bool waited = false;
|
bool waited = false;
|
||||||
bool released = false;
|
bool released = false;
|
||||||
@@ -74,6 +78,11 @@ uint64_t displayed_content = 0;
|
|||||||
uint32_t layer_count = 0;
|
uint32_t layer_count = 0;
|
||||||
uint32_t releases = 0;
|
uint32_t releases = 0;
|
||||||
uint32_t live_swapchains = 0;
|
uint32_t live_swapchains = 0;
|
||||||
|
uint32_t create_attempts = 0;
|
||||||
|
int creates_before_failure = -1; // xrCreateSwapchain fails once this reaches 0; -1 never
|
||||||
|
bool bridge_enabled = false;
|
||||||
|
std::vector<void*> forgotten; // images the backend had Aurora forget
|
||||||
|
int32_t shown_width = 0; // the scene layer's image width
|
||||||
XrTime display_time = 0;
|
XrTime display_time = 0;
|
||||||
XrStructureType layer_type = XR_TYPE_UNKNOWN;
|
XrStructureType layer_type = XR_TYPE_UNKNOWN;
|
||||||
XrPosef quad_pose{};
|
XrPosef quad_pose{};
|
||||||
@@ -160,6 +169,7 @@ bool aurora_d3d12_get_native_handles(AuroraD3D12NativeHandles* handles) {
|
|||||||
bool aurora_d3d12_enable_stereo_bridge(AuroraD3D12StereoSubmittedCallback cb, void* data) {
|
bool aurora_d3d12_enable_stereo_bridge(AuroraD3D12StereoSubmittedCallback cb, void* data) {
|
||||||
callback = cb;
|
callback = cb;
|
||||||
callback_data = data;
|
callback_data = data;
|
||||||
|
bridge_enabled = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool aurora_d3d12_set_stereo_targets_with_panel(uint64_t token, const AuroraD3D12StereoTarget* data,
|
bool aurora_d3d12_set_stereo_targets_with_panel(uint64_t token, const AuroraD3D12StereoTarget* data,
|
||||||
@@ -180,14 +190,26 @@ bool aurora_d3d12_cancel_stereo_targets(uint64_t token) {
|
|||||||
pending_token = 0;
|
pending_token = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
bool aurora_d3d12_forget_stereo_targets(void* const* resources, uint32_t count) {
|
||||||
|
Require(pending_token == 0); // Never while Aurora may be writing a target.
|
||||||
|
forgotten.insert(forgotten.end(), resources, resources + count);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
bool aurora_d3d12_disable_stereo_bridge() {
|
bool aurora_d3d12_disable_stereo_bridge() {
|
||||||
pending_token = 0;
|
pending_token = 0;
|
||||||
encoded = false;
|
encoded = false;
|
||||||
|
bridge_enabled = false;
|
||||||
return true; // Simulate a successful queue drain.
|
return true; // Simulate a successful queue drain.
|
||||||
}
|
}
|
||||||
|
|
||||||
XrResult XRAPI_CALL xrCreateSwapchain(XrSession, const XrSwapchainCreateInfo*, XrSwapchain* out) {
|
XrResult XRAPI_CALL xrCreateSwapchain(XrSession, const XrSwapchainCreateInfo* info, XrSwapchain* out) {
|
||||||
*out = reinterpret_cast<XrSwapchain>(new Swapchain);
|
++create_attempts;
|
||||||
|
if (creates_before_failure == 0) return XR_ERROR_OUT_OF_MEMORY;
|
||||||
|
if (creates_before_failure > 0) --creates_before_failure;
|
||||||
|
auto* chain = new Swapchain;
|
||||||
|
chain->width = info->width;
|
||||||
|
chain->height = info->height;
|
||||||
|
*out = reinterpret_cast<XrSwapchain>(chain);
|
||||||
++live_swapchains;
|
++live_swapchains;
|
||||||
return XR_SUCCESS;
|
return XR_SUCCESS;
|
||||||
}
|
}
|
||||||
@@ -236,6 +258,10 @@ XrResult XRAPI_CALL xrReleaseSwapchainImage(XrSwapchain handle,
|
|||||||
XrResult XRAPI_CALL xrDestroySwapchain(XrSwapchain handle) {
|
XrResult XRAPI_CALL xrDestroySwapchain(XrSwapchain handle) {
|
||||||
auto* chain = reinterpret_cast<Swapchain*>(handle);
|
auto* chain = reinterpret_cast<Swapchain*>(handle);
|
||||||
Require(!chain->acquired);
|
Require(!chain->acquired);
|
||||||
|
// An image Aurora has written goes only once Aurora forgot it, or with the whole bridge.
|
||||||
|
if (bridge_enabled && chain->released) {
|
||||||
|
Require(std::find(forgotten.begin(), forgotten.end(), static_cast<void*>(&chain->image)) != forgotten.end());
|
||||||
|
}
|
||||||
delete chain;
|
delete chain;
|
||||||
--live_swapchains;
|
--live_swapchains;
|
||||||
return XR_SUCCESS;
|
return XR_SUCCESS;
|
||||||
@@ -250,6 +276,10 @@ OpenXRRuntime::OpenXRRuntime(OpenXRLogCallback) {
|
|||||||
m_swapchain_formats = {DXGI_FORMAT_R8G8B8A8_UNORM_SRGB};
|
m_swapchain_formats = {DXGI_FORMAT_R8G8B8A8_UNORM_SRGB};
|
||||||
#endif
|
#endif
|
||||||
for (auto& view : m_view_configuration) {
|
for (auto& view : m_view_configuration) {
|
||||||
|
view.properties.recommendedImageRectWidth = 100;
|
||||||
|
view.properties.recommendedImageRectHeight = 80;
|
||||||
|
view.properties.maxImageRectWidth = 400;
|
||||||
|
view.properties.maxImageRectHeight = 320;
|
||||||
view.render_width = 100;
|
view.render_width = 100;
|
||||||
view.render_height = 80;
|
view.render_height = 80;
|
||||||
}
|
}
|
||||||
@@ -325,11 +355,16 @@ bool OpenXRRuntime::EndFrame(const OpenXRFrame& frame,
|
|||||||
const auto check_image = [](const XrSwapchainSubImage& subimage) {
|
const auto check_image = [](const XrSwapchainSubImage& subimage) {
|
||||||
const auto& chain = *reinterpret_cast<Swapchain*>(subimage.swapchain);
|
const auto& chain = *reinterpret_cast<Swapchain*>(subimage.swapchain);
|
||||||
Require(chain.released && !chain.acquired && chain.image.content != 0);
|
Require(chain.released && !chain.acquired && chain.image.content != 0);
|
||||||
|
// The layer shows its own image's size, whichever size the other pair has.
|
||||||
|
Require(subimage.imageRect.offset.x == 0 && subimage.imageRect.offset.y == 0);
|
||||||
|
Require(subimage.imageRect.extent.width == static_cast<int32_t>(chain.width));
|
||||||
|
Require(subimage.imageRect.extent.height == static_cast<int32_t>(chain.height));
|
||||||
return chain.image.content;
|
return chain.image.content;
|
||||||
};
|
};
|
||||||
if (layer_type == XR_TYPE_COMPOSITION_LAYER_PROJECTION) {
|
if (layer_type == XR_TYPE_COMPOSITION_LAYER_PROJECTION) {
|
||||||
auto& projection = *reinterpret_cast<const XrCompositionLayerProjection*>(layers[0]);
|
auto& projection = *reinterpret_cast<const XrCompositionLayerProjection*>(layers[0]);
|
||||||
Require(projection.viewCount == 2);
|
Require(projection.viewCount == 2);
|
||||||
|
shown_width = projection.views[0].subImage.imageRect.extent.width;
|
||||||
for (const auto& view : {projection.views[0], projection.views[1]}) {
|
for (const auto& view : {projection.views[0], projection.views[1]}) {
|
||||||
displayed_content = check_image(view.subImage);
|
displayed_content = check_image(view.subImage);
|
||||||
// Detect a new image paired with an old pose, or a repeated image
|
// Detect a new image paired with an old pose, or a repeated image
|
||||||
@@ -341,6 +376,7 @@ bool OpenXRRuntime::EndFrame(const OpenXRFrame& frame,
|
|||||||
Require(layer_type == XR_TYPE_COMPOSITION_LAYER_QUAD);
|
Require(layer_type == XR_TYPE_COMPOSITION_LAYER_QUAD);
|
||||||
const auto& quad = *reinterpret_cast<const XrCompositionLayerQuad*>(layers[0]);
|
const auto& quad = *reinterpret_cast<const XrCompositionLayerQuad*>(layers[0]);
|
||||||
displayed_content = check_image(quad.subImage);
|
displayed_content = check_image(quad.subImage);
|
||||||
|
shown_width = quad.subImage.imageRect.extent.width;
|
||||||
quad_pose = quad.pose;
|
quad_pose = quad.pose;
|
||||||
}
|
}
|
||||||
if (count == 2) {
|
if (count == 2) {
|
||||||
@@ -584,6 +620,111 @@ void TestPanelLayer() {
|
|||||||
display_time = 0;
|
display_time = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A new render scale rebuilds each swapchain pair the next time it is the one Aurora writes: the
|
||||||
|
// pair on display is never touched, Aurora forgets the old images before they go (checked by
|
||||||
|
// xrDestroySwapchain), and a size the runtime refuses keeps the eyes as they were.
|
||||||
|
void TestRenderScale() {
|
||||||
|
display_time = 0;
|
||||||
|
forgotten.clear();
|
||||||
|
OpenXRRuntime runtime;
|
||||||
|
OpenXRD3D12Backend backend;
|
||||||
|
Require(backend.QueryGraphicsRequirements(runtime) && backend.BindAurora(runtime));
|
||||||
|
OpenXRPresentation presentation;
|
||||||
|
OpenXRBackendFrame frame;
|
||||||
|
const auto begin = [&](uint32_t width, uint32_t height) {
|
||||||
|
Require(backend.BeginFrame(presentation, frame) == OpenXRBeginStatus::Ready);
|
||||||
|
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);
|
||||||
|
Require(live_swapchains == 4);
|
||||||
|
};
|
||||||
|
const auto finish = [&] {
|
||||||
|
Complete();
|
||||||
|
Require(backend.WaitForSubmission(frame, 0) == OpenXRSubmissionStatus::Success);
|
||||||
|
Require(backend.FinishFrame(frame, true));
|
||||||
|
Require(layer_count == 1 && displayed_content == frame.xr_frame.serial);
|
||||||
|
};
|
||||||
|
|
||||||
|
backend.SetRenderScale(1.0f); // The session's own scale rebuilds nothing.
|
||||||
|
begin(100, 80);
|
||||||
|
finish();
|
||||||
|
Require(forgotten.empty() && shown_width == 100);
|
||||||
|
|
||||||
|
backend.SetRenderScale(1.5f);
|
||||||
|
begin(150, 120); // The pair written next, while the one on display still shows the last frame.
|
||||||
|
Require(forgotten.size() == 2);
|
||||||
|
Require(backend.RepeatFrame(frame) && layer_count == 1 && shown_width == 100);
|
||||||
|
finish();
|
||||||
|
Require(shown_width == 150);
|
||||||
|
begin(150, 120); // Then the other pair, once it is the one written.
|
||||||
|
Require(forgotten.size() == 4);
|
||||||
|
finish();
|
||||||
|
begin(150, 120); // Both are the new size: nothing more is rebuilt.
|
||||||
|
finish();
|
||||||
|
Require(forgotten.size() == 4);
|
||||||
|
|
||||||
|
backend.SetRenderScale(5.0f); // Clamped to the runtime's maximum.
|
||||||
|
begin(400, 320);
|
||||||
|
finish();
|
||||||
|
begin(400, 320);
|
||||||
|
finish();
|
||||||
|
backend.SetRenderScale(1.5f);
|
||||||
|
begin(150, 120);
|
||||||
|
finish();
|
||||||
|
begin(150, 120);
|
||||||
|
finish();
|
||||||
|
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.
|
||||||
|
creates_before_failure = 1; // One eye's swapchain is made, then the other's is refused.
|
||||||
|
const uint32_t attempts = create_attempts;
|
||||||
|
backend.SetRenderScale(3.0f);
|
||||||
|
begin(150, 120);
|
||||||
|
finish();
|
||||||
|
backend.SetRenderScale(3.0f);
|
||||||
|
begin(150, 120);
|
||||||
|
finish();
|
||||||
|
Require(create_attempts == attempts + 2 && forgotten.size() == forgotten_before_refusal);
|
||||||
|
creates_before_failure = -1;
|
||||||
|
backend.SetRenderScale(0.5f); // A new size is tried again.
|
||||||
|
begin(50, 40);
|
||||||
|
finish();
|
||||||
|
begin(50, 40);
|
||||||
|
finish();
|
||||||
|
|
||||||
|
// Refused for the second pair only: the first follows it back to the size it kept.
|
||||||
|
creates_before_failure = 2;
|
||||||
|
backend.SetRenderScale(2.0f);
|
||||||
|
begin(200, 160);
|
||||||
|
finish();
|
||||||
|
Require(shown_width == 200);
|
||||||
|
begin(50, 40);
|
||||||
|
finish();
|
||||||
|
Require(shown_width == 50);
|
||||||
|
creates_before_failure = -1;
|
||||||
|
begin(50, 40);
|
||||||
|
finish();
|
||||||
|
const size_t forgotten_settled = forgotten.size();
|
||||||
|
begin(50, 40);
|
||||||
|
finish();
|
||||||
|
Require(forgotten.size() == forgotten_settled);
|
||||||
|
|
||||||
|
// Render-first pacing rebuilds the pair its packet will be rendered into.
|
||||||
|
expect_render_first = true;
|
||||||
|
backend.SetRenderScale(1.0f);
|
||||||
|
OpenXRBackendFrame packet;
|
||||||
|
Require(backend.PreparePacket(presentation, packet) == OpenXRBeginStatus::Ready);
|
||||||
|
Require(packet.render_width[0] == 100 && targets.size() == 2 && targets[0].width == 100);
|
||||||
|
Complete();
|
||||||
|
Require(backend.WaitForSubmission(packet, 0) == OpenXRSubmissionStatus::Success);
|
||||||
|
Require(backend.BeginFrameForPacket(packet, frame) == OpenXRBeginStatus::Ready);
|
||||||
|
Require(backend.CopyRenderedEyes(frame) == OpenXRSubmissionStatus::Success);
|
||||||
|
Require(backend.FinishFrame(frame, true));
|
||||||
|
Require(layer_count == 1 && shown_width == 100);
|
||||||
|
expect_render_first = false;
|
||||||
|
Require(backend.Shutdown() && live_swapchains == 0);
|
||||||
|
display_time = 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool SameRect(const XrRect2Di& rect, int32_t x, int32_t y, int32_t width, int32_t height) {
|
bool SameRect(const XrRect2Di& rect, int32_t x, int32_t y, int32_t width, int32_t height) {
|
||||||
return rect.offset.x == x && rect.offset.y == y && rect.extent.width == width && rect.extent.height == height;
|
return rect.offset.x == x && rect.offset.y == y && rect.extent.width == width && rect.extent.height == height;
|
||||||
}
|
}
|
||||||
@@ -610,6 +751,7 @@ void TestVirtualScreenContentRect() {
|
|||||||
int main() {
|
int main() {
|
||||||
TestRenderFirst();
|
TestRenderFirst();
|
||||||
TestPanelLayer();
|
TestPanelLayer();
|
||||||
|
TestRenderScale();
|
||||||
TestVirtualScreenContentRect();
|
TestVirtualScreenContentRect();
|
||||||
OpenXRRuntime runtime;
|
OpenXRRuntime runtime;
|
||||||
OpenXRD3D12Backend backend;
|
OpenXRD3D12Backend backend;
|
||||||
|
|||||||
Reference in new issue
Block a user