CDROM: Add option to disable speedup on MDEC/FMVs

This commit is contained in:
Stenzek 2025-07-22 23:03:16 +10:00
parent 4f1af2f6eb
commit b6b1a5e33c
No known key found for this signature in database
11 changed files with 116 additions and 17 deletions

View File

@ -8,6 +8,7 @@
#include "fullscreen_ui.h"
#include "host.h"
#include "interrupt_controller.h"
#include "mdec.h"
#include "settings.h"
#include "spu.h"
#include "system.h"
@ -74,6 +75,9 @@ enum : u32
MINIMUM_INTERRUPT_DELAY = 1000,
INTERRUPT_DELAY_CYCLES = 500,
MISSED_INT1_DELAY_CYCLES = 5000, // See CheckForSectorBufferReadComplete().
SINGLE_SPEED_SECTORS_PER_SECOND = 75, // 1X speed is 75 sectors per second.
DOUBLE_SPEED_SECTORS_PER_SECOND = 150, // 2X speed is 150 sectors per second.
};
static constexpr u8 INTERRUPT_REGISTER_MASK = 0x1F;
@ -1488,7 +1492,25 @@ bool CDROM::HasPendingDiscEvent()
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);
// Use MDEC as a heuristic for games that don't use XA audio in FMVs. But this is opt-in for now.
return (!s_state.mode.cdda && !s_state.mode.xa_enable && s_state.mode.double_speed &&
(!g_settings.mdec_disable_cdrom_speedup || !MDEC::IsActive()));
}
void CDROM::DisableReadSpeedup()
{
if (s_state.drive_state != CDROM::DriveState::Reading || !CanUseReadSpeedup())
return;
// Can't test the interval directly because max speedup changes the downcount directly.
const TickCount expected_ticks = System::GetTicksPerSecond() / DOUBLE_SPEED_SECTORS_PER_SECOND;
const TickCount ticks_since_last_sector = s_state.drive_event.GetTicksSinceLastExecution();
const TickCount ticks_until_next_sector = s_state.drive_event.GetTicksUntilNextExecution();
const TickCount sector_ticks = ticks_since_last_sector + ticks_until_next_sector;
if (sector_ticks >= expected_ticks)
return;
s_state.drive_event.Schedule(expected_ticks - ticks_since_last_sector);
}
TickCount CDROM::GetAckDelayForCommand(Command command)
@ -1526,9 +1548,9 @@ TickCount CDROM::GetTicksForRead()
const TickCount tps = System::GetTicksPerSecond();
if (g_settings.cdrom_read_speedup > 1 && CanUseReadSpeedup())
return tps / (150 * g_settings.cdrom_read_speedup);
return tps / (DOUBLE_SPEED_SECTORS_PER_SECOND * g_settings.cdrom_read_speedup);
return s_state.mode.double_speed ? (tps / 150) : (tps / 75);
return s_state.mode.double_speed ? (tps / DOUBLE_SPEED_SECTORS_PER_SECOND) : (tps / SINGLE_SPEED_SECTORS_PER_SECOND);
}
u32 CDROM::GetSectorsPerTrack(CDImage::LBA lba)
@ -4276,6 +4298,13 @@ void CDROM::DrawDebugWindow(float scale)
ImGui::TextColored(active_color, "Drive: %s (%d ticks remaining)",
s_drive_state_names[static_cast<u8>(s_state.drive_state)],
s_state.drive_event.IsActive() ? s_state.drive_event.GetTicksUntilNextExecution() : 0);
if (g_settings.cdrom_read_speedup != 1 && !CanUseReadSpeedup())
{
ImGui::SameLine();
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), 400.0f));
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "SPEEDUP BLOCKED");
}
}
ImGui::Text("Interrupt Enable Register: 0x%02X", s_state.interrupt_enable_register);

View File

@ -47,6 +47,7 @@ void DMARead(u32* words, u32 word_count);
void DrawDebugWindow(float scale);
void SetReadaheadSectors(u32 readahead_sectors);
void DisableReadSpeedup();
/// Reads a frame from the audio FIFO, used by the SPU.
std::tuple<s16, s16> GetAudioFrame();

View File

@ -6663,6 +6663,11 @@ void FullscreenUI::DrawAdvancedSettingsPage()
"MaxReadSpeedupCycles", Settings::DEFAULT_CDROM_MAX_READ_SPEEDUP_CYCLES, 1, 1000000,
FSUI_CSTR("%d cycles"));
DrawToggleSetting(
bsi, FSUI_VSTR("Disable Speedup on MDEC"),
FSUI_VSTR("Tries to detect FMVs and disable read speedup during games that don't use XA streaming audio."), "CDROM",
"DisableSpeedupOnMDEC", false);
DrawToggleSetting(bsi, FSUI_VSTR("Enable Region Check"),
FSUI_VSTR("Simulates the region check present in original, unmodified consoles."), "CDROM",
"RegionCheck", false);
@ -9540,6 +9545,7 @@ TRANSLATE_NOOP("FullscreenUI", "Determines whether a prompt will be displayed to
TRANSLATE_NOOP("FullscreenUI", "Determines which algorithm is used to convert interlaced frames to progressive for display on your system.");
TRANSLATE_NOOP("FullscreenUI", "Device Settings");
TRANSLATE_NOOP("FullscreenUI", "Disable Mailbox Presentation");
TRANSLATE_NOOP("FullscreenUI", "Disable Speedup on MDEC");
TRANSLATE_NOOP("FullscreenUI", "Disable Subdirectory Scanning");
TRANSLATE_NOOP("FullscreenUI", "Disable on 2D Polygons");
TRANSLATE_NOOP("FullscreenUI", "Disabled");
@ -10031,6 +10037,7 @@ TRANSLATE_NOOP("FullscreenUI", "Toggle Fullscreen");
TRANSLATE_NOOP("FullscreenUI", "Toggle every %d frames");
TRANSLATE_NOOP("FullscreenUI", "Toggles the macro when the button is pressed, instead of held.");
TRANSLATE_NOOP("FullscreenUI", "Top: ");
TRANSLATE_NOOP("FullscreenUI", "Tries to detect FMVs and disable read speedup during games that don't use XA streaming audio.");
TRANSLATE_NOOP("FullscreenUI", "Trigger");
TRANSLATE_NOOP("FullscreenUI", "Turbo Speed");
TRANSLATE_NOOP("FullscreenUI", "Type");

View File

@ -40,7 +40,7 @@ namespace GameDatabase {
enum : u32
{
GAME_DATABASE_CACHE_SIGNATURE = 0x45434C48,
GAME_DATABASE_CACHE_VERSION = 26,
GAME_DATABASE_CACHE_VERSION = 27,
};
static const Entry* GetEntryForId(std::string_view code);
@ -87,6 +87,7 @@ static constexpr const std::array s_trait_names = {
"DisableMultitap",
"DisableCDROMReadSpeedup",
"DisableCDROMSeekSpeedup",
"DisableCDROMSpeedupOnMDEC",
"DisableTrueColor",
"DisableFullTrueColor",
"DisableUpscaling",
@ -122,6 +123,7 @@ static constexpr const std::array s_trait_display_names = {
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Multitap", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable CD-ROM Read Speedup", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable CD-ROM Seek Speedup", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable CD-ROM Speedup on MDEC", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable True Color", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Full True Color", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Upscaling", "GameDatabase::Trait"),
@ -496,6 +498,19 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
settings.cdrom_seek_speedup = 1;
}
if (HasTrait(Trait::DisableCDROMSpeedupOnMDEC))
{
WARNING_LOG("Disabling CD-ROM speedup on MDEC.");
settings.mdec_disable_cdrom_speedup = true;
}
else if (settings.mdec_disable_cdrom_speedup && settings.cdrom_read_speedup != 1)
{
Host::AddIconOSDWarning(
"GameDBDisableCDROMSpeedupUnnecessary", ICON_EMOJI_WARNING,
TRANSLATE_STR("GameDatabase", "Disable CD-ROM speedup on MDEC is enabled, but it is not required for this game."),
Host::OSD_WARNING_DURATION);
}
if (display_crop_mode.has_value())
{
if (display_osd_messages && settings.display_crop_mode != display_crop_mode.value())

View File

@ -45,6 +45,7 @@ enum class Trait : u32
DisableMultitap,
DisableCDROMReadSpeedup,
DisableCDROMSeekSpeedup,
DisableCDROMSpeedupOnMDEC,
DisableTrueColor,
DisableFullTrueColor,
DisableUpscaling,

View File

@ -1,7 +1,8 @@
// 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 "mdec.h"
#include "cdrom.h"
#include "cpu_core.h"
#include "dma.h"
#include "system.h"
@ -29,6 +30,7 @@ static constexpr u32 DATA_IN_FIFO_SIZE = 1024;
static constexpr u32 DATA_OUT_FIFO_SIZE = 768;
static constexpr u32 NUM_BLOCKS = 6;
static constexpr TickCount TICKS_PER_BLOCK = 448;
static constexpr u8 ACTIVE_FRAME_COUNT = 30;
enum DataOutputDepth : u8
{
@ -131,12 +133,13 @@ struct MDECState
StatusRegister status = {};
bool enable_dma_in = false;
bool enable_dma_out = false;
State state = State::Idle;
u8 active_frame_count = 0;
u32 remaining_halfwords = 0;
// Even though the DMA is in words, we access the FIFO as halfwords.
InlineFIFOQueue<u16, DATA_IN_FIFO_SIZE / sizeof(u16)> data_in_fifo;
InlineFIFOQueue<u32, DATA_OUT_FIFO_SIZE / sizeof(u32)> data_out_fifo;
State state = State::Idle;
u32 remaining_halfwords = 0;
std::array<u8, 64> iq_uv{};
std::array<u8, 64> iq_y{};
@ -152,7 +155,9 @@ struct MDECState
alignas(VECTOR_ALIGNMENT) std::array<u32, 256> block_rgb{};
TimingEvent block_copy_out_event{"MDEC Block Copy Out", 1, 1, &MDEC::CopyOutBlock, nullptr};
#if defined(_DEBUG) || defined(_DEVEL)
u32 total_blocks_decoded = 0;
#endif
};
} // namespace
@ -161,7 +166,10 @@ ALIGN_TO_CACHE_LINE static MDECState s_state;
void MDEC::Initialize()
{
#if defined(_DEBUG) || defined(_DEVEL)
s_state.total_blocks_decoded = 0;
#endif
s_state.active_frame_count = 0;
Reset();
}
@ -172,6 +180,7 @@ void MDEC::Shutdown()
void MDEC::Reset()
{
s_state.active_frame_count = 0;
s_state.block_copy_out_event.Deactivate();
SoftReset();
}
@ -208,11 +217,24 @@ bool MDEC::DoState(StateWrapper& sw)
bool block_copy_out_pending = HasPendingBlockCopyOut();
sw.Do(&block_copy_out_pending);
if (sw.IsReading())
{
s_state.block_copy_out_event.SetState(block_copy_out_pending);
s_state.active_frame_count = 0;
}
return !sw.HasError();
}
bool MDEC::IsActive()
{
return (s_state.active_frame_count > 0);
}
void MDEC::EndFrame()
{
s_state.active_frame_count = (s_state.active_frame_count > 0) ? (s_state.active_frame_count - 1) : 0;
}
u32 MDEC::ReadRegister(u32 offset)
{
switch (offset)
@ -226,11 +248,11 @@ u32 MDEC::ReadRegister(u32 offset)
return s_state.status.bits;
}
[[unlikely]] default:
{
ERROR_LOG("Unknown MDEC register read: 0x{:08X}", offset);
return UINT32_C(0xFFFFFFFF);
}
[[unlikely]] default:
{
ERROR_LOG("Unknown MDEC register read: 0x{:08X}", offset);
return UINT32_C(0xFFFFFFFF);
}
}
}
@ -258,11 +280,11 @@ void MDEC::WriteRegister(u32 offset, u32 value)
return;
}
[[unlikely]] default:
{
ERROR_LOG("Unknown MDEC register write: 0x{:08X} <- 0x{:08X}", offset, value);
return;
}
[[unlikely]] default:
{
ERROR_LOG("Unknown MDEC register write: 0x{:08X} <- 0x{:08X}", offset, value);
return;
}
}
}
@ -381,6 +403,12 @@ void MDEC::WriteCommandRegister(u32 value)
void MDEC::Execute()
{
if (std::exchange(s_state.active_frame_count, ACTIVE_FRAME_COUNT) == 0)
{
if (g_settings.mdec_disable_cdrom_speedup)
CDROM::DisableReadSpeedup();
}
for (;;)
{
switch (s_state.state)
@ -544,7 +572,9 @@ bool MDEC::DecodeMonoMacroblock()
ScheduleBlockCopyOut(TICKS_PER_BLOCK * 6);
#if defined(_DEBUG) || defined(_DEVEL)
s_state.total_blocks_decoded++;
#endif
return true;
}
@ -599,7 +629,9 @@ bool MDEC::DecodeColoredMacroblock()
YUVToRGB_New(8, 8, s_state.blocks[0], s_state.blocks[1], s_state.blocks[5]);
}
#if defined(_DEBUG) || defined(_DEVEL)
s_state.total_blocks_decoded += 4;
#endif
ScheduleBlockCopyOut(TICKS_PER_BLOCK * 6);
return true;
@ -1128,7 +1160,9 @@ void MDEC::DrawDebugStateWindow(float scale)
static constexpr std::array<const char*, 4> output_depths = {{"4-bit", "8-bit", "24-bit", "15-bit"}};
static constexpr std::array<const char*, 7> block_names = {{"Crblk", "Cbblk", "Y1", "Y2", "Y3", "Y4", "Output"}};
#if defined(_DEBUG) || defined(_DEVEL)
ImGui::Text("Blocks Decoded: %u", s_state.total_blocks_decoded);
#endif
ImGui::Text("Data-In FIFO Size: %u (%u bytes)", s_state.data_in_fifo.GetSize(), s_state.data_in_fifo.GetSize() * 4);
ImGui::Text("Data-Out FIFO Size: %u (%u bytes)", s_state.data_out_fifo.GetSize(),
s_state.data_out_fifo.GetSize() * 4);

View File

@ -14,6 +14,9 @@ void Shutdown();
void Reset();
bool DoState(StateWrapper& sw);
bool IsActive();
void EndFrame();
// I/O
u32 ReadRegister(u32 offset);
void WriteRegister(u32 offset, u32 value);

View File

@ -371,6 +371,7 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
std::max(si.GetUIntValue("CDROM", "MaxSeekSpeedupCycles", DEFAULT_CDROM_MAX_SEEK_SPEEDUP_CYCLES), 1u);
cdrom_max_read_speedup_cycles =
std::max(si.GetUIntValue("CDROM", "MaxReadSpeedupCycles", DEFAULT_CDROM_MAX_READ_SPEEDUP_CYCLES), 1u);
mdec_disable_cdrom_speedup = si.GetBoolValue("CDROM", "DisableSpeedupOnMDEC", false);
audio_backend =
AudioStream::ParseBackendName(
@ -686,6 +687,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
si.SetUIntValue("CDROM", "SeekSpeedup", cdrom_seek_speedup);
si.SetUIntValue("CDROM", "MaxReadSpeedupCycles", cdrom_max_seek_speedup_cycles);
si.SetUIntValue("CDROM", "MaxSeekSpeedupCycles", cdrom_max_read_speedup_cycles);
si.SetBoolValue("CDROM", "DisableSpeedupOnMDEC", mdec_disable_cdrom_speedup);
si.SetStringValue("Audio", "Backend", AudioStream::GetBackendName(audio_backend));
si.SetStringValue("Audio", "Driver", audio_driver.c_str());

View File

@ -329,6 +329,7 @@ struct Settings : public GPUSettings
bool audio_output_muted : 1 = false;
bool use_old_mdec_routines : 1 = false;
bool mdec_disable_cdrom_speedup : 1 = false;
bool pcdrv_enable : 1 = false;
bool export_shared_memory : 1 = false;

View File

@ -2089,6 +2089,8 @@ void System::FrameDone()
// TODO: when running ahead, we can skip this (and the flush above)
if (!IsReplayingGPUDump()) [[likely]]
{
MDEC::EndFrame();
SPU::GeneratePendingSamples();
Cheats::ApplyFrameEndCodes();

View File

@ -310,6 +310,8 @@ void AdvancedSettingsWidget::addTweakOptions()
addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Max Seek Speedup Cycles"), "CDROM",
"MaxSeekSpeedupCycles", 1, 1000000, Settings::DEFAULT_CDROM_MAX_SEEK_SPEEDUP_CYCLES,
tr(" cycles"));
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Disable Speedup on MDEC"), "CDROM",
"DisableSpeedupOnMDEC", false);
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Region Check"), "CDROM", "RegionCheck", false);
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM SubQ Skew"), "CDROM", "SubQSkew", false);
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Allow Booting Without SBI File"), "CDROM",
@ -359,6 +361,7 @@ void AdvancedSettingsWidget::onResetToDefaultClicked()
Settings::DEFAULT_CDROM_MAX_READ_SPEEDUP_CYCLES); // CD-ROM Max Speedup Read Cycles
setIntRangeTweakOption(m_ui.tweakOptionTable, i++,
Settings::DEFAULT_CDROM_MAX_SEEK_SPEEDUP_CYCLES); // CD-ROM Max Speedup Seek Cycles
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM Disable Speedup on MDEC
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM Region Check
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM SubQ Skew
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Allow booting without SBI file
@ -395,6 +398,7 @@ void AdvancedSettingsWidget::onResetToDefaultClicked()
sif->DeleteValue("CDROM", "ReadaheadSectors");
sif->DeleteValue("CDROM", "MaxReadSpeedupCycles");
sif->DeleteValue("CDROM", "MaxSeekSpeedupCycles");
sif->DeleteValue("CDROM", "DisableSpeedupOnMDEC");
sif->DeleteValue("CDROM", "RegionCheck");
sif->DeleteValue("CDROM", "SubQSkew");
sif->DeleteValue("CDROM", "AllowBootingWithoutSBIFile");