diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 6efe3ca6b..08eed26d3 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -4270,6 +4270,12 @@ void FullscreenUI::DrawDisplaySettingsPage() "Display", "Scaling", Settings::DEFAULT_DISPLAY_SCALING, &Settings::ParseDisplayScaling, &Settings::GetDisplayScalingName, &Settings::GetDisplayScalingDisplayName, DisplayScalingMode::Count); + DrawEnumSetting( + bsi, FSUI_CSTR("Force Frame Timings"), + FSUI_CSTR("Utilizes the chosen frame timing regardless of the active region."), + "GPU", "ForceFrameTimings", Settings::DEFAULT_FORCE_FRAME_TIMINGS_MODE, &Settings::ParseForceFrameTimings, + &Settings::GetForceFrameTimingsName, &Settings::GetForceFrameTimingsDisplayName, ForceFrameTimingsMode::Count); + if (is_hardware) { DrawToggleSetting(bsi, FSUI_CSTR("True Color Rendering"), @@ -4309,12 +4315,6 @@ void FullscreenUI::DrawDisplaySettingsPage() "but others will break."), "GPU", "DisableInterlacing", true); - DrawToggleSetting( - bsi, FSUI_CSTR("Force NTSC Timings"), - FSUI_CSTR("Forces PAL games to run at NTSC timings, i.e. 60hz. Some PAL games will run at their \"normal\" " - "speeds, while others will break."), - "GPU", "ForceNTSCTimings", false); - MenuHeading(FSUI_CSTR("Advanced")); std::optional strvalue = bsi->GetOptionalSmallStringValue( diff --git a/src/core/game_database.cpp b/src/core/game_database.cpp index e4f58d81a..103d6c89e 100644 --- a/src/core/game_database.cpp +++ b/src/core/game_database.cpp @@ -75,7 +75,7 @@ static constexpr const std::array(GameDatabase::Tr "DisableTextureFiltering", "DisableSpriteTextureFiltering", "DisableScaledDithering", - "DisableForceNTSCTimings", + "DisableForceFrameTimings", "DisableWidescreen", "DisablePGXP", "DisablePGXPCulling", @@ -105,7 +105,7 @@ static constexpr const std::array(GameDatabase::Tr TRANSLATE_NOOP("GameDatabase", "Disable Texture Filtering"), TRANSLATE_NOOP("GameDatabase", "Disable Sprite Texture Filtering"), TRANSLATE_NOOP("GameDatabase", "Disable Scaled Dithering"), - TRANSLATE_NOOP("GameDatabase", "Disable Force NTSC Timings"), + TRANSLATE_NOOP("GameDatabase", "Disable Force Frame Timings"), TRANSLATE_NOOP("GameDatabase", "Disable Widescreen"), TRANSLATE_NOOP("GameDatabase", "Disable PGXP"), TRANSLATE_NOOP("GameDatabase", "Disable PGXP Culling"), @@ -590,12 +590,12 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes settings.gpu_widescreen_hack = false; } - if (HasTrait(Trait::DisableForceNTSCTimings)) + if (HasTrait(Trait::DisableForceFrameTimings)) { - if (display_osd_messages && settings.gpu_force_ntsc_timings) - APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Force NTSC timings disabled.")); + if (display_osd_messages && settings.gpu_force_frame_timings != ForceFrameTimingsMode::Disabled) + APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Force frame timings disabled.")); - settings.gpu_force_ntsc_timings = false; + settings.gpu_force_frame_timings = ForceFrameTimingsMode::Disabled; } if (HasTrait(Trait::DisablePGXP)) diff --git a/src/core/game_database.h b/src/core/game_database.h index 1f743c54c..2200a17c9 100644 --- a/src/core/game_database.h +++ b/src/core/game_database.h @@ -40,7 +40,7 @@ enum class Trait : u32 DisableTextureFiltering, DisableSpriteTextureFiltering, DisableScaledDithering, - DisableForceNTSCTimings, + DisableForceFrameTimings, DisableWidescreen, DisablePGXP, DisablePGXPCulling, diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index 3d9c746f6..efbec603c 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -88,7 +88,7 @@ GPU::~GPU() bool GPU::Initialize() { m_force_progressive_scan = g_settings.gpu_disable_interlacing; - m_force_ntsc_timings = g_settings.gpu_force_ntsc_timings; + m_force_frame_timings = g_settings.gpu_force_frame_timings; s_crtc_tick_event.Activate(); m_fifo_size = g_settings.gpu_fifo_size; m_max_run_ahead = g_settings.gpu_max_run_ahead; @@ -119,9 +119,9 @@ void GPU::UpdateSettings(const Settings& old_settings) m_fifo_size = g_settings.gpu_fifo_size; m_max_run_ahead = g_settings.gpu_max_run_ahead; - if (m_force_ntsc_timings != g_settings.gpu_force_ntsc_timings || m_console_is_pal != System::IsPALRegion()) + if (m_force_frame_timings != g_settings.gpu_force_frame_timings) { - m_force_ntsc_timings = g_settings.gpu_force_ntsc_timings; + m_force_frame_timings = g_settings.gpu_force_frame_timings; m_console_is_pal = System::IsPALRegion(); UpdateCRTCConfig(); } @@ -637,7 +637,7 @@ void GPU::UpdateCRTCConfig() cs.vertical_display_start = std::min(cs.regs.Y1, cs.vertical_total); cs.vertical_display_end = std::min(cs.regs.Y2, cs.vertical_total); - if (m_GPUSTAT.pal_mode && m_force_ntsc_timings) + if (m_GPUSTAT.pal_mode && m_force_frame_timings == ForceFrameTimingsMode::NTSC) { // scale to NTSC parameters cs.horizontal_display_start = @@ -655,6 +655,24 @@ void GPU::UpdateCRTCConfig() cs.horizontal_total = NTSC_TICKS_PER_LINE; cs.current_tick_in_scanline %= NTSC_TICKS_PER_LINE; } + else if (!m_GPUSTAT.pal_mode && m_force_frame_timings == ForceFrameTimingsMode::PAL) + { + // scale to PAL parameters + cs.horizontal_display_start = + static_cast((static_cast(cs.horizontal_display_start) * PAL_TICKS_PER_LINE) / NTSC_TICKS_PER_LINE); + cs.horizontal_display_end = static_cast( + ((static_cast(cs.horizontal_display_end) * PAL_TICKS_PER_LINE) + (NTSC_TICKS_PER_LINE - 1)) / + NTSC_TICKS_PER_LINE); + cs.vertical_display_start = + static_cast((static_cast(cs.vertical_display_start) * PAL_TOTAL_LINES) / NTSC_TOTAL_LINES); + cs.vertical_display_end = static_cast( + ((static_cast(cs.vertical_display_end) * PAL_TOTAL_LINES) + (NTSC_TOTAL_LINES - 1)) / NTSC_TOTAL_LINES); + + cs.vertical_total = PAL_TOTAL_LINES; + cs.current_scanline %= PAL_TOTAL_LINES; + cs.horizontal_total = PAL_TICKS_PER_LINE; + cs.current_tick_in_scanline %= PAL_TICKS_PER_LINE; + } cs.horizontal_display_start = static_cast(System::ScaleTicksToOverclock(static_cast(cs.horizontal_display_start))); diff --git a/src/core/gpu.h b/src/core/gpu.h index 58076cfb8..e7f3de0ff 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -505,7 +505,7 @@ protected: bool m_set_texture_disable_mask = false; bool m_drawing_area_changed = false; bool m_force_progressive_scan = false; - bool m_force_ntsc_timings = false; + ForceFrameTimingsMode m_force_frame_timings = ForceFrameTimingsMode::Disabled; struct CRTCState { diff --git a/src/core/imgui_overlays.cpp b/src/core/imgui_overlays.cpp index 05c72aa9a..290f34da7 100644 --- a/src/core/imgui_overlays.cpp +++ b/src/core/imgui_overlays.cpp @@ -444,8 +444,10 @@ void ImGuiManager::DrawEnhancementsOverlay() } if (g_settings.gpu_disable_interlacing) text.append(" ForceProg"); - if (g_settings.gpu_force_ntsc_timings && System::GetRegion() == ConsoleRegion::PAL) + if (g_settings.gpu_force_frame_timings == ForceFrameTimingsMode::NTSC && System::GetRegion() == ConsoleRegion::PAL) text.append(" PAL60"); + if (g_settings.gpu_force_frame_timings == ForceFrameTimingsMode::PAL && System::GetRegion() != ConsoleRegion::PAL) + text.append(" NTSC50"); if (g_settings.gpu_texture_filter != GPUTextureFilter::Nearest) { if (g_settings.gpu_sprite_texture_filter != g_settings.gpu_texture_filter) diff --git a/src/core/settings.cpp b/src/core/settings.cpp index cff0a06c4..1ca4048ac 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -229,7 +229,10 @@ void Settings::Load(SettingsInterface& si, SettingsInterface& controller_si) si.GetStringValue("GPU", "WireframeMode", GetGPUWireframeModeName(DEFAULT_GPU_WIREFRAME_MODE)).c_str()) .value_or(DEFAULT_GPU_WIREFRAME_MODE); gpu_disable_interlacing = si.GetBoolValue("GPU", "DisableInterlacing", true); - gpu_force_ntsc_timings = si.GetBoolValue("GPU", "ForceNTSCTimings", false); + gpu_force_frame_timings = + ParseForceFrameTimings( + si.GetStringValue("GPU", "ForceFrameTimings", GetForceFrameTimingsName(DEFAULT_FORCE_FRAME_TIMINGS_MODE)).c_str()) + .value_or(DEFAULT_FORCE_FRAME_TIMINGS_MODE); gpu_widescreen_hack = si.GetBoolValue("GPU", "WidescreenHack", false); display_24bit_chroma_smoothing = si.GetBoolValue("GPU", "ChromaSmoothing24Bit", false); gpu_pgxp_enable = si.GetBoolValue("GPU", "PGXPEnable", false); @@ -535,7 +538,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const si.SetUIntValue("GPU", "DownsampleScale", gpu_downsample_scale); si.SetStringValue("GPU", "WireframeMode", GetGPUWireframeModeName(gpu_wireframe_mode)); si.SetBoolValue("GPU", "DisableInterlacing", gpu_disable_interlacing); - si.SetBoolValue("GPU", "ForceNTSCTimings", gpu_force_ntsc_timings); + si.SetStringValue("GPU", "ForceFrameTimings", GetForceFrameTimingsName(gpu_force_frame_timings)); si.SetBoolValue("GPU", "WidescreenHack", gpu_widescreen_hack); si.SetBoolValue("GPU", "ChromaSmoothing24Bit", display_24bit_chroma_smoothing); si.SetBoolValue("GPU", "PGXPEnable", gpu_pgxp_enable); @@ -744,7 +747,7 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages) g_settings.gpu_sprite_texture_filter = GPUTextureFilter::Nearest; g_settings.gpu_line_detect_mode = GPULineDetectMode::Disabled; g_settings.gpu_disable_interlacing = false; - g_settings.gpu_force_ntsc_timings = false; + g_settings.gpu_force_frame_timings = ForceFrameTimingsMode::Disabled; g_settings.gpu_widescreen_hack = false; g_settings.gpu_pgxp_enable = false; g_settings.display_24bit_chroma_smoothing = false; @@ -1507,7 +1510,22 @@ const char* Settings::GetDisplayRotationDisplayName(DisplayRotation rotation) { return Host::TranslateToCString("Settings", s_display_rotation_display_names[static_cast(rotation)]); } +static constexpr const std::array s_display_force_frame_timings_names = { + "Disabled", "NTSC", "PAL" +}; +std::optional Settings::ParseForceFrameTimings(const char* str) +{ + int index = 0; + for (const char* name : s_display_force_frame_timings_names) + { + if (StringUtil::Strcasecmp(name, str) == 0) + return static_cast(index); + index++; + } + + return std::nullopt; +} static constexpr const std::array s_display_scaling_names = { "Nearest", "NearestInteger", "BilinearSmooth", "BilinearSharp", "BilinearInteger", }; @@ -1543,6 +1561,16 @@ const char* Settings::GetDisplayScalingDisplayName(DisplayScalingMode mode) return Host::TranslateToCString("DisplayScalingMode", s_display_scaling_display_names[static_cast(mode)]); } +const char* Settings::GetForceFrameTimingsName(ForceFrameTimingsMode mode) +{ + return s_display_force_frame_timings_names[static_cast(mode)]; +} + +const char* Settings::GetForceFrameTimingsDisplayName(ForceFrameTimingsMode mode) +{ + return Host::TranslateToCString("ForceFrameTimingsMode", s_display_force_frame_timings_names[static_cast(mode)]); +} + static constexpr const std::array s_display_exclusive_fullscreen_mode_names = { "Automatic", "Disallowed", diff --git a/src/core/settings.h b/src/core/settings.h index 6e2a22ef4..e640920df 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -122,7 +122,6 @@ struct Settings bool gpu_force_round_texcoords : 1 = false; bool gpu_accurate_blending : 1 = false; bool gpu_disable_interlacing : 1 = true; - bool gpu_force_ntsc_timings : 1 = false; bool gpu_widescreen_hack : 1 = false; bool gpu_pgxp_enable : 1 = false; bool gpu_pgxp_culling : 1 = true; @@ -133,6 +132,7 @@ struct Settings bool gpu_pgxp_preserve_proj_fp : 1 = false; bool gpu_pgxp_depth_buffer : 1 = false; bool gpu_pgxp_disable_2d : 1 = false; + ForceFrameTimingsMode gpu_force_frame_timings = DEFAULT_FORCE_FRAME_TIMINGS_MODE; GPUTextureFilter gpu_texture_filter = DEFAULT_GPU_TEXTURE_FILTER; GPUTextureFilter gpu_sprite_texture_filter = DEFAULT_GPU_TEXTURE_FILTER; GPULineDetectMode gpu_line_detect_mode = DEFAULT_GPU_LINE_DETECT_MODE; @@ -434,6 +434,10 @@ struct Settings static const char* GetDisplayScalingName(DisplayScalingMode mode); static const char* GetDisplayScalingDisplayName(DisplayScalingMode mode); + static std::optional ParseForceFrameTimings(const char* str); + static const char* GetForceFrameTimingsName(ForceFrameTimingsMode mode); + static const char* GetForceFrameTimingsDisplayName(ForceFrameTimingsMode mode); + static std::optional ParseDisplayExclusiveFullscreenControl(const char* str); static const char* GetDisplayExclusiveFullscreenControlName(DisplayExclusiveFullscreenControl mode); static const char* GetDisplayExclusiveFullscreenControlDisplayName(DisplayExclusiveFullscreenControl mode); @@ -492,6 +496,7 @@ struct Settings static constexpr DisplayAlignment DEFAULT_DISPLAY_ALIGNMENT = DisplayAlignment::Center; static constexpr DisplayRotation DEFAULT_DISPLAY_ROTATION = DisplayRotation::Normal; static constexpr DisplayScalingMode DEFAULT_DISPLAY_SCALING = DisplayScalingMode::BilinearSmooth; + static constexpr ForceFrameTimingsMode DEFAULT_FORCE_FRAME_TIMINGS_MODE = ForceFrameTimingsMode::Disabled; static constexpr DisplayExclusiveFullscreenControl DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL = DisplayExclusiveFullscreenControl::Automatic; static constexpr DisplayScreenshotMode DEFAULT_DISPLAY_SCREENSHOT_MODE = DisplayScreenshotMode::ScreenResolution; diff --git a/src/core/system.cpp b/src/core/system.cpp index cf58ff18f..207f43a2e 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -4304,7 +4304,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings) g_settings.gpu_sprite_texture_filter != old_settings.gpu_sprite_texture_filter || g_settings.gpu_line_detect_mode != old_settings.gpu_line_detect_mode || g_settings.gpu_disable_interlacing != old_settings.gpu_disable_interlacing || - g_settings.gpu_force_ntsc_timings != old_settings.gpu_force_ntsc_timings || + g_settings.gpu_force_frame_timings != old_settings.gpu_force_frame_timings || g_settings.gpu_downsample_mode != old_settings.gpu_downsample_mode || g_settings.gpu_downsample_scale != old_settings.gpu_downsample_scale || g_settings.gpu_wireframe_mode != old_settings.gpu_wireframe_mode || @@ -4531,9 +4531,9 @@ void System::WarnAboutUnsafeSettings() TinyString(TRANSLATE_SV("System", "Instant")) : TinyString::from_format("{}x", g_settings.cdrom_seek_speedup))); } - if (g_settings.gpu_force_ntsc_timings) + if (g_settings.gpu_force_frame_timings != ForceFrameTimingsMode::Disabled) { - append(ICON_FA_TV, TRANSLATE_SV("System", "Force NTSC timings is enabled. Games may run at incorrect speeds.")); + append(ICON_FA_TV, TRANSLATE_SV("System", "Force frame timings is enabled. Games may run at incorrect speeds.")); } if (!g_settings.IsUsingSoftwareRenderer()) { diff --git a/src/core/types.h b/src/core/types.h index 29bef0c94..f703a5ec5 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -275,3 +275,12 @@ enum class SaveStateCompressionMode : u8 Count, }; + +enum class ForceFrameTimingsMode : u8 +{ + Disabled, + NTSC, + PAL, + + Count, +}; diff --git a/src/duckstation-qt/graphicssettingswidget.cpp b/src/duckstation-qt/graphicssettingswidget.cpp index f2e9861a9..5aad8acdb 100644 --- a/src/duckstation-qt/graphicssettingswidget.cpp +++ b/src/duckstation-qt/graphicssettingswidget.cpp @@ -74,6 +74,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayScaling, "Display", "Scaling", &Settings::ParseDisplayScaling, &Settings::GetDisplayScalingName, Settings::DEFAULT_DISPLAY_SCALING); + SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.forceFrameTimings, "GPU", "ForceFrameTimings", + &Settings::ParseForceFrameTimings, &Settings::GetForceFrameTimingsName, + Settings::DEFAULT_FORCE_FRAME_TIMINGS_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); @@ -81,8 +84,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpDepthBuffer, "GPU", "PGXPDepthBuffer", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.force43For24Bit, "Display", "Force4_3For24Bit", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.chromaSmoothingFor24Bit, "GPU", "ChromaSmoothing24Bit", false); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.forceNTSCTimings, "GPU", "ForceNTSCTimings", false); - + connect(m_ui.renderer, QOverload::of(&QComboBox::currentIndexChanged), this, &GraphicsSettingsWidget::updateRendererDependentOptions); connect(m_ui.textureFiltering, QOverload::of(&QComboBox::currentIndexChanged), this, @@ -106,8 +108,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* !m_dialog->hasGameTrait(GameDatabase::Trait::ForceInterlacing)); SettingWidgetBinder::SetAvailability(m_ui.widescreenHack, !m_dialog->hasGameTrait(GameDatabase::Trait::DisableWidescreen)); - SettingWidgetBinder::SetAvailability(m_ui.forceNTSCTimings, - !m_dialog->hasGameTrait(GameDatabase::Trait::DisableForceNTSCTimings)); + SettingWidgetBinder::SetAvailability(m_ui.forceFrameTimings, + !m_dialog->hasGameTrait(GameDatabase::Trait::DisableForceFrameTimings)); // Advanced Tab @@ -336,6 +338,12 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* dialog->registerWidgetHelp( 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.forceFrameTimings, tr("Force Frame Timings"), tr("Disabled"), + tr("Utilizes the chosen frame timing regardless of the active region. " + "This feature can be used to force PAL games to run at 60Hz and NTSC games to run at 50Hz. " + "For most games which have a speed tied to the framerate, this will result in the game running approximately 17% faster or slower. " + "For variable frame rate games, it may not affect the 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 " @@ -366,11 +374,6 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* "Forces the rendering and display of frames to progressive mode.
This removes the \"combing\" effect seen in " "480i games by rendering them in 480p. Usually safe to enable.
May not be compatible with all " "games.")); - dialog->registerWidgetHelp( - m_ui.forceNTSCTimings, tr("Force NTSC Timings"), tr("Unchecked"), - tr("Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at 60hz.
For most games " - "which have a speed tied to the framerate, this will result in the game running approximately 17% faster. " - "
For variable frame rate games, it may not affect the speed.")); // Advanced Tab @@ -655,6 +658,13 @@ void GraphicsSettingsWidget::setupAdditionalUi() QString::fromUtf8(Settings::GetDisplayScalingDisplayName(static_cast(i)))); } + for (u32 i = 0; i < static_cast(ForceFrameTimingsMode::Count); i++) + { + m_ui.forceFrameTimings->addItem( + QString::fromUtf8(Settings::GetForceFrameTimingsName(static_cast(i)))); + } + + // Advanced Tab for (u32 i = 0; i < static_cast(DisplayExclusiveFullscreenControl::Count); i++) diff --git a/src/duckstation-qt/graphicssettingswidget.ui b/src/duckstation-qt/graphicssettingswidget.ui index 4da2d9be4..ad06db1c0 100644 --- a/src/duckstation-qt/graphicssettingswidget.ui +++ b/src/duckstation-qt/graphicssettingswidget.ui @@ -201,7 +201,7 @@ - + @@ -217,13 +217,6 @@ - - - - Force NTSC Timings - - - @@ -274,6 +267,16 @@ + + + + Force Frame Timings: + + + + + +