diff --git a/app/app.pro b/app/app.pro
index c707e2b0..4d9e9c52 100644
--- a/app/app.pro
+++ b/app/app.pro
@@ -108,7 +108,7 @@ unix:!macx {
}
}
win32 {
- LIBS += -llibssl -llibcrypto -lSDL2 -lSDL2_ttf -lavcodec -lavutil -lopus
+ LIBS += -llibssl -llibcrypto -lSDL2 -lSDL2_ttf -lavcodec -lavutil -lopus -ldxgi -ld3d11
CONFIG += ffmpeg
}
win32:!winrt {
@@ -300,14 +300,16 @@ win32 {
HEADERS += streaming/video/ffmpeg-renderers/dxutil.h
}
win32:!winrt {
- message(DXVA2 renderer selected)
+ message(DXVA2 and D3D11VA renderers selected)
SOURCES += \
streaming/video/ffmpeg-renderers/dxva2.cpp \
+ streaming/video/ffmpeg-renderers/d3d11va.cpp \
streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp
HEADERS += \
streaming/video/ffmpeg-renderers/dxva2.h \
+ streaming/video/ffmpeg-renderers/d3d11va.h \
streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h
}
macx {
diff --git a/app/resources.qrc b/app/resources.qrc
index 451a72df..453902f7 100644
--- a/app/resources.qrc
+++ b/app/resources.qrc
@@ -59,5 +59,7 @@
shaders/egl_opaque.vert
shaders/egl_overlay.frag
shaders/egl_overlay.vert
+ shaders/d3d11_vertex.fxc
+ shaders/d3d11_overlay_pixel.fxc
diff --git a/app/shaders/build_hlsl.bat b/app/shaders/build_hlsl.bat
new file mode 100644
index 00000000..4c6ebd0d
--- /dev/null
+++ b/app/shaders/build_hlsl.bat
@@ -0,0 +1,3 @@
+fxc /T vs_4_0_level_9_3 /Fo d3d11_vertex.fxc d3d11_vertex.hlsl
+
+fxc /T ps_4_0_level_9_3 /Fo d3d11_overlay_pixel.fxc d3d11_overlay_pixel.hlsl
\ No newline at end of file
diff --git a/app/shaders/d3d11_overlay_pixel.fxc b/app/shaders/d3d11_overlay_pixel.fxc
new file mode 100644
index 00000000..1f2bb61c
Binary files /dev/null and b/app/shaders/d3d11_overlay_pixel.fxc differ
diff --git a/app/shaders/d3d11_overlay_pixel.hlsl b/app/shaders/d3d11_overlay_pixel.hlsl
new file mode 100644
index 00000000..84f00d9e
--- /dev/null
+++ b/app/shaders/d3d11_overlay_pixel.hlsl
@@ -0,0 +1,13 @@
+Texture2D theTexture : register(t0);
+SamplerState theSampler : register(s0);
+
+struct ShaderInput
+{
+ float4 pos : SV_POSITION;
+ float2 tex : TEXCOORD0;
+};
+
+float4 main(ShaderInput input) : SV_TARGET
+{
+ return theTexture.Sample(theSampler, input.tex);
+}
\ No newline at end of file
diff --git a/app/shaders/d3d11_vertex.fxc b/app/shaders/d3d11_vertex.fxc
new file mode 100644
index 00000000..d0867c9a
Binary files /dev/null and b/app/shaders/d3d11_vertex.fxc differ
diff --git a/app/shaders/d3d11_vertex.hlsl b/app/shaders/d3d11_vertex.hlsl
new file mode 100644
index 00000000..fb643c8c
--- /dev/null
+++ b/app/shaders/d3d11_vertex.hlsl
@@ -0,0 +1,19 @@
+struct ShaderInput
+{
+ float2 pos : POSITION;
+ float2 tex : TEXCOORD0;
+};
+
+struct ShaderOutput
+{
+ float4 pos : SV_POSITION;
+ float2 tex : TEXCOORD0;
+};
+
+ShaderOutput main(ShaderInput input)
+{
+ ShaderOutput output;
+ output.pos = float4(input.pos, 0.0f, 1.0f);
+ output.tex = input.tex;
+ return output;
+}
\ No newline at end of file
diff --git a/app/streaming/video/ffmpeg-renderers/d3d11va.cpp b/app/streaming/video/ffmpeg-renderers/d3d11va.cpp
new file mode 100644
index 00000000..4bf90bcc
--- /dev/null
+++ b/app/streaming/video/ffmpeg-renderers/d3d11va.cpp
@@ -0,0 +1,1030 @@
+// For D3D11_DECODER_PROFILE values
+#include
+
+#include "d3d11va.h"
+#include "dxutil.h"
+#include "path.h"
+
+#include "streaming/streamutils.h"
+#include "streaming/session.h"
+
+#include
+#include
+
+#define SAFE_COM_RELEASE(x) if (x) { (x)->Release(); }
+
+typedef struct _VERTEX
+{
+ float x, y;
+ float tu, tv;
+} VERTEX, *PVERTEX;
+
+D3D11VARenderer::D3D11VARenderer()
+ : m_Factory(nullptr),
+ m_Device(nullptr),
+ m_SwapChain(nullptr),
+ m_DeviceContext(nullptr),
+ m_RenderTargetView(nullptr),
+ m_AllowTearing(false),
+ m_FrameWaitableObject(nullptr),
+ m_VideoPixelShader(nullptr),
+ m_OverlayLock(0),
+ m_OverlayPixelShader(nullptr),
+ m_HwContext(nullptr)
+{
+ RtlZeroMemory(m_OverlayVertexBuffers, sizeof(m_OverlayVertexBuffers));
+ RtlZeroMemory(m_OverlayTextures, sizeof(m_OverlayTextures));
+ RtlZeroMemory(m_OverlayTextureResourceViews, sizeof(m_OverlayTextureResourceViews));
+
+ m_ContextLock = SDL_CreateMutex();
+}
+
+D3D11VARenderer::~D3D11VARenderer()
+{
+ SDL_DestroyMutex(m_ContextLock);
+
+ SAFE_COM_RELEASE(m_VideoPixelShader);
+
+ for (int i = 0; i < ARRAYSIZE(m_OverlayVertexBuffers); i++) {
+ SAFE_COM_RELEASE(m_OverlayVertexBuffers[i]);
+ }
+
+ for (int i = 0; i < ARRAYSIZE(m_OverlayTextureResourceViews); i++) {
+ SAFE_COM_RELEASE(m_OverlayTextureResourceViews[i]);
+ }
+
+ for (int i = 0; i < ARRAYSIZE(m_OverlayTextures); i++) {
+ SAFE_COM_RELEASE(m_OverlayTextures[i]);
+ }
+
+ SAFE_COM_RELEASE(m_OverlayPixelShader);
+
+ SAFE_COM_RELEASE(m_RenderTargetView);
+
+ if (m_FrameWaitableObject != nullptr) {
+ CloseHandle(m_FrameWaitableObject);
+ }
+
+ if (m_SwapChain != nullptr) {
+ // It's illegal to destroy a full-screen swapchain. Make sure we're in windowed mode.
+ m_SwapChain->SetFullscreenState(FALSE, nullptr);
+
+ SAFE_COM_RELEASE(m_SwapChain);
+ }
+
+ if (m_HwContext != nullptr) {
+ // This will release m_Device and m_DeviceContext too
+ av_buffer_unref(&m_HwContext);
+ }
+ else {
+ SAFE_COM_RELEASE(m_Device);
+ SAFE_COM_RELEASE(m_DeviceContext);
+ }
+
+ SAFE_COM_RELEASE(m_Factory);
+}
+
+bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
+{
+ int adapterIndex, outputIndex;
+ HRESULT hr;
+
+ m_DecoderParams = *params;
+
+ // Use DXVA2 on anything older than Win10, so we don't have to handle a bunch
+ // of legacy Win7/Win8 codepaths in here.
+ if (!IsWindows10OrGreater()) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "D3D11VA renderer is only supported on Windows 10 or later.");
+ return false;
+ }
+
+ if (!SDL_DXGIGetOutputInfo(SDL_GetWindowDisplayIndex(params->window),
+ &adapterIndex, &outputIndex)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "SDL_DXGIGetOutputInfo() failed: %s",
+ SDL_GetError());
+ return false;
+ }
+
+ hr = CreateDXGIFactory(__uuidof(IDXGIFactory5), (void**)&m_Factory);
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "CreateDXGIFactory() failed: %x",
+ hr);
+ return false;
+ }
+
+ IDXGIAdapter* adapter;
+ hr = m_Factory->EnumAdapters(adapterIndex, &adapter);
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "IDXGIFactory::EnumAdapters() failed: %x",
+ hr);
+ return false;
+ }
+
+ hr = D3D11CreateDevice(adapter,
+ D3D_DRIVER_TYPE_UNKNOWN,
+ nullptr,
+ D3D11_CREATE_DEVICE_VIDEO_SUPPORT
+ #ifdef QT_DEBUG
+ | D3D11_CREATE_DEVICE_DEBUG
+ #endif
+ ,
+ nullptr,
+ 0,
+ D3D11_SDK_VERSION,
+ &m_Device,
+ nullptr,
+ &m_DeviceContext);
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "D3D11CreateDevice() failed: %x",
+ hr);
+ adapter->Release();
+ return false;
+ }
+
+ if (!checkDecoderSupport(adapter)) {
+ adapter->Release();
+ return false;
+ }
+
+ adapter->Release();
+ adapter = nullptr;
+
+#if 0
+ m_Windowed = (SDL_GetWindowFlags(params->window) & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN;
+#else
+ // Always use windowed or borderless windowed mode for now. SDL does mode-setting for us
+ // in full-screen exclusive mode, so this actually works out okay.
+ m_Windowed = true;
+#endif
+
+ DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
+ swapChainDesc.Stereo = FALSE;
+ swapChainDesc.SampleDesc.Count = 1;
+ swapChainDesc.SampleDesc.Quality = 0;
+ swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ swapChainDesc.BufferCount = 2;
+ swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
+ swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
+ swapChainDesc.Flags = 0;
+
+ DXGI_SWAP_CHAIN_FULLSCREEN_DESC fullScreenDesc = {};
+
+ if (m_Windowed) {
+ // Use the current window size as the swapchain size
+ SDL_GetWindowSize(params->window, (int*)&swapChainDesc.Width, (int*)&swapChainDesc.Height);
+ }
+ else {
+ // Use the current display mode as the swapchain size
+ SDL_DisplayMode sdlMode;
+ if (SDL_GetWindowDisplayMode(params->window, &sdlMode) < 0) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "SDL_GetWindowDisplayMode() failed: %s",
+ SDL_GetError());
+ return false;
+ }
+
+ swapChainDesc.Width = sdlMode.w;
+ swapChainDesc.Height = sdlMode.h;
+
+ // FIXME: The SDL referesh rate may not match the actual mode due to truncation
+ fullScreenDesc.RefreshRate.Numerator = sdlMode.refresh_rate;
+ fullScreenDesc.RefreshRate.Denominator = 1;
+
+ fullScreenDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
+ fullScreenDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
+ fullScreenDesc.Windowed = FALSE;
+ }
+
+ m_DisplayWidth = swapChainDesc.Width;
+ m_DisplayHeight = swapChainDesc.Height;
+
+ if (params->videoFormat == VIDEO_FORMAT_H265_MAIN10) {
+ swapChainDesc.Format = DXGI_FORMAT_R10G10B10A2_UNORM;
+ }
+ else {
+ swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+ }
+
+ // Use DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING with flip mode for non-vsync case, if possible
+ if (!params->enableVsync) {
+ // DXGI_PRESENT_ALLOW_TEARING may only be used in windowed mode
+ if (m_Windowed) {
+ BOOL allowTearing = FALSE;
+ hr = m_Factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING,
+ &allowTearing,
+ sizeof(allowTearing));
+ if (SUCCEEDED(hr)) {
+ // Use flip discard with allow tearing mode if possible.
+ swapChainDesc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
+ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
+ m_AllowTearing = true;
+ }
+ else {
+ SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
+ "GPU driver doesn't support DXGI_FEATURE_PRESENT_ALLOW_TEARING");
+
+ // Without tearing support, we'll have to use regular discard mode to get tearing
+ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
+ }
+ }
+ else {
+ // In full-screen exclusive mode, we'll have to use regular discard mode
+ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
+ }
+ }
+ else {
+ // In V-sync mode, we can always use flip discard
+ swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
+
+ // We'll use a waitable swapchain to ensure we get the lowest possible latency.
+ // NB: We can only use this option in windowed mode.
+ if (m_Windowed) {
+ swapChainDesc.Flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
+ }
+ }
+
+ SDL_SysWMinfo info;
+ SDL_VERSION(&info.version);
+ SDL_GetWindowWMInfo(params->window, &info);
+ SDL_assert(info.subsystem == SDL_SYSWM_WINDOWS);
+
+ IDXGISwapChain1* swapChain;
+ hr = m_Factory->CreateSwapChainForHwnd(m_Device,
+ info.info.win.window,
+ &swapChainDesc,
+ m_Windowed ? nullptr : &fullScreenDesc,
+ nullptr,
+ &swapChain);
+
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "IDXGIFactory::CreateSwapChainForHwnd() failed: %x",
+ hr);
+ return false;
+ }
+
+ hr = swapChain->QueryInterface(__uuidof(IDXGISwapChain4), (void**)&m_SwapChain);
+ swapChain->Release();
+
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "IDXGISwapChain::QueryInterface(IDXGISwapChain4) failed: %x",
+ hr);
+ return false;
+ }
+
+ // Disable Alt+Enter, PrintScreen, and window message snooping. This makes
+ // it safe to run the renderer on a separate rendering thread rather than
+ // requiring the main (message loop) thread.
+ hr = m_Factory->MakeWindowAssociation(info.info.win.window, DXGI_MWA_NO_WINDOW_CHANGES);
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "IDXGIFactory::MakeWindowAssociation() failed: %x",
+ hr);
+ return false;
+ }
+
+ if (!setupRenderingResources()) {
+ return false;
+ }
+
+ m_HwContext = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA);
+ if (!m_HwContext) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "Failed to allocate D3D11VA context");
+ return false;
+ }
+
+ AVHWDeviceContext* deviceContext = (AVHWDeviceContext*)m_HwContext->data;
+ AVD3D11VADeviceContext* d3d11vaDeviceContext = (AVD3D11VADeviceContext*)deviceContext->hwctx;
+
+ // AVHWDeviceContext takes ownership of these objects
+ d3d11vaDeviceContext->device = m_Device;
+ d3d11vaDeviceContext->device_context = m_DeviceContext;
+
+ // Set lock functions that we will use to synchronize with FFmpeg's usage of our device context
+ d3d11vaDeviceContext->lock = lockContext;
+ d3d11vaDeviceContext->unlock = unlockContext;
+ d3d11vaDeviceContext->lock_ctx = this;
+
+ int err = av_hwdevice_ctx_init(m_HwContext);
+ if (err < 0) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "Failed to initialize D3D11VA context: %d",
+ err);
+ return false;
+ }
+
+ if (params->enableVsync && m_Windowed) {
+ // We only want one buffered frame on our waitable swapchain
+ hr = m_SwapChain->SetMaximumFrameLatency(1);
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "IDXGISwapChain::SetMaximumFrameLatency() failed: %x",
+ hr);
+ return false;
+ }
+
+ m_FrameWaitableObject = m_SwapChain->GetFrameLatencyWaitableObject();
+ SDL_assert(m_FrameWaitableObject != nullptr);
+
+ // Wait for the swap chain to be ready. This is required because we don't
+ // we're waiting after presenting in the general case, not before.
+ WaitForSingleObjectEx(m_FrameWaitableObject, 1000, FALSE);
+ }
+ else {
+ IDXGIDevice1* dxgiDevice;
+
+ hr = m_Device->QueryInterface(__uuidof(IDXGIDevice1), (void **)&dxgiDevice);
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "ID3D11Device::QueryInterface(IDXGIDevice1) failed: %x",
+ hr);
+ return false;
+ }
+
+ // For the non-vsync case, we won't have a waitable swapchain,
+ // so we must use IDXGIDevice1::SetMaximumFrameLatency() instead.
+ hr = dxgiDevice->SetMaximumFrameLatency(1);
+
+ dxgiDevice->Release();
+
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "IDXGIDevice1::SetMaximumFrameLatency() failed: %x",
+ hr);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool D3D11VARenderer::prepareDecoderContext(AVCodecContext* context, AVDictionary**)
+{
+ context->hw_device_ctx = av_buffer_ref(m_HwContext);
+
+ SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
+ "Using D3D11VA accelerated renderer");
+
+ return true;
+}
+
+void D3D11VARenderer::setHdrMode(bool enabled)
+{
+ HRESULT hr;
+
+ // According to MSDN, we need to lock the context even if we're just using DXGI functions
+ // https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-render-multi-thread-intro
+ lockContext(this);
+
+ if (enabled) {
+ DXGI_HDR_METADATA_HDR10 hdr10Metadata;
+
+ hdr10Metadata.RedPrimary[0] = m_DecoderParams.hdrMetadata.displayPrimaries[0].x;
+ hdr10Metadata.RedPrimary[1] = m_DecoderParams.hdrMetadata.displayPrimaries[0].y;
+ hdr10Metadata.GreenPrimary[0] = m_DecoderParams.hdrMetadata.displayPrimaries[1].x;
+ hdr10Metadata.GreenPrimary[1] = m_DecoderParams.hdrMetadata.displayPrimaries[1].y;
+ hdr10Metadata.BluePrimary[0] = m_DecoderParams.hdrMetadata.displayPrimaries[2].x;
+ hdr10Metadata.BluePrimary[1] = m_DecoderParams.hdrMetadata.displayPrimaries[2].y;
+ hdr10Metadata.WhitePoint[0] = m_DecoderParams.hdrMetadata.whitePoint.x;
+ hdr10Metadata.WhitePoint[1] = m_DecoderParams.hdrMetadata.whitePoint.y;
+ hdr10Metadata.MaxMasteringLuminance = m_DecoderParams.hdrMetadata.maxDisplayMasteringLuminance;
+ hdr10Metadata.MinMasteringLuminance = m_DecoderParams.hdrMetadata.minDisplayMasteringLuminance;
+ hdr10Metadata.MaxContentLightLevel = m_DecoderParams.hdrMetadata.maxContentLightLevel;
+ hdr10Metadata.MaxFrameAverageLightLevel = m_DecoderParams.hdrMetadata.maxFrameAverageLightLevel;
+
+ hr = m_SwapChain->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(hdr10Metadata), &hdr10Metadata);
+ if (SUCCEEDED(hr)) {
+ SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
+ "Set display HDR mode: enabled");
+ }
+ else {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "Failed to enter HDR mode: %x",
+ hr);
+ }
+
+ // Switch to Rec 2020 PQ (SMPTE ST 2084) colorspace for HDR10 rendering
+ hr = m_SwapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "IDXGISwapChain::SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) failed: %x",
+ hr);
+ }
+ }
+ else {
+ // Restore default sRGB colorspace
+ hr = m_SwapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "IDXGISwapChain::SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709) failed: %x",
+ hr);
+ }
+
+ hr = m_SwapChain->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_NONE, 0, nullptr);
+ if (SUCCEEDED(hr)) {
+ SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
+ "Set display HDR mode: disabled");
+ }
+ else {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "Failed to exit HDR mode: %x",
+ hr);
+ }
+ }
+
+ unlockContext(this);
+}
+
+void D3D11VARenderer::renderFrame(AVFrame* frame)
+{
+ D3D11_VIEWPORT viewPort;
+
+ // Acquire the context lock for rendering to prevent concurrent
+ // access from inside FFmpeg's decoding code
+ lockContext(this);
+
+ // Clear the back buffer
+ const float clearColor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+ m_DeviceContext->ClearRenderTargetView(m_RenderTargetView, clearColor);
+
+ // Bind the back buffer. This needs to be done each time,
+ // because the render target view will be unbound by Present().
+ m_DeviceContext->OMSetRenderTargets(1, &m_RenderTargetView, nullptr);
+
+ viewPort.MinDepth = 0;
+ viewPort.MaxDepth = 1;
+
+ // Set the viewport to render the video with aspect ratio scaling
+ SDL_Rect src, dst;
+ src.x = src.y = 0;
+ src.w = m_DecoderParams.width;
+ src.h = m_DecoderParams.height;
+ dst.x = dst.y = 0;
+ dst.w = m_DisplayWidth;
+ dst.h = m_DisplayHeight;
+ StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
+ viewPort.TopLeftX = dst.x;
+ viewPort.TopLeftY = dst.y;
+ viewPort.Width = dst.w;
+ viewPort.Height = dst.h;
+ m_DeviceContext->RSSetViewports(1, &viewPort);
+
+ // TODO: Render video
+
+ // Set the viewport to render overlays at the full window size
+ viewPort.TopLeftX = viewPort.TopLeftY = 0;
+ viewPort.Width = m_DisplayWidth;
+ viewPort.Height = m_DisplayHeight;
+ m_DeviceContext->RSSetViewports(1, &viewPort);
+
+ // Render overlays on top of the video stream
+ for (int i = 0; i < Overlay::OverlayMax; i++) {
+ renderOverlay((Overlay::OverlayType)i);
+ }
+
+ UINT flags;
+ UINT syncInterval;
+
+ if (m_AllowTearing) {
+ SDL_assert(!m_DecoderParams.enableVsync);
+ SDL_assert(m_Windowed);
+
+ // If tearing is allowed, use DXGI_PRESENT_ALLOW_TEARING with syncInterval 0.
+ // It is not valid to use any other syncInterval values in tearing mode.
+ syncInterval = 0;
+ flags = DXGI_PRESENT_ALLOW_TEARING;
+ }
+ else if (!m_DecoderParams.enableVsync) {
+ // In any other non-vsync mode, just render with syncInterval 0.
+ // We'll probably have a non-flip swapchain here.
+ syncInterval = 0;
+ flags = 0;
+ }
+ else if (m_Windowed) {
+ SDL_assert(m_DecoderParams.enableVsync);
+
+ // In windowed mode, we'll have a waitable swapchain, so we can
+ // use syncInterval 0 and the wait will sync us with VBlank.
+ syncInterval = 0;
+ flags = 0;
+ }
+ else {
+ SDL_assert(!m_Windowed);
+ SDL_assert(m_DecoderParams.enableVsync);
+ SDL_assert(m_FrameWaitableObject == nullptr);
+
+ // In full-screen exclusive mode, we won't have waitable swapchain.
+ // We'll use syncInterval 1 to synchronize with VBlank and pass
+ // DXGI_PRESENT_DO_NOT_WAIT for our flags to avoid blocking any
+ // concurrent decoding operations in flight.
+ syncInterval = 1;
+ flags = DXGI_PRESENT_DO_NOT_WAIT;
+ }
+
+ HRESULT hr;
+ do {
+ // Present according to the decoder parameters
+ hr = m_SwapChain->Present(syncInterval, flags);
+ if (hr == DXGI_ERROR_WAS_STILL_DRAWING) {
+ // Unlock the context while we wait to try again
+ unlockContext(this);
+ SDL_Delay(1);
+ lockContext(this);
+ }
+ } while (hr == DXGI_ERROR_WAS_STILL_DRAWING);
+
+ // Release the context lock
+ unlockContext(this);
+
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "IDXGISwapChain::Present() failed: %x",
+ hr);
+
+ // The card may have been removed or crashed. Reset the decoder.
+ SDL_Event event;
+ event.type = SDL_RENDER_TARGETS_RESET;
+ SDL_PushEvent(&event);
+ return;
+ }
+
+ if (m_FrameWaitableObject != nullptr) {
+ SDL_assert(m_Windowed);
+ SDL_assert(m_DecoderParams.enableVsync);
+
+ // Wait for the pipeline to be ready for the next frame in V-Sync mode.
+ //
+ // MSDN advises us to wait *before* doing any rendering operations,
+ // however that assumes the a typical game which will latch inputs,
+ // run the engine, draw, etc. after WaitForSingleObjectEx(). In our case,
+ // we actually want wait *after* our rendering operations, because our AVFrame
+ // is already set in stone by the time we enter this function. Waiting after
+ // presenting allows a more recent frame to be received before renderFrame()
+ // is called again.
+ WaitForSingleObjectEx(m_FrameWaitableObject, 1000, FALSE);
+ }
+}
+
+void D3D11VARenderer::renderOverlay(Overlay::OverlayType type)
+{
+ if (!Session::get()->getOverlayManager().isOverlayEnabled(type)) {
+ return;
+ }
+
+ // If the overlay is being updated, just skip rendering it this frame
+ if (!SDL_AtomicTryLock(&m_OverlayLock)) {
+ return;
+ }
+
+ ID3D11Texture2D* overlayTexture = m_OverlayTextures[type];
+ ID3D11Buffer* overlayVertexBuffer = m_OverlayVertexBuffers[type];
+ ID3D11ShaderResourceView* overlayTextureResourceView = m_OverlayTextureResourceViews[type];
+
+ if (overlayTexture == nullptr) {
+ SDL_AtomicUnlock(&m_OverlayLock);
+ return;
+ }
+
+ // Reference these objects so they don't immediately go away if the
+ // overlay update thread tries to release them.
+ SDL_assert(overlayVertexBuffer != nullptr);
+ overlayTexture->AddRef();
+ overlayVertexBuffer->AddRef();
+ overlayTextureResourceView->AddRef();
+
+ SDL_AtomicUnlock(&m_OverlayLock);
+
+ // Bind vertex buffer and shader
+ UINT stride = sizeof(VERTEX);
+ UINT offset = 0;
+ m_DeviceContext->IASetVertexBuffers(0, 1, &overlayVertexBuffer, &stride, &offset);
+
+ // Bind pixel shader and resources
+ m_DeviceContext->PSSetShader(m_OverlayPixelShader, nullptr, 0);
+ m_DeviceContext->PSSetShaderResources(0, 1, &overlayTextureResourceView);
+
+ // Draw the overlay
+ m_DeviceContext->DrawIndexed(6, 0, 0);
+
+ overlayTextureResourceView->Release();
+ overlayTexture->Release();
+ overlayVertexBuffer->Release();
+}
+
+// This function must NOT use any DXGI or ID3D11DeviceContext methods
+// since it can be called on an arbitrary thread!
+void D3D11VARenderer::notifyOverlayUpdated(Overlay::OverlayType type)
+{
+ HRESULT hr;
+
+ SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type);
+ if (newSurface == nullptr && Session::get()->getOverlayManager().isOverlayEnabled(type)) {
+ // The overlay is enabled and there is no new surface. Leave the old texture alone.
+ return;
+ }
+
+ SDL_AtomicLock(&m_OverlayLock);
+ ID3D11Texture2D* oldTexture = m_OverlayTextures[type];
+ m_OverlayTextures[type] = nullptr;
+
+ ID3D11Buffer* oldVertexBuffer = m_OverlayVertexBuffers[type];
+ m_OverlayVertexBuffers[type] = nullptr;
+
+ ID3D11ShaderResourceView* oldTextureResourceView = m_OverlayTextureResourceViews[type];
+ m_OverlayTextureResourceViews[type] = nullptr;
+ SDL_AtomicUnlock(&m_OverlayLock);
+
+ SAFE_COM_RELEASE(oldTextureResourceView);
+ SAFE_COM_RELEASE(oldTexture);
+ SAFE_COM_RELEASE(oldVertexBuffer);
+
+ // If the overlay is disabled, we're done
+ if (!Session::get()->getOverlayManager().isOverlayEnabled(type)) {
+ SDL_FreeSurface(newSurface);
+ return;
+ }
+
+ // Create a texture with our pixel data
+ SDL_assert(!SDL_MUSTLOCK(newSurface));
+
+ D3D11_TEXTURE2D_DESC texDesc = {};
+ texDesc.Width = newSurface->w;
+ texDesc.Height = newSurface->h;
+ texDesc.MipLevels = 1;
+ texDesc.ArraySize = 1;
+ texDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ texDesc.SampleDesc.Count = 1;
+ texDesc.SampleDesc.Quality = 0;
+ texDesc.Usage = D3D11_USAGE_IMMUTABLE;
+ texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
+ texDesc.CPUAccessFlags = 0;
+ texDesc.MiscFlags = 0;
+
+ D3D11_SUBRESOURCE_DATA texData = {};
+ texData.pSysMem = newSurface->pixels;
+ texData.SysMemPitch = newSurface->pitch;
+
+ ID3D11Texture2D* newTexture;
+ hr = m_Device->CreateTexture2D(&texDesc, &texData, &newTexture);
+ if (FAILED(hr)) {
+ SDL_FreeSurface(newSurface);
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "ID3D11Device::CreateTexture2D() failed: %x",
+ hr);
+ return;
+ }
+
+ ID3D11ShaderResourceView* newTextureResourceView = nullptr;
+ hr = m_Device->CreateShaderResourceView((ID3D11Resource*)newTexture, nullptr, &newTextureResourceView);
+ if (FAILED(hr)) {
+ SAFE_COM_RELEASE(newTexture);
+ SDL_FreeSurface(newSurface);
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "ID3D11Device::CreateShaderResourceView() failed: %x",
+ hr);
+ return;
+ }
+
+ SDL_FRect renderRect = {};
+
+ if (type == Overlay::OverlayStatusUpdate) {
+ // Bottom Left
+ renderRect.x = 0;
+ renderRect.y = 0;
+ }
+ else if (type == Overlay::OverlayDebug) {
+ // Top left
+ renderRect.x = 0;
+ renderRect.y = m_DisplayHeight - newSurface->h;
+ }
+
+ renderRect.w = newSurface->w;
+ renderRect.h = newSurface->h;
+
+ // Convert screen space to normalized device coordinates
+ renderRect.x /= m_DisplayWidth / 2;
+ renderRect.w /= m_DisplayWidth / 2;
+ renderRect.y /= m_DisplayHeight / 2;
+ renderRect.h /= m_DisplayHeight / 2;
+ renderRect.x -= 1.0f;
+ renderRect.y -= 1.0f;
+
+ // The surface is no longer required
+ SDL_FreeSurface(newSurface);
+ newSurface = nullptr;
+
+ VERTEX verts[] =
+ {
+ {renderRect.x, renderRect.y, 0, 1},
+ {renderRect.x, renderRect.y+renderRect.h, 0, 0},
+ {renderRect.x+renderRect.w, renderRect.y, 1, 1},
+ {renderRect.x+renderRect.w, renderRect.y+renderRect.h, 1, 0},
+ };
+
+ D3D11_BUFFER_DESC vbDesc = {};
+ vbDesc.ByteWidth = sizeof(verts);
+ vbDesc.Usage = D3D11_USAGE_IMMUTABLE;
+ vbDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
+ vbDesc.CPUAccessFlags = 0;
+ vbDesc.MiscFlags = 0;
+ vbDesc.StructureByteStride = sizeof(VERTEX);
+
+ D3D11_SUBRESOURCE_DATA vbData = {};
+ vbData.pSysMem = verts;
+
+ ID3D11Buffer* newVertexBuffer;
+ hr = m_Device->CreateBuffer(&vbDesc, &vbData, &newVertexBuffer);
+ if (FAILED(hr)) {
+ SAFE_COM_RELEASE(newTextureResourceView);
+ SAFE_COM_RELEASE(newTexture);
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "ID3D11Device::CreateBuffer() failed: %x",
+ hr);
+ return;
+ }
+
+ SDL_AtomicLock(&m_OverlayLock);
+ m_OverlayVertexBuffers[type] = newVertexBuffer;
+ m_OverlayTextures[type] = newTexture;
+ m_OverlayTextureResourceViews[type] = newTextureResourceView;
+ SDL_AtomicUnlock(&m_OverlayLock);
+}
+
+bool D3D11VARenderer::checkDecoderSupport(IDXGIAdapter* adapter)
+{
+ HRESULT hr;
+ ID3D11VideoDevice* videoDevice;
+
+ // Derive a ID3D11VideoDevice from our ID3D11Device.
+ hr = m_Device->QueryInterface(__uuidof(ID3D11VideoDevice), (void**)&videoDevice);
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "ID3D11Device::QueryInterface(ID3D11VideoDevice) failed: %x",
+ hr);
+ return false;
+ }
+
+ // Check if the format is supported by this decoder
+ BOOL supported;
+ switch (m_DecoderParams.videoFormat)
+ {
+ case VIDEO_FORMAT_H264:
+ if (FAILED(videoDevice->CheckVideoDecoderFormat(&D3D11_DECODER_PROFILE_H264_VLD_NOFGT, DXGI_FORMAT_NV12, &supported))) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "GPU doesn't support H.264 decoding");
+ videoDevice->Release();
+ return false;
+ }
+ else if (!supported) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "GPU doesn't support H.264 decoding to NV12 format");
+ videoDevice->Release();
+ return false;
+ }
+ break;
+
+ case VIDEO_FORMAT_H265:
+ if (FAILED(videoDevice->CheckVideoDecoderFormat(&D3D11_DECODER_PROFILE_HEVC_VLD_MAIN, DXGI_FORMAT_NV12, &supported))) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "GPU doesn't support HEVC decoding");
+ videoDevice->Release();
+ return false;
+ }
+ else if (!supported) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "GPU doesn't support HEVC decoding to NV12 format");
+ videoDevice->Release();
+ return false;
+ }
+ break;
+
+ case VIDEO_FORMAT_H265_MAIN10:
+ if (FAILED(videoDevice->CheckVideoDecoderFormat(&D3D11_DECODER_PROFILE_HEVC_VLD_MAIN10, DXGI_FORMAT_P010, &supported))) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "GPU doesn't support HEVC Main10 decoding");
+ videoDevice->Release();
+ return false;
+ }
+ else if (!supported) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "GPU doesn't support HEVC Main10 decoding to P010 format");
+ videoDevice->Release();
+ return false;
+ }
+ break;
+
+ default:
+ SDL_assert(false);
+ videoDevice->Release();
+ return false;
+ }
+
+ videoDevice->Release();
+
+ DXGI_ADAPTER_DESC adapterDesc;
+ hr = adapter->GetDesc(&adapterDesc);
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "IDXGIAdapter::GetDesc() failed: %x",
+ hr);
+ return false;
+ }
+
+ if (DXUtil::isFormatHybridDecodedByHardware(m_DecoderParams.videoFormat, adapterDesc.VendorId, adapterDesc.DeviceId)) {
+ SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
+ "GPU decoding for format %x is blocked due to hardware limitations",
+ m_DecoderParams.videoFormat);
+ return false;
+ }
+
+ return true;
+}
+
+int D3D11VARenderer::getRendererAttributes()
+{
+ // This renderer supports HDR
+ return RENDERER_ATTRIBUTE_HDR_SUPPORT;
+}
+
+void D3D11VARenderer::lockContext(void *lock_ctx)
+{
+ auto me = (D3D11VARenderer*)lock_ctx;
+
+ SDL_LockMutex(me->m_ContextLock);
+}
+
+void D3D11VARenderer::unlockContext(void *lock_ctx)
+{
+ auto me = (D3D11VARenderer*)lock_ctx;
+
+ SDL_UnlockMutex(me->m_ContextLock);
+}
+
+bool D3D11VARenderer::setupRenderingResources()
+{
+ HRESULT hr;
+
+ m_DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+
+ // We use a common vertex shader for all pixel shaders
+ {
+ QByteArray vertexShaderBytecode = Path::readDataFile("d3d11_vertex.fxc");
+
+ ID3D11VertexShader* vertexShader;
+ hr = m_Device->CreateVertexShader(vertexShaderBytecode.constData(), vertexShaderBytecode.length(), nullptr, &vertexShader);
+ if (SUCCEEDED(hr)) {
+ m_DeviceContext->VSSetShader(vertexShader, nullptr, 0);
+ vertexShader->Release();
+ }
+ else {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "ID3D11Device::CreateVertexShader() failed: %x",
+ hr);
+ return false;
+ }
+
+ const D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
+ {
+ { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 8, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ };
+ ID3D11InputLayout* inputLayout;
+ hr = m_Device->CreateInputLayout(vertexDesc, ARRAYSIZE(vertexDesc), vertexShaderBytecode.constData(), vertexShaderBytecode.length(), &inputLayout);
+ if (SUCCEEDED(hr)) {
+ m_DeviceContext->IASetInputLayout(inputLayout);
+ inputLayout->Release();
+ }
+ else {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "ID3D11Device::CreateInputLayout() failed: %x",
+ hr);
+ return false;
+ }
+ }
+
+ {
+ QByteArray overlayPixelShaderBytecode = Path::readDataFile("d3d11_overlay_pixel.fxc");
+
+ hr = m_Device->CreatePixelShader(overlayPixelShaderBytecode.constData(), overlayPixelShaderBytecode.length(), nullptr, &m_OverlayPixelShader);
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "ID3D11Device::CreateVertexShader() failed: %x",
+ hr);
+ return false;
+ }
+ }
+
+ // We use a common sampler for all pixel shaders
+ {
+ D3D11_SAMPLER_DESC samplerDesc = {};
+ samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
+ samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
+ samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
+ samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
+ samplerDesc.MipLODBias = 0.0f;
+ samplerDesc.MaxAnisotropy = 1;
+ samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
+ samplerDesc.MinLOD = 0.0f;
+ samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;
+
+ ID3D11SamplerState* sampler;
+ hr = m_Device->CreateSamplerState(&samplerDesc, &sampler);
+ if (SUCCEEDED(hr)) {
+ m_DeviceContext->PSSetSamplers(0, 1, &sampler);
+ sampler->Release();
+ }
+ else {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "ID3D11Device::CreateSamplerState() failed: %x",
+ hr);
+ return false;
+ }
+ }
+
+ // Create our render target view
+ {
+ ID3D11Resource* backBufferResource;
+ hr = m_SwapChain->GetBuffer(0, __uuidof(ID3D11Resource), (void**)&backBufferResource);
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "IDXGISwapChain::GetBuffer() failed: %x",
+ hr);
+ return false;
+ }
+
+ hr = m_Device->CreateRenderTargetView(backBufferResource, nullptr, &m_RenderTargetView);
+ backBufferResource->Release();
+ if (FAILED(hr)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "ID3D11Device::CreateRenderTargetView() failed: %x",
+ hr);
+ return false;
+ }
+ }
+
+ // We use a common index buffer for all geometry
+ {
+ const int indexes[] = {0, 1, 2, 3, 2, 1};
+ D3D11_BUFFER_DESC indexBufferDesc = {};
+ indexBufferDesc.ByteWidth = sizeof(indexes);
+ indexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
+ indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
+ indexBufferDesc.CPUAccessFlags = 0;
+ indexBufferDesc.MiscFlags = 0;
+ indexBufferDesc.StructureByteStride = sizeof(int);
+
+ D3D11_SUBRESOURCE_DATA indexBufferData = {};
+ indexBufferData.pSysMem = indexes;
+ indexBufferData.SysMemPitch = sizeof(int);
+
+ ID3D11Buffer* indexBuffer;
+ hr = m_Device->CreateBuffer(&indexBufferDesc, &indexBufferData, &indexBuffer);
+ if (SUCCEEDED(hr)) {
+ m_DeviceContext->IASetIndexBuffer(indexBuffer, DXGI_FORMAT_R32_UINT, 0);
+ indexBuffer->Release();
+ }
+ else {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "ID3D11Device::CreateBuffer() failed: %x",
+ hr);
+ return false;
+ }
+ }
+
+ // Create our blend state
+ {
+ D3D11_BLEND_DESC blendDesc = {};
+ blendDesc.AlphaToCoverageEnable = FALSE;
+ blendDesc.IndependentBlendEnable = FALSE;
+ blendDesc.RenderTarget[0].BlendEnable = TRUE;
+ blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
+ blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
+ blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
+ blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
+ blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
+ blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
+ blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
+
+ ID3D11BlendState* blendState;
+ hr = m_Device->CreateBlendState(&blendDesc, &blendState);
+ if (SUCCEEDED(hr)) {
+ m_DeviceContext->OMSetBlendState(blendState, nullptr, 0xffffffff);
+ blendState->Release();
+ }
+ else {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
+ "ID3D11Device::CreateBlendState() failed: %x",
+ hr);
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/app/streaming/video/ffmpeg-renderers/d3d11va.h b/app/streaming/video/ffmpeg-renderers/d3d11va.h
new file mode 100644
index 00000000..e3528ae2
--- /dev/null
+++ b/app/streaming/video/ffmpeg-renderers/d3d11va.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "renderer.h"
+#include "pacer/pacer.h"
+
+#include
+#include
+
+extern "C" {
+#include
+}
+
+class D3D11VARenderer : public IFFmpegRenderer
+{
+public:
+ D3D11VARenderer();
+ virtual ~D3D11VARenderer() override;
+ virtual bool initialize(PDECODER_PARAMETERS params) override;
+ virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary**) override;
+ virtual void renderFrame(AVFrame* frame) override;
+ virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
+ virtual void setHdrMode(bool enabled) override;
+ virtual int getRendererAttributes() override;
+
+private:
+ static void lockContext(void* lock_ctx);
+ static void unlockContext(void* lock_ctx);
+
+ bool setupRenderingResources();
+ void renderOverlay(Overlay::OverlayType type);
+ bool checkDecoderSupport(IDXGIAdapter* adapter);
+
+ IDXGIFactory5* m_Factory;
+ ID3D11Device* m_Device;
+ IDXGISwapChain4* m_SwapChain;
+ ID3D11DeviceContext* m_DeviceContext;
+ ID3D11RenderTargetView* m_RenderTargetView;
+ SDL_mutex* m_ContextLock;
+
+ DECODER_PARAMETERS m_DecoderParams;
+ int m_DisplayWidth;
+ int m_DisplayHeight;
+ bool m_Windowed;
+
+ bool m_AllowTearing;
+ HANDLE m_FrameWaitableObject;
+ ID3D11PixelShader* m_VideoPixelShader;
+
+ SDL_SpinLock m_OverlayLock;
+ ID3D11Buffer* m_OverlayVertexBuffers[Overlay::OverlayMax];
+ ID3D11Texture2D* m_OverlayTextures[Overlay::OverlayMax];
+ ID3D11ShaderResourceView* m_OverlayTextureResourceViews[Overlay::OverlayMax];
+ ID3D11PixelShader* m_OverlayPixelShader;
+
+ AVBufferRef* m_HwContext;
+};
+
diff --git a/app/streaming/video/ffmpeg-renderers/dxva2.cpp b/app/streaming/video/ffmpeg-renderers/dxva2.cpp
index b658e5c0..19e44547 100644
--- a/app/streaming/video/ffmpeg-renderers/dxva2.cpp
+++ b/app/streaming/video/ffmpeg-renderers/dxva2.cpp
@@ -669,6 +669,12 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
bool DXVA2Renderer::initialize(PDECODER_PARAMETERS params)
{
+ // Don't use DXVA2 for HDR10. While it can render 10-bit color, it doesn't support
+ // the HDR colorspace and HDR display metadata required to enable HDR mode properly.
+ if (params->videoFormat == VIDEO_FORMAT_H265_MAIN10) {
+ return false;
+ }
+
m_VideoFormat = params->videoFormat;
m_VideoWidth = params->width;
m_VideoHeight = params->height;
diff --git a/app/streaming/video/ffmpeg.cpp b/app/streaming/video/ffmpeg.cpp
index 764695a1..88a28bf4 100644
--- a/app/streaming/video/ffmpeg.cpp
+++ b/app/streaming/video/ffmpeg.cpp
@@ -9,6 +9,7 @@
#ifdef Q_OS_WIN32
#include "ffmpeg-renderers/dxva2.h"
+#include "ffmpeg-renderers/d3d11va.h"
#endif
#ifdef Q_OS_DARWIN
@@ -599,8 +600,13 @@ IFFmpegRenderer* FFmpegVideoDecoder::createHwAccelRenderer(const AVCodecHWConfig
if (pass == 0) {
switch (hwDecodeCfg->device_type) {
#ifdef Q_OS_WIN32
+ // DXVA2 appears in the hwaccel list before D3D11VA, so we will implicitly
+ // prefer it. When we want to switch to D3D11VA by default, we'll need to
+ // move it into the second pass set below.
case AV_HWDEVICE_TYPE_DXVA2:
return new DXVA2Renderer();
+ case AV_HWDEVICE_TYPE_D3D11VA:
+ return new D3D11VARenderer();
#endif
#ifdef Q_OS_DARWIN
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX: