mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
feat: added EE clock Hz
fix: fix MPEG out of sync with new EE refactor
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user