Skip to content

Commit ff3b69f

Browse files
committed
Fix Video Super Resolution on systems without Windows 11 D3D12 interfaces
A user on Windows 10 with an RTX 3060 Ti could not stream at all, while RTX 4070 Ti and 5060 Ti machines worked. The GPU was not the cause: the 3060 Ti host was the only one still on Windows 10. D3D12CreateDevice() requested ID3D12Device9, an Agility SDK / Windows 11 interface, and failed with E_NOINTERFACE (0x80004002) since we ship no Agility runtime. ID3D12GraphicsCommandList7 and ID3D12VideoProcessCommandList3 had the same problem and would have failed right after. None of the methods we actually call require those versions, so lower each member to the minimum interface the code uses. ID3D12Device and ID3D12GraphicsCommandList cover every call site; ID3D12VideoProcessCommandList1 provides ProcessFrames1(). m_VideoDevice stays ID3D12VideoDevice2 because CreateVideoProcessor1() lives there. With video enhancement enabled, AV_HWDEVICE_TYPE_D3D11VA fell through to D3D12VARenderer in both passes, so D3D11VA was never instantiated. Once D3D12 failed there was nothing left but libplacebo/Vulkan, turning "upscaler unavailable" into "no video at all". Latch the failure in VideoEnhancement so D3D11VA can take over, and disable enhancement for that session so the stats overlay does not advertise a missing upscaler. Move the D3D12VA_ENABLED and IsWindows10OrGreater() checks ahead of device creation and latch them too, which makes D3D12VA_ENABLED=0 reproduce a system that lacks the required interfaces. The FSR1 hooks were destroyed after pl_vulkan_destroy() and pl_log_destroy(), a use-after-free of the pl_gpu they were parsed on that also leaked their GPU resources on every renderer recreation. Release them while the GPU is still alive. Parse the HDR shader variant only for 10-bit streams, since the SDR hook was previously duplicated for no benefit, and log a parse failure instead of silently rendering without the hook. Video enhancement availability only reflected that FFmpeg advertises a D3D12VA hwaccel, which stays true where the device cannot be created, so the setting looked usable on affected systems. Grey out the checkbox, the mode label and the mode dropdown when D3D12 is out of reach. Finally, log the OS version at startup. Renderer availability depends on it and the logs carried no way to recover it.
1 parent fddd6b8 commit ff3b69f

9 files changed

Lines changed: 134 additions & 46 deletions

File tree

app/backend/systemproperties.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,16 @@ bool SystemProperties::isVideoEnhancementSwitchable()
300300
*
301301
* Check if either Video Super-Resolution features can be used by the GPU.
302302
*
303+
* isAvailable() only reflects that FFmpeg advertises a hwaccel able to carry the
304+
* feature, which stays true on systems where the D3D12 device cannot actually be
305+
* created. On Windows the upscaler lives in the D3D12 renderer (the Vulkan/FSR1
306+
* path is not user-selectable there), so a D3D12 failure means the feature is out
307+
* of reach and the setting must be greyed out.
308+
*
303309
* \return bool Returns true if the GPU is capable
304310
*/
305311
bool SystemProperties::isVideoEnhancementAvailable()
306312
{
307-
return VideoEnhancement::getInstance().isAvailable();
313+
return VideoEnhancement::getInstance().isAvailable() &&
314+
VideoEnhancement::getInstance().isD3D12Available();
308315
}

app/gui/SettingsView.qml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -919,7 +919,11 @@ Flickable {
919919
visible: SystemProperties.isVideoEnhancementSwitchable()
920920
width: parent.width
921921
id: resSuperResolutionModeTitle
922-
text: qsTr("Video Super Resolution Mode<br><i>(Dropdown available only in Debug mode)</br></i>")
922+
enabled: SystemProperties.isVideoEnhancementAvailable()
923+
text: SystemProperties.isVideoEnhancementAvailable() ?
924+
qsTr("Video Super Resolution Mode<br><i>(Dropdown available only in Debug mode)</br></i>")
925+
:
926+
qsTr("Video Super Resolution Mode (unavailable on this system)")
923927
font.pointSize: 12
924928
wrapMode: Text.Wrap
925929
}
@@ -928,6 +932,7 @@ Flickable {
928932
AutoResizingComboBox {
929933

930934
visible: SystemProperties.isVideoEnhancementSwitchable()
935+
enabled: SystemProperties.isVideoEnhancementAvailable()
931936

932937
ToolTip.delay: 1000
933938
ToolTip.timeout: 5000

app/main.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <QElapsedTimer>
1414
#include <QTemporaryFile>
1515
#include <QRegularExpression>
16+
#include <QSysInfo>
1617

1718
#ifdef Q_OS_UNIX
1819
#include <sys/socket.h>
@@ -814,6 +815,13 @@ int main(int argc, char *argv[])
814815
break;
815816
}
816817

818+
// Log the OS we're running on.
819+
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
820+
"Running on %s (%s %s)",
821+
QSysInfo::prettyProductName().toUtf8().constData(),
822+
QSysInfo::kernelType().toUtf8().constData(),
823+
QSysInfo::kernelVersion().toUtf8().constData());
824+
817825
SDL_version compileVersion;
818826
SDL_VERSION(&compileVersion);
819827
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,

app/streaming/video/ffmpeg-renderers/d3d12va.cpp

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2512,31 +2512,42 @@ bool D3D12VARenderer::initialize(PDECODER_PARAMETERS params)
25122512
return false;
25132513
}
25142514

2515+
// Both of these are permanent for the lifetime of the process, so they latch
2516+
// the D3D12 renderer as unavailable to let D3D11VA take over. Setting
2517+
// D3D12VA_ENABLED=0 therefore reproduces exactly what a system without the
2518+
// required D3D12 interfaces does.
2519+
if (qgetenv("D3D12VA_ENABLED") == "0") {
2520+
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
2521+
"D3D12VA is disabled by environment variable");
2522+
m_VideoEnhancement->setD3D12Available(false);
2523+
return false;
2524+
} else if (!IsWindows10OrGreater()) {
2525+
// Use DXVA2 on anything older than Win10, so we don't have to handle a bunch
2526+
// of legacy Win7/Win8 codepaths in here.
2527+
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
2528+
"D3D12VA renderer is only supported on Windows 10 or later.");
2529+
m_VideoEnhancement->setD3D12Available(false);
2530+
return false;
2531+
}
2532+
25152533
// Device creation
25162534
m_hr = D3D12CreateDevice(
25172535
m_Adapter.Get(),
25182536
D3D_FEATURE_LEVEL_12_0,
25192537
IID_PPV_ARGS(&m_Device)
25202538
);
25212539
if(!verifyHResult(m_hr, "D3D12CreateDevice(... m_Device)")){
2540+
// This system cannot host a D3D12 device at all, so there is no point
2541+
// retrying this renderer. Let D3D11VA take over instead of falling all
2542+
// the way through to the Vulkan renderer.
2543+
m_VideoEnhancement->setD3D12Available(false);
25222544
return false;
25232545
}
25242546

25252547
// VideoDevice creation
25262548
m_hr = m_Device.As(&m_VideoDevice);
25272549
if(!verifyHResult(m_hr, "m_Device.As(&m_VideoDevice);")){
2528-
return false;
2529-
}
2530-
2531-
if (qgetenv("D3D12VA_ENABLED") == "0") {
2532-
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
2533-
"D3D12VA is disabled by environment variable");
2534-
return false;
2535-
} else if (!IsWindows10OrGreater()) {
2536-
// Use DXVA2 on anything older than Win10, so we don't have to handle a bunch
2537-
// of legacy Win7/Win8 codepaths in here.
2538-
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
2539-
"D3D12VA renderer is only supported on Windows 10 or later.");
2550+
m_VideoEnhancement->setD3D12Available(false);
25402551
return false;
25412552
}
25422553

app/streaming/video/ffmpeg-renderers/d3d12va.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ class D3D12VARenderer : public QObject, public IFFmpegRenderer
198198
UINT m_AdapterIndex = 0;
199199
ComPtr<IDXGIFactory6> m_Factory;
200200
ComPtr<IDXGIAdapter1> m_Adapter;
201-
ComPtr<ID3D12Device9> m_Device;
201+
ComPtr<ID3D12Device> m_Device;
202202
ComPtr<ID3D12VideoDevice2> m_VideoDevice;
203203
decoder m_Decoder;
204204
bool m_SkipRenderStep2 = false;
@@ -269,13 +269,13 @@ class D3D12VARenderer : public QObject, public IFFmpegRenderer
269269
std::vector<D3D12_VIDEO_PROCESS_OUTPUT_STREAM_ARGUMENTS> m_OutputArgsUpscalerConvert;
270270

271271
ComPtr<ID3D12CommandAllocator> m_VideoProcessCommandAllocator;
272-
ComPtr<ID3D12VideoProcessCommandList3> m_VideoProcessCommandList;
272+
ComPtr<ID3D12VideoProcessCommandList1> m_VideoProcessCommandList;
273273
ComPtr<ID3D12CommandQueue> m_VideoProcessCommandQueue;
274274
ComPtr<ID3D12CommandAllocator> m_GraphicsCommandAllocator;
275-
ComPtr<ID3D12GraphicsCommandList7> m_GraphicsCommandList;
275+
ComPtr<ID3D12GraphicsCommandList> m_GraphicsCommandList;
276276
ComPtr<ID3D12CommandQueue> m_GraphicsCommandQueue;
277277
ComPtr<ID3D12CommandAllocator> m_OverlayCommandAllocator;
278-
ComPtr<ID3D12GraphicsCommandList7> m_OverlayCommandList;
278+
ComPtr<ID3D12GraphicsCommandList> m_OverlayCommandList;
279279
ComPtr<ID3D12CommandQueue> m_OverlayCommandQueue;
280280
D3D12_RESOURCE_BARRIER m_Barrier;
281281

@@ -374,7 +374,7 @@ class D3D12VARenderer : public QObject, public IFFmpegRenderer
374374

375375
// Used for debug purpose only
376376
ComPtr<ID3D12CommandAllocator> m_PictureCommandAllocator;
377-
ComPtr<ID3D12GraphicsCommandList7> m_PictureCommandList;
377+
ComPtr<ID3D12GraphicsCommandList> m_PictureCommandList;
378378
ComPtr<ID3D12CommandQueue> m_PictureCommandQueue;
379379
void DebugExportToPNG(
380380
ID3D12Resource* srctexture,

app/streaming/video/ffmpeg-renderers/plvk.cpp

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,18 @@ PlVkRenderer::~PlVkRenderer()
182182
DrmMasterLocker locker;
183183

184184
pl_renderer_destroy(&m_Renderer);
185+
186+
// The FSR hooks own GPU resources, so they must be released while the
187+
// pl_gpu they were parsed on is still alive. m_FsrHookHDR stays null when
188+
// only the SDR variant was parsed and m_RenderParamsHDR points at m_FsrHook.
189+
if (m_FsrHookHDR != nullptr && m_FsrHookHDR != m_FsrHook) {
190+
pl_mpv_user_shader_destroy(&m_FsrHookHDR);
191+
}
192+
m_FsrHookHDR = nullptr;
193+
if (m_FsrHook != nullptr) {
194+
pl_mpv_user_shader_destroy(&m_FsrHook);
195+
}
196+
185197
pl_swapchain_destroy(&m_Swapchain);
186198
#ifdef Q_OS_DARWIN
187199
m_MetalTextureFactory.reset();
@@ -199,15 +211,6 @@ PlVkRenderer::~PlVkRenderer()
199211

200212
// m_Log must always be the last object destroyed
201213
pl_log_destroy(&m_Log);
202-
203-
if (m_FsrHook) {
204-
pl_mpv_user_shader_destroy(&m_FsrHook);
205-
m_FsrHook = nullptr;
206-
}
207-
if (m_FsrHookHDR) {
208-
pl_mpv_user_shader_destroy(&m_FsrHookHDR);
209-
m_FsrHookHDR = nullptr;
210-
}
211214
}
212215

213216
bool PlVkRenderer::chooseVulkanDevice(PDECODER_PARAMETERS params, bool hdrOutputRequired)
@@ -650,23 +653,32 @@ bool PlVkRenderer::initialize(PDECODER_PARAMETERS params)
650653
m_RenderParams.num_hooks = m_FsrHook ? 1 : 0;
651654

652655
// FSR1 (HDR)
653-
// The shader has been customized to set the sharpening at 0.75 and PQ at true for HDR
654-
std::string FsrShaderHDR;
655-
// Check if HDR is enabled by the user in the UI settings.
656+
// The shader has been customized to set the sharpening at 0.75 and PQ at true for HDR.
657+
// Only a 10-bit stream can carry PQ frames, so for anything else the SDR hook is reused
658+
// rather than parsing and allocating GPU resources for a second identical copy of it.
656659
if (params->videoFormat & VIDEO_FORMAT_MASK_10BIT) {
657-
FsrShaderHDR = loadGLSL(":/enhancer/FSR1_HDR.glsl");
658-
} else {
659-
FsrShaderHDR = loadGLSL(":/enhancer/FSR1.glsl");
660+
std::string FsrShaderHDR = loadGLSL(":/enhancer/FSR1_HDR.glsl");
661+
m_FsrHookHDR = pl_mpv_user_shader_parse(m_Vulkan->gpu, FsrShaderHDR.c_str(), FsrShaderHDR.size());
662+
m_RenderParamsHDR.hooks = &m_FsrHookHDR;
663+
m_RenderParamsHDR.num_hooks = m_FsrHookHDR ? 1 : 0;
664+
}
665+
else {
666+
m_RenderParamsHDR.hooks = &m_FsrHook;
667+
m_RenderParamsHDR.num_hooks = m_FsrHook ? 1 : 0;
660668
}
661-
m_FsrHookHDR = pl_mpv_user_shader_parse(m_Vulkan->gpu, FsrShaderHDR.c_str(), FsrShaderHDR.size());
662-
m_RenderParamsHDR.hooks = &m_FsrHookHDR;
663-
m_RenderParamsHDR.num_hooks = m_FsrHookHDR ? 1 : 0;
664669

665-
int drawableWidth, drawableHeight;
666-
SDL_Vulkan_GetDrawableSize(m_Window, &drawableWidth, &drawableHeight);
667-
m_VideoEnhancement->setRatio(static_cast<float>(drawableHeight) / static_cast<float>(params->height));
670+
if (m_RenderParams.num_hooks == 0 || m_RenderParamsHDR.num_hooks == 0) {
671+
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
672+
"Failed to parse the FSR1 shader. Video enhancement is disabled for this session.");
673+
m_VideoEnhancement->enableVideoEnhancement(false);
674+
}
675+
else {
676+
int drawableWidth, drawableHeight;
677+
SDL_Vulkan_GetDrawableSize(m_Window, &drawableWidth, &drawableHeight);
678+
m_VideoEnhancement->setRatio(static_cast<float>(drawableHeight) / static_cast<float>(params->height));
668679

669-
m_VideoEnhancement->setAlgo("Shader FSR1");
680+
m_VideoEnhancement->setAlgo("Shader FSR1");
681+
}
670682
}
671683

672684
return true;

app/streaming/video/ffmpeg.cpp

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,22 +1038,37 @@ IFFmpegRenderer* FFmpegVideoDecoder::createHwAccelRenderer(const AVCodecHWConfig
10381038

10391039
// Keep track of the Device Type selected
10401040
VideoEnhancement::getInstance().setDeviceType(hwDecodeCfg->device_type);
1041-
1041+
1042+
bool enableVideoEnhancement = params->enableVideoEnhancement;
1043+
#ifdef Q_OS_WIN32
1044+
// D3D11VA is only reached with enhancement requested when the D3D12 renderer
1045+
// is unusable on this system. That renderer is the one providing the upscaler,
1046+
// so the session runs without enhancement rather than advertising a missing one.
1047+
if (enableVideoEnhancement &&
1048+
hwDecodeCfg->device_type == AV_HWDEVICE_TYPE_D3D11VA &&
1049+
!VideoEnhancement::getInstance().isD3D12Available()) {
1050+
enableVideoEnhancement = false;
1051+
}
1052+
#endif
1053+
10421054
// Reset Video enhancer enabler
1043-
VideoEnhancement::getInstance().enableVideoEnhancement(params->enableVideoEnhancement);
1044-
1055+
VideoEnhancement::getInstance().enableVideoEnhancement(enableVideoEnhancement);
1056+
10451057
// First pass using our top-tier hwaccel implementations
10461058
if (pass == 0) {
10471059
switch (hwDecodeCfg->device_type) {
10481060
#ifdef Q_OS_WIN32
10491061
// DXVA2 appears in the hwaccel list before D3D11VA, so we only check for D3D11VA
10501062
// on the first pass to ensure we prefer D3D11VA over DXVA2.
10511063
case AV_HWDEVICE_TYPE_D3D11VA:
1052-
if (!params->enableVideoEnhancement){
1064+
if (!params->enableVideoEnhancement || !VideoEnhancement::getInstance().isD3D12Available()){
10531065
return new D3D11VARenderer(pass);
10541066
}
10551067
// Do not break here
10561068
case AV_HWDEVICE_TYPE_D3D12VA:
1069+
if (!VideoEnhancement::getInstance().isD3D12Available()){
1070+
return nullptr;
1071+
}
10571072
// D3D12VARenderer is also able to receive frame from AV_HWDEVICE_TYPE_D3D11VA via Interop
10581073
return new D3D12VARenderer(pass);
10591074
#endif
@@ -1117,11 +1132,14 @@ IFFmpegRenderer* FFmpegVideoDecoder::createHwAccelRenderer(const AVCodecHWConfig
11171132
case AV_HWDEVICE_TYPE_DXVA2:
11181133
return new DXVA2Renderer(pass);
11191134
case AV_HWDEVICE_TYPE_D3D11VA:
1120-
if (!params->enableVideoEnhancement){
1135+
if (!params->enableVideoEnhancement || !VideoEnhancement::getInstance().isD3D12Available()){
11211136
return new D3D11VARenderer(pass);
11221137
}
11231138
// Do not break here
11241139
case AV_HWDEVICE_TYPE_D3D12VA:
1140+
if (!VideoEnhancement::getInstance().isD3D12Available()){
1141+
return nullptr;
1142+
}
11251143
// D3D12VARenderer is also able to receive frame from AV_HWDEVICE_TYPE_D3D11VA via Interop
11261144
return new D3D12VARenderer(pass);
11271145
#endif

app/streaming/video/videoenhancement.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,29 @@ bool VideoEnhancement::isHDRcapable(){
159159
return m_HDRcapable;
160160
}
161161

162+
/**
163+
* \brief Set the D3D12 renderer availability
164+
*
165+
* The D3D12 renderer needs interfaces that are missing on older Windows releases.
166+
* Once its initialization has failed, there is no point retrying it for the rest
167+
* of the process, and the D3D11 renderer must be allowed to take over.
168+
*
169+
* \param bool available
170+
* \return void
171+
*/
172+
void VideoEnhancement::setD3D12Available(bool available){
173+
m_D3D12Available = available;
174+
}
175+
176+
/**
177+
* \brief Check the D3D12 renderer availability
178+
*
179+
* \return bool Returns false if the D3D12 renderer already failed to initialize
180+
*/
181+
bool VideoEnhancement::isD3D12Available(){
182+
return m_D3D12Available;
183+
}
184+
162185
/**
163186
* \brief Check if Video Enhancement feature is enabled
164187
*

app/streaming/video/videoenhancement.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ class VideoEnhancement
1717
bool m_Enabled = false;
1818
bool m_VSRcapable = false;
1919
bool m_HDRcapable = false;
20+
// Set to false once the D3D12 renderer proved it cannot be created on this system
21+
bool m_D3D12Available = true;
2022
float m_Ratio;
2123
std::string m_Algo;
2224
int m_DeviceType;
@@ -53,6 +55,8 @@ class VideoEnhancement
5355
bool isVSRcapable();
5456
void setHDRcapable(bool capable);
5557
bool isHDRcapable();
58+
void setD3D12Available(bool available);
59+
bool isD3D12Available();
5660
bool isVideoEnhancementEnabled();
5761
bool enableVideoEnhancement(bool activate = true);
5862
void setAdapterIndex(int adapterIndex);

0 commit comments

Comments
 (0)