DXGI可等待交换链没有等待

6

我设置了一个仅清除后台缓冲的DX12应用程序。

它真的非常简单:没有PSO,没有根...唯一的特点是它在开始新帧之前等待SwapChain完成Present() (msdn waitable swap chain) (我将帧延迟设置为1,并且仅有2个缓冲区)。

第一帧运行正常,但它立即开始绘制第二帧,当然,命令分配器会抱怨正在执行GPU上的命令时被重置。

我当然可以设置一个fence来等待GPU完成后再移动到新的帧,但我认为这是waitable swap chain对象的工作。

以下是渲染例程:

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 ++着色。

你的代码看起来没问题,但不清楚你是否实际调用了 GetFrameLatencyWaitableObject 来获取 waitable_renderer - user7860670
我根据你的建议和评论编辑了我的问题。 - Antoine Reux
1个回答

3
可等待交换链与保护正在使用GPU的d3d12对象不被修改或重置的工作无关。
可等待交换链允许您使用可等待对象将等待从呈现的帧末移动到帧开始。它具有减少延迟和更好地控制排队的优点。
通过对象进行围栏允许您查询GPU完成情况。建议不要只是盼着它能在某个系统上工作,因为在不同的驱动程序或不同的机器上可能无法正常工作。
由于您不希望每一帧都等待GPU完成,因此必须创建多个命令分配器,通常创建一个数量为min(maxlatency+1,swapchain buffer count),但出于安全考虑,我个人使用back buffer count + 1..3。您之后会发现,您需要创建更多的分配器来处理多线程。
对于您的代码意味着什么:
  1. 创建带有关联围栏值的环形缓冲区中的多个分配器
  2. 创建一个围栏(和全局围栏下一个值)
  3. 交换链等待
  4. 选择下一个分配器
  5. 如果fence.GetCompletedValue() < allocator.fenceValue,则等待完成
  6. 渲染
  7. 使用命令队列信号围栏,将围栏值存储到分配器并增加
  8. 跳转到步骤3

在什么情况下,渲染可以完成并呈现帧,但完成栅栏不会触发? - user7860670
交换链等待对象告诉您一个缓冲区已准备好填充,您对之前尝试填充的缓冲区没有任何假设,因为您仅使用一个分配器,第一次等待将是非操作(因为有2个缓冲区,其中一个尚未使用),但由于GPU可能仍在处理第一个缓冲区,因此无法重置分配器。要重用它,您首先需要在标记渲染结束的围栏上等待,但因为您不想序列化CPU和GPU,所以创建了多个分配器。 - galop1n
此外,在DX12中,您可以看到更多模式,可以将分配器与交换链后备缓冲区解绑,如果您重复使用捆绑包,如果您在单独的队列中使用异步计算等。 - galop1n
考虑到后备缓冲区只有在GPU完成处理命令时才会翻转,等待该缓冲区可用也意味着与其“附加”的命令列表已被处理,因此可以重置。但是如果交换链也将前缓冲区视为可用(对于前两帧),那么是的,我需要两个命令分配器(而且不需要GPU同步)。我今天晚些时候会尝试一下 :) - Antoine Reux

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接