Skip to content

The graphics scale is tier 0 in both modes: Star Fox's docked 3D was 768x432, smaller than handheld - #1304

Closed
lunks wants to merge 1 commit into
seedds:mainfrom
lunks:pr/gfx-scale-tier0
Closed

lunks wants to merge 1 commit into
seedds:mainfrom
lunks:pr/gfx-scale-tier0

Conversation

@lunks

@lunks lunks commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Ready for review, not ready to merge: it changes every Switch 2 game that reads the graphics scale, and the numbers below come from a build of our fork. If you have these games, any of this helps; paste the result here.

Missing:

  • regress.py check on the Switch 2 games you have built. Bananza and Star Fox are the two known to read the tier.
  • Donkey Kong Bananza on main, docked, before and after: its upscaler output becomes the 2880x1620 window and its dynamic resolution stops adding 9 to its GPU time, so check frame time and whether 60 fps holds.
  • Star Fox on main, docked: scene size before and after (768x432 to 960x540 on our fork).
  • Whether any other Switch 2 game reads it: unknown.

What and why

Who needs it: Star Fox players. Its note lists the 3D staying at 768x432 as open (The picture). Docked, the 3D renders smaller than handheld (960x540), on a GPU about twice as fast. Other Switch 2 games that size their picture by the same tier are likely affected too; Ocarina of Time's render size, listed open in #1299, may be one, not checked.

Cause: oe::GetCurrentGraphicsScale (am's graphics scaling parameters, 10 / 20 / 30 = tier 0 / 1 / 2) is the share of the GPU the system leaves the game, which the console lowers while it needs the GPU itself (GameChat's video). It is not the operation mode, and the host answered tier 1 docked. Star Fox keeps only "tier 0" (byte +0xa of its mode event, main+0xba69970) and renders 0.8 times smaller below it: its DLSS network is resized to 768x432 at boot for its 1920x1080 window, where tier 0 gives 960x540. Donkey Kong Bananza reads the same tier; its code scales its frame buffer by {1.0, 0.8, 2/3}[tier] (from the disassembly, main+0x30f840; its docked size with tier 0 was not measured on main).

The host now answers tier 0 in both modes. DM_GFX_SCALE=1 or 2 answers a reduced tier. Reference entry: The graphics scale is the GPU share, not the operation mode.

Checked

  • Tests (M5 Pro, macOS 27.0): make -C dm/host/tests GAME=040074A01BF12000 run passes (t_oe: 57 checks, with new cases: tier 0 docked and handheld, DM_GFX_SCALE=2, nonsense value keeps 0). test_docs.py passes; docs_symptoms.py --check passes.
  • Measured before and after, Star Fox (0400E88025B1A000, v131072) on a build of our fork (not main), M5 Pro: scene 768x432 before, 960x540 after, docked. Mean luminance gradient (x1000, 1080p) on Peppy's close-up 65 s into the intro: console footage 5.8, tier 0 here 5.3, tier 1 4.6. GPU time in that cutscene unchanged, about 7 ms p50 at its 30 fps.
  • Not measured: the same on main's build, Donkey Kong Bananza or any other game with this change, iOS.
  • Games run: none on this branch.
  • regress.py check: not run. regress.py impact would name the Switch 2 games; Bananza and Star Fox are the ones that read the tier here.

Docs

  • The finding is in docs/reference/services.md and the two game notes, and python3 dm/tools/docs_symptoms.py was run (the symptom index); DM_GFX_SCALE is in docs/env_switches.tsv
  • A chain tool changed: not applicable
  • No game files, keys or data extracted from a game in the change or the attachments

…ystem leaves a game, not the operation mode. Star Fox rendered 768x432 docked, smaller than handheld

oe::GetCurrentGraphicsScale (am's graphics scaling parameters, 10 / 20 / 30 -> tier 0 / 1 / 2) answered 1 docked. Games size their picture and their GPU budget by it, as a console does while it needs the GPU itself (GameChat's video): Star Fox keeps only "tier 0" (byte +0xa of its mode event, main+0xba69970) and below it renders 0.8 times smaller, so docked its DLSS network was resized to 768x432 at boot (960x540 handheld, and with tier 0 docked). Donkey Kong Bananza's code scales its frame buffer by {1.0, 0.8, 2/3}[tier] and adds 0 / 9 / 13 to the GPU time its dynamic resolution counts (from the disassembly, main+0x30f840; its docked size with tier 0 was not measured on main). A docked picture smaller than the handheld one on a GPU twice as fast is not the console's, and Star Fox's console footage matches tier 0: Peppy's close-up 65 s into it has a mean luminance gradient of 5.8 (x1000 at 1080p), tier 0 here 5.3, tier 1 4.6. GPU time in that cutscene is the same, about 7 ms p50 at its 30 fps.

The host answers 0 in both modes; DM_GFX_SCALE=1 or 2 answers a reduced tier (t_oe). Docs: services.md entry, both games' notes, env_switches.

Checked: make -C dm/host/tests GAME=040074A01BF12000 run (t_oe: 57 checks). The sizes and gradients above were measured on a build of our fork, not on main; no regress run, and the change reaches every Switch 2 game that reads the tier.
@lunks
lunks force-pushed the pr/gfx-scale-tier0 branch from 2491adf to 6542d07 Compare November 5, 2026 16:11
@lunks
lunks marked this pull request as ready for review November 5, 2026 16:11
WizardOfCodez pushed a commit to WizardOfCodez/nxnative that referenced this pull request Nov 5, 2026
…al camera gets an off-screen native window so the avatar settings no longer crash

- The rewriter's frame-step stub at main+0xb4498ac (build a7730d0f…) replaces the renderer's
  scene scale with DM_RESOLUTION_SCALE when it is set; the launcher offers it as "Resolution"
  (Game's own / 75% / 100%) for this build only. patch_nso.py gives frame-step code
  ctx.area(offset), a patch area in reach of the site, since main's text is 189 MB.
- nn::chat::GetVirtualCameraNativeWindowHandle returns an off-screen native window instead
  of 0: the game makes an NVN window of it; such a window acquires at once and presents
  nowhere.
- Probes: DM_PROBE_FP=1 logs s0-s15, and DM_PROBE_WATCH / DM_PROBE_POLL take as+<offset>.
- The game note records the picture's cause chain (with seedds#1304 and seedds#1305), the measurements,
  and the Venom 2 out-of-memory that seedds#1310 removes.
gnosticgeek pushed a commit to gnosticgeek/nxforge that referenced this pull request Nov 5, 2026
…odes: it is the GPU share the system leaves a game, not the operation mode. Star Fox rendered 768x432 docked, smaller than handheld

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
gnosticgeek pushed a commit to gnosticgeek/nxforge that referenced this pull request Nov 6, 2026
) replaces the pull request heads

Upstream landed Ocarina of Time in main (chain adec7d81d970: "holds 60 fps in
Kokiri Forest, 4 GB less") and closed the pull requests it folded in, with
reworded and partly different code. Mixing a pull request head with its landed
version hunk by hunk silently broke things (two GlobalToStorage tables, a
duplicate AF2ip, two mtk_pso definitions), so the rule here is one version per
feature:

- Pull requests upstream closed (seedds#1299, seedds#1300, seedds#1312, seedds#1343, seedds#1345, seedds#1353,
  seedds#1354, seedds#1359, seedds#1360, seedds#1373, seedds#1428, seedds#1430, seedds#1434): their hunks are taken out
  of our side before the three-way merge, so upstream's landed version stands
  alone. Gone with them: bindless pool table patching (reg_bump), the ghost
  table's per-submission hold, the growing raw pool table and its write-log
  sizing, seedds#1428's pool_buf spares (upstream's pool_table_take is the same
  idea), the ixc-based indirect mesh draw (upstream uses mdx), and their tests
  (t_pool_table, t_mesh_indirect, t_gmem_raw, finalized_bindless_pool,
  test_bnsh_nvn2, test_ndve).
- Open pull requests that upstream's landing supersedes: seedds#1394 (upstream's
  mtl_pool_addressed) and seedds#1393 pool_skip_retired (upstream's budgeted pool
  walk; the profile does not use it) are dropped with their tests and docs.
  seedds#1368's mesh pipeline prewarm is dropped too: upstream kept the background
  build in nvn_geometry.inc, and the prewarm's mtk_build / mtk_claim /
  g_warm_mps went with it.
- Kept, as upstream does not have them: NXForge's own features (GPU watchdog,
  present filters and anti-aliasing, aniso floor, quick settings, pipeline
  archive compaction, upload breakdown, memory report lines) and the still
  open pull requests seedds#1304, seedds#1305, seedds#1308, seedds#1320, seedds#1331, seedds#1332, seedds#1355, seedds#1356,
  seedds#1369, seedds#1376, seedds#1377, seedds#1379, seedds#1382, seedds#1390, seedds#1391, seedds#1392, seedds#1414, seedds#1420,
  seedds#1436, seedds#1439, seedds#1446, seedds#1453.
- Combined where both sides changed the same lines: GW_N 1 << 16 with seedds#1320's
  wide-write intervals (GW_PAGES = SBW_WPAGES, 128 slots), the CPU upload log
  with upstream's linear upload route, arch_open_end before the compaction
  limit, cbn_prog in geo_gs_one (seedds#1382).
- Translator: upstream's sources and binaries, plus seedds#1414's
  InstEmitAmpereMem diff and the LDSM SubgroupBarrier in
  InstEmitAmpereTensor.cs. The binaries and dmsh_chain.lock are upstream's
  until the Mac rebuilds them (dotnet publish, tool_lock --update-lock,
  dmsh_chain --header); dmsh_chain.h is regenerated for now.
- RED_ONCE keys: upstream took 153 and 154, so the present anti-aliasing lines
  move to 156 and 157 and seedds#1420's packaged-entry line to 158 (array 159).
- Host: hle_net.c and t_net_eventfd.c are upstream's (its eventfd poll waits
  out the whole timeout, which our 5d7920c fixed another way).
- Kept ours: dm/tools/romfs_image.py and its test, so the 26 GB RomFS image is
  not packed again.

dm/tools tests on Linux: same failures as on 875449e (Linux-only quirks),
plus test_nlg4 and test_dmsh_pool_arrays, which fail on upstream main too.
Not yet built on the Mac.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
FeReuss pushed a commit to FeReuss/nxnative-oot that referenced this pull request Nov 9, 2026
…esolution are basic settings

The Mac launcher had no way to pick a picture size for Ocarina of Time, the GPU share the game sees, or the console mode with a default that follows the build. Now:

- Render scale: "Game's own" (1.0, the game's full size, its own dynamic resolution as the sub-setting) or one of the game's own dynamic-resolution levels fixed (0.75 .. 0.5 in the profile's render_levels, shown in pixels from render_base). patches.py has a render-scale stub (the graphics update's scale apply block, found by function fingerprint beside the frame step and the dynamic-resolution stubs) that takes the loader's scale every frame while it is not 1.0; loader.c divides it by the graphics tier's picture so a level names the same size at every GPU share.
- GPU share (DM_GFX_SCALE: Whole GPU, 80 %, 67 %) and Console mode (DM_OPMODE) take Auto, the profile's tested value (hle_oe.c dm_gfx_scale and dm_opmode_value). The profile names tier 0 (gfx_scale 0), so the picture is unchanged by default and the launcher offers the game's own lighter modes.
- Dynamic resolution (the game's own climb under load) is the sub-setting of Render scale at "Game's own".
- Frame rate offers 30 and 60. 120 is not offered: the game steps its time in whole vsyncs, and fps_max 120 needs the game measured at that rate first.

The new settings.tsv rows carry level=basic and needs=; a choice row may have auto=<label>, its default, which the host reads as the profile's value. Needs PR seedds#1304 (the graphics scale is tier 0 in both modes), whose commit this one is built on.

Front-ends: interface: a launcher.json choice row can carry "auto" (the value its Auto option stands for, the game's tested default); Ocarina of Time's render scale is a choice of 1.0 first (Dynamic) then the game's own levels, with "base" for their pixels, where it was a range. A front end that draws choices needs nothing else; Auto is its default.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@b1tdr3amer24

Copy link
Copy Markdown
Contributor

Ocarina of Time (0400486024DEC000, v131072) reads the tier, docked: answered tier 1 it draws its output at 2048x1152 and its 3D at no more than 1536x864, 80% of the 2560x1440 / 1920x1080 it draws with tier 0.

Tested on main dbf84998 plus some open PRs (#1903 render scale among them), with #1838's DM_GRAPHICS_SCALE=0 as the switch. The same tier 0 answer as this PR. M4 Pro (20-core GPU), macOS 27.2, docked, fullscreen, same save:

tier 1 (main) tier 0
WindowCrop (game output) 2048x1152 2560x1440
3D at Render scale 1.0 1536x864 1920x1080
GPU p50 12.9-14.0 ms 15.0 ms
frames shown 55.8-59.8 fps 59.0 fps (56 windows of 300 frames, 33 with no dropped frame)

The log has oe::GetCurrentGraphicsScale: tier 0 and WindowCrop 0,0 2560x1440. No new [RED] lines, no crash, about 6 minutes of play. With tier 0, 1920x1080 is the game's own docked size; main's OoT note calls 1536x864 its top internal resolution, which is the tier 1 size. So the Render scale table in that note (1.0 = 1536x864, 0.75 = 1171x659) would move up with this PR.

Not checked: handheld, other Switch 2 games, regress.py.

@axl-devhub

axl-devhub commented Nov 10, 2026 •

Copy link
Copy Markdown

Ocarina of Time (0400486024DEC000, v131072) with the tier 0 answer, on a MacBook Pro M5 Pro (20-core GPU, 48 GB), macOS 27.0.1. Tested on main e45694b7 (chain bc8830d4ba5a) with only h_oe_GfxScale changed to return 0, the same answer as this PR.

Docked, fullscreen on the built-in screen, Dynamic resolution off, MetalFX present upscaling, one play session of about 24 minutes (87,000 frames) from a save:

tier 0
WindowCrop (game output) 2560x1440 (2048x1152 on main)
present MetalFX 2560x1440 → 3600x2025
GPU p50, mean of 289 windows of 300 frames 12.7 ms
frame rate, mean of the same windows 58.4 fps
footprint, highest 15.0 GB

No crash, no FATAL. Four [RED] lines: three texture type mismatch (slot 7; one of the three shaders, 9b310b3349825e9a_b, logged the same line on 6d9edb07 without the change, the other two I have no earlier run for) and one audio device callback gap. 37 of the 290 windows had an on-screen p99 over 20 ms; I did not compare that count with tier 1 on the same route, so I can't say the tier changed it.

Not checked: handheld, other Switch 2 games, regress.py.

seedds added a commit that referenced this pull request Nov 11, 2026
…eir full docked picture: oe::GetCurrentGraphicsScale (am's graphics scaling parameters, 10 / 20 / 30 = tier 0 / 1 / 2) was answered by the operation mode, 0 handheld and 1 docked, and games size their picture and their GPU budget by the tier, full at 0. Docked, Star Fox rendered its scene at 768x432, smaller than its 960x540 handheld (it keeps only "tier 0", byte +0xa of its mode event, main+0xba69970, and renders 0.8 times smaller below it); Donkey Kong Bananza's frame buffer is the display's size times {1.0, 0.8, 2/3}[tier] (main+0x30f840) and its dynamic resolution adds 0, 9 or 13 to its GPU time; Ocarina of Time drew 2048x1152 for 2560x1440. The host now answers 0 in both modes (h_oe_GfxScale, hle_oe.c); DM_GFX_SCALE=1 or 2 answers a reduced tier. That the tier is how much of the GPU the game may use is an inference from the games' code, and which tier a console reports in which mode is not measured: the reference entry (The graphics scale does not follow the operation mode) says so. The code, diagnosis and test are PR #1304's; here its conflict with main is resolved, its wording about the GPU share marked as an inference, and Bananza's and Ocarina's notes updated (Ocarina's sizes and times in its note were all taken with tier 1). Checked here: the host tests pass (t_oe 60 checks, with the tier 0 and DM_GFX_SCALE cases), test_docs passes. Donkey Kong Bananza docked on an M6 (16 GB), a window, fresh save into the mining tunnel, hosts of chain cfcc3a82c78b that differ in this answer only: frame buffer 2304x1296 → 2880x1620, main passes 1676x943 → 2095x1179, GPU p50 6.8-7.2 ms (three runs) → 8.3-8.9 ms (one run), 60 fps with both, the same four VP9 [RED] lines. Others' runs: Star Fox 768x432 → 960x540 on a build of the author's fork (M5 Pro); Ocarina of Time on main with only this answer changed, 2048x1152 → 2560x1440, 3D 1536x864 → 1920x1080, GPU p50 12.9-14.0 → 15.0 ms at about 59 fps (M4 Pro), and 12.7 ms at 58.4 fps over 24 minutes (M5 Pro). Not run: Bananza's busy views, where the larger picture leaves less room; Star Fox and Ocarina here; a slower Mac; the iPhone; lint_c.py; regress.py check (one game run by hand instead, the one with the frame-rate risk). Supersedes PR #1838, the same switch with the docked tier 1 kept (PR #1304)

Co-authored-by: Pedro Nascimento <91118+lunks@users.noreply.github.com>
@seedds seedds closed this Nov 11, 2026
@seedds

seedds commented Nov 11, 2026

Copy link
Copy Markdown
Owner

Landed on main as 084f5aa, with you as co-author: the code, the diagnosis and the test are yours. Thank you, and thanks to b1tdr3amer24 and axl-devhub for the Ocarina of Time runs on main, which are in the commit and in its note.

Changed on top of the PR:

  • The conflict with main in Star Fox's note is resolved.
  • The reference entry is now The graphics scale does not follow the operation mode. That the tier is the share of the GPU the system leaves the game (GameChat's video) is kept, but as an inference from the games' code; which tier a console reports in which mode stays open.
  • Ocarina of Time's note says its sizes and times were all taken with tier 1, and carries the two tier 0 runs.

Donkey Kong Bananza, the run that was missing (M6, 16 GB, docked, a window, fresh save into the mining tunnel, two hosts that differ in this answer only):

tier 1 tier 0
frame buffer 2304x1296 2880x1620
main passes 1676x943 2095x1179
GPU p50 6.8-7.2 ms (three runs) 8.3-8.9 ms (one run)
frame p50 16.6-16.7 ms 16.6-16.7 ms

Not run: Bananza's busy views, where the larger picture leaves less room; Star Fox on main; a slower Mac; the iPhone; regress.py check. A Star Fox docked run on main (scene size before and after) would still help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation game: 0400E88025B1A000 A game profile game: 040074A01BF12000 Donkey Kong Bananza host The Horizon host: SVCs, services, threads, memory (dm/host)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants