Frame rate optimization
Skill almasumdev/awesome-mobile-performance-agent-skills/.github/skills/rendering/frame-rate-optimization
Agent skills for profiling and optimizing mobile app performance (startup, memory, frame-rate, network).
npx -y skills add almasumdev/awesome-mobile-performance-agent-skills --skill frame-rate-optimizationAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Hit 60/90/120Hz frame rate budgets by balancing CPU (UI thread) and GPU (RenderThread/Metal) work. Use when frame time exceeds the device refresh budget.
SKILL.md
5.2 KB, as published. Nobody here has run it
Frame Rate Optimization
Instructions
A smooth UI is not about doing less; it is about doing work within the frame budget and not blocking the pipeline. This skill covers the mental model and the fixes that apply across iOS, Android, Flutter, and React Native.
1. Know the Budget
| Refresh rate | Frame budget | Slack after 4ms overhead |
|---|---|---|
| 60 Hz | 16.67 ms | ~12 ms |
| 90 Hz | 11.11 ms | ~7 ms |
| 120 Hz | 8.33 ms | ~4 ms |
Work on two threads must each fit in the budget:
- UI / main thread — layout, measure, produce a display list.
- Render thread / GPU — rasterize, composite, present.
2. Pipeline by Platform
- Android:
Choreographer.doFrame→View.measure/layout/draw→RenderThread→SurfaceFlinger. - iOS: Run loop →
CALayercommit →CARenderServer→ GPU → display. Core Animation always runs on a dedicated thread. - Flutter: Dart UI thread (widget → element → render object) → Skia/Impeller raster thread → GPU.
- React Native: JS thread → shadow thread (Yoga layout) → UI thread / Fabric → RenderThread.
If either thread exceeds the budget, a frame is dropped. Traces usually show which.
3. UI-Thread Wins
- Flatten view hierarchies. In Android avoid nested
LinearLayout; useConstraintLayoutor Compose. In iOS flattenUIStackViewnesting beyond 3 levels. - Cache expensive measurements. In SwiftUI avoid
GeometryReaderin list rows; preferfixedSize()orViewThatFits. In Compose hoistremember { }for computed text styles. - Avoid per-frame allocation. In Flutter use
constwidgets. In RN memoize row components (React.memo, stable keys).
Compose example — stable lambdas:
@Composable
fun FeedRow(item: Item, onClick: (Item) -> Unit) {
// onClick captured as stable - prefer passing item id rather than a new lambda per frame
Row(Modifier.clickable { onClick(item) }) { /* ... */ }
}
SwiftUI — equatable views:
struct Row: View, Equatable {
let item: Item
var body: some View { HStack { Text(item.title) } }
static func == (a: Row, b: Row) -> Bool { a.item.id == b.item.id }
}
List(items) { Row(item: $0).equatable() }
4. GPU / Raster-Thread Wins
- Avoid large blurs. A full-screen Gaussian blur on a 1080p phone costs ~4-6 ms on the raster thread. Replace with a static snapshot or a smaller blurred layer.
- Rasterize expensive static trees. Flutter:
RepaintBoundary. Android View:setLayerType(LAYER_TYPE_HARDWARE). iOS:layer.shouldRasterize = true; layer.rasterizationScale = UIScreen.main.scalefor static content only. - Prefer vector assets over large bitmaps for icons, but cap vector complexity. Extremely complex SVGs can beat large PNGs on CPU but cost more on GPU.
- Use platform-native gradients.
LinearGradienton GPU beats painting pixels in CPU.
5. Animations at High Refresh Rate
- Drive animations from the platform compositor, not JS/Dart setState loops.
- Flutter:
AnimatedBuilderwith aTicker; avoidsetStateper frame at root. - RN:
AnimatedwithuseNativeDriver: true, or Reanimated worklets. - Compose:
animate*AsState,Animatable, orrememberInfiniteTransition— they run on the choreographer, not recomposition. - SwiftUI:
withAnimationandTimelineView; Core Animation implicit animations when possible.
- Flutter:
RN Reanimated worklet:
const offset = useSharedValue(0);
const style = useAnimatedStyle(() => ({ transform: [{ translateX: offset.value }] }));
offset.value = withSpring(100);
6. Target the Right Refresh Rate
- iOS: request 120Hz with
CADisplayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 80, maximum: 120, preferred: 120). Without this, ProMotion falls back to 60Hz for UIKit animations. - Android: declare 120Hz support in manifest:
<meta-data android:name="android.max_aspect" .../>is not enough; setWindow.attributes.preferredDisplayModeIdor useSurface.setFrameRate()on Android 11+. - Flutter:
GestureBinding.instance.resamplingEnabled = truehelps touch-driven 120Hz.
7. Verify
adb shell dumpsys gfxinfo com.example.app framestats prints a histogram of frame times. Expect the 95th-percentile bucket under the budget.
Instruments Core Animation FPS gauge or UICoreAnimationFramesPerSecond metric from MetricKit can be wired into CI.
Flutter DevTools Performance shows UI and Raster thread stacks side by side for every frame.
Checklist
- Frame budget documented per screen (60/90/120Hz target known).
- View / widget hierarchy is ≤ 10 deep on hot screens.
- No full-screen blur without measurement justifying it.
-
const/ memoized rows and stable keys in lists. - Animations use platform compositors, not per-frame
setStateloops. - High-refresh-rate display modes requested on iOS and Android when appropriate.
-
gfxinfo framestatsor Core Animation FPS captured and p95 under budget.