2017-05-04 3 views
4

Je configure une application DX12 qui efface uniquement le backbuffer à chaque image.DXGI Waitable SwapChain n'attend pas

Il est vraiment barebone: pas PSO, pas de racine ... La seule particularité est qu'il attend sur le swapChain à faire avec le présent() avant de commencer un nouveau cadre (msdn waitable swap chain) (je mets le temps d'attente de trame à 1 aussi bien que sur seulement 2 tampons).

La première trame fonctionne bien mais elle commence immédiatement à dessiner la seconde trame, et bien sûr, l'allocateur de commandes se plaint de la réinitialisation alors que les commandes sont encore en cours d'exécution sur le GPU.

Je pourrais bien sûr installer une clôture pour attendre que l'unité centrale soit exécutée avant de passer à une nouvelle trame, mais je pensais que c'était le travail de l'objet chaîne d'attente attendue.

Voici le rendu de routine:

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(); 

boucle I sur cette routine avec un wich objet waitable je suis arrivé du swapchain:

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

et j'initialisés la swapchain avec le waitable indicateur:

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)); 

J'appelle le SetFrameLatency droit après la création du swapChain:

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(); 

Et le swapChain redimensionne qui va avec:

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)); 

Ma question est: je suis en train de quelque chose de manière incorrecte? ou est-ce la façon dont la chaîne d'attente fonctionne (c'est-à-dire que vous devez également synchroniser avec gpu avec des clôtures avant d'attendre que la chaîne d'échange devienne disponible)?

EDIT: Ajout d'appel SetFrameLatency + C++ coloration

+0

Votre code semble être bien, mais on ne sait pas si vous avez appelé réellement 'GetFrameLatencyWaitableObject 'pour obtenir' waitable_renderer'. – VTT

+0

J'ai modifié ma question en fonction de votre suggestion et de votre commentaire. –

Répondre

1

La chaîne d'échange waitable est indépendante des travaux de protection contre un objet de d3d12 à modifier ou réinitialiser pendant son utilisation par le processeur graphique.

Une chaîne d'attente attendue vous permet de déplacer l'attente de la fin de la trame dans Présenter au début de la trame avec un objet attendu. Il a l'avantage de lutter contre la latence et de donner plus de contrôle sur les files d'attente.

L'objet de clôture vous permet d'interroger le GPU pour qu'il soit terminé. Je vous recommande de ne pas croiser les doigts comme si cela fonctionnait un jour sur un système, il peut ne pas fonctionner le prochain avec un pilote différent ou une machine différente. Parce que vous ne voulez pas que chaque trame attende la fin du GPU, vous devez créer plusieurs allocateurs de commande, en général, créer un nombre de min(maxlatency+1,swapchain buffer count) mais pour des raisons de sécurité j'utilise personnellement back buffer count + 1..3. Vous découvrirez plus tard que vous allez créer beaucoup plus d'allocateurs pour gérer le multi-thread de toute façon.

Qu'est-ce que cela signifie pour votre code:

  1. Créer plusieurs assignateurs dans un tampon circulaire avec une valeur de clôture associée
  2. Créer une clôture (et une clôture mondiale valeur suivante)
  3. chaîne échange d'attente
  4. sélection située à côté allocateur
  5. si fence.GetCompletedValue() < allocator.fenceValue puis WaitCompletion
  6. rendu
  7. le signal
  8. la clôture avec la file d'attente de commande, stocker la valeur de clôture à allocateur et incrémenter
  9. Saut à 3
+0

Sous quelles conditions le rendu peut-il être complété et le cadre présenté mais la clôture d'achèvement ne se déclenche pas? – VTT

+0

L'objet d'attente de la chaîne d'échange vous indique qu'un tampon est prêt à être rempli, vous n'avez aucune hypothèse sur le tampon précédent que vous avez essayé de remplir, car vous n'utilisez qu'un seul allocateur, la première fois que vous attendez tampons et un n'était pas encore utilisé), mais parce que le gpu peut encore fonctionner sur le premier, vous ne pouvez pas réinitialiser l'allocateur. Pour le réutiliser, vous devez d'abord attendre sur une clôture marquant la fin du rendu, mais comme vous ne voulez pas sérialiser cpu et gpu, vous créez plusieurs allocateurs. – galop1n

+0

En outre, vous pouvez voir dans dx12 plus de modèles qui peuvent détacher votre allocateur des backbuffers swapchain, si vous réutilisez des bundles, si vous utilisez async dans une file d'attente séparée, ... – galop1n