Pix
FreeNot checkedWraps Microsoft PIX's pixtool.exe to enable LLMs to launch and attach to D3D12 processes, capture GPU traces, and perform fine-grained queries (e.g., root param
About
Wraps Microsoft PIX's pixtool.exe to enable LLMs to launch and attach to D3D12 processes, capture GPU traces, and perform fine-grained queries (e.g., root parameter bindings at specific events) without manual CSV dumps.
README
An MCP (Model Context Protocol) server for Microsoft PIX on Windows. It
wraps pixtool.exe so an LLM agent can launch and attach to D3D12 processes,
take GPU captures, and run fine-grained queries over the resulting
.wpix files — including answering questions like "what SRV is bound at
root parameter 0 of event 2372?" without you having to dump the entire
event list to CSV and grep through C++ export by hand.
Why a wrapper?
pixtool.exeis a real scriptable CLI, but its queries are coarse: it dumps the whole event list as CSV, the whole resource as bytes, the whole frame as a C++ project.pix-mcpindexes those coarse outputs into a SQLite DB beside the capture and parses the C++ export, so the LLM can ask precise questions instantly without re-running pixtool.
What it can do
Wraps every command exposed by pixtool.exe:
| pixtool command | MCP tool |
|---|---|
launch (one-shot, capture-and-exit) |
pix_capture_launched |
launch (long-running, F11 in-game) |
pix_launch_background |
attach <pid> + take-capture + save-capture |
pix_capture_attached |
programmatic-capture |
pix_capture_programmatic |
take-capture / save-capture |
(composed inside the workflows above) |
open-capture |
pix_open_capture (and implicitly chained by every capture-targeting tool) |
save-event-list |
pix_save_event_list |
save-resource |
pix_save_resource |
save-screenshot |
pix_save_screenshot |
list-counters |
pix_list_counters |
save-high-frequency-counters |
pix_save_high_frequency_counters |
export-to-cpp |
pix_export_to_cpp |
recapture-region |
pix_recapture_region |
upgrade-gpu-capture |
pix_upgrade_gpu_capture |
| any other / chained command | pix_raw |
And then the value-add — tools that don't have a pixtool equivalent because they operate on an indexed view of the capture:
| Tool | What it answers |
|---|---|
pix_index_events |
Build a SQLite index from save-event-list CSV. |
pix_find_events |
"All draws inside marker ShadowPass", "all dispatches between events 2000 and 2400", "every ResourceBarrier on queue 1". |
pix_get_event |
Full per-event record including counters, by Global ID. |
pix_list_markers |
All PIX markers with their [start, end] Global IDs. |
pix_marker_range |
Resolve a marker name to its Global ID range. |
pix_list_queues |
Queues seen, with event counts. |
pix_event_type_counts |
Histogram (draws vs dispatches vs barriers vs copies …). |
pix_top_by_counter |
Top N events sorted by a numeric counter (e.g. gpu_duration). |
pix_query_sql |
Read-only SELECT/WITH against the index DB. |
pix_parse_cpp_export |
Parse an existing export-to-cpp directory. |
pix_state_at_event |
Replay the C++ export and return every bound state at a target event. |
pix_get_resource_at_root_param |
"what's at graphics root param N of event G?" — includes structured resource_id + offset when the binding is GetGpuva(rid, off). |
pix_find_cpp_calls |
Search the parsed C++ export for specific D3D12 calls and their exact arguments. |
pix_get_root_signature_layout |
Parse the inline D3D12_ROOT_PARAMETER1 array and return per-slot {kind, visibility, register, register_space, descriptor_ranges, flags} for a given root sig ApiObjectId. |
pix_get_resource_bytes |
Read raw bytes from a resource at (offset, length) by decompressing resources.bin (XPRESS via Windows Cabinet API). Solves the "save-resource only does PNG/DDS" gap. |
pix_list_tracked_resources |
List every resource ID pix_get_resource_bytes can read, with chunk sizes and source-function names. |
pix_dump_cbuffer_at_root_param |
End-to-end "show me these cbuffer bytes": composes state replay + GpuVa parsing + raw-bytes extraction. Returns hex + optional float[] decoding. |
pix_capture_summary |
One-shot high-level overview of a capture. |
Install
git clone <this repo> pix-mcp
cd pix-mcp
python -m venv .venv
.venv\Scripts\activate
pip install -e ".[dev]"
Requires Windows and Microsoft PIX installed (default location
C:\Program Files\Microsoft PIX\<version>\pixtool.exe). pix-mcp auto-discovers
the newest installed version; override with PIX_MCP_PIXTOOL=<full path>.
Quick smoke test:
pix-mcp --help
.venv\Scripts\python.exe -c "from pix_mcp.config import find_pixtool; print(find_pixtool())"
Wiring it into Claude Code / Claude Desktop
Add to your MCP config (claude_desktop_config.json or the CLI equivalent):
{
"mcpServers": {
"pix": {
"command": "E:/github/pix-mcp/.venv/Scripts/pix-mcp.exe",
"args": [],
"env": {
"PIX_MCP_PIXTOOL": "C:/Program Files/Microsoft PIX/2603.25/pixtool.exe"
}
}
}
}
(Set PIX_MCP_PIXTOOL only if auto-discovery isn't finding the right install.)
Typical workflows
1. Capture a launched workload, then analyze it
pix_capture_launched(
exe="C:/games/MyGame.exe",
args="-windowed",
output_wpix="C:/captures/mygame.wpix",
frames=1,
)
pix_open_capture("C:/captures/mygame.wpix")
pix_index_events(capture="<handle>", counter_groups=["D3D:*"])
pix_capture_summary(capture="<handle>")
2. Interactive game session: launch, press F11 in-game, find the capture
pix_launch_background(exe="C:/games/MyGame.exe", capture_key="F11")
# ...play to the spot, press F11...
pix_find_recent_captures()
pix_open_capture("<path returned above>")
pix_index_events(capture="<handle>")
3. Fine-grained query: "what SRV is bound at root param 0 of event 2372?"
pix_open_capture("C:/captures/mygame.wpix")
pix_export_to_cpp(capture="<handle>", output_dir="C:/captures/mygame_cpp",
use_winpixeventruntime=True, use_agility_sdk=True)
pix_get_resource_at_root_param(capture="<handle>", global_id=2372, root_param_index=0)
# →
# {
# "global_id": 2372,
# "root_param_index": 0,
# "pipeline": "graphics",
# "binding": { "kind": "descriptor_table", "handle": "...", "raw": "..." },
# "graphics_pso": "...",
# "graphics_root_signature": "...",
# "descriptor_heaps": ["..."]
# }
For the full bound state at an event (PSO, root sig, all root params, IB/VB,
RTVs, viewports, scissors), use pix_state_at_event.
4. Find every draw inside a PIX marker
pix_marker_range(capture="<h>", marker_name="ShadowPass")
# {"found": true, "start": 2100, "end": 2480}
pix_find_events(capture="<h>", inside_marker="ShadowPass", event_type="draw", limit=50)
5. Top GPU-time draws
pix_index_events(capture="<h>", counters=["GPU Duration"])
pix_top_by_counter(capture="<h>", counter="gpu_duration", n=20, event_type="draw")
6. Recapture just the suspicious region for faster iteration
pix_recapture_region(capture="<h>", output_wpix="C:/captures/mygame_slice.wpix", start=2300, end=2600)
7. Dump raw cbuffer bytes from a draw call (no PIX UI)
pix_export_to_cpp(capture="<h>", output_dir="C:/captures/mygame_cpp",
use_winpixeventruntime=True, use_agility_sdk=True)
# One call replaces ~10 PIX-UI clicks:
pix_dump_cbuffer_at_root_param(
capture="<h>", global_id=2372, root_param_index=3,
length=256, preview_floats=64,
output_file="C:/scratch/basepass_cbuffer.bin",
)
# → {
# "resource_id": 2362,
# "offset": 2259712,
# "hex_preview": "00 00 80 3b ...",
# "preview_floats": [0.00390625, 0.0, ...],
# "output_file": "C:/scratch/basepass_cbuffer.bin"
# }
For lower-level access:
pix_get_root_signature_layout(capture="<h>", root_sig_obj_id=2361)
# → per-slot {kind, visibility, register, ranges, flags}
pix_get_resource_bytes(capture="<h>", resource_id=2362,
offset=2259712, length=256)
pix_list_tracked_resources(capture="<h>")
8. Escape hatch — chain arbitrary pixtool commands
pix_raw(args=[
"open-capture", "C:/captures/mygame.wpix",
"save-event-list", "C:/scratch/events.csv", "--counters=*",
"list-counters",
])
How the index works
The first time you call pix_index_events, pix-mcp runs:
pixtool open-capture <wpix> save-event-list <cache>/<stem>.event_list.csv [--counters=...]
Then it streams the CSV into <wpix>.pixmcp.db (next to the .wpix, or in
%LOCALAPPDATA%\pix-mcp\cache\ if that directory isn't writable). Schema:
events(global_id, queue_id, queue, name, event_type, parent_marker_id, depth,
counters JSON, raw JSON)
markers(id, name, start_global_id, end_global_id, parent_id, depth, queue_id, queue)
queues(queue_id, name, event_count)
counter_columns(name)
event_type is a classified label (draw, dispatch, copy, barrier,
begin_marker, end_marker, marker, clear, resolve, present,
execute_command_lists, execute_indirect, build_as, other). Markers are
reconstructed from PIX's BeginEvent/EndEvent rows with per-queue depth
tracking — so pix_find_events(inside_marker="ShadowPass") works.
counters is a JSON object — query it via SQL with
json_extract(counters, '$.gpu_duration'). raw is the entire original CSV
row for escape-hatch queries.
The index is keyed by the wpix's mtime so it auto-invalidates when you re-capture.
How the state-at-event query works
pix_export_to_cpp(parse_after=True) runs:
pixtool open-capture <wpix> export-to-cpp <dir> [--force --use-winpixeventruntime ...]
then immediately parses every .cpp under <dir>, picking out D3D12 calls
with regex. For each call we record (call_index, global_id, method, args, source_file, source_line). global_id is recovered from PIX's
// Event <id> comments where present.
pix_state_at_event walks that list up to your target event and replays the
state: graphics + compute root signatures, PSO, root parameters (descriptor
tables, root CBV/SRV/UAV, root 32-bit constants), descriptor heaps, IB/VB
views, RTVs/DSV, viewports/scissors, primitive topology.
For the specific "resource at root param N of event G" question,
pix_get_resource_at_root_param returns the exact args string PIX
emitted for the last Set*Root* call targeting that slot — so you get the
descriptor table base / GPU VA / 32-bit constant payload PIX serialized.
Caveats
- Windows only. The bridge to
pixtool.exeis Win32-native. - PIX must be installed. Set
PIX_MCP_PIXTOOLif auto-discovery fails. - The C++-export parser is regex-based and tolerates PIX's formatting variants
— it doesn't compile the export. If a future PIX version reshapes the
export significantly, parsing of unrecognized methods will fall back to
raw call records (still queryable via
pix_find_cpp_calls). - "Global ID" in the event list isn't guaranteed to match the call index in
the C++ export 1-to-1. We rely on PIX's
// Event <id>comments inside the export to associate them. If your PIX version doesn't emit those comments, passcall_index=instead ofglobal_id=topix_state_at_eventand usepix_find_cpp_callsto discover the right call. pix_capture_attachedrequires WinPix services running; some games block attach until you've launched them through the PIX UI once.
Layout
src/pix_mcp/
config.py # PIX install discovery, cache paths
pixtool.py # pixtool.exe subprocess wrapper (sync + async + background)
csv_parser.py # streaming parser for save-event-list CSV
index.py # SQLite indexer + query helpers
cpp_export.py # parser + state replay for export-to-cpp;
# also root-signature-layout extraction
resources_bin.py # static call-graph walk over CreateAndInitResource_*
# functions + XPRESS decompression of resources.bin
# (uses Windows Cabinet API via ctypes)
session.py # in-memory capture / launch session registry
server.py # FastMCP server with all tools
__main__.py # console entry point
License
MIT.
Installing Pix
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/elliotttate/pix-mcpFAQ
Is Pix MCP free?
Yes, Pix MCP is free — one-click install via Unyly at no cost.
Does Pix need an API key?
No, Pix runs without API keys or environment variables.
Is Pix hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Pix in Claude Desktop, Claude Code or Cursor?
Open Pix on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectCompare Pix with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
