Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Custom visualizers allow you to do mainly two things:
Extend the pipeline view texture viewer to run a custom compute shader, which can read any resource bound to any stage of the pipeline, and output a 2D texture displayed in the viewer.
Extend the pipeline view buffer viewer to run a custom compute shader, which can read any resource bound to any stage of the pipeline, and output both an index buffer and a vertex buffer to be displayed in the mesh viewer.
PIX extensibility settings
By using the Extensibility Paths setting, you can specify any number of folders for PIX to search for visualizers (*.hlsl files).
If your visualizer shaders #include any file that's not sitting next to the main HLSL visualizer files, then you can use the Extensibility Include Search Paths setting to add search paths for those files.

Custom texture visualizer
Assuming that you've configured paths to your visualizer shaders in the settings, you can now see your visualizers listed in the Visualization panel.

Visualization panel
Once you've selected a custom visualizer from the list in the Visualization panel, the Custom Visualization section appears. From there, you can select to override the default texture format for your visualizer output. By default, the selected output format will be compatible with the pipeline view's currently selected texture. That's also where any shader compilation warnings/errors will be displayed.

Creating a custom texture visualizer
A visualizer is a compute shader running against the selected event (that is, draw, dispatch, dispatch mesh) and which output will be displayed in the texture viewer in place of the currently selected texture/render target. In order to be successfully recognized by PIX, your entry point must be named main.
You can select the thread group size of your compute shader the same way you do with any compute shader by specifying it as an attribute (for example, [numthreads(8, 8, 1)]). PIX will invoke your compute shader so that one thread can execute per pixel.
For instance, if you have a texture of size 512x512 pixels, and your compute shader uses a threadgroup size of (8, 8, 1), then the resulting invocation done internally by the PIX engine would be Dispatch(64, 64, 1). If the texture size isn't a multiple of the threadgroup size, then more threads than pixels are invoked to cover all of them.
The output texture generated by your compute shader will reflect the largest coordinates that it outputs through the HLSL API (for example, PixExt_StorePixel_Float(uint2 offset, float4 pixel)).
When your shader code is compiled in the PIX engine, the root signature used is a modified version of the one bound at the selected event. That means that (through the PIX HLSL API) you get access to all resources as they are available to that event plus all resources bound to the IA and OM stages.
If you have errors in your shader (displayed in the Visualization panel > Custom Visualization section in the DXC Output text box), then you can simply edit the file to correct the errors, and use the refresh button in the texture viewer to rebuild and re-execute your visualizer.
Example
Here's a basic example of a visualizer shader.
Texture2D<float4> SelectedTexture : PixExt_SelectedResourceRegister;
// For the custom PSO to successfully build, the root signature
// of the selected event must have a valid binding for t0.
Texture2D<float4> SRV0 : register(t0);
[numthreads(8, 8, 1)]
void main(PixExt_ComputeInput input)
{
uint2 selSize, srvSize;
SelectedTexture.GetDimensions(selSize.x, selSize.y);
SRV0.GetDimensions(srvSize.x, srvSize.y);
float2 uvs = input.dispatchThreadId.xy / (float2)selSize;
int2 srvCoords = (int2)(srvSize * uvs);
float4 selectedValue = SelectedTexture.Load(int3(input.dispatchThreadId.xy, 0));
float4 srv0Value = SRV0.Load(int3(srvCoords, 0));
float4 finalValue = selectedValue * srv0Value.yzxw;
PixExt_StorePixel_Float(input.dispatchThreadId.xy, finalValue);
}
In the example above, we declare SelectedTexture to point to the pipeline view's currently selected texture by using the PIX HLSL API special register PixExt_SelectedResourceRegister. We also declare SRV0 at t0, knowing it will be present in the root signature of the GPU capture we're working against. You can always make your extension as generic or specific as your use case dictates. Finally, we output to the texture viewer using the PixExt_StorePixel_Float function, which takes a pixel position and a value as arguments.
To get more information on the API, refer to the HLSL API section.

Custom mesh visualizer
Assuming you've set up paths to your visualizer shaders in the settings, you can now see your visualizers listed in the Custom Visualization panel, which you can open using the following buffer viewer toolbar icon: {}.
Custom visualization panel
From that panel, you can select any available custom visualizer, and see any warning/error messages from the shader compiler.

Creating a custom mesh visualizer
A visualizer is a compute shader running against the selected event, and whose output will be displayed in the mesh viewer in place of the buffer viewer showing the currently selected buffer data. To be successfully recognized by PIX, your entry point must be named main.
You can select the thread group size of your compute shader as you do for any compute shader by specifying it as an attribute (for example, [numthreads(32, 1, 1)]). PIX will invoke your compute shader so that one thread can execute per buffer element.
For instance, if you have a buffer of 100 elements, and your compute shader uses a threadgroup size of (32, 1, 1), then the resulting invocation done internally by the PIX engine would be Dispatch(4, 1, 1). If the buffer size is not a multiple of the threadgroup size, then more threads than buffer elements are invoked to cover all of them.
The output index and vertex buffers allocated to your compute shader dynamically grow to fit all the data you output. You can output as much data as you need. Indices are 32-bit, and vertices are allowed to be only float4 positions.
When your shader code is compiled in the PIX engine, the root signature used is a modified version of the one bound at the selected event. That means that (through the PIX HLSL API) you get access to all resources as they are available to that event plus all resources bound to the IA and OM stages.
If you have errors in your shader (displayed in the Custom Visualization panel > DXC Output text box), then you can simply edit the file to correct the errors, and hit the refresh button in the mesh viewer to rebuild and re-execute your visualizer.
Example
Here's a basic example of a visualizer shader
struct Vertex
{
float3 position;
float2 uv;
};
StructuredBuffer<Vertex> Vertices : PixExt_VertexBufferRegister0;
[numthreads(32, 1, 1)]
void main(PixExt_ComputeInput input)
{
const uint vertexCount = PixExt_GetVertexCountPerInstance();
if (input.dispatchThreadId.x < vertexCount)
{
const uint vertexStart = PixExt_GetStartVertexLocation();
uint vertexOffset = input.dispatchThreadId.x + vertexStart;
Vertex vertex = Vertices[vertexOffset];
float4 newPosition = float4(vertex.position, 1.f);
PixExt_StoreIndex(input.dispatchThreadId.x, input.dispatchThreadId.x);
PixExt_StoreVertex(input.dispatchThreadId.x, newPosition);
}
}
In the example above, we access vertices declaring Vertices to point to the pipeline view currently bound vertex buffer 0 using the PIX HLSL API special register PixExt_VertexBufferRegister0. We then output to the mesh viewer using the PixExt_StoreIndex and PixExt_StoreVertex functions, which take buffer offsets and respectively index and vertex values as arguments.
To get more information on the API, refer to the HLSL API section.

Visualize a buffer as a texture
It is also possible to select a buffer and a custom visualizer outputting to a texture. In that case, the texture viewer will be displayed in place of the regular buffer viewer to display the result.
Custom buffer visualizer
The custom buffer visualizer works the same as the custom mesh visualizer, except instead of outputing to a pair of index and vertex buffers, it outputs to a RWStructuredBuffer. The buffer viewer is shown displaying the output and the buffer format applied to the viewer is automatically populated from the structured buffer template type. This kind of visualizer is useful, among other things, to reinterpret buffer data where multiple indirections might be involved or where the meaning of the data is not easily human-readable without further modification and interpretation. It can also be used to aggregate information coming from multiple resources and display the result in a specified format.
Creating a custom buffer visualizer
To create the visualizer, there are two required steps. A type is declared as the output type using PixExt_Declare_BufferOutput_Type. The buffer viewer format is derived from that type. Then, data is sent to output using PixExt_StoreBufferData where the offset into the structured buffer and the data to store are specified.
To get more information on the API, refer to the HLSL API section.
Example
struct OutputData
{
uint i;
float f;
};
PixExt_Declare_BufferOutput_Type(OutputData);
[numthreads(32, 1, 1)]
void main(PixExt_ComputeInput input)
{
OutputData output;
output.i = some_value;
output.f = some_other_value;
const uint bufferElementCount = PixExt_GetBufferElementCount();
if (input.dispatchThreadId.x < bufferElementCount)
{
PixExt_StoreBufferData(input.dispatchThreadId.x, output);
}
}

User constants
It is possible to add constants whose values can be set from the UI for every visualizer invocation. They are supported for all custom visualizer types.
HLSL example
PixExt_Declare_UserConstants_Start
PixExt_UserConstant_Int(int_constant_name);
PixExt_UserConstant_Uint(uint_constant_name);
PixExt_UserConstant_Float(float_constant_name);
PixExt_Declare_UserConstants_End
You are allowed to have a single user constant block (PixExt_Declare_UserConstants_Start / PixExt_Declare_UserConstants_End pair) in your visualizer. You can add any number of constants of 3 different types, namely int, uint, float. For each one declared, a new entry will appear in the visualization panel to enter the desired value. 0 is the default for all constants.

User configurations
Compiler and UI behavior options can be specified directly in HLSL. The syntax is as follows:
PixExt_Start_Configuration
PixExt_Config_OutputFormatOverride(PixExt_R16G16B16A16_UNORM);
PixExt_Config_HlslVersion(PixExt_HLSL2018);
PixExt_Config_ShaderModel(PixExt_SM_6_7);
PixExt_Config_MatrixPacking(PixExt_Row);
PixExt_End_Configuration
For details on all available options, refer to the PIX HLSL API section.
PIX HLSL API
To give you access to all resources, and to have a standard mean of outputting data to the viewers, PIX will inject internal implementation code when compiling your shader. The implementation is always subject to change, but the following documented API is stable across PIX versions.
Resource access
The API defines a set of special registers that you can use to get access to resources of different pipeline stages that are not inherently accessible.
Selected resource
PixExt_SelectedResourceRegister: Gives access to the resource currently selected in the pipeline view.
Render targets
PixExt_RenderTargetRegister0
PixExt_RenderTargetRegister1
PixExt_RenderTargetRegister2
PixExt_RenderTargetRegister3
PixExt_RenderTargetRegister4
PixExt_RenderTargetRegister5
PixExt_RenderTargetRegister6
PixExt_RenderTargetRegister7
Depth/stencil
PixExt_DepthRegister
PixExt_StencilRegister
Input assembly stage resources
PixExt_IndexBufferRegister
PixExt_VertexBufferRegister0
PixExt_VertexBufferRegister1
PixExt_VertexBufferRegister2
PixExt_VertexBufferRegister3
PixExt_VertexBufferRegister4
PixExt_VertexBufferRegister5
PixExt_VertexBufferRegister6
PixExt_VertexBufferRegister7
PixExt_VertexBufferRegister8
PixExt_VertexBufferRegister9
PixExt_VertexBufferRegister10
PixExt_VertexBufferRegister11
PixExt_VertexBufferRegister12
PixExt_VertexBufferRegister13
PixExt_VertexBufferRegister14
PixExt_VertexBufferRegister15
PixExt_VertexBufferRegister16
PixExt_VertexBufferRegister17
PixExt_VertexBufferRegister18
PixExt_VertexBufferRegister19
PixExt_VertexBufferRegister20
PixExt_VertexBufferRegister21
PixExt_VertexBufferRegister22
PixExt_VertexBufferRegister23
PixExt_VertexBufferRegister24
PixExt_VertexBufferRegister25
PixExt_VertexBufferRegister26
PixExt_VertexBufferRegister27
PixExt_VertexBufferRegister28
PixExt_VertexBufferRegister29
PixExt_VertexBufferRegister30
PixExt_VertexBufferRegister31
Variable-rate shading
PixExt_ShadingRateImageRegister
Shader inputs
You can use the PixExt_ComputeInput structure as an input to your main shader function. This is a helper, and isn't mandatory.
struct PixExt_ComputeInput
{
uint3 dispatchThreadId : SV_DispatchThreadID;
uint3 groupId : SV_GroupID;
uint3 groupThreadId : SV_GroupThreadID;
uint groupIndex : SV_GroupIndex;
};
Selected texture information
// Returns the currently selected sample.
uint PixExt_GetSelectedSample();
// Returns the currently selected mip.
uint PixExt_GetSelectedMip();
// Returns the currently selected slice.
uint PixExt_GetSelectedSlice();
Selected buffer information
// Returns the currently selected buffer element count.
uint PixExt_GetBufferElementCount();
Selected event arguments
// For a DrawIndexedInstanced event,
// returns the IndexCountPerInstance.
uint PixExt_GetIndexCountPerInstance();
// For a DrawIndexedInstanced event,
// returns the StartIndexLocation
uint PixExt_GetStartIndexLocation();
// For a DrawInstanced event,
// returns the VertexCountPerInstance.
uint PixExt_GetVertexCountPerInstance();
// For a DrawInstanced event,
// returns the StartVertexLocation.
uint PixExt_GetStartVertexLocation();
// For a DrawInstanced or DrawIndexedInstanced event,
// returns the InstanceCount.
uint PixExt_GetInstanceCount();
// For a DrawInstanced or DrawIndexedInstanced event,
// returns the StartInstanceLocation.
uint PixExt_GetStartInstanceLocation();
// For a Dispatch or DispatchMesh event,
// returns the ThreadGroupCountX.
uint PixExt_GetThreadGroupCountX();
// For a Dispatch or DispatchMesh event,
// returns the ThreadGroupCountY.
uint PixExt_GetThreadGroupCountY();
// For a Dispatch or DispatchMesh event,
// returns the ThreadGroupCountZ.
uint PixExt_GetThreadGroupCountZ();
Variable-rate shading
// Returns the screen-space VRS image pixel tile size.
uint PixExt_GetShadingRateImageTileSize();
Bind by resource name
You can explicitly bind to a resource by name using the PixExt_BindByName special register.
When used on a custom visualizer resource declaration, it instructs PIX to find a resource of
the same name in the selected event to bind to. For instance, using the following code in a
custom visualizer would result in a binding to a resource named g_texture found in the
selected event shaders.
Texture2D<float4> g_texture : PixExt_BindByName;
You must bind by name through SRV types (Buffer, StructuredBuffer, Texture2D, and so on). UAVs and CBVs aren't directly supported; but you can bind those resources as SRVs in your custom visualizer.
Assuming the following declarations in the capture shader code:
ConstantBuffer<ConstantData> g_constants : register(b2, space4);
RWBuffer<float4> g_buffer : register(u4, space2);
You can access the data through SRV bindings in your custom visualizer.
StructuredBuffer<ConstantData> g_constants : PixExt_BindByName;
Buffer<float4> g_buffer : PixExt_BindByName;
Root descriptor limitation
A root descriptor can be bound only as a raw buffer in a custom visualizer. Structured buffer binding isn't supported.
Shader outputs
You output data to the viewers using the following functions.
Texture
// Stores the given INT pixel data for texture viewer display.
//
// offset: Pixel position to write to.
// pixel: Pixel data to write.
void PixExt_StorePixel_Int(uint2 offset, int4 pixel);
// Stores the given UINT pixel data for texture viewer display.
//
// offset: Pixel position to write to.
// pixel: Pixel data to write.
void PixExt_StorePixel_Uint(uint2 offset, uint4 pixel);
// Stores the given FLOAT pixel data for texture viewer display.
//
// offset: Pixel position to write to.
// pixel: Pixel data to write.
void PixExt_StorePixel_Float(uint2 offset, float4 pixel);
Mesh
// Stores the given index for mesh viewer display.
//
// offset: Offset into the index buffer to write to.
// index: Index to write.
void PixExt_StoreIndex(uint offset, uint index);
// Stores the given vertex position for mesh viewer display.
//
// offset: Offset into the vertex buffer to write to.
// vertex: Vertex position to write.
void PixExt_StoreVertex(uint offset, float4 vertex);
Buffer
// Stores the given data for buffer viewer display.
//
// offset: Offset into the output structured buffer.
// data: Value to write.
template<typename TypeName>
void PixExt_StoreBufferData(uint offset, TypeName data);
Configurations
A single configuration section is allowed per visualizer.
Syntax:
PixExt_Start_Configuration
<List of configurations>
PixExt_End_Configuration
HLSL version
enum PixExtHlslVersion
{
PixExt_HLSL2018,
PixExt_HLSL2021
};
PixExt_Config_HlslVersion(PixExtHlslVersion);
Shader model
enum PixExtShaderModel
{
PixExt_SM_6_6,
PixExt_SM_6_7,
PixExt_SM_6_8
};
PixExt_Config_ShaderModel(PixExtShaderModel);
Matrix packing
enum PixExtMatrixPacking
{
PixExt_Row,
PixExt_Column
};
PixExt_Config_MatrixPacking(PixExtMatrixPacking);
Output format override
enum PixExtOutputFormatOverride
{
PixExt_R32G32B32A32_FLOAT,
PixExt_R32G32B32A32_UINT,
PixExt_R32G32B32A32_SINT,
PixExt_R32G32B32_FLOAT,
PixExt_R32G32B32_UINT,
PixExt_R32G32B32_SINT,
PixExt_R16G16B16A16_FLOAT,
PixExt_R16G16B16A16_UNORM,
PixExt_R16G16B16A16_UINT,
PixExt_R16G16B16A16_SNORM,
PixExt_R16G16B16A16_SINT,
PixExt_R32G32_FLOAT,
PixExt_R32G32_UINT,
PixExt_R32G32_SINT,
PixExt_R10G10B10A2_UNORM,
PixExt_R10G10B10A2_UINT,
PixExt_R11G11B10_FLOAT,
PixExt_R8G8B8A8_UNORM,
PixExt_R8G8B8A8_UNORM_SRGB,
PixExt_R8G8B8A8_UINT,
PixExt_R8G8B8A8_SNORM,
PixExt_R8G8B8A8_SINT,
PixExt_R16G16_FLOAT,
PixExt_R16G16_UNORM,
PixExt_R16G16_UINT,
PixExt_R16G16_SNORM,
PixExt_R16G16_SINT,
PixExt_R32_FLOAT,
PixExt_R32_UINT,
PixExt_R32_SINT,
PixExt_R8G8_UNORM,
PixExt_R8G8_UINT,
PixExt_R8G8_SNORM,
PixExt_R8G8_SINT,
PixExt_R16_FLOAT,
PixExt_R16_UNORM,
PixExt_R16_UINT,
PixExt_R16_SNORM,
PixExt_R16_SINT,
PixExt_R8_UNORM,
PixExt_R8_UINT,
PixExt_R8_SNORM,
PixExt_R8_SINT,
PixExt_R1_UNORM,
PixExt_R9G9B9E5_SHAREDEXP,
PixExt_R8G8_B8G8_UNORM,
PixExt_G8R8_G8B8_UNORM,
PixExt_B5G6R5_UNORM,
PixExt_B5G5R5A1_UNORM,
PixExt_B8G8R8A8_UNORM,
PixExt_B8G8R8X8_UNORM,
PixExt_B8G8R8A8_UNORM_SRGB
};
PixExt_Config_OutputFormatOverride(PixExtOutputFormatOverride);