mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 06:00:25 +02:00
Refactor QuestSurface and Build-Quest scripts for improved surface handling and performance logging
This commit is contained in:
1 parent
e52227baf5
commit
75f0f5f26d
4 files changed
+87
-9
No files matched your search
@@ -57,9 +57,11 @@ if (-not $CMakeDir -or -not (Test-Path (Join-Path $CMakeDir 'bin\cmake.exe'))) {
|
||||
}
|
||||
$sdkCMake = Get-ChildItem -Path (Join-Path $sdkRoot 'cmake') -Directory | Sort-Object Name -Descending | Select-Object -First 1
|
||||
if ($sdkCMake) { $env:PATH = (Join-Path $sdkCMake.FullName 'bin') + ';' + $env:PATH }
|
||||
# Parenthesised: PowerShell's comma binds tighter than +, which would join both
|
||||
# properties into a single line.
|
||||
$localProperties = @(
|
||||
'sdk.dir=' + $sdkRoot.Replace('\', '\\'),
|
||||
'cmake.dir=' + $CMakeDir.Replace('\', '\\')
|
||||
('sdk.dir=' + $sdkRoot.Replace('\', '\\')),
|
||||
('cmake.dir=' + $CMakeDir.Replace('\', '\\'))
|
||||
)
|
||||
Set-Content -Path (Join-Path $root 'local.properties') -Value $localProperties -Encoding ascii
|
||||
|
||||
|
||||
@@ -12,9 +12,21 @@ import org.libsdl.app.SDLSurface
|
||||
* activity mutex internal to libSDL3.so, so this subclass brackets SDL's
|
||||
* handling instead: begin takes Aurora's surface lock and pauses presentation,
|
||||
* end releases it and reports whether SDL left the surface ready.
|
||||
*
|
||||
* The surface buffer is also pinned to [BUFFER_WIDTH] x [BUFFER_HEIGHT]. An
|
||||
* immersive app's Android surface is never shown in the headset, yet SDL sizes
|
||||
* it to the whole display (4128x2208 on a Quest 3). Aurora sizes its
|
||||
* presentation snapshot from it, and in menus that snapshot is the image the
|
||||
* virtual screen shows in each eye. The screen spans about 900 eye pixels at
|
||||
* the default HUD size, so 1280x720 keeps menus sharp at a small fraction of
|
||||
* the cost.
|
||||
*/
|
||||
class QuestSurface(context: Context) : SDLSurface(context) {
|
||||
|
||||
init {
|
||||
holder.setFixedSize(BUFFER_WIDTH, BUFFER_HEIGHT)
|
||||
}
|
||||
|
||||
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
||||
nativeBeginSurfaceMutation()
|
||||
try {
|
||||
@@ -35,4 +47,9 @@ class QuestSurface(context: Context) : SDLSurface(context) {
|
||||
|
||||
private external fun nativeBeginSurfaceMutation()
|
||||
private external fun nativeEndSurfaceMutation(ready: Boolean)
|
||||
|
||||
private companion object {
|
||||
const val BUFFER_WIDTH = 1280
|
||||
const val BUFFER_HEIGHT = 720
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <SDL3/SDL_thread.h>
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
#include "android_debug.hpp"
|
||||
#include "system_info.hpp"
|
||||
#include "tracy/Tracy.hpp"
|
||||
|
||||
@@ -1165,8 +1166,22 @@ std::shared_ptr<PresentationImage> acquire_presentation_image(size_t slot, uint3
|
||||
return image;
|
||||
}
|
||||
|
||||
// A standalone headset never shows the app's Android surface while OpenXR drives the display, so presenting to it
|
||||
// (and copying the mirror image the desktop would show) is pure GPU cost there. Presentation snapshots are still
|
||||
// encoded: in menus the virtual-screen eyes are built from them. Desktop keeps its window mirror.
|
||||
bool headset_owns_display() noexcept {
|
||||
#if defined(__ANDROID__)
|
||||
return stereo_frame_provider_active();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool present_presentation_job(const PresentationJob& job) {
|
||||
ZoneScoped;
|
||||
if (headset_owns_display()) {
|
||||
return false;
|
||||
}
|
||||
const auto submissionStarted = PresentClock::now();
|
||||
// Keep the threshold far above compositor and scheduling jitter. The timings below separate a
|
||||
// real surface stall from a bad deadline, and only the former needs a rebuild.
|
||||
@@ -1857,8 +1872,12 @@ std::vector<PresentationJob> encode_sealed_frame(gfx::SealedFrame& sealedFrame,
|
||||
const bool immersiveReplay = stereoOutput && ctx.immersiveStereoPrepared;
|
||||
// One choice for the whole group: a slot showing the mono view next to slots
|
||||
// mirroring an eye would strobe between two different images.
|
||||
const MirrorPlan mirrorPlan = g_stereoMirrorState.Resolve(
|
||||
gfx::get_stereo_mirror_view(), stereo_frame_provider_active(), stereoOutput, immersiveReplay);
|
||||
// Nothing presents the snapshot on a headset, so it only needs the clear (see headset_owns_display).
|
||||
const bool headsetOnly = headset_owns_display();
|
||||
const MirrorPlan mirrorPlan =
|
||||
headsetOnly ? MirrorPlan::Black
|
||||
: g_stereoMirrorState.Resolve(gfx::get_stereo_mirror_view(), stereo_frame_provider_active(),
|
||||
stereoOutput, immersiveReplay);
|
||||
|
||||
// Each slot is submitted as soon as it is encoded, so the GPU starts slot 0 while slot 1 is still
|
||||
// recording. Queue order preserves the ordering the single batched buffer gave.
|
||||
@@ -1958,7 +1977,7 @@ std::vector<PresentationJob> encode_sealed_frame(gfx::SealedFrame& sealedFrame,
|
||||
for (uint32_t eye = 0; eye < AURORA_STEREO_EYE_COUNT; ++eye) {
|
||||
encode_virtual_screen_eye(encoder, completedMono, eye);
|
||||
}
|
||||
if (mirrorPlan == MirrorPlan::Black) {
|
||||
if (mirrorPlan == MirrorPlan::Black && !headsetOnly) {
|
||||
encode_presentation_snapshot(encoder, ctx.presentSource, *finalImage, true, MirrorPlan::Black);
|
||||
}
|
||||
}
|
||||
@@ -2077,6 +2096,26 @@ void record_frame_telemetry() {
|
||||
TracyPlot("aurora: mainThreadCpuUsPerFrame", static_cast<int64_t>((threadCpu100ns - previousThreadCpu100ns) / 10));
|
||||
previousProcessCpu100ns = processCpu100ns;
|
||||
previousThreadCpu100ns = threadCpu100ns;
|
||||
#endif
|
||||
#if defined(__ANDROID__)
|
||||
{
|
||||
// `adb shell setprop debug.wiicompiled.fpslog 1` before launch logs the game's rendered frame rate every five
|
||||
// seconds. The headset compositor's own log (logcat tag VrApi) repeats frames, so it cannot show this.
|
||||
static const bool fpsLog = android_debug::property_int("debug.wiicompiled.fpslog", 0) == 1;
|
||||
if (fpsLog) {
|
||||
static auto windowStart = std::chrono::steady_clock::now();
|
||||
static uint32_t windowFrames = 0;
|
||||
++windowFrames;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const std::chrono::duration<double> elapsed = now - windowStart;
|
||||
if (elapsed.count() >= 5.0) {
|
||||
Log.info("Game frame rate {:.1f} FPS ({} frames in {:.2f} s)", windowFrames / elapsed.count(), windowFrames,
|
||||
elapsed.count());
|
||||
windowStart = now;
|
||||
windowFrames = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
FrameMarkNamed("Aurora frame");
|
||||
}
|
||||
|
||||
+24
-4
@@ -116,6 +116,14 @@ menu → Start. Bindings are suggested for `oculus/touch_controller` and
|
||||
`QuestSurface` subclass brackets SDL's `surfaceChanged`/`surfaceDestroyed`
|
||||
with `aurora_android_begin/end_surface_mutation` (`aurora/android.h`), and
|
||||
Aurora's `SurfaceLock` owns its own recursive mutex. This is KartPad's design.
|
||||
- **No full-display mirror.** SDL sizes the app's Android surface to the whole
|
||||
display (4128x2208 on a Quest 3), which nobody sees while OpenXR drives the
|
||||
headset. `QuestSurface` pins the surface buffer to 1280x720. That size also
|
||||
sets Aurora's presentation snapshot, which is the image the menu virtual
|
||||
screen shows in each eye, where it spans about 900 pixels. While a stereo
|
||||
provider is registered on Android, Aurora skips the surface present and the
|
||||
desktop mirror copy (`headset_owns_display` in `lib/aurora.cpp`). The game's
|
||||
own render size is unaffected: at `resolution_multiplier = 1` it is 640x528.
|
||||
- **JNI only on the real thread stack.** Guest threads run on libco stacks
|
||||
inside the SDL thread, and SDL's Android event pump can reach Java (joystick
|
||||
polling, HIDAPI). ART binds JNI transitions to the thread's real stack, so
|
||||
@@ -246,6 +254,7 @@ 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.inject <n>:<button>` | Presses `a`, `b`, `x`, `y`, `start`, `up`, `down`, `left` or `right` for 12 XR frames each time `<n>` changes |
|
||||
| `debug.wiicompiled.fpslog 1` | Logs the game's rendered frame rate every 5 s. The compositor's `VrApi` log line gives headset FPS, `GPU%`, `CPU%` and app GPU time (`App=`) |
|
||||
|
||||
The injector makes headset tests possible with nobody wearing the headset.
|
||||
Keep the display awake, drive the menus, then take a compositor screenshot:
|
||||
@@ -262,10 +271,21 @@ screen is up, reach Grand Prix character select. A value left over from an
|
||||
earlier run is ignored on the first read. Presses only land while the XR
|
||||
session is `FOCUSED`.
|
||||
|
||||
Open measurement: the attract race advanced 603 game frames in about 14 s,
|
||||
roughly 43 FPS against the game's 60, with an optimized build (`-O3`,
|
||||
translated code `-O2`, `-mcpu=cortex-a77`). Profiling on the XR2 Gen 2 is the
|
||||
first performance task.
|
||||
Performance, measured 2026-09-16 on a 50cc Luigi Circuit start with the player
|
||||
idle, over 40 s, with an optimized build (`-O3`, translated code `-O2`,
|
||||
`-mcpu=cortex-a77`):
|
||||
|
||||
| Build | Game FPS | Headset FPS | App GPU time | GPU% |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Full-display mirror (4128x2208) | about 48.5 | 48.7 | 15.6 ms | 83 |
|
||||
| 1280x720 surface, no present | about 48.6 | 49.3 | 14.9 ms | 81 |
|
||||
|
||||
Removing the mirror saved about 0.7 ms of GPU time per frame but did not raise
|
||||
the game rate. Two leads remain. The game runs below 60 FPS with the GPU at
|
||||
about 80%, and CPU and GPU clock levels sit at 4/3. The headset FPS also
|
||||
follows the game rate instead of holding 72 Hz, so the pacing thread is not
|
||||
repeating the last layer as it does on desktop. Both need profiling on the
|
||||
XR2 Gen 2.
|
||||
|
||||
Verified on device since: the menus on the virtual screen, controller input
|
||||
(the user has driven races), and an immersive Grand Prix start with all 12
|
||||
|
||||
Reference in new issue
Block a user