mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-22 13:55:38 +00:00
System: Fallback to FIFO on AMD (no mailbox support)
This commit is contained in:
parent
98520978a8
commit
0f9a255093
|
@ -285,10 +285,11 @@ 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(),
|
||||
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))
|
||||
{
|
||||
|
|
|
@ -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,16 +2851,22 @@ 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 host_refresh_rate = g_gpu_device->GetWindowInfo().surface_refresh_rate;
|
||||
if (host_refresh_rate > 0.0f)
|
||||
{
|
||||
const float ratio = host_refresh_rate / System::GetThrottleFrequency();
|
||||
const bool can_sync_to_host = (ratio >= 0.95f && ratio <= 1.05f);
|
||||
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, can_sync_to_host ? "can sync" : "can't sync");
|
||||
ratio, s_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_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)
|
||||
{
|
||||
s_target_speed = ratio;
|
||||
|
||||
|
@ -2873,6 +2879,7 @@ void System::UpdateSpeedLimiterState()
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VERBOSE_LOG("Target speed: {}%", s_target_speed * 100.0f);
|
||||
VERBOSE_LOG("Preset timing: {}", s_optimal_frame_pacing ? "consistent" : "immediate");
|
||||
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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, ¤t_fbo);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
case GPUVSyncMode::Disabled:
|
||||
{
|
||||
// Prefer immediate > mailbox > fifo.
|
||||
if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR))
|
||||
{
|
||||
*present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
|
||||
}
|
||||
else if (requested_mode == VK_PRESENT_MODE_IMMEDIATE_KHR && CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
|
||||
else if (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;
|
||||
WARNING_LOG("Immediate not supported for vsync-disabled, using mailbox.");
|
||||
*present_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;
|
||||
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.");
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue