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

如何用资源屏障同步RWTexture2D的着色器访问并优化GPU性能?

D3D12资源屏障优化渲染循环性能问题

场景与问题

我有一个始终处于D3D12_RESOURCE_STATE_UNORDERED_ACCESS状态的RWTexture2D<float4> out,以及初始状态为D3D12_RESOURCE_STATE_COPY_SOURCE的RWTexture2D<float4> tex,渲染循环流程如下:

  1. 使用ClearUnorderedAccessViewFloat将out清零
  2. 执行DispatchRays,该操作会读写out
  3. 将tex从D3D12_RESOURCE_STATE_COPY_SOURCE转换为D3D12_RESOURCE_STATE_UNORDERED_ACCESS
  4. 调用计算着色器,转换out的值并写入tex
  5. 将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的状态转换屏障,优化后完整流程:

  1. 使用ClearUnorderedAccessViewFloat清零out
  2. 插入UAV屏障(或新Barrier)同步out
  3. 执行DispatchRays
  4. 插入资源状态转换屏障,将tex从COPY_SOURCE转为UNORDERED_ACCESS
    D3D12_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);
    
  5. 调用计算着色器处理out并写入tex
  6. 插入资源状态转换屏障,将tex从UNORDERED_ACCESS转为COPY_SOURCE
    texBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
    texBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
    
    commandList->ResourceBarrier(1, &texBarrier);
    
  7. 提交命令列表到队列,移除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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 18:23:12