Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ps2xRecomp/include/ps2recomp/elf_parser.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef PS2RECOMP_ELF_PARSER_H
#define PS2RECOMP_ELF_PARSER_H

#include <cstdint>
#include <elfio/elfio.hpp>
#include <string>
#include <vector>
Expand Down
8 changes: 6 additions & 2 deletions ps2xRuntime/src/lib/Kernel/Stubs/GS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1053,7 +1053,9 @@ namespace ps2_stubs
}

const uint32_t fbw = std::max<uint32_t>(1u, (w + 63u) / 64u);
const uint64_t pmode = makePmode(1u, 1u, 0u, 0u, 0u, 0x80u);
// Seed read circuit 2 only (EN2): the swap path programs DISPFB2/DISPLAY2 and
// never circuit 1, so enabling EN1 would advertise a circuit it never drives.
const uint64_t pmode = makePmode(0u, 1u, 0u, 0u, 0u, 0x80u);
const uint64_t smode2 =
(static_cast<uint64_t>(g_gparam.interlace & 0x1u) << 0) |
(static_cast<uint64_t>(g_gparam.ffmode & 0x1u) << 1);
Expand Down Expand Up @@ -1124,7 +1126,9 @@ namespace ps2_stubs
}

const uint32_t fbw = std::max<uint32_t>(1u, (w + 63u) / 64u);
const uint64_t pmode = makePmode(1u, 1u, 0u, 0u, 0u, 0x80u);
// Seed read circuit 2 only (EN2): the swap path programs DISPFB2/DISPLAY2 and
// never circuit 1, so enabling EN1 would advertise a circuit it never drives.
const uint64_t pmode = makePmode(0u, 1u, 0u, 0u, 0u, 0x80u);
const uint64_t smode2 =
(static_cast<uint64_t>(g_gparam.interlace & 0x1u) << 0) |
(static_cast<uint64_t>(g_gparam.ffmode & 0x1u) << 1);
Expand Down
5 changes: 3 additions & 2 deletions ps2xRuntime/src/lib/Kernel/Stubs/Helpers/Support.h
Original file line number Diff line number Diff line change
Expand Up @@ -1883,8 +1883,9 @@ namespace
auto &regs = runtime->memory().gs();
regs.pmode = env.pmode;
regs.smode2 = env.smode2;
regs.dispfb1 = env.dispfb;
regs.display1 = env.display;
// Circuit 1 (DISPFB1/DISPLAY1) is deliberately not programmed: the env
// describes read circuit 2 only, and the game drives circuit 1 itself via
// GS MMIO. Writing it here would collapse any two-buffer blend.
regs.dispfb2 = env.dispfb;
regs.display2 = env.display;
regs.bgcolor = env.bgcolor;
Expand Down
147 changes: 145 additions & 2 deletions ps2xTest/src/ps2_gs_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -466,12 +466,155 @@ void register_ps2_gs_tests()
setRegU32(ctx, 5, 1u);
ps2_stubs::sceGsSwapDBuffDc(rdram.data(), &ctx, &runtime);

t.Equals(runtime.memory().gs().dispfb1 & 0x1FFull, 151ull,
t.Equals(runtime.memory().gs().dispfb2 & 0x1FFull, 151ull,
"sceGsSwapDBuffDc should program GS to the selected display page");
t.Equals((runtime.memory().gs().display1 >> 32) & 0x0FFFull, 639ull,
t.Equals((runtime.memory().gs().display2 >> 32) & 0x0FFFull, 639ull,
"sceGsSwapDBuffDc should preserve the display width from the seeded env");
});

tc.Run("sceGsSwapDBuffDc drives read circuit 2 only and preserves circuit 1", [](TestCase &t)
{
PS2Runtime runtime;
t.IsTrue(runtime.memory().initialize(), "runtime memory initialize should succeed");

// Seed distinct circuit-1 sentinels AFTER initialize(); reset
// defaults are fbw=10 / 639x447, so these differ from them.
runtime.memory().gs().dispfb1 = 0xDEADBEEFDEADBEEFull;
runtime.memory().gs().display1 = 0xCAFEF00DCAFEF00Dull;
runtime.memory().gs().pmode = 0u;

std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
constexpr uint32_t kEnvAddr = 0x7000u;
constexpr uint32_t kDBuffSize = 0x330u;

R5900Context ctx{};
setRegU32(ctx, 4, kEnvAddr);
setRegU32(ctx, 5, 0u); // psm
setRegU32(ctx, 6, 640u); // width
setRegU32(ctx, 7, 448u); // height
std::memset(rdram.data() + kEnvAddr, 0xCD, kDBuffSize);
ps2_stubs::sceGsSetDefDBuffDc(rdram.data(), &ctx, &runtime);

std::memset(&ctx, 0, sizeof(ctx));
setRegU32(ctx, 4, kEnvAddr);
setRegU32(ctx, 5, 1u); // swap to page 1
ps2_stubs::sceGsSwapDBuffDc(rdram.data(), &ctx, &runtime);

// Double-buffer path is circuit-2-only: the seeded PMODE must not enable
// EN1, or the compositor blends a never-programmed circuit-1 surface.
t.Equals(runtime.memory().gs().pmode & 0x1ull, 0x0ull,
"sceGsSwapDBuffDc must not enable read circuit 1 (EN1) in PMODE");
t.Equals(runtime.memory().gs().pmode & 0x2ull, 0x2ull,
"sceGsSwapDBuffDc should enable read circuit 2 (EN2) in PMODE");

// The shared circuit-2-only helper must leave circuit 1 untouched.
t.Equals(runtime.memory().gs().dispfb1, 0xDEADBEEFDEADBEEFull,
"sceGsSwapDBuffDc must not clobber circuit-1 DISPFB1");
t.Equals(runtime.memory().gs().display1, 0xCAFEF00DCAFEF00Dull,
"sceGsSwapDBuffDc must not clobber circuit-1 DISPLAY1");
});

tc.Run("sceGsSwapDBuff drives read circuit 2 only and preserves circuit 1", [](TestCase &t)
{
PS2Runtime runtime;
t.IsTrue(runtime.memory().initialize(), "runtime memory initialize should succeed");

// Seed distinct circuit-1 sentinels AFTER initialize(); reset
// defaults are fbw=10 / 639x447, so these differ from them.
runtime.memory().gs().dispfb1 = 0xDEADBEEFDEADBEEFull;
runtime.memory().gs().display1 = 0xCAFEF00DCAFEF00Dull;
runtime.memory().gs().pmode = 0u;

std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
constexpr uint32_t kEnvAddr = 0x8000u;
constexpr uint32_t kDBuffSize = 0x330u;
constexpr uint32_t kStackAddr = 0xB00u;

R5900Context ctx{};
setRegU32(ctx, 4, kEnvAddr);
setRegU32(ctx, 5, 0u); // psm
setRegU32(ctx, 6, 640u); // width
setRegU32(ctx, 7, 448u); // height
// sceGsSetDefDBuff (non-Dc) reads its trailing ztest/zpsm/clear
// args from the guest stack via sp+16/sp+20/sp+24, so sp (r29)
// must point at valid, zero-initialized rdram.
setRegU32(ctx, 29, kStackAddr);
std::memset(rdram.data() + kEnvAddr, 0xCD, kDBuffSize);
ps2_stubs::sceGsSetDefDBuff(rdram.data(), &ctx, &runtime);

std::memset(&ctx, 0, sizeof(ctx));
setRegU32(ctx, 4, kEnvAddr);
setRegU32(ctx, 5, 1u); // swap to page 1
ps2_stubs::sceGsSwapDBuff(rdram.data(), &ctx, &runtime);

// Double-buffer path is circuit-2-only: the seeded PMODE must not enable
// EN1, or the compositor blends a never-programmed circuit-1 surface.
t.Equals(runtime.memory().gs().pmode & 0x1ull, 0x0ull,
"sceGsSwapDBuff must not enable read circuit 1 (EN1) in PMODE");
t.Equals(runtime.memory().gs().pmode & 0x2ull, 0x2ull,
"sceGsSwapDBuff should enable read circuit 2 (EN2) in PMODE");

// The shared circuit-2-only helper must leave circuit 1 untouched.
t.Equals(runtime.memory().gs().dispfb1, 0xDEADBEEFDEADBEEFull,
"sceGsSwapDBuff must not clobber circuit-1 DISPFB1");
t.Equals(runtime.memory().gs().display1, 0xCAFEF00DCAFEF00Dull,
"sceGsSwapDBuff must not clobber circuit-1 DISPLAY1");
});

tc.Run("sceGsPutDispEnv programs read circuit 2 only and leaves circuit 1 untouched", [](TestCase &t)
{
PS2Runtime runtime;
t.IsTrue(runtime.memory().initialize(), "runtime memory initialize should succeed");

runtime.memory().gs().dispfb1 = 0xDEADBEEFDEADBEEFull;
runtime.memory().gs().display1 = 0xCAFEF00DCAFEF00Dull;
runtime.memory().gs().dispfb2 = 0u;
runtime.memory().gs().display2 = 0u;
runtime.memory().gs().pmode = 0u;
runtime.memory().gs().smode2 = 0u;
runtime.memory().gs().bgcolor = 0u;

std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
constexpr uint32_t kEnvAddr = 0x6000u;
constexpr uint32_t kPmodeOffset = 0u;
constexpr uint32_t kSmode2Offset = 8u;
constexpr uint32_t kDispFbOffset = 16u;
constexpr uint32_t kDisplayOffset = 24u;
constexpr uint32_t kBgColorOffset = 32u;

const uint64_t pmode = 0x8007ull;
const uint64_t smode2 = 0x1ull;
const uint64_t dispfb = 0x9070ull;
const uint64_t display = 0x1234000000005678ull;
const uint64_t bgcolor = 0x00445566ull;

std::memcpy(rdram.data() + kEnvAddr + kPmodeOffset, &pmode, sizeof(pmode));
std::memcpy(rdram.data() + kEnvAddr + kSmode2Offset, &smode2, sizeof(smode2));
std::memcpy(rdram.data() + kEnvAddr + kDispFbOffset, &dispfb, sizeof(dispfb));
std::memcpy(rdram.data() + kEnvAddr + kDisplayOffset, &display, sizeof(display));
std::memcpy(rdram.data() + kEnvAddr + kBgColorOffset, &bgcolor, sizeof(bgcolor));

R5900Context ctx{};
setRegU32(ctx, 4, kEnvAddr);
ps2_stubs::sceGsPutDispEnv(rdram.data(), &ctx, &runtime);

t.Equals(runtime.memory().gs().pmode, pmode,
"sceGsPutDispEnv should program PMODE from the env");
t.Equals(runtime.memory().gs().smode2, smode2,
"sceGsPutDispEnv should program SMODE2 from the env");
t.Equals(runtime.memory().gs().dispfb2, dispfb,
"sceGsPutDispEnv should program DISPFB2 from the env");
t.Equals(runtime.memory().gs().display2, display,
"sceGsPutDispEnv should program DISPLAY2 from the env");
t.Equals(runtime.memory().gs().bgcolor, bgcolor,
"sceGsPutDispEnv should program BGCOLOR from the env");

t.Equals(runtime.memory().gs().dispfb1, 0xDEADBEEFDEADBEEFull,
"sceGsPutDispEnv must not clobber circuit-1 DISPFB1");
t.Equals(runtime.memory().gs().display1, 0xCAFEF00DCAFEF00Dull,
"sceGsPutDispEnv must not clobber circuit-1 DISPLAY1");
});

tc.Run("sceGsSetDefDBuffDc seeds a clear packet and swap clears the draw buffer", [](TestCase &t)
{
PS2Runtime runtime;
Expand Down