Three Houses: 60 fps also in the opening movie and character creation

The render thread forced present interval 2 while either of two flags was set: one for the full-screen
movie image (the opening movie), one from the scene object (the character creation screen after it, and
similar scenes). The engine's own 60 fps modes dropped to 30 there to keep 1080p; with the per-mode
tables at 1 it would also run the render thread's own steps at half speed. A cut at main+0x50fb7c
ignores both. Mac, new game: 59.9 fps through the movie and character creation; the movie lasts
210.6 s in both the original and the 60 fps build.
This commit is contained in:
seedds committed 2026-10-19 18:11:33 +08:00
1 parent 6ff963dae9
commit 0852da4980
3 files changed
+8 -3

No files matched your search

+2 -1
View File
@@ -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 ----
1 # Game profile of Fire Emblem: Three Houses (Switch, base game v0 + update 1.2.0 = v327680). In bring-up
18 # 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
19 present_interval # and the render thread wait a frame instead of 16 µs
20 # ---- Versions ---- present_interval
21 build # ---- Versions ----
22 build
23 package
24 shader_versions
25 # ---- Files ----
+5 -1
View File
@@ -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
+1 -1
View File
@@ -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=<name>` 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`)**