diff --git a/aurora-main/include/aurora/aurora.h b/aurora-main/include/aurora/aurora.h index 46bec06..ff9120f 100644 --- a/aurora-main/include/aurora/aurora.h +++ b/aurora-main/include/aurora/aurora.h @@ -289,6 +289,10 @@ typedef struct { bool hasD3D12AdapterLuid; uint32_t d3d12AdapterLuidLow; int32_t d3d12AdapterLuidHigh; + // Nobody watches the desktop window while the headset runs (a standalone + // headset such as the Steam Frame's native build): skip presenting it and stop + // the mono render after the last pass the eyes sample, as Android always does. + bool xrHeadsetOnly; } AuroraConfig; typedef struct { diff --git a/aurora-main/lib/aurora.cpp b/aurora-main/lib/aurora.cpp index 961cfae..3ab35d1 100644 --- a/aurora-main/lib/aurora.cpp +++ b/aurora-main/lib/aurora.cpp @@ -1416,7 +1416,7 @@ bool headset_owns_display() noexcept { #if defined(__ANDROID__) return stereo_frame_provider_active(); #else - return false; + return g_config.xrHeadsetOnly && stereo_frame_provider_active(); #endif } diff --git a/runtime/include/runtime_config.h b/runtime/include/runtime_config.h index 7f9466d..068c124 100644 --- a/runtime/include/runtime_config.h +++ b/runtime/include/runtime_config.h @@ -1,5 +1,12 @@ #pragma once +// The standalone headsets: the Quest (Android) and the Steam Frame, in its Android flavour and its +// native SteamOS build. They render on a mobile GPU with a game thread that is the bottleneck, so +// they share the defaults below and the headset panel's foveation and tracked-hands rows. +#if defined(__ANDROID__) || defined(MKW_HEADSET_STEAM_FRAME) +#define MKW_VR_STANDALONE 1 +#endif + #include #include #include @@ -193,7 +200,7 @@ inline constexpr uint32_t kDisabledPostProcessingPathsDefault = kPostProcessingB // standalone headset renders on a mobile GPU, so the Quest and the Steam Frame // start below it; the launcher's first Config.toml (GameStorage.kt) writes the // same value. -#if defined(__ANDROID__) +#if defined(MKW_VR_STANDALONE) inline constexpr float kVrRenderScaleDefault = 0.8f; #define MKW_VR_RENDER_SCALE_DEFAULT_TEXT "0.8" #else @@ -256,7 +263,7 @@ inline constexpr bool kVrCockpitItemThrowDefault = true; // 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__) +#if defined(MKW_VR_STANDALONE) inline constexpr bool kVrObjectCullingDefault = true; #define MKW_VR_OBJECT_CULLING_DEFAULT_TOML "true" #else @@ -310,7 +317,7 @@ inline bool IsSupportedVrRefreshRate(uint32_t value) { // from "off" to a level takes a restart; between levels and back to "off" it is // live. The standalone headsets (Quest, Steam Frame) start at "medium"; // elsewhere it does nothing. -#if defined(__ANDROID__) +#if defined(MKW_VR_STANDALONE) inline constexpr const char* kVrFoveationDefault = "medium"; #else inline constexpr const char* kVrFoveationDefault = "off"; @@ -1646,9 +1653,10 @@ inline bool ShowFps(bool fallback = false) { } // Runs the host side of the GX pipeline on its own thread (gx_thread.h). On by -// default on the Quest, where the game thread is the bottleneck; opt-in elsewhere. +// default on the standalone headsets, where the game thread is the bottleneck; +// opt-in elsewhere. inline bool GxThread() { -#if defined(__ANDROID__) +#if defined(MKW_VR_STANDALONE) return Get().gxThread.value_or(true); #else return Get().gxThread.value_or(false); diff --git a/runtime/src/settings_overlay.cpp b/runtime/src/settings_overlay.cpp index 60123d6..a6b430a 100644 --- a/runtime/src/settings_overlay.cpp +++ b/runtime/src/settings_overlay.cpp @@ -146,8 +146,8 @@ bool g_vrHudVirtualScreen = RuntimeConfigFile::VrHudVirtualScreen(true); int g_vrRaceView = static_cast(RuntimeConfigFile::GetVrRaceView()); bool g_vrFlatScreen = g_vrRaceView == static_cast(RuntimeConfigFile::VrRaceView::FlatScreen); constexpr std::array kVrRaceViewLabels{"Immersive", "Immersive window", "Flat screen"}; -#if defined(__ANDROID__) -#if !defined(MKW_HEADSET_STEAM_FRAME) +#if defined(MKW_VR_STANDALONE) +#if defined(__ANDROID__) && !defined(MKW_HEADSET_STEAM_FRAME) bool g_vrPassthrough = RuntimeConfigFile::VrPassthrough(); #endif bool g_vrHandTracking = RuntimeConfigFile::VrHandTracking(); @@ -1260,7 +1260,7 @@ void DrawVrSteeringWheelSettings() { "trailed item."); } ImGui::EndDisabled(); -#if defined(__ANDROID__) +#if defined(MKW_VR_STANDALONE) ImGui::BeginDisabled(!g_vrHandSteering && g_vrCockpitItemHand == 2); if (ImGui::Checkbox("Tracked hands", &g_vrHandTracking)) { RuntimeConfigFile::SetVrHandTracking(g_vrHandTracking); @@ -1491,8 +1491,8 @@ void DrawVrSettings() { eyes.scaled_height, g_vrRenderScalePercent); } } -#if defined(__ANDROID__) -#if !defined(MKW_HEADSET_STEAM_FRAME) +#if defined(MKW_VR_STANDALONE) +#if defined(__ANDROID__) && !defined(MKW_HEADSET_STEAM_FRAME) if (ImGui::Checkbox("Passthrough around the menu screen", &g_vrPassthrough)) { mkw::vr::OpenXRSetPassthrough(g_vrPassthrough); RuntimeConfigFile::SetVrPassthrough(g_vrPassthrough); @@ -1829,7 +1829,7 @@ void DrawVrCameraSettings() { RuntimeConfigFile::SetVrHandSteering(g_vrHandSteering); RuntimeConfigFile::SetVrCockpitItemHand(RuntimeConfigFile::kVrCockpitItemHandDefault); RuntimeConfigFile::SetVrCockpitItemThrow(g_vrCockpitItemThrow); -#if defined(__ANDROID__) +#if defined(MKW_VR_STANDALONE) g_vrHandTracking = RuntimeConfigFile::kVrHandTrackingDefault; RuntimeConfigFile::SetVrHandTracking(g_vrHandTracking); #endif @@ -2531,7 +2531,7 @@ void InitializeRuntimeSettings() noexcept { aurora_set_display_mode(static_cast(g_displayMode)); g_displayMode = static_cast(aurora_get_display_mode()); aurora_set_disable_copy_filter(g_disableCopyFilter); -#if defined(__ANDROID__) +#if defined(MKW_VR_STANDALONE) aurora_set_stereo_foveation(static_cast(g_vrFoveation)); #endif aurora_set_stereo_mirror_view(static_cast(g_vrMirrorView)); diff --git a/runtime/src/vr/openxr_integration.cpp b/runtime/src/vr/openxr_integration.cpp index 695b73b..9129939 100644 --- a/runtime/src/vr/openxr_integration.cpp +++ b/runtime/src/vr/openxr_integration.cpp @@ -745,6 +745,11 @@ private: // launched with foveation off does without them. aurora_config.xrFragmentDensityMap = RuntimeConfigFile::VrFoveation() != "off"; #endif +#if defined(MKW_HEADSET_STEAM_FRAME) && !defined(__ANDROID__) + // The Frame's native build: the headset is the only display anyone looks at, so the + // desktop window is neither presented nor fully rendered, as on Android. + aurora_config.xrHeadsetOnly = true; +#endif #if defined(_WIN32) if (kRequiredAuroraBackend != BACKEND_D3D12) return; const auto& requirements = backend_->GraphicsRequirements(); diff --git a/runtime/tests/vr_config_tests.cpp b/runtime/tests/vr_config_tests.cpp index ef3a79c..4e77da5 100644 --- a/runtime/tests/vr_config_tests.cpp +++ b/runtime/tests/vr_config_tests.cpp @@ -39,7 +39,7 @@ int main() { Require(!Parse("[vr]\nfoveation = \"ultra\"\n").vrFoveation.has_value()); Require(!Parse("[vr]\nfoveation = 2\n").vrFoveation.has_value()); Require(!Parse("[vr]\n").vrFoveation.has_value()); -#if defined(__ANDROID__) +#if defined(MKW_VR_STANDALONE) Require(std::string_view(RuntimeConfigFile::kVrFoveationDefault) == "medium"); #else Require(std::string_view(RuntimeConfigFile::kVrFoveationDefault) == "off"); @@ -101,7 +101,7 @@ int main() { 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__) +#if defined(MKW_VR_STANDALONE) Require(RuntimeConfigFile::kVrObjectCullingDefault); Require(std::string_view(MKW_VR_OBJECT_CULLING_DEFAULT_TOML) == "true"); #else