diff --git a/dm/games/010055D009F78000/game.tsv b/dm/games/010055D009F78000/game.tsv index 1932b8d..92f0040 100644 --- a/dm/games/010055D009F78000/game.tsv +++ b/dm/games/010055D009F78000/game.tsv @@ -18,7 +18,8 @@ pad_layout full # ---- Presentation ---- # KTGL updates the game 60 times a second on the main thread and presents the latest update from its render thread with the render mode's interval (2 on the Switch: 30 fps). # Rewriter cuts on 1.2.0 main (patch_nso.py) set every mode's interval to 1, as in the engine's own 60 fps modes, so game mode gives 60 fps at the same game speed; -# two more make the frame limiter sleep instead of yield and the render thread wait a frame instead of 16 µs +# one more drops the render thread's forced 30 fps in the opening movie and scenes such as character creation; two more make the frame limiter sleep instead of yield +# and the render thread wait a frame instead of 16 µs present_interval game # ---- Versions ---- diff --git a/dm/tools/patch_nso.py b/dm/tools/patch_nso.py index bc243e4..4bef2aa 100644 --- a/dm/tools/patch_nso.py +++ b/dm/tools/patch_nso.py @@ -180,10 +180,14 @@ CUTS = { # 0 = 1080p docked, 2 = 720p handheld; 3 / 5 their two-view variants; 1 and 4 are 720p / 1080p modes at interval 1 that nothing selects). Measured on the Mac: 59.2 updates # and 29.8 presents a second. The per-mode interval table (int at main+0xd13de0, read by main+0x4b0420 for the present and by the 6-tick fade counter in main+0x4b7530; # float at main+0xd13e40, the per-frame scroll step in main+0x4c2b30) becomes 1 for every mode, as in modes 1 and 4: every update is shown and the render thread's - # own steps stay at the same speed. Resolutions stay per mode. The game itself still forces interval 2 while frame flags +0xc55f / +0xc560 are set (main+0x50fb7c) + # own steps stay at the same speed. Resolutions stay per mode '89048449ba238c8cf565518b83bf02d3': [ (0xd13de0, '020000000100000002000000020000000100000002000000', '010000000100000001000000010000000100000001000000', 'per-render-mode present interval table {2,1,2,2,1,2} → all 1 (60 fps; the game logic already runs at 60 updates a second)', 'host-only'), (0xd13e40, '000000400000803f00000040000000400000803f00000040', '0000803f0000803f0000803f0000803f0000803f0000803f', 'per-render-mode interval as float {2.0,1.0,2.0,2.0,1.0,2.0} → all 1.0 (render-thread per-frame steps)', 'host-only'), + # The render thread still presents with interval 2 while either of two flags is set: main+0x21c9a03, a full-screen image drawn before the frame (main+0x4b4908: the opening movie), + # and frame +0xc55f / +0xc560 (from the object at app +0x40: the character creation screen after the movie, and so on). In the engine's own 60 fps modes this traded 60 fps for + # resolution in those scenes (main+0x50f7d0 drops 720p mode 1 to 1080p mode 0); here it would show them at 30 with the render thread's own steps at half speed. Both are ignored + (0x50fb7c, '2801082a', 'e8031f2a', 'render thread forced interval 2: orr w8,w9,w8 (either flag) → mov w8,wzr (the mode\'s interval, 1)', 'host-only'), # The frame limiter's wait (main+0x5be430: yield, read the tick, until 60/fps ticks of 1/59.94 s have passed since the last frame end; x8 = time passed and x23 = target, in 100 ns) # is a busy loop on the host: the yield back-off spins its first 2 ms on a performance core and then oversleeps by up to 250 µs, so frames took 16.8 ms (59.5 fps, a repeated # display frame every 2 s) and CPU energy went from 0.16 W at 30 fps to 1.5 W on the Mac. The yield becomes nn::os::SleepThread of 96% of the time left, and the loop goes back to diff --git a/docs/development.md b/docs/development.md index 601c289..6d7cbb3 100644 --- a/docs/development.md +++ b/docs/development.md @@ -215,7 +215,7 @@ The game's own work and its frame rate stay as they are; the host avoids work of - **Fixed:** the first loading screen took 78 s on the iPhone. The loader calls `nvnQueueFinish` after each resource it uploads, and that call waited for the display to take every queued frame; it now waits only for the GPU ([04](agents/04-graphics.md), Three Houses). - **Open:** 64-pixel rectangles in the ground where decals land. One red line per run says a texture buffer is bound where a shader samples 2D: the decal and light lists. - **Memory:** about 3.5 GB resident and 2.8 GB of Metal allocations on the Mac; the game itself makes a 1.5 GB GPU-only memory pool. -- **60 fps:** the Switch version shows 30, but KTGL already updates the game 60 times a second on its main thread, each update a fixed 1/60 s step; the render thread draws the latest update and presents it with the interval of the render mode (2 for the docked 1080p and handheld 720p modes; the engine also has a 720p and a 1080p mode at interval 1 that nothing selects). Per-build cuts on 1.2.0 `main` set every mode's interval to 1, so every update is shown and the game runs at the same speed; the render thread's own per-frame steps (a fade counter, a scroll) read the same table. Two more cuts make the waits cheap. The frame limiter (`main+0x5be430`) yielded and polled the clock, which on the host spun 2 ms a frame on a performance core and ran at 59.5 updates a second; it now sleeps the time left. The render thread waits for the next update with a timeout of one frame given in milliseconds to a wait that takes microseconds, so it polled every 16 µs (about 50,000 wakeups a second at 60 fps); it now waits a frame. Prologue battle on the Mac: 59.9 fps, 55% of a core and 1.8 W of CPU energy (the original at 30 fps: 44% and 1.7 W). The game still asks for interval 2 itself while two frame flags are set (`main+0x50fb7c`); they were never set in the title, the menus, loading or the battle (likely movies). +- **60 fps:** the Switch version shows 30, but KTGL already updates the game 60 times a second on its main thread, each update a fixed 1/60 s step; the render thread draws the latest update and presents it with the interval of the render mode (2 for the docked 1080p and handheld 720p modes; the engine also has a 720p and a 1080p mode at interval 1 that nothing selects). Per-build cuts on 1.2.0 `main` set every mode's interval to 1, so every update is shown and the game runs at the same speed; the render thread's own per-frame steps (a fade counter, a scroll) read the same table. Two more cuts make the waits cheap. The frame limiter (`main+0x5be430`) yielded and polled the clock, which on the host spun 2 ms a frame on a performance core and ran at 59.5 updates a second; it now sleeps the time left. The render thread waits for the next update with a timeout of one frame given in milliseconds to a wait that takes microseconds, so it polled every 16 µs (about 50,000 wakeups a second at 60 fps); it now waits a frame. Prologue battle on the Mac: 59.9 fps, 55% of a core and 1.8 W of CPU energy (the original at 30 fps: 44% and 1.7 W). The render thread also forced interval 2 while either of two flags was set: one for the opening movie, one for scenes such as the character creation screen after it (the engine's own 60 fps modes dropped to 30 there to keep 1080p). A fourth cut ignores both (`main+0x50fb7c`), so those scenes run at 60 too; the movie still lasts 210.6 s (6,318 frames at 30 fps), as in the original. - **Mac input:** a fresh save needs `DM_KBD=` for the name entry, and its confirm dialogs default to No. From a new game into the prologue battle (frame-based, about 6 minutes): `DM_KEYS="f360:A*185/45,f1200:Plus*35/219,f8700:A*120/250,f8760:Left~0.3*120/250,f8800:A*120/250,f9010:Plus*100/281"`; the game offers a save right before the battle. **Super Mario Galaxy (`010099C022B96000`)**