mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-28 16:45:41 +00:00
TimingEvents: Fix events ending up out-of-order
This commit is contained in:
parent
495b2ff29e
commit
efd8aab437
|
@ -22795,7 +22795,8 @@ SLES-01182:
|
||||||
- DigitalController
|
- DigitalController
|
||||||
- NeGcon
|
- NeGcon
|
||||||
settings:
|
settings:
|
||||||
dmaMaxSliceTicks: 100
|
dmaMaxSliceTicks: 500 # Needs smaller sizes to avoid menu corruption.
|
||||||
|
dmaHaltTicks: 250
|
||||||
metadata:
|
metadata:
|
||||||
publisher: "THQ"
|
publisher: "THQ"
|
||||||
developer: "Interactive Entertainment"
|
developer: "Interactive Entertainment"
|
||||||
|
@ -22819,7 +22820,8 @@ SLUS-00882:
|
||||||
rating: NoIssues
|
rating: NoIssues
|
||||||
comments: "Intro logos require the software renderer to display correctly."
|
comments: "Intro logos require the software renderer to display correctly."
|
||||||
settings:
|
settings:
|
||||||
dmaMaxSliceTicks: 100
|
dmaMaxSliceTicks: 500 # Needs smaller sizes to avoid menu corruption.
|
||||||
|
dmaHaltTicks: 250
|
||||||
controllers:
|
controllers:
|
||||||
- AnalogController
|
- AnalogController
|
||||||
- DigitalController
|
- DigitalController
|
||||||
|
@ -172114,6 +172116,9 @@ SLPS-00025:
|
||||||
- DigitalController
|
- DigitalController
|
||||||
traits:
|
traits:
|
||||||
- ForceRecompilerICache
|
- ForceRecompilerICache
|
||||||
|
settings:
|
||||||
|
dmaMaxSliceTicks: 500 # Stops a large GPU transfer breaking CD.
|
||||||
|
dmaHaltTicks: 300
|
||||||
codes:
|
codes:
|
||||||
- HASH-A8647D688C39B63F
|
- HASH-A8647D688C39B63F
|
||||||
- HASH-21D86F0985C11667
|
- HASH-21D86F0985C11667
|
||||||
|
|
|
@ -194,15 +194,12 @@ static TickCount TransferDeviceToMemory(u32 address, u32 increment, u32 word_cou
|
||||||
template<Channel channel>
|
template<Channel channel>
|
||||||
static TickCount TransferMemoryToDevice(u32 address, u32 increment, u32 word_count);
|
static TickCount TransferMemoryToDevice(u32 address, u32 increment, u32 word_count);
|
||||||
|
|
||||||
static TickCount GetMaxSliceTicks();
|
static TickCount GetMaxSliceTicks(TickCount max_slice_size);
|
||||||
|
|
||||||
// configuration
|
// configuration
|
||||||
namespace {
|
namespace {
|
||||||
struct DMAState
|
struct DMAState
|
||||||
{
|
{
|
||||||
TickCount max_slice_ticks = 1000;
|
|
||||||
TickCount halt_ticks = 100;
|
|
||||||
|
|
||||||
std::vector<u32> transfer_buffer;
|
std::vector<u32> transfer_buffer;
|
||||||
TimingEvent unhalt_event{"DMA Transfer Unhalt", 1, 1, &DMA::UnhaltTransfer, nullptr};
|
TimingEvent unhalt_event{"DMA Transfer Unhalt", 1, 1, &DMA::UnhaltTransfer, nullptr};
|
||||||
TickCount halt_ticks_remaining = 0;
|
TickCount halt_ticks_remaining = 0;
|
||||||
|
@ -241,9 +238,7 @@ struct fmt::formatter<DMA::Channel> : fmt::formatter<fmt::string_view>
|
||||||
|
|
||||||
void DMA::Initialize()
|
void DMA::Initialize()
|
||||||
{
|
{
|
||||||
s_state.max_slice_ticks = g_settings.dma_max_slice_ticks;
|
s_state.unhalt_event.SetInterval(g_settings.dma_halt_ticks);
|
||||||
s_state.halt_ticks = g_settings.dma_halt_ticks;
|
|
||||||
s_state.unhalt_event.SetInterval(s_state.max_slice_ticks);
|
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,16 +468,6 @@ void DMA::SetRequest(Channel channel, bool request)
|
||||||
s_channel_transfer_functions[static_cast<u32>(channel)]();
|
s_channel_transfer_functions[static_cast<u32>(channel)]();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DMA::SetMaxSliceTicks(TickCount ticks)
|
|
||||||
{
|
|
||||||
s_state.max_slice_ticks = ticks;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DMA::SetHaltTicks(TickCount ticks)
|
|
||||||
{
|
|
||||||
s_state.halt_ticks = ticks;
|
|
||||||
}
|
|
||||||
|
|
||||||
ALWAYS_INLINE_RELEASE bool DMA::CanTransferChannel(Channel channel, bool ignore_halt)
|
ALWAYS_INLINE_RELEASE bool DMA::CanTransferChannel(Channel channel, bool ignore_halt)
|
||||||
{
|
{
|
||||||
if (!s_state.DPCR.GetMasterEnable(channel))
|
if (!s_state.DPCR.GetMasterEnable(channel))
|
||||||
|
@ -547,15 +532,15 @@ ALWAYS_INLINE_RELEASE void DMA::CompleteTransfer(Channel channel, ChannelState&
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TickCount DMA::GetMaxSliceTicks()
|
TickCount DMA::GetMaxSliceTicks(TickCount max_slice_size)
|
||||||
{
|
{
|
||||||
const TickCount max = Pad::IsTransmitting() ? SLICE_SIZE_WHEN_TRANSMITTING_PAD : s_state.max_slice_ticks;
|
const TickCount max = Pad::IsTransmitting() ? SLICE_SIZE_WHEN_TRANSMITTING_PAD : max_slice_size;
|
||||||
if (!TimingEvents::IsRunningEvents())
|
if (!TimingEvents::IsRunningEvents())
|
||||||
return max;
|
return max;
|
||||||
|
|
||||||
const u32 current_ticks = TimingEvents::GetGlobalTickCounter();
|
const TickCount remaining_in_event_loop =
|
||||||
const u32 max_ticks = TimingEvents::GetEventRunTickCounter() + static_cast<u32>(max);
|
static_cast<TickCount>(TimingEvents::GetEventRunTickCounter() - TimingEvents::GetGlobalTickCounter());
|
||||||
return std::clamp(static_cast<TickCount>(max_ticks - current_ticks), 0, max);
|
return std::max<TickCount>(max - remaining_in_event_loop, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<DMA::Channel channel>
|
template<DMA::Channel channel>
|
||||||
|
@ -607,7 +592,7 @@ bool DMA::TransferChannel()
|
||||||
const u8* const ram_ptr = Bus::g_ram;
|
const u8* const ram_ptr = Bus::g_ram;
|
||||||
const u32 mask = Bus::g_ram_mask;
|
const u32 mask = Bus::g_ram_mask;
|
||||||
|
|
||||||
const TickCount slice_ticks = GetMaxSliceTicks();
|
const TickCount slice_ticks = GetMaxSliceTicks(g_settings.dma_max_slice_ticks);
|
||||||
TickCount remaining_ticks = slice_ticks;
|
TickCount remaining_ticks = slice_ticks;
|
||||||
while (cs.request && remaining_ticks > 0)
|
while (cs.request && remaining_ticks > 0)
|
||||||
{
|
{
|
||||||
|
@ -658,7 +643,7 @@ bool DMA::TransferChannel()
|
||||||
if (cs.request)
|
if (cs.request)
|
||||||
{
|
{
|
||||||
// stall the transfer for a bit if we ran for too long
|
// stall the transfer for a bit if we ran for too long
|
||||||
HaltTransfer(s_state.halt_ticks);
|
HaltTransfer(g_settings.dma_halt_ticks);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -677,7 +662,7 @@ bool DMA::TransferChannel()
|
||||||
|
|
||||||
const u32 block_size = cs.block_control.request.GetBlockSize();
|
const u32 block_size = cs.block_control.request.GetBlockSize();
|
||||||
u32 blocks_remaining = cs.block_control.request.GetBlockCount();
|
u32 blocks_remaining = cs.block_control.request.GetBlockCount();
|
||||||
TickCount ticks_remaining = GetMaxSliceTicks();
|
TickCount ticks_remaining = GetMaxSliceTicks(g_settings.dma_max_slice_ticks);
|
||||||
|
|
||||||
if (copy_to_device)
|
if (copy_to_device)
|
||||||
{
|
{
|
||||||
|
@ -732,7 +717,7 @@ bool DMA::TransferChannel()
|
||||||
{
|
{
|
||||||
// we got halted
|
// we got halted
|
||||||
if (!s_state.unhalt_event.IsActive())
|
if (!s_state.unhalt_event.IsActive())
|
||||||
HaltTransfer(s_state.halt_ticks);
|
HaltTransfer(g_settings.dma_halt_ticks);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,10 +34,6 @@ void WriteRegister(u32 offset, u32 value);
|
||||||
|
|
||||||
void SetRequest(Channel channel, bool request);
|
void SetRequest(Channel channel, bool request);
|
||||||
|
|
||||||
// changing interfaces
|
|
||||||
void SetMaxSliceTicks(TickCount ticks);
|
|
||||||
void SetHaltTicks(TickCount ticks);
|
|
||||||
|
|
||||||
void DrawDebugStateWindow();
|
void DrawDebugStateWindow();
|
||||||
|
|
||||||
} // namespace DMA
|
} // namespace DMA
|
||||||
|
|
|
@ -4135,9 +4135,6 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
||||||
TextureReplacements::Reload();
|
TextureReplacements::Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
DMA::SetMaxSliceTicks(g_settings.dma_max_slice_ticks);
|
|
||||||
DMA::SetHaltTicks(g_settings.dma_halt_ticks);
|
|
||||||
|
|
||||||
if (g_settings.audio_backend != old_settings.audio_backend ||
|
if (g_settings.audio_backend != old_settings.audio_backend ||
|
||||||
g_settings.increase_timer_resolution != old_settings.increase_timer_resolution ||
|
g_settings.increase_timer_resolution != old_settings.increase_timer_resolution ||
|
||||||
g_settings.emulation_speed != old_settings.emulation_speed ||
|
g_settings.emulation_speed != old_settings.emulation_speed ||
|
||||||
|
|
|
@ -24,6 +24,7 @@ struct TimingEventsState
|
||||||
TimingEvent* active_events_head = nullptr;
|
TimingEvent* active_events_head = nullptr;
|
||||||
TimingEvent* active_events_tail = nullptr;
|
TimingEvent* active_events_tail = nullptr;
|
||||||
TimingEvent* current_event = nullptr;
|
TimingEvent* current_event = nullptr;
|
||||||
|
TickCount current_event_new_downcount = 0;
|
||||||
u32 active_event_count = 0;
|
u32 active_event_count = 0;
|
||||||
u32 global_tick_counter = 0;
|
u32 global_tick_counter = 0;
|
||||||
u32 event_run_tick_counter = 0;
|
u32 event_run_tick_counter = 0;
|
||||||
|
@ -326,13 +327,20 @@ void TimingEvents::RunEvents()
|
||||||
// Factor late time into the time for the next invocation.
|
// Factor late time into the time for the next invocation.
|
||||||
const TickCount ticks_late = -event->m_downcount;
|
const TickCount ticks_late = -event->m_downcount;
|
||||||
const TickCount ticks_to_execute = event->m_time_since_last_run;
|
const TickCount ticks_to_execute = event->m_time_since_last_run;
|
||||||
event->m_downcount += event->m_interval;
|
|
||||||
|
// Why don't we modify event->m_downcount directly? Because otherwise the event list won't be sorted.
|
||||||
|
// Adding the interval may cause this event to have a greater downcount than the next, and a new event
|
||||||
|
// may be inserted at the front, despite having a higher downcount than the next.
|
||||||
|
s_state.current_event_new_downcount = event->m_downcount + event->m_interval;
|
||||||
event->m_time_since_last_run = 0;
|
event->m_time_since_last_run = 0;
|
||||||
|
|
||||||
// The cycles_late is only an indicator, it doesn't modify the cycles to execute.
|
// The cycles_late is only an indicator, it doesn't modify the cycles to execute.
|
||||||
event->m_callback(event->m_callback_param, ticks_to_execute, ticks_late);
|
event->m_callback(event->m_callback_param, ticks_to_execute, ticks_late);
|
||||||
if (event->m_active)
|
if (event->m_active)
|
||||||
|
{
|
||||||
|
event->m_downcount = s_state.current_event_new_downcount;
|
||||||
SortEvent(event);
|
SortEvent(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} while (pending_ticks > 0);
|
} while (pending_ticks > 0);
|
||||||
|
|
||||||
|
@ -458,25 +466,33 @@ void TimingEvent::Delay(TickCount ticks)
|
||||||
|
|
||||||
void TimingEvent::Schedule(TickCount ticks)
|
void TimingEvent::Schedule(TickCount ticks)
|
||||||
{
|
{
|
||||||
|
using namespace TimingEvents;
|
||||||
|
|
||||||
const TickCount pending_ticks = CPU::GetPendingTicks();
|
const TickCount pending_ticks = CPU::GetPendingTicks();
|
||||||
m_downcount = pending_ticks + ticks;
|
const TickCount new_downcount = pending_ticks + ticks;
|
||||||
|
|
||||||
|
// See note in RunEvents().
|
||||||
|
s_state.current_event_new_downcount =
|
||||||
|
(s_state.current_event == this) ? new_downcount : s_state.current_event_new_downcount;
|
||||||
|
|
||||||
if (!m_active)
|
if (!m_active)
|
||||||
{
|
{
|
||||||
// Event is going active, so we want it to only execute ticks from the current timestamp.
|
// Event is going active, so we want it to only execute ticks from the current timestamp.
|
||||||
|
m_downcount = new_downcount;
|
||||||
m_time_since_last_run = -pending_ticks;
|
m_time_since_last_run = -pending_ticks;
|
||||||
m_active = true;
|
m_active = true;
|
||||||
TimingEvents::AddActiveEvent(this);
|
AddActiveEvent(this);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Event is already active, so we leave the time since last run alone, and just modify the downcount.
|
// Event is already active, so we leave the time since last run alone, and just modify the downcount.
|
||||||
// If this is a call from an IO handler for example, re-sort the event queue.
|
// If this is a call from an IO handler for example, re-sort the event queue.
|
||||||
if (TimingEvents::s_state.current_event != this)
|
if (s_state.current_event != this)
|
||||||
{
|
{
|
||||||
TimingEvents::SortEvent(this);
|
m_downcount = new_downcount;
|
||||||
if (TimingEvents::s_state.active_events_head == this)
|
SortEvent(this);
|
||||||
TimingEvents::UpdateCPUDowncount();
|
if (s_state.active_events_head == this)
|
||||||
|
UpdateCPUDowncount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -494,21 +510,6 @@ void TimingEvent::SetPeriodAndSchedule(TickCount ticks)
|
||||||
Schedule(ticks);
|
Schedule(ticks);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimingEvent::Reset()
|
|
||||||
{
|
|
||||||
if (!m_active)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_downcount = m_interval;
|
|
||||||
m_time_since_last_run = 0;
|
|
||||||
if (TimingEvents::s_state.current_event != this)
|
|
||||||
{
|
|
||||||
TimingEvents::SortEvent(this);
|
|
||||||
if (TimingEvents::s_state.active_events_head == this)
|
|
||||||
TimingEvents::UpdateCPUDowncount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimingEvent::InvokeEarly(bool force /* = false */)
|
void TimingEvent::InvokeEarly(bool force /* = false */)
|
||||||
{
|
{
|
||||||
if (!m_active)
|
if (!m_active)
|
||||||
|
@ -519,12 +520,14 @@ void TimingEvent::InvokeEarly(bool force /* = false */)
|
||||||
if ((!force && ticks_to_execute < m_period) || ticks_to_execute <= 0)
|
if ((!force && ticks_to_execute < m_period) || ticks_to_execute <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Shouldn't be invoking early when we're the current event running.
|
||||||
|
DebugAssert(TimingEvents::s_state.current_event != this);
|
||||||
|
|
||||||
m_downcount = pending_ticks + m_interval;
|
m_downcount = pending_ticks + m_interval;
|
||||||
m_time_since_last_run -= ticks_to_execute;
|
m_time_since_last_run -= ticks_to_execute;
|
||||||
m_callback(m_callback_param, ticks_to_execute, 0);
|
m_callback(m_callback_param, ticks_to_execute, 0);
|
||||||
|
|
||||||
// Since we've changed the downcount, we need to re-sort the events.
|
// Since we've changed the downcount, we need to re-sort the events.
|
||||||
DebugAssert(TimingEvents::s_state.current_event != this);
|
|
||||||
TimingEvents::SortEvent(this);
|
TimingEvents::SortEvent(this);
|
||||||
if (TimingEvents::s_state.active_events_head == this)
|
if (TimingEvents::s_state.active_events_head == this)
|
||||||
TimingEvents::UpdateCPUDowncount();
|
TimingEvents::UpdateCPUDowncount();
|
||||||
|
@ -536,6 +539,7 @@ void TimingEvent::Activate()
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// leave the downcount intact
|
// leave the downcount intact
|
||||||
|
// if we're running events, this is going to be zero, so no effect
|
||||||
const TickCount pending_ticks = CPU::GetPendingTicks();
|
const TickCount pending_ticks = CPU::GetPendingTicks();
|
||||||
m_downcount += pending_ticks;
|
m_downcount += pending_ticks;
|
||||||
m_time_since_last_run -= pending_ticks;
|
m_time_since_last_run -= pending_ticks;
|
||||||
|
|
|
@ -38,8 +38,6 @@ public:
|
||||||
void SetIntervalAndSchedule(TickCount ticks);
|
void SetIntervalAndSchedule(TickCount ticks);
|
||||||
void SetPeriodAndSchedule(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
|
// 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.
|
// simulate a single cycle, the callback will still be invoked, otherwise it won't be.
|
||||||
void InvokeEarly(bool force = false);
|
void InvokeEarly(bool force = false);
|
||||||
|
|
Loading…
Reference in a new issue