mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 10:00:27 +02:00
Add adaptive resolution for the immersive eyes
[vr] adaptive_resolution (off by default, in the VR tab) lowers the race's eye resolution by 10% steps, down to 70%, while new eye frames fall below 85% of the game's 60 FPS, and raises it again after three seconds on time. Adapted from heurazy/Wiicompiled_VR-PLUS. The eyes render into the top-left corner of the full swapchain image and the projection layer shows only that corner, so the compositor upscales them and no swapchain is recreated, unlike render_scale. The Linux same-device bridge now copies an eye smaller than its target; the Quest's bridge and layer already did. The PC's D3D12 path copies whole eyes, so the setting is not offered on Windows. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sabhzu6VgoWcHtpSk4C9KZ
This commit is contained in:
10 files changed
+207
-6
No files matched your search
@@ -159,6 +159,7 @@ int g_vrFoveation = static_cast<int>(RuntimeConfigFile::VrFoveationLevelIndex(Ru
|
||||
bool g_vrEyeTrackedFoveation = RuntimeConfigFile::VrEyeTrackedFoveation();
|
||||
#endif
|
||||
bool g_vrRepeatFrames = RuntimeConfigFile::VrRepeatFrames();
|
||||
bool g_vrAdaptiveResolution = RuntimeConfigFile::VrAdaptiveResolution();
|
||||
bool g_vrFirstPerson = RuntimeConfigFile::VrFirstPerson(false);
|
||||
bool g_vrFirstPersonToggleClick = RuntimeConfigFile::VrFirstPersonToggleClick();
|
||||
// Set from any thread by the right-thumbstick click, applied on the game thread.
|
||||
@@ -1568,6 +1569,18 @@ void DrawVrSettings() {
|
||||
"fill the gaps itself, which doubles the HUD and the menu screen as you turn your head "
|
||||
"(SteamVR on the Steam Frame). Applies immediately.");
|
||||
}
|
||||
#if !defined(_WIN32)
|
||||
// Only the Vulkan backends show part of an eye image; the PC's copy whole eyes.
|
||||
if (ImGui::Checkbox("Adaptive resolution (experimental)", &g_vrAdaptiveResolution)) {
|
||||
RuntimeConfigFile::SetVrAdaptiveResolution(g_vrAdaptiveResolution);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"During races, lowers the eyes' resolution a step at a time, down to 70%%, while new "
|
||||
"frames fall behind the game's 60 FPS, and raises it again once they keep up. Each change "
|
||||
"rebuilds the foveation maps. Applies immediately.");
|
||||
}
|
||||
#endif
|
||||
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)];
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "runtime_config.h"
|
||||
#include "gx_thread.h"
|
||||
#include "runtime_log.h"
|
||||
#include "vr/adaptive_resolution.h"
|
||||
#include "vr/eye_gaze.h"
|
||||
#include "vr/mkw_vr_culling.h"
|
||||
#include "vr/mkw_vr_first_person.h"
|
||||
@@ -111,6 +112,15 @@ inline constexpr bool kWindowShapedEyesSupported = true;
|
||||
inline constexpr bool kWindowShapedEyesSupported = false;
|
||||
#endif
|
||||
|
||||
// Whether the immersive eyes can be rendered smaller than their swapchain images for
|
||||
// [vr] adaptive_resolution: the backend copies them into a corner of the image and its projection
|
||||
// layer shows that corner. The Vulkan backends do; the PC's D3D12 one copies whole eyes.
|
||||
#if defined(_WIN32)
|
||||
inline constexpr bool kScaledEyesSupported = false;
|
||||
#else
|
||||
inline constexpr bool kScaledEyesSupported = true;
|
||||
#endif
|
||||
|
||||
struct Quaternion {
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
@@ -1506,6 +1516,15 @@ private:
|
||||
destination.eyes[eye].height = source.render_height[eye];
|
||||
}
|
||||
}
|
||||
// The backend shows the part of each image this size fills, so the field of view is kept.
|
||||
if (const float scale = adaptive_scale_.load(std::memory_order_relaxed); kScaledEyesSupported && scale < 1.0f) {
|
||||
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||
source.render_width[eye] = AdaptiveResolution::Scaled(source.render_width[eye], scale);
|
||||
source.render_height[eye] = AdaptiveResolution::Scaled(source.render_height[eye], scale);
|
||||
destination.eyes[eye].width = source.render_width[eye];
|
||||
destination.eyes[eye].height = source.render_height[eye];
|
||||
}
|
||||
}
|
||||
// Read once so both eyes are built from the same angle even if the
|
||||
// settings slider moves between them.
|
||||
const float lean_back_radians =
|
||||
@@ -1879,7 +1898,21 @@ private:
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const float elapsed = std::chrono::duration<float>(now - timing_start_).count();
|
||||
if (elapsed >= 1.0f) {
|
||||
rendered_fps_.store(static_cast<float>(timing_submissions_) / elapsed, std::memory_order_relaxed);
|
||||
const float fps = static_cast<float>(timing_submissions_) / elapsed;
|
||||
rendered_fps_.store(fps, std::memory_order_relaxed);
|
||||
// Measured during races only: menus and loading screens run below 60 on their own.
|
||||
// The target is the game's 60 FPS, or the headset's rate when frames are interpolated.
|
||||
const bool adaptive = kScaledEyesSupported && RuntimeConfigFile::VrAdaptiveResolution();
|
||||
if (!adaptive || last_immersive_) {
|
||||
const float target = aurora_get_stereo_frame_interpolation() ? hz : std::min(hz, 60.0f);
|
||||
const float previous = adaptive_resolution_.Scale();
|
||||
const float scale = adaptive_resolution_.Observe(fps, target, adaptive);
|
||||
adaptive_scale_.store(scale, std::memory_order_relaxed);
|
||||
if (scale != previous) {
|
||||
RT_LOG(RT_TAG_RUNTIME) << "OpenXR: adaptive resolution " << static_cast<int>(scale * 100.0f + 0.5f)
|
||||
<< "% (" << fps << " of " << target << " FPS)" << std::endl;
|
||||
}
|
||||
}
|
||||
timing_start_ = now;
|
||||
timing_submissions_ = 0;
|
||||
}
|
||||
@@ -2031,6 +2064,9 @@ private:
|
||||
std::atomic<float> rendered_fps_{0};
|
||||
std::chrono::steady_clock::time_point timing_start_ = std::chrono::steady_clock::now();
|
||||
uint32_t timing_submissions_ = 0;
|
||||
// [vr] adaptive_resolution: measured on the pacing thread, read where eyes are published.
|
||||
AdaptiveResolution adaptive_resolution_;
|
||||
std::atomic<float> adaptive_scale_{1.0f};
|
||||
PFN_xrGetDisplayRefreshRateFB get_display_refresh_rate_ = nullptr;
|
||||
PFN_xrEnumerateDisplayRefreshRatesFB enumerate_refresh_rates_ = nullptr;
|
||||
PFN_xrRequestDisplayRefreshRateFB request_refresh_rate_ = nullptr;
|
||||
|
||||
@@ -744,10 +744,11 @@ public:
|
||||
views[eye].pose.position = frame.xr_frame.views[eye].pose.position;
|
||||
views[eye].fov = frame.xr_frame.views[eye].fov;
|
||||
views[eye].subImage.swapchain = retained_swapchains_[eye].handle;
|
||||
// The eyes may fill only a corner of the image ([vr] adaptive_resolution).
|
||||
views[eye].subImage.imageRect = {
|
||||
{0, 0},
|
||||
{static_cast<int32_t>(retained_swapchains_[eye].width),
|
||||
static_cast<int32_t>(retained_swapchains_[eye].height)}};
|
||||
{static_cast<int32_t>(std::min(frame.render_width[eye], retained_swapchains_[eye].width)),
|
||||
static_cast<int32_t>(std::min(frame.render_height[eye], retained_swapchains_[eye].height))}};
|
||||
views[eye].subImage.imageArrayIndex = 0;
|
||||
}
|
||||
XrCompositionLayerProjection projection{XR_TYPE_COMPOSITION_LAYER_PROJECTION};
|
||||
|
||||
Reference in new issue
Block a user