CDROM: Add 'maximum' read speedup option

"Instant" seek is now renamed to Maximum as well, for consistency.
This commit is contained in:
Stenzek 2025-01-09 15:22:16 +10:00
parent 0ad0859e9d
commit 561397a53c
No known key found for this signature in database
6 changed files with 193 additions and 27 deletions

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// 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<u32>(ticks / g_settings.cdrom_seek_speedup, MIN_SEEK_TICKS);
ticks = std::max<u32>(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)
{

View File

@ -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<const char* const> options,
bool translate_options, std::span<const int> 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<const char* const> options, bool translate_options,
std::span<const int> 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<const int> values) {
for (size_t i = 0; i < values.size(); i++)
{
if (values[i] == value)
return static_cast<int>(i);
}
return -1;
};
DebugAssert(options.size() == values.size());
const bool game_settings = IsEditingGameSettings(bsi);
const std::optional<int> value =
bsi->GetOptionalIntValue(section, key, game_settings ? std::nullopt : std::optional<int>(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<size_t>(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<size_t>(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");

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// 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,

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "consolesettingswidget.h"
@ -14,6 +14,8 @@
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QPushButton>
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."));

View File

@ -207,6 +207,11 @@
<string>10x (20x Speed)</string>
</property>
</item>
<item>
<property name="text">
<string>Maximum</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
@ -221,11 +226,6 @@
<property name="currentIndex">
<number>1</number>
</property>
<item>
<property name="text">
<string>Infinite/Instantaneous</string>
</property>
</item>
<item>
<property name="text">
<string>None (Normal Speed)</string>
@ -276,6 +276,11 @@
<string>10x</string>
</property>
</item>
<item>
<property name="text">
<string>Maximum</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0" colspan="2">

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
@ -29,8 +29,10 @@
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QSlider>
#include <QtWidgets/QSpinBox>
#include <memory>
#include <optional>
#include <span>
#include <type_traits>
namespace SettingWidgetBinder {
@ -790,6 +792,57 @@ static void BindWidgetToIntSetting(SettingsInterface* sif, WidgetType* widget, s
}
}
template<typename WidgetType>
static void BindWidgetToIntSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key,
int default_value, std::span<const int> values)
{
using Accessor = SettingAccessor<WidgetType>;
static constexpr auto value_to_index = [](s32 value, const std::span<const int> values) {
for (size_t i = 0; i < values.size(); i++)
{
if (values[i] == value)
return static_cast<int>(i);
}
return -1;
};
const s32 value = Host::GetBaseIntSettingValue(section.c_str(), key.c_str(), static_cast<s32>(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<int> 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<typename WidgetType>
static inline void BindWidgetAndLabelToIntSetting(SettingsInterface* sif, WidgetType* widget, QLabel* label,
const QString& label_suffix, std::string section, std::string key,