DirectX 12 Compute Shader Sample

Sample Title: DX12 Compute Shader Sample

Description: This sample demonstrates the fundamental usage of compute shaders within the DirectX 12 API. It covers resource binding, dispatching compute operations, and how to integrate compute shaders into a graphics pipeline for tasks such as data processing, simulations, or general-purpose GPU computing.

Topics Covered: Compute Shaders, UAVs (Unordered Access Views), SRVs (Shader Resource Views), CBVs (Constant Buffer Views), Root Signatures, Pipeline State Objects (PSOs), GPU Compute Operations, DXGI Adapter Enumeration.

Platform: Windows 10 and later

API: DirectX 12

Overview

Compute shaders in DirectX 12 provide a powerful mechanism to leverage the parallel processing capabilities of the GPU for tasks that are not strictly rendering-related. This sample provides a practical implementation to get you started with compute shaders, illustrating common patterns and best practices.

Key Concepts

Sample Code Snippets

Compute Shader (.hlsl)

ComputeShader.hlsl
RWTexture2D<float4> outputTexture : register(u0);
StructuredBuffer<float> inputBuffer : register(t0);

struct Constants
{
    float scale;
    uint frameCount;
};
cbuffer ConstantBuffer : register(b0)
{
    Constants g_constants;
}

// Define the thread group size
[numthreads(8, 8, 1)]
void CSMain( uint3 dispatchThreadID : SV_DispatchThreadID )
{
    // Get dimensions of the output texture
    uint2 texDim;
    outputTexture.GetDimensions(texDim.x, texDim.y);

    // Calculate UV coordinates for the texture
    float2 uv = float2(float)dispatchThreadID.x / float(texDim.x),
                        float(texDim.y) / float(texDim.y));

    // Example computation: Use input buffer and constants
    float sampledValue = inputBuffer[dispatchThreadID.x % 1024];
    float4 outputColor = float4(uv.x, uv.y, sampledValue * g_constants.scale, 1.0f);

    // Write to the output texture
    outputTexture[uint2(dispatchThreadID.x, dispatchThreadID.y)] = outputColor;
}

Root Signature (.json)

RootSignature.json

{
    "RootSignatureVersion": "1.0",
    "GlobalRootSignature": null,
    "RootParameters": [
        {
            "ParameterType": "DescriptorTable",
            "DescriptorCount": 1,
            "ShaderVisibility": "All",
            "TableEntries": [
                {
                    "DescriptorType": "UAV",
                    "RegisterRangeType": "Scalar",
                    "Offset": 0,
                    "RangeType": "Sequential"
                }
            ]
        },
        {
            "ParameterType": "DescriptorTable",
            "DescriptorCount": 1,
            "ShaderVisibility": "All",
            "TableEntries": [
                {
                    "DescriptorType": "SRV",
                    "RegisterRangeType": "Scalar",
                    "Offset": 0,
                    "RangeType": "Sequential"
                }
            ]
        },
        {
            "ParameterType": "DescriptorTable",
            "DescriptorCount": 1,
            "ShaderVisibility": "All",
            "TableEntries": [
                {
                    "DescriptorType": "CBuffer",
                    "RegisterRangeType": "Scalar",
                    "Offset": 0,
                    "RangeType": "Sequential"
                }
            ]
        }
    ],
    "StaticSamplers": [],
    "Constants": []
}

C++ Implementation Snippet (D3D12)

D3D12ComputeHelper.cpp
// Assume device, commandList, etc. are initialized

// Create root signature
// ... load root signature from JSON ...
Microsoft::WRL::ComPtr<ID3D12RootSignature> rootSignature;
// D3DCreate... or similar to create from blob

// Create compute shader pipeline state
Microsoft::WRL::ComPtr<ID3DBlob> computeShaderBlob;
// ... load compute shader bytecode ...

D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.pRootSignature = rootSignature.Get();
psoDesc.CS.pShaderBytecode = computeShaderBlob.Get();
psoDesc.CS.BytecodeLength = computeShaderBlob.GetSize();

Microsoft::WRL::ComPtr<ID3D12PipelineState> computePSO;
ThrowIfFailed(device->CreateComputePipelineState(&psoDesc, IID_PPV_ARGS(&computePSO));

// Setup descriptors and bind resources
// ... create UAV, SRV, CBV descriptors ...
D3D12_CPU_DESCRIPTOR_HANDLE uavHandle; // Handle to UAV descriptor
D3D12_CPU_DESCRIPTOR_HANDLE srvHandle; // Handle to SRV descriptor
D3D12_CPU_DESCRIPTOR_HANDLE ccbvHandle; // Handle to CBV descriptor

// Record commands
commandList->SetComputeRootSignature(rootSignature.Get());
commandList->SetPipelineState(computePSO.Get());

// Bind descriptors to correct root parameter indices
commandList->SetComputeRootDescriptorTable(0, uavHandle); // UAV is at index 0
commandList->SetComputeRootDescriptorTable(1, srvHandle); // SRV is at index 1
commandList->SetComputeRootDescriptorTable(2, ccbvHandle); // CBV is at index 2

// Dispatch the compute shader
UINT threadGroupCountX = (outputTextureWidth + 7 ) / 8;
UINT threadGroupCountY = (outputTextureHeight + 7 ) / 8;
commandList->Dispatch(threadGroupCountX, threadGroupCountY, 1);

Getting Started

To run this sample, you will need:

Download the complete sample from the DirectX Samples Repository.

Further Exploration

This sample serves as a foundational step into the world of DirectX 12 compute shaders. Experiment with the code, modify parameters, and observe the results to gain a deeper understanding of GPU-driven computation.