CDROM: Implement asynchronous disc reading

This commit is contained in:
Connor McLaughlin 2020-02-22 00:19:10 +09:00
parent 7ece901d57
commit 959a555274
13 changed files with 362 additions and 87 deletions

View File

@ -8,6 +8,8 @@ add_library(core
bus.inl bus.inl
cdrom.cpp cdrom.cpp
cdrom.h cdrom.h
cdrom_async_reader.cpp
cdrom_async_reader.h
controller.cpp controller.cpp
controller.h controller.h
cpu_code_cache.cpp cpu_code_cache.cpp

View File

@ -5,6 +5,7 @@
#include "dma.h" #include "dma.h"
#include "imgui.h" #include "imgui.h"
#include "interrupt_controller.h" #include "interrupt_controller.h"
#include "settings.h"
#include "spu.h" #include "spu.h"
#include "system.h" #include "system.h"
Log_SetChannel(CDROM); Log_SetChannel(CDROM);
@ -26,13 +27,13 @@ void CDROM::Initialize(System* system, DMA* dma, InterruptController* interrupt_
m_system->CreateTimingEvent("CDROM Command Event", 1, 1, std::bind(&CDROM::ExecuteCommand, this), false); m_system->CreateTimingEvent("CDROM Command Event", 1, 1, std::bind(&CDROM::ExecuteCommand, this), false);
m_drive_event = m_system->CreateTimingEvent("CDROM Drive Event", 1, 1, m_drive_event = m_system->CreateTimingEvent("CDROM Drive Event", 1, 1,
std::bind(&CDROM::ExecuteDrive, this, std::placeholders::_2), false); std::bind(&CDROM::ExecuteDrive, this, std::placeholders::_2), false);
if (m_system->GetSettings().cdrom_read_thread)
m_reader.StartThread();
} }
void CDROM::Reset() void CDROM::Reset()
{ {
if (m_media)
m_media->Seek(0);
SoftReset(); SoftReset();
} }
@ -49,7 +50,9 @@ void CDROM::SoftReset()
m_interrupt_flag_register = 0; m_interrupt_flag_register = 0;
m_pending_async_interrupt = 0; m_pending_async_interrupt = 0;
m_setloc_position = {}; m_setloc_position = {};
m_seek_position = {}; m_last_requested_sector = 0;
if (m_reader.HasMedia())
m_reader.QueueReadSector(m_last_requested_sector);
m_setloc_pending = false; m_setloc_pending = false;
m_read_after_seek = false; m_read_after_seek = false;
m_play_after_seek = false; m_play_after_seek = false;
@ -96,7 +99,7 @@ bool CDROM::DoState(StateWrapper& sw)
sw.Do(&m_interrupt_flag_register); sw.Do(&m_interrupt_flag_register);
sw.Do(&m_pending_async_interrupt); sw.Do(&m_pending_async_interrupt);
sw.DoPOD(&m_setloc_position); sw.DoPOD(&m_setloc_position);
sw.DoPOD(&m_seek_position); sw.DoPOD(&m_last_requested_sector);
sw.Do(&m_setloc_pending); sw.Do(&m_setloc_pending);
sw.Do(&m_read_after_seek); sw.Do(&m_read_after_seek);
sw.Do(&m_play_after_seek); sw.Do(&m_play_after_seek);
@ -121,31 +124,25 @@ bool CDROM::DoState(StateWrapper& sw)
sw.Do(&m_data_fifo); sw.Do(&m_data_fifo);
sw.Do(&m_sector_buffer); sw.Do(&m_sector_buffer);
u32 media_lba = m_media ? m_media->GetPositionOnDisc() : 0;
sw.Do(&media_lba);
if (sw.IsReading()) if (sw.IsReading())
{ {
if (m_reader.HasMedia())
m_reader.QueueReadSector(m_last_requested_sector);
UpdateCommandEvent(); UpdateCommandEvent();
m_drive_event->SetState(!IsDriveIdle()); m_drive_event->SetState(!IsDriveIdle());
// load up media if we had something in there before
if (m_media && !m_media->Seek(media_lba))
{
Log_ErrorPrint("Failed to seek CD media from save state. Ejecting.");
RemoveMedia();
}
} }
return !sw.HasError(); return !sw.HasError();
} }
bool CDROM::HasMedia() const
{
return m_reader.HasMedia();
}
std::string CDROM::GetMediaFileName() const std::string CDROM::GetMediaFileName() const
{ {
if (!m_media) return m_reader.GetMediaFileName();
return std::string();
return m_media->GetFileName();
} }
void CDROM::InsertMedia(std::unique_ptr<CDImage> media) void CDROM::InsertMedia(std::unique_ptr<CDImage> media)
@ -153,16 +150,16 @@ void CDROM::InsertMedia(std::unique_ptr<CDImage> media)
if (HasMedia()) if (HasMedia())
RemoveMedia(); RemoveMedia();
m_media = std::move(media); m_reader.SetMedia(std::move(media));
} }
void CDROM::RemoveMedia() void CDROM::RemoveMedia()
{ {
if (!m_media) if (!m_reader.HasMedia())
return; return;
Log_InfoPrintf("Removing CD..."); Log_InfoPrintf("Removing CD...");
m_media.reset(); m_reader.RemoveMedia();
m_secondary_status.shell_open = true; m_secondary_status.shell_open = true;
@ -176,6 +173,17 @@ void CDROM::RemoveMedia()
} }
} }
void CDROM::SetUseReadThread(bool enabled)
{
if (enabled == m_reader.IsUsingThread())
return;
if (enabled)
m_reader.StartThread();
else
m_reader.StopThread();
}
u8 CDROM::ReadRegister(u32 offset) u8 CDROM::ReadRegister(u32 offset)
{ {
switch (offset) switch (offset)
@ -502,7 +510,7 @@ TickCount CDROM::GetTicksForRead() const
TickCount CDROM::GetTicksForSeek() const TickCount CDROM::GetTicksForSeek() const
{ {
const CDImage::LBA current_lba = m_secondary_status.motor_on ? m_media->GetPositionOnDisc() : 0; const CDImage::LBA current_lba = m_secondary_status.motor_on ? m_reader.GetLastReadSector() : 0;
const CDImage::LBA new_lba = m_setloc_position.ToLBA(); const CDImage::LBA new_lba = m_setloc_position.ToLBA();
const u32 lba_diff = static_cast<u32>((new_lba > current_lba) ? (new_lba - current_lba) : (current_lba - new_lba)); const u32 lba_diff = static_cast<u32>((new_lba > current_lba) ? (new_lba - current_lba) : (current_lba - new_lba));
@ -558,7 +566,7 @@ void CDROM::ExecuteCommand()
SendACKAndStat(); SendACKAndStat();
// shell open bit is cleared after sending the status // shell open bit is cleared after sending the status
if (m_media) if (HasMedia())
m_secondary_status.shell_open = false; m_secondary_status.shell_open = false;
EndCommand(); EndCommand();
@ -652,7 +660,7 @@ void CDROM::ExecuteCommand()
{ {
const bool logical = (m_command == Command::SeekL); const bool logical = (m_command == Command::SeekL);
Log_DebugPrintf("CDROM %s command", logical ? "SeekL" : "SeekP"); Log_DebugPrintf("CDROM %s command", logical ? "SeekL" : "SeekP");
if (!m_media) if (!HasMedia())
{ {
SendErrorResponse(0x80); SendErrorResponse(0x80);
} }
@ -670,7 +678,7 @@ void CDROM::ExecuteCommand()
case Command::ReadS: case Command::ReadS:
{ {
Log_DebugPrintf("CDROM read command"); Log_DebugPrintf("CDROM read command");
if (!m_media) if (!HasMedia())
{ {
SendErrorResponse(0x80); SendErrorResponse(0x80);
} }
@ -689,7 +697,7 @@ void CDROM::ExecuteCommand()
u8 track = m_param_fifo.IsEmpty() ? 0 : m_param_fifo.Peek(0); u8 track = m_param_fifo.IsEmpty() ? 0 : m_param_fifo.Peek(0);
Log_DebugPrintf("CDROM play command, track=%u", track); Log_DebugPrintf("CDROM play command, track=%u", track);
if (!m_media) if (!HasMedia())
{ {
SendErrorResponse(0x80); SendErrorResponse(0x80);
} }
@ -825,11 +833,13 @@ void CDROM::ExecuteCommand()
case Command::GetTN: case Command::GetTN:
{ {
Log_DebugPrintf("CDROM GetTN command"); Log_DebugPrintf("CDROM GetTN command");
if (m_media) if (HasMedia())
{ {
m_reader.WaitForReadToComplete();
m_response_fifo.Push(m_secondary_status.bits); m_response_fifo.Push(m_secondary_status.bits);
m_response_fifo.Push(BinaryToBCD(Truncate8(m_media->GetTrackNumber()))); m_response_fifo.Push(BinaryToBCD(Truncate8(m_reader.GetMedia()->GetTrackNumber())));
m_response_fifo.Push(BinaryToBCD(Truncate8(m_media->GetTrackCount()))); m_response_fifo.Push(BinaryToBCD(Truncate8(m_reader.GetMedia()->GetTrackCount())));
SetInterrupt(Interrupt::ACK); SetInterrupt(Interrupt::ACK);
} }
else else
@ -847,11 +857,11 @@ void CDROM::ExecuteCommand()
Assert(m_param_fifo.GetSize() >= 1); Assert(m_param_fifo.GetSize() >= 1);
const u8 track = PackedBCDToBinary(m_param_fifo.Peek()); const u8 track = PackedBCDToBinary(m_param_fifo.Peek());
if (!m_media) if (!HasMedia())
{ {
SendErrorResponse(0x80); SendErrorResponse(0x80);
} }
else if (track > m_media->GetTrackCount()) else if (track > m_reader.GetMedia()->GetTrackCount())
{ {
SendErrorResponse(0x10); SendErrorResponse(0x10);
} }
@ -859,9 +869,9 @@ void CDROM::ExecuteCommand()
{ {
CDImage::Position pos; CDImage::Position pos;
if (track == 0) if (track == 0)
pos = CDImage::Position::FromLBA(m_media->GetLBACount()); pos = CDImage::Position::FromLBA(m_reader.GetMedia()->GetLBACount());
else else
pos = m_media->GetTrackStartMSFPosition(track); pos = m_reader.GetMedia()->GetTrackStartMSFPosition(track);
m_response_fifo.Push(m_secondary_status.bits); m_response_fifo.Push(m_secondary_status.bits);
m_response_fifo.Push(BinaryToBCD(Truncate8(pos.minute))); m_response_fifo.Push(BinaryToBCD(Truncate8(pos.minute)));
@ -1005,7 +1015,7 @@ void CDROM::ExecuteDrive(TickCount ticks_late)
void CDROM::BeginReading(TickCount ticks_late) void CDROM::BeginReading(TickCount ticks_late)
{ {
Log_DebugPrintf("Starting reading"); Log_DebugPrintf("Starting reading @ LBA %u", m_last_requested_sector);
if (m_setloc_pending) if (m_setloc_pending)
{ {
BeginSeeking(true, true, false); BeginSeeking(true, true, false);
@ -1022,6 +1032,8 @@ void CDROM::BeginReading(TickCount ticks_late)
m_drive_state = DriveState::Reading; m_drive_state = DriveState::Reading;
m_drive_event->SetInterval(ticks); m_drive_event->SetInterval(ticks);
m_drive_event->Schedule(ticks - ticks_late); m_drive_event->Schedule(ticks - ticks_late);
m_reader.QueueReadSector(m_last_requested_sector);
} }
void CDROM::BeginPlaying(u8 track_bcd, TickCount ticks_late) void CDROM::BeginPlaying(u8 track_bcd, TickCount ticks_late)
@ -1034,13 +1046,13 @@ void CDROM::BeginPlaying(u8 track_bcd, TickCount ticks_late)
if (track_bcd != 0) if (track_bcd != 0)
{ {
// play specific track? // play specific track?
if (track_bcd > m_media->GetTrackCount()) if (track_bcd > m_reader.GetMedia()->GetTrackCount())
{ {
// restart current track // restart current track
track_bcd = BinaryToBCD(Truncate8(m_media->GetTrackNumber())); track_bcd = BinaryToBCD(Truncate8(m_reader.GetMedia()->GetTrackNumber()));
} }
m_setloc_position = m_media->GetTrackStartMSFPosition(PackedBCDToBinary(track_bcd)); m_setloc_position = m_reader.GetMedia()->GetTrackStartMSFPosition(PackedBCDToBinary(track_bcd));
m_setloc_pending = true; m_setloc_pending = true;
} }
@ -1060,6 +1072,8 @@ void CDROM::BeginPlaying(u8 track_bcd, TickCount ticks_late)
m_drive_state = DriveState::Playing; m_drive_state = DriveState::Playing;
m_drive_event->SetInterval(ticks); m_drive_event->SetInterval(ticks);
m_drive_event->Schedule(ticks - ticks_late); m_drive_event->Schedule(ticks - ticks_late);
m_reader.QueueReadSector(m_last_requested_sector);
} }
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)
@ -1067,13 +1081,12 @@ void CDROM::BeginSeeking(bool logical, bool read_after_seek, bool play_after_see
if (!m_setloc_pending) if (!m_setloc_pending)
Log_WarningPrintf("Seeking without setloc set"); Log_WarningPrintf("Seeking without setloc set");
m_seek_position = m_setloc_position;
m_read_after_seek = read_after_seek; m_read_after_seek = read_after_seek;
m_play_after_seek = play_after_seek; m_play_after_seek = play_after_seek;
m_setloc_pending = false; m_setloc_pending = false;
Log_DebugPrintf("Seeking to [%02u:%02u:%02u] (%s)", m_seek_position.minute, m_seek_position.second, Log_DebugPrintf("Seeking to [%02u:%02u:%02u] (LBA %u) (%s)", m_setloc_position.minute, m_setloc_position.second,
m_seek_position.frame, logical ? "logical" : "physical"); m_setloc_position.frame, m_setloc_position.ToLBA(), logical ? "logical" : "physical");
const TickCount seek_time = GetTicksForSeek(); const TickCount seek_time = GetTicksForSeek();
@ -1084,11 +1097,8 @@ void CDROM::BeginSeeking(bool logical, bool read_after_seek, bool play_after_see
m_drive_state = logical ? DriveState::SeekingLogical : DriveState::SeekingPhysical; m_drive_state = logical ? DriveState::SeekingLogical : DriveState::SeekingPhysical;
m_drive_event->SetIntervalAndSchedule(seek_time); m_drive_event->SetIntervalAndSchedule(seek_time);
// Read sub-q early.. this is because we're not reading sectors while seeking. m_last_requested_sector = m_setloc_position.ToLBA();
// Fixes music looping in Spyro. m_reader.QueueReadSector(m_last_requested_sector);
CDImage::SubChannelQ subq;
if (m_media->Seek(m_seek_position) && m_media->ReadSubChannelQ(&subq) && subq.IsCRCValid())
m_last_subq = subq;
} }
void CDROM::DoSpinUpComplete() void CDROM::DoSpinUpComplete()
@ -1111,21 +1121,22 @@ void CDROM::DoSeekComplete(TickCount ticks_late)
m_secondary_status.ClearActiveBits(); m_secondary_status.ClearActiveBits();
m_sector_buffer.clear(); m_sector_buffer.clear();
bool seek_okay = m_reader.WaitForReadToComplete();
if (seek_okay)
{
m_last_subq = m_reader.GetSectorSubQ();
// seek and update sub-q for ReadP command // seek and update sub-q for ReadP command
const auto [seek_mm, seek_ss, seek_ff] = m_seek_position.ToBCD(); DebugAssert(m_last_requested_sector == m_reader.GetLastReadSector());
bool seek_okay = (m_last_subq.absolute_minute_bcd == seek_mm && m_last_subq.absolute_second_bcd == seek_ss && const auto [seek_mm, seek_ss, seek_ff] = CDImage::Position::FromLBA(m_last_requested_sector).ToBCD();
m_last_subq.absolute_frame_bcd == seek_ff); seek_okay = (m_last_subq.IsCRCValid() && m_last_subq.absolute_minute_bcd == seek_mm &&
m_last_subq.absolute_second_bcd == seek_ss && m_last_subq.absolute_frame_bcd == seek_ff);
if (seek_okay) if (seek_okay)
{ {
// check for data header for logical seeks // check for data header for logical seeks
if (logical) if (logical)
{ {
u8 raw_sector[CDImage::RAW_SECTOR_SIZE]; ProcessDataSectorHeader(m_reader.GetSectorBuffer().data(), false);
seek_okay &= m_media->ReadRawSector(raw_sector);
seek_okay &= m_media->Seek(m_media->GetPositionOnDisc() - 1);
if (seek_okay)
{
ProcessDataSectorHeader(raw_sector, false);
// ensure the location matches up (it should) // ensure the location matches up (it should)
seek_okay = (m_last_sector_header.minute == seek_mm && m_last_sector_header.second == seek_ss && seek_okay = (m_last_sector_header.minute == seek_mm && m_last_sector_header.second == seek_ss &&
@ -1154,8 +1165,9 @@ void CDROM::DoSeekComplete(TickCount ticks_late)
} }
else else
{ {
Log_WarningPrintf("%s seek to [%02u:%02u:%02u] failed", logical ? "Logical" : "Physical", m_seek_position.minute, CDImage::Position pos(CDImage::Position::FromLBA(m_last_requested_sector));
m_seek_position.second, m_seek_position.frame); Log_WarningPrintf("%s seek to [%02u:%02u:%02u] failed", logical ? "Logical" : "Physical", pos.minute, pos.second,
pos.frame);
m_secondary_status.seek_error = true; m_secondary_status.seek_error = true;
SendAsyncErrorResponse(0x80); SendAsyncErrorResponse(0x80);
} }
@ -1188,7 +1200,7 @@ void CDROM::DoStopComplete()
m_secondary_status.motor_on = false; m_secondary_status.motor_on = false;
m_sector_buffer.clear(); m_sector_buffer.clear();
m_media->Seek(0); m_reader.QueueReadSector(0);
m_async_response_fifo.Clear(); m_async_response_fifo.Clear();
m_async_response_fifo.Push(m_secondary_status.bits); m_async_response_fifo.Push(m_secondary_status.bits);
@ -1225,12 +1237,12 @@ void CDROM::DoTOCRead()
void CDROM::DoSectorRead() void CDROM::DoSectorRead()
{ {
if (!m_reader.WaitForReadToComplete())
Panic("Sector read failed");
// TODO: Error handling // TODO: Error handling
// TODO: Check SubQ checksum. // TODO: Check SubQ checksum.
CDImage::SubChannelQ subq; const CDImage::SubChannelQ& subq = m_reader.GetSectorSubQ();
if (!m_media->ReadSubChannelQ(&subq))
Panic("SubChannel Q read failed");
const bool is_data_sector = subq.control.data; const bool is_data_sector = subq.control.data;
m_secondary_status.playing_cdda = !is_data_sector; m_secondary_status.playing_cdda = !is_data_sector;
if (!is_data_sector) if (!is_data_sector)
@ -1245,7 +1257,7 @@ void CDROM::DoSectorRead()
{ {
// we don't want to update the position if the track changes, so we check it before reading the actual sector. // we don't want to update the position if the track changes, so we check it before reading the actual sector.
Log_DevPrintf("Auto pause at the end of track %u (LBA %u)", m_play_track_number_bcd, Log_DevPrintf("Auto pause at the end of track %u (LBA %u)", m_play_track_number_bcd,
m_media->GetPositionOnDisc()); m_reader.GetLastReadSector());
ClearAsyncInterrupt(); ClearAsyncInterrupt();
m_async_response_fifo.Push(m_secondary_status.bits); m_async_response_fifo.Push(m_secondary_status.bits);
@ -1258,21 +1270,17 @@ void CDROM::DoSectorRead()
} }
} }
u8 raw_sector[CDImage::RAW_SECTOR_SIZE];
if (!m_media->ReadRawSector(raw_sector))
Panic("Sector read failed");
if (subq.IsCRCValid()) if (subq.IsCRCValid())
{ {
m_last_subq = subq; m_last_subq = subq;
if (is_data_sector && m_drive_state == DriveState::Reading) if (is_data_sector && m_drive_state == DriveState::Reading)
{ {
ProcessDataSector(raw_sector, subq); ProcessDataSector(m_reader.GetSectorBuffer().data(), subq);
} }
else if (!is_data_sector && m_drive_state == DriveState::Playing) else if (!is_data_sector && m_drive_state == DriveState::Playing)
{ {
ProcessCDDASector(raw_sector, subq); ProcessCDDASector(m_reader.GetSectorBuffer().data(), subq);
} }
else if (m_drive_state != DriveState::Reading && m_drive_state != DriveState::Playing) else if (m_drive_state != DriveState::Reading && m_drive_state != DriveState::Playing)
{ {
@ -1280,16 +1288,19 @@ void CDROM::DoSectorRead()
} }
else else
{ {
Log_WarningPrintf("Skipping sector %u as it is a %s sector and we're not %s", m_media->GetPositionOnDisc() - 1, Log_WarningPrintf("Skipping sector %u as it is a %s sector and we're not %s", m_reader.GetLastReadSector(),
is_data_sector ? "data" : "audio", is_data_sector ? "reading" : "playing"); is_data_sector ? "data" : "audio", is_data_sector ? "reading" : "playing");
} }
} }
else else
{ {
const CDImage::Position pos(CDImage::Position::FromLBA(m_media->GetPositionOnDisc() - 1)); const CDImage::Position pos(CDImage::Position::FromLBA(m_reader.GetLastReadSector()));
Log_DevPrintf("Skipping sector %u [%02u:%02u:%02u] due to invalid subchannel Q", m_media->GetPositionOnDisc() - 1, Log_DevPrintf("Skipping sector %u [%02u:%02u:%02u] due to invalid subchannel Q", m_reader.GetLastReadSector(),
pos.minute, pos.second, pos.frame); pos.minute, pos.second, pos.frame);
} }
m_last_requested_sector++;
m_reader.QueueReadSector(m_last_requested_sector);
} }
void CDROM::ProcessDataSectorHeader(const u8* raw_sector, bool set_valid) void CDROM::ProcessDataSectorHeader(const u8* raw_sector, bool set_valid)
@ -1304,7 +1315,7 @@ void CDROM::ProcessDataSector(const u8* raw_sector, const CDImage::SubChannelQ&
{ {
ProcessDataSectorHeader(raw_sector, true); ProcessDataSectorHeader(raw_sector, true);
Log_DevPrintf("Read sector %u: mode %u submode 0x%02X", m_media->GetPositionOnDisc() - 1, Log_DevPrintf("Read sector %u: mode %u submode 0x%02X", m_last_requested_sector,
ZeroExtend32(m_last_sector_header.sector_mode), ZeroExtend32(m_last_sector_subheader.submode.bits)); ZeroExtend32(m_last_sector_header.sector_mode), ZeroExtend32(m_last_sector_subheader.submode.bits));
if (m_mode.xa_enable && m_last_sector_header.sector_mode == 2) if (m_mode.xa_enable && m_last_sector_header.sector_mode == 2)
@ -1503,7 +1514,7 @@ void CDROM::ProcessXAADPCMSector(const u8* raw_sector, const CDImage::SubChannel
void CDROM::ProcessCDDASector(const u8* raw_sector, const CDImage::SubChannelQ& subq) void CDROM::ProcessCDDASector(const u8* raw_sector, const CDImage::SubChannelQ& subq)
{ {
// For CDDA sectors, the whole sector contains the audio data. // For CDDA sectors, the whole sector contains the audio data.
Log_DevPrintf("Read sector %u as CDDA", m_media->GetPositionOnDisc()); Log_DevPrintf("Read sector %u as CDDA", m_last_requested_sector);
if (m_mode.report_audio) if (m_mode.report_audio)
{ {
@ -1594,16 +1605,17 @@ void CDROM::DrawDebugWindow()
// draw voice states // draw voice states
if (ImGui::CollapsingHeader("Media", ImGuiTreeNodeFlags_DefaultOpen)) if (ImGui::CollapsingHeader("Media", ImGuiTreeNodeFlags_DefaultOpen))
{ {
if (m_media) if (HasMedia())
{ {
const auto [disc_minute, disc_second, disc_frame] = m_media->GetMSFPositionOnDisc(); const CDImage* media = m_reader.GetMedia();
const auto [track_minute, track_second, track_frame] = m_media->GetMSFPositionInTrack(); const auto [disc_minute, disc_second, disc_frame] = media->GetMSFPositionOnDisc();
const auto [track_minute, track_second, track_frame] = media->GetMSFPositionInTrack();
ImGui::Text("Filename: %s", m_media->GetFileName().c_str()); ImGui::Text("Filename: %s", media->GetFileName().c_str());
ImGui::Text("Disc Position: MSF[%02u:%02u:%02u] LBA[%u]", disc_minute, disc_second, disc_frame, ImGui::Text("Disc Position: MSF[%02u:%02u:%02u] LBA[%u]", disc_minute, disc_second, disc_frame,
m_media->GetPositionOnDisc()); media->GetPositionOnDisc());
ImGui::Text("Track Position: Number[%u] MSF[%02u:%02u:%02u] LBA[%u]", m_media->GetTrackNumber(), track_minute, ImGui::Text("Track Position: Number[%u] MSF[%02u:%02u:%02u] LBA[%u]", media->GetTrackNumber(), track_minute,
track_second, track_frame, m_media->GetPositionInTrack()); track_second, track_frame, media->GetPositionInTrack());
ImGui::Text("Last Sector: %02X:%02X:%02X (Mode %u)", m_last_sector_header.minute, m_last_sector_header.second, ImGui::Text("Last Sector: %02X:%02X:%02X (Mode %u)", m_last_sector_header.minute, m_last_sector_header.second,
m_last_sector_header.frame, m_last_sector_header.sector_mode); m_last_sector_header.frame, m_last_sector_header.sector_mode);
} }

View File

@ -4,6 +4,7 @@
#include "common/cd_xa.h" #include "common/cd_xa.h"
#include "common/fifo_queue.h" #include "common/fifo_queue.h"
#include "common/heap_array.h" #include "common/heap_array.h"
#include "cdrom_async_reader.h"
#include "types.h" #include "types.h"
#include <array> #include <array>
#include <string> #include <string>
@ -27,7 +28,7 @@ public:
void Reset(); void Reset();
bool DoState(StateWrapper& sw); bool DoState(StateWrapper& sw);
bool HasMedia() const { return static_cast<bool>(m_media); } bool HasMedia() const;
std::string GetMediaFileName() const; std::string GetMediaFileName() const;
void InsertMedia(std::unique_ptr<CDImage> media); void InsertMedia(std::unique_ptr<CDImage> media);
void RemoveMedia(); void RemoveMedia();
@ -40,6 +41,8 @@ public:
// Render statistics debug window. // Render statistics debug window.
void DrawDebugWindow(); void DrawDebugWindow();
void SetUseReadThread(bool enabled);
private: private:
enum : u32 enum : u32
{ {
@ -219,7 +222,6 @@ private:
DMA* m_dma = nullptr; DMA* m_dma = nullptr;
InterruptController* m_interrupt_controller = nullptr; InterruptController* m_interrupt_controller = nullptr;
SPU* m_spu = nullptr; SPU* m_spu = nullptr;
std::unique_ptr<CDImage> m_media;
std::unique_ptr<TimingEvent> m_command_event; std::unique_ptr<TimingEvent> m_command_event;
std::unique_ptr<TimingEvent> m_drive_event; std::unique_ptr<TimingEvent> m_drive_event;
@ -235,7 +237,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::Position m_seek_position = {}; CDImage::LBA m_last_requested_sector{};
bool m_setloc_pending = false; bool m_setloc_pending = false;
bool m_read_after_seek = false; bool m_read_after_seek = false;
bool m_play_after_seek = false; bool m_play_after_seek = false;
@ -265,4 +267,6 @@ private:
InlineFIFOQueue<u8, RESPONSE_FIFO_SIZE> m_async_response_fifo; InlineFIFOQueue<u8, RESPONSE_FIFO_SIZE> m_async_response_fifo;
HeapFIFOQueue<u8, DATA_FIFO_SIZE> m_data_fifo; HeapFIFOQueue<u8, DATA_FIFO_SIZE> m_data_fifo;
std::vector<u8> m_sector_buffer; std::vector<u8> m_sector_buffer;
CDROMAsyncReader m_reader;
}; };

View File

@ -0,0 +1,162 @@
#include "cdrom_async_reader.h"
#include "common/assert.h"
#include "common/log.h"
#include "common/timer.h"
Log_SetChannel(CDROMAsyncReader);
CDROMAsyncReader::CDROMAsyncReader() = default;
CDROMAsyncReader::~CDROMAsyncReader()
{
StopThread();
}
void CDROMAsyncReader::StartThread()
{
if (IsUsingThread())
return;
m_shutdown_flag.store(false);
m_read_thread = std::thread(&CDROMAsyncReader::WorkerThreadEntryPoint, this);
}
void CDROMAsyncReader::StopThread()
{
if (!IsUsingThread())
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_shutdown_flag.store(true);
m_do_read_cv.notify_one();
}
m_read_thread.join();
}
void CDROMAsyncReader::SetMedia(std::unique_ptr<CDImage> media)
{
WaitForReadToComplete();
m_media = std::move(media);
}
void CDROMAsyncReader::RemoveMedia()
{
WaitForReadToComplete();
m_media.reset();
}
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();
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(); });
// 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())
{
Log_DebugPrintf("Skipping re-reading same sector %u", lba);
return;
}
m_sector_read_pending.store(true);
m_next_position_set.store(true);
m_next_position = lba;
m_do_read_cv.notify_one();
}
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()
{
if (!IsUsingThread())
return m_sector_read_result.load();
std::unique_lock<std::mutex> 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_sector_read_pending.load(); });
}
return m_sector_read_result.load();
}
void CDROMAsyncReader::DoSectorRead()
{
#ifdef _DEBUG
Common::Timer timer;
#endif
if (m_next_position_set.load())
{
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;
}
}
CDImage::LBA pos = m_media->GetPositionOnDisc();
if (!m_media->ReadSubChannelQ(&m_subq) || !m_media->ReadRawSector(m_sector_buffer.data()))
{
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);
#ifdef _DEBUG
if (timer.GetTimeMilliseconds() > 1.0f)
Log_WarningPrintf("Read LBA %u took %.2f msec", pos, timer.GetTimeMilliseconds());
#endif
}
void CDROMAsyncReader::WorkerThreadEntryPoint()
{
std::unique_lock lock(m_mutex);
while (!m_shutdown_flag.load())
{
m_do_read_cv.wait(lock, [this]() { return (m_shutdown_flag.load() || m_sector_read_pending.load()); });
if (m_sector_read_pending.load())
{
lock.unlock();
DoSectorRead();
lock.lock();
m_sector_read_pending.store(false);
m_notify_read_complete_cv.notify_one();
}
}
}

View File

@ -0,0 +1,56 @@
#pragma once
#include "common/cd_image.h"
#include "types.h"
#include <array>
#include <atomic>
#include <condition_variable>
#include <thread>
class CDROMAsyncReader
{
public:
using SectorBuffer = std::array<u8, CDImage::RAW_SECTOR_SIZE>;
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 bool HasMedia() const { return static_cast<bool>(m_media); }
const CDImage* GetMedia() const { return m_media.get(); }
const std::string GetMediaFileName() const { return m_media ? m_media->GetFileName() : std::string(); }
bool IsUsingThread() const { return m_read_thread.joinable(); }
void StartThread();
void StopThread();
void SetMedia(std::unique_ptr<CDImage> media);
void RemoveMedia();
void QueueReadSector(CDImage::LBA lba);
void QueueReadNextSector();
bool WaitForReadToComplete();
private:
void DoSectorRead();
void WorkerThreadEntryPoint();
std::unique_ptr<CDImage> m_media;
std::mutex m_mutex;
std::thread m_read_thread;
std::condition_variable m_do_read_cv;
std::condition_variable m_notify_read_complete_cv;
CDImage::LBA 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};
};

View File

@ -39,6 +39,7 @@
<ClCompile Include="bios.cpp" /> <ClCompile Include="bios.cpp" />
<ClCompile Include="bus.cpp" /> <ClCompile Include="bus.cpp" />
<ClCompile Include="cdrom.cpp" /> <ClCompile Include="cdrom.cpp" />
<ClCompile Include="cdrom_async_reader.cpp" />
<ClCompile Include="cpu_core.cpp" /> <ClCompile Include="cpu_core.cpp" />
<ClCompile Include="cpu_disasm.cpp" /> <ClCompile Include="cpu_disasm.cpp" />
<ClCompile Include="cpu_code_cache.cpp" /> <ClCompile Include="cpu_code_cache.cpp" />
@ -89,6 +90,7 @@
<ClInclude Include="bios.h" /> <ClInclude Include="bios.h" />
<ClInclude Include="bus.h" /> <ClInclude Include="bus.h" />
<ClInclude Include="cdrom.h" /> <ClInclude Include="cdrom.h" />
<ClInclude Include="cdrom_async_reader.h" />
<ClInclude Include="cpu_core.h" /> <ClInclude Include="cpu_core.h" />
<ClInclude Include="cpu_disasm.h" /> <ClInclude Include="cpu_disasm.h" />
<ClInclude Include="cpu_code_cache.h" /> <ClInclude Include="cpu_code_cache.h" />

View File

@ -40,6 +40,7 @@
<ClCompile Include="analog_controller.cpp" /> <ClCompile Include="analog_controller.cpp" />
<ClCompile Include="host_display.cpp" /> <ClCompile Include="host_display.cpp" />
<ClCompile Include="timing_event.cpp" /> <ClCompile Include="timing_event.cpp" />
<ClCompile Include="cdrom_async_reader.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="types.h" /> <ClInclude Include="types.h" />
@ -81,6 +82,7 @@
<ClInclude Include="controller.h" /> <ClInclude Include="controller.h" />
<ClInclude Include="analog_controller.h" /> <ClInclude Include="analog_controller.h" />
<ClInclude Include="timing_event.h" /> <ClInclude Include="timing_event.h" />
<ClInclude Include="cdrom_async_reader.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="cpu_core.inl" /> <None Include="cpu_core.inl" />

View File

@ -771,6 +771,8 @@ void HostInterface::SetDefaultSettings()
m_settings.display_fullscreen = false; m_settings.display_fullscreen = false;
m_settings.video_sync_enabled = true; m_settings.video_sync_enabled = true;
m_settings.cdrom_read_thread = true;
m_settings.audio_backend = AudioBackend::Cubeb; m_settings.audio_backend = AudioBackend::Cubeb;
m_settings.audio_sync_enabled = true; m_settings.audio_sync_enabled = true;
@ -800,6 +802,7 @@ void HostInterface::UpdateSettings(const std::function<void()>& apply_callback)
const bool old_audio_sync_enabled = m_settings.audio_sync_enabled; const bool old_audio_sync_enabled = m_settings.audio_sync_enabled;
const bool old_speed_limiter_enabled = m_settings.speed_limiter_enabled; const bool old_speed_limiter_enabled = m_settings.speed_limiter_enabled;
const bool old_display_linear_filtering = m_settings.display_linear_filtering; const bool old_display_linear_filtering = m_settings.display_linear_filtering;
const bool old_cdrom_read_thread = m_settings.cdrom_read_thread;
std::array<ControllerType, NUM_CONTROLLER_AND_CARD_PORTS> old_controller_types = m_settings.controller_types; std::array<ControllerType, NUM_CONTROLLER_AND_CARD_PORTS> old_controller_types = m_settings.controller_types;
apply_callback(); apply_callback();
@ -847,6 +850,9 @@ void HostInterface::UpdateSettings(const std::function<void()>& apply_callback)
{ {
m_system->UpdateGPUSettings(); m_system->UpdateGPUSettings();
} }
if (m_settings.cdrom_read_thread != old_cdrom_read_thread)
m_system->GetCDROM()->SetUseReadThread(m_settings.cdrom_read_thread);
} }
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)

View File

@ -30,6 +30,8 @@ void Settings::Load(SettingsInterface& si)
display_fullscreen = si.GetBoolValue("Display", "Fullscreen", false); display_fullscreen = si.GetBoolValue("Display", "Fullscreen", false);
video_sync_enabled = si.GetBoolValue("Display", "VSync", true); video_sync_enabled = si.GetBoolValue("Display", "VSync", true);
cdrom_read_thread = si.GetBoolValue("CDROM", "ReadThread", true);
audio_backend = audio_backend =
ParseAudioBackend(si.GetStringValue("Audio", "Backend", "Cubeb").c_str()).value_or(AudioBackend::Cubeb); ParseAudioBackend(si.GetStringValue("Audio", "Backend", "Cubeb").c_str()).value_or(AudioBackend::Cubeb);
audio_sync_enabled = si.GetBoolValue("Audio", "Sync", true); audio_sync_enabled = si.GetBoolValue("Audio", "Sync", true);
@ -79,6 +81,8 @@ void Settings::Save(SettingsInterface& si) const
si.SetBoolValue("Display", "Fullscreen", display_fullscreen); si.SetBoolValue("Display", "Fullscreen", display_fullscreen);
si.SetBoolValue("Display", "VSync", video_sync_enabled); si.SetBoolValue("Display", "VSync", video_sync_enabled);
si.SetBoolValue("CDROM", "ReadThread", cdrom_read_thread);
si.SetStringValue("Audio", "Backend", GetAudioBackendName(audio_backend)); si.SetStringValue("Audio", "Backend", GetAudioBackendName(audio_backend));
si.SetBoolValue("Audio", "Sync", audio_sync_enabled); si.SetBoolValue("Audio", "Sync", audio_sync_enabled);

View File

@ -52,6 +52,8 @@ struct Settings
bool display_fullscreen = false; bool display_fullscreen = false;
bool video_sync_enabled = true; bool video_sync_enabled = true;
bool cdrom_read_thread = true;
AudioBackend audio_backend = AudioBackend::Cubeb; AudioBackend audio_backend = AudioBackend::Cubeb;
bool audio_sync_enabled = true; bool audio_sync_enabled = true;

View File

@ -30,6 +30,7 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.saveStateOnExit, "General/SaveStateOnExit"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.saveStateOnExit, "General/SaveStateOnExit");
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.cpuExecutionMode, "CPU/ExecutionMode", SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.cpuExecutionMode, "CPU/ExecutionMode",
&Settings::ParseCPUExecutionMode, &Settings::GetCPUExecutionModeName); &Settings::ParseCPUExecutionMode, &Settings::GetCPUExecutionModeName);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromReadThread, "CDROM/ReadThread");
connect(m_ui.biosPathBrowse, &QPushButton::pressed, this, &ConsoleSettingsWidget::onBrowseBIOSPathButtonClicked); connect(m_ui.biosPathBrowse, &QPushButton::pressed, this, &ConsoleSettingsWidget::onBrowseBIOSPathButtonClicked);

View File

@ -182,6 +182,22 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>CDROM Emulation</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="cdromReadThread">
<property name="text">
<string>Use Read Thread (Asynchronous)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">

View File

@ -971,6 +971,12 @@ void SDLHostInterface::DrawSettingsWindow()
settings_changed |= ImGui::Checkbox("Save State On Exit", &m_settings.save_state_on_exit); settings_changed |= ImGui::Checkbox("Save State On Exit", &m_settings.save_state_on_exit);
} }
ImGui::NewLine();
if (DrawSettingsSectionHeader("CDROM Emulation"))
{
settings_changed |= ImGui::Checkbox("Use Read Thread (Asynchronous)", &m_settings.cdrom_read_thread);
}
ImGui::NewLine(); ImGui::NewLine();
if (DrawSettingsSectionHeader("Audio")) if (DrawSettingsSectionHeader("Audio"))
{ {