Skip to main content
Glama
pix-mcp

pix-mcp

pix-mcp

An MCP server for Microsoft PIX for Windows GPU/timing captures and GDK PIX Xbox GPU captures.

It drives pixtool.exe, PIX's own CLI, so anything PIX can do headlessly is reachable here. Every one of pixtool's 24 commands is covered by a tool, plus an escape hatch (pix_run_pixtool) for raw pipelines. On top of that, pix-mcp adds the parts pixtool leaves to you: an artifact cache (a 4.5 GB capture takes ~18 s to open, every time), paged responses, id-based navigation, and correct handling of two pixtool behaviours that are easy to get silently wrong (see pixtool quirks).

pixtool is not all of PIX, though. The queue list, the resource table, and the Pipeline State view exist only in the UI — there is no command for any of them. And pixtool can record a timing capture but has no command that opens one, so the CPU/GPU timeline was unreadable headlessly. So pix-mcp also ships a small C# sidecar that hosts PIX's own analysis engines in-process, which is where deep analysis and timing captures come from. It is optional: without it everything else still works.

Windows-only, by nature of PIX.

Install

Requires Python 3.10+ and PIX for Windows. The server finds pixtool.exe under C:\Program Files\Microsoft PIX\<version>\, preferring the newest install.

pip install -e .

Add it to an MCP client — for Claude Code:

claude mcp add pix -- pix-mcp

Or, without installing, point at the checkout:

{
  "mcpServers": {
    "pix": {
      "command": "python",
      "args": ["-m", "pix_mcp"],
      "env": { "PYTHONPATH": "C:\\path\\to\\pix-mcp\\src" }
    }
  }
}

Start any session with pix_get_server_status; it reports which PIX installs were found, whether pixtool answers, and the current limitations.

The analysis bridge (optional)

The deep-analysis tools and the timing-capture tools go through bridge/, a C# sidecar that loads PIX's own analysis assemblies. Building it needs the .NET 8+ SDK and a local PIX install:

dotnet build bridge/pixbridge.csproj -c Release

Or ask the server to do it: pix_build_bridge. pix_get_bridge_status says whether it is available and why not. Everything else works without it.

Xbox GPU captures (.xpix)

Xbox captures use GDK PIX, not the Windows pixtool.exe. The server recognizes the 3XIP container and its GPU platform tag, reports capture_kind: "xbox_gpu", and routes it to an independent xbox-bridge/ process. Install the Microsoft GDK and the .NET 8 Windows Desktop runtime plus a .NET 8 or newer SDK, then build:

dotnet build xbox-bridge/pixbridge-xbox.csproj -c Release
# For a nondefault GDK location:
dotnet build xbox-bridge/pixbridge-xbox.csproj -c Release '-p:GdkPixDir=C:\GDK\bin'

The same build is available as pix_build_bridge(platform="xbox"). Check it with pix_get_bridge_status(platform="xbox"); server status reports Windows and Xbox availability independently. SDK assemblies and capture files are not included in Git.

The workflow is:

  1. pix_open_capture(capture_path="C:\\captures\\GPU 1.xpix").

  2. Read pix_list_queues, pix_list_gpu_events, pix_list_resources and the stored screenshot with pix_save_screenshot. These do not start GPU replay.

  3. Call pix_start_analysis(capture_id=..., device_address="<devkit IP>") to authorize replay. The address must match the analysis device configured in GDK PIX. Replay can interrupt the title running on that devkit. wait_seconds=0 starts it without waiting; subsequent calls poll the same replay. Windows-only pix_open_capture flags, including remote, are rejected for Xbox.

  4. Once ready is true, collect exact counter names, rank timings, inspect pipeline bindings, and export RTV/depth PNGs with pix_save_resource(global_id=..., rtv=0, ...).

  5. pix_close_capture releases the native document. LRU eviction and file changes also release it. A hung bridge is terminated so later requests can recover.

Xbox timing uses the GDK's nanosecond counters, reported as time_unit. Use leaves_only=true for timing rankings: API draws and their native child events can carry the same duration. Queue-relative index and native global_id are different IDs; pipeline queries use the former and resource exports the latter.

Xbox capability

Support

Event paging/search/tree and CSV export

Supported, with cached repeat queries

Counter catalog and selected per-event counters

Supported; select at most 64 new counters per call

GPU replay and timing ranking

Explicit devkit selection required

Resource descriptors and names

Supported

Pipeline resource bindings and slots

Supported after replay

Batched draw attribution + selected counters

pix_list_draw_summaries; leaf draws only, paged event scan

Bound buffer / indirect argument bytes

pix_read_bound_buffer; bounded reads, indexed draw argument decoding

Shader ISA / IL and compiler statistics

pix_get_shader; stage and ISA variant selection, paged code

Post-shader vertices and instance projection bounds

pix_get_shader_output; native IDs, clip coordinates, paged bounds

Stored screenshot and bound RTV/depth export

PNG; resource export requires an explicit global_id

Floating-point texture PNG

PIX-rendered display visualization; values can clip to the display range

Root-signature layout and complete resource view history

Not implemented; returns unsupported_backend_operation

Windows pixtool-only operations, Xbox live capture/recapture

Not implemented by this backend

The GDK adapter uses the local SDK's Automation runtime plus internal document and request types, so compatibility must be rechecked after a GDK update. Compatibility testing covers a limited set of SDK builds and Xbox GPU capture variants; other versions and variants require separate validation.

For grass/LOD analysis after pix_start_analysis reports ready:

  1. Use pix_list_draw_summaries(resource_name_contains="Grass", counters=[...]) with exact names from the counter catalog to attribute leaf draws. Continue with cursor_position=next_cursor even when a filtered page contains no matches.

  2. Read a selected indexed indirect draw using pix_read_bound_buffer(event_index=..., stage="VS", binding="INDIRECT_ARGS", byte_count=20, data_format="draw_indexed"). This returns actual argument bytes, including signed base vertex. Buffer lengths alone do not establish submitted or unique vertex counts.

  3. Read pix_get_shader(event_index=..., stage="VS") for ISA and compilation statistics. Compiler instruction counts and occupancy estimates are static, not measured runtime cost.

  4. Use pix_get_shader_output(event_index=..., output_mode="instance_bounds") for per-instance NDC bounds, pixel width/height and viewport-clamped bounding-box fraction. Default output_mode="vertices" exposes exact output words, native IDs and attributes for inspecting shape. Xbox position export may be named Pos0 instead of SV_Position. Indexed output can repeat vertices; these rows are not unique vertices or VS invocations.

Projection bounds do not account for triangle clipping, depth, alpha or occlusion and do not identify an engine asset's LOD number. Check partial-result/warning fields and complete_projected_extent before judging size. Post-shader queries instrument the entire draw even when only one page is requested. The latest output is retained per capture so subsequent pages do not repeat instrumentation; visiting another event replaces it. These four detail tools currently support Xbox GPU captures only. Rebuild the Xbox bridge and restart the MCP server/client connection after installing the changes.

Environment

Variable

Purpose

PIX_MCP_PIXTOOL_PATH

Full path to a specific pixtool.exe, skipping discovery.

PIX_MCP_PIX_DIR

A PIX install directory to search instead of the default locations.

PIX_MCP_CACHE_DIR

Where extracted artifacts live. Defaults to %TEMP%\pix-mcp-cache.

PIX_MCP_TIMEOUT_SECONDS

Per-invocation pixtool timeout. Defaults to 1800.

PIX_MCP_MAX_SESSIONS

Open captures kept before the least-recently-used is evicted. Defaults to 8.

PIX_MCP_BRIDGE_PATH

Full path to pixbridge.exe, skipping discovery.

PIX_MCP_BRIDGE_TIMEOUT_SECONDS

Per-request bridge timeout. Defaults to 300.

PIX_MCP_BRIDGE_DISABLED

Set to 1 to leave the bridge alone entirely.

PIX_MCP_BRIDGE_AUTOBUILD

Set to 1 to build the bridge on first use if it is missing.

PIX_MCP_XBOX_PIX_DIR

GDK bin directory containing Microsoft.PIX.Automation.dll.

PIX_MCP_XBOX_BRIDGE_PATH

Full path to pixbridge-xbox.exe.

PIX_MCP_XBOX_BRIDGE_DISABLED

Set to 1 to disable the Xbox backend.

Related MCP server: RenderDoc MCP

Tools

Capture lifecycle

Tool

pix_open_capture

Register a .wpix or Xbox GPU .xpix and get a capture_id plus an overview.

pix_get_capture_overview

Frame, event, and category statistics.

pix_discover_queues

Find which named contexts the capture exposes.

pix_list_open_captures · pix_close_capture · pix_clear_capture_cache

Session bookkeeping.

Events

Tool

pix_list_events

Paged event list, filterable by category or name.

pix_search_events

Glob search over event names.

pix_get_event

One event by global id, with its ancestry.

pix_get_event_tree

Depth- and node-bounded hierarchy.

pix_export_event_list

Write the full CSV somewhere you choose.

Counters

Tool

pix_list_counters

The catalog, grouped and searchable (2,365 counters on an RTX 5080).

pix_get_counter_hotspots

Rank events by a counter; picks a duration counter if you don't name one.

pix_collect_counters

Attach chosen counter values to events.

pix_suggest_counter_pattern

Turn a counter name into the regex that selects it.

pix_save_high_frequency_counters

Time-series counter capture to CSV.

Deep analysis

Through the bridge, not pixtool. Each of these answers something pixtool has no command for, and none of them re-open the capture.

Tool

pix_list_queues

The real queue table: id, name, type, event count.

pix_list_gpu_events

Events from one queue, optionally with the decoded D3D12 call and its arguments.

pix_list_resources

PIX's resource table — every texture and buffer, with dimensions, format, and debug name.

pix_get_resource

One resource in full: descriptor, heap, initial state, view summary.

pix_get_resource_views

Every view of a resource and the events that bind it — where a texture is actually used, and as what.

pix_get_pipeline_state

PIX's Pipeline State view for one event: program type, root signature, bound views.

pix_list_draw_summaries

Xbox: batch leaf draw bindings, resource attribution and optional exact counters.

pix_read_bound_buffer

Xbox: read buffer words/bytes or decode indexed indirect arguments at an event.

pix_get_shader

Xbox: paged ISA/IL, variants, register metadata and compiler statistics.

pix_get_shader_output

Xbox: paged post-shader attributes or per-instance projected bounds.

pix_get_root_signature

The binding layout alone, when the bound-view detail is noise.

pix_start_analysis

Replay the capture on a GPU, which is where PIX's timing comes from. ~25 s for a 4.5 GB capture, once.

pix_get_event_timing

Per-event GPU timing off that timeline: start and duration for every event in a queue, ranked by cost on request.

pix_get_bridge_status · pix_build_bridge

Bridge health and one-command build.

pix_get_event_timing is the cheap way to find the expensive work in a frame. pix_get_counter_hotspots replays the capture once per counter; this replays it once for everything, then reads a timeline that already exists. The ranking is dominated by marker scopes — Frame, then Scene, then PostProcessing — which is the shape of the frame; leaves_only ranks the individual draws and dispatches inside instead.

Timing captures

.wpix names two unrelated file formats. A GPU capture is one frame, frozen and replayable — PIX's own GFXA container, and what every tool above reads. A timing capture is seconds of the whole machine's CPU and GPU timeline, and it is a SQLite database. Nothing that reads one can read the other, which is why PIX's GPU-capture API rejects a timing capture with SQLITE_NOTADB rather than a format error. pix_open_capture tells them apart by their header — the extension settles nothing — and reports capture_kind; a tool handed the wrong kind says so by name instead of failing somewhere deeper.

pixtool records these (pix_capture_timing) and cannot open them. The bridge can: PIX's timing views run on a managed assembly beside pixtool, and it turns out to run standalone — no UI, no GPU, no document.

Tool

pix_get_timing_summary

The window, the machine, and which tables actually have data. Start here.

pix_list_timing_processes

Every process alive during the capture, busiest first — a timing capture is machine-wide.

pix_list_timing_threads

One process's threads, with what was recorded for each.

pix_get_cpu_samples

Where one thread's CPU time went: when it ran, and on which core.

pix_get_context_switches

Every time a thread came on or off a core, and the wait reason it left for.

pix_get_core_activity

The inverse view: everything that ran on one core, whoever owned it.

pix_list_timing_gpu_queues

The GPU queues the capture saw.

pix_get_thread_timeline · pix_get_gpu_timeline

Named PIX scopes on a thread or a queue, with nesting.

pix_get_gpu_frames

The present timeline, one row per frame, plus frame pacing over the page.

pix_get_residency_operations

What the GPU made resident, evicted, or paged — and which object it was.

pix_list_timing_counters

Counters reported live during the capture, per process.

pix_list_storage_devices · pix_get_file_io

Disk activity: which device, which file, how long it blocked.

pix_get_page_faults

One thread's page faults, hard ones separated from the cheap kinds.

pix_list_metrics · pix_get_metric_values

Any metric as a series over time: a counter's values, one core's utilisation, one process's video memory.

pix_get_api_objects

Named D3D12 work on one thread — a pipeline compile, a resource creation — as spans.

pix_get_command_lists

Which command lists a queue executed, and which of them went in on one ExecuteCommandLists.

pix_get_allocation_moves

Allocations the driver migrated between heaps, or demoted out of video memory for good.

pix_list_custom_data_types · pix_get_custom_data

Lanes the title recorded itself, plus PIX's own per-monitor VSync train.

pix_list_controllers · pix_get_controller_input

Controller reads — where an end-to-end input-latency measurement starts.

pix_get_context_switches is the one that separates a thread that is slow because it is computing from one that is slow because it is blocked — a distinction a sample count cannot make, since a blocked thread simply produces fewer samples and looks idle. File I/O, page faults and residency are the same argument in three other places: a thread waiting on a synchronous read is off-core and reads as idle, and an allocation evicted and paged back over PCIe costs milliseconds on a frame whose draw calls are unchanged. None of it is visible in a GPU capture at all.

pix_get_core_activity answers the question the per-thread tools cannot: not "where did my thread's time go" but "was this core mine at all". It reports idle_share_of_page separately from busiest_process_share, because process 0 is Windows' Idle process — counting it as the busiest would turn "this core was a third idle" into "something else owned a third of this core", which is the opposite finding.

pix_get_metric_values is the other half of that answer, and the only tool here that returns a series rather than a list of events. The same core can be asked about twice — once for one process, once for all_processes — and the gap between the two answers is exactly how much of a busy core belonged to somebody else. The other lanes are the counters over time (the catalog pix_list_timing_counters returns is only their definitions), and per-process video memory.

Latency is reachable here and nowhere else. A GPU capture is one frame with no before and no after, so it cannot contain the span a player actually feels. A timing capture can: pix_get_controller_input timestamps the read, pix_get_gpu_frames timestamps the present, and the monitor lane in pix_get_custom_data timestamps the VSync that put the result on the display. PIX's own Dynamic Latency Instrumentation, when a device has it, anchors the first of those to the hardware rather than inferring it — latency_instrumented on pix_list_controllers says which.

Times are nanoseconds on the capture's own clock, and every window argument is in those units. pix_get_timing_summary reports both duration_seconds (first event to last) and recorded_seconds (the requested recording window); they differ because trailing ETW events land after the stop.

What was verified how. Checked against real rows on real captures — a 144 MB attach to a running Unreal editor and a 1 GB 180-second launch capture, the latter reading 17.6 M context switches: the summary, process, thread, CPU-sample, context-switch and GPU-queue queries; pix_list_timing_counters (12 counters, Local Budget / Local Usage / Local Resident in MB, grouped per adapter); pix_get_core_activity (3,080 samples on core 0, cross-process, and a core 63.8% owned by a process that was not the captured title); and pix_get_residency_operations (4 MakeResident rows naming an nv.sl.chi.heap.gpu descriptor heap).

Also on real rows:

  • pix_get_metric_values on the counter lane, checked against the bytes rather than against PIX: the Local Budget counter for the discrete adapter returns 15947.792384 MB, and decoding the PixCounterRange blob out of the capture's own table gives the same double. That is the RTX 5080's 16 GB. Ask for it by metric_id from pix_list_timing_counters and by that capture's ids — the same machine numbers its adapter groups differently between two recordings, so the counter that is 7 in one is 1 in the other.

  • The same tool on the cpu_usage lane, over all 85 samples of the 8.9-second capture at its 100 ms cadence: core 0 averaged 30.9% across every process and core 12 averaged 75.2% — the same imbalance pix_get_core_activity found from the other direction. Asking again with process_id instead of all_processes is what makes it a finding: the captured editor accounts for 8.1% of core 0 but only 1.2% of core 12, so that core was busy with somebody else almost entirely.

  • pix_get_custom_data on the monitor lane: 10,879 VSync markers over 180 seconds, reported as 16.666 ms median / 60.0 Hz — and a longest interval of 33.346 ms, which is exactly two refresh periods, so the display missed one. On the shorter capture the longest gap is 49.995 ms, three periods. pix_list_custom_data_types names the lane Monitor #1 (NVIDIA GeForce RTX 5080).

  • pix_get_api_objects: one span, Pipeline State Compilation, 177.7 µs — on one thread out of the 554 that process had.

  • pix_get_metric_values on the api_object_memory lane: the captured editor's D3D12 objects grew 131,072 → 196,608 bytes, which is the sum PIX records against those objects. Every other process on the machine returns an empty series with its reason, which is the correct answer for a process that never created a D3D12 object.

The API object result is also the caveat. pix_get_timing_summary reports api_objects: true from the object catalog, but the events are per thread and there may be a single one in the whole capture, so querying an arbitrary thread returns nothing and looks broken. api_object_events on each pix_list_timing_threads row is how to find a thread that has any, and the empty answer says so.

The rest run clean and return S_OK with zero rows on every capture obtainable here, for reasons that are facts about the recording rather than about the code:

  • pix_get_thread_timeline, pix_get_gpu_timeline, pix_get_gpu_frames and the PIX-marker path need a process that calls PIXBeginEvent and presents a D3D12 swapchain. No PIX-instrumented title was available to capture.

  • pix_list_storage_devices, pix_get_file_io, pix_get_page_faults and the two device lanes of pix_get_metric_values need ETW providers that pixtool take-new-timing-capture cannot enable — its only options are --duration, --sampleRate, --noCpuSamples, --noCallstacks and --noGpuTimings. Confirmed by scanning 129 threads across six processes for a single page fault, and by both captures reporting zero storage devices. A capture recorded from the PIX UI with file I/O turned on will populate them.

  • pix_get_command_lists, pix_get_allocation_moves, pix_list_controllers and pix_get_controller_input have empty source tables in both captures: ApiQueueExecutionCommandList, AllocationMigrations, DemotedAllocations, InputDevice and InputMarkerRange all hold zero rows. No controller was attached, video memory was never short, and neither capture carries GPU timings.

Those last row counts are counted, not inferred, and that is worth stating because it is the one place this file format helps: a timing capture is a SQLite database, so tools/probe_capture_tables.py opens it with Python's own sqlite3 and counts the table directly. "The query found nothing" and "there is nothing to find" are different claims, and this is the only part of the surface where the second one can be checked without going through PIX at all. Field mapping for the still-unpopulated tables above comes from PIX's own query metadata rather than from observed rows. Each returns a note explaining which kind of empty it is, so an empty answer is never ambiguous between "no data" and "broken".

Two things about recording, both learned the hard way: a process pixtool launch starts dies when pixtool exits, so launch and capture must be one command line; and attaching to an already-running process gives you the full CPU tables but no GPU frames and no PIX events, because D3D12 instrumentation has to be in the process before it creates its device.

pix_run_debug_layer · pix_collect_occupancy · pix_perform_playback · pix_recapture_playback · pix_recapture_region

Resources and export

pix_save_screenshot · pix_save_resource · pix_export_to_cpp · pix_upgrade_capture

Taking new captures

pix_capture_app · pix_capture_uwp_app · pix_capture_attached_process · pix_capture_programmatic · pix_capture_timing

These launch or attach to a process, so they are annotated openWorldHint. pixtool has no daemon: launching a target and capturing from it must happen inside one pixtool process, which is why these are single tools rather than a launch/capture pair.

Escape hatch

pix_run_pixtool builds an arbitrary command pipeline. Use it for anything the typed tools don't reach.

pixtool quirks worth knowing

These cost real time to discover, and both fail quietly — worth knowing whether or not you use this server.

--counters takes a regular expression, not a glob. pixtool expands * to .* and then runs a case-sensitive regex_search. So a name copied straight out of list-counters usually matches nothing: TOP to EOP Duration (ns) reads as a group around a literal ns, and . matches any character. You get an empty column rather than an error. pix-mcp escapes and anchors names passed as counters, so they select exactly themselves; counter_regexes takes raw patterns when you actually want a family. An unbalanced paren is worse — it surfaces as an opaque PIXTOOL99999 ... 0x8000ffff that aborts the rest of the pipeline, after the capture open has been paid for, so patterns are validated up front.

Quotes must open after the =. pixtool parses the raw command line itself rather than going through CommandLineToArgvW, and only honours a quote that begins after the =. --queue-name="3D Queue" parses; "--queue-name=3D Queue" — which is what Python's subprocess list quoting produces — is rejected as an invalid command line. pix-mcp quotes the value, not the token. A value containing a double quote cannot be expressed at all, so those are rejected with an explanation rather than silently mangled.

Two smaller ones: a failing command aborts the remaining commands in the pipeline, so batched extraction puts the most important command first; and pixtool exits non-zero even when --help succeeds.

Design notes

Every query re-opens the capture. pixtool has no interactive mode, so a "session" here is bookkeeping plus a cache directory, not a live process. Opening the 4.5 GB reference capture costs ~18 s — and roughly 90 s each if four opens run concurrently, so the server does not parallelise them. Artifacts are cached under a fingerprint of path, size, and mtime; repeat queries return in milliseconds, and editing the capture invalidates the cache.

Except through the bridge, which keeps it open. The sidecar holds the document, so the same 4.5 GB capture opens once in ~0.3 s and every query afterwards is effectively free. That is why the deep-analysis tools are not cached: there is nothing to amortise. It stays a separate process rather than a Python binding because the API is COM-with-C#-extensions, and because a crash in PIX's engine should not take the server with it.

The bridge hosts two engines, not one. A GPU capture goes through the COM document API; a timing capture goes through Microsoft.PIX.Core's managed query engine over the SQLite file. They share only the sidecar process and the handle table, so pix_open_capture has to decide which one a file belongs to before it can open it at all — hence the header sniff rather than a trial open, which on a 4.5 GB file would cost 18 seconds to learn nothing.

Responses are small by default. Lists are paged, text is chunked, and events are addressed by global_id, so an agent can navigate a large capture without flooding its context.

Errors are data. A failed tool returns {"ok": false, "error": {"code", "message", "details"}} with a message that says what to do next, rather than a transport-level failure.

Counters are expensive. Each matched counter costs another replay pass, which is why the default is exact selection rather than a wildcard.

Development

pip install -e . --group dev   # or: uv sync
pytest
ruff check .
mypy

tests/test_server.py holds the parity check: a table mapping all 24 pixtool commands to the tool that covers each. Add a command to pixtool and nothing covers it, and that test fails.

The tests are hermetic — no PIX install needed. The projection math checks also run without PIX/GDK using dotnet run --project tests/geometry/GeometryTests.csproj -c Release. tools/smoke.py is the end-to-end run against a real capture:

tools/smoke_full.py launches an independent server and exercises the real MCP stdio transport. It checks nonempty results, event/CSV consistency, numeric counter values, paging, cache reuse, PNG headers/dimensions, expected errors and native handle release. It writes each result and report.json; an unexpected failure exits nonzero. Choose a fresh output directory for a cold-cache run. Image contents still need visual inspection; a valid PNG alone cannot prove a texture is meaningful.

python tools/smoke_full.py 'C:\captures\GPU 1.xpix' --device-address '<devkit IP>' --output-dir '_probe/full-xbox'
python tools/smoke_full.py 'C:\captures\GPU 1.wpix' --output-dir '_probe/full-windows'
python tools/smoke_full.py 'C:\captures\timing.wpix' --output-dir '_probe/full-timing'

The earlier scripts are useful for narrower probes:

python tools/smoke.py "C:\path\to\capture.wpix"
python tools/smoke_mcp.py "C:\path\to\capture.wpix"        # through FastMCP
python tools/smoke_analysis.py "C:\path\to\capture.wpix"   # the bridge-backed tools
python tools/probe_timing_bridge.py "C:\path\to\timing.wpix"   # the timing engine
python tools/probe_timing_objects.py "C:\path\to\timing.wpix"  # objects, metrics, input
python tools/probe_objects_via_mcp.py "C:\path\to\timing.wpix" # the same, as tools
python tools/probe_capture_tables.py "C:\path\to\timing.wpix"  # row counts, via sqlite3

tools/probe_*.py are the scripts used to establish the pixtool behaviours above, kept because they are how you'd re-verify them against a future PIX release.

License

MIT

Available Tools

67 tools
pix_build_bridgeA
Idempotent

Compile the pixbridge sidecar. Needs the .NET SDK and a local PIX install.

Inputs: platform, timeout_seconds. Returns: object

pix_capture_appB

Launch a D3D12 executable, take GPU captures, and save them.

Inputs: executable (required), winml, frames, remote, +10 more. Returns: object

pix_capture_attached_processC

Attach to a running process and capture it.

Inputs: process_id (required), winml, frames, remote, +4 more. Returns: object

pix_capture_programmaticC

Wait for the app to trigger a programmatic capture, then save the results.

The app must call the PIX capture API (for example PIXBeginCapture) itself.

Inputs: output_directory (required), remote, setenv, executable, +5 more. Returns: object

pix_capture_timingC

Record a PIX timing capture (CPU/GPU timeline) of a launched or attached app.

Inputs: output_file (required), remote, setenv, executable, +9 more. Returns: object

pix_capture_uwp_appC

Launch a UWP app and capture it.

Get the package and application ids from the PIX UI: PC Connection / Select Target Process / Launch UWP, right-click the app, 'Copy pixtool Launch Command'.

Inputs: application_id (required), package_full_name (required), winml, frames, +7 more. Returns: object

pix_clear_capture_cacheA
Idempotent

Delete cached artifacts for a capture so the next query re-extracts them.

Inputs: capture_id (required). Returns: object

pix_close_captureB
Idempotent

Close a capture session, optionally deleting its cached artifacts.

Inputs: capture_id (required), clear_cache. Returns: object

pix_collect_countersB
Read-onlyIdempotent

Replay the capture collecting counters, returning per-event counter values.

counters takes exact names from pix_list_counters and escapes them. Use counter_regexes to match families, remembering that every counter matched is another replay pass over the capture.

Inputs: capture_id (required), limit, cursor, refresh, +5 more. Returns: object

pix_collect_occupancyB
Read-onlyIdempotent

Replay the capture collecting GPU occupancy data.

Inputs: capture_id (required), timeout_seconds. Returns: object

pix_discover_queuesA
Read-onlyIdempotent

Find which named contexts (command queues) a capture exposes.

With the analysis bridge available this is the real queue table, read from PIX's engine. Without it, pixtool has no command that lists queues, so each candidate name has to be tried and a miss still costs a full capture open; results are cached per capture, so asking twice is free.

Inputs: capture_id (required), refresh, candidate_names, timeout_seconds. Returns: object

pix_export_event_listA
Idempotent

Write a full event-list CSV to a caller-chosen path (pixtool save-event-list).

Inputs: capture_id (required), destination (required), counters, queue_name, +3 more. Returns: object

pix_export_to_cppA
Idempotent

Export the captured frame as a standalone C++ D3D12 project.

use_winpixeventruntime and use_agility_sdk acknowledge the WinPixEventRuntime and DirectX 12 Agility SDK licenses respectively.

Inputs: capture_id (required), output_directory (required), force, timeout_seconds, +3 more. Returns: object

pix_get_allocation_movesA
Read-onlyIdempotent

Allocations the driver moved between heaps, or demoted out of video memory.

The other half of pix_get_residency_operations. Residency says an allocation was evicted and made resident again; a migration says it was moved between heaps and whether that worked, and a demotion says it was pushed into system memory for good. A demoted texture still reads, just over PCIe on every frame that touches it, so the cost is permanent rather than a one-off.

kind selects "migration" or "demotion"; omitting it returns both in time order. Empty is the normal answer for a title inside its video memory budget.

Inputs: capture_id (required), kind, limit, end_time, +3 more. Returns: object

pix_get_api_objectsA
Read-onlyIdempotent

Named D3D12 API work on one thread, as spans.

This is PIX's API object lane: a span for each named piece of driver-level work the thread did, such as "Pipeline State Compilation". It is what turns an object id in some other table into a name, and what shows a compile or a resource creation landing on a thread that was supposed to be doing something else.

It is per thread and sparse. pix_list_timing_threads reports api_object_events on every row, which is how to find a thread that has any before querying one that does not.

Inputs: thread_id (required), capture_id (required), limit, end_time, +3 more. Returns: object

pix_get_bridge_statusA
Read-onlyIdempotent

Report whether the deep-analysis bridge is available, and how to build it.

Inputs: platform. Returns: object

pix_get_capture_overviewB
Read-onlyIdempotent

Return frame, event, and category statistics for an open GPU capture.

Inputs: capture_id (required), refresh, queue_name, timeout_seconds. Returns: object

pix_get_command_listsA
Read-onlyIdempotent

The command lists one GPU queue executed, grouped by submission.

pix_get_gpu_timeline says which named scopes the GPU ran; this says which command lists carried them. Rows sharing an execution_id went in on one ExecuteCommandLists call, and parallel_level is how PIX stacks lists that overlap - which distinguishes real parallelism from a submission that got split.

Omit queue_id for the busiest queue. Empty means the capture was recorded without GPU timings, which is what the --noGpuTimings switch does.

Inputs: capture_id (required), limit, end_time, queue_id, +3 more. Returns: object

pix_get_context_switchesA
Read-onlyIdempotent

One thread's scheduling history: every time it came on or off a core, and why.

A row where from_thread_id is this thread is the thread losing the core, and from_thread_wait_reason says what for. That is the column that separates a thread that is slow because it is computing from one that is slow because it is blocked - a distinction a sample count cannot make, since a blocked thread simply produces fewer samples and looks idle.

Wait reasons are the Windows KWAIT_REASON values: 0 is Executive, 1 FreePage, 5 Suspended, 6 UserRequest, 7 WrExecutive, 13 WrQueue, 31 WrDispatchInt.

Inputs: thread_id (required), capture_id (required), limit, end_time, +3 more. Returns: object

pix_get_controller_inputA
Read-onlyIdempotent

Controller reads, which is where an input latency measurement starts.

Each row is one read of a controller's state, timestamped when the title read it. Paired with pix_get_gpu_frames and the monitor lane from pix_get_custom_data, the span from an input row to the VSync that displayed its consequence is end-to-end latency - the number a player feels, and the one no GPU capture can produce.

Omit controller_id for the busiest device. Empty means no controller was present or the title reads input through an API PIX does not instrument.

Inputs: capture_id (required), limit, end_time, start_time, +3 more. Returns: object

pix_get_core_activityA
Read-onlyIdempotent

Everything that ran on one core, whichever process it belonged to.

pix_get_cpu_samples answers "where did this thread's time go". This answers "what was on this core", which is the question behind an affinity or contention problem: a core pinned by another process is a cause of stutter that no amount of looking at the title's own threads will reveal. threads_in_page is the page's own breakdown, and process_id on each row is how to tell whose thread it was.

busiest_process_share names the busiest real process; idle_share_of_page is the part of the page that was process 0, the Windows Idle process, which is the core having nothing to do rather than a competitor for it. A core with a low idle share and a busiest process that is not the title is the contention case.

core is an index; pix_get_timing_summary reports how many this machine had. Empty means either nothing ran there or the capture was taken with cpu_samples disabled.

Inputs: core (required), capture_id (required), limit, end_time, +3 more. Returns: object

pix_get_counter_hotspotsA
Read-onlyIdempotent

Rank events by a counter to find the most expensive GPU work.

With nothing specified the capture's best per-event duration counter is picked automatically, which is the usual starting point for a performance investigation.

Inputs: capture_id (required), limit, counter, refresh, +6 more. Returns: object

pix_get_cpu_samplesA
Read-onlyIdempotent

Where one thread's CPU time went, as PIX's sampler saw it.

One row per sample: the sampler interrupted the machine, found this thread running, and recorded when and on which core. Counting samples in a time range is how much of that range the thread spent executing; the spread of core ids is whether the scheduler kept it in one place.

No instruction pointer comes back with these - resolving a sample to a function needs PIX's symbol server, which is a UI concern - so this answers when a thread ran, not what line it was on.

Inputs: thread_id (required), capture_id (required), limit, end_time, +3 more. Returns: object

pix_get_custom_dataA
Read-onlyIdempotent

One custom data lane's events and markers, in time order.

For a monitor lane this is the VSync train, and the gap between a present in pix_get_gpu_frames and the next VSync here is the wait a frame actually spent on the display - the part of latency that no GPU capture contains.

Omit data_type_id for the fullest lane; kind selects "event" or "marker". pix_list_custom_data_types lists what is available.

Inputs: capture_id (required), kind, limit, end_time, +4 more. Returns: object

pix_get_eventB
Read-onlyIdempotent

Return one event by queue_id or global_id, with its parent chain.

Inputs: capture_id (required), queue_id, global_id, queue_name, +2 more. Returns: object

pix_get_event_timingA
Read-onlyIdempotent

Per-event GPU timing from PIX's own timeline, with start and duration for each.

top=N ranks the whole queue by GPU duration, which is the cheapest way to find the expensive work in a frame: pix_get_counter_hotspots replays the capture once per counter, and this reads a timeline that already exists. The ranking is dominated by marker scopes - Frame, then Scene, then PostProcessing - which is the shape of the frame; leaves_only=true ranks the individual draws and dispatches inside instead. Without top, events come in queue order from cursor_position.

Windows starts replay if needed. Xbox requires pix_start_analysis first, with an explicit device_address; its returned time_unit is nanoseconds.

Inputs: capture_id (required), top, limit, queue_id, +5 more. Returns: object

pix_get_event_treeC
Read-onlyIdempotent

Return the marker hierarchy as a bounded tree.

Inputs: capture_id (required), max_depth, max_nodes, queue_name, +2 more. Returns: object

pix_get_file_ioA
Read-onlyIdempotent

Every file operation on one device: what was touched, when, and for how long.

This is the lane that explains a hitch the CPU sampler cannot. A thread blocked in a synchronous read is off-core and looks idle; here the same stall is one row with a path, a start and an end. offset and size_bytes together are the access pattern, which is what separates a streaming system reading ahead from one seeking per request. status is the NTSTATUS: 0 succeeded, negative is the failure the caller saw.

Without device_id the device the captured title used most is chosen. Fails with no_devices when the capture recorded no file I/O at all - see pix_list_storage_devices for why that is the usual case.

Inputs: capture_id (required), limit, end_time, device_id, +3 more. Returns: object

pix_get_gpu_framesA
Read-onlyIdempotent

The present timeline: one row per frame, from the Present call to the flip.

This is the frame-pacing view, and the reason to take a timing capture rather than a GPU one. Each frame carries the whole chain as separate timestamps - when Present was called, when it reached the queue, when the GPU finished writing, when the display scanned it out - so a stutter can be attributed to the part of the chain that grew rather than just to the frame that was late. block_on_fence and wait_on_swapchain are where a CPU thread sat waiting for the GPU or for a buffer to free up.

Inputs: capture_id (required), limit, end_time, start_time, +2 more. Returns: object

pix_get_gpu_timelineA
Read-onlyIdempotent

One GPU queue's named scopes, as the GPU actually ran them.

The same shape as pix_get_thread_timeline but indexed by queue rather than thread, and the times are when the GPU executed the work rather than when the CPU submitted it. Without queue_id the busiest queue is used.

Inputs: capture_id (required), limit, end_time, queue_id, +4 more. Returns: object

pix_get_metric_valuesA
Read-onlyIdempotent

One metric sampled over time: a series rather than a list of events.

The lanes, and what each needs:

  • "pix_counter" (the default) - a counter reported while the capture ran, either by the title through PIXReportCounter or by the driver, such as the per-adapter video memory budget and usage. Takes metric_id from pix_list_timing_counters.

  • "cpu_usage" - one core's utilisation as a percentage, sampled every 100 ms. Takes core, plus either process_id or all_processes=true. Asking for the same core both ways is how to see whether a busy core was busy with the title or with something else.

  • "api_object_memory" - video memory held by one process's D3D12 objects, in bytes. Takes process_id.

  • "device_utilization" and "device_bandwidth" - per Win32 device. Take device_id from pix_list_storage_devices, and need a capture recorded with the ETW file provider.

in_page reports min, max, mean, first and last over the rows returned, which for a series is usually the whole question.

Inputs: capture_id (required), core, limit, metric, +8 more. Returns: object

pix_get_page_faultsA
Read-onlyIdempotent

One thread's page faults - the stalls that no CPU sample can show.

fault_type is what it cost. HardPageFault is the expensive one: the page was not in memory and the thread waited on the disk. DemandZero is a first touch of new memory, Transition a page reclaimed from the standby list, CopyOnWrite a private copy being made - all cheap, and worth having mainly to tell them apart from the hard kind.

A burst of hard faults on the game thread is a hitch caused by memory pressure rather than by work, and it appears in no other table here. thread_id comes from pix_list_timing_threads.

Like file I/O this needs a provider pix_capture_timing does not enable, so captures taken through this server return nothing; the query is correct and the table is empty.

Inputs: thread_id (required), capture_id (required), limit, end_time, +3 more. Returns: object

pix_get_pipeline_stateA
Read-onlyIdempotent

The pipeline state bound at one event: program type, root signature, and bound views.

This is PIX's Pipeline State view. event_index is the queue-relative index from pix_list_gpu_events, not the global id used by the pixtool-backed tools. Events that bind no pipeline - Signal, Present, ResourceBarrier - report no_pipeline_state.

Inputs: capture_id (required), event_index (required), queue_id, timeout_seconds, +1 more. Returns: object

pix_get_residency_operationsA
Read-onlyIdempotent

What the GPU made resident, evicted, or paged, and when.

Video memory is oversubscribed when the working set does not fit, and Windows resolves it by evicting an allocation and paging it back over PCIe when the GPU next needs it. The cost lands on a frame that otherwise looks ordinary - the same draws, the same state, several milliseconds longer - so it is invisible in the event list and in every GPU-capture tool here.

Each row names the object that moved, which is what makes it actionable: a texture atlas evicted every frame is a budget problem with an address. residency_count is the running number of resident objects after the operation.

operation filters to one of MakeResident, Evict, PageIn, PageOut; by default all four are queried and merged in time order. An empty result is the normal case for a title comfortably inside its video memory budget.

Inputs: capture_id (required), limit, end_time, operation, +3 more. Returns: object

pix_get_resourceA
Read-onlyIdempotent

Describe one resource: full descriptor, heap, initial state, and view summary.

index comes from pix_list_resources.

Inputs: index (required), capture_id (required), timeout_seconds. Returns: object

pix_get_resource_viewsA
Read-onlyIdempotent

Every view of one resource, and the events that bind it.

This answers "where is this texture actually used, and as what": each view carries its descriptor, and each binding names the event plus how it was reached - root parameter, bindless descriptor heap, or an API argument such as a copy source or a barrier transition.

Inputs: index (required), capture_id (required), limit, cursor_position, +2 more. Returns: object

pix_get_root_signatureA
Read-onlyIdempotent

The root signature bound at one event, without the bound-view detail.

A narrower pix_get_pipeline_state for when only the binding layout matters.

Inputs: capture_id (required), event_index (required), queue_id, timeout_seconds. Returns: object

pix_get_server_statusA
Read-onlyIdempotent

Report PIX installs, pixtool health, limits, and open sessions.

Inputs: no named parameters. Returns: object

pix_get_thread_timelineA
Read-onlyIdempotent

One thread's named PIX scopes: what it was doing, and for how long.

This is the lane PIX draws for a thread. Each row is one PIXBeginEvent/End pair with its own name, span and nesting depth; without stack_level the whole tree comes back flattened, deepest scopes included. duration is wall clock and execution_time is the part of it the thread was actually on a core for - the gap between them is time spent descheduled, which is the difference between slow code and a thread that was waiting.

Empty means the process never called PIXBeginEvent. Check pix_get_timing_summary.

Inputs: thread_id (required), capture_id (required), limit, end_time, +4 more. Returns: object

pix_get_timing_summaryA
Read-onlyIdempotent

What a timing capture covers: its window, the machine, and which tables have data.

Start here. The contains flags say which of the other tools will return anything - a capture of a program that never presented has no gpu_frames, and one taken of an uninstrumented process has no pix_events - so this is the difference between an empty answer that is a bug and one that is the truth.

Inputs: capture_id (required), timeout_seconds. Returns: object

pix_list_controllersC
Read-onlyIdempotent

The input controllers the capture saw.

latency_instrumented says whether PIX's Dynamic Latency Instrumentation was running for that device, which is the difference between input timestamps anchored to the hardware and timestamps inferred from when the title read them.

Inputs: capture_id (required), limit, cursor_position, timeout_seconds. Returns: object

pix_list_countersB
Read-onlyIdempotent

List GPU counters available for this capture, grouped and searchable.

Inputs: capture_id (required), group, limit, cursor, +3 more. Returns: object

pix_list_custom_data_typesA
Read-onlyIdempotent

The custom data lanes this capture carries, with how much is in each.

Two sources end up here. A title that calls PIX's custom data API declares its own lanes - a gameplay state, a streaming bucket, whatever it defines. PIX also records one per monitor, carrying that display's VSync signal, which is present on any capture and is the reference a present-to-display measurement needs.

event_count is spans, marker_count is instants. data_type_id is what pix_get_custom_data takes.

Inputs: capture_id (required), limit, cursor_position, timeout_seconds. Returns: object

pix_list_eventsC
Read-onlyIdempotent

List capture events, optionally restricted to one parent marker or category.

Inputs: capture_id (required), limit, cursor, parent, +4 more. Returns: object

pix_list_gpu_eventsA
Read-onlyIdempotent

List events from one queue, straight from PIX's engine.

Unlike pix_list_events this needs no CSV export, sees every queue, and can return the decoded D3D12 call with its arguments. Without queue_id the busiest GPU queue is used. Paging is by cursor_position; the response carries next_cursor.

Inputs: capture_id (required), limit, queue_id, name_contains, +3 more. Returns: object

pix_list_metricsA
Read-onlyIdempotent

The metric series this capture can plot, and what each one is keyed on.

These are the graph lanes above PIX's thread tracks: a value sampled repeatedly across the capture rather than a list of events. keys_available says how many keys exist for each lane - how many counters were reported, how many cores the machine had - which is a different question from whether the series has rows.

Feed a lane name to pix_get_metric_values.

Inputs: capture_id (required), timeout_seconds. Returns: object

pix_list_open_capturesA
Read-onlyIdempotent

List open capture sessions with their ids, paths, and cached artifacts.

Inputs: no named parameters. Returns: object

pix_list_queuesA
Read-onlyIdempotent

List every command queue in a capture, with its name, type, and event count.

This is the real queue table, not a guess. Use the id or name of the busiest graphics queue with pix_list_gpu_events or pix_get_pipeline_state; a capture whose default queue looks empty usually has its draws in a named one.

Inputs: capture_id (required), timeout_seconds. Returns: object

pix_list_resourcesA
Read-onlyIdempotent

List the D3D12 resources in a capture with their dimensions and formats.

This is PIX's resource table: every texture and buffer the frame touched, with the debug name the application set. Filter with name_contains, then pass an entry's index to pix_get_resource or pix_get_resource_views.

Inputs: capture_id (required), limit, name_contains, cursor_position, +1 more. Returns: object

pix_list_storage_devicesA
Read-onlyIdempotent

The storage devices this capture watched, busiest first.

device_id from here goes to pix_get_file_io. Sorted by title_operation_count - the I/O done by the process PIX attached to - rather than by the machine-wide total, because the busiest disk overall is often some other program's.

Usually empty. PIX records file I/O only when the session enabled the ETW file provider, which pix_capture_timing has no switch for, so captures taken through this server have none. A capture recorded from the PIX UI with file I/O turned on will populate this and pix_get_file_io.

Inputs: capture_id (required), timeout_seconds. Returns: object

pix_list_timing_countersA
Read-onlyIdempotent

The counters a timing capture carries, grouped as PIX groups them.

These are not the same thing as pix_list_counters. Those are replay-time hardware metrics for a GPU capture, measured by re-running draws; these were reported live while the timing capture ran - what the title passed to PIXReportCounter, plus what the platform contributed, such as the video memory budget and usage that the driver publishes per adapter.

Each row names the process that reported it, so a machine-wide capture separates the title's counters from everything else's.

Inputs: capture_id (required), limit, cursor_position, timeout_seconds. Returns: object

pix_list_timing_gpu_queuesA
Read-onlyIdempotent

The GPU queues this capture saw, with the work counted on each.

queue_id from here goes to pix_get_gpu_timeline. A queue with event_count 0 was seen but never recorded doing anything, usually because the capture did not include the process that owns it.

Inputs: capture_id (required), timeout_seconds. Returns: object

pix_list_timing_processesA
Read-onlyIdempotent

Every process alive during the capture, busiest first.

A timing capture is machine-wide: capturing one program still records the several hundred others that happened to be running. Only the one PIX attached to is sampled, so the first entry is almost always the one worth looking at and the rest are context for why it was slow. sampled=false means the process existed but did no measured work.

Inputs: capture_id (required), limit, name_contains, cursor_position, +1 more. Returns: object

pix_list_timing_threadsA
Read-onlyIdempotent

Every thread of one process, with what PIX recorded for each, busiest first.

process_id comes from pix_list_timing_processes. The thread_id here is PIX's own, packing the process and thread together, and is what the per-thread tools take; os_thread_id is the one a debugger would show. stack_levels is how deep this thread's PIX event nesting goes.

Inputs: capture_id (required), process_id (required), limit, cursor_position, +1 more. Returns: object

pix_open_captureC
Idempotent

Register a capture and return a capture_id plus a compact overview.

Inputs: capture_path (required), remote, prefetch, timeout_seconds, +5 more. Returns: object

pix_perform_playbackC
Idempotent

Replay the capture once (or in a loop), optionally timing command-list recording.

Inputs: capture_id (required), loop, time_cpu, loop_count, +4 more. Returns: object

pix_recapture_playbackB
Idempotent

Capture a replay of the open capture (pixtool must itself be launched for capture).

Inputs: capture_id (required), expand_execute, timeout_seconds, include_recreation. Returns: object

pix_recapture_regionA
Idempotent

Write a new .wpix containing only the events in [start_global_id, end_global_id].

Inputs: capture_id (required), output_file (required), end_global_id (required), start_global_id (required), +1 more. Returns: object

pix_run_debug_layerA
Read-onlyIdempotent

Replay the capture with the D3D12 debug layer on, to surface validation errors.

Inputs: capture_id (required), timeout_seconds. Returns: object

pix_run_pixtoolA
Destructive

Run an arbitrary pixtool command pipeline.

This is the escape hatch for pixtool features without a dedicated tool. Pass commands as a list of objects: {"name": "save-event-list", "positionals": ["out.csv"], "options": {"counters": "gpu__*"}}. Options with a null value become bare flags. If capture_id is given, open-capture is prepended.

pixtool aborts the remaining commands once one fails. Values containing spaces are quoted automatically; a value containing a double quote cannot be expressed.

Inputs: commands (required), capture_id, output_level, timeout_seconds. Returns: object

pix_save_high_frequency_countersC
Idempotent

Collect High Frequency Counters (a GPU-wide time series) and return sample rows.

Inputs: capture_id (required), limit, merge, cursor, +5 more. Returns: object

pix_save_resourceA
Idempotent

Save a bound resource (render target or depth buffer) from a draw call.

The file format follows the destination extension. Without global_id or marker, pixtool uses the last event in the capture that has the resource bound.

Inputs: capture_id (required), destination (required), rtv, depth, +3 more. Returns: object

pix_save_screenshotB
Idempotent

Save the capture's frame screenshot as a PNG.

Inputs: capture_id (required), refresh, destination, timeout_seconds. Returns: object

pix_search_eventsA
Read-onlyIdempotent

Search events by name (substring, glob, or regex) with optional filters.

Inputs: capture_id (required), name_pattern (required), limit, cursor, +7 more. Returns: object

pix_start_analysisA
Idempotent

Replay a capture on a GPU so that PIX's timing data exists. Slow, and once only.

PIX gets per-event GPU timing by replaying the capture on a real D3D12 device; the 4.5 GB reference capture takes about twenty-five seconds. This blocks for up to wait_seconds (90 by default, enough for most captures) and reports where it got to. The replay keeps running between calls, so polling is free; pass wait_seconds=0 to start it and return at once. Once ready is true, pix_get_event_timing answers for every event in every queue at no further cost.

Windows defaults to this machine. Xbox requires an explicit device_address matching the GDK PIX analysis devkit and may interrupt its running title.

Inputs: capture_id (required), wait_seconds, device_address, timeout_seconds. Returns: object

pix_suggest_counter_patternB
Read-onlyIdempotent

Show the regexes that select a counter exactly, or its whole family.

Inputs: counter_name (required). Returns: object

pix_upgrade_captureA
DestructiveIdempotent

Upgrade a GPU capture file to the latest PIX format.

Without a destination the source file is overwritten in place.

Inputs: source_capture_path (required), destination, timeout_seconds. Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 67 tools

    Disambiguation3/5

    Most tools have distinct purposes, but a few pairs overlap, such as pix_list_queues vs pix_discover_queues and pix_list_events vs pix_list_gpu_events. The detailed descriptions help, but the large number of tools makes misselection more likely.

    Naming Consistency5/5

    All tools follow a consistent snake_case verb_noun pattern with the pix_ prefix, such as list_resources, get_pipeline_state, and capture_timing. This consistency makes the tool's action and target predictable across the entire set.

    Tool Count2/5

    With 67 tools, this far exceeds the typical well-scoped MCP server range. While the PIX domain is broad, the count is unwieldy and could benefit from being split into focused servers by area such as capture, timing, or GPU analysis.

    Completeness5/5

    The tool set covers the full capture-analysis lifecycle: capture from various targets, open/close/upgrade captures, inspect GPU/CPU timing, events, resources, pipeline state, counters, occupancy, memory, input, and export. No obvious dead ends or missing core operations are apparent.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      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.
      3
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      MCP server for reading, filtering, and analyzing NVIDIA Nsight Graphics captures via ngfx-replay, exposing tools for capture inspection, GPU Trace profiling, and replay analysis.
      26
      2
      MIT
    • A
      license
      B
      quality
      D
      maintenance
      Enables AI clients to analyze RenderDoc captures by browsing draw/dispatch events, inspecting pipeline state and shaders, and paginating vertex/constant buffer data.
      13
      1
      MIT