Skip to content

iPhone GPU Performance (A12 XS,

iPhone GPU performance: what costs pixels on Apple GPUs

Section titled “iPhone GPU performance: what costs pixels on Apple GPUs”

Context: GitHub #124. One iPhone XS (iPhone11,2, A12) store session on 1.1 (BUILD 338) averaged 46 fps. Telemetry: samples.gpu_ms ~21.4 ms against a 16.6 ms budget, while tick_ms + render_cpu_ms was ~1.7 ms. The frame is GPU (fill-rate / bandwidth) bound, not CPU or draw-call bound.

  • No build regression. Apple TV HD (AppleTV11,1, A8, pinned 1080p) GPU time is flat at ~12 ms per frame across builds 303-338 with no evolved weapon. store-335 vs store-338 differ only in scripts/marketing, so their gap is loadout, not code.
  • The XS was never comfortably at 60 under load. It was under 60 fps at moderate enemy counts by build 204 (2026-10-02); the “held 60 on 09-29” note in docs/reference/fidelity-device-sim.md was most likely a light-load run.
  • Native resolution is the main multiplier. The XS renders world and UI at native 2436x1125 (UIScreen.scale 3, ~2.74 MP) into an RGBA16F target (viewport/hdr_2d=true), 1.32x the Apple TV’s pixels on a similar-class GPU. A ~720-line cap (~1559x720) is ~41% of the pixels. Implemented as a per-device-class render-scale tunable on fix/124-render-scale (1.1.1).
  • Event Horizon (evolved Orbit) is the one content-specific cost seen: ATV runs with it climb to 14.9 ms (5-15 min) and 18.4 ms / 48.6 fps (>15 min, 6 sessions, ~24 enemies but ~520 draws). Confounded with run length. Tracked as #152.
  • ~5 ms of the XS’s 21.4 ms is not explained by pixel count scaled from the ATV. Unresolved; A/B live on a TestFlight build with Remote Control armed (skill bh-console-live-ab).
  • Telemetry to use: samples.gpu_ms (populated on iOS/tvOS despite the stale “NULL on Metal” schema comment) plus the launch event’s win_w/h, screen_scale, content_scale. Do not use sim_ms.

How Apple GPUs spend time (tile-based deferred rendering)

Section titled “How Apple GPUs spend time (tile-based deferred rendering)”

Apple GPUs render the frame in small tiles held in fast on-chip memory. Work that stays in the tile is cheap; work that moves a whole frame through main memory is expensive. Practical consequences:

  1. Full-screen copies are the costliest pattern. Each BackBufferCopy / screen-reading effect ends the render pass, writes the frame out, and reads it back. Ours: shockwave, gravity lens, screen punch, spacetime warp, warp streaks, screen feedback, the Event Horizon warp. Fewer copies per frame, or smaller copy rects, beats shader micro-optimisation.
  2. Render-target format is bandwidth. RGBA16F (HDR 2D) is twice the bytes of RGBA8 on every load, store and copy.
  3. Blending and overdraw. Additive halos and soft glows cost per covered pixel per layer, and blending disables hidden-surface removal. Large soft sprites over large areas add up.
  4. Resolution multiplies all of the above. Hence the render-scale cap as the broadest lever, and existing reduced-resolution paths (FxHalfResHost at 0.5, NebulaBg at 0.35).
  5. Measure the right way. Godot’s quick fill-rate test: V-Sync off, compare fps in a large window vs a very small one; a big gain means fill-rate bound.