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

使用CopyResource()+Map()/Unmap()导致性能损耗的解决方案咨询

D3D11 UAV数据读取性能损耗解决方案

问题背景

无额外数据读取逻辑时3D场景FPS约5000,使用CopyResource+Map读取UAV数据后FPS降至2500,性能减半。已确认瓶颈为CPU等待GPU完成4字节数据拷贝,使用D3D11_MAP_FLAG_DO_NOT_WAIT时Map()持续返回DXGI_ERROR_WAS_STILL_DRAWING。

核心原因

GPU与CPU异步执行,调用CopyResource后GPU尚未完成数据拷贝,CPU立刻调用Map会进入阻塞等待状态,直接拉长单帧耗时。


解决方案

1. 双缓冲延迟读取(推荐)

通过双缓冲机制让CPU读取上一帧的GPU结果,当前帧的拷贝操作留到下一帧处理,彻底避免CPU等待GPU。

初始化代码修改

// 定义双缓冲用的staging buffer数组
ID3D11Buffer* outputResultBuffer[2] = { nullptr, nullptr };
D3D11_BUFFER_DESC outputDesc;

// --- 原outputBuffer初始化逻辑不变 ---
outputDesc.Usage = D3D11_USAGE_DEFAULT;
outputDesc.ByteWidth = sizeof(float);
outputDesc.BindFlags = D3D11_BIND_UNORDERED_ACCESS;
outputDesc.CPUAccessFlags = 0;
outputDesc.StructureByteStride = sizeof(float);
outputDesc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
FOG_TRACE(mDevice->CreateBuffer(&outputDesc, nullptr, &outputBuffer));

// 创建两个staging buffer用于双缓冲
outputDesc.Usage = D3D11_USAGE_STAGING;
outputDesc.BindFlags = 0;
outputDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
FOG_TRACE(mDevice->CreateBuffer(&outputDesc, nullptr, &outputResultBuffer[0]));
FOG_TRACE(mDevice->CreateBuffer(&outputDesc, nullptr, &outputResultBuffer[1]));

// --- 原UAV初始化逻辑不变 ---
D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc{};
uavDesc.Buffer.FirstElement = 0;
uavDesc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_APPEND;
uavDesc.Buffer.NumElements = 1;
uavDesc.Format = DXGI_FORMAT_UNKNOWN;
uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
FOG_TRACE(mDevice->CreateUnorderedAccessView(outputBuffer, &uavDesc, &unorderedAccessView));

// 双缓冲索引标记
int currentBufferIndex = 0;

更新阶段代码修改

const UINT offset = 0;
mDeviceContext->OMSetRenderTargetsAndUnorderedAccessViews(1, &mRenderTargetView, mDepthStencilView, 1, 1, &unorderedAccessView, &offset);
mDeviceContext->ClearDepthStencilView(mDepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

ObjectManager::Draw();

// 切换双缓冲:读取上一帧的buffer,当前帧数据拷贝到新的buffer
int readBufferIndex = currentBufferIndex;
currentBufferIndex = (currentBufferIndex + 1) % 2;

// 拷贝当前帧GPU数据到当前索引的staging buffer
mDeviceContext->CopyResource(outputResultBuffer[currentBufferIndex], outputBuffer);

// 读取上一帧已完成拷贝的buffer(无阻塞)
D3D11_MAPPED_SUBRESOURCE mappedBuffer;
HRESULT hr;
FOG_TRACE(hr = mDeviceContext->Map(outputResultBuffer[readBufferIndex], 0, D3D11_MAP_READ, 0, &mappedBuffer));

if (SUCCEEDED(hr))
{
    float* copy = (float*)(mappedBuffer.pData);
    OutputDebugString(String::ToStr(*copy) + L"\n");
}

mDeviceContext->Unmap(outputResultBuffer[readBufferIndex], 0);

const UINT var[4]{};
mDeviceContext->ClearUnorderedAccessViewUint(unorderedAccessView, var);

2. GPU查询同步(需实时读取当前帧数据时使用)

使用D3D11_QUERY_EVENT查询GPU是否完成拷贝操作,避免CPU无意义空等,同时可在等待间隙执行其他轻量CPU任务。

初始化代码修改

ID3D11Query* copyCompleteQuery = nullptr;
D3D11_QUERY_DESC queryDesc{};
queryDesc.Query = D3D11_QUERY_EVENT;
FOG_TRACE(mDevice->CreateQuery(&queryDesc, &copyCompleteQuery));

// --- 原其他初始化逻辑不变 ---

更新阶段代码修改

// --- 原渲染、Draw逻辑不变 ---
ObjectManager::Draw();

mDeviceContext->CopyResource(outputResultBuffer, outputBuffer);
// 提交查询,标记GPU操作完成节点
mDeviceContext->End(copyCompleteQuery);

// 等待GPU完成拷贝,期间可插入其他CPU任务
while (true)
{
    HRESULT hr = mDeviceContext->GetData(copyCompleteQuery, nullptr, 0, 0);
    if (hr == S_OK)
        break;
    // 示例:执行输入处理、UI逻辑更新等轻量任务
    Input::Update();
}

// 此时GPU已完成拷贝,Map无阻塞
D3D11_MAPPED_SUBRESOURCE mappedBuffer;
HRESULT hr;
FOG_TRACE(hr = mDeviceContext->Map(outputResultBuffer, 0, D3D11_MAP_READ, 0, &mappedBuffer));

if (SUCCEEDED(hr))
{
    float* copy = (float*)(mappedBuffer.pData);
    OutputDebugString(String::ToStr(*copy) + L"\n");
}

mDeviceContext->Unmap(outputResultBuffer, 0);

// --- 原Clear UAV逻辑不变 ---

3. 调整读取时机(并行CPU任务)

将Map/Unmap操作延迟到GPU执行渲染命令的间隙,让CPU在等待GPU的同时处理其他任务,减少空耗:

// 1. 提交所有GPU渲染命令
ObjectManager::Draw();
mDeviceContext->CopyResource(outputResultBuffer, outputBuffer);

// 2. 执行其他CPU任务(如游戏逻辑、UI更新、输入处理)
GameLogic::UpdateFrame();
UI::Render();

// 3. 此时GPU大概率已完成拷贝,执行Map读取
D3D11_MAPPED_SUBRESOURCE mappedBuffer;
HRESULT hr;
FOG_TRACE(hr = mDeviceContext->Map(outputResultBuffer, 0, D3D11_MAP_READ, 0, &mappedBuffer));

// --- 后续读取操作不变 ---

内容的提问来源于stack exchange,提问作者DownloaderGames

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 12:15:48