[Windows] Move DX::GetErrorDescription to WIN32Util
[xbmc.git] / xbmc / rendering / dx / DeviceResources.cpp
blob4ba3948fe389e2141d59b74ecd8cf0a125da96b6
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "DeviceResources.h"
11 #include "DirectXHelper.h"
12 #include "RenderContext.h"
13 #include "ServiceBroker.h"
14 #include "guilib/GUIComponent.h"
15 #include "guilib/GUIWindowManager.h"
16 #include "messaging/ApplicationMessenger.h"
17 #include "settings/Settings.h"
18 #include "settings/SettingsComponent.h"
19 #include "utils/SystemInfo.h"
20 #include "utils/log.h"
21 #include "windowing/GraphicContext.h"
23 #include "platform/win32/CharsetConverter.h"
24 #include "platform/win32/WIN32Util.h"
26 #ifdef TARGET_WINDOWS_STORE
27 #include <winrt/Windows.Graphics.Display.Core.h>
29 extern "C"
31 #include <libavutil/rational.h>
33 #endif
35 #ifdef _DEBUG
36 #include <dxgidebug.h>
37 #pragma comment(lib, "dxgi.lib")
38 #endif // _DEBUG
40 using namespace DirectX;
41 using namespace Microsoft::WRL;
42 using namespace Concurrency;
43 namespace winrt
45 using namespace Windows::Foundation;
48 #ifdef _DEBUG
49 #define breakOnDebug __debugbreak()
50 #else
51 #define breakOnDebug
52 #endif
53 #define LOG_HR(hr) \
54 CLog::LogF(LOGERROR, "function call at line {} ends with error: {}", __LINE__, \
55 CWIN32Util::FormatHRESULT(hr));
56 #define CHECK_ERR() if (FAILED(hr)) { LOG_HR(hr); breakOnDebug; return; }
57 #define RETURN_ERR(ret) if (FAILED(hr)) { LOG_HR(hr); breakOnDebug; return (##ret); }
59 bool DX::DeviceResources::CBackBuffer::Acquire(ID3D11Texture2D* pTexture)
61 if (!pTexture)
62 return false;
64 D3D11_TEXTURE2D_DESC desc;
65 pTexture->GetDesc(&desc);
67 m_width = desc.Width;
68 m_height = desc.Height;
69 m_format = desc.Format;
70 m_usage = desc.Usage;
72 m_texture = pTexture;
73 return true;
76 std::shared_ptr<DX::DeviceResources> DX::DeviceResources::Get()
78 static std::shared_ptr<DeviceResources> sDeviceResources(new DeviceResources);
79 return sDeviceResources;
82 // Constructor for DeviceResources.
83 DX::DeviceResources::DeviceResources()
84 : m_screenViewport()
85 , m_d3dFeatureLevel(D3D_FEATURE_LEVEL_9_1)
86 , m_outputSize()
87 , m_logicalSize()
88 , m_dpi(DisplayMetrics::Dpi100)
89 , m_effectiveDpi(DisplayMetrics::Dpi100)
90 , m_deviceNotify(nullptr)
91 , m_stereoEnabled(false)
92 , m_bDeviceCreated(false)
93 , m_IsHDROutput(false)
94 , m_IsTransferPQ(false)
98 DX::DeviceResources::~DeviceResources() = default;
100 void DX::DeviceResources::Release()
102 if (!m_bDeviceCreated)
103 return;
105 ReleaseBackBuffer();
106 OnDeviceLost(true);
107 DestroySwapChain();
109 m_adapter = nullptr;
110 m_dxgiFactory = nullptr;
111 m_output = nullptr;
112 m_deferrContext = nullptr;
113 m_d3dContext = nullptr;
114 m_d3dDevice = nullptr;
115 m_bDeviceCreated = false;
116 #ifdef _DEBUG
117 if (m_d3dDebug)
119 m_d3dDebug->ReportLiveDeviceObjects(D3D11_RLDO_SUMMARY | D3D11_RLDO_DETAIL);
120 m_d3dDebug = nullptr;
122 #endif
125 void DX::DeviceResources::GetOutput(IDXGIOutput** ppOutput) const
127 ComPtr<IDXGIOutput> pOutput;
128 if (!m_swapChain || FAILED(m_swapChain->GetContainingOutput(pOutput.GetAddressOf())) || !pOutput)
129 m_output.As(&pOutput);
130 *ppOutput = pOutput.Detach();
133 void DX::DeviceResources::GetCachedOutputAndDesc(IDXGIOutput** ppOutput,
134 DXGI_OUTPUT_DESC* outputDesc) const
136 ComPtr<IDXGIOutput> pOutput;
137 if (m_output)
139 m_output.As(&pOutput);
140 *outputDesc = m_outputDesc;
142 else if (m_swapChain && SUCCEEDED(m_swapChain->GetContainingOutput(pOutput.GetAddressOf())) &&
143 pOutput)
145 pOutput->GetDesc(outputDesc);
148 if (!pOutput)
149 CLog::LogF(LOGWARNING, "unable to retrieve current output");
151 *ppOutput = pOutput.Detach();
152 return;
155 DXGI_ADAPTER_DESC DX::DeviceResources::GetAdapterDesc() const
157 DXGI_ADAPTER_DESC desc{};
159 if (m_adapter)
160 m_adapter->GetDesc(&desc);
162 // GetDesc() returns VendorId == 0 in Xbox however, we need to know that
163 // GPU is AMD to apply workarounds in DXVA.cpp CheckCompatibility() same as desktop
164 if (CSysInfo::GetWindowsDeviceFamily() == CSysInfo::Xbox)
165 desc.VendorId = PCIV_AMD;
167 return desc;
170 void DX::DeviceResources::GetDisplayMode(DXGI_MODE_DESC* mode) const
172 DXGI_OUTPUT_DESC outDesc;
173 ComPtr<IDXGIOutput> pOutput;
174 DXGI_SWAP_CHAIN_DESC scDesc;
176 if (!m_swapChain)
177 return;
179 m_swapChain->GetDesc(&scDesc);
181 GetOutput(pOutput.GetAddressOf());
182 pOutput->GetDesc(&outDesc);
184 // desktop coords depend on DPI
185 mode->Width = DX::ConvertDipsToPixels(outDesc.DesktopCoordinates.right - outDesc.DesktopCoordinates.left, m_dpi);
186 mode->Height = DX::ConvertDipsToPixels(outDesc.DesktopCoordinates.bottom - outDesc.DesktopCoordinates.top, m_dpi);
187 mode->Format = scDesc.BufferDesc.Format;
188 mode->Scaling = scDesc.BufferDesc.Scaling;
189 mode->ScanlineOrdering = scDesc.BufferDesc.ScanlineOrdering;
191 #ifdef TARGET_WINDOWS_DESKTOP
192 DEVMODEW sDevMode = {};
193 sDevMode.dmSize = sizeof(sDevMode);
195 // EnumDisplaySettingsW is only one way to detect current refresh rate
196 if (EnumDisplaySettingsW(outDesc.DeviceName, ENUM_CURRENT_SETTINGS, &sDevMode))
198 int i = (((sDevMode.dmDisplayFrequency + 1) % 24) == 0 || ((sDevMode.dmDisplayFrequency + 1) % 30) == 0) ? 1 : 0;
199 mode->RefreshRate.Numerator = (sDevMode.dmDisplayFrequency + i) * 1000;
200 mode->RefreshRate.Denominator = 1000 + i;
201 if (sDevMode.dmDisplayFlags & DM_INTERLACED)
203 mode->RefreshRate.Numerator *= 2;
204 mode->ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UPPER_FIELD_FIRST; // guessing
206 else
207 mode->ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE;
209 #else
210 using namespace winrt::Windows::Graphics::Display::Core;
212 auto hdmiInfo = HdmiDisplayInformation::GetForCurrentView();
213 if (hdmiInfo) // Xbox only
215 auto currentMode = hdmiInfo.GetCurrentDisplayMode();
216 AVRational refresh = av_d2q(currentMode.RefreshRate(), 120000);
217 mode->RefreshRate.Numerator = refresh.num;
218 mode->RefreshRate.Denominator = refresh.den;
220 #endif
223 void DX::DeviceResources::SetViewPort(D3D11_VIEWPORT& viewPort) const
225 // convert logical viewport to real
226 D3D11_VIEWPORT realViewPort =
228 viewPort.TopLeftX,
229 viewPort.TopLeftY,
230 viewPort.Width,
231 viewPort.Height,
232 viewPort.MinDepth,
233 viewPort.MinDepth
236 m_deferrContext->RSSetViewports(1, &realViewPort);
239 bool DX::DeviceResources::SetFullScreen(bool fullscreen, RESOLUTION_INFO& res)
241 if (!m_bDeviceCreated || !m_swapChain)
242 return false;
244 critical_section::scoped_lock lock(m_criticalSection);
246 BOOL bFullScreen;
247 m_swapChain->GetFullscreenState(&bFullScreen, nullptr);
249 CLog::LogF(LOGDEBUG, "switching from {}({:.0f} x {:.0f}) to {}({} x {})",
250 bFullScreen ? "fullscreen " : "", m_outputSize.Width, m_outputSize.Height,
251 fullscreen ? "fullscreen " : "", res.iWidth, res.iHeight);
253 bool recreate = m_stereoEnabled != (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() == RENDER_STEREO_MODE_HARDWAREBASED);
254 if (!!bFullScreen && !fullscreen)
256 CLog::LogF(LOGDEBUG, "switching to windowed");
257 recreate |= SUCCEEDED(m_swapChain->SetFullscreenState(false, nullptr));
259 else if (fullscreen)
261 const bool isResValid = res.iWidth > 0 && res.iHeight > 0 && res.fRefreshRate > 0.f;
262 if (isResValid)
264 DXGI_MODE_DESC currentMode = {};
265 GetDisplayMode(&currentMode);
266 DXGI_SWAP_CHAIN_DESC scDesc;
267 m_swapChain->GetDesc(&scDesc);
269 bool is_interlaced = scDesc.BufferDesc.ScanlineOrdering > DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE;
270 float refreshRate = res.fRefreshRate;
271 if (res.dwFlags & D3DPRESENTFLAG_INTERLACED)
272 refreshRate *= 2;
274 if (currentMode.Width != res.iWidth
275 || currentMode.Height != res.iHeight
276 || DX::RationalToFloat(currentMode.RefreshRate) != refreshRate
277 || is_interlaced != (res.dwFlags & D3DPRESENTFLAG_INTERLACED ? true : false)
278 // force resolution change for stereo mode
279 // some drivers unable to create stereo swapchain if mode does not match @23.976
280 || CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() == RENDER_STEREO_MODE_HARDWAREBASED)
282 CLog::Log(LOGDEBUG, __FUNCTION__ ": changing display mode to {}x{}@{:0.3f}", res.iWidth,
283 res.iHeight, res.fRefreshRate,
284 res.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "");
286 int refresh = static_cast<int>(res.fRefreshRate);
287 int i = (refresh + 1) % 24 == 0 || (refresh + 1) % 30 == 0 ? 1 : 0;
289 currentMode.Width = res.iWidth;
290 currentMode.Height = res.iHeight;
291 currentMode.RefreshRate.Numerator = (refresh + i) * 1000;
292 currentMode.RefreshRate.Denominator = 1000 + i;
293 if (res.dwFlags & D3DPRESENTFLAG_INTERLACED)
295 currentMode.RefreshRate.Numerator *= 2;
296 currentMode.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UPPER_FIELD_FIRST; // guessing;
298 // sometimes the OS silently brings Kodi out of full screen mode
299 // in this case switching a resolution has no any effect and
300 // we have to enter into full screen mode before switching
301 if (!bFullScreen)
303 ComPtr<IDXGIOutput> pOutput;
304 GetOutput(pOutput.GetAddressOf());
306 CLog::LogF(LOGDEBUG, "fixup fullscreen mode before switching resolution");
307 recreate |= SUCCEEDED(m_swapChain->SetFullscreenState(true, pOutput.Get()));
308 m_swapChain->GetFullscreenState(&bFullScreen, nullptr);
310 bool resized = SUCCEEDED(m_swapChain->ResizeTarget(&currentMode));
311 if (resized)
313 // some system doesn't inform windowing about desktop size changes
314 // so we have to change output size before resizing buffers
315 m_outputSize.Width = static_cast<float>(currentMode.Width);
316 m_outputSize.Height = static_cast<float>(currentMode.Height);
318 recreate |= resized;
321 if (!bFullScreen)
323 ComPtr<IDXGIOutput> pOutput;
324 GetOutput(pOutput.GetAddressOf());
326 CLog::LogF(LOGDEBUG, "switching to fullscreen");
327 recreate |= SUCCEEDED(m_swapChain->SetFullscreenState(true, pOutput.Get()));
331 // resize backbuffer to proper handle fullscreen/stereo transition
332 if (recreate)
333 ResizeBuffers();
335 CLog::LogF(LOGDEBUG, "switching done.");
337 return recreate;
340 // Configures resources that don't depend on the Direct3D device.
341 void DX::DeviceResources::CreateDeviceIndependentResources()
345 // Configures the Direct3D device, and stores handles to it and the device context.
346 void DX::DeviceResources::CreateDeviceResources()
348 CLog::LogF(LOGDEBUG, "creating DirectX 11 device.");
350 CreateFactory();
352 UINT creationFlags = D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
353 #if defined(_DEBUG)
354 if (DX::SdkLayersAvailable())
356 // If the project is in a debug build, enable debugging via SDK Layers with this flag.
357 creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
359 #endif
361 // This array defines the set of DirectX hardware feature levels this app will support.
362 // Note the ordering should be preserved.
363 // Don't forget to declare your application's minimum required feature level in its
364 // description. All applications are assumed to support 9.1 unless otherwise stated.
365 std::vector<D3D_FEATURE_LEVEL> featureLevels;
366 if (CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10))
368 featureLevels.push_back(D3D_FEATURE_LEVEL_12_1);
369 featureLevels.push_back(D3D_FEATURE_LEVEL_12_0);
371 featureLevels.push_back(D3D_FEATURE_LEVEL_11_1);
372 featureLevels.push_back(D3D_FEATURE_LEVEL_11_0);
373 featureLevels.push_back(D3D_FEATURE_LEVEL_10_1);
374 featureLevels.push_back(D3D_FEATURE_LEVEL_10_0);
375 featureLevels.push_back(D3D_FEATURE_LEVEL_9_3);
376 featureLevels.push_back(D3D_FEATURE_LEVEL_9_2);
377 featureLevels.push_back(D3D_FEATURE_LEVEL_9_1);
379 // Create the Direct3D 11 API device object and a corresponding context.
380 ComPtr<ID3D11Device> device;
381 ComPtr<ID3D11DeviceContext> context;
383 D3D_DRIVER_TYPE drivertType = m_adapter != nullptr ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE;
384 HRESULT hr = D3D11CreateDevice(
385 m_adapter.Get(), // Create a device on specified adapter.
386 drivertType, // Create a device using scepcified driver.
387 nullptr, // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
388 creationFlags, // Set debug and Direct2D compatibility flags.
389 featureLevels.data(), // List of feature levels this app can support.
390 featureLevels.size(), // Size of the list above.
391 D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows Store apps.
392 &device, // Returns the Direct3D device created.
393 &m_d3dFeatureLevel, // Returns feature level of device created.
394 &context // Returns the device immediate context.
397 if (FAILED(hr))
399 CLog::LogF(LOGERROR, "unable to create hardware device with video support, error {}",
400 CWIN32Util::FormatHRESULT(hr));
401 CLog::LogF(LOGERROR, "trying to create hardware device without video support.");
403 creationFlags &= ~D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
405 hr = D3D11CreateDevice(m_adapter.Get(), drivertType, nullptr, creationFlags,
406 featureLevels.data(), featureLevels.size(), D3D11_SDK_VERSION, &device,
407 &m_d3dFeatureLevel, &context);
409 if (FAILED(hr))
411 CLog::LogF(LOGERROR, "unable to create hardware device, error {}",
412 CWIN32Util::FormatHRESULT(hr));
413 CLog::LogF(LOGERROR, "trying to create WARP device.");
415 hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, creationFlags,
416 featureLevels.data(), featureLevels.size(), D3D11_SDK_VERSION, &device,
417 &m_d3dFeatureLevel, &context);
419 if (FAILED(hr))
421 CLog::LogF(LOGFATAL, "unable to create WARP device. Rendering is not possible. Error {}",
422 CWIN32Util::FormatHRESULT(hr));
423 CHECK_ERR();
428 // Store pointers to the Direct3D 11.1 API device and immediate context.
429 hr = device.As(&m_d3dDevice); CHECK_ERR();
431 // Check shared textures support
432 CheckNV12SharedTexturesSupport();
434 #ifdef _DEBUG
435 if (SUCCEEDED(m_d3dDevice.As(&m_d3dDebug)))
437 ComPtr<ID3D11InfoQueue> d3dInfoQueue;
438 if (SUCCEEDED(m_d3dDebug.As(&d3dInfoQueue)))
440 std::vector<D3D11_MESSAGE_ID> hide =
442 D3D11_MESSAGE_ID_GETVIDEOPROCESSORFILTERRANGE_UNSUPPORTED, // avoid GETVIDEOPROCESSORFILTERRANGE_UNSUPPORTED (dx bug)
443 D3D11_MESSAGE_ID_DEVICE_RSSETSCISSORRECTS_NEGATIVESCISSOR // avoid warning for some labels out of screen
444 // Add more message IDs here as needed
447 D3D11_INFO_QUEUE_FILTER filter = {};
448 filter.DenyList.NumIDs = hide.size();
449 filter.DenyList.pIDList = hide.data();
450 d3dInfoQueue->AddStorageFilterEntries(&filter);
453 #endif
455 hr = context.As(&m_d3dContext); CHECK_ERR();
456 hr = m_d3dDevice->CreateDeferredContext1(0, &m_deferrContext); CHECK_ERR();
458 if (!m_adapter)
460 ComPtr<IDXGIDevice1> dxgiDevice;
461 ComPtr<IDXGIAdapter> adapter;
462 hr = m_d3dDevice.As(&dxgiDevice); CHECK_ERR();
463 hr = dxgiDevice->GetAdapter(&adapter); CHECK_ERR();
464 hr = adapter.As(&m_adapter); CHECK_ERR();
467 DXGI_ADAPTER_DESC aDesc;
468 m_adapter->GetDesc(&aDesc);
470 CLog::LogF(LOGINFO, "device is created on adapter '{}' with {}",
471 KODI::PLATFORM::WINDOWS::FromW(aDesc.Description),
472 GetFeatureLevelDescription(m_d3dFeatureLevel));
474 CheckDXVA2SharedDecoderSurfaces();
476 m_bDeviceCreated = true;
479 void DX::DeviceResources::ReleaseBackBuffer()
481 CLog::LogF(LOGDEBUG, "release buffers.");
483 m_backBufferTex.Release();
484 m_d3dDepthStencilView = nullptr;
485 if (m_deferrContext)
487 // Clear the previous window size specific context.
488 ID3D11RenderTargetView* nullViews[] = { nullptr, nullptr, nullptr, nullptr };
489 m_deferrContext->OMSetRenderTargets(4, nullViews, nullptr);
490 FinishCommandList(false);
492 m_deferrContext->Flush();
493 m_d3dContext->Flush();
497 void DX::DeviceResources::CreateBackBuffer()
499 if (!m_bDeviceCreated || !m_swapChain)
500 return;
502 CLog::LogF(LOGDEBUG, "create buffers.");
504 // Get swap chain back buffer.
505 ComPtr<ID3D11Texture2D> backBuffer;
506 HRESULT hr = m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer)); CHECK_ERR();
508 // Create back buffer texture from swap chain texture
509 if (!m_backBufferTex.Acquire(backBuffer.Get()))
511 CLog::LogF(LOGERROR, "failed to create render target.");
512 return;
515 // Create a depth stencil view for use with 3D rendering if needed.
516 CD3D11_TEXTURE2D_DESC depthStencilDesc(
517 DXGI_FORMAT_D24_UNORM_S8_UINT,
518 lround(m_outputSize.Width),
519 lround(m_outputSize.Height),
520 1, // This depth stencil view has only one texture.
521 1, // Use a single mipmap level.
522 D3D11_BIND_DEPTH_STENCIL
525 ComPtr<ID3D11Texture2D> depthStencil;
526 hr = m_d3dDevice->CreateTexture2D(
527 &depthStencilDesc,
528 nullptr,
529 &depthStencil
530 ); CHECK_ERR();
532 CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
533 hr = m_d3dDevice->CreateDepthStencilView(
534 depthStencil.Get(),
535 &depthStencilViewDesc,
536 &m_d3dDepthStencilView
537 ); CHECK_ERR();
539 // Set the 3D rendering viewport to target the entire window.
540 m_screenViewport = CD3D11_VIEWPORT(
541 0.0f,
542 0.0f,
543 m_outputSize.Width,
544 m_outputSize.Height
547 m_deferrContext->RSSetViewports(1, &m_screenViewport);
550 HRESULT DX::DeviceResources::CreateSwapChain(DXGI_SWAP_CHAIN_DESC1& desc, DXGI_SWAP_CHAIN_FULLSCREEN_DESC& fsDesc, IDXGISwapChain1** ppSwapChain) const
552 HRESULT hr;
553 #ifdef TARGET_WINDOWS_DESKTOP
554 hr = m_dxgiFactory->CreateSwapChainForHwnd(
555 m_d3dDevice.Get(),
556 m_window,
557 &desc,
558 &fsDesc,
559 nullptr,
560 ppSwapChain
561 ); RETURN_ERR(hr);
562 hr = m_dxgiFactory->MakeWindowAssociation(m_window, /*DXGI_MWA_NO_WINDOW_CHANGES |*/ DXGI_MWA_NO_ALT_ENTER);
563 #else
564 hr = m_dxgiFactory->CreateSwapChainForCoreWindow(
565 m_d3dDevice.Get(),
566 winrt::get_unknown(m_coreWindow),
567 &desc,
568 nullptr,
569 ppSwapChain
570 ); RETURN_ERR(hr);
571 #endif
572 return hr;
575 void DX::DeviceResources::DestroySwapChain()
577 if (!m_swapChain)
578 return;
580 BOOL bFullcreen = 0;
581 m_swapChain->GetFullscreenState(&bFullcreen, nullptr);
582 if (!!bFullcreen)
583 m_swapChain->SetFullscreenState(false, nullptr); // mandatory before releasing swapchain
584 m_swapChain = nullptr;
585 m_deferrContext->Flush();
586 m_d3dContext->Flush();
587 m_IsTransferPQ = false;
590 void DX::DeviceResources::ResizeBuffers()
592 if (!m_bDeviceCreated)
593 return;
595 CLog::LogF(LOGDEBUG, "resize buffers.");
597 bool bHWStereoEnabled = RENDER_STEREO_MODE_HARDWAREBASED ==
598 CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode();
599 bool windowed = true;
600 HRESULT hr = E_FAIL;
601 DXGI_SWAP_CHAIN_DESC1 scDesc = {};
603 if (m_swapChain)
605 BOOL bFullcreen = 0;
606 m_swapChain->GetFullscreenState(&bFullcreen, nullptr);
607 if (!!bFullcreen)
608 windowed = false;
610 m_swapChain->GetDesc1(&scDesc);
611 if ((scDesc.Stereo == TRUE) != bHWStereoEnabled) // check if swapchain needs to be recreated
612 DestroySwapChain();
615 if (m_swapChain) // If the swap chain already exists, resize it.
617 m_swapChain->GetDesc1(&scDesc);
618 hr = m_swapChain->ResizeBuffers(scDesc.BufferCount, lround(m_outputSize.Width),
619 lround(m_outputSize.Height), scDesc.Format,
620 windowed ? 0 : DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);
622 if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
624 // If the device was removed for any reason, a new device and swap chain will need to be created.
625 HandleDeviceLost(hr == DXGI_ERROR_DEVICE_REMOVED);
627 // Everything is set up now. Do not continue execution of this method. HandleDeviceLost will reenter this method
628 // and correctly set up the new device.
629 return;
631 else if (hr == DXGI_ERROR_INVALID_CALL)
633 // Called when Windows HDR is toggled externally to Kodi.
634 // Is forced to re-create swap chain to avoid crash.
635 CreateWindowSizeDependentResources();
636 return;
638 CHECK_ERR();
640 else // Otherwise, create a new one using the same adapter as the existing Direct3D device.
642 HDR_STATUS hdrStatus = CWIN32Util::GetWindowsHDRStatus();
643 const bool isHdrEnabled = (hdrStatus == HDR_STATUS::HDR_ON);
644 bool use10bit = (hdrStatus != HDR_STATUS::HDR_UNSUPPORTED);
646 // Xbox needs 10 bit swapchain to output true 4K resolution
647 #ifdef TARGET_WINDOWS_DESKTOP
648 // Some AMD graphics has issues with 10 bit in SDR.
649 // Enabled by default only in Intel and NVIDIA with latest drivers/hardware
650 if (m_d3dFeatureLevel < D3D_FEATURE_LEVEL_12_1 || GetAdapterDesc().VendorId == PCIV_AMD)
651 use10bit = false;
652 #endif
654 // 0 = Auto | 1 = Never | 2 = Always
655 int use10bitSetting = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
656 CSettings::SETTING_VIDEOSCREEN_10BITSURFACES);
658 if (use10bitSetting == 1)
659 use10bit = false;
660 else if (use10bitSetting == 2)
661 use10bit = true;
663 DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
664 swapChainDesc.Width = lround(m_outputSize.Width);
665 swapChainDesc.Height = lround(m_outputSize.Height);
666 swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
667 swapChainDesc.Stereo = bHWStereoEnabled;
668 swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
669 #ifdef TARGET_WINDOWS_DESKTOP
670 swapChainDesc.BufferCount = 6; // HDR 60 fps needs 6 buffers to avoid frame drops
671 #else
672 swapChainDesc.BufferCount = 3; // Xbox don't like 6 backbuffers (3 is fine even for 4K 60 fps)
673 #endif
674 // FLIP_DISCARD improves performance (needed in some systems for 4K HDR 60 fps)
675 swapChainDesc.SwapEffect = CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10)
676 ? DXGI_SWAP_EFFECT_FLIP_DISCARD
677 : DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
678 swapChainDesc.Flags = windowed ? 0 : DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
679 swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
680 swapChainDesc.SampleDesc.Count = 1;
681 swapChainDesc.SampleDesc.Quality = 0;
683 DXGI_SWAP_CHAIN_FULLSCREEN_DESC scFSDesc = {}; // unused for uwp
684 scFSDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
685 scFSDesc.Windowed = windowed;
687 ComPtr<IDXGISwapChain1> swapChain;
688 if (m_d3dFeatureLevel >= D3D_FEATURE_LEVEL_11_0 && !bHWStereoEnabled &&
689 (isHdrEnabled || use10bit))
691 swapChainDesc.Format = DXGI_FORMAT_R10G10B10A2_UNORM;
692 hr = CreateSwapChain(swapChainDesc, scFSDesc, &swapChain);
693 if (FAILED(hr))
695 CLog::LogF(LOGWARNING, "creating 10bit swapchain failed, fallback to 8bit.");
696 swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
700 if (!swapChain)
701 hr = CreateSwapChain(swapChainDesc, scFSDesc, &swapChain);
703 if (FAILED(hr) && bHWStereoEnabled)
705 // switch to stereo mode failed, create mono swapchain
706 CLog::LogF(LOGERROR, "creating stereo swap chain failed with error.");
707 CLog::LogF(LOGINFO, "fallback to monoscopic mode.");
709 swapChainDesc.Stereo = false;
710 bHWStereoEnabled = false;
712 hr = CreateSwapChain(swapChainDesc, scFSDesc, &swapChain); CHECK_ERR();
714 // fallback to split_horizontal mode.
715 CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoMode(
716 RENDER_STEREO_MODE_SPLIT_HORIZONTAL);
719 if (FAILED(hr))
721 CLog::LogF(LOGERROR, "unable to create swapchain.");
722 return;
725 m_IsHDROutput = (swapChainDesc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) && isHdrEnabled;
727 CLog::LogF(
728 LOGINFO, "{} bit swapchain is used with {} flip {} buffers and {} output (format {})",
729 (swapChainDesc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) ? 10 : 8, swapChainDesc.BufferCount,
730 (swapChainDesc.SwapEffect == DXGI_SWAP_EFFECT_FLIP_DISCARD) ? "discard" : "sequential",
731 m_IsHDROutput ? "HDR" : "SDR", DX::DXGIFormatToString(swapChainDesc.Format));
733 hr = swapChain.As(&m_swapChain); CHECK_ERR();
734 m_stereoEnabled = bHWStereoEnabled;
736 if (CServiceBroker::GetLogging().IsLogLevelLogged(LOGDEBUG) &&
737 CServiceBroker::GetLogging().CanLogComponent(LOGVIDEO))
739 std::string colorSpaces;
740 for (const DXGI_COLOR_SPACE_TYPE& colorSpace : GetSwapChainColorSpaces())
742 colorSpaces.append("\n");
743 colorSpaces.append(DX::DXGIColorSpaceTypeToString(colorSpace));
745 CLog::LogFC(LOGDEBUG, LOGVIDEO, "Color spaces supported by the swap chain:{}", colorSpaces);
748 // Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and
749 // ensures that the application will only render after each VSync, minimizing power consumption.
750 ComPtr<IDXGIDevice1> dxgiDevice;
751 hr = m_d3dDevice.As(&dxgiDevice); CHECK_ERR();
752 dxgiDevice->SetMaximumFrameLatency(1);
754 if (m_IsHDROutput)
755 SetHdrColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
758 CLog::LogF(LOGDEBUG, "end resize buffers.");
761 // These resources need to be recreated every time the window size is changed.
762 void DX::DeviceResources::CreateWindowSizeDependentResources()
764 ReleaseBackBuffer();
766 DestroySwapChain();
768 if (!m_dxgiFactory->IsCurrent()) // HDR toggling requires re-create factory
769 CreateFactory();
771 UpdateRenderTargetSize();
772 ResizeBuffers();
774 CreateBackBuffer();
777 // Determine the dimensions of the render target and whether it will be scaled down.
778 void DX::DeviceResources::UpdateRenderTargetSize()
780 m_effectiveDpi = m_dpi;
782 // To improve battery life on high resolution devices, render to a smaller render target
783 // and allow the GPU to scale the output when it is presented.
784 if (!DisplayMetrics::SupportHighResolutions && m_dpi > DisplayMetrics::DpiThreshold)
786 float width = DX::ConvertDipsToPixels(m_logicalSize.Width, m_dpi);
787 float height = DX::ConvertDipsToPixels(m_logicalSize.Height, m_dpi);
789 // When the device is in portrait orientation, height > width. Compare the
790 // larger dimension against the width threshold and the smaller dimension
791 // against the height threshold.
792 if (std::max(width, height) > DisplayMetrics::WidthThreshold && std::min(width, height) > DisplayMetrics::HeightThreshold)
794 // To scale the app we change the effective DPI. Logical size does not change.
795 m_effectiveDpi /= 2.0f;
799 // Calculate the necessary render target size in pixels.
800 m_outputSize.Width = DX::ConvertDipsToPixels(m_logicalSize.Width, m_effectiveDpi);
801 m_outputSize.Height = DX::ConvertDipsToPixels(m_logicalSize.Height, m_effectiveDpi);
803 // Prevent zero size DirectX content from being created.
804 m_outputSize.Width = std::max(m_outputSize.Width, 1.f);
805 m_outputSize.Height = std::max(m_outputSize.Height, 1.f);
808 void DX::DeviceResources::Register(ID3DResource* resource)
810 critical_section::scoped_lock lock(m_resourceSection);
811 m_resources.push_back(resource);
814 void DX::DeviceResources::Unregister(ID3DResource* resource)
816 critical_section::scoped_lock lock(m_resourceSection);
817 std::vector<ID3DResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource);
818 if (i != m_resources.end())
819 m_resources.erase(i);
822 void DX::DeviceResources::FinishCommandList(bool bExecute) const
824 if (m_d3dContext == m_deferrContext)
825 return;
827 ComPtr<ID3D11CommandList> pCommandList;
828 if (FAILED(m_deferrContext->FinishCommandList(true, &pCommandList)))
830 CLog::LogF(LOGERROR, "failed to finish command queue.");
831 return;
834 if (bExecute)
835 m_d3dContext->ExecuteCommandList(pCommandList.Get(), false);
838 // This method is called in the event handler for the SizeChanged event.
839 void DX::DeviceResources::SetLogicalSize(float width, float height)
842 #if defined(TARGET_WINDOWS_DESKTOP)
843 (!m_window)
844 #else
845 (!m_coreWindow)
846 #endif
847 return;
849 CLog::LogF(LOGDEBUG, "receive changing logical size to {:f} x {:f}", width, height);
851 if (m_logicalSize.Width != width || m_logicalSize.Height != height)
853 CLog::LogF(LOGDEBUG, "change logical size to {:f} x {:f}", width, height);
855 m_logicalSize = winrt::Size(width, height);
857 UpdateRenderTargetSize();
858 ResizeBuffers();
862 // This method is called in the event handler for the DpiChanged event.
863 void DX::DeviceResources::SetDpi(float dpi)
865 dpi = std::max(dpi, DisplayMetrics::Dpi100);
866 if (dpi != m_dpi)
867 m_dpi = dpi;
870 // This method is called in the event handler for the DisplayContentsInvalidated event.
871 void DX::DeviceResources::ValidateDevice()
873 // The D3D Device is no longer valid if the default adapter changed since the device
874 // was created or if the device has been removed.
876 // First, get the information for the default adapter from when the device was created.
877 ComPtr<IDXGIDevice1> dxgiDevice;
878 m_d3dDevice.As(&dxgiDevice);
880 ComPtr<IDXGIAdapter> deviceAdapter;
881 dxgiDevice->GetAdapter(&deviceAdapter);
883 ComPtr<IDXGIFactory2> dxgiFactory;
884 deviceAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory));
886 DXGI_ADAPTER_DESC1 previousDesc;
888 ComPtr<IDXGIAdapter1> previousDefaultAdapter;
889 dxgiFactory->EnumAdapters1(0, &previousDefaultAdapter);
891 previousDefaultAdapter->GetDesc1(&previousDesc);
894 // Next, get the information for the current default adapter.
895 DXGI_ADAPTER_DESC1 currentDesc;
897 ComPtr<IDXGIFactory1> currentFactory;
898 CreateDXGIFactory1(IID_PPV_ARGS(&currentFactory));
900 ComPtr<IDXGIAdapter1> currentDefaultAdapter;
901 currentFactory->EnumAdapters1(0, &currentDefaultAdapter);
903 currentDefaultAdapter->GetDesc1(&currentDesc);
905 // If the adapter LUIDs don't match, or if the device reports that it has been removed,
906 // a new D3D device must be created.
907 HRESULT hr = m_d3dDevice->GetDeviceRemovedReason();
908 if ( previousDesc.AdapterLuid.LowPart != currentDesc.AdapterLuid.LowPart
909 || previousDesc.AdapterLuid.HighPart != currentDesc.AdapterLuid.HighPart
910 || FAILED(hr))
912 // Release references to resources related to the old device.
913 dxgiDevice = nullptr;
914 deviceAdapter = nullptr;
915 dxgiFactory = nullptr;
917 // Create a new device and swap chain.
918 HandleDeviceLost(hr == DXGI_ERROR_DEVICE_REMOVED);
922 void DX::DeviceResources::OnDeviceLost(bool removed)
924 auto pGUI = CServiceBroker::GetGUI();
925 if (pGUI)
926 pGUI->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_RENDERER_LOST);
928 // tell any shared resources
929 for (auto res : m_resources)
931 // the most of resources like textures and buffers try to
932 // receive and save their status from current device.
933 // `removed` means that we have no possibility
934 // to use the device anymore, tell all resources about this.
935 res->OnDestroyDevice(removed);
939 void DX::DeviceResources::OnDeviceRestored()
941 // tell any shared resources
942 for (auto res : m_resources)
943 res->OnCreateDevice();
945 auto pGUI = CServiceBroker::GetGUI();
946 if (pGUI)
947 pGUI->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_RENDERER_RESET);
950 // Recreate all device resources and set them back to the current state.
951 void DX::DeviceResources::HandleDeviceLost(bool removed)
953 bool backbuferExists = m_backBufferTex.Get() != nullptr;
955 OnDeviceLost(removed);
956 if (m_deviceNotify != nullptr)
957 m_deviceNotify->OnDXDeviceLost();
959 if (backbuferExists)
960 ReleaseBackBuffer();
962 DestroySwapChain();
964 CreateDeviceResources();
965 UpdateRenderTargetSize();
966 ResizeBuffers();
968 if (backbuferExists)
969 CreateBackBuffer();
971 if (m_deviceNotify != nullptr)
972 m_deviceNotify->OnDXDeviceRestored();
973 OnDeviceRestored();
975 if (removed)
976 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr,
977 "ReloadSkin");
980 bool DX::DeviceResources::Begin()
982 HRESULT hr = m_swapChain->Present(0, DXGI_PRESENT_TEST);
984 // If the device was removed either by a disconnection or a driver upgrade, we
985 // must recreate all device resources.
986 if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
988 HandleDeviceLost(hr == DXGI_ERROR_DEVICE_REMOVED);
990 else
992 // not fatal errors
993 if (hr == DXGI_ERROR_INVALID_CALL)
995 CreateWindowSizeDependentResources();
999 m_deferrContext->OMSetRenderTargets(1, m_backBufferTex.GetAddressOfRTV(), m_d3dDepthStencilView.Get());
1001 return true;
1004 // Present the contents of the swap chain to the screen.
1005 void DX::DeviceResources::Present()
1007 FinishCommandList();
1009 // The first argument instructs DXGI to block until VSync, putting the application
1010 // to sleep until the next VSync. This ensures we don't waste any cycles rendering
1011 // frames that will never be displayed to the screen.
1012 DXGI_PRESENT_PARAMETERS parameters = {};
1013 HRESULT hr = m_swapChain->Present1(1, 0, &parameters);
1015 // If the device was removed either by a disconnection or a driver upgrade, we
1016 // must recreate all device resources.
1017 if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
1019 HandleDeviceLost(hr == DXGI_ERROR_DEVICE_REMOVED);
1021 else
1023 // not fatal errors
1024 if (hr == DXGI_ERROR_INVALID_CALL)
1026 CreateWindowSizeDependentResources();
1030 if (m_d3dContext == m_deferrContext)
1032 m_deferrContext->OMSetRenderTargets(1, m_backBufferTex.GetAddressOfRTV(), m_d3dDepthStencilView.Get());
1036 void DX::DeviceResources::ClearDepthStencil() const
1038 m_deferrContext->ClearDepthStencilView(m_d3dDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0, 0);
1041 void DX::DeviceResources::ClearRenderTarget(ID3D11RenderTargetView* pRTView, float color[4]) const
1043 m_deferrContext->ClearRenderTargetView(pRTView, color);
1046 void DX::DeviceResources::HandleOutputChange(const std::function<bool(DXGI_OUTPUT_DESC)>& cmpFunc)
1048 DXGI_ADAPTER_DESC currentDesc = {};
1049 DXGI_ADAPTER_DESC foundDesc = {};
1051 ComPtr<IDXGIFactory1> factory;
1052 if (m_adapter)
1053 m_adapter->GetDesc(&currentDesc);
1055 CreateDXGIFactory1(IID_IDXGIFactory1, &factory);
1057 ComPtr<IDXGIAdapter1> adapter;
1058 for (int i = 0; factory->EnumAdapters1(i, adapter.ReleaseAndGetAddressOf()) != DXGI_ERROR_NOT_FOUND; i++)
1060 adapter->GetDesc(&foundDesc);
1061 ComPtr<IDXGIOutput> output;
1062 for (int j = 0; adapter->EnumOutputs(j, output.ReleaseAndGetAddressOf()) != DXGI_ERROR_NOT_FOUND; j++)
1064 DXGI_OUTPUT_DESC outputDesc;
1065 output->GetDesc(&outputDesc);
1066 if (cmpFunc(outputDesc))
1068 output.As(&m_output);
1069 m_outputDesc = outputDesc;
1070 // check if adapter is changed
1071 if (currentDesc.AdapterLuid.HighPart != foundDesc.AdapterLuid.HighPart
1072 || currentDesc.AdapterLuid.LowPart != foundDesc.AdapterLuid.LowPart)
1074 // adapter is changed
1075 m_adapter = adapter;
1076 CLog::LogF(LOGDEBUG, "selected {} adapter. ",
1077 KODI::PLATFORM::WINDOWS::FromW(foundDesc.Description));
1078 // (re)init hooks into new driver
1079 Windowing()->InitHooks(output.Get());
1080 // recreate d3d11 device on new adapter
1081 if (m_d3dDevice)
1082 HandleDeviceLost(false);
1084 return;
1090 bool DX::DeviceResources::CreateFactory()
1092 HRESULT hr;
1093 #if defined(_DEBUG) && defined(TARGET_WINDOWS_STORE)
1094 bool debugDXGI = false;
1096 ComPtr<IDXGIInfoQueue> dxgiInfoQueue;
1097 if (SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(dxgiInfoQueue.GetAddressOf()))))
1099 debugDXGI = true;
1101 hr = CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, IID_PPV_ARGS(m_dxgiFactory.ReleaseAndGetAddressOf())); RETURN_ERR(false);
1103 dxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true);
1104 dxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true);
1108 if (!debugDXGI)
1109 #endif
1110 hr = CreateDXGIFactory1(IID_PPV_ARGS(m_dxgiFactory.ReleaseAndGetAddressOf())); RETURN_ERR(false);
1112 return true;
1115 void DX::DeviceResources::SetMonitor(HMONITOR monitor)
1117 HandleOutputChange([monitor](DXGI_OUTPUT_DESC outputDesc) {
1118 return outputDesc.Monitor == monitor;
1122 void DX::DeviceResources::RegisterDeviceNotify(IDeviceNotify* deviceNotify)
1124 m_deviceNotify = deviceNotify;
1127 HMONITOR DX::DeviceResources::GetMonitor() const
1129 if (m_swapChain)
1131 ComPtr<IDXGIOutput> output;
1132 GetOutput(output.GetAddressOf());
1133 if (output)
1135 DXGI_OUTPUT_DESC desc;
1136 output->GetDesc(&desc);
1137 return desc.Monitor;
1140 return nullptr;
1143 bool DX::DeviceResources::IsStereoAvailable() const
1145 if (m_dxgiFactory)
1146 return m_dxgiFactory->IsWindowedStereoEnabled();
1148 return false;
1151 void DX::DeviceResources::CheckNV12SharedTexturesSupport()
1153 if (m_d3dFeatureLevel < D3D_FEATURE_LEVEL_10_0 ||
1154 CSysInfo::GetWindowsDeviceFamily() != CSysInfo::Desktop)
1155 return;
1157 D3D11_FEATURE_DATA_D3D11_OPTIONS4 op4 = {};
1158 HRESULT hr = m_d3dDevice->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS4, &op4, sizeof(op4));
1159 m_NV12SharedTexturesSupport = SUCCEEDED(hr) && !!op4.ExtendedNV12SharedTextureSupported;
1160 CLog::LogF(LOGINFO, "extended NV12 shared textures is{}supported",
1161 m_NV12SharedTexturesSupport ? " " : " NOT ");
1164 void DX::DeviceResources::CheckDXVA2SharedDecoderSurfaces()
1166 if (CSysInfo::GetWindowsDeviceFamily() != CSysInfo::Desktop)
1167 return;
1169 VideoDriverInfo driver = GetVideoDriverVersion();
1170 driver.Log();
1172 if (!m_NV12SharedTexturesSupport)
1173 return;
1175 const DXGI_ADAPTER_DESC ad = GetAdapterDesc();
1177 m_DXVA2SharedDecoderSurfaces =
1178 ad.VendorId == PCIV_Intel ||
1179 (ad.VendorId == PCIV_NVIDIA && driver.valid && driver.majorVersion >= 465) ||
1180 (ad.VendorId == PCIV_AMD && driver.valid && driver.majorVersion >= 30 &&
1181 m_d3dFeatureLevel >= D3D_FEATURE_LEVEL_12_1);
1183 CLog::LogF(LOGINFO, "DXVA2 shared decoder surfaces is{}supported",
1184 m_DXVA2SharedDecoderSurfaces ? " " : " NOT ");
1186 m_DXVA2UseFence = m_DXVA2SharedDecoderSurfaces &&
1187 (ad.VendorId == PCIV_NVIDIA || ad.VendorId == PCIV_AMD) &&
1188 CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10_1703);
1190 if (m_DXVA2SharedDecoderSurfaces)
1191 CLog::LogF(LOGINFO, "DXVA2 shared decoder surfaces {} fence synchronization.",
1192 m_DXVA2UseFence ? "WITH" : "WITHOUT");
1194 m_DXVASuperResolutionSupport =
1195 m_d3dFeatureLevel >= D3D_FEATURE_LEVEL_12_1 &&
1196 ((ad.VendorId == PCIV_Intel && driver.valid && driver.majorVersion >= 31) ||
1197 (ad.VendorId == PCIV_NVIDIA && driver.valid && driver.majorVersion >= 530));
1199 if (m_DXVASuperResolutionSupport)
1200 CLog::LogF(LOGINFO, "DXVA Video Super Resolution is potentially supported");
1203 VideoDriverInfo DX::DeviceResources::GetVideoDriverVersion() const
1205 if (!m_adapter)
1206 return {};
1208 VideoDriverInfo driver{};
1209 const DXGI_ADAPTER_DESC ad = GetAdapterDesc();
1211 // Version retrieval with DXGI is more modern but requires WDDM >= 2.3 for guarantee of same
1212 // version returned by all driver components. Fallback to older method for older drivers.
1213 LARGE_INTEGER rawVersion{};
1214 if (SUCCEEDED(m_adapter->CheckInterfaceSupport(__uuidof(IDXGIDevice), &rawVersion)) &&
1215 static_cast<uint16_t>(rawVersion.QuadPart >> 48) >= 23)
1216 driver = CWIN32Util::FormatVideoDriverInfo(ad.VendorId, rawVersion.QuadPart);
1218 if (!driver.valid)
1219 driver = CWIN32Util::GetVideoDriverInfo(ad.VendorId, ad.Description);
1221 if (!driver.valid)
1222 driver = CWIN32Util::GetVideoDriverInfoDX(ad.VendorId, ad.AdapterLuid);
1224 return driver;
1227 #if defined(TARGET_WINDOWS_DESKTOP)
1228 // This method is called when the window (WND) is created (or re-created).
1229 void DX::DeviceResources::SetWindow(HWND window)
1231 m_window = window;
1233 CreateDeviceIndependentResources();
1234 CreateDeviceResources();
1236 #elif defined(TARGET_WINDOWS_STORE)
1237 // This method is called when the CoreWindow is created (or re-created).
1238 void DX::DeviceResources::SetWindow(const winrt::Windows::UI::Core::CoreWindow& window)
1240 using namespace winrt::Windows::UI::Core;
1241 using namespace winrt::Windows::Graphics::Display;
1243 m_coreWindow = window;
1244 auto dispatcher = m_coreWindow.Dispatcher();
1245 DispatchedHandler handler([&]()
1247 auto coreWindow = CoreWindow::GetForCurrentThread();
1248 m_logicalSize = winrt::Size(coreWindow.Bounds().Width, coreWindow.Bounds().Height);
1249 m_dpi = DisplayInformation::GetForCurrentView().LogicalDpi();
1250 SetWindowPos(coreWindow.Bounds());
1252 if (dispatcher.HasThreadAccess())
1253 handler();
1254 else
1255 dispatcher.RunAsync(CoreDispatcherPriority::High, handler).get();
1257 CreateDeviceIndependentResources();
1258 CreateDeviceResources();
1259 // we have to call this because we will not get initial WM_SIZE
1260 CreateWindowSizeDependentResources();
1263 void DX::DeviceResources::SetWindowPos(winrt::Rect rect)
1265 int centerX = rect.X + rect.Width / 2;
1266 int centerY = rect.Y + rect.Height / 2;
1268 HandleOutputChange([centerX, centerY](DXGI_OUTPUT_DESC outputDesc) {
1269 // DesktopCoordinates depends on the DPI of the desktop
1270 return outputDesc.DesktopCoordinates.left <= centerX && outputDesc.DesktopCoordinates.right >= centerX
1271 && outputDesc.DesktopCoordinates.top <= centerY && outputDesc.DesktopCoordinates.bottom >= centerY;
1275 // Call this method when the app suspends. It provides a hint to the driver that the app
1276 // is entering an idle state and that temporary buffers can be reclaimed for use by other apps.
1277 void DX::DeviceResources::Trim() const
1279 ComPtr<IDXGIDevice3> dxgiDevice;
1280 m_d3dDevice.As(&dxgiDevice);
1282 dxgiDevice->Trim();
1285 #endif
1287 void DX::DeviceResources::SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const
1289 ComPtr<IDXGISwapChain4> swapChain4;
1291 if (!m_swapChain)
1292 return;
1294 if (SUCCEEDED(m_swapChain.As(&swapChain4)))
1296 if (SUCCEEDED(swapChain4->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(hdr10), &hdr10)))
1298 CLog::LogF(LOGDEBUG,
1299 "(raw) RP {} {} | GP {} {} | BP {} {} | WP {} {} | Max ML {} | min ML "
1300 "{} | Max CLL {} | Max FALL {}",
1301 hdr10.RedPrimary[0], hdr10.RedPrimary[1], hdr10.GreenPrimary[0],
1302 hdr10.GreenPrimary[1], hdr10.BluePrimary[0], hdr10.BluePrimary[1],
1303 hdr10.WhitePoint[0], hdr10.WhitePoint[1], hdr10.MaxMasteringLuminance,
1304 hdr10.MinMasteringLuminance, hdr10.MaxContentLightLevel,
1305 hdr10.MaxFrameAverageLightLevel);
1307 constexpr double FACTOR_1 = 50000.0;
1308 constexpr double FACTOR_2 = 10000.0;
1309 const double RP_0 = static_cast<double>(hdr10.RedPrimary[0]) / FACTOR_1;
1310 const double RP_1 = static_cast<double>(hdr10.RedPrimary[1]) / FACTOR_1;
1311 const double GP_0 = static_cast<double>(hdr10.GreenPrimary[0]) / FACTOR_1;
1312 const double GP_1 = static_cast<double>(hdr10.GreenPrimary[1]) / FACTOR_1;
1313 const double BP_0 = static_cast<double>(hdr10.BluePrimary[0]) / FACTOR_1;
1314 const double BP_1 = static_cast<double>(hdr10.BluePrimary[1]) / FACTOR_1;
1315 const double WP_0 = static_cast<double>(hdr10.WhitePoint[0]) / FACTOR_1;
1316 const double WP_1 = static_cast<double>(hdr10.WhitePoint[1]) / FACTOR_1;
1317 const double Max_ML = static_cast<double>(hdr10.MaxMasteringLuminance) / FACTOR_2;
1318 const double min_ML = static_cast<double>(hdr10.MinMasteringLuminance) / FACTOR_2;
1320 CLog::LogF(LOGINFO,
1321 "RP {:.3f} {:.3f} | GP {:.3f} {:.3f} | BP {:.3f} {:.3f} | WP {:.3f} "
1322 "{:.3f} | Max ML {:.0f} | min ML {:.4f} | Max CLL {} | Max FALL {}",
1323 RP_0, RP_1, GP_0, GP_1, BP_0, BP_1, WP_0, WP_1, Max_ML, min_ML,
1324 hdr10.MaxContentLightLevel, hdr10.MaxFrameAverageLightLevel);
1326 else
1328 CLog::LogF(LOGERROR, "DXGI SetHDRMetaData failed");
1333 void DX::DeviceResources::SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace)
1335 ComPtr<IDXGISwapChain3> swapChain3;
1337 if (!m_swapChain)
1338 return;
1340 if (SUCCEEDED(m_swapChain.As(&swapChain3)))
1342 if (SUCCEEDED(swapChain3->SetColorSpace1(colorSpace)))
1344 m_IsTransferPQ = (colorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
1346 if (m_IsTransferPQ)
1347 DX::Windowing()->CacheSystemSdrPeakLuminance();
1349 CLog::LogF(LOGDEBUG, "DXGI SetColorSpace1 {} success",
1350 DX::DXGIColorSpaceTypeToString(colorSpace));
1352 else
1354 CLog::LogF(LOGERROR, "DXGI SetColorSpace1 {} failed",
1355 DX::DXGIColorSpaceTypeToString(colorSpace));
1360 HDR_STATUS DX::DeviceResources::ToggleHDR()
1362 DXGI_MODE_DESC md = {};
1363 GetDisplayMode(&md);
1365 // Xbox uses only full screen windowed mode and not needs recreate swapchain.
1366 // Recreate swapchain causes native 4K resolution is lost and quality obtained
1367 // is equivalent to 1080p upscaled to 4K (TO DO: investigate root cause).
1368 const bool isXbox = (CSysInfo::GetWindowsDeviceFamily() == CSysInfo::Xbox);
1370 DX::Windowing()->SetTogglingHDR(true);
1371 DX::Windowing()->SetAlteringWindow(true);
1373 // Toggle display HDR
1374 HDR_STATUS hdrStatus = CWIN32Util::ToggleWindowsHDR(md);
1376 // Kill swapchain
1377 if (!isXbox && m_swapChain && hdrStatus != HDR_STATUS::HDR_TOGGLE_FAILED)
1379 CLog::LogF(LOGDEBUG, "Re-create swapchain due HDR <-> SDR switch");
1380 DestroySwapChain();
1383 DX::Windowing()->SetAlteringWindow(false);
1385 // Re-create swapchain
1386 if (!isXbox && hdrStatus != HDR_STATUS::HDR_TOGGLE_FAILED)
1388 CreateWindowSizeDependentResources();
1390 DX::Windowing()->NotifyAppFocusChange(true);
1393 // On Xbox set new color space in same swapchain
1394 if (isXbox && hdrStatus != HDR_STATUS::HDR_TOGGLE_FAILED)
1396 if (hdrStatus == HDR_STATUS::HDR_ON)
1398 SetHdrColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
1399 m_IsHDROutput = true;
1401 else
1403 SetHdrColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
1404 m_IsHDROutput = false;
1408 return hdrStatus;
1411 void DX::DeviceResources::ApplyDisplaySettings()
1413 CLog::LogF(LOGDEBUG, "Re-create swapchain due Display Settings changed");
1415 DestroySwapChain();
1416 CreateWindowSizeDependentResources();
1419 DEBUG_INFO_RENDER DX::DeviceResources::GetDebugInfo() const
1421 if (!m_swapChain)
1422 return {};
1424 DXGI_SWAP_CHAIN_DESC1 desc = {};
1425 m_swapChain->GetDesc1(&desc);
1427 DXGI_MODE_DESC md = {};
1428 GetDisplayMode(&md);
1430 const int bits = (desc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) ? 10 : 8;
1431 const int max = (desc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) ? 1024 : 256;
1432 const int range_min = DX::Windowing()->UseLimitedColor() ? (max * 16) / 256 : 0;
1433 const int range_max = DX::Windowing()->UseLimitedColor() ? (max * 235) / 256 : max - 1;
1435 DEBUG_INFO_RENDER info;
1437 info.renderFlags = StringUtils::Format(
1438 "Swapchain: {} buffers, flip {}, {}, EOTF: {} (Windows HDR {})", desc.BufferCount,
1439 (desc.SwapEffect == DXGI_SWAP_EFFECT_FLIP_DISCARD) ? "discard" : "sequential",
1440 Windowing()->IsFullScreen()
1441 ? ((desc.Flags & DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH) ? "fullscreen exclusive"
1442 : "fullscreen windowed")
1443 : "windowed screen",
1444 m_IsTransferPQ ? "PQ" : "SDR", m_IsHDROutput ? "on" : "off");
1446 info.videoOutput = StringUtils::Format(
1447 "Surfaces: {}x{}{} @ {:.3f} Hz, pixel: {} {}-bit, range: {} ({}-{})", desc.Width, desc.Height,
1448 (md.ScanlineOrdering > DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE) ? "i" : "p",
1449 static_cast<double>(md.RefreshRate.Numerator) /
1450 static_cast<double>(md.RefreshRate.Denominator),
1451 DXGIFormatToShortString(desc.Format), bits,
1452 DX::Windowing()->UseLimitedColor() ? "limited" : "full", range_min, range_max);
1454 return info;
1457 std::vector<DXGI_COLOR_SPACE_TYPE> DX::DeviceResources::GetSwapChainColorSpaces() const
1459 if (!m_swapChain)
1460 return {};
1462 std::vector<DXGI_COLOR_SPACE_TYPE> result;
1463 HRESULT hr;
1465 ComPtr<IDXGISwapChain3> swapChain3;
1466 if (SUCCEEDED(hr = m_swapChain.As(&swapChain3)))
1468 UINT colorSpaceSupport = 0;
1469 for (UINT colorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
1470 colorSpace < DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_TOPLEFT_P2020; colorSpace++)
1472 DXGI_COLOR_SPACE_TYPE cs = static_cast<DXGI_COLOR_SPACE_TYPE>(colorSpace);
1473 if (SUCCEEDED(swapChain3->CheckColorSpaceSupport(cs, &colorSpaceSupport)) &&
1474 (colorSpaceSupport & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT))
1475 result.push_back(cs);
1478 else
1480 CLog::LogF(LOGDEBUG, "IDXGISwapChain3 is not available. Error {}",
1481 CWIN32Util::FormatHRESULT(hr));
1483 return result;
1486 bool DX::DeviceResources::SetMultithreadProtected(bool enabled) const
1488 BOOL wasEnabled = FALSE;
1489 ComPtr<ID3D11Multithread> multithread;
1490 HRESULT hr = m_d3dDevice.As(&multithread);
1491 if (SUCCEEDED(hr))
1492 wasEnabled = multithread->SetMultithreadProtected(enabled ? TRUE : FALSE);
1494 return (wasEnabled == TRUE ? true : false);
1497 bool DX::DeviceResources::IsGCNOrOlder() const
1499 if (CSysInfo::GetWindowsDeviceFamily() == CSysInfo::Xbox)
1500 return false;
1502 const VideoDriverInfo driver = GetVideoDriverVersion();
1504 if (driver.vendorId != PCIV_AMD)
1505 return false;
1507 if (driver.valid && CWIN32Util::IsDriverVersionAtLeast(driver.version, "31.0.22000.0"))
1508 return false;
1510 return true;