Skip to content

About

Hardware-paced Metal FPS limiter for NotProton & macOS Wine

Resources

Stars

8 stars

Watchers

0 watching

Forks

Latest commit

 

History

5 Commits

Folders and files

Repository files navigation

notproton-fpslimit

Hardware-Paced Metal Frame Rate Limiter for NotProton & macOS Wine

Language Platform Architecture Compatibility License Release

A lightweight, universal frame pacing plugin for NotProton.
Eliminates micro-stutter and delivers flat frametimes in DirectX & Vulkan games running on macOS via D3DMetal, DXMT, and DXVK.

Download notproton-fpslimit

Deadlock running with flat frametimes on Apple Silicon Metal HUD

Deadlock (DirectX 11 via D3DMetal) locked at 30 FPS on a 120Hz ProMotion display — flat 33.75ms frametime line on the Metal HUD.


Important

Compatibility: Requires macOS 14.0+ and NotProton. Built as a native universal fat binary supporting both Apple Silicon (M1/M2/M3/M4) and Intel Macs.


Features

  • Flat Frametime Delivery: Meters frame submission intervals and paces CoreAnimation presentation to eliminate judder and uneven display pulldown.
  • Hybrid Precision Pacing: Combines high-resolution kernel waits (mach_wait_until) with microsecond spin-yields (CPU_YIELD) on QOS_CLASS_USER_INTERACTIVE threads.
  • Bresenham Remainder Tracking: Accumulates fractional nanosecond clock remainders across frames to guarantee zero drift over multi-hour gaming sessions.
  • Native Steam UI Integration: Adds a clean slider directly into your game's Properties $\to$ Compatibility tab in Steam (snapping to Off, 30, 40, 60, 120, and 144 FPS).
  • Optional VSync Decoupling: Includes an optional sub-toggle to disable VSync quantization when immediate presentation is preferred.
  • Zero Dependencies: Pure C and Objective-C linking directly against system Metal and QuartzCore frameworks — minimal footprint, zero external overhead.

Why This Is Needed

When running Windows games on macOS through translation layers (such as D3DMetal or DXMT), games often exhibit micro-stutter even when the game's internal frame counter reports a steady 60 or 30 FPS:

Unpaced Frame Delivery (Judder & Micro-stutter):
Frame 1: |-- 33.3ms --|
Frame 2: |-- 33.5ms (slipped past vblank by 0.2ms!) ------> held to 41.7ms (5 vblanks)
Frame 3: |-- 25.0ms (catchup) --|

With notproton-fpslimit (Flat Cadence):
Frame 1: [33.3ms - 4 vblanks]
Frame 2: [33.3ms - 4 vblanks]
Frame 3: [33.3ms - 4 vblanks]

Without presentation duration awareness, if a frame finishes rendering even 0.1ms past a vertical refresh boundary on a 60Hz or 120Hz ProMotion display, macOS CoreAnimation holds that frame for an entire extra vblank. The result is irregular 3:2 or 5:3 pacing spikes.

notproton-fpslimit solves this by:

  1. Pacing CPU frame submission ahead of CAMetalLayer nextDrawable using Mach absolute time, interactive QoS priority, and Bresenham fractional clock tracking.
  2. Eliminating swapchain starvation and input latency by letting CoreAnimation and Metal present naturally without artificial compositor holds.

Installation

Option A: One-Line Install (Recommended)

Run this command in Terminal:

curl -fsSL https://raw.githubusercontent.com/Ghostkwebb/notproton-fpslimit/main/install.sh | bash

Option B: Manual Install

  1. Download notproton-fpslimit.zip from Releases.
  2. Unzip it and copy the files to your NotProton directory:
    • Copy libfpslimit.dylib $\to$ ~/Library/Application Support/notproton/
    • Copy fpslimit.conf $\to$ ~/Library/Application Support/notproton/plugins/
  3. Restart Steam.

Steam Configuration

  1. Open Steam.
  2. Right-click any game running via NotProton $\to$ Properties $\to$ Compatibility.
  3. Under the Developer section, adjust the FPS Limiter slider:
    • Off: Unconstrained frame rate
    • 30 FPS: Locked 4 vblanks on 120Hz ProMotion / 2 vblanks on 60Hz
    • 40 FPS: Locked 3 vblanks on 120Hz ProMotion (ideal handheld / battery sweet spot)
    • 60 FPS: Locked 2 vblanks on 120Hz ProMotion / 1 vblank on 60Hz
    • 120 FPS / 144 FPS: High refresh rate caps
  4. Launch your game.

(Optional) You can also set a custom FPS limit via Steam launch options:

NOTPROTON_FPS_LIMIT=45 %command%

Building from Source

Prerequisites

  • macOS 14.0+ (Sonoma or Sequoia)
  • Xcode Command Line Tools (xcode-select --install)

Build Universal Binary

git clone https://github.com/Ghostkwebb/notproton-fpslimit.git
cd notproton-fpslimit
make

Install Directly

make install

Uninstall

make uninstall

Technical Architecture

  • Layer Hooking: Swizzles CAMetalLayer nextDrawable via Objective-C runtime method swapping.
  • Swapchain Integrity: Paces CPU frame requests before calling into the driver, avoiding swapchain starvation and input queue delays without intrusive compositor holds.
  • Thread-Local State: Uses _Thread_local reentrancy guards to prevent swizzle recursion, and escalates pacing threads to QOS_CLASS_USER_INTERACTIVE.
  • Architecture Agnostic: Implements CPU yields via __builtin_arm_yield() on Apple Silicon (ARM64) and _mm_pause() on Intel (x86_64).

License

Distributed under the MIT License. See LICENSE for more information.


Crafted natively for macOS Gaming

About

Hardware-paced Metal FPS limiter for NotProton & macOS Wine

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages