Feature/mpeg decoder (#120)

* feat: added ffmepg as dependency

* feat: wip decoder video

* feat: some perf and cleanup

* feat:  added generic MPEG stream notification

* feat: CMakeLists.txt in ps2xStudio to configure SDL2 build options for static linking.
fix: fix ffmpeg setup for linux
fix: now MPEG decoder now identify that movie has ended and can play again anytime
feat: better audio stub to not block games

* feat: fix expansion test

* feat: foo

* a

* feat: finally added a helper to to prevent thread starvation

* feat: added basic  vu0 code execution

* feat: added yield Guest Execution After Wake to prevent deadlock

* feat: added  options  on  cmake for logs
feat: better input for keyboard pad

* feat: small corrections like top and itop vu branches etc

* feat: changes

* feat: working feature

* feat: fatal frame iop

* feat: test fix
feat: z buffer fix

* fix: gix GsPutIMR IMR

* feat: added rl imgui

* feat: added helper to get snapshot

* feat: added debug panel consuming snapshots

* feat: added pad snapshot
feat: added RCP debug events

* feat: final cleanup from old code

* feat: added EE timer counter
feat: applyed sound driver for Lotr
feat: better check for sound driver compat layout
feat: enquee and cosumed DMa cause
feat: added Pad execCMd
feat: update GS vsync signal flag
feat: custom IOPs for LotR

* feat: small cleanups

* fix: fix wrong import
This commit is contained in:
Ranieri
2026-06-26 21:04:39 -03:00
committed by GitHub
parent a111059a70
commit 8c8a97af65
65 changed files with 10749 additions and 1766 deletions
@@ -1,6 +1,7 @@
#include "MiniTest.h"
#include "ps2_runtime.h"
#include "ps2_syscalls.h"
#include "Stubs/DMA.h"
#include <atomic>
#include <chrono>
@@ -47,6 +48,9 @@ namespace
std::atomic<uint32_t> g_vblankStartHits{0u};
std::atomic<uint32_t> g_vblankEndHits{0u};
std::atomic<uint32_t> g_lastIntcArg{0u};
std::atomic<uint32_t> g_dmacSendHits{0u};
std::atomic<uint32_t> g_dmacSendLastCause{0u};
std::atomic<uint32_t> g_dmacSendLastChcr{0u};
void setRegU32(R5900Context &ctx, int reg, uint32_t value)
{
@@ -68,6 +72,25 @@ namespace
std::memcpy(rdram + addr, &value, sizeof(value));
}
void writeGuestU64(uint8_t *rdram, uint32_t addr, uint64_t value)
{
std::memcpy(rdram + addr, &value, sizeof(value));
}
uint64_t makeDmaTag(uint16_t qwc, uint8_t id, uint32_t addr, bool irq = false)
{
return static_cast<uint64_t>(qwc) |
(static_cast<uint64_t>(id & 0x7u) << 28) |
(irq ? (1ull << 31) : 0ull) |
(static_cast<uint64_t>(addr & 0x7FFFFFFFu) << 32);
}
void writeDmaTag(uint8_t *rdram, uint32_t tagAddr, uint64_t tagLo)
{
std::memset(rdram + tagAddr, 0, 16);
std::memcpy(rdram + tagAddr, &tagLo, sizeof(tagLo));
}
uint32_t readGuestU32(const uint8_t *rdram, uint32_t addr)
{
uint32_t value = 0;
@@ -123,6 +146,36 @@ namespace
ctx->pc = 0u;
}
void testDmacSendHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
const uint32_t cause = getRegU32(ctx, 4);
g_dmacSendHits.fetch_add(1u, std::memory_order_relaxed);
g_dmacSendLastCause.store(cause, std::memory_order_relaxed);
uint32_t channelBase = 0u;
if (cause == 0u)
{
channelBase = 0x10008000u;
}
else if (cause == 1u)
{
channelBase = 0x10009000u;
}
else if (cause == 2u)
{
channelBase = 0x1000A000u;
}
if (runtime && channelBase != 0u)
{
g_dmacSendLastChcr.store(runtime->memory().readIORegister(channelBase + 0x00u), std::memory_order_relaxed);
}
ctx->pc = 0u;
}
}
void register_ps2_runtime_interrupt_tests()
@@ -167,6 +220,39 @@ void register_ps2_runtime_interrupt_tests()
cleanupRuntime(env);
});
tc.Run("VSync worker updates GS CSR FIELD bit for MMIO polling loops", [](TestCase &t)
{
notifyRuntimeStop();
TestEnv env;
t.IsTrue(env.runtime.memory().initialize(), "runtime memory initialize should succeed");
constexpr uint32_t kFlagAddr = 0x1080u;
constexpr uint32_t kTickAddr = 0x1090u;
constexpr uint64_t kGsCsrFieldMask = 0x2000ull;
env.runtime.memory().gs().csr = 0x3ull;
R5900Context ctx{};
setRegU32(ctx, 4, kFlagAddr);
setRegU32(ctx, 5, kTickAddr);
t.IsTrue(callSyscall(0x73u, env.rdram.data(), &ctx, &env.runtime), "SetVSyncFlag syscall should dispatch");
const uint64_t initialField = env.runtime.memory().gs().csr & kGsCsrFieldMask;
const bool firstFieldFlip = waitUntil([&]() {
return (env.runtime.memory().gs().csr & kGsCsrFieldMask) != initialField;
}, std::chrono::milliseconds(300));
t.IsTrue(firstFieldFlip, "VSync worker should toggle GS CSR FIELD for direct CSR polling");
t.Equals(env.runtime.memory().gs().csr & 0x3ull, 0x3ull, "VSync FIELD update should preserve CSR status bits");
const uint64_t fieldAfterFirstFlip = env.runtime.memory().gs().csr & kGsCsrFieldMask;
const bool secondFieldFlip = waitUntil([&]() {
return (env.runtime.memory().gs().csr & kGsCsrFieldMask) != fieldAfterFirstFlip;
}, std::chrono::milliseconds(300));
t.IsTrue(secondFieldFlip, "VSync worker should keep alternating GS CSR FIELD");
cleanupRuntime(env);
});
tc.Run("INTC VBLANK handlers respect EnableIntc and DisableIntc masks", [](TestCase &t)
{
notifyRuntimeStop();
@@ -251,6 +337,102 @@ void register_ps2_runtime_interrupt_tests()
cleanupRuntime(env);
});
tc.Run("sceDmaSend dispatches completed VIF1 DMAC handler with latched END tag", [](TestCase &t)
{
notifyRuntimeStop();
TestEnv env;
t.IsTrue(env.runtime.memory().initialize(), "runtime memory initialize should succeed");
constexpr uint32_t kHandlerAddr = 0x00ABD100u;
constexpr uint32_t kVif1Ch = 0x10009000u;
constexpr uint32_t kTag0 = 0x00028000u;
constexpr uint32_t kTag1 = kTag0 + 0x20u;
uint8_t *rdram = env.runtime.memory().getRDRAM();
writeDmaTag(rdram, kTag0, makeDmaTag(1u, 1u, 0u, false)); // CNT
writeGuestU64(rdram, kTag0 + 0x10u, 0u);
writeGuestU64(rdram, kTag0 + 0x18u, 0u);
writeDmaTag(rdram, kTag1, makeDmaTag(0u, 7u, 0u, false)); // END
g_dmacSendHits.store(0u, std::memory_order_relaxed);
g_dmacSendLastCause.store(0u, std::memory_order_relaxed);
g_dmacSendLastChcr.store(0u, std::memory_order_relaxed);
env.runtime.registerFunction(kHandlerAddr, &testDmacSendHandler);
R5900Context addCtx{};
setRegU32(addCtx, 4, 1u);
setRegU32(addCtx, 5, kHandlerAddr);
setRegU32(addCtx, 6, 0u);
setRegU32(addCtx, 7, 0u);
ps2_syscalls::AddDmacHandler(rdram, &addCtx, &env.runtime);
t.IsTrue(getRegS32(addCtx, 2) > 0, "AddDmacHandler should register VIF1 handler");
R5900Context enableCtx{};
setRegU32(enableCtx, 4, 1u);
ps2_syscalls::EnableDmac(rdram, &enableCtx, &env.runtime);
t.Equals(getRegS32(enableCtx, 2), KE_OK, "EnableDmac should enable VIF1 cause");
R5900Context sendCtx{};
setRegU32(sendCtx, 4, kVif1Ch);
setRegU32(sendCtx, 5, kTag0);
ps2_stubs::sceDmaSend(rdram, &sendCtx, &env.runtime);
t.Equals(getRegS32(sendCtx, 2), 0, "sceDmaSend should succeed");
t.Equals(g_dmacSendHits.load(std::memory_order_relaxed), 1u, "sceDmaSend should dispatch the VIF1 DMAC handler");
t.Equals(g_dmacSendLastCause.load(std::memory_order_relaxed), 1u, "DMAC handler should observe VIF1 cause");
t.Equals(g_dmacSendLastChcr.load(std::memory_order_relaxed) & 0x100u, 0u, "handler should see VIF1 STR cleared");
t.Equals(g_dmacSendLastChcr.load(std::memory_order_relaxed) & 0x70000000u, 0x70000000u, "handler should see the latched END tag id");
cleanupRuntime(env);
});
tc.Run("MMIO VIF1 chain completion dispatches DMAC handler after CHCR store", [](TestCase &t)
{
notifyRuntimeStop();
TestEnv env;
t.IsTrue(env.runtime.memory().initialize(), "runtime memory initialize should succeed");
constexpr uint32_t kHandlerAddr = 0x00ABD180u;
constexpr uint32_t kVif1Ch = 0x10009000u;
constexpr uint32_t kTag0 = 0x00028200u;
constexpr uint32_t kTag1 = kTag0 + 0x20u;
uint8_t *rdram = env.runtime.memory().getRDRAM();
writeDmaTag(rdram, kTag0, makeDmaTag(1u, 1u, 0u, false)); // CNT
writeGuestU64(rdram, kTag0 + 0x10u, 0u);
writeGuestU64(rdram, kTag0 + 0x18u, 0u);
writeDmaTag(rdram, kTag1, makeDmaTag(0u, 7u, 0u, false)); // END
g_dmacSendHits.store(0u, std::memory_order_relaxed);
g_dmacSendLastCause.store(0u, std::memory_order_relaxed);
g_dmacSendLastChcr.store(0u, std::memory_order_relaxed);
env.runtime.registerFunction(kHandlerAddr, &testDmacSendHandler);
R5900Context addCtx{};
setRegU32(addCtx, 4, 1u);
setRegU32(addCtx, 5, kHandlerAddr);
setRegU32(addCtx, 6, 0u);
setRegU32(addCtx, 7, 0u);
ps2_syscalls::AddDmacHandler(rdram, &addCtx, &env.runtime);
t.IsTrue(getRegS32(addCtx, 2) > 0, "AddDmacHandler should register VIF1 handler");
R5900Context enableCtx{};
setRegU32(enableCtx, 4, 1u);
ps2_syscalls::EnableDmac(rdram, &enableCtx, &env.runtime);
t.Equals(getRegS32(enableCtx, 2), KE_OK, "EnableDmac should enable VIF1 cause");
R5900Context storeCtx{};
env.runtime.Store32(rdram, &storeCtx, kVif1Ch + 0x30u, kTag0);
env.runtime.Store32(rdram, &storeCtx, kVif1Ch + 0x00u, 0x185u);
t.Equals(g_dmacSendHits.load(std::memory_order_relaxed), 1u, "CHCR store should dispatch the VIF1 DMAC handler");
t.Equals(g_dmacSendLastCause.load(std::memory_order_relaxed), 1u, "DMAC handler should observe VIF1 cause");
t.Equals(g_dmacSendLastChcr.load(std::memory_order_relaxed) & 0x100u, 0u, "handler should see VIF1 STR cleared");
t.Equals(g_dmacSendLastChcr.load(std::memory_order_relaxed) & 0x70000000u, 0x70000000u, "handler should see the latched END tag id");
cleanupRuntime(env);
});
tc.Run("negative interrupt-safe EE syscall ids dispatch", [](TestCase &t)
{
notifyRuntimeStop();