diff --git a/src/common/align.h b/src/common/align.h index 831008061..1f72e7dde 100644 --- a/src/common/align.h +++ b/src/common/align.h @@ -61,20 +61,20 @@ constexpr T PreviousPow2(T value) return value - (value >> 1); } - ALWAYS_INLINE static void* AlignedMalloc(size_t size, size_t alignment) { #ifdef _MSC_VER return _aligned_malloc(size, alignment); #else // Unaligned sizes are slow on macOS. - #ifdef __APPLE__ +#ifdef __APPLE__ if (IsPow2(alignment)) size = (size + alignment - 1) & ~(alignment - 1); - #endif +#endif void* ret = nullptr; posix_memalign(&ret, alignment, size); return ret; +#endif } ALWAYS_INLINE static void AlignedFree(void* ptr) @@ -86,5 +86,4 @@ ALWAYS_INLINE static void AlignedFree(void* ptr) #endif } -#endif } // namespace Common diff --git a/src/core/gpu.h b/src/core/gpu.h index bc5c0d64d..70aa54855 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -159,29 +159,7 @@ public: float ComputeVerticalFrequency() const; float GetDisplayAspectRatio() const; -#ifdef _WIN32 - // gpu_hw_d3d11.cpp - static std::unique_ptr CreateHardwareD3D11Renderer(); - - // gpu_hw_d3d12.cpp - static std::unique_ptr CreateHardwareD3D12Renderer(); -#endif - -#ifdef __APPLE__ - static std::unique_ptr CreateHardwareMetalRenderer(); -#endif - -#ifdef WITH_OPENGL - // gpu_hw_opengl.cpp - static std::unique_ptr CreateHardwareOpenGLRenderer(); -#endif - -#ifdef WITH_VULKAN - // gpu_hw_vulkan.cpp - static std::unique_ptr CreateHardwareVulkanRenderer(); -#endif - - // gpu_sw.cpp + static std::unique_ptr CreateHardwareRenderer(); static std::unique_ptr CreateSoftwareRenderer(); // Converts window coordinates into horizontal ticks and scanlines. Returns false if out of range. Used for lightguns. diff --git a/src/core/gpu/d3d11_device.cpp b/src/core/gpu/d3d11_device.cpp index dabaed200..1e24bf70b 100644 --- a/src/core/gpu/d3d11_device.cpp +++ b/src/core/gpu/d3d11_device.cpp @@ -3,7 +3,6 @@ #include "d3d11_device.h" #include "../host_settings.h" -#include "../settings.h" #include "../shader_cache_version.h" #include "common/align.h" @@ -11,6 +10,7 @@ #include "common/file_system.h" #include "common/log.h" #include "common/path.h" +#include "common/rectangle.h" #include "common/string_util.h" #include "fmt/format.h" @@ -48,6 +48,12 @@ static void SetD3DDebugObjectName(ID3D11DeviceChild* obj, const std::string_view #endif } +// TODO: FIXME +namespace Host { +extern bool IsFullscreen(); +extern void SetFullscreen(bool enabled); +} + D3D11StreamBuffer::D3D11StreamBuffer() : m_size(0), m_position(0) { } @@ -155,13 +161,8 @@ D3D11Device::D3D11Device() = default; D3D11Device::~D3D11Device() { - // TODO: Make virtual Destroy() method instead due to order of shit.. - DestroyStagingBuffer(); - DestroyResources(); - DestroyBuffers(); - DestroySurface(); - m_context.Reset(); - m_device.Reset(); + // Should all be torn down by now. + Assert(!m_device); } 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) { - if (m_swap_chain && IsFullscreen()) + if (m_swap_chain && m_is_exclusive_fullscreen) { DXGI_SWAP_CHAIN_DESC desc; 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; } -bool D3D11Device::CreateDevice(const WindowInfo& wi, bool vsync) +bool D3D11Device::CreateDevice(const std::string_view& adapter, bool debug_device) { UINT create_flags = 0; - if (g_settings.gpu_use_debug_device) + if (debug_device) create_flags |= D3D11_CREATE_DEVICE_DEBUG; ComPtr temp_dxgi_factory; @@ -358,18 +359,19 @@ bool D3D11Device::CreateDevice(const WindowInfo& wi, bool vsync) } u32 adapter_index; - if (!g_settings.gpu_adapter.empty()) + if (!adapter.empty()) { AdapterAndModeList adapter_info(GetAdapterAndModeList(temp_dxgi_factory.Get())); for (adapter_index = 0; adapter_index < static_cast(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; } if (adapter_index == static_cast(adapter_info.adapter_names.size())) { - Log_WarningPrintf("Could not find adapter '%s', using first (%s)", g_settings.gpu_adapter.c_str(), - adapter_info.adapter_names[0].c_str()); + // TODO: Log_Fmt + Log_WarningPrintf( + fmt::format("Could not find adapter '{}', using first ({})", adapter, adapter_info.adapter_names[0]).c_str()); adapter_index = 0; } } @@ -408,7 +410,7 @@ bool D3D11Device::CreateDevice(const WindowInfo& wi, bool vsync) return false; } - if (g_settings.gpu_use_debug_device && IsDebuggerPresent()) + if (debug_device && IsDebuggerPresent()) { ComPtr info; hr = m_device.As(&info); @@ -420,7 +422,7 @@ bool D3D11Device::CreateDevice(const WindowInfo& wi, bool vsync) } #ifdef _DEBUG - if (g_settings.gpu_use_debug_device) + if (debug_device) m_context.As(&m_annotation); #endif @@ -452,27 +454,21 @@ bool D3D11Device::CreateDevice(const WindowInfo& wi, bool vsync) SetFeatures(); - m_window_info = wi; - m_vsync_enabled = vsync; - - if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr)) - { - m_window_info = {}; + if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain()) + return false; + + if (!CreateBuffers()) return false; - } return true; } -bool D3D11Device::SetupDevice() +void D3D11Device::DestroyDevice() { - if (!GPUDevice::SetupDevice()) - return false; - - if (!CreateBuffers() || !CreateResources()) - return false; - - return true; + DestroyStagingBuffer(); + DestroyBuffers(); + m_context.Reset(); + m_device.Reset(); } 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 client_rc_vec(window_rect.left, window_rect.top, window_rect.right, window_rect.bottom); -bool D3D11Device::DoneCurrent() -{ - return true; -} - -bool D3D11Device::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode) -{ + // The window might be on a different adapter to which we are rendering.. so we have to enumerate them all. HRESULT hr; + ComPtr first_output, intersecting_output; + + for (u32 adapter_index = 0; !intersecting_output; adapter_index++) + { + ComPtr 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 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 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(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) return false; - m_using_flip_model_swap_chain = fullscreen_mode || !Host::GetBoolSettingValue("Display", "UseBlitSwapChain", false); - const HWND window_hwnd = reinterpret_cast(m_window_info.window_handle); RECT client_rc{}; GetClientRect(window_hwnd, &client_rc); - const u32 width = static_cast(client_rc.right - client_rc.left); - const u32 height = static_cast(client_rc.bottom - client_rc.top); - DXGI_SWAP_CHAIN_DESC swap_chain_desc = {}; - swap_chain_desc.BufferDesc.Width = width; - swap_chain_desc.BufferDesc.Height = height; - swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + DXGI_MODE_DESC fullscreen_mode = {}; + ComPtr fullscreen_output; + if (Host::IsFullscreen()) + { + 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(client_rc.right - client_rc.left); + swap_chain_desc.Height = static_cast(client_rc.bottom - client_rc.top); + swap_chain_desc.Format = swap_chain_format; 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.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; - 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) 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; - swap_chain_desc.Windowed = FALSE; - swap_chain_desc.BufferDesc = *fullscreen_mode; + DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc; + DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {}; + + 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, - swap_chain_desc.BufferDesc.Height, m_using_flip_model_swap_chain ? "flip-discard" : "discard", - swap_chain_desc.Windowed ? "windowed" : "full-screen"); + if (!m_is_exclusive_fullscreen) + { + 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) { 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_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)) { - Log_ErrorPrintf("CreateSwapChain failed: 0x%08X", hr); + Log_ErrorPrintf("CreateSwapChainForHwnd failed: 0x%08X", hr); return false; } } - ComPtr dxgi_factory; - hr = m_swap_chain->GetParent(IID_PPV_ARGS(dxgi_factory.GetAddressOf())); - if (SUCCEEDED(hr)) + hr = m_dxgi_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES); + if (FAILED(hr)) + Log_WarningPrintf("MakeWindowAssociation() to disable ALT+ENTER failed"); + + if (!CreateSwapChainRTV()) { - hr = dxgi_factory->MakeWindowAssociation(swap_chain_desc.OutputWindow, DXGI_MWA_NO_WINDOW_CHANGES); - if (FAILED(hr)) - Log_WarningPrintf("MakeWindowAssociation() to disable ALT+ENTER failed"); + DestroySwapChain(); + return false; } - 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() @@ -607,16 +710,17 @@ bool D3D11Device::CreateSwapChainRTV() CD3D11_RENDER_TARGET_VIEW_DESC rtv_desc(D3D11_RTV_DIMENSION_TEXTURE2D, backbuffer_desc.Format, 0, 0, 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)) { Log_ErrorPrintf("CreateRenderTargetView for swap chain failed: 0x%08X", hr); + m_swap_chain_rtv.Reset(); return false; } m_window_info.surface_width = backbuffer_desc.Width; 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) { @@ -637,22 +741,41 @@ bool D3D11Device::CreateSwapChainRTV() return true; } -bool D3D11Device::ChangeWindow(const WindowInfo& new_wi) +void D3D11Device::DestroySwapChain() { - DestroySurface(); + if (!m_swap_chain) + return; - m_window_info = new_wi; - return CreateSwapChain(nullptr); + m_swap_chain_rtv.Reset(); + + // 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() { - m_window_info.SetSurfaceless(); - if (IsFullscreen()) - SetFullscreen(false, 0, 0, 0.0f); - - m_swap_chain_rtv.Reset(); - m_swap_chain.Reset(); + DestroySwapChain(); } 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" : ""); } -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; + m_window_info.surface_scale = new_window_scale; + + if (m_window_info.surface_width == static_cast(new_window_width) && + m_window_info.surface_height == static_cast(new_window_height)) + { + return; + } + m_swap_chain_rtv.Reset(); 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"); } -bool D3D11Device::SupportsFullscreen() const +bool D3D11Device::SupportsExclusiveFullscreen() const { 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(¤t_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(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() { 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) 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. // 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 @@ -791,7 +864,7 @@ bool D3D11Device::BeginPresent(bool skip_present) if (m_vsync_enabled && m_gpu_timing_enabled) 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->OMSetRenderTargets(1, m_swap_chain_rtv.GetAddressOf(), nullptr); m_current_framebuffer = nullptr; @@ -1367,7 +1440,7 @@ std::unique_ptr D3D11Device::CreateShaderFromSource(GPUShaderStage st Log_ErrorPrintf("Failed to compile '%s':\n%s", target, error_string.c_str()); 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) { 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*/) { if (!m_dynamic || (x + width) > GetMipWidth(level) || (y + height) > GetMipHeight(level) || layer > m_layers || - level > m_levels) + level > m_levels) { return false; } diff --git a/src/core/gpu/d3d11_device.h b/src/core/gpu/d3d11_device.h index 34fd2f89f..a153e794e 100644 --- a/src/core/gpu/d3d11_device.h +++ b/src/core/gpu/d3d11_device.h @@ -1,15 +1,17 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once + +#include "gpu_device.h" +#include "postprocessing_chain.h" + #include "common/timer.h" #include "common/window_info.h" #include "common/windows_headers.h" -#include "gpu_device.h" -#include "postprocessing_chain.h" + #include -#include +#include #include #include #include @@ -258,28 +260,25 @@ public: template using ComPtr = Microsoft::WRL::ComPtr; + D3D11Device(); + ~D3D11Device(); + ALWAYS_INLINE static D3D11Device& GetInstance() { return *static_cast(g_host_display.get()); } ALWAYS_INLINE static ID3D11Device* GetD3DDevice() { return GetInstance().m_device.Get(); } ALWAYS_INLINE static ID3D11DeviceContext* GetD3DContext() { return GetInstance().m_context.Get(); } - D3D11Device(); - ~D3D11Device(); + // returns the fullscreen mode to use for the specified dimensions + 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; 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; + bool UpdateWindow() override; + void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; + bool SupportsExclusiveFullscreen() const override; AdapterAndModeList GetAdapterAndModeList() override; void DestroySurface() override; @@ -346,6 +345,10 @@ public: static AdapterAndModeList StaticGetAdapterAndModeList(); +protected: + bool CreateDevice(const std::string_view& adapter, bool debug_device) override; + void DestroyDevice() override; + private: using RasterizationStateMap = std::unordered_map>; using DepthStateMap = std::unordered_map>; @@ -368,8 +371,9 @@ private: bool CheckStagingBufferSize(u32 width, u32 height, DXGI_FORMAT format); void DestroyStagingBuffer(); - bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode); + bool CreateSwapChain(); bool CreateSwapChainRTV(); + void DestroySwapChain(); bool CreateBuffers(); void DestroyBuffers(); @@ -388,8 +392,8 @@ private: ComPtr m_context; ComPtr m_annotation; - ComPtr m_dxgi_factory; - ComPtr m_swap_chain; + ComPtr m_dxgi_factory; + ComPtr m_swap_chain; ComPtr m_swap_chain_rtv; RasterizationStateMap m_rasterization_states; @@ -405,6 +409,7 @@ private: bool m_allow_tearing_supported = false; bool m_using_flip_model_swap_chain = true; bool m_using_allow_tearing = false; + bool m_is_exclusive_fullscreen = false; D3D11StreamBuffer m_vertex_buffer; D3D11StreamBuffer m_index_buffer; diff --git a/src/core/gpu/d3d12_gpu_device.cpp b/src/core/gpu/d3d12_gpu_device.cpp index d7c35aae2..0b9c6ecb5 100644 --- a/src/core/gpu/d3d12_gpu_device.cpp +++ b/src/core/gpu/d3d12_gpu_device.cpp @@ -28,7 +28,7 @@ D3D12GPUDevice::~D3D12GPUDevice() // DestroyRenderSurface() will exec the command list. DestroySurface(); - DestroyResources(); + //DestroyResources(); g_d3d12_context->Destroy(); } @@ -108,6 +108,7 @@ bool D3D12GPUDevice::SupportsTextureFormat(GPUTexture::Format format) const bool D3D12GPUDevice::GetHostRefreshRate(float* refresh_rate) { +#if 0 if (m_swap_chain && IsFullscreen()) { DXGI_SWAP_CHAIN_DESC desc; @@ -121,6 +122,7 @@ bool D3D12GPUDevice::GetHostRefreshRate(float* refresh_rate) return true; } } +#endif return GPUDevice::GetHostRefreshRate(refresh_rate); } @@ -130,6 +132,7 @@ void D3D12GPUDevice::SetVSync(bool enabled) m_vsync_enabled = enabled; } +#if 0 bool D3D12GPUDevice::CreateDevice(const WindowInfo& wi, bool vsync) { ComPtr temp_dxgi_factory; @@ -214,6 +217,7 @@ bool D3D12GPUDevice::DoneCurrent() { return true; } +#endif bool D3D12GPUDevice::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode) { @@ -327,6 +331,8 @@ void D3D12GPUDevice::DestroySwapChainRTVs() m_current_swap_chain_buffer = 0; } +#if 0 + bool D3D12GPUDevice::ChangeWindow(const WindowInfo& new_wi) { DestroySurface(); @@ -334,6 +340,7 @@ bool D3D12GPUDevice::ChangeWindow(const WindowInfo& new_wi) m_window_info = new_wi; return CreateSwapChain(nullptr); } +#endif 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. g_d3d12_context->ExecuteCommandList(true); +#if 0 if (IsFullscreen()) SetFullscreen(false, 0, 0, 0.0f); +#endif DestroySwapChainRTVs(); m_swap_chain.Reset(); } +#if 0 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; } - +#endif GPUDevice::AdapterAndModeList D3D12GPUDevice::GetAdapterAndModeList() { return GetAdapterAndModeList(m_dxgi_factory.Get()); } +#if 0 + bool D3D12GPUDevice::CreateResources() { D3D12::RootSignatureBuilder rsbuilder; @@ -550,6 +562,7 @@ void D3D12GPUDevice::DestroyResources() m_display_pipeline.Reset(); m_display_root_signature.Reset(); } +#endif #if 0 bool D3D12GPUDevice::CreateImGuiContext() @@ -619,6 +632,8 @@ float D3D12GPUDevice::GetAndResetAccumulatedGPUTime() return g_d3d12_context->GetAndResetAccumulatedGPUTime(); } +#if 0 + void D3D12GPUDevice::RenderImGui(ID3D12GraphicsCommandList* cmdlist) { ImGui::Render(); @@ -702,6 +717,8 @@ void D3D12GPUDevice::RenderSoftwareCursor(ID3D12GraphicsCommandList* cmdlist, s3 cmdlist->DrawInstanced(3, 1, 0, 0); } +#endif + GPUDevice::AdapterAndModeList D3D12GPUDevice::StaticGetAdapterAndModeList() { ComPtr dxgi_factory; diff --git a/src/core/gpu/d3d12_gpu_device.h b/src/core/gpu/d3d12_gpu_device.h index bec4d5861..0e666ad1c 100644 --- a/src/core/gpu/d3d12_gpu_device.h +++ b/src/core/gpu/d3d12_gpu_device.h @@ -32,17 +32,17 @@ public: bool HasSurface() const override; - bool CreateDevice(const WindowInfo& wi, bool vsync) override; - bool SetupDevice() override; + //bool CreateDevice(const WindowInfo& wi, bool vsync) override; + //bool SetupDevice() override; - bool MakeCurrent() override; - bool DoneCurrent() 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; + //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; @@ -69,13 +69,14 @@ public: protected: static AdapterAndModeList GetAdapterAndModeList(IDXGIFactory* dxgi_factory); - virtual bool CreateResources() override; - virtual void DestroyResources() override; + //virtual bool CreateResources() override; + //virtual void DestroyResources() override; bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode); bool CreateSwapChainRTV(); void DestroySwapChainRTVs(); +#if 0 void RenderDisplay(ID3D12GraphicsCommandList* cmdlist, D3D12::Texture* swap_chain_buf); void RenderSoftwareCursor(ID3D12GraphicsCommandList* cmdlist); void RenderImGui(ID3D12GraphicsCommandList* cmdlist); @@ -85,6 +86,7 @@ protected: s32 texture_view_height, bool linear_filter); void RenderSoftwareCursor(ID3D12GraphicsCommandList* cmdlist, s32 left, s32 top, s32 width, s32 height, GPUTexture* texture_handle); +#endif ComPtr m_dxgi_factory; ComPtr m_swap_chain; diff --git a/src/core/gpu/gpu_device.cpp b/src/core/gpu/gpu_device.cpp index 60a93f1ab..7737e092f 100644 --- a/src/core/gpu/gpu_device.cpp +++ b/src/core/gpu/gpu_device.cpp @@ -2,8 +2,10 @@ // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "gpu_device.h" +#include "../host_settings.h" #include "../settings.h" #include "../shadergen.h" +#include "../system.h" #include "postprocessing_chain.h" #include "common/align.h" @@ -210,21 +212,95 @@ RenderAPI GPUDevice::GetPreferredAPI() #endif } -bool GPUDevice::SetupDevice() +const char* GPUDevice::RenderAPIToString(RenderAPI api) { - // TODO: option to disable shader cache - if (true) + // TODO: Combine ES + switch (api) { - const std::string basename = GetShaderCacheBaseName("shaders", g_settings.gpu_use_debug_device); - const std::string filename = Path::Combine(EmuFolders::Cache, basename); - if (!m_shader_cache.Open(filename.c_str())) - Log_WarningPrintf("Failed to open shader cache."); + // clang-format off +#define CASE(x) case RenderAPI::x: return #x + CASE(None); + 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 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; } @@ -348,6 +424,12 @@ std::string GPUDevice::GetShaderCacheBaseName(const std::string_view& type, bool return {}; } +void GPUDevice::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) +{ + // TODO: REMOVE ME + UnreachableCode(); +} + void GPUDevice::RenderImGui() { GL_SCOPE("RenderImGui"); @@ -566,6 +648,19 @@ void GPUDevice::InvalidateRenderTarget(GPUTexture* t) 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 GPUDevice::CreateShaderFromBinary(GPUShaderStage stage, gsl::span data) { // TODO: REMOVE ME @@ -658,14 +753,16 @@ std::unique_ptr GPUDevice::CreateShader(GPUShaderStage stage, const s 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()) { + const std::string_view mode_view = mode; std::string_view::size_type sep1 = mode.find('x'); if (sep1 != std::string_view::npos) { - std::optional owidth = StringUtil::FromChars(mode.substr(0, sep1)); + std::optional owidth = StringUtil::FromChars(mode_view.substr(0, sep1)); 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); if (sep2 != std::string_view::npos) { - std::optional oheight = StringUtil::FromChars(mode.substr(sep1, sep2 - sep1)); + std::optional oheight = StringUtil::FromChars(mode_view.substr(sep1, sep2 - sep1)); 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()) { - std::optional orefresh_rate = StringUtil::FromChars(mode.substr(sep2)); + std::optional orefresh_rate = StringUtil::FromChars(mode_view.substr(sep2)); if (orefresh_rate.has_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); } +std::string GPUDevice::GetShaderDumpPath(const std::string_view& name) +{ + return Path::Combine(EmuFolders::Dumps, name); +} + bool GPUDevice::UpdateImGuiFontTexture() { ImGuiIO& io = ImGui::GetIO(); @@ -1529,7 +1631,7 @@ bool GPUDevice::WriteScreenshotToFile(std::string filename, bool internal_resolu return true; } -std::unique_ptr Host::CreateDisplayForAPI(RenderAPI api) +std::unique_ptr GPUDevice::CreateDeviceForAPI(RenderAPI api) { switch (api) { @@ -1558,18 +1660,6 @@ std::unique_ptr Host::CreateDisplayForAPI(RenderAPI api) #endif default: -#if defined(_WIN32) && defined(_M_ARM64) - return std::make_unique(); -#elif defined(_WIN32) - return std::make_unique(); -#elif defined(__APPLE__) - return WrapNewMetalDevice(); -#elif defined(WITH_OPENGL) - return std::make_unique(); -#elif defined(WITH_VULKAN) - return std::make_unique(); -#else return {}; -#endif } } diff --git a/src/core/gpu/gpu_device.h b/src/core/gpu/gpu_device.h index 14ec08870..3796d50b9 100644 --- a/src/core/gpu/gpu_device.h +++ b/src/core/gpu/gpu_device.h @@ -14,6 +14,7 @@ #include "gsl/span" #include +#include #include #include #include @@ -431,12 +432,21 @@ public: /// Returns the default/preferred API for the system. 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 CreateDeviceForAPI(RenderAPI api); + /// 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. 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 u32 GetMaxTextureSize() const { return m_max_texture_size; } ALWAYS_INLINE u32 GetMaxMultisamples() const { return m_max_multisamples; } @@ -466,29 +476,23 @@ public: virtual RenderAPI GetRenderAPI() const = 0; - virtual bool CreateDevice(const WindowInfo& wi, bool vsync) = 0; - virtual bool SetupDevice(); - virtual bool MakeCurrent() = 0; - virtual bool DoneCurrent() = 0; + bool Create(const std::string_view& adapter, const std::string_view& shader_cache_path, + bool debug_device, bool vsync); + void Destroy(); virtual bool HasSurface() const = 0; virtual void DestroySurface() = 0; - virtual bool ChangeWindow(const WindowInfo& wi) = 0; + virtual bool UpdateWindow(); - virtual bool SupportsFullscreen() const = 0; - virtual bool IsFullscreen() = 0; - virtual bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) = 0; + virtual bool SupportsExclusiveFullscreen() const; virtual AdapterAndModeList GetAdapterAndModeList() = 0; - virtual bool CreateResources(); - virtual void DestroyResources(); - virtual bool SetPostProcessingChain(const std::string_view& config); virtual std::string GetShaderCacheBaseName(const std::string_view& type, bool debug) const; /// 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. virtual std::unique_ptr 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); protected: + virtual bool CreateDevice(const std::string_view& adapter, bool debug_device); + virtual void DestroyDevice(); + virtual std::unique_ptr CreateShaderFromBinary(GPUShaderStage stage, gsl::span data); virtual std::unique_ptr CreateShaderFromSource(GPUShaderStage stage, const std::string_view& source, std::vector* 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 m_nearest_sampler; + std::unique_ptr m_linear_sampler; + + bool m_gpu_timing_enabled = false; + bool m_vsync_enabled = false; + +private: ALWAYS_INLINE bool HasSoftwareCursor() const { return static_cast(m_cursor_texture); } 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; 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); 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 m_nearest_sampler; - std::unique_ptr m_linear_sampler; - u64 m_last_frame_displayed_time = 0; s32 m_mouse_position_x = 0; @@ -688,8 +705,6 @@ protected: float m_cursor_texture_scale = 1.0f; bool m_display_changed = false; - bool m_gpu_timing_enabled = false; - bool m_vsync_enabled = false; std::unique_ptr m_post_processing_chain; }; @@ -698,20 +713,15 @@ protected: extern std::unique_ptr g_host_display; namespace Host { -std::unique_ptr CreateDisplayForAPI(RenderAPI api); +/// Called when the core is creating a render device. +/// This could also be fullscreen transition. +std::optional AcquireRenderWindow(bool recreate_window); -/// Creates the host display. This may create a new window. The API used depends on the current configuration. -bool AcquireHostDisplay(RenderAPI api); +/// Called before drawing the OSD and other display elements. +void BeginPresentFrame(); -/// Destroys the host display. This may close the display window. -void ReleaseHostDisplay(); - -/// 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(); +/// Called when the core is finished with a render window. +void ReleaseRenderWindow(); /// Provided by the host; renders the display. void RenderDisplay(bool skip_present); diff --git a/src/core/gpu/opengl_device.cpp b/src/core/gpu/opengl_device.cpp index 90e10567e..039bfaf61 100644 --- a/src/core/gpu/opengl_device.cpp +++ b/src/core/gpu/opengl_device.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "opengl_device.h" -#include "../settings.h" #include "opengl_pipeline.h" #include "opengl_stream_buffer.h" #include "opengl_texture.h" @@ -13,6 +12,8 @@ #include "common/log.h" #include "common/string_util.h" +#include "fmt/format.h" + #include #include @@ -28,15 +29,7 @@ OpenGLDevice::OpenGLDevice() OpenGLDevice::~OpenGLDevice() { - // TODO: Destroy() function - if (!m_gl_context) - return; - - DestroyResources(); - DestroyBuffers(); - - m_gl_context->DoneCurrent(); - m_gl_context.reset(); + Assert(!m_gl_context); } 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, ...) { #ifdef _DEBUG - if (!glPushDebugGroup || !g_settings.gpu_use_debug_device) + if (!glPushDebugGroup) return; std::va_list ap; @@ -216,7 +209,7 @@ void OpenGLDevice::PushDebugGroup(const char* fmt, ...) void OpenGLDevice::PopDebugGroup() { #ifdef _DEBUG - if (!glPopDebugGroup || !g_settings.gpu_use_debug_device) + if (!glPopDebugGroup) return; glPopDebugGroup(); @@ -226,7 +219,7 @@ void OpenGLDevice::PopDebugGroup() void OpenGLDevice::InsertDebugMessage(const char* fmt, ...) { #ifdef _DEBUG - if (!glDebugMessageInsert || !g_settings.gpu_use_debug_device) + if (!glDebugMessageInsert) return; std::va_list ap; @@ -275,9 +268,9 @@ bool OpenGLDevice::HasSurface() const 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) { Log_ErrorPrintf("Failed to create any GL context"); @@ -285,18 +278,26 @@ bool OpenGLDevice::CreateDevice(const WindowInfo& wi, bool vsync) return false; } + // Is this needed? m_window_info = m_gl_context->GetWindowInfo(); - m_vsync_enabled = vsync; - return true; -} -bool OpenGLDevice::SetupDevice() -{ - if (!GPUDevice::SetupDevice()) - return false; +#if 0 + // TODO: add these checks + 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 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. const char* gl_vendor = reinterpret_cast(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"); - 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); else glDebugMessageCallback(GLDebugCallback, nullptr); @@ -316,11 +317,19 @@ bool OpenGLDevice::SetupDevice() glEnable(GL_DEBUG_OUTPUT); 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()) return false; - if (!CreateBuffers() || !CreateResources()) + if (!CreateBuffers()) return false; return true; @@ -391,28 +400,27 @@ bool OpenGLDevice::CheckFeatures() return true; } -bool OpenGLDevice::MakeCurrent() +void OpenGLDevice::DestroyDevice() { - if (!m_gl_context->MakeCurrent()) - { - Log_ErrorPrintf("Failed to make GL context current"); - return false; - } + if (!m_gl_context) + return; - SetSwapInterval(); - return true; + DestroyBuffers(); + + m_gl_context->DoneCurrent(); + m_gl_context.reset(); } -bool OpenGLDevice::DoneCurrent() -{ - return m_gl_context->DoneCurrent(); -} - -bool OpenGLDevice::ChangeWindow(const WindowInfo& new_wi) +bool OpenGLDevice::UpdateWindow() { 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"); return false; @@ -420,17 +428,24 @@ bool OpenGLDevice::ChangeWindow(const WindowInfo& new_wi) m_window_info = m_gl_context->GetWindowInfo(); - // Update swap interval for new surface. - if (m_gl_context->IsCurrent()) + if (m_window_info.type != WindowInfo::Type::Surfaceless) + { + // reset vsync rate, since it (usually) gets lost SetSwapInterval(); + // TODO RenderBlankFrame(); + } 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(new_window_width) && + m_window_info.surface_height == static_cast(new_window_height)) + { return; + } m_gl_context->ResizeSurface(static_cast(new_window_width), static_cast(new_window_height)); m_window_info = m_gl_context->GetWindowInfo(); @@ -453,21 +468,6 @@ void OpenGLDevice::SetSwapInterval() 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() { AdapterAndModeList aml; diff --git a/src/core/gpu/opengl_device.h b/src/core/gpu/opengl_device.h index 2658b358a..41fb6024c 100644 --- a/src/core/gpu/opengl_device.h +++ b/src/core/gpu/opengl_device.h @@ -41,21 +41,13 @@ public: RenderAPI GetRenderAPI() 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; + 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::unique_ptr CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, @@ -142,6 +134,9 @@ protected: OpenGLPipeline::VertexArrayCache m_vao_cache; OpenGLPipeline::ProgramCache m_program_cache; + bool CreateDevice(const std::string_view& adapter, bool debug_device) override; + void DestroyDevice() override; + bool CreateBuffers(); void DestroyBuffers(); diff --git a/src/core/gpu/vulkan_gpu_device.cpp b/src/core/gpu/vulkan_gpu_device.cpp index 623f88bdd..efbad578f 100644 --- a/src/core/gpu/vulkan_gpu_device.cpp +++ b/src/core/gpu/vulkan_gpu_device.cpp @@ -31,7 +31,7 @@ VulkanGPUDevice::~VulkanGPUDevice() g_vulkan_context->WaitForGPUIdle(); DestroyStagingBuffer(); - DestroyResources(); + //DestroyResources(); Vulkan::ShaderCache::Destroy(); m_swap_chain.reset(); @@ -46,6 +46,8 @@ RenderAPI VulkanGPUDevice::GetRenderAPI() const return RenderAPI::Vulkan; } +#if 0 + bool VulkanGPUDevice::ChangeWindow(const WindowInfo& new_wi) { g_vulkan_context->WaitForGPUIdle(); @@ -118,6 +120,8 @@ bool VulkanGPUDevice::SetFullscreen(bool fullscreen, u32 width, u32 height, floa return false; } +#endif + GPUDevice::AdapterAndModeList VulkanGPUDevice::GetAdapterAndModeList() { 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(); } +#if 0 + bool VulkanGPUDevice::CreateDevice(const WindowInfo& wi, bool vsync) { WindowInfo local_wi(wi); @@ -251,6 +257,8 @@ bool VulkanGPUDevice::SetupDevice() return true; } +#endif + bool VulkanGPUDevice::HasSurface() const { return static_cast(m_swap_chain); @@ -385,6 +393,8 @@ bool VulkanGPUDevice::CheckStagingBufferSize(u32 required_size) return true; } +#if 0 + bool VulkanGPUDevice::CreateResources() { static constexpr char fullscreen_quad_vertex_shader[] = R"( @@ -556,7 +566,6 @@ void VulkanGPUDevice::DestroyResources() Vulkan::Util::SafeDestroySampler(m_linear_sampler); } -#if 0 bool VulkanGPUDevice::CreateImGuiContext() { const VkRenderPass render_pass = @@ -581,7 +590,6 @@ bool VulkanGPUDevice::UpdateImGuiFontTexture() g_vulkan_context->ExecuteCommandBuffer(true); return ImGui_ImplVulkan_CreateFontsTexture(); } -#endif bool VulkanGPUDevice::MakeCurrent() { @@ -593,7 +601,6 @@ bool VulkanGPUDevice::DoneCurrent() return true; } -#if 0 bool VulkanGPUDevice::Render(bool skip_present) { 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); } +#if 0 void VulkanGPUDevice::RenderDisplay() { 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); vkCmdDraw(cmdbuffer, 3, 1, 0, 0); } +#endif bool VulkanGPUDevice::SetGPUTimingEnabled(bool enabled) { diff --git a/src/core/gpu/vulkan_gpu_device.h b/src/core/gpu/vulkan_gpu_device.h index e4d732615..b2a39eff9 100644 --- a/src/core/gpu/vulkan_gpu_device.h +++ b/src/core/gpu/vulkan_gpu_device.h @@ -27,17 +27,17 @@ public: bool HasSurface() const override; - bool CreateDevice(const WindowInfo& wi, bool vsync) override; - bool SetupDevice() override; + //bool CreateDevice(const WindowInfo& wi, bool vsync) override; + //bool SetupDevice() override; - bool MakeCurrent() override; - bool DoneCurrent() 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; + //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; @@ -73,10 +73,11 @@ protected: bool CheckStagingBufferSize(u32 required_size); void DestroyStagingBuffer(); - bool CreateResources() override; - void DestroyResources() override; + //bool CreateResources() override; + //void DestroyResources() override; void BeginSwapChainRenderPass(VkFramebuffer framebuffer, u32 width, u32 height); +#if 0 void RenderDisplay(); void RenderImGui(); void RenderSoftwareCursor(); @@ -84,6 +85,7 @@ protected: 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); void RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 height, GPUTexture* texture_handle); +#endif std::unique_ptr m_swap_chain; diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index 453cb85cb..faede6220 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -2633,71 +2633,8 @@ void GPU_HW::ShaderCompileProgressTracker::Increment() } } -// TODO: Combine all these.. - -#ifdef _WIN32 - -std::unique_ptr GPU::CreateHardwareD3D11Renderer() +std::unique_ptr GPU::CreateHardwareRenderer() { - if (!Host::AcquireHostDisplay(RenderAPI::D3D11)) - { - Log_ErrorPrintf("Host render API is incompatible"); - return nullptr; - } - - std::unique_ptr gpu(std::make_unique()); - if (!gpu->Initialize()) - return nullptr; - - return gpu; -} - -#endif - -#ifdef __APPLE__ - -std::unique_ptr GPU::CreateHardwareMetalRenderer() -{ - if (!Host::AcquireHostDisplay(RenderAPI::Metal)) - { - Log_ErrorPrintf("Host render API is incompatible"); - return nullptr; - } - - std::unique_ptr gpu(std::make_unique()); - if (!gpu->Initialize()) - return nullptr; - - return gpu; -} - -#endif - -std::unique_ptr 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(std::make_unique()); if (!gpu->Initialize()) return nullptr; diff --git a/src/core/gpu_hw_d3d12.cpp b/src/core/gpu_hw_d3d12.cpp index 430473830..af6d893cb 100644 --- a/src/core/gpu_hw_d3d12.cpp +++ b/src/core/gpu_hw_d3d12.cpp @@ -1149,7 +1149,6 @@ void GPU_HW_D3D12::ClearDepthBuffer() cmdlist->ClearDepthStencilView(m_vram_depth_texture.GetRTVOrDSVDescriptor(), D3D12_CLEAR_FLAG_DEPTH, m_pgxp_depth_buffer ? 1.0f : 0.0f, 0, 0, nullptr); } -#endif std::unique_ptr GPU::CreateHardwareD3D12Renderer() { @@ -1165,3 +1164,4 @@ std::unique_ptr GPU::CreateHardwareD3D12Renderer() return gpu; } +#endif diff --git a/src/core/gpu_hw_vulkan.cpp b/src/core/gpu_hw_vulkan.cpp index 146472e9c..e0381e51f 100644 --- a/src/core/gpu_hw_vulkan.cpp +++ b/src/core/gpu_hw_vulkan.cpp @@ -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); } - -std::unique_ptr 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(std::make_unique()); - if (!gpu->Initialize()) - return nullptr; - - return gpu; -} diff --git a/src/core/gpu_sw.cpp b/src/core/gpu_sw.cpp index 7def67447..210e1f1b4 100644 --- a/src/core/gpu_sw.cpp +++ b/src/core/gpu_sw.cpp @@ -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::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(std::make_unique()); if (!gpu->Initialize()) return nullptr; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 90067f4bf..b78f86f6a 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -942,6 +942,9 @@ RenderAPI Settings::GetRenderAPIForRenderer(GPURenderer renderer) case GPURenderer::HardwareD3D12: return RenderAPI::D3D12; #endif +#ifdef __APPLE__ + return RenderAPI::Metal; +#endif #ifdef WITH_VULKAN case GPURenderer::HardwareVulkan: return RenderAPI::Vulkan; diff --git a/src/core/system.cpp b/src/core/system.cpp index 91107cf89..58a529e53 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -71,7 +71,9 @@ SystemBootParameters::SystemBootParameters(const SystemBootParameters&) = defaul 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; @@ -135,6 +137,7 @@ static std::string s_input_profile_name; static System::State s_state = System::State::Shutdown; static std::atomic_bool s_startup_cancelled{false}; +static bool s_keep_gpu_device_on_shutdown = false; static ConsoleRegion s_region = ConsoleRegion::NTSC_U; 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 g_gpu.reset(); if (force_recreate_display) - Host::ReleaseHostDisplay(); + Host::ReleaseGPUDevice(); if (!CreateGPU(renderer)) { @@ -1131,6 +1134,7 @@ bool System::BootSystem(SystemBootParameters parameters) Assert(s_state == State::Shutdown); s_state = State::Starting; s_startup_cancelled.store(false); + s_keep_gpu_device_on_shutdown = static_cast(g_host_display); s_region = g_settings.region; Host::OnSystemStarting(); @@ -1421,7 +1425,11 @@ bool System::Initialize(bool force_software_renderer) if (IsStartupCancelled()) { g_gpu.reset(); - Host::ReleaseHostDisplay(); + if (!s_keep_gpu_device_on_shutdown) + { + Host::ReleaseGPUDevice(); + Host::ReleaseRenderWindow(); + } if (g_settings.gpu_pgxp_enable) PGXP::Shutdown(); CPU::Shutdown(); @@ -1515,11 +1523,15 @@ void System::DestroySystem() ClearRunningGame(); // 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); UpdateSoftwareCursor(); - Host::ReleaseHostDisplay(); + } + else + { + Host::ReleaseGPUDevice(); + Host::ReleaseRenderWindow(); } s_bios_hash = {}; @@ -1621,41 +1633,26 @@ void System::RecreateSystem() 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 - case GPURenderer::HardwareOpenGL: - g_gpu = GPU::CreateHardwareOpenGLRenderer(); - break; -#endif + if (g_host_display) + { + Log_WarningPrintf("Recreating GPU device, expecting %s got %s", GPUDevice::RenderAPIToString(api), + GPUDevice::RenderAPIToString(g_host_display->GetRenderAPI())); + } -#ifdef WITH_VULKAN - case GPURenderer::HardwareVulkan: - g_gpu = GPU::CreateHardwareVulkanRenderer(); - 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; + Host::ReleaseGPUDevice(); + if (!Host::CreateGPUDevice(api)) + return false; } + if (renderer == GPURenderer::Software) + g_gpu = GPU::CreateSoftwareRenderer(); + else + g_gpu = GPU::CreateHardwareRenderer(); + if (!g_gpu) { Log_ErrorPrintf("Failed to initialize %s renderer, falling back to software renderer", @@ -1669,6 +1666,11 @@ bool System::CreateGPU(GPURenderer renderer) if (!g_gpu) { Log_ErrorPrintf("Failed to create fallback software renderer."); + if (!s_keep_gpu_device_on_shutdown) + { + Host::ReleaseGPUDevice(); + Host::ReleaseRenderWindow(); + } return false; } } diff --git a/src/core/system.h b/src/core/system.h index 8318a4881..9c1202762 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -498,6 +498,18 @@ void RequestExit(bool allow_confirm); /// Requests shut down of the current virtual machine. 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. bool IsFullscreen(); diff --git a/src/duckstation-nogui/nogui_host.cpp b/src/duckstation-nogui/nogui_host.cpp index d1db023fa..9edf3789d 100644 --- a/src/duckstation-nogui/nogui_host.cpp +++ b/src/duckstation-nogui/nogui_host.cpp @@ -82,8 +82,6 @@ static void UpdateWindowTitle(const std::string& game_title); static void CancelAsyncOp(); static void StartAsyncOp(std::function callback); static void AsyncOpThreadEntryPoint(std::function callback); -static bool AcquireHostDisplay(RenderAPI api); -static void ReleaseHostDisplay(); } // namespace NoGUIHost ////////////////////////////////////////////////////////////////////////// @@ -96,7 +94,7 @@ static bool s_save_state_on_shutdown = false; static bool s_was_paused_by_focus_loss = false; 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::mutex s_cpu_thread_events_mutex; 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) { Host::RunOnCPUThread([width, height, scale]() { - // TODO: Scale - g_host_display->ResizeWindow(width, height); + g_host_display->ResizeWindow(width, height, scale); ImGuiManager::WindowResized(); System::HostDisplayResized(); }); @@ -608,8 +605,8 @@ void NoGUIHost::CPUThreadEntryPoint() // input source setup must happen on emu thread CommonHost::Initialize(); - // start the GS thread up and get it going - if (AcquireHostDisplay(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer))) + // start the fullscreen UI and get it going + if (Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer)) && FullscreenUI::Initialize()) { // kick a game list refresh if we're not in batch mode if (!InBatchMode()) @@ -629,7 +626,8 @@ void NoGUIHost::CPUThreadEntryPoint() if (System::IsValid()) System::ShutdownSystem(false); - ReleaseHostDisplay(); + Host::ReleaseGPUDevice(); + Host::ReleaseRenderWindow(); CommonHost::Shutdown(); g_nogui_window->QuitMessageLoop(); @@ -652,53 +650,30 @@ void NoGUIHost::CPUThreadMainLoop() } } -bool NoGUIHost::AcquireHostDisplay(RenderAPI api) +std::optional Host::AcquireRenderWindow(bool recreate_window) { - Assert(!g_host_display); + std::optional wi; - g_nogui_window->ExecuteInMessageLoop([api]() { - if (g_nogui_window->CreatePlatformWindow(GetWindowTitle(System::GetGameTitle()))) + g_nogui_window->ExecuteInMessageLoop([&wi, recreate_window]() { + bool res = g_nogui_window->HasPlatformWindow(); + if (!res || recreate_window) { - const std::optional wi(g_nogui_window->GetPlatformWindowInfo()); - 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 + if (res) 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."); - return false; - } - - 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; + g_nogui_window->ReportError("Error", "Failed to create render window."); + return std::nullopt; } // reload input sources, since it might use the window handle @@ -706,43 +681,18 @@ bool NoGUIHost::AcquireHostDisplay(RenderAPI api) auto lock = Host::GetSettingsLock(); 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) - { - // 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(); + // Need to block here, otherwise the recreation message associates with the old window. g_nogui_window->ExecuteInMessageLoop([]() { g_nogui_window->DestroyPlatformWindow(); - s_host_display_created_or_destroyed.Post(); + s_platform_window_updated.Post(); }); - s_host_display_created_or_destroyed.Wait(); -} - -void Host::ReleaseHostDisplay() -{ - // we keep the fsui going, so no need to do anything here + s_platform_window_updated.Wait(); } void Host::OnSystemStarting() diff --git a/src/duckstation-nogui/nogui_platform.h b/src/duckstation-nogui/nogui_platform.h index 984f1b9c6..94e204845 100644 --- a/src/duckstation-nogui/nogui_platform.h +++ b/src/duckstation-nogui/nogui_platform.h @@ -24,6 +24,7 @@ public: virtual void SetDefaultConfig(SettingsInterface& si) = 0; virtual bool CreatePlatformWindow(std::string title) = 0; + virtual bool HasPlatformWindow() const = 0; virtual void DestroyPlatformWindow() = 0; virtual std::optional GetPlatformWindowInfo() = 0; diff --git a/src/duckstation-nogui/wayland_nogui_platform.cpp b/src/duckstation-nogui/wayland_nogui_platform.cpp index c6d4ec217..52bf96727 100644 --- a/src/duckstation-nogui/wayland_nogui_platform.cpp +++ b/src/duckstation-nogui/wayland_nogui_platform.cpp @@ -150,6 +150,11 @@ bool WaylandNoGUIPlatform::CreatePlatformWindow(std::string title) return true; } +bool WaylandNoGUIPlatform::HasPlatformWindow() const +{ + return (m_surface != nullptr); +} + void WaylandNoGUIPlatform::DestroyPlatformWindow() { m_window_info = {}; diff --git a/src/duckstation-nogui/wayland_nogui_platform.h b/src/duckstation-nogui/wayland_nogui_platform.h index 25f8b3714..2423abee5 100644 --- a/src/duckstation-nogui/wayland_nogui_platform.h +++ b/src/duckstation-nogui/wayland_nogui_platform.h @@ -29,6 +29,7 @@ public: void SetDefaultConfig(SettingsInterface& si) override; bool CreatePlatformWindow(std::string title) override; + bool HasPlatformWindow() const override; void DestroyPlatformWindow() override; std::optional GetPlatformWindowInfo() override; void SetPlatformWindowTitle(std::string title) override; diff --git a/src/duckstation-nogui/win32_nogui_platform.cpp b/src/duckstation-nogui/win32_nogui_platform.cpp index 9e34935d7..122036808 100644 --- a/src/duckstation-nogui/win32_nogui_platform.cpp +++ b/src/duckstation-nogui/win32_nogui_platform.cpp @@ -134,6 +134,11 @@ bool Win32NoGUIPlatform::CreatePlatformWindow(std::string title) return true; } +bool Win32NoGUIPlatform::HasPlatformWindow() const +{ + return (m_hwnd != NULL); +} + void Win32NoGUIPlatform::DestroyPlatformWindow() { if (!m_hwnd) diff --git a/src/duckstation-nogui/win32_nogui_platform.h b/src/duckstation-nogui/win32_nogui_platform.h index 836cc97d3..132cfa121 100644 --- a/src/duckstation-nogui/win32_nogui_platform.h +++ b/src/duckstation-nogui/win32_nogui_platform.h @@ -23,6 +23,7 @@ public: void SetDefaultConfig(SettingsInterface& si) override; bool CreatePlatformWindow(std::string title) override; + bool HasPlatformWindow() const override; void DestroyPlatformWindow() override; std::optional GetPlatformWindowInfo() override; void SetPlatformWindowTitle(std::string title) override; diff --git a/src/duckstation-nogui/x11_nogui_platform.cpp b/src/duckstation-nogui/x11_nogui_platform.cpp index 760d0d555..951b2a9c9 100644 --- a/src/duckstation-nogui/x11_nogui_platform.cpp +++ b/src/duckstation-nogui/x11_nogui_platform.cpp @@ -99,6 +99,11 @@ bool X11NoGUIPlatform::CreatePlatformWindow(std::string title) return true; } +bool X11NoGUIPlatform::HasPlatformWindow() const +{ + return m_window != 0; +} + void X11NoGUIPlatform::DestroyPlatformWindow() { m_window_info = {}; diff --git a/src/duckstation-nogui/x11_nogui_platform.h b/src/duckstation-nogui/x11_nogui_platform.h index 3a2583c9b..395353e7f 100644 --- a/src/duckstation-nogui/x11_nogui_platform.h +++ b/src/duckstation-nogui/x11_nogui_platform.h @@ -48,6 +48,7 @@ public: void SetDefaultConfig(SettingsInterface& si) override; bool CreatePlatformWindow(std::string title) override; + bool HasPlatformWindow() const override; void DestroyPlatformWindow() override; std::optional GetPlatformWindowInfo() override; void SetPlatformWindowTitle(std::string title) override; diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 9c89a6356..fa4d09c8e 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -9,8 +9,8 @@ #include "common/file_system.h" #include "common/log.h" #include "core/achievements.h" -#include "core/host.h" #include "core/gpu/gpu_device.h" +#include "core/host.h" #include "core/memory_card.h" #include "core/settings.h" #include "core/system.h" @@ -217,77 +217,33 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr #endif -bool MainWindow::createDisplay(bool fullscreen, bool render_to_main) +std::optional 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(fullscreen), static_cast(render_to_main)); - - const std::string fullscreen_mode(Host::GetBaseStringSettingValue("GPU", "FullscreenMode", "")); - const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty() && g_host_display->SupportsFullscreen()); - - createDisplayWidget(fullscreen, render_to_main, is_exclusive_fullscreen); - - std::optional 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"); + Log_DevPrintf( + "acquireRenderWindow() recreate=%s fullscreen=%s render_to_main=%s surfaceless=%s use_main_window_pos=%s", + recreate_window ? "true" : "false", fullscreen ? "true" : "false", render_to_main ? "true" : "false", + surfaceless ? "true" : "false", use_main_window_pos ? "true" : "false"); QWidget* container = m_display_container ? static_cast(m_display_container) : static_cast(m_display_widget); const bool is_fullscreen = isRenderingFullscreen(); 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); - if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main && !changing_surfaceless) - return true; + if (m_display_created && !recreate_window && fullscreen == is_fullscreen && is_rendering_to_main == render_to_main && + !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. // .. except on Wayland, where everything tends to break if you don't recreate. const bool has_container = (m_display_container != nullptr); 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 && - !needs_container && !changing_surfaceless) + if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main && + has_container == needs_container && !needs_container && !changing_surfaceless) { 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 if (!is_fullscreen && !is_rendering_to_main) @@ -305,53 +261,48 @@ bool MainWindow::updateDisplay(bool fullscreen, bool render_to_main, bool surfac updateDisplayWidgetCursor(); m_display_widget->setFocus(); + updateWindowState(); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - return true; + return m_display_widget->getWindowInfo(); } - g_host_display->DestroySurface(); - - destroyDisplayWidget(surfaceless || fullscreen); + destroyDisplayWidget(surfaceless); + m_display_created = true; // if we're going to surfaceless, we're done here if (surfaceless) - { - updateWindowState(); - updateDisplayRelatedActions(false, render_to_main, fullscreen); - return true; - } + return WindowInfo(); - 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 wi = m_display_widget->getWindowInfo(); 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); - return false; + return std::nullopt; } 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(); updateWindowState(); + + m_ui.actionStartFullscreenUI->setEnabled(false); + m_ui.actionStartFullscreenUI2->setEnabled(false); + updateDisplayWidgetCursor(); updateDisplayRelatedActions(true, render_to_main, fullscreen); m_display_widget->setFocus(); - QSignalBlocker blocker(m_ui.actionFullscreen); - m_ui.actionFullscreen->setChecked(fullscreen); - return true; + return wi; } -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), // 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. if (!s_use_central_widget) { - if (isVisible()) + if (isVisible() && g_emu_thread->shouldRenderToMain()) container->move(pos()); else restoreDisplayWindowGeometryFromConfig(); } - if (!is_exclusive_fullscreen) - container->showFullScreen(); - else - container->showNormal(); + container->showFullScreen(); } 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(); } 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); } + updateDisplayRelatedActions(true, render_to_main, fullscreen); + // We need the surface visible. QGuiApplication::sync(); } -void MainWindow::setDisplayFullscreen(const std::string& fullscreen_mode) -{ - 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) +void MainWindow::displayResizeRequested(qint32 width, qint32 height) { if (!m_display_widget) return; @@ -465,7 +399,7 @@ void MainWindow::displaySizeRequested(qint32 width, qint32 height) QtUtils::ResizePotentiallyFixedSizeWindow(this, width, height + extra_height); } -void MainWindow::destroyDisplay() +void MainWindow::releaseRenderWindow() { // Now we can safely destroy the display window. destroyDisplayWidget(true); @@ -473,6 +407,8 @@ void MainWindow::destroyDisplay() updateDisplayRelatedActions(false, false, false); + m_ui.actionViewSystemDisplay->setEnabled(false); + m_ui.actionFullscreen->setEnabled(false); m_ui.actionStartFullscreenUI->setEnabled(true); m_ui.actionStartFullscreenUI2->setEnabled(true); } @@ -1849,7 +1785,7 @@ bool MainWindow::isRenderingFullscreen() const if (!g_host_display || !m_display_widget) return false; - return getDisplayContainer()->isFullScreen() || g_host_display->IsFullscreen(); + return getDisplayContainer()->isFullScreen(); } bool MainWindow::isRenderingToMain() const @@ -2003,12 +1939,11 @@ void MainWindow::connectSignals() Qt::QueuedConnection); 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::createDisplayRequested, this, &MainWindow::createDisplay, + connect(g_emu_thread, &EmuThread::onAcquireRenderWindowRequested, this, &MainWindow::acquireRenderWindow, Qt::BlockingQueuedConnection); - connect(g_emu_thread, &EmuThread::destroyDisplayRequested, this, &MainWindow::destroyDisplay); - connect(g_emu_thread, &EmuThread::updateDisplayRequested, this, &MainWindow::updateDisplay, + connect(g_emu_thread, &EmuThread::onReleaseRenderWindowRequested, this, &MainWindow::releaseRenderWindow); + connect(g_emu_thread, &EmuThread::onResizeRenderWindowRequested, this, &MainWindow::displayResizeRequested, 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::systemStarting, this, &MainWindow::onSystemStarting); connect(g_emu_thread, &EmuThread::systemStarted, this, &MainWindow::onSystemStarted); diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index b38d518d2..94f4d2fe4 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -10,8 +10,8 @@ #include #include -#include "controllersettingsdialog.h" #include "common/window_info.h" +#include "controllersettingsdialog.h" #include "core/types.h" #include "displaywidget.h" #include "settingsdialog.h" @@ -110,10 +110,11 @@ public Q_SLOTS: private Q_SLOTS: void reportError(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); - void displaySizeRequested(qint32 width, qint32 height); - void destroyDisplay(); + + std::optional acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main, + bool surfaceless, bool use_main_window_pos); + void displayResizeRequested(qint32 width, qint32 height); + void releaseRenderWindow(); void focusDisplayWidget(); void onMouseModeRequested(bool relative_mode, bool hide_cursor); @@ -208,11 +209,10 @@ private: void restoreGeometryFromConfig(); void saveDisplayWindowGeometryToConfig(); 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 updateDisplayWidgetCursor(); void updateDisplayRelatedActions(bool has_surface, bool render_to_main, bool fullscreen); - void setDisplayFullscreen(const std::string& fullscreen_mode); SettingsDialog* getSettingsDialog(); void doSettings(const char* category = nullptr); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 80475a480..61c831749 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -100,7 +100,9 @@ static bool s_start_fullscreen_ui_fullscreen = false; EmuThread* g_emu_thread; 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; @@ -329,24 +331,31 @@ void EmuThread::setInitialState(std::optional override_fullscreen) 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) { CommonHost::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 */) { if (isOnThread()) @@ -392,7 +401,7 @@ void Host::RequestResizeHostDisplay(s32 new_window_width, s32 new_window_height) if (g_emu_thread->isFullscreen()) 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 */) @@ -449,7 +458,7 @@ void EmuThread::startFullscreenUI() setInitialState(s_start_fullscreen_ui_fullscreen ? std::optional(true) : std::optional()); 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; return; @@ -481,7 +490,8 @@ void EmuThread::stopFullscreenUI() return; m_run_fullscreen_ui = false; - releaseHostDisplay(); + Host::ReleaseGPUDevice(); + Host::ReleaseRenderWindow(); } void EmuThread::bootSystem(std::shared_ptr params) @@ -605,33 +615,9 @@ void EmuThread::onDisplayWindowMouseWheelEvent(const QPoint& delta_angle) 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 - 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); - } + Host::ResizeDisplayWindow(width, height, scale); } void EmuThread::redrawDisplayWindow() @@ -656,14 +642,15 @@ void EmuThread::toggleFullscreen() 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()) { - 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; } @@ -671,7 +658,8 @@ void EmuThread::setFullscreen(bool fullscreen) return; m_is_fullscreen = fullscreen; - updateDisplayState(); + m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain(); + Host::UpdateDisplayWindow(); } bool Host::IsFullscreen() @@ -681,7 +669,7 @@ bool Host::IsFullscreen() void Host::SetFullscreen(bool enabled) { - g_emu_thread->setFullscreen(enabled); + g_emu_thread->setFullscreen(enabled, true); } void EmuThread::setSurfaceless(bool surfaceless) @@ -696,7 +684,7 @@ void EmuThread::setSurfaceless(bool surfaceless) return; m_is_surfaceless = surfaceless; - updateDisplayState(); + Host::UpdateDisplayWindow(); } void EmuThread::requestDisplaySize(float scale) @@ -713,52 +701,25 @@ void EmuThread::requestDisplaySize(float scale) System::RequestDisplaySize(scale); } -bool EmuThread::acquireHostDisplay(RenderAPI api) +std::optional EmuThread::acquireRenderWindow(bool recreate_window) { - if (g_host_display) - { - if (g_host_display->GetRenderAPI() == api) - { - // current is fine - return true; - } + DebugAssert(g_host_display); + u32 fs_width, fs_height; + float fs_refresh_rate; + m_is_exclusive_fullscreen = (m_is_fullscreen && g_host_display->SupportsExclusiveFullscreen() && + GPUDevice::GetRequestedExclusiveFullscreenMode(&fs_width, &fs_height, &fs_refresh_rate)); - // otherwise we need to switch - releaseHostDisplay(); - } + const bool window_fullscreen = m_is_fullscreen && !m_is_exclusive_fullscreen; + 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); - if (!g_host_display) - return false; + return emit onAcquireRenderWindowRequested(recreate_window, window_fullscreen, render_to_main, m_is_surfaceless, + use_main_window_pos); +} - if (!createDisplayRequested(m_is_fullscreen, m_is_rendering_to_main)) - { - emit destroyDisplayRequested(); - 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::releaseRenderWindow() +{ + emit onReleaseRenderWindowRequested(); } void EmuThread::connectDisplaySignals(DisplayWidget* widget) @@ -774,45 +735,6 @@ void EmuThread::connectDisplaySignals(DisplayWidget* widget) 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() { CommonHost::OnSystemStarting(); @@ -1610,40 +1532,34 @@ void Host::CommitBaseSettingChanges() QtHost::QueueSettingsSave(); } -bool Host::AcquireHostDisplay(RenderAPI api) +std::optional 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()) - { - // keep display alive when running fsui - return; - } - - g_emu_thread->releaseHostDisplay(); + g_emu_thread->releaseRenderWindow(); } 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_height = 0; if (g_gpu) - { - // TODO: Fix renderer type - renderer = g_gpu->IsHardwareRenderer() ? GPURenderer::HardwareOpenGL : GPURenderer::Software; 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, - Q_ARG(const QString&, QString::fromUtf8(Settings::GetRendererName(renderer)))); - m_last_renderer = renderer; + Q_ARG(const QString&, renderer_str)); + 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) { @@ -1680,7 +1596,8 @@ void EmuThread::resetPerformanceCounters() m_last_video_fps = std::numeric_limits::infinity(); m_last_render_width = std::numeric_limits::max(); m_last_render_height = std::numeric_limits::max(); - m_last_renderer = GPURenderer::Count; + m_last_render_api = RenderAPI::None; + m_last_hardware_renderer = false; QString blank; QMetaObject::invokeMethod(g_main_window->getStatusRendererWidget(), "setText", Qt::QueuedConnection, diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index 161cf23be..834058e02 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -95,9 +95,9 @@ public: ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; } ALWAYS_INLINE bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; } - bool acquireHostDisplay(RenderAPI api); + std::optional acquireRenderWindow(bool recreate_window); void connectDisplaySignals(DisplayWidget* widget); - void releaseHostDisplay(); + void releaseRenderWindow(); void renderDisplay(bool skip_present); void startBackgroundControllerPollTimer(); @@ -132,11 +132,11 @@ Q_SIGNALS: void systemPaused(); void systemResumed(); void gameListRefreshed(); - bool createDisplayRequested(bool fullscreen, bool render_to_main); - bool updateDisplayRequested(bool fullscreen, bool render_to_main, bool surfaceless); - void displaySizeRequested(qint32 width, qint32 height); + std::optional onAcquireRenderWindowRequested(bool recreate_window, bool fullscreen, bool render_to_main, + bool surfaceless, bool use_main_window_pos); + void onResizeRenderWindowRequested(qint32 width, qint32 height); + void onReleaseRenderWindowRequested(); void focusDisplayWidgetRequested(); - void destroyDisplayRequested(); void runningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title); void inputProfileLoaded(); void mouseModeRequested(bool relative, bool hide_cursor); @@ -180,7 +180,7 @@ public Q_SLOTS: void saveScreenshot(); void redrawDisplayWindow(); void toggleFullscreen(); - void setFullscreen(bool fullscreen); + void setFullscreen(bool fullscreen, bool allow_render_to_main); void setSurfaceless(bool surfaceless); void requestDisplaySize(float scale); void loadCheatList(const QString& filename); @@ -194,7 +194,7 @@ private Q_SLOTS: void onDisplayWindowMouseMoveEvent(bool relative, float x, float y); void onDisplayWindowMouseButtonEvent(int button, bool pressed); 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 onDisplayWindowTextEntered(const QString& text); void doBackgroundControllerPoll(); @@ -210,7 +210,6 @@ private: void createBackgroundControllerPollTimer(); void destroyBackgroundControllerPollTimer(); void setInitialState(std::optional override_fullscreen); - void updateDisplayState(); QThread* m_ui_thread; QSemaphore m_started_semaphore; @@ -233,7 +232,8 @@ private: float m_last_video_fps = std::numeric_limits::infinity(); u32 m_last_render_width = std::numeric_limits::max(); u32 m_last_render_height = std::numeric_limits::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; diff --git a/src/frontend-common/common_host.cpp b/src/frontend-common/common_host.cpp index a6ded29ea..958c79b2a 100644 --- a/src/frontend-common/common_host.cpp +++ b/src/frontend-common/common_host.cpp @@ -129,14 +129,86 @@ void CommonHost::PumpMessagesOnCPUThread() #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; } -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(); + ImGuiManager::Shutdown(); + + Log_InfoPrintf("Destroying %s GPU device...", GPUDevice::RenderAPIToString(g_host_display->GetRenderAPI())); + g_host_display->Destroy(); + g_host_display.reset(); } #ifndef __ANDROID__ diff --git a/src/frontend-common/common_host.h b/src/frontend-common/common_host.h index b8d342f71..bb4d7f649 100644 --- a/src/frontend-common/common_host.h +++ b/src/frontend-common/common_host.h @@ -33,8 +33,6 @@ void OnSystemPaused(); void OnSystemResumed(); void OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name); void PumpMessagesOnCPUThread(); -bool CreateHostDisplayResources(); -void ReleaseHostDisplayResources(); /// Returns the time elapsed in the current play session. u64 GetSessionPlayedTime();