feat: added EE clock Hz

fix: fix MPEG out of sync with new EE refactor
This commit is contained in:
Ran-j
2026-08-07 21:54:33 -03:00
parent 3e5fb2b3b4
commit d91d2c1b75
7 changed files with 1101 additions and 257 deletions
+1 -1
View File
@@ -382,7 +382,7 @@ public:
EeScheduler &eeScheduler();
const EeScheduler &eeScheduler() const;
void postEeEvent(EeEvent event);
bool eeCheckpointDue() const noexcept;
bool eeCheckpointDue(uint32_t cycles = 32u) noexcept;
[[noreturn]] void eeWaitVSyncTicks(uint32_t ticks, uint32_t resumePc);
struct EeExitHandlerRegistration
+18 -4
View File
@@ -214,6 +214,9 @@ struct EeEventFlagSnapshot
struct EeKernelSnapshot
{
uint64_t sequence = 0;
uint64_t eeCycle = 0;
uint64_t sliceEndCycle = 0;
uint64_t nextEventCycle = 0;
int runningThreadId = 0;
std::vector<EeThreadSnapshot> threads;
std::vector<EeSemaphoreSnapshot> semaphores;
@@ -255,6 +258,10 @@ public:
static constexpr int kFirstThreadId = 2;
static constexpr int kLastThreadId = 255;
static constexpr int kPriorityCount = 128;
static constexpr uint64_t kEeClockHz = 294912000ull;
static constexpr uint32_t kGeneratedCheckpointCycles = 32u;
static constexpr uint32_t kGuestDispatchCycles = 8u;
static constexpr uint64_t kDefaultTimeSliceCycles = 65536ull;
explicit EeScheduler(PS2Runtime &runtime);
~EeScheduler();
@@ -266,7 +273,8 @@ public:
void run();
void requestStop();
void postEvent(EeEvent event);
[[nodiscard]] bool checkpointDue() const noexcept;
[[nodiscard]] bool checkpointDue(uint32_t cycles = kGeneratedCheckpointCycles) noexcept;
void accountCycles(uint32_t cycles) noexcept;
[[nodiscard]] bool isExecutingGuest() const noexcept;
// Kernel object API. All calls except postEvent/requestStop execute on the
@@ -349,7 +357,8 @@ public:
private:
struct ScheduledEvent
{
std::chrono::steady_clock::time_point deadline{};
uint64_t deadlineCycle = 0;
std::chrono::steady_clock::time_point hostDeadline{};
EeEvent event{};
uint64_t sequence = 0;
};
@@ -375,8 +384,10 @@ private:
static int waitObjectId(const EeWaitState &wait);
void writeGuestU32(uint32_t address, uint32_t value);
void waitForEvent();
void scheduleEvent(std::chrono::steady_clock::time_point deadline, EeEvent event);
void scheduleEvent(uint64_t deadlineCycle, std::chrono::steady_clock::time_point hostDeadline, EeEvent event);
void updateNextDeadline();
[[nodiscard]] bool hasReadyAtOrAbovePriority(int priority) const;
void renewTimeSlice();
void copyMainContextToRuntime();
PS2Runtime &m_runtime;
@@ -403,7 +414,10 @@ private:
uint32_t m_enabledDmacMask = 0xFFFFFFFFu;
int m_currentThreadId = 0;
bool m_rescheduleRequested = false;
bool m_timeSliceExpired = false;
bool m_insideInterrupt = false;
uint64_t m_eeCycle = 0;
uint64_t m_sliceEndCycle = kDefaultTimeSliceCycles;
std::thread::id m_executorThread{};
std::atomic<bool> m_running{false};
std::atomic<bool> m_guestExecuting{false};
@@ -425,7 +439,7 @@ private:
uint32_t m_gsVSyncCallbackGp = 0;
uint32_t m_gsVSyncCallbackSp = 0;
std::unordered_map<uint64_t, uint32_t> m_invocationStackTops;
std::atomic<int64_t> m_nextDeadlineNanoseconds{0};
std::atomic<uint64_t> m_nextDeadlineCycle{0};
mutable std::mutex m_snapshotMutex;
EeKernelSnapshot m_snapshot;
+206 -64
View File
@@ -36,6 +36,15 @@ namespace
constexpr uint64_t kAlarmTickMicroseconds = 64u;
constexpr uint32_t kDebugPublishDispatchInterval = 4096u;
constexpr uint64_t microsecondsToEeCycles(uint64_t microseconds)
{
return (microseconds * EeScheduler::kEeClockHz + 999999ull) / 1000000ull;
}
constexpr uint64_t kVBlankPeriodCycles = microsecondsToEeCycles(16667u);
constexpr uint64_t kVBlankDurationCycles = microsecondsToEeCycles(500u);
constexpr uint64_t kAlarmTickCycles = microsecondsToEeCycles(kAlarmTickMicroseconds);
template <typename Map>
int allocatePositiveId(int &nextId, const Map &objects)
{
@@ -90,7 +99,10 @@ void EeScheduler::reset(uint8_t *rdram, const R5900Context &mainContext)
m_enabledDmacMask = 0xFFFFFFFFu;
m_currentThreadId = 0;
m_rescheduleRequested = false;
m_timeSliceExpired = false;
m_insideInterrupt = false;
m_eeCycle = 0u;
m_sliceEndCycle = kDefaultTimeSliceCycles;
m_stopRequested.store(false, std::memory_order_release);
m_checkpointPending.store(false, std::memory_order_release);
m_debugPublishCountdown = 0u;
@@ -121,7 +133,8 @@ void EeScheduler::reset(uint8_t *rdram, const R5900Context &mainContext)
main.status = EeThreadStatus::Ready;
m_threads.emplace(main.id, std::move(main));
m_readyQueues[0].push_back(kMainThreadId);
scheduleEvent(std::chrono::steady_clock::now() + kVBlankPeriod,
scheduleEvent(m_eeCycle + kVBlankPeriodCycles,
std::chrono::steady_clock::now() + kVBlankPeriod,
EeEvent{EeEventType::VBlankStart, 0, 0});
publishSnapshot();
}
@@ -159,6 +172,7 @@ void EeScheduler::run()
m_pendingInvocations.pop_front();
owner->status = EeThreadStatus::Running;
m_currentThreadId = owner->id;
renewTimeSlice();
if (getRegU32(&invocation.context, 29) == 0u)
{
SET_GPR_U32(&invocation.context, 29, invocationStackTop());
@@ -258,11 +272,14 @@ void EeScheduler::run()
}
PS2Runtime::RecompiledFunction function = m_runtime.lookupFunction(context.pc);
if (checkpointDue(kGuestDispatchCycles))
{
continue;
}
try
{
m_insideInterrupt = !running->invocations.empty() &&
running->invocations.back().kind == GuestInvocationKind::Interrupt;
m_checkpointPending.store(false, std::memory_order_release);
m_insideInterrupt = !running->invocations.empty() && running->invocations.back().kind == GuestInvocationKind::Interrupt;
m_guestExecuting.store(true, std::memory_order_release);
function(m_rdram, &context, &m_runtime);
m_guestExecuting.store(false, std::memory_order_release);
@@ -286,9 +303,10 @@ void EeScheduler::run()
{
GuestThread *preempted = currentThread();
assert(preempted != nullptr);
enqueueReady(*preempted, true);
enqueueReady(*preempted, !m_timeSliceExpired);
m_currentThreadId = 0;
m_rescheduleRequested = false;
m_timeSliceExpired = false;
}
}
@@ -316,27 +334,48 @@ void EeScheduler::postEvent(EeEvent event)
{
std::lock_guard lock(m_eventMutex);
m_events.push_back(event);
m_checkpointPending.store(true, std::memory_order_release);
}
m_checkpointPending.store(true, std::memory_order_release);
m_eventCv.notify_one();
}
bool EeScheduler::checkpointDue() const noexcept
bool EeScheduler::checkpointDue(uint32_t cycles) noexcept
{
accountCycles(cycles);
if (m_checkpointPending.load(std::memory_order_acquire) ||
m_stopRequested.load(std::memory_order_acquire))
{
return true;
}
const int64_t deadline = m_nextDeadlineNanoseconds.load(std::memory_order_acquire);
if (deadline == 0)
const uint64_t nextEventCycle = m_nextDeadlineCycle.load(std::memory_order_acquire);
if (nextEventCycle != 0u && m_eeCycle >= nextEventCycle)
{
m_checkpointPending.store(true, std::memory_order_release);
return true;
}
if (m_eeCycle < m_sliceEndCycle)
{
return false;
}
const int64_t now = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
return now >= deadline;
const GuestThread *running = currentThread();
if (running != nullptr && hasReadyAtOrAbovePriority(running->currentPriority))
{
m_rescheduleRequested = true;
m_timeSliceExpired = true;
return true;
}
renewTimeSlice();
return false;
}
void EeScheduler::accountCycles(uint32_t cycles) noexcept
{
m_eeCycle += std::max<uint64_t>(1u, cycles);
}
bool EeScheduler::isExecutingGuest() const noexcept
@@ -737,6 +776,7 @@ void EeScheduler::transferIfRequested(bool interruptSafe)
m_currentThreadId = 0;
}
m_rescheduleRequested = false;
m_timeSliceExpired = false;
publishSnapshot();
throw EeDispatcherTransfer{};
}
@@ -998,8 +1038,8 @@ int EeScheduler::setAlarm(uint16_t ticks,
}
m_alarms.emplace(id, EeAlarm{id, ticks, handler, argument, gp, sp});
const uint64_t tickCount = ticks == 0u ? 1u : static_cast<uint64_t>(ticks);
scheduleEvent(std::chrono::steady_clock::now() +
std::chrono::microseconds(tickCount * kAlarmTickMicroseconds),
scheduleEvent(m_eeCycle + tickCount * kAlarmTickCycles,
std::chrono::steady_clock::now() + std::chrono::microseconds(tickCount * kAlarmTickMicroseconds),
EeEvent{EeEventType::Alarm, static_cast<uint32_t>(id), 0});
return id;
}
@@ -1409,6 +1449,9 @@ void EeScheduler::publishSnapshot()
{
EeKernelSnapshot next{};
next.sequence = ++m_snapshotSequence;
next.eeCycle = m_eeCycle;
next.sliceEndCycle = m_sliceEndCycle;
next.nextEventCycle = m_nextDeadlineCycle.load(std::memory_order_acquire);
next.runningThreadId = m_currentThreadId;
next.threads.reserve(m_threads.size());
for (const auto &[id, item] : m_threads)
@@ -1550,6 +1593,7 @@ void EeScheduler::makeRunning(GuestThread &item)
assert(item.status == EeThreadStatus::Ready);
item.status = EeThreadStatus::Running;
m_currentThreadId = item.id;
renewTimeSlice();
}
void EeScheduler::makeDormant(GuestThread &item)
@@ -1643,13 +1687,15 @@ void EeScheduler::applyPendingPreemption()
if (m_currentThreadId == 0)
{
m_rescheduleRequested = false;
m_timeSliceExpired = false;
return;
}
GuestThread *self = currentThread();
assert(self != nullptr);
enqueueReady(*self, true);
enqueueReady(*self, !m_timeSliceExpired);
m_currentThreadId = 0;
m_rescheduleRequested = false;
m_timeSliceExpired = false;
}
void EeScheduler::processPendingEvents()
@@ -1665,49 +1711,100 @@ void EeScheduler::processPendingEvents()
{
processEvent(event);
}
m_checkpointPending.store(false, std::memory_order_release);
{
std::lock_guard lock(m_eventMutex);
const uint64_t nextEventCycle = m_nextDeadlineCycle.load(std::memory_order_acquire);
const bool cycleEventDue = nextEventCycle != 0u && m_eeCycle >= nextEventCycle;
const bool pendingWork = !m_events.empty() || cycleEventDue || m_stopRequested.load(std::memory_order_acquire);
m_checkpointPending.store(pendingWork, std::memory_order_release);
}
applyPendingPreemption();
}
void EeScheduler::processDueDeadlines()
{
const auto now = std::chrono::steady_clock::now();
std::vector<ScheduledEvent> due;
for (;;)
{
std::lock_guard lock(m_eventMutex);
auto firstFuture = std::partition(m_deadlines.begin(), m_deadlines.end(), [now](const ScheduledEvent &item)
{ return item.deadline <= now; });
due.insert(due.end(),
std::make_move_iterator(m_deadlines.begin()),
std::make_move_iterator(firstFuture));
m_deadlines.erase(m_deadlines.begin(), firstFuture);
updateNextDeadline();
}
std::sort(due.begin(), due.end(), [](const ScheduledEvent &left, const ScheduledEvent &right)
{
if (left.deadline != right.deadline)
{
return left.deadline < right.deadline;
}
if (left.event.type != right.event.type)
{
return left.event.type < right.event.type;
}
if (left.event.id != right.event.id)
{
return left.event.id < right.event.id;
}
return left.sequence < right.sequence; });
for (ScheduledEvent &scheduled : due)
{
if (scheduled.event.type == EeEventType::VBlankStart)
std::vector<ScheduledEvent> due;
std::chrono::steady_clock::time_point pacingDeadline{};
{
scheduleEvent(scheduled.deadline + kVBlankDuration,
EeEvent{EeEventType::VBlankEnd, 0, m_vsyncTick + 1u});
scheduleEvent(scheduled.deadline + kVBlankPeriod,
EeEvent{EeEventType::VBlankStart, 0, 0});
std::unique_lock lock(m_eventMutex);
const auto now = std::chrono::steady_clock::now();
for (const ScheduledEvent &item : m_deadlines)
{
if (item.deadlineCycle <= m_eeCycle &&
(pacingDeadline == std::chrono::steady_clock::time_point{} ||
item.hostDeadline < pacingDeadline))
{
pacingDeadline = item.hostDeadline;
}
}
if (pacingDeadline == std::chrono::steady_clock::time_point{})
{
updateNextDeadline();
return;
}
if (now < pacingDeadline)
{
m_eventCv.wait_until(lock, pacingDeadline, [this]()
{ return !m_events.empty() ||
m_stopRequested.load(std::memory_order_acquire); });
if (!m_events.empty() || m_stopRequested.load(std::memory_order_acquire))
{
updateNextDeadline();
return;
}
}
const auto pacedNow = std::chrono::steady_clock::now();
auto firstFuture = std::partition(m_deadlines.begin(), m_deadlines.end(),
[this, pacedNow](const ScheduledEvent &item)
{ return item.deadlineCycle <= m_eeCycle &&
item.hostDeadline <= pacedNow; });
due.insert(due.end(),
std::make_move_iterator(m_deadlines.begin()),
std::make_move_iterator(firstFuture));
m_deadlines.erase(m_deadlines.begin(), firstFuture);
updateNextDeadline();
}
std::sort(due.begin(), due.end(), [](const ScheduledEvent &left, const ScheduledEvent &right)
{
if (left.deadlineCycle != right.deadlineCycle)
{
return left.deadlineCycle < right.deadlineCycle;
}
if (left.event.type != right.event.type)
{
return left.event.type < right.event.type;
}
if (left.event.id != right.event.id)
{
return left.event.id < right.event.id;
}
return left.sequence < right.sequence; });
if (due.empty())
{
return;
}
for (ScheduledEvent &scheduled : due)
{
if (scheduled.event.type == EeEventType::VBlankStart)
{
scheduleEvent(scheduled.deadlineCycle + kVBlankDurationCycles,
scheduled.hostDeadline + kVBlankDuration,
EeEvent{EeEventType::VBlankEnd, 0, m_vsyncTick + 1u});
scheduleEvent(scheduled.deadlineCycle + kVBlankPeriodCycles,
scheduled.hostDeadline + kVBlankPeriod,
EeEvent{EeEventType::VBlankStart, 0, 0});
}
processEvent(scheduled.event);
}
processEvent(scheduled.event);
}
}
@@ -1849,23 +1946,45 @@ void EeScheduler::writeGuestU32(uint32_t address, uint32_t value)
void EeScheduler::waitForEvent()
{
std::unique_lock lock(m_eventMutex);
if (!m_events.empty() || m_stopRequested.load(std::memory_order_acquire))
{
return;
}
if (m_deadlines.empty())
{
m_eventCv.wait(lock, [this]()
{ return !m_events.empty() || m_stopRequested.load(std::memory_order_acquire); });
return;
}
const auto next = std::min_element(m_deadlines.begin(), m_deadlines.end(), [](const ScheduledEvent &left, const ScheduledEvent &right)
{ return left.deadline < right.deadline; })
->deadline;
m_eventCv.wait_until(lock, next);
const auto next = std::min_element(m_deadlines.begin(), m_deadlines.end(),
[](const ScheduledEvent &left, const ScheduledEvent &right)
{
if (left.deadlineCycle != right.deadlineCycle)
{
return left.deadlineCycle < right.deadlineCycle;
}
return left.sequence < right.sequence;
});
const uint64_t deadlineCycle = next->deadlineCycle;
const auto hostDeadline = next->hostDeadline;
const bool signaled = m_eventCv.wait_until(lock, hostDeadline, [this]()
{ return !m_events.empty() ||
m_stopRequested.load(std::memory_order_acquire); });
if (!signaled)
{
m_eeCycle = std::max(m_eeCycle, deadlineCycle);
m_checkpointPending.store(true, std::memory_order_release);
}
}
void EeScheduler::scheduleEvent(std::chrono::steady_clock::time_point deadline, EeEvent event)
void EeScheduler::scheduleEvent(uint64_t deadlineCycle,
std::chrono::steady_clock::time_point hostDeadline,
EeEvent event)
{
{
std::lock_guard lock(m_eventMutex);
m_deadlines.push_back(ScheduledEvent{deadline, event, ++m_eventSequence});
m_deadlines.push_back(ScheduledEvent{deadlineCycle, hostDeadline, event, ++m_eventSequence});
updateNextDeadline();
}
m_eventCv.notify_one();
@@ -1875,15 +1994,38 @@ void EeScheduler::updateNextDeadline()
{
if (m_deadlines.empty())
{
m_nextDeadlineNanoseconds.store(0, std::memory_order_release);
m_nextDeadlineCycle.store(0u, std::memory_order_release);
return;
}
const auto it = std::min_element(m_deadlines.begin(), m_deadlines.end(), [](const ScheduledEvent &left, const ScheduledEvent &right)
{ return left.deadline < right.deadline; });
const int64_t nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(
it->deadline.time_since_epoch())
.count();
m_nextDeadlineNanoseconds.store(nanoseconds, std::memory_order_release);
const auto it = std::min_element(m_deadlines.begin(), m_deadlines.end(),
[](const ScheduledEvent &left, const ScheduledEvent &right)
{
if (left.deadlineCycle != right.deadlineCycle)
{
return left.deadlineCycle < right.deadlineCycle;
}
return left.sequence < right.sequence;
});
m_nextDeadlineCycle.store(it->deadlineCycle, std::memory_order_release);
}
bool EeScheduler::hasReadyAtOrAbovePriority(int priority) const
{
const int last = std::clamp(priority, 0, kPriorityCount - 1);
for (int p = 0; p <= last; ++p)
{
if (!m_readyQueues[static_cast<size_t>(p)].empty())
{
return true;
}
}
return false;
}
void EeScheduler::renewTimeSlice()
{
m_sliceEndCycle = m_eeCycle + kDefaultTimeSliceCycles;
m_timeSliceExpired = false;
}
void EeScheduler::copyMainContextToRuntime()
+403 -83
View File
@@ -1,14 +1,173 @@
#include "Common.h"
#include "CD.h"
#include "MPEG.h"
#include "runtime/ee_scheduler.h"
namespace ps2_stubs
{
namespace
{
uint32_t g_cdStReadTraceCount = 0u;
}
constexpr uint32_t kCdStreamBlocking = 1u;
constexpr uint32_t kDvdSectorsPerSecondX1 = 675u;
constexpr uint32_t kDvdSectorsPerSecondX4 = kDvdSectorsPerSecondX1 * 4u;
constexpr uint64_t kNtScFieldsPerSecondNumerator = 60000u;
constexpr uint64_t kNtScFieldsPerSecondDenominator = 1001u;
struct CdStreamTimingState
{
bool initialized = false;
bool active = false;
bool paused = false;
uint32_t capacitySectors = 64u;
uint32_t bankCount = 4u;
uint32_t sectorsPerBank = 16u;
uint32_t sectorsPerSecond = kDvdSectorsPerSecondX4;
uint64_t producedSectors = 0u;
uint64_t consumedSectors = 0u;
uint64_t productionRemainder = 0u;
uint64_t lastVSyncTick = 0u;
};
uint32_t g_cdStReadTraceCount = 0u;
CdStreamTimingState g_cdStreamTiming;
uint64_t currentCdStreamTick(PS2Runtime *runtime)
{
return runtime != nullptr ? runtime->eeScheduler().currentVSyncTick() : 0u;
}
uint32_t dvdStreamSectorsPerSecond(uint8_t spindleControl)
{
switch (spindleControl)
{
case 2u: // SCECdSpinX1
return kDvdSectorsPerSecondX1;
case 3u: // SCECdSpinX2
return kDvdSectorsPerSecondX1 * 2u;
case 11u: // SCECdSpin1p6
return 1080u;
case 4u: // SCECdSpinX4
case 0u: // SCECdSpinStm / max
case 1u: // optimized
case 20u: // max
default:
return kDvdSectorsPerSecondX4;
}
}
void resetCdStreamProduction(PS2Runtime *runtime)
{
g_cdStreamTiming.producedSectors = 0u;
g_cdStreamTiming.consumedSectors = 0u;
g_cdStreamTiming.productionRemainder = 0u;
g_cdStreamTiming.lastVSyncTick = currentCdStreamTick(runtime);
}
uint64_t totalCdStreamSectors()
{
if (g_cdStreamingEndLbn == 0xFFFFFFFFu || g_cdStreamingEndLbn < g_cdStreamingLbn)
{
return std::numeric_limits<uint64_t>::max();
}
return g_cdStreamTiming.consumedSectors + static_cast<uint64_t>(g_cdStreamingEndLbn - g_cdStreamingLbn);
}
void updateCdStreamProduction(PS2Runtime *runtime)
{
if (!g_cdStreamTiming.active || g_cdStreamTiming.paused || runtime == nullptr)
{
return;
}
const uint64_t tick = currentCdStreamTick(runtime);
if (tick <= g_cdStreamTiming.lastVSyncTick)
{
return;
}
const uint64_t elapsedTicks = tick - g_cdStreamTiming.lastVSyncTick;
g_cdStreamTiming.lastVSyncTick = tick;
// 59.94 Hz field clock: sectors = fields * sectors/s * 1001 / 60000.
const uint64_t unitsPerTick = static_cast<uint64_t>(g_cdStreamTiming.sectorsPerSecond) * kNtScFieldsPerSecondDenominator;
const uint64_t accumulated = g_cdStreamTiming.productionRemainder + elapsedTicks * unitsPerTick;
const uint64_t elapsedProduction = accumulated / kNtScFieldsPerSecondNumerator;
g_cdStreamTiming.productionRemainder = accumulated % kNtScFieldsPerSecondNumerator;
if (elapsedProduction == 0u)
{
return;
}
const uint64_t buffered = g_cdStreamTiming.producedSectors - g_cdStreamTiming.consumedSectors;
const uint64_t effectiveCapacity = std::max<uint32_t>(1u, g_cdStreamTiming.capacitySectors);
const uint64_t space = buffered < effectiveCapacity ? effectiveCapacity - buffered : 0u;
uint64_t newlyProduced = std::min(elapsedProduction, space);
const uint64_t streamTotal = totalCdStreamSectors();
if (streamTotal != std::numeric_limits<uint64_t>::max())
{
const uint64_t remainingToProduce = streamTotal > g_cdStreamTiming.producedSectors
? streamTotal - g_cdStreamTiming.producedSectors
: 0u;
newlyProduced = std::min(newlyProduced, remainingToProduce);
}
g_cdStreamTiming.producedSectors += newlyProduced;
if (elapsedProduction > newlyProduced)
{
g_cdStreamTiming.productionRemainder = 0u;
}
}
uint32_t bufferedCdStreamSectors(PS2Runtime *runtime)
{
updateCdStreamProduction(runtime);
const uint64_t buffered = g_cdStreamTiming.producedSectors - g_cdStreamTiming.consumedSectors;
return static_cast<uint32_t>(std::min<uint64_t>(buffered, std::numeric_limits<uint32_t>::max()));
}
uint32_t readableCdStreamSectors(PS2Runtime *runtime)
{
const uint32_t buffered = bufferedCdStreamSectors(runtime);
if (buffered == 0u)
{
return 0u;
}
const uint64_t streamTotal = totalCdStreamSectors();
if (streamTotal != std::numeric_limits<uint64_t>::max() && g_cdStreamTiming.producedSectors >= streamTotal)
{
return buffered;
}
const uint32_t bank = std::max(1u, g_cdStreamTiming.sectorsPerBank);
return (buffered / bank) * bank;
}
uint64_t cdStreamWakeTickForSectors(PS2Runtime *runtime, uint32_t sectorsNeeded)
{
const uint64_t now = currentCdStreamTick(runtime);
if (sectorsNeeded == 0u || g_cdStreamTiming.sectorsPerSecond == 0u)
{
return now;
}
const uint64_t requiredUnits = static_cast<uint64_t>(sectorsNeeded) * kNtScFieldsPerSecondNumerator;
const uint64_t remainingUnits = requiredUnits > g_cdStreamTiming.productionRemainder
? requiredUnits - g_cdStreamTiming.productionRemainder
: 0u;
const uint64_t unitsPerTick = static_cast<uint64_t>(g_cdStreamTiming.sectorsPerSecond) * kNtScFieldsPerSecondDenominator;
const uint64_t ticks = std::max<uint64_t>(1u, (remainingUnits + unitsPerTick - 1u) / unitsPerTick);
return now + ticks;
}
void restartCdStreamAt(uint32_t lbn, PS2Runtime *runtime)
{
g_cdStreamingLbn = lbn;
g_cdStreamingEndLbn = cdStreamingEndLbnForStart(lbn);
resetCdStreamProduction(runtime);
}
}
CdDebugSnapshot getCdDebugSnapshot()
{
@@ -41,9 +200,7 @@ namespace ps2_stubs
snapshot.files.push_back(std::move(row));
}
std::sort(snapshot.files.begin(), snapshot.files.end(), [](const CdDebugFileEntry &a, const CdDebugFileEntry &b)
{
return a.baseLbn < b.baseLbn;
});
{ return a.baseLbn < b.baseLbn; });
return snapshot;
}
@@ -241,6 +398,7 @@ namespace ps2_stubs
{
g_cdInitialized = true;
g_lastCdError = 0;
g_cdStreamTiming = {};
setReturnS32(ctx, 1);
}
@@ -492,8 +650,7 @@ namespace ps2_stubs
void sceCdSeek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
g_cdStreamingLbn = getRegU32(ctx, 4);
g_cdStreamingEndLbn = cdStreamingEndLbnForStart(g_cdStreamingLbn);
restartCdStreamAt(getRegU32(ctx, 4), runtime);
setReturnS32(ctx, 1);
}
@@ -509,6 +666,23 @@ namespace ps2_stubs
void sceCdStInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
const uint32_t bufferSectors = getRegU32(ctx, 4);
const uint32_t bankCount = getRegU32(ctx, 5);
const uint32_t bufferAddr = getRegU32(ctx, 6);
if (bufferSectors == 0u || bankCount == 0u || bufferAddr == 0u || bufferSectors / bankCount == 0u)
{
setReturnS32(ctx, 0);
return;
}
g_cdStreamTiming.initialized = true;
g_cdStreamTiming.active = false;
g_cdStreamTiming.paused = false;
g_cdStreamTiming.capacitySectors = bufferSectors;
g_cdStreamTiming.bankCount = bankCount;
g_cdStreamTiming.sectorsPerBank = std::max(1u, bufferSectors / bankCount);
resetCdStreamProduction(runtime);
setReturnS32(ctx, 1);
}
@@ -519,89 +693,216 @@ namespace ps2_stubs
void sceCdStPause(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
updateCdStreamProduction(runtime);
g_cdStreamTiming.paused = true;
setReturnS32(ctx, 1);
}
namespace
{
struct CdStReadContinuation
{
uint32_t requestedSectors = 0u;
uint32_t buffer = 0u;
uint32_t errorAddress = 0u;
uint32_t sectorsRead = 0u;
};
void finishCdStRead(uint8_t *rdram, R5900Context *ctx, const CdStReadContinuation &state, int32_t error)
{
if (int32_t *errorOut = reinterpret_cast<int32_t *>(getMemPtr(rdram, state.errorAddress)); errorOut)
{
*errorOut = error;
}
setReturnS32(ctx, static_cast<int32_t>(state.sectorsRead));
}
void continueCdStRead(uint8_t *rdram,
R5900Context *ctx,
PS2Runtime *runtime,
CdStReadContinuation state)
{
if (!g_cdStreamTiming.active || state.requestedSectors == 0u)
{
finishCdStRead(rdram, ctx, state, 0);
return;
}
for (;;)
{
uint32_t remaining = state.requestedSectors - state.sectorsRead;
bool atEnd = false;
if (g_cdStreamingEndLbn != 0xFFFFFFFFu)
{
if (g_cdStreamingLbn >= g_cdStreamingEndLbn)
{
remaining = 0u;
atEnd = true;
}
else
{
const uint32_t streamRemaining = g_cdStreamingEndLbn - g_cdStreamingLbn;
if (remaining > streamRemaining)
{
remaining = streamRemaining;
atEnd = true;
}
}
}
if (remaining == 0u)
{
if (atEnd || (g_cdStreamingEndLbn != 0xFFFFFFFFu && g_cdStreamingLbn >= g_cdStreamingEndLbn))
{
notifyMpegCdStreamEof(runtime);
}
finishCdStRead(rdram, ctx, state, 0);
return;
}
uint32_t available = readableCdStreamSectors(runtime);
if (runtime == nullptr)
{
available = remaining;
}
if (available == 0u)
{
if (!runtime)
{
finishCdStRead(rdram, ctx, state, 0);
return;
}
const uint32_t bank = std::max(1u, g_cdStreamTiming.sectorsPerBank);
const uint32_t wakeSectors = std::min(remaining, bank);
const uint32_t buffered = bufferedCdStreamSectors(runtime);
const uint32_t needed = wakeSectors > buffered ? wakeSectors - buffered : 1u;
const uint64_t wakeTick = cdStreamWakeTickForSectors(runtime, needed);
runtime->eeScheduler().waitVSync(
wakeTick - 1u,
-1,
[rdram, runtime, state](R5900Context &resumeContext)
{
if (static_cast<int32_t>(getRegU32(&resumeContext, 2)) < 0)
{
return;
}
continueCdStRead(rdram, &resumeContext, runtime, state);
});
}
uint32_t sectors = std::min(remaining, available);
const uint64_t destination64 = static_cast<uint64_t>(state.buffer) + static_cast<uint64_t>(state.sectorsRead) * kCdSectorSize;
const uint32_t destination = static_cast<uint32_t>(destination64);
const uint32_t offset = destination & PS2_RAM_MASK;
const size_t maxBytes = PS2_RAM_SIZE - offset;
sectors = std::min<uint32_t>(sectors, static_cast<uint32_t>(maxBytes / kCdSectorSize));
if (sectors == 0u)
{
g_lastCdError = -1;
finishCdStRead(rdram, ctx, state, g_lastCdError);
return;
}
const uint32_t readLbn = g_cdStreamingLbn;
const size_t readBytes = static_cast<size_t>(sectors) * kCdSectorSize;
if (!readCdSectors(readLbn, sectors, rdram + offset, readBytes))
{
finishCdStRead(rdram, ctx, state, g_lastCdError);
return;
}
g_cdStreamingLbn += sectors;
if (runtime == nullptr)
{
g_cdStreamTiming.producedSectors += sectors;
}
g_cdStreamTiming.consumedSectors += sectors;
state.sectorsRead += sectors;
const bool hitStreamEnd = g_cdStreamingEndLbn != 0xFFFFFFFFu && g_cdStreamingLbn >= g_cdStreamingEndLbn;
notifyMpegCdStreamDataProduced(static_cast<uint32_t>(readBytes), hitStreamEnd);
if (g_cdStReadTraceCount < 32u)
{
std::cerr << "[sceCdStRead] requested=" << state.requestedSectors
<< " accumulated=" << state.sectorsRead
<< " chunk=" << sectors
<< " buffered=" << readableCdStreamSectors(runtime)
<< " lbn=0x" << std::hex << readLbn
<< " end=0x" << g_cdStreamingEndLbn
<< std::dec << std::endl;
++g_cdStReadTraceCount;
}
if (state.sectorsRead >= state.requestedSectors || hitStreamEnd)
{
finishCdStRead(rdram, ctx, state, 0);
return;
}
// STMBLK is implemented by repeatedly consuming the stream ring.
// If a complete bank is still available, keep draining it before
// yielding. Otherwise the next iteration parks on the producer.
}
}
}
void sceCdStRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t requestedSectors = getRegU32(ctx, 4);
uint32_t sectors = requestedSectors;
uint32_t buf = getRegU32(ctx, 5);
uint32_t errAddr = getRegU32(ctx, 7);
const uint32_t requestedSectors = getRegU32(ctx, 4);
const uint32_t buffer = getRegU32(ctx, 5);
const uint32_t mode = getRegU32(ctx, 6);
const uint32_t errorAddress = getRegU32(ctx, 7);
uint32_t offset = buf & PS2_RAM_MASK;
size_t requestedBytes = static_cast<size_t>(requestedSectors) * kCdSectorSize;
const size_t maxBytes = PS2_RAM_SIZE - offset;
if (requestedBytes > maxBytes)
CdStReadContinuation state{};
state.requestedSectors = requestedSectors;
state.buffer = buffer;
state.errorAddress = errorAddress;
if (int32_t *errorOut = reinterpret_cast<int32_t *>(getMemPtr(rdram, errorAddress)); errorOut)
{
requestedBytes = maxBytes;
*errorOut = 0;
}
bool hitStreamEnd = false;
if (!g_cdStreamTiming.active || requestedSectors == 0u)
{
setReturnS32(ctx, 0);
return;
}
if (mode == kCdStreamBlocking)
{
continueCdStRead(rdram, ctx, runtime, state);
return;
}
uint32_t remaining = requestedSectors;
if (g_cdStreamingEndLbn != 0xFFFFFFFFu)
{
if (g_cdStreamingLbn >= g_cdStreamingEndLbn)
remaining = g_cdStreamingLbn < g_cdStreamingEndLbn
? std::min(remaining, g_cdStreamingEndLbn - g_cdStreamingLbn)
: 0u;
}
const uint32_t available = runtime != nullptr
? readableCdStreamSectors(runtime)
: remaining;
const uint32_t sectors = std::min(remaining, available);
if (sectors == 0u)
{
if (g_cdStreamingEndLbn != 0xFFFFFFFFu && g_cdStreamingLbn >= g_cdStreamingEndLbn)
{
sectors = 0u;
hitStreamEnd = true;
}
else
{
const uint32_t remaining = g_cdStreamingEndLbn - g_cdStreamingLbn;
if (sectors > remaining)
{
sectors = remaining;
hitStreamEnd = true;
}
notifyMpegCdStreamEof(runtime);
}
setReturnS32(ctx, 0);
return;
}
size_t bytes = static_cast<size_t>(sectors) * kCdSectorSize;
if (bytes > maxBytes)
{
bytes = maxBytes;
}
const uint32_t readLbn = g_cdStreamingLbn;
const bool ok = (sectors > 0u) && readCdSectors(readLbn, sectors, rdram + offset, bytes);
if (ok)
{
g_cdStreamingLbn += sectors;
if (requestedBytes > bytes)
{
std::memset(rdram + offset + bytes, 0, requestedBytes - bytes);
}
notifyMpegCdStreamDataProduced(
static_cast<uint32_t>(bytes),
hitStreamEnd || g_cdStreamingLbn == g_cdStreamingEndLbn);
}
else
{
if (requestedBytes > 0u)
{
std::memset(rdram + offset, 0, requestedBytes);
}
notifyMpegCdStreamEof(runtime);
}
if (int32_t *err = reinterpret_cast<int32_t *>(getMemPtr(rdram, errAddr)); err)
{
*err = ok ? 0 : g_lastCdError;
}
if (g_cdStReadTraceCount < 32u)
{
std::cerr << "[sceCdStRead] sectors=" << requestedSectors
<< " read=" << sectors
<< " buf=0x" << std::hex << buf
<< " lbn=0x" << readLbn
<< " end=0x" << g_cdStreamingEndLbn
<< std::dec << " ok=" << ok
<< " bytes=" << bytes << std::endl;
++g_cdStReadTraceCount;
}
setReturnS32(ctx, ok ? static_cast<int32_t>(sectors) : 0);
state.requestedSectors = sectors;
continueCdStRead(rdram, ctx, runtime, state);
}
void sceCdStream(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
@@ -611,43 +912,62 @@ namespace ps2_stubs
void sceCdStResume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
g_cdStreamTiming.paused = false;
g_cdStreamTiming.lastVSyncTick = currentCdStreamTick(runtime);
setReturnS32(ctx, 1);
}
void sceCdStSeek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
g_cdStreamingLbn = getRegU32(ctx, 4);
g_cdStreamingEndLbn = cdStreamingEndLbnForStart(g_cdStreamingLbn);
restartCdStreamAt(getRegU32(ctx, 4), runtime);
setReturnS32(ctx, 1);
}
void sceCdStSeekF(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
g_cdStreamingLbn = getRegU32(ctx, 4);
g_cdStreamingEndLbn = cdStreamingEndLbnForStart(g_cdStreamingLbn);
restartCdStreamAt(getRegU32(ctx, 4), runtime);
setReturnS32(ctx, 1);
}
void sceCdStStart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
g_cdStreamingLbn = getRegU32(ctx, 4);
g_cdStreamingEndLbn = cdStreamingEndLbnForStart(g_cdStreamingLbn);
const uint32_t lbn = getRegU32(ctx, 4);
const uint32_t modeAddr = getRegU32(ctx, 5);
uint8_t spindleControl = 0u;
if (const uint8_t *mode = getConstMemPtr(rdram, modeAddr); mode)
{
spindleControl = mode[1u];
}
restartCdStreamAt(lbn, runtime);
g_cdStreamTiming.active = true;
g_cdStreamTiming.paused = false;
g_cdStreamTiming.sectorsPerSecond = dvdStreamSectorsPerSecond(spindleControl);
g_cdStReadTraceCount = 0u;
notifyMpegCdStreamStart(runtime);
std::cerr << "[sceCdStStart] lbn=0x" << std::hex << g_cdStreamingLbn
<< " endLbn=0x" << g_cdStreamingEndLbn << std::dec << std::endl;
<< " endLbn=0x" << g_cdStreamingEndLbn << std::dec
<< " rate=" << g_cdStreamTiming.sectorsPerSecond << " sectors/s"
<< " buffer=" << g_cdStreamTiming.capacitySectors << " sectors"
<< " bank=" << g_cdStreamTiming.sectorsPerBank << " sectors"
<< std::endl;
setReturnS32(ctx, 1);
}
void sceCdStStat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
setReturnS32(ctx, 0);
const uint32_t buffered = bufferedCdStreamSectors(runtime);
const uint32_t bankSize = std::max(1u, g_cdStreamTiming.sectorsPerBank);
setReturnS32(ctx, static_cast<int32_t>((buffered / bankSize) * bankSize));
}
void sceCdStStop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
updateCdStreamProduction(runtime);
g_cdStreamTiming.active = false;
g_cdStreamTiming.paused = false;
notifyMpegCdStreamEof(runtime);
setReturnS32(ctx, 1);
}
+459 -103
View File
@@ -29,6 +29,8 @@ namespace ps2_stubs
{
int width = 0;
int height = 0;
int repeatPict = 0;
int64_t pts90k = -1;
std::vector<uint8_t> rgba;
};
@@ -46,13 +48,14 @@ namespace ps2_stubs
void configureFfmpegLogLevel()
{
static std::once_flag s_once;
std::call_once(s_once, [] {
std::call_once(s_once, []
{
#if AGRESSIVE_LOGS
av_log_set_level(AV_LOG_WARNING);
av_log_set_level(AV_LOG_WARNING);
#else
av_log_set_level(AV_LOG_ERROR);
av_log_set_level(AV_LOG_ERROR);
#endif
});
});
}
class MpegFfmpegDecoder
@@ -68,7 +71,7 @@ namespace ps2_stubs
MpegFfmpegDecoder(const MpegFfmpegDecoder &) = delete;
MpegFfmpegDecoder &operator=(const MpegFfmpegDecoder &) = delete;
bool feed(const uint8_t *data, size_t size, std::deque<MpegDecodedFrame> &frames)
bool feed(const uint8_t *data, size_t size, std::deque<MpegDecodedFrame> &frames, int64_t pts90k = -1, int64_t dts90k = -1)
{
if (!data || size == 0)
{
@@ -91,6 +94,8 @@ namespace ps2_stubs
const uint8_t *cursor = data;
size_t remaining = size;
int64_t parserPts = pts90k >= 0 ? pts90k : AV_NOPTS_VALUE;
int64_t parserDts = dts90k >= 0 ? dts90k : AV_NOPTS_VALUE;
while (remaining > 0)
{
uint8_t *packetData = nullptr;
@@ -104,8 +109,8 @@ namespace ps2_stubs
&packetSize,
cursor,
chunk,
AV_NOPTS_VALUE,
AV_NOPTS_VALUE,
parserPts,
parserDts,
0);
if (used < 0)
{
@@ -121,10 +126,16 @@ namespace ps2_stubs
cursor += used;
remaining -= static_cast<size_t>(used);
if (used > 0)
{
parserPts = AV_NOPTS_VALUE;
parserDts = AV_NOPTS_VALUE;
}
if (packetSize > 0)
{
++totalPacketsSent;
if (!sendPacket(packetData, static_cast<size_t>(packetSize), frames))
if (!sendPacket(packetData, static_cast<size_t>(packetSize), frames, m_parser->pts, m_parser->dts))
{
return false;
}
@@ -168,7 +179,7 @@ namespace ps2_stubs
AV_NOPTS_VALUE,
0);
(void)used;
if (packetSize > 0 && !sendPacket(packetData, static_cast<size_t>(packetSize), frames))
if (packetSize > 0 && !sendPacket(packetData, static_cast<size_t>(packetSize), frames, m_parser->pts, m_parser->dts))
{
return false;
}
@@ -253,7 +264,12 @@ namespace ps2_stubs
}
m_codecCtx->thread_count = 1;
m_codecCtx->skip_frame = AVDISCARD_NONKEY;
m_codecCtx->pkt_timebase = AVRational{1, 90000};
// feedElementaryStream() does not create the decoder until a valid
// MPEG sequence header has been found. Dropping non-key pictures
// here therefore throws away real presentation frames and makes
// movies finish early once the EE is fast enough to drain them.
m_codecCtx->skip_frame = AVDISCARD_DEFAULT;
m_codecCtx->err_recognition = 0;
const int ret = avcodec_open2(m_codecCtx, codec, nullptr);
if (ret < 0)
@@ -265,11 +281,14 @@ namespace ps2_stubs
m_initialized = true;
m_drained = false;
m_seenKeyframe = false;
return true;
}
bool sendPacket(const uint8_t *data, size_t size, std::deque<MpegDecodedFrame> &frames)
bool sendPacket(const uint8_t *data,
size_t size,
std::deque<MpegDecodedFrame> &frames,
int64_t pts = AV_NOPTS_VALUE,
int64_t dts = AV_NOPTS_VALUE)
{
if (!data || size == 0)
{
@@ -284,6 +303,8 @@ namespace ps2_stubs
return false;
}
std::memcpy(m_packet->data, data, size);
m_packet->pts = pts;
m_packet->dts = dts;
int ret = avcodec_send_packet(m_codecCtx, m_packet);
if (ret == AVERROR(EAGAIN))
@@ -332,12 +353,6 @@ namespace ps2_stubs
return true;
}
if (!m_seenKeyframe)
{
m_seenKeyframe = true;
m_codecCtx->skip_frame = AVDISCARD_DEFAULT;
}
if (!convertFrame(frames))
{
av_frame_unref(m_frame);
@@ -391,6 +406,10 @@ namespace ps2_stubs
MpegDecodedFrame decoded;
decoded.width = width;
decoded.height = height;
decoded.repeatPict = std::max(0, m_frame->repeat_pict);
decoded.pts90k = m_frame->best_effort_timestamp != AV_NOPTS_VALUE
? m_frame->best_effort_timestamp
: -1;
decoded.rgba.resize(static_cast<size_t>(width) * static_cast<size_t>(height) * 4u);
uint8_t *dstData[4] = {decoded.rgba.data(), nullptr, nullptr, nullptr};
@@ -423,14 +442,13 @@ namespace ps2_stubs
AVPixelFormat m_swsFormat = AV_PIX_FMT_NONE;
bool m_initialized = false;
bool m_drained = false;
bool m_seenKeyframe = false;
};
#else
// TODO
class MpegFfmpegDecoder
{
public:
bool feed(const uint8_t *, size_t, std::deque<MpegDecodedFrame> &)
bool feed(const uint8_t *, size_t, std::deque<MpegDecodedFrame> &, int64_t = -1, int64_t = -1)
{
static bool s_warnedNoFfmpeg = false;
if (!s_warnedNoFfmpeg)
@@ -460,6 +478,12 @@ namespace ps2_stubs
bool stream = false;
};
constexpr uint64_t kPictureClockOne = 1ull << 32u;
// NTSC-style fields at ~59.94 Hz to keep MPEG timing yet (29.97 fps).
constexpr uint64_t kDefaultPictureIntervalQ32 = 2ull * kPictureClockOne;
constexpr size_t kMpegTimingScanLimit = 4096u;
constexpr size_t kMaxDecodedPicturesAhead = 8u;
struct MpegPlaybackState
{
uint32_t picturesServed = 0u;
@@ -468,6 +492,7 @@ namespace ps2_stubs
uint32_t decodeMode = 0u;
uint32_t imageBufferAddr = 0u;
bool sawInput = false;
bool sawSequenceEnd = false;
bool streamEnded = false;
bool decoderFailed = false;
uint64_t cdStreamGeneration = 0u;
@@ -477,8 +502,16 @@ namespace ps2_stubs
std::vector<uint32_t> pssGuestAddrs;
std::deque<MpegDecodedFrame> decodedFrames;
std::unique_ptr<MpegFfmpegDecoder> decoder;
uint64_t pictureIntervalQ32 = 0u;
uint8_t frameRateCode = 0u;
uint8_t frameRateExtensionN = 0u;
uint8_t frameRateExtensionD = 0u;
bool hasFrameRateExtension = false;
std::vector<uint8_t> videoTimingScanBuffer;
uint64_t pictureIntervalQ32 = kDefaultPictureIntervalQ32;
uint64_t nextPictureTickQ32 = std::numeric_limits<uint64_t>::max();
uint64_t presentationEndTickQ32 = std::numeric_limits<uint64_t>::max();
int64_t firstPresentedPts90k = -1;
uint64_t ptsPresentationBaseTickQ32 = 0u;
};
struct MpegStreamCallbackEvent
@@ -528,9 +561,8 @@ namespace ps2_stubs
constexpr uint8_t kMpegPrivateStream1 = 0xBDu;
constexpr size_t kStartCodeNotFound = std::numeric_limits<size_t>::max();
constexpr uint32_t kMpegCallbackDataSize = 0x20u;
constexpr uint64_t kPictureClockOne = 1ull << 32u;
uint64_t mpegPictureIntervalQ32(uint8_t frameRateCode)
uint64_t mpegPictureIntervalQ32(uint8_t frameRateCode, uint8_t frameRateExtensionN = 0u, uint8_t frameRateExtensionD = 0u)
{
uint64_t frameRateNumerator = 0u;
uint64_t frameRateDenominator = 1u;
@@ -567,13 +599,179 @@ namespace ps2_stubs
return 0u;
}
// EE VSync runs at the NTSC field rate. Q32 preserves fractional
// cadences such as 24 fps without accumulating host-time drift.
const uint64_t denominator = 1001u * frameRateNumerator;
const uint64_t numerator = (60000u * frameRateDenominator) << 32u;
const uint64_t extensionNumerator = static_cast<uint64_t>(frameRateExtensionN) + 1u;
const uint64_t extensionDenominator = static_cast<uint64_t>(frameRateExtensionD) + 1u;
const uint64_t denominator = 1001u * frameRateNumerator * extensionNumerator;
const uint64_t numerator = (60000u * frameRateDenominator * extensionDenominator) << 32u;
return std::max(kPictureClockOne, (numerator + denominator / 2u) / denominator);
}
uint32_t readMpegBits(const uint8_t *data, size_t bitOffset, uint32_t bitCount)
{
uint32_t value = 0u;
for (uint32_t bit = 0u; bit < bitCount; ++bit)
{
const size_t absoluteBit = bitOffset + bit;
const uint8_t source = data[absoluteBit >> 3u];
value = (value << 1u) | ((source >> (7u - static_cast<uint32_t>(absoluteBit & 7u))) & 1u);
}
return value;
}
void updateMpegPictureTiming(MpegPlaybackState &playback, const uint8_t *data, size_t size)
{
if (!data || size == 0u)
{
return;
}
playback.videoTimingScanBuffer.insert(playback.videoTimingScanBuffer.end(), data, data + size);
if (playback.videoTimingScanBuffer.size() > kMpegTimingScanLimit)
{
const size_t discard = playback.videoTimingScanBuffer.size() - kMpegTimingScanLimit;
playback.videoTimingScanBuffer.erase(playback.videoTimingScanBuffer.begin(), playback.videoTimingScanBuffer.begin() + static_cast<std::ptrdiff_t>(discard));
}
const std::vector<uint8_t> &buffer = playback.videoTimingScanBuffer;
size_t lastSequenceHeader = kStartCodeNotFound;
for (size_t i = 0u; i + 7u < buffer.size(); ++i)
{
if (buffer[i + 0u] == 0x00u &&
buffer[i + 1u] == 0x00u &&
buffer[i + 2u] == 0x01u &&
buffer[i + 3u] == 0xB3u)
{
const uint8_t frameRateCode = buffer[i + 7u] & 0x0Fu;
if (mpegPictureIntervalQ32(frameRateCode) != 0u)
{
lastSequenceHeader = i;
}
}
}
if (lastSequenceHeader == kStartCodeNotFound)
{
return;
}
playback.frameRateCode = buffer[lastSequenceHeader + 7u] & 0x0Fu;
playback.frameRateExtensionN = 0u;
playback.frameRateExtensionD = 0u;
playback.hasFrameRateExtension = false;
playback.pictureIntervalQ32 = mpegPictureIntervalQ32(playback.frameRateCode);
for (size_t i = lastSequenceHeader + 8u; i + 9u < buffer.size(); ++i)
{
if (buffer[i + 0u] != 0x00u ||
buffer[i + 1u] != 0x00u ||
buffer[i + 2u] != 0x01u)
{
continue;
}
if (buffer[i + 3u] == 0xB3u)
{
break;
}
if (buffer[i + 3u] != 0xB5u)
{
continue;
}
const uint8_t *extension = buffer.data() + i + 4u;
if (readMpegBits(extension, 0u, 4u) != 1u)
{
continue;
}
playback.frameRateExtensionN = static_cast<uint8_t>(readMpegBits(extension, 41u, 2u));
playback.frameRateExtensionD = static_cast<uint8_t>(readMpegBits(extension, 43u, 5u));
playback.hasFrameRateExtension = true;
playback.pictureIntervalQ32 = mpegPictureIntervalQ32(
playback.frameRateCode,
playback.frameRateExtensionN,
playback.frameRateExtensionD);
break;
}
if (playback.pictureIntervalQ32 == 0u)
{
playback.pictureIntervalQ32 = kDefaultPictureIntervalQ32;
}
}
uint64_t decodedFrameIntervalQ32(const MpegPlaybackState &playback,
const MpegDecodedFrame &frame)
{
const uint64_t base = playback.pictureIntervalQ32 != 0u
? playback.pictureIntervalQ32
: kDefaultPictureIntervalQ32;
const uint64_t fields = static_cast<uint64_t>(2 + std::max(0, frame.repeatPict));
return std::max(kPictureClockOne, (base * fields + 1u) / 2u);
}
constexpr uint64_t kMpegPtsWrap = 1ull << 33u;
constexpr uint64_t kMpegPtsHalfWrap = 1ull << 32u;
int64_t mpegPtsDelta90k(int64_t fromPts, int64_t toPts)
{
if (fromPts < 0 || toPts < 0)
{
return 0;
}
uint64_t from = static_cast<uint64_t>(fromPts) & (kMpegPtsWrap - 1u);
uint64_t to = static_cast<uint64_t>(toPts) & (kMpegPtsWrap - 1u);
uint64_t delta = (to - from) & (kMpegPtsWrap - 1u);
if (delta >= kMpegPtsHalfWrap)
{
return -static_cast<int64_t>(kMpegPtsWrap - delta);
}
return static_cast<int64_t>(delta);
}
uint64_t mpegPtsDeltaToVSyncQ32(uint64_t delta90k)
{
// 90 kHz MPEG clock -> NTSC field clock (60000/1001 Hz):
// fields = pts * 60000 / (90000 * 1001) = pts * 2 / 3003.
constexpr uint64_t kPtsDivisor = 3003u;
const uint64_t whole = delta90k / kPtsDivisor;
const uint64_t remainder = delta90k % kPtsDivisor;
const uint64_t wholeQ32 = whole * 2u * kPictureClockOne;
const uint64_t remainderQ32 = ((remainder * 2u * kPictureClockOne) + kPtsDivisor / 2u) / kPtsDivisor;
return wholeQ32 + remainderQ32;
}
uint64_t presentationTickForFrame(MpegPlaybackState &playback, const MpegDecodedFrame &frame, uint64_t currentTickQ32)
{
if (frame.pts90k < 0)
{
if (playback.nextPictureTickQ32 == std::numeric_limits<uint64_t>::max())
{
playback.nextPictureTickQ32 = currentTickQ32;
}
return playback.nextPictureTickQ32;
}
if (playback.firstPresentedPts90k < 0)
{
playback.firstPresentedPts90k = frame.pts90k;
playback.ptsPresentationBaseTickQ32 = currentTickQ32;
return currentTickQ32;
}
const int64_t delta = mpegPtsDelta90k(playback.firstPresentedPts90k, frame.pts90k);
if (delta >= 0)
{
return playback.ptsPresentationBaseTickQ32 + mpegPtsDeltaToVSyncQ32(static_cast<uint64_t>(delta));
}
const uint64_t backwards = mpegPtsDeltaToVSyncQ32(static_cast<uint64_t>(-delta));
return playback.ptsPresentationBaseTickQ32 > backwards
? playback.ptsPresentationBaseTickQ32 - backwards
: 0u;
}
uint32_t align16(uint32_t value)
{
return (value + 15u) & ~15u;
@@ -622,8 +820,7 @@ namespace ps2_stubs
uint16_t readBe16(const uint8_t *p)
{
return static_cast<uint16_t>((static_cast<uint16_t>(p[0]) << 8u) |
static_cast<uint16_t>(p[1]));
return static_cast<uint16_t>((static_cast<uint16_t>(p[0]) << 8u) | static_cast<uint16_t>(p[1]));
}
bool isVideoStreamId(uint8_t streamId)
@@ -721,49 +918,90 @@ namespace ps2_stubs
return kStartCodeNotFound;
}
size_t parsePesPayloadOffset(const uint8_t *packet, size_t packetSize)
struct MpegPesHeader
{
size_t payloadOffset = 0u;
int64_t pts90k = -1;
int64_t dts90k = -1;
};
int64_t decodePesTimestamp90k(const uint8_t *p, size_t remaining)
{
if (!p || remaining < 5u)
{
return -1;
}
const uint64_t value =
(static_cast<uint64_t>((p[0] >> 1u) & 0x07u) << 30u) |
(static_cast<uint64_t>(p[1]) << 22u) |
(static_cast<uint64_t>((p[2] >> 1u) & 0x7Fu) << 15u) |
(static_cast<uint64_t>(p[3]) << 7u) |
static_cast<uint64_t>((p[4] >> 1u) & 0x7Fu);
return static_cast<int64_t>(value & (kMpegPtsWrap - 1u));
}
MpegPesHeader parsePesHeader(const uint8_t *packet, size_t packetSize)
{
MpegPesHeader result{};
result.payloadOffset = packetSize;
if (!packet || packetSize <= 6u)
{
return packetSize;
return result;
}
size_t pos = 6u;
if (packetSize >= 9u && (packet[pos] & 0xC0u) == 0x80u)
{
return std::min(packetSize, 9u + static_cast<size_t>(packet[pos + 2u]));
const uint8_t ptsDtsFlags = packet[pos + 1u] & 0xC0u;
const size_t headerDataLength = static_cast<size_t>(packet[pos + 2u]);
const size_t optionalStart = 9u;
const size_t optionalEnd = std::min(packetSize, optionalStart + headerDataLength);
if ((ptsDtsFlags == 0x80u || ptsDtsFlags == 0xC0u) && optionalEnd >= optionalStart + 5u)
{
result.pts90k = decodePesTimestamp90k(packet + optionalStart, optionalEnd - optionalStart);
}
if (ptsDtsFlags == 0xC0u && optionalEnd >= optionalStart + 10u)
{
result.dts90k = decodePesTimestamp90k(packet + optionalStart + 5u, optionalEnd - optionalStart - 5u);
}
result.payloadOffset = optionalEnd;
return result;
}
// MPEG-1 PES.
while (pos < packetSize && packet[pos] == 0xFFu)
{
++pos;
}
if (pos + 1u < packetSize && (packet[pos] & 0xC0u) == 0x40u)
{
pos += 2u;
}
if (pos >= packetSize)
{
return packetSize;
return result;
}
const uint8_t flags = packet[pos];
if ((flags & 0xF0u) == 0x20u)
const uint8_t marker = packet[pos] & 0xF0u;
if (marker == 0x20u && pos + 5u <= packetSize)
{
result.pts90k = decodePesTimestamp90k(packet + pos, packetSize - pos);
pos += 5u;
}
else if ((flags & 0xF0u) == 0x30u)
else if (marker == 0x30u && pos + 10u <= packetSize)
{
result.pts90k = decodePesTimestamp90k(packet + pos, packetSize - pos);
result.dts90k = decodePesTimestamp90k(packet + pos + 5u, packetSize - pos - 5u);
pos += 10u;
}
else if (flags == 0x0Fu)
else if (packet[pos] == 0x0Fu)
{
pos += 1u;
++pos;
}
return std::min(packetSize, pos);
result.payloadOffset = std::min(packetSize, pos);
return result;
}
void flushDecoderIfEnded(MpegPlaybackState &playback)
@@ -774,7 +1012,7 @@ namespace ps2_stubs
}
}
void feedElementaryStream(MpegPlaybackState &playback, const uint8_t *data, size_t size)
void feedElementaryStream(MpegPlaybackState &playback, const uint8_t *data, size_t size, int64_t pts90k = -1, int64_t dts90k = -1)
{
if (!data || size == 0)
{
@@ -800,6 +1038,7 @@ namespace ps2_stubs
}
playback.sawInput = true;
updateMpegPictureTiming(playback, data, size);
if (playback.waitingForVideoSequenceHeader)
{
playback.videoSequenceSyncBuffer.insert(
@@ -833,8 +1072,14 @@ namespace ps2_stubs
data = playback.videoSequenceSyncBuffer.data();
size = playback.videoSequenceSyncBuffer.size();
playback.pictureIntervalQ32 = mpegPictureIntervalQ32(data[7u] & 0x0Fu);
if (playback.pictureIntervalQ32 == 0u)
{
playback.pictureIntervalQ32 = kDefaultPictureIntervalQ32;
}
playback.nextPictureTickQ32 = std::numeric_limits<uint64_t>::max();
playback.presentationEndTickQ32 = std::numeric_limits<uint64_t>::max();
playback.firstPresentedPts90k = -1;
playback.ptsPresentationBaseTickQ32 = 0u;
playback.waitingForVideoSequenceHeader = false;
playback.decoderFailed = false;
playback.decoder.reset();
@@ -843,7 +1088,7 @@ namespace ps2_stubs
if (containsMpegSequenceEnd(data, size))
{
playback.streamEnded = true;
playback.sawSequenceEnd = true;
playback.cdStreamGeneration = g_mpeg_stub_state.cdStreamGeneration;
}
@@ -852,7 +1097,7 @@ namespace ps2_stubs
playback.decoder = std::make_unique<MpegFfmpegDecoder>();
}
if (!playback.decoder->feed(data, size, playback.decodedFrames))
if (!playback.decoder->feed(data, size, playback.decodedFrames, pts90k, dts90k))
{
playback.decoder.reset();
playback.waitingForVideoSequenceHeader = true;
@@ -909,13 +1154,17 @@ namespace ps2_stubs
uint32_t streamType,
uint32_t dataAddr,
uint32_t len,
std::vector<MpegStreamCallbackEvent> &callbackEvents)
std::vector<MpegStreamCallbackEvent> &callbackEvents,
int64_t pts90k = -1,
int64_t dts90k = -1)
{
MpegStreamCallbackEvent event{};
event.mpegAddr = mpegAddr;
event.streamType = streamType;
event.dataAddr = dataAddr;
event.len = len;
event.pts = pts90k >= 0 ? static_cast<uint64_t>(pts90k) : 0xFFFFFFFFFFFFFFFFull;
event.dts = dts90k >= 0 ? static_cast<uint64_t>(dts90k) : 0xFFFFFFFFFFFFFFFFull;
event.callbacks = matchingStreamCallbacks(mpegAddr, streamType);
if (!event.callbacks.empty())
{
@@ -1067,7 +1316,8 @@ namespace ps2_stubs
if (isVideoStreamId(streamId))
{
const size_t payloadStart = parsePesPayloadOffset(buffer.data(), packetEnd);
const MpegPesHeader pes = parsePesHeader(buffer.data(), packetEnd);
const size_t payloadStart = pes.payloadOffset;
if (payloadStart < packetEnd)
{
if (payloadStart < playback.pssGuestAddrs.size())
@@ -1077,17 +1327,22 @@ namespace ps2_stubs
kMpegStrM2V,
playback.pssGuestAddrs[payloadStart],
static_cast<uint32_t>(packetEnd - payloadStart),
callbackEvents);
callbackEvents,
pes.pts90k,
pes.dts90k);
}
feedElementaryStream(
playback,
buffer.data() + payloadStart,
packetEnd - payloadStart);
packetEnd - payloadStart,
pes.pts90k,
pes.dts90k);
}
}
else if (isAudioStreamId(streamId))
{
const size_t payloadStart = parsePesPayloadOffset(buffer.data(), packetEnd);
const MpegPesHeader pes = parsePesHeader(buffer.data(), packetEnd);
const size_t payloadStart = pes.payloadOffset;
if (payloadStart < packetEnd && payloadStart < playback.pssGuestAddrs.size())
{
queueStreamCallbackEvent(
@@ -1095,13 +1350,17 @@ namespace ps2_stubs
kMpegStrPCM,
playback.pssGuestAddrs[payloadStart],
static_cast<uint32_t>(packetEnd - payloadStart),
callbackEvents);
callbackEvents,
pes.pts90k,
pes.dts90k);
queueStreamCallbackEvent(
mpegAddr,
kMpegStrADPCM,
playback.pssGuestAddrs[payloadStart],
static_cast<uint32_t>(packetEnd - payloadStart),
callbackEvents);
callbackEvents,
pes.pts90k,
pes.dts90k);
}
}
@@ -1135,6 +1394,25 @@ namespace ps2_stubs
}
}
bool mpegDemuxBackpressured(const MpegPlaybackState &playback)
{
// Let EOF finalization drain any tail that is already in the guest
// ring, otherwise bound decode lead to a handful of pictures.
//
// Important: do not park sceMpegDemuxPss/Ring here. Code Veronica
// explicitly wakes its video thread before every demux call and that
// thread sleeps again after presenting one picture. A single host
// decoder feed can enqueue more than kMaxDecodedPicturesAhead frames;
// parking the producer then leaves the consumer asleep after draining
// just one frame, with nobody left to issue the next WakeupThread.
// Returning 0 bytes consumed instead leaves the guest ring intact and
// lets the game's producer loop wake the consumer again. Backpressure
// still propagates naturally to sceCdStRead because the ring does not
// advance while this is true.
return !g_mpeg_stub_state.currentCdStreamEofSeen &&
playback.decodedFrames.size() >= kMaxDecodedPicturesAhead;
}
void recordCdStreamBytesDemuxedUnlocked(
size_t consumed,
std::vector<uint32_t> &completedMpegIds,
@@ -1575,15 +1853,23 @@ namespace ps2_stubs
{
(void)rdram;
const uint32_t mpegAddr = getRegU32(ctx, 4);
bool wakePictureWaiter = false;
{
std::lock_guard<std::mutex> lock(g_mpeg_stub_mutex);
MpegPlaybackState &playback = getPlaybackState(mpegAddr);
const size_t framesBefore = playback.decodedFrames.size();
if (playback.decoder)
{
playback.decoder->flush(playback.decodedFrames);
}
wakePictureWaiter = playback.decodedFrames.size() != framesBefore ||
playback.streamEnded ||
playback.decoderFailed;
}
if (wakePictureWaiter)
{
runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK);
}
runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK);
setReturnS32(ctx, 0);
}
@@ -1594,9 +1880,11 @@ namespace ps2_stubs
const uint32_t byteCount = getRegU32(ctx, 6);
size_t copied = 0u;
bool wakePictureWaiter = false;
{
std::lock_guard<std::mutex> lock(g_mpeg_stub_mutex);
MpegPlaybackState &playback = getPlaybackState(mpegAddr);
const size_t framesBefore = playback.decodedFrames.size();
while (copied < byteCount)
{
const uint32_t curAddr = dataAddr + static_cast<uint32_t>(copied);
@@ -1610,9 +1898,13 @@ namespace ps2_stubs
feedElementaryStream(playback, src, chunk);
copied += chunk;
}
wakePictureWaiter = playback.decodedFrames.size() != framesBefore || playback.streamEnded || playback.decoderFailed;
}
runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK);
if (wakePictureWaiter)
{
runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK);
}
setReturnS32(ctx, static_cast<int32_t>(copied));
}
@@ -1793,24 +2085,46 @@ namespace ps2_stubs
std::vector<MpegStreamCallbackEvent> callbackEvents;
std::vector<uint32_t> completedMpegIds;
size_t consumed = 0u;
size_t decodedBefore = 0u;
size_t decodedCount = 0u;
uint32_t traceIdx = 0u;
bool eofChanged = false;
bool backpressured = false;
{
std::lock_guard<std::mutex> lock(g_mpeg_stub_mutex);
MpegPlaybackState &playback = getPlaybackState(mpegAddr);
consumed = appendGuestBytes(mpegAddr, playback, rdram, dataAddr, byteCount, callbackEvents);
recordCdStreamBytesDemuxedUnlocked(consumed, completedMpegIds, eofChanged);
decodedBefore = playback.decodedFrames.size();
backpressured = mpegDemuxBackpressured(playback);
if (!backpressured)
{
consumed = appendGuestBytes(mpegAddr, playback, rdram, dataAddr, byteCount, callbackEvents);
recordCdStreamBytesDemuxedUnlocked(consumed, completedMpegIds, eofChanged);
}
decodedCount = playback.decodedFrames.size();
traceIdx = g_mpeg_stub_state.demuxPssTraceCount++;
}
runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK);
if (backpressured)
{
if (traceIdx < 32u)
{
PS2_IF_AGRESSIVE_LOGS({
std::cerr << "[MPEG:DemuxPss:BACKPRESSURE] mpeg=0x" << std::hex << mpegAddr << std::dec << " decoded=" << decodedCount << std::endl;
});
}
setReturnS32(ctx, 0);
return;
}
const bool currentStreamCompleted = std::find(completedMpegIds.begin(), completedMpegIds.end(), mpegAddr) != completedMpegIds.end();
if (decodedCount != decodedBefore || eofChanged || currentStreamCompleted)
{
runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK);
}
for (const uint32_t completedMpegId : completedMpegIds)
{
if (completedMpegId != mpegAddr)
{
runtime->eeScheduler().completeExternalWait(
kMpegPictureWaitType, completedMpegId, KE_OK);
runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, completedMpegId, KE_OK);
}
}
@@ -1854,32 +2168,56 @@ namespace ps2_stubs
std::vector<MpegStreamCallbackEvent> callbackEvents;
std::vector<uint32_t> completedMpegIds;
size_t consumed = 0u;
size_t decodedBefore = 0u;
size_t decodedCount = 0u;
uint32_t traceIdx = 0u;
bool eofChanged = false;
bool backpressured = false;
{
std::lock_guard<std::mutex> lock(g_mpeg_stub_mutex);
MpegPlaybackState &playback = getPlaybackState(mpegAddr);
consumed = appendGuestRingBytes(
mpegAddr,
playback,
rdram,
dataAddr,
availableBytes,
ringBaseAddr,
ringSize,
callbackEvents);
recordCdStreamBytesDemuxedUnlocked(consumed, completedMpegIds, eofChanged);
decodedBefore = playback.decodedFrames.size();
backpressured = mpegDemuxBackpressured(playback);
if (!backpressured)
{
consumed = appendGuestRingBytes(
mpegAddr,
playback,
rdram,
dataAddr,
availableBytes,
ringBaseAddr,
ringSize,
callbackEvents);
recordCdStreamBytesDemuxedUnlocked(consumed, completedMpegIds, eofChanged);
}
decodedCount = playback.decodedFrames.size();
traceIdx = g_mpeg_stub_state.demuxRingTraceCount++;
}
runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK);
if (backpressured)
{
if (traceIdx < 32u)
{
PS2_IF_AGRESSIVE_LOGS({
std::cerr << "[MPEG:DemuxPssRing:BACKPRESSURE] mpeg=0x" << std::hex << mpegAddr
<< std::dec << " decoded=" << decodedCount
<< " avail=" << availableBytes << std::endl;
});
}
setReturnS32(ctx, 0);
return;
}
const bool currentStreamCompleted = std::find(completedMpegIds.begin(), completedMpegIds.end(), mpegAddr) != completedMpegIds.end();
if (decodedCount != decodedBefore || eofChanged || currentStreamCompleted)
{
runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK);
}
for (const uint32_t completedMpegId : completedMpegIds)
{
if (completedMpegId != mpegAddr)
{
runtime->eeScheduler().completeExternalWait(
kMpegPictureWaitType, completedMpegId, KE_OK);
runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, completedMpegId, KE_OK);
}
}
@@ -1986,40 +2324,38 @@ namespace ps2_stubs
if (!playback.decodedFrames.empty())
{
if (playback.pictureIntervalQ32 != 0u)
const uint64_t currentTick = runtime->eeScheduler().currentVSyncTick();
const uint64_t currentTickQ32 = currentTick << 32u;
const MpegDecodedFrame &nextFrame = playback.decodedFrames.front();
const uint64_t frameIntervalQ32 = decodedFrameIntervalQ32(playback, nextFrame);
uint64_t presentationTargetQ32 = presentationTickForFrame(playback, nextFrame, currentTickQ32);
if (currentTickQ32 > presentationTargetQ32 && currentTickQ32 - presentationTargetQ32 >= frameIntervalQ32)
{
const uint64_t currentTick = runtime->eeScheduler().currentVSyncTick();
const uint64_t currentTickQ32 = currentTick << 32u;
if (playback.nextPictureTickQ32 == std::numeric_limits<uint64_t>::max())
const uint64_t correction = currentTickQ32 - presentationTargetQ32;
presentationTargetQ32 = currentTickQ32;
if (nextFrame.pts90k >= 0 && playback.firstPresentedPts90k >= 0)
{
playback.nextPictureTickQ32 = currentTickQ32;
playback.ptsPresentationBaseTickQ32 += correction;
}
playback.nextPictureTickQ32 = currentTickQ32;
}
if (currentTickQ32 < playback.nextPictureTickQ32)
{
const uint64_t eligibleTick =
(playback.nextPictureTickQ32 + kPictureClockOne - 1u) >> 32u;
lock.unlock();
runtime->eeScheduler().waitVSync(
eligibleTick - 1u,
-1,
[rdram, runtime](R5900Context &resumeContext)
if (currentTickQ32 < presentationTargetQ32)
{
const uint64_t eligibleTick = (presentationTargetQ32 + kPictureClockOne - 1u) >> 32u;
lock.unlock();
runtime->eeScheduler().waitVSync(
eligibleTick - 1u,
-1,
[rdram, runtime](R5900Context &resumeContext)
{
if (static_cast<int32_t>(getRegU32(&resumeContext, 2)) < 0)
{
if (static_cast<int32_t>(getRegU32(&resumeContext, 2)) < 0)
{
return;
}
sceMpegGetPicture(rdram, &resumeContext, runtime);
});
}
// If decoding fell more than one frame behind, resume from the
// current field instead of releasing a burst of stale pictures.
if (currentTickQ32 - playback.nextPictureTickQ32 >= playback.pictureIntervalQ32)
{
playback.nextPictureTickQ32 = currentTickQ32;
}
playback.nextPictureTickQ32 += playback.pictureIntervalQ32;
return;
}
sceMpegGetPicture(rdram, &resumeContext, runtime);
});
}
frame = std::move(playback.decodedFrames.front());
@@ -2030,6 +2366,8 @@ namespace ps2_stubs
height = playback.height;
frameCount = playback.picturesServed;
playback.picturesServed += 1u;
playback.nextPictureTickQ32 = presentationTargetQ32 + frameIntervalQ32;
playback.presentationEndTickQ32 = playback.nextPictureTickQ32;
haveFrame = true;
if (g_mpeg_stub_state.pictureTraceCount < 32u)
{
@@ -2117,20 +2455,38 @@ namespace ps2_stubs
std::lock_guard<std::mutex> lock(g_mpeg_stub_mutex);
g_mpeg_stub_state.initialized = true;
MpegPlaybackState &playback = getPlaybackState(mpegAddr);
const bool ended = playback.streamEnded || (playback.decoderFailed && playback.sawInput);
// Only the producer/demux EOF is authoritative. A sequence_end_code can
// be observed while more PSS data is still buffered, and a decoder
// failure before producer EOF may still recover on a later sequence.
const bool producerEnded =
g_mpeg_stub_state.currentCdStreamEofSeen &&
playback.cdStreamGeneration == g_mpeg_stub_state.cdStreamGeneration;
const bool ended = producerEnded &&
(playback.streamEnded || (playback.decoderFailed && playback.sawInput));
const uint64_t presentationEnd = playback.presentationEndTickQ32;
const uint64_t currentTickQ32 = runtime != nullptr
? (runtime->eeScheduler().currentVSyncTick() << 32u)
: std::numeric_limits<uint64_t>::max();
const bool presentationComplete =
presentationEnd == std::numeric_limits<uint64_t>::max() ||
currentTickQ32 >= presentationEnd;
if (g_mpeg_stub_state.isEndTraceCount < 16u)
{
PS2_IF_AGRESSIVE_LOGS({
std::cerr << "[MPEG:IsEnd] mpeg=0x" << std::hex << mpegAddr << std::dec
<< " ended=" << ended
<< " producerEof=" << producerEnded
<< " seqEnd=" << playback.sawSequenceEnd
<< " streamEnded=" << playback.streamEnded
<< " presentationComplete=" << presentationComplete
<< " frames=" << playback.decodedFrames.size()
<< " sawInput=" << playback.sawInput << std::endl;
});
++g_mpeg_stub_state.isEndTraceCount;
}
setReturnS32(ctx, (ended && playback.decodedFrames.empty()) ? 1 : 0);
setReturnS32(ctx, (ended && playback.decodedFrames.empty() && presentationComplete) ? 1 : 0);
}
void sceMpegIsRefBuffEmpty(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
+4
View File
@@ -767,6 +767,10 @@ namespace
{
const EeKernelSnapshot snapshot = runtime.eeScheduler().snapshot();
ImGui::Text("Threads: %zu running=%d", snapshot.threads.size(), snapshot.runningThreadId);
ImGui::Text("EE cycle: %llu slice end: %llu next event: %llu",
static_cast<unsigned long long>(snapshot.eeCycle),
static_cast<unsigned long long>(snapshot.sliceEndCycle),
static_cast<unsigned long long>(snapshot.nextEventCycle));
if (ImGui::BeginTable("threads", 11, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2(0, 320)))
{
ImGui::TableSetupColumn("ID");
+10 -2
View File
@@ -1312,6 +1312,14 @@ bool PS2Runtime::dispatchGuestBranch(uint8_t *rdram,
ctx->pc = targetPc;
const bool isCall = (kind == GuestBranchKind::DirectCall || kind == GuestBranchKind::IndirectCall);
// Every inter-function transfer is also a deterministic EE safe point.
// Backward edges inside generated functions use eeCheckpointDue(), while
// this charge bounds straight-line call chains that have no local loop.
if (m_eeScheduler && m_eeScheduler->checkpointDue(EeScheduler::kGuestDispatchCycles))
{
return false;
}
if (kind == GuestBranchKind::Return)
{
if (!hasFunction(targetPc))
@@ -2165,9 +2173,9 @@ void PS2Runtime::postEeEvent(EeEvent event)
m_eeScheduler->postEvent(event);
}
bool PS2Runtime::eeCheckpointDue() const noexcept
bool PS2Runtime::eeCheckpointDue(uint32_t cycles) noexcept
{
return m_eeScheduler->checkpointDue();
return m_eeScheduler->checkpointDue(cycles);
}
[[noreturn]] void PS2Runtime::eeWaitVSyncTicks(uint32_t ticks, uint32_t resumePc)