Shader Debugger (Experimental)

July 2, 2026 · View on GitHub

Overview . Inspect . Capture . Record

Introduction

Back to top

WebGPU Inspector lets you debug WGSL shaders, letting you step through shader code, set breakpoints, inspect variable values.

Warning

The shader debugger is an experimental work in progress and not guaranteed to work on all shaders. It supports compute, vertex, and fragment shaders.

The shader debugger is a CPU interpreter of WGSL shaders. Because it does not actually run on the GPU, there will be some differences in how the shader is executed compared to running on the GPU. The shader debugger steps through a single thread of execution of the shader, whereas on the GPU the shader is SIMD multithreaded. This means some shader behaviors, like workgroup shared memory and barrier functions, will not behave the same with the shader debugger. The shader debugger is not suitable for debugging issues that invove these things, or issues like race conditions. The shader debugger is best suited for debugging things like shader logic issues.

Debugging a compute shader

Back to top

The shader debugger is integrated into the Capture tool. Capturing a frame will capture all buffers and textures used to render the frame.

Warning

To properly debug the shader, all buffers and textures used by the shader will need to have been captured. Large buffers may have been skipped, due to the Max Buffer Size capture property. If a buffer was skipped due to being too large, increase the Max Buffer Size to accommodate for the size needed by the buffer and capture the frame again.

Select a dispatchWorkgroups command from the capture. The command details will include the Compute Module used with the dispatch. Open the Compute module section and there will be a Debug button. Press the Debug button to start debugging the shader. This will open a tab with the debugger for the selected shader dispatch.

Debugging a vertex shader

Back to top

Select a draw or drawIndexed command from the capture. The command details include the render pipeline and its shader modules. Open the Vertex Module section (or the combined shader module section, if the pipeline uses a single module for both stages) and press the Debug / Debug Vertex button to start debugging the vertex shader for that draw. This opens a debugger tab for the selected vertex stage.

Instead of the compute Thread ID, the vertex debugger's toolbar provides a Vertex and an Instance input:

  • Vertex is the @builtin(vertex_index) of the vertex you wish to debug.
  • Instance is the @builtin(instance_index).

When you start debugging, the inspector performs the "vertex fetch" that the GPU would normally do: it decodes the draw's captured vertex buffers according to the pipeline's vertex layout and feeds the resulting attribute values (plus the vertex/instance builtins) into the shader for the selected vertex. All vertex buffers used by the draw must have been captured for this to work; see the Max Buffer Size note above.

Debugging a fragment shader

Back to top

Select a draw or drawIndexed command, open the Fragment Module section (or the combined shader module section), and press the Debug / Debug Fragment button. Fragment debugging works by picking a pixel in the draw's render target.

The fragment debugger's toolbar provides Pixel X, Y, Instance, and Prim inputs. Enter the pixel you want to debug and start debugging. Prim selects a specific primitive (triangle index) to debug at the pixel; the default of -1 selects the front-most primitive covering it. The inspector then reproduces what the GPU rasterizer does:

  1. It assembles the draw into triangles from the index/vertex buffers and topology.
  2. It runs the vertex shader for the relevant vertices and rasterizes to find the triangle that covers the picked pixel.
  3. It builds the four perspective-correct, interpolated inputs of the 2×2 pixel quad that contains the picked pixel, so that derivatives (dpdx/dpdy) and texture sampling resolve correctly.

You step through the picked pixel's lane; the other three lanes of the quad are advanced in lockstep at derivative and texture-sampling points. If no primitive in the draw covers the entered pixel, a message is shown in the toolbar (for example, No primitive covers pixel (x, y)) — pick a different pixel that lies inside the drawn geometry.

Rather than entering a pixel by hand, you can use Pixel History to click a pixel in a render-pass attachment, see every fragment that touched it, and open the fragment debugger pre-seeded with that fragment's pixel, instance, and primitive.

Note

Fragment debugging requires the draw's vertex buffers (and, for indexed draws, index buffer) to have been captured, since the inspector re-runs the vertex shader and rasterizes to locate the picked pixel.

Debugger Controls

Back to top

Invocation selection

The controls for choosing which shader invocation to debug depend on the shader stage:

  • Compute — a Thread ID (see below).
  • Vertex — a Vertex (vertex_index) and Instance (instance_index) index.
  • Fragment — a Pixel X, Y, and Instance.

Thread ID (compute)

Shader Debugger Thread ID

The Thread ID is the global_invocation_id of the thread you wish to debug. The global_invocation_id is based on the dispatch workgroup_id from the dispatch workgroup counts, and the local_invocation_id from the workgroup_size of the shader.

Enter the invocation you wish to debug (the Thread ID for compute, or the Vertex/Instance or Pixel for vertex/fragment) and press the button to start debugging that invocation. Keyboard shortcut: F8

Debugger Controls

Shader Debugger Controls

The shader debugger will start paused on the first statement of the compute shader kernel. The toolbar at the top gives controls for stepping through the shader.

Run from the current position all the way through to the end, or until a breakpoint has been reached. Pressing this button again will pause the execution. Keyboard shortcut: F5

Execute the current statement, stepping over any function calls. Keyboard shortcut: F10

Execute the current statement, stepping into any function calls. Keyboard shortcut: F11

Run from the current position until the current block or function has completed. Keyboard shortcut: F12

Restart debugging the shader from the beginning. Keyboard shortcut: F7

Note

The highlighted statement indicates the next statement to be executed, not the statement that was just executed.

Breakpoints

Back to top

Click in the gutter to the left of a line number in the shader editor to set a breakpoint on that line; a red marker appears. Click the marker again to remove the breakpoint. When you Continue (F5), the debugger runs until it reaches a breakpoint or the end of the shader.

Variables

Back to top

The Variables panel, on the right side of the debugger, shows every variable in scope at the current statement, along with its type and value. Each function call currently on the stack contributes its own group of variables; module-scope state is split into Globals (uniform/storage values), Constants (const / override values, shown dimmed since they never change while stepping — both groups collapsed by default), and Resources (bound textures and samplers, shown with their format, dimensions, and sampler modes).

Composite values — structs, arrays, vectors, and matrices — can be expanded to inspect their members and elements; structs show an inline preview of their first members, and matrices expand into their column vectors. Floating point values are shown rounded to a few significant digits (hover for full precision), and vec3/vec4 values that look like colors get a color swatch. When stepping, any value that changed since the previous step is highlighted. The entry point's inputs are expanded by default.

Right-clicking a variable (or a struct member / array element) opens a context menu with Copy Name (the full path, e.g. material.alphaCutoff), Copy Value (full precision), and Pin to Watch. Pinned paths appear in a Watch group at the top of the panel and stay pinned across restarts of the debug session, so a deeply nested member can be tracked without re-expanding its parents after every step.

The Filter variables by name field at the top of the panel narrows the list to variables whose name matches the entered text. The filter also matches struct member names, expanding the tree to show where the matches are.

Hovering a variable in the shader editor shows its current value; member and element chains like material.alphaCutoff or lights[2].color resolve to the hovered member's value.

Callstack

Back to top

The Callstack panel lists the chain of function calls that led to the current statement, with the source line of each frame. It updates as you step into and out of functions.

Race Detection

Back to top

Race detection is available for compute shaders only. The Detect Races button runs a data-race detector over the compute kernel. Unlike stepping, which executes a single thread, the detector runs every invocation of a workgroup in lockstep, parking each one when it reaches a barrier. The memory accesses between barriers are cross-checked: if two invocations touch overlapping memory in the same barrier phase and at least one is a write, that is a data race.

The results appear in the Race Detection panel. Each reported race describes the buffer, the conflicting invocations and the source lines involved; clicking a race jumps the editor to the relevant line. Barrier divergence issues (barriers that are not reached uniformly by all invocations) are reported as well.

A data race usually means a workgroupBarrier() or storageBarrier() is missing between the conflicting accesses.

Note

The race detector scans a single workgroup. It detects races within a workgroup, caused by missing barriers. Races between different workgroups on storage memory are not decidable, as WebGPU provides no cross-workgroup ordering guarantees.