Smooth Frames. Better Games.
FrameFlow main window before selecting a game.
FrameFlow is a lightweight Windows utility that limits a game's frame rate on an absolute, drift-free timeline so frames arrive evenly spaced. It detects the running game, remembers a profile per game, and shows live frametime statistics. Protected games are skipped by default.
This repository includes the validated pacing engine and an experimental DX11/DX12 hook. Game detection is read-only. Limiting starts only after the user chooses FrameFlow and enables Frame Pacing for that game run. The hook has not been validated against a broad game compatibility matrix. The original DXGI vtable hook crashed Euro Truck Simulator 2 version 1.61.1.9 when Steam Overlay called it recursively. An alternate Present detour survived manual testing with Steam Overlay enabled. In normal Steam launches, selecting FrameFlow and enabling Frame Pacing attached automatically. A 15-second pre-driving sample at 70 FPS averaged 70.000 FPS with 14.292 ms P99 frametime and no intervals over 1.5 times the target. During 30 seconds of driving, 17 intervals exceeded that threshold; all were dominated by time before the hook's Present call, while maximum hook overhead was 0.020 ms. The first attached game exited unexpectedly after about 94 seconds without a new Windows crash report; a second attached run stayed open for over two minutes during driving. The cause of the first exit is unresolved, and compatibility is still experimental. Start this title normally through Steam; direct early launch exits before the game initializes.
| Area | Status |
|---|---|
| Deadline-based pacing engine (QPC, high-resolution waitable timer, yield + spin) | Done, benchmarked |
| FrameFlow.Engine: detection, per-game profiles, telemetry, named-pipe IPC | Done |
| FrameFlow.UI (WinUI 3): main window, settings, advanced page | Done |
| FrameFlow.TestRenderer (DX11), paced by the engine exactly like a hooked game | Done |
| FrameFlow.Benchmark: accuracy and CPU cost report | Done |
| Anti-cheat detection (protected games skipped by default, explicit per-game risk choice) | Done |
DX11 / DX12 in-game integration (FrameFlow.Hook64.dll, normal launch or launcher) |
Experimental |
| FrameFlow Mini: FPS, frametime and graph over the game | Experimental |
| DX12 probe, paced through the hook and engine | Done |
| Tray, global hotkeys | Phase 5 |
Measured on the development machine (GTX 1080, 16 logical CPUs, Windows 10 22H2):
- Test renderer paced by the engine at 60 FPS for 39 s: average 16.6667 ms, std. dev. 0.0018 ms, P99 deviation 0.0083 ms, 1% low 59.97 FPS.
FrameFlow.Benchmark(Balanced): P99 deviation ≤ 0.006 ms from 30 to 240 FPS. A naiveSleep()limiter asked for 60 FPS delivered 32.4 FPS on the same machine.- CPU cost of the waiting phases: ~5% of one core at 60 FPS (≈0.3% of total CPU), rising to ~22% of one
core at 240 FPS. Full table:
docs/benchmark-report.md.
Run benchmark.bat to measure your own machine.
Requirements: Visual Studio 2022 with Desktop development with C++ (includes CMake and Ninja), Windows 10 SDK, and a .NET SDK 8 or newer (a per-user install works).
build.bat native engine + tools (CMake/Ninja/MSVC) and the WinUI app, output in bin\
test.bat unit tests (CTest)
benchmark.bat timing benchmark, writes docs\benchmark-report.md
package-portable.bat builds the standalone ZIP distribution in dist\
tools\package-installer.ps1 builds a compressed Windows Setup EXE from the standalone package
run.bat starts FrameFlow (the UI starts the engine)
run-testrenderer.bat starts the DX11 test renderer; choose a limiter and enable pacing in the UI
bin\FrameFlow.Launch.exe "C:\path\to\game.exe" [arguments] optional early-hook launch for x64 DX11/DX12 games
FrameFlow supports zero-install, fully isolated portable execution:
- Staging and Archive: Run
package-portable.bat nopauseto generatedist\FrameFlow-Portable\,dist\FrameFlow-v1.0.3-Portable.zip, and its.sha256checksum. - Portable Detection: Activated automatically if a
.portablemarker file ordata\directory is present alongside the executables, or when--portable/%FRAMEFLOW_PORTABLE%=1is passed. - Isolated Data: All profiles, UI and engine settings, and logs are stored inside the portable folder (
.\data\Profiles\,.\data\Logs\,.\data\ui-settings.json,.\data\engine-settings.json). Nothing is written to%LOCALAPPDATA%or the Windows Registry. - Launcher: Open
FrameFlow.exeor use the includedrun.batshortcut.
The downloadable ZIP contains Windows x64 binaries and their runtime files. Extract the entire archive before launching it; running the executable from inside the ZIP is unsupported. The release is experimental: DX11/DX12 hooks depend on each game's presentation path, and a detected game does not necessarily mean that its frame limit is active. Verify Frame Pacing and live statistics in the UI.
For a smaller download, the release also includes FrameFlow-v1.0.3-Lite.zip. It requires the
x64 .NET 8 Runtime already installed. Windows App SDK Runtime 1.7 is included in both ZIPs. The
standalone Portable ZIP also includes .NET 8 and is the safer choice for machines without it.
FrameFlow-v1.0.3-Setup.exe is a compressed, per-user Windows installer that includes both
runtimes. It installs shortcuts and an uninstaller without administrator rights. Settings and
profiles for this edition are stored under %LOCALAPPDATA%\FrameFlow and remain after uninstall.
The installer is not code-signed.
apps/FrameFlow.UI WinUI 3 app (C#, .NET 8, self-contained)
apps/FrameFlow.Engine background engine (C++20)
apps/FrameFlow.TestRenderer DX11 test scene using the same PresentPacer a hook will use
apps/FrameFlow.DX12Probe minimal DX12 swap-chain target for hook validation
apps/FrameFlow.SessionProbe read-only live present-cadence diagnostics for a hooked process
apps/FrameFlow.Benchmark pacing accuracy + CPU cost measurement
native/Core logging, JSON, paths, strings
native/Timing Clock, WaitableTimer, FramePacer (deadline timeline + hybrid wait)
native/Telemetry frame statistics (FPS, 1%/0.1% low, deviation) and graph buckets
native/ProcessDetection process scanner, game scoring, exclusions, anti-cheat detection
native/Profiles per-game JSON profiles
native/Display monitor + refresh detection, refresh-aware presets
native/IPC named pipe server, shared-memory session, protocol
native/Graphics IGraphicsBackend, PresentPacer (in-process pacing client)
apps/FrameFlow.Launch startup launcher and running-process attach helper
native/Hooks experimental DXGI import and swap-chain hooks
shared/Protocols IPC protocol specification
tests/ unit tests (no external framework)
docs/ architecture, roadmap, benchmark reports
For a normal source build, %LOCALAPPDATA%\FrameFlow\ stores profiles, settings and logs. The
portable release stores these under its own data\ directory. Nothing is uploaded; there is no
account and no analytics.
GitHub Actions builds and tests every pull request and push. Pushing a v1.0.3 tag creates a
GitHub Release with the Setup EXE, Portable and Lite ZIPs, and SHA-256 checksums. The tag version must match the version
in CMakeLists.txt and apps/FrameFlow.UI/FrameFlow.UI.csproj.
FrameFlow's original source and assets are open source under the MIT license. See third-party notices for bundled components, and contributing guidelines before submitting changes.
FrameFlow does not hide from, bypass, or tamper with anti-cheat software. When Easy Anti-Cheat, BattlEye, Riot Vanguard, FACEIT, Ricochet, EA AntiCheat, GameGuard or XIGNCODE3 is detected, integration is disabled by default. The Play at your own risk button permits an attempt for that game profile only after an explicit warning. The game's protection may reject it, and use may violate game rules or put an account at risk. Detection is heuristic and cannot identify every protection. If the engine stops, a paced process falls back to "pacing off" within two seconds.
Start a game normally; FrameFlow detects it without injecting into the process. Choose FrameFlow
under Limiter, then enable Frame Pacing to request the DX11/DX12 hook for this run.
For games that cannot tolerate late attachment, choose the game executable, close the game, and use
Launch with FrameFlow for an explicit early-hook attempt:
it briefly uses Windows debugging events to pause at the executable entry point, loads the hook,
and detaches before the game begins. By default, both paths skip protected games. The hook
intercepts standard D3D11CreateDevice, D3D11CreateDeviceAndSwapChain, and DXGI factory imports,
and can patch common DXGI Present vtables after a game has already created a swap chain. It paces
the first visible swap chain immediately before Present / Present1. The UI controls it
through the same shared-memory session as the test renderer. If the engine exits, pacing stops within
two seconds. The hook remains loaded until the game exits; it is not detached at runtime.
This path is not yet a universal game limiter. Attachment can arrive after a game has created a private or unsupported swap chain; some games reject external hooks or use unsupported presentation paths. In those cases the UI reports that FrameFlow cannot limit the game. The Mini OSD appears over windowed or borderless game windows; it shows a waiting message until a hook or an elevated ETW session supplies frame data. Check that the UI reports the FrameFlow pacing method before treating the FPS limit as precise.
For each detected game, choose Limiter in the bottom bar: Off detects games without limiting; FrameFlow requires a live DX11/DX12 hook session, established automatically where supported. The selected preference is saved in that game's profile, but pacing must be enabled for each run. Click the large FPS number to type any value from 10 to 1000; the slider markers are shortcuts only. The separate Pacing Mode menu controls how FrameFlow waits for each frame, not which limiter is selected. Efficiency uses the least busy waiting, Balanced is the default tradeoff, and Precision spends more CPU time waiting close to the target deadline. These modes cannot remove game-side stalls. The main FPS metric is the reciprocal of median frametime, so isolated spikes do not distort the typical value; 1% low and the frametime graph still expose those spikes. Mini OSD in the bottom bar opens FrameFlow's own small, click-through window over the selected game. It shows FPS, 1% low, frametime, and a short frametime graph. No RTSS installation is needed. The mini follows the game window and hides when the game is minimized or no longer in the foreground. It works with windowed and borderless presentation; exclusive fullscreen can cover desktop windows, so the mini is not guaranteed there. FrameFlow's full graph remains in the main window.
To save the live graph while staying in the game, click Timed screenshot beside Frametime Stability,
choose a delay from 1 to 600 seconds, and click Start countdown. Return to the game; the
running FrameFlow window saves its own UI to Pictures\FrameFlow after the delay, even if minimized.
The same menu opens the screenshots folder. Captures require the running UI to be connected to
the game for live graph data; they do not capture the game image.
The end-to-end smoke test uses the DX11 test renderer without its built-in pacer:
bin\FrameFlow.Launch.exe bin\FrameFlow.TestRenderer.exe --external-test-fps 60 --seconds 6 --report smoke.txt
This smoke path measures the hook independently of the UI and engine. For the full engine path,
start FrameFlow first and replace --external-test-fps 60 with --external.
The DX12 smoke test uses a separate minimal command-queue swap chain:
bin\FrameFlow.Launch.exe bin\FrameFlow.DX12Probe.exe --fps 60 --seconds 6 --report dx12-smoke.txt
The --fps option above drives the hook session for testing. Omit it when testing control through
the FrameFlow engine. DX12 is identified by the ID3D12CommandQueue passed to DXGI when the swap
chain is created; the engine then reports the session as DX12.
For a live hooked game, bin\FrameFlow.SessionProbe.exe <pid> 30 samples 30 seconds of the
shared present ring and prints frametime percentiles and intervals above the target. It measures
CPU-side calls to Present, not when each frame becomes visible on the monitor. Session version 5
also separates time outside the hook, hook overhead, the previous DXGI Present call, and
FrameFlow's wait.

