Every phone is fast for three minutes. The interesting number is the frame rate at minute twelve, on a warm device, with the screen at the brightness a player actually uses. That number is set by heat and battery, and it is measurable long before it becomes a complaint in a review.
Sustained, not peak
A modern flagship SoC can draw 8 to 12 watts for a short burst and 3.5 to 5 watts indefinitely. A mid-range phone sustains 2.5 to 3.5 watts. The difference between the two numbers is not a driver setting: it is the thermal design of the phone, and no game can negotiate with it.
What a game can do is decide where to spend the budget. A scene that renders at 60 fps and 6 watts will be throttled within four minutes; the same scene at 45 fps and 3.8 watts may run indefinitely without the system lowering the clock, and it will feel more consistent because the frame time does not change.
Measuring the budget
We use one scene and one run length: 20 minutes, fixed camera, capped frame rate, screen at 200 nits, airplane mode on, battery starting above 80 percent. Sample three values once per second: frame time from the engine, battery current from dumpsys battery, and the hottest thermal zone from /sys/class/thermal.
adb shell dumpsys battery | grep -E 'level|temperature|current now'
adb shell 'cat /sys/class/thermal/thermal_zone*/temp' | sort -n | tail -3
Battery current is not power, but on a single-rail phone it is proportional enough to compare two builds. Convert with the nominal voltage if you want watts, and do not compare across devices: a 4 500 mAh battery at 900 mA and a 5 000 mAh battery at 1 000 mA are not the same load.
| Device class | First 3 min | Minute 15 | Drop |
|---|---|---|---|
| Flagship, 2025 | 60 fps | 48 fps | 20% |
| Flagship, 2024 | 60 fps | 44 fps | 27% |
| Mid-range, 2024 | 58 fps | 41 fps | 29% |
| Budget, 2023 | 47 fps | 34 fps | 28% |
Every device dropped, and every device stabilised rather than continuing to fall. That plateau is the real budget. Design to it, and the first three minutes become a bonus rather than a promise you break.
Tell the system what you need
Android's Dynamic Performance Framework, available from API 31, lets a game report its target frame duration and the actual work duration per frame. The system uses that to adjust CPU frequency instead of guessing from utilisation, which is both faster to react and less prone to over-provisioning.
val manager = context.getSystemService(PerformanceHintManager::class.java)
val session = manager.createHintSession(
intArrayOf(renderThread.id, gameThread.id),
targetWorkDurationNanos,
)
// once per frame, after the work is done
session.reportActualWorkDuration(actualWorkNanos)
Two rules make it useful. Report the threads that do the work, not the whole process; a session with twenty threads gives the system no signal. And report honestly: a game that reports 8 ms while taking 14 trains the system to under-clock, and the resulting jank looks like a platform problem.
The framework also exposes getPreferredUpdateRate() on newer devices, which is the display's idea of a sustainable rate for the current thermal state. Treat it as advice, not a command.
Where the watts actually go
On a mid-range phone at 1080p, our scene spent roughly 45 percent of its energy on the GPU, 25 percent on the display backlight, 15 percent on CPU, and the rest on memory and radios. Two conclusions follow.
- The backlight is not free. A bright scene at high brightness costs more than a dark scene at the same frame rate. This is why OLED-friendly dark UI is a real optimisation on some devices and a rounding error on others.
- GPU work is the largest lever you control. Resolution scaling, shadow cascade count and post-processing are worth more than micro-optimising the CPU path.
A 20 percent render resolution cut on our scene saved 0.7 W and moved the sustained frame rate from 41 to 48 fps. Removing one post-processing pass saved 0.3 W. Both are cheaper than rewriting the culling code, and both are visible in the first minute of a trace.
Scheduling traps
Phones have big and little cores, and the scheduler moves threads between them. Pinning a render thread to a little core looks like a way to save power; in practice it adds frame-time variance, because the little core is shared with background work and its frequency changes more often than the big core's.
If you set thread priorities, set them once at thread start and do not fight the scheduler. setpriority with a small negative nice value on the render thread is reasonable; real-time priorities on Android usually replace jank with a watchdog timeout.
Worker pools deserve a second look. A job system that spawns one thread per core on an 8-core phone will keep the little cores busy for the whole frame, which costs energy without improving the critical path. Cap the pool at the number of big cores plus one and measure the effect on frame time; on our scene the cap changed nothing in frame time and saved 0.4 W.
Quality tiers from the thermal state
Instead of a fixed quality level, define three tiers and switch between them on the thermal signal, not on the frame rate. Switching on frame rate creates a feedback loop: the game lowers quality, the frame rate recovers, the game raises quality, the device heats again.
| Tier | Trigger | Change |
|---|---|---|
| High | Cold start, thermal status nominal | Full resolution, four shadow cascades, all post effects |
| Balanced | Thermal status light or moderate | 0.85 resolution scale, two cascades, bloom off |
| Sustained | Thermal status severe, or 10 minutes at balanced | 0.7 resolution scale, one cascade, shadows at half rate |
Hysteresis matters more than the thresholds. We require ten minutes below a status before moving back up, and we never move up more than one tier at a time. A game that visibly changes quality twice a minute feels worse than one that stays at the lower setting.
Checklist
- Measure 20 minutes, not 30 seconds, and report the plateau.
- Pick the target frame rate from the plateau, not from the cold benchmark.
- Report work duration through the performance hint API; do not overstate.
- Spend the first optimisation on resolution and shadows, not on CPU micro-work.
- Change tiers on thermal status with hysteresis, never on frame rate alone.
The numbers above come from the same test scene used in the frame profiling article. If you only have one device, the thermal curve note explains what the first ten minutes look like across price tiers.