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, bool Vulkan::Context::Create(std::string_view gpu_name, const WindowInfo* wi,
std::unique_ptr<SwapChain>* out_swap_chain, bool threaded_presentation, 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"); 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) || 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->CreateAllocator() || !g_vulkan_context->CreateGlobalDescriptorPool() ||
!g_vulkan_context->CreateCommandBuffers() || !g_vulkan_context->CreateTextureStreamBuffer() || !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. // Since we are destroying the instance, we're also responsible for destroying the surface.
if (surface != VK_NULL_HANDLE) if (surface != VK_NULL_HANDLE)

View file

@ -50,7 +50,7 @@ public:
// Creates a new context and sets it up as global. // 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, 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. // Creates a new context from a pre-existing instance.
static bool CreateFromExistingInstance(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface, static bool CreateFromExistingInstance(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface,

View file

@ -117,6 +117,21 @@ bool HostDisplay::ShouldSkipDisplayingFrame()
return false; 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) bool HostDisplay::GetHostRefreshRate(float* refresh_rate)
{ {
if (m_window_info.surface_refresh_rate > 0.0f) if (m_window_info.surface_refresh_rate > 0.0f)

View file

@ -67,7 +67,7 @@ public:
virtual bool HasDevice() const = 0; virtual bool HasDevice() const = 0;
virtual bool HasSurface() 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 SetupDevice() = 0;
virtual bool MakeCurrent() = 0; virtual bool MakeCurrent() = 0;
virtual bool DoneCurrent() = 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, virtual bool RenderScreenshot(u32 width, u32 height, std::vector<u32>* out_pixels, u32* out_stride,
GPUTexture::Format* out_format) = 0; GPUTexture::Format* out_format) = 0;
ALWAYS_INLINE bool IsVsyncEnabled() const { return m_vsync_enabled; }
virtual void SetVSync(bool enabled) = 0; virtual void SetVSync(bool enabled) = 0;
/// ImGui context management, usually called by derived classes. /// ImGui context management, usually called by derived classes.
@ -114,6 +115,7 @@ public:
bool UsesLowerLeftOrigin() const; bool UsesLowerLeftOrigin() const;
void SetDisplayMaxFPS(float max_fps); void SetDisplayMaxFPS(float max_fps);
bool ShouldSkipDisplayingFrame(); bool ShouldSkipDisplayingFrame();
void ThrottlePresentation();
void ClearDisplayTexture() void ClearDisplayTexture()
{ {
@ -243,6 +245,7 @@ protected:
bool m_display_changed = false; bool m_display_changed = false;
bool m_gpu_timing_enabled = 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. /// 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::PumpMessagesOnCPUThread();
Host::RenderDisplay(false); 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()) if (wi.has_value())
{ {
g_host_display = Host::CreateDisplayForAPI(api); 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(); g_host_display.reset();
} }
@ -675,8 +677,8 @@ bool NoGUIHost::AcquireHostDisplay(RenderAPI api)
return false; return false;
} }
if (!g_host_display->MakeCurrent() || !g_host_display->SetupDevice() || if (!g_host_display->MakeCurrent() || !g_host_display->SetupDevice() || !ImGuiManager::Initialize() ||
!ImGuiManager::Initialize() || !CommonHost::CreateHostDisplayResources()) !CommonHost::CreateHostDisplayResources())
{ {
ImGuiManager::Shutdown(); ImGuiManager::Shutdown();
CommonHost::ReleaseHostDisplayResources(); CommonHost::ReleaseHostDisplayResources();

View file

@ -218,7 +218,7 @@ bool MainWindow::createDisplay(bool fullscreen, bool render_to_main)
g_emu_thread->connectDisplaySignals(m_display_widget); 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.")); QMessageBox::critical(this, tr("Error"), tr("Failed to create host display device context."));
destroyDisplayWidget(true); destroyDisplayWidget(true);

View file

@ -754,18 +754,12 @@ bool EmuThread::acquireHostDisplay(RenderAPI api)
m_is_exclusive_fullscreen = g_host_display->IsFullscreen(); 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();
Log_ErrorPrint("Failed to initialize fullscreen UI"); m_run_fullscreen_ui = false;
releaseHostDisplay(); return false;
m_run_fullscreen_ui = false;
return false;
}
// start with vsync on
g_host_display->SetVSync(true);
} }
return true; return true;
@ -1439,7 +1433,11 @@ void EmuThread::run()
m_event_loop->processEvents(QEventLoop::AllEvents); m_event_loop->processEvents(QEventLoop::AllEvents);
CommonHost::PumpMessagesOnCPUThread(); CommonHost::PumpMessagesOnCPUThread();
if (g_host_display) if (g_host_display)
{
renderDisplay(false); 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 // noop
} }
void* Host::GetTopLevelWindowHandle() std::optional<WindowInfo> Host::GetTopLevelWindowInfo()
{ {
return nullptr; return std::nullopt;
} }
void Host::RefreshGameListAsync(bool invalidate_cache) void Host::RefreshGameListAsync(bool invalidate_cache)

View file

@ -38,7 +38,7 @@ bool RegTestHostDisplay::HasSurface() const
return true; return true;
} }
bool RegTestHostDisplay::CreateDevice(const WindowInfo& wi) bool RegTestHostDisplay::CreateDevice(const WindowInfo& wi, bool vsync)
{ {
m_window_info = wi; m_window_info = wi;
return true; return true;

View file

@ -15,7 +15,7 @@ public:
bool HasDevice() const override; bool HasDevice() const override;
bool HasSurface() const override; bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi) override; bool CreateDevice(const WindowInfo& wi, bool vsync) override;
bool SetupDevice() override; bool SetupDevice() override;
bool MakeCurrent() override; bool MakeCurrent() override;

View file

@ -194,10 +194,10 @@ bool D3D11HostDisplay::GetHostRefreshRate(float* refresh_rate)
void D3D11HostDisplay::SetVSync(bool enabled) 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; UINT create_flags = 0;
if (g_settings.gpu_use_debug_device) if (g_settings.gpu_use_debug_device)
@ -306,6 +306,7 @@ bool D3D11HostDisplay::CreateDevice(const WindowInfo& wi)
} }
m_window_info = wi; m_window_info = wi;
m_vsync_enabled = vsync;
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr)) 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 // This blows our our GPU usage number considerably, so read the timestamp before the final blit
// in this configuration. It does reduce accuracy a little, but better than seeing 100% all of // in this configuration. It does reduce accuracy a little, but better than seeing 100% all of
// the time, when it's more like a couple of percent. // 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(); PopTimestampQuery();
RenderDisplay(); RenderDisplay();
@ -685,13 +686,13 @@ bool D3D11HostDisplay::Render(bool skip_present)
RenderSoftwareCursor(); RenderSoftwareCursor();
if (!m_vsync && m_gpu_timing_enabled) if (!m_vsync_enabled && m_gpu_timing_enabled)
PopTimestampQuery(); 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); m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING);
else else
m_swap_chain->Present(BoolToUInt32(m_vsync), 0); m_swap_chain->Present(BoolToUInt32(m_vsync_enabled), 0);
if (m_gpu_timing_enabled) if (m_gpu_timing_enabled)
KickTimestampQuery(); KickTimestampQuery();

View file

@ -30,7 +30,7 @@ public:
bool HasDevice() const override; bool HasDevice() const override;
bool HasSurface() const override; bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi) override; bool CreateDevice(const WindowInfo& wi, bool vsync) override;
bool SetupDevice() override; bool SetupDevice() override;
bool MakeCurrent() override; bool MakeCurrent() override;
@ -141,7 +141,6 @@ protected:
bool m_allow_tearing_supported = false; bool m_allow_tearing_supported = false;
bool m_using_flip_model_swap_chain = true; bool m_using_flip_model_swap_chain = true;
bool m_using_allow_tearing = false; bool m_using_allow_tearing = false;
bool m_vsync = true;
FrontendCommon::PostProcessingChain m_post_processing_chain; FrontendCommon::PostProcessingChain m_post_processing_chain;
D3D11::Texture m_post_processing_input_texture; D3D11::Texture m_post_processing_input_texture;

View file

@ -139,10 +139,10 @@ bool D3D12HostDisplay::GetHostRefreshRate(float* refresh_rate)
void D3D12HostDisplay::SetVSync(bool enabled) 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; ComPtr<IDXGIFactory> temp_dxgi_factory;
HRESULT hr = CreateDXGIFactory(IID_PPV_ARGS(temp_dxgi_factory.GetAddressOf())); 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_window_info = wi;
m_vsync_enabled = vsync;
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr)) 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); swap_chain_buf.TransitionToState(D3D12_RESOURCE_STATE_PRESENT);
g_d3d12_context->ExecuteCommandList(false); 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); m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING);
else else
m_swap_chain->Present(BoolToUInt32(m_vsync), 0); m_swap_chain->Present(BoolToUInt32(m_vsync_enabled), 0);
return true; return true;
} }

View file

@ -32,7 +32,7 @@ public:
bool HasDevice() const override; bool HasDevice() const override;
bool HasSurface() const override; bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi) override; bool CreateDevice(const WindowInfo& wi, bool vsync) override;
bool SetupDevice() override; bool SetupDevice() override;
bool MakeCurrent() override; bool MakeCurrent() override;
@ -137,5 +137,4 @@ protected:
bool m_allow_tearing_supported = false; bool m_allow_tearing_supported = false;
bool m_using_allow_tearing = false; bool m_using_allow_tearing = false;
bool m_vsync = true;
}; };

View file

@ -638,7 +638,6 @@ void FullscreenUI::OnSystemDestroyed()
if (!IsInitialized()) if (!IsInitialized())
return; return;
g_host_display->SetVSync(true);
s_pause_menu_was_open = false; s_pause_menu_was_open = false;
SwitchToLanding(); SwitchToLanding();
} }
@ -668,15 +667,7 @@ void FullscreenUI::PauseForMenuOpen()
{ {
s_was_paused_on_quick_menu_open = (System::GetState() == System::State::Paused); 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) if (g_settings.pause_on_menu && !s_was_paused_on_quick_menu_open)
{ Host::RunOnCPUThread([]() { System::PauseSystem(true); });
Host::RunOnCPUThread([]() {
System::PauseSystem(true);
// force vsync on when pausing
if (g_host_display)
g_host_display->SetVSync(true);
});
}
s_pause_menu_was_open = 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) 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; return;
// Window framebuffer has to be bound to call SetSwapInterval. // Window framebuffer has to be bound to call SetSwapInterval.
@ -218,6 +218,7 @@ void OpenGLHostDisplay::SetVSync(bool enabled)
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
m_gl_context->SetSwapInterval(enabled ? 1 : 0); m_gl_context->SetSwapInterval(enabled ? 1 : 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
m_vsync_enabled = enabled;
} }
const char* OpenGLHostDisplay::GetGLSLVersionString() const const char* OpenGLHostDisplay::GetGLSLVersionString() const
@ -281,7 +282,7 @@ bool OpenGLHostDisplay::HasSurface() const
return m_window_info.type != WindowInfo::Type::Surfaceless; 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); m_gl_context = GL::Context::Create(wi);
if (!m_gl_context) if (!m_gl_context)
@ -292,6 +293,7 @@ bool OpenGLHostDisplay::CreateDevice(const WindowInfo& wi)
} }
m_window_info = m_gl_context->GetWindowInfo(); m_window_info = m_gl_context->GetWindowInfo();
SetVSync(vsync);
return true; return true;
} }
@ -328,9 +330,6 @@ bool OpenGLHostDisplay::SetupDevice()
if (!CreateResources()) if (!CreateResources())
return false; return false;
// Start with vsync on.
SetVSync(true);
return true; return true;
} }

View file

@ -23,7 +23,7 @@ public:
bool HasDevice() const override; bool HasDevice() const override;
bool HasSurface() const override; bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi) override; bool CreateDevice(const WindowInfo& wi, bool vsync) override;
bool SetupDevice() override; bool SetupDevice() override;
bool MakeCurrent() 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_window_info = m_swap_chain->GetWindowInfo();
m_vsync_enabled = m_swap_chain->IsVSyncEnabled();
return true; return true;
} }
@ -106,6 +107,7 @@ void VulkanHostDisplay::ResizeWindow(s32 new_window_width, s32 new_window_height
Panic("Failed to resize swap chain"); Panic("Failed to resize swap chain");
m_window_info = m_swap_chain->GetWindowInfo(); m_window_info = m_swap_chain->GetWindowInfo();
m_vsync_enabled = m_swap_chain->IsVSyncEnabled();
} }
bool VulkanHostDisplay::SupportsFullscreen() const bool VulkanHostDisplay::SupportsFullscreen() const
@ -206,19 +208,31 @@ bool VulkanHostDisplay::SupportsTextureFormat(GPUTexture::Format format) const
void VulkanHostDisplay::SetVSync(bool enabled) void VulkanHostDisplay::SetVSync(bool enabled)
{ {
if (!m_swap_chain) if (!m_swap_chain || m_swap_chain->IsVSyncEnabled() == enabled)
return; return;
// This swap chain should not be used by the current buffer, thus safe to destroy. // This swap chain should not be used by the current buffer, thus safe to destroy.
g_vulkan_context->WaitForGPUIdle(); g_vulkan_context->WaitForGPUIdle();
m_swap_chain->SetVSync(enabled); 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); WindowInfo local_wi(wi);
if (!Vulkan::Context::Create(g_settings.gpu_adapter, &local_wi, &m_swap_chain, g_settings.gpu_threaded_presentation, bool result =
g_settings.gpu_use_debug_device, false)) 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"); Log_ErrorPrintf("Failed to create Vulkan context");
m_window_info = {}; m_window_info = {};
@ -231,6 +245,7 @@ bool VulkanHostDisplay::CreateDevice(const WindowInfo& wi)
g_vulkan_context->GetDeviceDriverProperties().driverID == VK_DRIVER_ID_QUALCOMM_PROPRIETARY); g_vulkan_context->GetDeviceDriverProperties().driverID == VK_DRIVER_ID_QUALCOMM_PROPRIETARY);
m_window_info = m_swap_chain ? m_swap_chain->GetWindowInfo() : local_wi; 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; return true;
} }

View file

@ -27,7 +27,7 @@ public:
bool HasDevice() const override; bool HasDevice() const override;
bool HasSurface() const override; bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi) override; bool CreateDevice(const WindowInfo& wi, bool vsync) override;
bool SetupDevice() override; bool SetupDevice() override;
bool MakeCurrent() override; bool MakeCurrent() override;