Use when app runs at unexpected frame rate, stuck at 60fps on ProMotion, frame pacing issues, or configuring render loops. Covers MTKView, CADisplayLink, CAMetalDisplayLink, frame pacing, hitches, system caps.
View on GitHubSelect agents to install to:
npx add-skill https://github.com/CharlesWiltgen/Axiom/blob/main/.claude-plugin/plugins/axiom/skills/axiom-display-performance/SKILL.md -a claude-code --skill axiom-display-performanceInstallation paths:
.claude/skills/axiom-display-performance/# Display Performance Systematic diagnosis for frame rate issues on variable refresh rate displays (ProMotion, iPad Pro, future devices). Covers render loop configuration, frame pacing, hitch mechanics, and production telemetry. **Key insight**: "ProMotion available" does NOT mean your app automatically runs at 120Hz. You must configure it correctly, account for system caps, and ensure proper frame pacing. --- ## Part 1: Why You're Stuck at 60fps ### Diagnostic Order Check these in order when stuck at 60fps on ProMotion: 1. **Info.plist key missing?** (iPhone only) → Part 2 2. **Render loop configured for 60?** (MTKView defaults, CADisplayLink) → Part 3 3. **System caps enabled?** (Low Power Mode, Limit Frame Rate, Thermal) → Part 5 4. **Frame time > 8.33ms?** (Can't sustain 120fps) → Part 6 5. **Frame pacing issues?** (Micro-stuttering despite good FPS) → Part 7 6. **Measuring wrong thing?** (UIScreen vs actual presentation) → Part 9 --- ## Part 2: Enabling ProMotion on iPhone **Critical**: Core Animation won't access frame rates above 60Hz on iPhone unless you add this key. ```xml <!-- Info.plist --> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> ``` Without this key: - Your `preferredFrameRateRange` hints are ignored above 60Hz - Other animations may affect your CADisplayLink callback rate - iPad Pro does NOT require this key **When to add**: Any iPhone app that needs >60Hz for games, animations, or smooth scrolling. --- ## Part 3: Render Loop Configuration ### MTKView Defaults to 60fps **This is the most common cause.** MTKView's `preferredFramesPerSecond` defaults to 60. ```swift // ❌ WRONG: Implicit 60fps (default) let mtkView = MTKView(frame: frame, device: device) mtkView.delegate = self // Running at 60fps even on ProMotion! // ✅ CORRECT: Explicit 120fps request let mtkView = MTKView(frame: frame, device: device) mtkView.preferredFramesPerSecond = 120 mtkView.isPaused = false mtkView.enableSetNeedsDisplay = false // Continuous,