Add first-person camera logging and adjust object culling defaults for Quest

This commit is contained in:
iChris4 committed 2026-09-29 21:17:37 +02:00
1 parent e2c4eb3c8c
commit 75f075b234
7 files changed
+106 -154

No files matched your search

+36 -15
View File
@@ -60,7 +60,7 @@ first_person_hidden_model = 0
first_person_rotation = "yaw_pitch"
steering_wheel = true
native_steering_wheel = true
object_culling = true
object_culling = false
hand_steering = true
performance_level = "boost"
```
@@ -202,8 +202,7 @@ a render pass at every GX copy, because the copy reads what was drawn before it;
copies the desktop image made and never performs them, so it keeps drawing in the pass it has open,
and it leaves out whatever a later clear of the whole color and depth erases. The picture is the
same with less GPU memory traffic, which a tiled mobile GPU pays for at every split (the "Eye replay
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`).
plan" log line reports each new pass structure).
`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.
`performance_level` is the level asked of the runtime through `XR_EXT_performance_settings` for
@@ -381,8 +380,7 @@ never shows an unwritten panel. The quad hangs exactly where the pointer's hits
If a backend cannot make the panel's swapchains, it logs that once and the panel is drawn into the
eye images instead: through the eye's frustum and `viewFromCenter` onto the screen rectangle for an
immersive eye (including headset-rate interpolated eyes, which reuse the texture), and as a centred
rectangle on a virtual-screen eye image. On the Quest, `adb shell setprop
debug.wiicompiled.panel_layer 0` switches to that path at run time, to compare the two.
rectangle on a virtual-screen eye image.
Measured on a Quest 3 (base game, a Grand Prix start with the player idle, `render_scale = 0.8`,
60 FPS, eight interleaved rounds per state), the layer costs nothing while the panel is closed. While
@@ -504,8 +502,21 @@ headset is looking. Turn your head far enough in an immersive race, or look over
in first person, and karts, characters and course objects are simply missing until the game
camera catches up; the race intro's pan shows it too, since the other racers are culled from the
intro camera's narrow view. `object_culling = false` (F10 > Camera > Object culling, also on the
headset settings panel's Camera tab) draws them anyway, and takes effect immediately. The
default, `true`, leaves the game's culling in force.
headset settings panel's Camera tab) draws them anyway, and takes effect immediately. That is
the PC's default. The Quest defaults to `true`, the game's own culling, because every model
drawn costs its GPU twice, once per eye.
Measured on a Quest 3 (base game, the first Grand Prix race at Luigi Circuit after the intro, player
idle, first-person cockpit, `render_scale = 0.8`, foveation medium, six interleaved rounds per
state, GPU clock fixed at 492 MHz), turning culling off kept 60 FPS in the game and the headset:
| Culling | Draw calls a frame | Both eyes | App GPU per frame | GPU load | CPU load |
| --- | --- | --- | --- | --- | --- |
| On | 352 | 7.4 ms | 9.7 ms | 67% | 54% |
| Off | 482 | 8.9 ms | 11.0 ms | 74% | 64% |
A track that already keeps the Quest's GPU near its limit, such as Retro Rewind's SNES Ghost Valley
2, would lose headset frames to the extra eye time, hence the Quest default.
The game culls in two places, and the switch covers both. NW4R's scene gather tests each model's
bounding box against the camera frustum (`nw4r::math::FRUSTUM::IntersectAABB_Ex`); with culling
@@ -861,8 +872,8 @@ that keeps the colour inside the window with alpha 1 and leaves transparent blac
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
exactly across the image. It is drawn in the eye's own last render pass, so it adds no pass and no
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
tile load; an eye that is still split into several render passes 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
`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
@@ -883,8 +894,6 @@ reprojects it like any other. Aurora copies the smaller eye into the corner of t
(`vulkan_interop.cpp`), and does not foveate these eyes: their field of view follows the head, which
would rebuild the density map every frame, and they are small already. The PC backends copy whole eyes
into the swapchain, so there the window's eyes stay full size and masked.
`adb shell setprop debug.wiicompiled.window_eyes 0` renders them whole and masked on the Quest too, to
compare the two within one session.
Measured on a Quest 3 with a Retro Rewind race paused (the same 439 draw calls every frame,
`render_scale` 1.0, 60 FPS throughout), switching the race view from the headset panel:
@@ -897,7 +906,7 @@ Measured on a Quest 3 with a Retro Rewind race paused (the same 439 draw calls e
The headset raised the GPU's level for the fully immersive race and it still took longer: in clock
cycles the window's frame is about 42% cheaper (5.0 against 8.6 million), which lets the Quest keep the
GPU at its lowest level. The compositor's extra time is the passthrough. During a race at `render_scale`
0.8, switching `debug.wiicompiled.window_eyes`, both eyes took 6.3 to 7.5 ms through the window against
0.8, switching the two with a temporary debug property, both eyes took 6.3 to 7.5 ms through the window against
9.1 to 9.5 ms whole and masked at similar draw counts (the compositor's `SF` field read 0.31 against
0.80), with the clock wandering between 350 and 600 MHz. The saving is smaller than the eye's pixels
(about 13% of a whole eye's) would suggest because much of an eye's cost is the geometry of every draw,
@@ -943,8 +952,8 @@ that reason (its bloom chain splits the frame about 20 times). An eye is therefo
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:
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
`debug.wiicompiled.fdm 0` launches without density maps at all (`docs/quest-port.md`).
replay plan" lines. `debug.wiicompiled.fdm 0` launches without density maps at all
(`docs/quest-port.md`).
What it saves depends on how much of an eye's cost is shading pixels. The numbers below are from a
Quest 3 at Luigi Circuit's Grand Prix start: GPU time of both eyes per frame, all settings
@@ -970,7 +979,7 @@ did (39 to 41.5 FPS).
## Diagnostics
**F10 > Diagnostics** holds two bug-report aids.
**F10 > Diagnostics** holds three bug-report aids.
**OpenXR diagnostic logging** is off by default. When it is off, each hook on the pacing thread is
one atomic test. It applies immediately and is remembered as:
@@ -1047,6 +1056,18 @@ problems. This instrumentation does not change frame pacing or inspect image pix
- **Presentation changes.** While logging is on, every `[mkw-vr] presentation=` transition is
logged, not just the first 16.
**First-person camera logging** is off by default as well. When it is on, a race in first person
writes a set of `[mkw-vr] first-person` and `[mkw-vr] cockpit` lines to `console.log` once per
second: the anchor's head offset from the race camera, the scene's view matrix and the kart's
physics pose (with the raw bits of its translation), where each candidate camera sits relative to
the kart, and the cockpit's wheel placement and race-camera check. They are the first thing to
read when the first-person view is misplaced. It applies immediately and is remembered as:
```toml
[diagnostics]
first_person_logging = false
```
**Export Logs** opens the system folder picker. It then creates a
`WiiCompiled-logs-YYYYMMDD-HHMMSS` folder at the chosen location, containing:
+2 -5
View File
@@ -153,8 +153,8 @@ keys on it: the first launch with foveation on recompiles every pipeline once.
Measured on the Quest 3 on 2026-09-24. The game was launched with `foveation = "medium"` and
`debug.wiicompiled.fpslog 1`, then taken by injected presses to Luigi Circuit's Grand Prix
start, first-person cockpit, player idle. Settings were set through
`debug.wiicompiled.eye_passes` and `debug.wiicompiled.foveation`, interleaved in 11 s windows
start, first-person cockpit, player idle. Settings were switched through temporary debug
properties (removed since the choice was made), interleaved in 11 s windows
over three or four rounds. Each 5 s `GPU ms/frame` line was assigned to the setting active for
all of it, using the runtime's own switch log lines. The GPU stayed at clock level 3 (492 MHz)
at 0.8, and 492 to 525 MHz at 1.3, the unfoveated windows running at the higher clocks.
@@ -972,9 +972,6 @@ the app:
| --- | --- |
| `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.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 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.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.fpslog 1` | Logs the game's rendered frame rate every 5 s, with per-frame averages of the producer's waits for the frame worker's DONE and SEALED phases and of the worker's seal, permit wait, prepare and encode stretches, and of the draw calls the recorded frame holds and the primitives that merged into them (an overlay that stops draws merging shows up there first). A third line reports the GX thread's command ring (records, waits, busy share). A second line gives the GPU time per frame from timestamp queries on every pass (`mono` native render, `eyeL`/`eyeR` replays, `screen`, `panel`, `efbcopy`, `palette`, `peek`, plus `passes-span` from the first pass begin to the last pass end and `between-passes` for copies and idle gaps). The compositor's `VrApi` log line gives headset FPS, `GPU%`, `CPU%`, clock levels and app GPU time (`App=`) |
+25 -2
View File
@@ -93,6 +93,7 @@ struct RuntimeUserConfig {
// F10 > Diagnostics: OpenXR pacing and presentation logging in console.log.
// Off unless set; it is a bug-report aid, not something to leave running.
std::optional<bool> diagnosticsOpenXRLogging;
std::optional<bool> diagnosticsFirstPersonLogging;
std::optional<float> audioVolume;
std::optional<float> audioMusicVolume;
std::optional<float> audioSoundEffectsVolume;
@@ -238,8 +239,17 @@ inline constexpr bool kVrNativeSteeringWheelDefault = true;
inline constexpr bool kVrHandSteeringDefault = true;
// The game hides karts and objects its own camera cannot see, which a head
// turn in VR reveals. object_culling false draws them anyway (see
// vr/mkw_vr_culling.h); it only takes effect while VR is enabled.
// vr/mkw_vr_culling.h); it only takes effect while VR is enabled. The PC
// draws them by default; the Quest keeps the game's culling, since every
// extra model costs its GPU twice. The macro is the same default for the
// config file written on first launch.
#if defined(__ANDROID__)
inline constexpr bool kVrObjectCullingDefault = true;
#define MKW_VR_OBJECT_CULLING_DEFAULT_TOML "true"
#else
inline constexpr bool kVrObjectCullingDefault = false;
#define MKW_VR_OBJECT_CULLING_DEFAULT_TOML "false"
#endif
// The cockpit hands follow the headset's hand tracking (the controllers' touch
// sensors while they are held, the cameras once they are put down, when bare
// hands also drive). Opt-in, and only offered on the Quest for now; the
@@ -585,7 +595,7 @@ inline void EnsureConfigFile() {
"# The game hides karts and objects its own camera cannot see.\n"
"# object_culling = false draws them anyway, so a head turn or a\n"
"# look over the shoulder shows them; it costs GPU time.\n"
"object_culling = true\n"
"object_culling = " MKW_VR_OBJECT_CULLING_DEFAULT_TOML "\n"
"# Hand steering (by heurazy): squeeze a grip near the wheel or\n"
"# handlebar to take hold of it with the tracked controllers, and\n"
"# turn it to steer. Releasing both grips gives steering back to the\n"
@@ -881,6 +891,8 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
readRangedFloat("wheel_tracking_grace", kVrWheelTrackingGraceMin, kVrWheelTrackingGraceMax);
config.vrWheelHaptics = FindConfigValue<bool>(document, "vr", "wheel_haptics");
config.diagnosticsOpenXRLogging = FindConfigValue<bool>(document, "diagnostics", "openxr_logging");
config.diagnosticsFirstPersonLogging =
FindConfigValue<bool>(document, "diagnostics", "first_person_logging");
auto readVolume = [&](std::string_view key) -> std::optional<float> {
auto value = FindConfigFloat(document, "audio", key);
@@ -1705,6 +1717,17 @@ inline bool SetDiagnosticsOpenXRLogging(bool value) {
return WriteSetting("diagnostics", "openxr_logging", value ? "true" : "false");
}
// The first-person camera's once-a-second anchor, view, pose and cockpit lines
// (vr/mkw_vr_first_person.cpp): off unless someone is debugging the camera.
inline bool DiagnosticsFirstPersonLogging(bool fallback = false) {
return Get().diagnosticsFirstPersonLogging.value_or(fallback);
}
inline bool SetDiagnosticsFirstPersonLogging(bool value) {
Mutable().diagnosticsFirstPersonLogging = value;
return WriteSetting("diagnostics", "first_person_logging", value ? "true" : "false");
}
inline std::string VrFirstPersonRotation(std::string fallback = kVrFirstPersonRotationDefault) {
const auto& value = Get().vrFirstPersonRotation;
return value && IsSupportedVrFirstPersonRotation(*value) ? *value : std::move(fallback);
+15 -3
View File
@@ -184,6 +184,7 @@ int g_vrFrameInterpolationMode = [] {
}();
int g_vrFirstPersonHiddenModel = RuntimeConfigFile::VrFirstPersonHiddenModel();
bool g_openxrDiagnosticsLogging = RuntimeConfigFile::DiagnosticsOpenXRLogging(false);
bool g_firstPersonDiagnosticsLogging = RuntimeConfigFile::DiagnosticsFirstPersonLogging(false);
// Config spellings and menu labels for the desktop mirror, index-matched to
// AuroraStereoMirrorView so the combo selection converts to either directly.
constexpr std::array<const char*, 5> kVrMirrorViewNames{"normal", "both", "left", "right", "none"};
@@ -1465,7 +1466,7 @@ void DrawVrSettings() {
"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.
// Shows the live level Aurora holds.
g_vrFoveation = static_cast<int>(aurora_get_stereo_foveation());
if (ImGui::Combo("Foveated rendering", &g_vrFoveation, kVrFoveationLabels.data(),
static_cast<int>(kVrFoveationLabels.size()))) {
@@ -1736,8 +1737,8 @@ void DrawVrCameraSettings() {
ImGui::SetTooltip(
"The game's own culling: karts, characters and course objects outside its chase "
"camera's view are not drawn, so a head turn or a look over the shoulder finds them "
"missing. Off draws them anyway, at some GPU cost. The draw distance is unchanged, "
"and the Flat screen race view always keeps the game's culling.");
"missing. Off draws them anyway, at some GPU cost, and is the PC's default. The draw "
"distance is unchanged, and the Flat screen race view always keeps the game's culling.");
}
ImGui::Separator();
if (ImGui::Button("Reset first-person defaults")) {
@@ -1949,6 +1950,17 @@ void DrawDiagnosticsSettings() {
ImGui::TextDisabled("OpenXR is not running, so nothing is logged until a VR session starts.");
}
ImGui::PopTextWrapPos();
if (ImGui::Checkbox("First-person camera logging", &g_firstPersonDiagnosticsLogging)) {
RuntimeConfigFile::SetDiagnosticsFirstPersonLogging(g_firstPersonDiagnosticsLogging);
mkw::vr::MkwVRFirstPersonApplyConfiguredSettings();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"Writes the first-person camera's anchor, view and kart pose, and the cockpit's\n"
"wheel and view checks, to console.log once per second during a race.\n"
"For reporting a misplaced first-person view. Off by default; it costs a little\n"
"game-thread time while on. Applies immediately and is remembered.");
}
ImGui::Separator();
const bool exporting = g_logExportInProgress.load(std::memory_order_acquire);
+12 -2
View File
@@ -336,6 +336,8 @@ struct FirstPersonState {
float cockpit_units_per_meter = RuntimeConfigFile::kVrCockpitUnitsPerMeterDefault;
bool steering_wheel = RuntimeConfigFile::kVrSteeringWheelDefault;
bool native_steering_wheel = RuntimeConfigFile::kVrNativeSteeringWheelDefault;
// [diagnostics] first_person_logging: the once-a-second camera lines below.
bool diagnostic_logging = false;
// Read at the race draw boundary, consumed at the seal.
struct CockpitLatch {
bool valid = false;
@@ -1206,6 +1208,9 @@ void FinishNativeWheelFrameLocked() noexcept {
}
void LogCockpitLocked(uint64_t frame, const Mtx34& view_from_world) noexcept {
if (!g_state.diagnostic_logging) {
return;
}
if (g_state.logged_frame != 0 && frame - g_state.logged_frame < 60) {
return;
}
@@ -1237,8 +1242,12 @@ void LogCockpitLocked(uint64_t frame, const Mtx34& view_from_world) noexcept {
void LogAnchorLocked(uint64_t frame, const Mtx34& anchor, const Mtx34& view_from_world,
const KartPoseRead& kart, const Mtx34& kart_from_local) noexcept {
// One line per second at 60 Hz: enough to confirm the offsets on-device
// without drowning the log during a race.
// One set of lines per second at 60 Hz when [diagnostics] first_person_logging
// asks for them: enough to confirm the offsets on-device. They are written
// under the first-person lock, so they stay off otherwise.
if (!g_state.diagnostic_logging) {
return;
}
if (g_state.logged_frame != 0 && frame - g_state.logged_frame < 60) {
return;
}
@@ -1356,6 +1365,7 @@ void MkwVRFirstPersonApplyConfiguredSettings() noexcept {
g_state.cockpit_units_per_meter = cockpit_units;
g_state.steering_wheel = RuntimeConfigFile::VrSteeringWheel();
g_state.native_steering_wheel = RuntimeConfigFile::VrNativeSteeringWheel();
g_state.diagnostic_logging = RuntimeConfigFile::DiagnosticsFirstPersonLogging();
g_state.native_wheel_fallback = false;
g_state.native_wheel_unmatched = 0;
}
+2 -127
View File
@@ -41,7 +41,6 @@
#elif defined(__ANDROID__)
#include "vr/openxr_android.h"
#include "vr/openxr_vulkan.h"
#include <sys/system_properties.h>
#include <time.h>
#include <unistd.h>
#define XR_USE_TIMESPEC
@@ -503,7 +502,7 @@ public:
// 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).
// render pass.
aurora_set_stereo_stop_at_display_copy(true);
aurora_set_stereo_skip_copy_clears(false);
aurora_set_stereo_single_pass_eyes(true);
@@ -938,11 +937,8 @@ private:
// 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
// not make that layer has the panel drawn into the eyes instead.
const bool panel_layer = backend_->PanelLayerAvailable() && !PanelLayerForcedOff();
const bool panel_layer = backend_->PanelLayerAvailable();
aurora_set_stereo_panel_layer(panel_layer);
PollEyePassesOverride();
PollFoveationOverride();
PollWindowEyesOverride();
presentation.panel.requested = panel_layer && OpenXRSettingsPanelOpen();
// Pipeline caches are stored where their stall is least visible: once when a race
@@ -1508,11 +1504,7 @@ private:
}
bool WindowShapedEyes() const noexcept {
#if defined(__ANDROID__)
return kWindowShapedEyesSupported && !window_eyes_forced_off_;
#else
return kWindowShapedEyesSupported;
#endif
}
// The seated frame the controllers are located in for hand steering: the
@@ -1624,115 +1616,6 @@ private:
return screen;
}
// Android: `adb shell setprop debug.wiicompiled.panel_layer 0` draws the settings panel into
// the eyes again, to compare the two ways or to rule out a runtime's quad layers. Read about
// once a second.
bool PanelLayerForcedOff() noexcept {
#if defined(__ANDROID__)
if (panel_layer_poll_ == 0) {
panel_layer_poll_ = 72;
char value[PROP_VALUE_MAX] = {};
const bool off =
__system_property_get("debug.wiicompiled.panel_layer", value) > 0 && value[0] == '0';
if (off != panel_layer_forced_off_) {
panel_layer_forced_off_ = off;
RT_LOG(RT_TAG_RUNTIME) << "OpenXR: settings panel "
<< (off ? "drawn into the eyes" : "on its own layer")
<< " (debug.wiicompiled.panel_layer)" << std::endl;
}
}
--panel_layer_poll_;
return panel_layer_forced_off_;
#else
return false;
#endif
}
// 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
// session. An empty value restores the single pass every eye has otherwise. Read about once a
// second.
void PollEyePassesOverride() noexcept {
#if defined(__ANDROID__)
if (eye_passes_poll_ != 0) {
--eye_passes_poll_;
return;
}
eye_passes_poll_ = 72;
char value[PROP_VALUE_MAX] = {};
int override_value = -1;
if (__system_property_get("debug.wiicompiled.eye_passes", value) > 0 &&
(value[0] == '0' || value[0] == '1')) {
override_value = value[0] - '0';
}
if (override_value == eye_passes_override_) {
return;
}
eye_passes_override_ = override_value;
const bool single = override_value != 0;
aurora_set_stereo_single_pass_eyes(single);
RT_LOG(RT_TAG_RUNTIME) << "OpenXR: eyes replayed in "
<< (single ? "one render pass" : "one render pass per recorded pass")
<< (override_value >= 0 ? " (debug.wiicompiled.eye_passes)"
: " (debug.wiicompiled.eye_passes cleared)")
<< std::endl;
#endif
}
// Android: `adb shell setprop debug.wiicompiled.foveation <0-3>` overrides the foveation level
// (off, low, medium, high) within one session, for A/B timing; an empty value hands it back to
// the settings. Levels need a session launched with foveation on (see VrFoveation). Read about
// once a second.
void PollFoveationOverride() noexcept {
#if defined(__ANDROID__)
if (foveation_poll_ != 0) {
--foveation_poll_;
return;
}
foveation_poll_ = 72;
char value[PROP_VALUE_MAX] = {};
int override_value = -1;
if (__system_property_get("debug.wiicompiled.foveation", value) > 0 && value[0] >= '0' &&
value[0] <= '3' && value[1] == '\0') {
override_value = value[0] - '0';
}
if (override_value == foveation_override_) {
return;
}
foveation_override_ = override_value;
const uint32_t level = override_value >= 0
? static_cast<uint32_t>(override_value)
: RuntimeConfigFile::VrFoveationLevelIndex(RuntimeConfigFile::VrFoveation());
aurora_set_stereo_foveation(level);
RT_LOG(RT_TAG_RUNTIME) << "OpenXR: foveation " << RuntimeConfigFile::kVrFoveationLevels[level]
<< (override_value >= 0 ? " (debug.wiicompiled.foveation)"
: " (debug.wiicompiled.foveation cleared)")
<< (aurora_stereo_foveation_available() ? "" : "; unavailable in this session")
<< std::endl;
#endif
}
// Android: `adb shell setprop debug.wiicompiled.window_eyes 0` renders the immersive window's eyes
// whole and only masks them, as the PC does, to compare the cost within one session; an empty
// value aims them through the window again. Read about once a second.
void PollWindowEyesOverride() noexcept {
#if defined(__ANDROID__)
if (window_eyes_poll_ != 0) {
--window_eyes_poll_;
return;
}
window_eyes_poll_ = 72;
char value[PROP_VALUE_MAX] = {};
const bool off = __system_property_get("debug.wiicompiled.window_eyes", value) > 0 && value[0] == '0';
if (off != window_eyes_forced_off_) {
window_eyes_forced_off_ = off;
RT_LOG(RT_TAG_RUNTIME) << "OpenXR: immersive window eyes "
<< (off ? "rendered whole and masked" : "aimed through the window")
<< " (debug.wiicompiled.window_eyes)" << std::endl;
}
#endif
}
// The settings panel's layer hangs exactly where its pointer hits are
// tested, the rectangle it used to cover in the eyes.
static void PlacePanelLayer(OpenXRBackendFrame& frame, const OpenXRPointerScreen& screen) noexcept {
@@ -1957,14 +1840,6 @@ private:
std::unique_ptr<OpenXRRuntime> runtime_;
std::unique_ptr<GraphicsBackend> backend_;
#if defined(__ANDROID__)
uint32_t panel_layer_poll_ = 0;
bool panel_layer_forced_off_ = false;
uint32_t eye_passes_poll_ = 0;
int eye_passes_override_ = -1;
uint32_t foveation_poll_ = 0;
int foveation_override_ = -1;
uint32_t window_eyes_poll_ = 0;
bool window_eyes_forced_off_ = false;
#endif
std::unique_ptr<OpenXRInput> input_;
std::thread pacing_thread_;
+14
View File
@@ -41,6 +41,20 @@ int main() {
Require(!Parse("[vr]\nhand_tracking = 1\n").vrHandTracking.has_value());
Require(!RuntimeConfigFile::kVrHandTrackingDefault);
// [vr] object_culling: false draws what the game camera culls. The PC
// defaults to that; the Quest keeps the game's culling.
Require(Parse("[vr]\nobject_culling = true\n").vrObjectCulling == true);
Require(Parse("[vr]\nobject_culling = false\n").vrObjectCulling == false);
Require(!Parse("[vr]\n").vrObjectCulling.has_value());
Require(!Parse("[vr]\nobject_culling = 0\n").vrObjectCulling.has_value());
#if defined(__ANDROID__)
Require(RuntimeConfigFile::kVrObjectCullingDefault);
Require(std::string_view(MKW_VR_OBJECT_CULLING_DEFAULT_TOML) == "true");
#else
Require(!RuntimeConfigFile::kVrObjectCullingDefault);
Require(std::string_view(MKW_VR_OBJECT_CULLING_DEFAULT_TOML) == "false");
#endif
// [vr] immersive_window and flat_screen: one race view in two keys, Flat
// Screen mode winning, so a file that predates the window reads as before.
using RuntimeConfigFile::VrRaceView;