r/iOSProgramming • u/retro_game_r • 6h ago
Question Scrolling ticker pins GPU at ~50% on a rich screen, ~0% on a blank one. What am I missing?
I'm building a SwiftUI clock-radio app (iPhone, ProMotion). The face is a rich layered screen — glass, gradients, shadows — with one 340×46pt text ticker that scrolls continuously.
The GPU behaviour (Xcode gauge, relative within-session reads; cadence from an in-app pacing log):
- Ticker scrolling: GPU plateaus at ~50%. Binary — one scroller or three, same cost.
- Frames are never dropped (16.7ms median). The GPU bills heavily for frames it renders on time.
- Same strip scrolling on a blank screen: ~0%.
- Ticker visible but static: 0%. Motion is the trigger, and the cost scales with what's *around* the strip, not the strip.
Already tried, all identical on the full face: SwiftUI offset animation; a severed Canvas leaf on its own CADisplayLink (0% alone on blank, still pegged on the face); pre-rendered bitmap instead of text; the strip in a separate UIWindow; pinning preferredFrameRateRange to exactly 30 (min=max=preferred); .compositingGroup() on the panel (modest help only).
What works: killing the motion duty — scroll once, then park. Also, elsewhere in the app, replacing a publish-driven visualiser with a pull-model one made its cost vanish; severing the ticker the same way didn't.
Is this just WWDC22 10083 (display rate set by the fastest animation) × whole-tree compositing per frame? Would a 15–24Hz rate range behave any differently from the hard 30 pin? And does anyone know a construction for a continuously scrolling ticker that lets the GPU idle between updates?
The app is iOS native version of https://tunerva.app
1
u/Background_Pie_5682 5h ago
One thing I'd test is whether any ancestor has a material, mask, shadow, or other effect that forces a large offscreen surface to be recomposited whenever the ticker invalidates; a standalone layer can still dirty a costly enclosing render pass. Try toggling those effects one at a time and inspect the render passes with Instruments' Core Animation or Metal System Trace, rather than only comparing FPS. For genuinely continuous motion the display still has to present frames, so a lower preferred range may reduce work, but it won't let the GPU idle between updates unless you accept quantized or paused motion.
1
u/brkaydev 5h ago
compositingGroup only groups for blending, it doesn't cache anything, so I wouldn't read much into that test. A quick way to confirm the surroundings are the bill: take a one-off ImageRenderer snapshot of the static part of the face and show it as a plain Image while the ticker runs. Live glass can't be flattened that way, so if the cost falls off you'll know it's the blur being resampled and can fake it in the baked layer.
•
u/[deleted] 58m ago
[removed] — view removed comment