DXGI 可等待交换链不等待

问题描述 投票:0回答:1

我设置了一个 DX12 应用程序,仅清除每帧的后缓冲区。

它确实是准系统:没有 PSO,没有 root... 唯一的特殊性是,它在启动新帧之前等待交换链上的 Present() 完成(msdn 可等待交换链)(我也将帧延迟设置为 1,并且只有 2 个缓冲区)。

第一帧工作良好,但它立即开始绘制第二帧,当然,命令分配器抱怨它正在重置,而命令仍在 GPU 上执行。

我当然可以设置一个栅栏来等待 GPU 在移动到新框架之前完成,但我认为这是可等待交换链对象的工作。

这是渲染例程:

if (m_command_allocator->Reset() == E_FAIL) { throw; }

HRESULT res = S_OK;
res = m_command_list->Reset(m_command_allocator.Get(), nullptr);
if (res == E_FAIL || res == E_OUTOFMEMORY) { throw; }

m_command_list->ResourceBarrier(1, 
&CD3DX12_RESOURCE_BARRIER::Transition(m_render_targets[m_frame_index].Get(), 
D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));

m_command_list->RSSetViewports(1, &m_screen_viewport);
m_command_list->RSSetScissorRects(1, &m_scissor_rect);
m_command_list->ClearRenderTargetView(get_rtv_handle(), 
DirectX::Colors::BlueViolet, 0, nullptr);
m_command_list->OMSetRenderTargets(1, &get_rtv_handle(), true, nullptr);

m_command_list->ResourceBarrier(1, 
&CD3DX12_RESOURCE_BARRIER::Transition(m_render_targets[m_frame_index].Get(), 
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));

tools::throw_if_failed(m_command_list->Close());
ID3D12CommandList* ppCommandLists[] = { m_command_list.Get() };
m_command_queue->ExecuteCommandLists(_countof(ppCommandLists), 
ppCommandLists);

if (m_swap_chain->Present(1, 0) != S_OK) { throw; }
m_frame_index = m_swap_chain->GetCurrentBackBufferIndex();

我使用从交换链获得的可等待对象循环执行此例程:

while (WAIT_OBJECT_0 == WaitForSingleObjectEx(waitable_renderer, INFINITE, TRUE) && m_alive == true)
{
    m_graphics.render();
}

我用可等待标志初始化了交换链:

DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
swap_chain_desc.BufferCount = s_frame_count;
swap_chain_desc.Width = window_width;
swap_chain_desc.Height = window_height;
swap_chain_desc.Format = m_back_buffer_format;
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swap_chain_desc.SampleDesc.Count = 1;
swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;

ComPtr<IDXGISwapChain1> swap_chain;
tools::throw_if_failed(
    factory->CreateSwapChainForHwnd(m_command_queue.Get(), window_handle, &swap_chain_desc, nullptr, nullptr, &swap_chain));

我在创建交换链后立即调用 SetFrameLatency:

ComPtr<IDXGISwapChain2> swap_chain2;
tools::throw_if_failed(m_swap_chain.As(&swap_chain2));

tools::throw_if_failed(swap_chain2->SetMaximumFrameLatency(1));

m_waitable_renderer = swap_chain2->GetFrameLatencyWaitableObject();

交换链的大小也随之调整:

tools::throw_if_failed(
    m_swap_chain->ResizeBuffers(s_frame_count, window_width, window_height, m_back_buffer_format, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT));

我的问题是:我是否设置错误?或者这就是可等待交换链的工作方式(即,在等待交换链可用之前,您还需要与带有栅栏的 GPU 同步)?

编辑: 添加 SetFrameLatency 调用 + C++ 着色

directx dxgi directx-12
1个回答
3
投票

可等待交换链独立于保护 d3d12 对象在 GPU 仍在使用时被修改或重置的工作。

可等待交换链允许您将等待从 Present 中的帧末尾移动到具有可等待对象的帧开头。它的优点是可以对抗延迟并提供对排队的更多控制。

围栏对象允许您查询 GPU 是否完成。我建议您不要只是交叉手指,好像它有一天在一个系统上工作,它可能无法在下一个系统上使用不同的驱动程序或不同的机器工作。

因为你不希望每一帧都等待GPU完成,所以你必须创建几个命令分配器,通常,创建一个

min(maxlatency+1,swapchain buffer count)
的计数,但为了安全起见,我个人使用
back buffer count + 1..3
。您稍后会发现您将创建更多的分配器来处理多线程。

这对您的代码意味着什么:

  1. 在环形缓冲区中创建多个具有关联栅栏值的分配器
  2. 创建栅栏(和全局栅栏下一个值)
  3. 交换链等待
  4. 选择下一个分配器
  5. 如果栅栏.GetCompletedValue() < allocator.fenceValue then WaitCompletion
  6. 渲染
  7. 使用命令队列向栅栏发出信号,将栅栏值存储到分配器并递增
  8. 跳至3
© www.soinside.com 2019 - 2024. All rights reserved.