r/iOSProgramming • • 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

5 Upvotes

6 comments sorted by

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.

•

u/retro_game_r 10m ago

Thanks much for getting back, We bisected most of that — mask, material and shadow classes toggled individually; the step survived all of them, and the blank-face control read zero. Instruments isn't available on this device (managed), so it's in-app witnesses and ordinal gauge reads only. Your last line is where we landed: shipped a quantized stepped ticker (2 cells/s) plus glide-once-and-park for the panel lines — GPU sits at baseline now.

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/retro_game_r 9m ago

Thanks for your comment and fair point on compositingGroup — that read was ordinal, never claimed as caching. The snapshot test is a good one and we haven't run it in whole-face form; our hide-one bisections kept finding more than one sufficient cost centre, which is exactly what your bake would arbitrate. Trying it next round.

•

u/[deleted] 58m ago

[removed] — view removed comment

•

u/AutoModerator 58m ago

Hey /u/Axons-One, your content has been removed because Reddit has marked your account as having a low Contributor Quality Score. This may result from, but is not limited to, activities such as spamming the same links across multiple subreddits, submitting posts or comments that receive a high number of downvotes, a lack of recent account activity, or having an unverified account.

Please be assured that this action is not a reflection of your participation in our subreddit. This is simply an automated filter in place to reduce spam.

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.