locum
A frame capture and replay tool for Direct3D 11 and Direct3D 12 games on Windows. It runs as stand-in system DLLs in the game's folder and writes each captured frame to a SQLite database, which a command-line tool and a local web viewer read.
Features
- Records every draw, dispatch, clear, copy, update and map of a frame in GPU order, with each draw's shaders, state and bindings
- Records the constant-buffer bytes each draw read, with constants named from the shaders' DXBC or DXIL reflection
- Stores each used resource's contents as the frame began and the render and depth targets at the end of each pass
- Captures up to 64 consecutive frames from one request
- Keeps resource contents in a blob store shared by all captures of a game, keyed by XXH3-128 and LZ4-compressed
- Lists, filters, exports (DDS, PNG, raw) and queries captures with
locum-cli, including plain SQL - Traces a target's output lane back to the texture channels, constants and vertex attributes it came from
- Replays a draw or a whole frame offline on the GPU or on WARP and verifies every target against the game's
- Changes draws with rules (skip, solid colour, replacement shader, constant), live in Direct3D 11 games or in replay
- Names textures, buffers and shader programs after the game's own files through indexes of DDS files, shaders and key lists
- Diffs two captures draw by draw
- Writes a process memory dump at capture time and searches it for the values a draw saw (Direct3D 11)
- Reads, sets and calls game values from a
table.txtto set up a scene before a capture - Browses captures in a local web viewer with timeline, draw, resource, shader and SQL pages
- Compares the output of shaders whose results depend on GPU thread order by records rather than bytes
Getting Started
Requirements
- Windows, and a 64-bit game that uses Direct3D 11 or Direct3D 12
- Rust 1.99.0 (pinned in
rust-toolchain.toml) with the MSVC build tools; the DLL shims are linked withlib.exe - Bun for the viewer
dxcompiler.dllfor DXIL tools and DXIL replay rules: found on the DLL search path, in the Windows SDK, or atLOCUM_DXCOMPILER
Do not use locum on games with anti-cheat. Live override rules, memory dumps and the skip/max_mb capture options
are implemented for Direct3D 11 only.
Installation
Each release has locum-cli for Windows (x86_64), with SHA256SUMS;
cargo install locum-cli builds it too. The libraries are on crates.io (cargo add locum-capture). The DLLs that go
beside a game are built from the workspace:
cargo build --release
| Package | What it is |
|---|---|
locum |
locum.dll: wraps the Direct3D and DXGI objects and records captures |
locum-shim-d3d11, locum-shim-dxgi, locum-shim-d3d12 |
d3d11.dll, dxgi.dll, d3d12.dll: forward every export to locum.dll |
locum-shim-nvapi64 |
nvapi64.dll: an empty NVAPI for Direct3D 12 games |
locum-capture |
The capture format, blob store, formats, reflection and rule language |
locum-d3d11 |
Direct3D 11 readback shared by capture and replay |
locum-cli |
Reads, replays and verifies captures |
viewer |
Bun and Preact web viewer for captures |
locum-smoke, locum-smoke-d3d12, xtask |
Test programs and the cargo xtask build tasks |
Copy the DLLs from target/release/ next to the game's executable (for Unreal games, the one under Binaries/Win64):
- Direct3D 11:
locum.dll,d3d11.dll,dxgi.dll - Direct3D 12:
locum.dll,d3d12.dll,dxgi.dll,nvapi64.dll
Remove them again when you are done.
Usage
locum writes its log, shaders and captures to %LOCALAPPDATA%\locum (or LOCUM_DIR). With the game running, create
an empty file named capture there. locum removes the file and writes the next frame to captures\frame-<n>\.
New-Item "$env:LOCALAPPDATA\locum\capture"
locum-cli info last # what the capture holds and what it is missing
locum-cli draws last --ps 9f2c # draws that used a pixel shader
locum-cli draw last 120 # one draw: state, bindings, named constants, targets after it
locum-cli target last 120 rt0 -o rt0.png # a render target read back after its pass
locum-cli verify last # replay the frame and compare every target with the game's
To open the capture in the viewer, which prints its address (http://127.0.0.1:5170/...):
cd viewer
bun install
bun run viewer --db "$env:LOCALAPPDATA\locum\captures\frame-<n>"
Other commands:
locum-cli shader last 9f2c -d: a shader's constant buffers, bindings and disassembly (--wgslfor WGSL)locum-cli trace last 120 rt1.z: what one lane of a target is made oflocum-cli export last 812 -o tex.dds: a resource's contents as the frame beganlocum-cli replay last 120 --rule "skip ps=9f2c": a draw replayed with a rule, before and afterlocum-cli diff before after: two captures compared draw by drawlocum-cli --index files.db join last: what each resource was made fromlocum-cli sql last "SELECT ...": any query on the capture
locum-cli --help lists every command. settings.txt, overrides.txt, table.txt and the capture request options
are described in docs/README.md.
cargo xtask smoke runs the Direct3D 11 test programs through locum and verifies their captures on the GPU and WARP.
cargo xtask smoke --api d3d12 does the same for Direct3D 12, and bun test runs the viewer's tests.
docs/README.md is the full reference, and viewer/README.md covers the viewer.
locum is licensed under GPL-3.0-or-later.
Releasing
A release is a commit that changes a version. Once every CI job has passed on main, CI's release job
(.github/release.py) publishes each crate whose version crates.io does not have yet, in dependency order (trusted
publishing: no token is stored anywhere), tags the commit v<version> (<package>-v<version> for a package with a
version of its own) and creates the GitHub Release with the commits since the previous release as its notes and the binaries attached (<bin>-<version>-<target>.zip / .tar.gz with SHA256SUMS).
A commit that changes no version only runs CI.
- Bump the version when a change is worth releasing: a patch (0.x.Y) for fixes and data or manifest additions, a minor
(0.X.0) for new API or any breaking change before 1.0. Run
cargo checksoCargo.lockfollows. - Push to main. Never publish by hand; a new crate's first release is the one exception (the token holder makes it, then the crate gets its trusted-publishing config).