diff --git a/src/common/vulkan/context.cpp b/src/common/vulkan/context.cpp index ffb51c56f..4b87fd5f6 100644 --- a/src/common/vulkan/context.cpp +++ b/src/common/vulkan/context.cpp @@ -269,7 +269,7 @@ Vulkan::Context::GPUNameList Vulkan::Context::EnumerateGPUNames(VkInstance insta bool Vulkan::Context::Create(std::string_view gpu_name, const WindowInfo* wi, std::unique_ptr* out_swap_chain, bool threaded_presentation, - bool enable_debug_utils, bool enable_validation_layer) + bool enable_debug_utils, bool enable_validation_layer, bool vsync) { AssertMsg(!g_vulkan_context, "Has no current context"); @@ -349,7 +349,7 @@ bool Vulkan::Context::Create(std::string_view gpu_name, const WindowInfo* wi, if (!g_vulkan_context->CreateDevice(surface, enable_validation_layer, nullptr, 0, nullptr, 0, nullptr) || !g_vulkan_context->CreateAllocator() || !g_vulkan_context->CreateGlobalDescriptorPool() || !g_vulkan_context->CreateCommandBuffers() || !g_vulkan_context->CreateTextureStreamBuffer() || - (enable_surface && (*out_swap_chain = SwapChain::Create(wi_copy, surface, true)) == nullptr)) + (enable_surface && (*out_swap_chain = SwapChain::Create(wi_copy, surface, vsync)) == nullptr)) { // Since we are destroying the instance, we're also responsible for destroying the surface. if (surface != VK_NULL_HANDLE) diff --git a/src/common/vulkan/context.h b/src/common/vulkan/context.h index d6b5fcd7e..6a802fc6e 100644 --- a/src/common/vulkan/context.h +++ b/src/common/vulkan/context.h @@ -50,7 +50,7 @@ public: // Creates a new context and sets it up as global. static bool Create(std::string_view gpu_name, const WindowInfo* wi, std::unique_ptr* out_swap_chain, - bool threaded_presentation, bool enable_debug_utils, bool enable_validation_layer); + bool threaded_presentation, bool enable_debug_utils, bool enable_validation_layer, bool vsync); // Creates a new context from a pre-existing instance. static bool CreateFromExistingInstance(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface, diff --git a/src/core/host_display.cpp b/src/core/host_display.cpp index 2c2990c36..36e05526a 100644 --- a/src/core/host_display.cpp +++ b/src/core/host_display.cpp @@ -117,6 +117,21 @@ bool HostDisplay::ShouldSkipDisplayingFrame() return false; } +void HostDisplay::ThrottlePresentation() +{ + const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f; + + const u64 sleep_period = Common::Timer::ConvertNanosecondsToValue(1e+9f / static_cast(throttle_rate)); + const u64 current_ts = Common::Timer::GetCurrentValue(); + + if (current_ts >= m_last_frame_displayed_time) + m_last_frame_displayed_time = current_ts + sleep_period; + else + m_last_frame_displayed_time += sleep_period; + + Common::Timer::SleepUntil(m_last_frame_displayed_time, false); +} + bool HostDisplay::GetHostRefreshRate(float* refresh_rate) { if (m_window_info.surface_refresh_rate > 0.0f) diff --git a/src/core/host_display.h b/src/core/host_display.h index 935bffe6d..f0c0463c1 100644 --- a/src/core/host_display.h +++ b/src/core/host_display.h @@ -67,7 +67,7 @@ public: virtual bool HasDevice() const = 0; virtual bool HasSurface() const = 0; - virtual bool CreateDevice(const WindowInfo& wi) = 0; + virtual bool CreateDevice(const WindowInfo& wi, bool vsync) = 0; virtual bool SetupDevice() = 0; virtual bool MakeCurrent() = 0; virtual bool DoneCurrent() = 0; @@ -104,6 +104,7 @@ public: virtual bool RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, GPUTexture::Format* out_format) = 0; + ALWAYS_INLINE bool IsVsyncEnabled() const { return m_vsync_enabled; } virtual void SetVSync(bool enabled) = 0; /// ImGui context management, usually called by derived classes. @@ -114,6 +115,7 @@ public: bool UsesLowerLeftOrigin() const; void SetDisplayMaxFPS(float max_fps); bool ShouldSkipDisplayingFrame(); + void ThrottlePresentation(); void ClearDisplayTexture() { @@ -243,6 +245,7 @@ protected: bool m_display_changed = false; bool m_gpu_timing_enabled = false; + bool m_vsync_enabled = false; }; /// Returns a pointer to the current host display abstraction. Assumes AcquireHostDisplay() has been caled. diff --git a/src/duckstation-nogui/nogui_host.cpp b/src/duckstation-nogui/nogui_host.cpp index 955c493c1..2ce3c6104 100644 --- a/src/duckstation-nogui/nogui_host.cpp +++ b/src/duckstation-nogui/nogui_host.cpp @@ -640,6 +640,8 @@ void NoGUIHost::CPUThreadMainLoop() Host::PumpMessagesOnCPUThread(); Host::RenderDisplay(false); + if (!g_host_display->IsVsyncEnabled()) + g_host_display->ThrottlePresentation(); } } @@ -654,7 +656,7 @@ bool NoGUIHost::AcquireHostDisplay(RenderAPI api) if (wi.has_value()) { g_host_display = Host::CreateDisplayForAPI(api); - if (g_host_display && !g_host_display->CreateDevice(wi.value())) + if (g_host_display && !g_host_display->CreateDevice(wi.value(), System::ShouldUseVSync())) g_host_display.reset(); } @@ -675,8 +677,8 @@ bool NoGUIHost::AcquireHostDisplay(RenderAPI api) return false; } - if (!g_host_display->MakeCurrent() || !g_host_display->SetupDevice() || - !ImGuiManager::Initialize() || !CommonHost::CreateHostDisplayResources()) + if (!g_host_display->MakeCurrent() || !g_host_display->SetupDevice() || !ImGuiManager::Initialize() || + !CommonHost::CreateHostDisplayResources()) { ImGuiManager::Shutdown(); CommonHost::ReleaseHostDisplayResources(); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 36d272044..4230f7de1 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -218,7 +218,7 @@ bool MainWindow::createDisplay(bool fullscreen, bool render_to_main) g_emu_thread->connectDisplaySignals(m_display_widget); - if (!g_host_display->CreateDevice(wi.value())) + if (!g_host_display->CreateDevice(wi.value(), System::ShouldUseVSync())) { QMessageBox::critical(this, tr("Error"), tr("Failed to create host display device context.")); destroyDisplayWidget(true); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 85a5452de..793d47dfb 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -754,18 +754,12 @@ bool EmuThread::acquireHostDisplay(RenderAPI api) m_is_exclusive_fullscreen = g_host_display->IsFullscreen(); - if (m_run_fullscreen_ui) + if (m_run_fullscreen_ui && !FullscreenUI::Initialize()) { - if (!FullscreenUI::Initialize()) - { - Log_ErrorPrint("Failed to initialize fullscreen UI"); - releaseHostDisplay(); - m_run_fullscreen_ui = false; - return false; - } - - // start with vsync on - g_host_display->SetVSync(true); + Log_ErrorPrint("Failed to initialize fullscreen UI"); + releaseHostDisplay(); + m_run_fullscreen_ui = false; + return false; } return true; @@ -1439,7 +1433,11 @@ void EmuThread::run() m_event_loop->processEvents(QEventLoop::AllEvents); CommonHost::PumpMessagesOnCPUThread(); if (g_host_display) + { renderDisplay(false); + if (!g_host_display->IsVsyncEnabled()) + g_host_display->ThrottlePresentation(); + } } } diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index ce7fece5a..1ef5b0ff0 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -352,9 +352,9 @@ void Host::OnInputDeviceDisconnected(const std::string_view& identifier) // noop } -void* Host::GetTopLevelWindowHandle() +std::optional Host::GetTopLevelWindowInfo() { - return nullptr; + return std::nullopt; } void Host::RefreshGameListAsync(bool invalidate_cache) diff --git a/src/duckstation-regtest/regtest_host_display.cpp b/src/duckstation-regtest/regtest_host_display.cpp index 62569fb54..394e75962 100644 --- a/src/duckstation-regtest/regtest_host_display.cpp +++ b/src/duckstation-regtest/regtest_host_display.cpp @@ -38,7 +38,7 @@ bool RegTestHostDisplay::HasSurface() const return true; } -bool RegTestHostDisplay::CreateDevice(const WindowInfo& wi) +bool RegTestHostDisplay::CreateDevice(const WindowInfo& wi, bool vsync) { m_window_info = wi; return true; diff --git a/src/duckstation-regtest/regtest_host_display.h b/src/duckstation-regtest/regtest_host_display.h index 16d8d1dab..f3275b008 100644 --- a/src/duckstation-regtest/regtest_host_display.h +++ b/src/duckstation-regtest/regtest_host_display.h @@ -15,7 +15,7 @@ public: bool HasDevice() const override; bool HasSurface() const override; - bool CreateDevice(const WindowInfo& wi) override; + bool CreateDevice(const WindowInfo& wi, bool vsync) override; bool SetupDevice() override; bool MakeCurrent() override; diff --git a/src/frontend-common/d3d11_host_display.cpp b/src/frontend-common/d3d11_host_display.cpp index dada7c366..ec6b6af27 100644 --- a/src/frontend-common/d3d11_host_display.cpp +++ b/src/frontend-common/d3d11_host_display.cpp @@ -194,10 +194,10 @@ bool D3D11HostDisplay::GetHostRefreshRate(float* refresh_rate) void D3D11HostDisplay::SetVSync(bool enabled) { - m_vsync = enabled; + m_vsync_enabled = enabled; } -bool D3D11HostDisplay::CreateDevice(const WindowInfo& wi) +bool D3D11HostDisplay::CreateDevice(const WindowInfo& wi, bool vsync) { UINT create_flags = 0; if (g_settings.gpu_use_debug_device) @@ -306,6 +306,7 @@ bool D3D11HostDisplay::CreateDevice(const WindowInfo& wi) } m_window_info = wi; + m_vsync_enabled = vsync; if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr)) { @@ -675,7 +676,7 @@ bool D3D11HostDisplay::Render(bool skip_present) // 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 // the time, when it's more like a couple of percent. - if (m_vsync && m_gpu_timing_enabled) + if (m_vsync_enabled && m_gpu_timing_enabled) PopTimestampQuery(); RenderDisplay(); @@ -685,13 +686,13 @@ bool D3D11HostDisplay::Render(bool skip_present) RenderSoftwareCursor(); - if (!m_vsync && m_gpu_timing_enabled) + if (!m_vsync_enabled && m_gpu_timing_enabled) PopTimestampQuery(); - if (!m_vsync && m_using_allow_tearing) + if (!m_vsync_enabled && m_using_allow_tearing) m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING); else - m_swap_chain->Present(BoolToUInt32(m_vsync), 0); + m_swap_chain->Present(BoolToUInt32(m_vsync_enabled), 0); if (m_gpu_timing_enabled) KickTimestampQuery(); diff --git a/src/frontend-common/d3d11_host_display.h b/src/frontend-common/d3d11_host_display.h index 8549bbe84..a67ebe56e 100644 --- a/src/frontend-common/d3d11_host_display.h +++ b/src/frontend-common/d3d11_host_display.h @@ -30,7 +30,7 @@ public: bool HasDevice() const override; bool HasSurface() const override; - bool CreateDevice(const WindowInfo& wi) override; + bool CreateDevice(const WindowInfo& wi, bool vsync) override; bool SetupDevice() override; bool MakeCurrent() override; @@ -141,7 +141,6 @@ protected: bool m_allow_tearing_supported = false; bool m_using_flip_model_swap_chain = true; bool m_using_allow_tearing = false; - bool m_vsync = true; FrontendCommon::PostProcessingChain m_post_processing_chain; D3D11::Texture m_post_processing_input_texture; diff --git a/src/frontend-common/d3d12_host_display.cpp b/src/frontend-common/d3d12_host_display.cpp index a29e710de..97409df9c 100644 --- a/src/frontend-common/d3d12_host_display.cpp +++ b/src/frontend-common/d3d12_host_display.cpp @@ -139,10 +139,10 @@ bool D3D12HostDisplay::GetHostRefreshRate(float* refresh_rate) void D3D12HostDisplay::SetVSync(bool enabled) { - m_vsync = enabled; + m_vsync_enabled = enabled; } -bool D3D12HostDisplay::CreateDevice(const WindowInfo& wi) +bool D3D12HostDisplay::CreateDevice(const WindowInfo& wi, bool vsync) { ComPtr temp_dxgi_factory; HRESULT hr = CreateDXGIFactory(IID_PPV_ARGS(temp_dxgi_factory.GetAddressOf())); @@ -198,6 +198,7 @@ bool D3D12HostDisplay::CreateDevice(const WindowInfo& wi) } m_window_info = wi; + m_vsync_enabled = vsync; if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr)) { @@ -599,10 +600,10 @@ bool D3D12HostDisplay::Render(bool skip_present) swap_chain_buf.TransitionToState(D3D12_RESOURCE_STATE_PRESENT); g_d3d12_context->ExecuteCommandList(false); - if (!m_vsync && m_using_allow_tearing) + if (!m_vsync_enabled && m_using_allow_tearing) m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING); else - m_swap_chain->Present(BoolToUInt32(m_vsync), 0); + m_swap_chain->Present(BoolToUInt32(m_vsync_enabled), 0); return true; } diff --git a/src/frontend-common/d3d12_host_display.h b/src/frontend-common/d3d12_host_display.h index 993152f0a..720eecc6c 100644 --- a/src/frontend-common/d3d12_host_display.h +++ b/src/frontend-common/d3d12_host_display.h @@ -32,7 +32,7 @@ public: bool HasDevice() const override; bool HasSurface() const override; - bool CreateDevice(const WindowInfo& wi) override; + bool CreateDevice(const WindowInfo& wi, bool vsync) override; bool SetupDevice() override; bool MakeCurrent() override; @@ -137,5 +137,4 @@ protected: bool m_allow_tearing_supported = false; bool m_using_allow_tearing = false; - bool m_vsync = true; }; diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp index c78db52d8..f04498769 100644 --- a/src/frontend-common/fullscreen_ui.cpp +++ b/src/frontend-common/fullscreen_ui.cpp @@ -638,7 +638,6 @@ void FullscreenUI::OnSystemDestroyed() if (!IsInitialized()) return; - g_host_display->SetVSync(true); s_pause_menu_was_open = false; SwitchToLanding(); } @@ -668,15 +667,7 @@ void FullscreenUI::PauseForMenuOpen() { s_was_paused_on_quick_menu_open = (System::GetState() == System::State::Paused); if (g_settings.pause_on_menu && !s_was_paused_on_quick_menu_open) - { - Host::RunOnCPUThread([]() { - System::PauseSystem(true); - - // force vsync on when pausing - if (g_host_display) - g_host_display->SetVSync(true); - }); - } + Host::RunOnCPUThread([]() { System::PauseSystem(true); }); s_pause_menu_was_open = true; } diff --git a/src/frontend-common/opengl_host_display.cpp b/src/frontend-common/opengl_host_display.cpp index 61bd17d69..cb2ed716e 100644 --- a/src/frontend-common/opengl_host_display.cpp +++ b/src/frontend-common/opengl_host_display.cpp @@ -209,7 +209,7 @@ bool OpenGLHostDisplay::SupportsTextureFormat(GPUTexture::Format format) const void OpenGLHostDisplay::SetVSync(bool enabled) { - if (m_gl_context->GetWindowInfo().type == WindowInfo::Type::Surfaceless) + if (m_vsync_enabled == enabled || m_gl_context->GetWindowInfo().type == WindowInfo::Type::Surfaceless) return; // Window framebuffer has to be bound to call SetSwapInterval. @@ -218,6 +218,7 @@ void OpenGLHostDisplay::SetVSync(bool enabled) glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); m_gl_context->SetSwapInterval(enabled ? 1 : 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); + m_vsync_enabled = enabled; } const char* OpenGLHostDisplay::GetGLSLVersionString() const @@ -281,7 +282,7 @@ bool OpenGLHostDisplay::HasSurface() const return m_window_info.type != WindowInfo::Type::Surfaceless; } -bool OpenGLHostDisplay::CreateDevice(const WindowInfo& wi) +bool OpenGLHostDisplay::CreateDevice(const WindowInfo& wi, bool vsync) { m_gl_context = GL::Context::Create(wi); if (!m_gl_context) @@ -292,6 +293,7 @@ bool OpenGLHostDisplay::CreateDevice(const WindowInfo& wi) } m_window_info = m_gl_context->GetWindowInfo(); + SetVSync(vsync); return true; } @@ -328,9 +330,6 @@ bool OpenGLHostDisplay::SetupDevice() if (!CreateResources()) return false; - // Start with vsync on. - SetVSync(true); - return true; } diff --git a/src/frontend-common/opengl_host_display.h b/src/frontend-common/opengl_host_display.h index 645390666..6d3ecc59d 100644 --- a/src/frontend-common/opengl_host_display.h +++ b/src/frontend-common/opengl_host_display.h @@ -23,7 +23,7 @@ public: bool HasDevice() const override; bool HasSurface() const override; - bool CreateDevice(const WindowInfo& wi) override; + bool CreateDevice(const WindowInfo& wi, bool vsync) override; bool SetupDevice() override; bool MakeCurrent() override; diff --git a/src/frontend-common/vulkan_host_display.cpp b/src/frontend-common/vulkan_host_display.cpp index c7435e680..df4cf980b 100644 --- a/src/frontend-common/vulkan_host_display.cpp +++ b/src/frontend-common/vulkan_host_display.cpp @@ -95,6 +95,7 @@ bool VulkanHostDisplay::ChangeWindow(const WindowInfo& new_wi) } m_window_info = m_swap_chain->GetWindowInfo(); + m_vsync_enabled = m_swap_chain->IsVSyncEnabled(); return true; } @@ -106,6 +107,7 @@ void VulkanHostDisplay::ResizeWindow(s32 new_window_width, s32 new_window_height Panic("Failed to resize swap chain"); m_window_info = m_swap_chain->GetWindowInfo(); + m_vsync_enabled = m_swap_chain->IsVSyncEnabled(); } bool VulkanHostDisplay::SupportsFullscreen() const @@ -206,19 +208,31 @@ bool VulkanHostDisplay::SupportsTextureFormat(GPUTexture::Format format) const void VulkanHostDisplay::SetVSync(bool enabled) { - if (!m_swap_chain) + if (!m_swap_chain || m_swap_chain->IsVSyncEnabled() == enabled) return; // This swap chain should not be used by the current buffer, thus safe to destroy. g_vulkan_context->WaitForGPUIdle(); m_swap_chain->SetVSync(enabled); + m_vsync_enabled = m_swap_chain->IsVSyncEnabled(); } -bool VulkanHostDisplay::CreateDevice(const WindowInfo& wi) +bool VulkanHostDisplay::CreateDevice(const WindowInfo& wi, bool vsync) { WindowInfo local_wi(wi); - if (!Vulkan::Context::Create(g_settings.gpu_adapter, &local_wi, &m_swap_chain, g_settings.gpu_threaded_presentation, - g_settings.gpu_use_debug_device, false)) + bool result = + Vulkan::Context::Create(g_settings.gpu_adapter, &local_wi, &m_swap_chain, g_settings.gpu_threaded_presentation, + g_settings.gpu_use_debug_device, g_settings.gpu_use_debug_device, vsync); + + // If validation layers were enabled, try without. + if (!result && g_settings.gpu_use_debug_device) + { + Log_WarningPrintf("Failed to create Vulkan context with validation layers, trying without."); + result = Vulkan::Context::Create(g_settings.gpu_adapter, &local_wi, &m_swap_chain, + g_settings.gpu_threaded_presentation, false, false, vsync); + } + + if (!result) { Log_ErrorPrintf("Failed to create Vulkan context"); m_window_info = {}; @@ -231,6 +245,7 @@ bool VulkanHostDisplay::CreateDevice(const WindowInfo& wi) g_vulkan_context->GetDeviceDriverProperties().driverID == VK_DRIVER_ID_QUALCOMM_PROPRIETARY); m_window_info = m_swap_chain ? m_swap_chain->GetWindowInfo() : local_wi; + m_vsync_enabled = m_swap_chain ? m_swap_chain->IsVSyncEnabled() : false; return true; } diff --git a/src/frontend-common/vulkan_host_display.h b/src/frontend-common/vulkan_host_display.h index cddd825a1..69ade7c13 100644 --- a/src/frontend-common/vulkan_host_display.h +++ b/src/frontend-common/vulkan_host_display.h @@ -27,7 +27,7 @@ public: bool HasDevice() const override; bool HasSurface() const override; - bool CreateDevice(const WindowInfo& wi) override; + bool CreateDevice(const WindowInfo& wi, bool vsync) override; bool SetupDevice() override; bool MakeCurrent() override;