Direct3D 11中是否需显式等待计算着色器执行?
我有一个使用Direct3D 11渲染3D模型的C++程序,通过计算着色器修改顶点缓冲区内容,当前代码可正常运行:
ID3D11Buffer* nullPointer = nullptr; ID3D11Buffer* vertexBuffer = _vertexBuffer.get(); ID3D11UnorderedAccessView* vertexBufferUnorderedAccessView = _vertexBufferUnorderedAccessView.get(); ID3D11UnorderedAccessView* unorderedAccessViewNullptr[1] = { nullptr }; _d3dContext->CSSetShader(_computeShader.get(), nullptr, 0); _d3dContext->IASetVertexBuffers(0, 1, &nullPointer, &_vertexSize, &_offset); _d3dContext->CSSetUnorderedAccessViews(0, 1, &vertexBufferUnorderedAccessView, nullptr); _d3dContext->Dispatch(_threadGroupCount, 1, 1); // Do I need to somehow block here before executing the lines below? _d3dContext->CSSetUnorderedAccessViews(0, 1, unorderedAccessViewNullptr, nullptr); _d3dContext->IASetVertexBuffers(0, 1, &vertexBuffer, &_vertexSize, &_offset);
我不确定调用_d3dContext->Dispatch(_threadGroupCount, 1, 1)后是否需要阻塞CPU线程。我知道这个调用只是触发GPU执行计算着色器,CPU会继续执行后续代码,但计算着色器完成前不应该解绑顶点缓冲区。目前代码运行正常,但在OpenGL ES 3.1中调度计算着色器后需要显式等待:
glUseProgram(_program); // _program contains the compiled compute shader glDispatchCompute(_workGroupCount, 1, 1); // execute the compute shader glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); // block until the compute shader execution is complete
因此想知道Direct3D 11中是否有等效于OpenGL glMemoryBarrier的机制,是否需要在初始代码中添加类似同步操作。
你当前的代码能运行只是巧合,存在潜在风险,Direct3D 11必须通过显式同步机制确保计算着色器的写入完成后,后续图形管线阶段才能安全访问缓冲区。
Direct3D 11中等效于glMemoryBarrier的核心机制
Direct3D 11没有和glMemoryBarrier完全一一对应的API,但有两种针对性的同步方案:
资源状态转换屏障(Resource Barriers)
这是处理这类场景的最优方案(要求D3D11.1及以上版本)。当你从计算着色器的无序访问视图(UAV)写入顶点缓冲区,随后要在输入装配阶段读取该缓冲区时,必须转换资源状态并插入屏障,强制GPU完成之前的写入操作:// Dispatch之后插入资源屏障 D3D11_RESOURCE_BARRIER barrier = {}; barrier.Type = D3D11_RESOURCE_BARRIER_TYPE_TRANSITION; barrier.Flags = D3D11_RESOURCE_BARRIER_FLAG_NONE; barrier.Transition.pResource = _vertexBuffer.get(); barrier.Transition.Subresource = D3D11_RESOURCE_BARRIER_ALL_SUBRESOURCES; barrier.Transition.StateBefore = D3D11_RESOURCE_STATE_UNORDERED_ACCESS; barrier.Transition.StateAfter = D3D11_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER; _d3dContext->ResourceBarrier(1, &barrier);注意:在设置UAV之前,也需要确保顶点缓冲区处于
D3D11_RESOURCE_STATE_UNORDERED_ACCESS状态,可能需要提前插入一次状态转换屏障。Fence同步对象
如果需要强制CPU等待GPU完成计算(不推荐,会阻塞CPU降低性能),可以使用ID3D11Fence:// 初始化阶段创建Fence ID3D11Fence* fence = nullptr; UINT64 fenceValue = 0; _d3dDevice->CreateFence(fenceValue, D3D11_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)); HANDLE fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); // Dispatch之后触发同步 _d3dContext->Signal(fence, ++fenceValue); fence->SetEventOnCompletion(fenceValue, fenceEvent); WaitForSingleObject(fenceEvent, INFINITE);
为什么当前代码能正常运行?
这是因为GPU和CPU的执行时序刚好让计算着色器在输入装配阶段读取前完成了写入,但这种“巧合”不可靠。当GPU负载升高、场景复杂度增加时,CPU可能先执行到IASetVertexBuffers,此时计算着色器还未完成写入,会导致顶点数据错误、渲染崩溃等问题。
修正后的代码流程
_d3dContext->Dispatch(_threadGroupCount, 1, 1); // 插入资源屏障,确保计算写入完成并转换资源状态 D3D11_RESOURCE_BARRIER barrier = {}; barrier.Type = D3D11_RESOURCE_BARRIER_TYPE_TRANSITION; barrier.Flags = D3D11_RESOURCE_BARRIER_FLAG_NONE; barrier.Transition.pResource = _vertexBuffer.get(); barrier.Transition.Subresource = D3D11_RESOURCE_BARRIER_ALL_SUBRESOURCES; barrier.Transition.StateBefore = D3D11_RESOURCE_STATE_UNORDERED_ACCESS; barrier.Transition.StateAfter = D3D11_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER; _d3dContext->ResourceBarrier(1, &barrier); // 后续解绑UAV、设置顶点缓冲区 _d3dContext->CSSetUnorderedAccessViews(0, 1, unorderedAccessViewNullptr, nullptr); _d3dContext->IASetVertexBuffers(0, 1, &vertexBuffer, &_vertexSize, &_offset);
内容的提问来源于stack exchange,提问作者ackh

