HostDisplay: Manually throttle fullscreen UI presentation

Fixes flickering screen in fullscreen with Vulkan.
This commit is contained in:
Connor McLaughlin 2022-12-04 14:05:57 +10:00
parent eafa4fb1a3
commit 7d3ac98cc6
19 changed files with 81 additions and 58 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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.

View File

@ -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();

View File

@ -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);

View File

@ -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();
}
}
}

View File

@ -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)

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;