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:
iChris4 committed 2026-09-26 02:25:34 +02:00
1 parent f013f9d2f7
commit e4d198ee13
22 files changed
+779 -48

No files matched your search

+14 -2
View File
@@ -195,6 +195,10 @@ inline constexpr float kVrRenderScaleDefault = 0.8f;
inline constexpr float kVrRenderScaleDefault = 1.0f;
#define MKW_VR_RENDER_SCALE_DEFAULT_TEXT "1.0"
#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
// 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.vrRequired = FindConfigValue<bool>(document, "vr", "required");
if (auto value = FindConfigFloat(document, "vr", "render_scale");
value && *value >= 0.25f && *value <= 2.0f) {
value && *value >= kVrRenderScaleMin && *value <= kVrRenderScaleMax) {
config.vrRenderScale = *value;
}
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));
}
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) {
value = std::clamp(value, -kVrLeanBackDegreesLimit, kVrLeanBackDegreesLimit);
Mutable().vrLeanBackDegrees = value;
@@ -1578,7 +1590,7 @@ inline bool VrRequired(bool fallback = false) {
}
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) {
+8
View File
@@ -54,6 +54,14 @@ public:
bool QueryGraphicsRequirements(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,
OpenXRD3D12Frame& frame);
+19
View File
@@ -4,6 +4,7 @@
#include <aurora/aurora.h>
#include <cstdint>
#include <string>
namespace mkw::vr {
@@ -74,6 +75,24 @@ void OpenXRSetPassthrough(bool enabled) noexcept;
// altogether, makes it moot.
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.
// 0 = Off, 1 = Auto, otherwise 72/90/120 as a rendering-rate ceiling.
void OpenXRSetFrameInterpolationFps(uint32_t target) noexcept;
+24
View File
@@ -4,7 +4,9 @@
#include "vr/openxr_config.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <functional>
#include <string>
@@ -41,6 +43,28 @@ struct OpenXRViewConfiguration {
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 {
std::string runtime_name;
XrVersion runtime_version = 0;
+4
View File
@@ -51,6 +51,10 @@ public:
bool QueryGraphicsRequirements(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);
OpenXRSubmissionStatus WaitForSubmission(const OpenXRBackendFrame& frame,
uint32_t timeout_ms = UINT32_MAX);
+3
View File
@@ -51,6 +51,9 @@ public:
bool QueryGraphicsRequirements(OpenXRRuntime& runtime);
bool BindAurora(OpenXRRuntime& runtime);
// As OpenXRD3D12Backend::SetRenderScale.
void SetRenderScale(float scale);
OpenXRWindowsVulkanBeginStatus BeginFrame(const OpenXRWindowsVulkanPresentation& presentation,
OpenXRWindowsVulkanFrame& frame);
+3
View File
@@ -21,6 +21,9 @@ public:
bool BindAurora(OpenXRRuntime& runtime) {
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) {
return std::visit([&](auto& backend) -> OpenXRBeginStatus { return backend->BeginFrame(presentation, frame); }, backend_);
}