Add Flat Screen mode for VR races and update settings accordingly

This commit is contained in:
iChris4 committed 2026-09-23 17:15:53 +02:00
1 parent 2a89fac79d
commit 2a4686191e
16 files changed
+161 -50

No files matched your search

+27 -10
View File
@@ -45,6 +45,7 @@ world_units_per_meter = 500.0
hud_distance_meters = 2.0
hud_width_meters = 2.4
hud_virtual_screen = true
flat_screen = false
stop_at_display_copy = true
skip_copy_clears = true
first_person = false
@@ -139,10 +140,19 @@ and 0.8 on the Quest, whose mobile GPU needs the headroom.
launch and govern both the menu screen and the in-race 2D screen, so 2D content keeps its place
across the transition. `hud_virtual_screen` decides whether the race's 2D layer uses that screen;
it is live and can be flipped from the F10 settings bar.
`flat_screen` (default off) keeps races on that same flat screen, as the menus are, instead of
immersive stereo: the whole race, 3D world and HUD alike, is the game's own picture on the quad, as
in DolphinXR's Flat Screen mode. The first-person camera, hand steering, the lean-back angle, VR
frame interpolation and `hud_virtual_screen` shape only the immersive race view, so none of them
apply while it is on; the right-thumbstick first-person toggle is ignored rather than changing the saved
setting. It is live, as **F10 → VR → Flat Screen mode** (the headset panel's VR tab) and the Quest
launcher's Settings page, and turning it on or off mid-race switches on the next frame through the
presentation policy's safety generation.
`passthrough` (Quest only, default on) shows the room through the headset's cameras around the
menu screen and every other virtual screen, instead of black: an `XR_FB_passthrough`
reconstruction layer submitted under the screen's quad, as PPSSPP VR does, with the blend mode
left `OPAQUE`. An immersive race never shows it, and the cameras are paused for the race. It is
left `OPAQUE`. An immersive race never shows it, and the cameras are paused for the race; a race in
`flat_screen` is a virtual screen like the menus, so the room shows around it too. It is
live, from the headset panel's VR tab or the launcher's Settings page. The app declares
`com.oculus.feature.PASSTHROUGH`, without which Horizon OS composites nothing for that layer.
So that the room frames the picture rather than black bands, the Quest's menu quad shows only the
@@ -187,19 +197,21 @@ Touch controllers. The port is served through KPAD like a Bluetooth remote
| --- | --- |
| Right A | A |
| Right trigger | B |
| Right B | C (look behind) |
| Right stick up / down | 1 / 2 |
| Left X | − |
| Left menu | + |
| Left stick | Nunchuk stick |
| Left trigger | Z |
| Left grip | C |
| Left Y | Settings panel (not a Wii button) |
| Either grip | Takes hold of the wheel (not a Wii button) |
| Right stick click | First-person camera on / off (not a Wii button) |
| Right controller motion and aim | Wii Remote accelerometer and pointer |
| Left controller motion | Nunchuk accelerometer |
Analog inputs count as pressed past half travel. Right B, right stick left / right and the left
stick click are unbound, and no controller button presses HOME. The game's Wii Remote rumble vibrates
Analog inputs count as pressed past half travel. The grips, right stick left / right and the left
stick click press no Wii button, and nothing presses HOME. C sits on right B rather than a grip
because hand steering holds a grip down for a whole corner, and C is the game's look-behind. The game's Wii Remote rumble vibrates
both controllers, subject to the ordinary controller-vibration switch.
**Motion.** Each XR frame the aim and grip poses are located at the measured current time
@@ -234,8 +246,8 @@ Raw IR camera dots in `KPADGetUnifiedWpadStatus` stay invalid; the game reads th
controllers operate the panel and the game sees them idle.
**Hand steering.** With `hand_steering` on, in the first-person cockpit, a grip squeezed near the
steering wheel takes hold of it, and while held the wheel steers through the Nunchuk stick's X axis
and that grip stops pressing C; see [Steering wheel and hand
steering wheel takes hold of it, and while held the wheel steers through the Nunchuk stick's X axis;
see [Steering wheel and hand
steering](#steering-wheel-and-hand-steering). Turning the wheel moves the controllers, and the game's
own motion detection still reads them, so a sharp enough turn can read as a shake.
@@ -377,8 +389,9 @@ sliders exist because the comfortable value is a matter of taste and is best jud
headset.
The mode engages only in a single-screen race, the same content that already qualifies for
immersive stereo. Menus, split-screen, and the virtual-screen fallback are unaffected, and so is
the desktop mirror, which keeps showing the game's ordinary third-person view. If the kart or
immersive stereo. Menus, split-screen, `flat_screen`, and the virtual-screen fallback are
unaffected, and so is the desktop mirror, which keeps showing the game's ordinary third-person
view. If the kart or
camera cannot be read the camera stays where the game put it rather than guessing.
Your own driver sits exactly where your eyes are, so their head would fill the view.
@@ -453,8 +466,8 @@ and `wheel_haptics` gives a short pulse on grab and release.
While the wheel is held it replaces the left stick's X axis, in both the Wii Remote and the gamepad
presentation, and the game keeps its own steering curve. The stick's Y axis still aims items, and a
holding grip no longer reaches the game (it would press C on the Nunchuk or a shoulder on the
gamepad); the triggers, A and the right stick are unchanged. Releasing both grips gives steering back
holding grip no longer reaches the game (a shoulder on the gamepad; the Wii Remote presentation
leaves the grips unbound for this reason); the triggers, A and the right stick are unchanged. Releasing both grips gives steering back
to the stick. The settings panel withholds the wheel like any other input.
**A USB wheel.** With a USB wheel and pedals set up (see the README), the wheel drives the race as
@@ -498,6 +511,10 @@ The runtime deliberately fails safe instead of guessing which Mario Kart camera
confirm exactly one distinct race camera for the current GX frame.
- Leaving the race or observing zero or multiple cameras immediately returns presentation to the
virtual screen. Session/runtime loss safely tears down XR and continues on the desktop mirror.
- `flat_screen` clears the policy's `immersive_races`, so a race stays on the virtual screen
however complete the observations are. Changing it advances the safety generation like any
other change of presentation, and the pacing thread still treats that race as a race: pipeline
caches are not stored mid-race on the virtual screen either.
Aurora records the original GX frame once and replays it for both OpenXR eyes. Perspective GX draws
receive asymmetric headset projections, while the game's 2D layer goes on a fixed virtual screen
@@ -114,15 +114,23 @@ class SettingsPage(
}
private fun buildVr() {
val firstPerson = { c: TomlConfig -> c.bool("vr", "first_person") ?: false }
// Flat Screen mode keeps races on the menu screen, which none of the race view rows reach.
val immersive = { c: TomlConfig -> !(c.bool("vr", "flat_screen") ?: false) }
val firstPerson = { c: TomlConfig -> immersive(c) && (c.bool("vr", "first_person") ?: false) }
// The steering wheel and hand steering belong to the cockpit seat.
val cockpit = { c: TomlConfig -> firstPerson(c) && stringIndex(c, "vr", "first_person_seat", SEATS) == 0 }
section(R.string.section_vr_camera) {
toggle(
R.string.vr_flat_screen, R.string.vr_flat_screen_helper,
read = { !immersive(it) },
write = { c, value -> c.setBool("vr", "flat_screen", value) },
)
choice(
R.string.vr_camera, R.string.vr_camera_helper,
listOf(R.string.vr_camera_chase, R.string.vr_camera_first_person),
read = { if (firstPerson(it)) 1 else 0 },
read = { if (it.bool("vr", "first_person") == true) 1 else 0 },
write = { c, index -> c.setBool("vr", "first_person", index == 1) },
enabledIf = immersive,
)
choice(
R.string.vr_rotation, R.string.vr_rotation_helper,
@@ -172,6 +180,7 @@ class SettingsPage(
read = { number(it, "vr", "lean_back_degrees", -45.0, 45.0, 0.0) },
format = { "%.0f°".format(it) },
write = { c, value -> c.setFloat("vr", "lean_back_degrees", value) },
enabledIf = immersive,
)
}
section(R.string.section_vr_headset) {
@@ -203,6 +212,7 @@ class SettingsPage(
R.string.vr_hud_screen, R.string.vr_hud_screen_helper,
read = { it.bool("vr", "hud_virtual_screen") ?: true },
write = { c, value -> c.setBool("vr", "hud_virtual_screen", value) },
enabledIf = immersive,
)
slider(
R.string.vr_hud_distance, R.string.vr_hud_distance_helper, 0.5, 5.0, 0.1,
+4 -2
View File
@@ -172,6 +172,8 @@
<!-- VR tab -->
<string name="section_vr_camera">Camera</string>
<string name="vr_flat_screen">Flat Screen mode</string>
<string name="vr_flat_screen_helper">Plays races on the same flat screen as the menus instead of all around you. The camera settings below do not apply while it is on.</string>
<string name="vr_camera">Camera</string>
<string name="vr_camera_helper">Ride behind the kart like the game, or sit in the driver\'s seat.</string>
<string name="vr_camera_chase">Chase camera</string>
@@ -218,7 +220,7 @@
<string name="vr_hud_width">Screen width</string>
<string name="vr_hud_width_helper">How wide the menu screen and race HUD are.</string>
<string name="vr_passthrough">Passthrough around the menu screen</string>
<string name="vr_passthrough_helper">Shows your room through the headset\'s cameras around the menus instead of black. Races stay fully virtual.</string>
<string name="vr_passthrough_helper">Shows your room through the headset\'s cameras around the menus instead of black. Races stay fully virtual, except in Flat Screen mode.</string>
<!-- Graphics tab -->
<string name="section_graphics">Rendering</string>
@@ -256,7 +258,7 @@
<string name="controls_map_z">Z</string>
<string name="controls_map_z_value">Left trigger</string>
<string name="controls_map_c">C</string>
<string name="controls_map_c_value">Left grip</string>
<string name="controls_map_c_value">Right B</string>
<string name="controls_map_pointer">Pointer and motion</string>
<string name="controls_map_pointer_value">Aim and move the right controller</string>
<string name="controls_map_panel">Settings panel</string>
+3 -2
View File
@@ -165,7 +165,8 @@ suggested for `oculus/touch_controller` and `khr/simple_controller`.
(created on first use) or pauses it as each presentation arrives, and submits
it first, under the virtual screen's quad, or alone while there is no image
yet (startup, a recenter). An immersive race never submits it and pauses the
cameras. The quad is cropped to the snapshot Aurora letterboxes into the
cameras; a `flat_screen` race is a virtual screen, so it keeps the room.
The quad is cropped to the snapshot Aurora letterboxes into the
nearly square eye image (`OpenXRVirtualScreenContentRect`), or its black
bands would frame the picture against the room. The manifest's `com.oculus.feature.PASSTHROUGH` is what lets Horizon
OS composite it; DolphinXR found that without it every call succeeds and the
@@ -182,7 +183,7 @@ The app opens on `LauncherActivity` (`android/app/src/main/java/org/wiicompiled/
a 2D Horizon OS panel modelled on the PC launcher, WheelWizard VR, and using its
palette. **Home** has the Play button and reports a missing or incomplete `DATA`
(the check is the runtime's own `IsDvdDataRoot`: `files/` and `sys/fst.bin`).
**Settings** edits `Config.toml` in tabs: VR (camera, rotation, driver hiding,
**Settings** edits `Config.toml` in tabs: VR (Flat Screen mode, camera, rotation, driver hiding,
seat, hand steering, lean back, render scale, VR interpolation, virtual screen
size and distance),
Graphics (resolution, widescreen, bloom, shader stutter), Controls (controller
+21 -2
View File
@@ -57,6 +57,7 @@ struct RuntimeUserConfig {
std::optional<float> vrHudDistanceMeters;
std::optional<float> vrHudWidthMeters;
std::optional<bool> vrHudVirtualScreen;
std::optional<bool> vrFlatScreen;
std::optional<bool> vrPassthrough;
std::optional<bool> vrStopAtDisplayCopy;
std::optional<bool> vrSkipCopyClears;
@@ -496,6 +497,11 @@ inline void EnsureConfigFile() {
"# Changeable live from the F10 menu; the two sizes above place\n"
"# that screen and the menu screen alike and are read at launch.\n"
"hud_virtual_screen = true\n"
"# Flat Screen mode keeps races on that same screen, as the\n"
"# menus are, instead of all around you: no stereo race view, no\n"
"# first-person camera or hand steering. Changeable live from the\n"
"# F10 menu.\n"
"flat_screen = 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"
@@ -747,6 +753,7 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
config.vrHudWidthMeters = *value;
}
config.vrHudVirtualScreen = FindConfigValue<bool>(document, "vr", "hud_virtual_screen");
config.vrFlatScreen = FindConfigValue<bool>(document, "vr", "flat_screen");
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");
@@ -1061,6 +1068,11 @@ inline bool SetVrHudVirtualScreen(bool value) {
return WriteSetting("vr", "hud_virtual_screen", value ? "true" : "false");
}
inline bool SetVrFlatScreen(bool value) {
Mutable().vrFlatScreen = value;
return WriteSetting("vr", "flat_screen", value ? "true" : "false");
}
inline bool SetVrPassthrough(bool value) {
Mutable().vrPassthrough = value;
return WriteSetting("vr", "passthrough", value ? "true" : "false");
@@ -1518,9 +1530,16 @@ inline bool VrHudVirtualScreen(bool fallback = true) {
return Get().vrHudVirtualScreen.value_or(fallback);
}
// Races on the flat virtual screen the menus use, instead of immersive
// stereo. The launcher's Settings page shows the same default.
inline bool VrFlatScreen(bool fallback = false) {
return Get().vrFlatScreen.value_or(fallback);
}
// The room, through the headset's cameras, around the menu screen and every
// other virtual screen (never in a race). Only the Quest offers it; the
// launcher's Settings page shows the same default.
// other virtual screen, a Flat Screen race included (never an immersive
// race). Only the Quest offers it; the launcher's Settings page shows the same
// default.
inline bool VrPassthrough(bool fallback = true) {
return Get().vrPassthrough.value_or(fallback);
}
+4
View File
@@ -71,6 +71,7 @@ inline constexpr uint32_t kMkwVRRequiredImmersiveBindings =
struct MkwVRPolicyConfig {
bool enabled = false;
// False is Flat Screen mode: races stay on the virtual screen the menus use.
bool immersive_races = true;
float world_units_per_meter = 500.0f;
float hud_distance_meters = 2.0f;
@@ -151,6 +152,9 @@ void MkwVRPolicySetFirstPersonEngaged(bool engaged) noexcept;
// MkwVRPolicyConfigure so the F10 slider can retune it during a race without
// republishing (and revalidating) the whole configuration.
void MkwVRPolicySetFirstPersonUnitsPerMeter(float units_per_meter) noexcept;
// Flat Screen mode, live from the F10 menu. Unlike the scale above this changes
// which content is safe to present, so it advances the safety generation.
void MkwVRPolicySetImmersiveRaces(bool enabled) noexcept;
MkwVRPolicySnapshot MkwVRPolicyGetSnapshot() noexcept;
// This classifier is deliberately structural rather than heuristic: future
+5 -4
View File
@@ -79,10 +79,11 @@ inline float MaxWheelAngle(bool bike, const WheelTuning& tuning) noexcept {
return clamped * 0.01745329252f;
}
// Grips only grab. While a hand holds the wheel its squeeze is released for the
// game, where it would press C on the Nunchuk or a shoulder on the gamepad, and
// the wheel replaces the left stick's X axis, which both controller modes steer
// with. The stick's Y axis keeps aiming items forwards and backwards.
// Grips only grab. As a Wii Remote they press nothing at all (C, the game's
// look-behind, is right B); as a gamepad they are the shoulders, so a holding
// hand's squeeze is released for the game. The wheel replaces the left stick's
// X axis, which both controller modes steer with, and the stick's Y axis keeps
// aiming items forwards and backwards.
inline void ApplyHandSteering(std::array<wii_remote::HandInputs, 2>& hands, const WheelState& wheel) noexcept {
for (size_t hand = 0; hand < hands.size(); ++hand) {
if (wheel.held[hand]) {
+2 -1
View File
@@ -63,7 +63,8 @@ struct OpenXRPointerScreen {
// the seated frame every frame. With hand steering on, a squeezed grip near the
// steering wheel or handlebar takes hold of it; while held, the wheel replaces
// the left stick's X axis in both presentations and that grip no longer reaches
// the game (C on the Nunchuk, a shoulder on the gamepad).
// the game (a shoulder on the gamepad; as a Wii Remote the grips are unbound,
// so a hand on the wheel cannot hold down a button).
//
// A right-thumbstick click on its own toggles the first-person camera, as its
// F10 checkbox does (first_person_toggle_click).
+5 -3
View File
@@ -342,10 +342,12 @@ struct HandInputs {
// Adapted from DolphinXR's default "OpenXR Wii Remote" profile
// (Data/Sys/Profiles/Wiimote):
// right A -> A, right trigger -> B, right stick up/down -> 1/2,
// right A -> A, right trigger -> B, right B -> C, right stick up/down -> 1/2,
// left X -> -, left menu -> +,
// left grip -> C, left trigger -> Z, left stick -> Nunchuk stick.
// left trigger -> Z, left stick -> Nunchuk stick.
// HOME has no button; left Y opens the settings panel (openxr_settings_panel.h).
// The grips press nothing: they take hold of the wheel (openxr_driving.h), and C
// is the game's look-behind, which a hand on the wheel would otherwise hold down.
inline uint32_t RemoteButtons(const HandInputs& left, const HandInputs& right) noexcept {
uint32_t hold = 0;
const auto press = [&hold](bool held, uint32_t bit) {
@@ -355,11 +357,11 @@ inline uint32_t RemoteButtons(const HandInputs& left, const HandInputs& right) n
};
press(right.primary, kButtonA);
press(right.trigger > kPressThreshold, kButtonB);
press(right.secondary, kButtonC);
press(right.stick_y > kPressThreshold, kButtonOne);
press(right.stick_y < -kPressThreshold, kButtonTwo);
press(left.primary, kButtonMinus);
press(left.menu, kButtonPlus);
press(left.squeeze > kPressThreshold, kButtonC);
press(left.trigger > kPressThreshold, kButtonZ);
return hold;
}
+30 -6
View File
@@ -141,6 +141,7 @@ bool g_vrEnabled = RuntimeConfigFile::VrEnabled(true);
bool g_vrStopAtDisplayCopy = RuntimeConfigFile::VrStopAtDisplayCopy(true);
bool g_vrSkipCopyClears = RuntimeConfigFile::VrSkipCopyClears(true);
bool g_vrHudVirtualScreen = RuntimeConfigFile::VrHudVirtualScreen(true);
bool g_vrFlatScreen = RuntimeConfigFile::VrFlatScreen();
#if defined(__ANDROID__)
bool g_vrPassthrough = RuntimeConfigFile::VrPassthrough();
#endif
@@ -1354,8 +1355,9 @@ void DrawVrSettings() {
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, stick up/down = 1/2\n"
" Left: stick = Nunchuk stick, trigger = Z, grip = C, X = -, menu = +, Y = settings panel\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.");
}
@@ -1424,11 +1426,13 @@ void DrawVrSettings() {
ImGui::PopTextWrapPos();
ImGui::Separator();
ImGui::Text("VR 2D layer");
ImGui::BeginDisabled(g_vrFlatScreen);
if (ImGui::Checkbox("2D layer on a virtual screen", &g_vrHudVirtualScreen)) {
ApplyVrHudVirtualScreen();
RuntimeConfigFile::SetVrHudVirtualScreen(g_vrHudVirtualScreen);
}
if (ImGui::IsItemHovered()) {
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 "
@@ -1467,13 +1471,15 @@ void DrawVrSettings() {
ImGui::SetTooltip("Click to rebind. Esc cancels, Backspace unbinds.");
}
}
ImGui::BeginDisabled(g_vrFlatScreen);
if (ImGui::SliderFloat("Lean back angle (deg)", &g_vrLeanBackDegrees,
-RuntimeConfigFile::kVrLeanBackDegreesLimit,
RuntimeConfigFile::kVrLeanBackDegreesLimit, "%.1f")) {
mkw::vr::OpenXRSetLeanBackDegrees(g_vrLeanBackDegrees);
RuntimeConfigFile::SetVrLeanBackDegrees(g_vrLeanBackDegrees);
}
if (ImGui::IsItemHovered()) {
ImGui::EndDisabled();
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
ImGui::SetTooltip(
"Tilts the game camera back with you when you play reclined, so set it to "
"roughly how far back your seat is and the track comes back in front of you "
@@ -1489,12 +1495,26 @@ void DrawVrSettings() {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Shows your room through the headset's cameras around the menu screen and every "
"other screen outside a race, instead of black. Races stay fully virtual. "
"other screen outside an immersive race, instead of black. Immersive races stay "
"fully virtual; the Flat Screen race has the room around it too. "
"Applies immediately.");
}
#endif
ImGui::Separator();
ImGui::Text("VR camera");
if (ImGui::Checkbox("Flat Screen mode", &g_vrFlatScreen)) {
RuntimeConfigFile::SetVrFlatScreen(g_vrFlatScreen);
mkw::vr::MkwVRPolicySetImmersiveRaces(!g_vrFlatScreen);
mkw::vr::MkwVRFirstPersonApplyConfiguredSettings();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Plays races on the same flat screen as the menus, through the game's own camera, "
"instead of all around you in stereo. The first-person camera, hand steering and "
"the race view settings do not apply while it is on. Applies immediately.");
}
// Everything below shapes the immersive race view, which Flat Screen mode replaces.
ImGui::BeginDisabled(g_vrFlatScreen);
if (ImGui::Checkbox("First-person camera", &g_vrFirstPerson)) {
RuntimeConfigFile::SetVrFirstPerson(g_vrFirstPerson);
mkw::vr::MkwVRFirstPersonApplyConfiguredSettings();
@@ -1643,11 +1663,15 @@ void DrawVrSettings() {
mkw::vr::MkwVRFirstPersonApplyConfiguredSettings();
ApplyVrHudVirtualScreen();
}
ImGui::EndDisabled();
}
// The right-thumbstick click: flips the first-person camera exactly as its
// checkbox does. Game thread.
// checkbox does, so it does nothing in Flat Screen mode either. Game thread.
void ToggleFirstPersonCamera() {
if (g_vrFlatScreen) {
return;
}
g_vrFirstPerson = !g_vrFirstPerson;
RuntimeConfigFile::SetVrFirstPerson(g_vrFirstPerson);
mkw::vr::MkwVRFirstPersonApplyConfiguredSettings();
+5 -2
View File
@@ -1213,8 +1213,11 @@ void MkwVRFirstPersonApplyConfiguredSettings() noexcept {
const FirstPersonRotation rotation = mode == "full" ? FirstPersonRotation::Full
: mode == "yaw_pitch" ? FirstPersonRotation::YawPitch
: FirstPersonRotation::YawOnly;
MkwVRFirstPersonConfigure(RuntimeConfigFile::VrFirstPerson(false), offsets, units_per_meter,
rotation);
// Flat Screen mode shows the race on the menu screen through the game's own
// camera, where a hidden driver would only be missing from the kart.
MkwVRFirstPersonConfigure(RuntimeConfigFile::VrFirstPerson(false) &&
!RuntimeConfigFile::VrFlatScreen(),
offsets, units_per_meter, rotation);
MkwVRPolicySetFirstPersonUnitsPerMeter(units_per_meter);
const bool cockpit = RuntimeConfigFile::VrFirstPersonSeat() != "custom";
const float cockpit_units = RuntimeConfigFile::VrCockpitUnitsPerMeter();
+5
View File
@@ -257,6 +257,11 @@ void MkwVRPolicySetFirstPersonUnitsPerMeter(float units_per_meter) noexcept {
}
}
void MkwVRPolicySetImmersiveRaces(bool enabled) noexcept {
std::lock_guard<std::mutex> lock(g_policy_mutex);
ApplyPolicyMutation([&] { g_policy.config.immersive_races = enabled; });
}
MkwVRPolicySnapshot MkwVRPolicyGetSnapshot() noexcept {
std::lock_guard<std::mutex> lock(g_policy_mutex);
MkwVRPolicySnapshot snapshot;
+12 -9
View File
@@ -67,7 +67,7 @@ void ConfigurePolicy(bool enabled) noexcept {
MkwVRPolicyReset();
MkwVRPolicyConfig config{};
config.enabled = enabled;
config.immersive_races = true;
config.immersive_races = !RuntimeConfigFile::VrFlatScreen();
config.world_units_per_meter = RuntimeConfigFile::VrWorldUnitsPerMeter(500.0f);
config.hud_distance_meters = RuntimeConfigFile::VrHudDistanceMeters(2.0f);
config.hud_width_meters = RuntimeConfigFile::VrHudWidthMeters(2.4f);
@@ -753,7 +753,7 @@ private:
bool fatal = false;
uint32_t consecutive_skips = 0;
bool store_gate_set = false;
bool store_gate_immersive = false;
bool store_gate_racing = false;
bool presentation_logged = false;
VRPresentationMode logged_presentation = VRPresentationMode::Desktop;
uint32_t presentation_log_count = 0;
@@ -840,8 +840,9 @@ private:
aurora_get_stereo_screen_aspects(&picture_aspect, &snapshot_aspect)) {
presentation.quad_content_aspect = snapshot_aspect;
}
// The room around the menu screen and every other virtual screen; a race is
// fully virtual, and the cameras are paused for it.
// The room around the menu screen and every other virtual screen, a
// Flat Screen race included; an immersive race is fully virtual, and the
// cameras are paused for it.
presentation.passthrough = !immersive && passthrough_.load(std::memory_order_relaxed);
// The settings panel gets a compositor layer of its own while it is
// open, and Aurora leaves it out of the eyes. A backend that could
@@ -852,12 +853,14 @@ private:
// Pipeline caches are stored where their stall is invisible: once when a race ends,
// and by the compiler itself while the headset shows the virtual screen. Never
// mid-race.
if (!store_gate_set || immersive != store_gate_immersive) {
const bool left_race = store_gate_set && store_gate_immersive && !immersive;
// mid-race, and a race on the virtual screen (Flat Screen mode) is still a race.
const bool racing = immersive || (!policy.config.immersive_races &&
policy.scene.mode == VRSceneMode::Race);
if (!store_gate_set || racing != store_gate_racing) {
const bool left_race = store_gate_set && store_gate_racing && !racing;
store_gate_set = true;
store_gate_immersive = immersive;
aurora_set_pipeline_cache_idle_store(!immersive);
store_gate_racing = racing;
aurora_set_pipeline_cache_idle_store(!racing);
if (left_race) {
aurora_store_pipeline_caches();
}
+1 -1
View File
@@ -51,7 +51,7 @@ void TestHandOff() {
CheckNear(hands[0].stick_x, 0.6f, "a held wheel steers through the left stick");
CheckNear(hands[0].stick_y, 0.8f, "the stick keeps aiming items");
CheckNear(hands[0].squeeze, 1.0f, "a free hand's grip still reaches the game");
CheckNear(hands[1].squeeze, 0.0f, "a holding grip does not press C or a shoulder");
CheckNear(hands[1].squeeze, 0.0f, "a holding grip does not press a shoulder");
wheel.held = {true, true};
wheel.steering = 3.0f;
+16
View File
@@ -73,6 +73,22 @@ int main() {
check(MkwVRPolicyGetSnapshot().presentation == VRPresentationMode::VirtualScreen, "race entry needs fresh camera");
MkwVRPolicyPublishRaceCamera(camera);
check(MkwVRPolicyGetSnapshot().presentation == VRPresentationMode::ImmersiveRace, "race resumes with fresh camera");
{
const uint64_t immersive_tag = MkwVRPolicyGetSnapshot().content_tag;
MkwVRPolicySetFirstPersonEngaged(true);
MkwVRPolicySetImmersiveRaces(false);
const auto flat = MkwVRPolicyGetSnapshot();
check(flat.presentation == VRPresentationMode::VirtualScreen, "Flat Screen mode keeps races on the screen");
check(flat.content_tag != immersive_tag, "Flat Screen mode must invalidate immersive packets");
check(!flat.first_person_engaged, "Flat Screen mode never engages first person");
check(MkwVRPolicyRouteDraw(VRDrawClass::PerspectiveWorld, flat) == VRDrawRoute::VirtualScreen,
"Flat Screen mode routes the race world to the screen");
MkwVRPolicySetFirstPersonEngaged(false);
MkwVRPolicySetImmersiveRaces(true);
const auto resumed = MkwVRPolicyGetSnapshot();
check(resumed.presentation == VRPresentationMode::ImmersiveRace, "leaving Flat Screen mode resumes the race");
check(resumed.content_tag != flat.content_tag, "leaving Flat Screen mode must invalidate screen packets");
}
MkwVRPolicySetSessionActive(false);
check(MkwVRPolicyGetSnapshot().presentation == VRPresentationMode::Desktop, "inactive session uses desktop");
return failures == 0 ? 0 : 1;
+9 -6
View File
@@ -240,7 +240,9 @@ void TestButtons() {
right.primary = true;
right.trigger = 0.6f;
Check(RemoteButtons(left, right) == (kButtonA | kButtonB), "right A and trigger are A and B");
right.secondary = true;
Check(RemoteButtons(left, right) == (kButtonA | kButtonB | kButtonC),
"right A, trigger and B are A, B and C");
right = {};
right.stick_y = 0.9f;
@@ -260,16 +262,17 @@ void TestButtons() {
left.menu = true;
Check(RemoteButtons(left, right) == (kButtonMinus | kButtonPlus), "left X and menu are - and +");
left = {};
left.squeeze = 0.8f;
left.trigger = 0.7f;
Check(RemoteButtons(left, right) == (kButtonC | kButtonZ), "left grip and trigger are C and Z");
// Left Y is the settings panel's button, and nothing presses HOME.
Check(RemoteButtons(left, right) == kButtonZ, "left trigger is Z");
// Left Y is the settings panel's button, the grips take hold of the steering
// wheel, and nothing presses HOME.
left = {};
left.secondary = true;
right.secondary = true;
left.squeeze = 0.8f;
right.squeeze = 0.8f;
right.thumbstick_click = true;
left.thumbstick_click = true;
Check(RemoteButtons(left, right) == 0, "left Y, right B and the stick clicks are unbound");
Check(RemoteButtons(left, right) == 0, "left Y, the grips and the stick clicks are unbound");
left = {};
right = {};
left.menu = true;