GPUDevice: Add support for VRR and relaxed vsync

This commit is contained in:
Stenzek 2024-03-03 12:25:37 +10:00
parent d9e496284f
commit a1d7d214cf
No known key found for this signature in database
27 changed files with 474 additions and 364 deletions

View File

@ -3879,10 +3879,11 @@ void FullscreenUI::DrawDisplaySettingsPage()
"GPU", "UseSoftwareRendererForReadbacks", false);
}
DrawToggleSetting(
bsi, FSUI_CSTR("Enable VSync"),
DrawEnumSetting(
bsi, FSUI_CSTR("VSync"),
FSUI_CSTR("Synchronizes presentation of the console's frames to the host. Enable for smoother animations."),
"Display", "VSync", Settings::DEFAULT_VSYNC_VALUE);
"Display", "SyncMode", Settings::DEFAULT_DISPLAY_SYNC_MODE, &Settings::ParseDisplaySyncMode,
&Settings::GetDisplaySyncModeName, &Settings::GetDisplaySyncModeDisplayName, DisplaySyncMode::Count);
DrawToggleSetting(
bsi, FSUI_CSTR("Sync To Host Refresh Rate"),

View File

@ -265,15 +265,13 @@ bool Host::CreateGPUDevice(RenderAPI api)
if (g_settings.gpu_disable_texture_copy_to_self)
disabled_features |= GPUDevice::FEATURE_MASK_TEXTURE_COPY_TO_SELF;
// TODO: FSUI should always use vsync..
Error error;
const bool vsync = System::IsValid() ? System::ShouldUseVSync() : g_settings.video_sync_enabled;
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, vsync,
g_settings.gpu_threaded_presentation, exclusive_fullscreen_control,
static_cast<GPUDevice::FeatureMask>(disabled_features), &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::GetEffectiveDisplaySyncMode(),
g_settings.gpu_threaded_presentation, exclusive_fullscreen_control,
static_cast<GPUDevice::FeatureMask>(disabled_features), &error))
{
Log_ErrorPrintf("Failed to create GPU device.");
if (g_gpu_device)

View File

@ -243,6 +243,10 @@ void Settings::Load(SettingsInterface& si)
display_scaling =
ParseDisplayScaling(si.GetStringValue("Display", "Scaling", GetDisplayScalingName(DEFAULT_DISPLAY_SCALING)).c_str())
.value_or(DEFAULT_DISPLAY_SCALING);
display_sync_mode =
ParseDisplaySyncMode(
si.GetStringValue("Display", "SyncMode", GetDisplaySyncModeName(DEFAULT_DISPLAY_SYNC_MODE)).c_str())
.value_or(DEFAULT_DISPLAY_SYNC_MODE);
display_exclusive_fullscreen_control =
ParseDisplayExclusiveFullscreenControl(
si.GetStringValue("Display", "ExclusiveFullscreenControl",
@ -279,7 +283,6 @@ void Settings::Load(SettingsInterface& si)
display_show_enhancements = si.GetBoolValue("Display", "ShowEnhancements", false);
display_all_frames = si.GetBoolValue("Display", "DisplayAllFrames", false);
display_stretch_vertically = si.GetBoolValue("Display", "StretchVertically", false);
video_sync_enabled = si.GetBoolValue("Display", "VSync", DEFAULT_VSYNC_VALUE);
display_max_fps = si.GetFloatValue("Display", "MaxFPS", DEFAULT_DISPLAY_MAX_FPS);
display_osd_scale = si.GetFloatValue("Display", "OSDScale", DEFAULT_OSD_SCALE);
@ -504,6 +507,7 @@ void Settings::Save(SettingsInterface& si) const
si.SetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio));
si.SetStringValue("Display", "Alignment", GetDisplayAlignmentName(display_alignment));
si.SetStringValue("Display", "Scaling", GetDisplayScalingName(display_scaling));
si.SetStringValue("Display", "SyncMode", GetDisplaySyncModeName(display_sync_mode));
si.SetStringValue("Display", "ExclusiveFullscreenControl",
GetDisplayExclusiveFullscreenControlName(display_exclusive_fullscreen_control));
si.SetStringValue("Display", "ScreenshotMode", GetDisplayScreenshotModeName(display_screenshot_mode));
@ -524,7 +528,6 @@ void Settings::Save(SettingsInterface& si) const
si.SetBoolValue("Display", "ShowEnhancements", display_show_enhancements);
si.SetBoolValue("Display", "DisplayAllFrames", display_all_frames);
si.SetBoolValue("Display", "StretchVertically", display_stretch_vertically);
si.SetBoolValue("Display", "VSync", video_sync_enabled);
si.SetFloatValue("Display", "MaxFPS", display_max_fps);
si.SetFloatValue("Display", "OSDScale", display_osd_scale);
@ -1357,6 +1360,43 @@ const char* Settings::GetDisplayScalingDisplayName(DisplayScalingMode mode)
return Host::TranslateToCString("DisplayScalingMode", s_display_scaling_display_names[static_cast<int>(mode)]);
}
static constexpr const std::array s_display_sync_mode_names = {
"Disabled",
"VSync",
"VSyncRelaxed",
"VRR",
};
static constexpr const std::array s_display_sync_mode_display_names = {
TRANSLATE_NOOP("Settings", "Disabled"),
TRANSLATE_NOOP("Settings", "VSync"),
TRANSLATE_NOOP("Settings", "Relaxed VSync"),
TRANSLATE_NOOP("Settings", "VRR/FreeSync/GSync"),
};
std::optional<DisplaySyncMode> Settings::ParseDisplaySyncMode(const char* str)
{
int index = 0;
for (const char* name : s_display_sync_mode_names)
{
if (StringUtil::Strcasecmp(name, str) == 0)
return static_cast<DisplaySyncMode>(index);
index++;
}
return std::nullopt;
}
const char* Settings::GetDisplaySyncModeName(DisplaySyncMode mode)
{
return s_display_sync_mode_names[static_cast<size_t>(mode)];
}
const char* Settings::GetDisplaySyncModeDisplayName(DisplaySyncMode mode)
{
return Host::TranslateToCString("Settings", s_display_sync_mode_display_names[static_cast<size_t>(mode)]);
}
static constexpr const std::array s_display_exclusive_fullscreen_mode_names = {
"Automatic",
"Disallowed",

View File

@ -6,6 +6,7 @@
#include "types.h"
#include "util/audio_stream.h"
#include "util/gpu_types.h"
#include "common/log.h"
#include "common/settings_interface.h"
@ -18,8 +19,6 @@
#include <string_view>
#include <vector>
enum class RenderAPI : u32;
struct SettingInfo
{
enum class Type
@ -134,6 +133,7 @@ struct Settings
DisplayAspectRatio display_aspect_ratio = DEFAULT_DISPLAY_ASPECT_RATIO;
DisplayAlignment display_alignment = DEFAULT_DISPLAY_ALIGNMENT;
DisplayScalingMode display_scaling = DEFAULT_DISPLAY_SCALING;
DisplaySyncMode display_sync_mode = DEFAULT_DISPLAY_SYNC_MODE;
DisplayExclusiveFullscreenControl display_exclusive_fullscreen_control = DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL;
DisplayScreenshotMode display_screenshot_mode = DEFAULT_DISPLAY_SCREENSHOT_MODE;
DisplayScreenshotFormat display_screenshot_format = DEFAULT_DISPLAY_SCREENSHOT_FORMAT;
@ -159,7 +159,6 @@ struct Settings
bool display_show_enhancements : 1 = false;
bool display_all_frames : 1 = false;
bool display_stretch_vertically : 1 = false;
bool video_sync_enabled = DEFAULT_VSYNC_VALUE;
float display_osd_scale = 100.0f;
float display_max_fps = DEFAULT_DISPLAY_MAX_FPS;
float gpu_pgxp_tolerance = -1.0f;
@ -411,6 +410,10 @@ struct Settings
static const char* GetDisplayScalingName(DisplayScalingMode mode);
static const char* GetDisplayScalingDisplayName(DisplayScalingMode mode);
static std::optional<DisplaySyncMode> ParseDisplaySyncMode(const char* str);
static const char* GetDisplaySyncModeName(DisplaySyncMode mode);
static const char* GetDisplaySyncModeDisplayName(DisplaySyncMode mode);
static std::optional<DisplayExclusiveFullscreenControl> ParseDisplayExclusiveFullscreenControl(const char* str);
static const char* GetDisplayExclusiveFullscreenControlName(DisplayExclusiveFullscreenControl mode);
static const char* GetDisplayExclusiveFullscreenControlDisplayName(DisplayExclusiveFullscreenControl mode);
@ -484,6 +487,7 @@ struct Settings
static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::Auto;
static constexpr DisplayAlignment DEFAULT_DISPLAY_ALIGNMENT = DisplayAlignment::Center;
static constexpr DisplayScalingMode DEFAULT_DISPLAY_SCALING = DisplayScalingMode::BilinearSmooth;
static constexpr DisplaySyncMode DEFAULT_DISPLAY_SYNC_MODE = DisplaySyncMode::Disabled;
static constexpr DisplayExclusiveFullscreenControl DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL =
DisplayExclusiveFullscreenControl::Automatic;
static constexpr DisplayScreenshotMode DEFAULT_DISPLAY_SCREENSHOT_MODE = DisplayScreenshotMode::ScreenResolution;
@ -525,13 +529,11 @@ struct Settings
#ifndef __ANDROID__
static constexpr bool DEFAULT_SAVE_STATE_COMPRESSION = true;
static constexpr bool DEFAULT_SAVE_STATE_BACKUPS = true;
static constexpr bool DEFAULT_VSYNC_VALUE = false;
static constexpr bool DEFAULT_FAST_BOOT_VALUE = false;
static constexpr float DEFAULT_DISPLAY_MAX_FPS = 0.0f;
#else
static constexpr bool DEFAULT_SAVE_STATE_COMPRESSION = true;
static constexpr bool DEFAULT_SAVE_STATE_BACKUPS = false;
static constexpr bool DEFAULT_VSYNC_VALUE = false;
static constexpr bool DEFAULT_FAST_BOOT_VALUE = true;
static constexpr float DEFAULT_DISPLAY_MAX_FPS = 60.0f;
#endif

View File

@ -1849,6 +1849,12 @@ void System::FrameDone()
SaveRunaheadState();
}
// TODO: Kick cmdbuffer early
const DisplaySyncMode sync_mode = g_gpu_device->GetSyncMode();
const bool throttle_after_present = (sync_mode == DisplaySyncMode::Disabled);
if (!throttle_after_present && s_throttler_enabled && !IsExecutionInterrupted())
Throttle();
const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
if (current_time < s_next_frame_time || s_display_all_frames || s_last_frame_skipped)
{
@ -1860,7 +1866,7 @@ void System::FrameDone()
s_last_frame_skipped = true;
}
if (s_throttler_enabled && !IsExecutionInterrupted())
if (throttle_after_present && s_throttler_enabled && !IsExecutionInterrupted())
Throttle();
// Input poll already done above
@ -2638,10 +2644,14 @@ void System::UpdateSpeedLimiterState()
}
// When syncing to host and using vsync, we don't need to sleep.
if (s_syncing_to_host && ShouldUseVSync() && s_display_all_frames)
if (s_syncing_to_host && s_display_all_frames)
{
Log_InfoPrintf("Using host vsync for throttling.");
s_throttler_enabled = false;
const DisplaySyncMode effective_sync_mode = GetEffectiveDisplaySyncMode();
if (effective_sync_mode == DisplaySyncMode::VSync || effective_sync_mode == DisplaySyncMode::VSyncRelaxed)
{
Log_InfoPrintf("Using host vsync for throttling.");
s_throttler_enabled = false;
}
}
Log_VerbosePrintf("Target speed: %f%%", s_target_speed * 100.0f);
@ -2674,21 +2684,25 @@ void System::UpdateSpeedLimiterState()
void System::UpdateDisplaySync()
{
const bool video_sync_enabled = ShouldUseVSync();
const bool syncing_to_host_vsync = (s_syncing_to_host && video_sync_enabled && s_display_all_frames);
const DisplaySyncMode display_sync_mode = GetEffectiveDisplaySyncMode();
const bool syncing_to_host_vsync =
(s_syncing_to_host &&
(display_sync_mode == DisplaySyncMode::VSync || display_sync_mode == DisplaySyncMode::VSyncRelaxed) &&
s_display_all_frames);
const float max_display_fps = (s_throttler_enabled || s_syncing_to_host) ? 0.0f : g_settings.display_max_fps;
Log_VerbosePrintf("Using vsync: %s%s", video_sync_enabled ? "YES" : "NO",
Log_VerbosePrintf("Display sync: %s%s", Settings::GetDisplaySyncModeDisplayName(display_sync_mode),
syncing_to_host_vsync ? " (for throttling)" : "");
Log_VerbosePrintf("Max display fps: %f (%s)", max_display_fps,
s_display_all_frames ? "displaying all frames" : "skipping displaying frames when needed");
g_gpu_device->SetDisplayMaxFPS(max_display_fps);
g_gpu_device->SetVSync(video_sync_enabled);
g_gpu_device->SetSyncMode(display_sync_mode);
}
bool System::ShouldUseVSync()
DisplaySyncMode System::GetEffectiveDisplaySyncMode()
{
return g_settings.video_sync_enabled && !IsRunningAtNonStandardSpeed();
// Disable vsync if running outside 100%.
return (IsValid() && IsRunningAtNonStandardSpeed()) ? DisplaySyncMode::Disabled : g_settings.display_sync_mode;
}
bool System::IsFastForwardEnabled()
@ -3737,7 +3751,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
DMA::SetHaltTicks(g_settings.dma_halt_ticks);
if (g_settings.audio_backend != old_settings.audio_backend ||
g_settings.video_sync_enabled != old_settings.video_sync_enabled ||
g_settings.display_sync_mode != old_settings.display_sync_mode ||
g_settings.increase_timer_resolution != old_settings.increase_timer_resolution ||
g_settings.emulation_speed != old_settings.emulation_speed ||
g_settings.fast_forward_speed != old_settings.fast_forward_speed ||

View File

@ -455,7 +455,7 @@ void ToggleWidescreen();
bool IsRunningAtNonStandardSpeed();
/// Returns true if vsync should be used.
bool ShouldUseVSync();
DisplaySyncMode GetEffectiveDisplaySyncMode();
/// Quick switch between software and hardware rendering.
void ToggleSoftwareRendering();

View File

@ -51,8 +51,13 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.renderer, "GPU", "Renderer", &Settings::ParseRendererName,
&Settings::GetRendererName, Settings::DEFAULT_GPU_RENDERER);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vsync, "Display", "VSync", false);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.resolutionScale, "GPU", "ResolutionScale", 1);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.textureFiltering, "GPU", "TextureFilter",
&Settings::ParseTextureFilterName, &Settings::GetTextureFilterName,
Settings::DEFAULT_GPU_TEXTURE_FILTER);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode",
&Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName,
Settings::DEFAULT_GPU_DOWNSAMPLE_MODE);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayAspectRatio, "Display", "AspectRatio",
&Settings::ParseDisplayAspectRatio, &Settings::GetDisplayAspectRatioName,
Settings::DEFAULT_DISPLAY_ASPECT_RATIO);
@ -67,13 +72,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayScaling, "Display", "Scaling",
&Settings::ParseDisplayScaling, &Settings::GetDisplayScalingName,
Settings::DEFAULT_DISPLAY_SCALING);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.resolutionScale, "GPU", "ResolutionScale", 1);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.textureFiltering, "GPU", "TextureFilter",
&Settings::ParseTextureFilterName, &Settings::GetTextureFilterName,
Settings::DEFAULT_GPU_TEXTURE_FILTER);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode",
&Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName,
Settings::DEFAULT_GPU_DOWNSAMPLE_MODE);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displaySyncMode, "Display", "SyncMode",
&Settings::ParseDisplaySyncMode, &Settings::GetDisplaySyncModeName,
Settings::DEFAULT_DISPLAY_SYNC_MODE);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.gpuDownsampleScale, "GPU", "DownsampleScale", 1);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.trueColor, "GPU", "TrueColor", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableInterlacing, "GPU", "DisableInterlacing", true);
@ -243,9 +244,23 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
"renderers. <br>This option is only supported in Direct3D and Vulkan. OpenGL will always use the default "
"device."));
dialog->registerWidgetHelp(
m_ui.vsync, tr("VSync"), tr("Unchecked"),
tr("Enable this option to match DuckStation's refresh rate with your current monitor or screen. "
"VSync is automatically disabled when it is not possible (e.g. running at non-100% speed)."));
m_ui.resolutionScale, tr("Internal Resolution"), "1x",
tr("Setting this beyond 1x will enhance the resolution of rendered 3D polygons and lines. Only applies "
"to the hardware backends. <br>This option is usually safe, with most games looking fine at "
"higher resolutions. Higher resolutions require a more powerful GPU."));
dialog->registerWidgetHelp(
m_ui.gpuDownsampleMode, tr("Down-Sampling"), tr("Disabled"),
tr("Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games, "
"but should be disabled for pure 3D games. Only applies to the hardware renderers."));
dialog->registerWidgetHelp(m_ui.gpuDownsampleScale, tr("Down-Sampling Display Scale"), tr("1x"),
tr("Selects the resolution scale that will be applied to the final image. 1x will "
"downsample to the original console resolution."));
dialog->registerWidgetHelp(
m_ui.textureFiltering, tr("Texture Filtering"),
QString::fromUtf8(Settings::GetTextureFilterDisplayName(Settings::DEFAULT_GPU_TEXTURE_FILTER)),
tr("Smooths out the blockiness of magnified textures on 3D object by using filtering. <br>Will have a "
"greater effect on higher resolution scales. Only applies to the hardware renderers. <br>The JINC2 and "
"especially xBR filtering modes are very demanding, and may not be worth the speed penalty."));
dialog->registerWidgetHelp(
m_ui.displayAspectRatio, tr("Aspect Ratio"),
QString::fromUtf8(Settings::GetDisplayAspectRatioDisplayName(Settings::DEFAULT_DISPLAY_ASPECT_RATIO)),
@ -261,27 +276,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
m_ui.displayScaling, tr("Scaling"), tr("Bilinear (Smooth)"),
tr("Determines how the emulated console's output is upscaled or downscaled to your monitor's resolution."));
dialog->registerWidgetHelp(
m_ui.resolutionScale, tr("Internal Resolution"), "1x",
tr("Setting this beyond 1x will enhance the resolution of rendered 3D polygons and lines. Only applies "
"to the hardware backends. <br>This option is usually safe, with most games looking fine at "
"higher resolutions. Higher resolutions require a more powerful GPU."));
dialog->registerWidgetHelp(
m_ui.msaaMode, tr("Multi-Sampling"), tr("Disabled"),
tr("Uses multi-sampled anti-aliasing when rendering 3D polygons. Can improve visuals with a lower performance "
"requirement compared to upscaling, <strong>but often introduces rendering errors.</strong>"));
dialog->registerWidgetHelp(
m_ui.gpuDownsampleMode, tr("Down-Sampling"), tr("Disabled"),
tr("Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games, "
"but should be disabled for pure 3D games. Only applies to the hardware renderers."));
dialog->registerWidgetHelp(m_ui.gpuDownsampleScale, tr("Down-Sampling Display Scale"), tr("1x"),
tr("Selects the resolution scale that will be applied to the final image. 1x will "
"downsample to the original console resolution."));
dialog->registerWidgetHelp(
m_ui.textureFiltering, tr("Texture Filtering"),
QString::fromUtf8(Settings::GetTextureFilterDisplayName(Settings::DEFAULT_GPU_TEXTURE_FILTER)),
tr("Smooths out the blockiness of magnified textures on 3D object by using filtering. <br>Will have a "
"greater effect on higher resolution scales. Only applies to the hardware renderers. <br>The JINC2 and "
"especially xBR filtering modes are very demanding, and may not be worth the speed penalty."));
m_ui.displaySyncMode, tr("VSync"), tr("Unchecked"),
tr("Enable this option to match DuckStation's refresh rate with your current monitor or screen. "
"VSync is automatically disabled when it is not possible (e.g. running at non-100% speed)."));
dialog->registerWidgetHelp(
m_ui.trueColor, tr("True Color Rendering"), tr("Checked"),
tr("Forces the precision of colours output to the console's framebuffer to use the full 8 bits of precision per "
@ -354,9 +351,10 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
QString::fromUtf8(Settings::GetLineDetectModeName(Settings::DEFAULT_GPU_LINE_DETECT_MODE)),
tr("Attempts to detect one pixel high/wide lines that rely on non-upscaled rasterization "
"behavior, filling in gaps introduced by upscaling."));
dialog->registerWidgetHelp(m_ui.gpuWireframeMode, tr("Wireframe Mode"), tr("Disabled"),
tr("Draws a wireframe outline of the triangles rendered by the console's GPU, either as a "
"replacement or an overlay."));
dialog->registerWidgetHelp(
m_ui.msaaMode, tr("Multi-Sampling"), tr("Disabled"),
tr("Uses multi-sampled anti-aliasing when rendering 3D polygons. Can improve visuals with a lower performance "
"requirement compared to upscaling, <strong>but often introduces rendering errors.</strong>"));
dialog->registerWidgetHelp(
m_ui.debanding, tr("True Color Debanding"), tr("Unchecked"),
tr("Applies modern dithering techniques to further smooth out gradients when true color is enabled. "
@ -473,6 +471,10 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
// Debugging Tab
dialog->registerWidgetHelp(m_ui.gpuWireframeMode, tr("Wireframe Mode"), tr("Disabled"),
tr("Draws a wireframe outline of the triangles rendered by the console's GPU, either as a "
"replacement or an overlay."));
dialog->registerWidgetHelp(
m_ui.useDebugDevice, tr("Use Debug Device"), tr("Unchecked"),
tr("Enable debugging when supported by the host's renderer API. <strong>Only for developer use.</strong>"));
@ -505,6 +507,18 @@ void GraphicsSettingsWidget::setupAdditionalUi()
m_ui.renderer->addItem(QString::fromUtf8(Settings::GetRendererDisplayName(static_cast<GPURenderer>(i))));
}
for (u32 i = 0; i < static_cast<u32>(GPUTextureFilter::Count); i++)
{
m_ui.textureFiltering->addItem(
QString::fromUtf8(Settings::GetTextureFilterDisplayName(static_cast<GPUTextureFilter>(i))));
}
for (u32 i = 0; i < static_cast<u32>(GPUDownsampleMode::Count); i++)
{
m_ui.gpuDownsampleMode->addItem(
QString::fromUtf8(Settings::GetDownsampleModeDisplayName(static_cast<GPUDownsampleMode>(i))));
}
for (u32 i = 0; i < static_cast<u32>(DisplayAspectRatio::Count); i++)
{
m_ui.displayAspectRatio->addItem(
@ -523,26 +537,10 @@ void GraphicsSettingsWidget::setupAdditionalUi()
QString::fromUtf8(Settings::GetDisplayScalingDisplayName(static_cast<DisplayScalingMode>(i))));
}
for (u32 i = 0; i < static_cast<u32>(DisplaySyncMode::Count); i++)
{
if (m_dialog->isPerGameSettings())
m_ui.msaaMode->addItem(tr("Use Global Setting"));
m_ui.msaaMode->addItem(tr("Disabled"), GetMSAAModeValue(1, false));
for (uint i = 2; i <= 32; i *= 2)
m_ui.msaaMode->addItem(tr("%1x MSAA").arg(i), GetMSAAModeValue(i, false));
for (uint i = 2; i <= 32; i *= 2)
m_ui.msaaMode->addItem(tr("%1x SSAA").arg(i), GetMSAAModeValue(i, true));
}
for (u32 i = 0; i < static_cast<u32>(GPUTextureFilter::Count); i++)
{
m_ui.textureFiltering->addItem(
QString::fromUtf8(Settings::GetTextureFilterDisplayName(static_cast<GPUTextureFilter>(i))));
}
for (u32 i = 0; i < static_cast<u32>(GPUDownsampleMode::Count); i++)
{
m_ui.gpuDownsampleMode->addItem(
QString::fromUtf8(Settings::GetDownsampleModeDisplayName(static_cast<GPUDownsampleMode>(i))));
m_ui.displaySyncMode->addItem(
QString::fromUtf8(Settings::GetDisplaySyncModeDisplayName(static_cast<DisplaySyncMode>(i))));
}
// Advanced Tab
@ -559,18 +557,22 @@ void GraphicsSettingsWidget::setupAdditionalUi()
QString::fromUtf8(Settings::GetDisplayAlignmentDisplayName(static_cast<DisplayAlignment>(i))));
}
{
if (m_dialog->isPerGameSettings())
m_ui.msaaMode->addItem(tr("Use Global Setting"));
m_ui.msaaMode->addItem(tr("Disabled"), GetMSAAModeValue(1, false));
for (uint i = 2; i <= 32; i *= 2)
m_ui.msaaMode->addItem(tr("%1x MSAA").arg(i), GetMSAAModeValue(i, false));
for (uint i = 2; i <= 32; i *= 2)
m_ui.msaaMode->addItem(tr("%1x SSAA").arg(i), GetMSAAModeValue(i, true));
}
for (u32 i = 0; i < static_cast<u32>(GPULineDetectMode::Count); i++)
{
m_ui.gpuLineDetectMode->addItem(
QString::fromUtf8(Settings::GetLineDetectModeDisplayName(static_cast<GPULineDetectMode>(i))));
}
for (u32 i = 0; i < static_cast<u32>(GPUWireframeMode::Count); i++)
{
m_ui.gpuWireframeMode->addItem(
QString::fromUtf8(Settings::GetGPUWireframeModeDisplayName(static_cast<GPUWireframeMode>(i))));
}
// Capture Tab
for (u32 i = 0; i < static_cast<u32>(DisplayScreenshotMode::Count); i++)
@ -584,6 +586,14 @@ void GraphicsSettingsWidget::setupAdditionalUi()
m_ui.screenshotFormat->addItem(
QString::fromUtf8(Settings::GetDisplayScreenshotFormatDisplayName(static_cast<DisplayScreenshotFormat>(i))));
}
// Debugging Tab
for (u32 i = 0; i < static_cast<u32>(GPUWireframeMode::Count); i++)
{
m_ui.gpuWireframeMode->addItem(
QString::fromUtf8(Settings::GetGPUWireframeModeDisplayName(static_cast<GPUWireframeMode>(i))));
}
}
void GraphicsSettingsWidget::removePlatformSpecificUi()

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>584</width>
<height>446</height>
<height>434</height>
</rect>
</property>
<property name="windowTitle">
@ -50,18 +50,7 @@
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,0">
<item>
<widget class="QComboBox" name="adapter"/>
</item>
<item>
<widget class="QCheckBox" name="vsync">
<property name="text">
<string>VSync</string>
</property>
</widget>
</item>
</layout>
<widget class="QComboBox" name="adapter"/>
</item>
</layout>
</widget>
@ -98,74 +87,13 @@
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Aspect Ratio:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0,0,0">
<item>
<widget class="QComboBox" name="displayAspectRatio"/>
</item>
<item>
<widget class="QSpinBox" name="customAspectRatioNumerator">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="customAspectRatioSeparator">
<property name="text">
<string>:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="customAspectRatioDenominator">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Crop:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="displayCropMode"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Scaling:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="displayScaling"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="resolutionScaleLabel">
<property name="text">
<string>Internal Resolution:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="0" column="1">
<widget class="QComboBox" name="resolutionScale">
<item>
<property name="text">
@ -254,27 +182,115 @@
</item>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="msaaModeLabel">
<item row="1" column="0">
<widget class="QLabel" name="gpuDownsampleLabel">
<property name="text">
<string>Multi-Sampling:</string>
<string>Down-Sampling:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="msaaMode"/>
<item row="1" column="1">
<layout class="QHBoxLayout" name="gpuDownsampleLayout" stretch="1,0">
<item>
<widget class="QComboBox" name="gpuDownsampleMode"/>
</item>
<item>
<widget class="QSpinBox" name="gpuDownsampleScale">
<property name="suffix">
<string>x</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>16</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="0">
<item row="2" column="0">
<widget class="QLabel" name="textureFilteringLabel">
<property name="text">
<string>Texture Filtering:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="2" column="1">
<widget class="QComboBox" name="textureFiltering"/>
</item>
<item row="8" column="0" colspan="2">
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Aspect Ratio:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0,0,0">
<item>
<widget class="QComboBox" name="displayAspectRatio"/>
</item>
<item>
<widget class="QSpinBox" name="customAspectRatioNumerator">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="customAspectRatioSeparator">
<property name="text">
<string>:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="customAspectRatioDenominator">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Crop:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="displayCropMode"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Scaling:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="displayScaling"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>VSync:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QComboBox" name="displaySyncMode"/>
</item>
<item row="7" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<widget class="QCheckBox" name="pgxpEnable">
@ -283,6 +299,13 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="trueColor">
<property name="text">
<string>True Color Rendering</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="pgxpDepthBuffer">
<property name="text">
@ -304,20 +327,6 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="trueColor">
<property name="text">
<string>True Color Rendering</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="forceNTSCTimings">
<property name="text">
<string>Force NTSC Timings</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="disableInterlacing">
<property name="text">
@ -332,30 +341,10 @@
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0">
<widget class="QLabel" name="gpuDownsampleLabel">
<property name="text">
<string>Down-Sampling:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="gpuDownsampleLayout" stretch="1,0">
<item>
<widget class="QComboBox" name="gpuDownsampleMode"/>
</item>
<item>
<widget class="QSpinBox" name="gpuDownsampleScale">
<property name="suffix">
<string>x</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>16</number>
<item row="3" column="1">
<widget class="QCheckBox" name="forceNTSCTimings">
<property name="text">
<string>Force NTSC Timings</string>
</property>
</widget>
</item>
@ -477,6 +466,16 @@
<string>Rendering Options</string>
</property>
<layout class="QFormLayout" name="formLayout_6">
<item row="1" column="0">
<widget class="QLabel" name="gpuLineDetectModeLabel">
<property name="text">
<string>Line Detection:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="gpuLineDetectMode"/>
</item>
<item row="2" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="1">
@ -503,24 +502,14 @@
</layout>
</item>
<item row="0" column="0">
<widget class="QLabel" name="gpuLineDetectModeLabel">
<widget class="QLabel" name="msaaModeLabel">
<property name="text">
<string>Line Detection:</string>
<string>Multi-Sampling:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="gpuLineDetectMode"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="gpuWireframeModeLabel">
<property name="text">
<string>Wireframe Mode:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="gpuWireframeMode"/>
<widget class="QComboBox" name="msaaMode"/>
</item>
</layout>
</widget>
@ -1035,6 +1024,25 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_11">
<property name="title">
<string>Rendering Options</string>
</property>
<layout class="QFormLayout" name="formLayout_11">
<item row="0" column="0">
<widget class="QLabel" name="gpuWireframeModeLabel">
<property name="text">
<string>Wireframe Mode:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="gpuWireframeMode"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_9">
<property name="title">

View File

@ -1529,7 +1529,7 @@ void EmuThread::run()
if (g_gpu_device)
{
System::PresentDisplay(false);
if (!g_gpu_device->IsVsyncEnabled())
if (!g_gpu_device->IsVSyncActive())
g_gpu_device->ThrottlePresentation();
}
}

View File

@ -28,6 +28,7 @@ add_library(util
gpu_shader_cache.h
gpu_texture.cpp
gpu_texture.h
gpu_types.h
host.cpp
host.h
http_downloader.cpp

View File

@ -233,7 +233,8 @@ bool D3D11Device::CreateSwapChain()
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.SwapEffect = m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD;
m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain && !m_is_exclusive_fullscreen);
m_using_allow_tearing =
(m_allow_tearing_supported && m_using_flip_model_swap_chain && !m_is_exclusive_fullscreen);
if (m_using_allow_tearing)
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
@ -600,11 +601,6 @@ bool D3D11Device::GetHostRefreshRate(float* refresh_rate)
return GPUDevice::GetHostRefreshRate(refresh_rate);
}
void D3D11Device::SetVSync(bool enabled)
{
m_vsync_enabled = enabled;
}
bool D3D11Device::BeginPresent(bool skip_present)
{
if (skip_present)
@ -633,7 +629,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 (m_vsync_enabled && m_gpu_timing_enabled)
if ((m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed) && m_gpu_timing_enabled)
PopTimestampQuery();
static constexpr float clear_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
@ -650,13 +646,16 @@ void D3D11Device::EndPresent()
{
DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target);
if (!m_vsync_enabled && m_gpu_timing_enabled)
if (m_sync_mode != DisplaySyncMode::VSync && m_sync_mode != DisplaySyncMode::VSyncRelaxed && m_gpu_timing_enabled)
PopTimestampQuery();
if (!m_vsync_enabled && m_using_allow_tearing)
// DirectX has no concept of tear-or-sync. I guess if we measured times ourselves, we could implement it.
if (m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed)
m_swap_chain->Present(BoolToUInt32(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(BoolToUInt32(m_vsync_enabled), 0);
m_swap_chain->Present(0, 0);
if (m_gpu_timing_enabled)
KickTimestampQuery();

View File

@ -98,8 +98,6 @@ public:
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
void SetVSync(bool enabled) override;
bool BeginPresent(bool skip_present) override;
void EndPresent() override;

View File

@ -1060,11 +1060,6 @@ std::string D3D12Device::GetDriverInfo() const
return ret;
}
void D3D12Device::SetVSync(bool enabled)
{
m_vsync_enabled = enabled;
}
bool D3D12Device::BeginPresent(bool frame_skip)
{
if (InRenderPass())
@ -1112,10 +1107,13 @@ void D3D12Device::EndPresent()
SubmitCommandList(false);
if (!m_vsync_enabled && m_using_allow_tearing)
// DirectX has no concept of tear-or-sync. I guess if we measured times ourselves, we could implement it.
if (m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed)
m_swap_chain->Present(BoolToUInt32(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(static_cast<UINT>(m_vsync_enabled), 0);
m_swap_chain->Present(0, 0);
TrimTexturePool();
}

View File

@ -119,8 +119,6 @@ public:
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
void SetVSync(bool enabled) override;
bool BeginPresent(bool skip_present) override;
void EndPresent() override;

View File

@ -271,10 +271,11 @@ bool GPUDevice::IsSameRenderAPI(RenderAPI lhs, RenderAPI rhs)
}
bool GPUDevice::Create(const std::string_view& adapter, const std::string_view& shader_cache_path,
u32 shader_cache_version, bool debug_device, bool vsync, bool threaded_presentation,
std::optional<bool> exclusive_fullscreen_control, FeatureMask disabled_features, Error* error)
u32 shader_cache_version, bool debug_device, DisplaySyncMode sync_mode,
bool threaded_presentation, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error)
{
m_vsync_enabled = vsync;
m_sync_mode = sync_mode;
m_debug_device = debug_device;
if (!AcquireWindow(true))
@ -584,6 +585,11 @@ void GPUDevice::RenderImGui()
}
}
void GPUDevice::SetSyncMode(DisplaySyncMode mode)
{
m_sync_mode = mode;
}
void GPUDevice::UploadVertexBuffer(const void* vertices, u32 vertex_size, u32 vertex_count, u32* base_vertex)
{
void* map;

View File

@ -5,6 +5,7 @@
#include "gpu_shader_cache.h"
#include "gpu_texture.h"
#include "gpu_types.h"
#include "window_info.h"
#include "common/bitfield.h"
@ -25,17 +26,6 @@
class Error;
enum class RenderAPI : u32
{
None,
D3D11,
D3D12,
Vulkan,
OpenGL,
OpenGLES,
Metal
};
class GPUSampler
{
public:
@ -551,7 +541,7 @@ public:
virtual RenderAPI GetRenderAPI() const = 0;
bool Create(const std::string_view& adapter, const std::string_view& shader_cache_path, u32 shader_cache_version,
bool debug_device, bool vsync, bool threaded_presentation,
bool debug_device, DisplaySyncMode sync_mode, bool threaded_presentation,
std::optional<bool> exclusive_fullscreen_control, FeatureMask disabled_features, Error* error);
void Destroy();
@ -646,8 +636,12 @@ public:
/// Renders ImGui screen elements. Call before EndPresent().
void RenderImGui();
ALWAYS_INLINE bool IsVsyncEnabled() const { return m_vsync_enabled; }
virtual void SetVSync(bool enabled) = 0;
ALWAYS_INLINE DisplaySyncMode GetSyncMode() const { return m_sync_mode; }
ALWAYS_INLINE bool IsVSyncActive() const
{
return (m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed);
}
virtual void SetSyncMode(DisplaySyncMode mode);
ALWAYS_INLINE bool IsDebugDevice() const { return m_debug_device; }
ALWAYS_INLINE size_t GetVRAMUsage() const { return s_total_vram_usage; }
@ -760,8 +754,8 @@ private:
protected:
static Statistics s_stats;
DisplaySyncMode m_sync_mode = DisplaySyncMode::Disabled;
bool m_gpu_timing_enabled = false;
bool m_vsync_enabled = false;
bool m_debug_device = false;
};

24
src/util/gpu_types.h Normal file
View File

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
enum class RenderAPI : u32
{
None,
D3D11,
D3D12,
Vulkan,
OpenGL,
OpenGLES,
Metal
};
enum class DisplaySyncMode : u8
{
Disabled,
VSync,
VSyncRelaxed,
VRR,
Count
};

View File

@ -263,7 +263,7 @@ public:
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
void SetVSync(bool enabled) override;
void SetSyncMode(DisplaySyncMode mode) override;
bool BeginPresent(bool skip_present) override;
void EndPresent() override;

View File

@ -124,12 +124,15 @@ bool MetalDevice::GetHostRefreshRate(float* refresh_rate)
return GPUDevice::GetHostRefreshRate(refresh_rate);
}
void MetalDevice::SetVSync(bool enabled)
void MetalDevice::SetSyncMode(DisplaySyncMode mode)
{
m_vsync_enabled = enabled;
m_sync_mode = mode;
if (m_layer != nil)
{
const bool enabled = (mode == DisplaySyncMode::VSync || mode == DisplaySyncMode::VSyncRelaxed);
[m_layer setDisplaySyncEnabled:enabled];
}
}
bool MetalDevice::CreateDevice(const std::string_view& adapter, bool threaded_presentation,
@ -382,7 +385,8 @@ bool MetalDevice::CreateLayer()
}
});
[m_layer setDisplaySyncEnabled:m_vsync_enabled];
const bool sync_enabled = (m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed);
[m_layer setDisplaySyncEnabled:sync_enabled];
DebugAssert(m_layer_pass_desc == nil);
m_layer_pass_desc = [[MTLRenderPassDescriptor renderPassDescriptor] retain];

View File

@ -236,12 +236,12 @@ void OpenGLDevice::InsertDebugMessage(const char* msg)
#endif
}
void OpenGLDevice::SetVSync(bool enabled)
void OpenGLDevice::SetSyncMode(DisplaySyncMode mode)
{
if (m_vsync_enabled == enabled)
if (m_sync_mode == mode)
return;
m_vsync_enabled = enabled;
m_sync_mode = mode;
SetSwapInterval();
}
@ -577,7 +577,8 @@ void OpenGLDevice::SetSwapInterval()
return;
// Window framebuffer has to be bound to call SetSwapInterval.
const s32 interval = m_vsync_enabled ? 1 : 0;
const s32 interval =
(m_sync_mode == DisplaySyncMode::VSync) ? 1 : ((m_sync_mode == DisplaySyncMode::VSyncRelaxed) ? -1 : 0);
GLint current_fbo = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

View File

@ -98,7 +98,7 @@ public:
void Draw(u32 vertex_count, u32 base_vertex) override;
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override;
void SetVSync(bool enabled) override;
void SetSyncMode(DisplaySyncMode mode) override;
bool BeginPresent(bool skip_present) override;
void EndPresent() override;

View File

@ -2,6 +2,7 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\dep\msvc\vsprops\Configurations.props" />
<ItemGroup>
<ClInclude Include="gpu_types.h" />
<ClInclude Include="imgui_animated.h" />
<ClInclude Include="audio_stream.h" />
<ClInclude Include="cd_image.h" />

View File

@ -72,6 +72,7 @@
<ClInclude Include="opengl_context_egl_wayland.h" />
<ClInclude Include="opengl_context_egl_x11.h" />
<ClInclude Include="opengl_context_wgl.h" />
<ClInclude Include="gpu_types.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="jit_code_buffer.cpp" />

View File

@ -2022,7 +2022,7 @@ bool VulkanDevice::CreateDevice(const std::string_view& adapter, bool threaded_p
if (surface != VK_NULL_HANDLE)
{
m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, m_vsync_enabled, m_exclusive_fullscreen_control);
m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, m_sync_mode, m_exclusive_fullscreen_control);
if (!m_swap_chain)
{
Error::SetStringView(error, "Failed to create swap chain");
@ -2243,7 +2243,7 @@ bool VulkanDevice::UpdateWindow()
return false;
}
m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, m_vsync_enabled, m_exclusive_fullscreen_control);
m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, m_sync_mode, m_exclusive_fullscreen_control);
if (!m_swap_chain)
{
Log_ErrorPrintf("Failed to create swap chain");
@ -2319,24 +2319,27 @@ std::string VulkanDevice::GetDriverInfo() const
return ret;
}
void VulkanDevice::SetVSync(bool enabled)
void VulkanDevice::SetSyncMode(DisplaySyncMode mode)
{
if (!m_swap_chain || m_vsync_enabled == enabled)
if (m_sync_mode == mode)
return;
const DisplaySyncMode prev_mode = m_sync_mode;
m_sync_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->SetVSync(enabled))
if (!m_swap_chain->SetSyncMode(mode))
{
// Try switching back to the old mode..
if (!m_swap_chain->SetVSync(m_vsync_enabled))
if (!m_swap_chain->SetSyncMode(prev_mode))
{
Panic("Failed to reset old vsync mode after failure");
m_swap_chain.reset();
}
}
m_vsync_enabled = enabled;
}
bool VulkanDevice::BeginPresent(bool frame_skip)

View File

@ -126,7 +126,7 @@ public:
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
void SetVSync(bool enabled) override;
void SetSyncMode(DisplaySyncMode mode) override;
bool BeginPresent(bool skip_present) override;
void EndPresent() override;

View File

@ -22,9 +22,69 @@
Log_SetChannel(VulkanDevice);
VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync,
static VkFormat GetLinearFormat(VkFormat format)
{
switch (format)
{
case VK_FORMAT_R8_SRGB:
return VK_FORMAT_R8_UNORM;
case VK_FORMAT_R8G8_SRGB:
return VK_FORMAT_R8G8_UNORM;
case VK_FORMAT_R8G8B8_SRGB:
return VK_FORMAT_R8G8B8_UNORM;
case VK_FORMAT_R8G8B8A8_SRGB:
return VK_FORMAT_R8G8B8A8_UNORM;
case VK_FORMAT_B8G8R8_SRGB:
return VK_FORMAT_B8G8R8_UNORM;
case VK_FORMAT_B8G8R8A8_SRGB:
return VK_FORMAT_B8G8R8A8_UNORM;
default:
return format;
}
}
static const char* PresentModeToString(VkPresentModeKHR mode)
{
switch (mode)
{
case VK_PRESENT_MODE_IMMEDIATE_KHR:
return "VK_PRESENT_MODE_IMMEDIATE_KHR";
case VK_PRESENT_MODE_MAILBOX_KHR:
return "VK_PRESENT_MODE_MAILBOX_KHR";
case VK_PRESENT_MODE_FIFO_KHR:
return "VK_PRESENT_MODE_FIFO_KHR";
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
return "VK_PRESENT_MODE_FIFO_RELAXED_KHR";
case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR:
return "VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR";
case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR:
return "VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR";
default:
return "UNKNOWN_VK_PRESENT_MODE";
}
}
static VkPresentModeKHR GetPreferredPresentModeForVsyncMode(DisplaySyncMode mode)
{
static constexpr std::array<VkPresentModeKHR, static_cast<size_t>(DisplaySyncMode::Count)> modes = {{
VK_PRESENT_MODE_IMMEDIATE_KHR, // Disabled
VK_PRESENT_MODE_FIFO_KHR, // VSync
VK_PRESENT_MODE_FIFO_RELAXED_KHR, // VSyncRelaxed
VK_PRESENT_MODE_IMMEDIATE_KHR, // VRR ??
}};
return modes[static_cast<size_t>(mode)];
}
VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR requested_present_mode,
std::optional<bool> exclusive_fullscreen_control)
: m_window_info(wi), m_surface(surface), m_vsync_mode(vsync),
: m_window_info(wi), m_surface(surface), m_requested_present_mode(requested_present_mode),
m_exclusive_fullscreen_control(exclusive_fullscreen_control)
{
}
@ -160,38 +220,19 @@ void VulkanSwapChain::DestroyVulkanSurface(VkInstance instance, WindowInfo* wi,
#endif
}
std::unique_ptr<VulkanSwapChain> VulkanSwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync,
std::unique_ptr<VulkanSwapChain> VulkanSwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface,
DisplaySyncMode sync_mode,
std::optional<bool> exclusive_fullscreen_control)
{
const VkPresentModeKHR requested_mode = GetPreferredPresentModeForVsyncMode(sync_mode);
std::unique_ptr<VulkanSwapChain> swap_chain =
std::unique_ptr<VulkanSwapChain>(new VulkanSwapChain(wi, surface, vsync, exclusive_fullscreen_control));
std::unique_ptr<VulkanSwapChain>(new VulkanSwapChain(wi, surface, requested_mode, exclusive_fullscreen_control));
if (!swap_chain->CreateSwapChain())
return nullptr;
return swap_chain;
}
static VkFormat GetLinearFormat(VkFormat format)
{
switch (format)
{
case VK_FORMAT_R8_SRGB:
return VK_FORMAT_R8_UNORM;
case VK_FORMAT_R8G8_SRGB:
return VK_FORMAT_R8G8_UNORM;
case VK_FORMAT_R8G8B8_SRGB:
return VK_FORMAT_R8G8B8_UNORM;
case VK_FORMAT_R8G8B8A8_SRGB:
return VK_FORMAT_R8G8B8A8_UNORM;
case VK_FORMAT_B8G8R8_SRGB:
return VK_FORMAT_B8G8R8_UNORM;
case VK_FORMAT_B8G8R8A8_SRGB:
return VK_FORMAT_B8G8R8A8_UNORM;
default:
return format;
}
}
std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkSurfaceKHR surface)
{
VulkanDevice& dev = VulkanDevice::GetInstance();
@ -232,44 +273,8 @@ std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkSurface
return std::nullopt;
}
static const char* PresentModeToString(VkPresentModeKHR mode)
{
switch (mode)
{
case VK_PRESENT_MODE_IMMEDIATE_KHR:
return "VK_PRESENT_MODE_IMMEDIATE_KHR";
case VK_PRESENT_MODE_MAILBOX_KHR:
return "VK_PRESENT_MODE_MAILBOX_KHR";
case VK_PRESENT_MODE_FIFO_KHR:
return "VK_PRESENT_MODE_FIFO_KHR";
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
return "VK_PRESENT_MODE_FIFO_RELAXED_KHR";
case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR:
return "VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR";
case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR:
return "VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR";
default:
return "UNKNOWN_VK_PRESENT_MODE";
}
}
static VkPresentModeKHR GetPreferredPresentModeForVsyncMode(bool mode)
{
if (mode /*== VsyncMode::On*/)
return VK_PRESENT_MODE_FIFO_KHR;
/*else if (mode == VsyncMode::Adaptive)
return VK_PRESENT_MODE_FIFO_RELAXED_KHR;*/
else
return VK_PRESENT_MODE_IMMEDIATE_KHR;
}
std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, bool vsync)
std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface,
VkPresentModeKHR requested_mode)
{
VulkanDevice& dev = VulkanDevice::GetInstance();
VkResult res;
@ -294,18 +299,17 @@ std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkSurfaceKHR
};
// Use preferred mode if available.
const VkPresentModeKHR preferred_mode = GetPreferredPresentModeForVsyncMode(vsync);
VkPresentModeKHR selected_mode;
if (CheckForMode(preferred_mode))
if (CheckForMode(requested_mode))
{
selected_mode = preferred_mode;
selected_mode = requested_mode;
}
else if (!vsync /*vsync != VsyncMode::On*/ && CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
else if (requested_mode != VK_PRESENT_MODE_FIFO_KHR && CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
{
// Prefer mailbox over fifo for adaptive vsync/no-vsync.
selected_mode = VK_PRESENT_MODE_MAILBOX_KHR;
}
else if (vsync /*vsync != VsyncMode::Off*/ && CheckForMode(VK_PRESENT_MODE_FIFO_KHR))
else if (requested_mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR && CheckForMode(VK_PRESENT_MODE_FIFO_KHR))
{
// Fallback to FIFO if we're using any kind of vsync.
// This should never fail, FIFO is mandated.
@ -317,7 +321,7 @@ std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkSurfaceKHR
selected_mode = present_modes[0];
}
Log_DevPrintf("(SwapChain) Preferred present mode: %s, selected: %s", PresentModeToString(preferred_mode),
Log_DevPrintf("(SwapChain) Preferred present mode: %s, selected: %s", PresentModeToString(requested_mode),
PresentModeToString(selected_mode));
return selected_mode;
@ -329,7 +333,7 @@ bool VulkanSwapChain::CreateSwapChain()
// Select swap chain format and present mode
std::optional<VkSurfaceFormatKHR> surface_format = SelectSurfaceFormat(m_surface);
std::optional<VkPresentModeKHR> present_mode = SelectPresentMode(m_surface, m_vsync_mode);
std::optional<VkPresentModeKHR> present_mode = SelectPresentMode(m_surface, m_requested_present_mode);
if (!surface_format.has_value() || !present_mode.has_value())
return false;
@ -468,6 +472,7 @@ 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)
{
Log_ErrorPrintf("Unknown Vulkan surface format %u", static_cast<u32>(surface_format->format));
@ -634,12 +639,13 @@ bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_s
return true;
}
bool VulkanSwapChain::SetVSync(bool mode)
bool VulkanSwapChain::SetSyncMode(DisplaySyncMode mode)
{
if (m_vsync_mode == mode)
const VkPresentModeKHR present_mode = GetPreferredPresentModeForVsyncMode(mode);
if (m_requested_present_mode == present_mode)
return true;
m_vsync_mode = mode;
m_requested_present_mode = present_mode;
// Recreate the swap chain with the new present mode.
Log_VerbosePrintf("Recreating swap chain to change present mode.");

View File

@ -25,7 +25,7 @@ public:
static void DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, VkSurfaceKHR surface);
// Create a new swap chain from a pre-existing surface.
static std::unique_ptr<VulkanSwapChain> Create(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync,
static std::unique_ptr<VulkanSwapChain> Create(const WindowInfo& wi, VkSurfaceKHR surface, DisplaySyncMode sync_mode,
std::optional<bool> exclusive_fullscreen_control);
ALWAYS_INLINE VkSurfaceKHR GetSurface() const { return m_surface; }
@ -60,9 +60,12 @@ public:
}
// Returns true if the current present mode is synchronizing (adaptive or hard).
ALWAYS_INLINE bool IsPresentModeSynchronizing() const { return (m_vsync_mode /*!= VsyncMode::Off*/); }
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);
}
VkRenderPass GetRenderPass(VkAttachmentLoadOp load_op) const;
VkResult AcquireNextImage();
void ReleaseCurrentImage();
@ -70,19 +73,18 @@ 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 SetVSync(bool mode);
bool SetSyncMode(DisplaySyncMode mode);
private:
VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync,
VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR requested_present_mode,
std::optional<bool> exclusive_fullscreen_control);
static std::optional<VkSurfaceFormatKHR> SelectSurfaceFormat(VkSurfaceKHR surface);
static std::optional<VkPresentModeKHR> SelectPresentMode(VkSurfaceKHR surface, bool vsync);
static std::optional<VkPresentModeKHR> SelectPresentMode(VkSurfaceKHR surface, VkPresentModeKHR requested_mode);
bool CreateSwapChain();
void DestroySwapChain();
bool SetupSwapChainImages();
void DestroySwapChainImages();
void DestroySurface();
@ -109,7 +111,8 @@ private:
std::vector<ImageSemaphores> m_semaphores;
VkFormat m_format = VK_FORMAT_UNDEFINED;
bool m_vsync_mode = false;
VkPresentModeKHR m_requested_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
VkPresentModeKHR m_actual_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
u32 m_current_image = 0;
u32 m_current_semaphore = 0;