#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 winrt::Windows::Foundation;
using namespace winrt::Windows::Graphics::Display;
using namespace winrt::Windows::UI::Core;
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.put_void()
)
);
DX::ThrowIfFailed(
DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory3),
reinterpret_cast<::IUnknown**>(m_dwriteFactory.put())
)
);
DX::ThrowIfFailed(
CoCreateInstance(
CLSID_WICImagingFactory2,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(m_wicFactory.put())
)
);
}
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
};
winrt::com_ptr<ID3D11Device> device;
winrt::com_ptr<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.put(),
&m_d3dFeatureLevel,
context.put()
);
if (FAILED(hr))
{
DX::ThrowIfFailed(
D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_WARP,
0,
creationFlags,
featureLevels,
ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION,
device.put(),
&m_d3dFeatureLevel,
context.put()
)
);
}
if (!vAdapter && CreateAdaptersList(device)) {
return;
}
m_d3dDevice = device.as<ID3D11Device3>();
m_d3dContext = context.as<ID3D11DeviceContext3>();
m_dxgiDevice = m_d3dDevice.as<IDXGIDevice3>();
DX::ThrowIfFailed(
m_dxgiDevice->GetAdapter(m_dxgiAdapter.put())
);
winrt::com_ptr<IDXGIObject> adapterParent;
DX::ThrowIfFailed(
m_dxgiAdapter->GetParent(IID_PPV_ARGS(adapterParent.put()))
);
m_dxgiFactory = adapterParent.as<IDXGIFactory4>();
DX::ThrowIfFailed(
m_d2dFactory->CreateDevice(m_dxgiDevice.get(), m_d2dDevice.put())
);
DX::ThrowIfFailed(
m_d2dDevice->CreateDeviceContext(
D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
m_d2dContext.put()
)
);
}
bool DX::DeviceResources::CreateAdaptersList(winrt::com_ptr<ID3D11Device> device) {
auto dxgi_device = device.as<IDXGIDevice3>();
winrt::com_ptr<IDXGIAdapter> deviceAdapter;
dxgi_device->GetAdapter(deviceAdapter.put());
winrt::com_ptr<IDXGIObject> adapterParent;
deviceAdapter->GetParent(IID_PPV_ARGS(adapterParent.put()));
auto deviceFactory = adapterParent.as<IDXGIFactory4>();
DXGI_ADAPTER_DESC currentDefaultAdapterDesc;
deviceAdapter->GetDesc(¤tDefaultAdapterDesc);
std::string currentDefaultAdapterName = ConvertWStringToUTF8(currentDefaultAdapterDesc.Description);
UINT i = 0;
IDXGIAdapter* pAdapter;
IDXGIAdapter* customAdapter = nullptr;
auto deviceInfo = winrt::Windows::System::Profile::AnalyticsInfo::VersionInfo();
bool isXbox = deviceInfo.DeviceFamily() == L"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;
}
}
}
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();
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;
winrt::com_ptr<IDXGISwapChain1> swapChain;
DX::ThrowIfFailed(
m_dxgiFactory->CreateSwapChainForCoreWindow(
m_d3dDevice.get(),
winrt::get_unknown(coreWindow),
&swapChainDesc,
nullptr,
swapChain.put()
)
);
m_swapChain = swapChain.as<IDXGISwapChain3>();
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:
winrt::throw_hresult(E_FAIL);
}
DX::ThrowIfFailed(
m_swapChain->SetRotation(displayRotation)
);
winrt::com_ptr<ID3D11Texture2D1> backBuffer;
DX::ThrowIfFailed(
m_swapChain->GetBuffer(0, IID_PPV_ARGS(backBuffer.put()))
);
DX::ThrowIfFailed(
m_d3dDevice->CreateRenderTargetView1(
backBuffer.get(),
nullptr,
m_d3dRenderTargetView.put()
)
);
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
);
winrt::com_ptr<IDXGISurface2> dxgiBackBuffer;
DX::ThrowIfFailed(
m_swapChain->GetBuffer(0, IID_PPV_ARGS(dxgiBackBuffer.put()))
);
DX::ThrowIfFailed(
m_d2dContext->CreateBitmapFromDxgiSurface(
dxgiBackBuffer.get(),
&bitmapProperties,
m_d2dTargetBitmap.put()
)
);
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 (winrt::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()
{
auto window = CoreWindow::GetForCurrentThread();
DisplayInformation currentDisplayInformation = DisplayInformation::GetForCurrentView();
if (winrt::Windows::System::Profile::AnalyticsInfo::VersionInfo().DeviceFamily() == L"Windows.Xbox")
{
auto hdi = winrt::Windows::Graphics::Display::Core::HdmiDisplayInformation::GetForCurrentView();
if (hdi)
{
try
{
auto dm = hdi.GetCurrentDisplayMode();
float hdmi_width = static_cast<float>(dm.ResolutionWidthInRawPixels());
float hdmi_height = static_cast<float>(dm.ResolutionHeightInRawPixels());
m_logicalSize = winrt::Windows::Foundation::Size(hdmi_width, hdmi_height);
m_dpi = currentDisplayInformation.LogicalDpi() * 1.5f;
}
catch (const winrt::hresult_error&)
{
m_logicalSize = winrt::Windows::Foundation::Size(window.Bounds().Width, window.Bounds().Height);
m_dpi = currentDisplayInformation.LogicalDpi();
}
}
}
else
{
m_logicalSize = winrt::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(winrt::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()
{
winrt::com_ptr<IDXGIAdapter1> previousDefaultAdapter;
DX::ThrowIfFailed(m_dxgiFactory->EnumAdapters1(0, previousDefaultAdapter.put()));
DXGI_ADAPTER_DESC1 previousDesc;
DX::ThrowIfFailed(previousDefaultAdapter->GetDesc1(&previousDesc));
winrt::com_ptr<IDXGIFactory4> currentFactory;
DX::ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(currentFactory.put())));
winrt::com_ptr<IDXGIAdapter1> currentDefaultAdapter;
DX::ThrowIfFailed(currentFactory->EnumAdapters1(0, currentDefaultAdapter.put()));
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()
{
m_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;
}