From 561397a53cbae0567229337cbc215b17091a8e8d Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 9 Jan 2025 15:22:16 +1000 Subject: [PATCH] CDROM: Add 'maximum' read speedup option "Instant" seek is now renamed to Maximum as well, for consistency. --- src/core/cdrom.cpp | 34 ++++++-- src/core/fullscreen_ui.cpp | 89 ++++++++++++++++++-- src/core/system.cpp | 17 ++-- src/duckstation-qt/consolesettingswidget.cpp | 10 ++- src/duckstation-qt/consolesettingswidget.ui | 15 ++-- src/duckstation-qt/settingwidgetbinder.h | 55 +++++++++++- 6 files changed, 193 insertions(+), 27 deletions(-) diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 06a1b0bad..d7cbba44d 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "cdrom.h" @@ -75,7 +75,7 @@ enum : u32 static constexpr u8 INTERRUPT_REGISTER_MASK = 0x1F; -static constexpr TickCount MIN_SEEK_TICKS = 30000; +static constexpr TickCount INSTANT_SEEK_OR_READ_TICKS = 30000; enum class Interrupt : u8 { @@ -311,6 +311,7 @@ static void SendAsyncErrorResponse(u8 stat_bits = STAT_ERROR, u8 reason = 0x80); static void UpdateStatusRegister(); static void UpdateInterruptRequest(); static bool HasPendingDiscEvent(); +static bool CanUseReadSpeedup(); static TickCount GetAckDelayForCommand(Command command); static TickCount GetTicksForSpinUp(); @@ -1469,6 +1470,12 @@ bool CDROM::HasPendingDiscEvent() return (s_state.drive_event.IsActive() && s_state.drive_event.GetTicksUntilNextExecution() <= 0); } +bool CDROM::CanUseReadSpeedup() +{ + // Only use read speedup in 2X mode and when we're not playing/filtering XA. + return (!s_state.mode.cdda && !s_state.mode.xa_enable && s_state.mode.double_speed); +} + TickCount CDROM::GetAckDelayForCommand(Command command) { if (command == Command::Init) @@ -1503,7 +1510,7 @@ TickCount CDROM::GetTicksForRead() { const TickCount tps = System::GetTicksPerSecond(); - if (g_settings.cdrom_read_speedup > 1 && !s_state.mode.cdda && !s_state.mode.xa_enable && s_state.mode.double_speed) + if (g_settings.cdrom_read_speedup > 1 && CanUseReadSpeedup()) return tps / (150 * g_settings.cdrom_read_speedup); return s_state.mode.double_speed ? (tps / 150) : (tps / 75); @@ -1561,7 +1568,7 @@ u32 CDROM::GetSectorsPerTrack(CDImage::LBA lba) TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba, bool ignore_speed_change) { if (g_settings.cdrom_seek_speedup == 0) - return System::ScaleTicksToOverclock(MIN_SEEK_TICKS); + return System::ScaleTicksToOverclock(INSTANT_SEEK_OR_READ_TICKS); u32 ticks = 0; @@ -1631,7 +1638,7 @@ TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba, bool ignore_speed_change) } if (g_settings.cdrom_seek_speedup > 1) - ticks = std::max(ticks / g_settings.cdrom_seek_speedup, MIN_SEEK_TICKS); + ticks = std::max(ticks / g_settings.cdrom_seek_speedup, INSTANT_SEEK_OR_READ_TICKS); if (s_state.drive_state == DriveState::ChangingSpeedOrTOCRead && !ignore_speed_change) { @@ -1659,6 +1666,9 @@ TickCount CDROM::GetTicksForPause() if (!IsReadingOrPlaying()) return 7000; + if (g_settings.cdrom_read_speedup == 0 && CanUseReadSpeedup()) + return System::ScaleTicksToOverclock(INSTANT_SEEK_OR_READ_TICKS); + const u32 sectors_per_track = GetSectorsPerTrack(s_state.current_lba); const TickCount ticks_per_read = GetTicksForRead(); @@ -2803,7 +2813,7 @@ void CDROM::BeginSeeking(bool logical, bool read_after_seek, bool play_after_see { DEV_COLOR_LOG(StrongCyan, "Completing seek instantly due to not passing target {}.", LBAToMSFString(seek_lba - SUBQ_SECTOR_SKEW)); - seek_time = MIN_SEEK_TICKS; + seek_time = INSTANT_SEEK_OR_READ_TICKS; } else { @@ -3884,6 +3894,18 @@ void CDROM::CheckForSectorBufferReadComplete() s_state.request_register.BFRD = (s_state.request_register.BFRD && sb.position < sb.size); s_state.status.DRQSTS = s_state.request_register.BFRD; + // Maximum/immediate read speedup. Wait for the data portion of the sector to be read. + if (s_state.drive_state == DriveState::Reading && + sb.position >= + (s_state.mode.read_raw_sector ? (MODE2_HEADER_SIZE + DATA_SECTOR_OUTPUT_SIZE) : DATA_SECTOR_OUTPUT_SIZE) && + CanUseReadSpeedup() && g_settings.cdrom_read_speedup == 0) + { + const TickCount remaining_time = s_state.drive_event.GetTicksUntilNextExecution(); + const TickCount instant_ticks = System::ScaleTicksToOverclock(INSTANT_SEEK_OR_READ_TICKS); + if (remaining_time > instant_ticks) + s_state.drive_event.Schedule(instant_ticks); + } + // Buffer complete? if (sb.position >= sb.size) { diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 4f9bbdd89..4a501c1dc 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -311,6 +311,12 @@ static void DrawIntListSetting(SettingsInterface* bsi, const char* title, const float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont, const char* tr_context = TR_CONTEXT); +static void DrawIntListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, + const char* key, int default_value, std::span options, + bool translate_options, std::span values, bool enabled = true, + float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, + ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont, + const char* tr_context = TR_CONTEXT); static void DrawIntRangeSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key, int default_value, int min_value, int max_value, const char* format = "%d", bool enabled = true, @@ -2064,6 +2070,75 @@ void FullscreenUI::DrawIntListSetting(SettingsInterface* bsi, const char* title, } } +void FullscreenUI::DrawIntListSetting(SettingsInterface* bsi, const char* title, const char* summary, + const char* section, const char* key, int default_value, + std::span options, bool translate_options, + std::span values, bool enabled, float height, ImFont* font, + ImFont* summary_font, const char* tr_context) +{ + static constexpr auto value_to_index = [](s32 value, const std::span values) { + for (size_t i = 0; i < values.size(); i++) + { + if (values[i] == value) + return static_cast(i); + } + + return -1; + }; + + DebugAssert(options.size() == values.size()); + + const bool game_settings = IsEditingGameSettings(bsi); + + const std::optional value = + bsi->GetOptionalIntValue(section, key, game_settings ? std::nullopt : std::optional(default_value)); + const int index = value.has_value() ? value_to_index(value.value(), values) : -1; + const char* value_text = + (value.has_value()) ? + ((index < 0 || static_cast(index) >= options.size()) ? + FSUI_CSTR("Unknown") : + (translate_options ? Host::TranslateToCString(tr_context, options[index]) : options[index])) : + FSUI_CSTR("Use Global Setting"); + + if (MenuButtonWithValue(title, summary, value_text, enabled, height, font, summary_font)) + { + ImGuiFullscreen::ChoiceDialogOptions cd_options; + cd_options.reserve(options.size() + 1); + if (game_settings) + cd_options.emplace_back(FSUI_STR("Use Global Setting"), !value.has_value()); + for (size_t i = 0; i < options.size(); i++) + { + cd_options.emplace_back(translate_options ? Host::TranslateToString(tr_context, options[i]) : + std::string(options[i]), + (i == static_cast(index))); + } + OpenChoiceDialog(title, false, std::move(cd_options), + [game_settings, section = TinyString(section), key = TinyString(key), + values](s32 index, const std::string& title, bool checked) { + if (index >= 0) + { + auto lock = Host::GetSettingsLock(); + SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); + if (game_settings) + { + if (index == 0) + bsi->DeleteValue(section, key); + else + bsi->SetIntValue(section, key, values[index - 1]); + } + else + { + bsi->SetIntValue(section, key, values[index]); + } + + SetSettingsChanged(bsi); + } + + CloseChoiceDialog(); + }); + } +} + void FullscreenUI::DrawIntRangeSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key, int default_value, int min_value, int max_value, const char* format, bool enabled, float height, ImFont* font, @@ -3468,11 +3543,10 @@ void FullscreenUI::DrawConsoleSettingsPage() FSUI_NSTR("None (Double Speed)"), FSUI_NSTR("2x (Quad Speed)"), FSUI_NSTR("3x (6x Speed)"), FSUI_NSTR("4x (8x Speed)"), FSUI_NSTR("5x (10x Speed)"), FSUI_NSTR("6x (12x Speed)"), FSUI_NSTR("7x (14x Speed)"), FSUI_NSTR("8x (16x Speed)"), FSUI_NSTR("9x (18x Speed)"), - FSUI_NSTR("10x (20x Speed)"), + FSUI_NSTR("10x (20x Speed)"), FSUI_NSTR("Maximum"), }; static constexpr const std::array cdrom_seek_speeds = { - FSUI_NSTR("Infinite/Instantaneous"), FSUI_NSTR("None (Normal Speed)"), FSUI_NSTR("2x"), FSUI_NSTR("3x"), @@ -3483,8 +3557,11 @@ void FullscreenUI::DrawConsoleSettingsPage() FSUI_NSTR("8x"), FSUI_NSTR("9x"), FSUI_NSTR("10x"), + FSUI_NSTR("Maximum"), }; + static constexpr std::array cdrom_read_seek_speed_values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0}; + SettingsInterface* bsi = GetEditingSettingsInterface(); BeginMenuButtons(); @@ -3541,12 +3618,12 @@ void FullscreenUI::DrawConsoleSettingsPage() bsi, FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Read Speedup"), FSUI_CSTR( "Speeds up CD-ROM reads by the specified factor. May improve loading speeds in some games, and break others."), - "CDROM", "ReadSpeedup", 1, cdrom_read_speeds.data(), cdrom_read_speeds.size(), true, 1); + "CDROM", "ReadSpeedup", 1, cdrom_read_speeds, true, cdrom_read_seek_speed_values); DrawIntListSetting( bsi, FSUI_ICONSTR(ICON_FA_SEARCH, "Seek Speedup"), FSUI_CSTR( "Speeds up CD-ROM seeks by the specified factor. May improve loading speeds in some games, and break others."), - "CDROM", "SeekSpeedup", 1, cdrom_seek_speeds.data(), cdrom_seek_speeds.size(), true); + "CDROM", "SeekSpeedup", 1, cdrom_seek_speeds, true, cdrom_read_seek_speed_values); DrawIntRangeSetting( bsi, FSUI_ICONSTR(ICON_FA_FAST_FORWARD, "Readahead Sectors"), @@ -8122,6 +8199,7 @@ TRANSLATE_NOOP("FullscreenUI", "Disabled"); TRANSLATE_NOOP("FullscreenUI", "Disables dithering and uses the full 8 bits per channel of color information."); TRANSLATE_NOOP("FullscreenUI", "Disc {} | {}"); TRANSLATE_NOOP("FullscreenUI", "Discord Server"); +TRANSLATE_NOOP("FullscreenUI", "Displays DualShock/DualSense button icons in the footer and input binding, instead of Xbox buttons."); TRANSLATE_NOOP("FullscreenUI", "Displays popup messages on events such as achievement unlocks and leaderboard submissions."); TRANSLATE_NOOP("FullscreenUI", "Displays popup messages when starting, submitting, or failing a leaderboard challenge."); TRANSLATE_NOOP("FullscreenUI", "Double-Click Toggles Fullscreen"); @@ -8244,7 +8322,6 @@ TRANSLATE_NOOP("FullscreenUI", "Identifies any new files added to the game direc TRANSLATE_NOOP("FullscreenUI", "If not enabled, the current post processing chain will be ignored."); TRANSLATE_NOOP("FullscreenUI", "Increases the field of view from 4:3 to the chosen display aspect ratio in 3D games."); TRANSLATE_NOOP("FullscreenUI", "Increases the precision of polygon culling, reducing the number of holes in geometry."); -TRANSLATE_NOOP("FullscreenUI", "Infinite/Instantaneous"); TRANSLATE_NOOP("FullscreenUI", "Inhibit Screensaver"); TRANSLATE_NOOP("FullscreenUI", "Input Sources"); TRANSLATE_NOOP("FullscreenUI", "Input profile '{}' loaded."); @@ -8293,6 +8370,7 @@ TRANSLATE_NOOP("FullscreenUI", "Logs messages to the debug console where support TRANSLATE_NOOP("FullscreenUI", "Logs out of RetroAchievements."); TRANSLATE_NOOP("FullscreenUI", "Macro Button {}"); TRANSLATE_NOOP("FullscreenUI", "Makes games run closer to their console framerate, at a small cost to performance."); +TRANSLATE_NOOP("FullscreenUI", "Maximum"); TRANSLATE_NOOP("FullscreenUI", "Memory Card Busy"); TRANSLATE_NOOP("FullscreenUI", "Memory Card Directory"); TRANSLATE_NOOP("FullscreenUI", "Memory Card Port {}"); @@ -8567,6 +8645,7 @@ TRANSLATE_NOOP("FullscreenUI", "Unknown File Size"); TRANSLATE_NOOP("FullscreenUI", "Unlimited"); TRANSLATE_NOOP("FullscreenUI", "Use Blit Swap Chain"); TRANSLATE_NOOP("FullscreenUI", "Use Debug GPU Device"); +TRANSLATE_NOOP("FullscreenUI", "Use DualShock/DualSense Button Icons"); TRANSLATE_NOOP("FullscreenUI", "Use Global Setting"); TRANSLATE_NOOP("FullscreenUI", "Use Light Theme"); TRANSLATE_NOOP("FullscreenUI", "Use Old MDEC Routines"); diff --git a/src/core/system.cpp b/src/core/system.cpp index b46573ed4..0fb21f373 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "system.h" @@ -4722,18 +4722,21 @@ void System::WarnAboutUnsafeSettings() g_settings.GetCPUOverclockPercent(), g_settings.cpu_overclock_numerator, g_settings.cpu_overclock_denominator); } - if (g_settings.cdrom_read_speedup > 1) + if (g_settings.cdrom_read_speedup != 1) { - append_format( - ICON_EMOJI_WARNING, - TRANSLATE_FS("System", "CD-ROM read speedup set to {}x (effective speed {}x). This may crash games."), - g_settings.cdrom_read_speedup, g_settings.cdrom_read_speedup * 2); + TinyString speed; + if (g_settings.cdrom_read_speedup == 0) + speed = TRANSLATE_SV("System", "Maximum"); + else + speed.format("{}x", g_settings.cdrom_read_speedup); + append_format(ICON_EMOJI_WARNING, + TRANSLATE_FS("System", "CD-ROM read speedup set to {}. This may crash games."), speed); } if (g_settings.cdrom_seek_speedup != 1) { TinyString speed; if (g_settings.cdrom_seek_speedup == 0) - speed = TRANSLATE_SV("System", "Instant"); + speed = TRANSLATE_SV("System", "Maximum"); else speed.format("{}x", g_settings.cdrom_seek_speedup); append_format(ICON_EMOJI_WARNING, diff --git a/src/duckstation-qt/consolesettingswidget.cpp b/src/duckstation-qt/consolesettingswidget.cpp index 432b00006..171f1480b 100644 --- a/src/duckstation-qt/consolesettingswidget.cpp +++ b/src/duckstation-qt/consolesettingswidget.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "consolesettingswidget.h" @@ -14,6 +14,8 @@ #include #include +static constexpr const int CDROM_SPEEDUP_VALUES[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0}; + ConsoleSettingsWidget::ConsoleSettingsWidget(SettingsWindow* dialog, QWidget* parent) : QWidget(parent), m_dialog(dialog) { @@ -64,8 +66,10 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(SettingsWindow* dialog, QWidget* pa m_ui.cdromIgnoreDriveSubcode->setEnabled(false); } - SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cdromSeekSpeedup, "CDROM", "SeekSpeedup", 1); - SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cdromReadSpeedup, "CDROM", "ReadSpeedup", 1, 1); + SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cdromSeekSpeedup, "CDROM", "SeekSpeedup", 1, + CDROM_SPEEDUP_VALUES); + SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cdromReadSpeedup, "CDROM", "ReadSpeedup", 1, + CDROM_SPEEDUP_VALUES); dialog->registerWidgetHelp(m_ui.region, tr("Region"), tr("Auto-Detect"), tr("Determines the emulated hardware type.")); diff --git a/src/duckstation-qt/consolesettingswidget.ui b/src/duckstation-qt/consolesettingswidget.ui index 18c54b282..496e5ab26 100644 --- a/src/duckstation-qt/consolesettingswidget.ui +++ b/src/duckstation-qt/consolesettingswidget.ui @@ -207,6 +207,11 @@ 10x (20x Speed) + + + Maximum + + @@ -221,11 +226,6 @@ 1 - - - Infinite/Instantaneous - - None (Normal Speed) @@ -276,6 +276,11 @@ 10x + + + Maximum + + diff --git a/src/duckstation-qt/settingwidgetbinder.h b/src/duckstation-qt/settingwidgetbinder.h index 5989dc228..30e94d3f6 100644 --- a/src/duckstation-qt/settingwidgetbinder.h +++ b/src/duckstation-qt/settingwidgetbinder.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #pragma once @@ -29,8 +29,10 @@ #include #include #include + #include #include +#include #include namespace SettingWidgetBinder { @@ -790,6 +792,57 @@ static void BindWidgetToIntSetting(SettingsInterface* sif, WidgetType* widget, s } } +template +static void BindWidgetToIntSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, + int default_value, std::span values) +{ + using Accessor = SettingAccessor; + + static constexpr auto value_to_index = [](s32 value, const std::span values) { + for (size_t i = 0; i < values.size(); i++) + { + if (values[i] == value) + return static_cast(i); + } + + return -1; + }; + + const s32 value = Host::GetBaseIntSettingValue(section.c_str(), key.c_str(), static_cast(default_value)); + + if (sif) + { + Accessor::makeNullableInt(widget, value); + + int sif_value; + if (sif->GetIntValue(section.c_str(), key.c_str(), &sif_value)) + Accessor::setNullableIntValue(widget, value_to_index(sif_value, values)); + else + Accessor::setNullableIntValue(widget, std::nullopt); + + Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key), values]() { + if (std::optional new_value = Accessor::getNullableIntValue(widget); new_value.has_value()) + sif->SetIntValue(section.c_str(), key.c_str(), values[new_value.value()]); + else + sif->DeleteValue(section.c_str(), key.c_str()); + + QtHost::SaveGameSettings(sif, true); + g_emu_thread->reloadGameSettings(); + }); + } + else + { + Accessor::setIntValue(widget, value_to_index(value, values)); + + Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key), values]() { + const int new_value = Accessor::getIntValue(widget); + Host::SetBaseIntSettingValue(section.c_str(), key.c_str(), values[new_value]); + Host::CommitBaseSettingChanges(); + g_emu_thread->applySettings(); + }); + } +} + template static inline void BindWidgetAndLabelToIntSetting(SettingsInterface* sif, WidgetType* widget, QLabel* label, const QString& label_suffix, std::string section, std::string key,