HostDisplay: Manually throttle fullscreen UI presentation
Fixes flickering screen in fullscreen with Vulkan.
This commit is contained in:
parent
eafa4fb1a3
commit
7d3ac98cc6
|
@ -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<SwapChain>* 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)
|
||||
|
|
|
@ -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<SwapChain>* 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,
|
||||
|
|
|
@ -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<double>(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)
|
||||
|
|
|
@ -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<u32>* 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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -352,9 +352,9 @@ void Host::OnInputDeviceDisconnected(const std::string_view& identifier)
|
|||
// noop
|
||||
}
|
||||
|
||||
void* Host::GetTopLevelWindowHandle()
|
||||
std::optional<WindowInfo> Host::GetTopLevelWindowInfo()
|
||||
{
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Host::RefreshGameListAsync(bool invalidate_cache)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<IDXGIFactory> 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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue