diff options
Diffstat (limited to 'xbmc/rendering/dx')
-rw-r--r-- | xbmc/rendering/dx/CMakeLists.txt | 11 | ||||
-rw-r--r-- | xbmc/rendering/dx/DeviceResources.cpp | 1361 | ||||
-rw-r--r-- | xbmc/rendering/dx/DeviceResources.h | 187 | ||||
-rw-r--r-- | xbmc/rendering/dx/DirectXHelper.h | 198 | ||||
-rw-r--r-- | xbmc/rendering/dx/RenderContext.h | 31 | ||||
-rw-r--r-- | xbmc/rendering/dx/RenderSystemDX.cpp | 706 | ||||
-rw-r--r-- | xbmc/rendering/dx/RenderSystemDX.h | 106 | ||||
-rw-r--r-- | xbmc/rendering/dx/ScreenshotSurfaceWindows.cpp | 110 | ||||
-rw-r--r-- | xbmc/rendering/dx/ScreenshotSurfaceWindows.h | 22 |
9 files changed, 2732 insertions, 0 deletions
diff --git a/xbmc/rendering/dx/CMakeLists.txt b/xbmc/rendering/dx/CMakeLists.txt new file mode 100644 index 0000000..7466907 --- /dev/null +++ b/xbmc/rendering/dx/CMakeLists.txt @@ -0,0 +1,11 @@ +set(SOURCES DeviceResources.cpp + RenderSystemDX.cpp + ScreenshotSurfaceWindows.cpp) + +set(HEADERS DeviceResources.h + DirectXHelper.h + RenderContext.h + RenderSystemDX.h + ScreenshotSurfaceWindows.h) + +core_add_library(rendering_dx) diff --git a/xbmc/rendering/dx/DeviceResources.cpp b/xbmc/rendering/dx/DeviceResources.cpp new file mode 100644 index 0000000..b3ae890 --- /dev/null +++ b/xbmc/rendering/dx/DeviceResources.cpp @@ -0,0 +1,1361 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DeviceResources.h" + +#include "DirectXHelper.h" +#include "RenderContext.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "messaging/ApplicationMessenger.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/SystemInfo.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" + +#include "platform/win32/CharsetConverter.h" +#include "platform/win32/WIN32Util.h" + +#ifdef TARGET_WINDOWS_STORE +#include <winrt/Windows.Graphics.Display.Core.h> + +extern "C" +{ +#include <libavutil/rational.h> +} +#endif + +#ifdef _DEBUG +#include <dxgidebug.h> +#pragma comment(lib, "dxgi.lib") +#endif // _DEBUG + +using namespace DirectX; +using namespace Microsoft::WRL; +using namespace Concurrency; +namespace winrt +{ + using namespace Windows::Foundation; +} + +#ifdef _DEBUG +#define breakOnDebug __debugbreak() +#else +#define breakOnDebug +#endif +#define LOG_HR(hr) \ + CLog::LogF(LOGERROR, "function call at line {} ends with error: {}", __LINE__, \ + DX::GetErrorDescription(hr)); +#define CHECK_ERR() if (FAILED(hr)) { LOG_HR(hr); breakOnDebug; return; } +#define RETURN_ERR(ret) if (FAILED(hr)) { LOG_HR(hr); breakOnDebug; return (##ret); } + +bool DX::DeviceResources::CBackBuffer::Acquire(ID3D11Texture2D* pTexture) +{ + if (!pTexture) + return false; + + D3D11_TEXTURE2D_DESC desc; + pTexture->GetDesc(&desc); + + m_width = desc.Width; + m_height = desc.Height; + m_format = desc.Format; + m_usage = desc.Usage; + + m_texture = pTexture; + return true; +} + +std::shared_ptr<DX::DeviceResources> DX::DeviceResources::Get() +{ + static std::shared_ptr<DeviceResources> sDeviceResources(new DeviceResources); + return sDeviceResources; +} + +// Constructor for DeviceResources. +DX::DeviceResources::DeviceResources() + : m_screenViewport() + , m_d3dFeatureLevel(D3D_FEATURE_LEVEL_9_1) + , m_outputSize() + , m_logicalSize() + , m_dpi(DisplayMetrics::Dpi100) + , m_effectiveDpi(DisplayMetrics::Dpi100) + , m_deviceNotify(nullptr) + , m_stereoEnabled(false) + , m_bDeviceCreated(false) + , m_IsHDROutput(false) + , m_IsTransferPQ(false) +{ +} + +DX::DeviceResources::~DeviceResources() = default; + +void DX::DeviceResources::Release() +{ + if (!m_bDeviceCreated) + return; + + ReleaseBackBuffer(); + OnDeviceLost(true); + DestroySwapChain(); + + m_adapter = nullptr; + m_dxgiFactory = nullptr; + m_output = nullptr; + m_deferrContext = nullptr; + m_d3dContext = nullptr; + m_d3dDevice = nullptr; + m_bDeviceCreated = false; +#ifdef _DEBUG + if (m_d3dDebug) + { + m_d3dDebug->ReportLiveDeviceObjects(D3D11_RLDO_SUMMARY | D3D11_RLDO_DETAIL); + m_d3dDebug = nullptr; + } +#endif +} + +void DX::DeviceResources::GetOutput(IDXGIOutput** ppOutput) const +{ + ComPtr<IDXGIOutput> pOutput; + if (!m_swapChain || FAILED(m_swapChain->GetContainingOutput(pOutput.GetAddressOf())) || !pOutput) + m_output.As(&pOutput); + *ppOutput = pOutput.Detach(); +} + +void DX::DeviceResources::GetAdapterDesc(DXGI_ADAPTER_DESC* desc) const +{ + if (m_adapter) + m_adapter->GetDesc(desc); + + // GetDesc() returns VendorId == 0 in Xbox however, we need to know that + // GPU is AMD to apply workarounds in DXVA.cpp CheckCompatibility() same as desktop + if (CSysInfo::GetWindowsDeviceFamily() == CSysInfo::Xbox) + desc->VendorId = PCIV_AMD; +} + +void DX::DeviceResources::GetDisplayMode(DXGI_MODE_DESC* mode) const +{ + DXGI_OUTPUT_DESC outDesc; + ComPtr<IDXGIOutput> pOutput; + DXGI_SWAP_CHAIN_DESC scDesc; + + if (!m_swapChain) + return; + + m_swapChain->GetDesc(&scDesc); + + GetOutput(pOutput.GetAddressOf()); + pOutput->GetDesc(&outDesc); + + // desktop coords depend on DPI + mode->Width = DX::ConvertDipsToPixels(outDesc.DesktopCoordinates.right - outDesc.DesktopCoordinates.left, m_dpi); + mode->Height = DX::ConvertDipsToPixels(outDesc.DesktopCoordinates.bottom - outDesc.DesktopCoordinates.top, m_dpi); + mode->Format = scDesc.BufferDesc.Format; + mode->Scaling = scDesc.BufferDesc.Scaling; + mode->ScanlineOrdering = scDesc.BufferDesc.ScanlineOrdering; + +#ifdef TARGET_WINDOWS_DESKTOP + DEVMODEW sDevMode = {}; + sDevMode.dmSize = sizeof(sDevMode); + + // EnumDisplaySettingsW is only one way to detect current refresh rate + if (EnumDisplaySettingsW(outDesc.DeviceName, ENUM_CURRENT_SETTINGS, &sDevMode)) + { + int i = (((sDevMode.dmDisplayFrequency + 1) % 24) == 0 || ((sDevMode.dmDisplayFrequency + 1) % 30) == 0) ? 1 : 0; + mode->RefreshRate.Numerator = (sDevMode.dmDisplayFrequency + i) * 1000; + mode->RefreshRate.Denominator = 1000 + i; + if (sDevMode.dmDisplayFlags & DM_INTERLACED) + { + mode->RefreshRate.Numerator *= 2; + mode->ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UPPER_FIELD_FIRST; // guessing + } + else + mode->ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE; + } +#else + using namespace winrt::Windows::Graphics::Display::Core; + + auto hdmiInfo = HdmiDisplayInformation::GetForCurrentView(); + if (hdmiInfo) // Xbox only + { + auto currentMode = hdmiInfo.GetCurrentDisplayMode(); + AVRational refresh = av_d2q(currentMode.RefreshRate(), 60000); + mode->RefreshRate.Numerator = refresh.num; + mode->RefreshRate.Denominator = refresh.den; + } +#endif +} + +void DX::DeviceResources::SetViewPort(D3D11_VIEWPORT& viewPort) const +{ + // convert logical viewport to real + D3D11_VIEWPORT realViewPort = + { + viewPort.TopLeftX, + viewPort.TopLeftY, + viewPort.Width, + viewPort.Height, + viewPort.MinDepth, + viewPort.MinDepth + }; + + m_deferrContext->RSSetViewports(1, &realViewPort); +} + +bool DX::DeviceResources::SetFullScreen(bool fullscreen, RESOLUTION_INFO& res) +{ + if (!m_bDeviceCreated || !m_swapChain) + return false; + + critical_section::scoped_lock lock(m_criticalSection); + + BOOL bFullScreen; + m_swapChain->GetFullscreenState(&bFullScreen, nullptr); + + CLog::LogF(LOGDEBUG, "switching from {}({:.0f} x {:.0f}) to {}({} x {})", + bFullScreen ? "fullscreen " : "", m_outputSize.Width, m_outputSize.Height, + fullscreen ? "fullscreen " : "", res.iWidth, res.iHeight); + + bool recreate = m_stereoEnabled != (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() == RENDER_STEREO_MODE_HARDWAREBASED); + if (!!bFullScreen && !fullscreen) + { + CLog::LogF(LOGDEBUG, "switching to windowed"); + recreate |= SUCCEEDED(m_swapChain->SetFullscreenState(false, nullptr)); + } + else if (fullscreen) + { + const bool isResValid = res.iWidth > 0 && res.iHeight > 0 && res.fRefreshRate > 0.f; + if (isResValid) + { + DXGI_MODE_DESC currentMode = {}; + GetDisplayMode(¤tMode); + DXGI_SWAP_CHAIN_DESC scDesc; + m_swapChain->GetDesc(&scDesc); + + bool is_interlaced = scDesc.BufferDesc.ScanlineOrdering > DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE; + float refreshRate = res.fRefreshRate; + if (res.dwFlags & D3DPRESENTFLAG_INTERLACED) + refreshRate *= 2; + + if (currentMode.Width != res.iWidth + || currentMode.Height != res.iHeight + || DX::RationalToFloat(currentMode.RefreshRate) != refreshRate + || is_interlaced != (res.dwFlags & D3DPRESENTFLAG_INTERLACED ? true : false) + // force resolution change for stereo mode + // some drivers unable to create stereo swapchain if mode does not match @23.976 + || CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() == RENDER_STEREO_MODE_HARDWAREBASED) + { + CLog::Log(LOGDEBUG, __FUNCTION__ ": changing display mode to {}x{}@{:0.3f}", res.iWidth, + res.iHeight, res.fRefreshRate, + res.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : ""); + + int refresh = static_cast<int>(res.fRefreshRate); + int i = (refresh + 1) % 24 == 0 || (refresh + 1) % 30 == 0 ? 1 : 0; + + currentMode.Width = res.iWidth; + currentMode.Height = res.iHeight; + currentMode.RefreshRate.Numerator = (refresh + i) * 1000; + currentMode.RefreshRate.Denominator = 1000 + i; + if (res.dwFlags & D3DPRESENTFLAG_INTERLACED) + { + currentMode.RefreshRate.Numerator *= 2; + currentMode.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UPPER_FIELD_FIRST; // guessing; + } + // sometimes the OS silently brings Kodi out of full screen mode + // in this case switching a resolution has no any effect and + // we have to enter into full screen mode before switching + if (!bFullScreen) + { + ComPtr<IDXGIOutput> pOutput; + GetOutput(pOutput.GetAddressOf()); + + CLog::LogF(LOGDEBUG, "fixup fullscreen mode before switching resolution"); + recreate |= SUCCEEDED(m_swapChain->SetFullscreenState(true, pOutput.Get())); + m_swapChain->GetFullscreenState(&bFullScreen, nullptr); + } + bool resized = SUCCEEDED(m_swapChain->ResizeTarget(¤tMode)); + if (resized) + { + // some system doesn't inform windowing about desktop size changes + // so we have to change output size before resizing buffers + m_outputSize.Width = static_cast<float>(currentMode.Width); + m_outputSize.Height = static_cast<float>(currentMode.Height); + } + recreate |= resized; + } + } + if (!bFullScreen) + { + ComPtr<IDXGIOutput> pOutput; + GetOutput(pOutput.GetAddressOf()); + + CLog::LogF(LOGDEBUG, "switching to fullscreen"); + recreate |= SUCCEEDED(m_swapChain->SetFullscreenState(true, pOutput.Get())); + } + } + + // resize backbuffer to proper handle fullscreen/stereo transition + if (recreate) + ResizeBuffers(); + + CLog::LogF(LOGDEBUG, "switching done."); + + return recreate; +} + +// Configures resources that don't depend on the Direct3D device. +void DX::DeviceResources::CreateDeviceIndependentResources() +{ +} + +// Configures the Direct3D device, and stores handles to it and the device context. +void DX::DeviceResources::CreateDeviceResources() +{ + CLog::LogF(LOGDEBUG, "creating DirectX 11 device."); + + CreateFactory(); + + UINT creationFlags = D3D11_CREATE_DEVICE_VIDEO_SUPPORT; +#if defined(_DEBUG) + if (DX::SdkLayersAvailable()) + { + // If the project is in a debug build, enable debugging via SDK Layers with this flag. + creationFlags |= D3D11_CREATE_DEVICE_DEBUG; + } +#endif + + // This array defines the set of DirectX hardware feature levels this app will support. + // Note the ordering should be preserved. + // Don't forget to declare your application's minimum required feature level in its + // description. All applications are assumed to support 9.1 unless otherwise stated. + std::vector<D3D_FEATURE_LEVEL> featureLevels; + if (CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10)) + { + featureLevels.push_back(D3D_FEATURE_LEVEL_12_1); + featureLevels.push_back(D3D_FEATURE_LEVEL_12_0); + } + if (CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin8)) + featureLevels.push_back(D3D_FEATURE_LEVEL_11_1); + featureLevels.push_back(D3D_FEATURE_LEVEL_11_0); + featureLevels.push_back(D3D_FEATURE_LEVEL_10_1); + featureLevels.push_back(D3D_FEATURE_LEVEL_10_0); + featureLevels.push_back(D3D_FEATURE_LEVEL_9_3); + featureLevels.push_back(D3D_FEATURE_LEVEL_9_2); + featureLevels.push_back(D3D_FEATURE_LEVEL_9_1); + + // Create the Direct3D 11 API device object and a corresponding context. + ComPtr<ID3D11Device> device; + ComPtr<ID3D11DeviceContext> context; + + D3D_DRIVER_TYPE drivertType = m_adapter != nullptr ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE; + HRESULT hr = D3D11CreateDevice( + m_adapter.Get(), // Create a device on specified adapter. + drivertType, // Create a device using scepcified driver. + nullptr, // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE. + creationFlags, // Set debug and Direct2D compatibility flags. + featureLevels.data(), // List of feature levels this app can support. + featureLevels.size(), // Size of the list above. + D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows Store apps. + &device, // Returns the Direct3D device created. + &m_d3dFeatureLevel, // Returns feature level of device created. + &context // Returns the device immediate context. + ); + + if (FAILED(hr)) + { + CLog::LogF(LOGERROR, "unable to create hardware device, trying to create WARP devices then."); + hr = D3D11CreateDevice( + nullptr, + D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device. + nullptr, + creationFlags, + featureLevels.data(), + featureLevels.size(), + D3D11_SDK_VERSION, + &device, + &m_d3dFeatureLevel, + &context + ); + if (FAILED(hr)) + { + CLog::LogF(LOGFATAL, "unable to create WARP device. Rendering in not possible."); + CHECK_ERR(); + } + } + + // Store pointers to the Direct3D 11.1 API device and immediate context. + hr = device.As(&m_d3dDevice); CHECK_ERR(); + + // Check shared textures support + CheckNV12SharedTexturesSupport(); + +#ifdef _DEBUG + if (SUCCEEDED(m_d3dDevice.As(&m_d3dDebug))) + { + ComPtr<ID3D11InfoQueue> d3dInfoQueue; + if (SUCCEEDED(m_d3dDebug.As(&d3dInfoQueue))) + { + std::vector<D3D11_MESSAGE_ID> hide = + { + D3D11_MESSAGE_ID_GETVIDEOPROCESSORFILTERRANGE_UNSUPPORTED, // avoid GETVIDEOPROCESSORFILTERRANGE_UNSUPPORTED (dx bug) + D3D11_MESSAGE_ID_DEVICE_RSSETSCISSORRECTS_NEGATIVESCISSOR // avoid warning for some labels out of screen + // Add more message IDs here as needed + }; + + D3D11_INFO_QUEUE_FILTER filter = {}; + filter.DenyList.NumIDs = hide.size(); + filter.DenyList.pIDList = hide.data(); + d3dInfoQueue->AddStorageFilterEntries(&filter); + } + } +#endif + + hr = context.As(&m_d3dContext); CHECK_ERR(); + hr = m_d3dDevice->CreateDeferredContext1(0, &m_deferrContext); CHECK_ERR(); + + if (!m_adapter) + { + ComPtr<IDXGIDevice1> dxgiDevice; + ComPtr<IDXGIAdapter> adapter; + hr = m_d3dDevice.As(&dxgiDevice); CHECK_ERR(); + hr = dxgiDevice->GetAdapter(&adapter); CHECK_ERR(); + hr = adapter.As(&m_adapter); CHECK_ERR(); + } + + DXGI_ADAPTER_DESC aDesc; + m_adapter->GetDesc(&aDesc); + + CLog::LogF(LOGINFO, "device is created on adapter '{}' with {}", + KODI::PLATFORM::WINDOWS::FromW(aDesc.Description), + GetFeatureLevelDescription(m_d3dFeatureLevel)); + + CheckDXVA2SharedDecoderSurfaces(); + + m_bDeviceCreated = true; +} + +void DX::DeviceResources::ReleaseBackBuffer() +{ + CLog::LogF(LOGDEBUG, "release buffers."); + + m_backBufferTex.Release(); + m_d3dDepthStencilView = nullptr; + if (m_deferrContext) + { + // Clear the previous window size specific context. + ID3D11RenderTargetView* nullViews[] = { nullptr, nullptr, nullptr, nullptr }; + m_deferrContext->OMSetRenderTargets(4, nullViews, nullptr); + FinishCommandList(false); + + m_deferrContext->Flush(); + m_d3dContext->Flush(); + } +} + +void DX::DeviceResources::CreateBackBuffer() +{ + if (!m_bDeviceCreated || !m_swapChain) + return; + + CLog::LogF(LOGDEBUG, "create buffers."); + + // Get swap chain back buffer. + ComPtr<ID3D11Texture2D> backBuffer; + HRESULT hr = m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer)); CHECK_ERR(); + + // Create back buffer texture from swap chain texture + if (!m_backBufferTex.Acquire(backBuffer.Get())) + { + CLog::LogF(LOGERROR, "failed to create render target."); + return; + } + + // Create a depth stencil view for use with 3D rendering if needed. + CD3D11_TEXTURE2D_DESC depthStencilDesc( + DXGI_FORMAT_D24_UNORM_S8_UINT, + lround(m_outputSize.Width), + lround(m_outputSize.Height), + 1, // This depth stencil view has only one texture. + 1, // Use a single mipmap level. + D3D11_BIND_DEPTH_STENCIL + ); + + ComPtr<ID3D11Texture2D> depthStencil; + hr = m_d3dDevice->CreateTexture2D( + &depthStencilDesc, + nullptr, + &depthStencil + ); CHECK_ERR(); + + CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D); + hr = m_d3dDevice->CreateDepthStencilView( + depthStencil.Get(), + &depthStencilViewDesc, + &m_d3dDepthStencilView + ); CHECK_ERR(); + + // Set the 3D rendering viewport to target the entire window. + m_screenViewport = CD3D11_VIEWPORT( + 0.0f, + 0.0f, + m_outputSize.Width, + m_outputSize.Height + ); + + m_deferrContext->RSSetViewports(1, &m_screenViewport); +} + +HRESULT DX::DeviceResources::CreateSwapChain(DXGI_SWAP_CHAIN_DESC1& desc, DXGI_SWAP_CHAIN_FULLSCREEN_DESC& fsDesc, IDXGISwapChain1** ppSwapChain) const +{ + HRESULT hr; +#ifdef TARGET_WINDOWS_DESKTOP + hr = m_dxgiFactory->CreateSwapChainForHwnd( + m_d3dDevice.Get(), + m_window, + &desc, + &fsDesc, + nullptr, + ppSwapChain + ); RETURN_ERR(hr); + hr = m_dxgiFactory->MakeWindowAssociation(m_window, /*DXGI_MWA_NO_WINDOW_CHANGES |*/ DXGI_MWA_NO_ALT_ENTER); +#else + hr = m_dxgiFactory->CreateSwapChainForCoreWindow( + m_d3dDevice.Get(), + winrt::get_unknown(m_coreWindow), + &desc, + nullptr, + ppSwapChain + ); RETURN_ERR(hr); +#endif + return hr; +} + +void DX::DeviceResources::DestroySwapChain() +{ + if (!m_swapChain) + return; + + BOOL bFullcreen = 0; + m_swapChain->GetFullscreenState(&bFullcreen, nullptr); + if (!!bFullcreen) + m_swapChain->SetFullscreenState(false, nullptr); // mandatory before releasing swapchain + m_swapChain = nullptr; + m_deferrContext->Flush(); + m_d3dContext->Flush(); + m_IsTransferPQ = false; +} + +void DX::DeviceResources::ResizeBuffers() +{ + if (!m_bDeviceCreated) + return; + + CLog::LogF(LOGDEBUG, "resize buffers."); + + bool bHWStereoEnabled = RENDER_STEREO_MODE_HARDWAREBASED == + CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode(); + bool windowed = true; + HRESULT hr = E_FAIL; + DXGI_SWAP_CHAIN_DESC1 scDesc = {}; + + if (m_swapChain) + { + BOOL bFullcreen = 0; + m_swapChain->GetFullscreenState(&bFullcreen, nullptr); + if (!!bFullcreen) + windowed = false; + + m_swapChain->GetDesc1(&scDesc); + if ((scDesc.Stereo == TRUE) != bHWStereoEnabled) // check if swapchain needs to be recreated + DestroySwapChain(); + } + + if (m_swapChain) // If the swap chain already exists, resize it. + { + m_swapChain->GetDesc1(&scDesc); + hr = m_swapChain->ResizeBuffers(scDesc.BufferCount, lround(m_outputSize.Width), + lround(m_outputSize.Height), scDesc.Format, + windowed ? 0 : DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH); + + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) + { + // If the device was removed for any reason, a new device and swap chain will need to be created. + HandleDeviceLost(hr == DXGI_ERROR_DEVICE_REMOVED); + + // Everything is set up now. Do not continue execution of this method. HandleDeviceLost will reenter this method + // and correctly set up the new device. + return; + } + else if (hr == DXGI_ERROR_INVALID_CALL) + { + // Called when Windows HDR is toggled externally to Kodi. + // Is forced to re-create swap chain to avoid crash. + CreateWindowSizeDependentResources(); + return; + } + CHECK_ERR(); + } + else // Otherwise, create a new one using the same adapter as the existing Direct3D device. + { + HDR_STATUS hdrStatus = CWIN32Util::GetWindowsHDRStatus(); + const bool isHdrEnabled = (hdrStatus == HDR_STATUS::HDR_ON); + bool use10bit = (hdrStatus != HDR_STATUS::HDR_UNSUPPORTED); + +// Xbox needs 10 bit swapchain to output true 4K resolution +#ifdef TARGET_WINDOWS_DESKTOP + DXGI_ADAPTER_DESC ad = {}; + GetAdapterDesc(&ad); + + // Some AMD graphics has issues with 10 bit in SDR. + // Enabled by default only in Intel and NVIDIA with latest drivers/hardware + if (m_d3dFeatureLevel < D3D_FEATURE_LEVEL_12_1 || ad.VendorId == PCIV_AMD) + use10bit = false; +#endif + + // 0 = Auto | 1 = Never | 2 = Always + int use10bitSetting = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_VIDEOSCREEN_10BITSURFACES); + + if (use10bitSetting == 1) + use10bit = false; + else if (use10bitSetting == 2) + use10bit = true; + + DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; + swapChainDesc.Width = lround(m_outputSize.Width); + swapChainDesc.Height = lround(m_outputSize.Height); + swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + swapChainDesc.Stereo = bHWStereoEnabled; + swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; +#ifdef TARGET_WINDOWS_DESKTOP + swapChainDesc.BufferCount = 6; // HDR 60 fps needs 6 buffers to avoid frame drops +#else + swapChainDesc.BufferCount = 3; // Xbox don't like 6 backbuffers (3 is fine even for 4K 60 fps) +#endif + // FLIP_DISCARD improves performance (needed in some systems for 4K HDR 60 fps) + swapChainDesc.SwapEffect = CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10) + ? DXGI_SWAP_EFFECT_FLIP_DISCARD + : DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + swapChainDesc.Flags = windowed ? 0 : DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + swapChainDesc.SampleDesc.Count = 1; + swapChainDesc.SampleDesc.Quality = 0; + + DXGI_SWAP_CHAIN_FULLSCREEN_DESC scFSDesc = {}; // unused for uwp + scFSDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; + scFSDesc.Windowed = windowed; + + ComPtr<IDXGISwapChain1> swapChain; + if (m_d3dFeatureLevel >= D3D_FEATURE_LEVEL_11_0 && !bHWStereoEnabled && + (isHdrEnabled || use10bit)) + { + swapChainDesc.Format = DXGI_FORMAT_R10G10B10A2_UNORM; + hr = CreateSwapChain(swapChainDesc, scFSDesc, &swapChain); + if (FAILED(hr)) + { + CLog::LogF(LOGWARNING, "creating 10bit swapchain failed, fallback to 8bit."); + swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + } + } + + if (!swapChain) + hr = CreateSwapChain(swapChainDesc, scFSDesc, &swapChain); + + if (FAILED(hr) && bHWStereoEnabled) + { + // switch to stereo mode failed, create mono swapchain + CLog::LogF(LOGERROR, "creating stereo swap chain failed with error."); + CLog::LogF(LOGINFO, "fallback to monoscopic mode."); + + swapChainDesc.Stereo = false; + bHWStereoEnabled = false; + + hr = CreateSwapChain(swapChainDesc, scFSDesc, &swapChain); CHECK_ERR(); + + // fallback to split_horizontal mode. + CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoMode( + RENDER_STEREO_MODE_SPLIT_HORIZONTAL); + } + + if (FAILED(hr)) + { + CLog::LogF(LOGERROR, "unable to create swapchain."); + return; + } + + m_IsHDROutput = (swapChainDesc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) && isHdrEnabled; + + const int bits = (swapChainDesc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) ? 10 : 8; + std::string flip = + (swapChainDesc.SwapEffect == DXGI_SWAP_EFFECT_FLIP_DISCARD) ? "discard" : "sequential"; + + CLog::LogF(LOGINFO, "{} bit swapchain is used with {} flip {} buffers and {} output", bits, + swapChainDesc.BufferCount, flip, m_IsHDROutput ? "HDR" : "SDR"); + + hr = swapChain.As(&m_swapChain); CHECK_ERR(); + m_stereoEnabled = bHWStereoEnabled; + + // Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and + // ensures that the application will only render after each VSync, minimizing power consumption. + ComPtr<IDXGIDevice1> dxgiDevice; + hr = m_d3dDevice.As(&dxgiDevice); CHECK_ERR(); + dxgiDevice->SetMaximumFrameLatency(1); + } + + CLog::LogF(LOGDEBUG, "end resize buffers."); +} + +// These resources need to be recreated every time the window size is changed. +void DX::DeviceResources::CreateWindowSizeDependentResources() +{ + ReleaseBackBuffer(); + + DestroySwapChain(); + + if (!m_dxgiFactory->IsCurrent()) // HDR toggling requires re-create factory + CreateFactory(); + + UpdateRenderTargetSize(); + ResizeBuffers(); + + CreateBackBuffer(); +} + +// Determine the dimensions of the render target and whether it will be scaled down. +void DX::DeviceResources::UpdateRenderTargetSize() +{ + m_effectiveDpi = m_dpi; + + // To improve battery life on high resolution devices, render to a smaller render target + // and allow the GPU to scale the output when it is presented. + if (!DisplayMetrics::SupportHighResolutions && m_dpi > DisplayMetrics::DpiThreshold) + { + float width = DX::ConvertDipsToPixels(m_logicalSize.Width, m_dpi); + float height = DX::ConvertDipsToPixels(m_logicalSize.Height, m_dpi); + + // When the device is in portrait orientation, height > width. Compare the + // larger dimension against the width threshold and the smaller dimension + // against the height threshold. + if (std::max(width, height) > DisplayMetrics::WidthThreshold && std::min(width, height) > DisplayMetrics::HeightThreshold) + { + // To scale the app we change the effective DPI. Logical size does not change. + m_effectiveDpi /= 2.0f; + } + } + + // Calculate the necessary render target size in pixels. + m_outputSize.Width = DX::ConvertDipsToPixels(m_logicalSize.Width, m_effectiveDpi); + m_outputSize.Height = DX::ConvertDipsToPixels(m_logicalSize.Height, m_effectiveDpi); + + // Prevent zero size DirectX content from being created. + m_outputSize.Width = std::max(m_outputSize.Width, 1.f); + m_outputSize.Height = std::max(m_outputSize.Height, 1.f); +} + +void DX::DeviceResources::Register(ID3DResource* resource) +{ + critical_section::scoped_lock lock(m_resourceSection); + m_resources.push_back(resource); +} + +void DX::DeviceResources::Unregister(ID3DResource* resource) +{ + critical_section::scoped_lock lock(m_resourceSection); + std::vector<ID3DResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource); + if (i != m_resources.end()) + m_resources.erase(i); +} + +void DX::DeviceResources::FinishCommandList(bool bExecute) const +{ + if (m_d3dContext == m_deferrContext) + return; + + ComPtr<ID3D11CommandList> pCommandList; + if (FAILED(m_deferrContext->FinishCommandList(true, &pCommandList))) + { + CLog::LogF(LOGERROR, "failed to finish command queue."); + return; + } + + if (bExecute) + m_d3dContext->ExecuteCommandList(pCommandList.Get(), false); +} + +// This method is called in the event handler for the SizeChanged event. +void DX::DeviceResources::SetLogicalSize(float width, float height) +{ + if +#if defined(TARGET_WINDOWS_DESKTOP) + (!m_window) +#else + (!m_coreWindow) +#endif + return; + + CLog::LogF(LOGDEBUG, "receive changing logical size to {:f} x {:f}", width, height); + + if (m_logicalSize.Width != width || m_logicalSize.Height != height) + { + CLog::LogF(LOGDEBUG, "change logical size to {:f} x {:f}", width, height); + + m_logicalSize = winrt::Size(width, height); + + UpdateRenderTargetSize(); + ResizeBuffers(); + } +} + +// This method is called in the event handler for the DpiChanged event. +void DX::DeviceResources::SetDpi(float dpi) +{ + dpi = std::max(dpi, DisplayMetrics::Dpi100); + if (dpi != m_dpi) + m_dpi = dpi; +} + +// This method is called in the event handler for the DisplayContentsInvalidated event. +void DX::DeviceResources::ValidateDevice() +{ + // The D3D Device is no longer valid if the default adapter changed since the device + // was created or if the device has been removed. + + // First, get the information for the default adapter from when the device was created. + ComPtr<IDXGIDevice1> dxgiDevice; + m_d3dDevice.As(&dxgiDevice); + + ComPtr<IDXGIAdapter> deviceAdapter; + dxgiDevice->GetAdapter(&deviceAdapter); + + ComPtr<IDXGIFactory2> dxgiFactory; + deviceAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory)); + + DXGI_ADAPTER_DESC1 previousDesc; + { + ComPtr<IDXGIAdapter1> previousDefaultAdapter; + dxgiFactory->EnumAdapters1(0, &previousDefaultAdapter); + + previousDefaultAdapter->GetDesc1(&previousDesc); + } + + // Next, get the information for the current default adapter. + DXGI_ADAPTER_DESC1 currentDesc; + { + ComPtr<IDXGIFactory1> currentFactory; + CreateDXGIFactory1(IID_PPV_ARGS(¤tFactory)); + + ComPtr<IDXGIAdapter1> currentDefaultAdapter; + currentFactory->EnumAdapters1(0, ¤tDefaultAdapter); + + currentDefaultAdapter->GetDesc1(¤tDesc); + } + // If the adapter LUIDs don't match, or if the device reports that it has been removed, + // a new D3D device must be created. + HRESULT hr = m_d3dDevice->GetDeviceRemovedReason(); + if ( previousDesc.AdapterLuid.LowPart != currentDesc.AdapterLuid.LowPart + || previousDesc.AdapterLuid.HighPart != currentDesc.AdapterLuid.HighPart + || FAILED(hr)) + { + // Release references to resources related to the old device. + dxgiDevice = nullptr; + deviceAdapter = nullptr; + dxgiFactory = nullptr; + + // Create a new device and swap chain. + HandleDeviceLost(hr == DXGI_ERROR_DEVICE_REMOVED); + } +} + +void DX::DeviceResources::OnDeviceLost(bool removed) +{ + auto pGUI = CServiceBroker::GetGUI(); + if (pGUI) + pGUI->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_RENDERER_LOST); + + // tell any shared resources + for (auto res : m_resources) + { + // the most of resources like textures and buffers try to + // receive and save their status from current device. + // `removed` means that we have no possibility + // to use the device anymore, tell all resources about this. + res->OnDestroyDevice(removed); + } +} + +void DX::DeviceResources::OnDeviceRestored() +{ + // tell any shared resources + for (auto res : m_resources) + res->OnCreateDevice(); + + auto pGUI = CServiceBroker::GetGUI(); + if (pGUI) + pGUI->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_RENDERER_RESET); +} + +// Recreate all device resources and set them back to the current state. +void DX::DeviceResources::HandleDeviceLost(bool removed) +{ + bool backbuferExists = m_backBufferTex.Get() != nullptr; + + OnDeviceLost(removed); + if (m_deviceNotify != nullptr) + m_deviceNotify->OnDXDeviceLost(); + + if (backbuferExists) + ReleaseBackBuffer(); + + DestroySwapChain(); + + CreateDeviceResources(); + UpdateRenderTargetSize(); + ResizeBuffers(); + + if (backbuferExists) + CreateBackBuffer(); + + if (m_deviceNotify != nullptr) + m_deviceNotify->OnDXDeviceRestored(); + OnDeviceRestored(); + + if (removed) + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, + "ReloadSkin"); +} + +bool DX::DeviceResources::Begin() +{ + HRESULT hr = m_swapChain->Present(0, DXGI_PRESENT_TEST); + + // If the device was removed either by a disconnection or a driver upgrade, we + // must recreate all device resources. + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) + { + HandleDeviceLost(hr == DXGI_ERROR_DEVICE_REMOVED); + } + else + { + // not fatal errors + if (hr == DXGI_ERROR_INVALID_CALL) + { + CreateWindowSizeDependentResources(); + } + } + + m_deferrContext->OMSetRenderTargets(1, m_backBufferTex.GetAddressOfRTV(), m_d3dDepthStencilView.Get()); + + return true; +} + +// Present the contents of the swap chain to the screen. +void DX::DeviceResources::Present() +{ + FinishCommandList(); + + // The first argument instructs DXGI to block until VSync, putting the application + // to sleep until the next VSync. This ensures we don't waste any cycles rendering + // frames that will never be displayed to the screen. + DXGI_PRESENT_PARAMETERS parameters = {}; + HRESULT hr = m_swapChain->Present1(1, 0, ¶meters); + + // If the device was removed either by a disconnection or a driver upgrade, we + // must recreate all device resources. + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) + { + HandleDeviceLost(hr == DXGI_ERROR_DEVICE_REMOVED); + } + else + { + // not fatal errors + if (hr == DXGI_ERROR_INVALID_CALL) + { + CreateWindowSizeDependentResources(); + } + } + + if (m_d3dContext == m_deferrContext) + { + m_deferrContext->OMSetRenderTargets(1, m_backBufferTex.GetAddressOfRTV(), m_d3dDepthStencilView.Get()); + } +} + +void DX::DeviceResources::ClearDepthStencil() const +{ + m_deferrContext->ClearDepthStencilView(m_d3dDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0, 0); +} + +void DX::DeviceResources::ClearRenderTarget(ID3D11RenderTargetView* pRTView, float color[4]) const +{ + m_deferrContext->ClearRenderTargetView(pRTView, color); +} + +void DX::DeviceResources::HandleOutputChange(const std::function<bool(DXGI_OUTPUT_DESC)>& cmpFunc) +{ + DXGI_ADAPTER_DESC currentDesc = {}; + DXGI_ADAPTER_DESC foundDesc = {}; + + ComPtr<IDXGIFactory1> factory; + if (m_adapter) + m_adapter->GetDesc(¤tDesc); + + CreateDXGIFactory1(IID_IDXGIFactory1, &factory); + + ComPtr<IDXGIAdapter1> adapter; + for (int i = 0; factory->EnumAdapters1(i, adapter.ReleaseAndGetAddressOf()) != DXGI_ERROR_NOT_FOUND; i++) + { + adapter->GetDesc(&foundDesc); + ComPtr<IDXGIOutput> output; + for (int j = 0; adapter->EnumOutputs(j, output.ReleaseAndGetAddressOf()) != DXGI_ERROR_NOT_FOUND; j++) + { + DXGI_OUTPUT_DESC outputDesc; + output->GetDesc(&outputDesc); + if (cmpFunc(outputDesc)) + { + output.As(&m_output); + // check if adapter is changed + if (currentDesc.AdapterLuid.HighPart != foundDesc.AdapterLuid.HighPart + || currentDesc.AdapterLuid.LowPart != foundDesc.AdapterLuid.LowPart) + { + // adapter is changed + m_adapter = adapter; + CLog::LogF(LOGDEBUG, "selected {} adapter. ", + KODI::PLATFORM::WINDOWS::FromW(foundDesc.Description)); + // (re)init hooks into new driver + Windowing()->InitHooks(output.Get()); + // recreate d3d11 device on new adapter + if (m_d3dDevice) + HandleDeviceLost(false); + } + return; + } + } + } +} + +bool DX::DeviceResources::CreateFactory() +{ + HRESULT hr; +#if defined(_DEBUG) && defined(TARGET_WINDOWS_STORE) + bool debugDXGI = false; + { + ComPtr<IDXGIInfoQueue> dxgiInfoQueue; + if (SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(dxgiInfoQueue.GetAddressOf())))) + { + debugDXGI = true; + + hr = CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, IID_PPV_ARGS(m_dxgiFactory.ReleaseAndGetAddressOf())); RETURN_ERR(false); + + dxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true); + dxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true); + } + } + + if (!debugDXGI) +#endif + hr = CreateDXGIFactory1(IID_PPV_ARGS(m_dxgiFactory.ReleaseAndGetAddressOf())); RETURN_ERR(false); + + return true; +} + +void DX::DeviceResources::SetMonitor(HMONITOR monitor) +{ + HandleOutputChange([monitor](DXGI_OUTPUT_DESC outputDesc) { + return outputDesc.Monitor == monitor; + }); +} + +void DX::DeviceResources::RegisterDeviceNotify(IDeviceNotify* deviceNotify) +{ + m_deviceNotify = deviceNotify; +} + +HMONITOR DX::DeviceResources::GetMonitor() const +{ + if (m_swapChain) + { + ComPtr<IDXGIOutput> output; + GetOutput(output.GetAddressOf()); + if (output) + { + DXGI_OUTPUT_DESC desc; + output->GetDesc(&desc); + return desc.Monitor; + } + } + return nullptr; +} + +bool DX::DeviceResources::IsStereoAvailable() const +{ + if (m_dxgiFactory) + return m_dxgiFactory->IsWindowedStereoEnabled(); + + return false; +} + +void DX::DeviceResources::CheckNV12SharedTexturesSupport() +{ + if (m_d3dFeatureLevel < D3D_FEATURE_LEVEL_10_0 || + CSysInfo::GetWindowsDeviceFamily() != CSysInfo::Desktop) + return; + + D3D11_FEATURE_DATA_D3D11_OPTIONS4 op4 = {}; + HRESULT hr = m_d3dDevice->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS4, &op4, sizeof(op4)); + m_NV12SharedTexturesSupport = SUCCEEDED(hr) && !!op4.ExtendedNV12SharedTextureSupported; + CLog::LogF(LOGINFO, "extended NV12 shared textures is{}supported", + m_NV12SharedTexturesSupport ? " " : " NOT "); +} + +void DX::DeviceResources::CheckDXVA2SharedDecoderSurfaces() +{ + if (CSysInfo::GetWindowsDeviceFamily() != CSysInfo::Desktop) + return; + + VideoDriverInfo driver = GetVideoDriverVersion(); + + if (!m_NV12SharedTexturesSupport) + return; + + DXGI_ADAPTER_DESC ad = {}; + GetAdapterDesc(&ad); + + m_DXVA2SharedDecoderSurfaces = + ad.VendorId == PCIV_Intel || + (ad.VendorId == PCIV_NVIDIA && driver.valid && driver.majorVersion >= 465) || + (ad.VendorId == PCIV_AMD && driver.valid && driver.majorVersion >= 30 && + m_d3dFeatureLevel >= D3D_FEATURE_LEVEL_12_1); + + CLog::LogF(LOGINFO, "DXVA2 shared decoder surfaces is{}supported", + m_DXVA2SharedDecoderSurfaces ? " " : " NOT "); +} + +VideoDriverInfo DX::DeviceResources::GetVideoDriverVersion() +{ + DXGI_ADAPTER_DESC ad = {}; + GetAdapterDesc(&ad); + + VideoDriverInfo driver = CWIN32Util::GetVideoDriverInfo(ad.VendorId, ad.Description); + + if (ad.VendorId == PCIV_NVIDIA) + CLog::LogF(LOGINFO, "video driver version is {} {}.{} ({})", GetGFXProviderName(ad.VendorId), + driver.majorVersion, driver.minorVersion, driver.version); + else + CLog::LogF(LOGINFO, "video driver version is {} {}", GetGFXProviderName(ad.VendorId), + driver.version); + + return driver; +} + +#if defined(TARGET_WINDOWS_DESKTOP) +// This method is called when the window (WND) is created (or re-created). +void DX::DeviceResources::SetWindow(HWND window) +{ + m_window = window; + + CreateDeviceIndependentResources(); + CreateDeviceResources(); +} +#elif defined(TARGET_WINDOWS_STORE) +// This method is called when the CoreWindow is created (or re-created). +void DX::DeviceResources::SetWindow(const winrt::Windows::UI::Core::CoreWindow& window) +{ + using namespace winrt::Windows::UI::Core; + using namespace winrt::Windows::Graphics::Display; + + m_coreWindow = window; + auto dispatcher = m_coreWindow.Dispatcher(); + DispatchedHandler handler([&]() + { + auto coreWindow = CoreWindow::GetForCurrentThread(); + m_logicalSize = winrt::Size(coreWindow.Bounds().Width, coreWindow.Bounds().Height); + m_dpi = DisplayInformation::GetForCurrentView().LogicalDpi(); + SetWindowPos(coreWindow.Bounds()); + }); + if (dispatcher.HasThreadAccess()) + handler(); + else + dispatcher.RunAsync(CoreDispatcherPriority::High, handler).get(); + + CreateDeviceIndependentResources(); + CreateDeviceResources(); + // we have to call this because we will not get initial WM_SIZE + CreateWindowSizeDependentResources(); +} + +void DX::DeviceResources::SetWindowPos(winrt::Rect rect) +{ + int centerX = rect.X + rect.Width / 2; + int centerY = rect.Y + rect.Height / 2; + + HandleOutputChange([centerX, centerY](DXGI_OUTPUT_DESC outputDesc) { + // DesktopCoordinates depends on the DPI of the desktop + return outputDesc.DesktopCoordinates.left <= centerX && outputDesc.DesktopCoordinates.right >= centerX + && outputDesc.DesktopCoordinates.top <= centerY && outputDesc.DesktopCoordinates.bottom >= centerY; + }); +} + +// Call this method when the app suspends. It provides a hint to the driver that the app +// is entering an idle state and that temporary buffers can be reclaimed for use by other apps. +void DX::DeviceResources::Trim() const +{ + ComPtr<IDXGIDevice3> dxgiDevice; + m_d3dDevice.As(&dxgiDevice); + + dxgiDevice->Trim(); +} + +#endif + +void DX::DeviceResources::SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const +{ + ComPtr<IDXGISwapChain4> swapChain4; + + if (!m_swapChain) + return; + + if (SUCCEEDED(m_swapChain.As(&swapChain4))) + { + if (SUCCEEDED(swapChain4->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(hdr10), &hdr10))) + { + CLog::LogF(LOGDEBUG, + "(raw) RP {} {} | GP {} {} | BP {} {} | WP {} {} | Max ML {} | min ML " + "{} | Max CLL {} | Max FALL {}", + hdr10.RedPrimary[0], hdr10.RedPrimary[1], hdr10.GreenPrimary[0], + hdr10.GreenPrimary[1], hdr10.BluePrimary[0], hdr10.BluePrimary[1], + hdr10.WhitePoint[0], hdr10.WhitePoint[1], hdr10.MaxMasteringLuminance, + hdr10.MinMasteringLuminance, hdr10.MaxContentLightLevel, + hdr10.MaxFrameAverageLightLevel); + + constexpr double FACTOR_1 = 50000.0; + constexpr double FACTOR_2 = 10000.0; + const double RP_0 = static_cast<double>(hdr10.RedPrimary[0]) / FACTOR_1; + const double RP_1 = static_cast<double>(hdr10.RedPrimary[1]) / FACTOR_1; + const double GP_0 = static_cast<double>(hdr10.GreenPrimary[0]) / FACTOR_1; + const double GP_1 = static_cast<double>(hdr10.GreenPrimary[1]) / FACTOR_1; + const double BP_0 = static_cast<double>(hdr10.BluePrimary[0]) / FACTOR_1; + const double BP_1 = static_cast<double>(hdr10.BluePrimary[1]) / FACTOR_1; + const double WP_0 = static_cast<double>(hdr10.WhitePoint[0]) / FACTOR_1; + const double WP_1 = static_cast<double>(hdr10.WhitePoint[1]) / FACTOR_1; + const double Max_ML = static_cast<double>(hdr10.MaxMasteringLuminance) / FACTOR_2; + const double min_ML = static_cast<double>(hdr10.MinMasteringLuminance) / FACTOR_2; + + CLog::LogF(LOGINFO, + "RP {:.3f} {:.3f} | GP {:.3f} {:.3f} | BP {:.3f} {:.3f} | WP {:.3f} " + "{:.3f} | Max ML {:.0f} | min ML {:.4f} | Max CLL {} | Max FALL {}", + RP_0, RP_1, GP_0, GP_1, BP_0, BP_1, WP_0, WP_1, Max_ML, min_ML, + hdr10.MaxContentLightLevel, hdr10.MaxFrameAverageLightLevel); + } + else + { + CLog::LogF(LOGERROR, "DXGI SetHDRMetaData failed"); + } + } +} + +void DX::DeviceResources::SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace) +{ + ComPtr<IDXGISwapChain3> swapChain3; + + if (!m_swapChain) + return; + + if (SUCCEEDED(m_swapChain.As(&swapChain3))) + { + if (SUCCEEDED(swapChain3->SetColorSpace1(colorSpace))) + { + m_IsTransferPQ = (colorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020); + + CLog::LogF(LOGDEBUG, "DXGI SetColorSpace1 success"); + } + else + { + CLog::LogF(LOGERROR, "DXGI SetColorSpace1 failed"); + } + } +} + +HDR_STATUS DX::DeviceResources::ToggleHDR() +{ + DXGI_MODE_DESC md = {}; + GetDisplayMode(&md); + + DX::Windowing()->SetTogglingHDR(true); + DX::Windowing()->SetAlteringWindow(true); + + // Toggle display HDR + HDR_STATUS hdrStatus = CWIN32Util::ToggleWindowsHDR(md); + + // Kill swapchain + if (m_swapChain && hdrStatus != HDR_STATUS::HDR_TOGGLE_FAILED) + { + CLog::LogF(LOGDEBUG, "Re-create swapchain due HDR <-> SDR switch"); + DestroySwapChain(); + } + + DX::Windowing()->SetAlteringWindow(false); + + // Re-create swapchain + if (hdrStatus != HDR_STATUS::HDR_TOGGLE_FAILED) + { + CreateWindowSizeDependentResources(); + + DX::Windowing()->NotifyAppFocusChange(true); + } + + return hdrStatus; +} + +void DX::DeviceResources::ApplyDisplaySettings() +{ + CLog::LogF(LOGDEBUG, "Re-create swapchain due Display Settings changed"); + + DestroySwapChain(); + CreateWindowSizeDependentResources(); +} + +DEBUG_INFO_RENDER DX::DeviceResources::GetDebugInfo() const +{ + if (!m_swapChain) + return {}; + + DXGI_SWAP_CHAIN_DESC1 desc = {}; + m_swapChain->GetDesc1(&desc); + + DXGI_MODE_DESC md = {}; + GetDisplayMode(&md); + + const int bits = (desc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) ? 10 : 8; + const int max = (desc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) ? 1024 : 256; + const int range_min = DX::Windowing()->UseLimitedColor() ? (max * 16) / 256 : 0; + const int range_max = DX::Windowing()->UseLimitedColor() ? (max * 235) / 256 : max - 1; + + DEBUG_INFO_RENDER info; + + info.renderFlags = StringUtils::Format( + "Swapchain: {} buffers, flip {}, {}, EOTF: {} (Windows HDR {})", desc.BufferCount, + (desc.SwapEffect == DXGI_SWAP_EFFECT_FLIP_DISCARD) ? "discard" : "sequential", + Windowing()->IsFullScreen() + ? ((desc.Flags == DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH) ? "fullscreen exclusive" + : "fullscreen windowed") + : "windowed screen", + m_IsTransferPQ ? "PQ" : "SDR", m_IsHDROutput ? "on" : "off"); + + info.videoOutput = StringUtils::Format( + "Surfaces: {}x{}{} @ {:.3f} Hz, pixel: {} {}-bit, range: {} ({}-{})", desc.Width, desc.Height, + (md.ScanlineOrdering > DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE) ? "i" : "p", + static_cast<double>(md.RefreshRate.Numerator) / + static_cast<double>(md.RefreshRate.Denominator), + (desc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) ? "R10G10B10A2" : "B8G8R8A8", bits, + DX::Windowing()->UseLimitedColor() ? "limited" : "full", range_min, range_max); + + return info; +} diff --git a/xbmc/rendering/dx/DeviceResources.h b/xbmc/rendering/dx/DeviceResources.h new file mode 100644 index 0000000..d458902 --- /dev/null +++ b/xbmc/rendering/dx/DeviceResources.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DirectXHelper.h" +#include "HDRStatus.h" +#include "guilib/D3DResource.h" + +#include <functional> +#include <memory> + +#include <concrt.h> +#include <dxgi1_5.h> +#include <wrl.h> +#include <wrl/client.h> + +struct RESOLUTION_INFO; +struct DEBUG_INFO_RENDER; +struct VideoDriverInfo; + +namespace DX +{ + interface IDeviceNotify + { + virtual void OnDXDeviceLost() = 0; + virtual void OnDXDeviceRestored() = 0; + }; + + // Controls all the DirectX device resources. + class DeviceResources + { + public: + static std::shared_ptr<DX::DeviceResources> Get(); + + DeviceResources(); + virtual ~DeviceResources(); + void Release(); + + void ValidateDevice(); + void HandleDeviceLost(bool removed); + bool Begin(); + void Present(); + + // The size of the render target, in pixels. + winrt::Windows::Foundation::Size GetOutputSize() const { return m_outputSize; } + // The size of the render target, in dips. + winrt::Windows::Foundation::Size GetLogicalSize() const { return m_logicalSize; } + void SetLogicalSize(float width, float height); + float GetDpi() const { return m_effectiveDpi; } + void SetDpi(float dpi); + + // D3D Accessors. + bool HasValidDevice() const { return m_bDeviceCreated; } + ID3D11Device1* GetD3DDevice() const { return m_d3dDevice.Get(); } + ID3D11DeviceContext1* GetD3DContext() const { return m_deferrContext.Get(); } + ID3D11DeviceContext1* GetImmediateContext() const { return m_d3dContext.Get(); } + IDXGISwapChain1* GetSwapChain() const { return m_swapChain.Get(); } + IDXGIFactory2* GetIDXGIFactory2() const { return m_dxgiFactory.Get(); } + IDXGIAdapter1* GetAdapter() const { return m_adapter.Get(); } + ID3D11DepthStencilView* GetDSV() const { return m_d3dDepthStencilView.Get(); } + D3D_FEATURE_LEVEL GetDeviceFeatureLevel() const { return m_d3dFeatureLevel; } + CD3DTexture& GetBackBuffer() { return m_backBufferTex; } + + void GetOutput(IDXGIOutput** ppOutput) const; + void GetAdapterDesc(DXGI_ADAPTER_DESC *desc) const; + void GetDisplayMode(DXGI_MODE_DESC *mode) const; + + D3D11_VIEWPORT GetScreenViewport() const { return m_screenViewport; } + void SetViewPort(D3D11_VIEWPORT& viewPort) const; + + void ReleaseBackBuffer(); + void CreateBackBuffer(); + void ResizeBuffers(); + + bool SetFullScreen(bool fullscreen, RESOLUTION_INFO& res); + + // Apply display settings changes + void ApplyDisplaySettings(); + + // HDR display support + HDR_STATUS ToggleHDR(); + void SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const; + void SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace); + bool IsHDROutput() const { return m_IsHDROutput; } + bool IsTransferPQ() const { return m_IsTransferPQ; } + + // DX resources registration + void Register(ID3DResource *resource); + void Unregister(ID3DResource *resource); + + void FinishCommandList(bool bExecute = true) const; + void ClearDepthStencil() const; + void ClearRenderTarget(ID3D11RenderTargetView* pRTView, float color[4]) const; + void RegisterDeviceNotify(IDeviceNotify* deviceNotify); + + bool IsStereoAvailable() const; + bool IsStereoEnabled() const { return m_stereoEnabled; } + void SetStereoIdx(byte idx) { m_backBufferTex.SetViewIdx(idx); } + + void SetMonitor(HMONITOR monitor); + HMONITOR GetMonitor() const; +#if defined(TARGET_WINDOWS_DESKTOP) + void SetWindow(HWND window); +#elif defined(TARGET_WINDOWS_STORE) + void Trim() const; + void SetWindow(const winrt::Windows::UI::Core::CoreWindow& window); + void SetWindowPos(winrt::Windows::Foundation::Rect rect); +#endif // TARGET_WINDOWS_STORE + bool IsNV12SharedTexturesSupported() const { return m_NV12SharedTexturesSupport; } + bool IsDXVA2SharedDecoderSurfaces() const { return m_DXVA2SharedDecoderSurfaces; } + + // Gets debug info from swapchain + DEBUG_INFO_RENDER GetDebugInfo() const; + + private: + class CBackBuffer : public CD3DTexture + { + public: + CBackBuffer() : CD3DTexture() {} + void SetViewIdx(unsigned idx) { m_viewIdx = idx; } + bool Acquire(ID3D11Texture2D* pTexture); + }; + + HRESULT CreateSwapChain(DXGI_SWAP_CHAIN_DESC1 &desc, DXGI_SWAP_CHAIN_FULLSCREEN_DESC &fsDesc, IDXGISwapChain1 **ppSwapChain) const; + void DestroySwapChain(); + void CreateDeviceIndependentResources(); + void CreateDeviceResources(); + void CreateWindowSizeDependentResources(); + void UpdateRenderTargetSize(); + void OnDeviceLost(bool removed); + void OnDeviceRestored(); + void HandleOutputChange(const std::function<bool(DXGI_OUTPUT_DESC)>& cmpFunc); + bool CreateFactory(); + void CheckNV12SharedTexturesSupport(); + VideoDriverInfo GetVideoDriverVersion(); + void CheckDXVA2SharedDecoderSurfaces(); + + HWND m_window{ nullptr }; +#if defined(TARGET_WINDOWS_STORE) + winrt::Windows::UI::Core::CoreWindow m_coreWindow = nullptr; +#endif + Microsoft::WRL::ComPtr<IDXGIFactory2> m_dxgiFactory; + Microsoft::WRL::ComPtr<IDXGIAdapter1> m_adapter; + Microsoft::WRL::ComPtr<IDXGIOutput1> m_output; + + Microsoft::WRL::ComPtr<ID3D11Device1> m_d3dDevice; + Microsoft::WRL::ComPtr<ID3D11DeviceContext1> m_d3dContext; + Microsoft::WRL::ComPtr<ID3D11DeviceContext1> m_deferrContext; + Microsoft::WRL::ComPtr<IDXGISwapChain1> m_swapChain; +#ifdef _DEBUG + Microsoft::WRL::ComPtr<ID3D11Debug> m_d3dDebug; +#endif + + CBackBuffer m_backBufferTex; + Microsoft::WRL::ComPtr<ID3D11DepthStencilView> m_d3dDepthStencilView; + D3D11_VIEWPORT m_screenViewport; + + // Cached device properties. + D3D_FEATURE_LEVEL m_d3dFeatureLevel; + winrt::Windows::Foundation::Size m_outputSize; + winrt::Windows::Foundation::Size m_logicalSize; + float m_dpi; + + // This is the DPI that will be reported back to the app. It takes into account whether the app supports high resolution screens or not. + float m_effectiveDpi; + // The IDeviceNotify can be held directly as it owns the DeviceResources. + IDeviceNotify* m_deviceNotify; + + // scritical section + Concurrency::critical_section m_criticalSection; + Concurrency::critical_section m_resourceSection; + std::vector<ID3DResource*> m_resources; + + bool m_stereoEnabled; + bool m_bDeviceCreated; + bool m_IsHDROutput; + bool m_IsTransferPQ; + bool m_NV12SharedTexturesSupport{false}; + bool m_DXVA2SharedDecoderSurfaces{false}; + }; +} diff --git a/xbmc/rendering/dx/DirectXHelper.h b/xbmc/rendering/dx/DirectXHelper.h new file mode 100644 index 0000000..bb51ca6 --- /dev/null +++ b/xbmc/rendering/dx/DirectXHelper.h @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "commons/Exception.h" +#include "dxerr.h" + +#include "platform/win32/CharsetConverter.h" + +#include <d3d11_4.h> +#include <ppltasks.h> // For create_task + +enum PCI_Vendors +{ + PCIV_AMD = 0x1002, + PCIV_NVIDIA = 0x10DE, + PCIV_Intel = 0x8086, +}; + +namespace DX +{ +#define RATIONAL_TO_FLOAT(rational) ((rational.Denominator != 0) ? \ + static_cast<float>(rational.Numerator) / static_cast<float>(rational.Denominator) : 0.0f) + + namespace DisplayMetrics + { + // High resolution displays can require a lot of GPU and battery power to render. + // High resolution phones, for example, may suffer from poor battery life if + // games attempt to render at 60 frames per second at full fidelity. + // The decision to render at full fidelity across all platforms and form factors + // should be deliberate. + static const bool SupportHighResolutions = true; + + // The default thresholds that define a "high resolution" display. If the thresholds + // are exceeded and SupportHighResolutions is false, the dimensions will be scaled + // by 50%. + static const float Dpi100 = 96.0f; // 100% of standard desktop display. + static const float DpiThreshold = 192.0f; // 200% of standard desktop display. + static const float WidthThreshold = 1920.0f; // 1080p width. + static const float HeightThreshold = 1080.0f; // 1080p height. + }; + + inline void BreakIfFailed(HRESULT hr) + { + if (FAILED(hr)) + { + // Set a breakpoint on this line to catch Win32 API errors. +#if _DEBUG && !defined(TARGET_WINDOWS_STORE) + DebugBreak(); +#endif + throw new XbmcCommons::UncheckedException(__FUNCTION__, "Unhandled error"); + } + } + + // Converts a length in device-independent pixels (DIPs) to a length in physical pixels. + inline float ConvertDipsToPixels(float dips, float dpi) + { + static const float dipsPerInch = DisplayMetrics::Dpi100; + return floorf(dips * dpi / dipsPerInch + 0.5f); // Round to nearest integer. + } + + inline float ConvertPixelsToDips(float pixels, float dpi) + { + static const float dipsPerInch = DisplayMetrics::Dpi100; + return floorf(pixels / (dpi / dipsPerInch) + 0.5f); // Round to nearest integer. + } + + inline float RationalToFloat(DXGI_RATIONAL rational) + { + return RATIONAL_TO_FLOAT(rational); + } + + inline void GetRefreshRatio(uint32_t refresh, uint32_t *num, uint32_t *den) + { + int i = (((refresh + 1) % 24) == 0 || ((refresh + 1) % 30) == 0) ? 1 : 0; + *num = (refresh + i) * 1000; + *den = 1000 + i; + } + + inline std::string GetErrorDescription(HRESULT hr) + { + using namespace KODI::PLATFORM::WINDOWS; + + WCHAR buff[2048]; + DXGetErrorDescriptionW(hr, buff, 2048); + + return FromW(StringUtils::Format(L"{:X} - {} ({})", hr, DXGetErrorStringW(hr), buff)); + } + + inline std::string GetFeatureLevelDescription(D3D_FEATURE_LEVEL featureLevel) + { + uint32_t fl_major = (featureLevel & 0xF000u) >> 12; + uint32_t fl_minor = (featureLevel & 0x0F00u) >> 8; + + return StringUtils::Format("D3D_FEATURE_LEVEL_{}_{}", fl_major, fl_minor); + } + + inline std::string GetGFXProviderName(UINT vendorId) + { + std::string name; + switch (vendorId) + { + case PCIV_AMD: + name = "AMD"; + break; + case PCIV_Intel: + name = "Intel"; + break; + case PCIV_NVIDIA: + name = "NVIDIA"; + break; + } + + return name; + } + + template <typename T> struct SizeGen + { + SizeGen<T>() { Width = Height = 0; } + SizeGen<T>(T width, T height) { Width = width; Height = height; } + + bool operator !=(const SizeGen<T> &size) const + { + return Width != size.Width || Height != size.Height; + } + + const SizeGen<T> &operator -=(const SizeGen<T> &size) + { + Width -= size.Width; + Height -= size.Height; + return *this; + }; + + const SizeGen<T> &operator +=(const SizeGen<T> &size) + { + Width += size.Width; + Height += size.Height; + return *this; + }; + + const SizeGen<T> &operator -=(const T &size) + { + Width -= size; + Height -= size; + return *this; + }; + + const SizeGen<T> &operator +=(const T &size) + { + Width += size; + Height += size; + return *this; + }; + + T Width, Height; + }; + +#if defined(_DEBUG) + // Check for SDK Layer support. + inline bool SdkLayersAvailable() + { + HRESULT hr = D3D11CreateDevice( + nullptr, + D3D_DRIVER_TYPE_NULL, // There is no need to create a real hardware device. + nullptr, + D3D11_CREATE_DEVICE_DEBUG, // Check for the SDK layers. + nullptr, // Any feature level will do. + 0, + D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows Store apps. + nullptr, // No need to keep the D3D device reference. + nullptr, // No need to know the feature level. + nullptr // No need to keep the D3D device context reference. + ); + + return SUCCEEDED(hr); + } +#endif +} + +#ifdef TARGET_WINDOWS_DESKTOP +namespace winrt +{ + namespace Windows + { + namespace Foundation + { + typedef DX::SizeGen<float> Size; + typedef DX::SizeGen<int> SizeInt; + } + } +} +#endif diff --git a/xbmc/rendering/dx/RenderContext.h b/xbmc/rendering/dx/RenderContext.h new file mode 100644 index 0000000..9878691 --- /dev/null +++ b/xbmc/rendering/dx/RenderContext.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#if defined(TARGET_WINDOWS_DESKTOP) +#include "windowing/windows/WinSystemWin32DX.h" +#elif defined(TARGET_WINDOWS_STORE) +#include "windowing/win10/WinSystemWin10DX.h" +#endif +#include "ServiceBroker.h" + +namespace DX +{ +#if defined(TARGET_WINDOWS_DESKTOP) + __inline CWinSystemWin32DX* Windowing() + { + return dynamic_cast<CWinSystemWin32DX*>(CServiceBroker::GetRenderSystem()); + } +#elif defined(TARGET_WINDOWS_STORE) + __inline CWinSystemWin10DX* Windowing() + { + return dynamic_cast<CWinSystemWin10DX*>(CServiceBroker::GetRenderSystem()); + } +#endif +} diff --git a/xbmc/rendering/dx/RenderSystemDX.cpp b/xbmc/rendering/dx/RenderSystemDX.cpp new file mode 100644 index 0000000..cb1b0d5 --- /dev/null +++ b/xbmc/rendering/dx/RenderSystemDX.cpp @@ -0,0 +1,706 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ +#include "RenderSystemDX.h" + +#include "application/Application.h" + +#include <mutex> +#if defined(TARGET_WINDOWS_DESKTOP) +#include "cores/RetroPlayer/process/windows/RPProcessInfoWin.h" +#include "cores/RetroPlayer/rendering/VideoRenderers/RPWinRenderer.h" +#endif +#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h" +#include "cores/VideoPlayer/DVDCodecs/Video/DXVA.h" +#if defined(TARGET_WINDOWS_STORE) +#include "cores/VideoPlayer/Process/windows/ProcessInfoWin10.h" +#else +#include "cores/VideoPlayer/Process/windows/ProcessInfoWin.h" +#endif +#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h" +#include "cores/VideoPlayer/VideoRenderers/WinRenderer.h" +#include "cores/VideoPlayer/VideoRenderers/RenderManager.h" +#include "guilib/D3DResource.h" +#include "guilib/GUIShaderDX.h" +#include "guilib/GUITextureD3D.h" +#include "guilib/GUIWindowManager.h" +#include "utils/MathUtils.h" +#include "utils/log.h" + +#include <DirectXPackedVector.h> + +extern "C" { +#include <libavutil/pixfmt.h> +} + +using namespace KODI; +using namespace DirectX; +using namespace DirectX::PackedVector; +using namespace Microsoft::WRL; +using namespace std::chrono_literals; + +CRenderSystemDX::CRenderSystemDX() : CRenderSystemBase() + , m_interlaced(false) +{ + m_bVSync = true; + + memset(&m_viewPort, 0, sizeof m_viewPort); + memset(&m_scissor, 0, sizeof m_scissor); +} + +CRenderSystemDX::~CRenderSystemDX() = default; + +bool CRenderSystemDX::InitRenderSystem() +{ + m_bVSync = true; + + // check various device capabilities + CheckDeviceCaps(); + + if (!CreateStates() || !InitGUIShader()) + return false; + + m_bRenderCreated = true; + m_deviceResources->RegisterDeviceNotify(this); + + // register platform dependent objects +#if defined(TARGET_WINDOWS_STORE) + VIDEOPLAYER::CProcessInfoWin10::Register(); +#else + VIDEOPLAYER::CProcessInfoWin::Register(); +#endif + CDVDFactoryCodec::ClearHWAccels(); + DXVA::CDecoder::Register(); + VIDEOPLAYER::CRendererFactory::ClearRenderer(); + CWinRenderer::Register(); +#if defined(TARGET_WINDOWS_DESKTOP) + RETRO::CRPProcessInfoWin::Register(); + RETRO::CRPProcessInfoWin::RegisterRendererFactory(new RETRO::CWinRendererFactory); +#endif + m_viewPort = m_deviceResources->GetScreenViewport(); + RestoreViewPort(); + + auto outputSize = m_deviceResources->GetOutputSize(); + // set camera to center of screen + CPoint camPoint = { outputSize.Width * 0.5f, outputSize.Height * 0.5f }; + SetCameraPosition(camPoint, outputSize.Width, outputSize.Height); + + DXGI_ADAPTER_DESC AIdentifier = {}; + m_deviceResources->GetAdapterDesc(&AIdentifier); + m_RenderRenderer = KODI::PLATFORM::WINDOWS::FromW(AIdentifier.Description); + uint32_t version = 0; + for (uint32_t decimal = m_deviceResources->GetDeviceFeatureLevel() >> 8, round = 0; decimal > 0; decimal >>= 4, ++round) + version += (decimal % 16) * std::pow(10, round); + m_RenderVersion = StringUtils::Format("{:.1f}", static_cast<float>(version) / 10.0f); + + CGUITextureD3D::Register(); + + return true; +} + +void CRenderSystemDX::OnResize() +{ + if (!m_bRenderCreated) + return; + + auto outputSize = m_deviceResources->GetOutputSize(); + + // set camera to center of screen + CPoint camPoint = { outputSize.Width * 0.5f, outputSize.Height * 0.5f }; + SetCameraPosition(camPoint, outputSize.Width, outputSize.Height); + + CheckInterlacedStereoView(); +} + +bool CRenderSystemDX::IsFormatSupport(DXGI_FORMAT format, unsigned int usage) const +{ + ComPtr<ID3D11Device1> pD3DDev = m_deviceResources->GetD3DDevice(); + UINT supported; + pD3DDev->CheckFormatSupport(format, &supported); + return (supported & usage) != 0; +} + +bool CRenderSystemDX::DestroyRenderSystem() +{ + std::unique_lock<CCriticalSection> lock(m_resourceSection); + + if (m_pGUIShader) + { + m_pGUIShader->End(); + delete m_pGUIShader; + m_pGUIShader = nullptr; + } + m_rightEyeTex.Release(); + m_BlendEnableState = nullptr; + m_BlendDisableState = nullptr; + m_RSScissorDisable = nullptr; + m_RSScissorEnable = nullptr; + m_depthStencilState = nullptr; + + return true; +} + +void CRenderSystemDX::CheckInterlacedStereoView() +{ + RENDER_STEREO_MODE stereoMode = CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode(); + + if ( m_rightEyeTex.Get() + && RENDER_STEREO_MODE_INTERLACED != stereoMode + && RENDER_STEREO_MODE_CHECKERBOARD != stereoMode) + { + m_rightEyeTex.Release(); + } + + if ( !m_rightEyeTex.Get() + && ( RENDER_STEREO_MODE_INTERLACED == stereoMode + || RENDER_STEREO_MODE_CHECKERBOARD == stereoMode)) + { + const auto outputSize = m_deviceResources->GetOutputSize(); + DXGI_FORMAT texFormat = m_deviceResources->GetBackBuffer().GetFormat(); + if (!m_rightEyeTex.Create(outputSize.Width, outputSize.Height, 1, D3D11_USAGE_DEFAULT, texFormat)) + { + CLog::Log(LOGERROR, "{} - Failed to create right eye buffer.", __FUNCTION__); + CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoMode(RENDER_STEREO_MODE_SPLIT_HORIZONTAL); // try fallback to split horizontal + } + else + m_deviceResources->Unregister(&m_rightEyeTex); // we will handle its health + } +} + +bool CRenderSystemDX::CreateStates() +{ + auto m_pD3DDev = m_deviceResources->GetD3DDevice(); + auto m_pContext = m_deviceResources->GetD3DContext(); + + if (!m_pD3DDev) + return false; + + m_BlendEnableState = nullptr; + m_BlendDisableState = nullptr; + + // Initialize the description of the stencil state. + D3D11_DEPTH_STENCIL_DESC depthStencilDesc = {}; + + // Set up the description of the stencil state. + depthStencilDesc.DepthEnable = false; + depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; + depthStencilDesc.DepthFunc = D3D11_COMPARISON_NEVER; + depthStencilDesc.StencilEnable = false; + depthStencilDesc.StencilReadMask = 0xFF; + depthStencilDesc.StencilWriteMask = 0xFF; + + // Stencil operations if pixel is front-facing. + depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; + depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR; + depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; + depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + + // Stencil operations if pixel is back-facing. + depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; + depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR; + depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; + depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + + // Create the depth stencil state. + HRESULT hr = m_pD3DDev->CreateDepthStencilState(&depthStencilDesc, &m_depthStencilState); + if(FAILED(hr)) + return false; + + // Set the depth stencil state. + m_pContext->OMSetDepthStencilState(m_depthStencilState.Get(), 0); + + D3D11_RASTERIZER_DESC rasterizerState; + rasterizerState.CullMode = D3D11_CULL_NONE; + rasterizerState.FillMode = D3D11_FILL_SOLID;// DEBUG - D3D11_FILL_WIREFRAME + rasterizerState.FrontCounterClockwise = false; + rasterizerState.DepthBias = 0; + rasterizerState.DepthBiasClamp = 0.0f; + rasterizerState.DepthClipEnable = true; + rasterizerState.SlopeScaledDepthBias = 0.0f; + rasterizerState.ScissorEnable = false; + rasterizerState.MultisampleEnable = false; + rasterizerState.AntialiasedLineEnable = false; + + if (FAILED(m_pD3DDev->CreateRasterizerState(&rasterizerState, &m_RSScissorDisable))) + return false; + + rasterizerState.ScissorEnable = true; + if (FAILED(m_pD3DDev->CreateRasterizerState(&rasterizerState, &m_RSScissorEnable))) + return false; + + m_pContext->RSSetState(m_RSScissorDisable.Get()); // by default + + D3D11_BLEND_DESC blendState = {}; + blendState.RenderTarget[0].BlendEnable = true; + blendState.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; // D3D11_BLEND_SRC_ALPHA; + blendState.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; // D3D11_BLEND_INV_SRC_ALPHA; + blendState.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + blendState.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + blendState.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; + blendState.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + blendState.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + + m_pD3DDev->CreateBlendState(&blendState, &m_BlendEnableState); + + blendState.RenderTarget[0].BlendEnable = false; + m_pD3DDev->CreateBlendState(&blendState, &m_BlendDisableState); + + // by default + m_pContext->OMSetBlendState(m_BlendEnableState.Get(), nullptr, 0xFFFFFFFF); + m_BlendEnabled = true; + + return true; +} + +void CRenderSystemDX::PresentRender(bool rendered, bool videoLayer) +{ + if (!m_bRenderCreated) + return; + + if ( rendered + && ( m_stereoMode == RENDER_STEREO_MODE_INTERLACED + || m_stereoMode == RENDER_STEREO_MODE_CHECKERBOARD)) + { + auto m_pContext = m_deviceResources->GetD3DContext(); + + // all views prepared, let's merge them before present + m_pContext->OMSetRenderTargets(1, m_deviceResources->GetBackBuffer().GetAddressOfRTV(), m_deviceResources->GetDSV()); + + auto outputSize = m_deviceResources->GetOutputSize(); + CRect destRect = { 0.0f, 0.0f, float(outputSize.Width), float(outputSize.Height) }; + + SHADER_METHOD method = RENDER_STEREO_MODE_INTERLACED == m_stereoMode + ? SHADER_METHOD_RENDER_STEREO_INTERLACED_RIGHT + : SHADER_METHOD_RENDER_STEREO_CHECKERBOARD_RIGHT; + SetAlphaBlendEnable(true); + CD3DTexture::DrawQuad(destRect, 0, &m_rightEyeTex, nullptr, method); + CD3DHelper::PSClearShaderResources(m_pContext); + } + + // time for decoder that may require the context + { + std::unique_lock<CCriticalSection> lock(m_decoderSection); + XbmcThreads::EndTime<> timer; + timer.Set(5ms); + while (!m_decodingTimer.IsTimePast() && !timer.IsTimePast()) + { + m_decodingEvent.wait(lock, 1ms); + } + } + + PresentRenderImpl(rendered); +} + +void CRenderSystemDX::RequestDecodingTime() +{ + std::unique_lock<CCriticalSection> lock(m_decoderSection); + m_decodingTimer.Set(3ms); +} + +void CRenderSystemDX::ReleaseDecodingTime() +{ + std::unique_lock<CCriticalSection> lock(m_decoderSection); + m_decodingTimer.SetExpired(); + m_decodingEvent.notify(); +} + +bool CRenderSystemDX::BeginRender() +{ + if (!m_bRenderCreated) + return false; + + m_limitedColorRange = CServiceBroker::GetWinSystem()->UseLimitedColor(); + m_inScene = m_deviceResources->Begin(); + return m_inScene; +} + +bool CRenderSystemDX::EndRender() +{ + m_inScene = false; + + if (!m_bRenderCreated) + return false; + + return true; +} + +bool CRenderSystemDX::ClearBuffers(UTILS::COLOR::Color color) +{ + if (!m_bRenderCreated) + return false; + + float fColor[4]; + CD3DHelper::XMStoreColor(fColor, color); + ID3D11RenderTargetView* pRTView = m_deviceResources->GetBackBuffer().GetRenderTarget(); + + if ( m_stereoMode != RENDER_STEREO_MODE_OFF + && m_stereoMode != RENDER_STEREO_MODE_MONO) + { + // if stereo anaglyph/tab/sbs, data was cleared when left view was rendered + if (m_stereoView == RENDER_STEREO_VIEW_RIGHT) + { + // execute command's queue + m_deviceResources->FinishCommandList(); + + // do not clear RT for anaglyph modes + if ( m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_GREEN_MAGENTA + || m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_RED_CYAN + || m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_YELLOW_BLUE) + { + pRTView = nullptr; + } + // for interlaced/checkerboard clear view for right texture + else if (m_stereoMode == RENDER_STEREO_MODE_INTERLACED + || m_stereoMode == RENDER_STEREO_MODE_CHECKERBOARD) + { + pRTView = m_rightEyeTex.GetRenderTarget(); + } + } + } + + if (pRTView == nullptr) + return true; + + auto outputSize = m_deviceResources->GetOutputSize(); + CRect clRect(0.0f, 0.0f, + static_cast<float>(outputSize.Width), + static_cast<float>(outputSize.Height)); + + // Unlike Direct3D 9, D3D11 ClearRenderTargetView always clears full extent of the resource view. + // Viewport and scissor settings are not applied. So clear RT by drawing full sized rect with clear color + if (m_ScissorsEnabled && m_scissor != clRect) + { + bool alphaEnabled = m_BlendEnabled; + if (alphaEnabled) + SetAlphaBlendEnable(false); + + CGUITextureD3D::DrawQuad(clRect, color); + + if (alphaEnabled) + SetAlphaBlendEnable(true); + } + else + m_deviceResources->ClearRenderTarget(pRTView, fColor); + + m_deviceResources->ClearDepthStencil(); + return true; +} + +void CRenderSystemDX::CaptureStateBlock() +{ + if (!m_bRenderCreated) + return; +} + +void CRenderSystemDX::ApplyStateBlock() +{ + if (!m_bRenderCreated) + return; + + auto m_pContext = m_deviceResources->GetD3DContext(); + + m_pContext->RSSetState(m_ScissorsEnabled ? m_RSScissorEnable.Get() : m_RSScissorDisable.Get()); + m_pContext->OMSetDepthStencilState(m_depthStencilState.Get(), 0); + float factors[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + m_pContext->OMSetBlendState(m_BlendEnabled ? m_BlendEnableState.Get() : m_BlendDisableState.Get(), factors, 0xFFFFFFFF); + + m_pGUIShader->ApplyStateBlock(); +} + +void CRenderSystemDX::SetCameraPosition(const CPoint &camera, int screenWidth, int screenHeight, float stereoFactor) +{ + if (!m_bRenderCreated) + return; + + // grab the viewport dimensions and location + float w = m_viewPort.Width * 0.5f; + float h = m_viewPort.Height * 0.5f; + + XMFLOAT2 offset = XMFLOAT2(camera.x - screenWidth*0.5f, camera.y - screenHeight*0.5f); + + // world view. Until this is moved onto the GPU (via a vertex shader for instance), we set it to the identity here. + m_pGUIShader->SetWorld(XMMatrixIdentity()); + + // Initialize the view matrix camera view. + // Multiply the Y coord by -1 then translate so that everything is relative to the camera position. + XMMATRIX flipY = XMMatrixScaling(1.0, -1.0f, 1.0f); + XMMATRIX translate = XMMatrixTranslation(-(w + offset.x - stereoFactor), -(h + offset.y), 2 * h); + m_pGUIShader->SetView(XMMatrixMultiply(translate, flipY)); + + // projection onto screen space + m_pGUIShader->SetProjection(XMMatrixPerspectiveOffCenterLH((-w - offset.x)*0.5f, (w - offset.x)*0.5f, (-h + offset.y)*0.5f, (h + offset.y)*0.5f, h, 100 * h)); +} + +void CRenderSystemDX::Project(float &x, float &y, float &z) +{ + if (!m_bRenderCreated) + return; + + m_pGUIShader->Project(x, y, z); +} + +CRect CRenderSystemDX::GetBackBufferRect() +{ + auto outputSize = m_deviceResources->GetOutputSize(); + return CRect(0.f, 0.f, static_cast<float>(outputSize.Width), static_cast<float>(outputSize.Height)); +} + +void CRenderSystemDX::GetViewPort(CRect& viewPort) +{ + if (!m_bRenderCreated) + return; + + viewPort.x1 = m_viewPort.TopLeftX; + viewPort.y1 = m_viewPort.TopLeftY; + viewPort.x2 = m_viewPort.TopLeftX + m_viewPort.Width; + viewPort.y2 = m_viewPort.TopLeftY + m_viewPort.Height; +} + +void CRenderSystemDX::SetViewPort(const CRect& viewPort) +{ + if (!m_bRenderCreated) + return; + + m_viewPort.MinDepth = 0.0f; + m_viewPort.MaxDepth = 1.0f; + m_viewPort.TopLeftX = viewPort.x1; + m_viewPort.TopLeftY = viewPort.y1; + m_viewPort.Width = viewPort.x2 - viewPort.x1; + m_viewPort.Height = viewPort.y2 - viewPort.y1; + + m_deviceResources->SetViewPort(m_viewPort); + m_pGUIShader->SetViewPort(m_viewPort); +} + +void CRenderSystemDX::RestoreViewPort() +{ + if (!m_bRenderCreated) + return; + + m_deviceResources->SetViewPort(m_viewPort); + m_pGUIShader->SetViewPort(m_viewPort); +} + +bool CRenderSystemDX::ScissorsCanEffectClipping() +{ + if (!m_bRenderCreated) + return false; + + return m_pGUIShader != nullptr && m_pGUIShader->HardwareClipIsPossible(); +} + +CRect CRenderSystemDX::ClipRectToScissorRect(const CRect &rect) +{ + if (!m_bRenderCreated) + return CRect(); + + float xFactor = m_pGUIShader->GetClipXFactor(); + float xOffset = m_pGUIShader->GetClipXOffset(); + float yFactor = m_pGUIShader->GetClipYFactor(); + float yOffset = m_pGUIShader->GetClipYOffset(); + + return CRect(rect.x1 * xFactor + xOffset, + rect.y1 * yFactor + yOffset, + rect.x2 * xFactor + xOffset, + rect.y2 * yFactor + yOffset); +} + +void CRenderSystemDX::SetScissors(const CRect& rect) +{ + if (!m_bRenderCreated) + return; + + auto m_pContext = m_deviceResources->GetD3DContext(); + + m_scissor = rect; + CD3D11_RECT scissor(MathUtils::round_int(rect.x1) + , MathUtils::round_int(rect.y1) + , MathUtils::round_int(rect.x2) + , MathUtils::round_int(rect.y2)); + + m_pContext->RSSetScissorRects(1, &scissor); + m_pContext->RSSetState(m_RSScissorEnable.Get()); + m_ScissorsEnabled = true; +} + +void CRenderSystemDX::ResetScissors() +{ + if (!m_bRenderCreated) + return; + + auto m_pContext = m_deviceResources->GetD3DContext(); + auto outputSize = m_deviceResources->GetOutputSize(); + + m_scissor.SetRect(0.0f, 0.0f, + static_cast<float>(outputSize.Width), + static_cast<float>(outputSize.Height)); + + m_pContext->RSSetState(m_RSScissorDisable.Get()); + m_ScissorsEnabled = false; +} + +void CRenderSystemDX::OnDXDeviceLost() +{ + CRenderSystemDX::DestroyRenderSystem(); +} + +void CRenderSystemDX::OnDXDeviceRestored() +{ + CRenderSystemDX::InitRenderSystem(); +} + +void CRenderSystemDX::SetStereoMode(RENDER_STEREO_MODE mode, RENDER_STEREO_VIEW view) +{ + CRenderSystemBase::SetStereoMode(mode, view); + + if (!m_bRenderCreated) + return; + + auto m_pContext = m_deviceResources->GetD3DContext(); + + UINT writeMask = D3D11_COLOR_WRITE_ENABLE_ALL; + if(m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_RED_CYAN) + { + if(m_stereoView == RENDER_STEREO_VIEW_LEFT) + writeMask = D3D11_COLOR_WRITE_ENABLE_RED; + else if(m_stereoView == RENDER_STEREO_VIEW_RIGHT) + writeMask = D3D11_COLOR_WRITE_ENABLE_BLUE | D3D11_COLOR_WRITE_ENABLE_GREEN; + } + if(m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_GREEN_MAGENTA) + { + if(m_stereoView == RENDER_STEREO_VIEW_LEFT) + writeMask = D3D11_COLOR_WRITE_ENABLE_GREEN; + else if(m_stereoView == RENDER_STEREO_VIEW_RIGHT) + writeMask = D3D11_COLOR_WRITE_ENABLE_BLUE | D3D11_COLOR_WRITE_ENABLE_RED; + } + if (m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_YELLOW_BLUE) + { + if (m_stereoView == RENDER_STEREO_VIEW_LEFT) + writeMask = D3D11_COLOR_WRITE_ENABLE_RED | D3D11_COLOR_WRITE_ENABLE_GREEN; + else if (m_stereoView == RENDER_STEREO_VIEW_RIGHT) + writeMask = D3D11_COLOR_WRITE_ENABLE_BLUE; + } + if ( RENDER_STEREO_MODE_INTERLACED == m_stereoMode + || RENDER_STEREO_MODE_CHECKERBOARD == m_stereoMode) + { + if (m_stereoView == RENDER_STEREO_VIEW_RIGHT) + { + m_pContext->OMSetRenderTargets(1, m_rightEyeTex.GetAddressOfRTV(), m_deviceResources->GetDSV()); + } + } + else if (RENDER_STEREO_MODE_HARDWAREBASED == m_stereoMode) + { + m_deviceResources->SetStereoIdx(m_stereoView == RENDER_STEREO_VIEW_RIGHT ? 1 : 0); + + m_pContext->OMSetRenderTargets(1, m_deviceResources->GetBackBuffer().GetAddressOfRTV(), m_deviceResources->GetDSV()); + } + + auto m_pD3DDev = m_deviceResources->GetD3DDevice(); + + D3D11_BLEND_DESC desc; + m_BlendEnableState->GetDesc(&desc); + // update blend state + if (desc.RenderTarget[0].RenderTargetWriteMask != writeMask) + { + m_BlendDisableState = nullptr; + m_BlendEnableState = nullptr; + + desc.RenderTarget[0].RenderTargetWriteMask = writeMask; + m_pD3DDev->CreateBlendState(&desc, &m_BlendEnableState); + + desc.RenderTarget[0].BlendEnable = false; + m_pD3DDev->CreateBlendState(&desc, &m_BlendDisableState); + + float blendFactors[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + m_pContext->OMSetBlendState(m_BlendEnabled ? m_BlendEnableState.Get() : m_BlendDisableState.Get(), blendFactors, 0xFFFFFFFF); + } +} + +bool CRenderSystemDX::SupportsStereo(RENDER_STEREO_MODE mode) const +{ + switch (mode) + { + case RENDER_STEREO_MODE_ANAGLYPH_RED_CYAN: + case RENDER_STEREO_MODE_ANAGLYPH_GREEN_MAGENTA: + case RENDER_STEREO_MODE_ANAGLYPH_YELLOW_BLUE: + case RENDER_STEREO_MODE_INTERLACED: + case RENDER_STEREO_MODE_CHECKERBOARD: + return true; + case RENDER_STEREO_MODE_HARDWAREBASED: + return m_deviceResources->IsStereoAvailable(); + default: + return CRenderSystemBase::SupportsStereo(mode); + } +} + +void CRenderSystemDX::FlushGPU() const +{ + if (!m_bRenderCreated) + return; + + m_deviceResources->FinishCommandList(); + m_deviceResources->GetImmediateContext()->Flush(); +} + +bool CRenderSystemDX::InitGUIShader() +{ + delete m_pGUIShader; + m_pGUIShader = nullptr; + + m_pGUIShader = new CGUIShaderDX(); + if (!m_pGUIShader->Initialize()) + { + CLog::LogF(LOGERROR, "Failed to initialize GUI shader."); + return false; + } + + m_pGUIShader->ApplyStateBlock(); + return true; +} + +void CRenderSystemDX::SetAlphaBlendEnable(bool enable) +{ + if (!m_bRenderCreated) + return; + + m_deviceResources->GetD3DContext()->OMSetBlendState(enable ? m_BlendEnableState.Get() : m_BlendDisableState.Get(), nullptr, 0xFFFFFFFF); + m_BlendEnabled = enable; +} + +CD3DTexture& CRenderSystemDX::GetBackBuffer() +{ + if (m_stereoView == RENDER_STEREO_VIEW_RIGHT && m_rightEyeTex.Get()) + return m_rightEyeTex; + + return m_deviceResources->GetBackBuffer(); +} + +void CRenderSystemDX::CheckDeviceCaps() +{ + const auto feature_level = m_deviceResources->GetDeviceFeatureLevel(); + if (feature_level < D3D_FEATURE_LEVEL_9_3) + m_maxTextureSize = D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION; + else if (feature_level < D3D_FEATURE_LEVEL_10_0) + m_maxTextureSize = D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION; + else if (feature_level < D3D_FEATURE_LEVEL_11_0) + m_maxTextureSize = D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION; + else + // 11_x and greater feature level. Limit this size to avoid memory overheads + m_maxTextureSize = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION >> 1; +} + +bool CRenderSystemDX::SupportsNPOT(bool dxt) const +{ + // MSDN says: + // At feature levels 9_1, 9_2 and 9_3, the display device supports the use + // of 2D textures with dimensions that are not powers of two under two conditions: + // 1) only one MIP-map level for each texture can be created - we are using both 1 and 0 mipmap levels + // 2) no wrap sampler modes for textures are allowed - we are using clamp everywhere + // At feature levels 10_0, 10_1 and 11_0, the display device unconditionally supports the use of 2D textures with dimensions that are not powers of two. + // taking in account first condition we setup caps NPOT for FE > 9.x only + return m_deviceResources->GetDeviceFeatureLevel() > D3D_FEATURE_LEVEL_9_3 ? true : false; +} diff --git a/xbmc/rendering/dx/RenderSystemDX.h b/xbmc/rendering/dx/RenderSystemDX.h new file mode 100644 index 0000000..9a18e22 --- /dev/null +++ b/xbmc/rendering/dx/RenderSystemDX.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "DeviceResources.h" +#include "rendering/RenderSystem.h" +#include "threads/Condition.h" +#include "threads/CriticalSection.h" +#include "threads/SystemClock.h" +#include "utils/ColorUtils.h" + +#include <wrl/client.h> + +class ID3DResource; +class CGUIShaderDX; +enum AVPixelFormat; +enum AVPixelFormat; + +class CRenderSystemDX : public CRenderSystemBase, DX::IDeviceNotify +{ +public: + CRenderSystemDX(); + virtual ~CRenderSystemDX(); + + // CRenderBase overrides + bool InitRenderSystem() override; + bool DestroyRenderSystem() override; + bool BeginRender() override; + bool EndRender() override; + void PresentRender(bool rendered, bool videoLayer) override; + bool ClearBuffers(UTILS::COLOR::Color color) override; + void SetViewPort(const CRect& viewPort) override; + void GetViewPort(CRect& viewPort) override; + void RestoreViewPort() override; + CRect ClipRectToScissorRect(const CRect &rect) override; + bool ScissorsCanEffectClipping() override; + void SetScissors(const CRect &rect) override; + void ResetScissors() override; + void CaptureStateBlock() override; + void ApplyStateBlock() override; + void SetCameraPosition(const CPoint &camera, int screenWidth, int screenHeight, float stereoFactor = 0.f) override; + void SetStereoMode(RENDER_STEREO_MODE mode, RENDER_STEREO_VIEW view) override; + bool SupportsStereo(RENDER_STEREO_MODE mode) const override; + void Project(float &x, float &y, float &z) override; + bool SupportsNPOT(bool dxt) const override; + + // IDeviceNotify overrides + void OnDXDeviceLost() override; + void OnDXDeviceRestored() override; + + // CRenderSystemDX methods + CGUIShaderDX* GetGUIShader() const { return m_pGUIShader; } + bool Interlaced() const { return m_interlaced; } + bool IsFormatSupport(DXGI_FORMAT format, unsigned int usage) const; + CRect GetBackBufferRect(); + CD3DTexture& GetBackBuffer(); + + void FlushGPU() const; + void RequestDecodingTime(); + void ReleaseDecodingTime(); + void SetAlphaBlendEnable(bool enable); + + // empty overrides + bool IsExtSupported(const char* extension) const override { return false; } + bool ResetRenderSystem(int width, int height) override { return true; } + +protected: + virtual void PresentRenderImpl(bool rendered) = 0; + + bool CreateStates(); + bool InitGUIShader(); + void OnResize(); + void CheckInterlacedStereoView(void); + void CheckDeviceCaps(); + + CCriticalSection m_resourceSection; + CCriticalSection m_decoderSection; + + // our adapter could change as we go + bool m_interlaced; + bool m_inScene{ false }; ///< True if we're in a BeginScene()/EndScene() block + bool m_BlendEnabled{ false }; + bool m_ScissorsEnabled{ false }; + D3D11_VIEWPORT m_viewPort; + CRect m_scissor; + CGUIShaderDX* m_pGUIShader{ nullptr }; + Microsoft::WRL::ComPtr<ID3D11DepthStencilState> m_depthStencilState; + Microsoft::WRL::ComPtr<ID3D11BlendState> m_BlendEnableState; + Microsoft::WRL::ComPtr<ID3D11BlendState> m_BlendDisableState; + Microsoft::WRL::ComPtr<ID3D11RasterizerState> m_RSScissorDisable; + Microsoft::WRL::ComPtr<ID3D11RasterizerState> m_RSScissorEnable; + // stereo interlaced/checkerboard intermediate target + CD3DTexture m_rightEyeTex; + + XbmcThreads::EndTime<> m_decodingTimer; + XbmcThreads::ConditionVariable m_decodingEvent; + + std::shared_ptr<DX::DeviceResources> m_deviceResources; +}; + diff --git a/xbmc/rendering/dx/ScreenshotSurfaceWindows.cpp b/xbmc/rendering/dx/ScreenshotSurfaceWindows.cpp new file mode 100644 index 0000000..7a62ad6 --- /dev/null +++ b/xbmc/rendering/dx/ScreenshotSurfaceWindows.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ScreenshotSurfaceWindows.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "rendering/dx/DeviceResources.h" +#include "utils/Screenshot.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" + +#include <mutex> + +#include <wrl/client.h> + +using namespace Microsoft::WRL; + +void CScreenshotSurfaceWindows::Register() +{ + CScreenShot::Register(CScreenshotSurfaceWindows::CreateSurface); +} + +std::unique_ptr<IScreenshotSurface> CScreenshotSurfaceWindows::CreateSurface() +{ + return std::unique_ptr<CScreenshotSurfaceWindows>(new CScreenshotSurfaceWindows()); +} + +bool CScreenshotSurfaceWindows::Capture() +{ + CWinSystemBase* winsystem = CServiceBroker::GetWinSystem(); + if (!winsystem) + return false; + + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (!gui) + return false; + + std::unique_lock<CCriticalSection> lock(winsystem->GetGfxContext()); + gui->GetWindowManager().Render(); + + auto deviceResources = DX::DeviceResources::Get(); + deviceResources->FinishCommandList(); + + ComPtr<ID3D11DeviceContext> pImdContext = deviceResources->GetImmediateContext(); + ComPtr<ID3D11Device> pDevice = deviceResources->GetD3DDevice(); + CD3DTexture& backbuffer = deviceResources->GetBackBuffer(); + if (!backbuffer.Get()) + return false; + + D3D11_TEXTURE2D_DESC desc = {}; + backbuffer.GetDesc(&desc); + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.BindFlags = 0; + + ComPtr<ID3D11Texture2D> pCopyTexture = nullptr; + if (SUCCEEDED(pDevice->CreateTexture2D(&desc, nullptr, &pCopyTexture))) + { + // take copy + pImdContext->CopyResource(pCopyTexture.Get(), backbuffer.Get()); + + D3D11_MAPPED_SUBRESOURCE res; + if (SUCCEEDED(pImdContext->Map(pCopyTexture.Get(), 0, D3D11_MAP_READ, 0, &res))) + { + m_width = desc.Width; + m_height = desc.Height; + m_stride = res.RowPitch; + m_buffer = new unsigned char[m_height * m_stride]; + if (desc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) + { + // convert R10G10B10A2 -> B8G8R8A8 + for (int y = 0; y < m_height; y++) + { + uint32_t* pixels10 = reinterpret_cast<uint32_t*>(static_cast<uint8_t*>(res.pData) + y * res.RowPitch); + uint8_t* pixels8 = m_buffer + y * m_stride; + + for (int x = 0; x < m_width; x++, pixels10++, pixels8 += 4) + { + // actual bit per channel is A2B10G10R10 + uint32_t pixel = *pixels10; + // R + pixels8[2] = static_cast<uint8_t>((pixel & 0x3FF) * 255 / 1023); + // G + pixel >>= 10; + pixels8[1] = static_cast<uint8_t>((pixel & 0x3FF) * 255 / 1023); + // B + pixel >>= 10; + pixels8[0] = static_cast<uint8_t>((pixel & 0x3FF) * 255 / 1023); + // A + pixels8[3] = 0xFF; + } + } + } + else + memcpy(m_buffer, res.pData, m_height * m_stride); + pImdContext->Unmap(pCopyTexture.Get(), 0); + } + else + CLog::LogF(LOGERROR, "MAP_READ failed."); + } + + return m_buffer != nullptr; +} diff --git a/xbmc/rendering/dx/ScreenshotSurfaceWindows.h b/xbmc/rendering/dx/ScreenshotSurfaceWindows.h new file mode 100644 index 0000000..e492e44 --- /dev/null +++ b/xbmc/rendering/dx/ScreenshotSurfaceWindows.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/IScreenshotSurface.h" + +#include <memory> + +class CScreenshotSurfaceWindows : public IScreenshotSurface +{ +public: + static void Register(); + static std::unique_ptr<IScreenshotSurface> CreateSurface(); + + bool Capture() override; +}; |