You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Direct3D 11中是否需显式等待计算着色器执行?

问题:Direct3D 11中计算着色器Dispatch后是否需要同步?

我有一个使用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,但有两种针对性的同步方案:

  1. 资源状态转换屏障(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状态,可能需要提前插入一次状态转换屏障。

  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 01:27:05