如何用资源屏障同步RWTexture2D的着色器访问并优化GPU性能?
场景与问题
我有一个始终处于D3D12_RESOURCE_STATE_UNORDERED_ACCESS状态的RWTexture2D<float4> out,以及初始状态为D3D12_RESOURCE_STATE_COPY_SOURCE的RWTexture2D<float4> tex,渲染循环流程如下:
- 使用
ClearUnorderedAccessViewFloat将out清零 - 执行
DispatchRays,该操作会读写out - 将
tex从D3D12_RESOURCE_STATE_COPY_SOURCE转换为D3D12_RESOURCE_STATE_UNORDERED_ACCESS - 调用计算着色器,转换
out的值并写入tex - 将
tex从D3D12_RESOURCE_STATE_UNORDERED_ACCESS转换为D3D12_RESOURCE_STATE_COPY_SOURCE
目前在步骤1、2之后调用wait_for_gpu等待GPU完成,性能极差,想通过资源屏障优化。试过传统ResourceBarrier的UAV屏障,但不确定是否能保证ClearUnorderedAccessViewFloat完成后再执行DispatchRays;另外不清楚新的Barrier方法该怎么用,尝试指定D3D12_BARRIER_GROUP包含D3D12_TEXTURE_BARRIER,设置SyncBefore = D3D12_BARRIER_SYNC_CLEAR_UNORDERED_ACCESS_VIEW但头文件里没有这个枚举,SyncAfter = D3D12_BARRIER_SYNC_RAYTRACING,求最佳实现方案。
附wait_for_gpu代码:
void wait_for_gpu() { command_queue->Signal(fence, fence_values[frame_index]); fence->SetEventOnCompletion(fence_values[frame_index], fence_event); WaitForSingleObjectEx(fence_event, INFINITE, FALSE); ++d3d.fence_values[frame_index]; }
解决方案
一、传统ResourceBarrier实现(UAV屏障)
你之前尝试的UAV屏障是正确的选择,因为ClearUnorderedAccessViewFloat和DispatchRays都操作同一个UAV资源out,需要通过UAV屏障强制GPU完成所有对该UAV的先前写入操作,之后才允许后续的读写操作,完全能满足Clear完成后再执行光线追踪的同步要求。
正确代码写法:
// 在ClearUnorderedAccessViewFloat之后、DispatchRays之前插入UAV屏障 D3D12_RESOURCE_BARRIER uavBarrier{}; uavBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; uavBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; uavBarrier.UAV.pResource = out; // 你的RWTexture2D资源指针 commandList->ResourceBarrier(1, &uavBarrier);
二、新的Barrier方法(D3D12 1.2+)
如果环境支持Windows 10 1809+的新D3D12屏障系统,可以用更灵活的Barrier API替代。由于头文件缺少D3D12_BARRIER_SYNC_CLEAR_UNORDERED_ACCESS_VIEW,可以用通用同步标记替代:
// 定义纹理屏障 D3D12_TEXTURE_BARRIER textureBarrier{}; textureBarrier.pResource = out; textureBarrier.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; textureBarrier.Flags = D3D12_TEXTURE_BARRIER_FLAG_NONE; // 定义屏障组 D3D12_BARRIER_GROUP barrierGroup{}; barrierGroup.Type = D3D12_BARRIER_TYPE_TEXTURE; barrierGroup.NumBarriers = 1; barrierGroup.pTextureBarriers = &textureBarrier; // 设置同步标记:前序操作是CLEAR,后续是RAYTRACING barrierGroup.SyncBefore = D3D12_BARRIER_SYNC_RENDER_TARGET; // 用RT同步替代,ClearUAV本质是写入UAV,逻辑一致 barrierGroup.SyncAfter = D3D12_BARRIER_SYNC_RAYTRACING; // 插入屏障 commandList->Barrier(1, &barrierGroup);
后续更新头文件到最新版本后,SyncBefore可替换为D3D12_BARRIER_SYNC_CLEAR_UNORDERED_ACCESS_VIEW,同步逻辑更准确。
三、完整渲染循环的同步优化
除了out的同步,还要处理tex的状态转换屏障,优化后完整流程:
- 使用
ClearUnorderedAccessViewFloat清零out - 插入UAV屏障(或新Barrier)同步
out - 执行
DispatchRays - 插入资源状态转换屏障,将
tex从COPY_SOURCE转为UNORDERED_ACCESSD3D12_RESOURCE_BARRIER texBarrier{}; texBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; texBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; texBarrier.Transition.pResource = tex; texBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; texBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; texBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; commandList->ResourceBarrier(1, &texBarrier); - 调用计算着色器处理
out并写入tex - 插入资源状态转换屏障,将
tex从UNORDERED_ACCESS转为COPY_SOURCEtexBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; texBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; commandList->ResourceBarrier(1, &texBarrier); - 提交命令列表到队列,移除
wait_for_gpu调用,改用多帧缓冲+栅栏的帧同步方案避免CPU-GPU阻塞
四、替代wait_for_gpu的帧同步方案
不要每帧等待GPU完成,改用三重缓冲配合栅栏,仅等待最老的一帧完成再复用资源,示例逻辑:
// 初始化:为每个帧分配栅栏值 UINT frameIndex = 0; UINT64 fenceValues[3] = {0, 0, 0}; HANDLE fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); // 渲染循环 void renderFrame() { // 等待当前帧对应的栅栏完成(三重缓冲下仅等待3帧前的任务) UINT64 currentFenceValue = fenceValues[frameIndex]; if (fence->GetCompletedValue() < currentFenceValue) { fence->SetEventOnCompletion(currentFenceValue, fenceEvent); WaitForSingleObjectEx(fenceEvent, INFINITE, FALSE); } // 记录当前帧命令列表(包含所有屏障和渲染操作) recordCommandList(); // 提交命令列表到队列 commandQueue->ExecuteCommandLists(1, &commandList); // 更新栅栏值 fenceValues[frameIndex] = ++currentFenceValue; commandQueue->Signal(fence, fenceValues[frameIndex]); // 切换帧索引 frameIndex = (frameIndex + 1) % 3; }
内容的提问来源于stack exchange,提问作者0xbadf00d

