CDROMAsyncReader: Support reading ahead more sectors

This commit is contained in:
Connor McLaughlin 2021-07-12 21:08:04 +10:00
parent 552b0098ef
commit a32ef4a963
10 changed files with 351 additions and 162 deletions

View file

@ -99,8 +99,8 @@ void CDROM::Initialize()
[](void* param, TickCount ticks, TickCount ticks_late) { static_cast<CDROM*>(param)->ExecuteDrive(ticks_late); }, [](void* param, TickCount ticks, TickCount ticks_late) { static_cast<CDROM*>(param)->ExecuteDrive(ticks_late); },
this, false); this, false);
if (g_settings.cdrom_read_thread) if (g_settings.cdrom_readahead_sectors > 0)
m_reader.StartThread(); m_reader.StartThread(g_settings.cdrom_readahead_sectors);
Reset(); Reset();
} }
@ -232,7 +232,8 @@ void CDROM::SoftReset(TickCount ticks_late)
{ {
m_drive_state = DriveState::SeekingImplicit; m_drive_state = DriveState::SeekingImplicit;
m_drive_event->SetIntervalAndSchedule(total_ticks); 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_start_lba = m_current_lba;
m_seek_end_lba = 0; m_seek_end_lba = 0;
} }
@ -306,14 +307,12 @@ bool CDROM::DoState(StateWrapper& sw)
} }
sw.Do(&m_audio_fifo); sw.Do(&m_audio_fifo);
sw.Do(&m_requested_lba);
u32 requested_sector = (sw.IsWriting() ? (m_reader.WaitForReadToComplete(), m_reader.GetLastReadSector()) : 0);
sw.Do(&requested_sector);
if (sw.IsReading()) if (sw.IsReading())
{ {
if (m_reader.HasMedia()) if (m_reader.HasMedia())
m_reader.QueueReadSector(requested_sector); m_reader.QueueReadSector(m_requested_lba);
UpdateCommandEvent(); UpdateCommandEvent();
m_drive_event->SetState(!IsDriveIdle()); m_drive_event->SetState(!IsDriveIdle());
} }
@ -397,15 +396,18 @@ std::unique_ptr<CDImage> CDROM::RemoveMedia(bool force /* = false */)
return image; 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; return;
if (enabled) if (want_thread)
m_reader.StartThread(); m_reader.StartThread(readahead_sectors);
else else
m_reader.StopThread(); m_reader.StopThread();
m_reader.QueueReadSector(m_requested_lba);
} }
void CDROM::CPUClockChanged() void CDROM::CPUClockChanged()
@ -1417,8 +1419,6 @@ void CDROM::ExecuteCommand(TickCount ticks_late)
Log_DebugPrintf("CDROM GetTN command"); Log_DebugPrintf("CDROM GetTN command");
if (CanReadMedia()) if (CanReadMedia())
{ {
m_reader.WaitForReadToComplete();
Log_DevPrintf("GetTN -> %u %u", m_reader.GetMedia()->GetFirstTrackNumber(), Log_DevPrintf("GetTN -> %u %u", m_reader.GetMedia()->GetFirstTrackNumber(),
m_reader.GetMedia()->GetLastTrackNumber()); 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_read_sector_buffer = 0;
m_current_write_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 */) 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_read_sector_buffer = 0;
m_current_write_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) 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_start_lba = m_current_lba;
m_seek_end_lba = seek_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() 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 = m_current_lba;
m_physical_lba_update_tick = TimingEvents::GetGlobalTickCounter(); m_physical_lba_update_tick = TimingEvents::GetGlobalTickCounter();
m_physical_lba_update_carry = 0; m_physical_lba_update_carry = 0;
@ -2275,7 +2279,8 @@ void CDROM::DoSectorRead()
is_data_sector ? "data" : "audio", is_data_sector ? "reading" : "playing"); 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) void CDROM::ProcessDataSectorHeader(const u8* raw_sector)
@ -2698,12 +2703,13 @@ void CDROM::DrawDebugWindow()
if (media->HasSubImages()) if (media->HasSubImages())
{ {
ImGui::Text("Filename: %s [Subimage %u of %u]", media->GetFileName().c_str(), media->GetCurrentSubImage() + 1u, ImGui::Text("Filename: %s [Subimage %u of %u] [%u buffered sectors]", media->GetFileName().c_str(),
media->GetSubImageCount()); media->GetCurrentSubImage() + 1u, media->GetSubImageCount(), m_reader.GetBufferedSectorCount());
} }
else 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, ImGui::Text("Disc Position: MSF[%02u:%02u:%02u] LBA[%u]", disc_position.minute, disc_position.second,

View file

@ -45,7 +45,7 @@ public:
// Render statistics debug window. // Render statistics debug window.
void DrawDebugWindow(); void DrawDebugWindow();
void SetUseReadThread(bool enabled); void SetReadaheadSectors(u32 readahead_sectors);
/// Reads a frame from the audio FIFO, used by the SPU. /// Reads a frame from the audio FIFO, used by the SPU.
ALWAYS_INLINE std::tuple<s16, s16> GetAudioFrame() ALWAYS_INLINE std::tuple<s16, s16> GetAudioFrame()
@ -347,6 +347,7 @@ private:
u8 m_pending_async_interrupt = 0; u8 m_pending_async_interrupt = 0;
CDImage::Position m_setloc_position = {}; CDImage::Position m_setloc_position = {};
CDImage::LBA m_requested_lba{};
CDImage::LBA m_current_lba{}; // this is the hold position CDImage::LBA m_current_lba{}; // this is the hold position
CDImage::LBA m_seek_start_lba{}; CDImage::LBA m_seek_start_lba{};
CDImage::LBA m_seek_end_lba{}; CDImage::LBA m_seek_end_lba{};

View file

@ -11,13 +11,18 @@ CDROMAsyncReader::~CDROMAsyncReader()
StopThread(); StopThread();
} }
void CDROMAsyncReader::StartThread() void CDROMAsyncReader::StartThread(u32 readahead_count)
{ {
if (IsUsingThread()) if (IsUsingThread())
return; StopThread();
m_buffers.clear();
m_buffers.resize(readahead_count);
EmptyBuffers();
m_shutdown_flag.store(false); m_shutdown_flag.store(false);
m_read_thread = std::thread(&CDROMAsyncReader::WorkerThreadEntryPoint, this); m_read_thread = std::thread(&CDROMAsyncReader::WorkerThreadEntryPoint, this);
Log_InfoPrintf("Read thread started with readahead of %u sectors", readahead_count);
} }
void CDROMAsyncReader::StopThread() void CDROMAsyncReader::StopThread()
@ -27,25 +32,28 @@ void CDROMAsyncReader::StopThread()
{ {
std::unique_lock<std::mutex> lock(m_mutex); std::unique_lock<std::mutex> 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_shutdown_flag.store(true);
m_do_read_cv.notify_one(); m_do_read_cv.notify_one();
} }
m_read_thread.join(); m_read_thread.join();
EmptyBuffers();
m_buffers.clear();
} }
void CDROMAsyncReader::SetMedia(std::unique_ptr<CDImage> media) void CDROMAsyncReader::SetMedia(std::unique_ptr<CDImage> media)
{ {
WaitForReadToComplete(); if (IsUsingThread())
CancelReadahead();
m_media = std::move(media); m_media = std::move(media);
} }
std::unique_ptr<CDImage> CDROMAsyncReader::RemoveMedia() std::unique_ptr<CDImage> CDROMAsyncReader::RemoveMedia()
{ {
WaitForReadToComplete(); if (IsUsingThread())
CancelReadahead();
return std::move(m_media); return std::move(m_media);
} }
@ -53,26 +61,39 @@ void CDROMAsyncReader::QueueReadSector(CDImage::LBA lba)
{ {
if (!IsUsingThread()) if (!IsUsingThread())
{ {
m_sector_read_pending.store(true); ReadSectorNonThreaded(lba);
m_next_position_set.store(true);
m_next_position = lba;
DoSectorRead();
return; return;
} }
std::unique_lock<std::mutex> lock(m_mutex); const u32 buffer_count = m_buffer_count.load();
if (m_sector_read_pending.load()) if (buffer_count > 0)
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 // don't re-read the same sector if it was the last one we read
// the CDC code does this when seeking->reading // the CDC code does this when seeking->reading
if (m_last_read_sector == lba && m_sector_read_result.load()) const u32 buffer_front = m_buffer_front.load();
if (m_buffers[buffer_front].lba == lba)
{ {
Log_DebugPrintf("Skipping re-reading same sector %u", lba); Log_DebugPrintf("Skipping re-reading same sector %u", lba);
return; return;
} }
m_sector_read_pending.store(true); // did we readahead to the correct sector?
const u32 next_buffer = (buffer_front + 1) % static_cast<u32>(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;
}
}
// we need to toss away our readahead and start fresh
Log_DebugPrintf("Readahead buffer miss, queueing seek to %u", lba);
std::unique_lock<std::mutex> lock(m_mutex);
m_next_position_set.store(true); m_next_position_set.store(true);
m_next_position = lba; m_next_position = lba;
m_do_read_cv.notify_one(); 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) bool CDROMAsyncReader::ReadSectorUncached(CDImage::LBA lba, CDImage::SubChannelQ* subq, SectorBuffer* data)
{ {
WaitForReadToComplete(); if (IsUsingThread())
CancelReadahead();
if (m_media->GetPositionOnDisc() != lba && !m_media->Seek(lba)) if (m_media->GetPositionOnDisc() != lba && !m_media->Seek(lba))
{ {
@ -97,88 +119,203 @@ bool CDROMAsyncReader::ReadSectorUncached(CDImage::LBA lba, CDImage::SubChannelQ
return true; return true;
} }
void CDROMAsyncReader::QueueReadNextSector()
{
if (!IsUsingThread())
{
m_sector_read_pending.store(true);
DoSectorRead();
return;
}
std::unique_lock<std::mutex> lock(m_mutex);
if (m_sector_read_pending.load())
m_notify_read_complete_cv.wait(lock, [this]() { return !m_sector_read_pending.load(); });
m_sector_read_pending.store(true);
m_do_read_cv.notify_one();
}
bool CDROMAsyncReader::WaitForReadToComplete() bool CDROMAsyncReader::WaitForReadToComplete()
{ {
if (!IsUsingThread()) // Safe without locking with memory_order_seq_cst.
return m_sector_read_result.load(); if (!m_next_position_set.load() && m_buffer_count.load() > 0)
std::unique_lock<std::mutex> lock(m_mutex);
if (m_sector_read_pending.load())
{ {
Log_DebugPrintf("Sector read pending, waiting"); Log_TracePrintf("Returning sector %u", m_buffers[m_buffer_front.load()].lba);
return m_buffers[m_buffer_front.load()].result;
}
Common::Timer wait_timer; Common::Timer wait_timer;
m_notify_read_complete_cv.wait(lock, [this]() { return !m_sector_read_pending.load(); }); Log_DebugPrintf("Sector read pending, waiting");
std::unique_lock<std::mutex> lock(m_mutex);
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;
}
const u32 front = m_buffer_front.load();
const double wait_time = wait_timer.GetTimeMilliseconds(); const double wait_time = wait_timer.GetTimeMilliseconds();
if (wait_time > 1.0f) if (wait_time > 1.0f)
Log_WarningPrintf("Had to wait %.2f msec for LBA %u", wait_time, m_last_read_sector); 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;
} }
return m_sector_read_result.load(); void CDROMAsyncReader::WaitForIdle()
{
if (!IsUsingThread())
return;
std::unique_lock<std::mutex> lock(m_mutex);
m_notify_read_complete_cv.wait(lock, [this]() { return (!m_is_reading.load() && !m_next_position_set.load()); });
} }
void CDROMAsyncReader::DoSectorRead() void CDROMAsyncReader::EmptyBuffers()
{
m_buffer_front.store(0);
m_buffer_back.store(0);
m_buffer_count.store(0);
}
bool CDROMAsyncReader::ReadSectorIntoBuffer(std::unique_lock<std::mutex>& lock)
{ {
Common::Timer timer; Common::Timer timer;
if (m_next_position_set.load()) const u32 slot = m_buffer_back.load();
{ m_buffer_back.store((slot + 1) % static_cast<u32>(m_buffers.size()));
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;
}
}
const CDImage::LBA pos = m_media->GetPositionOnDisc(); BufferSlot& buffer = m_buffers[slot];
if (!m_media->ReadRawSector(m_sector_buffer.data(), &m_subq)) buffer.lba = m_media->GetPositionOnDisc();
m_is_reading.store(true);
lock.unlock();
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;
}
m_last_read_sector = pos;
m_sector_read_result.store(true);
const double read_time = timer.GetTimeMilliseconds(); const double read_time = timer.GetTimeMilliseconds();
if (read_time > 1.0f) if (read_time > 1.0f)
Log_DevPrintf("Read LBA %u took %.2f msec", pos, read_time); Log_DevPrintf("Read LBA %u took %.2f msec", buffer.lba, read_time);
}
else
{
Log_ErrorPrintf("Read of LBA %u failed", buffer.lba);
}
lock.lock();
m_is_reading.store(false);
m_buffer_count.fetch_add(1);
m_notify_read_complete_cv.notify_all();
return true;
}
void CDROMAsyncReader::ReadSectorNonThreaded(CDImage::LBA lba)
{
Common::Timer timer;
m_buffers.resize(1);
m_seek_error.store(false);
EmptyBuffers();
if (m_media->GetPositionOnDisc() != lba && !m_media->Seek(lba))
{
Log_WarningPrintf("Seek to LBA %u failed", lba);
m_seek_error.store(true);
return;
}
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)
{
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_buffer_count.fetch_add(1);
}
void CDROMAsyncReader::CancelReadahead()
{
Log_DevPrintf("Cancelling readahead");
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() void CDROMAsyncReader::WorkerThreadEntryPoint()
{ {
std::unique_lock lock(m_mutex); 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()); }); m_do_read_cv.wait(
if (m_sector_read_pending.load()) lock, [this]() { return (m_shutdown_flag.load() || m_next_position_set.load() || m_can_readahead.load()); });
if (m_shutdown_flag.load())
break;
for (;;)
{ {
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(); lock.unlock();
DoSectorRead();
// 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(); lock.lock();
m_sector_read_pending.store(false); m_is_reading.store(false);
m_notify_read_complete_cv.notify_one();
// 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<u32>(m_buffers.size()) - m_buffer_count.load());
while (m_buffer_count.load() < static_cast<u32>(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;
} }
} }
} }

View file

@ -11,33 +11,49 @@ class CDROMAsyncReader
public: public:
using SectorBuffer = std::array<u8, CDImage::RAW_SECTOR_SIZE>; using SectorBuffer = std::array<u8, CDImage::RAW_SECTOR_SIZE>;
struct BufferSlot
{
CDImage::LBA lba;
SectorBuffer data;
CDImage::SubChannelQ subq;
bool result;
};
CDROMAsyncReader(); CDROMAsyncReader();
~CDROMAsyncReader(); ~CDROMAsyncReader();
const CDImage::LBA GetLastReadSector() const { return m_last_read_sector; } const CDImage::LBA GetLastReadSector() const { return m_buffers[m_buffer_front.load()].lba; }
const SectorBuffer& GetSectorBuffer() const { return m_sector_buffer; } const SectorBuffer& GetSectorBuffer() const { return m_buffers[m_buffer_front.load()].data; }
const CDImage::SubChannelQ& GetSectorSubQ() const { return m_subq; } 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<u32>(m_buffers.size()); }
const bool HasMedia() const { return static_cast<bool>(m_media); } const bool HasMedia() const { return static_cast<bool>(m_media); }
const CDImage* GetMedia() const { return m_media.get(); } const CDImage* GetMedia() const { return m_media.get(); }
const std::string& GetMediaFileName() const { return m_media->GetFileName(); } const std::string& GetMediaFileName() const { return m_media->GetFileName(); }
bool IsUsingThread() const { return m_read_thread.joinable(); } bool IsUsingThread() const { return m_read_thread.joinable(); }
void StartThread(); void StartThread(u32 readahead_count = 8);
void StopThread(); void StopThread();
void SetMedia(std::unique_ptr<CDImage> media); void SetMedia(std::unique_ptr<CDImage> media);
std::unique_ptr<CDImage> RemoveMedia(); std::unique_ptr<CDImage> RemoveMedia();
void QueueReadSector(CDImage::LBA lba); void QueueReadSector(CDImage::LBA lba);
void QueueReadNextSector();
bool WaitForReadToComplete(); bool WaitForReadToComplete();
void WaitForIdle();
/// Bypasses the sector cache and reads directly from the image. /// Bypasses the sector cache and reads directly from the image.
bool ReadSectorUncached(CDImage::LBA lba, CDImage::SubChannelQ* subq, SectorBuffer* data); bool ReadSectorUncached(CDImage::LBA lba, CDImage::SubChannelQ* subq, SectorBuffer* data);
private: private:
void DoSectorRead(); void EmptyBuffers();
bool ReadSectorIntoBuffer(std::unique_lock<std::mutex>& lock);
void ReadSectorNonThreaded(CDImage::LBA lba);
void CancelReadahead();
void WorkerThreadEntryPoint(); void WorkerThreadEntryPoint();
std::unique_ptr<CDImage> m_media; std::unique_ptr<CDImage> m_media;
@ -47,13 +63,16 @@ private:
std::condition_variable m_do_read_cv; std::condition_variable m_do_read_cv;
std::condition_variable m_notify_read_complete_cv; std::condition_variable m_notify_read_complete_cv;
CDImage::LBA m_next_position{}; std::atomic<CDImage::LBA> m_next_position{};
std::atomic_bool m_next_position_set{false}; std::atomic_bool m_next_position_set{false};
std::atomic_bool m_sector_read_pending{false};
std::atomic_bool m_shutdown_flag{true}; std::atomic_bool m_shutdown_flag{true};
CDImage::LBA m_last_read_sector{}; std::atomic_bool m_is_reading{ false };
CDImage::SubChannelQ m_subq{}; std::atomic_bool m_can_readahead{ false };
SectorBuffer m_sector_buffer{}; std::atomic_bool m_seek_error{ false };
std::atomic_bool m_sector_read_result{false};
std::vector<BufferSlot> m_buffers;
std::atomic<u32> m_buffer_front{ 0 };
std::atomic<u32> m_buffer_back{ 0 };
std::atomic<u32> m_buffer_count{ 0 };
}; };

View file

@ -565,7 +565,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
si.SetStringValue("Display", "PostProcessChain", ""); si.SetStringValue("Display", "PostProcessChain", "");
si.SetFloatValue("Display", "MaxFPS", Settings::DEFAULT_DISPLAY_MAX_FPS); 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", "RegionCheck", false);
si.SetBoolValue("CDROM", "LoadImageToRAM", false); si.SetBoolValue("CDROM", "LoadImageToRAM", false);
si.SetBoolValue("CDROM", "MuteCDAudio", false); si.SetBoolValue("CDROM", "MuteCDAudio", false);
@ -853,8 +853,8 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
PGXP::Initialize(); PGXP::Initialize();
} }
if (g_settings.cdrom_read_thread != old_settings.cdrom_read_thread) if (g_settings.cdrom_readahead_sectors != old_settings.cdrom_readahead_sectors)
g_cdrom.SetUseReadThread(g_settings.cdrom_read_thread); g_cdrom.SetReadaheadSectors(g_settings.cdrom_readahead_sectors);
if (g_settings.memory_card_types != old_settings.memory_card_types || if (g_settings.memory_card_types != old_settings.memory_card_types ||
g_settings.memory_card_paths != old_settings.memory_card_paths || g_settings.memory_card_paths != old_settings.memory_card_paths ||

View file

@ -242,7 +242,7 @@ void Settings::Load(SettingsInterface& si)
display_post_process_chain = si.GetStringValue("Display", "PostProcessChain", ""); display_post_process_chain = si.GetStringValue("Display", "PostProcessChain", "");
display_max_fps = si.GetFloatValue("Display", "MaxFPS", DEFAULT_DISPLAY_MAX_FPS); display_max_fps = si.GetFloatValue("Display", "MaxFPS", DEFAULT_DISPLAY_MAX_FPS);
cdrom_read_thread = si.GetBoolValue("CDROM", "ReadThread", true); cdrom_readahead_sectors = static_cast<u8>(si.GetIntValue("CDROM", "ReadaheadSectors", DEFAULT_CDROM_READAHEAD_SECTORS));
cdrom_region_check = si.GetBoolValue("CDROM", "RegionCheck", false); cdrom_region_check = si.GetBoolValue("CDROM", "RegionCheck", false);
cdrom_load_image_to_ram = si.GetBoolValue("CDROM", "LoadImageToRAM", false); cdrom_load_image_to_ram = si.GetBoolValue("CDROM", "LoadImageToRAM", false);
cdrom_mute_cd_audio = si.GetBoolValue("CDROM", "MuteCDAudio", 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.SetStringValue("Display", "PostProcessChain", display_post_process_chain.c_str());
si.SetFloatValue("Display", "MaxFPS", display_max_fps); 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", "RegionCheck", cdrom_region_check);
si.SetBoolValue("CDROM", "LoadImageToRAM", cdrom_load_image_to_ram); si.SetBoolValue("CDROM", "LoadImageToRAM", cdrom_load_image_to_ram);
si.SetBoolValue("CDROM", "MuteCDAudio", cdrom_mute_cd_audio); si.SetBoolValue("CDROM", "MuteCDAudio", cdrom_mute_cd_audio);

View file

@ -152,7 +152,7 @@ struct Settings
float gpu_pgxp_tolerance = -1.0f; float gpu_pgxp_tolerance = -1.0f;
float gpu_pgxp_depth_clear_threshold = 300.0f / 4096.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_region_check = false;
bool cdrom_load_image_to_ram = false; bool cdrom_load_image_to_ram = false;
bool cdrom_mute_cd_audio = false; bool cdrom_mute_cd_audio = false;
@ -379,6 +379,9 @@ struct Settings
static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan; static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan;
static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::Auto; 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_1_TYPE = ControllerType::DigitalController;
static constexpr ControllerType DEFAULT_CONTROLLER_2_TYPE = ControllerType::None; static constexpr ControllerType DEFAULT_CONTROLLER_2_TYPE = ControllerType::None;
static constexpr MemoryCardType DEFAULT_MEMORY_CARD_1_TYPE = MemoryCardType::PerGameTitle; static constexpr MemoryCardType DEFAULT_MEMORY_CARD_1_TYPE = MemoryCardType::PerGameTitle;

View file

@ -1,4 +1,5 @@
#include "consolesettingswidget.h" #include "consolesettingswidget.h"
#include "common/cd_image.h"
#include "core/system.h" #include "core/system.h"
#include "qtutils.h" #include "qtutils.h"
#include "settingsdialog.h" #include "settingsdialog.h"
@ -28,6 +29,17 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
qApp->translate("MultitapMode", Settings::GetMultitapModeDisplayName(static_cast<MultitapMode>(i)))); qApp->translate("MultitapMode", Settings::GetMultitapModeDisplayName(static_cast<MultitapMode>(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<float>(i) * TIME_PER_SECTOR_DOUBLE_SPEED, 0, 'f', 0)
.arg(static_cast<float>(i * CDImage::DATA_SECTOR_SIZE) / 1024.0f));
}
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.region, "Console", "Region", SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.region, "Console", "Region",
&Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName, &Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName,
Settings::DEFAULT_CONSOLE_REGION); Settings::DEFAULT_CONSOLE_REGION);
@ -37,7 +49,8 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
Settings::DEFAULT_CPU_EXECUTION_MODE); Settings::DEFAULT_CPU_EXECUTION_MODE);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableCPUClockSpeedControl, "CPU", SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableCPUClockSpeedControl, "CPU",
"OverclockEnable", false); "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.cdromRegionCheck, "CDROM", "RegionCheck", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromLoadImageToRAM, "CDROM", "LoadImageToRAM", SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromLoadImageToRAM, "CDROM", "LoadImageToRAM",
false); false);
@ -74,9 +87,10 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
m_ui.cdromSeekSpeedup, tr("CD-ROM Seek Speedup"), tr("None (Normal Speed)"), 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 " 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.")); "times, but crash games which do not expect the CD-ROM to operate faster."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(m_ui.cdromReadaheadSectors, tr("Asynchronous Readahead"), tr("8 Sectors"),
m_ui.cdromReadThread, tr("Use Read Thread (Asynchronous)"), tr("Checked"), tr("Reduces hitches in emulation by reading/decompressing CD data asynchronously on a "
tr("Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread.")); "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"), dialog->registerWidgetHelp(m_ui.cdromRegionCheck, tr("Enable Region Check"), tr("Checked"),
tr("Simulates the region check present in original, unmodified consoles.")); tr("Simulates the region check present in original, unmodified consoles."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(

View file

@ -137,14 +137,17 @@
<string>CD-ROM Emulation</string> <string>CD-ROM Emulation</string>
</property> </property>
<layout class="QFormLayout" name="formLayout_4"> <layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0"> <item row="0" column="1">
<widget class="QComboBox" name="cdromReadaheadSectors"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
<string>Read Speedup:</string> <string>Read Speedup:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="1" column="1">
<widget class="QComboBox" name="cdromReadSpeedup"> <widget class="QComboBox" name="cdromReadSpeedup">
<item> <item>
<property name="text"> <property name="text">
@ -198,46 +201,14 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="2"> <item row="2" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QCheckBox" name="cdromReadThread">
<property name="text">
<string>Use Read Thread (Asynchronous)</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="cdromRegionCheck">
<property name="text">
<string>Enable Region Check</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="cdromLoadImageToRAM">
<property name="text">
<string>Preload Image To RAM</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="cdromLoadImagePatches">
<property name="text">
<string>Apply Image Patches</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_5">
<property name="text"> <property name="text">
<string>Seek Speedup:</string> <string>Seek Speedup:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="2" column="1">
<widget class="QComboBox" name="cdromSeekSpeedup"> <widget class="QComboBox" name="cdromSeekSpeedup">
<property name="currentIndex"> <property name="currentIndex">
<number>1</number> <number>1</number>
@ -299,6 +270,38 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QCheckBox" name="cdromRegionCheck">
<property name="text">
<string>Enable Region Check</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="cdromLoadImageToRAM">
<property name="text">
<string>Preload Image To RAM</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="cdromLoadImagePatches">
<property name="text">
<string>Apply Image Patches</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Async Readahead:</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View file

@ -1588,10 +1588,16 @@ void DrawSettingsWindow()
}); });
} }
settings_changed |= ToggleButton( s32 readahead_sectors = s_settings_copy.cdrom_readahead_sectors;
"Enable Read Thread", if (RangeButton(
"Readahead Sectors",
"Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread.", "Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread.",
&s_settings_copy.cdrom_read_thread); &readahead_sectors, 0, 32, 1))
{
s_settings_copy.cdrom_readahead_sectors = static_cast<u8>(readahead_sectors);
settings_changed = true;
}
settings_changed |= settings_changed |=
ToggleButton("Enable Region Check", "Simulates the region check present in original, unmodified consoles.", ToggleButton("Enable Region Check", "Simulates the region check present in original, unmodified consoles.",
&s_settings_copy.cdrom_region_check); &s_settings_copy.cdrom_region_check);