From b6b1a5e33cef099721804f31349e774c6d626055 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 22 Jul 2025 23:03:16 +1000 Subject: [PATCH] CDROM: Add option to disable speedup on MDEC/FMVs --- src/core/cdrom.cpp | 35 ++++++++++- src/core/cdrom.h | 1 + src/core/fullscreen_ui.cpp | 7 +++ src/core/game_database.cpp | 17 +++++- src/core/game_database.h | 1 + src/core/mdec.cpp | 60 +++++++++++++++---- src/core/mdec.h | 3 + src/core/settings.cpp | 2 + src/core/settings.h | 1 + src/core/system.cpp | 2 + src/duckstation-qt/advancedsettingswidget.cpp | 4 ++ 11 files changed, 116 insertions(+), 17 deletions(-) diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 9db1eb892..a75267312 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -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(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); diff --git a/src/core/cdrom.h b/src/core/cdrom.h index 23d83a02e..944d47c5a 100644 --- a/src/core/cdrom.h +++ b/src/core/cdrom.h @@ -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 GetAudioFrame(); diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 2189a5c6b..69908c68a 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -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"); diff --git a/src/core/game_database.cpp b/src/core/game_database.cpp index c29512e99..79564302e 100644 --- a/src/core/game_database.cpp +++ b/src/core/game_database.cpp @@ -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()) diff --git a/src/core/game_database.h b/src/core/game_database.h index eb3873253..320f35c09 100644 --- a/src/core/game_database.h +++ b/src/core/game_database.h @@ -45,6 +45,7 @@ enum class Trait : u32 DisableMultitap, DisableCDROMReadSpeedup, DisableCDROMSeekSpeedup, + DisableCDROMSpeedupOnMDEC, DisableTrueColor, DisableFullTrueColor, DisableUpscaling, diff --git a/src/core/mdec.cpp b/src/core/mdec.cpp index 5e1e0ece3..8337ed564 100644 --- a/src/core/mdec.cpp +++ b/src/core/mdec.cpp @@ -1,7 +1,8 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // 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 data_in_fifo; InlineFIFOQueue data_out_fifo; - State state = State::Idle; - u32 remaining_halfwords = 0; std::array iq_uv{}; std::array iq_y{}; @@ -152,7 +155,9 @@ struct MDECState alignas(VECTOR_ALIGNMENT) std::array 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 output_depths = {{"4-bit", "8-bit", "24-bit", "15-bit"}}; static constexpr std::array 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); diff --git a/src/core/mdec.h b/src/core/mdec.h index 8d1f36dff..63e44e004 100644 --- a/src/core/mdec.h +++ b/src/core/mdec.h @@ -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); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 529a5851c..fd653563e 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -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()); diff --git a/src/core/settings.h b/src/core/settings.h index 1dc788dad..652c9c9db 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -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; diff --git a/src/core/system.cpp b/src/core/system.cpp index 3bbc310cc..09b1c0177 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -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(); diff --git a/src/duckstation-qt/advancedsettingswidget.cpp b/src/duckstation-qt/advancedsettingswidget.cpp index 21e453282..2a8ee765b 100644 --- a/src/duckstation-qt/advancedsettingswidget.cpp +++ b/src/duckstation-qt/advancedsettingswidget.cpp @@ -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");