mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 09:00:28 +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
@@ -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)];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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, ®ion);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user