From 62f765b2b45e8c332c5b9f6fd337ec8ceb39da6d Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 19 Aug 2018 21:53:39 -0700 Subject: [PATCH] Switch DXVA2 to IDirect3D9Ex APIs for more detailed control of rendering --- app/app.pro | 2 +- .../video/ffmpeg-renderers/dxva2.cpp | 167 +++++++++++++----- app/streaming/video/ffmpeg-renderers/dxva2.h | 5 +- .../ffmpeg-renderers/pacer/dxvsyncsource.cpp | 8 +- 4 files changed, 123 insertions(+), 59 deletions(-) diff --git a/app/app.pro b/app/app.pro index 43d3df28..f55d9374 100644 --- a/app/app.pro +++ b/app/app.pro @@ -34,7 +34,7 @@ win32 { LIBS += -L$$PWD/../libs/windows/lib/x64 } - LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib + LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib d3d9.lib dwmapi.lib } macx { INCLUDEPATH += $$PWD/../libs/mac/include $$PWD/../libs/mac/Frameworks/SDL2.framework/Versions/A/Headers diff --git a/app/streaming/video/ffmpeg-renderers/dxva2.cpp b/app/streaming/video/ffmpeg-renderers/dxva2.cpp index 7e254071..6145b71c 100644 --- a/app/streaming/video/ffmpeg-renderers/dxva2.cpp +++ b/app/streaming/video/ffmpeg-renderers/dxva2.cpp @@ -3,6 +3,9 @@ #include "../ffmpeg.h" #include +#include +#include + #include DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68,0x4951,0x4C54,0x88,0xFE,0xAB,0xD2,0x5C,0x15,0xB3,0xD6); @@ -10,7 +13,6 @@ DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68,0x4951,0x4C54,0x88,0xFE,0xAB,0x #define SAFE_COM_RELEASE(x) if (x) { (x)->Release(); } DXVA2Renderer::DXVA2Renderer() : - m_SdlRenderer(nullptr), m_DecService(nullptr), m_Decoder(nullptr), m_SurfacesUsed(0), @@ -41,10 +43,6 @@ DXVA2Renderer::~DXVA2Renderer() if (m_Pool != nullptr) { av_buffer_pool_uninit(&m_Pool); } - - if (m_SdlRenderer != nullptr) { - SDL_DestroyRenderer(m_SdlRenderer); - } } void DXVA2Renderer::ffPoolDummyDelete(void*, uint8_t*) @@ -430,38 +428,108 @@ bool DXVA2Renderer::isDecoderBlacklisted() return result; } +bool DXVA2Renderer::initializeDevice(SDL_Window* window) +{ + SDL_SysWMinfo info; + + SDL_VERSION(&info.version); + SDL_GetWindowWMInfo(window, &info); + + IDirect3D9Ex* d3d9ex; + HRESULT hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d9ex); + if (FAILED(hr)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Direct3DCreate9Ex() failed: %x", + hr); + return false; + } + + int adapterIndex = SDL_Direct3D9GetAdapterIndex(SDL_GetWindowDisplayIndex(window)); + Uint32 windowFlags = SDL_GetWindowFlags(window); + + D3DDISPLAYMODEEX currentMode; + currentMode.Size = sizeof(currentMode); + d3d9ex->GetAdapterDisplayModeEx(adapterIndex, ¤tMode, nullptr); + + D3DPRESENT_PARAMETERS d3dpp = {}; + d3dpp.hDeviceWindow = info.info.win.window; + d3dpp.BackBufferCount = 1; + d3dpp.Flags = D3DPRESENTFLAG_VIDEO; + d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; + + if ((windowFlags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) { + d3dpp.Windowed = false; + d3dpp.BackBufferWidth = currentMode.Width; + d3dpp.BackBufferHeight = currentMode.Height; + d3dpp.FullScreen_RefreshRateInHz = currentMode.RefreshRate; + d3dpp.BackBufferFormat = currentMode.Format; + } + else { + d3dpp.Windowed = true; + d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; + + SDL_GetWindowSize(window, (int*)&d3dpp.BackBufferWidth, (int*)&d3dpp.BackBufferHeight); + } + + BOOL dwmEnabled; + DwmIsCompositionEnabled(&dwmEnabled); + if (d3dpp.Windowed && dwmEnabled) { + // If composition enabled, disable v-sync and let DWM manage things + // to reduce latency by avoiding double v-syncing. + d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + + // Use FlipEx mode if DWM is running to increase efficiency + d3dpp.SwapEffect = D3DSWAPEFFECT_FLIPEX; + + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Windowed mode with DWM running"); + } + else { + // Uncomposited desktop or full-screen exclusive mode + d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; + + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "V-Sync enabled"); + } + + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Windowed: %d | Present Interval: %d", + d3dpp.Windowed, d3dpp.PresentationInterval); + + hr = d3d9ex->CreateDeviceEx(adapterIndex, + D3DDEVTYPE_HAL, + d3dpp.hDeviceWindow, + D3DCREATE_MULTITHREADED | D3DCREATE_SOFTWARE_VERTEXPROCESSING, + &d3dpp, + d3dpp.Windowed ? nullptr : ¤tMode, + &m_Device); + + d3d9ex->Release(); + + if (FAILED(hr)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "CreateDeviceEx() failed: %x", + hr); + return false; + } + + hr = m_Device->SetMaximumFrameLatency(1); + if (FAILED(hr)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "SetMaximumFrameLatency() failed: %x", + hr); + return false; + } + + return true; +} + bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, int height, int) { m_VideoFormat = videoFormat; m_VideoWidth = width; m_VideoHeight = height; - // FFmpeg will be decoding on different threads than the main thread that we're - // currently running on right now. We must set this hint so SDL will pass - // D3DCREATE_MULTITHREADED to IDirect3D9::CreateDevice(). - SDL_SetHint(SDL_HINT_RENDER_DIRECT3D_THREADSAFE, "1"); - - m_SdlRenderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); - if (!m_SdlRenderer) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "SDL_CreateRenderer() failed: %s", - SDL_GetError()); - return false; - } - - m_Device = SDL_RenderGetD3D9Device(m_SdlRenderer); - if (m_Device == nullptr) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "SDL_RenderGetD3D9Device() failed: %s", - SDL_GetError()); - return false; - } - - // Draw a black frame until the video stream starts rendering - SDL_SetRenderDrawColor(m_SdlRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE); - SDL_RenderClear(m_SdlRenderer); - SDL_RenderPresent(m_SdlRenderer); - RtlZeroMemory(&m_Desc, sizeof(m_Desc)); int alignment; @@ -485,6 +553,10 @@ bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, i m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame; m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2'); + if (!initializeDevice(window)) { + return false; + } + if (!initializeDecoder()) { return false; } @@ -639,30 +711,27 @@ void DXVA2Renderer::renderFrameAtVsync(AVFrame *frame) bltParams.Alpha = DXVA2_Fixed32OpaqueAlpha(); - if (SDL_RenderClear(m_SdlRenderer) != 0) { - SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, - "SDL_RenderClear() failed: %s", - SDL_GetError()); - - // We're going to cheat a little bit here. It seems SDL's - // renderer may flake out in scenarios like moving the window - // between monitors, so generate a synthetic reset event for - // the main loop to consume. - SDL_Event event; - event.type = SDL_RENDER_TARGETS_RESET; - SDL_PushEvent(&event); - - return; - } + m_Device->Clear(0, nullptr, D3DCLEAR_TARGET, D3DCOLOR_ARGB(255, 0, 0, 0), 0.0f, 0); hr = m_Processor->VideoProcessBlt(m_RenderTarget, &bltParams, &sample, 1, nullptr); if (FAILED(hr)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "VideoProcessBlt() failed: %x", hr); + SDL_Event event; + event.type = SDL_RENDER_TARGETS_RESET; + SDL_PushEvent(&event); + return; } - // We must try to present to trigger SDL's logic to recover the render target, - // even if VideoProcessBlt() fails. - SDL_RenderPresent(m_SdlRenderer); + hr = m_Device->PresentEx(nullptr, nullptr, nullptr, nullptr, 0); + if (FAILED(hr)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "PresentEx() failed: %x", + hr); + SDL_Event event; + event.type = SDL_RENDER_TARGETS_RESET; + SDL_PushEvent(&event); + return; + } } diff --git a/app/streaming/video/ffmpeg-renderers/dxva2.h b/app/streaming/video/ffmpeg-renderers/dxva2.h index b7edceac..458eac56 100644 --- a/app/streaming/video/ffmpeg-renderers/dxva2.h +++ b/app/streaming/video/ffmpeg-renderers/dxva2.h @@ -27,6 +27,7 @@ public: private: bool initializeDecoder(); bool initializeRenderer(); + bool initializeDevice(SDL_Window* window); bool isDecoderBlacklisted(); static @@ -45,8 +46,6 @@ private: int m_DisplayWidth; int m_DisplayHeight; - SDL_Renderer* m_SdlRenderer; - struct dxva_context m_DXVAContext; IDirect3DSurface9* m_DecSurfaces[19]; DXVA2_ConfigPictureDecode m_Config; @@ -55,7 +54,7 @@ private: int m_SurfacesUsed; AVBufferPool* m_Pool; - IDirect3DDevice9* m_Device; + IDirect3DDevice9Ex* m_Device; IDirect3DSurface9* m_RenderTarget; IDirectXVideoProcessorService* m_ProcService; IDirectXVideoProcessor* m_Processor; diff --git a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp index 3f653721..01ee371f 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp +++ b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp @@ -144,6 +144,8 @@ int DxVsyncSource::vsyncThread(void* context) waitForVblankEventParams.hDevice = 0; waitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId; + me->m_Pacer->vsyncCallback(); + status = me->m_D3DKMTWaitForVerticalBlankEvent(&waitForVblankEventParams); if (status != STATUS_SUCCESS) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, @@ -152,12 +154,6 @@ int DxVsyncSource::vsyncThread(void* context) SDL_Delay(10); continue; } - - // Try to delay to the middle of the V-sync period to give the frame - // from the host time to arrive. - SDL_Delay(500 / monitorMode.dmDisplayFrequency); - - me->m_Pacer->vsyncCallback(); } if (openAdapterParams.hAdapter != 0) {