Плавный deltaTime и GPU

PC разработчики почему то, часто забывают или не интересуются как работает DirectX изнутри. Типичный пример: большинство думает, что Present блокирующий вызов и приводит к синхронизации. На самом же деле Present фактически просто добавляет в PushBuffer команду которая делает flip для отображаемой на экране GPU памяти, но это в том случае, если все хорошо.

Если же CPU заполняет PushBuffer, быстрее чем GPU успевает его исполнять, DX Runtime некоторое время (2 кадра) продолжает класть все команды в PushBuffer надеясь, что GPU успеет или CPU будет поменьше данных давать.

После того, как GPU “отстал” от CPU на пару кадров происходит ожидание GPU при этом CPU простаивает (stall frame). Это очень неприятно, т.к. deltaTime становится крайне неравномерным и брюква становится уже не той (см.лекцию: Баткин Борис. О некоторых особенностях приготовления брюквы.)

В случае проблем график deltaTime типично принимает вид, но может и отличаться (90% случаев на практике, график был именно такой)

Возникает вопрос: как же это забороть ? Забороть совсем нельзя (зоопарк конфигураций, CPU с GPU могут отличаться на порядки по загруженности/производительности), можно уменьшить вред, от этого эффекта, для этого нужно сделать выхов Present синхронизирующим. Что бы получить следующую картину:

При использовании DX9 можно использовать механизм Query, в предыдущих DX Query отсутствуют, но можно использовать Lock/Unlock бэфбуффера.

Код для DirectX9:

...Init...

D3D()->CreateQuery(D3DQUERYTYPE_EVENT, &pEventQuery);

...Frame...

HRESULT issueResult = &pEventQuery->Issue(D3DISSUE_END);
if (issueResult != D3DERR_DEVICELOST)
{
 HRESULT queryResult = &pEventQuery->GetData( NULL, 0, D3DGETDATA_FLUSH );
 if (queryResult == S_FALSE)
 {
   for (;;)
   {
     queryResult = &pEventQuery->GetData( NULL, 0, D3DGETDATA_FLUSH);
     if (queryResult != S_FALSE)
     {
       break;
     }

    //Тут делаем, что хотим - видеокарта не успевает,
    //CPU время можно тратить
    Sleep(0);
   }
 }
}

D3D()->Present();
_Winnie C++ Colorizer

Код для DirectX8:

D3DLOCKED_RECT lockedRect;
d3d8BackBufferSurface->LockRect(&lockedRect, NULL, D3DLOCK_READONLY );
d3d8BackBufferSurface->UnlockRect();

D3D()->Present();
_Winnie C++ Colorizer

Код для DirectX7:

DDSURFACEDESC2 surfaceDesc;
sDesc.dwSize=sizeof(surfaceDesc);
d3d7BackBuffer->Lock(NULL, &surfaceDesc, DDLOCK_WAIT, NULL);
d3d7BackBuffer->Unlock(NULL);

D3D()->Present();
_Winnie C++ Colorizer

Leave a comment

Your comment