#include "pch.h"
#include <algorithm>
#include "DeviceResources.h"
#include "DirectXHelper.h"
#include "Core/Config.h"
#include "Common/StringUtils.h"
#include "Common/Data/Encoding/Utf8.h"
using namespace D2D1;
using namespace DirectX;
using namespace Microsoft::WRL;
using namespace Windows::Foundation;
using namespace Windows::Graphics::Display;
using namespace Windows::UI::Core;
using namespace Windows::UI::Xaml::Controls;
using namespace Platform;
namespace DisplayMetrics
{
static const bool SupportHighResolutions = true;
static const float DpiThreshold = 192.0f;
static const float WidthThreshold = 1920.0f;
static const float HeightThreshold = 1080.0f;
};
namespace ScreenRotation
{
static const XMFLOAT4X4 Rotation0(
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
);
static const XMFLOAT4X4 Rotation90(
0.0f, 1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
);
static const XMFLOAT4X4 Rotation180(
-1.0f, 0.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
);
static const XMFLOAT4X4 Rotation270(
0.0f, -1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
);
};
DX::DeviceResources::DeviceResources() :
m_screenViewport(),
m_d3dFeatureLevel(D3D_FEATURE_LEVEL_9_1),
m_d3dRenderTargetSize(),
m_outputSize(),
m_logicalSize(),
m_nativeOrientation(DisplayOrientations::None),
m_currentOrientation(DisplayOrientations::None),
m_dpi(-1.0f),
m_effectiveDpi(-1.0f),
m_deviceNotify(nullptr)
{
CreateDeviceIndependentResources();
CreateDeviceResources();
}
void DX::DeviceResources::CreateDeviceIndependentResources()
{
D2D1_FACTORY_OPTIONS options;
ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));
#if defined(_DEBUG)
options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
DX::ThrowIfFailed(
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(ID2D1Factory3),
&options,
&m_d2dFactory
)
);
DX::ThrowIfFailed(
DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory3),
&m_dwriteFactory
)
);
DX::ThrowIfFailed(
CoCreateInstance(
CLSID_WICImagingFactory2,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&m_wicFactory)
)
);
}
void DX::DeviceResources::CreateDeviceResources(IDXGIAdapter* vAdapter)
{
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#if defined(_DEBUG)
if (DX::SdkLayersAvailable())
{
creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
}
#endif
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;
auto hardwareType = (vAdapter != nullptr ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE);
HRESULT hr = D3D11CreateDevice(
vAdapter,
hardwareType,
0,
creationFlags,
featureLevels,
ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION,
&device,
&m_d3dFeatureLevel,
&context
);
if (FAILED(hr))
{
DX::ThrowIfFailed(
D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_WARP,
0,
creationFlags,
featureLevels,
ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION,
&device,
&m_d3dFeatureLevel,
&context
)
);
}
if (!vAdapter && CreateAdaptersList(device)) {
return;
}
DX::ThrowIfFailed(
device.As(&m_d3dDevice)
);
DX::ThrowIfFailed(
context.As(&m_d3dContext)
);
DX::ThrowIfFailed(
m_d3dDevice.As(&m_dxgiDevice)
);
DX::ThrowIfFailed(
m_dxgiDevice->GetAdapter(&m_dxgiAdapter)
);
DX::ThrowIfFailed(
m_dxgiAdapter->GetParent(IID_PPV_ARGS(&m_dxgiFactory))
);
DX::ThrowIfFailed(
m_d3dDevice.As(&m_dxgiDevice)
);
DX::ThrowIfFailed(
m_d2dFactory->CreateDevice(m_dxgiDevice.Get(), &m_d2dDevice)
);
DX::ThrowIfFailed(
m_d2dDevice->CreateDeviceContext(
D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
&m_d2dContext
)
);
}
bool DX::DeviceResources::CreateAdaptersList(ComPtr<ID3D11Device> device) {
ComPtr<IDXGIDevice3> dxgi_device;
DX::ThrowIfFailed(
device.As(&dxgi_device)
);
Microsoft::WRL::ComPtr<IDXGIAdapter> deviceAdapter;
dxgi_device->GetAdapter(&deviceAdapter);
Microsoft::WRL::ComPtr<IDXGIFactory4> deviceFactory;
deviceAdapter->GetParent(IID_PPV_ARGS(&deviceFactory));
DXGI_ADAPTER_DESC currentDefaultAdapterDesc;
deviceAdapter->GetDesc(¤tDefaultAdapterDesc);
std::string currentDefaultAdapterName = ConvertWStringToUTF8(currentDefaultAdapterDesc.Description);
UINT i = 0;
IDXGIAdapter* pAdapter;
IDXGIAdapter* customAdapter = nullptr;
auto deviceInfo = Windows::System::Profile::AnalyticsInfo::VersionInfo;
bool isXbox = deviceInfo->DeviceFamily == "Windows.Xbox";
while (deviceFactory->EnumAdapters(i, &pAdapter) != DXGI_ERROR_NOT_FOUND)
{
++i;
DXGI_ADAPTER_DESC vAdapterDesc;
pAdapter->GetDesc(&vAdapterDesc);
auto adapterDescription = ConvertWStringToUTF8(vAdapterDesc.Description);
if (isXbox && adapterDescription == "Microsoft Basic Render Driver") {
continue;
}
m_vAdapters.push_back(adapterDescription);
if (!g_Config.sD3D11Device.empty() && g_Config.sD3D11Device == adapterDescription) {
if (adapterDescription != currentDefaultAdapterName) {
customAdapter = pAdapter;
}
}
}
deviceFactory->Release();
if (m_vAdapters.size() == 1) {
m_vAdapters.clear();
}
bool reCreateDevice = false;
if (customAdapter) {
reCreateDevice = true;
CreateDeviceResources(customAdapter);
}
return reCreateDevice;
}
void DX::DeviceResources::CreateWindowSizeDependentResources()
{
auto coreWindow = CoreWindow::GetForCurrentThread();
SetWindow(coreWindow);
ID3D11RenderTargetView* nullViews[] = {nullptr};
m_d3dContext->OMSetRenderTargets(ARRAYSIZE(nullViews), nullViews, nullptr);
m_d3dRenderTargetView = nullptr;
m_d2dContext->SetTarget(nullptr);
m_d2dTargetBitmap = nullptr;
m_d3dContext->Flush1(D3D11_CONTEXT_TYPE_ALL, nullptr);
UpdateRenderTargetSize();
DXGI_MODE_ROTATION displayRotation = ComputeDisplayRotation();
bool swapDimensions = displayRotation == DXGI_MODE_ROTATION_ROTATE90 || displayRotation == DXGI_MODE_ROTATION_ROTATE270;
m_d3dRenderTargetSize.Width = swapDimensions ? m_outputSize.Height : m_outputSize.Width;
m_d3dRenderTargetSize.Height = swapDimensions ? m_outputSize.Width : m_outputSize.Height;
if (m_swapChain != nullptr)
{
HRESULT hr = m_swapChain->ResizeBuffers(
2,
lround(m_d3dRenderTargetSize.Width),
lround(m_d3dRenderTargetSize.Height),
DXGI_FORMAT_B8G8R8A8_UNORM,
0
);
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
{
HandleDeviceLost();
return;
}
else
{
DX::ThrowIfFailed(hr);
}
}
else
{
DXGI_SCALING scaling = DisplayMetrics::SupportHighResolutions ? DXGI_SCALING_NONE : DXGI_SCALING_STRETCH;
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};
swapChainDesc.Width = lround(m_d3dRenderTargetSize.Width);
swapChainDesc.Height = lround(m_d3dRenderTargetSize.Height);
swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
swapChainDesc.Stereo = false;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 2;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
swapChainDesc.Flags = 0;
swapChainDesc.Scaling = scaling;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
ComPtr<IDXGISwapChain1> swapChain;
DX::ThrowIfFailed(
m_dxgiFactory->CreateSwapChainForCoreWindow(
m_d3dDevice.Get(),
reinterpret_cast<IUnknown*>(coreWindow),
&swapChainDesc,
nullptr,
&swapChain
)
);
DX::ThrowIfFailed(
swapChain.As(&m_swapChain)
);
DX::ThrowIfFailed(
m_dxgiDevice->SetMaximumFrameLatency(1)
);
}
switch (displayRotation)
{
case DXGI_MODE_ROTATION_IDENTITY:
m_orientationTransform2D = Matrix3x2F::Identity();
m_orientationTransform3D = ScreenRotation::Rotation0;
break;
case DXGI_MODE_ROTATION_ROTATE90:
m_orientationTransform2D =
Matrix3x2F::Rotation(90.0f) *
Matrix3x2F::Translation(m_logicalSize.Height, 0.0f);
m_orientationTransform3D = ScreenRotation::Rotation270;
break;
case DXGI_MODE_ROTATION_ROTATE180:
m_orientationTransform2D =
Matrix3x2F::Rotation(180.0f) *
Matrix3x2F::Translation(m_logicalSize.Width, m_logicalSize.Height);
m_orientationTransform3D = ScreenRotation::Rotation180;
break;
case DXGI_MODE_ROTATION_ROTATE270:
m_orientationTransform2D =
Matrix3x2F::Rotation(270.0f) *
Matrix3x2F::Translation(0.0f, m_logicalSize.Width);
m_orientationTransform3D = ScreenRotation::Rotation90;
break;
default:
throw ref new FailureException();
}
DX::ThrowIfFailed(
m_swapChain->SetRotation(displayRotation)
);
ComPtr<ID3D11Texture2D1> backBuffer;
DX::ThrowIfFailed(
m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))
);
DX::ThrowIfFailed(
m_d3dDevice->CreateRenderTargetView1(
backBuffer.Get(),
nullptr,
&m_d3dRenderTargetView
)
);
m_screenViewport = CD3D11_VIEWPORT(
0.0f,
0.0f,
m_d3dRenderTargetSize.Width,
m_d3dRenderTargetSize.Height
);
m_d3dContext->RSSetViewports(1, &m_screenViewport);
D2D1_BITMAP_PROPERTIES1 bitmapProperties =
D2D1::BitmapProperties1(
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
m_dpi,
m_dpi
);
ComPtr<IDXGISurface2> dxgiBackBuffer;
DX::ThrowIfFailed(
m_swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer))
);
DX::ThrowIfFailed(
m_d2dContext->CreateBitmapFromDxgiSurface(
dxgiBackBuffer.Get(),
&bitmapProperties,
&m_d2dTargetBitmap
)
);
m_d2dContext->SetTarget(m_d2dTargetBitmap.Get());
m_d2dContext->SetDpi(m_effectiveDpi, m_effectiveDpi);
m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
}
void DX::DeviceResources::UpdateRenderTargetSize()
{
m_effectiveDpi = m_dpi;
if (Windows::System::Profile::AnalyticsInfo::VersionInfo->DeviceFamily == L"Windows.Xbox")
{
m_effectiveDpi = 96.0f / static_cast<float>(m_logicalSize.Height) * 1080.0f;
}
else
{
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);
if (std::max(width, height) > DisplayMetrics::WidthThreshold && std::min(width, height) > DisplayMetrics::HeightThreshold)
{
m_effectiveDpi /= 2.0f;
}
}
}
m_outputSize.Width = DX::ConvertDipsToPixels(m_logicalSize.Width, m_effectiveDpi);
m_outputSize.Height = DX::ConvertDipsToPixels(m_logicalSize.Height, m_effectiveDpi);
m_outputSize.Width = std::max(m_outputSize.Width, 1.0f);
m_outputSize.Height = std::max(m_outputSize.Height, 1.0f);
}
void DX::DeviceResources::SetWindow(CoreWindow^ window)
{
DisplayInformation^ currentDisplayInformation = DisplayInformation::GetForCurrentView();
if (Windows::System::Profile::AnalyticsInfo::VersionInfo->DeviceFamily == L"Windows.Xbox")
{
const auto hdi = Windows::Graphics::Display::Core::HdmiDisplayInformation::GetForCurrentView();
if (hdi)
{
try
{
const auto dm = hdi->GetCurrentDisplayMode();
const float hdmi_width = (float)dm->ResolutionWidthInRawPixels;
const float hdmi_height = (float)dm->ResolutionHeightInRawPixels;
m_logicalSize = Windows::Foundation::Size(hdmi_width, hdmi_height);
m_dpi = currentDisplayInformation->LogicalDpi * 1.5f;
}
catch (const Platform::Exception^)
{
m_logicalSize = Windows::Foundation::Size(window->Bounds.Width, window->Bounds.Height);
m_dpi = currentDisplayInformation->LogicalDpi;
}
}
}
else
{
m_logicalSize = Windows::Foundation::Size(window->Bounds.Width, window->Bounds.Height);
m_dpi = currentDisplayInformation->LogicalDpi;
}
m_nativeOrientation = currentDisplayInformation->NativeOrientation;
m_currentOrientation = currentDisplayInformation->CurrentOrientation;
m_d2dContext->SetDpi(m_dpi, m_dpi);
}
void DX::DeviceResources::SetLogicalSize(Windows::Foundation::Size logicalSize)
{
if (m_logicalSize != logicalSize)
{
m_logicalSize = logicalSize;
CreateWindowSizeDependentResources();
}
}
void DX::DeviceResources::SetDpi(float dpi)
{
if (dpi != m_dpi)
{
m_dpi = dpi;
CreateWindowSizeDependentResources();
}
}
void DX::DeviceResources::SetCurrentOrientation(DisplayOrientations currentOrientation)
{
if (m_currentOrientation != currentOrientation)
{
m_currentOrientation = currentOrientation;
CreateWindowSizeDependentResources();
}
}
void DX::DeviceResources::ValidateDevice()
{
ComPtr<IDXGIAdapter1> previousDefaultAdapter;
DX::ThrowIfFailed(m_dxgiFactory->EnumAdapters1(0, &previousDefaultAdapter));
DXGI_ADAPTER_DESC1 previousDesc;
DX::ThrowIfFailed(previousDefaultAdapter->GetDesc1(&previousDesc));
ComPtr<IDXGIFactory4> currentFactory;
DX::ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(¤tFactory)));
ComPtr<IDXGIAdapter1> currentDefaultAdapter;
DX::ThrowIfFailed(currentFactory->EnumAdapters1(0, ¤tDefaultAdapter));
DXGI_ADAPTER_DESC1 currentDesc;
DX::ThrowIfFailed(currentDefaultAdapter->GetDesc1(¤tDesc));
if (previousDesc.AdapterLuid.LowPart != currentDesc.AdapterLuid.LowPart ||
previousDesc.AdapterLuid.HighPart != currentDesc.AdapterLuid.HighPart ||
FAILED(m_d3dDevice->GetDeviceRemovedReason()))
{
previousDefaultAdapter = nullptr;
HandleDeviceLost();
}
}
void DX::DeviceResources::HandleDeviceLost()
{
m_swapChain = nullptr;
if (m_deviceNotify != nullptr)
{
m_deviceNotify->OnDeviceLost();
}
CreateDeviceResources();
m_d2dContext->SetDpi(m_dpi, m_dpi);
CreateWindowSizeDependentResources();
if (m_deviceNotify != nullptr)
{
m_deviceNotify->OnDeviceRestored();
}
}
void DX::DeviceResources::RegisterDeviceNotify(DX::IDeviceNotify* deviceNotify)
{
m_deviceNotify = deviceNotify;
}
void DX::DeviceResources::Trim()
{
ComPtr<IDXGIDevice3> dxgiDevice;
m_d3dDevice.As(&dxgiDevice);
dxgiDevice->Trim();
}
void DX::DeviceResources::Present()
{
DXGI_PRESENT_PARAMETERS parameters = { 0 };
HRESULT hr = m_swapChain->Present1(1, 0, ¶meters);
m_d3dContext->DiscardView1(m_d3dRenderTargetView.Get(), nullptr, 0);
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
{
HandleDeviceLost();
}
else
{
DX::ThrowIfFailed(hr);
}
}
DXGI_MODE_ROTATION DX::DeviceResources::ComputeDisplayRotation()
{
DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED;
switch (m_nativeOrientation)
{
case DisplayOrientations::Landscape:
switch (m_currentOrientation)
{
case DisplayOrientations::Landscape:
rotation = DXGI_MODE_ROTATION_IDENTITY;
break;
case DisplayOrientations::Portrait:
rotation = DXGI_MODE_ROTATION_ROTATE270;
break;
case DisplayOrientations::LandscapeFlipped:
rotation = DXGI_MODE_ROTATION_ROTATE180;
break;
case DisplayOrientations::PortraitFlipped:
rotation = DXGI_MODE_ROTATION_ROTATE90;
break;
}
break;
case DisplayOrientations::Portrait:
switch (m_currentOrientation)
{
case DisplayOrientations::Landscape:
rotation = DXGI_MODE_ROTATION_ROTATE90;
break;
case DisplayOrientations::Portrait:
rotation = DXGI_MODE_ROTATION_IDENTITY;
break;
case DisplayOrientations::LandscapeFlipped:
rotation = DXGI_MODE_ROTATION_ROTATE270;
break;
case DisplayOrientations::PortraitFlipped:
rotation = DXGI_MODE_ROTATION_ROTATE180;
break;
}
break;
}
return rotation;
}