OK,我们现在回到计算着色器最具价值的一个部分,通用计算GPGPU。当然现在有很多方法实现通用计算,比如英伟达提出的CUDA解决方案,可能这在深度学习方面还是不错的,不过如果了解过CUDA的人,会对环境配置就感到困惑,比如我,所以我还是倾向于采用D3D提供的并行计算来进行通用计算的设计。
学习这个部分你需要对计算着色器有一定的了解,不了的话可以去看我第十四篇文章,至少了解到启动计算着色器:
mCommandList->Dispatch(x, y, z);
因为本篇我们就主要面对数据的输入输出(CUDA中是直接使用内存开辟函数来进行的)。
一、计算数据输入
数据与资源的准备
std::vector<Data> dataA(NumDataElements);
std::vector<Data> dataB(NumDataElements);//两组输入数据
for(int i = 0; i < NumDataElements; ++i)
{
dataA[i].=为数据赋值;
dataB[i].=为数据赋值;
}
UINT64 byteSize = dataA.size()*sizeof(Data);
// 数据通过上传堆存储于默认堆
mInputBufferA = d3dUtil::CreateDefaultBuffer(
md3dDevice.Get(),
mCommandList.Get(),
dataA.data(),
byteSize,
mInputUploadBufferA);
mInputBufferB = d3dUtil::CreateDefaultBuffer(
md3dDevice.Get(),
mCommandList.Get(),
dataB.data(),
byteSize,
mInputUploadBufferB);
// 使用UAV创建可以写数据的资源
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS),
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,//资源状态
nullptr,
IID_PPV_ARGS(&mOutputBuffer)));
//为拷贝数据准备的回读堆
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_READBACK),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(byteSize),
D3D12_RESOURCE_STATE_COPY_DEST,//资源状态
nullptr,
IID_PPV_ARGS(&mReadBackBuffer)));
根签名
CD3DX12_ROOT_PARAMETER slotRootParameter[3];
// 以根描述符来设置资源与寄存器槽的关联
slotRootParameter[0].InitAsShaderResourceView(0);
slotRootParameter[1].InitAsShaderResourceView(1);
slotRootParameter[2].InitAsUnorderedAccessView(0);
ComputPSO设置
与计算着色器部分一致
D3D12_COMPUTE_PIPELINE_STATE_DESC computePsoDesc = {};
computePsoDesc.pRootSignature = mRootSignature.Get();
computePsoDesc.CS =
{
reinterpret_cast<BYTE*>(mShaders["vecAddCS"]->GetBufferPointer()),
mShaders["vecAddCS"]->GetBufferSize()
};
computePsoDesc.Flags = D3D12_PIPELINE_STATE_FLAG_NONE;
ThrowIfFailed(md3dDevice->CreateComputePipelineState(&computePsoDesc, IID_PPV_ARGS(&mPSOs["vecAdd"])));
将资源绑定到流水线
设置寄存器槽与默认堆的对应关系,进行数据的填充
mCommandList->SetComputeRootShaderResourceView(0, mInputBufferA->GetGPUVirtualAddress());
mCommandList->SetComputeRootShaderResourceView(1, mInputBufferB->GetGPUVirtualAddress());
mCommandList->SetComputeRootUnorderedAccessView(2, mOutputBuffer->GetGPUVirtualAddress());
对应到shader中为:
StructuredBuffer<Data> gInputA : register(t0);
StructuredBuffer<Data> gInputB : register(t1);
RWStructuredBuffer<Data> gOutput : register(u0);
启动计算着色器
mCommandList->Dispatch(1, 1, 1);//根据需要设置
对应计算着色器
//描述的是一个组内的设计,即一个数据并行算法的设计
[numthreads(32, 1, 1)]//32组数据一起执行相同的计算过程
void CS(int3 dtid : SV_DispatchThreadID)
{
gOutput[dtid.x = 计算逻辑;
}
计算完成后,数据全部进入gOutput,这是一个UAV。
二、数据的输出
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mOutputBuffer.Get(),
D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE));
mCommandList->CopyResource(mReadBackBuffer.Get(), mOutputBuffer.Get());
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mOutputBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COMMON));
1.转换资源状态为拷贝数据的资源;
2.使用CopyResource实现将资源从GPU拷贝到回读堆
3.将回读堆数据映射到一个数组mappedData ,则可从数组取数据
Data* mappedData = nullptr;
ThrowIfFailed(mReadBackBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mappedData)));
std::ofstream fout("results.txt");
for(int i = 0; i < NumDataElements; ++i)
{
fout << "(" << mappedData[i].v1.x << ", " << mappedData[i].v1.y << ", " << mappedData[i].v1.z <<
", " << mappedData[i].v2.x << ", " << mappedData[i].v2.y << ")" << std::endl;
}
mReadBackBuffer->Unmap(0, nullptr);
综上实现了数据传入,计算,传出。
建立这么一个框架之后,实际需要我们进行编程的地方就是标红部分。是不是既简单又通用。