System: Fallback to FIFO on AMD (no mailbox support)

This commit is contained in:
Stenzek 2024-05-24 22:48:06 +10:00
parent 98520978a8
commit 0f9a255093
No known key found for this signature in database
17 changed files with 201 additions and 174 deletions

View file

@ -285,12 +285,13 @@ bool Host::CreateGPUDevice(RenderAPI api, Error* error)
disabled_features |= GPUDevice::FEATURE_MASK_TEXTURE_COPY_TO_SELF;
Error create_error;
if (!g_gpu_device || !g_gpu_device->Create(
g_settings.gpu_adapter,
g_settings.gpu_disable_shader_cache ? std::string_view() : std::string_view(EmuFolders::Cache),
SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device, System::GetEffectiveVSyncMode(),
g_settings.gpu_threaded_presentation, exclusive_fullscreen_control,
static_cast<GPUDevice::FeatureMask>(disabled_features), &create_error))
if (!g_gpu_device || !g_gpu_device->Create(g_settings.gpu_adapter,
g_settings.gpu_disable_shader_cache ? std::string_view() :
std::string_view(EmuFolders::Cache),
SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device,
System::GetEffectiveVSyncMode(), System::ShouldAllowPresentThrottle(),
g_settings.gpu_threaded_presentation, exclusive_fullscreen_control,
static_cast<GPUDevice::FeatureMask>(disabled_features), &create_error))
{
ERROR_LOG("Failed to create GPU device: {}", create_error.GetDescription());
if (g_gpu_device)

View file

@ -123,7 +123,6 @@ static void UpdatePerformanceCounters();
static void AccumulatePreFrameSleepTime();
static void UpdatePreFrameSleepTime();
static void UpdateDisplayVSync();
static void UpdateDisplayMaxFPS();
static void SetRewinding(bool enabled);
static bool SaveRewindState();
@ -186,6 +185,8 @@ static bool s_turbo_enabled = false;
static bool s_throttler_enabled = false;
static bool s_optimal_frame_pacing = false;
static bool s_pre_frame_sleep = false;
static bool s_can_sync_to_host = false;
static bool s_syncing_to_host = false;
static bool s_syncing_to_host_with_vsync = false;
static bool s_skip_presenting_duplicate_frames = false;
static u32 s_skipped_frame_count = 0;
@ -1264,7 +1265,7 @@ void System::PauseSystem(bool paused)
Host::OnSystemPaused();
Host::OnIdleStateChanged();
UpdateDisplayMaxFPS();
UpdateDisplayVSync();
InvalidateDisplay();
}
else
@ -1279,7 +1280,7 @@ void System::PauseSystem(bool paused)
Host::OnSystemResumed();
Host::OnIdleStateChanged();
UpdateDisplayMaxFPS();
UpdateDisplayVSync();
ResetPerformanceCounters();
ResetThrottler();
}
@ -1823,7 +1824,6 @@ void System::DestroySystem()
if (s_keep_gpu_device_on_shutdown && g_gpu_device)
{
UpdateDisplayVSync();
UpdateDisplayMaxFPS();
}
else
{
@ -1982,7 +1982,7 @@ void System::FrameDone()
s_skipped_frame_count < MAX_SKIPPED_DUPLICATE_FRAME_COUNT) ||
(!s_optimal_frame_pacing && current_time > s_next_frame_time &&
s_skipped_frame_count < MAX_SKIPPED_TIMEOUT_FRAME_COUNT) ||
g_gpu_device->ShouldSkipDisplayingFrame()) &&
g_gpu_device->ShouldSkipPresentingFrame()) &&
!s_syncing_to_host_with_vsync && !IsExecutionInterrupted());
if (!skip_this_frame)
{
@ -2851,25 +2851,32 @@ void System::UpdateSpeedLimiterState()
g_gpu_device->GetWindowInfo().IsSurfaceless(); // surfaceless check for regtest
s_skip_presenting_duplicate_frames = s_throttler_enabled && g_settings.display_skip_presenting_duplicate_frames;
s_pre_frame_sleep = s_optimal_frame_pacing && g_settings.display_pre_frame_sleep;
s_can_sync_to_host = false;
s_syncing_to_host = false;
s_syncing_to_host_with_vsync = false;
if (const float host_refresh_rate = g_gpu_device->GetWindowInfo().surface_refresh_rate; host_refresh_rate > 0.0f)
if (g_settings.sync_to_host_refresh_rate)
{
const float ratio = host_refresh_rate / System::GetThrottleFrequency();
const bool can_sync_to_host = (ratio >= 0.95f && ratio <= 1.05f);
INFO_LOG("Refresh rate: Host={}hz Guest={}hz Ratio={} - {}", host_refresh_rate, System::GetThrottleFrequency(),
ratio, can_sync_to_host ? "can sync" : "can't sync");
if (can_sync_to_host && g_settings.sync_to_host_refresh_rate && s_target_speed == 1.0f)
const float host_refresh_rate = g_gpu_device->GetWindowInfo().surface_refresh_rate;
if (host_refresh_rate > 0.0f)
{
s_target_speed = ratio;
const float ratio = host_refresh_rate / System::GetThrottleFrequency();
s_can_sync_to_host = (ratio >= 0.95f && ratio <= 1.05f);
INFO_LOG("Refresh rate: Host={}hz Guest={}hz Ratio={} - {}", host_refresh_rate, System::GetThrottleFrequency(),
ratio, s_can_sync_to_host ? "can sync" : "can't sync");
// When syncing to host and using vsync, we don't need to sleep.
s_syncing_to_host_with_vsync = g_settings.display_vsync;
if (s_syncing_to_host_with_vsync)
s_syncing_to_host = (s_can_sync_to_host && g_settings.sync_to_host_refresh_rate && s_target_speed == 1.0f);
if (s_syncing_to_host)
{
INFO_LOG("Using host vsync for throttling.");
s_throttler_enabled = false;
s_target_speed = ratio;
// When syncing to host and using vsync, we don't need to sleep.
s_syncing_to_host_with_vsync = g_settings.display_vsync;
if (s_syncing_to_host_with_vsync)
{
INFO_LOG("Using host vsync for throttling.");
s_throttler_enabled = false;
}
}
}
}
@ -2885,7 +2892,6 @@ void System::UpdateSpeedLimiterState()
UpdateThrottlePeriod();
ResetThrottler();
UpdateDisplayVSync();
UpdateDisplayMaxFPS();
if (g_settings.increase_timer_resolution)
SetTimerResolutionIncreased(s_throttler_enabled);
@ -2895,30 +2901,18 @@ void System::UpdateDisplayVSync()
{
static constexpr std::array<const char*, static_cast<size_t>(GPUVSyncMode::Count)> vsync_modes = {{
"Disabled",
"DoubleBuffered",
"TripleBuffered",
"FIFO",
"Mailbox",
}};
// Avoid flipping vsync on and off by manually throttling when vsync is on.
const GPUVSyncMode vsync_mode = GetEffectiveVSyncMode();
VERBOSE_LOG("VSync: {}{}", vsync_modes[static_cast<size_t>(vsync_mode)],
s_syncing_to_host_with_vsync ? " (for throttling)" : "");
const bool allow_present_throttle = ShouldAllowPresentThrottle();
VERBOSE_LOG("VSync: {}{}{}", vsync_modes[static_cast<size_t>(vsync_mode)],
s_syncing_to_host_with_vsync ? " (for throttling)" : "",
allow_present_throttle ? " (present throttle allowed)" : "");
g_gpu_device->SetVSyncMode(vsync_mode);
}
void System::UpdateDisplayMaxFPS()
{
const GPUVSyncMode vsync_mode = GetEffectiveVSyncMode();
const float max_display_fps =
(!IsPaused() && IsValid() &&
(s_target_speed == 0.0f ||
(vsync_mode != GPUVSyncMode::Disabled && s_target_speed != 1.0f && !s_syncing_to_host_with_vsync))) ?
g_gpu_device->GetWindowInfo().surface_refresh_rate :
0.0f;
VERBOSE_LOG("Max display fps: {}", max_display_fps);
g_gpu_device->SetDisplayMaxFPS(max_display_fps);
g_gpu_device->SetVSyncMode(vsync_mode, allow_present_throttle);
}
GPUVSyncMode System::GetEffectiveVSyncMode()
@ -2928,16 +2922,21 @@ GPUVSyncMode System::GetEffectiveVSyncMode()
return GPUVSyncMode::Disabled;
// If there's no VM, or we're using vsync for timing, then we always use double-buffered (blocking).
if (s_state == State::Shutdown || s_state == State::Stopping || s_syncing_to_host_with_vsync)
return GPUVSyncMode::DoubleBuffered;
// Try to keep the same present mode whether we're running or not, since it'll avoid flicker.
const bool valid_vm = (s_state != State::Shutdown && s_state != State::Stopping);
if (s_can_sync_to_host || (!valid_vm && g_settings.sync_to_host_refresh_rate))
return GPUVSyncMode::FIFO;
// For PAL games, we always want to triple buffer, because otherwise we'll be tearing.
// Or for when we aren't using sync-to-host-refresh, to avoid dropping frames.
// Force vsync off when not running at 100% speed.
// We can avoid this by manually throttling when vsync is on (see above).
return (s_throttler_enabled && g_gpu_device->GetWindowInfo().surface_refresh_rate == 0.0f) ?
GPUVSyncMode::Disabled :
GPUVSyncMode::TripleBuffered;
// Allow present skipping when running outside of normal speed, if mailbox isn't supported.
return GPUVSyncMode::Mailbox;
}
bool System::ShouldAllowPresentThrottle()
{
const bool valid_vm = (s_state != State::Shutdown && s_state != State::Stopping);
return !valid_vm || !IsRunningAtNonStandardSpeed();
}
bool System::IsFastForwardEnabled()
@ -4037,10 +4036,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
if (g_gpu_device)
{
if (g_settings.display_vsync != old_settings.display_vsync)
{
UpdateDisplayVSync();
UpdateDisplayMaxFPS();
}
}
}
@ -4553,8 +4549,7 @@ bool System::IsRunningAtNonStandardSpeed()
if (!IsValid())
return false;
const float target_speed = GetTargetSpeed();
return (target_speed <= 0.95f || target_speed >= 1.05f);
return (s_target_speed == 1.0f || s_syncing_to_host);
}
s32 System::GetAudioOutputVolume()

View file

@ -12,6 +12,7 @@
#include <memory>
#include <optional>
#include <string>
#include <utility>
class ByteStream;
class CDImage;
@ -463,6 +464,7 @@ bool IsRunningAtNonStandardSpeed();
/// Returns true if vsync should be used.
GPUVSyncMode GetEffectiveVSyncMode();
bool ShouldAllowPresentThrottle();
/// Quick switch between software and hardware rendering.
void ToggleSoftwareRendering();

View file

@ -201,7 +201,7 @@ u32 D3D11Device::GetSwapChainBufferCount() const
{
// With vsync off, we only need two buffers. Same for blocking vsync.
// With triple buffering, we need three.
return (m_vsync_mode == GPUVSyncMode::TripleBuffered) ? 3 : 2;
return (m_vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2;
}
bool D3D11Device::CreateSwapChain()
@ -588,8 +588,10 @@ void D3D11Device::InvalidateRenderTarget(GPUTexture* t)
static_cast<D3D11Texture*>(t)->CommitClear(m_context.Get());
}
void D3D11Device::SetVSyncMode(GPUVSyncMode mode)
void D3D11Device::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
{
m_allow_present_throttle = allow_present_throttle;
if (m_vsync_mode == mode)
return;
@ -634,7 +636,7 @@ bool D3D11Device::BeginPresent(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 (IsVSyncModeBlocking() && m_gpu_timing_enabled)
if (m_vsync_mode == GPUVSyncMode::FIFO && m_gpu_timing_enabled)
PopTimestampQuery();
static constexpr float clear_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
@ -652,16 +654,12 @@ void D3D11Device::EndPresent(bool explicit_present)
DebugAssert(!explicit_present);
DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target);
if (!IsVSyncModeBlocking() && m_gpu_timing_enabled)
if (m_vsync_mode != GPUVSyncMode::FIFO && m_gpu_timing_enabled)
PopTimestampQuery();
// DirectX has no concept of tear-or-sync. I guess if we measured times ourselves, we could implement it.
if (IsVSyncModeBlocking())
m_swap_chain->Present(1, 0);
else if (m_using_allow_tearing) // Disabled or VRR, VRR requires the allow tearing flag :/
m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING);
else
m_swap_chain->Present(0, 0);
const UINT sync_interval = static_cast<UINT>(m_vsync_mode == GPUVSyncMode::FIFO);
const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0;
m_swap_chain->Present(sync_interval, flags);
if (m_gpu_timing_enabled)
KickTimestampQuery();

View file

@ -96,7 +96,7 @@ public:
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override;
void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
void SetVSyncMode(GPUVSyncMode mode) override;
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;

View file

@ -801,7 +801,7 @@ u32 D3D12Device::GetSwapChainBufferCount() const
{
// With vsync off, we only need two buffers. Same for blocking vsync.
// With triple buffering, we need three.
return (m_vsync_mode == GPUVSyncMode::TripleBuffered) ? 3 : 2;
return (m_vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2;
}
bool D3D12Device::CreateSwapChain()
@ -1084,8 +1084,9 @@ std::string D3D12Device::GetDriverInfo() const
return ret;
}
void D3D12Device::SetVSyncMode(GPUVSyncMode mode)
void D3D12Device::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
{
m_allow_present_throttle = allow_present_throttle;
if (m_vsync_mode == mode)
return;
@ -1158,13 +1159,9 @@ void D3D12Device::SubmitPresent()
{
DebugAssert(m_swap_chain);
// DirectX has no concept of tear-or-sync. I guess if we measured times ourselves, we could implement it.
if (IsVSyncModeBlocking())
m_swap_chain->Present(1, 0);
else if (m_using_allow_tearing) // Disabled or VRR, VRR requires the allow tearing flag :/
m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING);
else
m_swap_chain->Present(0, 0);
const UINT sync_interval = static_cast<UINT>(m_vsync_mode == GPUVSyncMode::FIFO);
const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0;
m_swap_chain->Present(sync_interval, flags);
}
#ifdef _DEBUG

View file

@ -118,7 +118,7 @@ public:
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override;
void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
void SetVSyncMode(GPUVSyncMode mode) override;
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;

View file

@ -276,10 +276,11 @@ bool GPUDevice::IsSameRenderAPI(RenderAPI lhs, RenderAPI rhs)
}
bool GPUDevice::Create(std::string_view adapter, std::string_view shader_cache_path, u32 shader_cache_version,
bool debug_device, GPUVSyncMode vsync, bool threaded_presentation,
bool debug_device, GPUVSyncMode vsync, bool allow_present_throttle, bool threaded_presentation,
std::optional<bool> exclusive_fullscreen_control, FeatureMask disabled_features, Error* error)
{
m_vsync_mode = vsync;
m_allow_present_throttle = allow_present_throttle;
m_debug_device = debug_device;
if (!AcquireWindow(true))
@ -970,11 +971,6 @@ void GPUDevice::TrimTexturePool()
}
}
void GPUDevice::SetDisplayMaxFPS(float max_fps)
{
m_display_frame_interval = (max_fps > 0.0f) ? (1.0f / max_fps) : 0.0f;
}
bool GPUDevice::ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u32 new_height, GPUTexture::Type type,
GPUTexture::Format format, bool preserve /* = true */)
{
@ -1015,14 +1011,18 @@ bool GPUDevice::ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u
return true;
}
bool GPUDevice::ShouldSkipDisplayingFrame()
bool GPUDevice::ShouldSkipPresentingFrame()
{
if (m_display_frame_interval == 0.0f)
// Only needed with FIFO. But since we're so fast, we allow it always.
if (!m_allow_present_throttle)
return false;
const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f;
const float throttle_period = 1.0f / throttle_rate;
const u64 now = Common::Timer::GetCurrentValue();
const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time);
if (diff < m_display_frame_interval)
if (diff < throttle_period)
return true;
m_last_frame_displayed_time = now;

View file

@ -39,8 +39,8 @@ enum class RenderAPI : u32
enum class GPUVSyncMode : u8
{
Disabled,
DoubleBuffered,
TripleBuffered,
FIFO,
Mailbox,
Count
};
@ -581,8 +581,8 @@ public:
virtual RenderAPI GetRenderAPI() const = 0;
bool Create(std::string_view adapter, std::string_view shader_cache_path, u32 shader_cache_version, bool debug_device,
GPUVSyncMode vsync, bool threaded_presentation, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error);
GPUVSyncMode vsync, bool allow_present_throttle, bool threaded_presentation,
std::optional<bool> exclusive_fullscreen_control, FeatureMask disabled_features, Error* error);
void Destroy();
virtual bool HasSurface() const = 0;
@ -681,8 +681,8 @@ public:
void RenderImGui();
ALWAYS_INLINE GPUVSyncMode GetVSyncMode() const { return m_vsync_mode; }
ALWAYS_INLINE bool IsVSyncModeBlocking() const { return (m_vsync_mode >= GPUVSyncMode::DoubleBuffered); }
virtual void SetVSyncMode(GPUVSyncMode mode) = 0;
ALWAYS_INLINE bool IsVSyncModeBlocking() const { return (m_vsync_mode == GPUVSyncMode::FIFO); }
virtual void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) = 0;
ALWAYS_INLINE bool IsDebugDevice() const { return m_debug_device; }
ALWAYS_INLINE size_t GetVRAMUsage() const { return s_total_vram_usage; }
@ -690,10 +690,9 @@ public:
bool UpdateImGuiFontTexture();
bool UsesLowerLeftOrigin() const;
static Common::Rectangle<s32> FlipToLowerLeft(const Common::Rectangle<s32>& rc, s32 target_height);
void SetDisplayMaxFPS(float max_fps);
bool ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u32 new_height, GPUTexture::Type type,
GPUTexture::Format format, bool preserve = true);
bool ShouldSkipDisplayingFrame();
bool ShouldSkipPresentingFrame();
void ThrottlePresentation();
virtual bool SupportsTextureFormat(GPUTexture::Format format) const = 0;
@ -736,6 +735,7 @@ protected:
u32 m_max_multisamples = 0;
WindowInfo m_window_info;
u64 m_last_frame_displayed_time = 0;
GPUShaderCache m_shader_cache;
@ -793,14 +793,11 @@ private:
size_t m_pool_vram_usage = 0;
u32 m_texture_pool_counter = 0;
// TODO: Move out.
u64 m_last_frame_displayed_time = 0;
float m_display_frame_interval = 0.0f;
protected:
static Statistics s_stats;
GPUVSyncMode m_vsync_mode = GPUVSyncMode::Disabled;
bool m_allow_present_throttle = false;
bool m_gpu_timing_enabled = false;
bool m_debug_device = false;
};

View file

@ -263,7 +263,7 @@ public:
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
void SetVSyncMode(GPUVSyncMode mode) override;
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
bool BeginPresent(bool skip_present) override;
void EndPresent(bool explicit_submit) override;

View file

@ -115,14 +115,18 @@ bool MetalDevice::HasSurface() const
return (m_layer != nil);
}
void MetalDevice::SetVSyncMode(GPUVSyncMode mode)
void MetalDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
{
// Metal does not support mailbox mode.
mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode;
m_allow_present_throttle = allow_present_throttle;
if (m_vsync_mode == mode)
return;
m_vsync_mode = mode;
if (m_layer != nil)
[m_layer setDisplaySyncEnabled:m_vsync_mode >= GPUVSyncMode::DoubleBuffered];
[m_layer setDisplaySyncEnabled:m_vsync_mode == GPUVSyncMode::FIFO];
}
bool MetalDevice::CreateDevice(std::string_view adapter, bool threaded_presentation,
@ -396,7 +400,9 @@ bool MetalDevice::CreateLayer()
}
});
[m_layer setDisplaySyncEnabled:m_vsync_mode >= GPUVSyncMode::DoubleBuffered];
// Metal does not support mailbox mode.
m_vsync_mode = (m_vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : m_vsync_mode;
[m_layer setDisplaySyncEnabled:m_vsync_mode == GPUVSyncMode::FIFO];
DebugAssert(m_layer_pass_desc == nil);
m_layer_pass_desc = [[MTLRenderPassDescriptor renderPassDescriptor] retain];

View file

@ -238,8 +238,12 @@ void OpenGLDevice::InsertDebugMessage(const char* msg)
#endif
}
void OpenGLDevice::SetVSyncMode(GPUVSyncMode mode)
void OpenGLDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
{
// OpenGL does not support Mailbox.
mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode;
m_allow_present_throttle = allow_present_throttle;
if (m_vsync_mode == mode)
return;
@ -286,6 +290,7 @@ bool OpenGLDevice::CreateDevice(std::string_view adapter, bool threaded_presenta
// Is this needed?
m_window_info = m_gl_context->GetWindowInfo();
m_vsync_mode = (m_vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : m_vsync_mode;
const bool opengl_is_available =
((!m_gl_context->IsGLES() && (GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) ||
@ -583,7 +588,7 @@ void OpenGLDevice::SetSwapInterval()
return;
// Window framebuffer has to be bound to call SetSwapInterval.
const s32 interval = (m_vsync_mode >= GPUVSyncMode::DoubleBuffered) ? 1 : 0;
const s32 interval = static_cast<s32>(m_vsync_mode == GPUVSyncMode::FIFO);
GLint current_fbo = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

View file

@ -100,7 +100,7 @@ public:
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override;
void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
void SetVSyncMode(GPUVSyncMode mode) override;
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
bool BeginPresent(bool skip_present) override;
void EndPresent(bool explicit_present) override;

View file

@ -436,19 +436,6 @@ bool VulkanDevice::SelectDeviceFeatures()
return true;
}
VkPresentModeKHR VulkanDevice::SelectPresentMode() const
{
// Use mailbox/triple buffering for "normal" vsync, due to PAL refresh rate mismatch.
// Otherwise, use FIFO when syncing to host, because we don't want to return early.
static constexpr std::array<VkPresentModeKHR, static_cast<size_t>(GPUVSyncMode::Count)> modes = {{
VK_PRESENT_MODE_IMMEDIATE_KHR, // Disabled
VK_PRESENT_MODE_FIFO_KHR, // DoubleBuffered
VK_PRESENT_MODE_MAILBOX_KHR, // TripleBuffered
}};
return modes[static_cast<size_t>(m_vsync_mode)];
}
bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer)
{
u32 queue_family_count;
@ -2052,8 +2039,9 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, bool threaded_presenta
if (surface != VK_NULL_HANDLE)
{
m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, SelectPresentMode(), m_exclusive_fullscreen_control);
if (!m_swap_chain)
VkPresentModeKHR present_mode;
if (!VulkanSwapChain::SelectPresentMode(surface, &m_vsync_mode, &present_mode) ||
!(m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, present_mode, m_exclusive_fullscreen_control)))
{
Error::SetStringView(error, "Failed to create swap chain");
return false;
@ -2273,8 +2261,9 @@ bool VulkanDevice::UpdateWindow()
return false;
}
m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, SelectPresentMode(), m_exclusive_fullscreen_control);
if (!m_swap_chain)
VkPresentModeKHR present_mode;
if (!VulkanSwapChain::SelectPresentMode(surface, &m_vsync_mode, &present_mode) ||
!(m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, present_mode, m_exclusive_fullscreen_control)))
{
ERROR_LOG("Failed to create swap chain");
VulkanSwapChain::DestroyVulkanSurface(m_instance, &m_window_info, surface);
@ -2349,18 +2338,32 @@ std::string VulkanDevice::GetDriverInfo() const
return ret;
}
void VulkanDevice::SetVSyncMode(GPUVSyncMode mode)
void VulkanDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
{
m_allow_present_throttle = allow_present_throttle;
if (!m_swap_chain)
{
// For when it is re-created.
m_vsync_mode = mode;
return;
}
VkPresentModeKHR present_mode;
if (!VulkanSwapChain::SelectPresentMode(m_swap_chain->GetSurface(), &mode, &present_mode))
{
ERROR_LOG("Ignoring vsync mode change.");
return;
}
// Actually changed? If using a fallback, it might not have.
if (m_vsync_mode == mode)
return;
m_vsync_mode = mode;
if (!m_swap_chain)
return;
// This swap chain should not be used by the current buffer, thus safe to destroy.
WaitForGPUIdle();
if (!m_swap_chain->SetRequestedPresentMode(SelectPresentMode()))
if (!m_swap_chain->SetPresentMode(present_mode))
{
Panic("Failed to update swap chain present mode.");
m_swap_chain.reset();

View file

@ -129,7 +129,7 @@ public:
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
void SetVSyncMode(GPUVSyncMode mode) override;
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
bool BeginPresent(bool skip_present) override;
void EndPresent(bool explicit_present) override;
@ -324,7 +324,6 @@ private:
bool enable_debug_utils);
bool SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface);
bool SelectDeviceFeatures();
VkPresentModeKHR SelectPresentMode() const;
bool CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer);
void ProcessDeviceExtensions();

View file

@ -72,7 +72,7 @@ static const char* PresentModeToString(VkPresentModeKHR mode)
VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode,
std::optional<bool> exclusive_fullscreen_control)
: m_window_info(wi), m_surface(surface), m_requested_present_mode(present_mode),
: m_window_info(wi), m_surface(surface), m_present_mode(present_mode),
m_exclusive_fullscreen_control(exclusive_fullscreen_control)
{
}
@ -260,8 +260,7 @@ std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkSurface
return std::nullopt;
}
std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface,
VkPresentModeKHR requested_mode)
bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsync_mode, VkPresentModeKHR* present_mode)
{
VulkanDevice& dev = VulkanDevice::GetInstance();
VkResult res;
@ -270,7 +269,7 @@ std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkSurfaceKHR
if (res != VK_SUCCESS || mode_count == 0)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ");
return std::nullopt;
return false;
}
std::vector<VkPresentModeKHR> present_modes(mode_count);
@ -279,43 +278,71 @@ std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkSurfaceKHR
Assert(res == VK_SUCCESS);
// Checks if a particular mode is supported, if it is, returns that mode.
auto CheckForMode = [&present_modes](VkPresentModeKHR check_mode) {
const auto CheckForMode = [&present_modes](VkPresentModeKHR check_mode) {
auto it = std::find_if(present_modes.begin(), present_modes.end(),
[check_mode](VkPresentModeKHR mode) { return check_mode == mode; });
return it != present_modes.end();
};
// Use preferred mode if available.
VkPresentModeKHR selected_mode;
if (CheckForMode(requested_mode))
switch (*vsync_mode)
{
selected_mode = requested_mode;
}
else if (requested_mode == VK_PRESENT_MODE_IMMEDIATE_KHR && CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
{
// Prefer mailbox over FIFO for vsync-off, since we don't want to block.
selected_mode = VK_PRESENT_MODE_MAILBOX_KHR;
}
else
{
// Fallback to FIFO if we we can't use mailbox. This should never fail, FIFO is mandated.
selected_mode = VK_PRESENT_MODE_FIFO_KHR;
case GPUVSyncMode::Disabled:
{
// Prefer immediate > mailbox > fifo.
if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR))
{
*present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
}
else if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
{
WARNING_LOG("Immediate not supported for vsync-disabled, using mailbox.");
*present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
}
else
{
WARNING_LOG("Mailbox not supported for vsync-disabled, using FIFO.");
*present_mode = VK_PRESENT_MODE_FIFO_KHR;
*vsync_mode = GPUVSyncMode::FIFO;
}
}
break;
case GPUVSyncMode::FIFO:
{
// FIFO is always available.
*present_mode = VK_PRESENT_MODE_FIFO_KHR;
}
break;
case GPUVSyncMode::Mailbox:
{
// Mailbox > fifo.
if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
{
*present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
}
else
{
WARNING_LOG("Mailbox not supported for vsync-mailbox, using FIFO.");
*present_mode = VK_PRESENT_MODE_FIFO_KHR;
*vsync_mode = GPUVSyncMode::FIFO;
}
}
break;
DefaultCaseIsUnreachable()
}
DEV_LOG("Preferred present mode: {}, selected: {}", PresentModeToString(requested_mode),
PresentModeToString(selected_mode));
return selected_mode;
return true;
}
bool VulkanSwapChain::CreateSwapChain()
{
VulkanDevice& dev = VulkanDevice::GetInstance();
// Select swap chain format and present mode
// Select swap chain format
std::optional<VkSurfaceFormatKHR> surface_format = SelectSurfaceFormat(m_surface);
std::optional<VkPresentModeKHR> present_mode = SelectPresentMode(m_surface, m_requested_present_mode);
if (!surface_format.has_value() || !present_mode.has_value())
if (!surface_format.has_value())
return false;
// Look up surface properties to determine image count and dimensions
@ -331,9 +358,9 @@ bool VulkanSwapChain::CreateSwapChain()
// Select number of images in swap chain, we prefer one buffer in the background to work on in triple-buffered mode.
// maxImageCount can be zero, in which case there isn't an upper limit on the number of buffers.
u32 image_count = std::clamp<u32>(
(m_requested_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount,
(m_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount,
(surface_capabilities.maxImageCount == 0) ? std::numeric_limits<u32>::max() : surface_capabilities.maxImageCount);
DEV_LOG("Creating a swap chain with {} images", image_count);
DEV_LOG("Creating a swap chain with {} images in present mode {}", image_count, PresentModeToString(m_present_mode));
// Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here
// determines window size? Android sometimes lags updating currentExtent, so don't use it.
@ -392,7 +419,7 @@ bool VulkanSwapChain::CreateSwapChain()
nullptr,
transform,
alpha,
present_mode.value(),
m_present_mode,
VK_TRUE,
old_swap_chain};
std::array<uint32_t, 2> indices = {{
@ -453,7 +480,6 @@ bool VulkanSwapChain::CreateSwapChain()
m_window_info.surface_width = std::max(1u, size.width);
m_window_info.surface_height = std::max(1u, size.height);
m_window_info.surface_format = VulkanDevice::GetFormatForVkFormat(surface_format->format);
m_actual_present_mode = present_mode.value();
if (m_window_info.surface_format == GPUTexture::Format::Unknown)
{
ERROR_LOG("Unknown Vulkan surface format {}", static_cast<u32>(surface_format->format));
@ -624,12 +650,12 @@ bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_s
return true;
}
bool VulkanSwapChain::SetRequestedPresentMode(VkPresentModeKHR mode)
bool VulkanSwapChain::SetPresentMode(VkPresentModeKHR present_mode)
{
if (m_requested_present_mode == mode)
if (m_present_mode == present_mode)
return true;
m_requested_present_mode = mode;
m_present_mode = present_mode;
// Recreate the swap chain with the new present mode.
VERBOSE_LOG("Recreating swap chain to change present mode.");

View file

@ -29,6 +29,9 @@ public:
VkPresentModeKHR present_mode,
std::optional<bool> exclusive_fullscreen_control);
// Determines present mode to use.
static bool SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsync_mode, VkPresentModeKHR* present_mode);
ALWAYS_INLINE VkSurfaceKHR GetSurface() const { return m_surface; }
ALWAYS_INLINE VkSwapchainKHR GetSwapChain() const { return m_swap_chain; }
ALWAYS_INLINE const VkSwapchainKHR* GetSwapChainPtr() const { return &m_swap_chain; }
@ -61,11 +64,8 @@ public:
}
// Returns true if the current present mode is synchronizing (adaptive or hard).
ALWAYS_INLINE bool IsPresentModeSynchronizing() const
{
return (m_actual_present_mode == VK_PRESENT_MODE_FIFO_KHR ||
m_actual_present_mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR);
}
ALWAYS_INLINE bool IsPresentModeSynchronizing() const { return (m_present_mode == VK_PRESENT_MODE_FIFO_KHR); }
ALWAYS_INLINE VkPresentModeKHR GetPresentMode() const { return m_present_mode; }
VkResult AcquireNextImage();
void ReleaseCurrentImage();
@ -74,14 +74,13 @@ public:
bool ResizeSwapChain(u32 new_width = 0, u32 new_height = 0, float new_scale = 1.0f);
// Change vsync enabled state. This may fail as it causes a swapchain recreation.
bool SetRequestedPresentMode(VkPresentModeKHR mode);
bool SetPresentMode(VkPresentModeKHR present_mode);
private:
VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode,
std::optional<bool> exclusive_fullscreen_control);
static std::optional<VkSurfaceFormatKHR> SelectSurfaceFormat(VkSurfaceKHR surface);
static std::optional<VkPresentModeKHR> SelectPresentMode(VkSurfaceKHR surface, VkPresentModeKHR requested_mode);
bool CreateSwapChain();
void DestroySwapChain();
@ -108,15 +107,14 @@ private:
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
VkSwapchainKHR m_swap_chain = VK_NULL_HANDLE;
u32 m_current_image = 0;
u32 m_current_semaphore = 0;
std::vector<Image> m_images;
std::vector<ImageSemaphores> m_semaphores;
u32 m_current_image = 0;
u32 m_current_semaphore = 0;
VkFormat m_format = VK_FORMAT_UNDEFINED;
VkPresentModeKHR m_requested_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
VkPresentModeKHR m_actual_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
VkPresentModeKHR m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
std::optional<VkResult> m_image_acquire_result;
std::optional<bool> m_exclusive_fullscreen_control;