From a32ef4a963119172ea9c6250977138b86512d7ba Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Mon, 12 Jul 2021 21:08:04 +1000 Subject: [PATCH] CDROMAsyncReader: Support reading ahead more sectors --- src/core/cdrom.cpp | 48 ++-- src/core/cdrom.h | 3 +- src/core/cdrom_async_reader.cpp | 273 ++++++++++++++----- src/core/cdrom_async_reader.h | 43 ++- src/core/host_interface.cpp | 6 +- src/core/settings.cpp | 4 +- src/core/settings.h | 5 +- src/duckstation-qt/consolesettingswidget.cpp | 22 +- src/duckstation-qt/consolesettingswidget.ui | 75 ++--- src/frontend-common/fullscreen_ui.cpp | 14 +- 10 files changed, 341 insertions(+), 152 deletions(-) diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 5729a9ee5..6e8cc3057 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -99,8 +99,8 @@ void CDROM::Initialize() [](void* param, TickCount ticks, TickCount ticks_late) { static_cast(param)->ExecuteDrive(ticks_late); }, this, false); - if (g_settings.cdrom_read_thread) - m_reader.StartThread(); + if (g_settings.cdrom_readahead_sectors > 0) + m_reader.StartThread(g_settings.cdrom_readahead_sectors); Reset(); } @@ -232,7 +232,8 @@ void CDROM::SoftReset(TickCount ticks_late) { m_drive_state = DriveState::SeekingImplicit; m_drive_event->SetIntervalAndSchedule(total_ticks); - m_reader.QueueReadSector(0); + m_requested_lba = 0; + m_reader.QueueReadSector(m_requested_lba); m_seek_start_lba = m_current_lba; m_seek_end_lba = 0; } @@ -306,14 +307,12 @@ bool CDROM::DoState(StateWrapper& sw) } sw.Do(&m_audio_fifo); - - u32 requested_sector = (sw.IsWriting() ? (m_reader.WaitForReadToComplete(), m_reader.GetLastReadSector()) : 0); - sw.Do(&requested_sector); + sw.Do(&m_requested_lba); if (sw.IsReading()) { if (m_reader.HasMedia()) - m_reader.QueueReadSector(requested_sector); + m_reader.QueueReadSector(m_requested_lba); UpdateCommandEvent(); m_drive_event->SetState(!IsDriveIdle()); } @@ -397,15 +396,18 @@ std::unique_ptr CDROM::RemoveMedia(bool force /* = false */) return image; } -void CDROM::SetUseReadThread(bool enabled) +void CDROM::SetReadaheadSectors(u32 readahead_sectors) { - if (enabled == m_reader.IsUsingThread()) + const bool want_thread = (readahead_sectors > 0); + if (want_thread == m_reader.IsUsingThread() && m_reader.GetReadaheadCount() == readahead_sectors) return; - if (enabled) - m_reader.StartThread(); + if (want_thread) + m_reader.StartThread(readahead_sectors); else m_reader.StopThread(); + + m_reader.QueueReadSector(m_requested_lba); } void CDROM::CPUClockChanged() @@ -1417,8 +1419,6 @@ void CDROM::ExecuteCommand(TickCount ticks_late) Log_DebugPrintf("CDROM GetTN command"); if (CanReadMedia()) { - m_reader.WaitForReadToComplete(); - Log_DevPrintf("GetTN -> %u %u", m_reader.GetMedia()->GetFirstTrackNumber(), m_reader.GetMedia()->GetLastTrackNumber()); @@ -1757,7 +1757,8 @@ void CDROM::BeginReading(TickCount ticks_late /* = 0 */, bool after_seek /* = fa m_current_read_sector_buffer = 0; m_current_write_sector_buffer = 0; - m_reader.QueueReadSector(m_current_lba); + m_requested_lba = m_current_lba; + m_reader.QueueReadSector(m_requested_lba); } void CDROM::BeginPlaying(u8 track, TickCount ticks_late /* = 0 */, bool after_seek /* = false */) @@ -1799,7 +1800,8 @@ void CDROM::BeginPlaying(u8 track, TickCount ticks_late /* = 0 */, bool after_se m_current_read_sector_buffer = 0; m_current_write_sector_buffer = 0; - m_reader.QueueReadSector(m_current_lba); + m_requested_lba = m_current_lba; + m_reader.QueueReadSector(m_requested_lba); } void CDROM::BeginSeeking(bool logical, bool read_after_seek, bool play_after_seek) @@ -1828,7 +1830,8 @@ void CDROM::BeginSeeking(bool logical, bool read_after_seek, bool play_after_see m_seek_start_lba = m_current_lba; m_seek_end_lba = seek_lba; - m_reader.QueueReadSector(seek_lba); + m_requested_lba = seek_lba; + m_reader.QueueReadSector(m_requested_lba); } void CDROM::UpdatePositionWhileSeeking() @@ -2022,9 +2025,10 @@ bool CDROM::CompleteSeek() } } } + + m_current_lba = m_reader.GetLastReadSector(); } - m_current_lba = m_reader.GetLastReadSector(); m_physical_lba = m_current_lba; m_physical_lba_update_tick = TimingEvents::GetGlobalTickCounter(); m_physical_lba_update_carry = 0; @@ -2275,7 +2279,8 @@ void CDROM::DoSectorRead() is_data_sector ? "data" : "audio", is_data_sector ? "reading" : "playing"); } - m_reader.QueueReadSector(next_sector); + m_requested_lba = next_sector; + m_reader.QueueReadSector(m_requested_lba); } void CDROM::ProcessDataSectorHeader(const u8* raw_sector) @@ -2698,12 +2703,13 @@ void CDROM::DrawDebugWindow() if (media->HasSubImages()) { - ImGui::Text("Filename: %s [Subimage %u of %u]", media->GetFileName().c_str(), media->GetCurrentSubImage() + 1u, - media->GetSubImageCount()); + ImGui::Text("Filename: %s [Subimage %u of %u] [%u buffered sectors]", media->GetFileName().c_str(), + media->GetCurrentSubImage() + 1u, media->GetSubImageCount(), m_reader.GetBufferedSectorCount()); } else { - ImGui::Text("Filename: %s", media->GetFileName().c_str()); + ImGui::Text("Filename: %s [%u buffered sectors]", media->GetFileName().c_str(), + m_reader.GetBufferedSectorCount()); } ImGui::Text("Disc Position: MSF[%02u:%02u:%02u] LBA[%u]", disc_position.minute, disc_position.second, diff --git a/src/core/cdrom.h b/src/core/cdrom.h index 240a7e984..ea3786c2b 100644 --- a/src/core/cdrom.h +++ b/src/core/cdrom.h @@ -45,7 +45,7 @@ public: // Render statistics debug window. void DrawDebugWindow(); - void SetUseReadThread(bool enabled); + void SetReadaheadSectors(u32 readahead_sectors); /// Reads a frame from the audio FIFO, used by the SPU. ALWAYS_INLINE std::tuple GetAudioFrame() @@ -347,6 +347,7 @@ private: u8 m_pending_async_interrupt = 0; CDImage::Position m_setloc_position = {}; + CDImage::LBA m_requested_lba{}; CDImage::LBA m_current_lba{}; // this is the hold position CDImage::LBA m_seek_start_lba{}; CDImage::LBA m_seek_end_lba{}; diff --git a/src/core/cdrom_async_reader.cpp b/src/core/cdrom_async_reader.cpp index 44fb8e08a..c298767a5 100644 --- a/src/core/cdrom_async_reader.cpp +++ b/src/core/cdrom_async_reader.cpp @@ -11,13 +11,18 @@ CDROMAsyncReader::~CDROMAsyncReader() StopThread(); } -void CDROMAsyncReader::StartThread() +void CDROMAsyncReader::StartThread(u32 readahead_count) { if (IsUsingThread()) - return; + StopThread(); + + m_buffers.clear(); + m_buffers.resize(readahead_count); + EmptyBuffers(); m_shutdown_flag.store(false); m_read_thread = std::thread(&CDROMAsyncReader::WorkerThreadEntryPoint, this); + Log_InfoPrintf("Read thread started with readahead of %u sectors", readahead_count); } void CDROMAsyncReader::StopThread() @@ -27,25 +32,28 @@ void CDROMAsyncReader::StopThread() { std::unique_lock lock(m_mutex); - if (m_sector_read_pending.load()) - m_notify_read_complete_cv.wait(lock, [this]() { return !m_sector_read_pending.load(); }); - m_shutdown_flag.store(true); m_do_read_cv.notify_one(); } m_read_thread.join(); + EmptyBuffers(); + m_buffers.clear(); } void CDROMAsyncReader::SetMedia(std::unique_ptr media) { - WaitForReadToComplete(); + if (IsUsingThread()) + CancelReadahead(); + m_media = std::move(media); } std::unique_ptr CDROMAsyncReader::RemoveMedia() { - WaitForReadToComplete(); + if (IsUsingThread()) + CancelReadahead(); + return std::move(m_media); } @@ -53,26 +61,39 @@ void CDROMAsyncReader::QueueReadSector(CDImage::LBA lba) { if (!IsUsingThread()) { - m_sector_read_pending.store(true); - m_next_position_set.store(true); - m_next_position = lba; - DoSectorRead(); + ReadSectorNonThreaded(lba); return; } - std::unique_lock lock(m_mutex); - if (m_sector_read_pending.load()) - m_notify_read_complete_cv.wait(lock, [this]() { return !m_sector_read_pending.load(); }); - - // don't re-read the same sector if it was the last one we read - // the CDC code does this when seeking->reading - if (m_last_read_sector == lba && m_sector_read_result.load()) + const u32 buffer_count = m_buffer_count.load(); + if (buffer_count > 0) { - Log_DebugPrintf("Skipping re-reading same sector %u", lba); - return; + // don't re-read the same sector if it was the last one we read + // the CDC code does this when seeking->reading + const u32 buffer_front = m_buffer_front.load(); + if (m_buffers[buffer_front].lba == lba) + { + Log_DebugPrintf("Skipping re-reading same sector %u", lba); + return; + } + + // did we readahead to the correct sector? + const u32 next_buffer = (buffer_front + 1) % static_cast(m_buffers.size()); + if (m_buffer_count > 1 && m_buffers[next_buffer].lba == lba) + { + // great, don't need a seek, but still kick the thread to start reading ahead again + Log_DebugPrintf("Readahead buffer hit for sector %u", lba); + m_buffer_front.store(next_buffer); + m_buffer_count.fetch_sub(1); + m_can_readahead.store(true); + m_do_read_cv.notify_one(); + return; + } } - m_sector_read_pending.store(true); + // we need to toss away our readahead and start fresh + Log_DebugPrintf("Readahead buffer miss, queueing seek to %u", lba); + std::unique_lock lock(m_mutex); m_next_position_set.store(true); m_next_position = lba; m_do_read_cv.notify_one(); @@ -80,7 +101,8 @@ void CDROMAsyncReader::QueueReadSector(CDImage::LBA lba) bool CDROMAsyncReader::ReadSectorUncached(CDImage::LBA lba, CDImage::SubChannelQ* subq, SectorBuffer* data) { - WaitForReadToComplete(); + if (IsUsingThread()) + CancelReadahead(); if (m_media->GetPositionOnDisc() != lba && !m_media->Seek(lba)) { @@ -97,88 +119,203 @@ bool CDROMAsyncReader::ReadSectorUncached(CDImage::LBA lba, CDImage::SubChannelQ return true; } -void CDROMAsyncReader::QueueReadNextSector() +bool CDROMAsyncReader::WaitForReadToComplete() { - if (!IsUsingThread()) + // Safe without locking with memory_order_seq_cst. + if (!m_next_position_set.load() && m_buffer_count.load() > 0) { - m_sector_read_pending.store(true); - DoSectorRead(); - return; + Log_TracePrintf("Returning sector %u", m_buffers[m_buffer_front.load()].lba); + return m_buffers[m_buffer_front.load()].result; } + Common::Timer wait_timer; + Log_DebugPrintf("Sector read pending, waiting"); + std::unique_lock lock(m_mutex); - if (m_sector_read_pending.load()) - m_notify_read_complete_cv.wait(lock, [this]() { return !m_sector_read_pending.load(); }); + m_notify_read_complete_cv.wait( + lock, [this]() { return (m_buffer_count.load() > 0 || m_seek_error.load()) && !m_next_position_set.load(); }); + if (m_seek_error.load()) + { + m_seek_error.store(false); + return false; + } - m_sector_read_pending.store(true); - m_do_read_cv.notify_one(); + const u32 front = m_buffer_front.load(); + const double wait_time = wait_timer.GetTimeMilliseconds(); + if (wait_time > 1.0f) + Log_WarningPrintf("Had to wait %.2f msec for LBA %u", wait_time, m_buffers[front].lba); + + Log_TracePrintf("Returning sector %u after waiting", m_buffers[front].lba); + return m_buffers[front].result; } -bool CDROMAsyncReader::WaitForReadToComplete() +void CDROMAsyncReader::WaitForIdle() { if (!IsUsingThread()) - return m_sector_read_result.load(); + return; std::unique_lock lock(m_mutex); - if (m_sector_read_pending.load()) - { - Log_DebugPrintf("Sector read pending, waiting"); + m_notify_read_complete_cv.wait(lock, [this]() { return (!m_is_reading.load() && !m_next_position_set.load()); }); +} - Common::Timer wait_timer; - m_notify_read_complete_cv.wait(lock, [this]() { return !m_sector_read_pending.load(); }); +void CDROMAsyncReader::EmptyBuffers() +{ + m_buffer_front.store(0); + m_buffer_back.store(0); + m_buffer_count.store(0); +} + +bool CDROMAsyncReader::ReadSectorIntoBuffer(std::unique_lock& lock) +{ + Common::Timer timer; + + const u32 slot = m_buffer_back.load(); + m_buffer_back.store((slot + 1) % static_cast(m_buffers.size())); + + BufferSlot& buffer = m_buffers[slot]; + buffer.lba = m_media->GetPositionOnDisc(); + m_is_reading.store(true); + lock.unlock(); + + Log_TracePrintf("Reading LBA %u...", buffer.lba); - const double wait_time = wait_timer.GetTimeMilliseconds(); - if (wait_time > 1.0f) - Log_WarningPrintf("Had to wait %.2f msec for LBA %u", wait_time, m_last_read_sector); + buffer.result = m_media->ReadRawSector(buffer.data.data(), &buffer.subq); + if (buffer.result) + { + const double read_time = timer.GetTimeMilliseconds(); + if (read_time > 1.0f) + Log_DevPrintf("Read LBA %u took %.2f msec", buffer.lba, read_time); + } + else + { + Log_ErrorPrintf("Read of LBA %u failed", buffer.lba); } - return m_sector_read_result.load(); + lock.lock(); + m_is_reading.store(false); + m_buffer_count.fetch_add(1); + m_notify_read_complete_cv.notify_all(); + return true; } -void CDROMAsyncReader::DoSectorRead() +void CDROMAsyncReader::ReadSectorNonThreaded(CDImage::LBA lba) { Common::Timer timer; - if (m_next_position_set.load()) + m_buffers.resize(1); + m_seek_error.store(false); + EmptyBuffers(); + + if (m_media->GetPositionOnDisc() != lba && !m_media->Seek(lba)) { - if (m_media->GetPositionOnDisc() != m_next_position && !m_media->Seek(m_next_position)) - { - Log_WarningPrintf("Seek to LBA %u failed", m_next_position); - m_sector_read_result.store(false); - return; - } + Log_WarningPrintf("Seek to LBA %u failed", lba); + m_seek_error.store(true); + return; } - const CDImage::LBA pos = m_media->GetPositionOnDisc(); - if (!m_media->ReadRawSector(m_sector_buffer.data(), &m_subq)) + BufferSlot& buffer = m_buffers.front(); + buffer.lba = m_media->GetPositionOnDisc(); + + Log_TracePrintf("Reading LBA %u...", buffer.lba); + + buffer.result = m_media->ReadRawSector(buffer.data.data(), &buffer.subq); + if (buffer.result) { - m_sector_read_result.store(false); - Log_WarningPrintf("Read of LBA %u failed", pos); - return; + const double read_time = timer.GetTimeMilliseconds(); + if (read_time > 1.0f) + Log_DevPrintf("Read LBA %u took %.2f msec", buffer.lba, read_time); + } + else + { + Log_ErrorPrintf("Read of LBA %u failed", buffer.lba); } - m_last_read_sector = pos; - m_sector_read_result.store(true); + m_buffer_count.fetch_add(1); +} + +void CDROMAsyncReader::CancelReadahead() +{ + Log_DevPrintf("Cancelling readahead"); - const double read_time = timer.GetTimeMilliseconds(); - if (read_time > 1.0f) - Log_DevPrintf("Read LBA %u took %.2f msec", pos, read_time); + std::unique_lock lock(m_mutex); + + // wait until the read thread is idle + m_notify_read_complete_cv.wait(lock, [this]() { return !m_is_reading.load(); }); + + // prevent it from doing any more when it re-acquires the lock + m_can_readahead.store(false); + EmptyBuffers(); } void CDROMAsyncReader::WorkerThreadEntryPoint() { std::unique_lock lock(m_mutex); - while (!m_shutdown_flag.load()) + for (;;) { - m_do_read_cv.wait(lock, [this]() { return (m_shutdown_flag.load() || m_sector_read_pending.load()); }); - if (m_sector_read_pending.load()) + m_do_read_cv.wait( + lock, [this]() { return (m_shutdown_flag.load() || m_next_position_set.load() || m_can_readahead.load()); }); + if (m_shutdown_flag.load()) + break; + + for (;;) { - lock.unlock(); - DoSectorRead(); - lock.lock(); - m_sector_read_pending.store(false); - m_notify_read_complete_cv.notify_one(); + if (m_next_position_set.load()) + { + // discard buffers, we're seeking to a new location + const CDImage::LBA seek_location = m_next_position.load(); + EmptyBuffers(); + m_next_position_set.store(false); + m_seek_error.store(false); + m_is_reading.store(true); + lock.unlock(); + + // seek without lock held in case it takes time + Log_DebugPrintf("Seeking to LBA %u...", seek_location); + const bool seek_result = (m_media->GetPositionOnDisc() == seek_location || m_media->Seek(seek_location)); + + lock.lock(); + m_is_reading.store(false); + + // did another request come in? abort if so + if (m_next_position_set.load()) + continue; + + // did we fail the seek? + if (!seek_result) + { + // add the error result, and don't try to read ahead + Log_WarningPrintf("Seek to LBA %u failed", seek_location); + m_seek_error.store(true); + m_notify_read_complete_cv.notify_all(); + break; + } + + // go go read ahead! + m_can_readahead.store(true); + } + + if (!m_can_readahead.load()) + break; + + // readahead time! read as many sectors as we have space for + Log_DebugPrintf("Reading ahead %u sectors...", static_cast(m_buffers.size()) - m_buffer_count.load()); + while (m_buffer_count.load() < static_cast(m_buffers.size())) + { + if (m_next_position_set.load()) + { + // a seek request came in while we're reading, so bail out + break; + } + + // stop reading if we hit the end or get an error + if (!ReadSectorIntoBuffer(lock)) + break; + } + + // readahead buffer is full or errored at this point + m_can_readahead.store(false); + break; } } } diff --git a/src/core/cdrom_async_reader.h b/src/core/cdrom_async_reader.h index 2e13a4771..76e3d9273 100644 --- a/src/core/cdrom_async_reader.h +++ b/src/core/cdrom_async_reader.h @@ -11,33 +11,49 @@ class CDROMAsyncReader public: using SectorBuffer = std::array; + struct BufferSlot + { + CDImage::LBA lba; + SectorBuffer data; + CDImage::SubChannelQ subq; + bool result; + }; + CDROMAsyncReader(); ~CDROMAsyncReader(); - const CDImage::LBA GetLastReadSector() const { return m_last_read_sector; } - const SectorBuffer& GetSectorBuffer() const { return m_sector_buffer; } - const CDImage::SubChannelQ& GetSectorSubQ() const { return m_subq; } + const CDImage::LBA GetLastReadSector() const { return m_buffers[m_buffer_front.load()].lba; } + const SectorBuffer& GetSectorBuffer() const { return m_buffers[m_buffer_front.load()].data; } + const CDImage::SubChannelQ& GetSectorSubQ() const { return m_buffers[m_buffer_front.load()].subq; } + const u32 GetBufferedSectorCount() const { return m_buffer_count.load(); } + const bool HasBufferedSectors() const { return (m_buffer_count.load() > 0); } + const u32 GetReadaheadCount() const { return static_cast(m_buffers.size()); } + const bool HasMedia() const { return static_cast(m_media); } const CDImage* GetMedia() const { return m_media.get(); } const std::string& GetMediaFileName() const { return m_media->GetFileName(); } bool IsUsingThread() const { return m_read_thread.joinable(); } - void StartThread(); + void StartThread(u32 readahead_count = 8); void StopThread(); void SetMedia(std::unique_ptr media); std::unique_ptr RemoveMedia(); void QueueReadSector(CDImage::LBA lba); - void QueueReadNextSector(); bool WaitForReadToComplete(); + void WaitForIdle(); /// Bypasses the sector cache and reads directly from the image. bool ReadSectorUncached(CDImage::LBA lba, CDImage::SubChannelQ* subq, SectorBuffer* data); private: - void DoSectorRead(); + void EmptyBuffers(); + bool ReadSectorIntoBuffer(std::unique_lock& lock); + void ReadSectorNonThreaded(CDImage::LBA lba); + void CancelReadahead(); + void WorkerThreadEntryPoint(); std::unique_ptr m_media; @@ -47,13 +63,16 @@ private: std::condition_variable m_do_read_cv; std::condition_variable m_notify_read_complete_cv; - CDImage::LBA m_next_position{}; + std::atomic m_next_position{}; std::atomic_bool m_next_position_set{false}; - std::atomic_bool m_sector_read_pending{false}; std::atomic_bool m_shutdown_flag{true}; - CDImage::LBA m_last_read_sector{}; - CDImage::SubChannelQ m_subq{}; - SectorBuffer m_sector_buffer{}; - std::atomic_bool m_sector_read_result{false}; + std::atomic_bool m_is_reading{ false }; + std::atomic_bool m_can_readahead{ false }; + std::atomic_bool m_seek_error{ false }; + + std::vector m_buffers; + std::atomic m_buffer_front{ 0 }; + std::atomic m_buffer_back{ 0 }; + std::atomic m_buffer_count{ 0 }; }; diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index 0473d878a..75284693a 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -565,7 +565,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetStringValue("Display", "PostProcessChain", ""); si.SetFloatValue("Display", "MaxFPS", Settings::DEFAULT_DISPLAY_MAX_FPS); - si.SetBoolValue("CDROM", "ReadThread", true); + si.SetIntValue("CDROM", "ReadaheadSectors", Settings::DEFAULT_CDROM_READAHEAD_SECTORS); si.SetBoolValue("CDROM", "RegionCheck", false); si.SetBoolValue("CDROM", "LoadImageToRAM", false); si.SetBoolValue("CDROM", "MuteCDAudio", false); @@ -853,8 +853,8 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings) PGXP::Initialize(); } - if (g_settings.cdrom_read_thread != old_settings.cdrom_read_thread) - g_cdrom.SetUseReadThread(g_settings.cdrom_read_thread); + if (g_settings.cdrom_readahead_sectors != old_settings.cdrom_readahead_sectors) + g_cdrom.SetReadaheadSectors(g_settings.cdrom_readahead_sectors); if (g_settings.memory_card_types != old_settings.memory_card_types || g_settings.memory_card_paths != old_settings.memory_card_paths || diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 4ad70ecf1..f83fb23ff 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -242,7 +242,7 @@ void Settings::Load(SettingsInterface& si) display_post_process_chain = si.GetStringValue("Display", "PostProcessChain", ""); display_max_fps = si.GetFloatValue("Display", "MaxFPS", DEFAULT_DISPLAY_MAX_FPS); - cdrom_read_thread = si.GetBoolValue("CDROM", "ReadThread", true); + cdrom_readahead_sectors = static_cast(si.GetIntValue("CDROM", "ReadaheadSectors", DEFAULT_CDROM_READAHEAD_SECTORS)); cdrom_region_check = si.GetBoolValue("CDROM", "RegionCheck", false); cdrom_load_image_to_ram = si.GetBoolValue("CDROM", "LoadImageToRAM", false); cdrom_mute_cd_audio = si.GetBoolValue("CDROM", "MuteCDAudio", false); @@ -419,7 +419,7 @@ void Settings::Save(SettingsInterface& si) const si.SetStringValue("Display", "PostProcessChain", display_post_process_chain.c_str()); si.SetFloatValue("Display", "MaxFPS", display_max_fps); - si.SetBoolValue("CDROM", "ReadThread", cdrom_read_thread); + si.SetIntValue("CDROM", "ReadaheadSectors", cdrom_readahead_sectors); si.SetBoolValue("CDROM", "RegionCheck", cdrom_region_check); si.SetBoolValue("CDROM", "LoadImageToRAM", cdrom_load_image_to_ram); si.SetBoolValue("CDROM", "MuteCDAudio", cdrom_mute_cd_audio); diff --git a/src/core/settings.h b/src/core/settings.h index 170cef3c0..e26828ad9 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -152,7 +152,7 @@ struct Settings float gpu_pgxp_tolerance = -1.0f; float gpu_pgxp_depth_clear_threshold = 300.0f / 4096.0f; - bool cdrom_read_thread = true; + u8 cdrom_readahead_sectors = DEFAULT_CDROM_READAHEAD_SECTORS; bool cdrom_region_check = false; bool cdrom_load_image_to_ram = false; bool cdrom_mute_cd_audio = false; @@ -379,6 +379,9 @@ struct Settings static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan; static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::Auto; + + static constexpr u8 DEFAULT_CDROM_READAHEAD_SECTORS = 8; + static constexpr ControllerType DEFAULT_CONTROLLER_1_TYPE = ControllerType::DigitalController; static constexpr ControllerType DEFAULT_CONTROLLER_2_TYPE = ControllerType::None; static constexpr MemoryCardType DEFAULT_MEMORY_CARD_1_TYPE = MemoryCardType::PerGameTitle; diff --git a/src/duckstation-qt/consolesettingswidget.cpp b/src/duckstation-qt/consolesettingswidget.cpp index 8a451686d..782ff26e7 100644 --- a/src/duckstation-qt/consolesettingswidget.cpp +++ b/src/duckstation-qt/consolesettingswidget.cpp @@ -1,4 +1,5 @@ #include "consolesettingswidget.h" +#include "common/cd_image.h" #include "core/system.h" #include "qtutils.h" #include "settingsdialog.h" @@ -28,6 +29,17 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW qApp->translate("MultitapMode", Settings::GetMultitapModeDisplayName(static_cast(i)))); } + static constexpr float TIME_PER_SECTOR_DOUBLE_SPEED = 1000.0f / 150.0f; + m_ui.cdromReadaheadSectors->addItem(tr("Disabled (Synchronous)")); + for (u32 i = 1; i <= 32; i++) + { + m_ui.cdromReadaheadSectors->addItem(tr("%1 sectors (%2 KB / %3 ms)") + .arg(i) + + .arg(static_cast(i) * TIME_PER_SECTOR_DOUBLE_SPEED, 0, 'f', 0) + .arg(static_cast(i * CDImage::DATA_SECTOR_SIZE) / 1024.0f)); + } + SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.region, "Console", "Region", &Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName, Settings::DEFAULT_CONSOLE_REGION); @@ -37,7 +49,8 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW Settings::DEFAULT_CPU_EXECUTION_MODE); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableCPUClockSpeedControl, "CPU", "OverclockEnable", false); - SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromReadThread, "CDROM", "ReadThread", true); + SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.cdromReadaheadSectors, "CDROM", "ReadaheadSectors", + Settings::DEFAULT_CDROM_READAHEAD_SECTORS); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromRegionCheck, "CDROM", "RegionCheck", false); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromLoadImageToRAM, "CDROM", "LoadImageToRAM", false); @@ -74,9 +87,10 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW m_ui.cdromSeekSpeedup, tr("CD-ROM Seek Speedup"), tr("None (Normal Speed)"), tr("Reduces the simulated time for the CD-ROM sled to move to different areas of the disc. Can improve loading " "times, but crash games which do not expect the CD-ROM to operate faster.")); - dialog->registerWidgetHelp( - m_ui.cdromReadThread, tr("Use Read Thread (Asynchronous)"), tr("Checked"), - tr("Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread.")); + dialog->registerWidgetHelp(m_ui.cdromReadaheadSectors, tr("Asynchronous Readahead"), tr("8 Sectors"), + tr("Reduces hitches in emulation by reading/decompressing CD data asynchronously on a " + "worker thread. Higher sector numbers can reduce spikes when streaming FMVs or audio " + "on slower storage or when using compression formats such as CHD.")); dialog->registerWidgetHelp(m_ui.cdromRegionCheck, tr("Enable Region Check"), tr("Checked"), tr("Simulates the region check present in original, unmodified consoles.")); dialog->registerWidgetHelp( diff --git a/src/duckstation-qt/consolesettingswidget.ui b/src/duckstation-qt/consolesettingswidget.ui index 2727f1abc..c1f1424fa 100644 --- a/src/duckstation-qt/consolesettingswidget.ui +++ b/src/duckstation-qt/consolesettingswidget.ui @@ -137,14 +137,17 @@ CD-ROM Emulation - + + + + Read Speedup: - + @@ -198,46 +201,14 @@ - - - - - - Use Read Thread (Asynchronous) - - - - - - - Enable Region Check - - - - - - - Preload Image To RAM - - - - - - - Apply Image Patches - - - - - - + Seek Speedup: - + 1 @@ -299,6 +270,38 @@ + + + + + + Enable Region Check + + + + + + + Preload Image To RAM + + + + + + + Apply Image Patches + + + + + + + + + Async Readahead: + + + diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp index b82cdd857..61ed1fbe4 100644 --- a/src/frontend-common/fullscreen_ui.cpp +++ b/src/frontend-common/fullscreen_ui.cpp @@ -1588,10 +1588,16 @@ void DrawSettingsWindow() }); } - settings_changed |= ToggleButton( - "Enable Read Thread", - "Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread.", - &s_settings_copy.cdrom_read_thread); + s32 readahead_sectors = s_settings_copy.cdrom_readahead_sectors; + if (RangeButton( + "Readahead Sectors", + "Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread.", + &readahead_sectors, 0, 32, 1)) + { + s_settings_copy.cdrom_readahead_sectors = static_cast(readahead_sectors); + settings_changed = true; + } + settings_changed |= ToggleButton("Enable Region Check", "Simulates the region check present in original, unmodified consoles.", &s_settings_copy.cdrom_region_check);