Implement event-based scheduler instead of lock-step components
This commit is contained in:
parent
624888e131
commit
1b9609ef61
|
@ -59,34 +59,34 @@ void AudioStream::Shutdown()
|
|||
m_output_paused = true;
|
||||
}
|
||||
|
||||
void AudioStream::BeginWrite(SampleType** buffer_ptr, u32* num_samples)
|
||||
void AudioStream::BeginWrite(SampleType** buffer_ptr, u32* num_frames)
|
||||
{
|
||||
m_buffer_mutex.lock();
|
||||
|
||||
EnsureBuffer();
|
||||
|
||||
Buffer& buffer = m_buffers[m_first_free_buffer];
|
||||
*buffer_ptr = buffer.data.data() + buffer.write_position;
|
||||
*num_samples = m_buffer_size - buffer.write_position;
|
||||
*buffer_ptr = buffer.data.data() + (buffer.write_position * m_channels);
|
||||
*num_frames = m_buffer_size - buffer.write_position;
|
||||
}
|
||||
|
||||
void AudioStream::WriteSamples(const SampleType* samples, u32 num_samples)
|
||||
void AudioStream::WriteFrames(const SampleType* frames, u32 num_frames)
|
||||
{
|
||||
u32 remaining_samples = num_samples;
|
||||
u32 remaining_frames = num_frames;
|
||||
std::unique_lock<std::mutex> lock(m_buffer_mutex);
|
||||
|
||||
while (remaining_samples > 0)
|
||||
while (remaining_frames > 0)
|
||||
{
|
||||
EnsureBuffer();
|
||||
|
||||
Buffer& buffer = m_buffers[m_first_free_buffer];
|
||||
const u32 to_this_buffer = std::min(m_buffer_size - buffer.write_position, remaining_samples);
|
||||
const u32 to_this_buffer = std::min(m_buffer_size - buffer.write_position, remaining_frames);
|
||||
|
||||
const u32 copy_count = to_this_buffer * m_channels;
|
||||
std::memcpy(&buffer.data[buffer.write_position * m_channels], samples, copy_count * sizeof(SampleType));
|
||||
samples += copy_count;
|
||||
std::memcpy(&buffer.data[buffer.write_position * m_channels], frames, copy_count * sizeof(SampleType));
|
||||
frames += copy_count;
|
||||
|
||||
remaining_samples -= to_this_buffer;
|
||||
remaining_frames -= to_this_buffer;
|
||||
buffer.write_position += to_this_buffer;
|
||||
|
||||
// End of the buffer?
|
||||
|
@ -102,11 +102,11 @@ void AudioStream::WriteSamples(const SampleType* samples, u32 num_samples)
|
|||
}
|
||||
}
|
||||
|
||||
void AudioStream::EndWrite(u32 num_samples)
|
||||
void AudioStream::EndWrite(u32 num_frames)
|
||||
{
|
||||
Buffer& buffer = m_buffers[m_first_free_buffer];
|
||||
DebugAssert((buffer.write_position + num_samples) <= m_buffer_size);
|
||||
buffer.write_position += num_samples;
|
||||
DebugAssert((buffer.write_position + num_frames) <= m_buffer_size);
|
||||
buffer.write_position += num_frames;
|
||||
|
||||
// End of the buffer?
|
||||
if (buffer.write_position == m_buffer_size)
|
||||
|
|
|
@ -37,9 +37,9 @@ public:
|
|||
|
||||
void Shutdown();
|
||||
|
||||
void BeginWrite(SampleType** buffer_ptr, u32* num_samples);
|
||||
void WriteSamples(const SampleType* samples, u32 num_samples);
|
||||
void EndWrite(u32 num_samples);
|
||||
void BeginWrite(SampleType** buffer_ptr, u32* num_frames);
|
||||
void WriteFrames(const SampleType* frames, u32 num_frames);
|
||||
void EndWrite(u32 num_frames);
|
||||
|
||||
static std::unique_ptr<AudioStream> CreateNullAudioStream();
|
||||
|
||||
|
|
|
@ -65,6 +65,8 @@ add_library(core
|
|||
system.h
|
||||
timers.cpp
|
||||
timers.h
|
||||
timing_event.cpp
|
||||
timing_event.h
|
||||
types.h
|
||||
)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include "cdrom.h"
|
||||
#include "common/log.h"
|
||||
#include "common/cd_image.h"
|
||||
#include "common/log.h"
|
||||
#include "common/state_wrapper.h"
|
||||
#include "dma.h"
|
||||
#include "imgui.h"
|
||||
|
@ -22,6 +22,10 @@ void CDROM::Initialize(System* system, DMA* dma, InterruptController* interrupt_
|
|||
m_dma = dma;
|
||||
m_interrupt_controller = interrupt_controller;
|
||||
m_spu = spu;
|
||||
m_command_event =
|
||||
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,
|
||||
std::bind(&CDROM::ExecuteDrive, this, std::placeholders::_2), false);
|
||||
}
|
||||
|
||||
void CDROM::Reset()
|
||||
|
@ -35,9 +39,9 @@ void CDROM::Reset()
|
|||
void CDROM::SoftReset()
|
||||
{
|
||||
m_command = Command::None;
|
||||
m_command_event->Deactivate();
|
||||
m_drive_state = DriveState::Idle;
|
||||
m_command_remaining_ticks = 0;
|
||||
m_drive_remaining_ticks = 0;
|
||||
m_drive_event->Deactivate();
|
||||
m_status.bits = 0;
|
||||
m_secondary_status.bits = 0;
|
||||
m_mode.bits = 0;
|
||||
|
@ -85,8 +89,6 @@ bool CDROM::DoState(StateWrapper& sw)
|
|||
{
|
||||
sw.Do(&m_command);
|
||||
sw.Do(&m_drive_state);
|
||||
sw.Do(&m_command_remaining_ticks);
|
||||
sw.Do(&m_drive_remaining_ticks);
|
||||
sw.Do(&m_status.bits);
|
||||
sw.Do(&m_secondary_status.bits);
|
||||
sw.Do(&m_mode.bits);
|
||||
|
@ -124,10 +126,8 @@ bool CDROM::DoState(StateWrapper& sw)
|
|||
|
||||
if (sw.IsReading())
|
||||
{
|
||||
if (HasPendingCommand())
|
||||
m_system->SetDowncount(m_command_remaining_ticks);
|
||||
if (!IsDriveIdle())
|
||||
m_system->SetDowncount(m_drive_remaining_ticks);
|
||||
UpdateCommandEvent();
|
||||
m_drive_event->SetState(!IsDriveIdle());
|
||||
|
||||
// load up media if we had something in there before
|
||||
if (m_media && !m_media->Seek(media_lba))
|
||||
|
@ -361,8 +361,8 @@ void CDROM::WriteRegister(u32 offset, u8 value)
|
|||
{
|
||||
if (HasPendingAsyncInterrupt())
|
||||
DeliverAsyncInterrupt();
|
||||
else if (HasPendingCommand())
|
||||
m_system->SetDowncount(m_command_remaining_ticks);
|
||||
else
|
||||
UpdateCommandEvent();
|
||||
}
|
||||
|
||||
// Bit 6 clears the parameter FIFO.
|
||||
|
@ -443,6 +443,7 @@ void CDROM::DeliverAsyncInterrupt()
|
|||
m_pending_async_interrupt = 0;
|
||||
UpdateInterruptRequest();
|
||||
UpdateStatusRegister();
|
||||
UpdateCommandEvent();
|
||||
}
|
||||
|
||||
void CDROM::SendACKAndStat()
|
||||
|
@ -520,73 +521,11 @@ TickCount CDROM::GetTicksForSeek() const
|
|||
return ticks;
|
||||
}
|
||||
|
||||
void CDROM::Execute(TickCount ticks)
|
||||
{
|
||||
if (HasPendingCommand() && !HasPendingInterrupt())
|
||||
{
|
||||
m_command_remaining_ticks -= ticks;
|
||||
if (m_command_remaining_ticks <= 0)
|
||||
ExecuteCommand();
|
||||
else
|
||||
m_system->SetDowncount(m_command_remaining_ticks);
|
||||
}
|
||||
|
||||
if (m_drive_state != DriveState::Idle)
|
||||
{
|
||||
m_drive_remaining_ticks -= ticks;
|
||||
if (m_drive_remaining_ticks <= 0)
|
||||
{
|
||||
switch (m_drive_state)
|
||||
{
|
||||
case DriveState::SpinningUp:
|
||||
DoSpinUpComplete();
|
||||
break;
|
||||
|
||||
case DriveState::SeekingPhysical:
|
||||
case DriveState::SeekingLogical:
|
||||
DoSeekComplete();
|
||||
break;
|
||||
|
||||
case DriveState::Pausing:
|
||||
DoPauseComplete();
|
||||
break;
|
||||
|
||||
case DriveState::Stopping:
|
||||
DoStopComplete();
|
||||
break;
|
||||
|
||||
case DriveState::ReadingID:
|
||||
DoIDRead();
|
||||
break;
|
||||
|
||||
case DriveState::ReadingTOC:
|
||||
DoTOCRead();
|
||||
break;
|
||||
|
||||
case DriveState::Reading:
|
||||
case DriveState::Playing:
|
||||
DoSectorRead();
|
||||
break;
|
||||
|
||||
case DriveState::Idle:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_system->SetDowncount(m_drive_remaining_ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CDROM::BeginCommand(Command command)
|
||||
{
|
||||
m_system->Synchronize();
|
||||
|
||||
m_command = command;
|
||||
m_command_remaining_ticks = GetAckDelayForCommand();
|
||||
m_system->SetDowncount(m_command_remaining_ticks);
|
||||
m_command_event->SetDowncount(GetAckDelayForCommand());
|
||||
UpdateCommandEvent();
|
||||
UpdateStatusRegister();
|
||||
}
|
||||
|
||||
|
@ -595,7 +534,7 @@ void CDROM::EndCommand()
|
|||
m_param_fifo.Clear();
|
||||
|
||||
m_command = Command::None;
|
||||
m_command_remaining_ticks = 0;
|
||||
m_command_event->Deactivate();
|
||||
UpdateStatusRegister();
|
||||
}
|
||||
|
||||
|
@ -645,7 +584,7 @@ void CDROM::ExecuteCommand()
|
|||
SendACKAndStat();
|
||||
|
||||
m_drive_state = DriveState::ReadingID;
|
||||
m_drive_remaining_ticks = 18000;
|
||||
m_drive_event->Schedule(18000);
|
||||
}
|
||||
|
||||
EndCommand();
|
||||
|
@ -664,7 +603,7 @@ void CDROM::ExecuteCommand()
|
|||
SendACKAndStat();
|
||||
|
||||
m_drive_state = DriveState::ReadingTOC;
|
||||
m_drive_remaining_ticks = MASTER_CLOCK / 2; // half a second
|
||||
m_drive_event->Schedule(MASTER_CLOCK / 2); // half a second
|
||||
}
|
||||
|
||||
EndCommand();
|
||||
|
@ -767,12 +706,12 @@ void CDROM::ExecuteCommand()
|
|||
case Command::Pause:
|
||||
{
|
||||
const bool was_reading = (m_drive_state == DriveState::Reading || m_drive_state == DriveState::Playing);
|
||||
const TickCount pause_time = was_reading ? (m_mode.double_speed ? 2000000 : 1000000) : 7000;
|
||||
Log_DebugPrintf("CDROM pause command");
|
||||
SendACKAndStat();
|
||||
|
||||
m_drive_state = DriveState::Pausing;
|
||||
m_drive_remaining_ticks = was_reading ? (m_mode.double_speed ? 2000000 : 1000000) : 7000;
|
||||
m_system->SetDowncount(m_drive_remaining_ticks);
|
||||
m_drive_event->Schedule(pause_time);
|
||||
|
||||
EndCommand();
|
||||
return;
|
||||
|
@ -781,12 +720,12 @@ void CDROM::ExecuteCommand()
|
|||
case Command::Stop:
|
||||
{
|
||||
const bool was_motor_on = m_secondary_status.motor_on;
|
||||
const TickCount stop_time = was_motor_on ? (m_mode.double_speed ? 25000000 : 13000000) : 7000;
|
||||
Log_DebugPrintf("CDROM stop command");
|
||||
SendACKAndStat();
|
||||
|
||||
m_drive_state = DriveState::Stopping;
|
||||
m_drive_remaining_ticks = was_motor_on ? (m_mode.double_speed ? 25000000 : 13000000) : 7000;
|
||||
m_system->SetDowncount(m_drive_remaining_ticks);
|
||||
m_drive_event->Schedule(stop_time);
|
||||
|
||||
EndCommand();
|
||||
return;
|
||||
|
@ -801,8 +740,7 @@ void CDROM::ExecuteCommand()
|
|||
m_mode.bits = 0;
|
||||
|
||||
m_drive_state = DriveState::SpinningUp;
|
||||
m_drive_remaining_ticks = 80000;
|
||||
m_system->SetDowncount(m_drive_remaining_ticks);
|
||||
m_drive_event->Schedule(80000);
|
||||
|
||||
EndCommand();
|
||||
return;
|
||||
|
@ -821,8 +759,7 @@ void CDROM::ExecuteCommand()
|
|||
SendACKAndStat();
|
||||
|
||||
m_drive_state = DriveState::SpinningUp;
|
||||
m_drive_remaining_ticks = 80000;
|
||||
m_system->SetDowncount(m_drive_remaining_ticks);
|
||||
m_drive_event->Schedule(80000);
|
||||
}
|
||||
|
||||
EndCommand();
|
||||
|
@ -1011,7 +948,62 @@ void CDROM::ExecuteTestCommand(u8 subcommand)
|
|||
}
|
||||
}
|
||||
|
||||
void CDROM::BeginReading()
|
||||
void CDROM::UpdateCommandEvent()
|
||||
{
|
||||
// if there's a pending interrupt, we can't execute the command yet
|
||||
// so deactivate it until the interrupt is acknowledged
|
||||
if (!HasPendingCommand() || HasPendingInterrupt())
|
||||
{
|
||||
m_command_event->Deactivate();
|
||||
return;
|
||||
}
|
||||
else if (HasPendingCommand())
|
||||
{
|
||||
m_command_event->Activate();
|
||||
}
|
||||
}
|
||||
|
||||
void CDROM::ExecuteDrive(TickCount ticks_late)
|
||||
{
|
||||
switch (m_drive_state)
|
||||
{
|
||||
case DriveState::SpinningUp:
|
||||
DoSpinUpComplete();
|
||||
break;
|
||||
|
||||
case DriveState::SeekingPhysical:
|
||||
case DriveState::SeekingLogical:
|
||||
DoSeekComplete(ticks_late);
|
||||
break;
|
||||
|
||||
case DriveState::Pausing:
|
||||
DoPauseComplete();
|
||||
break;
|
||||
|
||||
case DriveState::Stopping:
|
||||
DoStopComplete();
|
||||
break;
|
||||
|
||||
case DriveState::ReadingID:
|
||||
DoIDRead();
|
||||
break;
|
||||
|
||||
case DriveState::ReadingTOC:
|
||||
DoTOCRead();
|
||||
break;
|
||||
|
||||
case DriveState::Reading:
|
||||
case DriveState::Playing:
|
||||
DoSectorRead();
|
||||
break;
|
||||
|
||||
case DriveState::Idle:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CDROM::BeginReading(TickCount ticks_late)
|
||||
{
|
||||
Log_DebugPrintf("Starting reading");
|
||||
if (m_setloc_pending)
|
||||
|
@ -1026,12 +1018,13 @@ void CDROM::BeginReading()
|
|||
// TODO: Should the sector buffer be cleared here?
|
||||
m_sector_buffer.clear();
|
||||
|
||||
const TickCount ticks = GetTicksForRead();
|
||||
m_drive_state = DriveState::Reading;
|
||||
m_drive_remaining_ticks = GetTicksForRead();
|
||||
m_system->SetDowncount(m_drive_remaining_ticks);
|
||||
m_drive_event->SetInterval(ticks);
|
||||
m_drive_event->Schedule(ticks - ticks_late);
|
||||
}
|
||||
|
||||
void CDROM::BeginPlaying(u8 track_bcd)
|
||||
void CDROM::BeginPlaying(u8 track_bcd, TickCount ticks_late)
|
||||
{
|
||||
Log_DebugPrintf("Starting playing CDDA track %x", track_bcd);
|
||||
m_last_cdda_report_frame_nibble = 0xFF;
|
||||
|
@ -1063,9 +1056,10 @@ void CDROM::BeginPlaying(u8 track_bcd)
|
|||
// TODO: Should the sector buffer be cleared here?
|
||||
m_sector_buffer.clear();
|
||||
|
||||
const TickCount ticks = GetTicksForRead();
|
||||
m_drive_state = DriveState::Playing;
|
||||
m_drive_remaining_ticks = GetTicksForRead();
|
||||
m_system->SetDowncount(m_drive_remaining_ticks);
|
||||
m_drive_event->SetInterval(ticks);
|
||||
m_drive_event->Schedule(ticks - ticks_late);
|
||||
}
|
||||
|
||||
void CDROM::BeginSeeking(bool logical, bool read_after_seek, bool play_after_seek)
|
||||
|
@ -1088,8 +1082,7 @@ void CDROM::BeginSeeking(bool logical, bool read_after_seek, bool play_after_see
|
|||
m_sector_buffer.clear();
|
||||
|
||||
m_drive_state = logical ? DriveState::SeekingLogical : DriveState::SeekingPhysical;
|
||||
m_drive_remaining_ticks = seek_time;
|
||||
m_system->SetDowncount(m_drive_remaining_ticks);
|
||||
m_drive_event->SetIntervalAndSchedule(seek_time);
|
||||
|
||||
// Read sub-q early.. this is because we're not reading sectors while seeking.
|
||||
// Fixes music looping in Spyro.
|
||||
|
@ -1101,6 +1094,7 @@ void CDROM::BeginSeeking(bool logical, bool read_after_seek, bool play_after_see
|
|||
void CDROM::DoSpinUpComplete()
|
||||
{
|
||||
m_drive_state = DriveState::Idle;
|
||||
m_drive_event->Deactivate();
|
||||
|
||||
m_secondary_status.motor_on = true;
|
||||
|
||||
|
@ -1109,10 +1103,11 @@ void CDROM::DoSpinUpComplete()
|
|||
SetAsyncInterrupt(Interrupt::INT2);
|
||||
}
|
||||
|
||||
void CDROM::DoSeekComplete()
|
||||
void CDROM::DoSeekComplete(TickCount ticks_late)
|
||||
{
|
||||
const bool logical = (m_drive_state == DriveState::SeekingLogical);
|
||||
m_drive_state = DriveState::Idle;
|
||||
m_drive_event->Deactivate();
|
||||
m_secondary_status.ClearActiveBits();
|
||||
m_sector_buffer.clear();
|
||||
|
||||
|
@ -1145,11 +1140,11 @@ void CDROM::DoSeekComplete()
|
|||
// INT2 is not sent on play/read
|
||||
if (m_read_after_seek)
|
||||
{
|
||||
BeginReading();
|
||||
BeginReading(ticks_late);
|
||||
}
|
||||
else if (m_play_after_seek)
|
||||
{
|
||||
BeginPlaying(m_play_track_number_bcd);
|
||||
BeginPlaying(m_play_track_number_bcd, ticks_late);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1175,6 +1170,7 @@ void CDROM::DoPauseComplete()
|
|||
{
|
||||
Log_DebugPrintf("Pause complete");
|
||||
m_drive_state = DriveState::Idle;
|
||||
m_drive_event->Deactivate();
|
||||
m_secondary_status.ClearActiveBits();
|
||||
m_sector_buffer.clear();
|
||||
|
||||
|
@ -1187,6 +1183,7 @@ void CDROM::DoStopComplete()
|
|||
{
|
||||
Log_DebugPrintf("Stop complete");
|
||||
m_drive_state = DriveState::Idle;
|
||||
m_drive_event->Deactivate();
|
||||
m_secondary_status.ClearActiveBits();
|
||||
m_secondary_status.motor_on = false;
|
||||
m_sector_buffer.clear();
|
||||
|
@ -1204,6 +1201,7 @@ void CDROM::DoIDRead()
|
|||
|
||||
Log_DebugPrintf("ID read complete");
|
||||
m_drive_state = DriveState::Idle;
|
||||
m_drive_event->Deactivate();
|
||||
m_secondary_status.ClearActiveBits();
|
||||
m_secondary_status.motor_on = true;
|
||||
m_sector_buffer.clear();
|
||||
|
@ -1219,6 +1217,7 @@ void CDROM::DoTOCRead()
|
|||
{
|
||||
Log_DebugPrintf("TOC read complete");
|
||||
m_drive_state = DriveState::Idle;
|
||||
m_drive_event->Deactivate();
|
||||
m_async_response_fifo.Clear();
|
||||
m_async_response_fifo.Push(m_secondary_status.bits);
|
||||
SetAsyncInterrupt(Interrupt::INT2);
|
||||
|
@ -1254,6 +1253,7 @@ void CDROM::DoSectorRead()
|
|||
|
||||
m_secondary_status.ClearActiveBits();
|
||||
m_drive_state = DriveState::Idle;
|
||||
m_drive_event->Deactivate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1290,9 +1290,6 @@ void CDROM::DoSectorRead()
|
|||
Log_DevPrintf("Skipping sector %u [%02u:%02u:%02u] due to invalid subchannel Q", m_media->GetPositionOnDisc() - 1,
|
||||
pos.minute, pos.second, pos.frame);
|
||||
}
|
||||
|
||||
m_drive_remaining_ticks += GetTicksForRead();
|
||||
m_system->SetDowncount(m_drive_remaining_ticks);
|
||||
}
|
||||
|
||||
void CDROM::ProcessDataSectorHeader(const u8* raw_sector, bool set_valid)
|
||||
|
@ -1711,7 +1708,7 @@ void CDROM::DrawDebugWindow()
|
|||
if (HasPendingCommand())
|
||||
{
|
||||
ImGui::TextColored(active_color, "Command: 0x%02X (%d ticks remaining)", static_cast<u8>(m_command),
|
||||
m_command_remaining_ticks);
|
||||
m_command_event->IsActive() ? m_command_event->GetTicksUntilNextExecution() : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1725,7 +1722,8 @@ void CDROM::DrawDebugWindow()
|
|||
else
|
||||
{
|
||||
ImGui::TextColored(active_color, "Drive: %s (%d ticks remaining)",
|
||||
drive_state_names[static_cast<u8>(m_drive_state)], m_drive_remaining_ticks);
|
||||
drive_state_names[static_cast<u8>(m_drive_state)],
|
||||
m_drive_event->IsActive() ? m_drive_event->GetTicksUntilNextExecution() : 0);
|
||||
}
|
||||
|
||||
ImGui::Text("Interrupt Enable Register: 0x%02X", m_interrupt_enable_register);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
class StateWrapper;
|
||||
|
||||
class System;
|
||||
class TimingEvent;
|
||||
class DMA;
|
||||
class InterruptController;
|
||||
class SPU;
|
||||
|
@ -36,8 +37,6 @@ public:
|
|||
void WriteRegister(u32 offset, u8 value);
|
||||
void DMARead(u32* words, u32 word_count);
|
||||
|
||||
void Execute(TickCount ticks);
|
||||
|
||||
// Render statistics debug window.
|
||||
void DrawDebugWindow();
|
||||
|
||||
|
@ -198,10 +197,12 @@ private:
|
|||
void EndCommand(); // also updates status register
|
||||
void ExecuteCommand();
|
||||
void ExecuteTestCommand(u8 subcommand);
|
||||
void BeginReading();
|
||||
void BeginPlaying(u8 track_bcd);
|
||||
void UpdateCommandEvent();
|
||||
void ExecuteDrive(TickCount ticks_late);
|
||||
void BeginReading(TickCount ticks_late = 0);
|
||||
void BeginPlaying(u8 track_bcd, TickCount ticks_late = 0);
|
||||
void DoSpinUpComplete();
|
||||
void DoSeekComplete();
|
||||
void DoSeekComplete(TickCount ticks_late);
|
||||
void DoPauseComplete();
|
||||
void DoStopComplete();
|
||||
void DoIDRead();
|
||||
|
@ -219,11 +220,11 @@ private:
|
|||
InterruptController* m_interrupt_controller = nullptr;
|
||||
SPU* m_spu = nullptr;
|
||||
std::unique_ptr<CDImage> m_media;
|
||||
std::unique_ptr<TimingEvent> m_command_event;
|
||||
std::unique_ptr<TimingEvent> m_drive_event;
|
||||
|
||||
Command m_command = Command::None;
|
||||
DriveState m_drive_state = DriveState::Idle;
|
||||
TickCount m_command_remaining_ticks = 0;
|
||||
TickCount m_drive_remaining_ticks = 0;
|
||||
|
||||
StatusRegister m_status = {};
|
||||
SecondaryStatusRegister m_secondary_status = {};
|
||||
|
|
|
@ -82,6 +82,7 @@
|
|||
<ClCompile Include="spu.cpp" />
|
||||
<ClCompile Include="system.cpp" />
|
||||
<ClCompile Include="timers.cpp" />
|
||||
<ClCompile Include="timing_event.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="analog_controller.h" />
|
||||
|
@ -121,6 +122,7 @@
|
|||
<ClInclude Include="spu.h" />
|
||||
<ClInclude Include="system.h" />
|
||||
<ClInclude Include="timers.h" />
|
||||
<ClInclude Include="timing_event.h" />
|
||||
<ClInclude Include="types.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
<ClCompile Include="controller.cpp" />
|
||||
<ClCompile Include="analog_controller.cpp" />
|
||||
<ClCompile Include="host_display.cpp" />
|
||||
<ClCompile Include="timing_event.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="types.h" />
|
||||
|
@ -79,10 +80,11 @@
|
|||
<ClInclude Include="sio.h" />
|
||||
<ClInclude Include="controller.h" />
|
||||
<ClInclude Include="analog_controller.h" />
|
||||
<ClInclude Include="timing_event.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="cpu_core.inl" />
|
||||
<None Include="bus.inl" />
|
||||
<None Include="gte.inl" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -51,11 +51,8 @@ public:
|
|||
ALWAYS_INLINE void ResetPendingTicks() { m_pending_ticks = 0; }
|
||||
ALWAYS_INLINE void AddPendingTicks(TickCount ticks) { m_pending_ticks += ticks; }
|
||||
|
||||
ALWAYS_INLINE void SetDowncount(TickCount downcount)
|
||||
{
|
||||
m_downcount = (downcount < m_downcount) ? downcount : m_downcount;
|
||||
}
|
||||
ALWAYS_INLINE void ResetDowncount() { m_downcount = MAX_SLICE_SIZE; }
|
||||
ALWAYS_INLINE TickCount GetDowncount() const { return m_downcount; }
|
||||
ALWAYS_INLINE void SetDowncount(TickCount downcount) { m_downcount = downcount; }
|
||||
|
||||
// Sets the PC and flushes the pipeline.
|
||||
void SetPC(u32 new_pc);
|
||||
|
|
113
src/core/dma.cpp
113
src/core/dma.cpp
|
@ -1,8 +1,9 @@
|
|||
#include "dma.h"
|
||||
#include "common/log.h"
|
||||
#include "bus.h"
|
||||
#include "cdrom.h"
|
||||
#include "common/log.h"
|
||||
#include "common/state_wrapper.h"
|
||||
#include "common/string_util.h"
|
||||
#include "gpu.h"
|
||||
#include "interrupt_controller.h"
|
||||
#include "mdec.h"
|
||||
|
@ -25,14 +26,28 @@ void DMA::Initialize(System* system, Bus* bus, InterruptController* interrupt_co
|
|||
m_spu = spu;
|
||||
m_mdec = mdec;
|
||||
m_transfer_buffer.resize(32);
|
||||
|
||||
for (u32 i = 0; i < NUM_CHANNELS; i++)
|
||||
{
|
||||
m_state[i].transfer_event = system->CreateTimingEvent(
|
||||
StringUtil::StdStringFromFormat("DMA%u Transfer", i), 1, 1,
|
||||
std::bind(&DMA::TransferChannel, this, static_cast<Channel>(i), std::placeholders::_2), false);
|
||||
}
|
||||
}
|
||||
|
||||
void DMA::Reset()
|
||||
{
|
||||
m_transfer_in_progress = false;
|
||||
std::memset(&m_state, 0, sizeof(m_state));
|
||||
m_DPCR.bits = 0x07654321;
|
||||
m_DICR.bits = 0;
|
||||
for (u32 i = 0; i < NUM_CHANNELS; i++)
|
||||
{
|
||||
ChannelState& cs = m_state[i];
|
||||
cs.base_address = 0;
|
||||
cs.block_control.bits = 0;
|
||||
cs.channel_control.bits = 0;
|
||||
cs.request = false;
|
||||
cs.transfer_event->Deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
bool DMA::DoState(StateWrapper& sw)
|
||||
|
@ -43,7 +58,6 @@ bool DMA::DoState(StateWrapper& sw)
|
|||
sw.Do(&cs.base_address);
|
||||
sw.Do(&cs.block_control.bits);
|
||||
sw.Do(&cs.channel_control.bits);
|
||||
sw.Do(&cs.transfer_ticks);
|
||||
sw.Do(&cs.request);
|
||||
}
|
||||
|
||||
|
@ -52,13 +66,11 @@ bool DMA::DoState(StateWrapper& sw)
|
|||
|
||||
if (sw.IsReading())
|
||||
{
|
||||
m_transfer_min_ticks = std::numeric_limits<TickCount>::max();
|
||||
for (const ChannelState& cs : m_state)
|
||||
for (u32 i = 0; i < NUM_CHANNELS; i++)
|
||||
{
|
||||
if (cs.transfer_ticks > 0)
|
||||
m_transfer_min_ticks = std::min(m_transfer_min_ticks, cs.transfer_ticks);
|
||||
m_state[i].transfer_event->Deactivate();
|
||||
UpdateChannelTransferEvent(static_cast<Channel>(i));
|
||||
}
|
||||
m_system->SetDowncount(m_transfer_min_ticks);
|
||||
}
|
||||
|
||||
return !sw.HasError();
|
||||
|
@ -126,7 +138,7 @@ void DMA::WriteRegister(u32 offset, u32 value)
|
|||
{
|
||||
Log_TracePrintf("DMA channel %u block control <- 0x%08X", channel_index, value);
|
||||
state.block_control.bits = value;
|
||||
QueueTransferChannel(static_cast<Channel>(channel_index));
|
||||
UpdateChannelTransferEvent(static_cast<Channel>(channel_index));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -135,7 +147,7 @@ void DMA::WriteRegister(u32 offset, u32 value)
|
|||
state.channel_control.bits = (state.channel_control.bits & ~ChannelState::ChannelControl::WRITE_MASK) |
|
||||
(value & ChannelState::ChannelControl::WRITE_MASK);
|
||||
Log_TracePrintf("DMA channel %u channel control <- 0x%08X", channel_index, state.channel_control.bits);
|
||||
QueueTransferChannel(static_cast<Channel>(channel_index));
|
||||
UpdateChannelTransferEvent(static_cast<Channel>(channel_index));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -151,7 +163,8 @@ void DMA::WriteRegister(u32 offset, u32 value)
|
|||
{
|
||||
Log_TracePrintf("DPCR <- 0x%08X", value);
|
||||
m_DPCR.bits = value;
|
||||
QueueTransfer();
|
||||
for (u32 i = 0; i < NUM_CHANNELS; i++)
|
||||
UpdateChannelTransferEvent(static_cast<Channel>(i));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -180,7 +193,7 @@ void DMA::SetRequest(Channel channel, bool request)
|
|||
|
||||
cs.request = request;
|
||||
if (request)
|
||||
QueueTransfer();
|
||||
UpdateChannelTransferEvent(channel);
|
||||
}
|
||||
|
||||
TickCount DMA::GetTransferDelay(Channel channel) const
|
||||
|
@ -188,6 +201,10 @@ TickCount DMA::GetTransferDelay(Channel channel) const
|
|||
const ChannelState& cs = m_state[static_cast<u32>(channel)];
|
||||
switch (channel)
|
||||
{
|
||||
case Channel::MDECin:
|
||||
case Channel::MDECout:
|
||||
return 1;
|
||||
|
||||
case Channel::SPU:
|
||||
{
|
||||
if (cs.channel_control.sync_mode == SyncMode::Request)
|
||||
|
@ -198,7 +215,7 @@ TickCount DMA::GetTransferDelay(Channel channel) const
|
|||
break;
|
||||
|
||||
default:
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,9 +234,6 @@ bool DMA::CanTransferChannel(Channel channel) const
|
|||
if (cs.channel_control.sync_mode == SyncMode::Manual && !cs.channel_control.start_trigger)
|
||||
return false;
|
||||
|
||||
if (cs.transfer_ticks > 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -244,72 +258,34 @@ void DMA::UpdateIRQ()
|
|||
}
|
||||
}
|
||||
|
||||
void DMA::QueueTransferChannel(Channel channel)
|
||||
void DMA::UpdateChannelTransferEvent(Channel channel)
|
||||
{
|
||||
ChannelState& cs = m_state[static_cast<u32>(channel)];
|
||||
if (cs.transfer_ticks > 0 || !CanTransferChannel(channel))
|
||||
if (!CanTransferChannel(channel))
|
||||
{
|
||||
cs.transfer_event->Deactivate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cs.transfer_event->IsActive())
|
||||
return;
|
||||
|
||||
const TickCount ticks = GetTransferDelay(channel);
|
||||
if (ticks == 0)
|
||||
{
|
||||
// immediate transfer
|
||||
TransferChannel(channel);
|
||||
TransferChannel(channel, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_transfer_in_progress)
|
||||
m_system->Synchronize();
|
||||
|
||||
cs.transfer_ticks = ticks;
|
||||
m_transfer_min_ticks = std::min(m_transfer_min_ticks, ticks);
|
||||
m_system->SetDowncount(ticks);
|
||||
cs.transfer_event->SetPeriodAndSchedule(ticks);
|
||||
}
|
||||
|
||||
void DMA::QueueTransfer()
|
||||
{
|
||||
for (u32 i = 0; i < NUM_CHANNELS; i++)
|
||||
QueueTransferChannel(static_cast<Channel>(i));
|
||||
}
|
||||
|
||||
void DMA::Execute(TickCount ticks)
|
||||
{
|
||||
m_transfer_min_ticks -= ticks;
|
||||
if (m_transfer_min_ticks > 0)
|
||||
{
|
||||
m_system->SetDowncount(m_transfer_min_ticks);
|
||||
return;
|
||||
}
|
||||
|
||||
DebugAssert(!m_transfer_in_progress);
|
||||
m_transfer_in_progress = true;
|
||||
|
||||
// keep going until all transfers are done. one channel can start others (e.g. MDEC)
|
||||
m_transfer_min_ticks = std::numeric_limits<TickCount>::max();
|
||||
for (u32 i = 0; i < NUM_CHANNELS; i++)
|
||||
{
|
||||
const Channel channel = static_cast<Channel>(i);
|
||||
if (m_state[i].transfer_ticks <= 0)
|
||||
continue;
|
||||
|
||||
m_state[i].transfer_ticks -= ticks;
|
||||
if (CanTransferChannel(channel))
|
||||
{
|
||||
TransferChannel(channel);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_transfer_min_ticks = std::min(m_transfer_min_ticks, ticks);
|
||||
}
|
||||
}
|
||||
|
||||
m_system->SetDowncount(m_transfer_min_ticks);
|
||||
m_transfer_in_progress = false;
|
||||
}
|
||||
|
||||
void DMA::TransferChannel(Channel channel)
|
||||
void DMA::TransferChannel(Channel channel, TickCount ticks_late)
|
||||
{
|
||||
ChannelState& cs = m_state[static_cast<u32>(channel)];
|
||||
cs.transfer_event->Deactivate();
|
||||
|
||||
const bool copy_to_device = cs.channel_control.copy_to_device;
|
||||
|
||||
// start/trigger bit is cleared on beginning of transfer
|
||||
|
@ -416,7 +392,6 @@ void DMA::TransferChannel(Channel channel)
|
|||
}
|
||||
|
||||
// start/busy bit is cleared on end of transfer
|
||||
cs.transfer_ticks = 0;
|
||||
cs.channel_control.enable_busy = false;
|
||||
if (m_DICR.IsIRQEnabled(channel))
|
||||
{
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
#include "common/bitfield.h"
|
||||
#include "types.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class StateWrapper;
|
||||
|
||||
class System;
|
||||
class TimingEvent;
|
||||
class Bus;
|
||||
class InterruptController;
|
||||
class GPU;
|
||||
|
@ -49,8 +51,6 @@ public:
|
|||
// changing interfaces
|
||||
void SetGPU(GPU* gpu) { m_gpu = gpu; }
|
||||
|
||||
void Execute(TickCount ticks);
|
||||
|
||||
private:
|
||||
static constexpr PhysicalMemoryAddress BASE_ADDRESS_MASK = UINT32_C(0x00FFFFFF);
|
||||
static constexpr PhysicalMemoryAddress ADDRESS_MASK = UINT32_C(0x001FFFFC);
|
||||
|
@ -72,10 +72,8 @@ private:
|
|||
bool CanRunAnyChannels() const;
|
||||
void UpdateIRQ();
|
||||
|
||||
void QueueTransferChannel(Channel channel);
|
||||
void QueueTransfer();
|
||||
|
||||
void TransferChannel(Channel channel);
|
||||
void UpdateChannelTransferEvent(Channel channel);
|
||||
void TransferChannel(Channel channel, TickCount ticks_late);
|
||||
|
||||
// from device -> memory
|
||||
void TransferDeviceToMemory(Channel channel, u32 address, u32 increment, u32 word_count);
|
||||
|
@ -95,6 +93,7 @@ private:
|
|||
|
||||
struct ChannelState
|
||||
{
|
||||
std::unique_ptr<TimingEvent> transfer_event;
|
||||
u32 base_address;
|
||||
|
||||
union BlockControl
|
||||
|
@ -131,7 +130,6 @@ private:
|
|||
static constexpr u32 WRITE_MASK = 0b01110001'01110111'00000111'00000011;
|
||||
} channel_control;
|
||||
|
||||
TickCount transfer_ticks = 0;
|
||||
bool request = false;
|
||||
};
|
||||
|
||||
|
@ -207,7 +205,4 @@ private:
|
|||
master_flag = master_enable && ((((bits >> 16) & u32(0b1111111)) & ((bits >> 24) & u32(0b1111111))) != 0);
|
||||
}
|
||||
} m_DICR = {};
|
||||
|
||||
TickCount m_transfer_min_ticks = 0;
|
||||
bool m_transfer_in_progress = false;
|
||||
};
|
||||
|
|
216
src/core/gpu.cpp
216
src/core/gpu.cpp
|
@ -1,6 +1,6 @@
|
|||
#include "gpu.h"
|
||||
#include "common/log.h"
|
||||
#include "common/heap_array.h"
|
||||
#include "common/log.h"
|
||||
#include "common/state_wrapper.h"
|
||||
#include "dma.h"
|
||||
#include "host_interface.h"
|
||||
|
@ -27,6 +27,8 @@ bool GPU::Initialize(HostDisplay* host_display, System* system, DMA* dma, Interr
|
|||
m_interrupt_controller = interrupt_controller;
|
||||
m_timers = timers;
|
||||
m_force_progressive_scan = m_system->GetSettings().gpu_force_progressive_scan;
|
||||
m_tick_event =
|
||||
m_system->CreateTimingEvent("GPU Tick", 1, 1, std::bind(&GPU::Execute, this, std::placeholders::_1), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -63,6 +65,9 @@ void GPU::SoftReset()
|
|||
m_draw_mode.SetTextureWindow(0);
|
||||
UpdateGPUSTAT();
|
||||
UpdateCRTCConfig();
|
||||
|
||||
m_tick_event->Deactivate();
|
||||
UpdateSliceTicks();
|
||||
}
|
||||
|
||||
bool GPU::DoState(StateWrapper& sw)
|
||||
|
@ -273,6 +278,11 @@ void GPU::DMAWrite(const u32* words, u32 word_count)
|
|||
}
|
||||
}
|
||||
|
||||
void GPU::Synchronize()
|
||||
{
|
||||
m_tick_event->InvokeEarly();
|
||||
}
|
||||
|
||||
void GPU::UpdateCRTCConfig()
|
||||
{
|
||||
static constexpr std::array<TickCount, 8> dot_clock_dividers = {{10, 8, 5, 4, 7, 7, 7, 7}};
|
||||
|
@ -357,31 +367,31 @@ void GPU::UpdateCRTCConfig()
|
|||
|
||||
// Ensure the numbers are sane, and not due to a misconfigured active display range.
|
||||
cs.display_aspect_ratio = (std::isnormal(display_ratio) && display_ratio != 0.0f) ? display_ratio : (4.0f / 3.0f);
|
||||
m_tick_event->SetInterval(cs.horizontal_total);
|
||||
}
|
||||
|
||||
UpdateSliceTicks();
|
||||
static TickCount GPUTicksToSystemTicks(u32 gpu_ticks)
|
||||
{
|
||||
// convert to master clock, rounding up as we want to overshoot not undershoot
|
||||
return (gpu_ticks * 7 + 10) / 11;
|
||||
}
|
||||
|
||||
void GPU::UpdateSliceTicks()
|
||||
{
|
||||
// the next event is at the end of the next scanline
|
||||
#if 1
|
||||
TickCount ticks_until_next_event;
|
||||
if (m_crtc_state.current_tick_in_scanline < m_crtc_state.horizontal_display_start)
|
||||
ticks_until_next_event = m_crtc_state.horizontal_display_start - m_crtc_state.current_tick_in_scanline;
|
||||
else if (m_crtc_state.current_tick_in_scanline < m_crtc_state.horizontal_display_end)
|
||||
ticks_until_next_event = m_crtc_state.horizontal_display_end - m_crtc_state.current_tick_in_scanline;
|
||||
else
|
||||
ticks_until_next_event = m_crtc_state.horizontal_total - m_crtc_state.current_tick_in_scanline;
|
||||
#else
|
||||
// or at vblank. this will depend on the timer config..
|
||||
const TickCount ticks_until_next_event =
|
||||
((m_crtc_state.vertical_total - m_crtc_state.current_scanline) * m_crtc_state.horizontal_total) -
|
||||
m_crtc_state.current_tick_in_scanline;
|
||||
#endif
|
||||
// figure out how many GPU ticks until the next vblank
|
||||
const u32 lines_until_vblank =
|
||||
(m_crtc_state.current_scanline >= m_crtc_state.vertical_display_end ?
|
||||
(m_crtc_state.vertical_total - m_crtc_state.current_scanline + m_crtc_state.vertical_display_end) :
|
||||
(m_crtc_state.vertical_display_end - m_crtc_state.current_scanline));
|
||||
const u32 ticks_until_vblank =
|
||||
lines_until_vblank * m_crtc_state.horizontal_total - m_crtc_state.current_tick_in_scanline;
|
||||
const u32 ticks_until_hblank =
|
||||
(m_crtc_state.current_tick_in_scanline >= m_crtc_state.horizontal_display_end) ?
|
||||
(m_crtc_state.horizontal_total - m_crtc_state.current_tick_in_scanline + m_crtc_state.horizontal_display_end) :
|
||||
(m_crtc_state.horizontal_display_end - m_crtc_state.current_tick_in_scanline);
|
||||
|
||||
// convert to master clock, rounding up as we want to overshoot not undershoot
|
||||
const TickCount system_ticks = (ticks_until_next_event * 7 + 10) / 11;
|
||||
m_system->SetDowncount(system_ticks);
|
||||
m_tick_event->Schedule(GPUTicksToSystemTicks(ticks_until_vblank));
|
||||
m_tick_event->SetPeriod(GPUTicksToSystemTicks(ticks_until_hblank));
|
||||
}
|
||||
|
||||
void GPU::Execute(TickCount ticks)
|
||||
|
@ -393,13 +403,76 @@ void GPU::Execute(TickCount ticks)
|
|||
m_crtc_state.fractional_ticks = temp % 7;
|
||||
}
|
||||
|
||||
while (m_crtc_state.current_tick_in_scanline >= m_crtc_state.horizontal_total)
|
||||
if (m_crtc_state.current_tick_in_scanline < m_crtc_state.horizontal_total)
|
||||
{
|
||||
m_crtc_state.current_tick_in_scanline -= m_crtc_state.horizontal_total;
|
||||
m_crtc_state.current_scanline++;
|
||||
// short path when we execute <1 line.. this shouldn't occur often.
|
||||
const bool old_hblank = m_crtc_state.in_hblank;
|
||||
const bool new_hblank = m_crtc_state.current_tick_in_scanline < m_crtc_state.horizontal_display_start ||
|
||||
m_crtc_state.current_tick_in_scanline >= m_crtc_state.horizontal_display_end;
|
||||
if (!old_hblank && new_hblank && m_timers->IsUsingExternalClock(HBLANK_TIMER_INDEX))
|
||||
m_timers->AddTicks(HBLANK_TIMER_INDEX, 1);
|
||||
|
||||
UpdateSliceTicks();
|
||||
return;
|
||||
}
|
||||
|
||||
u32 lines_to_draw = m_crtc_state.current_tick_in_scanline / m_crtc_state.horizontal_total;
|
||||
m_crtc_state.current_tick_in_scanline %= m_crtc_state.horizontal_total;
|
||||
#if 0
|
||||
Log_WarningPrintf("Old line: %u, new line: %u, drawing %u", m_crtc_state.current_scanline,
|
||||
m_crtc_state.current_scanline + lines_to_draw, lines_to_draw);
|
||||
#endif
|
||||
|
||||
const bool old_hblank = m_crtc_state.in_hblank;
|
||||
const bool new_hblank = m_crtc_state.current_tick_in_scanline < m_crtc_state.horizontal_display_start ||
|
||||
m_crtc_state.current_tick_in_scanline >= m_crtc_state.horizontal_display_end;
|
||||
m_crtc_state.in_hblank = new_hblank;
|
||||
if (m_timers->IsUsingExternalClock(HBLANK_TIMER_INDEX))
|
||||
{
|
||||
const u32 hblank_timer_ticks = BoolToUInt32(!old_hblank) + BoolToUInt32(new_hblank) + (lines_to_draw - 1);
|
||||
m_timers->AddTicks(HBLANK_TIMER_INDEX, static_cast<TickCount>(hblank_timer_ticks));
|
||||
}
|
||||
|
||||
while (lines_to_draw > 0)
|
||||
{
|
||||
const u32 lines_to_draw_this_loop =
|
||||
std::min(lines_to_draw, m_crtc_state.vertical_total - m_crtc_state.current_scanline);
|
||||
const u32 prev_scanline = m_crtc_state.current_scanline;
|
||||
m_crtc_state.current_scanline += lines_to_draw_this_loop;
|
||||
DebugAssert(m_crtc_state.current_scanline <= m_crtc_state.vertical_total);
|
||||
lines_to_draw -= lines_to_draw_this_loop;
|
||||
|
||||
// clear the vblank flag if the beam would pass through the display area
|
||||
if (prev_scanline < m_crtc_state.vertical_display_start &&
|
||||
m_crtc_state.current_scanline >= m_crtc_state.vertical_display_end)
|
||||
{
|
||||
m_crtc_state.in_vblank = false;
|
||||
}
|
||||
|
||||
const bool new_vblank = m_crtc_state.current_scanline < m_crtc_state.vertical_display_start ||
|
||||
m_crtc_state.current_scanline >= m_crtc_state.vertical_display_end;
|
||||
if (m_crtc_state.in_vblank != new_vblank)
|
||||
{
|
||||
m_crtc_state.in_vblank = new_vblank;
|
||||
|
||||
if (new_vblank)
|
||||
{
|
||||
static u32 x = 0;
|
||||
Log_DebugPrintf("Now in v-blank %u ticks %u hblanks", m_system->GetGlobalTickCounter() - x);
|
||||
x = m_system->GetGlobalTickCounter();
|
||||
m_interrupt_controller->InterruptRequest(InterruptController::IRQ::VBLANK);
|
||||
|
||||
// flush any pending draws and "scan out" the image
|
||||
FlushRender();
|
||||
UpdateDisplay();
|
||||
m_system->IncrementFrameNumber();
|
||||
}
|
||||
|
||||
m_timers->SetGate(HBLANK_TIMER_INDEX, new_vblank);
|
||||
}
|
||||
|
||||
// past the end of vblank?
|
||||
if (m_crtc_state.current_scanline >= m_crtc_state.vertical_total)
|
||||
if (m_crtc_state.current_scanline == m_crtc_state.vertical_total)
|
||||
{
|
||||
// start the new frame
|
||||
m_crtc_state.current_scanline = 0;
|
||||
|
@ -415,47 +488,18 @@ void GPU::Execute(TickCount ticks)
|
|||
m_GPUSTAT.interlaced_field = false;
|
||||
}
|
||||
}
|
||||
|
||||
const bool new_vblank = m_crtc_state.current_scanline < m_crtc_state.vertical_display_start ||
|
||||
m_crtc_state.current_scanline >= m_crtc_state.vertical_display_end;
|
||||
if (m_crtc_state.in_vblank != new_vblank)
|
||||
{
|
||||
m_crtc_state.in_vblank = new_vblank;
|
||||
|
||||
if (new_vblank)
|
||||
{
|
||||
Log_DebugPrintf("Now in v-blank");
|
||||
m_interrupt_controller->InterruptRequest(InterruptController::IRQ::VBLANK);
|
||||
|
||||
// flush any pending draws and "scan out" the image
|
||||
FlushRender();
|
||||
UpdateDisplay();
|
||||
m_system->IncrementFrameNumber();
|
||||
}
|
||||
|
||||
m_timers->SetGate(HBLANK_TIMER_INDEX, new_vblank);
|
||||
}
|
||||
|
||||
// alternating even line bit in 240-line mode
|
||||
if (m_GPUSTAT.In480iMode())
|
||||
{
|
||||
m_GPUSTAT.drawing_even_line =
|
||||
ConvertToBoolUnchecked((m_crtc_state.regs.Y + BoolToUInt32(!m_GPUSTAT.interlaced_field)) & u32(1));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_GPUSTAT.drawing_even_line =
|
||||
ConvertToBoolUnchecked((m_crtc_state.regs.Y + m_crtc_state.current_scanline) & u32(1));
|
||||
}
|
||||
}
|
||||
|
||||
const bool new_hblank = m_crtc_state.current_tick_in_scanline < m_crtc_state.horizontal_display_start ||
|
||||
m_crtc_state.current_tick_in_scanline >= m_crtc_state.horizontal_display_end;
|
||||
if (m_crtc_state.in_hblank != new_hblank)
|
||||
// alternating even line bit in 240-line mode
|
||||
if (m_GPUSTAT.In480iMode())
|
||||
{
|
||||
m_crtc_state.in_hblank = new_hblank;
|
||||
if (new_hblank && m_timers->IsUsingExternalClock(HBLANK_TIMER_INDEX))
|
||||
m_timers->AddTicks(HBLANK_TIMER_INDEX, 1);
|
||||
m_GPUSTAT.drawing_even_line =
|
||||
ConvertToBoolUnchecked((m_crtc_state.regs.Y + BoolToUInt32(!m_GPUSTAT.interlaced_field)) & u32(1));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_GPUSTAT.drawing_even_line =
|
||||
ConvertToBoolUnchecked((m_crtc_state.regs.Y + m_crtc_state.current_scanline) & u32(1));
|
||||
}
|
||||
|
||||
UpdateSliceTicks();
|
||||
|
@ -560,17 +604,29 @@ void GPU::WriteGP1(u32 value)
|
|||
|
||||
case 0x06: // Set horizontal display range
|
||||
{
|
||||
m_crtc_state.regs.horizontal_display_range = param & CRTCState::Regs::HORIZONTAL_DISPLAY_RANGE_MASK;
|
||||
Log_DebugPrintf("Horizontal display range <- 0x%08X", m_crtc_state.regs.horizontal_display_range);
|
||||
UpdateCRTCConfig();
|
||||
const u32 new_value = param & CRTCState::Regs::HORIZONTAL_DISPLAY_RANGE_MASK;
|
||||
Log_DebugPrintf("Horizontal display range <- 0x%08X", new_value);
|
||||
|
||||
if (m_crtc_state.regs.horizontal_display_range != new_value)
|
||||
{
|
||||
m_tick_event->InvokeEarly(true);
|
||||
m_crtc_state.regs.horizontal_display_range = new_value;
|
||||
UpdateCRTCConfig();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x07: // Set display start address
|
||||
{
|
||||
m_crtc_state.regs.vertical_display_range = param & CRTCState::Regs::VERTICAL_DISPLAY_RANGE_MASK;
|
||||
Log_DebugPrintf("Vertical display range <- 0x%08X", m_crtc_state.regs.vertical_display_range);
|
||||
UpdateCRTCConfig();
|
||||
const u32 new_value = param & CRTCState::Regs::VERTICAL_DISPLAY_RANGE_MASK;
|
||||
Log_DebugPrintf("Vertical display range <- 0x%08X", new_value);
|
||||
|
||||
if (m_crtc_state.regs.vertical_display_range != new_value)
|
||||
{
|
||||
m_tick_event->InvokeEarly(true);
|
||||
m_crtc_state.regs.vertical_display_range = new_value;
|
||||
UpdateCRTCConfig();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -590,16 +646,22 @@ void GPU::WriteGP1(u32 value)
|
|||
};
|
||||
|
||||
const GP1_08h dm{param};
|
||||
m_GPUSTAT.horizontal_resolution_1 = dm.horizontal_resolution_1;
|
||||
m_GPUSTAT.vertical_resolution = dm.vertical_resolution;
|
||||
m_GPUSTAT.pal_mode = dm.pal_mode;
|
||||
m_GPUSTAT.display_area_color_depth_24 = dm.display_area_color_depth;
|
||||
m_GPUSTAT.vertical_interlace = dm.vertical_interlace;
|
||||
m_GPUSTAT.horizontal_resolution_2 = dm.horizontal_resolution_2;
|
||||
m_GPUSTAT.reverse_flag = dm.reverse_flag;
|
||||
|
||||
GPUSTAT new_GPUSTAT{m_GPUSTAT.bits};
|
||||
new_GPUSTAT.horizontal_resolution_1 = dm.horizontal_resolution_1;
|
||||
new_GPUSTAT.vertical_resolution = dm.vertical_resolution;
|
||||
new_GPUSTAT.pal_mode = dm.pal_mode;
|
||||
new_GPUSTAT.display_area_color_depth_24 = dm.display_area_color_depth;
|
||||
new_GPUSTAT.vertical_interlace = dm.vertical_interlace;
|
||||
new_GPUSTAT.horizontal_resolution_2 = dm.horizontal_resolution_2;
|
||||
new_GPUSTAT.reverse_flag = dm.reverse_flag;
|
||||
Log_DebugPrintf("Set display mode <- 0x%08X", dm.bits);
|
||||
UpdateCRTCConfig();
|
||||
|
||||
if (m_GPUSTAT.bits != new_GPUSTAT.bits)
|
||||
{
|
||||
m_tick_event->InvokeEarly(true);
|
||||
m_GPUSTAT.bits = new_GPUSTAT.bits;
|
||||
UpdateCRTCConfig();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ class StateWrapper;
|
|||
class HostDisplay;
|
||||
|
||||
class System;
|
||||
class TimingEvent;
|
||||
class DMA;
|
||||
class InterruptController;
|
||||
class Timers;
|
||||
|
@ -127,12 +128,12 @@ public:
|
|||
void DMARead(u32* words, u32 word_count);
|
||||
void DMAWrite(const u32* words, u32 word_count);
|
||||
|
||||
// Synchronizes the CRTC, updating the hblank timer.
|
||||
void Synchronize();
|
||||
|
||||
// Recompile shaders/recreate framebuffers when needed.
|
||||
virtual void UpdateSettings();
|
||||
|
||||
// Ticks for hblank/vblank.
|
||||
void Execute(TickCount ticks);
|
||||
|
||||
// gpu_hw_d3d11.cpp
|
||||
static std::unique_ptr<GPU> CreateHardwareD3D11Renderer();
|
||||
|
||||
|
@ -299,6 +300,9 @@ protected:
|
|||
// Updates dynamic bits in GPUSTAT (ready to send VRAM/ready to receive DMA)
|
||||
void UpdateGPUSTAT();
|
||||
|
||||
// Ticks for hblank/vblank.
|
||||
void Execute(TickCount ticks);
|
||||
|
||||
/// Returns true if scanout should be interlaced.
|
||||
bool IsDisplayInterlaced() const { return !m_force_progressive_scan && m_GPUSTAT.In480iMode(); }
|
||||
|
||||
|
@ -331,6 +335,8 @@ protected:
|
|||
InterruptController* m_interrupt_controller = nullptr;
|
||||
Timers* m_timers = nullptr;
|
||||
|
||||
std::unique_ptr<TimingEvent> m_tick_event;
|
||||
|
||||
// Pointer to VRAM, used for reads/writes. In the hardware backends, this is the shadow buffer.
|
||||
u16* m_vram_ptr = nullptr;
|
||||
|
||||
|
|
|
@ -360,7 +360,7 @@ void HostInterface::Throttle()
|
|||
const s64 sleep_time = static_cast<s64>(m_last_throttle_time - time);
|
||||
if (std::abs(sleep_time) >= MAX_VARIANCE_TIME)
|
||||
{
|
||||
#ifdef Y_BUILD_CONFIG_RELEASE
|
||||
#ifndef _DEBUG
|
||||
// Don't display the slow messages in debug, it'll always be slow...
|
||||
// Limit how often the messages are displayed.
|
||||
if (m_speed_lost_time_timestamp.GetTimeSeconds() >= 1.0f)
|
||||
|
@ -437,6 +437,9 @@ void HostInterface::UpdateSpeedLimiterState()
|
|||
(audio_sync_enabled && video_sync_enabled) ? " and video" : (video_sync_enabled ? "video" : ""));
|
||||
|
||||
m_audio_stream->SetSync(audio_sync_enabled);
|
||||
if (audio_sync_enabled)
|
||||
m_audio_stream->EmptyBuffers();
|
||||
|
||||
m_display->SetVSync(video_sync_enabled);
|
||||
m_throttle_timer.Reset();
|
||||
m_last_throttle_time = 0;
|
||||
|
|
|
@ -68,6 +68,9 @@ public:
|
|||
/// Returns a path relative to the user directory.
|
||||
std::string GetUserDirectoryRelativePath(const char* format, ...) const;
|
||||
|
||||
/// Throttles the system, i.e. sleeps until it's time to execute the next frame.
|
||||
void Throttle();
|
||||
|
||||
protected:
|
||||
using ThrottleClock = std::chrono::steady_clock;
|
||||
|
||||
|
@ -131,9 +134,6 @@ protected:
|
|||
|
||||
void RunFrame();
|
||||
|
||||
/// Throttles the system, i.e. sleeps until it's time to execute the next frame.
|
||||
void Throttle();
|
||||
|
||||
void UpdateSpeedLimiterState();
|
||||
|
||||
void DrawFPSWindow();
|
||||
|
|
|
@ -15,6 +15,8 @@ void MDEC::Initialize(System* system, DMA* dma)
|
|||
{
|
||||
m_system = system;
|
||||
m_dma = dma;
|
||||
m_block_copy_out_event = system->CreateTimingEvent("MDEC Block Copy Out", TICKS_PER_BLOCK, TICKS_PER_BLOCK,
|
||||
std::bind(&MDEC::CopyOutBlock, this), false);
|
||||
}
|
||||
|
||||
void MDEC::Reset()
|
||||
|
@ -39,8 +41,11 @@ bool MDEC::DoState(StateWrapper& sw)
|
|||
sw.Do(&m_current_coefficient);
|
||||
sw.Do(&m_current_q_scale);
|
||||
sw.Do(&m_block_rgb);
|
||||
sw.Do(&m_block_copy_out_ticks);
|
||||
sw.Do(&m_block_copy_out_pending);
|
||||
|
||||
bool block_copy_out_pending = HasPendingBlockCopyOut();
|
||||
sw.Do(&block_copy_out_pending);
|
||||
if (sw.IsReading())
|
||||
m_block_copy_out_event->SetState(block_copy_out_pending);
|
||||
|
||||
return !sw.HasError();
|
||||
}
|
||||
|
@ -131,6 +136,11 @@ void MDEC::DMAWrite(const u32* words, u32 word_count)
|
|||
} while (word_count > 0);
|
||||
}
|
||||
|
||||
bool MDEC::HasPendingBlockCopyOut() const
|
||||
{
|
||||
return m_block_copy_out_event->IsActive();
|
||||
}
|
||||
|
||||
void MDEC::SoftReset()
|
||||
{
|
||||
m_status.bits = 0;
|
||||
|
@ -143,8 +153,7 @@ void MDEC::SoftReset()
|
|||
m_current_block = 0;
|
||||
m_current_coefficient = 64;
|
||||
m_current_q_scale = 0;
|
||||
m_block_copy_out_ticks = TICKS_PER_BLOCK;
|
||||
m_block_copy_out_pending = false;
|
||||
m_block_copy_out_event->Deactivate();
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
|
@ -173,11 +182,10 @@ u32 MDEC::ReadDataRegister()
|
|||
if (m_data_out_fifo.IsEmpty())
|
||||
{
|
||||
// Stall the CPU until we're done processing.
|
||||
if (m_block_copy_out_pending)
|
||||
if (HasPendingBlockCopyOut())
|
||||
{
|
||||
Log_DevPrint("MDEC data out FIFO empty on read - stalling CPU");
|
||||
m_system->StallCPU(m_block_copy_out_ticks);
|
||||
Execute(m_block_copy_out_ticks);
|
||||
m_system->StallCPU(m_block_copy_out_event->GetTicksUntilNextExecution());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -205,27 +213,9 @@ void MDEC::WriteCommandRegister(u32 value)
|
|||
ExecutePendingCommand();
|
||||
}
|
||||
|
||||
void MDEC::Execute(TickCount ticks)
|
||||
{
|
||||
if (!m_block_copy_out_pending)
|
||||
return;
|
||||
|
||||
m_block_copy_out_ticks -= ticks;
|
||||
if (m_block_copy_out_ticks <= 0)
|
||||
{
|
||||
DebugAssert(m_command == Command::DecodeMacroblock);
|
||||
CopyOutBlock();
|
||||
|
||||
if (m_remaining_halfwords == 0)
|
||||
EndCommand();
|
||||
else
|
||||
ExecutePendingCommand();
|
||||
}
|
||||
}
|
||||
|
||||
void MDEC::ExecutePendingCommand()
|
||||
{
|
||||
if (m_block_copy_out_pending)
|
||||
if (HasPendingBlockCopyOut())
|
||||
{
|
||||
// can't do anything while waiting
|
||||
UpdateStatus();
|
||||
|
@ -234,7 +224,7 @@ void MDEC::ExecutePendingCommand()
|
|||
|
||||
while (!m_data_in_fifo.IsEmpty())
|
||||
{
|
||||
DebugAssert(!m_block_copy_out_pending);
|
||||
DebugAssert(!HasPendingBlockCopyOut());
|
||||
|
||||
if (m_command == Command::None)
|
||||
{
|
||||
|
@ -417,20 +407,16 @@ bool MDEC::DecodeColoredMacroblock()
|
|||
|
||||
void MDEC::ScheduleBlockCopyOut(TickCount ticks)
|
||||
{
|
||||
DebugAssert(!m_block_copy_out_pending);
|
||||
DebugAssert(!HasPendingBlockCopyOut());
|
||||
Log_DebugPrintf("Scheduling block copy out in %d ticks", ticks);
|
||||
|
||||
m_system->Synchronize();
|
||||
m_block_copy_out_pending = true;
|
||||
m_block_copy_out_ticks = ticks;
|
||||
m_system->SetDowncount(ticks);
|
||||
m_block_copy_out_event->Schedule(TICKS_PER_BLOCK);
|
||||
}
|
||||
|
||||
void MDEC::CopyOutBlock()
|
||||
{
|
||||
DebugAssert(m_block_copy_out_pending);
|
||||
m_block_copy_out_pending = false;
|
||||
m_block_copy_out_ticks = 0;
|
||||
DebugAssert(m_command == Command::DecodeMacroblock);
|
||||
m_block_copy_out_event->Deactivate();
|
||||
|
||||
Log_DebugPrintf("Copying out block");
|
||||
|
||||
|
@ -535,13 +521,9 @@ void MDEC::CopyOutBlock()
|
|||
|
||||
// if we've copied out all blocks, command is complete
|
||||
if (m_remaining_halfwords == 0)
|
||||
{
|
||||
DebugAssert(m_command == Command::DecodeMacroblock);
|
||||
m_command = Command::None;
|
||||
m_current_block = 0;
|
||||
m_current_coefficient = 64;
|
||||
m_current_q_scale = 0;
|
||||
}
|
||||
EndCommand();
|
||||
else
|
||||
ExecutePendingCommand();
|
||||
}
|
||||
|
||||
static constexpr std::array<u8, 64> zigzag = {{0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42,
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
#include "common/fifo_queue.h"
|
||||
#include "types.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
class StateWrapper;
|
||||
|
||||
class System;
|
||||
class TimingEvent;
|
||||
class DMA;
|
||||
|
||||
class MDEC
|
||||
|
@ -26,8 +28,6 @@ public:
|
|||
void DMARead(u32* words, u32 word_count);
|
||||
void DMAWrite(const u32* words, u32 word_count);
|
||||
|
||||
void Execute(TickCount ticks);
|
||||
|
||||
void DrawDebugStateWindow();
|
||||
|
||||
private:
|
||||
|
@ -88,6 +88,7 @@ private:
|
|||
};
|
||||
|
||||
bool HasPendingCommand() const { return m_command != Command::None; }
|
||||
bool HasPendingBlockCopyOut() const;
|
||||
|
||||
void SoftReset();
|
||||
void UpdateStatus();
|
||||
|
@ -138,8 +139,7 @@ private:
|
|||
u16 m_current_q_scale = 0;
|
||||
|
||||
std::array<u32, 256> m_block_rgb{};
|
||||
TickCount m_block_copy_out_ticks = TICKS_PER_BLOCK;
|
||||
bool m_block_copy_out_pending = false;
|
||||
std::unique_ptr<TimingEvent> m_block_copy_out_event;
|
||||
|
||||
u32 m_total_blocks_decoded = 0;
|
||||
};
|
||||
|
|
|
@ -16,6 +16,8 @@ void Pad::Initialize(System* system, InterruptController* interrupt_controller)
|
|||
{
|
||||
m_system = system;
|
||||
m_interrupt_controller = interrupt_controller;
|
||||
m_transfer_event = system->CreateTimingEvent("Pad Serial Transfer", 1, 1,
|
||||
std::bind(&Pad::TransferEvent, this, std::placeholders::_2), false);
|
||||
}
|
||||
|
||||
void Pad::Reset()
|
||||
|
@ -81,7 +83,6 @@ bool Pad::DoState(StateWrapper& sw)
|
|||
}
|
||||
|
||||
sw.Do(&m_state);
|
||||
sw.Do(&m_ticks_remaining);
|
||||
sw.Do(&m_JOY_CTRL.bits);
|
||||
sw.Do(&m_JOY_STAT.bits);
|
||||
sw.Do(&m_JOY_MODE.bits);
|
||||
|
@ -91,6 +92,9 @@ bool Pad::DoState(StateWrapper& sw)
|
|||
sw.Do(&m_receive_buffer_full);
|
||||
sw.Do(&m_transmit_buffer_full);
|
||||
|
||||
if (sw.IsReading() && IsTransmitting())
|
||||
m_transfer_event->Activate();
|
||||
|
||||
return !sw.HasError();
|
||||
}
|
||||
|
||||
|
@ -213,24 +217,6 @@ void Pad::WriteRegister(u32 offset, u32 value)
|
|||
}
|
||||
}
|
||||
|
||||
void Pad::Execute(TickCount ticks)
|
||||
{
|
||||
if (m_state == State::Idle)
|
||||
return;
|
||||
|
||||
m_ticks_remaining -= ticks;
|
||||
if (m_ticks_remaining > 0)
|
||||
{
|
||||
m_system->SetDowncount(m_ticks_remaining);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_state == State::Transmitting)
|
||||
DoTransfer();
|
||||
else
|
||||
DoACK();
|
||||
}
|
||||
|
||||
void Pad::SoftReset()
|
||||
{
|
||||
if (IsTransmitting())
|
||||
|
@ -254,6 +240,14 @@ void Pad::UpdateJoyStat()
|
|||
m_JOY_STAT.TXRDY = !m_transmit_buffer_full;
|
||||
}
|
||||
|
||||
void Pad::TransferEvent(TickCount ticks_late)
|
||||
{
|
||||
if (m_state == State::Transmitting)
|
||||
DoTransfer(ticks_late);
|
||||
else
|
||||
DoACK();
|
||||
}
|
||||
|
||||
void Pad::BeginTransfer()
|
||||
{
|
||||
DebugAssert(m_state == State::Idle && CanTransfer());
|
||||
|
@ -278,13 +272,11 @@ void Pad::BeginTransfer()
|
|||
// test in (7) will fail, and it won't send any more data. So, the transfer/interrupt must be delayed
|
||||
// until after (4) and (5) have been completed.
|
||||
|
||||
m_system->Synchronize();
|
||||
m_state = State::Transmitting;
|
||||
m_ticks_remaining = GetTransferTicks();
|
||||
m_system->SetDowncount(m_ticks_remaining);
|
||||
m_transfer_event->SetPeriodAndSchedule(GetTransferTicks());
|
||||
}
|
||||
|
||||
void Pad::DoTransfer()
|
||||
void Pad::DoTransfer(TickCount ticks_late)
|
||||
{
|
||||
Log_DebugPrintf("Transferring slot %d", m_JOY_CTRL.SLOT.GetValue());
|
||||
|
||||
|
@ -361,11 +353,10 @@ void Pad::DoTransfer()
|
|||
const TickCount ack_timer = GetACKTicks();
|
||||
Log_DebugPrintf("Delaying ACK for %d ticks", ack_timer);
|
||||
m_state = State::WaitingForACK;
|
||||
m_ticks_remaining += ack_timer;
|
||||
if (m_ticks_remaining <= 0)
|
||||
if (ticks_late >= ack_timer)
|
||||
DoACK();
|
||||
else
|
||||
m_system->SetDowncount(m_ticks_remaining);
|
||||
m_transfer_event->SetPeriodAndSchedule(ack_timer - ticks_late);
|
||||
}
|
||||
|
||||
UpdateJoyStat();
|
||||
|
@ -395,7 +386,7 @@ void Pad::EndTransfer()
|
|||
Log_DebugPrintf("Ending transfer");
|
||||
|
||||
m_state = State::Idle;
|
||||
m_ticks_remaining = 0;
|
||||
m_transfer_event->Deactivate();
|
||||
}
|
||||
|
||||
void Pad::ResetDeviceTransferState()
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
class StateWrapper;
|
||||
|
||||
class System;
|
||||
class TimingEvent;
|
||||
class InterruptController;
|
||||
class Controller;
|
||||
class MemoryCard;
|
||||
|
@ -31,8 +32,6 @@ public:
|
|||
u32 ReadRegister(u32 offset);
|
||||
void WriteRegister(u32 offset, u32 value);
|
||||
|
||||
void Execute(TickCount ticks);
|
||||
|
||||
private:
|
||||
static constexpr u32 NUM_SLOTS = 2;
|
||||
|
||||
|
@ -97,8 +96,9 @@ private:
|
|||
|
||||
void SoftReset();
|
||||
void UpdateJoyStat();
|
||||
void TransferEvent(TickCount ticks_late);
|
||||
void BeginTransfer();
|
||||
void DoTransfer();
|
||||
void DoTransfer(TickCount ticks_late);
|
||||
void DoACK();
|
||||
void EndTransfer();
|
||||
void ResetDeviceTransferState();
|
||||
|
@ -109,8 +109,8 @@ private:
|
|||
std::array<std::unique_ptr<Controller>, NUM_SLOTS> m_controllers;
|
||||
std::array<std::unique_ptr<MemoryCard>, NUM_SLOTS> m_memory_cards;
|
||||
|
||||
std::unique_ptr<TimingEvent> m_transfer_event;
|
||||
State m_state = State::Idle;
|
||||
TickCount m_ticks_remaining = 0;
|
||||
|
||||
JOY_CTRL m_JOY_CTRL = {};
|
||||
JOY_STAT m_JOY_STAT = {};
|
||||
|
|
225
src/core/spu.cpp
225
src/core/spu.cpp
|
@ -1,6 +1,6 @@
|
|||
#include "spu.h"
|
||||
#include "common/log.h"
|
||||
#include "common/audio_stream.h"
|
||||
#include "common/log.h"
|
||||
#include "common/state_wrapper.h"
|
||||
#include "dma.h"
|
||||
#include "host_interface.h"
|
||||
|
@ -24,6 +24,8 @@ void SPU::Initialize(System* system, DMA* dma, InterruptController* interrupt_co
|
|||
m_system = system;
|
||||
m_dma = dma;
|
||||
m_interrupt_controller = interrupt_controller;
|
||||
m_sample_event = m_system->CreateTimingEvent("SPU Sample", SYSCLK_TICKS_PER_SPU_TICK, SYSCLK_TICKS_PER_SPU_TICK,
|
||||
std::bind(&SPU::Execute, this, std::placeholders::_1), false);
|
||||
}
|
||||
|
||||
void SPU::Reset()
|
||||
|
@ -65,6 +67,7 @@ void SPU::Reset()
|
|||
}
|
||||
|
||||
m_ram.fill(0);
|
||||
UpdateEventInterval();
|
||||
}
|
||||
|
||||
bool SPU::DoState(StateWrapper& sw)
|
||||
|
@ -108,7 +111,10 @@ bool SPU::DoState(StateWrapper& sw)
|
|||
sw.DoBytes(m_ram.data(), RAM_SIZE);
|
||||
|
||||
if (sw.IsReading())
|
||||
{
|
||||
m_system->GetHostInterface()->GetAudioStream()->EmptyBuffers();
|
||||
UpdateEventInterval();
|
||||
}
|
||||
|
||||
return !sw.HasError();
|
||||
}
|
||||
|
@ -178,6 +184,7 @@ u16 SPU::ReadRegister(u32 offset)
|
|||
|
||||
case 0x1F801DAE - SPU_BASE:
|
||||
// Log_DebugPrintf("SPU status register -> 0x%04X", ZeroExtend32(m_SPUCNT.bits));
|
||||
m_sample_event->InvokeEarly();
|
||||
return m_SPUSTAT.bits;
|
||||
|
||||
case 0x1F801DB0 - SPU_BASE:
|
||||
|
@ -205,7 +212,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
case 0x1F801D80 - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU main volume left <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
m_main_volume_left.bits = value;
|
||||
return;
|
||||
}
|
||||
|
@ -213,7 +220,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
case 0x1F801D82 - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU main volume right <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
m_main_volume_right.bits = value;
|
||||
return;
|
||||
}
|
||||
|
@ -221,7 +228,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
case 0x1F801D88 - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU key on low <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
m_key_on_register = (m_key_on_register & 0xFFFF0000) | ZeroExtend32(value);
|
||||
|
||||
u16 bits = value;
|
||||
|
@ -240,7 +247,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
case 0x1F801D8A - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU key on high <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
m_key_on_register = (m_key_on_register & 0x0000FFFF) | (ZeroExtend32(value) << 16);
|
||||
|
||||
u16 bits = value;
|
||||
|
@ -259,7 +266,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
case 0x1F801D8C - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU key off low <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
m_key_on_register = (m_key_on_register & 0xFFFF0000) | ZeroExtend32(value);
|
||||
|
||||
u16 bits = value;
|
||||
|
@ -278,7 +285,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
case 0x1F801D8E - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU key off high <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
m_key_on_register = (m_key_on_register & 0x0000FFFF) | (ZeroExtend32(value) << 16);
|
||||
|
||||
u16 bits = value;
|
||||
|
@ -296,7 +303,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
|
||||
case 0x1F801D90 - SPU_BASE:
|
||||
{
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
m_pitch_modulation_enable_register = (m_pitch_modulation_enable_register & 0xFFFF0000) | ZeroExtend32(value);
|
||||
Log_DebugPrintf("SPU pitch modulation enable register <- 0x%08X", m_pitch_modulation_enable_register);
|
||||
}
|
||||
|
@ -304,7 +311,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
|
||||
case 0x1F801D92 - SPU_BASE:
|
||||
{
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
m_pitch_modulation_enable_register =
|
||||
(m_pitch_modulation_enable_register & 0x0000FFFF) | (ZeroExtend32(value) << 16);
|
||||
Log_DebugPrintf("SPU pitch modulation enable register <- 0x%08X", m_pitch_modulation_enable_register);
|
||||
|
@ -314,7 +321,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
case 0x1F801D94 - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU noise mode register <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
m_noise_mode_register = (m_noise_mode_register & 0xFFFF0000) | ZeroExtend32(value);
|
||||
}
|
||||
break;
|
||||
|
@ -322,7 +329,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
case 0x1F801D96 - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU noise mode register <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
m_noise_mode_register = (m_noise_mode_register & 0x0000FFFF) | (ZeroExtend32(value) << 16);
|
||||
}
|
||||
break;
|
||||
|
@ -330,7 +337,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
case 0x1F801D98 - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU reverb on register <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
m_reverb_on_register = (m_reverb_on_register & 0xFFFF0000) | ZeroExtend32(value);
|
||||
}
|
||||
break;
|
||||
|
@ -338,7 +345,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
case 0x1F801D9A - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU reverb off register <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
m_reverb_on_register = (m_reverb_on_register & 0x0000FFFF) | (ZeroExtend32(value) << 16);
|
||||
}
|
||||
break;
|
||||
|
@ -346,6 +353,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
case 0x1F801DA4 - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU IRQ address register <- 0x%04X", ZeroExtend32(value));
|
||||
m_sample_event->InvokeEarly();
|
||||
m_irq_address = value;
|
||||
return;
|
||||
}
|
||||
|
@ -369,6 +377,8 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
case 0x1F801DAA - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU control register <- 0x%04X", ZeroExtend32(value));
|
||||
m_sample_event->InvokeEarly(true);
|
||||
|
||||
m_SPUCNT.bits = value;
|
||||
m_SPUSTAT.mode = m_SPUCNT.mode.GetValue();
|
||||
m_SPUSTAT.dma_read_write_request = m_SPUCNT.ram_transfer_mode >= RAMTransferMode::DMAWrite;
|
||||
|
@ -377,6 +387,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
m_SPUSTAT.irq9_flag = false;
|
||||
|
||||
UpdateDMARequest();
|
||||
UpdateEventInterval();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -390,7 +401,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
case 0x1F801DB0 - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU left cd audio register <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
m_cd_audio_volume_left = value;
|
||||
}
|
||||
break;
|
||||
|
@ -398,7 +409,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
case 0x1F801DB2 - SPU_BASE:
|
||||
{
|
||||
Log_DebugPrintf("SPU right cd audio register <- 0x%04X", ZeroExtend32(value));
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
m_cd_audio_volume_right = value;
|
||||
}
|
||||
break;
|
||||
|
@ -424,6 +435,12 @@ u16 SPU::ReadVoiceRegister(u32 offset)
|
|||
const u32 voice_index = (offset / 0x10); //((offset >> 4) & 0x1F);
|
||||
Assert(voice_index < 24);
|
||||
|
||||
if (reg_index >= 6)
|
||||
{
|
||||
// adsr volume needs to be updated when reading
|
||||
m_sample_event->InvokeEarly();
|
||||
}
|
||||
|
||||
return m_voices[voice_index].regs.index[reg_index];
|
||||
}
|
||||
|
||||
|
@ -436,7 +453,7 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value)
|
|||
|
||||
Voice& voice = m_voices[voice_index];
|
||||
if (voice.IsOn())
|
||||
m_system->Synchronize();
|
||||
m_sample_event->InvokeEarly();
|
||||
|
||||
switch (reg_index)
|
||||
{
|
||||
|
@ -606,13 +623,103 @@ void SPU::IncrementCaptureBufferPosition()
|
|||
|
||||
void SPU::Execute(TickCount ticks)
|
||||
{
|
||||
TickCount num_samples = (ticks + m_ticks_carry) / SYSCLK_TICKS_PER_SPU_TICK;
|
||||
DebugAssert(m_SPUCNT.enable || m_SPUCNT.cd_audio_enable);
|
||||
|
||||
u32 remaining_frames = static_cast<u32>((ticks + m_ticks_carry) / SYSCLK_TICKS_PER_SPU_TICK);
|
||||
m_ticks_carry = (ticks + m_ticks_carry) % SYSCLK_TICKS_PER_SPU_TICK;
|
||||
if (num_samples == 0 || (!m_SPUCNT.enable && !m_SPUCNT.cd_audio_enable))
|
||||
|
||||
while (remaining_frames > 0)
|
||||
{
|
||||
AudioStream* const output_stream = m_system->GetHostInterface()->GetAudioStream();
|
||||
s16* output_frame;
|
||||
u32 output_frame_space;
|
||||
output_stream->BeginWrite(&output_frame, &output_frame_space);
|
||||
|
||||
const u32 frames_in_this_batch = std::min(remaining_frames, output_frame_space);
|
||||
for (u32 i = 0; i < frames_in_this_batch; i++)
|
||||
{
|
||||
s32 left_sum = 0;
|
||||
s32 right_sum = 0;
|
||||
if (m_SPUCNT.enable)
|
||||
{
|
||||
for (u32 voice = 0; voice < NUM_VOICES; voice++)
|
||||
{
|
||||
const auto [left, right] = SampleVoice(voice);
|
||||
left_sum += left;
|
||||
right_sum += right;
|
||||
}
|
||||
|
||||
if (!m_SPUCNT.mute_n)
|
||||
{
|
||||
left_sum = 0;
|
||||
right_sum = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Mix in CD audio.
|
||||
s16 cd_audio_left;
|
||||
s16 cd_audio_right;
|
||||
if (!m_cd_audio_buffer.IsEmpty())
|
||||
{
|
||||
cd_audio_left = m_cd_audio_buffer.Pop();
|
||||
cd_audio_right = m_cd_audio_buffer.Pop();
|
||||
if (m_SPUCNT.cd_audio_enable)
|
||||
{
|
||||
left_sum += ApplyVolume(s32(cd_audio_left), m_cd_audio_volume_left);
|
||||
right_sum += ApplyVolume(s32(cd_audio_right), m_cd_audio_volume_right);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cd_audio_left = 0;
|
||||
cd_audio_right = 0;
|
||||
}
|
||||
|
||||
// Apply main volume before clamping.
|
||||
*(output_frame++) = Clamp16(ApplyVolume(left_sum, m_main_volume_left.GetVolume()));
|
||||
*(output_frame++) = Clamp16(ApplyVolume(right_sum, m_main_volume_right.GetVolume()));
|
||||
|
||||
// Write to capture buffers.
|
||||
WriteToCaptureBuffer(0, cd_audio_left);
|
||||
WriteToCaptureBuffer(1, cd_audio_right);
|
||||
WriteToCaptureBuffer(2, Clamp16(m_voices[1].last_amplitude));
|
||||
WriteToCaptureBuffer(3, Clamp16(m_voices[3].last_amplitude));
|
||||
IncrementCaptureBufferPosition();
|
||||
}
|
||||
|
||||
output_stream->EndWrite(frames_in_this_batch);
|
||||
remaining_frames -= frames_in_this_batch;
|
||||
}
|
||||
}
|
||||
|
||||
void SPU::UpdateEventInterval()
|
||||
{
|
||||
if (!m_SPUCNT.enable && !m_SPUCNT.cd_audio_enable)
|
||||
{
|
||||
m_sample_event->Deactivate();
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't generate more than the audio buffer since in a single slice, otherwise we'll both overflow the buffers when
|
||||
// we do write it, and the audio thread will underflow since it won't have enough data it the game isn't messing with
|
||||
// the SPU state.
|
||||
const u32 max_slice_frames = m_system->GetHostInterface()->GetAudioStream()->GetBufferSize();
|
||||
|
||||
// TODO: Make this predict how long until the interrupt will be hit instead...
|
||||
const u32 interval = m_SPUCNT.irq9_enable ? 1 : max_slice_frames;
|
||||
const TickCount interval_ticks = static_cast<TickCount>(interval) * SYSCLK_TICKS_PER_SPU_TICK;
|
||||
if (m_sample_event->IsActive() && m_sample_event->GetInterval() == interval_ticks)
|
||||
return;
|
||||
|
||||
for (TickCount i = 0; i < num_samples; i++)
|
||||
GenerateSample();
|
||||
// Ensure all pending ticks have been executed, since we won't get them back after rescheduling.
|
||||
m_sample_event->InvokeEarly(true);
|
||||
m_sample_event->SetInterval(interval_ticks);
|
||||
m_sample_event->Schedule(interval_ticks - m_ticks_carry);
|
||||
}
|
||||
|
||||
void SPU::GeneratePendingSamples()
|
||||
{
|
||||
m_sample_event->InvokeEarly();
|
||||
}
|
||||
|
||||
void SPU::Voice::KeyOn()
|
||||
|
@ -952,80 +1059,22 @@ std::tuple<s32, s32> SPU::SampleVoice(u32 voice_index)
|
|||
return std::make_tuple(left, right);
|
||||
}
|
||||
|
||||
void SPU::EnsureCDAudioSpace(u32 num_samples)
|
||||
void SPU::EnsureCDAudioSpace(u32 remaining_frames)
|
||||
{
|
||||
if (m_cd_audio_buffer.GetSpace() < (num_samples * 2))
|
||||
if (m_cd_audio_buffer.IsEmpty())
|
||||
{
|
||||
Log_WarningPrintf("SPU CD Audio buffer overflow - writing %u samples with %u samples space", num_samples,
|
||||
// we want the audio to start playing at the right point, not a few cycles early, otherwise this'll cause sync issues.
|
||||
m_sample_event->InvokeEarly();
|
||||
}
|
||||
|
||||
if (m_cd_audio_buffer.GetSpace() < (remaining_frames * 2))
|
||||
{
|
||||
Log_WarningPrintf("SPU CD Audio buffer overflow - writing %u samples with %u samples space", remaining_frames,
|
||||
m_cd_audio_buffer.GetSpace() / 2);
|
||||
m_cd_audio_buffer.Remove((num_samples * 2) - m_cd_audio_buffer.GetSpace());
|
||||
m_cd_audio_buffer.Remove((remaining_frames * 2) - m_cd_audio_buffer.GetSpace());
|
||||
}
|
||||
}
|
||||
|
||||
void SPU::GenerateSample()
|
||||
{
|
||||
s32 left_sum = 0;
|
||||
s32 right_sum = 0;
|
||||
if (m_SPUCNT.enable)
|
||||
{
|
||||
for (u32 i = 0; i < NUM_VOICES; i++)
|
||||
{
|
||||
const auto [left, right] = SampleVoice(i);
|
||||
left_sum += left;
|
||||
right_sum += right;
|
||||
}
|
||||
|
||||
if (!m_SPUCNT.mute_n)
|
||||
{
|
||||
left_sum = 0;
|
||||
right_sum = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Mix in CD audio.
|
||||
s16 cd_audio_left;
|
||||
s16 cd_audio_right;
|
||||
if (!m_cd_audio_buffer.IsEmpty())
|
||||
{
|
||||
cd_audio_left = m_cd_audio_buffer.Pop();
|
||||
cd_audio_right = m_cd_audio_buffer.Pop();
|
||||
if (m_SPUCNT.cd_audio_enable)
|
||||
{
|
||||
left_sum += ApplyVolume(s32(cd_audio_left), m_cd_audio_volume_left);
|
||||
right_sum += ApplyVolume(s32(cd_audio_right), m_cd_audio_volume_right);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cd_audio_left = 0;
|
||||
cd_audio_right = 0;
|
||||
}
|
||||
|
||||
// Apply main volume before clamping.
|
||||
std::array<AudioStream::SampleType, 2> out_samples;
|
||||
out_samples[0] = Clamp16(ApplyVolume(left_sum, m_main_volume_left.GetVolume()));
|
||||
out_samples[1] = Clamp16(ApplyVolume(right_sum, m_main_volume_right.GetVolume()));
|
||||
m_system->GetHostInterface()->GetAudioStream()->WriteSamples(out_samples.data(), 1);
|
||||
|
||||
// Write to capture buffers.
|
||||
WriteToCaptureBuffer(0, cd_audio_left);
|
||||
WriteToCaptureBuffer(1, cd_audio_right);
|
||||
WriteToCaptureBuffer(2, Clamp16(m_voices[1].last_amplitude));
|
||||
WriteToCaptureBuffer(3, Clamp16(m_voices[3].last_amplitude));
|
||||
IncrementCaptureBufferPosition();
|
||||
|
||||
#if 0
|
||||
static FILE* fp = nullptr;
|
||||
if (!fp)
|
||||
fp = std::fopen("D:\\spu.raw", "wb");
|
||||
if (fp)
|
||||
{
|
||||
std::fwrite(out_samples.data(), sizeof(AudioStream::SampleType), 2, fp);
|
||||
std::fflush(fp);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SPU::DrawDebugStateWindow()
|
||||
{
|
||||
static const ImVec4 active_color{1.0f, 1.0f, 1.0f, 1.0f};
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
#include "common/fifo_queue.h"
|
||||
#include "types.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
class AudioStream;
|
||||
class StateWrapper;
|
||||
|
||||
class System;
|
||||
class TimingEvent;
|
||||
class DMA;
|
||||
class InterruptController;
|
||||
|
||||
|
@ -27,8 +28,6 @@ public:
|
|||
void DMARead(u32* words, u32 word_count);
|
||||
void DMAWrite(const u32* words, u32 word_count);
|
||||
|
||||
void Execute(TickCount ticks);
|
||||
|
||||
// Render statistics debug window.
|
||||
void DrawDebugStateWindow();
|
||||
|
||||
|
@ -40,6 +39,9 @@ public:
|
|||
}
|
||||
void EnsureCDAudioSpace(u32 num_samples);
|
||||
|
||||
// Executes the SPU, generating any pending samples.
|
||||
void GeneratePendingSamples();
|
||||
|
||||
private:
|
||||
static constexpr u32 RAM_SIZE = 512 * 1024;
|
||||
static constexpr u32 RAM_MASK = RAM_SIZE - 1;
|
||||
|
@ -282,11 +284,13 @@ private:
|
|||
|
||||
void ReadADPCMBlock(u16 address, ADPCMBlock* block);
|
||||
std::tuple<s32, s32> SampleVoice(u32 voice_index);
|
||||
void GenerateSample();
|
||||
void Execute(TickCount ticks);
|
||||
void UpdateEventInterval();
|
||||
|
||||
System* m_system = nullptr;
|
||||
DMA* m_dma = nullptr;
|
||||
InterruptController* m_interrupt_controller = nullptr;
|
||||
std::unique_ptr<TimingEvent> m_sample_event = nullptr;
|
||||
|
||||
SPUCNT m_SPUCNT = {};
|
||||
SPUSTAT m_SPUSTAT = {};
|
||||
|
|
|
@ -40,7 +40,11 @@ System::System(HostInterface* host_interface) : m_host_interface(host_interface)
|
|||
m_cpu_execution_mode = host_interface->m_settings.cpu_execution_mode;
|
||||
}
|
||||
|
||||
System::~System() = default;
|
||||
System::~System()
|
||||
{
|
||||
// we have to explicitly destroy components because they can deregister events
|
||||
DestroyComponents();
|
||||
}
|
||||
|
||||
std::unique_ptr<System> System::Create(HostInterface* host_interface)
|
||||
{
|
||||
|
@ -56,7 +60,7 @@ bool System::RecreateGPU(GPURenderer renderer)
|
|||
// save current state
|
||||
std::unique_ptr<ByteStream> state_stream = ByteStream_CreateGrowableMemoryStream();
|
||||
StateWrapper sw(state_stream.get(), StateWrapper::Mode::Write);
|
||||
const bool state_valid = m_gpu->DoState(sw);
|
||||
const bool state_valid = m_gpu->DoState(sw) && DoEventsState(sw);
|
||||
if (!state_valid)
|
||||
Log_ErrorPrintf("Failed to save old GPU state when switching renderers");
|
||||
|
||||
|
@ -73,6 +77,7 @@ bool System::RecreateGPU(GPURenderer renderer)
|
|||
state_stream->SeekAbsolute(0);
|
||||
sw.SetMode(StateWrapper::Mode::Read);
|
||||
m_gpu->DoState(sw);
|
||||
DoEventsState(sw);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -188,11 +193,26 @@ void System::InitializeComponents()
|
|||
|
||||
m_cdrom->Initialize(this, m_dma.get(), m_interrupt_controller.get(), m_spu.get());
|
||||
m_pad->Initialize(this, m_interrupt_controller.get());
|
||||
m_timers->Initialize(this, m_interrupt_controller.get());
|
||||
m_timers->Initialize(this, m_interrupt_controller.get(), m_gpu.get());
|
||||
m_spu->Initialize(this, m_dma.get(), m_interrupt_controller.get());
|
||||
m_mdec->Initialize(this, m_dma.get());
|
||||
}
|
||||
|
||||
void System::DestroyComponents()
|
||||
{
|
||||
m_mdec.reset();
|
||||
m_spu.reset();
|
||||
m_timers.reset();
|
||||
m_pad.reset();
|
||||
m_cdrom.reset();
|
||||
m_gpu.reset();
|
||||
m_interrupt_controller.reset();
|
||||
m_dma.reset();
|
||||
m_bus.reset();
|
||||
m_cpu_code_cache.reset();
|
||||
m_cpu.reset();
|
||||
}
|
||||
|
||||
bool System::CreateGPU(GPURenderer renderer)
|
||||
{
|
||||
switch (renderer)
|
||||
|
@ -230,6 +250,7 @@ bool System::CreateGPU(GPURenderer renderer)
|
|||
|
||||
m_bus->SetGPU(m_gpu.get());
|
||||
m_dma->SetGPU(m_gpu.get());
|
||||
m_timers->SetGPU(m_gpu.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -298,6 +319,9 @@ bool System::DoState(StateWrapper& sw)
|
|||
if (!sw.DoMarker("SIO") || !m_sio->DoState(sw))
|
||||
return false;
|
||||
|
||||
if (!sw.DoMarker("Events") || !DoEventsState(sw))
|
||||
return false;
|
||||
|
||||
return !sw.HasError();
|
||||
}
|
||||
|
||||
|
@ -318,6 +342,7 @@ void System::Reset()
|
|||
m_frame_number = 1;
|
||||
m_internal_frame_number = 0;
|
||||
m_global_tick_counter = 0;
|
||||
m_last_event_run_time = 0;
|
||||
}
|
||||
|
||||
bool System::LoadState(ByteStream* state)
|
||||
|
@ -335,23 +360,28 @@ bool System::SaveState(ByteStream* state)
|
|||
void System::RunFrame()
|
||||
{
|
||||
// Duplicated to avoid branch in the while loop, as the downcount can be quite low at times.
|
||||
u32 current_frame_number = m_frame_number;
|
||||
m_frame_done = false;
|
||||
if (m_cpu_execution_mode == CPUExecutionMode::Interpreter)
|
||||
{
|
||||
while (current_frame_number == m_frame_number)
|
||||
do
|
||||
{
|
||||
UpdateCPUDowncount();
|
||||
m_cpu->Execute();
|
||||
Synchronize();
|
||||
}
|
||||
RunEvents();
|
||||
} while (!m_frame_done);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (current_frame_number == m_frame_number)
|
||||
do
|
||||
{
|
||||
UpdateCPUDowncount();
|
||||
m_cpu_code_cache->Execute();
|
||||
Synchronize();
|
||||
}
|
||||
RunEvents();
|
||||
} while (!m_frame_done);
|
||||
}
|
||||
|
||||
// Generate any pending samples from the SPU before sleeping, this way we reduce the chances of underruns.
|
||||
m_spu->GeneratePendingSamples();
|
||||
}
|
||||
|
||||
bool System::LoadEXE(const char* filename, std::vector<u8>& bios_image)
|
||||
|
@ -438,34 +468,11 @@ bool System::SetExpansionROM(const char* filename)
|
|||
return true;
|
||||
}
|
||||
|
||||
void System::Synchronize()
|
||||
{
|
||||
const TickCount pending_ticks = m_cpu->GetPendingTicks();
|
||||
if (pending_ticks == 0)
|
||||
return;
|
||||
|
||||
m_cpu->ResetPendingTicks();
|
||||
m_cpu->ResetDowncount();
|
||||
|
||||
m_global_tick_counter += static_cast<u32>(pending_ticks);
|
||||
|
||||
m_gpu->Execute(pending_ticks);
|
||||
m_timers->Execute(pending_ticks);
|
||||
m_cdrom->Execute(pending_ticks);
|
||||
m_pad->Execute(pending_ticks);
|
||||
m_spu->Execute(pending_ticks);
|
||||
m_mdec->Execute(pending_ticks);
|
||||
m_dma->Execute(pending_ticks);
|
||||
}
|
||||
|
||||
void System::SetDowncount(TickCount downcount)
|
||||
{
|
||||
m_cpu->SetDowncount(downcount);
|
||||
}
|
||||
|
||||
void System::StallCPU(TickCount ticks)
|
||||
{
|
||||
m_cpu->AddPendingTicks(ticks);
|
||||
if (m_cpu->GetPendingTicks() >= m_cpu->GetDowncount() && !m_running_events)
|
||||
RunEvents();
|
||||
}
|
||||
|
||||
Controller* System::GetController(u32 slot) const
|
||||
|
@ -530,6 +537,202 @@ void System::RemoveMedia()
|
|||
m_cdrom->RemoveMedia();
|
||||
}
|
||||
|
||||
std::unique_ptr<TimingEvent> System::CreateTimingEvent(std::string name, TickCount period, TickCount interval,
|
||||
TimingEventCallback callback, bool activate)
|
||||
{
|
||||
std::unique_ptr<TimingEvent> event =
|
||||
std::make_unique<TimingEvent>(this, std::move(name), period, interval, std::move(callback));
|
||||
if (activate)
|
||||
event->Activate();
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
static bool CompareEvents(const TimingEvent* lhs, const TimingEvent* rhs)
|
||||
{
|
||||
return lhs->GetDowncount() > rhs->GetDowncount();
|
||||
}
|
||||
|
||||
void System::AddActiveEvent(TimingEvent* event)
|
||||
{
|
||||
m_events.push_back(event);
|
||||
if (!m_running_events)
|
||||
{
|
||||
std::push_heap(m_events.begin(), m_events.end(), CompareEvents);
|
||||
if (!m_frame_done)
|
||||
UpdateCPUDowncount();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_events_need_sorting = true;
|
||||
}
|
||||
}
|
||||
|
||||
void System::RemoveActiveEvent(TimingEvent* event)
|
||||
{
|
||||
auto iter = std::find_if(m_events.begin(), m_events.end(), [event](const auto& it) { return event == it; });
|
||||
if (iter == m_events.end())
|
||||
{
|
||||
Panic("Attempt to remove inactive event");
|
||||
return;
|
||||
}
|
||||
|
||||
m_events.erase(iter);
|
||||
if (!m_running_events)
|
||||
{
|
||||
std::make_heap(m_events.begin(), m_events.end(), CompareEvents);
|
||||
if (!m_events.empty() && !m_frame_done)
|
||||
UpdateCPUDowncount();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_events_need_sorting = true;
|
||||
}
|
||||
}
|
||||
|
||||
void System::SortEvents()
|
||||
{
|
||||
if (!m_running_events)
|
||||
{
|
||||
std::make_heap(m_events.begin(), m_events.end(), CompareEvents);
|
||||
if (!m_frame_done)
|
||||
UpdateCPUDowncount();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_events_need_sorting = true;
|
||||
}
|
||||
}
|
||||
|
||||
void System::RunEvents()
|
||||
{
|
||||
DebugAssert(!m_running_events && !m_events.empty());
|
||||
|
||||
const TickCount pending_ticks = m_cpu->GetPendingTicks();
|
||||
m_global_tick_counter += static_cast<u32>(pending_ticks);
|
||||
m_cpu->ResetPendingTicks();
|
||||
|
||||
TickCount time = static_cast<TickCount>(m_global_tick_counter - m_last_event_run_time);
|
||||
m_running_events = true;
|
||||
m_last_event_run_time = m_global_tick_counter;
|
||||
|
||||
// Apply downcount to all events.
|
||||
// This will result in a negative downcount for those events which are late.
|
||||
for (TimingEvent* evt : m_events)
|
||||
{
|
||||
evt->m_downcount -= time;
|
||||
evt->m_time_since_last_run += time;
|
||||
}
|
||||
|
||||
// Now we can actually run the callbacks.
|
||||
while (m_events.front()->GetDowncount() <= 0)
|
||||
{
|
||||
TimingEvent* evt = m_events.front();
|
||||
const TickCount ticks_late = -evt->m_downcount;
|
||||
std::pop_heap(m_events.begin(), m_events.end(), CompareEvents);
|
||||
|
||||
// Factor late time into the time for the next invocation.
|
||||
const TickCount ticks_to_execute = evt->m_time_since_last_run;
|
||||
evt->m_downcount += evt->m_interval;
|
||||
evt->m_time_since_last_run = 0;
|
||||
|
||||
// The cycles_late is only an indicator, it doesn't modify the cycles to execute.
|
||||
evt->m_callback(ticks_to_execute, ticks_late);
|
||||
|
||||
// Place it in the appropriate position in the queue.
|
||||
if (m_events_need_sorting)
|
||||
{
|
||||
// Another event may have been changed by this event, or the interval/downcount changed.
|
||||
std::make_heap(m_events.begin(), m_events.end(), CompareEvents);
|
||||
m_events_need_sorting = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Keep the event list in a heap. The event we just serviced will be in the last place,
|
||||
// so we can use push_here instead of make_heap, which should be faster.
|
||||
std::push_heap(m_events.begin(), m_events.end(), CompareEvents);
|
||||
}
|
||||
}
|
||||
|
||||
m_running_events = false;
|
||||
m_cpu->SetDowncount(m_events.front()->GetDowncount());
|
||||
}
|
||||
|
||||
void System::UpdateCPUDowncount()
|
||||
{
|
||||
m_cpu->SetDowncount(m_events[0]->GetDowncount());
|
||||
}
|
||||
|
||||
bool System::DoEventsState(StateWrapper& sw)
|
||||
{
|
||||
if (sw.IsReading())
|
||||
{
|
||||
// Load timestamps for the clock events.
|
||||
// Any oneshot events should be recreated by the load state method, so we can fix up their times here.
|
||||
u32 event_count = 0;
|
||||
sw.Do(&event_count);
|
||||
|
||||
for (u32 i = 0; i < event_count; i++)
|
||||
{
|
||||
std::string event_name;
|
||||
TickCount downcount, time_since_last_run, period, interval;
|
||||
sw.Do(&event_name);
|
||||
sw.Do(&downcount);
|
||||
sw.Do(&time_since_last_run);
|
||||
sw.Do(&period);
|
||||
sw.Do(&interval);
|
||||
if (sw.HasError())
|
||||
return false;
|
||||
|
||||
TimingEvent* event = FindActiveEvent(event_name.c_str());
|
||||
if (!event)
|
||||
{
|
||||
Log_WarningPrintf("Save state has event '%s', but couldn't find this event when loading.", event_name.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Using reschedule is safe here since we call sort afterwards.
|
||||
event->m_downcount = downcount;
|
||||
event->m_time_since_last_run = time_since_last_run;
|
||||
event->m_period = period;
|
||||
event->m_interval = interval;
|
||||
}
|
||||
|
||||
sw.Do(&m_last_event_run_time);
|
||||
|
||||
Log_DevPrintf("Loaded %u events from save state.", event_count);
|
||||
SortEvents();
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 event_count = static_cast<u32>(m_events.size());
|
||||
sw.Do(&event_count);
|
||||
|
||||
for (TimingEvent* evt : m_events)
|
||||
{
|
||||
sw.Do(&evt->m_name);
|
||||
sw.Do(&evt->m_downcount);
|
||||
sw.Do(&evt->m_time_since_last_run);
|
||||
sw.Do(&evt->m_period);
|
||||
sw.Do(&evt->m_interval);
|
||||
}
|
||||
|
||||
sw.Do(&m_last_event_run_time);
|
||||
|
||||
Log_DevPrintf("Wrote %u events to save state.", event_count);
|
||||
}
|
||||
|
||||
return !sw.HasError();
|
||||
}
|
||||
|
||||
TimingEvent* System::FindActiveEvent(const char* name)
|
||||
{
|
||||
auto iter =
|
||||
std::find_if(m_events.begin(), m_events.end(), [&name](auto& ev) { return ev->GetName().compare(name) == 0; });
|
||||
|
||||
return (iter != m_events.end()) ? *iter : nullptr;
|
||||
}
|
||||
|
||||
void System::UpdateRunningGame(const char* path, CDImage* image)
|
||||
{
|
||||
m_running_game_path.clear();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include "host_interface.h"
|
||||
#include "timing_event.h"
|
||||
#include "types.h"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
@ -28,6 +29,8 @@ class SIO;
|
|||
|
||||
class System
|
||||
{
|
||||
friend TimingEvent;
|
||||
|
||||
public:
|
||||
~System();
|
||||
|
||||
|
@ -52,7 +55,11 @@ public:
|
|||
u32 GetFrameNumber() const { return m_frame_number; }
|
||||
u32 GetInternalFrameNumber() const { return m_internal_frame_number; }
|
||||
u32 GetGlobalTickCounter() const { return m_global_tick_counter; }
|
||||
void IncrementFrameNumber() { m_frame_number++; }
|
||||
void IncrementFrameNumber()
|
||||
{
|
||||
m_frame_number++;
|
||||
m_frame_done = true;
|
||||
}
|
||||
void IncrementInternalFrameNumber() { m_internal_frame_number++; }
|
||||
|
||||
const Settings& GetSettings() { return m_host_interface->GetSettings(); }
|
||||
|
@ -78,9 +85,6 @@ public:
|
|||
bool LoadEXE(const char* filename, std::vector<u8>& bios_image);
|
||||
bool SetExpansionROM(const char* filename);
|
||||
|
||||
void SetDowncount(TickCount downcount);
|
||||
void Synchronize();
|
||||
|
||||
// Adds ticks to the global tick counter, simulating the CPU being stalled.
|
||||
void StallCPU(TickCount ticks);
|
||||
|
||||
|
@ -94,6 +98,12 @@ public:
|
|||
bool InsertMedia(const char* path);
|
||||
void RemoveMedia();
|
||||
|
||||
/// Creates a new event.
|
||||
std::unique_ptr<TimingEvent> CreateTimingEvent(std::string name, TickCount period, TickCount interval,
|
||||
TimingEventCallback callback, bool activate);
|
||||
|
||||
bool RUNNING_EVENTS() const { return m_running_events; }
|
||||
|
||||
private:
|
||||
System(HostInterface* host_interface);
|
||||
|
||||
|
@ -101,6 +111,33 @@ private:
|
|||
bool CreateGPU(GPURenderer renderer);
|
||||
|
||||
void InitializeComponents();
|
||||
void DestroyComponents();
|
||||
|
||||
// Active event management
|
||||
void AddActiveEvent(TimingEvent* event);
|
||||
void RemoveActiveEvent(TimingEvent* event);
|
||||
void SortEvents();
|
||||
|
||||
// Runs any pending events. Call when CPU downcount is zero.
|
||||
void RunEvents();
|
||||
|
||||
// Updates the downcount of the CPU (event scheduling).
|
||||
void UpdateCPUDowncount();
|
||||
|
||||
bool DoEventsState(StateWrapper& sw);
|
||||
|
||||
// Event lookup, use with care.
|
||||
// If you modify an event, call SortEvents afterwards.
|
||||
TimingEvent* FindActiveEvent(const char* name);
|
||||
|
||||
// Event enumeration, use with care.
|
||||
// Don't remove an event while enumerating the list, as it will invalidate the iterator.
|
||||
template<typename T>
|
||||
void EnumerateActiveEvents(T callback) const
|
||||
{
|
||||
for (const TimingEvent* ev : m_events)
|
||||
callback(ev);
|
||||
}
|
||||
|
||||
void UpdateRunningGame(const char* path, CDImage* image);
|
||||
|
||||
|
@ -123,6 +160,12 @@ private:
|
|||
u32 m_internal_frame_number = 1;
|
||||
u32 m_global_tick_counter = 0;
|
||||
|
||||
std::vector<TimingEvent*> m_events;
|
||||
u32 m_last_event_run_time = 0;
|
||||
bool m_running_events = false;
|
||||
bool m_events_need_sorting = false;
|
||||
bool m_frame_done = false;
|
||||
|
||||
std::string m_running_game_path;
|
||||
std::string m_running_game_code;
|
||||
std::string m_running_game_title;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "timers.h"
|
||||
#include "common/log.h"
|
||||
#include "common/state_wrapper.h"
|
||||
#include "gpu.h"
|
||||
#include "interrupt_controller.h"
|
||||
#include "system.h"
|
||||
#include <imgui.h>
|
||||
|
@ -10,10 +11,13 @@ Timers::Timers() = default;
|
|||
|
||||
Timers::~Timers() = default;
|
||||
|
||||
void Timers::Initialize(System* system, InterruptController* interrupt_controller)
|
||||
void Timers::Initialize(System* system, InterruptController* interrupt_controller, GPU* gpu)
|
||||
{
|
||||
m_system = system;
|
||||
m_interrupt_controller = interrupt_controller;
|
||||
m_gpu = gpu;
|
||||
m_sysclk_event = system->CreateTimingEvent("Timer SysClk Interrupt", 1, 1,
|
||||
std::bind(&Timers::AddSysClkTicks, this, std::placeholders::_1), false);
|
||||
}
|
||||
|
||||
void Timers::Reset()
|
||||
|
@ -30,6 +34,7 @@ void Timers::Reset()
|
|||
}
|
||||
|
||||
m_sysclk_div_8_carry = 0;
|
||||
UpdateSysClkEvent();
|
||||
}
|
||||
|
||||
bool Timers::DoState(StateWrapper& sw)
|
||||
|
@ -47,6 +52,10 @@ bool Timers::DoState(StateWrapper& sw)
|
|||
}
|
||||
|
||||
sw.Do(&m_sysclk_div_8_carry);
|
||||
|
||||
if (sw.IsReading())
|
||||
UpdateSysClkEvent();
|
||||
|
||||
return !sw.HasError();
|
||||
}
|
||||
|
||||
|
@ -88,12 +97,12 @@ void Timers::AddTicks(u32 timer, TickCount count)
|
|||
bool interrupt_request = false;
|
||||
if (cs.counter >= cs.target && old_counter < cs.target)
|
||||
{
|
||||
interrupt_request = true;
|
||||
interrupt_request |= cs.mode.irq_at_target;
|
||||
cs.mode.reached_target = true;
|
||||
}
|
||||
if (cs.counter >= 0xFFFF)
|
||||
{
|
||||
interrupt_request = true;
|
||||
interrupt_request |= cs.mode.irq_on_overflow;
|
||||
cs.mode.reached_overflow = true;
|
||||
}
|
||||
|
||||
|
@ -126,7 +135,7 @@ void Timers::AddTicks(u32 timer, TickCount count)
|
|||
}
|
||||
}
|
||||
|
||||
void Timers::Execute(TickCount sysclk_ticks)
|
||||
void Timers::AddSysClkTicks(TickCount sysclk_ticks)
|
||||
{
|
||||
if (!m_states[0].external_counting_enabled && m_states[0].counting_enabled)
|
||||
AddTicks(0, sysclk_ticks);
|
||||
|
@ -143,7 +152,7 @@ void Timers::Execute(TickCount sysclk_ticks)
|
|||
AddTicks(2, sysclk_ticks);
|
||||
}
|
||||
|
||||
UpdateDowncount();
|
||||
UpdateSysClkEvent();
|
||||
}
|
||||
|
||||
u32 Timers::ReadRegister(u32 offset)
|
||||
|
@ -157,13 +166,28 @@ u32 Timers::ReadRegister(u32 offset)
|
|||
{
|
||||
case 0x00:
|
||||
{
|
||||
m_system->Synchronize();
|
||||
if (timer_index < 2)
|
||||
{
|
||||
// timers 0/1 depend on the GPU
|
||||
if (cs.external_counting_enabled)
|
||||
m_gpu->Synchronize();
|
||||
}
|
||||
|
||||
m_sysclk_event->InvokeEarly();
|
||||
|
||||
return cs.counter;
|
||||
}
|
||||
|
||||
case 0x04:
|
||||
{
|
||||
m_system->Synchronize();
|
||||
if (timer_index < 2)
|
||||
{
|
||||
// timers 0/1 depend on the GPU
|
||||
if (cs.external_counting_enabled)
|
||||
m_gpu->Synchronize();
|
||||
}
|
||||
|
||||
m_sysclk_event->InvokeEarly();
|
||||
|
||||
const u32 bits = cs.mode.bits;
|
||||
cs.mode.reached_overflow = false;
|
||||
|
@ -192,7 +216,7 @@ void Timers::WriteRegister(u32 offset, u32 value)
|
|||
case 0x00:
|
||||
{
|
||||
Log_DebugPrintf("Timer %u write counter %u", timer_index, value);
|
||||
m_system->Synchronize();
|
||||
m_sysclk_event->InvokeEarly();
|
||||
cs.counter = value & u32(0xFFFF);
|
||||
}
|
||||
break;
|
||||
|
@ -200,7 +224,7 @@ void Timers::WriteRegister(u32 offset, u32 value)
|
|||
case 0x04:
|
||||
{
|
||||
Log_DebugPrintf("Timer %u write mode register 0x%04X", timer_index, value);
|
||||
m_system->Synchronize();
|
||||
m_sysclk_event->InvokeEarly();
|
||||
cs.mode.bits = value & u32(0x1FFF);
|
||||
cs.use_external_clock = (cs.mode.clock_source & (timer_index == 2 ? 2 : 1)) != 0;
|
||||
cs.counter = 0;
|
||||
|
@ -210,13 +234,14 @@ void Timers::WriteRegister(u32 offset, u32 value)
|
|||
|
||||
UpdateCountingEnabled(cs);
|
||||
UpdateIRQ(timer_index);
|
||||
UpdateSysClkEvent();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
{
|
||||
Log_DebugPrintf("Timer %u write target 0x%04X", timer_index, ZeroExtend32(Truncate16(value)));
|
||||
m_system->Synchronize();
|
||||
m_sysclk_event->InvokeEarly();
|
||||
cs.target = value & u32(0xFFFF);
|
||||
}
|
||||
break;
|
||||
|
@ -267,16 +292,19 @@ void Timers::UpdateIRQ(u32 index)
|
|||
static_cast<InterruptController::IRQ>(static_cast<u32>(InterruptController::IRQ::TMR0) + index));
|
||||
}
|
||||
|
||||
void Timers::UpdateDowncount()
|
||||
TickCount Timers::GetTicksUntilNextInterrupt() const
|
||||
{
|
||||
TickCount min_ticks = std::numeric_limits<TickCount>::max();
|
||||
for (u32 i = 0; i < NUM_TIMERS; i++)
|
||||
{
|
||||
CounterState& cs = m_states[i];
|
||||
if (!cs.counting_enabled || (i < 2 && cs.external_counting_enabled))
|
||||
const CounterState& cs = m_states[i];
|
||||
if (!cs.counting_enabled || (i < 2 && cs.external_counting_enabled) ||
|
||||
(!cs.mode.irq_at_target && !cs.mode.irq_on_overflow))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TickCount min_ticks_for_this_timer = min_ticks;
|
||||
TickCount min_ticks_for_this_timer = std::numeric_limits<TickCount>::max();
|
||||
if (cs.mode.irq_at_target && cs.counter < cs.target)
|
||||
min_ticks_for_this_timer = static_cast<TickCount>(cs.target - cs.counter);
|
||||
if (cs.mode.irq_on_overflow && cs.counter < cs.target)
|
||||
|
@ -288,7 +316,17 @@ void Timers::UpdateDowncount()
|
|||
min_ticks = std::min(min_ticks, min_ticks_for_this_timer);
|
||||
}
|
||||
|
||||
m_system->SetDowncount(min_ticks);
|
||||
return min_ticks;
|
||||
}
|
||||
|
||||
void Timers::UpdateSysClkEvent()
|
||||
{
|
||||
// Still update once every 100ms. If we get polled we'll execute sooner.
|
||||
const TickCount ticks = GetTicksUntilNextInterrupt();
|
||||
if (ticks == std::numeric_limits<TickCount>::max())
|
||||
m_sysclk_event->Schedule(MAX_SLICE_SIZE);
|
||||
else
|
||||
m_sysclk_event->Schedule(ticks);
|
||||
}
|
||||
|
||||
void Timers::DrawDebugStateWindow()
|
||||
|
|
|
@ -2,11 +2,14 @@
|
|||
#include "common/bitfield.h"
|
||||
#include "types.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
class StateWrapper;
|
||||
|
||||
class System;
|
||||
class TimingEvent;
|
||||
class InterruptController;
|
||||
class GPU;
|
||||
|
||||
class Timers
|
||||
{
|
||||
|
@ -14,7 +17,7 @@ public:
|
|||
Timers();
|
||||
~Timers();
|
||||
|
||||
void Initialize(System* system, InterruptController* interrupt_controller);
|
||||
void Initialize(System* system, InterruptController* interrupt_controller, GPU* gpu);
|
||||
void Reset();
|
||||
bool DoState(StateWrapper& sw);
|
||||
|
||||
|
@ -25,11 +28,13 @@ public:
|
|||
// dot clock/hblank/sysclk div 8
|
||||
bool IsUsingExternalClock(u32 timer) const { return m_states[timer].external_counting_enabled; }
|
||||
void AddTicks(u32 timer, TickCount ticks);
|
||||
void Execute(TickCount sysclk_ticks);
|
||||
|
||||
u32 ReadRegister(u32 offset);
|
||||
void WriteRegister(u32 offset, u32 value);
|
||||
|
||||
// changing interfaces
|
||||
void SetGPU(GPU* gpu) { m_gpu = gpu; }
|
||||
|
||||
private:
|
||||
static constexpr u32 NUM_TIMERS = 3;
|
||||
|
||||
|
@ -73,10 +78,15 @@ private:
|
|||
void UpdateCountingEnabled(CounterState& cs);
|
||||
void UpdateIRQ(u32 index);
|
||||
|
||||
void UpdateDowncount();
|
||||
void AddSysClkTicks(TickCount sysclk_ticks);
|
||||
|
||||
TickCount GetTicksUntilNextInterrupt() const;
|
||||
void UpdateSysClkEvent();
|
||||
|
||||
System* m_system = nullptr;
|
||||
InterruptController* m_interrupt_controller = nullptr;
|
||||
GPU* m_gpu = nullptr;
|
||||
std::unique_ptr<TimingEvent> m_sysclk_event = nullptr;
|
||||
|
||||
std::array<CounterState, NUM_TIMERS> m_states{};
|
||||
u32 m_sysclk_div_8_carry = 0; // partial ticks for timer 3 with sysclk/8
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
#include "timing_event.h"
|
||||
#include "common/assert.h"
|
||||
#include "cpu_core.h"
|
||||
#include "system.h"
|
||||
|
||||
TimingEvent::TimingEvent(System* system, std::string name, TickCount period, TickCount interval,
|
||||
TimingEventCallback callback)
|
||||
: m_downcount(interval), m_time_since_last_run(0), m_period(period), m_interval(interval),
|
||||
m_callback(std::move(callback)), m_system(system), m_name(std::move(name)), m_active(false)
|
||||
{
|
||||
}
|
||||
|
||||
TimingEvent::~TimingEvent()
|
||||
{
|
||||
if (m_active)
|
||||
m_system->RemoveActiveEvent(this);
|
||||
}
|
||||
|
||||
TickCount TimingEvent::GetTicksSinceLastExecution() const
|
||||
{
|
||||
return m_system->m_cpu->GetPendingTicks() + m_time_since_last_run;
|
||||
}
|
||||
|
||||
TickCount TimingEvent::GetTicksUntilNextExecution() const
|
||||
{
|
||||
return std::max(m_downcount - m_system->m_cpu->GetPendingTicks(), static_cast<TickCount>(0));
|
||||
}
|
||||
|
||||
void TimingEvent::Schedule(TickCount ticks)
|
||||
{
|
||||
m_downcount = ticks;
|
||||
m_time_since_last_run = 0;
|
||||
|
||||
// Factor in partial time if this was rescheduled outside of an event handler. Say, an MMIO write.
|
||||
if (!m_system->m_running_events)
|
||||
{
|
||||
const TickCount pending_ticks = m_system->m_cpu->GetPendingTicks();
|
||||
m_downcount += pending_ticks;
|
||||
m_time_since_last_run -= pending_ticks;
|
||||
}
|
||||
|
||||
if (m_active)
|
||||
{
|
||||
// If this is a call from an IO handler for example, re-sort the event queue.
|
||||
m_system->SortEvents();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_active = true;
|
||||
m_system->AddActiveEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
void TimingEvent::SetIntervalAndSchedule(TickCount ticks)
|
||||
{
|
||||
SetInterval(ticks);
|
||||
Schedule(ticks);
|
||||
}
|
||||
|
||||
void TimingEvent::SetPeriodAndSchedule(TickCount ticks)
|
||||
{
|
||||
SetPeriod(ticks);
|
||||
SetInterval(ticks);
|
||||
Schedule(ticks);
|
||||
}
|
||||
|
||||
void TimingEvent::Reset()
|
||||
{
|
||||
if (!m_active)
|
||||
return;
|
||||
|
||||
m_downcount = m_interval;
|
||||
m_time_since_last_run = 0;
|
||||
m_system->SortEvents();
|
||||
}
|
||||
|
||||
void TimingEvent::InvokeEarly(bool force /* = false */)
|
||||
{
|
||||
if (!m_active)
|
||||
return;
|
||||
|
||||
const TickCount pending_ticks = m_system->m_running_events ? 0 : m_system->m_cpu->GetPendingTicks();
|
||||
const TickCount ticks_to_execute = m_time_since_last_run + pending_ticks;
|
||||
if (!force && ticks_to_execute < m_period)
|
||||
return;
|
||||
|
||||
m_downcount = pending_ticks + m_interval;
|
||||
m_time_since_last_run -= ticks_to_execute;
|
||||
m_callback(ticks_to_execute, 0);
|
||||
|
||||
// Since we've changed the downcount, we need to re-sort the events.
|
||||
m_system->SortEvents();
|
||||
}
|
||||
|
||||
void TimingEvent::Activate()
|
||||
{
|
||||
if (m_active)
|
||||
return;
|
||||
|
||||
// leave the downcount intact
|
||||
const TickCount pending_ticks = m_system->m_running_events ? 0 : m_system->m_cpu->GetPendingTicks();
|
||||
m_downcount += pending_ticks;
|
||||
m_time_since_last_run -= pending_ticks;
|
||||
|
||||
m_active = true;
|
||||
m_system->AddActiveEvent(this);
|
||||
}
|
||||
|
||||
void TimingEvent::Deactivate()
|
||||
{
|
||||
if (!m_active)
|
||||
return;
|
||||
|
||||
const TickCount pending_ticks = m_system->m_running_events ? 0 : m_system->m_cpu->GetPendingTicks();
|
||||
m_downcount -= pending_ticks;
|
||||
m_time_since_last_run += pending_ticks;
|
||||
|
||||
m_active = false;
|
||||
m_system->RemoveActiveEvent(this);
|
||||
}
|
||||
|
||||
void TimingEvent::SetDowncount(TickCount downcount)
|
||||
{
|
||||
const TickCount pending_ticks = m_system->m_running_events ? 0 : m_system->m_cpu->GetPendingTicks();
|
||||
m_downcount = downcount + pending_ticks;
|
||||
m_time_since_last_run = -pending_ticks;
|
||||
|
||||
if (m_active)
|
||||
m_system->SortEvents();
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
class System;
|
||||
class TimingEvent;
|
||||
|
||||
// Event callback type. Second parameter is the number of cycles the event was executed "late".
|
||||
using TimingEventCallback = std::function<void(TickCount ticks, TickCount ticks_late)>;
|
||||
|
||||
class TimingEvent
|
||||
{
|
||||
friend System;
|
||||
|
||||
public:
|
||||
TimingEvent(System* system, std::string name, TickCount period, TickCount interval, TimingEventCallback callback);
|
||||
~TimingEvent();
|
||||
|
||||
System* GetSystem() const { return m_system; }
|
||||
const std::string& GetName() const { return m_name; }
|
||||
bool IsActive() const { return m_active; }
|
||||
|
||||
// Returns the number of ticks between each event.
|
||||
TickCount GetPeriod() const { return m_period; }
|
||||
TickCount GetInterval() const { return m_interval; }
|
||||
|
||||
TickCount GetDowncount() const { return m_downcount; }
|
||||
|
||||
// Includes pending time.
|
||||
TickCount GetTicksSinceLastExecution() const;
|
||||
TickCount GetTicksUntilNextExecution() const;
|
||||
|
||||
void Schedule(TickCount ticks);
|
||||
void SetIntervalAndSchedule(TickCount ticks);
|
||||
void SetPeriodAndSchedule(TickCount ticks);
|
||||
|
||||
void Reset();
|
||||
|
||||
// Services the event with the current accmulated time. If force is set, when not enough time is pending to
|
||||
// simulate a single cycle, the callback will still be invoked, otherwise it won't be.
|
||||
void InvokeEarly(bool force = false);
|
||||
|
||||
// Deactivates the event, preventing it from firing again.
|
||||
// Do not call within a callback, return Deactivate instead.
|
||||
void Activate();
|
||||
void Deactivate();
|
||||
|
||||
ALWAYS_INLINE void SetState(bool active)
|
||||
{
|
||||
if (active)
|
||||
Activate();
|
||||
else
|
||||
Deactivate();
|
||||
}
|
||||
|
||||
// Directly alters the downcount of the event.
|
||||
void SetDowncount(TickCount downcount);
|
||||
|
||||
// Directly alters the interval of the event.
|
||||
void SetInterval(TickCount interval) { m_interval = interval; }
|
||||
void SetPeriod(TickCount period) { m_period = period; }
|
||||
|
||||
private:
|
||||
TickCount m_downcount;
|
||||
TickCount m_time_since_last_run;
|
||||
TickCount m_period;
|
||||
TickCount m_interval;
|
||||
|
||||
TimingEventCallback m_callback;
|
||||
System* m_system;
|
||||
std::string m_name;
|
||||
bool m_active;
|
||||
};
|
Loading…
Reference in New Issue