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

+31
View File
@@ -238,6 +238,8 @@ SDL_Scancode g_vrRecenterScancode = [] {
}();
bool g_vrRecenterRebinding = false;
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();
std::array<int32_t, PAD_MAX_CONTROLLERS> g_configuredControllerIndices = [] {
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,
kVrInterpolationLabels.data(), static_cast<int>(kVrInterpolationLabels.size()))) {
const auto target = kVrInterpolationFps[static_cast<size_t>(g_vrFrameInterpolationMode)];
+78 -2
View File
@@ -195,6 +195,11 @@ public:
}
owns_session_ = true;
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()) {
DestroySwapchains();
@@ -226,6 +231,21 @@ public:
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,
OpenXRD3D12Frame& frame) {
frame = {};
@@ -238,6 +258,9 @@ public:
Fail("BeginFrame called while another OpenXR frame is active");
return OpenXRD3D12BeginStatus::Error;
}
if (!ResizeWritablePair()) {
return OpenXRD3D12BeginStatus::Error;
}
const OpenXRFrameStatus status = runtime_->WaitFrame(frame.xr_frame);
if (status != OpenXRFrameStatus::Ready) {
@@ -372,6 +395,7 @@ public:
const auto status = KeepAliveCycle();
if (status != OpenXRBeginStatus::Ready) return status;
}
if (!ResizeWritablePair()) return OpenXRBeginStatus::Error;
packet.xr_frame.serial = next_packet_serial_++;
packet.xr_frame.predicted_display_time = last_display_time_ + 2 * last_display_period_;
packet.xr_frame.predicted_display_period = last_display_period_;
@@ -814,8 +838,7 @@ private:
bool CreateSwapchainPair(std::array<EyeSwapchain, kOpenXREyeCount>& pair) {
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
const auto& view = runtime_->ViewConfiguration()[eye];
if (!CreateSwapchain(pair[eye], view.render_width, view.render_height,
if (!CreateSwapchain(pair[eye], eye_size_[eye].width, eye_size_[eye].height,
eye == 0 ? "left eye" : "right eye")) {
return false;
}
@@ -823,6 +846,53 @@ private:
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) {
swapchain.width = width;
swapchain.height = height;
@@ -1063,6 +1133,10 @@ private:
OpenXRD3D12GraphicsRequirements requirements_{};
std::array<EyeSwapchain, kOpenXREyeCount> eye_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_,
// shown from retained_panel_swapchain_ (see FinishFrame).
EyeSwapchain panel_swapchain_{};
@@ -1119,6 +1193,8 @@ bool OpenXRD3D12Backend::BindAurora(OpenXRRuntime& runtime) {
return m_impl->BindAurora(runtime);
}
void OpenXRD3D12Backend::SetRenderScale(float scale) { m_impl->SetRenderScale(scale); }
OpenXRD3D12BeginStatus OpenXRD3D12Backend::BeginFrame(
const OpenXRD3D12Presentation& presentation, OpenXRD3D12Frame& frame) {
return m_impl->BeginFrame(presentation, frame);
+69
View File
@@ -474,6 +474,13 @@ public:
ResetPreparedObjects();
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_);
if (!input_->Create(*runtime_)) {
RT_LOG(RT_TAG_RUNTIME) << "OpenXR controller input unavailable: " << input_->LastError()
@@ -563,6 +570,12 @@ public:
rendered_fps_.store(0, std::memory_order_relaxed);
interpolation_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();
applied_session_run_serial_ = 0;
session_was_active_ = false;
@@ -605,6 +618,23 @@ public:
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 {
if (teardown_requested_.load(std::memory_order_acquire)) {
Shutdown();
@@ -631,6 +661,17 @@ private:
// at the headset's refresh rate.
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) {
#if defined(_WIN32)
kRequiredAuroraBackend = aurora_config.desiredBackend == BACKEND_VULKAN ? BACKEND_VULKAN : BACKEND_D3D12;
@@ -927,6 +968,8 @@ private:
RT_LOG(RT_TAG_RUNTIME) << "OpenXR " << kGraphicsBackendName << " pacing: "
<< (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 (!RenderFirstCycle(presentation, policy, immersive, consecutive_skips,
immersive_submission_logged)) {
@@ -949,6 +992,7 @@ private:
fatal = true;
break;
}
NoteEyeSize(frame);
UpdateFrameTiming(frame.xr_frame);
if (diagnostics::Enabled()) {
@@ -1140,6 +1184,7 @@ private:
SetError(backend_->LastError());
return false;
}
NoteEyeSize(packet);
// The head pose this packet was located with places the screens and aims the pointer.
ServiceRecenterRequest();
UpdateVirtualScreenPose(packet);
@@ -1917,6 +1962,13 @@ private:
std::atomic_bool teardown_requested_{false};
std::atomic_bool recenter_requested_{false};
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 immersive_window_{RuntimeConfigFile::VrImmersiveWindow()};
std::atomic_uint32_t frame_interpolation_fps_{RuntimeConfigFile::VrFrameInterpolationFps()};
@@ -2049,6 +2101,23 @@ void OpenXRSetImmersiveWindow(bool enabled) noexcept {
#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 {
#if MKW_OPENXR_GRAPHICS_BACKEND
OpenXRIntegration::Get().SetFrameInterpolationFps(target);
+3 -14
View File
@@ -48,14 +48,6 @@ bool IsFinitePositive(float value) noexcept {
(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) {
switch (type) {
case XR_REFERENCE_SPACE_TYPE_VIEW:
@@ -309,12 +301,9 @@ bool OpenXRRuntime::EnumerateViewConfiguration() {
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
OpenXRViewConfiguration& destination = m_view_configuration[eye];
destination.properties = properties[eye];
destination.render_width = ScaledDimension(
properties[eye].recommendedImageRectWidth,
properties[eye].maxImageRectWidth, m_config.resolution_scale);
destination.render_height = ScaledDimension(
properties[eye].recommendedImageRectHeight,
properties[eye].maxImageRectHeight, m_config.resolution_scale);
const OpenXREyeSize size = OpenXRScaledEyeSize(properties[eye], m_config.resolution_scale);
destination.render_width = size.width;
destination.render_height = size.height;
}
return true;
}
+168 -19
View File
@@ -181,6 +181,9 @@ public:
// The settings panel's layer image follows the eyes.
bool panel = false;
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); }
};
@@ -291,6 +294,11 @@ public:
return Fail("OpenXR rejected the Vulkan device binding");
}
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()) {
DestroySwapchains();
@@ -329,6 +337,21 @@ public:
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) {
frame = {};
frame.presentation = presentation;
@@ -340,6 +363,9 @@ public:
Fail("BeginFrame called while another OpenXR frame is active");
return OpenXRBeginStatus::Error;
}
if (!ResizeWritablePair()) {
return OpenXRBeginStatus::Error;
}
const OpenXRFrameStatus status = runtime_->WaitFrame(frame.xr_frame);
if (status != OpenXRFrameStatus::Ready) {
@@ -423,14 +449,7 @@ public:
panel_target = SlotTargetLocked(panel_slots_[slot]);
}
pending_copy_ = {frame.xr_frame.serial, slot, target_count, panel, {}};
for (uint32_t eye = 0; eye < target_count; ++eye) {
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;
}
RecordCopyImagesLocked(target_count, panel);
}
{
std::lock_guard lock(submission_mutex_);
@@ -543,6 +562,9 @@ public:
return primed;
}
}
if (!ResizeWritablePair()) {
return OpenXRBeginStatus::Error;
}
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
// display slot after that: two periods past the last predicted display time.
@@ -774,14 +796,7 @@ public:
diagnostics::OnSwapchainAcquire(acquire_timer);
{
std::lock_guard lock(vk_mutex_);
for (uint32_t eye = 0; eye < target_count; ++eye) {
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;
}
RecordCopyImagesLocked(target_count, panel);
}
return OpenXRBeginStatus::Ready;
}
@@ -1568,6 +1583,20 @@ private:
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.
EyeSlot& CopySlotLocked(const PendingCopy& copy, uint32_t n) noexcept {
return n < copy.target_count ? slots_[n][copy.slot] : panel_slots_[copy.slot];
@@ -1670,7 +1699,8 @@ private:
VkImageCopy region{};
region.srcSubresource = {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],
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
@@ -1804,8 +1834,7 @@ private:
bool CreateSwapchainPair(std::array<EyeSwapchain, kOpenXREyeCount>& pair) {
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
const auto& view = runtime_->ViewConfiguration()[eye];
if (!CreateSwapchain(pair[eye], view.render_width, view.render_height,
if (!CreateSwapchain(pair[eye], eye_size_[eye].width, eye_size_[eye].height,
eye == 0 ? "left eye" : "right eye")) {
return false;
}
@@ -1813,6 +1842,121 @@ private:
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) {
swapchain.width = width;
swapchain.height = height;
@@ -2030,6 +2174,9 @@ private:
OpenXRVulkanGraphicsRequirements requirements_{};
std::array<EyeSwapchain, kOpenXREyeCount> eye_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_,
// shown from retained_panel_swapchain_ (see FinishFrame).
EyeSwapchain panel_swapchain_{};
@@ -2110,6 +2257,8 @@ bool OpenXRVulkanBackend::BindAurora(OpenXRRuntime& runtime) {
return m_impl->BindAurora(runtime);
}
void OpenXRVulkanBackend::SetRenderScale(float scale) { m_impl->SetRenderScale(scale); }
OpenXRBeginStatus OpenXRVulkanBackend::BeginFrame(const OpenXRPresentation& presentation,
OpenXRBackendFrame& frame) {
return m_impl->BeginFrame(presentation, frame);
+75 -2
View File
@@ -216,6 +216,11 @@ public:
if (!runtime.CreateSession(&binding)) return Fail("OpenXR rejected Dawn's Vulkan device binding");
owns_session_ = true;
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 = [&] {
DestroySwapchains();
runtime.DestroySession();
@@ -248,6 +253,21 @@ public:
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,
OpenXRWindowsVulkanFrame& frame) {
frame = {};
@@ -260,6 +280,9 @@ public:
Fail("BeginFrame called while another OpenXR frame is active");
return OpenXRWindowsVulkanBeginStatus::Error;
}
if (!ResizeWritablePair()) {
return OpenXRWindowsVulkanBeginStatus::Error;
}
const OpenXRFrameStatus status = runtime_->WaitFrame(frame.xr_frame);
if (status != OpenXRFrameStatus::Ready) {
@@ -394,6 +417,7 @@ public:
const auto status = KeepAliveCycle();
if (status != OpenXRBeginStatus::Ready) return status;
}
if (!ResizeWritablePair()) return OpenXRBeginStatus::Error;
packet.xr_frame.serial = next_packet_serial_++;
packet.xr_frame.predicted_display_time = last_display_time_ + 2 * last_display_period_;
packet.xr_frame.predicted_display_period = last_display_period_;
@@ -841,8 +865,7 @@ private:
bool CreateSwapchainPair(std::array<EyeSwapchain, kOpenXREyeCount>& pair) {
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
const auto& view = runtime_->ViewConfiguration()[eye];
if (!CreateSwapchain(pair[eye], view.render_width, view.render_height,
if (!CreateSwapchain(pair[eye], eye_size_[eye].width, eye_size_[eye].height,
eye == 0 ? "left eye" : "right eye")) {
return false;
}
@@ -850,6 +873,51 @@ private:
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) {
swapchain.width = width;
swapchain.height = height;
@@ -1102,6 +1170,9 @@ private:
OpenXRWindowsVulkanGraphicsRequirements requirements_{};
std::array<EyeSwapchain, kOpenXREyeCount> eye_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_,
// shown from retained_panel_swapchain_ (see FinishFrame).
EyeSwapchain panel_swapchain_{};
@@ -1158,6 +1229,8 @@ bool OpenXRWindowsVulkanBackend::BindAurora(OpenXRRuntime& runtime) {
return m_impl->BindAurora(runtime);
}
void OpenXRWindowsVulkanBackend::SetRenderScale(float scale) { m_impl->SetRenderScale(scale); }
OpenXRWindowsVulkanBeginStatus OpenXRWindowsVulkanBackend::BeginFrame(
const OpenXRWindowsVulkanPresentation& presentation, OpenXRWindowsVulkanFrame& frame) {
return m_impl->BeginFrame(presentation, frame);