Как сделать ровно один рендер для вертикальной синхронизации (без повторов, без пропусков)?

Я пытаюсь сделать вертикальные синхронизированные рендеры, чтобы ровно один рендеринг выполнялся за вертикальную синхронизацию, без пропуска или повторения каких-либо кадров. Мне нужно, чтобы это работало под Windows 7 и (в будущем) Windows 8.

Это в основном состоит из рисования последовательности QUADS это соответствовало бы экрану так, чтобы пиксель из исходных изображений соответствовал 1:1 пикселю на экране. Часть рендеринга не проблема, ни с OpenGL, ни с DirectX. Проблема в правильной синхронизации.

Ранее я пытался использовать OpenGL, с WGL_EXT_swap_control расширение, рисуя и затем вызывая

SwapBuffers(g_hDC);
glFinish();

Я перепробовал все комбинации и перестановки этих двух инструкций вместе с glFlush(), но это было ненадежно.

Затем я попытался с Direct3D 10, рисуя и затем вызывая

g_pSwapChain->Present(1, 0);
pOutput->WaitForVBlank();

где g_pSwapChain это IDXGISwapChain* а также pOutput это IDXGIOutput* связанный с этим SwapChain.

Обе версии, OpenGL и Direct3D, приводят к одному и тому же: первая последовательность, скажем, 60 кадров, не длится дольше, чем должна (вместо 1000 мс при 60 Гц длится примерно 1030 или 1050 мс), кажутся следующие работать нормально (около 1000.40мс), но время от времени кажется, что пропускает кадр. Я делаю измерения с QueryPerformanceCounter,

В Direct3D при попытке выполнить цикл, состоящий всего из WaitForVBlank, длительность 1000 итераций постоянно составляет 1000.40 с небольшими изменениями.

Таким образом, проблема здесь заключается в том, что мы не знаем точно, когда каждая из функций вызывает возврат, и выполняется ли своп во время вертикальной синхронизации (не раньше, чтобы избежать разрывов).

В идеале (если я не ошибаюсь), чтобы достичь того, что я хочу, было бы выполнить один рендеринг, дождаться начала синхронизации, поменять местами во время синхронизации, а затем дождаться окончания синхронизации. Как это сделать с OpenGL или DirectX?

Изменить: тестовый цикл просто WaitForVSync 60x занимает последовательно от 1000.30мс до 1000.50мс. Та же петля с Present(1,0) до WaitForVSyncс ничем иным, без рендеринга, занимает то же время, но иногда происходит сбой и занимает 1017 мс, как если бы он повторял кадр. Там нет рендеринга, так что здесь что-то не так.

6 ответов

У меня такая же проблема в DX11. Я хочу гарантировать, что мой код рендеринга кадров будет в точности кратен частоте обновления монитора, чтобы избежать задержки мультибуферизации.

Просто звоню pSwapChain->present(1,0) недостаточно Это предотвратит разрыв в полноэкранном режиме, но не заставит vblank произойти. Текущий вызов является асинхронным, и он сразу возвращается, если есть еще кадровые буферы, которые еще предстоит заполнить. Таким образом, если ваш код рендеринга генерирует новый кадр очень быстро (скажем, 10 мс для рендеринга всего) и пользователь установил для драйвера "Максимальное количество предварительно отрендеренных фреймов" значение 4, то вы будете рендерить на четыре кадра раньше, чем увидит пользователь. Это означает 4*16,7=67мс задержки между действием мыши и откликом экрана, что недопустимо. Обратите внимание, что настройки драйвера выигрывают - даже если ваше приложение запросило pOutput->setMaximumFrameLatency(1)Вы получите 4 кадра независимо. Таким образом, единственный способ гарантировать отсутствие задержки мыши независимо от настроек драйвера - это чтобы ваш цикл рендеринга добровольно ожидал следующего вертикального интервала обновления, чтобы вы никогда не использовали эти дополнительные frameBuffers.

IDXGIOutput::WaitForVBlank() предназначен для этой цели. Но это не работает! Когда я звоню следующее

<render something in ~10ms>
pSwapChain->present(1,0);
pOutput->waitForVBlank();

и я измеряю время, необходимое для waitForVBlank() звонок, чтобы вернуться, я вижу его чередуются между 6 мс и 22 мс, примерно.

Как это может случиться? Как мог waitForVBlank() когда-либо дольше, чем 16,7 мс для завершения? В DX9 мы решили эту проблему, используя getRasterState() реализовать нашу, гораздо более точную версию waitForVBlank. Но этот вызов устарел в DX11.

Есть ли другой способ гарантировать, что мой кадр точно соответствует частоте обновления монитора? Есть ли другой способ шпионить за текущей линией сканирования, как это делал getRasterState?

Ранее я пытался использовать OpenGL с расширением WGL_EXT_swap_control, рисуя и затем вызывая

SwapBuffers(g_hDC);
glFinish();

Этот glFinish() или glFlush является излишним. SwapBuffers подразумевает glFinish.

Может ли быть так, что в настройках вашего графического драйвера вы установили "V-Blank / V-Sync off"?

В настоящее время мы используем DX9 и хотим перейти на DX11. В настоящее время мы используем GetRasterState() вручную синхронизировать с экраном. В DX11 этого не происходит, но я обнаружил, что создание устройства DirectDraw7, похоже, не нарушает работу DX11. Так что просто добавьте это к своему коду, и вы сможете получить позицию линии сканирования.

IDirectDraw7* ddraw = nullptr;
DirectDrawCreateEx( NULL, reinterpret_cast<LPVOID*>(&ddraw), IID_IDirectDraw7, NULL );
DWORD scanline = -1;
ddraw->GetScanLine( &scanline );

В Windows 8.1 и Windows 10 вы можете использовать DXGI 1.3 DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT, Смотрите MSDN. Пример здесь для приложений Магазина Windows 8, но он также должен быть адаптирован к классам Windows Win Swapchains.

Вы можете также найти это видео полезным.

При создании устройства Direct3D установите PresentationInterval параметр D3DPRESENT_PARAMETERS структура для D3DPRESENT_INTERVAL_DEFAULT,

Если вы работаете в режиме ядра или ring-0, вы можете попытаться прочитать бит 3 из входного регистра VGA (03bah,03dah). Информация довольно старая, но, хотя здесь намекнули, что бит мог изменить местоположение или может быть устаревшим в более поздней версии Windows 2000 и выше, я на самом деле сомневаюсь в этом. Вторая ссылка содержит очень старый исходный код, который пытается раскрыть сигнал vblank для старых версий Windows. Он больше не работает, но теоретически восстановление его с помощью последней версии Windows SDK должно исправить это.

Сложная часть - это создание и регистрация драйвера устройства, который надежно предоставляет эту информацию, а затем извлекает ее из вашего приложения.

Другие вопросы по тегам