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
5 changes: 5 additions & 0 deletions ps2xRuntime/include/runtime/ps2_memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ class PS2Memory
void flushMaskedPath3Packets(bool drainImmediately = true);

void submitGifPacket(GifPathId pathId, const uint8_t *data, uint32_t sizeBytes, bool drainImmediately = true, bool path2DirectHl = false);
void forwardVif1DirectData(const uint8_t *data, uint32_t sizeBytes, bool directHl);
void processGIFPacket(uint32_t srcPhysAddr, uint32_t qwCount);
void processGIFPacket(const uint8_t *data, uint32_t sizeBytes);
bool tryProcessNativeGifImageUploadChain(GS &gs, uint32_t tadr, uint32_t chcr);
Expand Down Expand Up @@ -409,6 +410,10 @@ class PS2Memory
bool m_path3Masked = false;
uint32_t m_vif1PendingPath2ImageQwc = 0u;
bool m_vif1PendingPath2DirectHl = false;
// Remaining data qwords of a VIF1 DIRECT/DIRECTHL cut off by the end of a processVIF1Data() buffer; the next
// buffer starts with that raw data (no VIFcodes).
uint32_t m_vif1PendingDirectQwc = 0u;
bool m_vif1PendingDirectHl = false;
std::vector<std::vector<uint8_t>> m_path3MaskedFifo;

struct PendingTransfer
Expand Down
4 changes: 4 additions & 0 deletions ps2xRuntime/src/lib/ps2_memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ bool PS2Memory::initialize(size_t ramSize)
m_path3MaskedFifo.clear();
m_vif1PendingPath2ImageQwc = 0u;
m_vif1PendingPath2DirectHl = false;
m_vif1PendingDirectQwc = 0u;
m_vif1PendingDirectHl = false;
resetEeTimers();

try
Expand Down Expand Up @@ -1231,6 +1233,8 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
std::memset(&vif1_regs, 0, sizeof(vif1_regs));
m_vif1PendingPath2ImageQwc = 0u;
m_vif1PendingPath2DirectHl = false;
m_vif1PendingDirectQwc = 0u;
m_vif1PendingDirectHl = false;
m_path3Masked = false;
if (wasPath3Masked)
flushMaskedPath3Packets();
Expand Down
80 changes: 55 additions & 25 deletions ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,47 @@ void PS2Memory::processVIF1Data(uint32_t srcPhys, uint32_t sizeBytes)
processVIF1Data(m_rdram + srcPhys, sizeBytes);
}

// Forwards the data of a VIF1 DIRECT/DIRECTHL to GIF PATH2. The GS frontends consume each forwarded packet on its own,
// starting with a GIFtag, while on hardware PATH2 is one continuous GIF stream: a DIRECT may end with an IMAGE GIFtag
// whose pixel data arrives in a later DIRECT (typically "MARK; DIRECT n" in the next DMAtag's TTE words). Such
// continuation data is re-wrapped here in a synthesized IMAGE tag. Only data that really is DIRECT payload is
// wrapped; the VIFcodes between the DIRECTs are parsed as VIFcodes (they used to be taken as the first 8 bytes of
// the image, shifting every uploaded texture by two words).
void PS2Memory::forwardVif1DirectData(const uint8_t *data, uint32_t sizeBytes, bool directHl)
{
while (sizeBytes >= 16u)
{
if (m_vif1PendingPath2ImageQwc != 0u)
{
const uint32_t chunkQw = std::min<uint32_t>(m_vif1PendingPath2ImageQwc, sizeBytes / 16u);
std::vector<uint8_t> imagePacket(16u + static_cast<size_t>(chunkQw) * 16u, 0u);
const uint64_t imageTag =
static_cast<uint64_t>(chunkQw & 0x7FFFu) |
((m_vif1PendingPath2ImageQwc == chunkQw) ? (1ull << 15) : 0ull) |
(static_cast<uint64_t>(kGifFmtImage) << 58);
std::memcpy(imagePacket.data(), &imageTag, sizeof(imageTag));
std::memcpy(imagePacket.data() + 16u, data, static_cast<size_t>(chunkQw) * 16u);
submitGifPacket(GifPathId::Path2, imagePacket.data(), static_cast<uint32_t>(imagePacket.size()), true,
m_vif1PendingPath2DirectHl);
m_vif1PendingPath2ImageQwc -= chunkQw;
if (m_vif1PendingPath2ImageQwc == 0u)
m_vif1PendingPath2DirectHl = false;
data += chunkQw * 16u;
sizeBytes -= chunkQw * 16u;
continue;
}

submitGifPacket(GifPathId::Path2, data, sizeBytes, true, directHl);
const uint32_t pendingImageQw = pendingGifImageQwc(data, sizeBytes);
if (pendingImageQw != 0u)
{
m_vif1PendingPath2ImageQwc = pendingImageQw;
m_vif1PendingPath2DirectHl = directHl;
}
break;
}
}

void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes)
{
if (sizeBytes == 0u)
Expand All @@ -304,30 +345,19 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes)

while (pos + 4 <= sizeBytes)
{
if (m_vif1PendingPath2ImageQwc != 0u)
if (m_vif1PendingDirectQwc != 0u)
{
// Continuation of a DIRECT that was cut off at the end of the previous buffer: raw GIF data, no VIFcodes.
const uint32_t availableQw = (sizeBytes - pos) / 16u;
if (availableQw == 0u)
{
break;
}

const uint32_t chunkQw = std::min<uint32_t>(m_vif1PendingPath2ImageQwc, availableQw);
std::vector<uint8_t> imagePacket(16u + static_cast<size_t>(chunkQw) * 16u, 0u);
const uint64_t imageTag =
static_cast<uint64_t>(chunkQw & 0x7FFFu) |
((m_vif1PendingPath2ImageQwc == chunkQw) ? (1ull << 15) : 0ull) |
(static_cast<uint64_t>(kGifFmtImage) << 58);
std::memcpy(imagePacket.data(), &imageTag, sizeof(imageTag));
std::memcpy(imagePacket.data() + 16u, data + pos, static_cast<size_t>(chunkQw) * 16u);
submitGifPacket(GifPathId::Path2, imagePacket.data(), static_cast<uint32_t>(imagePacket.size()), true, m_vif1PendingPath2DirectHl);

const uint32_t chunkQw = std::min<uint32_t>(m_vif1PendingDirectQwc, availableQw);
forwardVif1DirectData(data + pos, chunkQw * 16u, m_vif1PendingDirectHl);
pos += chunkQw * 16u;
m_vif1PendingPath2ImageQwc -= chunkQw;
if (m_vif1PendingPath2ImageQwc == 0u)
{
m_vif1PendingPath2DirectHl = false;
}
m_vif1PendingDirectQwc -= chunkQw;
continue;
}

Expand Down Expand Up @@ -490,22 +520,22 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes)
uint32_t qwCount = imm;
if (qwCount == 0)
qwCount = 65536;
const uint32_t requestedQw = qwCount;
const uint32_t availableQw = (sizeBytes - pos) / 16u;
const bool truncated = qwCount > availableQw;
if (qwCount > availableQw)
qwCount = availableQw;

const bool directHl = (opcode == VIF_DIRECTHL);
if (truncated)
{
// The rest of this DIRECT's data starts the next buffer (see m_vif1PendingDirectQwc).
m_vif1PendingDirectQwc = requestedQw - qwCount;
m_vif1PendingDirectHl = directHl;
}
if (qwCount > 0)
{
const bool directHl = (opcode == VIF_DIRECTHL);
submitGifPacket(GifPathId::Path2, data + pos, qwCount * 16, true, directHl);

const uint32_t pendingImageQw = pendingGifImageQwc(data + pos, qwCount * 16u);
if (pendingImageQw != 0u)
{
m_vif1PendingPath2ImageQwc = pendingImageQw;
m_vif1PendingPath2DirectHl = directHl;
}
forwardVif1DirectData(data + pos, qwCount * 16u, directHl);
}

pos += qwCount * 16;
Expand Down
69 changes: 66 additions & 3 deletions ps2xTest/src/ps2_memory_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2112,7 +2112,7 @@ void register_ps2_memory_tests()
t.IsTrue(imageOk, "VIF1 DIRECT image should update GS VRAM through GIF path2");
});

tc.Run("VIF1 DIRECT image tag can continue with raw image qwords", [](TestCase &t)
tc.Run("VIF1 DIRECT cut off at the end of a buffer continues with raw image qwords", [](TestCase &t)
{
PS2Memory mem;
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");
Expand All @@ -2137,10 +2137,72 @@ void register_ps2_memory_tests()
gs.writeRegister(GS_REG_TRXREG, (4ull << 0) | (1ull << 32));
gs.writeRegister(GS_REG_TRXDIR, 0ull);

// DIRECT 2 QW: the IMAGE tag and one pixel qword. The buffer ends after the tag, so the pixel qword
// is the start of the next buffer and carries no VIFcode.
std::vector<uint8_t> first;
appendU32(first, makeVifCmd(0x50u, 0u, 2u));
appendU64(first, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true));
appendU64(first, 0ull);

std::vector<uint8_t> second;
for (uint32_t i = 0; i < 16u; ++i)
{
second.push_back(static_cast<uint8_t>(0xA0u + i));
}

mem.processVIF1Data(first.data(), static_cast<uint32_t>(first.size()));
mem.processVIF1Data(second.data(), static_cast<uint32_t>(second.size()));

const uint8_t *vramOut = mem.getGSVRAM();
bool imageOk = true;
for (uint32_t x = 0; x < 4u && imageOk; ++x)
{
const uint32_t off = GSPSMCT32::addrPSMCT32(0u, 1u, x, 0u);
for (uint32_t c = 0; c < 4u; ++c)
{
if (vramOut[off + c] != static_cast<uint8_t>(0xA0u + x * 4u + c))
{
imageOk = false;
break;
}
}
}
t.IsTrue(imageOk, "raw qwords of a DIRECT cut off at a buffer end should continue the PATH2 image upload");
});

tc.Run("VIF1 DIRECT image tag continues in a later DIRECT after intervening VIFcodes", [](TestCase &t)
{
PS2Memory mem;
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");

GS gs;
gs.init(mem.getGSVRAM(), static_cast<uint32_t>(PS2_GS_VRAM_SIZE), &mem.gs());
GifArbiter arbiter([&](const uint8_t *data, uint32_t sizeBytes)
{
gs.processGIFPacket(data, sizeBytes);
});
mem.setGifArbiter(&arbiter);

const uint64_t bitblt =
(static_cast<uint64_t>(0u) << 0) |
(static_cast<uint64_t>(1u) << 16) |
(static_cast<uint64_t>(0u) << 24) |
(static_cast<uint64_t>(0u) << 32) |
(static_cast<uint64_t>(1u) << 48) |
(static_cast<uint64_t>(0u) << 56);
gs.writeRegister(GS_REG_BITBLTBUF, bitblt);
gs.writeRegister(GS_REG_TRXPOS, 0ull);
gs.writeRegister(GS_REG_TRXREG, (4ull << 0) | (1ull << 32));
gs.writeRegister(GS_REG_TRXDIR, 0ull);

// The first DIRECT ends with an IMAGE tag; its pixel data is the payload of a second DIRECT that follows
// "MARK; DIRECT 1". The VIFcodes in between must be parsed as VIFcodes, not taken as image data.
std::vector<uint8_t> packet;
appendU32(packet, makeVifCmd(0x50u, 0u, 1u)); // DIRECT 1 QW payload: GIF IMAGE tag only.
appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true));
appendU64(packet, 0ull);
appendU32(packet, makeVifCmd(0x07u, 0u, 0x1234u)); // MARK
appendU32(packet, makeVifCmd(0x50u, 0u, 1u)); // DIRECT 1 QW payload: the pixels.
for (uint32_t i = 0; i < 16u; ++i)
{
packet.push_back(static_cast<uint8_t>(0xA0u + i));
Expand All @@ -2162,7 +2224,7 @@ void register_ps2_memory_tests()
}
}
}
t.IsTrue(imageOk, "raw qwords after a DIRECT image tag should continue the PATH2 image upload");
t.IsTrue(imageOk, "image data in a later DIRECT should not be shifted by the VIFcodes before it");
});

tc.Run("VIF1 DIRECT finds an image continuation after packed setup", [](TestCase &t)
Expand Down Expand Up @@ -2194,6 +2256,7 @@ void register_ps2_memory_tests()
appendU64(packet, GS_REG_TEXA);
appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true));
appendU64(packet, 0ull);
appendU32(packet, makeVifCmd(0x50u, 0u, 1u)); // DIRECT 1 QW payload: the pixels.
for (uint32_t i = 0; i < 16u; ++i)
packet.push_back(static_cast<uint8_t>(0xC0u + i));

Expand All @@ -2213,7 +2276,7 @@ void register_ps2_memory_tests()
}
}
}
t.IsTrue(imageOk, "raw image continuation after packed setup should not be decoded as VIF/GIF registers");
t.IsTrue(imageOk, "image continuation after packed setup should not be decoded as VIF/GIF registers");
});

tc.Run("unaligned accesses throw", [](TestCase &t)
Expand Down