mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-10-02 02:31:51 -04:00
feat: bad wip mpeg fix for code veronica
This commit is contained in:
@@ -249,6 +249,7 @@ inline void ps2TraceGuestWrite(uint8_t *rdram,
|
||||
(void)valueHi;
|
||||
(void)op;
|
||||
(void)ctx;
|
||||
// TODO we dont need this anymore so on next release it will be deleted
|
||||
}
|
||||
|
||||
inline void ps2TraceGuestRangeWrite(uint8_t *rdram,
|
||||
@@ -262,6 +263,7 @@ inline void ps2TraceGuestRangeWrite(uint8_t *rdram,
|
||||
(void)size;
|
||||
(void)op;
|
||||
(void)ctx;
|
||||
// TODO we dont need this anymore so on next release it will be deleted
|
||||
}
|
||||
|
||||
class PS2Runtime
|
||||
|
||||
@@ -322,13 +322,10 @@ public:
|
||||
[[nodiscard]] uint64_t currentVSyncTick() const noexcept;
|
||||
uint32_t setGsVSyncCallback(uint32_t callback, uint32_t gp, uint32_t sp);
|
||||
|
||||
[[noreturn]] void waitVSync(uint64_t afterTick, int fixedResult = -1);
|
||||
[[noreturn]] void waitVSync(uint64_t afterTick, int fixedResult = -1, std::function<void(R5900Context &)> completion = {});
|
||||
void completeVSync(uint64_t tick);
|
||||
void completeExternalWait(uint32_t type, uint64_t token, int result);
|
||||
[[noreturn]] void waitExternal(EeWaitReason reason,
|
||||
uint32_t type,
|
||||
uint64_t token,
|
||||
std::function<void(R5900Context &)> completion = {});
|
||||
[[noreturn]] void waitExternal(EeWaitReason reason, uint32_t type, uint64_t token, std::function<void(R5900Context &)> completion = {});
|
||||
|
||||
[[nodiscard]] GuestThread *thread(int id);
|
||||
[[nodiscard]] const GuestThread *thread(int id) const;
|
||||
|
||||
@@ -204,8 +204,6 @@ struct GSRegisters
|
||||
uint64_t extdata; // External data
|
||||
uint64_t extwrite; // External write
|
||||
uint64_t bgcolor; // Background color
|
||||
// Status remains atomic because the renderer/UI can observe it while the
|
||||
// single EE executor updates SIGNAL, FINISH and FIELD.
|
||||
std::atomic<uint64_t> csr;
|
||||
std::atomic<uint64_t> vsyncTick;
|
||||
uint64_t imr; // Interrupt mask
|
||||
|
||||
@@ -1004,10 +1004,8 @@ int EeScheduler::cancelAlarm(int id)
|
||||
{
|
||||
std::lock_guard lock(m_eventMutex);
|
||||
std::erase_if(m_deadlines, [id](const ScheduledEvent &scheduled)
|
||||
{
|
||||
return scheduled.event.type == EeEventType::Alarm &&
|
||||
scheduled.event.id == static_cast<uint32_t>(id);
|
||||
});
|
||||
{ return scheduled.event.type == EeEventType::Alarm &&
|
||||
scheduled.event.id == static_cast<uint32_t>(id); });
|
||||
updateNextDeadline();
|
||||
}
|
||||
return KE_OK;
|
||||
@@ -1235,9 +1233,12 @@ uint32_t EeScheduler::setGsVSyncCallback(uint32_t callback, uint32_t gp, uint32_
|
||||
return previous;
|
||||
}
|
||||
|
||||
[[noreturn]] void EeScheduler::waitVSync(uint64_t afterTick, int fixedResult)
|
||||
[[noreturn]] void EeScheduler::waitVSync(uint64_t afterTick, int fixedResult, std::function<void(R5900Context &)> completion)
|
||||
{
|
||||
blockCurrent(EeWaitState{EeWaitReason::VSync, EeVSyncWait{afterTick, fixedResult}});
|
||||
blockCurrent(EeWaitState{
|
||||
EeWaitReason::VSync,
|
||||
EeVSyncWait{afterTick, fixedResult},
|
||||
std::move(completion)});
|
||||
}
|
||||
|
||||
void EeScheduler::completeVSync(uint64_t tick)
|
||||
@@ -1438,10 +1439,10 @@ void EeScheduler::publishSnapshot()
|
||||
for (const auto &[id, item] : m_eventFlags)
|
||||
{
|
||||
next.eventFlags.push_back(EeEventFlagSnapshot{id,
|
||||
item.bits,
|
||||
item.initBits,
|
||||
item.attr,
|
||||
static_cast<uint32_t>(item.waiters.size())});
|
||||
item.bits,
|
||||
item.initBits,
|
||||
item.attr,
|
||||
static_cast<uint32_t>(item.waiters.size())});
|
||||
}
|
||||
std::sort(next.eventFlags.begin(), next.eventFlags.end(), [](const auto &left, const auto &right)
|
||||
{ return left.id < right.id; });
|
||||
@@ -1686,8 +1687,7 @@ void EeScheduler::processDueDeadlines()
|
||||
{
|
||||
return left.event.id < right.event.id;
|
||||
}
|
||||
return left.sequence < right.sequence;
|
||||
});
|
||||
return left.sequence < right.sequence; });
|
||||
for (ScheduledEvent &scheduled : due)
|
||||
{
|
||||
if (scheduled.event.type == EeEventType::VBlankStart)
|
||||
@@ -1806,7 +1806,7 @@ void EeScheduler::finishEventWaiters(EeEventFlag &flag, bool interruptSafe)
|
||||
bool EeScheduler::eventCondition(uint32_t current, uint32_t requested, uint32_t mode)
|
||||
{
|
||||
return (mode & WEF_OR) != 0u ? (current & requested) != 0u
|
||||
: (current & requested) == requested;
|
||||
: (current & requested) == requested;
|
||||
}
|
||||
|
||||
int EeScheduler::waitObjectId(const EeWaitState &wait)
|
||||
|
||||
@@ -571,10 +571,9 @@ namespace ps2_stubs
|
||||
{
|
||||
std::memset(rdram + offset + bytes, 0, requestedBytes - bytes);
|
||||
}
|
||||
if (hitStreamEnd || g_cdStreamingLbn == g_cdStreamingEndLbn)
|
||||
{
|
||||
notifyMpegCdStreamEof(runtime);
|
||||
}
|
||||
notifyMpegCdStreamDataProduced(
|
||||
static_cast<uint32_t>(bytes),
|
||||
hitStreamEnd || g_cdStreamingLbn == g_cdStreamingEndLbn);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -477,6 +477,8 @@ namespace ps2_stubs
|
||||
std::vector<uint32_t> pssGuestAddrs;
|
||||
std::deque<MpegDecodedFrame> decodedFrames;
|
||||
std::unique_ptr<MpegFfmpegDecoder> decoder;
|
||||
uint64_t pictureIntervalQ32 = 0u;
|
||||
uint64_t nextPictureTickQ32 = std::numeric_limits<uint64_t>::max();
|
||||
};
|
||||
|
||||
struct MpegStreamCallbackEvent
|
||||
@@ -495,6 +497,9 @@ namespace ps2_stubs
|
||||
bool initialized = false;
|
||||
uint32_t nextCallbackHandle = 1u;
|
||||
uint64_t cdStreamGeneration = 0u;
|
||||
uint64_t cdStreamBytesProduced = 0u;
|
||||
uint64_t cdStreamBytesDemuxed = 0u;
|
||||
bool cdStreamEofPending = false;
|
||||
bool currentCdStreamEofSeen = false;
|
||||
uint32_t feedEsTraceCount = 0u;
|
||||
uint32_t demuxPssTraceCount = 0u;
|
||||
@@ -523,6 +528,51 @@ 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 frameRateNumerator = 0u;
|
||||
uint64_t frameRateDenominator = 1u;
|
||||
switch (frameRateCode)
|
||||
{
|
||||
case 1u:
|
||||
frameRateNumerator = 24000u;
|
||||
frameRateDenominator = 1001u;
|
||||
break;
|
||||
case 2u:
|
||||
frameRateNumerator = 24u;
|
||||
break;
|
||||
case 3u:
|
||||
frameRateNumerator = 25u;
|
||||
break;
|
||||
case 4u:
|
||||
frameRateNumerator = 30000u;
|
||||
frameRateDenominator = 1001u;
|
||||
break;
|
||||
case 5u:
|
||||
frameRateNumerator = 30u;
|
||||
break;
|
||||
case 6u:
|
||||
frameRateNumerator = 50u;
|
||||
break;
|
||||
case 7u:
|
||||
frameRateNumerator = 60000u;
|
||||
frameRateDenominator = 1001u;
|
||||
break;
|
||||
case 8u:
|
||||
frameRateNumerator = 60u;
|
||||
break;
|
||||
default:
|
||||
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;
|
||||
return std::max(kPictureClockOne, (numerator + denominator / 2u) / denominator);
|
||||
}
|
||||
|
||||
uint32_t align16(uint32_t value)
|
||||
{
|
||||
@@ -644,12 +694,12 @@ namespace ps2_stubs
|
||||
|
||||
size_t findMpegSequenceHeader(const uint8_t *data, size_t size)
|
||||
{
|
||||
if (!data || size < 7u)
|
||||
if (!data || size < 8u)
|
||||
{
|
||||
return kStartCodeNotFound;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i + 6u < size; ++i)
|
||||
for (size_t i = 0; i + 7u < size; ++i)
|
||||
{
|
||||
if (data[i] == 0x00u &&
|
||||
data[i + 1u] == 0x00u &&
|
||||
@@ -660,7 +710,9 @@ namespace ps2_stubs
|
||||
(static_cast<uint32_t>(data[i + 5u]) >> 4u);
|
||||
const uint32_t height = ((static_cast<uint32_t>(data[i + 5u]) & 0x0Fu) << 8u) |
|
||||
static_cast<uint32_t>(data[i + 6u]);
|
||||
if (width != 0u && height != 0u && width <= 4096u && height <= 4096u)
|
||||
const uint8_t frameRateCode = data[i + 7u] & 0x0Fu;
|
||||
if (width != 0u && height != 0u && width <= 4096u && height <= 4096u &&
|
||||
mpegPictureIntervalQ32(frameRateCode) != 0u)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
@@ -781,6 +833,8 @@ namespace ps2_stubs
|
||||
|
||||
data = playback.videoSequenceSyncBuffer.data();
|
||||
size = playback.videoSequenceSyncBuffer.size();
|
||||
playback.pictureIntervalQ32 = mpegPictureIntervalQ32(data[7u] & 0x0Fu);
|
||||
playback.nextPictureTickQ32 = std::numeric_limits<uint64_t>::max();
|
||||
playback.waitingForVideoSequenceHeader = false;
|
||||
playback.decoderFailed = false;
|
||||
playback.decoder.reset();
|
||||
@@ -1064,6 +1118,36 @@ namespace ps2_stubs
|
||||
flushDecoderIfEnded(playback);
|
||||
}
|
||||
|
||||
void finalizeCdStreamEofUnlocked(std::vector<uint32_t> &completedMpegIds, bool &changed)
|
||||
{
|
||||
g_mpeg_stub_state.cdStreamEofPending = false;
|
||||
g_mpeg_stub_state.currentCdStreamEofSeen = true;
|
||||
for (auto &[mpegAddr, playback] : g_mpeg_stub_state.playbackByMpeg)
|
||||
{
|
||||
completedMpegIds.push_back(mpegAddr);
|
||||
if (!playback.sawInput || playback.streamEnded)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
finishPlaybackStream(mpegAddr, playback);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void recordCdStreamBytesDemuxedUnlocked(
|
||||
size_t consumed,
|
||||
std::vector<uint32_t> &completedMpegIds,
|
||||
bool &changed)
|
||||
{
|
||||
g_mpeg_stub_state.cdStreamBytesDemuxed += consumed;
|
||||
if (g_mpeg_stub_state.cdStreamEofPending &&
|
||||
g_mpeg_stub_state.cdStreamBytesDemuxed >= g_mpeg_stub_state.cdStreamBytesProduced)
|
||||
{
|
||||
finalizeCdStreamEofUnlocked(completedMpegIds, changed);
|
||||
}
|
||||
}
|
||||
|
||||
void appendPssBytes(uint32_t mpegAddr,
|
||||
MpegPlaybackState &playback,
|
||||
const uint8_t *data,
|
||||
@@ -1385,6 +1469,9 @@ namespace ps2_stubs
|
||||
g_mpeg_stub_state.initialized = false;
|
||||
g_mpeg_stub_state.nextCallbackHandle = 1u;
|
||||
g_mpeg_stub_state.cdStreamGeneration = 0u;
|
||||
g_mpeg_stub_state.cdStreamBytesProduced = 0u;
|
||||
g_mpeg_stub_state.cdStreamBytesDemuxed = 0u;
|
||||
g_mpeg_stub_state.cdStreamEofPending = false;
|
||||
g_mpeg_stub_state.currentCdStreamEofSeen = false;
|
||||
g_mpeg_stub_state.feedEsTraceCount = 0u;
|
||||
g_mpeg_stub_state.demuxPssTraceCount = 0u;
|
||||
@@ -1424,6 +1511,9 @@ namespace ps2_stubs
|
||||
(void)runtime;
|
||||
std::lock_guard<std::mutex> lock(g_mpeg_stub_mutex);
|
||||
++g_mpeg_stub_state.cdStreamGeneration;
|
||||
g_mpeg_stub_state.cdStreamBytesProduced = 0u;
|
||||
g_mpeg_stub_state.cdStreamBytesDemuxed = 0u;
|
||||
g_mpeg_stub_state.cdStreamEofPending = false;
|
||||
g_mpeg_stub_state.currentCdStreamEofSeen = false;
|
||||
g_mpeg_stub_state.feedEsTraceCount = 0u;
|
||||
g_mpeg_stub_state.demuxPssTraceCount = 0u;
|
||||
@@ -1442,24 +1532,23 @@ namespace ps2_stubs
|
||||
});
|
||||
}
|
||||
|
||||
void notifyMpegCdStreamDataProduced(uint32_t byteCount, bool endOfStream)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mpeg_stub_mutex);
|
||||
g_mpeg_stub_state.cdStreamBytesProduced += byteCount;
|
||||
if (endOfStream)
|
||||
{
|
||||
g_mpeg_stub_state.cdStreamEofPending = true;
|
||||
}
|
||||
}
|
||||
|
||||
void notifyMpegCdStreamEof(PS2Runtime *runtime)
|
||||
{
|
||||
std::vector<uint32_t> completedMpegIds;
|
||||
bool changed = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mpeg_stub_mutex);
|
||||
g_mpeg_stub_state.currentCdStreamEofSeen = true;
|
||||
for (auto &[mpegAddr, playback] : g_mpeg_stub_state.playbackByMpeg)
|
||||
{
|
||||
completedMpegIds.push_back(mpegAddr);
|
||||
if (!playback.sawInput || playback.streamEnded)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
finishPlaybackStream(mpegAddr, playback);
|
||||
changed = true;
|
||||
}
|
||||
finalizeCdStreamEofUnlocked(completedMpegIds, changed);
|
||||
}
|
||||
|
||||
if (changed)
|
||||
@@ -1702,17 +1791,28 @@ namespace ps2_stubs
|
||||
const uint32_t byteCount = getRegU32(ctx, 6);
|
||||
|
||||
std::vector<MpegStreamCallbackEvent> callbackEvents;
|
||||
std::vector<uint32_t> completedMpegIds;
|
||||
size_t consumed = 0u;
|
||||
size_t decodedCount = 0u;
|
||||
uint32_t traceIdx = 0u;
|
||||
bool eofChanged = 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);
|
||||
decodedCount = playback.decodedFrames.size();
|
||||
traceIdx = g_mpeg_stub_state.demuxPssTraceCount++;
|
||||
}
|
||||
runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK);
|
||||
for (const uint32_t completedMpegId : completedMpegIds)
|
||||
{
|
||||
if (completedMpegId != mpegAddr)
|
||||
{
|
||||
runtime->eeScheduler().completeExternalWait(
|
||||
kMpegPictureWaitType, completedMpegId, KE_OK);
|
||||
}
|
||||
}
|
||||
|
||||
if (traceIdx < 32u)
|
||||
{
|
||||
@@ -1752,9 +1852,11 @@ namespace ps2_stubs
|
||||
const uint32_t ringSize = readAbiArg4(rdram, ctx);
|
||||
|
||||
std::vector<MpegStreamCallbackEvent> callbackEvents;
|
||||
std::vector<uint32_t> completedMpegIds;
|
||||
size_t consumed = 0u;
|
||||
size_t decodedCount = 0u;
|
||||
uint32_t traceIdx = 0u;
|
||||
bool eofChanged = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_mpeg_stub_mutex);
|
||||
MpegPlaybackState &playback = getPlaybackState(mpegAddr);
|
||||
@@ -1767,10 +1869,19 @@ namespace ps2_stubs
|
||||
ringBaseAddr,
|
||||
ringSize,
|
||||
callbackEvents);
|
||||
recordCdStreamBytesDemuxedUnlocked(consumed, completedMpegIds, eofChanged);
|
||||
decodedCount = playback.decodedFrames.size();
|
||||
traceIdx = g_mpeg_stub_state.demuxRingTraceCount++;
|
||||
}
|
||||
runtime->eeScheduler().completeExternalWait(kMpegPictureWaitType, mpegAddr, KE_OK);
|
||||
for (const uint32_t completedMpegId : completedMpegIds)
|
||||
{
|
||||
if (completedMpegId != mpegAddr)
|
||||
{
|
||||
runtime->eeScheduler().completeExternalWait(
|
||||
kMpegPictureWaitType, completedMpegId, KE_OK);
|
||||
}
|
||||
}
|
||||
|
||||
if (traceIdx < 32u)
|
||||
{
|
||||
@@ -1875,6 +1986,42 @@ namespace ps2_stubs
|
||||
|
||||
if (!playback.decodedFrames.empty())
|
||||
{
|
||||
if (playback.pictureIntervalQ32 != 0u)
|
||||
{
|
||||
const uint64_t currentTick = runtime->eeScheduler().currentVSyncTick();
|
||||
const uint64_t currentTickQ32 = currentTick << 32u;
|
||||
if (playback.nextPictureTickQ32 == std::numeric_limits<uint64_t>::max())
|
||||
{
|
||||
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 (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;
|
||||
}
|
||||
|
||||
frame = std::move(playback.decodedFrames.front());
|
||||
playback.decodedFrames.pop_front();
|
||||
playback.width = static_cast<uint32_t>(frame.width);
|
||||
@@ -1948,10 +2095,16 @@ namespace ps2_stubs
|
||||
(void)rdram;
|
||||
std::lock_guard<std::mutex> lock(g_mpeg_stub_mutex);
|
||||
const uint64_t cdStreamGeneration = g_mpeg_stub_state.cdStreamGeneration;
|
||||
const uint64_t cdStreamBytesProduced = g_mpeg_stub_state.cdStreamBytesProduced;
|
||||
const uint64_t cdStreamBytesDemuxed = g_mpeg_stub_state.cdStreamBytesDemuxed;
|
||||
const bool cdStreamEofPending = g_mpeg_stub_state.cdStreamEofPending;
|
||||
const bool currentCdStreamEofSeen = g_mpeg_stub_state.currentCdStreamEofSeen;
|
||||
resetMpegStubStateUnlocked();
|
||||
g_mpeg_stub_state.initialized = true;
|
||||
g_mpeg_stub_state.cdStreamGeneration = cdStreamGeneration;
|
||||
g_mpeg_stub_state.cdStreamBytesProduced = cdStreamBytesProduced;
|
||||
g_mpeg_stub_state.cdStreamBytesDemuxed = cdStreamBytesDemuxed;
|
||||
g_mpeg_stub_state.cdStreamEofPending = cdStreamEofPending;
|
||||
g_mpeg_stub_state.currentCdStreamEofSeen = currentCdStreamEofSeen;
|
||||
setReturnU32(ctx, 0u);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace ps2_stubs
|
||||
void resetMpegStubState();
|
||||
void enqueueMpegDecodedFrameForTesting(uint32_t mpegAddr);
|
||||
void notifyMpegCdStreamStart(PS2Runtime *runtime = nullptr);
|
||||
void notifyMpegCdStreamDataProduced(uint32_t byteCount, bool endOfStream);
|
||||
void notifyMpegCdStreamEof(PS2Runtime *runtime = nullptr);
|
||||
void sceMpegFlush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void sceMpegAddBs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
|
||||
@@ -151,6 +151,10 @@ namespace
|
||||
std::atomic<uint32_t> gMpegWaitStage{0u};
|
||||
std::atomic<uint32_t> gMpegNoDuplicateStage{0u};
|
||||
std::atomic<uint32_t> gMpegNoDuplicateProducerStage{0u};
|
||||
std::atomic<uint32_t> gMpegPacingStage{0u};
|
||||
std::atomic<uint32_t> gMpegPacingFirstProducerStage{0u};
|
||||
std::atomic<uint32_t> gMpegPacingSecondProducerStage{0u};
|
||||
std::atomic<uint64_t> gMpegPacingResumeTick{0u};
|
||||
|
||||
constexpr uint32_t kMpegWaitMainPc = 0x00125000u;
|
||||
constexpr uint32_t kMpegWaitResumePc = 0x00125010u;
|
||||
@@ -162,6 +166,13 @@ namespace
|
||||
constexpr uint32_t kMpegNoDuplicateProducerPc = 0x00125050u;
|
||||
constexpr uint32_t kMpegNoDuplicateHandle = 0x00124000u;
|
||||
constexpr uint32_t kMpegNoDuplicateImage = 0x00131000u;
|
||||
constexpr uint32_t kMpegPacingMainPc = 0x00125060u;
|
||||
constexpr uint32_t kMpegPacingAfterFirstPc = 0x00125070u;
|
||||
constexpr uint32_t kMpegPacingAfterSecondPc = 0x00125080u;
|
||||
constexpr uint32_t kMpegPacingFirstProducerPc = 0x00125090u;
|
||||
constexpr uint32_t kMpegPacingSecondProducerPc = 0x001250A0u;
|
||||
constexpr uint32_t kMpegPacingHandle = 0x00124800u;
|
||||
constexpr uint32_t kMpegPacingImage = 0x00132000u;
|
||||
constexpr uint32_t kIpuInitMainPc = 0x00125100u;
|
||||
constexpr uint32_t kIpuInitResumePc = 0x00125104u;
|
||||
constexpr uint32_t kIpuSetD4Pc = 0x00126428u;
|
||||
@@ -251,6 +262,51 @@ namespace
|
||||
ctx->pc = 0u;
|
||||
}
|
||||
|
||||
void testMpegPacingMain(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
setRegU32(*ctx, 4, kMpegPacingHandle);
|
||||
setRegU32(*ctx, 5, kMpegPacingImage);
|
||||
ctx->pc = kMpegPacingAfterFirstPc;
|
||||
ps2_stubs::sceMpegGetPicture(rdram, ctx, runtime);
|
||||
}
|
||||
|
||||
void testMpegPacingAfterFirst(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
gMpegPacingStage.store(1u, std::memory_order_release);
|
||||
setRegU32(*ctx, 4, kMpegPacingHandle);
|
||||
setRegU32(*ctx, 5, kMpegPacingImage);
|
||||
ctx->pc = kMpegPacingAfterSecondPc;
|
||||
ps2_stubs::sceMpegGetPicture(rdram, ctx, runtime);
|
||||
}
|
||||
|
||||
void testMpegPacingAfterSecond(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
gMpegPacingResumeTick.store(
|
||||
runtime->eeScheduler().currentVSyncTick(),
|
||||
std::memory_order_release);
|
||||
gMpegPacingStage.store(3u, std::memory_order_release);
|
||||
ctx->pc = 0u;
|
||||
runtime->requestStop();
|
||||
}
|
||||
|
||||
void testMpegPacingFirstProducer(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
gMpegPacingFirstProducerStage.store(
|
||||
gMpegPacingStage.load(std::memory_order_acquire),
|
||||
std::memory_order_release);
|
||||
runtime->eeScheduler().postEvent(EeEvent{EeEventType::VBlankStart, 0u, 0u});
|
||||
ctx->pc = 0u;
|
||||
}
|
||||
|
||||
void testMpegPacingSecondProducer(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
gMpegPacingSecondProducerStage.store(
|
||||
gMpegPacingStage.load(std::memory_order_acquire),
|
||||
std::memory_order_release);
|
||||
runtime->eeScheduler().postEvent(EeEvent{EeEventType::VBlankStart, 0u, 0u});
|
||||
ctx->pc = 0u;
|
||||
}
|
||||
|
||||
void testRecordMpegStreamCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
if (!rdram || !ctx)
|
||||
@@ -694,6 +750,66 @@ void register_ps2_runtime_expansion_tests()
|
||||
"resumed GetPicture should publish the configured height");
|
||||
});
|
||||
|
||||
tc.Run("CD EOF becomes visible only after the final guest MPEG demux", [](TestCase &t)
|
||||
{
|
||||
PS2Runtime runtime;
|
||||
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
|
||||
ps2_stubs::resetMpegStubState();
|
||||
ps2_stubs::notifyMpegCdStreamStart();
|
||||
|
||||
constexpr uint32_t kMpegAddr = 0x00126000u;
|
||||
constexpr uint32_t kPacketAddr = 0x00127000u;
|
||||
const std::vector<uint8_t> finalPacket = {
|
||||
0x00u, 0x00u, 0x01u, 0xE0u,
|
||||
0x00u, 0x04u,
|
||||
0x80u, 0x00u, 0x00u,
|
||||
0x00u};
|
||||
std::memcpy(rdram.data() + kPacketAddr, finalPacket.data(), finalPacket.size());
|
||||
|
||||
ps2_stubs::notifyMpegCdStreamDataProduced(
|
||||
static_cast<uint32_t>(finalPacket.size()), true);
|
||||
|
||||
R5900Context beforeDemuxCtx{};
|
||||
setRegU32(beforeDemuxCtx, 4, kMpegAddr);
|
||||
ps2_stubs::sceMpegIsEnd(rdram.data(), &beforeDemuxCtx, &runtime);
|
||||
t.Equals(getRegS32(beforeDemuxCtx, 2), 0,
|
||||
"physical CD EOF must not end MPEG before the final guest buffer is demuxed");
|
||||
|
||||
constexpr uint32_t kFirstPartSize = 5u;
|
||||
R5900Context firstDemuxCtx{};
|
||||
setRegU32(firstDemuxCtx, 4, kMpegAddr);
|
||||
setRegU32(firstDemuxCtx, 5, kPacketAddr);
|
||||
setRegU32(firstDemuxCtx, 6, kFirstPartSize);
|
||||
setRegU32(firstDemuxCtx, 7, kPacketAddr);
|
||||
setRegU32(firstDemuxCtx, 8, static_cast<uint32_t>(finalPacket.size()));
|
||||
ps2_stubs::sceMpegDemuxPssRing(rdram.data(), &firstDemuxCtx, &runtime);
|
||||
t.Equals(getRegS32(firstDemuxCtx, 2), static_cast<int32_t>(kFirstPartSize),
|
||||
"the partial final MPEG buffer should be consumed");
|
||||
|
||||
R5900Context midwayCtx{};
|
||||
setRegU32(midwayCtx, 4, kMpegAddr);
|
||||
ps2_stubs::sceMpegIsEnd(rdram.data(), &midwayCtx, &runtime);
|
||||
t.Equals(getRegS32(midwayCtx, 2), 0,
|
||||
"a partial final MPEG demux must keep physical CD EOF pending");
|
||||
|
||||
const uint32_t remainingSize = static_cast<uint32_t>(finalPacket.size()) - kFirstPartSize;
|
||||
R5900Context finalDemuxCtx{};
|
||||
setRegU32(finalDemuxCtx, 4, kMpegAddr);
|
||||
setRegU32(finalDemuxCtx, 5, kPacketAddr + kFirstPartSize);
|
||||
setRegU32(finalDemuxCtx, 6, remainingSize);
|
||||
setRegU32(finalDemuxCtx, 7, kPacketAddr);
|
||||
setRegU32(finalDemuxCtx, 8, static_cast<uint32_t>(finalPacket.size()));
|
||||
ps2_stubs::sceMpegDemuxPssRing(rdram.data(), &finalDemuxCtx, &runtime);
|
||||
t.Equals(getRegS32(finalDemuxCtx, 2), static_cast<int32_t>(remainingSize),
|
||||
"the final guest MPEG bytes should be consumed before EOF is committed");
|
||||
|
||||
R5900Context afterDemuxCtx{};
|
||||
setRegU32(afterDemuxCtx, 4, kMpegAddr);
|
||||
ps2_stubs::sceMpegIsEnd(rdram.data(), &afterDemuxCtx, &runtime);
|
||||
t.Equals(getRegS32(afterDemuxCtx, 2), 1,
|
||||
"MPEG should end after the pending final guest buffer is demuxed");
|
||||
});
|
||||
|
||||
tc.Run("sceMpegGetPicture waits for new decoder output instead of duplicating the last frame", [](TestCase &t)
|
||||
{
|
||||
PS2Runtime runtime;
|
||||
@@ -726,6 +842,76 @@ void register_ps2_runtime_expansion_tests()
|
||||
"only the injected decoder frame should be counted as served");
|
||||
});
|
||||
|
||||
tc.Run("sceMpegGetPicture resumes its HLE operation at the MPEG frame cadence", [](TestCase &t)
|
||||
{
|
||||
PS2Runtime runtime;
|
||||
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
|
||||
ps2_stubs::resetMpegStubState();
|
||||
ps2_stubs::notifyMpegCdStreamStart();
|
||||
|
||||
constexpr uint32_t kSequencePacketAddr = 0x00127000u;
|
||||
const std::vector<uint8_t> sequencePacket = {
|
||||
0x00u, 0x00u, 0x01u, 0xE0u,
|
||||
0x00u, 0x0Bu,
|
||||
0x80u, 0x00u, 0x00u,
|
||||
0x00u, 0x00u, 0x01u, 0xB3u,
|
||||
0x14u, 0x01u, 0x60u, 0x14u};
|
||||
std::memcpy(
|
||||
rdram.data() + kSequencePacketAddr,
|
||||
sequencePacket.data(),
|
||||
sequencePacket.size());
|
||||
|
||||
R5900Context demuxContext{};
|
||||
runtime.eeScheduler().reset(rdram.data(), demuxContext);
|
||||
setRegU32(demuxContext, 4, kMpegPacingHandle);
|
||||
setRegU32(demuxContext, 5, kSequencePacketAddr);
|
||||
setRegU32(demuxContext, 6, static_cast<uint32_t>(sequencePacket.size()));
|
||||
setRegU32(demuxContext, 7, kSequencePacketAddr);
|
||||
setRegU32(demuxContext, 8, static_cast<uint32_t>(sequencePacket.size()));
|
||||
ps2_stubs::sceMpegDemuxPssRing(rdram.data(), &demuxContext, &runtime);
|
||||
t.Equals(
|
||||
getRegS32(demuxContext, 2),
|
||||
static_cast<int32_t>(sequencePacket.size()),
|
||||
"the 29.97 fps MPEG sequence header should be consumed");
|
||||
|
||||
ps2_stubs::enqueueMpegDecodedFrameForTesting(kMpegPacingHandle);
|
||||
ps2_stubs::enqueueMpegDecodedFrameForTesting(kMpegPacingHandle);
|
||||
runtime.registerFunction(kMpegPacingMainPc, testMpegPacingMain);
|
||||
runtime.registerFunction(kMpegPacingAfterFirstPc, testMpegPacingAfterFirst);
|
||||
runtime.registerFunction(kMpegPacingAfterSecondPc, testMpegPacingAfterSecond);
|
||||
runtime.registerFunction(kMpegPacingFirstProducerPc, testMpegPacingFirstProducer);
|
||||
runtime.registerFunction(kMpegPacingSecondProducerPc, testMpegPacingSecondProducer);
|
||||
gMpegPacingStage.store(0u, std::memory_order_release);
|
||||
gMpegPacingFirstProducerStage.store(0u, std::memory_order_release);
|
||||
gMpegPacingSecondProducerStage.store(0u, std::memory_order_release);
|
||||
gMpegPacingResumeTick.store(0u, std::memory_order_release);
|
||||
|
||||
R5900Context mainContext{};
|
||||
mainContext.pc = kMpegPacingMainPc;
|
||||
EeScheduler &ee = runtime.eeScheduler();
|
||||
ee.reset(rdram.data(), mainContext);
|
||||
const int firstProducerId = ee.createThread(EeThreadCreateParams{
|
||||
0u, kMpegPacingFirstProducerPc, 0u, 0u, 0u, 10, 0u});
|
||||
const int secondProducerId = ee.createThread(EeThreadCreateParams{
|
||||
0u, kMpegPacingSecondProducerPc, 0u, 0u, 0u, 11, 0u});
|
||||
t.IsTrue(firstProducerId > 1 && secondProducerId > firstProducerId,
|
||||
"two VSync producer guest threads should be created");
|
||||
t.Equals(ee.startThread(firstProducerId, 0u, mainContext, false), 0,
|
||||
"the first VSync producer should become ready");
|
||||
t.Equals(ee.startThread(secondProducerId, 0u, mainContext, false), 0,
|
||||
"the second VSync producer should become ready");
|
||||
ee.run();
|
||||
|
||||
t.Equals(gMpegPacingFirstProducerStage.load(std::memory_order_acquire), 1u,
|
||||
"the second GetPicture should wait before the first VSync");
|
||||
t.Equals(gMpegPacingSecondProducerStage.load(std::memory_order_acquire), 1u,
|
||||
"29.97 fps must still be waiting after one 59.94 Hz VSync");
|
||||
t.Equals(gMpegPacingResumeTick.load(std::memory_order_acquire), 2ull,
|
||||
"the pending GetPicture should complete after two VSync ticks");
|
||||
t.Equals(gMpegPacingStage.load(std::memory_order_acquire), 3u,
|
||||
"VSync completion must re-enter GetPicture before its guest continuation");
|
||||
});
|
||||
|
||||
tc.Run("sceSdRemote isolates voice transfers from block streaming state", [](TestCase &t)
|
||||
{
|
||||
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
|
||||
|
||||
Reference in New Issue
Block a user