Как синхронизировать CPU и GPU, используя забор в DirectX / Direct3D 12? - PullRequest
1 голос
/ 24 октября 2019

Я начинаю изучать Direct3D 12 и испытываю трудности в понимании синхронизации CPU-GPU. Насколько я понимаю, забор (ID3D12Fence) - это не более, чем значение UINT64 (unsigned long long), используемое в качестве счетчика. Но его методы смущают меня. Ниже приведена часть исходного кода из примера D3D12. (https://github.com/d3dcoder/d3d12book)

void D3DApp::FlushCommandQueue()
{
    // Advance the fence value to mark commands up to this fence point.
    mCurrentFence++;

    // Add an instruction to the command queue to set a new fence point.  Because we 
    // are on the GPU timeline, the new fence point won't be set until the GPU finishes
    // processing all the commands prior to this Signal().
    ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence));

    // Wait until the GPU has completed commands up to this fence point.
    if(mFence->GetCompletedValue() < mCurrentFence)
    {
        HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);

        // Fire event when GPU hits current fence.  
        ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence, eventHandle));

        // Wait until the GPU hits current fence event is fired.
        WaitForSingleObject(eventHandle, INFINITE);
        CloseHandle(eventHandle);
    }
}

Насколько я понимаю, эта часть пытается «очистить» очередь команд, что в основном заставляет процессор ждатьГрафический процессор, пока он не достигнет заданного «значения Fence», чтобы у CPU и GPU было одинаковое значение ограждения.

Q. Если этот Signal () является функцией, которая позволяет GPU обновлять значение ограждения внутри заданного ID3D12FenceЗачем нужно это значение mCurrentFence?

Согласно Microsoft Doc, в нем говорится «Обновляет забор до указанного значения.» Какое указанное значение? Что мне нужно, это «Получить последнее значение списка завершенных команд». , не установлен или не указан. Для чего это заданное значение?

Мне кажется, оно должно быть похоже на

// Suppose mCurrentFence is 1 after submitting 1 command list (Index 0), and the thread reached to here for the FIRST time
ThrowIfFailed(mCommandQueue->Signal(mFence.Get()));
// At this point Fence value inside mFence is updated
if (m_Fence->GetCompletedValue() < mCurrentFence)
{
...
}

, если m_Fence-> GetCompletedValue () равно 0,

если (0 <1) </p>

GPU не управлял списком команд (Index 0), то CPU должен ждать, пока GPU не последует. Тогда имеет смысл вызвать SetEventOnCompletion, WaitForSingleObject и т. Д.

если (1 <1) </p>

Графический процессор завершил список команд (индекс 0), поэтому процессору не нужно ждать.

Увеличить mCurrentFence где-нибудь, где выполняется список команд.

mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
mCurrentFence++;

1 Ответ

1 голос
/ 27 октября 2019

mCommandQueue->Signal(mFence.Get(), mCurrentFence) устанавливает значение ограждения на mCurrentFence, как только все ранее поставленные в очередь команды в очереди команд были выполнены. В этом случае «указанным значением» является mCurrentFence.

При запуске оба значения fence и mCurrentFence устанавливаются в 0. Далее mCurrentFence устанавливается в 1. Затем мы делаем mCommandQueue->Signal(mFence.Get(), 1)который устанавливает забор на 1, как только все будет выполнено в этой очереди. Наконец, мы вызываем mFence->SetEventOnCompletion(1, eventHandle), а затем WaitForSingleObject, чтобы подождать, пока ограничитель не будет установлен на 1.

Замените 1 на 2 для следующей итерации и т. Д.

Обратите внимание, что mCommandQueue->Signalявляется неблокирующей операцией и не сразу устанавливает значение забора, только после того, как все другие команды gpu были выполнены. Вы можете предположить, что m_Fence->GetCompletedValue() < mCurrentFence всегда истинно в этом примере.

почему это значение mCurrentFence необходимо?

Я полагаю, что это не обязательно необходимо, но вы избегаетедополнительный вызов API, отслеживая значение забора таким образом. В этом случае вы также можете сделать:

// retrieve last value of the fence and increment by one (Additional API call)
auto nextFence = mFence->GetCompletedValue() + 1;
ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), nextFence));

// Wait until the GPU has completed commands up to this fence point.
if(mFence->GetCompletedValue() < nextFence)
{
    HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);  
    ThrowIfFailed(mFence->SetEventOnCompletion(nextFence, eventHandle));
    WaitForSingleObject(eventHandle, INFINITE);
    CloseHandle(eventHandle);
}
...