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); "GPU", "UseSoftwareRendererForReadbacks", false);
} }
DrawToggleSetting( DrawEnumSetting(
bsi, FSUI_CSTR("Enable VSync"), bsi, FSUI_CSTR("VSync"),
FSUI_CSTR("Synchronizes presentation of the console's frames to the host. Enable for smoother animations."), 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( DrawToggleSetting(
bsi, FSUI_CSTR("Sync To Host Refresh Rate"), bsi, FSUI_CSTR("Sync To Host Refresh Rate"),

View File

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

View File

@ -243,6 +243,10 @@ void Settings::Load(SettingsInterface& si)
display_scaling = display_scaling =
ParseDisplayScaling(si.GetStringValue("Display", "Scaling", GetDisplayScalingName(DEFAULT_DISPLAY_SCALING)).c_str()) ParseDisplayScaling(si.GetStringValue("Display", "Scaling", GetDisplayScalingName(DEFAULT_DISPLAY_SCALING)).c_str())
.value_or(DEFAULT_DISPLAY_SCALING); .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 = display_exclusive_fullscreen_control =
ParseDisplayExclusiveFullscreenControl( ParseDisplayExclusiveFullscreenControl(
si.GetStringValue("Display", "ExclusiveFullscreenControl", si.GetStringValue("Display", "ExclusiveFullscreenControl",
@ -279,7 +283,6 @@ void Settings::Load(SettingsInterface& si)
display_show_enhancements = si.GetBoolValue("Display", "ShowEnhancements", false); display_show_enhancements = si.GetBoolValue("Display", "ShowEnhancements", false);
display_all_frames = si.GetBoolValue("Display", "DisplayAllFrames", false); display_all_frames = si.GetBoolValue("Display", "DisplayAllFrames", false);
display_stretch_vertically = si.GetBoolValue("Display", "StretchVertically", 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_max_fps = si.GetFloatValue("Display", "MaxFPS", DEFAULT_DISPLAY_MAX_FPS);
display_osd_scale = si.GetFloatValue("Display", "OSDScale", DEFAULT_OSD_SCALE); 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", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio));
si.SetStringValue("Display", "Alignment", GetDisplayAlignmentName(display_alignment)); si.SetStringValue("Display", "Alignment", GetDisplayAlignmentName(display_alignment));
si.SetStringValue("Display", "Scaling", GetDisplayScalingName(display_scaling)); si.SetStringValue("Display", "Scaling", GetDisplayScalingName(display_scaling));
si.SetStringValue("Display", "SyncMode", GetDisplaySyncModeName(display_sync_mode));
si.SetStringValue("Display", "ExclusiveFullscreenControl", si.SetStringValue("Display", "ExclusiveFullscreenControl",
GetDisplayExclusiveFullscreenControlName(display_exclusive_fullscreen_control)); GetDisplayExclusiveFullscreenControlName(display_exclusive_fullscreen_control));
si.SetStringValue("Display", "ScreenshotMode", GetDisplayScreenshotModeName(display_screenshot_mode)); 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", "ShowEnhancements", display_show_enhancements);
si.SetBoolValue("Display", "DisplayAllFrames", display_all_frames); si.SetBoolValue("Display", "DisplayAllFrames", display_all_frames);
si.SetBoolValue("Display", "StretchVertically", display_stretch_vertically); si.SetBoolValue("Display", "StretchVertically", display_stretch_vertically);
si.SetBoolValue("Display", "VSync", video_sync_enabled);
si.SetFloatValue("Display", "MaxFPS", display_max_fps); si.SetFloatValue("Display", "MaxFPS", display_max_fps);
si.SetFloatValue("Display", "OSDScale", display_osd_scale); 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)]); 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 = { static constexpr const std::array s_display_exclusive_fullscreen_mode_names = {
"Automatic", "Automatic",
"Disallowed", "Disallowed",

View File

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

View File

@ -1849,6 +1849,12 @@ void System::FrameDone()
SaveRunaheadState(); 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(); const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
if (current_time < s_next_frame_time || s_display_all_frames || s_last_frame_skipped) 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; s_last_frame_skipped = true;
} }
if (s_throttler_enabled && !IsExecutionInterrupted()) if (throttle_after_present && s_throttler_enabled && !IsExecutionInterrupted())
Throttle(); Throttle();
// Input poll already done above // Input poll already done above
@ -2638,11 +2644,15 @@ void System::UpdateSpeedLimiterState()
} }
// When syncing to host and using vsync, we don't need to sleep. // 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)
{
const DisplaySyncMode effective_sync_mode = GetEffectiveDisplaySyncMode();
if (effective_sync_mode == DisplaySyncMode::VSync || effective_sync_mode == DisplaySyncMode::VSyncRelaxed)
{ {
Log_InfoPrintf("Using host vsync for throttling."); Log_InfoPrintf("Using host vsync for throttling.");
s_throttler_enabled = false; s_throttler_enabled = false;
} }
}
Log_VerbosePrintf("Target speed: %f%%", s_target_speed * 100.0f); Log_VerbosePrintf("Target speed: %f%%", s_target_speed * 100.0f);
@ -2674,21 +2684,25 @@ void System::UpdateSpeedLimiterState()
void System::UpdateDisplaySync() void System::UpdateDisplaySync()
{ {
const bool video_sync_enabled = ShouldUseVSync(); const DisplaySyncMode display_sync_mode = GetEffectiveDisplaySyncMode();
const bool syncing_to_host_vsync = (s_syncing_to_host && video_sync_enabled && s_display_all_frames); 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; 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)" : ""); syncing_to_host_vsync ? " (for throttling)" : "");
Log_VerbosePrintf("Max display fps: %f (%s)", max_display_fps, Log_VerbosePrintf("Max display fps: %f (%s)", max_display_fps,
s_display_all_frames ? "displaying all frames" : "skipping displaying frames when needed"); s_display_all_frames ? "displaying all frames" : "skipping displaying frames when needed");
g_gpu_device->SetDisplayMaxFPS(max_display_fps); 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() bool System::IsFastForwardEnabled()
@ -3737,7 +3751,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
DMA::SetHaltTicks(g_settings.dma_halt_ticks); DMA::SetHaltTicks(g_settings.dma_halt_ticks);
if (g_settings.audio_backend != old_settings.audio_backend || 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.increase_timer_resolution != old_settings.increase_timer_resolution ||
g_settings.emulation_speed != old_settings.emulation_speed || g_settings.emulation_speed != old_settings.emulation_speed ||
g_settings.fast_forward_speed != old_settings.fast_forward_speed || g_settings.fast_forward_speed != old_settings.fast_forward_speed ||

View File

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

View File

@ -51,8 +51,13 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.renderer, "GPU", "Renderer", &Settings::ParseRendererName, SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.renderer, "GPU", "Renderer", &Settings::ParseRendererName,
&Settings::GetRendererName, Settings::DEFAULT_GPU_RENDERER); &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", SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayAspectRatio, "Display", "AspectRatio",
&Settings::ParseDisplayAspectRatio, &Settings::GetDisplayAspectRatioName, &Settings::ParseDisplayAspectRatio, &Settings::GetDisplayAspectRatioName,
Settings::DEFAULT_DISPLAY_ASPECT_RATIO); Settings::DEFAULT_DISPLAY_ASPECT_RATIO);
@ -67,13 +72,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayScaling, "Display", "Scaling", SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayScaling, "Display", "Scaling",
&Settings::ParseDisplayScaling, &Settings::GetDisplayScalingName, &Settings::ParseDisplayScaling, &Settings::GetDisplayScalingName,
Settings::DEFAULT_DISPLAY_SCALING); Settings::DEFAULT_DISPLAY_SCALING);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.resolutionScale, "GPU", "ResolutionScale", 1); SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displaySyncMode, "Display", "SyncMode",
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.textureFiltering, "GPU", "TextureFilter", &Settings::ParseDisplaySyncMode, &Settings::GetDisplaySyncModeName,
&Settings::ParseTextureFilterName, &Settings::GetTextureFilterName, Settings::DEFAULT_DISPLAY_SYNC_MODE);
Settings::DEFAULT_GPU_TEXTURE_FILTER);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode",
&Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName,
Settings::DEFAULT_GPU_DOWNSAMPLE_MODE);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.gpuDownsampleScale, "GPU", "DownsampleScale", 1); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.gpuDownsampleScale, "GPU", "DownsampleScale", 1);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.trueColor, "GPU", "TrueColor", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.trueColor, "GPU", "TrueColor", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableInterlacing, "GPU", "DisableInterlacing", true); 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 " "renderers. <br>This option is only supported in Direct3D and Vulkan. OpenGL will always use the default "
"device.")); "device."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.vsync, tr("VSync"), tr("Unchecked"), m_ui.resolutionScale, tr("Internal Resolution"), "1x",
tr("Enable this option to match DuckStation's refresh rate with your current monitor or screen. " tr("Setting this beyond 1x will enhance the resolution of rendered 3D polygons and lines. Only applies "
"VSync is automatically disabled when it is not possible (e.g. running at non-100% speed).")); "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( dialog->registerWidgetHelp(
m_ui.displayAspectRatio, tr("Aspect Ratio"), m_ui.displayAspectRatio, tr("Aspect Ratio"),
QString::fromUtf8(Settings::GetDisplayAspectRatioDisplayName(Settings::DEFAULT_DISPLAY_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)"), 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.")); tr("Determines how the emulated console's output is upscaled or downscaled to your monitor's resolution."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.resolutionScale, tr("Internal Resolution"), "1x", m_ui.displaySyncMode, tr("VSync"), tr("Unchecked"),
tr("Setting this beyond 1x will enhance the resolution of rendered 3D polygons and lines. Only applies " tr("Enable this option to match DuckStation's refresh rate with your current monitor or screen. "
"to the hardware backends. <br>This option is usually safe, with most games looking fine at " "VSync is automatically disabled when it is not possible (e.g. running at non-100% speed)."));
"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."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.trueColor, tr("True Color Rendering"), tr("Checked"), 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 " 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)), 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 " tr("Attempts to detect one pixel high/wide lines that rely on non-upscaled rasterization "
"behavior, filling in gaps introduced by upscaling.")); "behavior, filling in gaps introduced by upscaling."));
dialog->registerWidgetHelp(m_ui.gpuWireframeMode, tr("Wireframe Mode"), tr("Disabled"), dialog->registerWidgetHelp(
tr("Draws a wireframe outline of the triangles rendered by the console's GPU, either as a " m_ui.msaaMode, tr("Multi-Sampling"), tr("Disabled"),
"replacement or an overlay.")); 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( dialog->registerWidgetHelp(
m_ui.debanding, tr("True Color Debanding"), tr("Unchecked"), m_ui.debanding, tr("True Color Debanding"), tr("Unchecked"),
tr("Applies modern dithering techniques to further smooth out gradients when true color is enabled. " 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 // 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( dialog->registerWidgetHelp(
m_ui.useDebugDevice, tr("Use Debug Device"), tr("Unchecked"), 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>")); 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)))); 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++) for (u32 i = 0; i < static_cast<u32>(DisplayAspectRatio::Count); i++)
{ {
m_ui.displayAspectRatio->addItem( m_ui.displayAspectRatio->addItem(
@ -523,26 +537,10 @@ void GraphicsSettingsWidget::setupAdditionalUi()
QString::fromUtf8(Settings::GetDisplayScalingDisplayName(static_cast<DisplayScalingMode>(i)))); 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.displaySyncMode->addItem(
m_ui.msaaMode->addItem(tr("Use Global Setting")); QString::fromUtf8(Settings::GetDisplaySyncModeDisplayName(static_cast<DisplaySyncMode>(i))));
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))));
} }
// Advanced Tab // Advanced Tab
@ -559,18 +557,22 @@ void GraphicsSettingsWidget::setupAdditionalUi()
QString::fromUtf8(Settings::GetDisplayAlignmentDisplayName(static_cast<DisplayAlignment>(i)))); 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++) for (u32 i = 0; i < static_cast<u32>(GPULineDetectMode::Count); i++)
{ {
m_ui.gpuLineDetectMode->addItem( m_ui.gpuLineDetectMode->addItem(
QString::fromUtf8(Settings::GetLineDetectModeDisplayName(static_cast<GPULineDetectMode>(i)))); 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 // Capture Tab
for (u32 i = 0; i < static_cast<u32>(DisplayScreenshotMode::Count); i++) for (u32 i = 0; i < static_cast<u32>(DisplayScreenshotMode::Count); i++)
@ -584,6 +586,14 @@ void GraphicsSettingsWidget::setupAdditionalUi()
m_ui.screenshotFormat->addItem( m_ui.screenshotFormat->addItem(
QString::fromUtf8(Settings::GetDisplayScreenshotFormatDisplayName(static_cast<DisplayScreenshotFormat>(i)))); 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() void GraphicsSettingsWidget::removePlatformSpecificUi()

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>584</width> <width>584</width>
<height>446</height> <height>434</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -50,19 +50,8 @@
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,0">
<item>
<widget class="QComboBox" name="adapter"/> <widget class="QComboBox" name="adapter"/>
</item> </item>
<item>
<widget class="QCheckBox" name="vsync">
<property name="text">
<string>VSync</string>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -98,74 +87,13 @@
</property> </property>
<layout class="QFormLayout" name="formLayout_2"> <layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0"> <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"> <widget class="QLabel" name="resolutionScaleLabel">
<property name="text"> <property name="text">
<string>Internal Resolution:</string> <string>Internal Resolution:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="0" column="1">
<widget class="QComboBox" name="resolutionScale"> <widget class="QComboBox" name="resolutionScale">
<item> <item>
<property name="text"> <property name="text">
@ -254,27 +182,115 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="1" column="0">
<widget class="QLabel" name="msaaModeLabel"> <widget class="QLabel" name="gpuDownsampleLabel">
<property name="text"> <property name="text">
<string>Multi-Sampling:</string> <string>Down-Sampling:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="1" column="1">
<widget class="QComboBox" name="msaaMode"/> <layout class="QHBoxLayout" name="gpuDownsampleLayout" stretch="1,0">
<item>
<widget class="QComboBox" name="gpuDownsampleMode"/>
</item> </item>
<item row="6" column="0"> <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="2" column="0">
<widget class="QLabel" name="textureFilteringLabel"> <widget class="QLabel" name="textureFilteringLabel">
<property name="text"> <property name="text">
<string>Texture Filtering:</string> <string>Texture Filtering:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="2" column="1">
<widget class="QComboBox" name="textureFiltering"/> <widget class="QComboBox" name="textureFiltering"/>
</item> </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"> <layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0"> <item row="1" column="0">
<widget class="QCheckBox" name="pgxpEnable"> <widget class="QCheckBox" name="pgxpEnable">
@ -283,6 +299,13 @@
</property> </property>
</widget> </widget>
</item> </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"> <item row="1" column="1">
<widget class="QCheckBox" name="pgxpDepthBuffer"> <widget class="QCheckBox" name="pgxpDepthBuffer">
<property name="text"> <property name="text">
@ -304,20 +327,6 @@
</property> </property>
</widget> </widget>
</item> </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"> <item row="3" column="0">
<widget class="QCheckBox" name="disableInterlacing"> <widget class="QCheckBox" name="disableInterlacing">
<property name="text"> <property name="text">
@ -332,30 +341,10 @@
</property> </property>
</widget> </widget>
</item> </item>
</layout> <item row="3" column="1">
</item> <widget class="QCheckBox" name="forceNTSCTimings">
<item row="5" column="0">
<widget class="QLabel" name="gpuDownsampleLabel">
<property name="text"> <property name="text">
<string>Down-Sampling:</string> <string>Force NTSC Timings</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>
</property> </property>
</widget> </widget>
</item> </item>
@ -477,6 +466,16 @@
<string>Rendering Options</string> <string>Rendering Options</string>
</property> </property>
<layout class="QFormLayout" name="formLayout_6"> <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"> <item row="2" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_5"> <layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="1"> <item row="0" column="1">
@ -503,24 +502,14 @@
</layout> </layout>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="gpuLineDetectModeLabel"> <widget class="QLabel" name="msaaModeLabel">
<property name="text"> <property name="text">
<string>Line Detection:</string> <string>Multi-Sampling:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QComboBox" name="gpuLineDetectMode"/> <widget class="QComboBox" name="msaaMode"/>
</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"/>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -1035,6 +1024,25 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </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> <item>
<widget class="QGroupBox" name="groupBox_9"> <widget class="QGroupBox" name="groupBox_9">
<property name="title"> <property name="title">

View File

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

View File

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

View File

@ -233,7 +233,8 @@ bool D3D11Device::CreateSwapChain()
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; 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; 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) if (m_using_allow_tearing)
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_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); return GPUDevice::GetHostRefreshRate(refresh_rate);
} }
void D3D11Device::SetVSync(bool enabled)
{
m_vsync_enabled = enabled;
}
bool D3D11Device::BeginPresent(bool skip_present) bool D3D11Device::BeginPresent(bool skip_present)
{ {
if (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 // This blows our our GPU usage number considerably, so read the timestamp before the final blit
// in this configuration. It does reduce accuracy a little, but better than seeing 100% all of // in this configuration. It does reduce accuracy a little, but better than seeing 100% all of
// the time, when it's more like a couple of percent. // the time, when it's more like a couple of percent.
if (m_vsync_enabled && m_gpu_timing_enabled) if ((m_sync_mode == DisplaySyncMode::VSync || m_sync_mode == DisplaySyncMode::VSyncRelaxed) && 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};
@ -650,13 +646,16 @@ void D3D11Device::EndPresent()
{ {
DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target); 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(); 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); m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING);
else else
m_swap_chain->Present(BoolToUInt32(m_vsync_enabled), 0); m_swap_chain->Present(0, 0);
if (m_gpu_timing_enabled) if (m_gpu_timing_enabled)
KickTimestampQuery(); KickTimestampQuery();

View File

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

View File

@ -1060,11 +1060,6 @@ std::string D3D12Device::GetDriverInfo() const
return ret; return ret;
} }
void D3D12Device::SetVSync(bool enabled)
{
m_vsync_enabled = enabled;
}
bool D3D12Device::BeginPresent(bool frame_skip) bool D3D12Device::BeginPresent(bool frame_skip)
{ {
if (InRenderPass()) if (InRenderPass())
@ -1112,10 +1107,13 @@ void D3D12Device::EndPresent()
SubmitCommandList(false); 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); m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING);
else else
m_swap_chain->Present(static_cast<UINT>(m_vsync_enabled), 0); m_swap_chain->Present(0, 0);
TrimTexturePool(); TrimTexturePool();
} }

View File

@ -119,8 +119,6 @@ public:
bool SetGPUTimingEnabled(bool enabled) override; bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override; float GetAndResetAccumulatedGPUTime() override;
void SetVSync(bool enabled) override;
bool BeginPresent(bool skip_present) override; bool BeginPresent(bool skip_present) override;
void EndPresent() 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, 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, u32 shader_cache_version, bool debug_device, DisplaySyncMode sync_mode,
std::optional<bool> exclusive_fullscreen_control, FeatureMask disabled_features, Error* error) 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; m_debug_device = debug_device;
if (!AcquireWindow(true)) 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 GPUDevice::UploadVertexBuffer(const void* vertices, u32 vertex_size, u32 vertex_count, u32* base_vertex)
{ {
void* map; void* map;

View File

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

View File

@ -124,13 +124,16 @@ bool MetalDevice::GetHostRefreshRate(float* refresh_rate)
return GPUDevice::GetHostRefreshRate(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) if (m_layer != nil)
{
const bool enabled = (mode == DisplaySyncMode::VSync || mode == DisplaySyncMode::VSyncRelaxed);
[m_layer setDisplaySyncEnabled:enabled]; [m_layer setDisplaySyncEnabled:enabled];
} }
}
bool MetalDevice::CreateDevice(const std::string_view& adapter, bool threaded_presentation, bool MetalDevice::CreateDevice(const std::string_view& adapter, bool threaded_presentation,
std::optional<bool> exclusive_fullscreen_control, FeatureMask disabled_features, std::optional<bool> exclusive_fullscreen_control, FeatureMask disabled_features,
@ -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); DebugAssert(m_layer_pass_desc == nil);
m_layer_pass_desc = [[MTLRenderPassDescriptor renderPassDescriptor] retain]; m_layer_pass_desc = [[MTLRenderPassDescriptor renderPassDescriptor] retain];

View File

@ -236,12 +236,12 @@ void OpenGLDevice::InsertDebugMessage(const char* msg)
#endif #endif
} }
void OpenGLDevice::SetVSync(bool enabled) void OpenGLDevice::SetSyncMode(DisplaySyncMode mode)
{ {
if (m_vsync_enabled == enabled) if (m_sync_mode == mode)
return; return;
m_vsync_enabled = enabled; m_sync_mode = mode;
SetSwapInterval(); SetSwapInterval();
} }
@ -577,7 +577,8 @@ 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_enabled ? 1 : 0; const s32 interval =
(m_sync_mode == DisplaySyncMode::VSync) ? 1 : ((m_sync_mode == DisplaySyncMode::VSyncRelaxed) ? -1 : 0);
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

@ -98,7 +98,7 @@ public:
void Draw(u32 vertex_count, u32 base_vertex) override; void Draw(u32 vertex_count, u32 base_vertex) override;
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 SetVSync(bool enabled) override; void SetSyncMode(DisplaySyncMode mode) override;
bool BeginPresent(bool skip_present) override; bool BeginPresent(bool skip_present) override;
void EndPresent() override; void EndPresent() override;

View File

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

View File

@ -72,6 +72,7 @@
<ClInclude Include="opengl_context_egl_wayland.h" /> <ClInclude Include="opengl_context_egl_wayland.h" />
<ClInclude Include="opengl_context_egl_x11.h" /> <ClInclude Include="opengl_context_egl_x11.h" />
<ClInclude Include="opengl_context_wgl.h" /> <ClInclude Include="opengl_context_wgl.h" />
<ClInclude Include="gpu_types.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="jit_code_buffer.cpp" /> <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) 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) if (!m_swap_chain)
{ {
Error::SetStringView(error, "Failed to create swap chain"); Error::SetStringView(error, "Failed to create swap chain");
@ -2243,7 +2243,7 @@ bool VulkanDevice::UpdateWindow()
return false; 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) if (!m_swap_chain)
{ {
Log_ErrorPrintf("Failed to create swap chain"); Log_ErrorPrintf("Failed to create swap chain");
@ -2319,24 +2319,27 @@ std::string VulkanDevice::GetDriverInfo() const
return ret; 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; 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->SetVSync(enabled)) if (!m_swap_chain->SetSyncMode(mode))
{ {
// Try switching back to the old 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"); Panic("Failed to reset old vsync mode after failure");
m_swap_chain.reset(); m_swap_chain.reset();
} }
} }
m_vsync_enabled = enabled;
} }
bool VulkanDevice::BeginPresent(bool frame_skip) bool VulkanDevice::BeginPresent(bool frame_skip)

View File

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

View File

@ -22,9 +22,69 @@
Log_SetChannel(VulkanDevice); 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) 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) m_exclusive_fullscreen_control(exclusive_fullscreen_control)
{ {
} }
@ -160,38 +220,19 @@ void VulkanSwapChain::DestroyVulkanSurface(VkInstance instance, WindowInfo* wi,
#endif #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) std::optional<bool> exclusive_fullscreen_control)
{ {
const VkPresentModeKHR requested_mode = GetPreferredPresentModeForVsyncMode(sync_mode);
std::unique_ptr<VulkanSwapChain> swap_chain = 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()) if (!swap_chain->CreateSwapChain())
return nullptr; return nullptr;
return swap_chain; 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) std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkSurfaceKHR surface)
{ {
VulkanDevice& dev = VulkanDevice::GetInstance(); VulkanDevice& dev = VulkanDevice::GetInstance();
@ -232,44 +273,8 @@ std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkSurface
return std::nullopt; return std::nullopt;
} }
static const char* PresentModeToString(VkPresentModeKHR mode) std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface,
{ VkPresentModeKHR requested_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)
{ {
VulkanDevice& dev = VulkanDevice::GetInstance(); VulkanDevice& dev = VulkanDevice::GetInstance();
VkResult res; VkResult res;
@ -294,18 +299,17 @@ std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkSurfaceKHR
}; };
// Use preferred mode if available. // Use preferred mode if available.
const VkPresentModeKHR preferred_mode = GetPreferredPresentModeForVsyncMode(vsync);
VkPresentModeKHR selected_mode; 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. // Prefer mailbox over fifo for adaptive vsync/no-vsync.
selected_mode = VK_PRESENT_MODE_MAILBOX_KHR; 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. // Fallback to FIFO if we're using any kind of vsync.
// This should never fail, FIFO is mandated. // This should never fail, FIFO is mandated.
@ -317,7 +321,7 @@ std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkSurfaceKHR
selected_mode = present_modes[0]; 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)); PresentModeToString(selected_mode));
return selected_mode; return selected_mode;
@ -329,7 +333,7 @@ bool VulkanSwapChain::CreateSwapChain()
// Select swap chain format and present mode // Select swap chain format and present mode
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_vsync_mode); 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() || !present_mode.has_value())
return false; return false;
@ -468,6 +472,7 @@ 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)
{ {
Log_ErrorPrintf("Unknown Vulkan surface format %u", static_cast<u32>(surface_format->format)); 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; 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; return true;
m_vsync_mode = mode; m_requested_present_mode = present_mode;
// Recreate the swap chain with the new present mode. // Recreate the swap chain with the new present mode.
Log_VerbosePrintf("Recreating swap chain to change 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); static void DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, VkSurfaceKHR surface);
// Create a new swap chain from a pre-existing 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); std::optional<bool> exclusive_fullscreen_control);
ALWAYS_INLINE VkSurfaceKHR GetSurface() const { return m_surface; } 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). // 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(); VkResult AcquireNextImage();
void ReleaseCurrentImage(); void ReleaseCurrentImage();
@ -70,19 +73,18 @@ 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 SetVSync(bool mode); bool SetSyncMode(DisplaySyncMode mode);
private: 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); 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, bool vsync); static std::optional<VkPresentModeKHR> SelectPresentMode(VkSurfaceKHR surface, VkPresentModeKHR requested_mode);
bool CreateSwapChain(); bool CreateSwapChain();
void DestroySwapChain(); void DestroySwapChain();
bool SetupSwapChainImages();
void DestroySwapChainImages(); void DestroySwapChainImages();
void DestroySurface(); void DestroySurface();
@ -109,7 +111,8 @@ private:
std::vector<ImageSemaphores> m_semaphores; std::vector<ImageSemaphores> m_semaphores;
VkFormat m_format = VK_FORMAT_UNDEFINED; 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_image = 0;
u32 m_current_semaphore = 0; u32 m_current_semaphore = 0;