Rework GPU initialization/teardown

This commit is contained in:
Stenzek 2023-08-06 15:30:54 +10:00
parent 1cf8413ec5
commit 7625385f27
33 changed files with 855 additions and 850 deletions

View File

@ -61,20 +61,20 @@ constexpr T PreviousPow2(T value)
return value - (value >> 1); return value - (value >> 1);
} }
ALWAYS_INLINE static void* AlignedMalloc(size_t size, size_t alignment) ALWAYS_INLINE static void* AlignedMalloc(size_t size, size_t alignment)
{ {
#ifdef _MSC_VER #ifdef _MSC_VER
return _aligned_malloc(size, alignment); return _aligned_malloc(size, alignment);
#else #else
// Unaligned sizes are slow on macOS. // Unaligned sizes are slow on macOS.
#ifdef __APPLE__ #ifdef __APPLE__
if (IsPow2(alignment)) if (IsPow2(alignment))
size = (size + alignment - 1) & ~(alignment - 1); size = (size + alignment - 1) & ~(alignment - 1);
#endif #endif
void* ret = nullptr; void* ret = nullptr;
posix_memalign(&ret, alignment, size); posix_memalign(&ret, alignment, size);
return ret; return ret;
#endif
} }
ALWAYS_INLINE static void AlignedFree(void* ptr) ALWAYS_INLINE static void AlignedFree(void* ptr)
@ -86,5 +86,4 @@ ALWAYS_INLINE static void AlignedFree(void* ptr)
#endif #endif
} }
#endif
} // namespace Common } // namespace Common

View File

@ -159,29 +159,7 @@ public:
float ComputeVerticalFrequency() const; float ComputeVerticalFrequency() const;
float GetDisplayAspectRatio() const; float GetDisplayAspectRatio() const;
#ifdef _WIN32 static std::unique_ptr<GPU> CreateHardwareRenderer();
// gpu_hw_d3d11.cpp
static std::unique_ptr<GPU> CreateHardwareD3D11Renderer();
// gpu_hw_d3d12.cpp
static std::unique_ptr<GPU> CreateHardwareD3D12Renderer();
#endif
#ifdef __APPLE__
static std::unique_ptr<GPU> CreateHardwareMetalRenderer();
#endif
#ifdef WITH_OPENGL
// gpu_hw_opengl.cpp
static std::unique_ptr<GPU> CreateHardwareOpenGLRenderer();
#endif
#ifdef WITH_VULKAN
// gpu_hw_vulkan.cpp
static std::unique_ptr<GPU> CreateHardwareVulkanRenderer();
#endif
// gpu_sw.cpp
static std::unique_ptr<GPU> CreateSoftwareRenderer(); static std::unique_ptr<GPU> CreateSoftwareRenderer();
// Converts window coordinates into horizontal ticks and scanlines. Returns false if out of range. Used for lightguns. // Converts window coordinates into horizontal ticks and scanlines. Returns false if out of range. Used for lightguns.

View File

@ -3,7 +3,6 @@
#include "d3d11_device.h" #include "d3d11_device.h"
#include "../host_settings.h" #include "../host_settings.h"
#include "../settings.h"
#include "../shader_cache_version.h" #include "../shader_cache_version.h"
#include "common/align.h" #include "common/align.h"
@ -11,6 +10,7 @@
#include "common/file_system.h" #include "common/file_system.h"
#include "common/log.h" #include "common/log.h"
#include "common/path.h" #include "common/path.h"
#include "common/rectangle.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "fmt/format.h" #include "fmt/format.h"
@ -48,6 +48,12 @@ static void SetD3DDebugObjectName(ID3D11DeviceChild* obj, const std::string_view
#endif #endif
} }
// TODO: FIXME
namespace Host {
extern bool IsFullscreen();
extern void SetFullscreen(bool enabled);
}
D3D11StreamBuffer::D3D11StreamBuffer() : m_size(0), m_position(0) D3D11StreamBuffer::D3D11StreamBuffer() : m_size(0), m_position(0)
{ {
} }
@ -155,13 +161,8 @@ D3D11Device::D3D11Device() = default;
D3D11Device::~D3D11Device() D3D11Device::~D3D11Device()
{ {
// TODO: Make virtual Destroy() method instead due to order of shit.. // Should all be torn down by now.
DestroyStagingBuffer(); Assert(!m_device);
DestroyResources();
DestroyBuffers();
DestroySurface();
m_context.Reset();
m_device.Reset();
} }
RenderAPI D3D11Device::GetRenderAPI() const RenderAPI D3D11Device::GetRenderAPI() const
@ -321,7 +322,7 @@ void D3D11Device::ResolveTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u3
bool D3D11Device::GetHostRefreshRate(float* refresh_rate) bool D3D11Device::GetHostRefreshRate(float* refresh_rate)
{ {
if (m_swap_chain && IsFullscreen()) if (m_swap_chain && m_is_exclusive_fullscreen)
{ {
DXGI_SWAP_CHAIN_DESC desc; DXGI_SWAP_CHAIN_DESC desc;
if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 && if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 &&
@ -343,10 +344,10 @@ void D3D11Device::SetVSync(bool enabled)
m_vsync_enabled = enabled; m_vsync_enabled = enabled;
} }
bool D3D11Device::CreateDevice(const WindowInfo& wi, bool vsync) bool D3D11Device::CreateDevice(const std::string_view& adapter, bool debug_device)
{ {
UINT create_flags = 0; UINT create_flags = 0;
if (g_settings.gpu_use_debug_device) if (debug_device)
create_flags |= D3D11_CREATE_DEVICE_DEBUG; create_flags |= D3D11_CREATE_DEVICE_DEBUG;
ComPtr<IDXGIFactory> temp_dxgi_factory; ComPtr<IDXGIFactory> temp_dxgi_factory;
@ -358,18 +359,19 @@ bool D3D11Device::CreateDevice(const WindowInfo& wi, bool vsync)
} }
u32 adapter_index; u32 adapter_index;
if (!g_settings.gpu_adapter.empty()) if (!adapter.empty())
{ {
AdapterAndModeList adapter_info(GetAdapterAndModeList(temp_dxgi_factory.Get())); AdapterAndModeList adapter_info(GetAdapterAndModeList(temp_dxgi_factory.Get()));
for (adapter_index = 0; adapter_index < static_cast<u32>(adapter_info.adapter_names.size()); adapter_index++) for (adapter_index = 0; adapter_index < static_cast<u32>(adapter_info.adapter_names.size()); adapter_index++)
{ {
if (g_settings.gpu_adapter == adapter_info.adapter_names[adapter_index]) if (adapter == adapter_info.adapter_names[adapter_index])
break; break;
} }
if (adapter_index == static_cast<u32>(adapter_info.adapter_names.size())) if (adapter_index == static_cast<u32>(adapter_info.adapter_names.size()))
{ {
Log_WarningPrintf("Could not find adapter '%s', using first (%s)", g_settings.gpu_adapter.c_str(), // TODO: Log_Fmt
adapter_info.adapter_names[0].c_str()); Log_WarningPrintf(
fmt::format("Could not find adapter '{}', using first ({})", adapter, adapter_info.adapter_names[0]).c_str());
adapter_index = 0; adapter_index = 0;
} }
} }
@ -408,7 +410,7 @@ bool D3D11Device::CreateDevice(const WindowInfo& wi, bool vsync)
return false; return false;
} }
if (g_settings.gpu_use_debug_device && IsDebuggerPresent()) if (debug_device && IsDebuggerPresent())
{ {
ComPtr<ID3D11InfoQueue> info; ComPtr<ID3D11InfoQueue> info;
hr = m_device.As(&info); hr = m_device.As(&info);
@ -420,7 +422,7 @@ bool D3D11Device::CreateDevice(const WindowInfo& wi, bool vsync)
} }
#ifdef _DEBUG #ifdef _DEBUG
if (g_settings.gpu_use_debug_device) if (debug_device)
m_context.As(&m_annotation); m_context.As(&m_annotation);
#endif #endif
@ -452,27 +454,21 @@ bool D3D11Device::CreateDevice(const WindowInfo& wi, bool vsync)
SetFeatures(); SetFeatures();
m_window_info = wi; if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain())
m_vsync_enabled = vsync; return false;
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr)) if (!CreateBuffers())
{
m_window_info = {};
return false; return false;
}
return true; return true;
} }
bool D3D11Device::SetupDevice() void D3D11Device::DestroyDevice()
{ {
if (!GPUDevice::SetupDevice()) DestroyStagingBuffer();
return false; DestroyBuffers();
m_context.Reset();
if (!CreateBuffers() || !CreateResources()) m_device.Reset();
return false;
return true;
} }
void D3D11Device::SetFeatures() void D3D11Device::SetFeatures()
@ -512,58 +508,160 @@ void D3D11Device::SetFeatures()
} }
} }
bool D3D11Device::MakeCurrent() bool D3D11Device::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width,
u32 height, float refresh_rate, DXGI_FORMAT format,
DXGI_MODE_DESC* fullscreen_mode, IDXGIOutput** output)
{ {
return true; // We need to find which monitor the window is located on.
} const Common::Rectangle<s32> client_rc_vec(window_rect.left, window_rect.top, window_rect.right, window_rect.bottom);
bool D3D11Device::DoneCurrent() // The window might be on a different adapter to which we are rendering.. so we have to enumerate them all.
{
return true;
}
bool D3D11Device::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
{
HRESULT hr; HRESULT hr;
ComPtr<IDXGIOutput> first_output, intersecting_output;
for (u32 adapter_index = 0; !intersecting_output; adapter_index++)
{
ComPtr<IDXGIAdapter1> adapter;
hr = factory->EnumAdapters1(adapter_index, adapter.GetAddressOf());
if (hr == DXGI_ERROR_NOT_FOUND)
break;
else if (FAILED(hr))
continue;
for (u32 output_index = 0;; output_index++)
{
ComPtr<IDXGIOutput> this_output;
DXGI_OUTPUT_DESC output_desc;
hr = adapter->EnumOutputs(output_index, this_output.GetAddressOf());
if (hr == DXGI_ERROR_NOT_FOUND)
break;
else if (FAILED(hr) || FAILED(this_output->GetDesc(&output_desc)))
continue;
const Common::Rectangle<s32> output_rc(output_desc.DesktopCoordinates.left, output_desc.DesktopCoordinates.top,
output_desc.DesktopCoordinates.right,
output_desc.DesktopCoordinates.bottom);
if (!client_rc_vec.Intersects(output_rc))
{
intersecting_output = std::move(this_output);
break;
}
// Fallback to the first monitor.
if (!first_output)
first_output = std::move(this_output);
}
}
if (!intersecting_output)
{
if (!first_output)
{
Log_ErrorPrintf("No DXGI output found. Can't use exclusive fullscreen.");
return false;
}
Log_WarningPrint("No DXGI output found for window, using first.");
intersecting_output = std::move(first_output);
}
DXGI_MODE_DESC request_mode = {};
request_mode.Width = width;
request_mode.Height = height;
request_mode.Format = format;
request_mode.RefreshRate.Numerator = static_cast<UINT>(std::floor(refresh_rate * 1000.0f));
request_mode.RefreshRate.Denominator = 1000u;
if (FAILED(hr = intersecting_output->FindClosestMatchingMode(&request_mode, fullscreen_mode, nullptr)) ||
request_mode.Format != format)
{
Log_ErrorPrintf("Failed to find closest matching mode, hr=%08X", hr);
return false;
}
*output = intersecting_output.Get();
intersecting_output->AddRef();
return true;
}
bool D3D11Device::CreateSwapChain()
{
constexpr DXGI_FORMAT swap_chain_format = DXGI_FORMAT_R8G8B8A8_UNORM;
if (m_window_info.type != WindowInfo::Type::Win32) if (m_window_info.type != WindowInfo::Type::Win32)
return false; return false;
m_using_flip_model_swap_chain = fullscreen_mode || !Host::GetBoolSettingValue("Display", "UseBlitSwapChain", false);
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle); const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
RECT client_rc{}; RECT client_rc{};
GetClientRect(window_hwnd, &client_rc); GetClientRect(window_hwnd, &client_rc);
const u32 width = static_cast<u32>(client_rc.right - client_rc.left);
const u32 height = static_cast<u32>(client_rc.bottom - client_rc.top);
DXGI_SWAP_CHAIN_DESC swap_chain_desc = {}; DXGI_MODE_DESC fullscreen_mode = {};
swap_chain_desc.BufferDesc.Width = width; ComPtr<IDXGIOutput> fullscreen_output;
swap_chain_desc.BufferDesc.Height = height; if (Host::IsFullscreen())
swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; {
u32 fullscreen_width, fullscreen_height;
float fullscreen_refresh_rate;
m_is_exclusive_fullscreen =
GetRequestedExclusiveFullscreenMode(&fullscreen_width, &fullscreen_height, &fullscreen_refresh_rate) &&
GetRequestedExclusiveFullscreenModeDesc(m_dxgi_factory.Get(), client_rc, fullscreen_width, fullscreen_height,
fullscreen_refresh_rate, swap_chain_format, &fullscreen_mode,
fullscreen_output.GetAddressOf());
}
else
{
m_is_exclusive_fullscreen = false;
}
m_using_flip_model_swap_chain =
!Host::GetBoolSettingValue("Display", "UseBlitSwapChain", false) || m_is_exclusive_fullscreen;
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
swap_chain_desc.Width = static_cast<u32>(client_rc.right - client_rc.left);
swap_chain_desc.Height = static_cast<u32>(client_rc.bottom - client_rc.top);
swap_chain_desc.Format = swap_chain_format;
swap_chain_desc.SampleDesc.Count = 1; swap_chain_desc.SampleDesc.Count = 1;
swap_chain_desc.BufferCount = 2; swap_chain_desc.BufferCount = 3;
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.OutputWindow = window_hwnd;
swap_chain_desc.Windowed = TRUE;
swap_chain_desc.SwapEffect = m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD; swap_chain_desc.SwapEffect = m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD;
m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain && !fullscreen_mode); m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain && !m_is_exclusive_fullscreen);
if (m_using_allow_tearing) if (m_using_allow_tearing)
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
if (fullscreen_mode) HRESULT hr = S_OK;
if (m_is_exclusive_fullscreen)
{ {
swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc;
swap_chain_desc.Windowed = FALSE; DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
swap_chain_desc.BufferDesc = *fullscreen_mode;
fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
fs_sd_desc.Width = fullscreen_mode.Width;
fs_sd_desc.Height = fullscreen_mode.Height;
fs_desc.RefreshRate = fullscreen_mode.RefreshRate;
fs_desc.ScanlineOrdering = fullscreen_mode.ScanlineOrdering;
fs_desc.Scaling = fullscreen_mode.Scaling;
fs_desc.Windowed = FALSE;
Log_VerbosePrintf("Creating a %dx%d exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height);
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &fs_sd_desc, &fs_desc,
fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf());
if (FAILED(hr))
{
Log_WarningPrintf("Failed to create fullscreen swap chain, trying windowed.");
m_is_exclusive_fullscreen = false;
m_using_allow_tearing = m_allow_tearing_supported && m_using_flip_model_swap_chain;
}
} }
Log_InfoPrintf("Creating a %dx%d %s %s swap chain", swap_chain_desc.BufferDesc.Width, if (!m_is_exclusive_fullscreen)
swap_chain_desc.BufferDesc.Height, m_using_flip_model_swap_chain ? "flip-discard" : "discard", {
swap_chain_desc.Windowed ? "windowed" : "full-screen"); Log_VerbosePrintf("Creating a %dx%d %s windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height,
m_using_flip_model_swap_chain ? "flip-discard" : "discard");
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &swap_chain_desc, nullptr, nullptr,
m_swap_chain.ReleaseAndGetAddressOf());
}
hr = m_dxgi_factory->CreateSwapChain(m_device.Get(), &swap_chain_desc, m_swap_chain.GetAddressOf());
if (FAILED(hr) && m_using_flip_model_swap_chain) if (FAILED(hr) && m_using_flip_model_swap_chain)
{ {
Log_WarningPrintf("Failed to create a flip-discard swap chain, trying discard."); Log_WarningPrintf("Failed to create a flip-discard swap chain, trying discard.");
@ -572,24 +670,29 @@ bool D3D11Device::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
m_using_flip_model_swap_chain = false; m_using_flip_model_swap_chain = false;
m_using_allow_tearing = false; m_using_allow_tearing = false;
hr = m_dxgi_factory->CreateSwapChain(m_device.Get(), &swap_chain_desc, m_swap_chain.GetAddressOf()); hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &swap_chain_desc, nullptr, nullptr,
m_swap_chain.ReleaseAndGetAddressOf());
if (FAILED(hr)) if (FAILED(hr))
{ {
Log_ErrorPrintf("CreateSwapChain failed: 0x%08X", hr); Log_ErrorPrintf("CreateSwapChainForHwnd failed: 0x%08X", hr);
return false; return false;
} }
} }
ComPtr<IDXGIFactory> dxgi_factory; hr = m_dxgi_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
hr = m_swap_chain->GetParent(IID_PPV_ARGS(dxgi_factory.GetAddressOf())); if (FAILED(hr))
if (SUCCEEDED(hr)) Log_WarningPrintf("MakeWindowAssociation() to disable ALT+ENTER failed");
if (!CreateSwapChainRTV())
{ {
hr = dxgi_factory->MakeWindowAssociation(swap_chain_desc.OutputWindow, DXGI_MWA_NO_WINDOW_CHANGES); DestroySwapChain();
if (FAILED(hr)) return false;
Log_WarningPrintf("MakeWindowAssociation() to disable ALT+ENTER failed");
} }
return CreateSwapChainRTV(); // Render a frame as soon as possible to clear out whatever was previously being displayed.
m_context->ClearRenderTargetView(m_swap_chain_rtv.Get(), s_clear_color.data());
m_swap_chain->Present(0, m_using_allow_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0);
return true;
} }
bool D3D11Device::CreateSwapChainRTV() bool D3D11Device::CreateSwapChainRTV()
@ -607,16 +710,17 @@ bool D3D11Device::CreateSwapChainRTV()
CD3D11_RENDER_TARGET_VIEW_DESC rtv_desc(D3D11_RTV_DIMENSION_TEXTURE2D, backbuffer_desc.Format, 0, 0, CD3D11_RENDER_TARGET_VIEW_DESC rtv_desc(D3D11_RTV_DIMENSION_TEXTURE2D, backbuffer_desc.Format, 0, 0,
backbuffer_desc.ArraySize); backbuffer_desc.ArraySize);
hr = m_device->CreateRenderTargetView(backbuffer.Get(), &rtv_desc, m_swap_chain_rtv.GetAddressOf()); hr = m_device->CreateRenderTargetView(backbuffer.Get(), &rtv_desc, m_swap_chain_rtv.ReleaseAndGetAddressOf());
if (FAILED(hr)) if (FAILED(hr))
{ {
Log_ErrorPrintf("CreateRenderTargetView for swap chain failed: 0x%08X", hr); Log_ErrorPrintf("CreateRenderTargetView for swap chain failed: 0x%08X", hr);
m_swap_chain_rtv.Reset();
return false; return false;
} }
m_window_info.surface_width = backbuffer_desc.Width; m_window_info.surface_width = backbuffer_desc.Width;
m_window_info.surface_height = backbuffer_desc.Height; m_window_info.surface_height = backbuffer_desc.Height;
Log_InfoPrintf("Swap chain buffer size: %ux%u", m_window_info.surface_width, m_window_info.surface_height); Log_VerbosePrintf("Swap chain buffer size: %ux%u", m_window_info.surface_width, m_window_info.surface_height);
if (m_window_info.type == WindowInfo::Type::Win32) if (m_window_info.type == WindowInfo::Type::Win32)
{ {
@ -637,22 +741,41 @@ bool D3D11Device::CreateSwapChainRTV()
return true; return true;
} }
bool D3D11Device::ChangeWindow(const WindowInfo& new_wi) void D3D11Device::DestroySwapChain()
{ {
DestroySurface(); if (!m_swap_chain)
return;
m_window_info = new_wi; m_swap_chain_rtv.Reset();
return CreateSwapChain(nullptr);
// switch out of fullscreen before destroying
BOOL is_fullscreen;
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen)
m_swap_chain->SetFullscreenState(FALSE, nullptr);
m_swap_chain.Reset();
m_is_exclusive_fullscreen = false;
}
bool D3D11Device::UpdateWindow()
{
DestroySwapChain();
if (!AcquireWindow(false))
return false;
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain())
{
Log_ErrorPrintf("Failed to create swap chain on updated window");
return false;
}
return true;
} }
void D3D11Device::DestroySurface() void D3D11Device::DestroySurface()
{ {
m_window_info.SetSurfaceless(); DestroySwapChain();
if (IsFullscreen())
SetFullscreen(false, 0, 0, 0.0f);
m_swap_chain_rtv.Reset();
m_swap_chain.Reset();
} }
std::string D3D11Device::GetShaderCacheBaseName(const std::string_view& type, bool debug) const std::string D3D11Device::GetShaderCacheBaseName(const std::string_view& type, bool debug) const
@ -670,11 +793,19 @@ std::string D3D11Device::GetShaderCacheBaseName(const std::string_view& type, bo
return fmt::format("d3d_{}_{}{}", type, flname, debug ? "_debug" : ""); return fmt::format("d3d_{}_{}{}", type, flname, debug ? "_debug" : "");
} }
void D3D11Device::ResizeWindow(s32 new_window_width, s32 new_window_height) void D3D11Device::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
{ {
if (!m_swap_chain) if (!m_swap_chain || m_is_exclusive_fullscreen)
return; return;
m_window_info.surface_scale = new_window_scale;
if (m_window_info.surface_width == static_cast<u32>(new_window_width) &&
m_window_info.surface_height == static_cast<u32>(new_window_height))
{
return;
}
m_swap_chain_rtv.Reset(); m_swap_chain_rtv.Reset();
HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN,
@ -686,79 +817,11 @@ void D3D11Device::ResizeWindow(s32 new_window_width, s32 new_window_height)
Panic("Failed to recreate swap chain RTV after resize"); Panic("Failed to recreate swap chain RTV after resize");
} }
bool D3D11Device::SupportsFullscreen() const bool D3D11Device::SupportsExclusiveFullscreen() const
{ {
return true; return true;
} }
bool D3D11Device::IsFullscreen()
{
BOOL is_fullscreen = FALSE;
return (m_swap_chain && SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen);
}
bool D3D11Device::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
{
if (!m_swap_chain)
return false;
BOOL is_fullscreen = FALSE;
HRESULT hr = m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr);
if (!fullscreen)
{
// leaving fullscreen
if (is_fullscreen)
return SUCCEEDED(m_swap_chain->SetFullscreenState(FALSE, nullptr));
else
return true;
}
IDXGIOutput* output;
if (FAILED(hr = m_swap_chain->GetContainingOutput(&output)))
return false;
DXGI_SWAP_CHAIN_DESC current_desc;
hr = m_swap_chain->GetDesc(&current_desc);
if (FAILED(hr))
return false;
DXGI_MODE_DESC new_mode = current_desc.BufferDesc;
new_mode.Width = width;
new_mode.Height = height;
new_mode.RefreshRate.Numerator = static_cast<UINT>(std::floor(refresh_rate * 1000.0f));
new_mode.RefreshRate.Denominator = 1000u;
DXGI_MODE_DESC closest_mode;
if (FAILED(hr = output->FindClosestMatchingMode(&new_mode, &closest_mode, nullptr)) ||
new_mode.Format != current_desc.BufferDesc.Format)
{
Log_ErrorPrintf("Failed to find closest matching mode, hr=%08X", hr);
return false;
}
if (new_mode.Width == current_desc.BufferDesc.Width && new_mode.Height == current_desc.BufferDesc.Height &&
new_mode.RefreshRate.Numerator == current_desc.BufferDesc.RefreshRate.Numerator &&
new_mode.RefreshRate.Denominator == current_desc.BufferDesc.RefreshRate.Denominator)
{
Log_InfoPrintf("Fullscreen mode already set");
return true;
}
m_swap_chain_rtv.Reset();
m_swap_chain.Reset();
if (!CreateSwapChain(&closest_mode))
{
Log_ErrorPrintf("Failed to create a fullscreen swap chain");
if (!CreateSwapChain(nullptr))
Panic("Failed to recreate windowed swap chain");
return false;
}
return true;
}
bool D3D11Device::CreateBuffers() bool D3D11Device::CreateBuffers()
{ {
if (!m_vertex_buffer.Create(m_device.Get(), D3D11_BIND_VERTEX_BUFFER, VERTEX_BUFFER_SIZE) || if (!m_vertex_buffer.Create(m_device.Get(), D3D11_BIND_VERTEX_BUFFER, VERTEX_BUFFER_SIZE) ||
@ -784,6 +847,16 @@ bool D3D11Device::BeginPresent(bool skip_present)
if (skip_present || !m_swap_chain) if (skip_present || !m_swap_chain)
return false; return false;
// Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode.
// This might get called repeatedly if it takes a while to switch back, that's the host's problem.
BOOL is_fullscreen;
if (m_is_exclusive_fullscreen &&
(FAILED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
{
Host::SetFullscreen(false);
return false;
}
// When using vsync, the time here seems to include the time for the buffer to become available. // When using vsync, the time here seems to include the time for the buffer to become available.
// This blows our our GPU usage number considerably, so read the timestamp before the final blit // This blows our our GPU usage number considerably, so read the timestamp before the final blit
// in this configuration. It does reduce accuracy a little, but better than seeing 100% all of // in this configuration. It does reduce accuracy a little, but better than seeing 100% all of
@ -791,7 +864,7 @@ bool D3D11Device::BeginPresent(bool skip_present)
if (m_vsync_enabled && m_gpu_timing_enabled) if (m_vsync_enabled && m_gpu_timing_enabled)
PopTimestampQuery(); PopTimestampQuery();
static constexpr float clear_color[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; static constexpr float clear_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
m_context->ClearRenderTargetView(m_swap_chain_rtv.Get(), clear_color); m_context->ClearRenderTargetView(m_swap_chain_rtv.Get(), clear_color);
m_context->OMSetRenderTargets(1, m_swap_chain_rtv.GetAddressOf(), nullptr); m_context->OMSetRenderTargets(1, m_swap_chain_rtv.GetAddressOf(), nullptr);
m_current_framebuffer = nullptr; m_current_framebuffer = nullptr;
@ -1367,7 +1440,7 @@ std::unique_ptr<GPUShader> D3D11Device::CreateShaderFromSource(GPUShaderStage st
Log_ErrorPrintf("Failed to compile '%s':\n%s", target, error_string.c_str()); Log_ErrorPrintf("Failed to compile '%s':\n%s", target, error_string.c_str());
auto fp = FileSystem::OpenManagedCFile( auto fp = FileSystem::OpenManagedCFile(
Path::Combine(EmuFolders::DataRoot, fmt::format("bad_shader_{}.txt", s_next_bad_shader_id++)).c_str(), "wb"); GetShaderDumpPath(fmt::format("bad_shader_{}.txt", s_next_bad_shader_id++)).c_str(), "wb");
if (fp) if (fp)
{ {
std::fwrite(source.data(), source.size(), 1, fp.get()); std::fwrite(source.data(), source.size(), 1, fp.get());
@ -1734,7 +1807,7 @@ bool D3D11Texture::Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u32
u32 level /*= 0*/) u32 level /*= 0*/)
{ {
if (!m_dynamic || (x + width) > GetMipWidth(level) || (y + height) > GetMipHeight(level) || layer > m_layers || if (!m_dynamic || (x + width) > GetMipWidth(level) || (y + height) > GetMipHeight(level) || layer > m_layers ||
level > m_levels) level > m_levels)
{ {
return false; return false;
} }

View File

@ -1,15 +1,17 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once #pragma once
#include "gpu_device.h"
#include "postprocessing_chain.h"
#include "common/timer.h" #include "common/timer.h"
#include "common/window_info.h" #include "common/window_info.h"
#include "common/windows_headers.h" #include "common/windows_headers.h"
#include "gpu_device.h"
#include "postprocessing_chain.h"
#include <d3d11_1.h> #include <d3d11_1.h>
#include <dxgi.h> #include <dxgi1_5.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include <string_view> #include <string_view>
@ -258,28 +260,25 @@ public:
template<typename T> template<typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>; using ComPtr = Microsoft::WRL::ComPtr<T>;
D3D11Device();
~D3D11Device();
ALWAYS_INLINE static D3D11Device& GetInstance() { return *static_cast<D3D11Device*>(g_host_display.get()); } ALWAYS_INLINE static D3D11Device& GetInstance() { return *static_cast<D3D11Device*>(g_host_display.get()); }
ALWAYS_INLINE static ID3D11Device* GetD3DDevice() { return GetInstance().m_device.Get(); } ALWAYS_INLINE static ID3D11Device* GetD3DDevice() { return GetInstance().m_device.Get(); }
ALWAYS_INLINE static ID3D11DeviceContext* GetD3DContext() { return GetInstance().m_context.Get(); } ALWAYS_INLINE static ID3D11DeviceContext* GetD3DContext() { return GetInstance().m_context.Get(); }
D3D11Device(); // returns the fullscreen mode to use for the specified dimensions
~D3D11Device(); static bool GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width,
u32 height, float refresh_rate, DXGI_FORMAT format,
DXGI_MODE_DESC* fullscreen_mode, IDXGIOutput** output);
RenderAPI GetRenderAPI() const override; RenderAPI GetRenderAPI() const override;
bool HasSurface() const override; bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi, bool vsync) override; bool UpdateWindow() override;
bool SetupDevice() override; void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
bool SupportsExclusiveFullscreen() const override;
bool MakeCurrent() override;
bool DoneCurrent() override;
bool ChangeWindow(const WindowInfo& new_wi) override;
void ResizeWindow(s32 new_window_width, s32 new_window_height) override;
bool SupportsFullscreen() const override;
bool IsFullscreen() override;
bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
AdapterAndModeList GetAdapterAndModeList() override; AdapterAndModeList GetAdapterAndModeList() override;
void DestroySurface() override; void DestroySurface() override;
@ -346,6 +345,10 @@ public:
static AdapterAndModeList StaticGetAdapterAndModeList(); static AdapterAndModeList StaticGetAdapterAndModeList();
protected:
bool CreateDevice(const std::string_view& adapter, bool debug_device) override;
void DestroyDevice() override;
private: private:
using RasterizationStateMap = std::unordered_map<u8, ComPtr<ID3D11RasterizerState>>; using RasterizationStateMap = std::unordered_map<u8, ComPtr<ID3D11RasterizerState>>;
using DepthStateMap = std::unordered_map<u8, ComPtr<ID3D11DepthStencilState>>; using DepthStateMap = std::unordered_map<u8, ComPtr<ID3D11DepthStencilState>>;
@ -368,8 +371,9 @@ private:
bool CheckStagingBufferSize(u32 width, u32 height, DXGI_FORMAT format); bool CheckStagingBufferSize(u32 width, u32 height, DXGI_FORMAT format);
void DestroyStagingBuffer(); void DestroyStagingBuffer();
bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode); bool CreateSwapChain();
bool CreateSwapChainRTV(); bool CreateSwapChainRTV();
void DestroySwapChain();
bool CreateBuffers(); bool CreateBuffers();
void DestroyBuffers(); void DestroyBuffers();
@ -388,8 +392,8 @@ private:
ComPtr<ID3D11DeviceContext1> m_context; ComPtr<ID3D11DeviceContext1> m_context;
ComPtr<ID3DUserDefinedAnnotation> m_annotation; ComPtr<ID3DUserDefinedAnnotation> m_annotation;
ComPtr<IDXGIFactory> m_dxgi_factory; ComPtr<IDXGIFactory5> m_dxgi_factory;
ComPtr<IDXGISwapChain> m_swap_chain; ComPtr<IDXGISwapChain1> m_swap_chain;
ComPtr<ID3D11RenderTargetView> m_swap_chain_rtv; ComPtr<ID3D11RenderTargetView> m_swap_chain_rtv;
RasterizationStateMap m_rasterization_states; RasterizationStateMap m_rasterization_states;
@ -405,6 +409,7 @@ private:
bool m_allow_tearing_supported = false; bool m_allow_tearing_supported = false;
bool m_using_flip_model_swap_chain = true; bool m_using_flip_model_swap_chain = true;
bool m_using_allow_tearing = false; bool m_using_allow_tearing = false;
bool m_is_exclusive_fullscreen = false;
D3D11StreamBuffer m_vertex_buffer; D3D11StreamBuffer m_vertex_buffer;
D3D11StreamBuffer m_index_buffer; D3D11StreamBuffer m_index_buffer;

View File

@ -28,7 +28,7 @@ D3D12GPUDevice::~D3D12GPUDevice()
// DestroyRenderSurface() will exec the command list. // DestroyRenderSurface() will exec the command list.
DestroySurface(); DestroySurface();
DestroyResources(); //DestroyResources();
g_d3d12_context->Destroy(); g_d3d12_context->Destroy();
} }
@ -108,6 +108,7 @@ bool D3D12GPUDevice::SupportsTextureFormat(GPUTexture::Format format) const
bool D3D12GPUDevice::GetHostRefreshRate(float* refresh_rate) bool D3D12GPUDevice::GetHostRefreshRate(float* refresh_rate)
{ {
#if 0
if (m_swap_chain && IsFullscreen()) if (m_swap_chain && IsFullscreen())
{ {
DXGI_SWAP_CHAIN_DESC desc; DXGI_SWAP_CHAIN_DESC desc;
@ -121,6 +122,7 @@ bool D3D12GPUDevice::GetHostRefreshRate(float* refresh_rate)
return true; return true;
} }
} }
#endif
return GPUDevice::GetHostRefreshRate(refresh_rate); return GPUDevice::GetHostRefreshRate(refresh_rate);
} }
@ -130,6 +132,7 @@ void D3D12GPUDevice::SetVSync(bool enabled)
m_vsync_enabled = enabled; m_vsync_enabled = enabled;
} }
#if 0
bool D3D12GPUDevice::CreateDevice(const WindowInfo& wi, bool vsync) bool D3D12GPUDevice::CreateDevice(const WindowInfo& wi, bool vsync)
{ {
ComPtr<IDXGIFactory> temp_dxgi_factory; ComPtr<IDXGIFactory> temp_dxgi_factory;
@ -214,6 +217,7 @@ bool D3D12GPUDevice::DoneCurrent()
{ {
return true; return true;
} }
#endif
bool D3D12GPUDevice::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode) bool D3D12GPUDevice::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
{ {
@ -327,6 +331,8 @@ void D3D12GPUDevice::DestroySwapChainRTVs()
m_current_swap_chain_buffer = 0; m_current_swap_chain_buffer = 0;
} }
#if 0
bool D3D12GPUDevice::ChangeWindow(const WindowInfo& new_wi) bool D3D12GPUDevice::ChangeWindow(const WindowInfo& new_wi)
{ {
DestroySurface(); DestroySurface();
@ -334,6 +340,7 @@ bool D3D12GPUDevice::ChangeWindow(const WindowInfo& new_wi)
m_window_info = new_wi; m_window_info = new_wi;
return CreateSwapChain(nullptr); return CreateSwapChain(nullptr);
} }
#endif
void D3D12GPUDevice::DestroySurface() void D3D12GPUDevice::DestroySurface()
{ {
@ -342,12 +349,15 @@ void D3D12GPUDevice::DestroySurface()
// For some reason if we don't execute the command list here, the swap chain is in use.. not sure where. // For some reason if we don't execute the command list here, the swap chain is in use.. not sure where.
g_d3d12_context->ExecuteCommandList(true); g_d3d12_context->ExecuteCommandList(true);
#if 0
if (IsFullscreen()) if (IsFullscreen())
SetFullscreen(false, 0, 0, 0.0f); SetFullscreen(false, 0, 0, 0.0f);
#endif
DestroySwapChainRTVs(); DestroySwapChainRTVs();
m_swap_chain.Reset(); m_swap_chain.Reset();
} }
#if 0
void D3D12GPUDevice::ResizeWindow(s32 new_window_width, s32 new_window_height) void D3D12GPUDevice::ResizeWindow(s32 new_window_width, s32 new_window_height)
{ {
@ -441,12 +451,14 @@ bool D3D12GPUDevice::SetFullscreen(bool fullscreen, u32 width, u32 height, float
return true; return true;
} }
#endif
GPUDevice::AdapterAndModeList D3D12GPUDevice::GetAdapterAndModeList() GPUDevice::AdapterAndModeList D3D12GPUDevice::GetAdapterAndModeList()
{ {
return GetAdapterAndModeList(m_dxgi_factory.Get()); return GetAdapterAndModeList(m_dxgi_factory.Get());
} }
#if 0
bool D3D12GPUDevice::CreateResources() bool D3D12GPUDevice::CreateResources()
{ {
D3D12::RootSignatureBuilder rsbuilder; D3D12::RootSignatureBuilder rsbuilder;
@ -550,6 +562,7 @@ void D3D12GPUDevice::DestroyResources()
m_display_pipeline.Reset(); m_display_pipeline.Reset();
m_display_root_signature.Reset(); m_display_root_signature.Reset();
} }
#endif
#if 0 #if 0
bool D3D12GPUDevice::CreateImGuiContext() bool D3D12GPUDevice::CreateImGuiContext()
@ -619,6 +632,8 @@ float D3D12GPUDevice::GetAndResetAccumulatedGPUTime()
return g_d3d12_context->GetAndResetAccumulatedGPUTime(); return g_d3d12_context->GetAndResetAccumulatedGPUTime();
} }
#if 0
void D3D12GPUDevice::RenderImGui(ID3D12GraphicsCommandList* cmdlist) void D3D12GPUDevice::RenderImGui(ID3D12GraphicsCommandList* cmdlist)
{ {
ImGui::Render(); ImGui::Render();
@ -702,6 +717,8 @@ void D3D12GPUDevice::RenderSoftwareCursor(ID3D12GraphicsCommandList* cmdlist, s3
cmdlist->DrawInstanced(3, 1, 0, 0); cmdlist->DrawInstanced(3, 1, 0, 0);
} }
#endif
GPUDevice::AdapterAndModeList D3D12GPUDevice::StaticGetAdapterAndModeList() GPUDevice::AdapterAndModeList D3D12GPUDevice::StaticGetAdapterAndModeList()
{ {
ComPtr<IDXGIFactory> dxgi_factory; ComPtr<IDXGIFactory> dxgi_factory;

View File

@ -32,17 +32,17 @@ public:
bool HasSurface() const override; bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi, bool vsync) override; //bool CreateDevice(const WindowInfo& wi, bool vsync) override;
bool SetupDevice() override; //bool SetupDevice() override;
bool MakeCurrent() override; //bool MakeCurrent() override;
bool DoneCurrent() override; //bool DoneCurrent() override;
bool ChangeWindow(const WindowInfo& new_wi) override; //bool ChangeWindow(const WindowInfo& new_wi) override;
void ResizeWindow(s32 new_window_width, s32 new_window_height) override; //void ResizeWindow(s32 new_window_width, s32 new_window_height) override;
bool SupportsFullscreen() const override; //bool SupportsFullscreen() const override;
bool IsFullscreen() override; //bool IsFullscreen() override;
bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; //bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
AdapterAndModeList GetAdapterAndModeList() override; AdapterAndModeList GetAdapterAndModeList() override;
void DestroySurface() override; void DestroySurface() override;
@ -69,13 +69,14 @@ public:
protected: protected:
static AdapterAndModeList GetAdapterAndModeList(IDXGIFactory* dxgi_factory); static AdapterAndModeList GetAdapterAndModeList(IDXGIFactory* dxgi_factory);
virtual bool CreateResources() override; //virtual bool CreateResources() override;
virtual void DestroyResources() override; //virtual void DestroyResources() override;
bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode); bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode);
bool CreateSwapChainRTV(); bool CreateSwapChainRTV();
void DestroySwapChainRTVs(); void DestroySwapChainRTVs();
#if 0
void RenderDisplay(ID3D12GraphicsCommandList* cmdlist, D3D12::Texture* swap_chain_buf); void RenderDisplay(ID3D12GraphicsCommandList* cmdlist, D3D12::Texture* swap_chain_buf);
void RenderSoftwareCursor(ID3D12GraphicsCommandList* cmdlist); void RenderSoftwareCursor(ID3D12GraphicsCommandList* cmdlist);
void RenderImGui(ID3D12GraphicsCommandList* cmdlist); void RenderImGui(ID3D12GraphicsCommandList* cmdlist);
@ -85,6 +86,7 @@ protected:
s32 texture_view_height, bool linear_filter); s32 texture_view_height, bool linear_filter);
void RenderSoftwareCursor(ID3D12GraphicsCommandList* cmdlist, s32 left, s32 top, s32 width, s32 height, void RenderSoftwareCursor(ID3D12GraphicsCommandList* cmdlist, s32 left, s32 top, s32 width, s32 height,
GPUTexture* texture_handle); GPUTexture* texture_handle);
#endif
ComPtr<IDXGIFactory> m_dxgi_factory; ComPtr<IDXGIFactory> m_dxgi_factory;
ComPtr<IDXGISwapChain> m_swap_chain; ComPtr<IDXGISwapChain> m_swap_chain;

View File

@ -2,8 +2,10 @@
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "gpu_device.h" #include "gpu_device.h"
#include "../host_settings.h"
#include "../settings.h" #include "../settings.h"
#include "../shadergen.h" #include "../shadergen.h"
#include "../system.h"
#include "postprocessing_chain.h" #include "postprocessing_chain.h"
#include "common/align.h" #include "common/align.h"
@ -210,21 +212,95 @@ RenderAPI GPUDevice::GetPreferredAPI()
#endif #endif
} }
bool GPUDevice::SetupDevice() const char* GPUDevice::RenderAPIToString(RenderAPI api)
{ {
// TODO: option to disable shader cache // TODO: Combine ES
if (true) switch (api)
{ {
const std::string basename = GetShaderCacheBaseName("shaders", g_settings.gpu_use_debug_device); // clang-format off
const std::string filename = Path::Combine(EmuFolders::Cache, basename); #define CASE(x) case RenderAPI::x: return #x
if (!m_shader_cache.Open(filename.c_str())) CASE(None);
Log_WarningPrintf("Failed to open shader cache."); CASE(D3D11);
CASE(D3D12);
CASE(Metal);
CASE(Vulkan);
CASE(OpenGL);
CASE(OpenGLES);
#undef CASE
// clang-format on
default:
return "Unknown";
} }
else }
bool GPUDevice::Create(const std::string_view& adapter, const std::string_view& shader_cache_path, bool debug_device,
bool vsync)
{
m_vsync_enabled = vsync;
if (!AcquireWindow(true))
{ {
Log_WarningPrintf("Shader cache is disabled."); Log_ErrorPrintf("Failed to acquire window from host.");
return false;
} }
if (!CreateDevice(adapter, debug_device))
{
Log_ErrorPrintf("Failed to create device.");
return false;
}
if (!shader_cache_path.empty())
OpenShaderCache(shader_cache_path, debug_device);
else
Log_WarningPrintf("Shader cache is disabled.");
if (!CreateResources())
{
Log_ErrorPrintf("Failed to create base resources.");
return false;
}
return true;
}
void GPUDevice::Destroy()
{
if (HasSurface())
DestroySurface();
DestroyResources();
DestroyDevice();
}
bool GPUDevice::UpdateWindow()
{
// TODO: REMOVE ME
UnreachableCode();
return false;
}
bool GPUDevice::SupportsExclusiveFullscreen() const
{
return false;
}
void GPUDevice::OpenShaderCache(const std::string_view& base_path, bool debug)
{
// TODO: option to disable shader cache
const std::string basename = GetShaderCacheBaseName("shaders", debug);
const std::string filename = Path::Combine(base_path, basename);
if (!m_shader_cache.Open(filename.c_str()))
Log_WarningPrintf("Failed to open shader cache.");
}
bool GPUDevice::AcquireWindow(bool recreate_window)
{
std::optional<WindowInfo> wi = Host::AcquireRenderWindow(recreate_window);
if (!wi.has_value())
return false;
Log_InfoPrintf("Render window is %ux%u.", wi->surface_width, wi->surface_height);
m_window_info = wi.value();
return true; return true;
} }
@ -348,6 +424,12 @@ std::string GPUDevice::GetShaderCacheBaseName(const std::string_view& type, bool
return {}; return {};
} }
void GPUDevice::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
{
// TODO: REMOVE ME
UnreachableCode();
}
void GPUDevice::RenderImGui() void GPUDevice::RenderImGui()
{ {
GL_SCOPE("RenderImGui"); GL_SCOPE("RenderImGui");
@ -566,6 +648,19 @@ void GPUDevice::InvalidateRenderTarget(GPUTexture* t)
t->SetState(GPUTexture::State::Invalidated); t->SetState(GPUTexture::State::Invalidated);
} }
bool GPUDevice::CreateDevice(const std::string_view& adapter, bool debug_device)
{
// TODO: REMOVE ME
UnreachableCode();
return false;
}
void GPUDevice::DestroyDevice()
{
// TODO: REMOVE ME
UnreachableCode();
}
std::unique_ptr<GPUShader> GPUDevice::CreateShaderFromBinary(GPUShaderStage stage, gsl::span<const u8> data) std::unique_ptr<GPUShader> GPUDevice::CreateShaderFromBinary(GPUShaderStage stage, gsl::span<const u8> data)
{ {
// TODO: REMOVE ME // TODO: REMOVE ME
@ -658,14 +753,16 @@ std::unique_ptr<GPUShader> GPUDevice::CreateShader(GPUShaderStage stage, const s
return shader; return shader;
} }
bool GPUDevice::ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate) bool GPUDevice::GetRequestedExclusiveFullscreenMode(u32* width, u32* height, float* refresh_rate)
{ {
const std::string mode = Host::GetBaseStringSettingValue("GPU", "FullscreenMode", "");
if (!mode.empty()) if (!mode.empty())
{ {
const std::string_view mode_view = mode;
std::string_view::size_type sep1 = mode.find('x'); std::string_view::size_type sep1 = mode.find('x');
if (sep1 != std::string_view::npos) if (sep1 != std::string_view::npos)
{ {
std::optional<u32> owidth = StringUtil::FromChars<u32>(mode.substr(0, sep1)); std::optional<u32> owidth = StringUtil::FromChars<u32>(mode_view.substr(0, sep1));
sep1++; sep1++;
while (sep1 < mode.length() && std::isspace(mode[sep1])) while (sep1 < mode.length() && std::isspace(mode[sep1]))
@ -676,7 +773,7 @@ bool GPUDevice::ParseFullscreenMode(const std::string_view& mode, u32* width, u3
std::string_view::size_type sep2 = mode.find('@', sep1); std::string_view::size_type sep2 = mode.find('@', sep1);
if (sep2 != std::string_view::npos) if (sep2 != std::string_view::npos)
{ {
std::optional<u32> oheight = StringUtil::FromChars<u32>(mode.substr(sep1, sep2 - sep1)); std::optional<u32> oheight = StringUtil::FromChars<u32>(mode_view.substr(sep1, sep2 - sep1));
sep2++; sep2++;
while (sep2 < mode.length() && std::isspace(mode[sep2])) while (sep2 < mode.length() && std::isspace(mode[sep2]))
@ -684,7 +781,7 @@ bool GPUDevice::ParseFullscreenMode(const std::string_view& mode, u32* width, u3
if (oheight.has_value() && sep2 < mode.length()) if (oheight.has_value() && sep2 < mode.length())
{ {
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(mode.substr(sep2)); std::optional<float> orefresh_rate = StringUtil::FromChars<float>(mode_view.substr(sep2));
if (orefresh_rate.has_value()) if (orefresh_rate.has_value())
{ {
*width = owidth.value(); *width = owidth.value();
@ -709,6 +806,11 @@ std::string GPUDevice::GetFullscreenModeString(u32 width, u32 height, float refr
return StringUtil::StdStringFromFormat("%u x %u @ %f hz", width, height, refresh_rate); return StringUtil::StdStringFromFormat("%u x %u @ %f hz", width, height, refresh_rate);
} }
std::string GPUDevice::GetShaderDumpPath(const std::string_view& name)
{
return Path::Combine(EmuFolders::Dumps, name);
}
bool GPUDevice::UpdateImGuiFontTexture() bool GPUDevice::UpdateImGuiFontTexture()
{ {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
@ -1529,7 +1631,7 @@ bool GPUDevice::WriteScreenshotToFile(std::string filename, bool internal_resolu
return true; return true;
} }
std::unique_ptr<GPUDevice> Host::CreateDisplayForAPI(RenderAPI api) std::unique_ptr<GPUDevice> GPUDevice::CreateDeviceForAPI(RenderAPI api)
{ {
switch (api) switch (api)
{ {
@ -1558,18 +1660,6 @@ std::unique_ptr<GPUDevice> Host::CreateDisplayForAPI(RenderAPI api)
#endif #endif
default: default:
#if defined(_WIN32) && defined(_M_ARM64)
return std::make_unique<D3D12GPUDevice>();
#elif defined(_WIN32)
return std::make_unique<D3D11Device>();
#elif defined(__APPLE__)
return WrapNewMetalDevice();
#elif defined(WITH_OPENGL)
return std::make_unique<OpenGLDevice>();
#elif defined(WITH_VULKAN)
return std::make_unique<VulkanGPUDevice>();
#else
return {}; return {};
#endif
} }
} }

View File

@ -14,6 +14,7 @@
#include "gsl/span" #include "gsl/span"
#include <memory> #include <memory>
#include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <tuple> #include <tuple>
@ -431,12 +432,21 @@ public:
/// Returns the default/preferred API for the system. /// Returns the default/preferred API for the system.
static RenderAPI GetPreferredAPI(); static RenderAPI GetPreferredAPI();
/// Returns a string representing the specified API.
static const char* RenderAPIToString(RenderAPI api);
/// Returns a new device for the specified API.
static std::unique_ptr<GPUDevice> CreateDeviceForAPI(RenderAPI api);
/// Parses a fullscreen mode into its components (width * height @ refresh hz) /// Parses a fullscreen mode into its components (width * height @ refresh hz)
static bool ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate); static bool GetRequestedExclusiveFullscreenMode(u32* width, u32* height, float* refresh_rate);
/// Converts a fullscreen mode to a string. /// Converts a fullscreen mode to a string.
static std::string GetFullscreenModeString(u32 width, u32 height, float refresh_rate); static std::string GetFullscreenModeString(u32 width, u32 height, float refresh_rate);
/// Returns the directory bad shaders are saved to.
static std::string GetShaderDumpPath(const std::string_view& name);
ALWAYS_INLINE const Features& GetFeatures() const { return m_features; } ALWAYS_INLINE const Features& GetFeatures() const { return m_features; }
ALWAYS_INLINE u32 GetMaxTextureSize() const { return m_max_texture_size; } ALWAYS_INLINE u32 GetMaxTextureSize() const { return m_max_texture_size; }
ALWAYS_INLINE u32 GetMaxMultisamples() const { return m_max_multisamples; } ALWAYS_INLINE u32 GetMaxMultisamples() const { return m_max_multisamples; }
@ -466,29 +476,23 @@ public:
virtual RenderAPI GetRenderAPI() const = 0; virtual RenderAPI GetRenderAPI() const = 0;
virtual bool CreateDevice(const WindowInfo& wi, bool vsync) = 0; bool Create(const std::string_view& adapter, const std::string_view& shader_cache_path,
virtual bool SetupDevice(); bool debug_device, bool vsync);
virtual bool MakeCurrent() = 0; void Destroy();
virtual bool DoneCurrent() = 0;
virtual bool HasSurface() const = 0; virtual bool HasSurface() const = 0;
virtual void DestroySurface() = 0; virtual void DestroySurface() = 0;
virtual bool ChangeWindow(const WindowInfo& wi) = 0; virtual bool UpdateWindow();
virtual bool SupportsFullscreen() const = 0; virtual bool SupportsExclusiveFullscreen() const;
virtual bool IsFullscreen() = 0;
virtual bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) = 0;
virtual AdapterAndModeList GetAdapterAndModeList() = 0; virtual AdapterAndModeList GetAdapterAndModeList() = 0;
virtual bool CreateResources();
virtual void DestroyResources();
virtual bool SetPostProcessingChain(const std::string_view& config); virtual bool SetPostProcessingChain(const std::string_view& config);
virtual std::string GetShaderCacheBaseName(const std::string_view& type, bool debug) const; virtual std::string GetShaderCacheBaseName(const std::string_view& type, bool debug) const;
/// Call when the window size changes externally to recreate any resources. /// Call when the window size changes externally to recreate any resources.
virtual void ResizeWindow(s32 new_window_width, s32 new_window_height) = 0; virtual void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale);
/// Creates an abstracted RGBA8 texture. If dynamic, the texture can be updated with UpdateTexture() below. /// Creates an abstracted RGBA8 texture. If dynamic, the texture can be updated with UpdateTexture() below.
virtual std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, virtual std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
@ -623,13 +627,37 @@ public:
bool WriteScreenshotToFile(std::string filename, bool internal_resolution = false, bool compress_on_thread = false); bool WriteScreenshotToFile(std::string filename, bool internal_resolution = false, bool compress_on_thread = false);
protected: protected:
virtual bool CreateDevice(const std::string_view& adapter, bool debug_device);
virtual void DestroyDevice();
virtual std::unique_ptr<GPUShader> CreateShaderFromBinary(GPUShaderStage stage, gsl::span<const u8> data); virtual std::unique_ptr<GPUShader> CreateShaderFromBinary(GPUShaderStage stage, gsl::span<const u8> data);
virtual std::unique_ptr<GPUShader> CreateShaderFromSource(GPUShaderStage stage, const std::string_view& source, virtual std::unique_ptr<GPUShader> CreateShaderFromSource(GPUShaderStage stage, const std::string_view& source,
std::vector<u8>* out_binary = nullptr); std::vector<u8>* out_binary = nullptr);
bool AcquireWindow(bool recreate_window);
Features m_features = {};
u32 m_max_texture_size = 0;
u32 m_max_multisamples = 0;
WindowInfo m_window_info;
GPUShaderCache m_shader_cache;
std::unique_ptr<GPUSampler> m_nearest_sampler;
std::unique_ptr<GPUSampler> m_linear_sampler;
bool m_gpu_timing_enabled = false;
bool m_vsync_enabled = false;
private:
ALWAYS_INLINE bool HasSoftwareCursor() const { return static_cast<bool>(m_cursor_texture); } ALWAYS_INLINE bool HasSoftwareCursor() const { return static_cast<bool>(m_cursor_texture); }
ALWAYS_INLINE bool HasDisplayTexture() const { return (m_display_texture != nullptr); } ALWAYS_INLINE bool HasDisplayTexture() const { return (m_display_texture != nullptr); }
void OpenShaderCache(const std::string_view& base_path, bool debug);
bool CreateResources();
void DestroyResources();
bool IsUsingLinearFiltering() const; bool IsUsingLinearFiltering() const;
void CalculateDrawRect(s32 window_width, s32 window_height, float* out_left, float* out_top, float* out_width, void CalculateDrawRect(s32 window_width, s32 window_height, float* out_left, float* out_top, float* out_width,
@ -648,17 +676,6 @@ protected:
bool linear_filter); bool linear_filter);
void RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 height, GPUTexture* texture); void RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 height, GPUTexture* texture);
Features m_features = {};
u32 m_max_texture_size = 0;
u32 m_max_multisamples = 0;
WindowInfo m_window_info;
GPUShaderCache m_shader_cache;
std::unique_ptr<GPUSampler> m_nearest_sampler;
std::unique_ptr<GPUSampler> m_linear_sampler;
u64 m_last_frame_displayed_time = 0; u64 m_last_frame_displayed_time = 0;
s32 m_mouse_position_x = 0; s32 m_mouse_position_x = 0;
@ -688,8 +705,6 @@ protected:
float m_cursor_texture_scale = 1.0f; float m_cursor_texture_scale = 1.0f;
bool m_display_changed = false; bool m_display_changed = false;
bool m_gpu_timing_enabled = false;
bool m_vsync_enabled = false;
std::unique_ptr<PostProcessingChain> m_post_processing_chain; std::unique_ptr<PostProcessingChain> m_post_processing_chain;
}; };
@ -698,20 +713,15 @@ protected:
extern std::unique_ptr<GPUDevice> g_host_display; extern std::unique_ptr<GPUDevice> g_host_display;
namespace Host { namespace Host {
std::unique_ptr<GPUDevice> CreateDisplayForAPI(RenderAPI api); /// Called when the core is creating a render device.
/// This could also be fullscreen transition.
std::optional<WindowInfo> AcquireRenderWindow(bool recreate_window);
/// Creates the host display. This may create a new window. The API used depends on the current configuration. /// Called before drawing the OSD and other display elements.
bool AcquireHostDisplay(RenderAPI api); void BeginPresentFrame();
/// Destroys the host display. This may close the display window. /// Called when the core is finished with a render window.
void ReleaseHostDisplay(); void ReleaseRenderWindow();
/// Returns false if the window was completely occluded. If frame_skip is set, the frame won't be
/// displayed, but the GPU command queue will still be flushed.
// bool BeginPresentFrame(bool frame_skip);
/// Presents the frame to the display, and renders OSD elements.
// void EndPresentFrame();
/// Provided by the host; renders the display. /// Provided by the host; renders the display.
void RenderDisplay(bool skip_present); void RenderDisplay(bool skip_present);

View File

@ -2,7 +2,6 @@
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "opengl_device.h" #include "opengl_device.h"
#include "../settings.h"
#include "opengl_pipeline.h" #include "opengl_pipeline.h"
#include "opengl_stream_buffer.h" #include "opengl_stream_buffer.h"
#include "opengl_texture.h" #include "opengl_texture.h"
@ -13,6 +12,8 @@
#include "common/log.h" #include "common/log.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "fmt/format.h"
#include <array> #include <array>
#include <tuple> #include <tuple>
@ -28,15 +29,7 @@ OpenGLDevice::OpenGLDevice()
OpenGLDevice::~OpenGLDevice() OpenGLDevice::~OpenGLDevice()
{ {
// TODO: Destroy() function Assert(!m_gl_context);
if (!m_gl_context)
return;
DestroyResources();
DestroyBuffers();
m_gl_context->DoneCurrent();
m_gl_context.reset();
} }
void OpenGLDevice::BindUpdateTextureUnit() void OpenGLDevice::BindUpdateTextureUnit()
@ -201,7 +194,7 @@ void OpenGLDevice::ResolveTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u
void OpenGLDevice::PushDebugGroup(const char* fmt, ...) void OpenGLDevice::PushDebugGroup(const char* fmt, ...)
{ {
#ifdef _DEBUG #ifdef _DEBUG
if (!glPushDebugGroup || !g_settings.gpu_use_debug_device) if (!glPushDebugGroup)
return; return;
std::va_list ap; std::va_list ap;
@ -216,7 +209,7 @@ void OpenGLDevice::PushDebugGroup(const char* fmt, ...)
void OpenGLDevice::PopDebugGroup() void OpenGLDevice::PopDebugGroup()
{ {
#ifdef _DEBUG #ifdef _DEBUG
if (!glPopDebugGroup || !g_settings.gpu_use_debug_device) if (!glPopDebugGroup)
return; return;
glPopDebugGroup(); glPopDebugGroup();
@ -226,7 +219,7 @@ void OpenGLDevice::PopDebugGroup()
void OpenGLDevice::InsertDebugMessage(const char* fmt, ...) void OpenGLDevice::InsertDebugMessage(const char* fmt, ...)
{ {
#ifdef _DEBUG #ifdef _DEBUG
if (!glDebugMessageInsert || !g_settings.gpu_use_debug_device) if (!glDebugMessageInsert)
return; return;
std::va_list ap; std::va_list ap;
@ -275,9 +268,9 @@ bool OpenGLDevice::HasSurface() const
return m_window_info.type != WindowInfo::Type::Surfaceless; return m_window_info.type != WindowInfo::Type::Surfaceless;
} }
bool OpenGLDevice::CreateDevice(const WindowInfo& wi, bool vsync) bool OpenGLDevice::CreateDevice(const std::string_view& adapter, bool debug_device)
{ {
m_gl_context = GL::Context::Create(wi); m_gl_context = GL::Context::Create(m_window_info);
if (!m_gl_context) if (!m_gl_context)
{ {
Log_ErrorPrintf("Failed to create any GL context"); Log_ErrorPrintf("Failed to create any GL context");
@ -285,18 +278,26 @@ bool OpenGLDevice::CreateDevice(const WindowInfo& wi, bool vsync)
return false; return false;
} }
// Is this needed?
m_window_info = m_gl_context->GetWindowInfo(); m_window_info = m_gl_context->GetWindowInfo();
m_vsync_enabled = vsync;
return true;
}
bool OpenGLDevice::SetupDevice() #if 0
{ // TODO: add these checks
if (!GPUDevice::SetupDevice()) const bool opengl_is_available = ((g_host_display->GetRenderAPI() == RenderAPI::OpenGL &&
return false; (GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) ||
(g_host_display->GetRenderAPI() == RenderAPI::OpenGLES && GLAD_GL_ES_VERSION_3_1));
if (!opengl_is_available)
{
Host::AddOSDMessage(Host::TranslateStdString("OSDMessage",
"OpenGL renderer unavailable, your driver or hardware is not "
"recent enough. OpenGL 3.1 or OpenGL ES 3.1 is required."),
20.0f);
return nullptr;
}
#endif
OpenGLTexture::s_use_pbo_for_uploads = true; OpenGLTexture::s_use_pbo_for_uploads = true;
if (GetRenderAPI() == RenderAPI::OpenGLES) if (m_gl_context->IsGLES())
{ {
// Adreno seems to corrupt textures through PBOs... and Mali is slow. // Adreno seems to corrupt textures through PBOs... and Mali is slow.
const char* gl_vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR)); const char* gl_vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
@ -306,9 +307,9 @@ bool OpenGLDevice::SetupDevice()
Log_VerbosePrintf("Using PBO for uploads: %s", OpenGLTexture::s_use_pbo_for_uploads ? "yes" : "no"); Log_VerbosePrintf("Using PBO for uploads: %s", OpenGLTexture::s_use_pbo_for_uploads ? "yes" : "no");
if (g_settings.gpu_use_debug_device && GLAD_GL_KHR_debug) if (debug_device && GLAD_GL_KHR_debug)
{ {
if (GetRenderAPI() == RenderAPI::OpenGLES) if (m_gl_context->IsGLES())
glDebugMessageCallbackKHR(GLDebugCallback, nullptr); glDebugMessageCallbackKHR(GLDebugCallback, nullptr);
else else
glDebugMessageCallback(GLDebugCallback, nullptr); glDebugMessageCallback(GLDebugCallback, nullptr);
@ -316,11 +317,19 @@ bool OpenGLDevice::SetupDevice()
glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
} }
else
{
// Nail the function pointers so that we don't waste time calling them.
glPushDebugGroup = nullptr;
glPopDebugGroup = nullptr;
glDebugMessageInsert = nullptr;
glObjectLabel = nullptr;
}
if (!CheckFeatures()) if (!CheckFeatures())
return false; return false;
if (!CreateBuffers() || !CreateResources()) if (!CreateBuffers())
return false; return false;
return true; return true;
@ -391,28 +400,27 @@ bool OpenGLDevice::CheckFeatures()
return true; return true;
} }
bool OpenGLDevice::MakeCurrent() void OpenGLDevice::DestroyDevice()
{ {
if (!m_gl_context->MakeCurrent()) if (!m_gl_context)
{ return;
Log_ErrorPrintf("Failed to make GL context current");
return false;
}
SetSwapInterval(); DestroyBuffers();
return true;
m_gl_context->DoneCurrent();
m_gl_context.reset();
} }
bool OpenGLDevice::DoneCurrent() bool OpenGLDevice::UpdateWindow()
{
return m_gl_context->DoneCurrent();
}
bool OpenGLDevice::ChangeWindow(const WindowInfo& new_wi)
{ {
Assert(m_gl_context); Assert(m_gl_context);
if (!m_gl_context->ChangeSurface(new_wi)) DestroySurface();
if (!AcquireWindow(false))
return false;
if (!m_gl_context->ChangeSurface(m_window_info))
{ {
Log_ErrorPrintf("Failed to change surface"); Log_ErrorPrintf("Failed to change surface");
return false; return false;
@ -420,17 +428,24 @@ bool OpenGLDevice::ChangeWindow(const WindowInfo& new_wi)
m_window_info = m_gl_context->GetWindowInfo(); m_window_info = m_gl_context->GetWindowInfo();
// Update swap interval for new surface. if (m_window_info.type != WindowInfo::Type::Surfaceless)
if (m_gl_context->IsCurrent()) {
// reset vsync rate, since it (usually) gets lost
SetSwapInterval(); SetSwapInterval();
// TODO RenderBlankFrame();
}
return true; return true;
} }
void OpenGLDevice::ResizeWindow(s32 new_window_width, s32 new_window_height) void OpenGLDevice::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
{ {
if (!m_gl_context) m_window_info.surface_scale = new_window_scale;
if (m_window_info.surface_width == static_cast<u32>(new_window_width) &&
m_window_info.surface_height == static_cast<u32>(new_window_height))
{
return; return;
}
m_gl_context->ResizeSurface(static_cast<u32>(new_window_width), static_cast<u32>(new_window_height)); m_gl_context->ResizeSurface(static_cast<u32>(new_window_width), static_cast<u32>(new_window_height));
m_window_info = m_gl_context->GetWindowInfo(); m_window_info = m_gl_context->GetWindowInfo();
@ -453,21 +468,6 @@ void OpenGLDevice::SetSwapInterval()
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
} }
bool OpenGLDevice::SupportsFullscreen() const
{
return false;
}
bool OpenGLDevice::IsFullscreen()
{
return false;
}
bool OpenGLDevice::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
{
return false;
}
GPUDevice::AdapterAndModeList OpenGLDevice::GetAdapterAndModeList() GPUDevice::AdapterAndModeList OpenGLDevice::GetAdapterAndModeList()
{ {
AdapterAndModeList aml; AdapterAndModeList aml;

View File

@ -41,21 +41,13 @@ public:
RenderAPI GetRenderAPI() const override; RenderAPI GetRenderAPI() const override;
bool HasSurface() const override; bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi, bool vsync) override;
bool SetupDevice() override;
bool MakeCurrent() override;
bool DoneCurrent() override;
bool ChangeWindow(const WindowInfo& new_wi) override;
void ResizeWindow(s32 new_window_width, s32 new_window_height) override;
bool SupportsFullscreen() const override;
bool IsFullscreen() override;
bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
AdapterAndModeList GetAdapterAndModeList() override;
void DestroySurface() override; void DestroySurface() override;
bool UpdateWindow() override;
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
AdapterAndModeList GetAdapterAndModeList() override;
std::string GetShaderCacheBaseName(const std::string_view& type, bool debug) const override; std::string GetShaderCacheBaseName(const std::string_view& type, bool debug) const override;
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
@ -142,6 +134,9 @@ protected:
OpenGLPipeline::VertexArrayCache m_vao_cache; OpenGLPipeline::VertexArrayCache m_vao_cache;
OpenGLPipeline::ProgramCache m_program_cache; OpenGLPipeline::ProgramCache m_program_cache;
bool CreateDevice(const std::string_view& adapter, bool debug_device) override;
void DestroyDevice() override;
bool CreateBuffers(); bool CreateBuffers();
void DestroyBuffers(); void DestroyBuffers();

View File

@ -31,7 +31,7 @@ VulkanGPUDevice::~VulkanGPUDevice()
g_vulkan_context->WaitForGPUIdle(); g_vulkan_context->WaitForGPUIdle();
DestroyStagingBuffer(); DestroyStagingBuffer();
DestroyResources(); //DestroyResources();
Vulkan::ShaderCache::Destroy(); Vulkan::ShaderCache::Destroy();
m_swap_chain.reset(); m_swap_chain.reset();
@ -46,6 +46,8 @@ RenderAPI VulkanGPUDevice::GetRenderAPI() const
return RenderAPI::Vulkan; return RenderAPI::Vulkan;
} }
#if 0
bool VulkanGPUDevice::ChangeWindow(const WindowInfo& new_wi) bool VulkanGPUDevice::ChangeWindow(const WindowInfo& new_wi)
{ {
g_vulkan_context->WaitForGPUIdle(); g_vulkan_context->WaitForGPUIdle();
@ -118,6 +120,8 @@ bool VulkanGPUDevice::SetFullscreen(bool fullscreen, u32 width, u32 height, floa
return false; return false;
} }
#endif
GPUDevice::AdapterAndModeList VulkanGPUDevice::GetAdapterAndModeList() GPUDevice::AdapterAndModeList VulkanGPUDevice::GetAdapterAndModeList()
{ {
return StaticGetAdapterAndModeList(m_window_info.type != WindowInfo::Type::Surfaceless ? &m_window_info : nullptr); return StaticGetAdapterAndModeList(m_window_info.type != WindowInfo::Type::Surfaceless ? &m_window_info : nullptr);
@ -211,6 +215,8 @@ void VulkanGPUDevice::SetVSync(bool enabled)
m_vsync_enabled = m_swap_chain->IsVSyncEnabled(); m_vsync_enabled = m_swap_chain->IsVSyncEnabled();
} }
#if 0
bool VulkanGPUDevice::CreateDevice(const WindowInfo& wi, bool vsync) bool VulkanGPUDevice::CreateDevice(const WindowInfo& wi, bool vsync)
{ {
WindowInfo local_wi(wi); WindowInfo local_wi(wi);
@ -251,6 +257,8 @@ bool VulkanGPUDevice::SetupDevice()
return true; return true;
} }
#endif
bool VulkanGPUDevice::HasSurface() const bool VulkanGPUDevice::HasSurface() const
{ {
return static_cast<bool>(m_swap_chain); return static_cast<bool>(m_swap_chain);
@ -385,6 +393,8 @@ bool VulkanGPUDevice::CheckStagingBufferSize(u32 required_size)
return true; return true;
} }
#if 0
bool VulkanGPUDevice::CreateResources() bool VulkanGPUDevice::CreateResources()
{ {
static constexpr char fullscreen_quad_vertex_shader[] = R"( static constexpr char fullscreen_quad_vertex_shader[] = R"(
@ -556,7 +566,6 @@ void VulkanGPUDevice::DestroyResources()
Vulkan::Util::SafeDestroySampler(m_linear_sampler); Vulkan::Util::SafeDestroySampler(m_linear_sampler);
} }
#if 0
bool VulkanGPUDevice::CreateImGuiContext() bool VulkanGPUDevice::CreateImGuiContext()
{ {
const VkRenderPass render_pass = const VkRenderPass render_pass =
@ -581,7 +590,6 @@ bool VulkanGPUDevice::UpdateImGuiFontTexture()
g_vulkan_context->ExecuteCommandBuffer(true); g_vulkan_context->ExecuteCommandBuffer(true);
return ImGui_ImplVulkan_CreateFontsTexture(); return ImGui_ImplVulkan_CreateFontsTexture();
} }
#endif
bool VulkanGPUDevice::MakeCurrent() bool VulkanGPUDevice::MakeCurrent()
{ {
@ -593,7 +601,6 @@ bool VulkanGPUDevice::DoneCurrent()
return true; return true;
} }
#if 0
bool VulkanGPUDevice::Render(bool skip_present) bool VulkanGPUDevice::Render(bool skip_present)
{ {
if (skip_present || !m_swap_chain) if (skip_present || !m_swap_chain)
@ -686,6 +693,7 @@ void VulkanGPUDevice::BeginSwapChainRenderPass(VkFramebuffer framebuffer, u32 wi
vkCmdBeginRenderPass(g_vulkan_context->GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE); vkCmdBeginRenderPass(g_vulkan_context->GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE);
} }
#if 0
void VulkanGPUDevice::RenderDisplay() void VulkanGPUDevice::RenderDisplay()
{ {
const Vulkan::Util::DebugScope debugScope(g_vulkan_context->GetCurrentCommandBuffer(), const Vulkan::Util::DebugScope debugScope(g_vulkan_context->GetCurrentCommandBuffer(),
@ -797,6 +805,7 @@ void VulkanGPUDevice::RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 hei
Vulkan::Util::SetViewportAndClampScissor(cmdbuffer, left, top, width, height); Vulkan::Util::SetViewportAndClampScissor(cmdbuffer, left, top, width, height);
vkCmdDraw(cmdbuffer, 3, 1, 0, 0); vkCmdDraw(cmdbuffer, 3, 1, 0, 0);
} }
#endif
bool VulkanGPUDevice::SetGPUTimingEnabled(bool enabled) bool VulkanGPUDevice::SetGPUTimingEnabled(bool enabled)
{ {

View File

@ -27,17 +27,17 @@ public:
bool HasSurface() const override; bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi, bool vsync) override; //bool CreateDevice(const WindowInfo& wi, bool vsync) override;
bool SetupDevice() override; //bool SetupDevice() override;
bool MakeCurrent() override; //bool MakeCurrent() override;
bool DoneCurrent() override; //bool DoneCurrent() override;
bool ChangeWindow(const WindowInfo& new_wi) override; //bool ChangeWindow(const WindowInfo& new_wi) override;
void ResizeWindow(s32 new_window_width, s32 new_window_height) override; //void ResizeWindow(s32 new_window_width, s32 new_window_height) override;
bool SupportsFullscreen() const override; //bool SupportsFullscreen() const override;
bool IsFullscreen() override; //bool IsFullscreen() override;
bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; //bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
AdapterAndModeList GetAdapterAndModeList() override; AdapterAndModeList GetAdapterAndModeList() override;
void DestroySurface() override; void DestroySurface() override;
@ -73,10 +73,11 @@ protected:
bool CheckStagingBufferSize(u32 required_size); bool CheckStagingBufferSize(u32 required_size);
void DestroyStagingBuffer(); void DestroyStagingBuffer();
bool CreateResources() override; //bool CreateResources() override;
void DestroyResources() override; //void DestroyResources() override;
void BeginSwapChainRenderPass(VkFramebuffer framebuffer, u32 width, u32 height); void BeginSwapChainRenderPass(VkFramebuffer framebuffer, u32 width, u32 height);
#if 0
void RenderDisplay(); void RenderDisplay();
void RenderImGui(); void RenderImGui();
void RenderSoftwareCursor(); void RenderSoftwareCursor();
@ -84,6 +85,7 @@ protected:
void RenderDisplay(s32 left, s32 top, s32 width, s32 height, Vulkan::Texture* texture, s32 texture_view_x, void RenderDisplay(s32 left, s32 top, s32 width, s32 height, Vulkan::Texture* texture, s32 texture_view_x,
s32 texture_view_y, s32 texture_view_width, s32 texture_view_height, bool linear_filter); s32 texture_view_y, s32 texture_view_width, s32 texture_view_height, bool linear_filter);
void RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 height, GPUTexture* texture_handle); void RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 height, GPUTexture* texture_handle);
#endif
std::unique_ptr<Vulkan::SwapChain> m_swap_chain; std::unique_ptr<Vulkan::SwapChain> m_swap_chain;

View File

@ -2633,71 +2633,8 @@ void GPU_HW::ShaderCompileProgressTracker::Increment()
} }
} }
// TODO: Combine all these.. std::unique_ptr<GPU> GPU::CreateHardwareRenderer()
#ifdef _WIN32
std::unique_ptr<GPU> GPU::CreateHardwareD3D11Renderer()
{ {
if (!Host::AcquireHostDisplay(RenderAPI::D3D11))
{
Log_ErrorPrintf("Host render API is incompatible");
return nullptr;
}
std::unique_ptr<GPU_HW> gpu(std::make_unique<GPU_HW>());
if (!gpu->Initialize())
return nullptr;
return gpu;
}
#endif
#ifdef __APPLE__
std::unique_ptr<GPU> GPU::CreateHardwareMetalRenderer()
{
if (!Host::AcquireHostDisplay(RenderAPI::Metal))
{
Log_ErrorPrintf("Host render API is incompatible");
return nullptr;
}
std::unique_ptr<GPU_HW> gpu(std::make_unique<GPU_HW>());
if (!gpu->Initialize())
return nullptr;
return gpu;
}
#endif
std::unique_ptr<GPU> GPU::CreateHardwareOpenGLRenderer()
{
// Don't re-request GL when we already have GLES here...
const RenderAPI current_api = g_host_display ? g_host_display->GetRenderAPI() : RenderAPI::None;
if (current_api != RenderAPI::OpenGL && current_api != RenderAPI::OpenGLES &&
!Host::AcquireHostDisplay(RenderAPI::OpenGL))
{
Log_ErrorPrintf("Host render API type is incompatible");
return nullptr;
}
#if 0
const bool opengl_is_available = ((g_host_display->GetRenderAPI() == RenderAPI::OpenGL &&
(GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) ||
(g_host_display->GetRenderAPI() == RenderAPI::OpenGLES && GLAD_GL_ES_VERSION_3_1));
if (!opengl_is_available)
{
Host::AddOSDMessage(Host::TranslateStdString("OSDMessage",
"OpenGL renderer unavailable, your driver or hardware is not "
"recent enough. OpenGL 3.1 or OpenGL ES 3.1 is required."),
20.0f);
return nullptr;
}
#endif
std::unique_ptr<GPU_HW> gpu(std::make_unique<GPU_HW>()); std::unique_ptr<GPU_HW> gpu(std::make_unique<GPU_HW>());
if (!gpu->Initialize()) if (!gpu->Initialize())
return nullptr; return nullptr;

View File

@ -1149,7 +1149,6 @@ void GPU_HW_D3D12::ClearDepthBuffer()
cmdlist->ClearDepthStencilView(m_vram_depth_texture.GetRTVOrDSVDescriptor(), D3D12_CLEAR_FLAG_DEPTH, cmdlist->ClearDepthStencilView(m_vram_depth_texture.GetRTVOrDSVDescriptor(), D3D12_CLEAR_FLAG_DEPTH,
m_pgxp_depth_buffer ? 1.0f : 0.0f, 0, 0, nullptr); m_pgxp_depth_buffer ? 1.0f : 0.0f, 0, 0, nullptr);
} }
#endif
std::unique_ptr<GPU> GPU::CreateHardwareD3D12Renderer() std::unique_ptr<GPU> GPU::CreateHardwareD3D12Renderer()
{ {
@ -1165,3 +1164,4 @@ std::unique_ptr<GPU> GPU::CreateHardwareD3D12Renderer()
return gpu; return gpu;
} }
#endif

View File

@ -1858,19 +1858,3 @@ void GPU_HW_Vulkan::DownsampleFramebufferAdaptive(Vulkan::Texture& source, u32 l
g_host_display->SetDisplayTexture(&m_display_texture, left, top, width, height); g_host_display->SetDisplayTexture(&m_display_texture, left, top, width, height);
} }
std::unique_ptr<GPU> GPU::CreateHardwareVulkanRenderer()
{
if (!Host::AcquireHostDisplay(RenderAPI::Vulkan))
{
Log_ErrorPrintf("Host render API is incompatible");
return nullptr;
}
Assert(g_vulkan_shader_cache);
std::unique_ptr<GPU_HW_Vulkan> gpu(std::make_unique<GPU_HW_Vulkan>());
if (!gpu->Initialize())
return nullptr;
return gpu;
}

View File

@ -895,10 +895,6 @@ void GPU_SW::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32
std::unique_ptr<GPU> GPU::CreateSoftwareRenderer() std::unique_ptr<GPU> GPU::CreateSoftwareRenderer()
{ {
// we need something to draw in.. but keep the current api if we have one
if (!g_host_display && !Host::AcquireHostDisplay(GPUDevice::GetPreferredAPI()))
return nullptr;
std::unique_ptr<GPU_SW> gpu(std::make_unique<GPU_SW>()); std::unique_ptr<GPU_SW> gpu(std::make_unique<GPU_SW>());
if (!gpu->Initialize()) if (!gpu->Initialize())
return nullptr; return nullptr;

View File

@ -942,6 +942,9 @@ RenderAPI Settings::GetRenderAPIForRenderer(GPURenderer renderer)
case GPURenderer::HardwareD3D12: case GPURenderer::HardwareD3D12:
return RenderAPI::D3D12; return RenderAPI::D3D12;
#endif #endif
#ifdef __APPLE__
return RenderAPI::Metal;
#endif
#ifdef WITH_VULKAN #ifdef WITH_VULKAN
case GPURenderer::HardwareVulkan: case GPURenderer::HardwareVulkan:
return RenderAPI::Vulkan; return RenderAPI::Vulkan;

View File

@ -71,7 +71,9 @@ SystemBootParameters::SystemBootParameters(const SystemBootParameters&) = defaul
SystemBootParameters::SystemBootParameters(SystemBootParameters&& other) = default; SystemBootParameters::SystemBootParameters(SystemBootParameters&& other) = default;
SystemBootParameters::SystemBootParameters(std::string filename_) : filename(std::move(filename_)) {} SystemBootParameters::SystemBootParameters(std::string filename_) : filename(std::move(filename_))
{
}
SystemBootParameters::~SystemBootParameters() = default; SystemBootParameters::~SystemBootParameters() = default;
@ -135,6 +137,7 @@ static std::string s_input_profile_name;
static System::State s_state = System::State::Shutdown; static System::State s_state = System::State::Shutdown;
static std::atomic_bool s_startup_cancelled{false}; static std::atomic_bool s_startup_cancelled{false};
static bool s_keep_gpu_device_on_shutdown = false;
static ConsoleRegion s_region = ConsoleRegion::NTSC_U; static ConsoleRegion s_region = ConsoleRegion::NTSC_U;
TickCount System::g_ticks_per_second = System::MASTER_CLOCK; TickCount System::g_ticks_per_second = System::MASTER_CLOCK;
@ -802,7 +805,7 @@ bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_display, bool
// create new renderer // create new renderer
g_gpu.reset(); g_gpu.reset();
if (force_recreate_display) if (force_recreate_display)
Host::ReleaseHostDisplay(); Host::ReleaseGPUDevice();
if (!CreateGPU(renderer)) if (!CreateGPU(renderer))
{ {
@ -1131,6 +1134,7 @@ bool System::BootSystem(SystemBootParameters parameters)
Assert(s_state == State::Shutdown); Assert(s_state == State::Shutdown);
s_state = State::Starting; s_state = State::Starting;
s_startup_cancelled.store(false); s_startup_cancelled.store(false);
s_keep_gpu_device_on_shutdown = static_cast<bool>(g_host_display);
s_region = g_settings.region; s_region = g_settings.region;
Host::OnSystemStarting(); Host::OnSystemStarting();
@ -1421,7 +1425,11 @@ bool System::Initialize(bool force_software_renderer)
if (IsStartupCancelled()) if (IsStartupCancelled())
{ {
g_gpu.reset(); g_gpu.reset();
Host::ReleaseHostDisplay(); if (!s_keep_gpu_device_on_shutdown)
{
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
}
if (g_settings.gpu_pgxp_enable) if (g_settings.gpu_pgxp_enable)
PGXP::Shutdown(); PGXP::Shutdown();
CPU::Shutdown(); CPU::Shutdown();
@ -1515,11 +1523,15 @@ void System::DestroySystem()
ClearRunningGame(); ClearRunningGame();
// Restore present-all-frames behavior. // Restore present-all-frames behavior.
if (g_host_display) if (s_keep_gpu_device_on_shutdown && g_host_display)
{ {
g_host_display->SetDisplayMaxFPS(0.0f); g_host_display->SetDisplayMaxFPS(0.0f);
UpdateSoftwareCursor(); UpdateSoftwareCursor();
Host::ReleaseHostDisplay(); }
else
{
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
} }
s_bios_hash = {}; s_bios_hash = {};
@ -1621,41 +1633,26 @@ void System::RecreateSystem()
bool System::CreateGPU(GPURenderer renderer) bool System::CreateGPU(GPURenderer renderer)
{ {
switch (renderer) const RenderAPI api = Settings::GetRenderAPIForRenderer(renderer);
if (!g_host_display || g_host_display->GetRenderAPI() != api)
{ {
#ifdef WITH_OPENGL if (g_host_display)
case GPURenderer::HardwareOpenGL: {
g_gpu = GPU::CreateHardwareOpenGLRenderer(); Log_WarningPrintf("Recreating GPU device, expecting %s got %s", GPUDevice::RenderAPIToString(api),
break; GPUDevice::RenderAPIToString(g_host_display->GetRenderAPI()));
#endif }
#ifdef WITH_VULKAN Host::ReleaseGPUDevice();
case GPURenderer::HardwareVulkan: if (!Host::CreateGPUDevice(api))
g_gpu = GPU::CreateHardwareVulkanRenderer(); return false;
break;
#endif
#ifdef _WIN32
case GPURenderer::HardwareD3D11:
g_gpu = GPU::CreateHardwareD3D11Renderer();
break;
case GPURenderer::HardwareD3D12:
g_gpu = GPU::CreateHardwareD3D12Renderer();
break;
#endif
#ifdef __APPLE__
case GPURenderer::HardwareMetal:
g_gpu = GPU::CreateHardwareMetalRenderer();
break;
#endif
case GPURenderer::Software:
default:
g_gpu = GPU::CreateSoftwareRenderer();
break;
} }
if (renderer == GPURenderer::Software)
g_gpu = GPU::CreateSoftwareRenderer();
else
g_gpu = GPU::CreateHardwareRenderer();
if (!g_gpu) if (!g_gpu)
{ {
Log_ErrorPrintf("Failed to initialize %s renderer, falling back to software renderer", Log_ErrorPrintf("Failed to initialize %s renderer, falling back to software renderer",
@ -1669,6 +1666,11 @@ bool System::CreateGPU(GPURenderer renderer)
if (!g_gpu) if (!g_gpu)
{ {
Log_ErrorPrintf("Failed to create fallback software renderer."); Log_ErrorPrintf("Failed to create fallback software renderer.");
if (!s_keep_gpu_device_on_shutdown)
{
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
}
return false; return false;
} }
} }

View File

@ -498,6 +498,18 @@ void RequestExit(bool allow_confirm);
/// Requests shut down of the current virtual machine. /// Requests shut down of the current virtual machine.
void RequestSystemShutdown(bool allow_confirm, bool save_state); void RequestSystemShutdown(bool allow_confirm, bool save_state);
/// Attempts to create the rendering device backend.
bool CreateGPUDevice(RenderAPI api);
/// Handles fullscreen transitions and such.
void UpdateDisplayWindow();
/// Called when the window is resized.
void ResizeDisplayWindow(s32 width, s32 height, float scale);
/// Destroys any active rendering device.
void ReleaseGPUDevice();
/// Returns true if the hosting application is currently fullscreen. /// Returns true if the hosting application is currently fullscreen.
bool IsFullscreen(); bool IsFullscreen();

View File

@ -82,8 +82,6 @@ static void UpdateWindowTitle(const std::string& game_title);
static void CancelAsyncOp(); static void CancelAsyncOp();
static void StartAsyncOp(std::function<void(ProgressCallback*)> callback); static void StartAsyncOp(std::function<void(ProgressCallback*)> callback);
static void AsyncOpThreadEntryPoint(std::function<void(ProgressCallback*)> callback); static void AsyncOpThreadEntryPoint(std::function<void(ProgressCallback*)> callback);
static bool AcquireHostDisplay(RenderAPI api);
static void ReleaseHostDisplay();
} // namespace NoGUIHost } // namespace NoGUIHost
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -96,7 +94,7 @@ static bool s_save_state_on_shutdown = false;
static bool s_was_paused_by_focus_loss = false; static bool s_was_paused_by_focus_loss = false;
static Threading::Thread s_cpu_thread; static Threading::Thread s_cpu_thread;
static Threading::KernelSemaphore s_host_display_created_or_destroyed; static Threading::KernelSemaphore s_platform_window_updated;
static std::atomic_bool s_running{false}; static std::atomic_bool s_running{false};
static std::mutex s_cpu_thread_events_mutex; static std::mutex s_cpu_thread_events_mutex;
static std::condition_variable s_cpu_thread_event_done; static std::condition_variable s_cpu_thread_event_done;
@ -415,8 +413,7 @@ void NoGUIHost::StartSystem(SystemBootParameters params)
void NoGUIHost::ProcessPlatformWindowResize(s32 width, s32 height, float scale) void NoGUIHost::ProcessPlatformWindowResize(s32 width, s32 height, float scale)
{ {
Host::RunOnCPUThread([width, height, scale]() { Host::RunOnCPUThread([width, height, scale]() {
// TODO: Scale g_host_display->ResizeWindow(width, height, scale);
g_host_display->ResizeWindow(width, height);
ImGuiManager::WindowResized(); ImGuiManager::WindowResized();
System::HostDisplayResized(); System::HostDisplayResized();
}); });
@ -608,8 +605,8 @@ void NoGUIHost::CPUThreadEntryPoint()
// input source setup must happen on emu thread // input source setup must happen on emu thread
CommonHost::Initialize(); CommonHost::Initialize();
// start the GS thread up and get it going // start the fullscreen UI and get it going
if (AcquireHostDisplay(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer))) if (Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer)) && FullscreenUI::Initialize())
{ {
// kick a game list refresh if we're not in batch mode // kick a game list refresh if we're not in batch mode
if (!InBatchMode()) if (!InBatchMode())
@ -629,7 +626,8 @@ void NoGUIHost::CPUThreadEntryPoint()
if (System::IsValid()) if (System::IsValid())
System::ShutdownSystem(false); System::ShutdownSystem(false);
ReleaseHostDisplay(); Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
CommonHost::Shutdown(); CommonHost::Shutdown();
g_nogui_window->QuitMessageLoop(); g_nogui_window->QuitMessageLoop();
@ -652,53 +650,30 @@ void NoGUIHost::CPUThreadMainLoop()
} }
} }
bool NoGUIHost::AcquireHostDisplay(RenderAPI api) std::optional<WindowInfo> Host::AcquireRenderWindow(bool recreate_window)
{ {
Assert(!g_host_display); std::optional<WindowInfo> wi;
g_nogui_window->ExecuteInMessageLoop([api]() { g_nogui_window->ExecuteInMessageLoop([&wi, recreate_window]() {
if (g_nogui_window->CreatePlatformWindow(GetWindowTitle(System::GetGameTitle()))) bool res = g_nogui_window->HasPlatformWindow();
if (!res || recreate_window)
{ {
const std::optional<WindowInfo> wi(g_nogui_window->GetPlatformWindowInfo()); if (res)
if (wi.has_value())
{
g_host_display = Host::CreateDisplayForAPI(api);
if (g_host_display && !g_host_display->CreateDevice(wi.value(), System::ShouldUseVSync()))
g_host_display.reset();
}
if (g_host_display)
g_host_display->DoneCurrent();
else
g_nogui_window->DestroyPlatformWindow(); g_nogui_window->DestroyPlatformWindow();
}
s_host_display_created_or_destroyed.Post(); res = g_nogui_window->CreatePlatformWindow(NoGUIHost::GetWindowTitle(System::GetGameTitle()));
}
if (res)
wi = g_nogui_window->GetPlatformWindowInfo();
s_platform_window_updated.Post();
}); });
s_host_display_created_or_destroyed.Wait(); s_platform_window_updated.Wait();
if (!g_host_display) if (!wi.has_value())
{ {
g_nogui_window->ReportError("Error", "Failed to create host display."); g_nogui_window->ReportError("Error", "Failed to create render window.");
return false; return std::nullopt;
}
if (!g_host_display->MakeCurrent() || !g_host_display->SetupDevice() || !ImGuiManager::Initialize() ||
!CommonHost::CreateHostDisplayResources())
{
ImGuiManager::Shutdown();
CommonHost::ReleaseHostDisplayResources();
g_host_display.reset();
g_nogui_window->DestroyPlatformWindow();
return false;
}
if (!FullscreenUI::Initialize())
{
g_nogui_window->ReportError("Error", "Failed to initialize fullscreen UI");
ReleaseHostDisplay();
return false;
} }
// reload input sources, since it might use the window handle // reload input sources, since it might use the window handle
@ -706,43 +681,18 @@ bool NoGUIHost::AcquireHostDisplay(RenderAPI api)
auto lock = Host::GetSettingsLock(); auto lock = Host::GetSettingsLock();
InputManager::ReloadSources(*Host::GetSettingsInterface(), lock); InputManager::ReloadSources(*Host::GetSettingsInterface(), lock);
} }
return true;
return wi;
} }
bool Host::AcquireHostDisplay(RenderAPI api) void Host::ReleaseRenderWindow()
{ {
if (g_host_display && g_host_display->GetRenderAPI() == api) // Need to block here, otherwise the recreation message associates with the old window.
{
// current is fine
return true;
}
// otherwise we need to switch
NoGUIHost::ReleaseHostDisplay();
return NoGUIHost::AcquireHostDisplay(api);
}
void NoGUIHost::ReleaseHostDisplay()
{
if (!g_host_display)
return;
// close input sources, since it might use the window handle
InputManager::CloseSources();
CommonHost::ReleaseHostDisplayResources();
ImGuiManager::Shutdown();
g_host_display.reset();
g_nogui_window->ExecuteInMessageLoop([]() { g_nogui_window->ExecuteInMessageLoop([]() {
g_nogui_window->DestroyPlatformWindow(); g_nogui_window->DestroyPlatformWindow();
s_host_display_created_or_destroyed.Post(); s_platform_window_updated.Post();
}); });
s_host_display_created_or_destroyed.Wait(); s_platform_window_updated.Wait();
}
void Host::ReleaseHostDisplay()
{
// we keep the fsui going, so no need to do anything here
} }
void Host::OnSystemStarting() void Host::OnSystemStarting()

View File

@ -24,6 +24,7 @@ public:
virtual void SetDefaultConfig(SettingsInterface& si) = 0; virtual void SetDefaultConfig(SettingsInterface& si) = 0;
virtual bool CreatePlatformWindow(std::string title) = 0; virtual bool CreatePlatformWindow(std::string title) = 0;
virtual bool HasPlatformWindow() const = 0;
virtual void DestroyPlatformWindow() = 0; virtual void DestroyPlatformWindow() = 0;
virtual std::optional<WindowInfo> GetPlatformWindowInfo() = 0; virtual std::optional<WindowInfo> GetPlatformWindowInfo() = 0;

View File

@ -150,6 +150,11 @@ bool WaylandNoGUIPlatform::CreatePlatformWindow(std::string title)
return true; return true;
} }
bool WaylandNoGUIPlatform::HasPlatformWindow() const
{
return (m_surface != nullptr);
}
void WaylandNoGUIPlatform::DestroyPlatformWindow() void WaylandNoGUIPlatform::DestroyPlatformWindow()
{ {
m_window_info = {}; m_window_info = {};

View File

@ -29,6 +29,7 @@ public:
void SetDefaultConfig(SettingsInterface& si) override; void SetDefaultConfig(SettingsInterface& si) override;
bool CreatePlatformWindow(std::string title) override; bool CreatePlatformWindow(std::string title) override;
bool HasPlatformWindow() const override;
void DestroyPlatformWindow() override; void DestroyPlatformWindow() override;
std::optional<WindowInfo> GetPlatformWindowInfo() override; std::optional<WindowInfo> GetPlatformWindowInfo() override;
void SetPlatformWindowTitle(std::string title) override; void SetPlatformWindowTitle(std::string title) override;

View File

@ -134,6 +134,11 @@ bool Win32NoGUIPlatform::CreatePlatformWindow(std::string title)
return true; return true;
} }
bool Win32NoGUIPlatform::HasPlatformWindow() const
{
return (m_hwnd != NULL);
}
void Win32NoGUIPlatform::DestroyPlatformWindow() void Win32NoGUIPlatform::DestroyPlatformWindow()
{ {
if (!m_hwnd) if (!m_hwnd)

View File

@ -23,6 +23,7 @@ public:
void SetDefaultConfig(SettingsInterface& si) override; void SetDefaultConfig(SettingsInterface& si) override;
bool CreatePlatformWindow(std::string title) override; bool CreatePlatformWindow(std::string title) override;
bool HasPlatformWindow() const override;
void DestroyPlatformWindow() override; void DestroyPlatformWindow() override;
std::optional<WindowInfo> GetPlatformWindowInfo() override; std::optional<WindowInfo> GetPlatformWindowInfo() override;
void SetPlatformWindowTitle(std::string title) override; void SetPlatformWindowTitle(std::string title) override;

View File

@ -99,6 +99,11 @@ bool X11NoGUIPlatform::CreatePlatformWindow(std::string title)
return true; return true;
} }
bool X11NoGUIPlatform::HasPlatformWindow() const
{
return m_window != 0;
}
void X11NoGUIPlatform::DestroyPlatformWindow() void X11NoGUIPlatform::DestroyPlatformWindow()
{ {
m_window_info = {}; m_window_info = {};

View File

@ -48,6 +48,7 @@ public:
void SetDefaultConfig(SettingsInterface& si) override; void SetDefaultConfig(SettingsInterface& si) override;
bool CreatePlatformWindow(std::string title) override; bool CreatePlatformWindow(std::string title) override;
bool HasPlatformWindow() const override;
void DestroyPlatformWindow() override; void DestroyPlatformWindow() override;
std::optional<WindowInfo> GetPlatformWindowInfo() override; std::optional<WindowInfo> GetPlatformWindowInfo() override;
void SetPlatformWindowTitle(std::string title) override; void SetPlatformWindowTitle(std::string title) override;

View File

@ -9,8 +9,8 @@
#include "common/file_system.h" #include "common/file_system.h"
#include "common/log.h" #include "common/log.h"
#include "core/achievements.h" #include "core/achievements.h"
#include "core/host.h"
#include "core/gpu/gpu_device.h" #include "core/gpu/gpu_device.h"
#include "core/host.h"
#include "core/memory_card.h" #include "core/memory_card.h"
#include "core/settings.h" #include "core/settings.h"
#include "core/system.h" #include "core/system.h"
@ -217,77 +217,33 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr
#endif #endif
bool MainWindow::createDisplay(bool fullscreen, bool render_to_main) std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main,
bool surfaceless, bool use_main_window_pos)
{ {
Log_DevPrintf("createDisplay(%u, %u)", static_cast<u32>(fullscreen), static_cast<u32>(render_to_main)); Log_DevPrintf(
"acquireRenderWindow() recreate=%s fullscreen=%s render_to_main=%s surfaceless=%s use_main_window_pos=%s",
const std::string fullscreen_mode(Host::GetBaseStringSettingValue("GPU", "FullscreenMode", "")); recreate_window ? "true" : "false", fullscreen ? "true" : "false", render_to_main ? "true" : "false",
const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty() && g_host_display->SupportsFullscreen()); surfaceless ? "true" : "false", use_main_window_pos ? "true" : "false");
createDisplayWidget(fullscreen, render_to_main, is_exclusive_fullscreen);
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
if (!wi.has_value())
{
QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
destroyDisplayWidget(true);
return false;
}
g_emu_thread->connectDisplaySignals(m_display_widget);
if (!g_host_display->CreateDevice(wi.value(), System::ShouldUseVSync()))
{
QMessageBox::critical(this, tr("Error"), tr("Failed to create host display device context."));
destroyDisplayWidget(true);
return false;
}
m_display_created = true;
if (is_exclusive_fullscreen)
setDisplayFullscreen(fullscreen_mode);
updateWindowTitle();
updateWindowState();
m_ui.actionStartFullscreenUI->setEnabled(false);
m_ui.actionStartFullscreenUI2->setEnabled(false);
updateDisplayWidgetCursor();
updateDisplayRelatedActions(true, render_to_main, fullscreen);
m_display_widget->setFocus();
g_host_display->DoneCurrent();
return true;
}
bool MainWindow::updateDisplay(bool fullscreen, bool render_to_main, bool surfaceless)
{
Log_DevPrintf("updateDisplay() fullscreen=%s render_to_main=%s surfaceless=%s", fullscreen ? "true" : "false",
render_to_main ? "true" : "false", surfaceless ? "true" : "false");
QWidget* container = QWidget* container =
m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget); m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
const bool is_fullscreen = isRenderingFullscreen(); const bool is_fullscreen = isRenderingFullscreen();
const bool is_rendering_to_main = isRenderingToMain(); const bool is_rendering_to_main = isRenderingToMain();
const std::string fullscreen_mode(Host::GetBaseStringSettingValue("GPU", "FullscreenMode", ""));
const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty() && g_host_display->SupportsFullscreen());
const bool changing_surfaceless = (!m_display_widget != surfaceless); const bool changing_surfaceless = (!m_display_widget != surfaceless);
if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main && !changing_surfaceless) if (m_display_created && !recreate_window && fullscreen == is_fullscreen && is_rendering_to_main == render_to_main &&
return true; !changing_surfaceless)
{
return m_display_widget ? m_display_widget->getWindowInfo() : WindowInfo();
}
// Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off. // Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off.
// .. except on Wayland, where everything tends to break if you don't recreate. // .. except on Wayland, where everything tends to break if you don't recreate.
const bool has_container = (m_display_container != nullptr); const bool has_container = (m_display_container != nullptr);
const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main); const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main);
if (!is_rendering_to_main && !render_to_main && !is_exclusive_fullscreen && has_container == needs_container && if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main &&
!needs_container && !changing_surfaceless) has_container == needs_container && !needs_container && !changing_surfaceless)
{ {
Log_DevPrintf("Toggling to %s without recreating surface", (fullscreen ? "fullscreen" : "windowed")); Log_DevPrintf("Toggling to %s without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
if (g_host_display->IsFullscreen())
g_host_display->SetFullscreen(false, 0, 0, 0.0f);
// since we don't destroy the display widget, we need to save it here // since we don't destroy the display widget, we need to save it here
if (!is_fullscreen && !is_rendering_to_main) if (!is_fullscreen && !is_rendering_to_main)
@ -305,53 +261,48 @@ bool MainWindow::updateDisplay(bool fullscreen, bool render_to_main, bool surfac
updateDisplayWidgetCursor(); updateDisplayWidgetCursor();
m_display_widget->setFocus(); m_display_widget->setFocus();
updateWindowState();
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
return true; return m_display_widget->getWindowInfo();
} }
g_host_display->DestroySurface(); destroyDisplayWidget(surfaceless);
m_display_created = true;
destroyDisplayWidget(surfaceless || fullscreen);
// if we're going to surfaceless, we're done here // if we're going to surfaceless, we're done here
if (surfaceless) if (surfaceless)
{ return WindowInfo();
updateWindowState();
updateDisplayRelatedActions(false, render_to_main, fullscreen);
return true;
}
createDisplayWidget(fullscreen, render_to_main, is_exclusive_fullscreen); createDisplayWidget(fullscreen, render_to_main, use_main_window_pos);
// we need the surface visible.. this might be able to be replaced with something else
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo(); std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
if (!wi.has_value()) if (!wi.has_value())
{ {
QMessageBox::critical(this, tr("Error"), tr("Failed to get new window info from widget")); QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
destroyDisplayWidget(true); destroyDisplayWidget(true);
return false; return std::nullopt;
} }
g_emu_thread->connectDisplaySignals(m_display_widget); g_emu_thread->connectDisplaySignals(m_display_widget);
if (!g_host_display->ChangeWindow(wi.value()))
Panic("Failed to recreate surface on new widget.");
if (is_exclusive_fullscreen)
setDisplayFullscreen(fullscreen_mode);
updateWindowTitle(); updateWindowTitle();
updateWindowState(); updateWindowState();
m_ui.actionStartFullscreenUI->setEnabled(false);
m_ui.actionStartFullscreenUI2->setEnabled(false);
updateDisplayWidgetCursor(); updateDisplayWidgetCursor();
updateDisplayRelatedActions(true, render_to_main, fullscreen); updateDisplayRelatedActions(true, render_to_main, fullscreen);
m_display_widget->setFocus(); m_display_widget->setFocus();
QSignalBlocker blocker(m_ui.actionFullscreen); return wi;
m_ui.actionFullscreen->setChecked(fullscreen);
return true;
} }
void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main, bool is_exclusive_fullscreen) void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main, bool use_main_window_pos)
{ {
// If we're rendering to main and were hidden (e.g. coming back from fullscreen), // If we're rendering to main and were hidden (e.g. coming back from fullscreen),
// make sure we're visible before trying to add ourselves. Otherwise Wayland breaks. // make sure we're visible before trying to add ourselves. Otherwise Wayland breaks.
@ -387,20 +338,21 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main, bool
// and positioning has no effect anyway. // and positioning has no effect anyway.
if (!s_use_central_widget) if (!s_use_central_widget)
{ {
if (isVisible()) if (isVisible() && g_emu_thread->shouldRenderToMain())
container->move(pos()); container->move(pos());
else else
restoreDisplayWindowGeometryFromConfig(); restoreDisplayWindowGeometryFromConfig();
} }
if (!is_exclusive_fullscreen) container->showFullScreen();
container->showFullScreen();
else
container->showNormal();
} }
else if (!render_to_main) else if (!render_to_main)
{ {
restoreDisplayWindowGeometryFromConfig(); // See lameland comment above.
if (use_main_window_pos && !s_use_central_widget)
container->move(pos());
else
restoreDisplayWindowGeometryFromConfig();
container->showNormal(); container->showNormal();
} }
else if (s_use_central_widget) else if (s_use_central_widget)
@ -419,31 +371,13 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main, bool
m_ui.mainContainer->setCurrentIndex(1); m_ui.mainContainer->setCurrentIndex(1);
} }
updateDisplayRelatedActions(true, render_to_main, fullscreen);
// We need the surface visible. // We need the surface visible.
QGuiApplication::sync(); QGuiApplication::sync();
} }
void MainWindow::setDisplayFullscreen(const std::string& fullscreen_mode) void MainWindow::displayResizeRequested(qint32 width, qint32 height)
{
u32 width, height;
float refresh_rate;
bool result = false;
if (GPUDevice::ParseFullscreenMode(fullscreen_mode, &width, &height, &refresh_rate))
{
result = g_host_display->SetFullscreen(true, width, height, refresh_rate);
if (result)
{
Host::AddOSDMessage(Host::TranslateStdString("OSDMessage", "Acquired exclusive fullscreen."), 10.0f);
}
else
{
Host::AddOSDMessage(Host::TranslateStdString("OSDMessage", "Failed to acquire exclusive fullscreen."), 10.0f);
}
}
}
void MainWindow::displaySizeRequested(qint32 width, qint32 height)
{ {
if (!m_display_widget) if (!m_display_widget)
return; return;
@ -465,7 +399,7 @@ void MainWindow::displaySizeRequested(qint32 width, qint32 height)
QtUtils::ResizePotentiallyFixedSizeWindow(this, width, height + extra_height); QtUtils::ResizePotentiallyFixedSizeWindow(this, width, height + extra_height);
} }
void MainWindow::destroyDisplay() void MainWindow::releaseRenderWindow()
{ {
// Now we can safely destroy the display window. // Now we can safely destroy the display window.
destroyDisplayWidget(true); destroyDisplayWidget(true);
@ -473,6 +407,8 @@ void MainWindow::destroyDisplay()
updateDisplayRelatedActions(false, false, false); updateDisplayRelatedActions(false, false, false);
m_ui.actionViewSystemDisplay->setEnabled(false);
m_ui.actionFullscreen->setEnabled(false);
m_ui.actionStartFullscreenUI->setEnabled(true); m_ui.actionStartFullscreenUI->setEnabled(true);
m_ui.actionStartFullscreenUI2->setEnabled(true); m_ui.actionStartFullscreenUI2->setEnabled(true);
} }
@ -1849,7 +1785,7 @@ bool MainWindow::isRenderingFullscreen() const
if (!g_host_display || !m_display_widget) if (!g_host_display || !m_display_widget)
return false; return false;
return getDisplayContainer()->isFullScreen() || g_host_display->IsFullscreen(); return getDisplayContainer()->isFullScreen();
} }
bool MainWindow::isRenderingToMain() const bool MainWindow::isRenderingToMain() const
@ -2003,12 +1939,11 @@ void MainWindow::connectSignals()
Qt::QueuedConnection); Qt::QueuedConnection);
connect(g_emu_thread, &EmuThread::errorReported, this, &MainWindow::reportError, Qt::BlockingQueuedConnection); connect(g_emu_thread, &EmuThread::errorReported, this, &MainWindow::reportError, Qt::BlockingQueuedConnection);
connect(g_emu_thread, &EmuThread::messageConfirmed, this, &MainWindow::confirmMessage, Qt::BlockingQueuedConnection); connect(g_emu_thread, &EmuThread::messageConfirmed, this, &MainWindow::confirmMessage, Qt::BlockingQueuedConnection);
connect(g_emu_thread, &EmuThread::createDisplayRequested, this, &MainWindow::createDisplay, connect(g_emu_thread, &EmuThread::onAcquireRenderWindowRequested, this, &MainWindow::acquireRenderWindow,
Qt::BlockingQueuedConnection); Qt::BlockingQueuedConnection);
connect(g_emu_thread, &EmuThread::destroyDisplayRequested, this, &MainWindow::destroyDisplay); connect(g_emu_thread, &EmuThread::onReleaseRenderWindowRequested, this, &MainWindow::releaseRenderWindow);
connect(g_emu_thread, &EmuThread::updateDisplayRequested, this, &MainWindow::updateDisplay, connect(g_emu_thread, &EmuThread::onResizeRenderWindowRequested, this, &MainWindow::displayResizeRequested,
Qt::BlockingQueuedConnection); Qt::BlockingQueuedConnection);
connect(g_emu_thread, &EmuThread::displaySizeRequested, this, &MainWindow::displaySizeRequested);
connect(g_emu_thread, &EmuThread::focusDisplayWidgetRequested, this, &MainWindow::focusDisplayWidget); connect(g_emu_thread, &EmuThread::focusDisplayWidgetRequested, this, &MainWindow::focusDisplayWidget);
connect(g_emu_thread, &EmuThread::systemStarting, this, &MainWindow::onSystemStarting); connect(g_emu_thread, &EmuThread::systemStarting, this, &MainWindow::onSystemStarting);
connect(g_emu_thread, &EmuThread::systemStarted, this, &MainWindow::onSystemStarted); connect(g_emu_thread, &EmuThread::systemStarted, this, &MainWindow::onSystemStarted);

View File

@ -10,8 +10,8 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include "controllersettingsdialog.h"
#include "common/window_info.h" #include "common/window_info.h"
#include "controllersettingsdialog.h"
#include "core/types.h" #include "core/types.h"
#include "displaywidget.h" #include "displaywidget.h"
#include "settingsdialog.h" #include "settingsdialog.h"
@ -110,10 +110,11 @@ public Q_SLOTS:
private Q_SLOTS: private Q_SLOTS:
void reportError(const QString& title, const QString& message); void reportError(const QString& title, const QString& message);
bool confirmMessage(const QString& title, const QString& message); bool confirmMessage(const QString& title, const QString& message);
bool createDisplay(bool fullscreen, bool render_to_main);
bool updateDisplay(bool fullscreen, bool render_to_main, bool surfaceless); std::optional<WindowInfo> acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main,
void displaySizeRequested(qint32 width, qint32 height); bool surfaceless, bool use_main_window_pos);
void destroyDisplay(); void displayResizeRequested(qint32 width, qint32 height);
void releaseRenderWindow();
void focusDisplayWidget(); void focusDisplayWidget();
void onMouseModeRequested(bool relative_mode, bool hide_cursor); void onMouseModeRequested(bool relative_mode, bool hide_cursor);
@ -208,11 +209,10 @@ private:
void restoreGeometryFromConfig(); void restoreGeometryFromConfig();
void saveDisplayWindowGeometryToConfig(); void saveDisplayWindowGeometryToConfig();
void restoreDisplayWindowGeometryFromConfig(); void restoreDisplayWindowGeometryFromConfig();
void createDisplayWidget(bool fullscreen, bool render_to_main, bool is_exclusive_fullscreen); void createDisplayWidget(bool fullscreen, bool render_to_main, bool use_main_window_pos);
void destroyDisplayWidget(bool show_game_list); void destroyDisplayWidget(bool show_game_list);
void updateDisplayWidgetCursor(); void updateDisplayWidgetCursor();
void updateDisplayRelatedActions(bool has_surface, bool render_to_main, bool fullscreen); void updateDisplayRelatedActions(bool has_surface, bool render_to_main, bool fullscreen);
void setDisplayFullscreen(const std::string& fullscreen_mode);
SettingsDialog* getSettingsDialog(); SettingsDialog* getSettingsDialog();
void doSettings(const char* category = nullptr); void doSettings(const char* category = nullptr);

View File

@ -100,7 +100,9 @@ static bool s_start_fullscreen_ui_fullscreen = false;
EmuThread* g_emu_thread; EmuThread* g_emu_thread;
GDBServer* g_gdb_server; GDBServer* g_gdb_server;
EmuThread::EmuThread(QThread* ui_thread) : QThread(), m_ui_thread(ui_thread) {} EmuThread::EmuThread(QThread* ui_thread) : QThread(), m_ui_thread(ui_thread)
{
}
EmuThread::~EmuThread() = default; EmuThread::~EmuThread() = default;
@ -329,24 +331,31 @@ void EmuThread::setInitialState(std::optional<bool> override_fullscreen)
m_is_surfaceless = false; m_is_surfaceless = false;
} }
void EmuThread::checkForSettingsChanges(const Settings& old_settings)
{
if (g_main_window)
{
QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection);
updatePerformanceCounters();
}
if (g_host_display)
{
const bool render_to_main = shouldRenderToMain();
if (m_is_rendering_to_main != render_to_main)
{
m_is_rendering_to_main = render_to_main;
g_host_display->UpdateWindow();
}
}
}
void Host::CheckForSettingsChanges(const Settings& old_settings) void Host::CheckForSettingsChanges(const Settings& old_settings)
{ {
CommonHost::CheckForSettingsChanges(old_settings); CommonHost::CheckForSettingsChanges(old_settings);
g_emu_thread->checkForSettingsChanges(old_settings); g_emu_thread->checkForSettingsChanges(old_settings);
} }
void EmuThread::checkForSettingsChanges(const Settings& old_settings)
{
const bool render_to_main = shouldRenderToMain();
if (m_is_rendering_to_main != render_to_main)
{
m_is_rendering_to_main = render_to_main;
updateDisplayState();
}
QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection);
}
void EmuThread::setDefaultSettings(bool system /* = true */, bool controller /* = true */) void EmuThread::setDefaultSettings(bool system /* = true */, bool controller /* = true */)
{ {
if (isOnThread()) if (isOnThread())
@ -392,7 +401,7 @@ void Host::RequestResizeHostDisplay(s32 new_window_width, s32 new_window_height)
if (g_emu_thread->isFullscreen()) if (g_emu_thread->isFullscreen())
return; return;
emit g_emu_thread->displaySizeRequested(new_window_width, new_window_height); emit g_emu_thread->onResizeRenderWindowRequested(new_window_width, new_window_height);
} }
void EmuThread::applySettings(bool display_osd_messages /* = false */) void EmuThread::applySettings(bool display_osd_messages /* = false */)
@ -449,7 +458,7 @@ void EmuThread::startFullscreenUI()
setInitialState(s_start_fullscreen_ui_fullscreen ? std::optional<bool>(true) : std::optional<bool>()); setInitialState(s_start_fullscreen_ui_fullscreen ? std::optional<bool>(true) : std::optional<bool>());
m_run_fullscreen_ui = true; m_run_fullscreen_ui = true;
if (!acquireHostDisplay(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer))) if (!Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer)))
{ {
m_run_fullscreen_ui = false; m_run_fullscreen_ui = false;
return; return;
@ -481,7 +490,8 @@ void EmuThread::stopFullscreenUI()
return; return;
m_run_fullscreen_ui = false; m_run_fullscreen_ui = false;
releaseHostDisplay(); Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
} }
void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params) void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
@ -605,33 +615,9 @@ void EmuThread::onDisplayWindowMouseWheelEvent(const QPoint& delta_angle)
InputManager::UpdatePointerRelativeDelta(0, InputPointerAxis::WheelY, dy); InputManager::UpdatePointerRelativeDelta(0, InputPointerAxis::WheelY, dy);
} }
void EmuThread::onDisplayWindowResized(int width, int height) void EmuThread::onDisplayWindowResized(int width, int height, float scale)
{ {
// this can be null if it was destroyed and the main thread is late catching up Host::ResizeDisplayWindow(width, height, scale);
if (!g_host_display)
return;
Log_DevPrintf("Display window resized to %dx%d", width, height);
g_host_display->ResizeWindow(width, height);
ImGuiManager::WindowResized();
System::HostDisplayResized();
// re-render the display, since otherwise it will be out of date and stretched if paused
if (System::IsValid())
{
if (m_is_exclusive_fullscreen && !g_host_display->IsFullscreen())
{
// we lost exclusive fullscreen, switch to borderless
Host::AddOSDMessage(Host::TranslateStdString("OSDMessage", "Lost exclusive fullscreen."), 10.0f);
m_is_exclusive_fullscreen = false;
m_is_fullscreen = false;
m_lost_exclusive_fullscreen = true;
}
// force redraw if we're paused
if (!System::IsRunning() && !FullscreenUI::HasActiveWindow())
renderDisplay(false);
}
} }
void EmuThread::redrawDisplayWindow() void EmuThread::redrawDisplayWindow()
@ -656,14 +642,15 @@ void EmuThread::toggleFullscreen()
return; return;
} }
setFullscreen(!m_is_fullscreen); setFullscreen(!m_is_fullscreen, true);
} }
void EmuThread::setFullscreen(bool fullscreen) void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main)
{ {
if (!isOnThread()) if (!isOnThread())
{ {
QMetaObject::invokeMethod(this, "setFullscreen", Qt::QueuedConnection, Q_ARG(bool, fullscreen)); QMetaObject::invokeMethod(this, "setFullscreen", Qt::QueuedConnection, Q_ARG(bool, fullscreen),
Q_ARG(bool, allow_render_to_main));
return; return;
} }
@ -671,7 +658,8 @@ void EmuThread::setFullscreen(bool fullscreen)
return; return;
m_is_fullscreen = fullscreen; m_is_fullscreen = fullscreen;
updateDisplayState(); m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain();
Host::UpdateDisplayWindow();
} }
bool Host::IsFullscreen() bool Host::IsFullscreen()
@ -681,7 +669,7 @@ bool Host::IsFullscreen()
void Host::SetFullscreen(bool enabled) void Host::SetFullscreen(bool enabled)
{ {
g_emu_thread->setFullscreen(enabled); g_emu_thread->setFullscreen(enabled, true);
} }
void EmuThread::setSurfaceless(bool surfaceless) void EmuThread::setSurfaceless(bool surfaceless)
@ -696,7 +684,7 @@ void EmuThread::setSurfaceless(bool surfaceless)
return; return;
m_is_surfaceless = surfaceless; m_is_surfaceless = surfaceless;
updateDisplayState(); Host::UpdateDisplayWindow();
} }
void EmuThread::requestDisplaySize(float scale) void EmuThread::requestDisplaySize(float scale)
@ -713,52 +701,25 @@ void EmuThread::requestDisplaySize(float scale)
System::RequestDisplaySize(scale); System::RequestDisplaySize(scale);
} }
bool EmuThread::acquireHostDisplay(RenderAPI api) std::optional<WindowInfo> EmuThread::acquireRenderWindow(bool recreate_window)
{ {
if (g_host_display) DebugAssert(g_host_display);
{ u32 fs_width, fs_height;
if (g_host_display->GetRenderAPI() == api) float fs_refresh_rate;
{ m_is_exclusive_fullscreen = (m_is_fullscreen && g_host_display->SupportsExclusiveFullscreen() &&
// current is fine GPUDevice::GetRequestedExclusiveFullscreenMode(&fs_width, &fs_height, &fs_refresh_rate));
return true;
}
// otherwise we need to switch const bool window_fullscreen = m_is_fullscreen && !m_is_exclusive_fullscreen;
releaseHostDisplay(); const bool render_to_main = !m_is_exclusive_fullscreen && !window_fullscreen && m_is_rendering_to_main;
} const bool use_main_window_pos = m_is_exclusive_fullscreen && shouldRenderToMain();
g_host_display = Host::CreateDisplayForAPI(api); return emit onAcquireRenderWindowRequested(recreate_window, window_fullscreen, render_to_main, m_is_surfaceless,
if (!g_host_display) use_main_window_pos);
return false; }
if (!createDisplayRequested(m_is_fullscreen, m_is_rendering_to_main)) void EmuThread::releaseRenderWindow()
{ {
emit destroyDisplayRequested(); emit onReleaseRenderWindowRequested();
g_host_display.reset();
return false;
}
if (!g_host_display->MakeCurrent() || !g_host_display->SetupDevice() || !ImGuiManager::Initialize() ||
!CommonHost::CreateHostDisplayResources())
{
ImGuiManager::Shutdown();
CommonHost::ReleaseHostDisplayResources();
g_host_display.reset();
emit destroyDisplayRequested();
return false;
}
m_is_exclusive_fullscreen = g_host_display->IsFullscreen();
if (m_run_fullscreen_ui && !FullscreenUI::Initialize())
{
Log_ErrorPrint("Failed to initialize fullscreen UI");
releaseHostDisplay();
m_run_fullscreen_ui = false;
return false;
}
return true;
} }
void EmuThread::connectDisplaySignals(DisplayWidget* widget) void EmuThread::connectDisplaySignals(DisplayWidget* widget)
@ -774,45 +735,6 @@ void EmuThread::connectDisplaySignals(DisplayWidget* widget)
connect(widget, &DisplayWidget::windowMouseWheelEvent, this, &EmuThread::onDisplayWindowMouseWheelEvent); connect(widget, &DisplayWidget::windowMouseWheelEvent, this, &EmuThread::onDisplayWindowMouseWheelEvent);
} }
void EmuThread::updateDisplayState()
{
if (!g_host_display)
return;
// this expects the context to get moved back to us afterwards
g_host_display->DoneCurrent();
updateDisplayRequested(m_is_fullscreen, m_is_rendering_to_main && !m_is_fullscreen, m_is_surfaceless);
if (!g_host_display->MakeCurrent())
Panic("Failed to make device context current after updating");
m_is_exclusive_fullscreen = g_host_display->IsFullscreen();
ImGuiManager::WindowResized();
System::HostDisplayResized();
if (!System::IsShutdown())
{
System::UpdateSoftwareCursor();
if (!FullscreenUI::IsInitialized() || System::IsPaused())
redrawDisplayWindow();
}
System::UpdateSpeedLimiterState();
}
void EmuThread::releaseHostDisplay()
{
if (!g_host_display)
return;
CommonHost::ReleaseHostDisplayResources();
ImGuiManager::Shutdown();
g_host_display.reset();
emit destroyDisplayRequested();
m_is_fullscreen = false;
}
void Host::OnSystemStarting() void Host::OnSystemStarting()
{ {
CommonHost::OnSystemStarting(); CommonHost::OnSystemStarting();
@ -1610,40 +1532,34 @@ void Host::CommitBaseSettingChanges()
QtHost::QueueSettingsSave(); QtHost::QueueSettingsSave();
} }
bool Host::AcquireHostDisplay(RenderAPI api) std::optional<WindowInfo> Host::AcquireRenderWindow(bool recreate_window)
{ {
return g_emu_thread->acquireHostDisplay(api); return g_emu_thread->acquireRenderWindow(recreate_window);
} }
void Host::ReleaseHostDisplay() void Host::ReleaseRenderWindow()
{ {
if (g_emu_thread->isRunningFullscreenUI()) g_emu_thread->releaseRenderWindow();
{
// keep display alive when running fsui
return;
}
g_emu_thread->releaseHostDisplay();
} }
void EmuThread::updatePerformanceCounters() void EmuThread::updatePerformanceCounters()
{ {
GPURenderer renderer = GPURenderer::Count; const RenderAPI render_api = g_host_display ? g_host_display->GetRenderAPI() : RenderAPI::None;
const bool hardware_renderer = g_gpu && g_gpu->IsHardwareRenderer();
u32 render_width = 0; u32 render_width = 0;
u32 render_height = 0; u32 render_height = 0;
if (g_gpu) if (g_gpu)
{
// TODO: Fix renderer type
renderer = g_gpu->IsHardwareRenderer() ? GPURenderer::HardwareOpenGL : GPURenderer::Software;
std::tie(render_width, render_height) = g_gpu->GetEffectiveDisplayResolution(); std::tie(render_width, render_height) = g_gpu->GetEffectiveDisplayResolution();
}
if (renderer != m_last_renderer) if (render_api != m_last_render_api || hardware_renderer != m_last_hardware_renderer)
{ {
const QString renderer_str = hardware_renderer ? QString::fromUtf8(GPUDevice::RenderAPIToString(render_api)) :
qApp->translate("GPURenderer", "Software");
QMetaObject::invokeMethod(g_main_window->getStatusRendererWidget(), "setText", Qt::QueuedConnection, QMetaObject::invokeMethod(g_main_window->getStatusRendererWidget(), "setText", Qt::QueuedConnection,
Q_ARG(const QString&, QString::fromUtf8(Settings::GetRendererName(renderer)))); Q_ARG(const QString&, renderer_str));
m_last_renderer = renderer; m_last_render_api = render_api;
m_last_hardware_renderer = hardware_renderer;
} }
if (render_width != m_last_render_width || render_height != m_last_render_height) if (render_width != m_last_render_width || render_height != m_last_render_height)
{ {
@ -1680,7 +1596,8 @@ void EmuThread::resetPerformanceCounters()
m_last_video_fps = std::numeric_limits<float>::infinity(); m_last_video_fps = std::numeric_limits<float>::infinity();
m_last_render_width = std::numeric_limits<u32>::max(); m_last_render_width = std::numeric_limits<u32>::max();
m_last_render_height = std::numeric_limits<u32>::max(); m_last_render_height = std::numeric_limits<u32>::max();
m_last_renderer = GPURenderer::Count; m_last_render_api = RenderAPI::None;
m_last_hardware_renderer = false;
QString blank; QString blank;
QMetaObject::invokeMethod(g_main_window->getStatusRendererWidget(), "setText", Qt::QueuedConnection, QMetaObject::invokeMethod(g_main_window->getStatusRendererWidget(), "setText", Qt::QueuedConnection,

View File

@ -95,9 +95,9 @@ public:
ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; } ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; }
ALWAYS_INLINE bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; } ALWAYS_INLINE bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
bool acquireHostDisplay(RenderAPI api); std::optional<WindowInfo> acquireRenderWindow(bool recreate_window);
void connectDisplaySignals(DisplayWidget* widget); void connectDisplaySignals(DisplayWidget* widget);
void releaseHostDisplay(); void releaseRenderWindow();
void renderDisplay(bool skip_present); void renderDisplay(bool skip_present);
void startBackgroundControllerPollTimer(); void startBackgroundControllerPollTimer();
@ -132,11 +132,11 @@ Q_SIGNALS:
void systemPaused(); void systemPaused();
void systemResumed(); void systemResumed();
void gameListRefreshed(); void gameListRefreshed();
bool createDisplayRequested(bool fullscreen, bool render_to_main); std::optional<WindowInfo> onAcquireRenderWindowRequested(bool recreate_window, bool fullscreen, bool render_to_main,
bool updateDisplayRequested(bool fullscreen, bool render_to_main, bool surfaceless); bool surfaceless, bool use_main_window_pos);
void displaySizeRequested(qint32 width, qint32 height); void onResizeRenderWindowRequested(qint32 width, qint32 height);
void onReleaseRenderWindowRequested();
void focusDisplayWidgetRequested(); void focusDisplayWidgetRequested();
void destroyDisplayRequested();
void runningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title); void runningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title);
void inputProfileLoaded(); void inputProfileLoaded();
void mouseModeRequested(bool relative, bool hide_cursor); void mouseModeRequested(bool relative, bool hide_cursor);
@ -180,7 +180,7 @@ public Q_SLOTS:
void saveScreenshot(); void saveScreenshot();
void redrawDisplayWindow(); void redrawDisplayWindow();
void toggleFullscreen(); void toggleFullscreen();
void setFullscreen(bool fullscreen); void setFullscreen(bool fullscreen, bool allow_render_to_main);
void setSurfaceless(bool surfaceless); void setSurfaceless(bool surfaceless);
void requestDisplaySize(float scale); void requestDisplaySize(float scale);
void loadCheatList(const QString& filename); void loadCheatList(const QString& filename);
@ -194,7 +194,7 @@ private Q_SLOTS:
void onDisplayWindowMouseMoveEvent(bool relative, float x, float y); void onDisplayWindowMouseMoveEvent(bool relative, float x, float y);
void onDisplayWindowMouseButtonEvent(int button, bool pressed); void onDisplayWindowMouseButtonEvent(int button, bool pressed);
void onDisplayWindowMouseWheelEvent(const QPoint& delta_angle); void onDisplayWindowMouseWheelEvent(const QPoint& delta_angle);
void onDisplayWindowResized(int width, int height); void onDisplayWindowResized(int width, int height, float scale);
void onDisplayWindowKeyEvent(int key, bool pressed); void onDisplayWindowKeyEvent(int key, bool pressed);
void onDisplayWindowTextEntered(const QString& text); void onDisplayWindowTextEntered(const QString& text);
void doBackgroundControllerPoll(); void doBackgroundControllerPoll();
@ -210,7 +210,6 @@ private:
void createBackgroundControllerPollTimer(); void createBackgroundControllerPollTimer();
void destroyBackgroundControllerPollTimer(); void destroyBackgroundControllerPollTimer();
void setInitialState(std::optional<bool> override_fullscreen); void setInitialState(std::optional<bool> override_fullscreen);
void updateDisplayState();
QThread* m_ui_thread; QThread* m_ui_thread;
QSemaphore m_started_semaphore; QSemaphore m_started_semaphore;
@ -233,7 +232,8 @@ private:
float m_last_video_fps = std::numeric_limits<float>::infinity(); float m_last_video_fps = std::numeric_limits<float>::infinity();
u32 m_last_render_width = std::numeric_limits<u32>::max(); u32 m_last_render_width = std::numeric_limits<u32>::max();
u32 m_last_render_height = std::numeric_limits<u32>::max(); u32 m_last_render_height = std::numeric_limits<u32>::max();
GPURenderer m_last_renderer = GPURenderer::Count; RenderAPI m_last_render_api = RenderAPI::None;
bool m_last_hardware_renderer = false;
}; };
extern EmuThread* g_emu_thread; extern EmuThread* g_emu_thread;

View File

@ -129,14 +129,86 @@ void CommonHost::PumpMessagesOnCPUThread()
#endif #endif
} }
bool CommonHost::CreateHostDisplayResources() bool Host::CreateGPUDevice(RenderAPI api)
{ {
DebugAssert(!g_host_display);
Log_InfoPrintf("Trying to create a %s GPU device...", GPUDevice::RenderAPIToString(api));
g_host_display = GPUDevice::CreateDeviceForAPI(api);
// TODO: option to disable shader cache
// TODO: FSUI should always use vsync..
const bool vsync = System::IsValid() ? System::ShouldUseVSync() : g_settings.video_sync_enabled;
if (!g_host_display ||
!g_host_display->Create(g_settings.gpu_adapter, EmuFolders::Cache, g_settings.gpu_use_debug_device, vsync))
{
Log_ErrorPrintf("Failed to initialize GPU device.");
if (g_host_display)
g_host_display->Destroy();
g_host_display.reset();
return false;
}
if (!ImGuiManager::Initialize())
{
Log_ErrorPrintf("Failed to initialize ImGuiManager.");
g_host_display->Destroy();
g_host_display.reset();
return false;
}
return true; return true;
} }
void CommonHost::ReleaseHostDisplayResources() void Host::UpdateDisplayWindow()
{ {
if (!g_host_display)
return;
if (!g_host_display->UpdateWindow())
{
Host::ReportErrorAsync("Error", "Failed to change window after update. The log may contain more information.");
return;
}
ImGuiManager::WindowResized();
// If we're paused, re-present the current frame at the new window size.
if (System::IsValid() && System::IsPaused())
RenderDisplay(false);
}
void Host::ResizeDisplayWindow(s32 width, s32 height, float scale)
{
if (!g_host_display)
return;
Log_DevPrintf("Display window resized to %dx%d", width, height);
g_host_display->ResizeWindow(width, height, scale);
ImGuiManager::WindowResized();
// If we're paused, re-present the current frame at the new window size.
if (System::IsValid())
{
if (System::IsPaused())
RenderDisplay(false);
System::HostDisplayResized();
}
}
void Host::ReleaseGPUDevice()
{
if (!g_host_display)
return;
SaveStateSelectorUI::DestroyTextures(); SaveStateSelectorUI::DestroyTextures();
ImGuiManager::Shutdown();
Log_InfoPrintf("Destroying %s GPU device...", GPUDevice::RenderAPIToString(g_host_display->GetRenderAPI()));
g_host_display->Destroy();
g_host_display.reset();
} }
#ifndef __ANDROID__ #ifndef __ANDROID__

View File

@ -33,8 +33,6 @@ void OnSystemPaused();
void OnSystemResumed(); void OnSystemResumed();
void OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name); void OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name);
void PumpMessagesOnCPUThread(); void PumpMessagesOnCPUThread();
bool CreateHostDisplayResources();
void ReleaseHostDisplayResources();
/// Returns the time elapsed in the current play session. /// Returns the time elapsed in the current play session.
u64 GetSessionPlayedTime(); u64 GetSessionPlayedTime();