431 lines
12 KiB
C++
431 lines
12 KiB
C++
/*
|
|
Copyright 2021 flyinghead
|
|
|
|
This file is part of Flycast.
|
|
|
|
Flycast is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Flycast is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "dx11context.h"
|
|
#ifndef LIBRETRO
|
|
#include "rend/osd.h"
|
|
#ifdef USE_SDL
|
|
#include "sdl/sdl.h"
|
|
#endif
|
|
#include "hw/pvr/Renderer_if.h"
|
|
#include "emulator.h"
|
|
#include "dx11_driver.h"
|
|
#include "imgui/backends/imgui_impl_dx11.h"
|
|
#include <dxgi.h>
|
|
#include <dxgi1_6.h>
|
|
#ifdef TARGET_UWP
|
|
#include <windows.h>
|
|
#include <gamingdeviceinformation.h>
|
|
#endif
|
|
|
|
#include "nowide/stackstring.hpp"
|
|
|
|
DX11Context theDX11Context;
|
|
|
|
bool DX11Context::init(bool keepCurrentWindow)
|
|
{
|
|
NOTICE_LOG(RENDERER, "DX11 Context initializing");
|
|
GraphicsContext::instance = this;
|
|
#ifdef USE_SDL
|
|
if (!keepCurrentWindow && !sdl_recreate_window(0))
|
|
return false;
|
|
#endif
|
|
#ifdef TARGET_UWP
|
|
GAMING_DEVICE_MODEL_INFORMATION info {};
|
|
GetGamingDeviceModelInformation(&info);
|
|
if (info.vendorId == GAMING_DEVICE_VENDOR_ID_MICROSOFT && info.deviceId != GAMING_DEVICE_DEVICE_ID_NONE)
|
|
{
|
|
Windows::Graphics::Display::Core::HdmiDisplayInformation^ dispInfo = Windows::Graphics::Display::Core::HdmiDisplayInformation::GetForCurrentView();
|
|
Windows::Graphics::Display::Core::HdmiDisplayMode^ displayMode = dispInfo->GetCurrentDisplayMode();
|
|
NOTICE_LOG(RENDERER, "HDMI resolution: %d x %d", displayMode->ResolutionWidthInRawPixels, displayMode->ResolutionHeightInRawPixels);
|
|
settings.display.width = displayMode->ResolutionWidthInRawPixels;
|
|
settings.display.height = displayMode->ResolutionHeightInRawPixels;
|
|
settings.display.uiScale = settings.display.width / 1920.0f * 1.4f;
|
|
}
|
|
#endif
|
|
|
|
// Use high performance GPU on Windows 10 (1803 or later)
|
|
ComPtr<IDXGIFactory1> dxgiFactory;
|
|
ComPtr<IDXGIFactory6> dxgiFactory6;
|
|
ComPtr<IDXGIAdapter> dxgiAdapter;
|
|
HRESULT hr;
|
|
hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void **)&dxgiFactory.get());
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
dxgiFactory.as(dxgiFactory6);
|
|
if (dxgiFactory6)
|
|
{
|
|
dxgiFactory6->EnumAdapterByGpuPreference(0, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, __uuidof(IDXGIAdapter), (void **)&dxgiAdapter.get());
|
|
dxgiFactory6.reset();
|
|
}
|
|
}
|
|
dxgiFactory.reset();
|
|
|
|
D3D_FEATURE_LEVEL featureLevels[] =
|
|
{
|
|
D3D_FEATURE_LEVEL_11_1,
|
|
D3D_FEATURE_LEVEL_11_0,
|
|
D3D_FEATURE_LEVEL_10_1,
|
|
D3D_FEATURE_LEVEL_10_0,
|
|
};
|
|
hr = D3D11CreateDevice(
|
|
dxgiAdapter.get(), // High performance GPU, or fallback to use the default adapter.
|
|
dxgiAdapter.get() == nullptr ? D3D_DRIVER_TYPE_HARDWARE : D3D_DRIVER_TYPE_UNKNOWN, // D3D_DRIVER_TYPE_UNKNOWN is required when providing an adapter.
|
|
nullptr,
|
|
D3D11_CREATE_DEVICE_BGRA_SUPPORT, // | D3D11_CREATE_DEVICE_DEBUG,
|
|
featureLevels,
|
|
ARRAYSIZE(featureLevels),
|
|
D3D11_SDK_VERSION,
|
|
&pDevice.get(),
|
|
&featureLevel,
|
|
&pDeviceContext.get());
|
|
if (FAILED(hr)) {
|
|
WARN_LOG(RENDERER, "D3D11 device creation failed: %x", hr);
|
|
return false;
|
|
}
|
|
|
|
ComPtr<IDXGIDevice2> dxgiDevice;
|
|
pDevice.as(dxgiDevice);
|
|
|
|
dxgiAdapter.reset();
|
|
dxgiDevice->GetAdapter(&dxgiAdapter.get());
|
|
DXGI_ADAPTER_DESC desc;
|
|
dxgiAdapter->GetDesc(&desc);
|
|
nowide::stackstring wdesc;
|
|
wdesc.convert(desc.Description);
|
|
adapterDesc = wdesc.get();
|
|
adapterVersion = std::to_string(desc.Revision);
|
|
vendorId = desc.VendorId;
|
|
|
|
dxgiAdapter->GetParent(__uuidof(IDXGIFactory1), (void **)&dxgiFactory.get());
|
|
|
|
ComPtr<IDXGIFactory2> dxgiFactory2;
|
|
dxgiFactory.as(dxgiFactory2);
|
|
|
|
if (dxgiFactory2)
|
|
{
|
|
// DX 11.1
|
|
DXGI_SWAP_CHAIN_DESC1 desc{};
|
|
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
|
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
|
|
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
|
desc.BufferCount = 2;
|
|
desc.SampleDesc.Count = 1;
|
|
desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
|
|
|
|
#ifdef TARGET_UWP
|
|
desc.Width = settings.display.width;
|
|
desc.Height = settings.display.height;
|
|
hr = dxgiFactory2->CreateSwapChainForCoreWindow(pDevice, (IUnknown *)window, &desc, nullptr, &swapchain1.get());
|
|
#else
|
|
hr = dxgiFactory2->CreateSwapChainForHwnd(pDevice, (HWND)window, &desc, nullptr, nullptr, &swapchain1.get());
|
|
#endif
|
|
if (SUCCEEDED(hr))
|
|
swapchain1.as(swapchain);
|
|
}
|
|
else
|
|
{
|
|
// DX 11.0
|
|
swapchain1.reset();
|
|
#ifdef TARGET_UWP
|
|
return false;
|
|
#endif
|
|
DXGI_SWAP_CHAIN_DESC desc{};
|
|
desc.BufferCount = 2;
|
|
desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
desc.BufferDesc.RefreshRate.Numerator = 60;
|
|
desc.BufferDesc.RefreshRate.Denominator = 1;
|
|
desc.OutputWindow = (HWND)window;
|
|
desc.Windowed = TRUE;
|
|
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
|
|
desc.BufferCount = 2;
|
|
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
|
desc.SampleDesc.Count = 1;
|
|
desc.SampleDesc.Quality = 0;
|
|
|
|
hr = dxgiFactory->CreateSwapChain(pDevice, &desc, &swapchain.get());
|
|
}
|
|
if (FAILED(hr)) {
|
|
WARN_LOG(RENDERER, "D3D11 swap chain creation failed: %x", hr);
|
|
pDevice.reset();
|
|
return false;
|
|
}
|
|
|
|
#ifndef TARGET_UWP
|
|
// Prevent DXGI from monitoring our message queue for ALT+Enter
|
|
dxgiFactory->MakeWindowAssociation((HWND)window, DXGI_MWA_NO_WINDOW_CHANGES);
|
|
#endif
|
|
D3D11_FEATURE_DATA_SHADER_CACHE cacheSupport{};
|
|
if (SUCCEEDED(pDevice->CheckFeatureSupport(D3D11_FEATURE_SHADER_CACHE, &cacheSupport, (UINT)sizeof(cacheSupport))))
|
|
{
|
|
_hasShaderCache = cacheSupport.SupportFlags & D3D11_SHADER_CACHE_SUPPORT_AUTOMATIC_DISK_CACHE;
|
|
if (!_hasShaderCache)
|
|
NOTICE_LOG(RENDERER, "No system-provided shader cache");
|
|
}
|
|
|
|
initVideoRouting();
|
|
|
|
imguiDriver = std::unique_ptr<ImGuiDriver>(new DX11Driver(pDevice, pDeviceContext));
|
|
resize();
|
|
shaders.init(pDevice, &D3DCompile);
|
|
overlay.init(pDevice, pDeviceContext, &shaders, &samplers);
|
|
bool success = checkTextureSupport();
|
|
if (!success)
|
|
term();
|
|
return success;
|
|
}
|
|
|
|
void DX11Context::initVideoRouting()
|
|
{
|
|
#ifdef VIDEO_ROUTING
|
|
extern void os_VideoRoutingTermDX();
|
|
extern void os_VideoRoutingInitSpoutDXWithDevice(ID3D11Device* pDevice);
|
|
os_VideoRoutingTermDX();
|
|
if (config::VideoRouting)
|
|
{
|
|
os_VideoRoutingInitSpoutDXWithDevice(pDevice.get());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void DX11Context::term()
|
|
{
|
|
NOTICE_LOG(RENDERER, "DX11 Context terminating");
|
|
GraphicsContext::instance = nullptr;
|
|
overlay.term();
|
|
samplers.term();
|
|
shaders.term();
|
|
imguiDriver.reset();
|
|
renderTargetView.reset();
|
|
swapchain1.reset();
|
|
swapchain.reset();
|
|
if (pDeviceContext)
|
|
{
|
|
pDeviceContext->ClearState();
|
|
pDeviceContext->Flush();
|
|
}
|
|
pDeviceContext.reset();
|
|
pDevice.reset();
|
|
d3dcompiler = nullptr;
|
|
if (d3dcompilerHandle != NULL)
|
|
{
|
|
FreeLibrary(d3dcompilerHandle);
|
|
d3dcompilerHandle = NULL;
|
|
}
|
|
#ifdef VIDEO_ROUTING
|
|
extern void os_VideoRoutingTermDX();
|
|
os_VideoRoutingTermDX();
|
|
#endif
|
|
}
|
|
|
|
void DX11Context::Present()
|
|
{
|
|
if (!frameRendered)
|
|
return;
|
|
frameRendered = false;
|
|
bool swapOnVSync = !settings.input.fastForwardMode && config::VSync;
|
|
HRESULT hr;
|
|
if (!swapchain)
|
|
{
|
|
hr = DXGI_ERROR_DEVICE_RESET;
|
|
}
|
|
else if (swapOnVSync)
|
|
{
|
|
int swapInterval = std::min(4, std::max(1, (int)(settings.display.refreshRate / 60)));
|
|
hr = swapchain->Present(swapInterval, 0);
|
|
}
|
|
else
|
|
{
|
|
hr = swapchain->Present(0, DXGI_PRESENT_DO_NOT_WAIT);
|
|
}
|
|
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
|
|
{
|
|
WARN_LOG(RENDERER, "Present failed: device removed/reset");
|
|
handleDeviceLost();
|
|
}
|
|
else if (hr != DXGI_ERROR_WAS_STILL_DRAWING && FAILED(hr))
|
|
WARN_LOG(RENDERER, "Present failed %x", hr);
|
|
}
|
|
|
|
void DX11Context::EndImGuiFrame()
|
|
{
|
|
if (pDevice && pDeviceContext && renderTargetView)
|
|
{
|
|
if (!overlayOnly)
|
|
{
|
|
pDeviceContext->OMSetRenderTargets(1, &renderTargetView.get(), nullptr);
|
|
const FLOAT black[4] { 0.f, 0.f, 0.f, 1.f };
|
|
pDeviceContext->ClearRenderTargetView(renderTargetView, black);
|
|
if (renderer != nullptr)
|
|
renderer->RenderLastFrame();
|
|
}
|
|
if (overlayOnly)
|
|
{
|
|
if (crosshairsNeeded() || config::FloatVMUs)
|
|
overlay.draw(settings.display.width, settings.display.height, config::FloatVMUs, true);
|
|
}
|
|
else
|
|
{
|
|
overlay.draw(settings.display.width, settings.display.height, true, false);
|
|
}
|
|
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
|
|
}
|
|
frameRendered = true;
|
|
}
|
|
|
|
void DX11Context::resize()
|
|
{
|
|
if (!pDevice)
|
|
return;
|
|
if (swapchain)
|
|
{
|
|
ID3D11RenderTargetView *nullRTV = nullptr;
|
|
pDeviceContext->OMSetRenderTargets(1, &nullRTV, nullptr);
|
|
renderTargetView.reset();
|
|
#ifdef TARGET_UWP
|
|
HRESULT hr = swapchain->ResizeBuffers(2, settings.display.width, settings.display.height, DXGI_FORMAT_R8G8B8A8_UNORM, 0);
|
|
#else
|
|
HRESULT hr = swapchain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);
|
|
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
|
|
{
|
|
handleDeviceLost();
|
|
return;
|
|
}
|
|
#endif
|
|
if (FAILED(hr))
|
|
{
|
|
WARN_LOG(RENDERER, "ResizeBuffers failed: %x", hr);
|
|
return;
|
|
}
|
|
|
|
// Create a render target view
|
|
ComPtr<ID3D11Texture2D> backBuffer;
|
|
hr = swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void **)&backBuffer.get());
|
|
if (FAILED(hr))
|
|
{
|
|
WARN_LOG(RENDERER, "swapChain->GetBuffer() failed: %x", hr);
|
|
return;
|
|
}
|
|
|
|
hr = pDevice->CreateRenderTargetView(backBuffer, nullptr, &renderTargetView.get());
|
|
if (FAILED(hr))
|
|
{
|
|
WARN_LOG(RENDERER, "CreateRenderTargetView failed: %x", hr);
|
|
return;
|
|
}
|
|
pDeviceContext->OMSetRenderTargets(1, &renderTargetView.get(), nullptr);
|
|
|
|
if (swapchain1)
|
|
{
|
|
DXGI_SWAP_CHAIN_DESC1 desc;
|
|
swapchain1->GetDesc1(&desc);
|
|
#ifndef TARGET_UWP
|
|
settings.display.width = desc.Width;
|
|
settings.display.height = desc.Height;
|
|
#endif
|
|
NOTICE_LOG(RENDERER, "Swapchain resized: %d x %d", desc.Width, desc.Height);
|
|
}
|
|
else
|
|
{
|
|
DXGI_SWAP_CHAIN_DESC desc;
|
|
swapchain->GetDesc(&desc);
|
|
settings.display.width = desc.BufferDesc.Width;
|
|
settings.display.height = desc.BufferDesc.Height;
|
|
NOTICE_LOG(RENDERER, "Swapchain resized: %d x %d", desc.BufferDesc.Width, desc.BufferDesc.Height);
|
|
}
|
|
}
|
|
// TODO minimized window
|
|
}
|
|
|
|
void DX11Context::handleDeviceLost()
|
|
{
|
|
if (pDevice)
|
|
{
|
|
HRESULT hr = pDevice->GetDeviceRemovedReason();
|
|
WARN_LOG(RENDERER, "Device removed reason: %x", hr);
|
|
}
|
|
rend_term_renderer();
|
|
term();
|
|
if (init(true))
|
|
{
|
|
rend_init_renderer();
|
|
}
|
|
else
|
|
{
|
|
Renderer* rend_norend();
|
|
renderer = rend_norend();
|
|
renderer->Init();
|
|
}
|
|
}
|
|
|
|
const pD3DCompile DX11Context::getCompiler()
|
|
{
|
|
if (d3dcompiler == nullptr)
|
|
{
|
|
#ifndef TARGET_UWP
|
|
d3dcompilerHandle = LoadLibraryA("d3dcompiler_47.dll");
|
|
if (d3dcompilerHandle == NULL)
|
|
d3dcompilerHandle = LoadLibraryA("d3dcompiler_46.dll");
|
|
if (d3dcompilerHandle == NULL)
|
|
{
|
|
WARN_LOG(RENDERER, "Neither d3dcompiler_47.dll or d3dcompiler_46.dll can be loaded");
|
|
return D3DCompile;
|
|
}
|
|
d3dcompiler = (pD3DCompile)GetProcAddress(d3dcompilerHandle, "D3DCompile");
|
|
#endif
|
|
if (d3dcompiler == nullptr)
|
|
d3dcompiler = D3DCompile;
|
|
}
|
|
return d3dcompiler;
|
|
}
|
|
#endif // !LIBRETRO
|
|
|
|
bool DX11Context::checkTextureSupport()
|
|
{
|
|
const DXGI_FORMAT formats[] = { DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_B4G4R4A4_UNORM, DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_A8_UNORM };
|
|
const char * const fmtNames[] = { "B5G5R5A1", "B4G4R4A4", "B5G6R5", "B8G8R8A8", "A8" };
|
|
const TextureType dcTexTypes[] = { TextureType::_5551, TextureType::_4444, TextureType::_565, TextureType::_8888, TextureType::_8 };
|
|
UINT support;
|
|
for (std::size_t i = 0; i < std::size(formats); i++)
|
|
{
|
|
supportedTexFormats[(int)dcTexTypes[i]] = false;
|
|
pDevice->CheckFormatSupport(formats[i], &support);
|
|
if ((support & (D3D11_FORMAT_SUPPORT_TEXTURE2D | D3D11_FORMAT_SUPPORT_SHADER_SAMPLE)) != (D3D11_FORMAT_SUPPORT_TEXTURE2D | D3D11_FORMAT_SUPPORT_SHADER_SAMPLE))
|
|
{
|
|
if (formats[i] == DXGI_FORMAT_B8G8R8A8_UNORM)
|
|
{
|
|
// Can't do much without this format
|
|
ERROR_LOG(RENDERER, "Fatal: Format %s not supported", fmtNames[i]);
|
|
return false;
|
|
}
|
|
WARN_LOG(RENDERER, "Format %s not supported", fmtNames[i]);
|
|
}
|
|
else
|
|
{
|
|
if ((support & D3D11_FORMAT_SUPPORT_MIP) == 0)
|
|
WARN_LOG(RENDERER, "Format %s doesn't support mipmaps", fmtNames[i]);
|
|
else if ((support & (D3D11_FORMAT_SUPPORT_MIP_AUTOGEN | D3D11_FORMAT_SUPPORT_RENDER_TARGET)) != (D3D11_FORMAT_SUPPORT_MIP_AUTOGEN | D3D11_FORMAT_SUPPORT_RENDER_TARGET))
|
|
WARN_LOG(RENDERER, "Format %s doesn't support mipmap autogen", fmtNames[i]);
|
|
else
|
|
supportedTexFormats[(int)dcTexTypes[i]] = true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|