- build 2026.06
- stack · 5 components
- published 2026-06-27
FrameLimiter
A frame cap that actually does less GPU work, not just shows fewer frames. It paces the one call every Metal game goes through and lets back-pressure do the rest.
- 01Platform
- Apple Silicon · native Metal
- 02Injection
- DYLD_INSERT_LIBRARIES dylib
- 03Pacing
- mach_wait_until · sleeps, never spins
- 04Control
- live-tunable fps file · no restart
why it matters
- On a fanless Apple Silicon laptop, running way above what the screen shows just drains the battery and makes heat for frames you never see. The cap stops that.
- A limiter can only cap downward. Asking for more than the refresh only helps if the game already renders that fast with VSync off.
- The cap lives in a little file the dylib watches, so flctl can change it or turn the limiter off mid-game, no restart.
- For Steam it installs by adding an LSEnvironment block to the game's Info.plist and re-signing, which keeps the real executable so Game Mode and the over-60 Hz path still work.
visuals
engineering notes
FrameLimiter is a frame-rate limiter for native Metal games on Apple Silicon. It’s a small dylib you inject with DYLD_INSERT_LIBRARIES that caps a game’s frame rate to whatever target you want, separate from the game’s own VSync, so you can trade input lag for less GPU power and heat.
One hook
A game gets each frame by asking its CAMetalLayer for the next drawable. FrameLimiter hooks -[CAMetalLayer nextDrawable] (method swizzling) and paces that one call with mach_wait_until. Basically every native Metal game goes through that method, whether it’s raw Metal, MetalKit/MTKView, SDL2, or MoltenVK, so one hook covers all of them. It sleeps until the next frame is due instead of busy-looping, because busy-looping would burn the power the cap is trying to save.
Back-pressure
Pacing nextDrawable does more than just delay frame presentation. Holding the call back drains the drawable pool and stalls the render thread, which applies back-pressure so the GPU stops rendering new frames. On a fanless laptop, running at 200+ FPS just wastes battery and makes the machine hot for frames the screen can’t even show.
Adaptive VSync and live control
A limiter can only cap downward. If your target is above the display refresh, FrameLimiter forces displaySyncEnabled off so the game can run faster than the panel. That tears on a fixed-refresh screen, which is the trade for lower lag; at or below the refresh it leaves the game’s VSync alone. The cap itself lives in a small file the dylib watches, so flctl can change it, disable it, or toggle it while the game runs without a restart.
Steam on macOS can’t pass environment variables through launch options, so installing per game means touching the game bundle. FrameLimiter adds an LSEnvironment block to the game’s Info.plist and ad-hoc re-signs it, so the system injects the dylib when the game launches as itself. I could’ve just replaced the executable, but that makes macOS stop recognizing the app, which kills Game Mode and the direct-to-display path that lets a game go past 60 Hz. Editing Info.plist keeps the app’s identity, so those keep working.