Refactor VR settings: remove deprecated options and update eye replay handling

This commit is contained in:
iChris4 committed 2026-09-26 03:02:07 +02:00
1 parent e4d198ee13
commit 566a22857c
6 files changed
+169 -237

No files matched your search

+19 -20
View File
@@ -47,9 +47,6 @@ hud_width_meters = 2.4
hud_virtual_screen = true hud_virtual_screen = true
flat_screen = false flat_screen = false
immersive_window = false immersive_window = false
stop_at_display_copy = true
skip_copy_clears = true
single_pass_eyes = true
first_person = false first_person = false
first_person_toggle_click = true first_person_toggle_click = true
first_person_seat = "cockpit" first_person_seat = "cockpit"
@@ -187,16 +184,17 @@ panel's rectangle), at the same size per pixel, so nothing moves.
`hand_tracking` (Quest only, default off) makes the first-person cockpit's hands follow the `hand_tracking` (Quest only, default off) makes the first-person cockpit's hands follow the
headset's hand tracking; see "Tracked hands" under headset's hand tracking; see "Tracked hands" under
[Steering wheel and hand steering](#steering-wheel-and-hand-steering). [Steering wheel and hand steering](#steering-wheel-and-hand-steering).
`stop_at_display_copy` ends eye replay at the final `GXCopyDisp`, matching the frame shown on the How each eye is replayed is fixed; the former `stop_at_display_copy`, `skip_copy_clears` and
desktop. `skip_copy_clears` independently suppresses the EFB reset performed after a copy. Both `single_pass_eyes` settings are ignored. An eye ends at the frame's final `GXCopyDisp`, so it holds
default on and can be changed live from the F10 settings bar for diagnostics. the frame shown on the desktop. It keeps the EFB reset that follows a display copy: Aurora marks
`single_pass_eyes` draws each eye in one render pass. The desktop image ends a render pass at every only a display copy's reset, the final one lies past the replay's end, and an earlier one erases
GX copy, because the copy reads what was drawn before it; an eye samples the copies the desktop exactly what the final copy did not show. And it is drawn in one render pass. The desktop image ends
image made and never performs them, so it keeps drawing in the pass it has open, and it leaves out a render pass at every GX copy, because the copy reads what was drawn before it; an eye samples the
whatever a later clear of the whole color and depth erases. The picture is the same with less GPU copies the desktop image made and never performs them, so it keeps drawing in the pass it has open,
memory traffic, which a tiled mobile GPU pays for at every split (the "Eye replay plan" log line and it leaves out whatever a later clear of the whole color and depth erases. The picture is the
reports each new pass structure). It defaults on and is live; turning it off replays one render pass same with less GPU memory traffic, which a tiled mobile GPU pays for at every split (the "Eye replay
per recorded pass. plan" log line reports each new pass structure). On the Quest, `debug.wiicompiled.eye_passes 0`
replays one render pass per recorded pass again, for A/B timing (`docs/quest-port.md`).
`first_person` and the `first_person_*` values are the first-person camera described below. All `first_person` and the `first_person_*` values are the first-person camera described below. All
four are live and are also exposed in the F10 settings bar. four are live and are also exposed in the F10 settings bar.
`performance_level` is the level asked of the runtime through `XR_EXT_performance_settings` for `performance_level` is the level asked of the runtime through `XR_EXT_performance_settings` for
@@ -216,7 +214,7 @@ Settings page has it too.
The headset's tracked controllers reach the game through an OpenXR action set synced on the pacing The headset's tracked controllers reach the game through an OpenXR action set synced on the pacing
thread (`runtime/src/vr/openxr_input.cpp`), which feeds a virtual SDL gamepad that Aurora assigns thread (`runtime/src/vr/openxr_input.cpp`), which feeds a virtual SDL gamepad that Aurora assigns
to a port like any other. `controller_mode` decides what the game finds on that port, and is live to a port like any other. `controller_mode` decides what the game finds on that port, and is live
from **F10 > VR > VR controllers**; the game sees a change as a controller reconnection. from **F10 > Controller settings > VR controllers**; the game sees a change as a controller reconnection.
The pacing thread only publishes that gamepad; the game thread writes it to SDL where it already The pacing thread only publishes that gamepad; the game thread writes it to SDL where it already
polls controllers (`OpenXRApplyControllerState`, called from `PAD__Read_HLE` and the overlay's polls controllers (`OpenXRApplyControllerState`, called from `PAD__Read_HLE` and the overlay's
@@ -319,7 +317,7 @@ thumbsticks together** instead); the left controller's menu button and the panel
also close it. It can be opened from the desktop as well, with also close it. It can be opened from the desktop as well, with
**F10 → VR → Show these settings in the headset**. **F10 → VR → Show these settings in the headset**.
The panel has the F10 bar's menus as tabs (VR, Graphics, Controllers, Audio, Diagnostics) and a The panel has the F10 bar's menus as tabs (VR, Camera, Graphics, Controllers, Audio, Diagnostics) and a
*Recenter view* button. Aim a controller at it: the cursor goes where you aim, a trigger (or A / X) *Recenter view* button. Aim a controller at it: the cursor goes where you aim, a trigger (or A / X)
selects and drags sliders, and a thumbstick scrolls. Whichever hand last pulled its trigger does the selects and drags sliders, and a thumbstick scrolls. Whichever hand last pulled its trigger does the
pointing. Changes apply exactly as they do from the F10 bar, and the two stay in step. pointing. Changes apply exactly as they do from the F10 bar, and the two stay in step.
@@ -540,7 +538,7 @@ full-lock angle as the stick (`wheel_kart_degrees`, `wheel_bike_degrees`), and h
aside while it drives. aside while it drives.
**Tracked hands.** `hand_tracking` (Quest only for now, default off; the Quest launcher's **Tracked hands.** `hand_tracking` (Quest only for now, default off; the Quest launcher's
Settings > VR and the headset panel's VR tab, under hand steering, which it needs) poses the Settings > VR and the headset panel's Camera tab, under hand steering, which it needs) poses the
cockpit hands from the headset's hand tracking instead of curling them with the grip. Two hand cockpit hands from the headset's hand tracking instead of curling them with the grip. Two hand
trackers (`XR_EXT_hand_tracking`) are located every XR frame at the display time. While the trackers (`XR_EXT_hand_tracking`) are located every XR frame at the display time. While the
controllers are held the Quest builds the joints from their touch sensors controllers are held the Quest builds the joints from their touch sensors
@@ -769,8 +767,9 @@ eye's last draw Aurora covers the eye with one full-screen triangle (`aurora-mai
that keeps the colour inside the window with alpha 1 and leaves transparent black outside it, with a that keeps the colour inside the window with alpha 1 and leaves transparent black outside it, with a
one-pixel ramp at the edge. The triangle carries, at each corner, where that pixel's ray meets the one-pixel ramp at the edge. The triangle carries, at each corner, where that pixel's ray meets the
window's plane in homogeneous window coordinates (`stereo_replay::window_mask`), which interpolate window's plane in homogeneous window coordinates (`stereo_replay::window_mask`), which interpolate
exactly across the image. With `single_pass_eyes` it is drawn in the eye's own last render pass, so it exactly across the image. It is drawn in the eye's own last render pass, so it adds no pass and no
adds no pass and no tile load; otherwise it takes a pass of its own, as the cockpit overlay does. On tile load; an eye replayed one render pass per recorded pass (`debug.wiicompiled.eye_passes 0`)
gives it a pass of its own, as the cockpit overlay has. On
the Quest the backend submits the passthrough layer, then the projection layer with the Quest the backend submits the passthrough layer, then the projection layer with
`XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT` (premultiplied alpha), then the settings panel. `XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT` (premultiplied alpha), then the settings panel.
The flag travels with the packet, so the eyes Aurora masked and the layer that blends them always The flag travels with the packet, so the eyes Aurora masked and the layer that blends them always
@@ -848,7 +847,7 @@ pipeline flag. That is why the launch decides.
A density map forces Adreno into binned rendering, where every extra render pass in an eye stores A density map forces Adreno into binned rendering, where every extra render pass in an eye stores
and reloads the whole eye. DolphinXR measured foveation as a net loss on Mario Kart Wii for exactly and reloads the whole eye. DolphinXR measured foveation as a net loss on Mario Kart Wii for exactly
that reason (its bloom chain splits the frame about 20 times). An eye is therefore foveated only that reason (its bloom chain splits the frame about 20 times). An eye is therefore foveated only
when it is drawn in a single render pass (`single_pass_eyes`); an eye that a partial clear still when it is drawn in a single render pass, as every eye is by default; an eye that a partial clear still
splits is drawn at full rate. The session log reports what happened: "Fragment density maps: splits is drawn at full rate. The session log reports what happened: "Fragment density maps:
enabled" at startup, one "eye foveation" line per eye and level with the map's size, and the "Eye enabled" at startup, one "eye foveation" line per eye and level with the map's size, and the "Eye
replay plan" lines. `debug.wiicompiled.foveation <0-3>` overrides the level for A/B timing, and replay plan" lines. `debug.wiicompiled.foveation <0-3>` overrides the level for A/B timing, and
@@ -858,7 +857,7 @@ What it saves depends on how much of an eye's cost is shading pixels. The number
Quest 3 at Luigi Circuit's Grand Prix start: GPU time of both eyes per frame, all settings Quest 3 at Luigi Circuit's Grand Prix start: GPU time of both eyes per frame, all settings
interleaved within one session (`docs/quest-port.md` has the method). interleaved within one session (`docs/quest-port.md` has the method).
| `render_scale` (eye size) | One pass per recorded pass | `single_pass_eyes` | `low` | `medium` | `high` | | `render_scale` (eye size) | One pass per recorded pass | One pass per eye | `low` | `medium` | `high` |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 0.8 (1344x1408) | 5.82 ms | 5.11 ms | 5.09 ms | 5.36 ms | 5.11 ms | | 0.8 (1344x1408) | 5.82 ms | 5.11 ms | 5.09 ms | 5.36 ms | 5.11 ms |
| 1.3 (2184x2288) | 6.32 ms | 5.81 ms | 5.33 ms | 5.00 ms | 4.56 ms | | 1.3 (2184x2288) | 6.32 ms | 5.81 ms | 5.33 ms | 5.00 ms | 4.56 ms |
+2 -2
View File
@@ -144,7 +144,7 @@ Aurora (`lib/webgpu/fdm.cpp`, `lib/gfx/foveation.hpp`) builds one map per eye,
32 pixels per texel (42x44 for 1344x1408 eyes). The map is centred on that eye's 32 pixels per texel (42x44 for 1344x1408 eyes). The map is centred on that eye's
forward direction and rebuilt when the eye's size, field of view or level forward direction and rebuilt when the eye's size, field of view or level
changes. It is bound to a second view of the eye texture that only a changes. It is bound to a second view of the eye texture that only a
single-render-pass immersive eye renders through (`single_pass_eyes`). Density single-render-pass immersive eye renders through (every eye, by default). Density
bytes are 255, 127 and 63: a fragment covers 1/density pixels rounded down to a bytes are 255, 127 and 63: a fragment covers 1/density pixels rounded down to a
supported size, so a half written as 128 could round back to one pixel. supported size, so a half written as 128 could round back to one pixel.
Changing the level is live. The launch decides whether the device has density Changing the level is live. The launch decides whether the device has density
@@ -919,7 +919,7 @@ the app:
| `debug.wiicompiled.vtxpad 0` | Turns the stride padding off, to re-check a driver update | | `debug.wiicompiled.vtxpad 0` | Turns the stride padding off, to re-check a driver update |
| `debug.wiicompiled.validation 1` | Keeps WebGPU validation and robustness on in release builds | | `debug.wiicompiled.validation 1` | Keeps WebGPU validation and robustness on in release builds |
| `debug.wiicompiled.panel_layer 0` | Draws the headset settings panel into the eye images instead of on its own quad layer (`OPENXR.md`, Settings in the headset); read about once a second, so it can be switched while the panel is open | | `debug.wiicompiled.panel_layer 0` | Draws the headset settings panel into the eye images instead of on its own quad layer (`OPENXR.md`, Settings in the headset); read about once a second, so it can be switched while the panel is open |
| `debug.wiicompiled.eye_passes 0` | Replays each eye in one render pass per recorded pass, as before `single_pass_eyes` (`OPENXR.md`); `1` forces the single pass and an empty value restores the setting. Read about once a second, for A/B timing inside one session | | `debug.wiicompiled.eye_passes 0` | Replays each eye in one render pass per recorded pass, as before eyes were drawn in a single pass (`OPENXR.md`); `1` or an empty value restores the single pass. Read about once a second, for A/B timing inside one session |
| `debug.wiicompiled.foveation <0-3>` | Overrides the foveation level (off, low, medium, high) within one session; an empty value restores the setting. Needs a session launched with foveation on. Read about once a second | | `debug.wiicompiled.foveation <0-3>` | Overrides the foveation level (off, low, medium, high) within one session; an empty value restores the setting. Needs a session launched with foveation on. Read about once a second |
| `debug.wiicompiled.fdm 0` | Launches without fragment density maps at all, whatever `foveation` says, which also drops their flag from every pipeline; `1` asks for them even with `foveation = "off"` | | `debug.wiicompiled.fdm 0` | Launches without fragment density maps at all, whatever `foveation` says, which also drops their flag from every pipeline; `1` asks for them even with `foveation = "off"` |
| `debug.wiicompiled.inject <n>:<button>` | Presses `a`, `b`, `x`, `y`, `start`, `up`, `down`, `left` or `right` for 12 XR frames each time `<n>` changes. As a Wii Remote, `x`/`y`/`start` are 1/2/+, the directions push the Nunchuk stick, and `home`, `c` and `z` also exist. `panel` presses the settings panel's button (left Y, or both thumbsticks as a gamepad), opening or closing it (see `OPENXR.md`). `flick` plays the bare hands' flick, one 150 ms shake of the remote (a trick off a ramp, a wheelie on a bike), with the controllers or none | | `debug.wiicompiled.inject <n>:<button>` | Presses `a`, `b`, `x`, `y`, `start`, `up`, `down`, `left` or `right` for 12 XR frames each time `<n>` changes. As a Wii Remote, `x`/`y`/`start` are 1/2/+, the directions push the Nunchuk stick, and `home`, `c` and `z` also exist. `panel` presses the settings panel's button (left Y, or both thumbsticks as a gamepad), opening or closing it (see `OPENXR.md`). `flick` plays the bare hands' flick, one 150 ms shake of the remote (a trick off a ramp, a wheelie on a bike), with the controllers or none |
-44
View File
@@ -60,9 +60,6 @@ struct RuntimeUserConfig {
std::optional<bool> vrFlatScreen; std::optional<bool> vrFlatScreen;
std::optional<bool> vrImmersiveWindow; std::optional<bool> vrImmersiveWindow;
std::optional<bool> vrPassthrough; std::optional<bool> vrPassthrough;
std::optional<bool> vrStopAtDisplayCopy;
std::optional<bool> vrSkipCopyClears;
std::optional<bool> vrSinglePassEyes;
std::optional<std::string> vrMirrorView; std::optional<std::string> vrMirrorView;
std::optional<std::string> vrControllerMode; std::optional<std::string> vrControllerMode;
std::optional<uint32_t> vrFrameInterpolationFps; std::optional<uint32_t> vrFrameInterpolationFps;
@@ -538,17 +535,6 @@ inline void EnsureConfigFile() {
"# Quest (black elsewhere). Flat Screen mode wins over it.\n" "# Quest (black elsewhere). Flat Screen mode wins over it.\n"
"# Changeable live from the F10 menu.\n" "# Changeable live from the F10 menu.\n"
"immersive_window = false\n" "immersive_window = false\n"
"# EFB replay controls for the per-eye views, changeable live\n"
"# from the F10 menu. stop_at_display_copy ends each eye at the\n"
"# frame's final GXCopyDisp; skip_copy_clears drops the EFB\n"
"# reset a GX copy performs afterwards. Both keep that reset\n"
"# from erasing the eye, and both are safe to turn off.\n"
"stop_at_display_copy = true\n"
"skip_copy_clears = true\n"
"# Draw each eye in one render pass across the frame's GX copies,\n"
"# which only the desktop image performs: the same picture with\n"
"# less GPU memory traffic. Changeable live from the F10 menu.\n"
"single_pass_eyes = true\n"
"# Put the camera at the Player 1 driver's head instead of behind\n" "# Put the camera at the Player 1 driver's head instead of behind\n"
"# the kart, with the horizon kept level. Changeable live from the\n" "# the kart, with the horizon kept level. Changeable live from the\n"
"# F10 menu, and only during a single-screen race.\n" "# F10 menu, and only during a single-screen race.\n"
@@ -796,9 +782,6 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
config.vrFlatScreen = FindConfigValue<bool>(document, "vr", "flat_screen"); config.vrFlatScreen = FindConfigValue<bool>(document, "vr", "flat_screen");
config.vrImmersiveWindow = FindConfigValue<bool>(document, "vr", "immersive_window"); config.vrImmersiveWindow = FindConfigValue<bool>(document, "vr", "immersive_window");
config.vrPassthrough = FindConfigValue<bool>(document, "vr", "passthrough"); config.vrPassthrough = FindConfigValue<bool>(document, "vr", "passthrough");
config.vrStopAtDisplayCopy = FindConfigValue<bool>(document, "vr", "stop_at_display_copy");
config.vrSkipCopyClears = FindConfigValue<bool>(document, "vr", "skip_copy_clears");
config.vrSinglePassEyes = FindConfigValue<bool>(document, "vr", "single_pass_eyes");
config.vrFirstPerson = FindConfigValue<bool>(document, "vr", "first_person"); config.vrFirstPerson = FindConfigValue<bool>(document, "vr", "first_person");
config.vrFirstPersonToggleClick = FindConfigValue<bool>(document, "vr", "first_person_toggle_click"); config.vrFirstPersonToggleClick = FindConfigValue<bool>(document, "vr", "first_person_toggle_click");
if (auto value = FindConfigFloat(document, "vr", "first_person_units_per_meter"); if (auto value = FindConfigFloat(document, "vr", "first_person_units_per_meter");
@@ -1130,21 +1113,6 @@ inline bool SetVrPassthrough(bool value) {
return WriteSetting("vr", "passthrough", value ? "true" : "false"); return WriteSetting("vr", "passthrough", value ? "true" : "false");
} }
inline bool SetVrStopAtDisplayCopy(bool value) {
Mutable().vrStopAtDisplayCopy = value;
return WriteSetting("vr", "stop_at_display_copy", value ? "true" : "false");
}
inline bool SetVrSkipCopyClears(bool value) {
Mutable().vrSkipCopyClears = value;
return WriteSetting("vr", "skip_copy_clears", value ? "true" : "false");
}
inline bool SetVrSinglePassEyes(bool value) {
Mutable().vrSinglePassEyes = value;
return WriteSetting("vr", "single_pass_eyes", value ? "true" : "false");
}
inline bool SetVrFirstPerson(bool value) { inline bool SetVrFirstPerson(bool value) {
Mutable().vrFirstPerson = value; Mutable().vrFirstPerson = value;
return WriteSetting("vr", "first_person", value ? "true" : "false"); return WriteSetting("vr", "first_person", value ? "true" : "false");
@@ -1654,18 +1622,6 @@ inline bool VrPassthrough(bool fallback = true) {
return Get().vrPassthrough.value_or(fallback); return Get().vrPassthrough.value_or(fallback);
} }
inline bool VrStopAtDisplayCopy(bool fallback = true) {
return Get().vrStopAtDisplayCopy.value_or(fallback);
}
inline bool VrSkipCopyClears(bool fallback = true) {
return Get().vrSkipCopyClears.value_or(fallback);
}
inline bool VrSinglePassEyes(bool fallback = true) {
return Get().vrSinglePassEyes.value_or(fallback);
}
inline bool VrFirstPerson(bool fallback = false) { inline bool VrFirstPerson(bool fallback = false) {
return Get().vrFirstPerson.value_or(fallback); return Get().vrFirstPerson.value_or(fallback);
} }
+138 -163
View File
@@ -139,9 +139,6 @@ bool g_showFps = RuntimeConfigFile::ShowFps();
// The same default the VR path itself takes (kVrEnabledDefault), so the F10 switch // The same default the VR path itself takes (kVrEnabledDefault), so the F10 switch
// shows what an unconfigured installation actually starts in. // shows what an unconfigured installation actually starts in.
bool g_vrEnabled = RuntimeConfigFile::VrEnabled(true); bool g_vrEnabled = RuntimeConfigFile::VrEnabled(true);
bool g_vrStopAtDisplayCopy = RuntimeConfigFile::VrStopAtDisplayCopy(true);
bool g_vrSkipCopyClears = RuntimeConfigFile::VrSkipCopyClears(true);
bool g_vrSinglePassEyes = RuntimeConfigFile::VrSinglePassEyes(true);
bool g_vrHudVirtualScreen = RuntimeConfigFile::VrHudVirtualScreen(true); bool g_vrHudVirtualScreen = RuntimeConfigFile::VrHudVirtualScreen(true);
// Race view: Immersive, Immersive window or Flat screen (RuntimeConfigFile::VrRaceView), and // Race view: Immersive, Immersive window or Flat screen (RuntimeConfigFile::VrRaceView), and
// Flat Screen mode as the flag the race view rows below are disabled by. // Flat Screen mode as the flag the race view rows below are disabled by.
@@ -868,7 +865,43 @@ void DrawRumbleSettings() {
} }
} }
// What the headset's controllers are to the game. One choice for the pair, whichever port they
// land on, and live like the rest of the VR settings: without a session it is what the next starts on.
void DrawVrControllerSettings() {
if (ImGui::Combo("VR controllers", &g_vrControllerMode, kVrControllerModeLabels.data(),
static_cast<int>(kVrControllerModeLabels.size()))) {
mkw::vr::OpenXRSetControllerMode(static_cast<mkw::vr::OpenXRControllerMode>(g_vrControllerMode));
RuntimeConfigFile::SetVrControllerMode(kVrControllerModeNames[static_cast<size_t>(g_vrControllerMode)]);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Wii Remote + Nunchuk: the right controller is a Wii Remote, with motion and a pointer "
"that lands where you aim on the virtual screen; the left one is the Nunchuk.\n"
" Right: A = A, trigger = B, B = C (look behind), stick up/down = 1/2\n"
" Left: stick = Nunchuk stick, trigger = Z, X = -, menu = +, Y = settings panel\n"
" The grips press nothing; they take hold of the wheel with hand steering.\n"
"Gamepad: both controllers are one ordinary controller, read as a GameCube pad.\n"
"Applies immediately; the game sees the controller change as a reconnection.");
}
if (mkw::vr::OpenXRIsRunning() &&
mkw::vr::OpenXRGetControllerMode() == mkw::vr::OpenXRControllerMode::WiiRemote) {
mkw::vr::OpenXRWiiRemoteSample remote;
if (mkw::vr::OpenXRReadWiiRemote(remote)) {
if (remote.pointer_valid) {
ImGui::TextDisabled("Pointer %+.2f %+.2f | Remote %+.2f %+.2f %+.2f g", remote.pointer[0],
remote.pointer[1], remote.acc[0], remote.acc[1], remote.acc[2]);
} else {
ImGui::TextDisabled("Pointer off screen | Remote %+.2f %+.2f %+.2f g", remote.acc[0],
remote.acc[1], remote.acc[2]);
}
}
}
}
void DrawControllerSettings() { void DrawControllerSettings() {
// First, since the per-port settings below stop early when a port has nothing to show.
DrawVrControllerSettings();
ImGui::Separator();
if (ImGui::CollapsingHeader("USB wheel and pedals (player 1)")) { if (ImGui::CollapsingHeader("USB wheel and pedals (player 1)")) {
physical_wheel::DrawSettings(); physical_wheel::DrawSettings();
ImGui::Separator(); ImGui::Separator();
@@ -1364,65 +1397,30 @@ void DrawVrSettings() {
RuntimeConfigFile::SetVrEnabled(g_vrEnabled); RuntimeConfigFile::SetVrEnabled(g_vrEnabled);
} }
ImGui::TextDisabled("OpenXR mode changes take effect after restarting the game."); ImGui::TextDisabled("OpenXR mode changes take effect after restarting the game.");
// Live, unlike the enable toggle above, so it is left usable either way: // Everything below is live, unlike the enable toggle, so it is left usable either way: set
// set before a restart it is simply what the next session starts on. // before a restart it is simply what the next session starts on.
if (ImGui::Combo("Desktop view", &g_vrMirrorView, kVrMirrorViewLabels.data(), if (ImGui::Combo("Race view", &g_vrRaceView, kVrRaceViewLabels.data(),
static_cast<int>(kVrMirrorViewLabels.size()))) { static_cast<int>(kVrRaceViewLabels.size()))) {
aurora_set_stereo_mirror_view(static_cast<AuroraStereoMirrorView>(g_vrMirrorView)); const auto view = static_cast<RuntimeConfigFile::VrRaceView>(g_vrRaceView);
RuntimeConfigFile::SetVrMirrorView(kVrMirrorViewNames[static_cast<size_t>(g_vrMirrorView)]); g_vrFlatScreen = view == RuntimeConfigFile::VrRaceView::FlatScreen;
RuntimeConfigFile::SetVrRaceView(view);
mkw::vr::MkwVRPolicySetImmersiveRaces(!g_vrFlatScreen);
mkw::vr::OpenXRSetImmersiveWindow(view == RuntimeConfigFile::VrRaceView::ImmersiveWindow);
mkw::vr::MkwVRFirstPersonApplyConfiguredSettings();
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip( ImGui::SetTooltip(
"What this window shows while the headset is running. Normal keeps the ordinary " "Immersive plays races all around you in stereo. Immersive window keeps that "
"desktop view, the eye choices mirror what you are actually seeing in the headset, " "stereo view but shows it only through a window where the menu screen sits, with "
"and None leaves the window black. Menus reach the headset as a screen showing this " "your room around it on the Quest (black elsewhere); look through it from another "
"same desktop image, so the eye choices only differ from Normal during a race."); "angle and the view shifts as through a real window. Flat screen plays races on "
} "the menu screen through the game's own camera; the Camera settings and the lean-back "
ImGui::BeginDisabled(!mkw::vr::OpenXRIsRunning()); "angle do not apply to it. Applies immediately.");
bool settingsPanelOpen = mkw::vr::OpenXRSettingsPanelOpen();
if (ImGui::Checkbox("Show these settings in the headset", &settingsPanelOpen)) {
mkw::vr::OpenXRSetSettingsPanelOpen(settingsPanelOpen);
}
ImGui::EndDisabled();
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
ImGui::SetTooltip(
"Opens these settings on a panel in front of you, in menus and races alike.\n"
"In the headset, left Y opens and closes it too (with Gamepad VR controllers,\n"
"click both thumbsticks together instead).\n"
"Aim at it and pull a trigger to change a setting; push a thumbstick to scroll.\n"
"While it is open the game does not see the VR controllers.");
}
if (ImGui::Combo("VR controllers", &g_vrControllerMode, kVrControllerModeLabels.data(),
static_cast<int>(kVrControllerModeLabels.size()))) {
mkw::vr::OpenXRSetControllerMode(static_cast<mkw::vr::OpenXRControllerMode>(g_vrControllerMode));
RuntimeConfigFile::SetVrControllerMode(kVrControllerModeNames[static_cast<size_t>(g_vrControllerMode)]);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Wii Remote + Nunchuk: the right controller is a Wii Remote, with motion and a pointer "
"that lands where you aim on the virtual screen; the left one is the Nunchuk.\n"
" Right: A = A, trigger = B, B = C (look behind), stick up/down = 1/2\n"
" Left: stick = Nunchuk stick, trigger = Z, X = -, menu = +, Y = settings panel\n"
" The grips press nothing; they take hold of the wheel with hand steering.\n"
"Gamepad: both controllers are one ordinary controller, read as a GameCube pad.\n"
"Applies immediately; the game sees the controller change as a reconnection.");
}
if (mkw::vr::OpenXRIsRunning() &&
mkw::vr::OpenXRGetControllerMode() == mkw::vr::OpenXRControllerMode::WiiRemote) {
mkw::vr::OpenXRWiiRemoteSample remote;
if (mkw::vr::OpenXRReadWiiRemote(remote)) {
if (remote.pointer_valid) {
ImGui::TextDisabled("Pointer %+.2f %+.2f | Remote %+.2f %+.2f %+.2f g", remote.pointer[0],
remote.pointer[1], remote.acc[0], remote.acc[1], remote.acc[2]);
} else {
ImGui::TextDisabled("Pointer off screen | Remote %+.2f %+.2f %+.2f g", remote.acc[0],
remote.acc[1], remote.acc[2]);
}
}
} }
// Live, and usable without a session like the mirror above. It applies when the slider is let ImGui::Separator();
// go rather than at every step of a drag, since each new size means new eye swapchains. // Applied 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, ImGui::SliderInt("Render resolution", &g_vrRenderScalePercent,
static_cast<int>(RuntimeConfigFile::kVrRenderScaleMin * 100.0f), static_cast<int>(RuntimeConfigFile::kVrRenderScaleMin * 100.0f),
static_cast<int>(RuntimeConfigFile::kVrRenderScaleMax * 100.0f), "%d%%", static_cast<int>(RuntimeConfigFile::kVrRenderScaleMax * 100.0f), "%d%%",
@@ -1449,7 +1447,38 @@ void DrawVrSettings() {
eyes.scaled_height, g_vrRenderScalePercent); eyes.scaled_height, g_vrRenderScalePercent);
} }
} }
#if defined(__ANDROID__)
if (ImGui::Checkbox("Passthrough around the menu screen", &g_vrPassthrough)) {
mkw::vr::OpenXRSetPassthrough(g_vrPassthrough);
RuntimeConfigFile::SetVrPassthrough(g_vrPassthrough);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Shows your room through the headset's cameras around the menu screen and every "
"other screen outside an immersive race, instead of black. Immersive races stay "
"fully virtual; the immersive window and the Flat Screen race have the room "
"around them too. Applies immediately.");
}
// Shows the live level, which debug.wiicompiled.foveation can override.
g_vrFoveation = static_cast<int>(aurora_get_stereo_foveation());
if (ImGui::Combo("Foveated rendering", &g_vrFoveation, kVrFoveationLabels.data(),
static_cast<int>(kVrFoveationLabels.size()))) {
aurora_set_stereo_foveation(static_cast<uint32_t>(g_vrFoveation));
RuntimeConfigFile::SetVrFoveation(
std::string(RuntimeConfigFile::kVrFoveationLevels[static_cast<size_t>(g_vrFoveation)]));
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"%s", aurora_stereo_foveation_available()
? "Shades the edges of the race view in 2x2, then 4x4 pixel blocks, where the "
"lenses blur the picture anyway, to free GPU time for a higher render scale or a "
"steadier frame rate. Higher levels start closer to the centre; High also "
"coarsens the corners of the HUD. Menus are never foveated. Applies immediately."
: "Shades the edges of the race view in 2x2, then 4x4 pixel blocks, where the "
"lenses blur the picture anyway, to free GPU time. This session started with it "
"off, or without a GPU that supports it: a new level applies after a restart.");
}
#endif
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)];
@@ -1471,65 +1500,21 @@ void DrawVrSettings() {
} }
} }
// Like the mirror above and unlike the enable toggle, these two apply to the
// very next frame, so they can be compared from inside a running race.
ImGui::Separator(); ImGui::Separator();
ImGui::Text("VR eye replay (EFB)"); if (ImGui::Combo("Desktop view", &g_vrMirrorView, kVrMirrorViewLabels.data(),
if (ImGui::Checkbox("Stop eye at display copy", &g_vrStopAtDisplayCopy)) { static_cast<int>(kVrMirrorViewLabels.size()))) {
aurora_set_stereo_stop_at_display_copy(g_vrStopAtDisplayCopy); aurora_set_stereo_mirror_view(static_cast<AuroraStereoMirrorView>(g_vrMirrorView));
RuntimeConfigFile::SetVrStopAtDisplayCopy(g_vrStopAtDisplayCopy); RuntimeConfigFile::SetVrMirrorView(kVrMirrorViewNames[static_cast<size_t>(g_vrMirrorView)]);
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip( ImGui::SetTooltip(
"Ends each eye at the frame's final GXCopyDisp, so an eye holds exactly the " "What this window shows while the headset is running. Normal keeps the ordinary "
"image the game presented. Turn off to replay the whole pass list."); "desktop view, the eye choices mirror what you are actually seeing in the headset, "
} "and None leaves the window black. Menus reach the headset as a screen showing this "
if (ImGui::Checkbox("Skip EFB copy clears", &g_vrSkipCopyClears)) { "same desktop image, so the eye choices only differ from Normal during a race.");
aurora_set_stereo_skip_copy_clears(g_vrSkipCopyClears);
RuntimeConfigFile::SetVrSkipCopyClears(g_vrSkipCopyClears);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Drops the EFB reset a GX copy performs after copying. That reset prepares the "
"Wii's reused EFB for the next frame; an eye attachment is built fresh, so "
"replaying it only erases the eye.");
}
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + Scaled(380.0f));
ImGui::TextDisabled(
"Both apply on the next frame. Turning either off restores the raw replay and is "
"expected to black out the eyes.");
ImGui::PopTextWrapPos();
// Shows the live state, which the Quest's debug.wiicompiled.eye_passes can override.
g_vrSinglePassEyes = aurora_get_stereo_single_pass_eyes();
if (ImGui::Checkbox("One render pass per eye", &g_vrSinglePassEyes)) {
aurora_set_stereo_single_pass_eyes(g_vrSinglePassEyes);
RuntimeConfigFile::SetVrSinglePassEyes(g_vrSinglePassEyes);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Keeps drawing each eye in the render pass it has open across the frame's GX "
"copies, which only the desktop image performs, and skips what a later clear "
"erases. Same picture, less GPU memory traffic; turn off to compare.");
} }
ImGui::Separator(); ImGui::Separator();
ImGui::Text("VR 2D layer");
ImGui::BeginDisabled(
g_vrFlatScreen || g_vrRaceView == static_cast<int>(RuntimeConfigFile::VrRaceView::ImmersiveWindow));
if (ImGui::Checkbox("2D layer on a virtual screen", &g_vrHudVirtualScreen)) {
ApplyVrHudVirtualScreen();
RuntimeConfigFile::SetVrHudVirtualScreen(g_vrHudVirtualScreen);
}
ImGui::EndDisabled();
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
ImGui::SetTooltip(
"Puts the minimap, race position, item roulette and the rest of the race HUD on a "
"screen fixed ahead of the kart camera. Turn off to leave them stretched across "
"the whole view. Its size and distance are the [vr] hud_width_meters and "
"hud_distance_meters read at launch. The immersive window is that screen, and "
"always carries them.");
}
ImGui::Separator();
ImGui::Text("VR view");
if (ImGui::Button("Recenter view")) { if (ImGui::Button("Recenter view")) {
mkw::vr::OpenXRRequestRecenter(); mkw::vr::OpenXRRequestRecenter();
} }
@@ -1576,59 +1561,46 @@ void DrawVrSettings() {
"does pitch the view, and looking sideways while it is set will roll the " "does pitch the view, and looking sideways while it is set will roll the "
"horizon the way a real recline would."); "horizon the way a real recline would.");
} }
#if defined(__ANDROID__)
if (ImGui::Checkbox("Passthrough around the menu screen", &g_vrPassthrough)) {
mkw::vr::OpenXRSetPassthrough(g_vrPassthrough);
RuntimeConfigFile::SetVrPassthrough(g_vrPassthrough);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Shows your room through the headset's cameras around the menu screen and every "
"other screen outside an immersive race, instead of black. Immersive races stay "
"fully virtual; the immersive window and the Flat Screen race have the room "
"around them too. Applies immediately.");
}
// Shows the live level, which debug.wiicompiled.foveation can override.
g_vrFoveation = static_cast<int>(aurora_get_stereo_foveation());
if (ImGui::Combo("Foveated rendering", &g_vrFoveation, kVrFoveationLabels.data(),
static_cast<int>(kVrFoveationLabels.size()))) {
aurora_set_stereo_foveation(static_cast<uint32_t>(g_vrFoveation));
RuntimeConfigFile::SetVrFoveation(
std::string(RuntimeConfigFile::kVrFoveationLevels[static_cast<size_t>(g_vrFoveation)]));
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"%s", aurora_stereo_foveation_available()
? "Shades the edges of the race view in 2x2, then 4x4 pixel blocks, where the "
"lenses blur the picture anyway, to free GPU time for a higher render scale or a "
"steadier frame rate. Higher levels start closer to the centre; High also "
"coarsens the corners of the HUD. Menus are never foveated. Applies immediately."
: "Shades the edges of the race view in 2x2, then 4x4 pixel blocks, where the "
"lenses blur the picture anyway, to free GPU time. This session started with it "
"off, or without a GPU that supports it: a new level applies after a restart.");
}
#endif
ImGui::Separator(); ImGui::Separator();
ImGui::Text("VR camera"); ImGui::BeginDisabled(
if (ImGui::Combo("Race view", &g_vrRaceView, kVrRaceViewLabels.data(), g_vrFlatScreen || g_vrRaceView == static_cast<int>(RuntimeConfigFile::VrRaceView::ImmersiveWindow));
static_cast<int>(kVrRaceViewLabels.size()))) { if (ImGui::Checkbox("2D layer on a virtual screen", &g_vrHudVirtualScreen)) {
const auto view = static_cast<RuntimeConfigFile::VrRaceView>(g_vrRaceView); ApplyVrHudVirtualScreen();
g_vrFlatScreen = view == RuntimeConfigFile::VrRaceView::FlatScreen; RuntimeConfigFile::SetVrHudVirtualScreen(g_vrHudVirtualScreen);
RuntimeConfigFile::SetVrRaceView(view);
mkw::vr::MkwVRPolicySetImmersiveRaces(!g_vrFlatScreen);
mkw::vr::OpenXRSetImmersiveWindow(view == RuntimeConfigFile::VrRaceView::ImmersiveWindow);
mkw::vr::MkwVRFirstPersonApplyConfiguredSettings();
} }
if (ImGui::IsItemHovered()) { ImGui::EndDisabled();
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
ImGui::SetTooltip( ImGui::SetTooltip(
"Immersive plays races all around you in stereo. Immersive window keeps that " "Puts the minimap, race position, item roulette and the rest of the race HUD on a "
"stereo view but shows it only through a window where the menu screen sits, with " "screen fixed ahead of the kart camera. Turn off to leave them stretched across "
"your room around it on the Quest (black elsewhere); look through it from another " "the whole view. Its size and distance are the [vr] hud_width_meters and "
"angle and the view shifts as through a real window. Flat screen plays races on " "hud_distance_meters read at launch. The immersive window is that screen, and "
"the menu screen through the game's own camera; the first-person camera, hand " "always carries them.");
"steering and the race view settings do not apply to it. Applies immediately."); }
ImGui::BeginDisabled(!mkw::vr::OpenXRIsRunning());
bool settingsPanelOpen = mkw::vr::OpenXRSettingsPanelOpen();
if (ImGui::Checkbox("Show these settings in the headset", &settingsPanelOpen)) {
mkw::vr::OpenXRSetSettingsPanelOpen(settingsPanelOpen);
}
ImGui::EndDisabled();
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
ImGui::SetTooltip(
"Opens these settings on a panel in front of you, in menus and races alike.\n"
"In the headset, left Y opens and closes it too (with Gamepad VR controllers,\n"
"click both thumbsticks together instead).\n"
"Aim at it and pull a trigger to change a setting; push a thumbstick to scroll.\n"
"While it is open the game does not see the VR controllers.");
}
}
// The first-person camera and its cockpit's steering wheel: the F10 bar's Camera menu and the
// headset panel's Camera tab.
void DrawVrCameraSettings() {
// All of it shapes the immersive race view, which Flat Screen mode (VR > Race view) replaces.
if (g_vrFlatScreen) {
ImGui::TextDisabled("Race view (under VR) is Flat screen: these apply to the immersive views.");
} }
// Everything below shapes the immersive race view, which Flat Screen mode replaces.
ImGui::BeginDisabled(g_vrFlatScreen); ImGui::BeginDisabled(g_vrFlatScreen);
if (ImGui::Checkbox("First-person camera", &g_vrFirstPerson)) { if (ImGui::Checkbox("First-person camera", &g_vrFirstPerson)) {
RuntimeConfigFile::SetVrFirstPerson(g_vrFirstPerson); RuntimeConfigFile::SetVrFirstPerson(g_vrFirstPerson);
@@ -2155,6 +2127,11 @@ void DrawTopBar() {
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("Camera")) {
DrawVrCameraSettings();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Controller settings")) { if (ImGui::BeginMenu("Controller settings")) {
DrawControllerSettings(); DrawControllerSettings();
// Nest capture under this menu so opening/closing the modal preserves // Nest capture under this menu so opening/closing the modal preserves
@@ -2351,6 +2328,7 @@ void DrawVrSettingsPanelWindow() {
} }
}; };
tab("VR", DrawVrSettings); tab("VR", DrawVrSettings);
tab("Camera", DrawVrCameraSettings);
tab("Graphics", [] { tab("Graphics", [] {
DrawResolutionMenu(); DrawResolutionMenu();
ImGui::Separator(); ImGui::Separator();
@@ -2435,9 +2413,6 @@ void InitializeRuntimeSettings() noexcept {
aurora_set_display_mode(static_cast<AuroraDisplayMode>(g_displayMode)); aurora_set_display_mode(static_cast<AuroraDisplayMode>(g_displayMode));
g_displayMode = static_cast<int>(aurora_get_display_mode()); g_displayMode = static_cast<int>(aurora_get_display_mode());
aurora_set_disable_copy_filter(g_disableCopyFilter); aurora_set_disable_copy_filter(g_disableCopyFilter);
aurora_set_stereo_stop_at_display_copy(g_vrStopAtDisplayCopy);
aurora_set_stereo_skip_copy_clears(g_vrSkipCopyClears);
aurora_set_stereo_single_pass_eyes(g_vrSinglePassEyes);
#if defined(__ANDROID__) #if defined(__ANDROID__)
aurora_set_stereo_foveation(static_cast<uint32_t>(g_vrFoveation)); aurora_set_stereo_foveation(static_cast<uint32_t>(g_vrFoveation));
#endif #endif
+10 -3
View File
@@ -498,6 +498,13 @@ public:
interpolation_stopping_ = false; interpolation_stopping_ = false;
} }
teardown_requested_.store(false, std::memory_order_release); teardown_requested_.store(false, std::memory_order_release);
// How every eye is replayed, fixed: it ends at the frame's final GXCopyDisp, so it holds the
// image the game presented; it keeps the EFB reset after a display copy, which can only be
// an earlier one's and erases what that last copy did not show; and it is drawn in one
// render pass (the Quest's debug.wiicompiled.eye_passes splits it again, for A/B timing).
aurora_set_stereo_stop_at_display_copy(true);
aurora_set_stereo_skip_copy_clears(false);
aurora_set_stereo_single_pass_eyes(true);
WithdrawPublishedFrame(); WithdrawPublishedFrame();
aurora_set_stereo_frame_provider(&OpenXRIntegration::ProvideStereoFrame, this); aurora_set_stereo_frame_provider(&OpenXRIntegration::ProvideStereoFrame, this);
provider_registered_ = true; provider_registered_ = true;
@@ -1640,7 +1647,8 @@ private:
// Android: `adb shell setprop debug.wiicompiled.eye_passes 0` replays each eye in one render // Android: `adb shell setprop debug.wiicompiled.eye_passes 0` replays each eye in one render
// pass per recorded pass again, and 1 forces the single pass, to compare the two within one // pass per recorded pass again, and 1 forces the single pass, to compare the two within one
// session. An empty value hands the switch back to the settings. Read about once a second. // session. An empty value restores the single pass every eye has otherwise. Read about once a
// second.
void PollEyePassesOverride() noexcept { void PollEyePassesOverride() noexcept {
#if defined(__ANDROID__) #if defined(__ANDROID__)
if (eye_passes_poll_ != 0) { if (eye_passes_poll_ != 0) {
@@ -1658,8 +1666,7 @@ private:
return; return;
} }
eye_passes_override_ = override_value; eye_passes_override_ = override_value;
const bool single = const bool single = override_value != 0;
override_value >= 0 ? override_value == 1 : RuntimeConfigFile::VrSinglePassEyes();
aurora_set_stereo_single_pass_eyes(single); aurora_set_stereo_single_pass_eyes(single);
RT_LOG(RT_TAG_RUNTIME) << "OpenXR: eyes replayed in " RT_LOG(RT_TAG_RUNTIME) << "OpenXR: eyes replayed in "
<< (single ? "one render pass" : "one render pass per recorded pass") << (single ? "one render pass" : "one render pass per recorded pass")
-5
View File
@@ -30,11 +30,6 @@ int main() {
Require(RuntimeConfigFile::VrFoveationLevelIndex("high") == 3); Require(RuntimeConfigFile::VrFoveationLevelIndex("high") == 3);
Require(RuntimeConfigFile::VrFoveationLevelIndex("ultra") == 0); Require(RuntimeConfigFile::VrFoveationLevelIndex("ultra") == 0);
// [vr] single_pass_eyes: each eye replayed in one render pass.
Require(Parse("[vr]\nsingle_pass_eyes = true\n").vrSinglePassEyes == true);
Require(Parse("[vr]\nsingle_pass_eyes = false\n").vrSinglePassEyes == false);
Require(!Parse("[vr]\n").vrSinglePassEyes.has_value());
// [vr] hand_tracking: the cockpit hands follow the headset's hand tracking. // [vr] hand_tracking: the cockpit hands follow the headset's hand tracking.
Require(Parse("[vr]\nhand_tracking = true\n").vrHandTracking == true); Require(Parse("[vr]\nhand_tracking = true\n").vrHandTracking == true);
Require(Parse("[vr]\nhand_tracking = false\n").vrHandTracking == false); Require(Parse("[vr]\nhand_tracking = false\n").vrHandTracking == false);