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; disabled_features |= GPUDevice::FEATURE_MASK_TEXTURE_COPY_TO_SELF;
Error create_error; Error create_error;
if (!g_gpu_device || !g_gpu_device->Create( if (!g_gpu_device || !g_gpu_device->Create(g_settings.gpu_adapter,
g_settings.gpu_adapter, g_settings.gpu_disable_shader_cache ? std::string_view() :
g_settings.gpu_disable_shader_cache ? std::string_view() : std::string_view(EmuFolders::Cache), std::string_view(EmuFolders::Cache),
SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device, System::GetEffectiveVSyncMode(), SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device,
g_settings.gpu_threaded_presentation, exclusive_fullscreen_control, System::GetEffectiveVSyncMode(), System::ShouldAllowPresentThrottle(),
static_cast<GPUDevice::FeatureMask>(disabled_features), &create_error)) 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()); ERROR_LOG("Failed to create GPU device: {}", create_error.GetDescription());
if (g_gpu_device) if (g_gpu_device)

View file

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

View file

@ -12,6 +12,7 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <utility>
class ByteStream; class ByteStream;
class CDImage; class CDImage;
@ -463,6 +464,7 @@ bool IsRunningAtNonStandardSpeed();
/// Returns true if vsync should be used. /// Returns true if vsync should be used.
GPUVSyncMode GetEffectiveVSyncMode(); GPUVSyncMode GetEffectiveVSyncMode();
bool ShouldAllowPresentThrottle();
/// Quick switch between software and hardware rendering. /// Quick switch between software and hardware rendering.
void ToggleSoftwareRendering(); 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 vsync off, we only need two buffers. Same for blocking vsync.
// With triple buffering, we need three. // 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() bool D3D11Device::CreateSwapChain()
@ -588,8 +588,10 @@ void D3D11Device::InvalidateRenderTarget(GPUTexture* t)
static_cast<D3D11Texture*>(t)->CommitClear(m_context.Get()); 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) if (m_vsync_mode == mode)
return; 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 // 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 (IsVSyncModeBlocking() && m_gpu_timing_enabled) if (m_vsync_mode == GPUVSyncMode::FIFO && m_gpu_timing_enabled)
PopTimestampQuery(); PopTimestampQuery();
static constexpr float clear_color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; 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(!explicit_present);
DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target); 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(); PopTimestampQuery();
// DirectX has no concept of tear-or-sync. I guess if we measured times ourselves, we could implement it. const UINT sync_interval = static_cast<UINT>(m_vsync_mode == GPUVSyncMode::FIFO);
if (IsVSyncModeBlocking()) const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0;
m_swap_chain->Present(1, 0); m_swap_chain->Present(sync_interval, flags);
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);
if (m_gpu_timing_enabled) if (m_gpu_timing_enabled)
KickTimestampQuery(); KickTimestampQuery();

View file

@ -96,7 +96,7 @@ public:
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override; 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 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; bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() 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 vsync off, we only need two buffers. Same for blocking vsync.
// With triple buffering, we need three. // 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() bool D3D12Device::CreateSwapChain()
@ -1084,8 +1084,9 @@ std::string D3D12Device::GetDriverInfo() const
return ret; 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) if (m_vsync_mode == mode)
return; return;
@ -1158,13 +1159,9 @@ void D3D12Device::SubmitPresent()
{ {
DebugAssert(m_swap_chain); DebugAssert(m_swap_chain);
// DirectX has no concept of tear-or-sync. I guess if we measured times ourselves, we could implement it. const UINT sync_interval = static_cast<UINT>(m_vsync_mode == GPUVSyncMode::FIFO);
if (IsVSyncModeBlocking()) const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0;
m_swap_chain->Present(1, 0); m_swap_chain->Present(sync_interval, flags);
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);
} }
#ifdef _DEBUG #ifdef _DEBUG

View file

@ -118,7 +118,7 @@ public:
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override; 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 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; bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() 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 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) std::optional<bool> exclusive_fullscreen_control, FeatureMask disabled_features, Error* error)
{ {
m_vsync_mode = vsync; m_vsync_mode = vsync;
m_allow_present_throttle = allow_present_throttle;
m_debug_device = debug_device; m_debug_device = debug_device;
if (!AcquireWindow(true)) 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, bool GPUDevice::ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u32 new_height, GPUTexture::Type type,
GPUTexture::Format format, bool preserve /* = true */) GPUTexture::Format format, bool preserve /* = true */)
{ {
@ -1015,14 +1011,18 @@ bool GPUDevice::ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u
return true; 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; 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 u64 now = Common::Timer::GetCurrentValue();
const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time); const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time);
if (diff < m_display_frame_interval) if (diff < throttle_period)
return true; return true;
m_last_frame_displayed_time = now; m_last_frame_displayed_time = now;

View file

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

View file

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

View file

@ -115,14 +115,18 @@ bool MetalDevice::HasSurface() const
return (m_layer != nil); 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) if (m_vsync_mode == mode)
return; return;
m_vsync_mode = mode; m_vsync_mode = mode;
if (m_layer != nil) 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, 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); DebugAssert(m_layer_pass_desc == nil);
m_layer_pass_desc = [[MTLRenderPassDescriptor renderPassDescriptor] retain]; m_layer_pass_desc = [[MTLRenderPassDescriptor renderPassDescriptor] retain];

View file

@ -238,8 +238,12 @@ void OpenGLDevice::InsertDebugMessage(const char* msg)
#endif #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) if (m_vsync_mode == mode)
return; return;
@ -286,6 +290,7 @@ bool OpenGLDevice::CreateDevice(std::string_view adapter, bool threaded_presenta
// Is this needed? // Is this needed?
m_window_info = m_gl_context->GetWindowInfo(); 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 = const bool opengl_is_available =
((!m_gl_context->IsGLES() && (GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) || ((!m_gl_context->IsGLES() && (GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) ||
@ -583,7 +588,7 @@ void OpenGLDevice::SetSwapInterval()
return; return;
// Window framebuffer has to be bound to call SetSwapInterval. // 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; GLint current_fbo = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo); glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); 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 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 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; bool BeginPresent(bool skip_present) override;
void EndPresent(bool explicit_present) override; void EndPresent(bool explicit_present) override;

View file

@ -436,19 +436,6 @@ bool VulkanDevice::SelectDeviceFeatures()
return true; 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) bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer)
{ {
u32 queue_family_count; u32 queue_family_count;
@ -2052,8 +2039,9 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, bool threaded_presenta
if (surface != VK_NULL_HANDLE) if (surface != VK_NULL_HANDLE)
{ {
m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, SelectPresentMode(), m_exclusive_fullscreen_control); VkPresentModeKHR present_mode;
if (!m_swap_chain) 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"); Error::SetStringView(error, "Failed to create swap chain");
return false; return false;
@ -2273,8 +2261,9 @@ bool VulkanDevice::UpdateWindow()
return false; return false;
} }
m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, SelectPresentMode(), m_exclusive_fullscreen_control); VkPresentModeKHR present_mode;
if (!m_swap_chain) 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"); ERROR_LOG("Failed to create swap chain");
VulkanSwapChain::DestroyVulkanSurface(m_instance, &m_window_info, surface); VulkanSwapChain::DestroyVulkanSurface(m_instance, &m_window_info, surface);
@ -2349,18 +2338,32 @@ std::string VulkanDevice::GetDriverInfo() const
return ret; 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) if (m_vsync_mode == mode)
return; return;
m_vsync_mode = mode; m_vsync_mode = mode;
if (!m_swap_chain)
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.
WaitForGPUIdle(); WaitForGPUIdle();
if (!m_swap_chain->SetRequestedPresentMode(SelectPresentMode())) if (!m_swap_chain->SetPresentMode(present_mode))
{ {
Panic("Failed to update swap chain present mode."); Panic("Failed to update swap chain present mode.");
m_swap_chain.reset(); m_swap_chain.reset();

View file

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

View file

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

View file

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