컴퓨트 파이프라인은 그래픽스 파이프라인보다 더 쉽습니다.

리소스와 연산이 분리되어 있어서 더 쉽게 이해 할 수 있습니다.

컴퓨트 파이프라인을 살펴 봅시다.

Resources

레스터라이제이션, 레이트레이싱 파이프라인과 마찬가지로 계산에는 삼각형 데이터를 가진 구조화 버퍼, 렌더링에 관여되지 않는 정렬되지 않은 엑세스 뷰(UAV), 상수버퍼 뷰와 같은 리소스들이 필요합니다.

image.png

// Create the root signature.

D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {};

// This is the highest version the sample supports. If
// CheckFeatureSupport succeeds, the HighestVersion returned will not be
// greater than this.
featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;

if (FAILED(device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE,
                                        &featureData,
                                        sizeof(featureData))))
{
    featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
}

D3D12_DESCRIPTOR_RANGE1 ranges[1];
ranges[0].BaseShaderRegister = 0;
ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
ranges[0].NumDescriptors = 1;
ranges[0].RegisterSpace = 0;
ranges[0].OffsetInDescriptorsFromTableStart = 0;
ranges[0].Flags = D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE;

D3D12_ROOT_PARAMETER1 rootParameters[1];
rootParameters[0].ParameterType =
    D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
rootParameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;

rootParameters[0].DescriptorTable.NumDescriptorRanges = 1;
rootParameters[0].DescriptorTable.pDescriptorRanges = ranges;

D3D12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1;
rootSignatureDesc.Desc_1_1.Flags = D3D12_ROOT_SIGNATURE_FLAG_NONE;
rootSignatureDesc.Desc_1_1.NumParameters = 1;
rootSignatureDesc.Desc_1_1.pParameters = rootParameters;
rootSignatureDesc.Desc_1_1.NumStaticSamplers = 0;
rootSignatureDesc.Desc_1_1.pStaticSamplers = nullptr;

ID3DBlob* signatureBlob;
ID3DBlob* error;
try
{
    ThrowIfFailed(D3D12SerializeVersionedRootSignature(
        &rootSignatureDesc, &signatureBlob, &error));
    ThrowIfFailed(mDevice->CreateRootSignature(
        0, signature->GetBufferPointer(), signatureBlob->GetBufferSize(),
        IID_PPV_ARGS(&rootSignature)));
    rootSignature->SetName(L"Hello Compute Root Signature");
}
catch (std::exception e)
{
    const char* errStr = (const char*)error->GetBufferPointer();
    std::cout << errStr;
    error->Release();
    error = nullptr;
}

if (signatureBlob)
{
    signatureBlob->Release();
    signatureBlob = nullptr;
}

Compute Shaders

컴퓨트 쉐이더는 간단합니다. 기존의 렌더링 파이프라인들 보다. 오직 하나의 스테이지만 존재합니다.

RWTexture2D<float4> tOutput : register(u0);

[numthreads(16, 16, 1)]
void main(uint3 groupThreadID : SV_GroupThreadID,       // The current thread group (so pixel) of this group defined by `numthreads`
         uint3 groupID : SV_GroupID,                    // The current thread group ID, the group of threads defined in `Dispatch(x,y,z)`
         uint  groupIndex : SV_GroupIndex,              // The index of this group (so represent the group ID linearly)
         uint3 dispatchThreadID: SV_DispatchThreadID)   // Your current pixel
{
    tOutput[dispatchThreadID.xy] = float4( float(groupThreadID.x) / 16.0, float(groupThreadID.y) / 16.0, dispatchThreadID.x / 1280.0, 1.0);
}

Pipeline State

image.png

컴퓨트 파이프라인은 셰이더와 루트 시그니처만 사용합니다. 그게 전부 다입니다.

D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.pRootSignature = rootSignature;
D3D12_SHADER_BYTECODE csBytecode;
csBytecode.pShaderBytecode = compShader->GetBufferPointer();
csBytecode.BytecodeLength = compShader->GetBufferSize();
psoDesc.CS = csBytecode;

try
{
    ThrowIfFailed(mDevice->CreateComputePipelineState(
        &psoDesc, IID_PPV_ARGS(&pipelineState)));
}
catch (std::exception e)
{
    std::cout << "Failed to create Compute Pipeline!";
}

if (compShader)
{
    compShader->Release();
    compShader = nullptr;
}