1 Commits

Author SHA1 Message Date
Ranieri cad1ca0bb5 Feature/added execution guess gs psmt (#104)
* fix: CRITICAL fix on code gen on generating BEQ translation, I added a small yeld because goto could spin forever and monopolize guest execution

* feat: add scratchpad alias base and improve scratchpad address handling

* feat: added debug logging on GifArbiter for submit and drain operations

* feat: add interrupt and thread management syscall implementations
fix: change some IDs calls to match ps2sdk

* feat: added vif1 logs

* feat: added logs on gs gpu
feat: added performLocalToLocalTransfer to GS emulation path for TRXDIR = 2. (emulates the PS2 GS “copy this rectangle from one place in VRAM to another”)

* feat: added PSMT8 and refactor PSMT4

* feat: some identation on vu1
feat: added some logs on vu1

* feat: added GuestExecutionScope to temporarily stop owning guest execution, then restore it exactly as it was.
feat: added vsync wizardry
feat: added some regression test

* fix: fix gs logger

* feat: remove extra logs I think they will help no one
feat: move join all threads to prevent the app to get stuck on close, but now it random crash on closing
feat: one more small test on psmt4 to try fix ghosting on re code veronica

* feat: added a small case for exporter from ghidra for metal slug 3

* feat: added ugly code to pass on test
2026-03-18 19:14:40 -03:00
32 changed files with 3942 additions and 685 deletions
+16 -6
View File
@@ -227,6 +227,19 @@ namespace ps2recomp
const uint32_t branchPc = branchInst.address; const uint32_t branchPc = branchInst.address;
const uint32_t delayPc = branchInst.address + 4u; const uint32_t delayPc = branchInst.address + 4u;
const uint32_t fallthroughPc = branchInst.address + 8u; const uint32_t fallthroughPc = branchInst.address + 8u;
auto emitInternalTarget = [&](uint32_t target, uint32_t sourcePc, std::string_view indent)
{
ss << fmt::format("{}ctx->pc = 0x{:X}u;\n", indent, target);
if (target <= sourcePc)
{
ss << fmt::format("{}runtime->cooperativeGuestYield();\n", indent);
ss << fmt::format("{}goto label_{:x};\n", indent, target);
}
else
{
ss << fmt::format("{}goto label_{:x};\n", indent, target);
}
};
std::vector<uint32_t> sortedInternalTargets; std::vector<uint32_t> sortedInternalTargets;
if (branchInst.opcode == OPCODE_SPECIAL && if (branchInst.opcode == OPCODE_SPECIAL &&
@@ -293,8 +306,7 @@ namespace ps2recomp
if (internalTargets.contains(target)) if (internalTargets.contains(target))
{ {
ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target); emitInternalTarget(target, branchPc, " ");
ss << fmt::format(" goto label_{:x};\n", target);
} }
else else
{ {
@@ -579,8 +591,7 @@ namespace ps2recomp
if (internalTargets.contains(target)) if (internalTargets.contains(target))
{ {
ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target); emitInternalTarget(target, branchPc, " ");
ss << fmt::format(" goto label_{:x};\n", target);
} }
else else
{ {
@@ -606,8 +617,7 @@ namespace ps2recomp
ss << " if (" << branchTakenVar << ") {\n"; ss << " if (" << branchTakenVar << ") {\n";
if (internalTargets.contains(target)) if (internalTargets.contains(target))
{ {
ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target); emitInternalTarget(target, branchPc, " ");
ss << fmt::format(" goto label_{:x};\n", target);
} }
else else
{ {
@@ -376,7 +376,7 @@ public class ExportPS2Functions extends GhidraScript {
MemoryBlock fromBlock = currentProgram.getMemory().getBlock(from); MemoryBlock fromBlock = currentProgram.getMemory().getBlock(from);
if (fromBlock == null || !fromBlock.isExecute()) { if (fromBlock == null || !fromBlock.isExecute()) {
return true; continue; // lets ignore DATA/non-code refs
} }
} }
+5
View File
@@ -216,6 +216,10 @@ public:
const uint8_t *lockDisplaySnapshot(uint32_t &outSize); const uint8_t *lockDisplaySnapshot(uint32_t &outSize);
void unlockDisplaySnapshot(); void unlockDisplaySnapshot();
uint32_t getLastDisplayBaseBytes() const; uint32_t getLastDisplayBaseBytes() const;
const GSFrameReg &getContextFrame(int index) const
{
return m_ctx[(index != 0) ? 1 : 0].frame;
}
uint32_t consumeLocalToHostBytes(uint8_t *dst, uint32_t maxBytes); uint32_t consumeLocalToHostBytes(uint8_t *dst, uint32_t maxBytes);
@@ -227,6 +231,7 @@ private:
void vertexKick(bool drawing); void vertexKick(bool drawing);
void processImageData(const uint8_t *data, uint32_t sizeBytes); void processImageData(const uint8_t *data, uint32_t sizeBytes);
void performLocalToLocalTransfer();
void performLocalToHostToBuffer(); void performLocalToHostToBuffer();
GSContext &activeContext(); GSContext &activeContext();
+39 -45
View File
@@ -6,53 +6,47 @@
namespace GSPSMT4 namespace GSPSMT4
{ {
static const uint8_t blockTable4[8][4] = { static const uint16_t columnTable4[16][32] = {
{ 0, 2, 8, 10 }, {0, 8, 16, 24, 32, 40, 48, 56, 2, 10, 18, 26, 34, 42, 50, 58, 4, 12, 20, 28, 36, 44, 52, 60, 6, 14, 22, 30, 38, 46, 54, 62},
{ 1, 3, 9, 11 }, {512, 520, 528, 536, 544, 552, 560, 568, 514, 522, 530, 538, 546, 554, 562, 570, 516, 524, 532, 540, 548, 556, 564, 572, 518, 526, 534, 542, 550, 558, 566, 574},
{ 4, 6, 12, 14 }, {33, 41, 49, 57, 1, 9, 17, 25, 35, 43, 51, 59, 3, 11, 19, 27, 37, 45, 53, 61, 5, 13, 21, 29, 39, 47, 55, 63, 7, 15, 23, 31},
{ 5, 7, 13, 15 }, {545, 553, 561, 569, 513, 521, 529, 537, 547, 555, 563, 571, 515, 523, 531, 539, 549, 557, 565, 573, 517, 525, 533, 541, 551, 559, 567, 575, 519, 527, 535, 543},
{ 16, 18, 24, 26 }, {1056, 1064, 1072, 1080, 1024, 1032, 1040, 1048, 1058, 1066, 1074, 1082, 1026, 1034, 1042, 1050, 1060, 1068, 1076, 1084, 1028, 1036, 1044, 1052, 1062, 1070, 1078, 1086, 1030, 1038, 1046, 1054},
{ 17, 19, 25, 27 }, {1568, 1576, 1584, 1592, 1536, 1544, 1552, 1560, 1570, 1578, 1586, 1594, 1538, 1546, 1554, 1562, 1572, 1580, 1588, 1596, 1540, 1548, 1556, 1564, 1574, 1582, 1590, 1598, 1542, 1550, 1558, 1566},
{ 20, 22, 28, 30 }, {1025, 1033, 1041, 1049, 1057, 1065, 1073, 1081, 1027, 1035, 1043, 1051, 1059, 1067, 1075, 1083, 1029, 1037, 1045, 1053, 1061, 1069, 1077, 1085, 1031, 1039, 1047, 1055, 1063, 1071, 1079, 1087},
{ 21, 23, 29, 31 }, {1537, 1545, 1553, 1561, 1569, 1577, 1585, 1593, 1539, 1547, 1555, 1563, 1571, 1579, 1587, 1595, 1541, 1549, 1557, 1565, 1573, 1581, 1589, 1597, 1543, 1551, 1559, 1567, 1575, 1583, 1591, 1599},
}; {2048, 2056, 2064, 2072, 2080, 2088, 2096, 2104, 2050, 2058, 2066, 2074, 2082, 2090, 2098, 2106, 2052, 2060, 2068, 2076, 2084, 2092, 2100, 2108, 2054, 2062, 2070, 2078, 2086, 2094, 2102, 2110},
{2560, 2568, 2576, 2584, 2592, 2600, 2608, 2616, 2562, 2570, 2578, 2586, 2594, 2602, 2610, 2618, 2564, 2572, 2580, 2588, 2596, 2604, 2612, 2620, 2566, 2574, 2582, 2590, 2598, 2606, 2614, 2622},
{2081, 2089, 2097, 2105, 2049, 2057, 2065, 2073, 2083, 2091, 2099, 2107, 2051, 2059, 2067, 2075, 2085, 2093, 2101, 2109, 2053, 2061, 2069, 2077, 2087, 2095, 2103, 2111, 2055, 2063, 2071, 2079},
{2593, 2601, 2609, 2617, 2561, 2569, 2577, 2585, 2595, 2603, 2611, 2619, 2563, 2571, 2579, 2587, 2597, 2605, 2613, 2621, 2565, 2573, 2581, 2589, 2599, 2607, 2615, 2623, 2567, 2575, 2583, 2591},
{3104, 3112, 3120, 3128, 3072, 3080, 3088, 3096, 3106, 3114, 3122, 3130, 3074, 3082, 3090, 3098, 3108, 3116, 3124, 3132, 3076, 3084, 3092, 3100, 3110, 3118, 3126, 3134, 3078, 3086, 3094, 3102},
{3616, 3624, 3632, 3640, 3584, 3592, 3600, 3608, 3618, 3626, 3634, 3642, 3586, 3594, 3602, 3610, 3620, 3628, 3636, 3644, 3588, 3596, 3604, 3612, 3622, 3630, 3638, 3646, 3590, 3598, 3606, 3614},
{3073, 3081, 3089, 3097, 3105, 3113, 3121, 3129, 3075, 3083, 3091, 3099, 3107, 3115, 3123, 3131, 3077, 3085, 3093, 3101, 3109, 3117, 3125, 3133, 3079, 3087, 3095, 3103, 3111, 3119, 3127, 3135},
{3585, 3593, 3601, 3609, 3617, 3625, 3633, 3641, 3587, 3595, 3603, 3611, 3619, 3627, 3635, 3643, 3589, 3597, 3605, 3613, 3621, 3629, 3637, 3645, 3591, 3599, 3607, 3615, 3623, 3631, 3639, 3647},
};
static const uint16_t columnTable4[16][32] = { inline uint32_t pageLocalNibbleOffset(uint32_t x, uint32_t y)
{ 0, 8, 32, 40, 64, 72, 96, 104, 2, 10, 34, 42, 66, 74, 98, 106, 4, 12, 36, 44, 68, 76, 100, 108, 6, 14, 38, 46, 70, 78, 102, 110 }, {
{ 16, 24, 48, 56, 80, 88, 112, 120, 18, 26, 50, 58, 82, 90, 114, 122, 20, 28, 52, 60, 84, 92, 116, 124, 22, 30, 54, 62, 86, 94, 118, 126 }, uint32_t yy = y & 0x7Fu;
{ 65, 73, 97, 105, 1, 9, 33, 41, 67, 75, 99, 107, 3, 11, 35, 43, 69, 77, 101, 109, 5, 13, 37, 45, 71, 79, 103, 111, 7, 15, 39, 47 }, uint32_t xx = x & 0x7Fu;
{ 81, 89, 113, 121, 17, 25, 49, 57, 83, 91, 115, 123, 19, 27, 51, 59, 85, 93, 117, 125, 21, 29, 53, 61, 87, 95, 119, 127, 23, 31, 55, 63 }, uint32_t blockBase = (((xx >> 5) & 3u) << 12) +
{ 192, 200, 224, 232, 128, 136, 160, 168, 194, 202, 226, 234, 130, 138, 162, 170, 196, 204, 228, 236, 132, 140, 164, 172, 198, 206, 230, 238, 134, 142, 166, 174 }, (((yy >> 4) & 7u) << 6);
{ 208, 216, 240, 248, 144, 152, 176, 184, 210, 218, 242, 250, 146, 154, 178, 186, 212, 220, 244, 252, 148, 156, 180, 188, 214, 222, 246, 254, 150, 158, 182, 190 }, return blockBase + columnTable4[yy & 15u][xx & 31u];
{ 129, 137, 161, 169, 193, 201, 225, 233, 131, 139, 163, 171, 195, 203, 227, 235, 133, 141, 165, 173, 197, 205, 229, 237, 135, 143, 167, 175, 199, 207, 231, 239 }, }
{ 145, 153, 177, 185, 209, 217, 241, 249, 147, 155, 179, 187, 211, 219, 243, 251, 149, 157, 181, 189, 213, 221, 245, 253, 151, 159, 183, 191, 215, 223, 247, 255 },
{ 256, 264, 288, 296, 320, 328, 352, 360, 258, 266, 290, 298, 322, 330, 354, 362, 260, 268, 292, 300, 324, 332, 356, 364, 262, 270, 294, 302, 326, 334, 358, 366 },
{ 272, 280, 304, 312, 336, 344, 368, 376, 274, 282, 306, 314, 338, 346, 370, 378, 276, 284, 308, 316, 340, 348, 372, 380, 278, 286, 310, 318, 342, 350, 374, 382 },
{ 321, 329, 353, 361, 257, 265, 289, 297, 323, 331, 355, 363, 259, 267, 291, 299, 325, 333, 357, 365, 261, 269, 293, 301, 327, 335, 359, 367, 263, 271, 295, 303 },
{ 337, 345, 369, 377, 273, 281, 305, 313, 339, 347, 371, 379, 275, 283, 307, 315, 341, 349, 373, 381, 277, 285, 309, 317, 343, 351, 375, 383, 279, 287, 311, 319 },
{ 448, 456, 480, 488, 384, 392, 416, 424, 450, 458, 482, 490, 386, 394, 418, 426, 452, 460, 484, 492, 388, 396, 420, 428, 454, 462, 486, 494, 390, 398, 422, 430 },
{ 464, 472, 496, 504, 400, 408, 432, 440, 466, 474, 498, 506, 402, 410, 434, 442, 468, 476, 500, 508, 404, 412, 436, 444, 470, 478, 502, 510, 406, 414, 438, 446 },
{ 385, 393, 417, 425, 449, 457, 481, 489, 387, 395, 419, 427, 451, 459, 483, 491, 389, 397, 421, 429, 453, 461, 485, 493, 391, 399, 423, 431, 455, 463, 487, 495 },
{ 401, 409, 433, 441, 465, 473, 497, 505, 403, 411, 435, 443, 467, 475, 499, 507, 405, 413, 437, 445, 469, 477, 501, 509, 407, 415, 439, 447, 471, 479, 503, 511 },
};
inline uint32_t blockIdPSMT4(uint32_t block, uint32_t width, uint32_t x, uint32_t y) inline uint32_t addrPSMT4(uint32_t block, uint32_t width, uint32_t x, uint32_t y)
{ {
return block + ((y >> 2) & ~0x1Fu) * (width >> 7) + ((x >> 2) & ~0x1Fu) const uint32_t pagesPerRow = ((width >> 1u) != 0u) ? (width >> 1u) : 1u;
+ blockTable4[(y >> 4) & 7][(x >> 5) & 3]; const uint32_t localNibble = pageLocalNibbleOffset(x, y);
} const uint32_t localByte = localNibble >> 1u;
const uint32_t localRow = localByte >> 8u;
inline uint32_t addrPSMT4(uint32_t block, uint32_t width, uint32_t x, uint32_t y) const uint32_t localColumnByte = localByte & 0xFFu;
{ const uint32_t globalByte =
uint32_t page = (block >> 5) + (y >> 7) * (width >> 1) + (x >> 7); block * 256u +
uint32_t blk = block & 0x1Fu; (((y >> 7u) * 32u + localRow) * (pagesPerRow * 256u)) +
uint32_t yy = y & 0x7Fu; ((x >> 7u) * 256u + localColumnByte);
uint32_t xx = x & 0x7Fu; return (globalByte << 1u) | (localNibble & 1u);
uint32_t blockId = blk + blockTable4[(yy >> 4) & 7][(xx >> 5) & 3]; }
uint32_t column = columnTable4[yy & 15u][xx & 31u];
uint32_t offset = (blockId << 9) + column;
return (page << 14) + offset;
}
} }
+256
View File
@@ -0,0 +1,256 @@
#ifndef PS2_GS_PSMT8_H
#define PS2_GS_PSMT8_H
#include <array>
#include <cstdint>
namespace GSPSMT8
{
static constexpr uint8_t blockTable8[4][8] = {
{0, 1, 4, 5, 16, 17, 20, 21},
{2, 3, 6, 7, 18, 19, 22, 23},
{8, 9, 12, 13, 24, 25, 28, 29},
{10, 11, 14, 15, 26, 27, 30, 31},
};
static constexpr uint8_t blockTable32[32] = {
0,
1,
4,
5,
16,
17,
20,
21,
2,
3,
6,
7,
18,
19,
22,
23,
8,
9,
12,
13,
24,
25,
28,
29,
10,
11,
14,
15,
26,
27,
30,
31,
};
inline const std::array<uint8_t, 32> &index32X()
{
static const std::array<uint8_t, 32> table = []
{
std::array<uint8_t, 32> result{};
for (uint8_t i = 0; i < 4; ++i)
{
for (uint8_t j = 0; j < 8; ++j)
{
const uint8_t index = blockTable32[i * 8u + j];
result[index] = j;
}
}
return result;
}();
return table;
}
inline const std::array<uint8_t, 32> &index32Y()
{
static const std::array<uint8_t, 32> table = []
{
std::array<uint8_t, 32> result{};
for (uint8_t i = 0; i < 4; ++i)
{
for (uint8_t j = 0; j < 8; ++j)
{
const uint8_t index = blockTable32[i * 8u + j];
result[index] = i;
}
}
return result;
}();
return table;
}
inline const std::array<uint8_t, 256> &columnTable8()
{
static const std::array<uint8_t, 256> table = []
{
std::array<uint8_t, 256> result{};
static constexpr uint8_t lut[128] = {
0,
36,
8,
44,
1,
37,
9,
45,
2,
38,
10,
46,
3,
39,
11,
47,
4,
32,
12,
40,
5,
33,
13,
41,
6,
34,
14,
42,
7,
35,
15,
43,
16,
52,
24,
60,
17,
53,
25,
61,
18,
54,
26,
62,
19,
55,
27,
63,
20,
48,
28,
56,
21,
49,
29,
57,
22,
50,
30,
58,
23,
51,
31,
59,
4,
32,
12,
40,
5,
33,
13,
41,
6,
34,
14,
42,
7,
35,
15,
43,
0,
36,
8,
44,
1,
37,
9,
45,
2,
38,
10,
46,
3,
39,
11,
47,
20,
48,
28,
56,
21,
49,
29,
57,
22,
50,
30,
58,
23,
51,
31,
59,
16,
52,
24,
60,
17,
53,
25,
61,
18,
54,
26,
62,
19,
55,
27,
63,
};
uint32_t outputIndex = 0u;
for (uint32_t k = 0; k < 4u; ++k)
{
uint32_t inputBase = (k % 2u) * 64u;
for (uint32_t i = 0; i < 16u; ++i)
{
for (uint32_t j = 0; j < 4u; ++j)
{
result[k * 64u + lut[inputBase++]] = static_cast<uint8_t>(outputIndex++);
}
}
}
return result;
}();
return table;
}
inline uint32_t addrPSMT8(uint32_t block, uint32_t width, uint32_t x, uint32_t y)
{
const uint32_t page = (block >> 5) + (y >> 6) * (width >> 1) + (x >> 7);
const uint32_t blockId = (block & 0x1Fu) + blockTable8[(y >> 4) & 3u][(x >> 4) & 7u];
const uint32_t pageOffset = (blockId >> 5) << 13;
const uint32_t localBlock = blockId & 0x1Fu;
const uint32_t blockBase = static_cast<uint32_t>(index32Y()[localBlock]) * 2048u +
static_cast<uint32_t>(index32X()[localBlock]) * 32u;
const uint32_t column = columnTable8()[(y & 0xFu) * 16u + (x & 0xFu)];
return (page << 13) + pageOffset + blockBase + column;
}
}
#endif
+3 -2
View File
@@ -10,10 +10,11 @@ class GSRasterizer
public: public:
void drawPrimitive(GS *gs); void drawPrimitive(GS *gs);
void writePixel(GS *gs, int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a); void writePixel(GS *gs, int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
uint32_t sampleTexture(GS *gs, float s, float t, uint16_t u, uint16_t v); uint32_t sampleTexture(GS *gs, float s, float t, float q, uint16_t u, uint16_t v);
uint32_t readTexelPSMCT32(GS *gs, uint32_t tbp0, uint32_t tbw, int texU, int texV); uint32_t readTexelPSMCT32(GS *gs, uint32_t tbp0, uint32_t tbw, int texU, int texV);
uint32_t readTexelPSMCT16(GS *gs, uint32_t tbp0, uint32_t tbw, int texU, int texV);
uint32_t readTexelPSMT4(GS *gs, uint32_t tbp0, uint32_t tbw, int texU, int texV); uint32_t readTexelPSMT4(GS *gs, uint32_t tbp0, uint32_t tbw, int texU, int texV);
uint32_t lookupCLUT(GS *gs, uint8_t index, uint32_t cbp, uint8_t cpsm, uint8_t csa); uint32_t lookupCLUT(GS *gs, uint8_t index, uint32_t cbp, uint8_t cpsm, uint8_t csm, uint8_t csa, uint8_t sourcePsm);
private: private:
void drawSprite(GS *gs); void drawSprite(GS *gs);
+34 -2
View File
@@ -23,6 +23,7 @@ constexpr uint32_t PS2_RAM_SIZE = 32u * 1024u * 1024u; // 32MB
constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1u; // Mask for 32MB alignment constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1u; // Mask for 32MB alignment
constexpr uint32_t PS2_RAM_BASE = 0x00000000; // Physical base of RDRAM constexpr uint32_t PS2_RAM_BASE = 0x00000000; // Physical base of RDRAM
constexpr uint32_t PS2_SCRATCHPAD_BASE = 0x70000000; constexpr uint32_t PS2_SCRATCHPAD_BASE = 0x70000000;
constexpr uint32_t PS2_SCRATCHPAD_ALIAS_BASE = 0xF0000000;
constexpr uint32_t PS2_SCRATCHPAD_SIZE = 16u * 1024u; // 16KB constexpr uint32_t PS2_SCRATCHPAD_SIZE = 16u * 1024u; // 16KB
constexpr uint32_t PS2_IO_BASE = 0x10000000; // Base for many I/O regs (Timers, DMAC, INTC) constexpr uint32_t PS2_IO_BASE = 0x10000000; // Base for many I/O regs (Timers, DMAC, INTC)
constexpr uint32_t PS2_IO_SIZE = 0x10000; // 64KB constexpr uint32_t PS2_IO_SIZE = 0x10000; // 64KB
@@ -81,12 +82,40 @@ inline uint8_t *ps2GetScratchpadHostPtr()
return ps2ScratchpadHostPtrStorage().load(std::memory_order_relaxed); return ps2ScratchpadHostPtrStorage().load(std::memory_order_relaxed);
} }
inline bool ps2ResolveGuestPointer(uint32_t addr, uint32_t &offset, bool &scratch) inline bool ps2IsScratchpadAddress(uint32_t addr)
{ {
if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)) if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE))
{
return true;
}
if ((addr & 0x80000000u) != 0u)
{
const uint32_t lower = addr & 0x7FFFFFFFu;
return lower >= PS2_SCRATCHPAD_BASE &&
lower < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE);
}
return false;
}
inline uint32_t ps2ScratchpadOffset(uint32_t addr)
{
if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE))
{
return addr - PS2_SCRATCHPAD_BASE;
}
const uint32_t lower = addr & 0x7FFFFFFFu;
return lower - PS2_SCRATCHPAD_BASE;
}
inline bool ps2ResolveGuestPointer(uint32_t addr, uint32_t &offset, bool &scratch)
{
if (ps2IsScratchpadAddress(addr))
{ {
scratch = true; scratch = true;
offset = addr - PS2_SCRATCHPAD_BASE; offset = ps2ScratchpadOffset(addr);
return true; return true;
} }
@@ -276,6 +305,8 @@ public:
using Vu1MscalCallback = std::function<void(uint32_t startPC, uint32_t itop)>; using Vu1MscalCallback = std::function<void(uint32_t startPC, uint32_t itop)>;
void setVu1MscalCallback(Vu1MscalCallback cb) { m_vu1MscalCallback = std::move(cb); } void setVu1MscalCallback(Vu1MscalCallback cb) { m_vu1MscalCallback = std::move(cb); }
using Vu1MscntCallback = std::function<void(uint32_t itop)>;
void setVu1MscntCallback(Vu1MscntCallback cb) { m_vu1MscntCallback = std::move(cb); }
uint8_t *getVU1Code() { return m_vu1Code; } uint8_t *getVU1Code() { return m_vu1Code; }
const uint8_t *getVU1Code() const { return m_vu1Code; } const uint8_t *getVU1Code() const { return m_vu1Code; }
@@ -344,6 +375,7 @@ public:
GifPacketCallback m_gifPacketCallback; GifPacketCallback m_gifPacketCallback;
GifArbiter *m_gifArbiter = nullptr; GifArbiter *m_gifArbiter = nullptr;
Vu1MscalCallback m_vu1MscalCallback; Vu1MscalCallback m_vu1MscalCallback;
Vu1MscntCallback m_vu1MscntCallback;
uint8_t *m_vu1Code = nullptr; uint8_t *m_vu1Code = nullptr;
uint8_t *m_vu1Data = nullptr; uint8_t *m_vu1Data = nullptr;
+45
View File
@@ -378,6 +378,33 @@ public:
using RecompiledFunction = void (*)(uint8_t *, R5900Context *, PS2Runtime *); using RecompiledFunction = void (*)(uint8_t *, R5900Context *, PS2Runtime *);
class GuestExecutionScope
{
public:
explicit GuestExecutionScope(PS2Runtime *runtime) noexcept;
~GuestExecutionScope();
GuestExecutionScope(const GuestExecutionScope &) = delete;
GuestExecutionScope &operator=(const GuestExecutionScope &) = delete;
private:
PS2Runtime *m_runtime = nullptr;
};
class GuestExecutionReleaseScope
{
public:
explicit GuestExecutionReleaseScope(PS2Runtime *runtime) noexcept;
~GuestExecutionReleaseScope();
GuestExecutionReleaseScope(const GuestExecutionReleaseScope &) = delete;
GuestExecutionReleaseScope &operator=(const GuestExecutionReleaseScope &) = delete;
private:
PS2Runtime *m_runtime = nullptr;
uint32_t m_depth = 0u;
};
void registerFunction(uint32_t address, RecompiledFunction func); void registerFunction(uint32_t address, RecompiledFunction func);
RecompiledFunction lookupFunction(uint32_t address); RecompiledFunction lookupFunction(uint32_t address);
bool hasFunction(uint32_t address) const; bool hasFunction(uint32_t address) const;
@@ -409,9 +436,15 @@ public:
void guestFree(uint32_t guestAddr); void guestFree(uint32_t guestAddr);
uint32_t guestHeapBase() const; uint32_t guestHeapBase() const;
uint32_t guestHeapEnd() const; uint32_t guestHeapEnd() const;
uint32_t reserveAsyncCallbackStack(uint32_t size, uint32_t alignment = 16u);
void dispatchLoop(uint8_t *rdram, R5900Context *ctx); void dispatchLoop(uint8_t *rdram, R5900Context *ctx);
void cooperativeGuestYield();
void requestStop(); void requestStop();
bool isStopRequested() const; bool isStopRequested() const;
uint32_t guestExecutionWaiterCountForTesting() const
{
return m_guestExecutionWaiters.load(std::memory_order_acquire);
}
uint8_t Load8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr); uint8_t Load8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
uint16_t Load16(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr); uint16_t Load16(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
@@ -496,9 +529,16 @@ private:
uint32_t allocateGuestBlockLocked(uint32_t size, uint32_t alignment); uint32_t allocateGuestBlockLocked(uint32_t size, uint32_t alignment);
void freeGuestBlockLocked(uint32_t guestAddr); void freeGuestBlockLocked(uint32_t guestAddr);
void coalesceGuestHeapLocked(); void coalesceGuestHeapLocked();
void enterGuestExecution();
void leaveGuestExecution();
uint32_t releaseGuestExecution();
void reacquireGuestExecution(uint32_t depth);
void HandleIntegerOverflow(R5900Context *ctx); void HandleIntegerOverflow(R5900Context *ctx);
friend class GuestExecutionScope;
friend class GuestExecutionReleaseScope;
private: private:
PS2Memory m_memory; PS2Memory m_memory;
GifArbiter m_gifArbiter; GifArbiter m_gifArbiter;
@@ -508,13 +548,18 @@ private:
PSPadBackend m_padBackend; PSPadBackend m_padBackend;
VU1Interpreter m_vu1; VU1Interpreter m_vu1;
R5900Context m_cpuContext; R5900Context m_cpuContext;
mutable std::recursive_mutex m_guestExecutionMutex;
mutable std::atomic<uint32_t> m_guestExecutionWaiters{0u};
mutable std::mutex m_guestHeapMutex; mutable std::mutex m_guestHeapMutex;
mutable std::mutex m_asyncCallbackStackMutex;
std::vector<GuestHeapBlock> m_guestHeapBlocks; std::vector<GuestHeapBlock> m_guestHeapBlocks;
uint32_t m_guestHeapBase = 0x00100000u; uint32_t m_guestHeapBase = 0x00100000u;
uint32_t m_guestHeapEnd = 0x00100000u; uint32_t m_guestHeapEnd = 0x00100000u;
uint32_t m_guestHeapLimit = PS2_RAM_SIZE; uint32_t m_guestHeapLimit = PS2_RAM_SIZE;
uint32_t m_guestHeapSuggestedBase = 0x00100000u; uint32_t m_guestHeapSuggestedBase = 0x00100000u;
bool m_guestHeapConfigured = false; bool m_guestHeapConfigured = false;
uint32_t m_asyncCallbackStackFloor = 0x01F00000u;
uint32_t m_asyncCallbackStackTop = PS2_RAM_SIZE;
std::unordered_map<uint32_t, RecompiledFunction> m_functionTable; std::unordered_map<uint32_t, RecompiledFunction> m_functionTable;
std::atomic<bool> m_stopRequested{false}; std::atomic<bool> m_stopRequested{false};
+1 -1
View File
@@ -15,7 +15,7 @@ namespace ps2_stubs
PS2_TEST_HOOK_LIST(PS2_DECLARE_TEST_HOOK) PS2_TEST_HOOK_LIST(PS2_DECLARE_TEST_HOOK)
#undef PS2_DECLARE_TEST_HOOK #undef PS2_DECLARE_TEST_HOOK
void resetGsSyncVCallbackState(); void resetGsSyncVCallbackState();
void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime); void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime, uint64_t tick);
void syMalloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); void syMalloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
void sndr_trans_func(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime); void sndr_trans_func(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
+5
View File
@@ -26,6 +26,11 @@ namespace ps2_syscalls
void initializeGuestKernelState(uint8_t *rdram); void initializeGuestKernelState(uint8_t *rdram);
void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId); void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId);
void notifyRuntimeStop(); void notifyRuntimeStop();
void joinAllGuestHostThreads();
void detachAllGuestHostThreads();
void EnsureVSyncWorkerRunning(uint8_t *rdram, PS2Runtime *runtime);
uint64_t GetCurrentVSyncTick();
uint64_t WaitForNextVSyncTick(uint8_t *rdram, PS2Runtime *runtime);
void WaitVSyncTick(uint8_t *rdram, PS2Runtime *runtime); void WaitVSyncTick(uint8_t *rdram, PS2Runtime *runtime);
} }
+77 -11
View File
@@ -1,6 +1,29 @@
#include "ps2_gif_arbiter.h" #include "ps2_gif_arbiter.h"
#include <algorithm> #include <algorithm>
#include <atomic>
#include <cstring> #include <cstring>
#include <iostream>
namespace
{
std::atomic<uint32_t> s_debugGifArbiterSubmitCount{0};
std::atomic<uint32_t> s_debugGifArbiterDrainCount{0};
const char *pathName(GifPathId id)
{
switch (id)
{
case GifPathId::Path1:
return "path1";
case GifPathId::Path2:
return "path2";
case GifPathId::Path3:
return "path3";
default:
return "path?";
}
}
}
GifArbiter::GifArbiter(ProcessPacketFn processFn) GifArbiter::GifArbiter(ProcessPacketFn processFn)
: m_processFn(std::move(processFn)) : m_processFn(std::move(processFn))
@@ -23,6 +46,26 @@ void GifArbiter::submit(GifPathId pathId, const uint8_t *data, uint32_t sizeByte
if (!data || sizeBytes < 16 || !m_processFn) if (!data || sizeBytes < 16 || !m_processFn)
return; return;
const uint32_t debugIndex = s_debugGifArbiterSubmitCount.fetch_add(1, std::memory_order_relaxed);
if (debugIndex < 96u)
{
uint64_t tagLo = 0;
std::memcpy(&tagLo, data, sizeof(tagLo));
const uint32_t nloop = static_cast<uint32_t>(tagLo & 0x7FFFu);
const uint8_t flg = static_cast<uint8_t>((tagLo >> 58) & 0x3u);
uint32_t nreg = static_cast<uint32_t>((tagLo >> 60) & 0xFu);
if (nreg == 0u)
nreg = 16u;
std::cout << "[gif:submit] idx=" << debugIndex
<< " path=" << pathName(pathId)
<< " size=" << sizeBytes
<< " nloop=" << nloop
<< " flg=" << static_cast<uint32_t>(flg)
<< " nreg=" << nreg
<< " directhl=" << static_cast<uint32_t>(path2DirectHl ? 1u : 0u)
<< std::endl;
}
GifArbiterPacket pkt; GifArbiterPacket pkt;
pkt.pathId = pathId; pkt.pathId = pathId;
pkt.path2DirectHl = (pathId == GifPathId::Path2) && path2DirectHl; pkt.path2DirectHl = (pathId == GifPathId::Path2) && path2DirectHl;
@@ -38,23 +81,46 @@ void GifArbiter::drain()
return; return;
std::stable_sort(m_queue.begin(), m_queue.end(), std::stable_sort(m_queue.begin(), m_queue.end(),
[](const GifArbiterPacket &a, const GifArbiterPacket &b) { [](const GifArbiterPacket &a, const GifArbiterPacket &b)
// DIRECTHL cannot preempt PATH3 IMAGE transfers. {
if (a.path2DirectHl != b.path2DirectHl || a.path3Image != b.path3Image) // DIRECTHL cannot preempt PATH3 IMAGE transfers.
{ if (a.path2DirectHl != b.path2DirectHl || a.path3Image != b.path3Image)
if (a.path3Image && b.path2DirectHl) {
return true; if (a.path3Image && b.path2DirectHl)
if (a.path2DirectHl && b.path3Image) return true;
return false; if (a.path2DirectHl && b.path3Image)
} return false;
return pathPriority(a.pathId) < pathPriority(b.pathId); }
}); return pathPriority(a.pathId) < pathPriority(b.pathId);
});
for (size_t i = 0; i < m_queue.size(); ++i) for (size_t i = 0; i < m_queue.size(); ++i)
{ {
auto &pkt = m_queue[i]; auto &pkt = m_queue[i];
if (!pkt.data.empty()) if (!pkt.data.empty())
{
const uint32_t debugIndex = s_debugGifArbiterDrainCount.fetch_add(1, std::memory_order_relaxed);
if (debugIndex < 96u)
{
uint64_t tagLo = 0;
std::memcpy(&tagLo, pkt.data.data(), sizeof(tagLo));
const uint32_t nloop = static_cast<uint32_t>(tagLo & 0x7FFFu);
const uint8_t flg = static_cast<uint8_t>((tagLo >> 58) & 0x3u);
uint32_t nreg = static_cast<uint32_t>((tagLo >> 60) & 0xFu);
if (nreg == 0u)
nreg = 16u;
std::cout << "[gif:drain] idx=" << debugIndex
<< " path=" << pathName(pkt.pathId)
<< " size=" << pkt.data.size()
<< " nloop=" << nloop
<< " flg=" << static_cast<uint32_t>(flg)
<< " nreg=" << nreg
<< " directhl=" << static_cast<uint32_t>(pkt.path2DirectHl ? 1u : 0u)
<< " path3image=" << static_cast<uint32_t>(pkt.path3Image ? 1u : 0u)
<< std::endl;
}
m_processFn(pkt.data.data(), static_cast<uint32_t>(pkt.data.size())); m_processFn(pkt.data.data(), static_cast<uint32_t>(pkt.data.size()));
}
} }
m_queue.clear(); m_queue.clear();
} }
+622 -120
View File
@@ -1,20 +1,189 @@
#include "ps2_gs_gpu.h" #include "ps2_gs_gpu.h"
#include "ps2_gs_common.h" #include "ps2_gs_common.h"
#include "ps2_gs_psmt4.h" #include "ps2_gs_psmt4.h"
#include "ps2_gs_psmt8.h"
#include "ps2_memory.h" #include "ps2_memory.h"
#include <atomic>
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <iostream>
#include <sstream>
namespace namespace
{ {
static inline uint64_t loadLE64(const uint8_t *p) static inline uint64_t loadLE64(const uint8_t *p)
{ {
uint64_t v; uint64_t v;
std::memcpy(&v, p, 8); std::memcpy(&v, p, 8);
return v; return v;
} }
std::atomic<uint32_t> s_debugGifPacketCount{0};
std::atomic<uint32_t> s_debugGsRegisterCount{0};
std::atomic<uint32_t> s_debugGsPackedVertexCount{0};
std::atomic<uint32_t> s_debugGsVertexKickCount{0};
std::atomic<uint32_t> s_debugCopyRegCount{0};
std::atomic<uint32_t> s_debugTexaWriteCount{0};
std::atomic<uint32_t> s_debugCvFontUploadCount{0};
std::atomic<uint32_t> s_debugLocalCopyCount{0};
bool supportsFormatAwareLocalCopy(uint8_t psm)
{
switch (psm)
{
case GS_PSM_CT32:
case GS_PSM_Z32:
case GS_PSM_CT24:
case GS_PSM_Z24:
case GS_PSM_CT16:
case GS_PSM_CT16S:
case GS_PSM_Z16:
case GS_PSM_Z16S:
case GS_PSM_T8:
case GS_PSM_T4:
return true;
default:
return false;
}
}
uint32_t readTransferPixel(const uint8_t *vram,
uint32_t vramSize,
uint32_t basePtr,
uint8_t widthBlocks,
uint8_t psm,
uint32_t x,
uint32_t y)
{
const uint32_t width = (widthBlocks != 0u) ? static_cast<uint32_t>(widthBlocks) : 1u;
const uint32_t base = basePtr * 256u;
switch (psm)
{
case GS_PSM_CT32:
case GS_PSM_Z32:
{
const uint32_t off = base + ((y * width * 64u) + x) * 4u;
if (off + 4u > vramSize)
return 0u;
uint32_t value = 0u;
std::memcpy(&value, vram + off, sizeof(value));
return value;
}
case GS_PSM_CT24:
case GS_PSM_Z24:
{
const uint32_t off = base + ((y * width * 64u) + x) * 4u;
if (off + 3u > vramSize)
return 0u;
return static_cast<uint32_t>(vram[off + 0u]) |
(static_cast<uint32_t>(vram[off + 1u]) << 8) |
(static_cast<uint32_t>(vram[off + 2u]) << 16);
}
case GS_PSM_CT16:
case GS_PSM_CT16S:
case GS_PSM_Z16:
case GS_PSM_Z16S:
{
const uint32_t off = base + ((y * width * 64u) + x) * 2u;
if (off + 2u > vramSize)
return 0u;
uint16_t value = 0u;
std::memcpy(&value, vram + off, sizeof(value));
return value;
}
case GS_PSM_T8:
{
const uint32_t off = GSPSMT8::addrPSMT8(basePtr, width, x, y);
return (off < vramSize) ? vram[off] : 0u;
}
case GS_PSM_T4:
{
const uint32_t nibbleAddr = GSPSMT4::addrPSMT4(basePtr, width, x, y);
const uint32_t byteOff = nibbleAddr >> 1;
if (byteOff >= vramSize)
return 0u;
const int shift = static_cast<int>((nibbleAddr & 1u) << 2);
return static_cast<uint32_t>((vram[byteOff] >> shift) & 0x0Fu);
}
default:
return 0u;
}
}
void writeTransferPixel(uint8_t *vram,
uint32_t vramSize,
uint32_t basePtr,
uint8_t widthBlocks,
uint8_t psm,
uint32_t x,
uint32_t y,
uint32_t value)
{
const uint32_t width = (widthBlocks != 0u) ? static_cast<uint32_t>(widthBlocks) : 1u;
const uint32_t base = basePtr * 256u;
switch (psm)
{
case GS_PSM_CT32:
case GS_PSM_Z32:
{
const uint32_t off = base + ((y * width * 64u) + x) * 4u;
if (off + 4u > vramSize)
return;
std::memcpy(vram + off, &value, sizeof(value));
return;
}
case GS_PSM_CT24:
case GS_PSM_Z24:
{
const uint32_t off = base + ((y * width * 64u) + x) * 4u;
if (off + 3u > vramSize)
return;
vram[off + 0u] = static_cast<uint8_t>(value & 0xFFu);
vram[off + 1u] = static_cast<uint8_t>((value >> 8) & 0xFFu);
vram[off + 2u] = static_cast<uint8_t>((value >> 16) & 0xFFu);
return;
}
case GS_PSM_CT16:
case GS_PSM_CT16S:
case GS_PSM_Z16:
case GS_PSM_Z16S:
{
const uint32_t off = base + ((y * width * 64u) + x) * 2u;
if (off + 2u > vramSize)
return;
const uint16_t value16 = static_cast<uint16_t>(value & 0xFFFFu);
std::memcpy(vram + off, &value16, sizeof(value16));
return;
}
case GS_PSM_T8:
{
const uint32_t off = GSPSMT8::addrPSMT8(basePtr, width, x, y);
if (off < vramSize)
vram[off] = static_cast<uint8_t>(value & 0xFFu);
return;
}
case GS_PSM_T4:
{
const uint32_t nibbleAddr = GSPSMT4::addrPSMT4(basePtr, width, x, y);
const uint32_t byteOff = nibbleAddr >> 1;
if (byteOff >= vramSize)
return;
const uint8_t nibble = static_cast<uint8_t>(value & 0x0Fu);
uint8_t &dst = vram[byteOff];
if ((nibbleAddr & 1u) != 0u)
dst = static_cast<uint8_t>((dst & 0x0Fu) | (nibble << 4));
else
dst = static_cast<uint8_t>((dst & 0xF0u) | nibble);
return;
}
default:
return;
}
}
} }
using namespace GSInternal; using namespace GSInternal;
@@ -36,10 +205,15 @@ void GS::reset()
{ {
std::memset(m_ctx, 0, sizeof(m_ctx)); std::memset(m_ctx, 0, sizeof(m_ctx));
m_prim = {}; m_prim = {};
m_curR = 0x80; m_curG = 0x80; m_curB = 0x80; m_curA = 0x80; m_curR = 0x80;
m_curG = 0x80;
m_curB = 0x80;
m_curA = 0x80;
m_curQ = 1.0f; m_curQ = 1.0f;
m_curS = 0.0f; m_curT = 0.0f; m_curS = 0.0f;
m_curU = 0; m_curV = 0; m_curT = 0.0f;
m_curU = 0;
m_curV = 0;
m_curFog = 0; m_curFog = 0;
m_prmodecont = true; m_prmodecont = true;
m_bitbltbuf = {}; m_bitbltbuf = {};
@@ -68,7 +242,8 @@ GSContext &GS::activeContext()
void GS::snapshotVRAM() void GS::snapshotVRAM()
{ {
if (!m_vram || m_vramSize == 0) return; if (!m_vram || m_vramSize == 0)
return;
std::lock_guard<std::mutex> lock(m_snapshotMutex); std::lock_guard<std::mutex> lock(m_snapshotMutex);
m_displaySnapshot.resize(m_vramSize); m_displaySnapshot.resize(m_vramSize);
std::memcpy(m_displaySnapshot.data(), m_vram, m_vramSize); std::memcpy(m_displaySnapshot.data(), m_vram, m_vramSize);
@@ -106,6 +281,25 @@ void GS::processGIFPacket(const uint8_t *data, uint32_t sizeBytes)
if (!data || sizeBytes < 16 || !m_vram) if (!data || sizeBytes < 16 || !m_vram)
return; return;
const uint32_t packetIndex = s_debugGifPacketCount.fetch_add(1, std::memory_order_relaxed);
if (packetIndex < 48u)
{
const uint64_t tagLo = loadLE64(data);
const uint32_t nloop = static_cast<uint32_t>(tagLo & 0x7FFFu);
const uint8_t flg = static_cast<uint8_t>((tagLo >> 58) & 0x3u);
uint32_t nreg = static_cast<uint32_t>((tagLo >> 60) & 0xFu);
if (nreg == 0u)
nreg = 16u;
std::cout << "[gs:gif] idx=" << packetIndex
<< " size=" << sizeBytes
<< " nloop=" << nloop
<< " flg=" << static_cast<uint32_t>(flg)
<< " nreg=" << nreg
<< " ctx0fbp=" << m_ctx[0].frame.fbp
<< " ctx1fbp=" << m_ctx[1].frame.fbp
<< std::endl;
}
if (sizeBytes >= 16) if (sizeBytes >= 16)
{ {
const uint64_t tagLo = loadLE64(data); const uint64_t tagLo = loadLE64(data);
@@ -129,7 +323,8 @@ void GS::processGIFPacket(const uint8_t *data, uint32_t sizeBytes)
uint32_t nloop = static_cast<uint32_t>(tagLo & 0x7FFF); uint32_t nloop = static_cast<uint32_t>(tagLo & 0x7FFF);
uint8_t flg = static_cast<uint8_t>((tagLo >> 58) & 0x3); uint8_t flg = static_cast<uint8_t>((tagLo >> 58) & 0x3);
uint32_t nreg = static_cast<uint32_t>((tagLo >> 60) & 0xF); uint32_t nreg = static_cast<uint32_t>((tagLo >> 60) & 0xF);
if (nreg == 0) nreg = 16; if (nreg == 0)
nreg = 16;
bool pre = ((tagLo >> 46) & 1) != 0; bool pre = ((tagLo >> 46) & 1) != 0;
if (pre) if (pre)
@@ -203,7 +398,8 @@ void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi)
std::memcpy(&m_curS, &sBits, 4); std::memcpy(&m_curS, &sBits, 4);
std::memcpy(&m_curT, &tBits, 4); std::memcpy(&m_curT, &tBits, 4);
std::memcpy(&m_curQ, &qBits, 4); std::memcpy(&m_curQ, &qBits, 4);
if (m_curQ == 0.0f) m_curQ = 1.0f; if (m_curQ == 0.0f)
m_curQ = 1.0f;
break; break;
} }
case 0x03: case 0x03:
@@ -217,13 +413,33 @@ void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi)
uint32_t z = static_cast<uint32_t>((hi >> 4) & 0xFFFFFF); uint32_t z = static_cast<uint32_t>((hi >> 4) & 0xFFFFFF);
uint8_t f = static_cast<uint8_t>((hi >> 36) & 0xFF); uint8_t f = static_cast<uint8_t>((hi >> 36) & 0xFF);
bool adk = ((hi >> 47) & 1) != 0; bool adk = ((hi >> 47) & 1) != 0;
const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed);
if (debugIndex < 64u)
{
std::cout << "[gs:packed-xyzf] idx=" << debugIndex
<< " x=" << x
<< " y=" << y
<< " z=0x" << std::hex << z
<< std::dec
<< " fog=" << static_cast<uint32_t>(f)
<< " kick=" << static_cast<uint32_t>(!adk ? 1u : 0u)
<< " prim=" << static_cast<uint32_t>(m_prim.type)
<< std::endl;
}
GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts];
vtx.x = static_cast<float>(x) / 16.0f; vtx.x = static_cast<float>(x) / 16.0f;
vtx.y = static_cast<float>(y) / 16.0f; vtx.y = static_cast<float>(y) / 16.0f;
vtx.z = static_cast<float>(z); vtx.z = static_cast<float>(z);
vtx.r = m_curR; vtx.g = m_curG; vtx.b = m_curB; vtx.a = m_curA; vtx.r = m_curR;
vtx.q = m_curQ; vtx.s = m_curS; vtx.t = m_curT; vtx.g = m_curG;
vtx.u = m_curU; vtx.v = m_curV; vtx.fog = f; vtx.b = m_curB;
vtx.a = m_curA;
vtx.q = m_curQ;
vtx.s = m_curS;
vtx.t = m_curT;
vtx.u = m_curU;
vtx.v = m_curV;
vtx.fog = f;
vertexKick(!adk); vertexKick(!adk);
break; break;
} }
@@ -233,13 +449,32 @@ void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi)
uint16_t y = static_cast<uint16_t>((lo >> 32) & 0xFFFF); uint16_t y = static_cast<uint16_t>((lo >> 32) & 0xFFFF);
uint32_t z = static_cast<uint32_t>(hi & 0xFFFFFFFF); uint32_t z = static_cast<uint32_t>(hi & 0xFFFFFFFF);
bool adk = ((hi >> 47) & 1) != 0; bool adk = ((hi >> 47) & 1) != 0;
const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed);
if (debugIndex < 64u)
{
std::cout << "[gs:packed-xyz] idx=" << debugIndex
<< " x=" << x
<< " y=" << y
<< " z=0x" << std::hex << z
<< std::dec
<< " kick=" << static_cast<uint32_t>(!adk ? 1u : 0u)
<< " prim=" << static_cast<uint32_t>(m_prim.type)
<< std::endl;
}
GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts];
vtx.x = static_cast<float>(x) / 16.0f; vtx.x = static_cast<float>(x) / 16.0f;
vtx.y = static_cast<float>(y) / 16.0f; vtx.y = static_cast<float>(y) / 16.0f;
vtx.z = static_cast<float>(z); vtx.z = static_cast<float>(z);
vtx.r = m_curR; vtx.g = m_curG; vtx.b = m_curB; vtx.a = m_curA; vtx.r = m_curR;
vtx.q = m_curQ; vtx.s = m_curS; vtx.t = m_curT; vtx.g = m_curG;
vtx.u = m_curU; vtx.v = m_curV; vtx.fog = m_curFog; vtx.b = m_curB;
vtx.a = m_curA;
vtx.q = m_curQ;
vtx.s = m_curS;
vtx.t = m_curT;
vtx.u = m_curU;
vtx.v = m_curV;
vtx.fog = m_curFog;
vertexKick(!adk); vertexKick(!adk);
break; break;
} }
@@ -248,26 +483,59 @@ void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi)
break; break;
case 0x0C: case 0x0C:
{ {
const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed);
if (debugIndex < 64u)
{
std::cout << "[gs:packed-xyzf3] idx=" << debugIndex
<< " x=" << static_cast<uint32_t>(lo & 0xFFFFu)
<< " y=" << static_cast<uint32_t>((lo >> 32) & 0xFFFFu)
<< " kick=0"
<< " prim=" << static_cast<uint32_t>(m_prim.type)
<< std::endl;
}
GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts];
vtx.x = static_cast<float>(lo & 0xFFFF) / 16.0f; vtx.x = static_cast<float>(lo & 0xFFFF) / 16.0f;
vtx.y = static_cast<float>((lo >> 32) & 0xFFFF) / 16.0f; vtx.y = static_cast<float>((lo >> 32) & 0xFFFF) / 16.0f;
vtx.z = static_cast<float>((hi >> 4) & 0xFFFFFF); vtx.z = static_cast<float>((hi >> 4) & 0xFFFFFF);
vtx.r = m_curR; vtx.g = m_curG; vtx.b = m_curB; vtx.a = m_curA; vtx.r = m_curR;
vtx.q = m_curQ; vtx.s = m_curS; vtx.t = m_curT; vtx.g = m_curG;
vtx.u = m_curU; vtx.v = m_curV; vtx.b = m_curB;
vtx.a = m_curA;
vtx.q = m_curQ;
vtx.s = m_curS;
vtx.t = m_curT;
vtx.u = m_curU;
vtx.v = m_curV;
vtx.fog = static_cast<uint8_t>((hi >> 36) & 0xFF); vtx.fog = static_cast<uint8_t>((hi >> 36) & 0xFF);
vertexKick(false); vertexKick(false);
break; break;
} }
case 0x0D: case 0x0D:
{ {
const uint32_t debugIndex = s_debugGsPackedVertexCount.fetch_add(1, std::memory_order_relaxed);
if (debugIndex < 64u)
{
std::cout << "[gs:packed-xyz3] idx=" << debugIndex
<< " x=" << static_cast<uint32_t>(lo & 0xFFFFu)
<< " y=" << static_cast<uint32_t>((lo >> 32) & 0xFFFFu)
<< " kick=0"
<< " prim=" << static_cast<uint32_t>(m_prim.type)
<< std::endl;
}
GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts];
vtx.x = static_cast<float>(lo & 0xFFFF) / 16.0f; vtx.x = static_cast<float>(lo & 0xFFFF) / 16.0f;
vtx.y = static_cast<float>((lo >> 32) & 0xFFFF) / 16.0f; vtx.y = static_cast<float>((lo >> 32) & 0xFFFF) / 16.0f;
vtx.z = static_cast<float>(hi & 0xFFFFFFFF); vtx.z = static_cast<float>(hi & 0xFFFFFFFF);
vtx.r = m_curR; vtx.g = m_curG; vtx.b = m_curB; vtx.a = m_curA; vtx.r = m_curR;
vtx.q = m_curQ; vtx.s = m_curS; vtx.t = m_curT; vtx.g = m_curG;
vtx.u = m_curU; vtx.v = m_curV; vtx.fog = m_curFog; vtx.b = m_curB;
vtx.a = m_curA;
vtx.q = m_curQ;
vtx.s = m_curS;
vtx.t = m_curT;
vtx.u = m_curU;
vtx.v = m_curV;
vtx.fog = m_curFog;
vertexKick(false); vertexKick(false);
break; break;
} }
@@ -287,6 +555,67 @@ void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi)
void GS::writeRegister(uint8_t regAddr, uint64_t value) void GS::writeRegister(uint8_t regAddr, uint64_t value)
{ {
const bool interestingReg =
regAddr == GS_REG_PRIM ||
regAddr == GS_REG_RGBAQ ||
regAddr == GS_REG_ST ||
regAddr == GS_REG_UV ||
regAddr == GS_REG_XYZ2 ||
regAddr == GS_REG_XYZ3 ||
regAddr == GS_REG_XYZF2 ||
regAddr == GS_REG_XYZF3 ||
regAddr == GS_REG_TEX0_1 ||
regAddr == GS_REG_TEX0_2 ||
regAddr == GS_REG_XYOFFSET_1 ||
regAddr == GS_REG_XYOFFSET_2 ||
regAddr == GS_REG_SCISSOR_1 ||
regAddr == GS_REG_SCISSOR_2 ||
regAddr == GS_REG_FRAME_1 ||
regAddr == GS_REG_FRAME_2 ||
regAddr == GS_REG_ALPHA_1 ||
regAddr == GS_REG_ALPHA_2 ||
regAddr == GS_REG_TEST_1 ||
regAddr == GS_REG_TEST_2 ||
regAddr == GS_REG_BITBLTBUF ||
regAddr == GS_REG_TRXPOS ||
regAddr == GS_REG_TRXREG ||
regAddr == GS_REG_TRXDIR;
if (interestingReg)
{
const uint32_t debugIndex = s_debugGsRegisterCount.fetch_add(1, std::memory_order_relaxed);
if (debugIndex < 128u)
{
std::cout << "[gs:reg] idx=" << debugIndex
<< " reg=0x" << std::hex << static_cast<uint32_t>(regAddr)
<< " value=0x" << value
<< std::dec
<< std::endl;
}
}
const bool isCopyRelevantReg =
regAddr == GS_REG_PRIM ||
regAddr == GS_REG_TEX0_2 ||
regAddr == GS_REG_TEX1_2 ||
regAddr == GS_REG_ALPHA_2 ||
regAddr == GS_REG_TEST_2 ||
regAddr == GS_REG_FRAME_2 ||
regAddr == GS_REG_XYOFFSET_2 ||
regAddr == GS_REG_SCISSOR_2;
if (isCopyRelevantReg &&
s_debugCopyRegCount.fetch_add(1u, std::memory_order_relaxed) < 64u)
{
std::cout << "[gs:copy-reg] reg=0x"
<< std::hex << static_cast<uint32_t>(regAddr)
<< " value=0x" << value
<< std::dec
<< " primCtxt=" << static_cast<uint32_t>(m_prim.ctxt)
<< " ctx0fbp=" << m_ctx[0].frame.fbp
<< " ctx1fbp=" << m_ctx[1].frame.fbp
<< std::endl;
}
switch (regAddr) switch (regAddr)
{ {
case GS_REG_PRIM: case GS_REG_PRIM:
@@ -312,7 +641,8 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value)
m_curA = static_cast<uint8_t>((value >> 24) & 0xFF); m_curA = static_cast<uint8_t>((value >> 24) & 0xFF);
uint32_t qBits = static_cast<uint32_t>((value >> 32) & 0xFFFFFFFF); uint32_t qBits = static_cast<uint32_t>((value >> 32) & 0xFFFFFFFF);
std::memcpy(&m_curQ, &qBits, 4); std::memcpy(&m_curQ, &qBits, 4);
if (m_curQ == 0.0f) m_curQ = 1.0f; if (m_curQ == 0.0f)
m_curQ = 1.0f;
break; break;
} }
case GS_REG_ST: case GS_REG_ST:
@@ -337,9 +667,15 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value)
vtx.y = static_cast<float>((value >> 16) & 0xFFFF) / 16.0f; vtx.y = static_cast<float>((value >> 16) & 0xFFFF) / 16.0f;
vtx.z = static_cast<float>((value >> 32) & 0xFFFFFF); vtx.z = static_cast<float>((value >> 32) & 0xFFFFFF);
vtx.fog = static_cast<uint8_t>((value >> 56) & 0xFF); vtx.fog = static_cast<uint8_t>((value >> 56) & 0xFF);
vtx.r = m_curR; vtx.g = m_curG; vtx.b = m_curB; vtx.a = m_curA; vtx.r = m_curR;
vtx.q = m_curQ; vtx.s = m_curS; vtx.t = m_curT; vtx.g = m_curG;
vtx.u = m_curU; vtx.v = m_curV; vtx.b = m_curB;
vtx.a = m_curA;
vtx.q = m_curQ;
vtx.s = m_curS;
vtx.t = m_curT;
vtx.u = m_curU;
vtx.v = m_curV;
vertexKick(regAddr == GS_REG_XYZF2); vertexKick(regAddr == GS_REG_XYZF2);
break; break;
} }
@@ -350,9 +686,16 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value)
vtx.x = static_cast<float>(value & 0xFFFF) / 16.0f; vtx.x = static_cast<float>(value & 0xFFFF) / 16.0f;
vtx.y = static_cast<float>((value >> 16) & 0xFFFF) / 16.0f; vtx.y = static_cast<float>((value >> 16) & 0xFFFF) / 16.0f;
vtx.z = static_cast<float>((value >> 32) & 0xFFFFFFFF); vtx.z = static_cast<float>((value >> 32) & 0xFFFFFFFF);
vtx.r = m_curR; vtx.g = m_curG; vtx.b = m_curB; vtx.a = m_curA; vtx.r = m_curR;
vtx.q = m_curQ; vtx.s = m_curS; vtx.t = m_curT; vtx.g = m_curG;
vtx.u = m_curU; vtx.v = m_curV; vtx.fog = m_curFog; vtx.b = m_curB;
vtx.a = m_curA;
vtx.q = m_curQ;
vtx.s = m_curS;
vtx.t = m_curT;
vtx.u = m_curU;
vtx.v = m_curV;
vtx.fog = m_curFog;
vertexKick(regAddr == GS_REG_XYZ2); vertexKick(regAddr == GS_REG_XYZ2);
break; break;
} }
@@ -500,58 +843,7 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value)
if (m_trxdir == 2 && m_vram) if (m_trxdir == 2 && m_vram)
{ {
uint32_t sbp = m_bitbltbuf.sbp; performLocalToLocalTransfer();
uint8_t sbw = m_bitbltbuf.sbw;
uint8_t spsm = m_bitbltbuf.spsm;
uint32_t dbp = m_bitbltbuf.dbp;
uint8_t dbw = m_bitbltbuf.dbw;
uint8_t dpsm = m_bitbltbuf.dpsm;
if (sbw == 0) sbw = 1;
if (dbw == 0) dbw = 1;
uint32_t srcBase = sbp * 256u;
uint32_t dstBase = dbp * 256u;
uint32_t srcBpp = bitsPerPixel(spsm) / 8u;
uint32_t dstBpp = bitsPerPixel(dpsm) / 8u;
if (srcBpp == 0) srcBpp = 4;
if (dstBpp == 0) dstBpp = 4;
uint32_t srcStride = static_cast<uint32_t>(sbw) * 64u * srcBpp;
uint32_t dstStride = static_cast<uint32_t>(dbw) * 64u * dstBpp;
uint32_t rrw = m_trxreg.rrw;
uint32_t rrh = m_trxreg.rrh;
uint32_t ssax = m_trxpos.ssax;
uint32_t ssay = m_trxpos.ssay;
uint32_t dsax = m_trxpos.dsax;
uint32_t dsay = m_trxpos.dsay;
uint32_t copyBpp = (srcBpp < dstBpp) ? srcBpp : dstBpp;
uint32_t rowBytes = rrw * copyBpp;
if (dstBase > srcBase)
{
for (int row = static_cast<int>(rrh) - 1; row >= 0; --row)
{
uint32_t srcOff = srcBase + (ssay + row) * srcStride + ssax * srcBpp;
uint32_t dstOff = dstBase + (dsay + row) * dstStride + dsax * dstBpp;
if (srcOff + rowBytes <= m_vramSize && dstOff + rowBytes <= m_vramSize)
std::memmove(m_vram + dstOff, m_vram + srcOff, rowBytes);
}
}
else
{
for (uint32_t row = 0; row < rrh; ++row)
{
uint32_t srcOff = srcBase + (ssay + row) * srcStride + ssax * srcBpp;
uint32_t dstOff = dstBase + (dsay + row) * dstStride + dsax * dstBpp;
if (srcOff + rowBytes <= m_vramSize && dstOff + rowBytes <= m_vramSize)
std::memmove(m_vram + dstOff, m_vram + srcOff, rowBytes);
}
}
if (sbp == 0u && (dbp == 0u || dbp == 0x20u) && rrw >= 640u && rrh >= 512u) {
m_lastDisplayBaseBytes = (dbp == 0x20u) ? 8192u : 0u;
snapshotVRAM();
}
} }
else if (m_trxdir == 1 && m_vram) else if (m_trxdir == 1 && m_vram)
{ {
@@ -578,15 +870,29 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value)
case GS_REG_MIPTBP1_2: case GS_REG_MIPTBP1_2:
case GS_REG_MIPTBP2_1: case GS_REG_MIPTBP2_1:
case GS_REG_MIPTBP2_2: case GS_REG_MIPTBP2_2:
case GS_REG_TEXA:
break; break;
case GS_REG_TEXA:
{
const uint32_t texaIndex = s_debugTexaWriteCount.fetch_add(1u, std::memory_order_relaxed);
if (texaIndex < 24u)
{
std::cout << "[gs:texa] idx=" << texaIndex
<< " value=0x" << std::hex << value
<< " ta0=0x" << ((value >> 0) & 0xFFu)
<< " aem=" << ((value >> 15) & 0x1u)
<< " ta1=0x" << ((value >> 32) & 0xFFu)
<< std::dec
<< std::endl;
}
break;
}
case GS_REG_SIGNAL: case GS_REG_SIGNAL:
{ {
if (m_privRegs) if (m_privRegs)
{ {
uint32_t id = static_cast<uint32_t>(value & 0xFFFFFFFF); uint32_t id = static_cast<uint32_t>(value & 0xFFFFFFFF);
uint32_t mask = static_cast<uint32_t>(value >> 32); uint32_t mask = static_cast<uint32_t>(value >> 32);
uint32_t lo = static_cast<uint32_t>(m_privRegs->siglblid & 0xFFFFFFFF); uint32_t lo = static_cast<uint32_t>(m_privRegs->siglblid & 0xFFFFFFFF);
lo = (lo & ~mask) | (id & mask); lo = (lo & ~mask) | (id & mask);
m_privRegs->siglblid = (m_privRegs->siglblid & 0xFFFFFFFF00000000ULL) | lo; m_privRegs->siglblid = (m_privRegs->siglblid & 0xFFFFFFFF00000000ULL) | lo;
m_privRegs->csr |= 0x1; m_privRegs->csr |= 0x1;
@@ -603,9 +909,9 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value)
{ {
if (m_privRegs) if (m_privRegs)
{ {
uint32_t id = static_cast<uint32_t>(value & 0xFFFFFFFF); uint32_t id = static_cast<uint32_t>(value & 0xFFFFFFFF);
uint32_t mask = static_cast<uint32_t>(value >> 32); uint32_t mask = static_cast<uint32_t>(value >> 32);
uint32_t hi = static_cast<uint32_t>(m_privRegs->siglblid >> 32); uint32_t hi = static_cast<uint32_t>(m_privRegs->siglblid >> 32);
hi = (hi & ~mask) | (id & mask); hi = (hi & ~mask) | (id & mask);
m_privRegs->siglblid = (static_cast<uint64_t>(hi) << 32) | (m_privRegs->siglblid & 0xFFFFFFFF); m_privRegs->siglblid = (static_cast<uint64_t>(hi) << 32) | (m_privRegs->siglblid & 0xFFFFFFFF);
} }
@@ -628,25 +934,156 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value)
} }
} }
void GS::performLocalToLocalTransfer()
{
if (!m_vram)
return;
uint32_t sbp = m_bitbltbuf.sbp;
uint8_t sbw = m_bitbltbuf.sbw;
uint8_t spsm = m_bitbltbuf.spsm;
uint32_t dbp = m_bitbltbuf.dbp;
uint8_t dbw = m_bitbltbuf.dbw;
uint8_t dpsm = m_bitbltbuf.dpsm;
if (sbw == 0)
sbw = 1;
if (dbw == 0)
dbw = 1;
const uint32_t rrw = m_trxreg.rrw;
const uint32_t rrh = m_trxreg.rrh;
const uint32_t ssax = m_trxpos.ssax;
const uint32_t ssay = m_trxpos.ssay;
const uint32_t dsax = m_trxpos.dsax;
const uint32_t dsay = m_trxpos.dsay;
const bool formatAware = (spsm == dpsm) && supportsFormatAwareLocalCopy(spsm);
if ((spsm == GS_PSM_T4 || dpsm == GS_PSM_T4) &&
s_debugLocalCopyCount.fetch_add(1u, std::memory_order_relaxed) < 96u)
{
std::cout << "[gs:l2l] sbp=" << sbp
<< " dbp=" << dbp
<< " sbw=" << static_cast<uint32_t>(sbw)
<< " dbw=" << static_cast<uint32_t>(dbw)
<< " spsm=0x" << std::hex << static_cast<uint32_t>(spsm)
<< " dpsm=0x" << static_cast<uint32_t>(dpsm) << std::dec
<< " ss=(" << ssax << "," << ssay << ")"
<< " ds=(" << dsax << "," << dsay << ")"
<< " rr=(" << rrw << "," << rrh << ")"
<< " formatAware=" << (formatAware ? 1 : 0) << std::endl;
}
if (formatAware)
{
std::vector<uint32_t> staged;
staged.reserve(static_cast<size_t>(rrw) * static_cast<size_t>(rrh));
for (uint32_t row = 0; row < rrh; ++row)
{
for (uint32_t col = 0; col < rrw; ++col)
{
staged.push_back(readTransferPixel(m_vram, m_vramSize, sbp, sbw, spsm, ssax + col, ssay + row));
}
}
size_t idx = 0;
for (uint32_t row = 0; row < rrh; ++row)
{
for (uint32_t col = 0; col < rrw; ++col, ++idx)
{
writeTransferPixel(m_vram, m_vramSize, dbp, dbw, dpsm, dsax + col, dsay + row, staged[idx]);
}
}
}
else
{
const uint32_t srcBase = sbp * 256u;
const uint32_t dstBase = dbp * 256u;
uint32_t srcBpp = bitsPerPixel(spsm) / 8u;
uint32_t dstBpp = bitsPerPixel(dpsm) / 8u;
if (srcBpp == 0)
srcBpp = 4;
if (dstBpp == 0)
dstBpp = 4;
const uint32_t srcStride = static_cast<uint32_t>(sbw) * 64u * srcBpp;
const uint32_t dstStride = static_cast<uint32_t>(dbw) * 64u * dstBpp;
const uint32_t copyBpp = (srcBpp < dstBpp) ? srcBpp : dstBpp;
const uint32_t rowBytes = rrw * copyBpp;
if (dstBase > srcBase)
{
for (int row = static_cast<int>(rrh) - 1; row >= 0; --row)
{
const uint32_t srcOff = srcBase + (ssay + static_cast<uint32_t>(row)) * srcStride + ssax * srcBpp;
const uint32_t dstOff = dstBase + (dsay + static_cast<uint32_t>(row)) * dstStride + dsax * dstBpp;
if (srcOff + rowBytes <= m_vramSize && dstOff + rowBytes <= m_vramSize)
std::memmove(m_vram + dstOff, m_vram + srcOff, rowBytes);
}
}
else
{
for (uint32_t row = 0; row < rrh; ++row)
{
const uint32_t srcOff = srcBase + (ssay + row) * srcStride + ssax * srcBpp;
const uint32_t dstOff = dstBase + (dsay + row) * dstStride + dsax * dstBpp;
if (srcOff + rowBytes <= m_vramSize && dstOff + rowBytes <= m_vramSize)
std::memmove(m_vram + dstOff, m_vram + srcOff, rowBytes);
}
}
}
if (sbp == 0u && (dbp == 0u || dbp == 0x20u) && rrw >= 640u && rrh >= 512u)
{
m_lastDisplayBaseBytes = (dbp == 0x20u) ? 8192u : 0u;
snapshotVRAM();
}
}
void GS::vertexKick(bool drawing) void GS::vertexKick(bool drawing)
{ {
++m_vtxCount; ++m_vtxCount;
++m_vtxIndex; ++m_vtxIndex;
const uint32_t debugIndex = s_debugGsVertexKickCount.fetch_add(1, std::memory_order_relaxed);
if (debugIndex < 96u)
{
std::cout << "[gs:kick] idx=" << debugIndex
<< " drawing=" << static_cast<uint32_t>(drawing ? 1u : 0u)
<< " prim=" << static_cast<uint32_t>(m_prim.type)
<< " vtxCount=" << m_vtxCount
<< std::endl;
}
if (!drawing) if (!drawing)
return; return;
int needed = 0; int needed = 0;
switch (m_prim.type) switch (m_prim.type)
{ {
case GS_PRIM_POINT: needed = 1; break; case GS_PRIM_POINT:
case GS_PRIM_LINE: needed = 2; break; needed = 1;
case GS_PRIM_LINESTRIP: needed = 2; break; break;
case GS_PRIM_TRIANGLE: needed = 3; break; case GS_PRIM_LINE:
case GS_PRIM_TRISTRIP: needed = 3; break; needed = 2;
case GS_PRIM_TRIFAN: needed = 3; break; break;
case GS_PRIM_SPRITE: needed = 2; break; case GS_PRIM_LINESTRIP:
default: return; needed = 2;
break;
case GS_PRIM_TRIANGLE:
needed = 3;
break;
case GS_PRIM_TRISTRIP:
needed = 3;
break;
case GS_PRIM_TRIFAN:
needed = 3;
break;
case GS_PRIM_SPRITE:
needed = 2;
break;
default:
return;
} }
if (m_vtxCount < needed) if (m_vtxCount < needed)
@@ -690,7 +1127,8 @@ void GS::processImageData(const uint8_t *data, uint32_t sizeBytes)
uint8_t dbw = m_bitbltbuf.dbw; uint8_t dbw = m_bitbltbuf.dbw;
uint8_t dpsm = m_bitbltbuf.dpsm; uint8_t dpsm = m_bitbltbuf.dpsm;
if (dbw == 0) dbw = 1; if (dbw == 0)
dbw = 1;
uint32_t base = dbp * 256u; uint32_t base = dbp * 256u;
uint32_t bpp = bitsPerPixel(dpsm); uint32_t bpp = bitsPerPixel(dpsm);
uint32_t stridePixels = static_cast<uint32_t>(dbw) * 64u; uint32_t stridePixels = static_cast<uint32_t>(dbw) * 64u;
@@ -702,32 +1140,80 @@ void GS::processImageData(const uint8_t *data, uint32_t sizeBytes)
if (bpp == 4) if (bpp == 4)
{ {
uint32_t rowBytes = (rrw + 1u) / 2u;
if (rowBytes == 0) rowBytes = 1;
uint32_t widthBlocks = (dbw != 0) ? static_cast<uint32_t>(dbw) : 1u; uint32_t widthBlocks = (dbw != 0) ? static_cast<uint32_t>(dbw) : 1u;
for (uint32_t y = 0; y < rrh && (y * rowBytes) < sizeBytes; ++y) uint32_t offset = 0;
// T4 image uploads can be split across multiple GIF IMAGE packets.
// Keep advancing from the previous HWREG position instead of restarting at (0, 0).
auto writeT4Nibble = [&](uint8_t nibble, uint8_t srcByte, uint32_t srcLo, uint32_t srcHi)
{ {
uint32_t srcRowOff = y * rowBytes; if (m_hwregY >= rrh)
for (uint32_t x = 0; x < rrw && (srcRowOff + (x / 2u)) < sizeBytes; ++x) return;
const uint32_t vx = dsax + m_hwregX;
const uint32_t vy = dsay + m_hwregY;
const uint32_t nibbleAddr = GSPSMT4::addrPSMT4(dbp, widthBlocks, vx, vy);
const uint32_t byteOff = nibbleAddr >> 1;
if (byteOff < m_vramSize)
{ {
uint32_t srcByte = data[srcRowOff + (x / 2u)]; const int shift = static_cast<int>((nibbleAddr & 1u) << 2);
uint32_t nibble = (x & 1u) ? ((srcByte >> 4) & 0xFu) : (srcByte & 0xFu); uint8_t &b = m_vram[byteOff];
b = static_cast<uint8_t>((b & (0xF0u >> shift)) | ((nibble & 0x0Fu) << shift));
}
++m_hwregX;
if (m_hwregX >= rrw)
{
m_hwregX = 0;
++m_hwregY;
}
};
uint32_t vx = dsax + x; while (offset < sizeBytes && m_hwregY < rrh)
uint32_t vy = dsay + y; {
uint32_t nibbleAddr = GSPSMT4::addrPSMT4(dbp, widthBlocks, vx, vy); const uint8_t srcByte = data[offset++];
uint32_t byteOff = nibbleAddr >> 1; const uint32_t srcLo = srcByte & 0x0Fu;
const uint32_t srcHi = (srcByte >> 4) & 0x0Fu;
const uint32_t xBefore = m_hwregX;
if (byteOff < m_vramSize) writeT4Nibble(static_cast<uint8_t>(srcLo), srcByte, srcLo, srcHi);
{ if ((xBefore + 1u) < rrw && m_hwregY < rrh)
int shift = static_cast<int>((nibbleAddr & 1u) << 2); {
uint8_t &b = m_vram[byteOff]; writeT4Nibble(static_cast<uint8_t>(srcHi), srcByte, srcLo, srcHi);
b = static_cast<uint8_t>((b & (0xF0u >> shift)) | ((nibble & 0x0Fu) << shift)); }
} }
}
else if (dpsm == GS_PSM_T8)
{
uint32_t offset = 0;
while (offset < sizeBytes && m_hwregY < rrh)
{
uint32_t pixelsLeft = rrw - m_hwregX;
uint32_t pixelsToCopy = std::min<uint32_t>(pixelsLeft, sizeBytes - offset);
if (pixelsToCopy == 0)
{
break;
}
for (uint32_t i = 0; i < pixelsToCopy; ++i)
{
const uint32_t vx = dsax + m_hwregX + i;
const uint32_t vy = dsay + m_hwregY;
const uint32_t dst = GSPSMT8::addrPSMT8(dbp, dbw, vx, vy);
if (dst < m_vramSize)
{
m_vram[dst] = data[offset + i];
}
}
offset += pixelsToCopy;
m_hwregX += pixelsToCopy;
if (m_hwregX >= rrw)
{
m_hwregX = 0;
++m_hwregY;
} }
} }
m_hwregX = 0;
m_hwregY = rrh;
} }
else if (dpsm == GS_PSM_CT24 || dpsm == GS_PSM_Z24) else if (dpsm == GS_PSM_CT24 || dpsm == GS_PSM_Z24)
{ {
@@ -773,7 +1259,8 @@ void GS::processImageData(const uint8_t *data, uint32_t sizeBytes)
else else
{ {
uint32_t bytesPerPixel = bpp / 8u; uint32_t bytesPerPixel = bpp / 8u;
if (bytesPerPixel == 0) bytesPerPixel = 4; if (bytesPerPixel == 0)
bytesPerPixel = 4;
uint32_t strideBytes = stridePixels * bytesPerPixel; uint32_t strideBytes = stridePixels * bytesPerPixel;
uint32_t rowBytes = rrw * bytesPerPixel; uint32_t rowBytes = rrw * bytesPerPixel;
@@ -814,7 +1301,8 @@ void GS::performLocalToHostToBuffer()
uint8_t sbw = m_bitbltbuf.sbw; uint8_t sbw = m_bitbltbuf.sbw;
uint8_t spsm = m_bitbltbuf.spsm; uint8_t spsm = m_bitbltbuf.spsm;
if (sbw == 0) sbw = 1; if (sbw == 0)
sbw = 1;
uint32_t base = sbp * 256u; uint32_t base = sbp * 256u;
uint32_t bpp = bitsPerPixel(spsm); uint32_t bpp = bitsPerPixel(spsm);
uint32_t stridePixels = static_cast<uint32_t>(sbw) * 64u; uint32_t stridePixels = static_cast<uint32_t>(sbw) * 64u;
@@ -827,7 +1315,8 @@ void GS::performLocalToHostToBuffer()
if (bpp == 4) if (bpp == 4)
{ {
uint32_t rowBytes = (rrw + 1u) / 2u; uint32_t rowBytes = (rrw + 1u) / 2u;
if (rowBytes == 0) rowBytes = 1; if (rowBytes == 0)
rowBytes = 1;
m_localToHostBuffer.reserve(rowBytes * rrh); m_localToHostBuffer.reserve(rowBytes * rrh);
uint32_t widthBlocks = static_cast<uint32_t>(sbw); uint32_t widthBlocks = static_cast<uint32_t>(sbw);
for (uint32_t y = 0; y < rrh; ++y) for (uint32_t y = 0; y < rrh; ++y)
@@ -851,6 +1340,18 @@ void GS::performLocalToHostToBuffer()
} }
} }
} }
else if (spsm == GS_PSM_T8)
{
m_localToHostBuffer.reserve(rrw * rrh);
for (uint32_t y = 0; y < rrh; ++y)
{
for (uint32_t x = 0; x < rrw; ++x)
{
const uint32_t src = GSPSMT8::addrPSMT8(sbp, sbw, ssax + x, ssay + y);
m_localToHostBuffer.push_back((src < m_vramSize) ? m_vram[src] : 0u);
}
}
}
else if (spsm == GS_PSM_CT24 || spsm == GS_PSM_Z24) else if (spsm == GS_PSM_CT24 || spsm == GS_PSM_Z24)
{ {
uint32_t storageBpp = 4; uint32_t storageBpp = 4;
@@ -875,7 +1376,8 @@ void GS::performLocalToHostToBuffer()
else else
{ {
uint32_t bytesPerPixel = bpp / 8u; uint32_t bytesPerPixel = bpp / 8u;
if (bytesPerPixel == 0) bytesPerPixel = 4; if (bytesPerPixel == 0)
bytesPerPixel = 4;
uint32_t strideBytes = stridePixels * bytesPerPixel; uint32_t strideBytes = stridePixels * bytesPerPixel;
uint32_t rowBytes = rrw * bytesPerPixel; uint32_t rowBytes = rrw * bytesPerPixel;
m_localToHostBuffer.reserve(rowBytes * rrh); m_localToHostBuffer.reserve(rowBytes * rrh);
+482 -105
View File
@@ -2,15 +2,289 @@
#include "ps2_gs_gpu.h" #include "ps2_gs_gpu.h"
#include "ps2_gs_common.h" #include "ps2_gs_common.h"
#include "ps2_gs_psmt4.h" #include "ps2_gs_psmt4.h"
#include "ps2_gs_psmt8.h"
#include <atomic>
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <fstream>
#include <iostream>
#include <sstream>
using namespace GSInternal; using namespace GSInternal;
namespace
{
float fabsQ(float q)
{
return (std::fabs(q) > 1.0e-8f) ? q : 1.0f;
}
uint32_t decodePSMCT16(uint16_t pixel)
{
const uint32_t r = ((pixel >> 0) & 0x1Fu) << 3;
const uint32_t g = ((pixel >> 5) & 0x1Fu) << 3;
const uint32_t b = ((pixel >> 10) & 0x1Fu) << 3;
const uint32_t a = (pixel & 0x8000u) ? 0x80u : 0u;
return r | (g << 8) | (b << 16) | (a << 24);
}
uint16_t encodePSMCT16(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
return static_cast<uint16_t>(((r >> 3) & 0x1Fu) |
(((g >> 3) & 0x1Fu) << 5) |
(((b >> 3) & 0x1Fu) << 10) |
((a >= 0x40u) ? 0x8000u : 0u));
}
std::atomic<uint32_t> s_debugPrimitiveCount{0};
std::atomic<uint32_t> s_debugPixelCount{0};
std::atomic<uint32_t> s_debugContext1PrimitiveCount{0};
std::atomic<uint32_t> s_debugFbp150PixelCount{0};
bool passesAlphaTest(uint64_t testReg, uint8_t alpha)
{
if ((testReg & 0x1u) == 0u)
return true;
const uint8_t atst = static_cast<uint8_t>((testReg >> 1) & 0x7u);
const uint8_t aref = static_cast<uint8_t>((testReg >> 4) & 0xFFu);
switch (atst)
{
case 0:
return false;
case 1:
return true;
case 2:
return alpha < aref;
case 3:
return alpha <= aref;
case 4:
return alpha == aref;
case 5:
return alpha >= aref;
case 6:
return alpha > aref;
case 7:
return alpha != aref;
default:
return true;
}
}
struct AlphaTestResult
{
bool writeFramebuffer;
bool preserveDestinationAlpha;
};
AlphaTestResult classifyAlphaTest(uint64_t testReg, uint8_t alpha)
{
const bool pass = passesAlphaTest(testReg, alpha);
if (pass)
return {true, false};
// TEST.AFAIL controls what happens when the alpha comparison fails.
switch (static_cast<uint8_t>((testReg >> 12) & 0x3u))
{
case 1: // FB_ONLY
return {true, false};
case 3: // RGB_ONLY
return {true, true};
case 0: // KEEP
case 2: // ZB_ONLY
default:
return {false, false};
}
}
struct TextureCombineResult
{
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
TextureCombineResult combineTexture(const GSTex0Reg &tex,
uint8_t vr,
uint8_t vg,
uint8_t vb,
uint8_t va,
uint8_t tr,
uint8_t tg,
uint8_t tb,
uint8_t ta)
{
const bool useTextureRgb = tex.tcc != 0u;
TextureCombineResult out{vr, vg, vb, ta};
switch (tex.tfx)
{
case 0:
if (useTextureRgb)
{
out.r = clampU8((tr * vr) >> 7);
out.g = clampU8((tg * vg) >> 7);
out.b = clampU8((tb * vb) >> 7);
}
out.a = clampU8((ta * va) >> 7);
break;
case 1:
if (useTextureRgb)
{
out.r = tr;
out.g = tg;
out.b = tb;
}
out.a = ta;
break;
case 2:
case 3:
if (useTextureRgb)
{
out.r = clampU8((tr * vr) >> 7);
out.g = clampU8((tg * vg) >> 7);
out.b = clampU8((tb * vb) >> 7);
}
out.a = ta;
break;
default:
if (useTextureRgb)
{
out.r = tr;
out.g = tg;
out.b = tb;
}
out.a = ta;
break;
}
return out;
}
uint32_t swizzleClutIndexCSM1(uint32_t index)
{
return (index & 0xE7u) | ((index & 0x08u) << 1u) | ((index & 0x10u) >> 1u);
}
uint32_t resolveClutIndex(uint8_t index, uint8_t csm, uint8_t csa, uint8_t sourcePsm)
{
uint32_t clutIndex = static_cast<uint32_t>(index);
if (sourcePsm == GS_PSM_T4)
{
clutIndex = (static_cast<uint32_t>(csa) << 4u) | (clutIndex & 0x0Fu);
if (csm == 0u)
clutIndex = swizzleClutIndexCSM1(clutIndex);
}
else if (sourcePsm == GS_PSM_T8 && csm == 0u)
{
clutIndex = swizzleClutIndexCSM1(clutIndex);
}
return clutIndex;
}
}
void GSRasterizer::drawPrimitive(GS *gs) void GSRasterizer::drawPrimitive(GS *gs)
{ {
const uint32_t primitiveIndex = s_debugPrimitiveCount.fetch_add(1, std::memory_order_relaxed);
if (primitiveIndex < 64u)
{
const auto &ctx = gs->activeContext();
std::cout << "[gs:prim] idx=" << primitiveIndex
<< " type=" << static_cast<uint32_t>(gs->m_prim.type)
<< " tme=" << static_cast<uint32_t>(gs->m_prim.tme)
<< " abe=" << static_cast<uint32_t>(gs->m_prim.abe)
<< " fst=" << static_cast<uint32_t>(gs->m_prim.fst)
<< " ctxt=" << static_cast<uint32_t>(gs->m_prim.ctxt)
<< " fbp=" << ctx.frame.fbp
<< " fbw=" << ctx.frame.fbw
<< " psm=0x" << std::hex << static_cast<uint32_t>(ctx.frame.psm) << std::dec
<< " tex0=("
<< "tbp0=" << ctx.tex0.tbp0
<< " tbw=" << static_cast<uint32_t>(ctx.tex0.tbw)
<< " psm=0x" << std::hex << static_cast<uint32_t>(ctx.tex0.psm) << std::dec
<< " tw=" << static_cast<uint32_t>(ctx.tex0.tw)
<< " th=" << static_cast<uint32_t>(ctx.tex0.th)
<< " tcc=" << static_cast<uint32_t>(ctx.tex0.tcc)
<< " tfx=" << static_cast<uint32_t>(ctx.tex0.tfx)
<< " cbp=" << ctx.tex0.cbp
<< " cpsm=0x" << std::hex << static_cast<uint32_t>(ctx.tex0.cpsm) << std::dec
<< " csm=" << static_cast<uint32_t>(ctx.tex0.csm)
<< " csa=" << static_cast<uint32_t>(ctx.tex0.csa)
<< ")"
<< " ofx=" << (ctx.xyoffset.ofx >> 4)
<< " ofy=" << (ctx.xyoffset.ofy >> 4)
<< " scissor=(" << ctx.scissor.x0
<< "," << ctx.scissor.y0
<< ")-(" << ctx.scissor.x1
<< "," << ctx.scissor.y1 << ")"
<< " test=0x" << std::hex << ctx.test
<< " alpha=0x" << ctx.alpha
<< std::dec
<< " v0=(" << gs->m_vtxQueue[0].x << "," << gs->m_vtxQueue[0].y << ")"
<< " uv0=(" << (gs->m_vtxQueue[0].u >> 4) << "," << (gs->m_vtxQueue[0].v >> 4) << ")"
<< " stq0=(" << gs->m_vtxQueue[0].s << "," << gs->m_vtxQueue[0].t << "," << gs->m_vtxQueue[0].q << ")"
<< " v1=(" << gs->m_vtxQueue[1].x << "," << gs->m_vtxQueue[1].y << ")"
<< " uv1=(" << (gs->m_vtxQueue[1].u >> 4) << "," << (gs->m_vtxQueue[1].v >> 4) << ")"
<< " stq1=(" << gs->m_vtxQueue[1].s << "," << gs->m_vtxQueue[1].t << "," << gs->m_vtxQueue[1].q << ")"
<< " v2=(" << gs->m_vtxQueue[2].x << "," << gs->m_vtxQueue[2].y << ")"
<< " uv2=(" << (gs->m_vtxQueue[2].u >> 4) << "," << (gs->m_vtxQueue[2].v >> 4) << ")"
<< " stq2=(" << gs->m_vtxQueue[2].s << "," << gs->m_vtxQueue[2].t << "," << gs->m_vtxQueue[2].q << ")"
<< " rgba0=(" << static_cast<uint32_t>(gs->m_vtxQueue[0].r) << ","
<< static_cast<uint32_t>(gs->m_vtxQueue[0].g) << ","
<< static_cast<uint32_t>(gs->m_vtxQueue[0].b) << ","
<< static_cast<uint32_t>(gs->m_vtxQueue[0].a) << ")"
<< " rgba1=(" << static_cast<uint32_t>(gs->m_vtxQueue[1].r) << ","
<< static_cast<uint32_t>(gs->m_vtxQueue[1].g) << ","
<< static_cast<uint32_t>(gs->m_vtxQueue[1].b) << ","
<< static_cast<uint32_t>(gs->m_vtxQueue[1].a) << ")"
<< " rgba2=(" << static_cast<uint32_t>(gs->m_vtxQueue[2].r) << ","
<< static_cast<uint32_t>(gs->m_vtxQueue[2].g) << ","
<< static_cast<uint32_t>(gs->m_vtxQueue[2].b) << ","
<< static_cast<uint32_t>(gs->m_vtxQueue[2].a) << ")"
<< std::endl;
}
const auto &ctx = gs->activeContext();
if ((gs->m_prim.ctxt != 0u || ctx.frame.fbp == 150u) &&
s_debugContext1PrimitiveCount.fetch_add(1u, std::memory_order_relaxed) < 32u)
{
std::cout << "[gs:copy-prim]"
<< " type=" << static_cast<uint32_t>(gs->m_prim.type)
<< " tme=" << static_cast<uint32_t>(gs->m_prim.tme)
<< " abe=" << static_cast<uint32_t>(gs->m_prim.abe)
<< " fst=" << static_cast<uint32_t>(gs->m_prim.fst)
<< " ctxt=" << static_cast<uint32_t>(gs->m_prim.ctxt)
<< " fbp=" << ctx.frame.fbp
<< " fbw=" << ctx.frame.fbw
<< " psm=0x" << std::hex << static_cast<uint32_t>(ctx.frame.psm) << std::dec
<< " tex0=("
<< "tbp0=" << ctx.tex0.tbp0
<< " tbw=" << static_cast<uint32_t>(ctx.tex0.tbw)
<< " psm=0x" << std::hex << static_cast<uint32_t>(ctx.tex0.psm) << std::dec
<< " tcc=" << static_cast<uint32_t>(ctx.tex0.tcc)
<< " tfx=" << static_cast<uint32_t>(ctx.tex0.tfx)
<< " cbp=" << ctx.tex0.cbp
<< " cpsm=0x" << std::hex << static_cast<uint32_t>(ctx.tex0.cpsm) << std::dec
<< " csm=" << static_cast<uint32_t>(ctx.tex0.csm)
<< " csa=" << static_cast<uint32_t>(ctx.tex0.csa)
<< ")"
<< " ofx=" << (ctx.xyoffset.ofx >> 4)
<< " ofy=" << (ctx.xyoffset.ofy >> 4)
<< " scissor=(" << ctx.scissor.x0
<< "," << ctx.scissor.y0
<< ")-(" << ctx.scissor.x1
<< "," << ctx.scissor.y1 << ")"
<< " test=0x" << std::hex << ctx.test
<< " alpha=0x" << ctx.alpha
<< std::dec << std::endl;
}
switch (gs->m_prim.type) switch (gs->m_prim.type)
{ {
case GS_PRIM_SPRITE: case GS_PRIM_SPRITE:
@@ -46,18 +320,65 @@ void GSRasterizer::writePixel(GS *gs, int x, int y, uint8_t r, uint8_t g, uint8_
y < ctx.scissor.y0 || y > ctx.scissor.y1) y < ctx.scissor.y0 || y > ctx.scissor.y1)
return; return;
const AlphaTestResult alphaTest = classifyAlphaTest(ctx.test, a);
if (!alphaTest.writeFramebuffer)
return;
uint32_t fbBase = ctx.frame.fbp * 8192u; uint32_t fbBase = ctx.frame.fbp * 8192u;
uint32_t stride = fbStride(ctx.frame.fbw, ctx.frame.psm); uint32_t stride = fbStride(ctx.frame.fbw, ctx.frame.psm);
if (stride == 0) return; if (stride == 0)
uint32_t off = fbBase + static_cast<uint32_t>(y) * stride + static_cast<uint32_t>(x) * 4u;
if (off + 4 > gs->m_vramSize)
return; return;
const uint32_t bytesPerPixel = std::max<uint32_t>(1u, bitsPerPixel(ctx.frame.psm) / 8u);
uint32_t off = fbBase + static_cast<uint32_t>(y) * stride + static_cast<uint32_t>(x) * bytesPerPixel;
if (off + bytesPerPixel > gs->m_vramSize)
return;
const uint32_t pixelIndex = s_debugPixelCount.fetch_add(1, std::memory_order_relaxed);
if (pixelIndex < 32u)
{
std::cout << "[gs:pixel] idx=" << pixelIndex
<< " xy=(" << x << "," << y << ")"
<< " rgba=(" << static_cast<uint32_t>(r) << ","
<< static_cast<uint32_t>(g) << ","
<< static_cast<uint32_t>(b) << ","
<< static_cast<uint32_t>(a) << ")"
<< " fbp=" << ctx.frame.fbp
<< " fbw=" << ctx.frame.fbw
<< " psm=0x" << std::hex << static_cast<uint32_t>(ctx.frame.psm) << std::dec
<< " off=0x" << std::hex << off << std::dec
<< std::endl;
}
if (ctx.frame.fbp == 150u &&
s_debugFbp150PixelCount.fetch_add(1u, std::memory_order_relaxed) < 32u)
{
std::cout << "[gs:fbp150-pixel]"
<< " xy=(" << x << "," << y << ")"
<< " rgba=(" << static_cast<uint32_t>(r) << ","
<< static_cast<uint32_t>(g) << ","
<< static_cast<uint32_t>(b) << ","
<< static_cast<uint32_t>(a) << ")"
<< " scissor=(" << ctx.scissor.x0
<< "," << ctx.scissor.y0
<< ")-(" << ctx.scissor.x1
<< "," << ctx.scissor.y1 << ")"
<< " off=0x" << std::hex << off << std::dec << std::endl;
}
if (gs->m_prim.abe) if (gs->m_prim.abe)
{ {
uint32_t existing; uint32_t existing = 0u;
std::memcpy(&existing, gs->m_vram + off, 4); if (bytesPerPixel == 2u)
{
uint16_t packed = 0u;
std::memcpy(&packed, gs->m_vram + off, 2);
existing = decodePSMCT16(packed);
}
else
{
std::memcpy(&existing, gs->m_vram + off, 4);
}
uint8_t dr = existing & 0xFF; uint8_t dr = existing & 0xFF;
uint8_t dg = (existing >> 8) & 0xFF; uint8_t dg = (existing >> 8) & 0xFF;
uint8_t db = (existing >> 16) & 0xFF; uint8_t db = (existing >> 16) & 0xFF;
@@ -68,26 +389,40 @@ void GSRasterizer::writePixel(GS *gs, int x, int y, uint8_t r, uint8_t g, uint8_
uint8_t bsel = (alphaReg >> 2) & 3; uint8_t bsel = (alphaReg >> 2) & 3;
uint8_t csel = (alphaReg >> 4) & 3; uint8_t csel = (alphaReg >> 4) & 3;
uint8_t dsel = (alphaReg >> 6) & 3; uint8_t dsel = (alphaReg >> 6) & 3;
uint8_t fix = static_cast<uint8_t>((alphaReg >> 32) & 0xFF); uint8_t fix = static_cast<uint8_t>((alphaReg >> 32) & 0xFF);
auto pickRGB = [&](uint8_t sel, int cs, int cd) -> int { auto pickRGB = [&](uint8_t sel, int cs, int cd) -> int
if (sel == 0) return cs; {
if (sel == 1) return cd; if (sel == 0)
return cs;
if (sel == 1)
return cd;
return 0; return 0;
}; };
int cAlpha = (csel == 0) ? a : (csel == 1) ? da : fix; int cAlpha = (csel == 0) ? a : (csel == 1) ? da
: fix;
r = clampU8(((pickRGB(asel, r, dr) - pickRGB(bsel, r, dr)) * cAlpha >> 7) + pickRGB(dsel, r, dr)); r = clampU8(((pickRGB(asel, r, dr) - pickRGB(bsel, r, dr)) * cAlpha >> 7) + pickRGB(dsel, r, dr));
g = clampU8(((pickRGB(asel, g, dg) - pickRGB(bsel, g, dg)) * cAlpha >> 7) + pickRGB(dsel, g, dg)); g = clampU8(((pickRGB(asel, g, dg) - pickRGB(bsel, g, dg)) * cAlpha >> 7) + pickRGB(dsel, g, dg));
b = clampU8(((pickRGB(asel, b, db) - pickRGB(bsel, b, db)) * cAlpha >> 7) + pickRGB(dsel, b, db)); b = clampU8(((pickRGB(asel, b, db) - pickRGB(bsel, b, db)) * cAlpha >> 7) + pickRGB(dsel, b, db));
} }
uint32_t pixel = static_cast<uint32_t>(r)
| (static_cast<uint32_t>(g) << 8)
| (static_cast<uint32_t>(b) << 16)
| (static_cast<uint32_t>(a) << 24);
uint32_t mask = ctx.frame.fbmsk; uint32_t mask = ctx.frame.fbmsk;
if (bytesPerPixel == 2u)
{
uint16_t pixel = encodePSMCT16(r, g, b, a);
if ((mask & 0xFFFFu) != 0u)
{
uint16_t existing = 0u;
std::memcpy(&existing, gs->m_vram + off, 2);
pixel = static_cast<uint16_t>((pixel & ~mask) | (existing & mask));
}
std::memcpy(gs->m_vram + off, &pixel, 2);
return;
}
uint32_t pixel = static_cast<uint32_t>(r) | (static_cast<uint32_t>(g) << 8) | (static_cast<uint32_t>(b) << 16) | (static_cast<uint32_t>(a) << 24);
if (mask != 0) if (mask != 0)
{ {
uint32_t existing; uint32_t existing;
@@ -95,12 +430,20 @@ void GSRasterizer::writePixel(GS *gs, int x, int y, uint8_t r, uint8_t g, uint8_
pixel = (pixel & ~mask) | (existing & mask); pixel = (pixel & ~mask) | (existing & mask);
} }
if (alphaTest.preserveDestinationAlpha)
{
uint32_t existing = 0u;
std::memcpy(&existing, gs->m_vram + off, 4);
pixel = (pixel & 0x00FFFFFFu) | (existing & 0xFF000000u);
}
std::memcpy(gs->m_vram + off, &pixel, 4); std::memcpy(gs->m_vram + off, &pixel, 4);
} }
uint32_t GSRasterizer::readTexelPSMCT32(GS *gs, uint32_t tbp0, uint32_t tbw, int texU, int texV) uint32_t GSRasterizer::readTexelPSMCT32(GS *gs, uint32_t tbp0, uint32_t tbw, int texU, int texV)
{ {
if (tbw == 0) tbw = 1; if (tbw == 0)
tbw = 1;
uint32_t base = tbp0 * 256u; uint32_t base = tbp0 * 256u;
uint32_t stride = tbw * 64u * 4u; uint32_t stride = tbw * 64u * 4u;
uint32_t off = base + static_cast<uint32_t>(texV) * stride + static_cast<uint32_t>(texU) * 4u; uint32_t off = base + static_cast<uint32_t>(texV) * stride + static_cast<uint32_t>(texU) * 4u;
@@ -111,9 +454,24 @@ uint32_t GSRasterizer::readTexelPSMCT32(GS *gs, uint32_t tbp0, uint32_t tbw, int
return texel; return texel;
} }
uint32_t GSRasterizer::readTexelPSMCT16(GS *gs, uint32_t tbp0, uint32_t tbw, int texU, int texV)
{
if (tbw == 0)
tbw = 1;
uint32_t base = tbp0 * 256u;
uint32_t stride = tbw * 64u * 2u;
uint32_t off = base + static_cast<uint32_t>(texV) * stride + static_cast<uint32_t>(texU) * 2u;
if (off + 2 > gs->m_vramSize)
return 0xFFFF00FFu;
uint16_t texel;
std::memcpy(&texel, gs->m_vram + off, 2);
return decodePSMCT16(texel);
}
uint32_t GSRasterizer::readTexelPSMT4(GS *gs, uint32_t tbp0, uint32_t tbw, int texU, int texV) uint32_t GSRasterizer::readTexelPSMT4(GS *gs, uint32_t tbp0, uint32_t tbw, int texU, int texV)
{ {
if (tbw == 0) tbw = 1; if (tbw == 0)
tbw = 1;
uint32_t nibbleAddr = GSPSMT4::addrPSMT4(tbp0, tbw, static_cast<uint32_t>(texU), static_cast<uint32_t>(texV)); uint32_t nibbleAddr = GSPSMT4::addrPSMT4(tbp0, tbw, static_cast<uint32_t>(texU), static_cast<uint32_t>(texV));
uint32_t byteOff = nibbleAddr >> 1; uint32_t byteOff = nibbleAddr >> 1;
if (byteOff >= gs->m_vramSize) if (byteOff >= gs->m_vramSize)
@@ -124,13 +482,20 @@ uint32_t GSRasterizer::readTexelPSMT4(GS *gs, uint32_t tbp0, uint32_t tbw, int t
return idx; return idx;
} }
uint32_t GSRasterizer::lookupCLUT(GS *gs, uint8_t index, uint32_t cbp, uint8_t cpsm, uint8_t csa) uint32_t GSRasterizer::lookupCLUT(GS *gs,
uint8_t index,
uint32_t cbp,
uint8_t cpsm,
uint8_t csm,
uint8_t csa,
uint8_t sourcePsm)
{ {
uint32_t clutBase = cbp * 256u; uint32_t clutBase = cbp * 256u;
const uint32_t clutIndex = resolveClutIndex(index, csm, csa, sourcePsm);
if (cpsm == GS_PSM_CT32 || cpsm == GS_PSM_CT24) if (cpsm == GS_PSM_CT32 || cpsm == GS_PSM_CT24)
{ {
uint32_t off = clutBase + (static_cast<uint32_t>(csa) * 16u + index) * 4u; uint32_t off = clutBase + clutIndex * 4u;
if (off + 4 > gs->m_vramSize) if (off + 4 > gs->m_vramSize)
return 0xFFFF00FFu; return 0xFFFF00FFu;
uint32_t color; uint32_t color;
@@ -140,7 +505,7 @@ uint32_t GSRasterizer::lookupCLUT(GS *gs, uint8_t index, uint32_t cbp, uint8_t c
if (cpsm == GS_PSM_CT16 || cpsm == GS_PSM_CT16S) if (cpsm == GS_PSM_CT16 || cpsm == GS_PSM_CT16S)
{ {
uint32_t off = clutBase + (static_cast<uint32_t>(csa) * 16u + index) * 2u; uint32_t off = clutBase + clutIndex * 2u;
if (off + 2 > gs->m_vramSize) if (off + 2 > gs->m_vramSize)
return 0xFFFF00FFu; return 0xFFFF00FFu;
uint16_t c16; uint16_t c16;
@@ -155,15 +520,13 @@ uint32_t GSRasterizer::lookupCLUT(GS *gs, uint8_t index, uint32_t cbp, uint8_t c
return 0xFFFF00FFu; return 0xFFFF00FFu;
} }
uint32_t GSRasterizer::sampleTexture(GS *gs, float s, float t, uint16_t u, uint16_t v) uint32_t GSRasterizer::sampleTexture(GS *gs, float s, float t, float q, uint16_t u, uint16_t v)
{ {
const auto &ctx = gs->activeContext(); const auto &ctx = gs->activeContext();
const auto &tex = ctx.tex0; const auto &tex = ctx.tex0;
int texW = 1 << tex.tw; int texW = 1 << tex.tw;
int texH = 1 << tex.th; int texH = 1 << tex.th;
if (texW == 0) texW = 1;
if (texH == 0) texH = 1;
int texU, texV; int texU, texV;
if (gs->m_prim.fst) if (gs->m_prim.fst)
@@ -173,7 +536,7 @@ uint32_t GSRasterizer::sampleTexture(GS *gs, float s, float t, uint16_t u, uint1
} }
else else
{ {
float invQ = (gs->m_curQ != 0.0f) ? (1.0f / gs->m_curQ) : 1.0f; const float invQ = 1.0f / fabsQ(q);
texU = static_cast<int>(s * invQ * static_cast<float>(texW)); texU = static_cast<int>(s * invQ * static_cast<float>(texW));
texV = static_cast<int>(t * invQ * static_cast<float>(texH)); texV = static_cast<int>(t * invQ * static_cast<float>(texH));
} }
@@ -184,21 +547,24 @@ uint32_t GSRasterizer::sampleTexture(GS *gs, float s, float t, uint16_t u, uint1
if (tex.psm == GS_PSM_CT32 || tex.psm == GS_PSM_CT24) if (tex.psm == GS_PSM_CT32 || tex.psm == GS_PSM_CT24)
return readTexelPSMCT32(gs, tex.tbp0, tex.tbw, texU, texV); return readTexelPSMCT32(gs, tex.tbp0, tex.tbw, texU, texV);
if (tex.psm == GS_PSM_CT16 || tex.psm == GS_PSM_CT16S)
return readTexelPSMCT16(gs, tex.tbp0, tex.tbw, texU, texV);
if (tex.psm == GS_PSM_T4) if (tex.psm == GS_PSM_T4)
{ {
uint32_t idx = readTexelPSMT4(gs, tex.tbp0, tex.tbw, texU, texV); uint32_t idx = readTexelPSMT4(gs, tex.tbp0, tex.tbw, texU, texV);
return lookupCLUT(gs, static_cast<uint8_t>(idx), tex.cbp, tex.cpsm, tex.csa); return lookupCLUT(gs, static_cast<uint8_t>(idx), tex.cbp, tex.cpsm, tex.csm, tex.csa, tex.psm);
} }
if (tex.psm == GS_PSM_T8) if (tex.psm == GS_PSM_T8)
{ {
if (tex.tbw == 0) return 0xFFFF00FFu; if (tex.tbw == 0)
uint32_t base = tex.tbp0 * 256u; return 0xFFFF00FFu;
uint32_t stride = static_cast<uint32_t>(tex.tbw) * 64u; uint32_t off = GSPSMT8::addrPSMT8(tex.tbp0, tex.tbw, static_cast<uint32_t>(texU), static_cast<uint32_t>(texV));
uint32_t off = base + static_cast<uint32_t>(texV) * stride + static_cast<uint32_t>(texU); if (off >= gs->m_vramSize)
if (off >= gs->m_vramSize) return 0xFFFF00FFu; return 0xFFFF00FFu;
uint8_t idx = gs->m_vram[off]; uint8_t idx = gs->m_vram[off];
return lookupCLUT(gs, idx, tex.cbp, tex.cpsm, tex.csa); return lookupCLUT(gs, idx, tex.cbp, tex.cpsm, tex.csm, tex.csa, tex.psm);
} }
return 0xFFFF00FFu; return 0xFFFF00FFu;
@@ -218,13 +584,16 @@ void GSRasterizer::drawSprite(GS *gs)
int x1 = static_cast<int>(v1.x) - ofx; int x1 = static_cast<int>(v1.x) - ofx;
int y1 = static_cast<int>(v1.y) - ofy; int y1 = static_cast<int>(v1.y) - ofy;
if (x0 > x1) std::swap(x0, x1); if (x0 > x1)
if (y0 > y1) std::swap(y0, y1); std::swap(x0, x1);
if (y0 > y1)
std::swap(y0, y1);
// If the sprite rectangle is fully outside scissor, nothing should render. // If the sprite rectangle is fully outside scissor, nothing should render.
if (x1 < ctx.scissor.x0 || x0 > ctx.scissor.x1 || if (x1 < ctx.scissor.x0 || x0 > ctx.scissor.x1 ||
y1 < ctx.scissor.y0 || y0 > ctx.scissor.y1) y1 < ctx.scissor.y0 || y0 > ctx.scissor.y1)
{ {
// maybe a log here idk ?
return; return;
} }
@@ -237,12 +606,14 @@ void GSRasterizer::drawSprite(GS *gs)
if (gs->m_prim.tme) if (gs->m_prim.tme)
{ {
const auto &tex = ctx.tex0; const auto &tex = ctx.tex0;
int texW = 1 << tex.tw; int texW = 1 << tex.tw;
int texH = 1 << tex.th; int texH = 1 << tex.th;
if (texW == 0) texW = 1; if (texW == 0)
if (texH == 0) texH = 1; texW = 1;
if (texH == 0)
texH = 1;
float u0f, v0f, u1f, v1f; float u0f, v0f, u1f, v1f;
if (gs->m_prim.fst) if (gs->m_prim.fst)
{ {
@@ -253,16 +624,20 @@ void GSRasterizer::drawSprite(GS *gs)
} }
else else
{ {
u0f = v0.s * static_cast<float>(texW); const float q0 = fabsQ(v0.q);
v0f = v0.t * static_cast<float>(texH); const float q1 = fabsQ(v1.q);
u1f = v1.s * static_cast<float>(texW); u0f = (v0.s / q0) * static_cast<float>(texW);
v1f = v1.t * static_cast<float>(texH); v0f = (v0.t / q0) * static_cast<float>(texH);
u1f = (v1.s / q1) * static_cast<float>(texW);
v1f = (v1.t / q1) * static_cast<float>(texH);
} }
float spriteW = static_cast<float>(x1 - x0); float spriteW = static_cast<float>(x1 - x0);
float spriteH = static_cast<float>(y1 - y0); float spriteH = static_cast<float>(y1 - y0);
if (spriteW < 1.0f) spriteW = 1.0f; if (spriteW < 1.0f)
if (spriteH < 1.0f) spriteH = 1.0f; spriteW = 1.0f;
if (spriteH < 1.0f)
spriteH = 1.0f;
for (int y = y0; y <= y1; ++y) for (int y = y0; y <= y1; ++y)
{ {
@@ -277,21 +652,22 @@ void GSRasterizer::drawSprite(GS *gs)
int tu = clampInt(static_cast<int>(texUf), 0, texW - 1); int tu = clampInt(static_cast<int>(texUf), 0, texW - 1);
uint32_t texel; uint32_t texel;
uint32_t sampleIndexValue = 0u;
if (tex.psm == GS_PSM_CT32 || tex.psm == GS_PSM_CT24) if (tex.psm == GS_PSM_CT32 || tex.psm == GS_PSM_CT24)
texel = readTexelPSMCT32(gs, tex.tbp0, tex.tbw, tu, tv); texel = readTexelPSMCT32(gs, tex.tbp0, tex.tbw, tu, tv);
else if (tex.psm == GS_PSM_CT16 || tex.psm == GS_PSM_CT16S)
texel = readTexelPSMCT16(gs, tex.tbp0, tex.tbw, tu, tv);
else if (tex.psm == GS_PSM_T4) else if (tex.psm == GS_PSM_T4)
{ {
uint32_t idx = readTexelPSMT4(gs, tex.tbp0, tex.tbw, tu, tv); sampleIndexValue = readTexelPSMT4(gs, tex.tbp0, tex.tbw, tu, tv);
texel = lookupCLUT(gs, static_cast<uint8_t>(idx), tex.cbp, tex.cpsm, tex.csa); texel = lookupCLUT(gs, static_cast<uint8_t>(sampleIndexValue), tex.cbp, tex.cpsm, tex.csm, tex.csa, tex.psm);
} }
else if (tex.psm == GS_PSM_T8) else if (tex.psm == GS_PSM_T8)
{ {
uint32_t base = tex.tbp0 * 256u; uint32_t off = GSPSMT8::addrPSMT8(tex.tbp0, tex.tbw ? tex.tbw : 1u,
uint32_t tbw = tex.tbw ? tex.tbw : 1u; static_cast<uint32_t>(tu), static_cast<uint32_t>(tv));
uint32_t stride = tbw * 64u; sampleIndexValue = (off < gs->m_vramSize) ? gs->m_vram[off] : 0u;
uint32_t off = base + static_cast<uint32_t>(tv) * stride + static_cast<uint32_t>(tu); texel = lookupCLUT(gs, static_cast<uint8_t>(sampleIndexValue), tex.cbp, tex.cpsm, tex.csm, tex.csa, tex.psm);
uint8_t idx = (off < gs->m_vramSize) ? gs->m_vram[off] : 0;
texel = lookupCLUT(gs, idx, tex.cbp, tex.cpsm, tex.csa);
} }
else else
texel = 0xFFFF00FFu; texel = 0xFFFF00FFu;
@@ -301,27 +677,8 @@ void GSRasterizer::drawSprite(GS *gs)
uint8_t tb = static_cast<uint8_t>((texel >> 16) & 0xFF); uint8_t tb = static_cast<uint8_t>((texel >> 16) & 0xFF);
uint8_t ta = static_cast<uint8_t>((texel >> 24) & 0xFF); uint8_t ta = static_cast<uint8_t>((texel >> 24) & 0xFF);
uint8_t fr, fg, fb, fa; const TextureCombineResult color = combineTexture(tex, r, g, b, a, tr, tg, tb, ta);
if (tex.tfx == 0) writePixel(gs, x, y, color.r, color.g, color.b, color.a);
{
fr = clampU8((tr * r) >> 7);
fg = clampU8((tg * g) >> 7);
fb = clampU8((tb * b) >> 7);
fa = ta;
}
else if (tex.tfx == 1)
{
fr = tr; fg = tg; fb = tb; fa = ta;
}
else
{
fr = clampU8((tr * r) >> 7);
fg = clampU8((tg * g) >> 7);
fb = clampU8((tb * b) >> 7);
fa = ta;
}
writePixel(gs, x, y, fr, fg, fb, fa);
} }
} }
} }
@@ -364,7 +721,8 @@ void GSRasterizer::drawTriangle(GS *gs)
if (std::fabs(denom) < 0.001f) if (std::fabs(denom) < 0.001f)
return; return;
float invDenom = 1.0f / denom; const float winding = (denom < 0.0f) ? -1.0f : 1.0f;
const float invAbsDenom = 1.0f / std::fabs(denom);
for (int y = minY; y <= maxY; ++y) for (int y = minY; y <= maxY; ++y)
{ {
@@ -373,8 +731,8 @@ void GSRasterizer::drawTriangle(GS *gs)
{ {
float px = static_cast<float>(x) + 0.5f; float px = static_cast<float>(x) + 0.5f;
float w0 = ((fy1 - fy2) * (px - fx2) + (fx2 - fx1) * (py - fy2)) * invDenom; float w0 = (((fy1 - fy2) * (px - fx2) + (fx2 - fx1) * (py - fy2)) * winding) * invAbsDenom;
float w1 = ((fy2 - fy0) * (px - fx2) + (fx0 - fx2) * (py - fy2)) * invDenom; float w1 = (((fy2 - fy0) * (px - fx2) + (fx0 - fx2) * (py - fy2)) * winding) * invAbsDenom;
float w2 = 1.0f - w0 - w1; float w2 = 1.0f - w0 - w1;
if (w0 < 0.0f || w1 < 0.0f || w2 < 0.0f) if (w0 < 0.0f || w1 < 0.0f || w2 < 0.0f)
@@ -390,50 +748,57 @@ void GSRasterizer::drawTriangle(GS *gs)
} }
else else
{ {
r = v2.r; g = v2.g; b = v2.b; a = v2.a; r = v2.r;
g = v2.g;
b = v2.b;
a = v2.a;
} }
if (gs->m_prim.tme) if (gs->m_prim.tme)
{ {
float is, it; float is, it, iq;
uint16_t iu, iv; uint16_t iu, iv;
if (gs->m_prim.fst) if (gs->m_prim.fst)
{ {
iu = static_cast<uint16_t>(v0.u * w0 + v1.u * w1 + v2.u * w2); iu = static_cast<uint16_t>(v0.u * w0 + v1.u * w1 + v2.u * w2);
iv = static_cast<uint16_t>(v0.v * w0 + v1.v * w1 + v2.v * w2); iv = static_cast<uint16_t>(v0.v * w0 + v1.v * w1 + v2.v * w2);
is = 0; it = 0; is = 0.0f;
it = 0.0f;
iq = 1.0f;
} }
else else
{ {
is = v0.s * w0 + v1.s * w1 + v2.s * w2; const float invQ0 = 1.0f / fabsQ(v0.q);
it = v0.t * w0 + v1.t * w1 + v2.t * w2; const float invQ1 = 1.0f / fabsQ(v1.q);
iu = 0; iv = 0; const float invQ2 = 1.0f / fabsQ(v2.q);
const float sOverQ = (v0.s * invQ0) * w0 + (v1.s * invQ1) * w1 + (v2.s * invQ2) * w2;
const float tOverQ = (v0.t * invQ0) * w0 + (v1.t * invQ1) * w1 + (v2.t * invQ2) * w2;
const float invQ = invQ0 * w0 + invQ1 * w1 + invQ2 * w2;
iq = (std::fabs(invQ) > 1.0e-8f) ? (1.0f / invQ) : 1.0f;
is = sOverQ * iq;
it = tOverQ * iq;
iu = 0;
iv = 0;
} }
uint32_t texel = sampleTexture(gs, is, it, iu, iv);
uint32_t texel = sampleTexture(gs, is, it, iq, iu, iv);
uint8_t tr = static_cast<uint8_t>(texel & 0xFF); uint8_t tr = static_cast<uint8_t>(texel & 0xFF);
uint8_t tg = static_cast<uint8_t>((texel >> 8) & 0xFF); uint8_t tg = static_cast<uint8_t>((texel >> 8) & 0xFF);
uint8_t tb = static_cast<uint8_t>((texel >> 16) & 0xFF); uint8_t tb = static_cast<uint8_t>((texel >> 16) & 0xFF);
uint8_t ta = static_cast<uint8_t>((texel >> 24) & 0xFF); uint8_t ta = static_cast<uint8_t>((texel >> 24) & 0xFF);
const auto &tex = ctx.tex0; const auto &tex = ctx.tex0;
if (tex.tfx == 0) const uint8_t shadeR = r;
{ const uint8_t shadeG = g;
r = clampU8((tr * r) >> 7); const uint8_t shadeB = b;
g = clampU8((tg * g) >> 7); const uint8_t shadeA = a;
b = clampU8((tb * b) >> 7); const TextureCombineResult color = combineTexture(tex, shadeR, shadeG, shadeB, shadeA, tr, tg, tb, ta);
a = ta;
} r = color.r;
else if (tex.tfx == 1) g = color.g;
{ b = color.b;
r = tr; g = tg; b = tb; a = ta; a = color.a;
}
else
{
r = clampU8((tr * r) >> 7);
g = clampU8((tg * g) >> 7);
b = clampU8((tb * b) >> 7);
a = ta;
}
} }
writePixel(gs, x, y, r, g, b, a); writePixel(gs, x, y, r, g, b, a);
@@ -462,7 +827,8 @@ void GSRasterizer::drawLine(GS *gs)
int err = dx + dy; int err = dx + dy;
int totalSteps = std::max(std::abs(x1 - x0), std::abs(y1 - y0)); int totalSteps = std::max(std::abs(x1 - x0), std::abs(y1 - y0));
if (totalSteps == 0) totalSteps = 1; if (totalSteps == 0)
totalSteps = 1;
int step = 0; int step = 0;
for (;;) for (;;)
@@ -478,7 +844,10 @@ void GSRasterizer::drawLine(GS *gs)
} }
else else
{ {
r = v1.r; g = v1.g; b = v1.b; a = v1.a; r = v1.r;
g = v1.g;
b = v1.b;
a = v1.a;
} }
writePixel(gs, x0, y0, r, g, b, a); writePixel(gs, x0, y0, r, g, b, a);
@@ -487,8 +856,16 @@ void GSRasterizer::drawLine(GS *gs)
break; break;
int e2 = 2 * err; int e2 = 2 * err;
if (e2 >= dy) { err += dy; x0 += sx; } if (e2 >= dy)
if (e2 <= dx) { err += dx; y0 += sy; } {
err += dy;
x0 += sx;
}
if (e2 <= dx)
{
err += dx;
y0 += sy;
}
++step; ++step;
} }
} }
+19 -8
View File
@@ -224,15 +224,14 @@ bool PS2Memory::initialize(size_t ramSize)
bool PS2Memory::isScratchpad(uint32_t address) const bool PS2Memory::isScratchpad(uint32_t address) const
{ {
return address >= PS2_SCRATCHPAD_BASE && return ps2IsScratchpadAddress(address);
address < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE;
} }
uint32_t PS2Memory::translateAddress(uint32_t virtualAddress) uint32_t PS2Memory::translateAddress(uint32_t virtualAddress)
{ {
if (isScratchpad(virtualAddress)) if (isScratchpad(virtualAddress))
{ {
return virtualAddress - PS2_SCRATCHPAD_BASE; return ps2ScratchpadOffset(virtualAddress);
} }
// EE uncached aliases of main RAM (per PS2 memory map): // EE uncached aliases of main RAM (per PS2 memory map):
@@ -837,7 +836,7 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
pt.qwc = qwCount; pt.qwc = qwCount;
if (channelBase == 0x1000A000) if (channelBase == 0x1000A000)
m_pendingGifTransfers.push_back(pt); m_pendingGifTransfers.push_back(pt);
else if (channelBase == 0x10009000 && !scratch) else if (channelBase == 0x10009000)
m_pendingVif1Transfers.push_back(pt); m_pendingVif1Transfers.push_back(pt);
}; };
@@ -1029,6 +1028,13 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
{ {
enqueueTransfer(madr, qwc); enqueueTransfer(madr, qwc);
} }
const bool autoProcessTransfers =
(channelBase == 0x1000A000u) ? (m_gifPacketCallback || m_gifArbiter != nullptr) : true;
if (autoProcessTransfers)
{
processPendingTransfers();
}
} }
} }
return true; return true;
@@ -1105,9 +1111,11 @@ void PS2Memory::processPendingTransfers()
{ {
processVIF1Data(p.chainData.data(), static_cast<uint32_t>(p.chainData.size())); processVIF1Data(p.chainData.data(), static_cast<uint32_t>(p.chainData.size()));
} }
else if (p.qwc > 0 && !p.fromScratchpad) else if (p.qwc > 0)
{ {
uint32_t srcPhys = 0; uint32_t srcPhys = 0;
const uint64_t bytes64 = static_cast<uint64_t>(p.qwc) * 16ull;
uint32_t sizeBytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast<uint32_t>(bytes64);
try try
{ {
srcPhys = translateAddress(p.srcAddr); srcPhys = translateAddress(p.srcAddr);
@@ -1116,10 +1124,13 @@ void PS2Memory::processPendingTransfers()
{ {
continue; continue;
} }
if (srcPhys < PS2_RAM_SIZE) if (p.fromScratchpad)
{
if (srcPhys + sizeBytes <= PS2_SCRATCHPAD_SIZE && sizeBytes > 0u)
processVIF1Data(m_scratchpad + srcPhys, sizeBytes);
}
else if (srcPhys < PS2_RAM_SIZE)
{ {
const uint64_t bytes64 = static_cast<uint64_t>(p.qwc) * 16ull;
uint32_t sizeBytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast<uint32_t>(bytes64);
if (srcPhys + sizeBytes > PS2_RAM_SIZE) if (srcPhys + sizeBytes > PS2_RAM_SIZE)
sizeBytes = PS2_RAM_SIZE - srcPhys; sizeBytes = PS2_RAM_SIZE - srcPhys;
if (sizeBytes > 0) if (sizeBytes > 0)
+414 -63
View File
@@ -92,6 +92,7 @@ namespace
}; };
thread_local DispatchHistory g_dispatchHistory; thread_local DispatchHistory g_dispatchHistory;
thread_local std::unordered_map<PS2Runtime *, uint32_t> g_guestExecutionDepths;
void pushDispatchPc(uint32_t pc) void pushDispatchPc(uint32_t pc)
{ {
@@ -320,6 +321,40 @@ namespace
} }
} }
PS2Runtime::GuestExecutionScope::GuestExecutionScope(PS2Runtime *runtime) noexcept
: m_runtime(runtime)
{
if (m_runtime)
{
m_runtime->enterGuestExecution();
}
}
PS2Runtime::GuestExecutionScope::~GuestExecutionScope()
{
if (m_runtime)
{
m_runtime->leaveGuestExecution();
}
}
PS2Runtime::GuestExecutionReleaseScope::GuestExecutionReleaseScope(PS2Runtime *runtime) noexcept
: m_runtime(runtime)
{
if (m_runtime)
{
m_depth = m_runtime->releaseGuestExecution();
}
}
PS2Runtime::GuestExecutionReleaseScope::~GuestExecutionReleaseScope()
{
if (m_runtime && m_depth != 0u)
{
m_runtime->reacquireGuestExecution(m_depth);
}
}
static void UploadFrame(Texture2D &tex, PS2Runtime *rt) static void UploadFrame(Texture2D &tex, PS2Runtime *rt)
{ {
// For now lets keep the display snapshot in sync with rasterized VRAM so the host frame // For now lets keep the display snapshot in sync with rasterized VRAM so the host frame
@@ -348,75 +383,159 @@ static void UploadFrame(Texture2D &tex, PS2Runtime *rt)
if (height > FB_HEIGHT) if (height > FB_HEIGHT)
height = FB_HEIGHT; height = FB_HEIGHT;
uint32_t baseBytes = fbp * 8192u;
const uint32_t bytesPerPixel = (psm == 2u || psm == 0x0Au) ? 2u : 4u;
uint32_t strideBytes = (fbw ? fbw : (FB_WIDTH / 64)) * 64 * bytesPerPixel;
std::vector<uint8_t> scratch(FB_WIDTH * FB_HEIGHT * 4, 0);
uint8_t *rdram = rt->memory().getRDRAM(); uint8_t *rdram = rt->memory().getRDRAM();
uint8_t *gsvram = rt->memory().getGSVRAM(); uint8_t *gsvram = rt->memory().getGSVRAM();
uint32_t snapSize = 0; uint32_t snapSize = 0;
const uint8_t *snapVram = rt->gs().lockDisplaySnapshot(snapSize); const uint8_t *snapVram = rt->gs().lockDisplaySnapshot(snapSize);
const uint8_t *vramSrc = (snapVram && snapSize > 0) ? snapVram : gsvram; const uint8_t *vramSrc = (snapVram && snapSize > 0) ? snapVram : gsvram;
auto fillScratchFromFrame = [&](uint32_t srcFbp,
if (snapVram) uint32_t srcFbw,
uint32_t srcPsm,
std::vector<uint8_t> &outScratch) -> bool
{ {
baseBytes = rt->gs().getLastDisplayBaseBytes(); outScratch.assign(FB_WIDTH * FB_HEIGHT * 4u, 0u);
}
if (psm == 0u) const uint32_t baseBytes = srcFbp * 8192u;
const uint32_t bytesPerPixel = (srcPsm == 2u || srcPsm == 0x0Au) ? 2u : 4u;
const uint32_t strideBytes = (srcFbw ? srcFbw : (FB_WIDTH / 64u)) * 64u * bytesPerPixel;
if (srcPsm == 0u)
{
for (uint32_t y = 0; y < height; ++y)
{
uint32_t srcOff = baseBytes + y * strideBytes;
uint32_t dstOff = y * FB_WIDTH * 4u;
uint32_t copyW = width * 4u;
if (srcOff + copyW <= PS2_GS_VRAM_SIZE && vramSrc)
{
std::memcpy(&outScratch[dstOff], vramSrc + srcOff, copyW);
}
else
{
uint32_t rdramIdx = srcOff & PS2_RAM_MASK;
if (rdramIdx + copyW > PS2_RAM_SIZE)
{
copyW = PS2_RAM_SIZE - rdramIdx;
}
std::memcpy(&outScratch[dstOff], rdram + rdramIdx, copyW);
}
uint8_t *row = outScratch.data() + dstOff;
for (uint32_t x = 0; x < width; ++x)
{
row[x * 4u + 3u] = 255u;
}
}
return true;
}
if (srcPsm == 2u || srcPsm == 0x0Au)
{
const uint32_t srcLineBytes = width * 2u;
for (uint32_t y = 0; y < height; ++y)
{
uint32_t srcOff = baseBytes + y * strideBytes;
uint32_t dstOff = y * FB_WIDTH * 4u;
const uint8_t *src = nullptr;
if (srcOff + srcLineBytes <= PS2_GS_VRAM_SIZE && vramSrc)
{
src = vramSrc + srcOff;
}
else if ((srcOff & PS2_RAM_MASK) + srcLineBytes <= PS2_RAM_SIZE)
{
src = rdram + (srcOff & PS2_RAM_MASK);
}
if (!src)
{
continue;
}
uint8_t *dst = outScratch.data() + dstOff;
for (uint32_t x = 0; x < width; ++x)
{
uint16_t p = *reinterpret_cast<const uint16_t *>(src + x * 2u);
uint32_t r = (p >> 10) & 31u;
uint32_t g = (p >> 5) & 31u;
uint32_t b = p & 31u;
dst[x * 4u + 0u] = static_cast<uint8_t>((r << 3) | (r >> 2));
dst[x * 4u + 1u] = static_cast<uint8_t>((g << 3) | (g >> 2));
dst[x * 4u + 2u] = static_cast<uint8_t>((b << 3) | (b >> 2));
dst[x * 4u + 3u] = 255u;
}
}
return true;
}
return false;
};
auto analyzeScratch = [&](const std::vector<uint8_t> &scratchBuf,
uint32_t &outNonBlack,
uint32_t &outFirstColor,
uint32_t &outFirstX,
uint32_t &outFirstY)
{ {
outNonBlack = 0u;
outFirstColor = 0u;
outFirstX = 0u;
outFirstY = 0u;
for (uint32_t y = 0; y < height; ++y) for (uint32_t y = 0; y < height; ++y)
{ {
uint32_t srcOff = baseBytes + y * strideBytes; const uint8_t *row = scratchBuf.data() + y * FB_WIDTH * 4u;
uint32_t dstOff = y * FB_WIDTH * 4;
uint32_t copyW = width * 4;
uint32_t srcIdx = srcOff;
if (srcIdx + copyW <= PS2_GS_VRAM_SIZE && vramSrc)
std::memcpy(&scratch[dstOff], vramSrc + srcIdx, copyW);
else
{
uint32_t rdramIdx = srcOff & PS2_RAM_MASK;
if (rdramIdx + copyW > PS2_RAM_SIZE)
copyW = PS2_RAM_SIZE - rdramIdx;
std::memcpy(&scratch[dstOff], rdram + rdramIdx, copyW);
}
uint8_t *row = scratch.data() + dstOff;
for (uint32_t x = 0; x < width; ++x) for (uint32_t x = 0; x < width; ++x)
row[x * 4 + 3] = 255u; {
const uint8_t r = row[x * 4u + 0u];
const uint8_t g = row[x * 4u + 1u];
const uint8_t b = row[x * 4u + 2u];
if (r != 0u || g != 0u || b != 0u)
{
++outNonBlack;
if (outFirstColor == 0u)
{
outFirstColor = static_cast<uint32_t>(r) |
(static_cast<uint32_t>(g) << 8) |
(static_cast<uint32_t>(b) << 16);
outFirstX = x;
outFirstY = y;
}
}
}
} }
} };
else if (psm == 2u)
auto countLinearPageNonBlack = [&](uint32_t probeFbp) -> uint32_t
{ {
const uint32_t srcLineBytes = width * 2u; if (!vramSrc)
for (uint32_t y = 0; y < height; ++y)
{ {
uint32_t srcOff = baseBytes + y * strideBytes; return 0u;
uint32_t dstOff = y * FB_WIDTH * 4; }
const uint8_t *src = nullptr; const uint32_t probeBaseBytes = probeFbp * 8192u;
if (srcOff + srcLineBytes <= PS2_GS_VRAM_SIZE && vramSrc) const uint32_t probeStrideBytes = 10u * 64u * 4u;
src = vramSrc + srcOff; uint32_t count = 0u;
else if ((srcOff & PS2_RAM_MASK) + srcLineBytes <= PS2_RAM_SIZE) for (uint32_t py = 0; py < height; ++py)
src = rdram + (srcOff & PS2_RAM_MASK); {
if (!src) const uint32_t srcOff = probeBaseBytes + py * probeStrideBytes;
continue; if (srcOff + width * 4u > PS2_GS_VRAM_SIZE)
uint8_t *dst = scratch.data() + dstOff;
for (uint32_t x = 0; x < width; ++x)
{ {
uint16_t p = *reinterpret_cast<const uint16_t *>(src + x * 2); break;
uint32_t r = (p >> 10) & 31u; }
uint32_t g = (p >> 5) & 31u; const uint8_t *row = vramSrc + srcOff;
uint32_t b = p & 31u; for (uint32_t px = 0; px < width; ++px)
dst[x * 4 + 0] = static_cast<uint8_t>((r << 3) | (r >> 2)); {
dst[x * 4 + 1] = static_cast<uint8_t>((g << 3) | (g >> 2)); const uint8_t r = row[px * 4u + 0u];
dst[x * 4 + 2] = static_cast<uint8_t>((b << 3) | (b >> 2)); const uint8_t g = row[px * 4u + 1u];
dst[x * 4 + 3] = 255u; const uint8_t b = row[px * 4u + 2u];
if (r != 0u || g != 0u || b != 0u)
{
++count;
}
} }
} }
} return count;
else };
std::vector<uint8_t> scratch;
if (!fillScratchFromFrame(fbp, fbw, psm, scratch))
{ {
rt->gs().unlockDisplaySnapshot(); rt->gs().unlockDisplaySnapshot();
Image blank = GenImageColor(FB_WIDTH, FB_HEIGHT, MAGENTA); Image blank = GenImageColor(FB_WIDTH, FB_HEIGHT, MAGENTA);
@@ -425,8 +544,96 @@ static void UploadFrame(Texture2D &tex, PS2Runtime *rt)
return; return;
} }
uint32_t selectedFbp = fbp;
uint32_t selectedFbw = fbw;
uint32_t selectedPsm = psm;
uint32_t nonBlack = 0u;
uint32_t firstColor = 0u;
uint32_t firstX = 0u;
uint32_t firstY = 0u;
analyzeScratch(scratch, nonBlack, firstColor, firstX, firstY);
int fallbackContext = -1;
const bool allowFallbackPresentation = (fbp == 0u);
if (allowFallbackPresentation && nonBlack == 0u)
{
for (int contextIndex = 0; contextIndex < 2; ++contextIndex)
{
const GSFrameReg &candidate = rt->gs().getContextFrame(contextIndex);
if (candidate.fbp == selectedFbp &&
candidate.fbw == selectedFbw &&
candidate.psm == selectedPsm)
{
continue;
}
std::vector<uint8_t> candidateScratch;
if (!fillScratchFromFrame(candidate.fbp, candidate.fbw, candidate.psm, candidateScratch))
{
continue;
}
uint32_t candidateNonBlack = 0u;
uint32_t candidateFirstColor = 0u;
uint32_t candidateFirstX = 0u;
uint32_t candidateFirstY = 0u;
analyzeScratch(candidateScratch, candidateNonBlack, candidateFirstColor, candidateFirstX, candidateFirstY);
if (candidateNonBlack == 0u)
{
continue;
}
scratch.swap(candidateScratch);
selectedFbp = candidate.fbp;
selectedFbw = candidate.fbw;
selectedPsm = candidate.psm;
nonBlack = candidateNonBlack;
firstColor = candidateFirstColor;
firstX = candidateFirstX;
firstY = candidateFirstY;
fallbackContext = contextIndex;
break;
}
}
rt->gs().unlockDisplaySnapshot(); rt->gs().unlockDisplaySnapshot();
static uint32_t s_uploadDebugCount = 0u;
static uint32_t s_lastLoggedFbp = std::numeric_limits<uint32_t>::max();
static uint32_t s_lastLoggedNonBlack = std::numeric_limits<uint32_t>::max();
const bool shouldProbe = (s_uploadDebugCount < 96u) || (fbp != s_lastLoggedFbp);
if (shouldProbe || fallbackContext >= 0)
{
if (s_uploadDebugCount < 96u || selectedFbp != s_lastLoggedFbp || nonBlack != s_lastLoggedNonBlack || fallbackContext >= 0)
{
const uint32_t page0NonBlack = countLinearPageNonBlack(0u);
const uint32_t page150NonBlack = countLinearPageNonBlack(150u);
std::cout << "[frame:upload] idx=" << s_uploadDebugCount
<< " fbp=" << selectedFbp
<< " fbw=" << selectedFbw
<< " psm=0x" << std::hex << selectedPsm << std::dec
<< " size=" << width << "x" << height
<< " nonBlack=" << nonBlack
<< " page0=" << page0NonBlack
<< " page150=" << page150NonBlack
<< " allowFallback=" << static_cast<uint32_t>(allowFallbackPresentation ? 1u : 0u);
if (fallbackContext >= 0)
{
std::cout << " displayFbp=" << fbp
<< " fallbackCtx=" << fallbackContext;
}
if (firstColor != 0u)
{
std::cout << " first=(" << firstX << "," << firstY << ")"
<< " rgb=0x" << std::hex << firstColor << std::dec;
}
std::cout << std::endl;
}
s_lastLoggedFbp = selectedFbp;
s_lastLoggedNonBlack = nonBlack;
++s_uploadDebugCount;
}
UpdateTexture(tex, scratch.data()); UpdateTexture(tex, scratch.data());
} }
@@ -448,11 +655,14 @@ PS2Runtime::PS2Runtime()
m_guestHeapLimit = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE); m_guestHeapLimit = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE);
m_guestHeapSuggestedBase = kGuestHeapDefaultBase; m_guestHeapSuggestedBase = kGuestHeapDefaultBase;
m_guestHeapConfigured = false; m_guestHeapConfigured = false;
m_asyncCallbackStackFloor = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE);
m_asyncCallbackStackTop = PS2_RAM_SIZE;
} }
PS2Runtime::~PS2Runtime() PS2Runtime::~PS2Runtime()
{ {
requestStop(); requestStop();
ps2_syscalls::detachAllGuestHostThreads();
if (IsWindowReady()) if (IsWindowReady())
{ {
CloseWindow(); CloseWindow();
@@ -473,13 +683,17 @@ bool PS2Runtime::initialize(const char *title)
m_gs.init(m_memory.getGSVRAM(), static_cast<uint32_t>(PS2_GS_VRAM_SIZE), &m_memory.gs()); m_gs.init(m_memory.getGSVRAM(), static_cast<uint32_t>(PS2_GS_VRAM_SIZE), &m_memory.gs());
m_gs.reset(); m_gs.reset();
m_gifArbiter.setProcessPacketFn([this](const uint8_t *data, uint32_t size) { m_gs.processGIFPacket(data, size); }); m_gifArbiter.setProcessPacketFn([this](const uint8_t *data, uint32_t size)
{ m_gs.processGIFPacket(data, size); });
m_memory.setGifArbiter(&m_gifArbiter); m_memory.setGifArbiter(&m_gifArbiter);
m_memory.setVu1MscalCallback([this](uint32_t startPC, uint32_t itop) { m_memory.setVu1MscalCallback([this](uint32_t startPC, uint32_t itop)
m_vu1.execute(m_memory.getVU1Code(), PS2_VU1_CODE_SIZE, { m_vu1.execute(m_memory.getVU1Code(), PS2_VU1_CODE_SIZE,
m_memory.getVU1Data(), PS2_VU1_DATA_SIZE, m_memory.getVU1Data(), PS2_VU1_DATA_SIZE,
m_gs, &m_memory, startPC, itop, 65536); m_gs, &m_memory, startPC, itop, 65536); });
}); m_memory.setVu1MscntCallback([this](uint32_t itop)
{ m_vu1.resume(m_memory.getVU1Code(), PS2_VU1_CODE_SIZE,
m_memory.getVU1Data(), PS2_VU1_DATA_SIZE,
m_gs, &m_memory, itop, 65536); });
m_iop.init(m_memory.getRDRAM()); m_iop.init(m_memory.getRDRAM());
m_iop.reset(); m_iop.reset();
@@ -707,6 +921,12 @@ bool PS2Runtime::loadELF(const std::string &elfPath)
m_guestHeapLimit = hardLimit; m_guestHeapLimit = hardLimit;
} }
} }
{
std::lock_guard<std::mutex> lock(m_asyncCallbackStackMutex);
const uint32_t hardLimit = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE);
m_asyncCallbackStackFloor = std::min(std::max(hardLimit, suggestedHeapBase), PS2_RAM_SIZE);
m_asyncCallbackStackTop = PS2_RAM_SIZE;
}
LoadedModule module; LoadedModule module;
module.name = elfPath.substr(elfPath.find_last_of("/\\") + 1); module.name = elfPath.substr(elfPath.find_last_of("/\\") + 1);
@@ -784,7 +1004,19 @@ void PS2Runtime::registerFunction(uint32_t address, RecompiledFunction func)
bool PS2Runtime::hasFunction(uint32_t address) const bool PS2Runtime::hasFunction(uint32_t address) const
{ {
return m_functionTable.find(address) != m_functionTable.end(); auto it = m_functionTable.find(address);
if (it != m_functionTable.end())
{
return true;
}
if (address == 0x2913E4u)
{
auto parent = m_functionTable.find(0x2913B0u);
return parent != m_functionTable.end();
}
return false;
} }
PS2Runtime::RecompiledFunction PS2Runtime::lookupFunction(uint32_t address) PS2Runtime::RecompiledFunction PS2Runtime::lookupFunction(uint32_t address)
@@ -797,9 +1029,6 @@ PS2Runtime::RecompiledFunction PS2Runtime::lookupFunction(uint32_t address)
return it->second; return it->second;
} }
// Some games dispatch to internal basic-block addresses that belong to a
// larger recompiled function. Map known hot-path aliases to their parent
// function entry so execution can resume from the current ctx->pc.
if (address == 0x2913E4u) if (address == 0x2913E4u)
{ {
auto parent = m_functionTable.find(0x2913B0u); auto parent = m_functionTable.find(0x2913B0u);
@@ -1499,6 +1728,44 @@ uint32_t PS2Runtime::guestHeapEnd() const
return m_guestHeapConfigured ? m_guestHeapEnd : m_guestHeapSuggestedBase; return m_guestHeapConfigured ? m_guestHeapEnd : m_guestHeapSuggestedBase;
} }
uint32_t PS2Runtime::reserveAsyncCallbackStack(uint32_t size, uint32_t alignment)
{
if (size == 0u)
{
return 0u;
}
const uint32_t normalizedAlignment = normalizeGuestHeapAlignment(alignment);
const uint32_t allocSize = alignGuestHeapValue(size, kGuestHeapDefaultAlignment);
if (allocSize == 0u)
{
return 0u;
}
std::lock_guard<std::mutex> lock(m_asyncCallbackStackMutex);
uint32_t top = m_asyncCallbackStackTop;
if (top > PS2_RAM_SIZE)
{
top = PS2_RAM_SIZE;
}
top &= ~(kGuestHeapDefaultAlignment - 1u);
if (top <= allocSize)
{
return 0u;
}
uint32_t base = top - allocSize;
base &= ~(normalizedAlignment - 1u);
if (base < m_asyncCallbackStackFloor || base >= top)
{
return 0u;
}
m_asyncCallbackStackTop = base;
return top - 0x10u;
}
void PS2Runtime::dispatchLoop(uint8_t *rdram, R5900Context *ctx) void PS2Runtime::dispatchLoop(uint8_t *rdram, R5900Context *ctx)
{ {
uint32_t lastPc = std::numeric_limits<uint32_t>::max(); uint32_t lastPc = std::numeric_limits<uint32_t>::max();
@@ -1533,7 +1800,10 @@ void PS2Runtime::dispatchLoop(uint8_t *rdram, R5900Context *ctx)
const uint32_t dispatchedPc = pc; const uint32_t dispatchedPc = pc;
const uint32_t dispatchedRa = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0)); const uint32_t dispatchedRa = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0));
fn(rdram, ctx, this); {
GuestExecutionScope guestExecution(this);
fn(rdram, ctx, this);
}
if (ctx->pc == 0u) if (ctx->pc == 0u)
{ {
@@ -1555,6 +1825,77 @@ void PS2Runtime::dispatchLoop(uint8_t *rdram, R5900Context *ctx)
} }
} }
void PS2Runtime::enterGuestExecution()
{
m_guestExecutionWaiters.fetch_add(1u, std::memory_order_acq_rel);
m_guestExecutionMutex.lock();
m_guestExecutionWaiters.fetch_sub(1u, std::memory_order_acq_rel);
++g_guestExecutionDepths[this];
}
void PS2Runtime::leaveGuestExecution()
{
auto it = g_guestExecutionDepths.find(this);
if (it == g_guestExecutionDepths.end() || it->second == 0u)
{
return;
}
--it->second;
m_guestExecutionMutex.unlock();
if (it->second == 0u)
{
g_guestExecutionDepths.erase(it);
}
}
uint32_t PS2Runtime::releaseGuestExecution()
{
auto it = g_guestExecutionDepths.find(this);
if (it == g_guestExecutionDepths.end() || it->second == 0u)
{
return 0u;
}
const uint32_t depth = it->second;
for (uint32_t i = 0; i < depth; ++i)
{
m_guestExecutionMutex.unlock();
}
g_guestExecutionDepths.erase(it);
return depth;
}
void PS2Runtime::reacquireGuestExecution(uint32_t depth)
{
if (depth == 0u)
{
return;
}
uint32_t &heldDepth = g_guestExecutionDepths[this];
for (uint32_t i = 0; i < depth; ++i)
{
m_guestExecutionWaiters.fetch_add(1u, std::memory_order_acq_rel);
m_guestExecutionMutex.lock();
m_guestExecutionWaiters.fetch_sub(1u, std::memory_order_acq_rel);
++heldDepth;
}
}
void PS2Runtime::cooperativeGuestYield()
{
GuestExecutionReleaseScope release(this);
if (m_guestExecutionWaiters.load(std::memory_order_acquire) != 0u)
{
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
else
{
std::this_thread::yield();
}
}
uint8_t PS2Runtime::Load8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr) uint8_t PS2Runtime::Load8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr)
{ {
try try
@@ -1752,7 +2093,6 @@ void PS2Runtime::run()
while (!isStopRequested() && g_activeThreads.load(std::memory_order_relaxed) > 0) while (!isStopRequested() && g_activeThreads.load(std::memory_order_relaxed) > 0)
{ {
tick++; tick++;
ps2_stubs::dispatchGsSyncVCallback(m_memory.getRDRAM(), this);
if ((tick % 120) == 0) if ((tick % 120) == 0)
{ {
uint64_t curDma = m_memory.dmaStartCount(); uint64_t curDma = m_memory.dmaStartCount();
@@ -1880,6 +2220,17 @@ void PS2Runtime::run()
} }
} }
if (g_activeThreads.load(std::memory_order_relaxed) == 0)
{
ps2_syscalls::joinAllGuestHostThreads();
}
else
{
std::cerr << "[run] guest host threads did not stop within timeout; detaching remaining worker threads"
<< std::endl;
ps2_syscalls::detachAllGuestHostThreads();
}
UnloadTexture(frameTex); UnloadTexture(frameTex);
CloseWindow(); CloseWindow();
+38 -8
View File
@@ -83,6 +83,18 @@ namespace ps2_syscalls
case 0x17: case 0x17:
DisableDmac(rdram, ctx, runtime); DisableDmac(rdram, ctx, runtime);
return true; return true;
case static_cast<uint32_t>(-0x1A):
iEnableIntc(rdram, ctx, runtime);
return true;
case static_cast<uint32_t>(-0x1B):
iDisableIntc(rdram, ctx, runtime);
return true;
case static_cast<uint32_t>(-0x1C):
iEnableDmac(rdram, ctx, runtime);
return true;
case static_cast<uint32_t>(-0x1D):
iDisableDmac(rdram, ctx, runtime);
return true;
case 0x18: case 0x18:
case 0xFC: case 0xFC:
SetAlarm(rdram, ctx, runtime); SetAlarm(rdram, ctx, runtime);
@@ -119,13 +131,17 @@ namespace ps2_syscalls
TerminateThread(rdram, ctx, runtime); TerminateThread(rdram, ctx, runtime);
return true; return true;
case 0x29: case 0x29:
case static_cast<uint32_t>(-0x2A):
ChangeThreadPriority(rdram, ctx, runtime); ChangeThreadPriority(rdram, ctx, runtime);
return true; return true;
case static_cast<uint32_t>(-0x2A):
iChangeThreadPriority(rdram, ctx, runtime);
return true;
case 0x2B: case 0x2B:
case static_cast<uint32_t>(-0x2C):
RotateThreadReadyQueue(rdram, ctx, runtime); RotateThreadReadyQueue(rdram, ctx, runtime);
return true; return true;
case static_cast<uint32_t>(-0x2C):
iRotateThreadReadyQueue(rdram, ctx, runtime);
return true;
case 0x2D: case 0x2D:
ReleaseWaitThread(rdram, ctx, runtime); ReleaseWaitThread(rdram, ctx, runtime);
return true; return true;
@@ -137,9 +153,11 @@ namespace ps2_syscalls
GetThreadId(rdram, ctx, runtime); GetThreadId(rdram, ctx, runtime);
return true; return true;
case 0x30: case 0x30:
case static_cast<uint32_t>(-0x31):
ReferThreadStatus(rdram, ctx, runtime); ReferThreadStatus(rdram, ctx, runtime);
return true; return true;
case static_cast<uint32_t>(-0x31):
iReferThreadStatus(rdram, ctx, runtime);
return true;
case 0x32: case 0x32:
SleepThread(rdram, ctx, runtime); SleepThread(rdram, ctx, runtime);
return true; return true;
@@ -217,7 +235,7 @@ namespace ps2_syscalls
case 0x52: case 0x52:
SetEventFlag(rdram, ctx, runtime); SetEventFlag(rdram, ctx, runtime);
return true; return true;
case 0x53: case static_cast<uint32_t>(-0x53):
iSetEventFlag(rdram, ctx, runtime); iSetEventFlag(rdram, ctx, runtime);
return true; return true;
case 0x54: case 0x54:
@@ -267,13 +285,17 @@ namespace ps2_syscalls
FlushCache(rdram, ctx, runtime); FlushCache(rdram, ctx, runtime);
return true; return true;
case 0x70: case 0x70:
case static_cast<uint32_t>(-0x70):
GsGetIMR(rdram, ctx, runtime); GsGetIMR(rdram, ctx, runtime);
return true; return true;
case static_cast<uint32_t>(-0x70):
iGsGetIMR(rdram, ctx, runtime);
return true;
case 0x71: case 0x71:
case static_cast<uint32_t>(-0x71):
GsPutIMR(rdram, ctx, runtime); GsPutIMR(rdram, ctx, runtime);
return true; return true;
case static_cast<uint32_t>(-0x71):
iGsPutIMR(rdram, ctx, runtime);
return true;
case 0x73: case 0x73:
SetVSyncFlag(rdram, ctx, runtime); SetVSyncFlag(rdram, ctx, runtime);
return true; return true;
@@ -352,8 +374,6 @@ namespace ps2_syscalls
threadInfo->cv.notify_all(); threadInfo->cv.notify_all();
} }
joinAllHostThreads();
std::vector<std::shared_ptr<SemaInfo>> semas; std::vector<std::shared_ptr<SemaInfo>> semas;
{ {
std::lock_guard<std::mutex> lock(g_sema_map_mutex); std::lock_guard<std::mutex> lock(g_sema_map_mutex);
@@ -407,4 +427,14 @@ namespace ps2_syscalls
g_syscall_overrides.clear(); g_syscall_overrides.clear();
} }
} }
void joinAllGuestHostThreads()
{
joinAllHostThreads();
}
void detachAllGuestHostThreads()
{
detachAllHostThreads();
}
} }
+54 -5
View File
@@ -1,7 +1,9 @@
// Based on Blackline Interactive implementation // Based on Blackline Interactive implementation
#include "ps2_memory.h" #include "ps2_memory.h"
#include <atomic>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
#include <iostream>
enum VIFCmd : uint8_t enum VIFCmd : uint8_t
{ {
@@ -27,6 +29,12 @@ enum VIFCmd : uint8_t
VIF_DIRECTHL = 0x51, VIF_DIRECTHL = 0x51,
}; };
namespace
{
std::atomic<uint32_t> s_debugVu1KickCount{0};
std::atomic<uint32_t> s_debugVif1OpcodeCount{0};
}
void PS2Memory::processVIF1Data(uint32_t srcPhys, uint32_t sizeBytes) void PS2Memory::processVIF1Data(uint32_t srcPhys, uint32_t sizeBytes)
{ {
if (!m_rdram || !m_gsVRAM || sizeBytes == 0u) if (!m_rdram || !m_gsVRAM || sizeBytes == 0u)
@@ -67,6 +75,18 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes)
uint8_t num = (cmd >> 16) & 0xFF; uint8_t num = (cmd >> 16) & 0xFF;
const bool irq = (cmd & 0x80000000u) != 0u; const bool irq = (cmd & 0x80000000u) != 0u;
const uint32_t opcodeIndex = s_debugVif1OpcodeCount.fetch_add(1, std::memory_order_relaxed);
if (opcodeIndex < 160u)
{
std::cout << "[vif1:cmd] idx=" << opcodeIndex
<< " opcode=0x" << std::hex << static_cast<uint32_t>(opcode)
<< " imm=0x" << imm
<< std::dec
<< " num=" << static_cast<uint32_t>(num)
<< " irq=" << static_cast<uint32_t>(irq ? 1u : 0u)
<< std::endl;
}
// Track most-recent command for VIFn_CODE emulation. // Track most-recent command for VIFn_CODE emulation.
vif1_regs.code = cmd; vif1_regs.code = cmd;
vif1_regs.num = num; vif1_regs.num = num;
@@ -132,6 +152,16 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes)
vif1_regs.stat ^= (1u << 7); // toggle DBF vif1_regs.stat ^= (1u << 7); // toggle DBF
recomputeVif1Tops(); recomputeVif1Tops();
uint32_t startPC = (uint32_t)imm * 8u; uint32_t startPC = (uint32_t)imm * 8u;
const uint32_t kickIndex = s_debugVu1KickCount.fetch_add(1, std::memory_order_relaxed);
if (kickIndex < 48u)
{
std::cout << "[vif1:mscal] idx=" << kickIndex
<< " opcode=0x" << std::hex << static_cast<uint32_t>(opcode)
<< " imm=0x" << imm
<< " startPc=0x" << startPC
<< " itop=0x" << vif1_regs.itop
<< std::dec << std::endl;
}
if (m_vu1MscalCallback) if (m_vu1MscalCallback)
m_vu1MscalCallback(startPC, vif1_regs.itop); m_vu1MscalCallback(startPC, vif1_regs.itop);
continue; continue;
@@ -141,6 +171,16 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes)
vif1_regs.itops = vif1_regs.itop & 0x3FFu; vif1_regs.itops = vif1_regs.itop & 0x3FFu;
vif1_regs.stat ^= (1u << 7); // toggle DBF vif1_regs.stat ^= (1u << 7); // toggle DBF
recomputeVif1Tops(); recomputeVif1Tops();
const uint32_t kickIndex = s_debugVu1KickCount.fetch_add(1, std::memory_order_relaxed);
if (kickIndex < 48u)
{
std::cout << "[vif1:mscnt] idx=" << kickIndex
<< " itop=0x" << std::hex << vif1_regs.itop
<< " pc=resume"
<< std::dec << std::endl;
}
if (m_vu1MscntCallback)
m_vu1MscntCallback(vif1_regs.itop);
continue; continue;
} }
else if (opcode == VIF_STMASK) else if (opcode == VIF_STMASK)
@@ -222,11 +262,20 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes)
int bitsPerComponent = 32; int bitsPerComponent = 32;
switch (vl) switch (vl)
{ {
case 0: bitsPerComponent = 32; break; case 0:
case 1: bitsPerComponent = 16; break; bitsPerComponent = 32;
case 2: bitsPerComponent = 8; break; break;
case 3: bitsPerComponent = (vn == 3) ? 4 : 16; break; case 1:
default: break; bitsPerComponent = 16;
break;
case 2:
bitsPerComponent = 8;
break;
case 3:
bitsPerComponent = (vn == 3) ? 4 : 16;
break;
default:
break;
} }
int bitsPerVector = (vl == 3 && vn == 3) ? 16 : (components * bitsPerComponent); int bitsPerVector = (vl == 3 && vn == 3) ? 16 : (components * bitsPerComponent);
uint32_t bytesPerVector = (bitsPerVector + 7) / 8; uint32_t bytesPerVector = (bitsPerVector + 7) / 8;
+346 -146
View File
@@ -2,25 +2,33 @@
#include "ps2_gs_gpu.h" #include "ps2_gs_gpu.h"
#include "ps2_gif_arbiter.h" #include "ps2_gif_arbiter.h"
#include "ps2_memory.h" #include "ps2_memory.h"
#include <atomic>
#include <cmath> #include <cmath>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <iostream>
#include <limits> #include <limits>
#include <vector> #include <vector>
namespace
{
std::atomic<uint32_t> s_debugVu1XgkickCount{0};
}
// Instruction field extraction helpers // Instruction field extraction helpers
static inline uint8_t DEST(uint32_t i) { return (uint8_t)((i >> 21) & 0xF); } static inline uint8_t DEST(uint32_t i) { return (uint8_t)((i >> 21) & 0xF); }
static inline uint8_t FT(uint32_t i) { return (uint8_t)((i >> 16) & 0x1F); } static inline uint8_t FT(uint32_t i) { return (uint8_t)((i >> 16) & 0x1F); }
static inline uint8_t FS(uint32_t i) { return (uint8_t)((i >> 11) & 0x1F); } static inline uint8_t FS(uint32_t i) { return (uint8_t)((i >> 11) & 0x1F); }
static inline uint8_t FD(uint32_t i) { return (uint8_t)((i >> 6) & 0x1F); } static inline uint8_t FD(uint32_t i) { return (uint8_t)((i >> 6) & 0x1F); }
static inline uint8_t BC(uint32_t i) { return (uint8_t)(i & 0x3); } static inline uint8_t BC(uint32_t i) { return (uint8_t)(i & 0x3); }
// Lower instruction field helpers // Lower instruction field helpers
static inline uint8_t LIT(uint32_t i) { return (uint8_t)((i >> 16) & 0x1F); } static inline uint8_t LIT(uint32_t i) { return (uint8_t)((i >> 16) & 0x1F); }
static inline uint8_t LIS(uint32_t i) { return (uint8_t)((i >> 11) & 0x1F); } static inline uint8_t LIS(uint32_t i) { return (uint8_t)((i >> 11) & 0x1F); }
static inline uint8_t LID(uint32_t i) { return (uint8_t)((i >> 6) & 0x1F); } static inline uint8_t LID(uint32_t i) { return (uint8_t)((i >> 6) & 0x1F); }
static inline int16_t IMM11(uint32_t i){ return (int16_t)(int32_t)((int32_t)(i << 21) >> 21); } static inline int16_t IMM11(uint32_t i) { return (int16_t)(int32_t)((int32_t)(i << 21) >> 21); }
static inline int16_t IMM15(uint32_t i){ static inline int16_t IMM15(uint32_t i)
{
uint32_t lo11 = i & 0x7FF; uint32_t lo11 = i & 0x7FF;
uint32_t hi4 = (i >> 21) & 0xF; uint32_t hi4 = (i >> 21) & 0xF;
uint32_t raw = (hi4 << 11) | lo11; uint32_t raw = (hi4 << 11) | lo11;
@@ -46,10 +54,14 @@ float VU1Interpreter::broadcast(const float *vf, uint8_t bc)
void VU1Interpreter::applyDest(float *dst, const float *result, uint8_t dest) void VU1Interpreter::applyDest(float *dst, const float *result, uint8_t dest)
{ {
if (dest & 0x8) dst[0] = result[0]; // x if (dest & 0x8)
if (dest & 0x4) dst[1] = result[1]; // y dst[0] = result[0]; // x
if (dest & 0x2) dst[2] = result[2]; // z if (dest & 0x4)
if (dest & 0x1) dst[3] = result[3]; // w dst[1] = result[1]; // y
if (dest & 0x2)
dst[2] = result[2]; // z
if (dest & 0x1)
dst[3] = result[3]; // w
} }
void VU1Interpreter::applyDestAcc(const float *result, uint8_t dest) void VU1Interpreter::applyDestAcc(const float *result, uint8_t dest)
@@ -58,10 +70,10 @@ void VU1Interpreter::applyDestAcc(const float *result, uint8_t dest)
} }
void VU1Interpreter::execute(uint8_t *vuCode, uint32_t codeSize, void VU1Interpreter::execute(uint8_t *vuCode, uint32_t codeSize,
uint8_t *vuData, uint32_t dataSize, uint8_t *vuData, uint32_t dataSize,
GS &gs, PS2Memory *memory, GS &gs, PS2Memory *memory,
uint32_t startPC, uint32_t itop, uint32_t startPC, uint32_t itop,
uint32_t maxCycles) uint32_t maxCycles)
{ {
m_state.pc = startPC; m_state.pc = startPC;
m_state.ebit = false; m_state.ebit = false;
@@ -74,9 +86,9 @@ void VU1Interpreter::execute(uint8_t *vuCode, uint32_t codeSize,
} }
void VU1Interpreter::resume(uint8_t *vuCode, uint32_t codeSize, void VU1Interpreter::resume(uint8_t *vuCode, uint32_t codeSize,
uint8_t *vuData, uint32_t dataSize, uint8_t *vuData, uint32_t dataSize,
GS &gs, PS2Memory *memory, GS &gs, PS2Memory *memory,
uint32_t itop, uint32_t maxCycles) uint32_t itop, uint32_t maxCycles)
{ {
m_state.ebit = false; m_state.ebit = false;
m_state.itop = itop; m_state.itop = itop;
@@ -84,8 +96,8 @@ void VU1Interpreter::resume(uint8_t *vuCode, uint32_t codeSize,
} }
void VU1Interpreter::run(uint8_t *vuCode, uint32_t codeSize, void VU1Interpreter::run(uint8_t *vuCode, uint32_t codeSize,
uint8_t *vuData, uint32_t dataSize, uint8_t *vuData, uint32_t dataSize,
GS &gs, PS2Memory *memory, uint32_t maxCycles) GS &gs, PS2Memory *memory, uint32_t maxCycles)
{ {
for (uint32_t cycle = 0; cycle < maxCycles; ++cycle) for (uint32_t cycle = 0; cycle < maxCycles; ++cycle)
{ {
@@ -122,7 +134,8 @@ void VU1Interpreter::run(uint8_t *vuCode, uint32_t codeSize,
m_state.vi[0] = 0; m_state.vi[0] = 0;
uint32_t nextPC = m_state.pc + 8; uint32_t nextPC = m_state.pc + 8;
if (nextPC >= codeSize) nextPC = 0; if (nextPC >= codeSize)
nextPC = 0;
m_state.pc = nextPC; m_state.pc = nextPC;
if (m_state.ebit) if (m_state.ebit)
@@ -152,125 +165,171 @@ void VU1Interpreter::execUpper(uint32_t instr)
// Upper opcode decoding (bits 5:0 of upper word) // Upper opcode decoding (bits 5:0 of upper word)
switch (op) switch (op)
{ {
case 0x00: case 0x01: case 0x02: case 0x03: // ADDbc case 0x00:
case 0x01:
case 0x02:
case 0x03: // ADDbc
{ {
float bc = broadcast(vt, op & 3); float bc = broadcast(vt, op & 3);
for (int c = 0; c < 4; c++) result[c] = vs[c] + bc; for (int c = 0; c < 4; c++)
result[c] = vs[c] + bc;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
} }
case 0x04: case 0x05: case 0x06: case 0x07: // SUBbc case 0x04:
case 0x05:
case 0x06:
case 0x07: // SUBbc
{ {
float bc = broadcast(vt, op & 3); float bc = broadcast(vt, op & 3);
for (int c = 0; c < 4; c++) result[c] = vs[c] - bc; for (int c = 0; c < 4; c++)
result[c] = vs[c] - bc;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
} }
case 0x08: case 0x09: case 0x0A: case 0x0B: // MADDbc case 0x08:
case 0x09:
case 0x0A:
case 0x0B: // MADDbc
{ {
float bc = broadcast(vt, op & 3); float bc = broadcast(vt, op & 3);
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] + vs[c] * bc; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * bc;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
} }
case 0x0C: case 0x0D: case 0x0E: case 0x0F: // MSUBbc case 0x0C:
case 0x0D:
case 0x0E:
case 0x0F: // MSUBbc
{ {
float bc = broadcast(vt, op & 3); float bc = broadcast(vt, op & 3);
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] - vs[c] * bc; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * bc;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
} }
case 0x10: case 0x11: case 0x12: case 0x13: // MAXbc case 0x10:
case 0x11:
case 0x12:
case 0x13: // MAXbc
{ {
float bc = broadcast(vt, op & 3); float bc = broadcast(vt, op & 3);
for (int c = 0; c < 4; c++) result[c] = (vs[c] > bc) ? vs[c] : bc; for (int c = 0; c < 4; c++)
result[c] = (vs[c] > bc) ? vs[c] : bc;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
} }
case 0x14: case 0x15: case 0x16: case 0x17: // MINIbc case 0x14:
case 0x15:
case 0x16:
case 0x17: // MINIbc
{ {
float bc = broadcast(vt, op & 3); float bc = broadcast(vt, op & 3);
for (int c = 0; c < 4; c++) result[c] = (vs[c] < bc) ? vs[c] : bc; for (int c = 0; c < 4; c++)
result[c] = (vs[c] < bc) ? vs[c] : bc;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
} }
case 0x18: case 0x19: case 0x1A: case 0x1B: // MULbc case 0x18:
case 0x19:
case 0x1A:
case 0x1B: // MULbc
{ {
float bc = broadcast(vt, op & 3); float bc = broadcast(vt, op & 3);
for (int c = 0; c < 4; c++) result[c] = vs[c] * bc; for (int c = 0; c < 4; c++)
result[c] = vs[c] * bc;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
} }
case 0x1C: // MULq case 0x1C: // MULq
for (int c = 0; c < 4; c++) result[c] = vs[c] * m_state.q; for (int c = 0; c < 4; c++)
result[c] = vs[c] * m_state.q;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x1D: // MAXi case 0x1D: // MAXi
for (int c = 0; c < 4; c++) result[c] = (vs[c] > m_state.i) ? vs[c] : m_state.i; for (int c = 0; c < 4; c++)
result[c] = (vs[c] > m_state.i) ? vs[c] : m_state.i;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x1E: // MULi case 0x1E: // MULi
for (int c = 0; c < 4; c++) result[c] = vs[c] * m_state.i; for (int c = 0; c < 4; c++)
result[c] = vs[c] * m_state.i;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x1F: // MINIi case 0x1F: // MINIi
for (int c = 0; c < 4; c++) result[c] = (vs[c] < m_state.i) ? vs[c] : m_state.i; for (int c = 0; c < 4; c++)
result[c] = (vs[c] < m_state.i) ? vs[c] : m_state.i;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x20: // ADDq case 0x20: // ADDq
for (int c = 0; c < 4; c++) result[c] = vs[c] + m_state.q; for (int c = 0; c < 4; c++)
result[c] = vs[c] + m_state.q;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x21: // MADDq case 0x21: // MADDq
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] + vs[c] * m_state.q; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * m_state.q;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x22: // ADDi case 0x22: // ADDi
for (int c = 0; c < 4; c++) result[c] = vs[c] + m_state.i; for (int c = 0; c < 4; c++)
result[c] = vs[c] + m_state.i;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x23: // MADDi case 0x23: // MADDi
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] + vs[c] * m_state.i; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * m_state.i;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x24: // SUBq case 0x24: // SUBq
for (int c = 0; c < 4; c++) result[c] = vs[c] - m_state.q; for (int c = 0; c < 4; c++)
result[c] = vs[c] - m_state.q;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x25: // MSUBq case 0x25: // MSUBq
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] - vs[c] * m_state.q; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * m_state.q;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x26: // SUBi case 0x26: // SUBi
for (int c = 0; c < 4; c++) result[c] = vs[c] - m_state.i; for (int c = 0; c < 4; c++)
result[c] = vs[c] - m_state.i;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x27: // MSUBi case 0x27: // MSUBi
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] - vs[c] * m_state.i; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * m_state.i;
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x28: // ADD case 0x28: // ADD
for (int c = 0; c < 4; c++) result[c] = vs[c] + vt[c]; for (int c = 0; c < 4; c++)
result[c] = vs[c] + vt[c];
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x29: // MADD case 0x29: // MADD
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] + vs[c] * vt[c]; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * vt[c];
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x2A: // MUL case 0x2A: // MUL
for (int c = 0; c < 4; c++) result[c] = vs[c] * vt[c]; for (int c = 0; c < 4; c++)
result[c] = vs[c] * vt[c];
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x2B: // MAX case 0x2B: // MAX
for (int c = 0; c < 4; c++) result[c] = (vs[c] > vt[c]) ? vs[c] : vt[c]; for (int c = 0; c < 4; c++)
result[c] = (vs[c] > vt[c]) ? vs[c] : vt[c];
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x2C: // SUB case 0x2C: // SUB
for (int c = 0; c < 4; c++) result[c] = vs[c] - vt[c]; for (int c = 0; c < 4; c++)
result[c] = vs[c] - vt[c];
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x2D: // MSUB case 0x2D: // MSUB
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] - vs[c] * vt[c]; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * vt[c];
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x2E: // OPMSUB case 0x2E: // OPMSUB
@@ -281,12 +340,16 @@ void VU1Interpreter::execUpper(uint32_t instr)
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x2F: // MINI case 0x2F: // MINI
for (int c = 0; c < 4; c++) result[c] = (vs[c] < vt[c]) ? vs[c] : vt[c]; for (int c = 0; c < 4; c++)
result[c] = (vs[c] < vt[c]) ? vs[c] : vt[c];
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
// Special1 group (0x3C..0x3F with secondary field) // Special1 group (0x3C..0x3F with secondary field)
case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x3C:
case 0x3D:
case 0x3E:
case 0x3F:
{ {
uint8_t special = (instr >> 6) & 0x1F; uint8_t special = (instr >> 6) & 0x1F;
uint8_t sop = (instr & 0x3) | ((instr >> 4) & 0x3C); uint8_t sop = (instr & 0x3) | ((instr >> 4) & 0x3C);
@@ -301,95 +364,160 @@ void VU1Interpreter::execUpper(uint32_t instr)
(void)bc2; (void)bc2;
switch (funct) switch (funct)
{ {
case 0x00: case 0x01: case 0x02: case 0x03: // ADDAbc case 0x00:
case 0x01:
case 0x02:
case 0x03: // ADDAbc
{ {
float bc = broadcast(vt, funct & 3); float bc = broadcast(vt, funct & 3);
for (int c = 0; c < 4; c++) result[c] = vs[c] + bc; for (int c = 0; c < 4; c++)
result[c] = vs[c] + bc;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
} }
case 0x04: case 0x05: case 0x06: case 0x07: // SUBAbc case 0x04:
case 0x05:
case 0x06:
case 0x07: // SUBAbc
{ {
float bc = broadcast(vt, funct & 3); float bc = broadcast(vt, funct & 3);
for (int c = 0; c < 4; c++) result[c] = vs[c] - bc; for (int c = 0; c < 4; c++)
result[c] = vs[c] - bc;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
} }
case 0x08: case 0x09: case 0x0A: case 0x0B: // MADDAbc case 0x08:
case 0x09:
case 0x0A:
case 0x0B: // MADDAbc
{ {
float bc = broadcast(vt, funct & 3); float bc = broadcast(vt, funct & 3);
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] + vs[c] * bc; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * bc;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
} }
case 0x0C: case 0x0D: case 0x0E: case 0x0F: // MSUBAbc case 0x0C:
case 0x0D:
case 0x0E:
case 0x0F: // MSUBAbc
{ {
float bc = broadcast(vt, funct & 3); float bc = broadcast(vt, funct & 3);
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] - vs[c] * bc; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * bc;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
} }
case 0x10: // ITOF0 case 0x10: // ITOF0
for (int c = 0; c < 4; c++) { int32_t iv; std::memcpy(&iv, &vs[c], 4); result[c] = (float)iv; } for (int c = 0; c < 4; c++)
{
int32_t iv;
std::memcpy(&iv, &vs[c], 4);
result[c] = (float)iv;
}
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x11: // ITOF4 case 0x11: // ITOF4
for (int c = 0; c < 4; c++) { int32_t iv; std::memcpy(&iv, &vs[c], 4); result[c] = (float)iv / 16.0f; } for (int c = 0; c < 4; c++)
{
int32_t iv;
std::memcpy(&iv, &vs[c], 4);
result[c] = (float)iv / 16.0f;
}
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x12: // ITOF12 case 0x12: // ITOF12
for (int c = 0; c < 4; c++) { int32_t iv; std::memcpy(&iv, &vs[c], 4); result[c] = (float)iv / 4096.0f; } for (int c = 0; c < 4; c++)
{
int32_t iv;
std::memcpy(&iv, &vs[c], 4);
result[c] = (float)iv / 4096.0f;
}
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x13: // ITOF15 case 0x13: // ITOF15
for (int c = 0; c < 4; c++) { int32_t iv; std::memcpy(&iv, &vs[c], 4); result[c] = (float)iv / 32768.0f; } for (int c = 0; c < 4; c++)
{
int32_t iv;
std::memcpy(&iv, &vs[c], 4);
result[c] = (float)iv / 32768.0f;
}
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x14: // FTOI0 case 0x14: // FTOI0
for (int c = 0; c < 4; c++) { int32_t iv = (int32_t)vs[c]; std::memcpy(&result[c], &iv, 4); } for (int c = 0; c < 4; c++)
{
int32_t iv = (int32_t)vs[c];
std::memcpy(&result[c], &iv, 4);
}
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x15: // FTOI4 case 0x15: // FTOI4
for (int c = 0; c < 4; c++) { int32_t iv = (int32_t)(vs[c] * 16.0f); std::memcpy(&result[c], &iv, 4); } for (int c = 0; c < 4; c++)
{
int32_t iv = (int32_t)(vs[c] * 16.0f);
std::memcpy(&result[c], &iv, 4);
}
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x16: // FTOI12 case 0x16: // FTOI12
for (int c = 0; c < 4; c++) { int32_t iv = (int32_t)(vs[c] * 4096.0f); std::memcpy(&result[c], &iv, 4); } for (int c = 0; c < 4; c++)
{
int32_t iv = (int32_t)(vs[c] * 4096.0f);
std::memcpy(&result[c], &iv, 4);
}
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x17: // FTOI15 case 0x17: // FTOI15
for (int c = 0; c < 4; c++) { int32_t iv = (int32_t)(vs[c] * 32768.0f); std::memcpy(&result[c], &iv, 4); } for (int c = 0; c < 4; c++)
{
int32_t iv = (int32_t)(vs[c] * 32768.0f);
std::memcpy(&result[c], &iv, 4);
}
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x18: case 0x19: case 0x1A: case 0x1B: // MULAbc case 0x18:
case 0x19:
case 0x1A:
case 0x1B: // MULAbc
{ {
float bc = broadcast(vt, funct & 3); float bc = broadcast(vt, funct & 3);
for (int c = 0; c < 4; c++) result[c] = vs[c] * bc; for (int c = 0; c < 4; c++)
result[c] = vs[c] * bc;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
} }
case 0x1C: // MULAq case 0x1C: // MULAq
for (int c = 0; c < 4; c++) result[c] = vs[c] * m_state.q; for (int c = 0; c < 4; c++)
result[c] = vs[c] * m_state.q;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x1D: // ABS case 0x1D: // ABS
for (int c = 0; c < 4; c++) result[c] = std::fabs(vs[c]); for (int c = 0; c < 4; c++)
result[c] = std::fabs(vs[c]);
applyDest(vd, result, dest); applyDest(vd, result, dest);
return; return;
case 0x1E: // MULAi case 0x1E: // MULAi
for (int c = 0; c < 4; c++) result[c] = vs[c] * m_state.i; for (int c = 0; c < 4; c++)
result[c] = vs[c] * m_state.i;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x1F: // CLIP case 0x1F: // CLIP
{ {
float w = std::fabs(vt[3]); float w = std::fabs(vt[3]);
uint32_t flags = 0; uint32_t flags = 0;
if (vs[0] > +w) flags |= 0x01; if (vs[0] > +w)
if (vs[0] < -w) flags |= 0x02; flags |= 0x01;
if (vs[1] > +w) flags |= 0x04; if (vs[0] < -w)
if (vs[1] < -w) flags |= 0x08; flags |= 0x02;
if (vs[2] > +w) flags |= 0x10; if (vs[1] > +w)
if (vs[2] < -w) flags |= 0x20; flags |= 0x04;
if (vs[1] < -w)
flags |= 0x08;
if (vs[2] > +w)
flags |= 0x10;
if (vs[2] < -w)
flags |= 0x20;
m_state.clip = (m_state.clip << 6) | flags; m_state.clip = (m_state.clip << 6) | flags;
return; return;
} }
@@ -403,55 +531,68 @@ void VU1Interpreter::execUpper(uint32_t instr)
switch (funct) switch (funct)
{ {
case 0x00: // ADDAq case 0x00: // ADDAq
for (int c = 0; c < 4; c++) result[c] = vs[c] + m_state.q; for (int c = 0; c < 4; c++)
result[c] = vs[c] + m_state.q;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x01: // MADDAq case 0x01: // MADDAq
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] + vs[c] * m_state.q; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * m_state.q;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x02: // ADDAi case 0x02: // ADDAi
for (int c = 0; c < 4; c++) result[c] = vs[c] + m_state.i; for (int c = 0; c < 4; c++)
result[c] = vs[c] + m_state.i;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x03: // MADDAi case 0x03: // MADDAi
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] + vs[c] * m_state.i; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * m_state.i;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x04: // SUBAq case 0x04: // SUBAq
for (int c = 0; c < 4; c++) result[c] = vs[c] - m_state.q; for (int c = 0; c < 4; c++)
result[c] = vs[c] - m_state.q;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x05: // MSUBAq case 0x05: // MSUBAq
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] - vs[c] * m_state.q; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * m_state.q;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x06: // SUBAi case 0x06: // SUBAi
for (int c = 0; c < 4; c++) result[c] = vs[c] - m_state.i; for (int c = 0; c < 4; c++)
result[c] = vs[c] - m_state.i;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x07: // MSUBAi case 0x07: // MSUBAi
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] - vs[c] * m_state.i; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * m_state.i;
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x08: // ADDA case 0x08: // ADDA
for (int c = 0; c < 4; c++) result[c] = vs[c] + vt[c]; for (int c = 0; c < 4; c++)
result[c] = vs[c] + vt[c];
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x09: // MADDA case 0x09: // MADDA
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] + vs[c] * vt[c]; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] + vs[c] * vt[c];
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x0A: // MULA case 0x0A: // MULA
for (int c = 0; c < 4; c++) result[c] = vs[c] * vt[c]; for (int c = 0; c < 4; c++)
result[c] = vs[c] * vt[c];
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x0C: // SUBA case 0x0C: // SUBA
for (int c = 0; c < 4; c++) result[c] = vs[c] - vt[c]; for (int c = 0; c < 4; c++)
result[c] = vs[c] - vt[c];
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x0D: // MSUBA case 0x0D: // MSUBA
for (int c = 0; c < 4; c++) result[c] = m_state.acc[c] - vs[c] * vt[c]; for (int c = 0; c < 4; c++)
result[c] = m_state.acc[c] - vs[c] * vt[c];
applyDestAcc(result, dest); applyDestAcc(result, dest);
return; return;
case 0x0E: // OPMULA case 0x0E: // OPMULA
@@ -475,7 +616,10 @@ void VU1Interpreter::execUpper(uint32_t instr)
return; return;
} }
case 0x30: case 0x31: case 0x32: case 0x33: // iadd-like upper? No, these are valid upper ops case 0x30:
case 0x31:
case 0x32:
case 0x33: // iadd-like upper? No, these are valid upper ops
default: default:
// NOP / unimplemented upper // NOP / unimplemented upper
return; return;
@@ -524,10 +668,14 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
{ {
float tmp[4]; float tmp[4];
std::memcpy(tmp, vuData + addr, 16); std::memcpy(tmp, vuData + addr, 16);
if (dest & 0x8) tmp[0] = m_state.vf[is][0]; if (dest & 0x8)
if (dest & 0x4) tmp[1] = m_state.vf[is][1]; tmp[0] = m_state.vf[is][0];
if (dest & 0x2) tmp[2] = m_state.vf[is][2]; if (dest & 0x4)
if (dest & 0x1) tmp[3] = m_state.vf[is][3]; tmp[1] = m_state.vf[is][1];
if (dest & 0x2)
tmp[2] = m_state.vf[is][2];
if (dest & 0x1)
tmp[3] = m_state.vf[is][3];
std::memcpy(vuData + addr, tmp, 16); std::memcpy(vuData + addr, tmp, 16);
} }
return; return;
@@ -543,13 +691,18 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
if (addr + 16 <= dataSize) if (addr + 16 <= dataSize)
{ {
int comp = 0; int comp = 0;
if (dest & 0x8) comp = 0; if (dest & 0x8)
else if (dest & 0x4) comp = 1; comp = 0;
else if (dest & 0x2) comp = 2; else if (dest & 0x4)
else comp = 3; comp = 1;
else if (dest & 0x2)
comp = 2;
else
comp = 3;
uint32_t v; uint32_t v;
std::memcpy(&v, vuData + addr + comp * 4, 4); std::memcpy(&v, vuData + addr + comp * 4, 4);
if (it != 0) m_state.vi[it] = (int32_t)(int16_t)(v & 0xFFFF); if (it != 0)
m_state.vi[it] = (int32_t)(int16_t)(v & 0xFFFF);
} }
return; return;
} }
@@ -564,10 +717,14 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
if (addr + 16 <= dataSize) if (addr + 16 <= dataSize)
{ {
uint32_t val = (uint32_t)(uint16_t)(m_state.vi[it] & 0xFFFF); uint32_t val = (uint32_t)(uint16_t)(m_state.vi[it] & 0xFFFF);
if (dest & 0x8) std::memcpy(vuData + addr + 0, &val, 4); if (dest & 0x8)
if (dest & 0x4) std::memcpy(vuData + addr + 4, &val, 4); std::memcpy(vuData + addr + 0, &val, 4);
if (dest & 0x2) std::memcpy(vuData + addr + 8, &val, 4); if (dest & 0x4)
if (dest & 0x1) std::memcpy(vuData + addr + 12, &val, 4); std::memcpy(vuData + addr + 4, &val, 4);
if (dest & 0x2)
std::memcpy(vuData + addr + 8, &val, 4);
if (dest & 0x1)
std::memcpy(vuData + addr + 12, &val, 4);
} }
return; return;
} }
@@ -592,7 +749,8 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
case 0x10: // FCEQ case 0x10: // FCEQ
{ {
uint32_t imm24 = instr & 0xFFFFFF; uint32_t imm24 = instr & 0xFFFFFF;
if (1 != 0) m_state.vi[1] = ((m_state.clip & 0xFFFFFF) == imm24) ? 1 : 0; if (1 != 0)
m_state.vi[1] = ((m_state.clip & 0xFFFFFF) == imm24) ? 1 : 0;
return; return;
} }
case 0x11: // FCSET case 0x11: // FCSET
@@ -603,19 +761,22 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
case 0x12: // FCAND case 0x12: // FCAND
{ {
uint32_t imm24 = instr & 0xFFFFFF; uint32_t imm24 = instr & 0xFFFFFF;
if (1 != 0) m_state.vi[1] = ((m_state.clip & imm24) != 0) ? 1 : 0; if (1 != 0)
m_state.vi[1] = ((m_state.clip & imm24) != 0) ? 1 : 0;
return; return;
} }
case 0x13: // FCOR case 0x13: // FCOR
{ {
uint32_t imm24 = instr & 0xFFFFFF; uint32_t imm24 = instr & 0xFFFFFF;
if (1 != 0) m_state.vi[1] = ((m_state.clip | imm24) == 0xFFFFFF) ? 1 : 0; if (1 != 0)
m_state.vi[1] = ((m_state.clip | imm24) == 0xFFFFFF) ? 1 : 0;
return; return;
} }
case 0x14: // FSEQ case 0x14: // FSEQ
{ {
uint16_t imm12 = instr & 0xFFF; uint16_t imm12 = instr & 0xFFF;
if (1 != 0) m_state.vi[1] = ((m_state.status & 0xFFF) == imm12) ? 1 : 0; if (1 != 0)
m_state.vi[1] = ((m_state.status & 0xFFF) == imm12) ? 1 : 0;
return; return;
} }
case 0x15: // FSSET case 0x15: // FSSET
@@ -626,34 +787,39 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
case 0x16: // FSAND case 0x16: // FSAND
{ {
uint16_t imm12 = instr & 0xFFF; uint16_t imm12 = instr & 0xFFF;
if (1 != 0) m_state.vi[1] = (int32_t)(m_state.status & imm12); if (1 != 0)
m_state.vi[1] = (int32_t)(m_state.status & imm12);
return; return;
} }
case 0x17: // FSOR case 0x17: // FSOR
{ {
uint16_t imm12 = instr & 0xFFF; uint16_t imm12 = instr & 0xFFF;
if (1 != 0) m_state.vi[1] = ((m_state.status | imm12) == 0xFFF) ? 1 : 0; if (1 != 0)
m_state.vi[1] = ((m_state.status | imm12) == 0xFFF) ? 1 : 0;
return; return;
} }
case 0x18: // FMAND case 0x18: // FMAND
{ {
uint8_t it = LIT(instr); uint8_t it = LIT(instr);
uint8_t is = LIS(instr); uint8_t is = LIS(instr);
if (it != 0) m_state.vi[it] = (int32_t)(m_state.mac & (uint32_t)(uint16_t)m_state.vi[is]); if (it != 0)
m_state.vi[it] = (int32_t)(m_state.mac & (uint32_t)(uint16_t)m_state.vi[is]);
return; return;
} }
case 0x1A: // FMEQ case 0x1A: // FMEQ
{ {
uint8_t it = LIT(instr); uint8_t it = LIT(instr);
uint8_t is = LIS(instr); uint8_t is = LIS(instr);
if (it != 0) m_state.vi[it] = ((m_state.mac & 0xFFFF) == (uint32_t)(uint16_t)m_state.vi[is]) ? 1 : 0; if (it != 0)
m_state.vi[it] = ((m_state.mac & 0xFFFF) == (uint32_t)(uint16_t)m_state.vi[is]) ? 1 : 0;
return; return;
} }
case 0x1C: // FMOR case 0x1C: // FMOR
{ {
uint8_t it = LIT(instr); uint8_t it = LIT(instr);
uint8_t is = LIS(instr); uint8_t is = LIS(instr);
if (it != 0) m_state.vi[it] = (int32_t)(m_state.mac | (uint32_t)(uint16_t)m_state.vi[is]); if (it != 0)
m_state.vi[it] = (int32_t)(m_state.mac | (uint32_t)(uint16_t)m_state.vi[is]);
return; return;
} }
case 0x20: // B (unconditional branch) case 0x20: // B (unconditional branch)
@@ -669,7 +835,8 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
uint8_t it = LIT(instr); uint8_t it = LIT(instr);
int16_t imm = IMM11(instr); int16_t imm = IMM11(instr);
uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF; uint32_t target = (m_state.pc + 8 + imm * 8) & 0x3FFF;
if (it != 0) m_state.vi[it] = (int32_t)((m_state.pc + 16) / 8); if (it != 0)
m_state.vi[it] = (int32_t)((m_state.pc + 16) / 8);
m_state.pc = target - 8; m_state.pc = target - 8;
return; return;
} }
@@ -685,7 +852,8 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
uint8_t it = LIT(instr); uint8_t it = LIT(instr);
uint8_t is = LIS(instr); uint8_t is = LIS(instr);
uint32_t target = ((uint32_t)(uint16_t)m_state.vi[is] * 8u) & 0x3FFF; uint32_t target = ((uint32_t)(uint16_t)m_state.vi[is] * 8u) & 0x3FFF;
if (it != 0) m_state.vi[it] = (int32_t)((m_state.pc + 16) / 8); if (it != 0)
m_state.vi[it] = (int32_t)((m_state.pc + 16) / 8);
m_state.pc = target - 8; m_state.pc = target - 8;
return; return;
} }
@@ -806,7 +974,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
} }
case 0x01: // MR32 (rotate right by 32 bits = shift xyzw -> yzwx) case 0x01: // MR32 (rotate right by 32 bits = shift xyzw -> yzwx)
{ {
float tmp[4] = { m_state.vf[is][1], m_state.vf[is][2], m_state.vf[is][3], m_state.vf[is][0] }; float tmp[4] = {m_state.vf[is][1], m_state.vf[is][2], m_state.vf[is][3], m_state.vf[is][0]};
applyDest(m_state.vf[it], tmp, dest); applyDest(m_state.vf[it], tmp, dest);
return; return;
} }
@@ -815,20 +983,27 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
float result[4]; float result[4];
int32_t val = (int32_t)(int16_t)(m_state.vi[is] & 0xFFFF); int32_t val = (int32_t)(int16_t)(m_state.vi[is] & 0xFFFF);
std::memcpy(&result[0], &val, 4); std::memcpy(&result[0], &val, 4);
result[1] = result[0]; result[2] = result[0]; result[3] = result[0]; result[1] = result[0];
result[2] = result[0];
result[3] = result[0];
applyDest(m_state.vf[it], result, dest); applyDest(m_state.vf[it], result, dest);
return; return;
} }
case 0x04: // MTIR (Move To Integer Register) case 0x04: // MTIR (Move To Integer Register)
{ {
int comp = 0; int comp = 0;
if (dest & 0x8) comp = 0; if (dest & 0x8)
else if (dest & 0x4) comp = 1; comp = 0;
else if (dest & 0x2) comp = 2; else if (dest & 0x4)
else comp = 3; comp = 1;
else if (dest & 0x2)
comp = 2;
else
comp = 3;
uint32_t fval; uint32_t fval;
std::memcpy(&fval, &m_state.vf[is][comp], 4); std::memcpy(&fval, &m_state.vf[is][comp], 4);
if (it != 0) m_state.vi[it] = (int32_t)(int16_t)(fval & 0xFFFF); if (it != 0)
m_state.vi[it] = (int32_t)(int16_t)(fval & 0xFFFF);
return; return;
} }
case 0x05: // RNEXT case 0x05: // RNEXT
@@ -847,7 +1022,8 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
std::memcpy(tmp, vuData + addr, 16); std::memcpy(tmp, vuData + addr, 16);
applyDest(m_state.vf[it], tmp, dest); applyDest(m_state.vf[it], tmp, dest);
} }
if (is != 0) m_state.vi[is] = (int16_t)(m_state.vi[is] + 1); if (is != 0)
m_state.vi[is] = (int16_t)(m_state.vi[is] + 1);
return; return;
} }
case 0x11: // SQI (Store Quadword, post-increment) case 0x11: // SQI (Store Quadword, post-increment)
@@ -858,18 +1034,24 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
{ {
float tmp[4]; float tmp[4];
std::memcpy(tmp, vuData + addr, 16); std::memcpy(tmp, vuData + addr, 16);
if (dest & 0x8) tmp[0] = m_state.vf[is][0]; if (dest & 0x8)
if (dest & 0x4) tmp[1] = m_state.vf[is][1]; tmp[0] = m_state.vf[is][0];
if (dest & 0x2) tmp[2] = m_state.vf[is][2]; if (dest & 0x4)
if (dest & 0x1) tmp[3] = m_state.vf[is][3]; tmp[1] = m_state.vf[is][1];
if (dest & 0x2)
tmp[2] = m_state.vf[is][2];
if (dest & 0x1)
tmp[3] = m_state.vf[is][3];
std::memcpy(vuData + addr, tmp, 16); std::memcpy(vuData + addr, tmp, 16);
} }
if (it != 0) m_state.vi[it] = (int16_t)(m_state.vi[it] + 1); if (it != 0)
m_state.vi[it] = (int16_t)(m_state.vi[it] + 1);
return; return;
} }
case 0x12: // LQD (Load Quadword, pre-decrement) case 0x12: // LQD (Load Quadword, pre-decrement)
{ {
if (is != 0) m_state.vi[is] = (int16_t)(m_state.vi[is] - 1); if (is != 0)
m_state.vi[is] = (int16_t)(m_state.vi[is] - 1);
uint32_t addr = ((uint32_t)(uint16_t)m_state.vi[is]) * 16u; uint32_t addr = ((uint32_t)(uint16_t)m_state.vi[is]) * 16u;
addr &= (dataSize - 1); addr &= (dataSize - 1);
if (addr + 16 <= dataSize) if (addr + 16 <= dataSize)
@@ -882,17 +1064,22 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
} }
case 0x13: // SQD (Store Quadword, pre-decrement) case 0x13: // SQD (Store Quadword, pre-decrement)
{ {
if (it != 0) m_state.vi[it] = (int16_t)(m_state.vi[it] - 1); if (it != 0)
m_state.vi[it] = (int16_t)(m_state.vi[it] - 1);
uint32_t addr = ((uint32_t)(uint16_t)m_state.vi[it]) * 16u; uint32_t addr = ((uint32_t)(uint16_t)m_state.vi[it]) * 16u;
addr &= (dataSize - 1); addr &= (dataSize - 1);
if (addr + 16 <= dataSize) if (addr + 16 <= dataSize)
{ {
float tmp[4]; float tmp[4];
std::memcpy(tmp, vuData + addr, 16); std::memcpy(tmp, vuData + addr, 16);
if (dest & 0x8) tmp[0] = m_state.vf[is][0]; if (dest & 0x8)
if (dest & 0x4) tmp[1] = m_state.vf[is][1]; tmp[0] = m_state.vf[is][0];
if (dest & 0x2) tmp[2] = m_state.vf[is][2]; if (dest & 0x4)
if (dest & 0x1) tmp[3] = m_state.vf[is][3]; tmp[1] = m_state.vf[is][1];
if (dest & 0x2)
tmp[2] = m_state.vf[is][2];
if (dest & 0x1)
tmp[3] = m_state.vf[is][3];
std::memcpy(vuData + addr, tmp, 16); std::memcpy(vuData + addr, tmp, 16);
} }
return; return;
@@ -936,7 +1123,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
return; return;
case 0x1B: // ELENG case 0x1B: // ELENG
{ {
float s = m_state.vf[is][0]*m_state.vf[is][0] + m_state.vf[is][1]*m_state.vf[is][1] + m_state.vf[is][2]*m_state.vf[is][2]; float s = m_state.vf[is][0] * m_state.vf[is][0] + m_state.vf[is][1] * m_state.vf[is][1] + m_state.vf[is][2] * m_state.vf[is][2];
m_state.p = std::sqrt(s); m_state.p = std::sqrt(s);
return; return;
} }
@@ -949,7 +1136,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
} }
case 0x1D: // ERLENG case 0x1D: // ERLENG
{ {
float s = m_state.vf[is][0]*m_state.vf[is][0] + m_state.vf[is][1]*m_state.vf[is][1] + m_state.vf[is][2]*m_state.vf[is][2]; float s = m_state.vf[is][0] * m_state.vf[is][0] + m_state.vf[is][1] * m_state.vf[is][1] + m_state.vf[is][2] * m_state.vf[is][2];
float len = std::sqrt(s); float len = std::sqrt(s);
m_state.p = (len != 0.0f) ? (1.0f / len) : std::numeric_limits<float>::max(); m_state.p = (len != 0.0f) ? (1.0f / len) : std::numeric_limits<float>::max();
return; return;
@@ -960,7 +1147,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
return; return;
case 0x1F: // MFP (Move From P register) case 0x1F: // MFP (Move From P register)
{ {
float result[4] = { m_state.p, m_state.p, m_state.p, m_state.p }; float result[4] = {m_state.p, m_state.p, m_state.p, m_state.p};
applyDest(m_state.vf[it], result, dest); applyDest(m_state.vf[it], result, dest);
return; return;
} }
@@ -1035,6 +1222,17 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
if (totalBytes == 0u) if (totalBytes == 0u)
return; return;
const uint32_t debugIndex = s_debugVu1XgkickCount.fetch_add(1, std::memory_order_relaxed);
if (debugIndex < 64u)
{
std::cout << "[vu1:xgkick] idx=" << debugIndex
<< " addr=0x" << std::hex << addr
<< " totalBytes=0x" << totalBytes
<< std::dec
<< " wrap=" << static_cast<uint32_t>((addr + totalBytes > dataSize) ? 1u : 0u)
<< std::endl;
}
if (addr + totalBytes <= dataSize) if (addr + totalBytes <= dataSize)
{ {
if (memory) if (memory)
@@ -1059,12 +1257,14 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
} }
case 0x3E: // XTOP case 0x3E: // XTOP
{ {
if (it != 0) m_state.vi[it] = (int32_t)m_state.itop; if (it != 0)
m_state.vi[it] = (int32_t)m_state.itop;
return; return;
} }
case 0x3F: // XITOP case 0x3F: // XITOP
{ {
if (it != 0) m_state.vi[it] = (int32_t)m_state.itop; if (it != 0)
m_state.vi[it] = (int32_t)m_state.itop;
return; return;
} }
default: default:
@@ -1412,6 +1412,7 @@ namespace
mem.writeIORegister(channelBase + 0x10u, madr); mem.writeIORegister(channelBase + 0x10u, madr);
mem.writeIORegister(channelBase + 0x30u, tadr); mem.writeIORegister(channelBase + 0x30u, tadr);
mem.writeIORegister(channelBase + 0x00u, chcr); mem.writeIORegister(channelBase + 0x00u, chcr);
mem.processPendingTransfers();
std::lock_guard<std::mutex> lock(g_dmaStubMutex); std::lock_guard<std::mutex> lock(g_dmaStubMutex);
g_dmaPendingPolls[channelBase] = 1; g_dmaPendingPolls[channelBase] = 1;
@@ -1492,6 +1493,65 @@ namespace
uint64_t bgcolor; uint64_t bgcolor;
}; };
struct GsGiftagMem
{
uint64_t lo;
uint64_t hi;
};
struct GsRegPairMem
{
uint64_t value;
uint64_t reg;
};
struct GsDrawEnv1Mem
{
GsRegPairMem frame1;
GsRegPairMem zbuf1;
GsRegPairMem xyoffset1;
GsRegPairMem scissor1;
GsRegPairMem prmodecont;
GsRegPairMem colclamp;
GsRegPairMem dthe;
GsRegPairMem test1;
};
struct GsDrawEnv2Mem
{
GsRegPairMem frame2;
GsRegPairMem zbuf2;
GsRegPairMem xyoffset2;
GsRegPairMem scissor2;
GsRegPairMem prmodecont;
GsRegPairMem colclamp;
GsRegPairMem dthe;
GsRegPairMem test2;
};
struct GsClearMem
{
GsRegPairMem testa;
GsRegPairMem prim;
GsRegPairMem rgbaq;
GsRegPairMem xyz2a;
GsRegPairMem xyz2b;
GsRegPairMem testb;
};
struct GsDBuffDcMem
{
GsDispEnvMem disp[2];
GsGiftagMem giftag0;
GsDrawEnv1Mem draw01;
GsDrawEnv2Mem draw02;
GsClearMem clear0;
GsGiftagMem giftag1;
GsDrawEnv1Mem draw11;
GsDrawEnv2Mem draw12;
GsClearMem clear1;
};
struct GsImageMem struct GsImageMem
{ {
uint16_t x; uint16_t x;
@@ -1503,33 +1563,14 @@ namespace
uint8_t psm; uint8_t psm;
}; };
#pragma pack(push, 1)
struct GsDrawEnvMem
{
uint16_t offset_x;
uint16_t offset_y;
uint16_t clip_x;
uint16_t clip_y;
uint16_t clip_w;
uint16_t clip_h;
uint16_t vram_addr;
uint8_t fbw;
uint8_t psm;
uint16_t vram_x;
uint16_t vram_y;
uint32_t draw_mask;
uint8_t auto_clear;
uint8_t pad[3];
uint8_t bg_r;
uint8_t bg_g;
uint8_t bg_b;
uint8_t bg_a;
float bg_q;
};
#pragma pack(pop)
static_assert(sizeof(GsImageMem) == 12, "GsImageMem size mismatch"); static_assert(sizeof(GsImageMem) == 12, "GsImageMem size mismatch");
static_assert(sizeof(GsDrawEnvMem) == 36, "GsDrawEnvMem size mismatch"); static_assert(sizeof(GsDispEnvMem) == 40, "GsDispEnvMem size mismatch");
static_assert(sizeof(GsGiftagMem) == 16, "GsGiftagMem size mismatch");
static_assert(sizeof(GsRegPairMem) == 16, "GsRegPairMem size mismatch");
static_assert(sizeof(GsDrawEnv1Mem) == 128, "GsDrawEnv1Mem size mismatch");
static_assert(sizeof(GsDrawEnv2Mem) == 128, "GsDrawEnv2Mem size mismatch");
static_assert(sizeof(GsClearMem) == 96, "GsClearMem size mismatch");
static_assert(sizeof(GsDBuffDcMem) == 0x330, "GsDBuffDcMem size mismatch");
constexpr uint32_t kGsParamScratchOffset = 0x100; constexpr uint32_t kGsParamScratchOffset = 0x100;
GsGParam g_gparam{1, 2, 1, 3}; // Default: interlaced NTSC, frame mode. GsGParam g_gparam{1, 2, 1, 3}; // Default: interlaced NTSC, frame mode.
@@ -1564,6 +1605,53 @@ namespace
(static_cast<uint64_t>(dh & 0x07FF) << 44); (static_cast<uint64_t>(dh & 0x07FF) << 44);
} }
static uint64_t makeFrame(uint32_t fbp, uint32_t fbw, uint32_t psm, uint32_t fbmsk)
{
return (static_cast<uint64_t>(fbp & 0x1FFu) << 0) |
(static_cast<uint64_t>(fbw & 0x3Fu) << 16) |
(static_cast<uint64_t>(psm & 0x3Fu) << 24) |
(static_cast<uint64_t>(fbmsk) << 32);
}
static uint64_t makeZbuf(uint32_t zbp, uint32_t psm, bool zmsk)
{
return (static_cast<uint64_t>(zbp & 0x1FFu) << 0) |
(static_cast<uint64_t>(psm & 0xFu) << 24) |
(static_cast<uint64_t>(zmsk ? 1u : 0u) << 32);
}
static uint64_t makeXYOffset(int32_t width, int32_t height)
{
const int32_t offX = 0x800 - (width >> 1);
const int32_t offY = 0x800 - (height >> 1);
return (static_cast<uint64_t>(static_cast<uint32_t>(offY) & 0xFFFFu) << 36) |
(static_cast<uint64_t>(static_cast<uint32_t>(offX) & 0xFFFFu) << 4);
}
static uint64_t makeScissor(int32_t width, int32_t height)
{
return (static_cast<uint64_t>(0u) << 0) |
(static_cast<uint64_t>(static_cast<uint32_t>(width - 1) & 0x7FFu) << 16) |
(static_cast<uint64_t>(0u) << 32) |
(static_cast<uint64_t>(static_cast<uint32_t>(height - 1) & 0x7FFu) << 48);
}
static uint64_t makeTest(uint32_t ztest)
{
if ((ztest & 0x3u) == 0u)
{
return 0x30000ULL;
}
return (static_cast<uint64_t>(ztest & 0x3u) << 17) | 0x10000ULL;
}
static uint64_t makeGiftagAplusD(uint32_t nloop)
{
return (static_cast<uint64_t>(nloop & 0x7FFFu) << 0) |
(static_cast<uint64_t>(1u) << 15) |
(static_cast<uint64_t>(1u) << 60);
}
static uint32_t readStackU32(uint8_t *rdram, R5900Context *ctx, uint32_t offset) static uint32_t readStackU32(uint8_t *rdram, R5900Context *ctx, uint32_t offset)
{ {
uint32_t sp = getRegU32(ctx, 29); uint32_t sp = getRegU32(ctx, 29);
@@ -1712,6 +1800,99 @@ namespace
return true; return true;
} }
static bool readGsDBuffDc(uint8_t *rdram, uint32_t addr, GsDBuffDcMem &out)
{
const uint8_t *ptr = getConstMemPtr(rdram, addr);
if (!ptr)
return false;
std::memcpy(&out, ptr, sizeof(out));
return true;
}
static bool writeGsDBuffDc(uint8_t *rdram, uint32_t addr, const GsDBuffDcMem &db)
{
uint8_t *ptr = getMemPtr(rdram, addr);
if (!ptr)
return false;
std::memcpy(ptr, &db, sizeof(db));
return true;
}
static bool readGsRegPairs(uint8_t *rdram, uint32_t addr, GsRegPairMem *pairs, size_t pairCount)
{
if (!pairs || pairCount == 0u)
return false;
const uint8_t *ptr = getConstMemPtr(rdram, addr);
if (!ptr)
return false;
std::memcpy(pairs, ptr, pairCount * sizeof(GsRegPairMem));
return true;
}
static void applyGsDispEnv(PS2Runtime *runtime, const GsDispEnvMem &env)
{
if (!runtime)
return;
auto &regs = runtime->memory().gs();
regs.pmode = env.pmode;
regs.smode2 = env.smode2;
regs.dispfb1 = env.dispfb;
regs.display1 = env.display;
regs.bgcolor = env.bgcolor;
}
static void applyGsRegPairs(PS2Runtime *runtime, const GsRegPairMem *pairs, size_t pairCount)
{
if (!runtime || !pairs)
return;
for (size_t i = 0; i < pairCount; ++i)
{
runtime->gs().writeRegister(static_cast<uint8_t>(pairs[i].reg & 0xFFu), pairs[i].value);
}
}
static void seedGsDrawEnv1(GsDrawEnv1Mem &env,
int32_t width,
int32_t height,
uint32_t fbp,
uint32_t fbw,
uint32_t psm,
uint32_t zbp,
uint32_t zpsm,
uint32_t ztest,
bool dthe)
{
env.frame1 = {makeFrame(fbp, fbw, psm, 0u), GS_REG_FRAME_1};
env.zbuf1 = {makeZbuf(zbp, zpsm, (ztest & 0x3u) == 0u), GS_REG_ZBUF_1};
env.xyoffset1 = {makeXYOffset(width, height), GS_REG_XYOFFSET_1};
env.scissor1 = {makeScissor(width, height), GS_REG_SCISSOR_1};
env.prmodecont = {1u, GS_REG_PRMODECONT};
env.colclamp = {1u, GS_REG_COLCLAMP};
env.dthe = {dthe ? 1u : 0u, GS_REG_DTHE};
env.test1 = {makeTest(ztest), GS_REG_TEST_1};
}
static void seedGsDrawEnv2(GsDrawEnv2Mem &env,
int32_t width,
int32_t height,
uint32_t fbp,
uint32_t fbw,
uint32_t psm,
uint32_t zbp,
uint32_t zpsm,
uint32_t ztest,
bool dthe)
{
env.frame2 = {makeFrame(fbp, fbw, psm, 0u), GS_REG_FRAME_2};
env.zbuf2 = {makeZbuf(zbp, zpsm, (ztest & 0x3u) == 0u), GS_REG_ZBUF_2};
env.xyoffset2 = {makeXYOffset(width, height), GS_REG_XYOFFSET_2};
env.scissor2 = {makeScissor(width, height), GS_REG_SCISSOR_2};
env.prmodecont = {1u, GS_REG_PRMODECONT};
env.colclamp = {1u, GS_REG_COLCLAMP};
env.dthe = {dthe ? 1u : 0u, GS_REG_DTHE};
env.test2 = {makeTest(ztest), GS_REG_TEST_2};
}
static uint32_t writeGsGParamToScratch(PS2Runtime *runtime) static uint32_t writeGsGParamToScratch(PS2Runtime *runtime)
{ {
if (!runtime) if (!runtime)
+197 -90
View File
@@ -1,28 +1,48 @@
namespace namespace
{ {
std::mutex g_gs_sync_v_mutex;
uint64_t g_gs_sync_v_base_tick = 0u;
std::mutex g_gs_sync_v_callback_mutex; std::mutex g_gs_sync_v_callback_mutex;
uint32_t g_gs_sync_v_callback_func = 0u; uint32_t g_gs_sync_v_callback_func = 0u;
uint32_t g_gs_sync_v_callback_gp = 0u; uint32_t g_gs_sync_v_callback_gp = 0u;
uint32_t g_gs_sync_v_callback_sp = 0u; uint32_t g_gs_sync_v_callback_sp = 0u;
uint32_t g_gs_sync_v_callback_stack_base = 0u; uint32_t g_gs_sync_v_callback_stack_base = 0u;
uint32_t g_gs_sync_v_callback_stack_top = 0u; uint32_t g_gs_sync_v_callback_stack_top = 0u;
uint64_t g_gs_sync_v_callback_tick = 0u;
uint32_t g_gs_sync_v_callback_bad_pc_logs = 0u; uint32_t g_gs_sync_v_callback_bad_pc_logs = 0u;
} }
static void resetGsSyncVState()
{
std::lock_guard<std::mutex> lock(g_gs_sync_v_mutex);
g_gs_sync_v_base_tick = ps2_syscalls::GetCurrentVSyncTick();
}
static int32_t getGsSyncVFieldForTick(uint64_t tick)
{
std::lock_guard<std::mutex> lock(g_gs_sync_v_mutex);
if (tick <= g_gs_sync_v_base_tick)
{
return 0;
}
return static_cast<int32_t>((tick - g_gs_sync_v_base_tick - 1u) & 1u);
}
void resetGsSyncVCallbackState() void resetGsSyncVCallbackState()
{ {
std::lock_guard<std::mutex> lock(g_gs_sync_v_callback_mutex); {
g_gs_sync_v_callback_func = 0u; std::lock_guard<std::mutex> lock(g_gs_sync_v_callback_mutex);
g_gs_sync_v_callback_gp = 0u; g_gs_sync_v_callback_func = 0u;
g_gs_sync_v_callback_sp = 0u; g_gs_sync_v_callback_gp = 0u;
g_gs_sync_v_callback_stack_base = 0u; g_gs_sync_v_callback_sp = 0u;
g_gs_sync_v_callback_stack_top = 0u; g_gs_sync_v_callback_stack_base = 0u;
g_gs_sync_v_callback_tick = 0u; g_gs_sync_v_callback_stack_top = 0u;
g_gs_sync_v_callback_bad_pc_logs = 0u; g_gs_sync_v_callback_bad_pc_logs = 0u;
}
resetGsSyncVState();
} }
void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime) void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime, uint64_t tick)
{ {
if (!rdram || !runtime) if (!rdram || !runtime)
{ {
@@ -31,20 +51,17 @@ void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime)
uint32_t callback = 0u; uint32_t callback = 0u;
uint32_t gp = 0u; uint32_t gp = 0u;
uint32_t sp = 0u;
uint32_t callbackStackTop = 0u; uint32_t callbackStackTop = 0u;
uint64_t tick = 0u; const uint64_t callbackTick = (tick != 0u) ? tick : ps2_syscalls::GetCurrentVSyncTick();
{ {
std::lock_guard<std::mutex> lock(g_gs_sync_v_callback_mutex); std::lock_guard<std::mutex> lock(g_gs_sync_v_callback_mutex);
callback = g_gs_sync_v_callback_func; callback = g_gs_sync_v_callback_func;
gp = g_gs_sync_v_callback_gp; gp = g_gs_sync_v_callback_gp;
sp = g_gs_sync_v_callback_sp;
callbackStackTop = g_gs_sync_v_callback_stack_top; callbackStackTop = g_gs_sync_v_callback_stack_top;
if (callback == 0u) if (callback == 0u)
{ {
return; return;
} }
tick = ++g_gs_sync_v_callback_tick;
} }
if (!runtime->hasFunction(callback)) if (!runtime->hasFunction(callback))
@@ -55,14 +72,14 @@ void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime)
if (callbackStackTop == 0u) if (callbackStackTop == 0u)
{ {
constexpr uint32_t kCallbackStackSize = 0x4000u; constexpr uint32_t kCallbackStackSize = 0x4000u;
const uint32_t stackBase = runtime->guestMalloc(kCallbackStackSize, 16u); const uint32_t stackTop = runtime->reserveAsyncCallbackStack(kCallbackStackSize, 16u);
if (stackBase != 0u) if (stackTop != 0u)
{ {
std::lock_guard<std::mutex> lock(g_gs_sync_v_callback_mutex); std::lock_guard<std::mutex> lock(g_gs_sync_v_callback_mutex);
if (g_gs_sync_v_callback_stack_top == 0u) if (g_gs_sync_v_callback_stack_top == 0u)
{ {
g_gs_sync_v_callback_stack_base = stackBase; g_gs_sync_v_callback_stack_base = stackTop - (kCallbackStackSize - 0x10u);
g_gs_sync_v_callback_stack_top = stackBase + kCallbackStackSize - 0x10u; g_gs_sync_v_callback_stack_top = stackTop;
} }
callbackStackTop = g_gs_sync_v_callback_stack_top; callbackStackTop = g_gs_sync_v_callback_stack_top;
} }
@@ -72,9 +89,9 @@ void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime)
{ {
R5900Context callbackCtx{}; R5900Context callbackCtx{};
SET_GPR_U32(&callbackCtx, 28, gp); SET_GPR_U32(&callbackCtx, 28, gp);
SET_GPR_U32(&callbackCtx, 29, (callbackStackTop != 0u) ? callbackStackTop : ((sp != 0u) ? sp : (PS2_RAM_SIZE - 0x10u))); SET_GPR_U32(&callbackCtx, 29, (callbackStackTop != 0u) ? callbackStackTop : (PS2_RAM_SIZE - 0x10u));
SET_GPR_U32(&callbackCtx, 31, 0u); SET_GPR_U32(&callbackCtx, 31, 0u);
SET_GPR_U32(&callbackCtx, 4, static_cast<uint32_t>(tick)); SET_GPR_U32(&callbackCtx, 4, static_cast<uint32_t>(callbackTick));
callbackCtx.pc = callback; callbackCtx.pc = callback;
uint32_t steps = 0u; uint32_t steps = 0u;
@@ -159,6 +176,7 @@ void sceGsExecLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
uint32_t dsax = static_cast<uint32_t>(img.x); uint32_t dsax = static_cast<uint32_t>(img.x);
uint32_t dsay = static_cast<uint32_t>(img.y); uint32_t dsay = static_cast<uint32_t>(img.y);
// Full messy
uint64_t *q = reinterpret_cast<uint64_t *>(pkt); uint64_t *q = reinterpret_cast<uint64_t *>(pkt);
q[0] = 0x1000000000000004ULL; q[0] = 0x1000000000000004ULL;
q[1] = 0x0E0E0E0E0E0E0E0EULL; q[1] = 0x0E0E0E0E0E0E0E0EULL;
@@ -219,11 +237,11 @@ void sceGsExecStoreImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
uint32_t sbp = (static_cast<uint32_t>(img.vram_addr) * 2048u) / 256u; uint32_t sbp = (static_cast<uint32_t>(img.vram_addr) * 2048u) / 256u;
uint64_t bitbltbuf = (static_cast<uint64_t>(sbp & 0x3FFFu) << 0) | uint64_t bitbltbuf = (static_cast<uint64_t>(sbp & 0x3FFFu) << 0) |
(static_cast<uint64_t>(fbw & 0x3Fu) << 16) | (static_cast<uint64_t>(fbw & 0x3Fu) << 16) |
(static_cast<uint64_t>(img.psm & 0x3Fu) << 24) | (static_cast<uint64_t>(img.psm & 0x3Fu) << 24) |
(static_cast<uint64_t>(0u) << 32) | (static_cast<uint64_t>(0u) << 32) |
(static_cast<uint64_t>(1u) << 48) | (static_cast<uint64_t>(1u) << 48) |
(static_cast<uint64_t>(0u) << 56); (static_cast<uint64_t>(0u) << 56);
uint64_t trxpos = (static_cast<uint64_t>(img.x & 0x7FFu) << 0) | uint64_t trxpos = (static_cast<uint64_t>(img.x & 0x7FFu) << 0) |
(static_cast<uint64_t>(img.y & 0x7FFu) << 16) | (static_cast<uint64_t>(img.y & 0x7FFu) << 16) |
(static_cast<uint64_t>(0u) << 32) | (static_cast<uint64_t>(0u) << 32) |
@@ -278,40 +296,26 @@ void sceGsGetGParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void sceGsPutDispEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceGsPutDispEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{ {
uint32_t envAddr = getRegU32(ctx, 4); uint32_t envAddr = getRegU32(ctx, 4);
uint8_t *ptr = getMemPtr(rdram, envAddr); GsDispEnvMem env{};
if (!ptr) if (!readGsDispEnv(rdram, envAddr, env))
{ {
setReturnS32(ctx, -1); setReturnS32(ctx, -1);
return; return;
} }
constexpr uint32_t GIF_CHANNEL = 0x1000A000; applyGsDispEnv(runtime, env);
constexpr uint32_t QWC = 5;
constexpr uint32_t CHCR_STR_MODE0 = 0x101u;
auto &mem = runtime->memory();
mem.writeIORegister(GIF_CHANNEL + 0x10u, envAddr);
mem.writeIORegister(GIF_CHANNEL + 0x20u, QWC);
mem.writeIORegister(GIF_CHANNEL + 0x00u, CHCR_STR_MODE0);
setReturnS32(ctx, 0); setReturnS32(ctx, 0);
} }
void sceGsPutDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceGsPutDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{ {
uint32_t envAddr = getRegU32(ctx, 4); uint32_t envAddr = getRegU32(ctx, 4);
uint8_t *ptr = getMemPtr(rdram, envAddr); GsRegPairMem pairs[8]{};
if (!ptr) if (!readGsRegPairs(rdram, envAddr, pairs, 8u))
{ {
setReturnS32(ctx, -1); setReturnS32(ctx, -1);
return; return;
} }
applyGsRegPairs(runtime, pairs, 8u);
constexpr uint32_t GIF_CHANNEL = 0x1000A000;
constexpr uint32_t QWC = 9;
constexpr uint32_t CHCR_STR_MODE0 = 0x101u;
auto &mem = runtime->memory();
mem.writeIORegister(GIF_CHANNEL + 0x10u, envAddr);
mem.writeIORegister(GIF_CHANNEL + 0x20u, QWC);
mem.writeIORegister(GIF_CHANNEL + 0x00u, CHCR_STR_MODE0);
setReturnS32(ctx, 0); setReturnS32(ctx, 0);
} }
@@ -328,6 +332,7 @@ void sceGsResetGraph(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
g_gparam.omode = static_cast<uint8_t>(omode & 0xFF); g_gparam.omode = static_cast<uint8_t>(omode & 0xFF);
g_gparam.ffmode = static_cast<uint8_t>(ffmode & 0x1); g_gparam.ffmode = static_cast<uint8_t>(ffmode & 0x1);
writeGsGParamToScratch(runtime); writeGsGParamToScratch(runtime);
resetGsSyncVState();
uint64_t pmode = makePmode(1, 0, 0, 0, 0, 0x80); uint64_t pmode = makePmode(1, 0, 0, 0, 0, 0x80);
uint64_t smode2 = (interlace & 0x1) | ((ffmode & 0x1) << 1); uint64_t smode2 = (interlace & 0x1) | ((ffmode & 0x1) << 1);
@@ -385,6 +390,62 @@ void sceGsSetDefClear(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void sceGsSetDefDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceGsSetDefDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{ {
const uint32_t envAddr = getRegU32(ctx, 4);
uint32_t psm = getRegU32(ctx, 5);
uint32_t w = getRegU32(ctx, 6);
uint32_t h = getRegU32(ctx, 7);
const uint32_t ztest = readStackU32(rdram, ctx, 16);
const uint32_t zpsm = readStackU32(rdram, ctx, 20);
const uint32_t clear = readStackU32(rdram, ctx, 24);
(void)clear;
if (w == 0u)
{
w = 640u;
}
if (h == 0u)
{
h = 448u;
}
const uint32_t fbw = std::max<uint32_t>(1u, (w + 63u) / 64u);
const uint64_t pmode = makePmode(1u, 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);
const uint64_t dispfb = makeDispFb(0u, fbw, psm, 0u, 0u);
const uint64_t display = makeDisplay(636u, 32u, 0u, 0u, w - 1u, h - 1u);
const int32_t drawWidth = static_cast<int32_t>(w);
const int32_t drawHeight = static_cast<int32_t>(h);
uint32_t zbufAddr = 0u;
{
R5900Context temp = *ctx;
sceGszbufaddr(rdram, &temp, runtime);
zbufAddr = getRegU32(&temp, 2);
}
GsDBuffDcMem db{};
db.disp[0].pmode = pmode;
db.disp[0].smode2 = smode2;
db.disp[0].dispfb = dispfb;
db.disp[0].display = display;
db.disp[0].bgcolor = 0u;
db.disp[1] = db.disp[0];
db.giftag0 = {makeGiftagAplusD(14u), 0x0E0E0E0E0E0E0E0EULL};
seedGsDrawEnv1(db.draw01, drawWidth, drawHeight, 0u, fbw, psm, zbufAddr, zpsm, ztest, false);
seedGsDrawEnv2(db.draw02, drawWidth, drawHeight, 0u, fbw, psm, zbufAddr, zpsm, ztest, false);
db.giftag1 = db.giftag0;
seedGsDrawEnv1(db.draw11, drawWidth, drawHeight, 0u, fbw, psm, zbufAddr, zpsm, ztest, false);
seedGsDrawEnv2(db.draw12, drawWidth, drawHeight, 0u, fbw, psm, zbufAddr, zpsm, ztest, false);
if (!writeGsDBuffDc(rdram, envAddr, db))
{
setReturnS32(ctx, -1);
return;
}
setReturnS32(ctx, 0); setReturnS32(ctx, 0);
} }
@@ -429,6 +490,60 @@ void sceGsSetDefDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
sceGszbufaddr(rdram, ctx, runtime); sceGszbufaddr(rdram, ctx, runtime);
int32_t zbuf = static_cast<int32_t>(static_cast<int16_t>(getRegU32(ctx, 2) & 0xFFFF)); int32_t zbuf = static_cast<int32_t>(static_cast<int16_t>(getRegU32(ctx, 2) & 0xFFFF));
GsDrawEnv1Mem env{};
seedGsDrawEnv1(env,
w,
h,
0u,
fbw,
psm,
static_cast<uint32_t>(zbuf),
param_6 & 0xFu,
param_5 & 0x3u,
(param_2 & 2u) != 0u);
uint8_t *const ptr = getMemPtr(rdram, envAddr);
if (!ptr)
{
setReturnS32(ctx, 8);
return;
}
std::memcpy(ptr, &env, sizeof(env));
setReturnS32(ctx, 8);
}
void sceGsSetDefDrawEnv2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
uint32_t envAddr = getRegU32(ctx, 4);
uint32_t param_2 = getRegU32(ctx, 5);
int32_t w = static_cast<int32_t>(static_cast<int16_t>(getRegU32(ctx, 6) & 0xFFFF));
int32_t h = static_cast<int32_t>(static_cast<int16_t>(getRegU32(ctx, 7) & 0xFFFF));
uint32_t param_5 = readStackU32(rdram, ctx, 16);
uint32_t param_6 = readStackU32(rdram, ctx, 20);
if (w <= 0)
w = 640;
if (h <= 0)
h = 448;
uint32_t psm = param_2 & 0xFU;
uint32_t fbw = ((static_cast<uint32_t>(w) + 63u) >> 6) & 0x3FU;
sceGszbufaddr(rdram, ctx, runtime);
int32_t zbuf = static_cast<int32_t>(static_cast<int16_t>(getRegU32(ctx, 2) & 0xFFFF));
GsDrawEnv2Mem env{};
seedGsDrawEnv2(env,
w,
h,
0u,
fbw,
psm,
static_cast<uint32_t>(zbuf),
param_6 & 0xFu,
param_5 & 0x3u,
(param_2 & 2u) != 0u);
uint8_t *const ptr = getMemPtr(rdram, envAddr); uint8_t *const ptr = getMemPtr(rdram, envAddr);
if (!ptr) if (!ptr)
{ {
@@ -436,48 +551,10 @@ void sceGsSetDefDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
return; return;
} }
uint64_t *const words = reinterpret_cast<uint64_t *>(ptr); std::memcpy(ptr, &env, sizeof(env));
words[0] = 0x1000000000008008ULL;
words[1] = 0x000000000000000EULL;
words[2] = (static_cast<uint64_t>(fbw) << 16) | (static_cast<uint64_t>(psm) << 24);
words[3] = 0x4c;
words[4] = (static_cast<uint64_t>(zbuf) & 0xFFFFULL) | (static_cast<uint64_t>(param_6 & 0xF) << 24) |
(param_5 == 0 ? 0x100000000ULL : 0ULL);
words[5] = 0x4e;
int32_t off_x = 0x800 - (w >> 1);
int32_t off_y = 0x800 - (h >> 1);
words[6] = (static_cast<uint64_t>(static_cast<uint32_t>(off_y) & 0xFFFF) << 36) |
(static_cast<uint32_t>(off_x) & 0xFFFF) * 16ULL;
words[7] = 0x18;
words[8] = (static_cast<uint64_t>(static_cast<uint32_t>(h - 1) & 0xFFFF) << 48) |
(static_cast<uint64_t>(static_cast<uint32_t>(w - 1) & 0xFFFF) << 16);
words[9] = 0x40;
words[10] = 1;
words[11] = 0x1a;
words[12] = 1;
words[13] = 0x46;
words[14] = (param_2 & 2) ? 1ULL : 0ULL;
words[15] = 0x45;
words[16] = (param_5 == 0) ? 0x30000ULL : ((static_cast<uint64_t>(param_5 & 3) << 17) | 0x10000ULL);
words[17] = 0x47;
setReturnS32(ctx, 8); setReturnS32(ctx, 8);
} }
void sceGsSetDefDrawEnv2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
sceGsSetDefDrawEnv(rdram, ctx, runtime);
}
void sceGsSetDefLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceGsSetDefLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{ {
uint32_t imgAddr = getRegU32(ctx, 4); uint32_t imgAddr = getRegU32(ctx, 4);
@@ -503,9 +580,29 @@ void sceGsSetDefStoreImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtim
void sceGsSwapDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceGsSwapDBuffDc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{ {
static int cur = 0; const uint32_t envAddr = getRegU32(ctx, 4);
cur ^= 1; const uint32_t which = getRegU32(ctx, 5) & 1u;
setReturnS32(ctx, cur);
GsDBuffDcMem db{};
if (!runtime || !readGsDBuffDc(rdram, envAddr, db))
{
setReturnS32(ctx, -1);
return;
}
applyGsDispEnv(runtime, db.disp[which]);
if (which == 0u)
{
applyGsRegPairs(runtime, reinterpret_cast<const GsRegPairMem *>(&db.draw01), 8u);
applyGsRegPairs(runtime, reinterpret_cast<const GsRegPairMem *>(&db.draw02), 8u);
}
else
{
applyGsRegPairs(runtime, reinterpret_cast<const GsRegPairMem *>(&db.draw11), 8u);
applyGsRegPairs(runtime, reinterpret_cast<const GsRegPairMem *>(&db.draw12), 8u);
}
setReturnS32(ctx, static_cast<int32_t>(which ^ 1u));
} }
void sceGsSyncPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceGsSyncPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
@@ -577,13 +674,18 @@ void sceGsSyncPath(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void sceGsSyncV(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceGsSyncV(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{ {
setReturnS32(ctx, 0); const uint64_t tick = ps2_syscalls::WaitForNextVSyncTick(rdram, runtime);
if (g_gparam.interlace != 0u)
{
setReturnS32(ctx, getGsSyncVFieldForTick(tick));
return;
}
setReturnS32(ctx, 1);
} }
void sceGsSyncVCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void sceGsSyncVCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{ {
(void)rdram;
const uint32_t newCallback = getRegU32(ctx, 4); const uint32_t newCallback = getRegU32(ctx, 4);
const uint32_t callerPc = ctx ? ctx->pc : 0u; const uint32_t callerPc = ctx ? ctx->pc : 0u;
const uint32_t callerRa = ctx ? getRegU32(ctx, 31) : 0u; const uint32_t callerRa = ctx ? getRegU32(ctx, 31) : 0u;
@@ -615,6 +717,11 @@ void sceGsSyncVCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
++s_syncVCallbackLogCount; ++s_syncVCallbackLogCount;
} }
if (newCallback != 0u)
{
ps2_syscalls::EnsureVSyncWorkerRunning(rdram, runtime);
}
setReturnU32(ctx, oldCallback); setReturnU32(ctx, oldCallback);
} }
+4 -1
View File
@@ -1645,7 +1645,10 @@ void sceIpuInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
if (setD4) if (setD4)
{ {
ctx->r[4] = _mm_set_epi64x(0, 1); ctx->r[4] = _mm_set_epi64x(0, 1);
setD4(rdram, ctx, runtime); {
PS2Runtime::GuestExecutionScope guestExecution(runtime);
setD4(rdram, ctx, runtime);
}
} }
mem.write32(REG_IPU_CTRL, 0x40000000u); mem.write32(REG_IPU_CTRL, 0x40000000u);
@@ -128,7 +128,10 @@ void syMalloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
const uint32_t returnPc = getRegU32(ctx, 31); const uint32_t returnPc = getRegU32(ctx, 31);
PS2Runtime::RecompiledFunction syMallocFn = runtime->lookupFunction(kCvSyMallocAddr); PS2Runtime::RecompiledFunction syMallocFn = runtime->lookupFunction(kCvSyMallocAddr);
ctx->pc = kCvSyMallocAddr; ctx->pc = kCvSyMallocAddr;
syMallocFn(rdram, ctx, runtime); {
PS2Runtime::GuestExecutionScope guestExecution(runtime);
syMallocFn(rdram, ctx, runtime);
}
if (ctx->pc == kCvSyMallocAddr || ctx->pc == 0u) if (ctx->pc == kCvSyMallocAddr || ctx->pc == 0u)
{ {
@@ -355,7 +358,10 @@ void sdDrvInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
const uint32_t returnPc = getRegU32(ctx, 31); const uint32_t returnPc = getRegU32(ctx, 31);
PS2Runtime::RecompiledFunction sdrInit = runtime->lookupFunction(kSdrInitAddr); PS2Runtime::RecompiledFunction sdrInit = runtime->lookupFunction(kSdrInitAddr);
ctx->pc = kSdrInitAddr; ctx->pc = kSdrInitAddr;
sdrInit(rdram, ctx, runtime); {
PS2Runtime::GuestExecutionScope guestExecution(runtime);
sdrInit(rdram, ctx, runtime);
}
if (ctx->pc == kSdrInitAddr || ctx->pc == 0u) if (ctx->pc == kSdrInitAddr || ctx->pc == 0u)
{ {
@@ -17,7 +17,7 @@ static void throwIfTerminated(const std::shared_ptr<ThreadInfo> &info)
} }
} }
static void waitWhileSuspended(const std::shared_ptr<ThreadInfo> &info) static void waitWhileSuspended(const std::shared_ptr<ThreadInfo> &info, PS2Runtime *runtime = nullptr)
{ {
if (!info) if (!info)
return; return;
@@ -28,8 +28,11 @@ static void waitWhileSuspended(const std::shared_ptr<ThreadInfo> &info)
info->status = THS_SUSPEND; info->status = THS_SUSPEND;
info->waitType = TSW_NONE; info->waitType = TSW_NONE;
info->waitId = 0; info->waitId = 0;
info->cv.wait(lock, [&]() {
{ return info->suspendCount == 0 || info->terminated.load(); }); PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime);
info->cv.wait(lock, [&]()
{ return info->suspendCount == 0 || info->terminated.load(); });
}
if (info->terminated.load()) if (info->terminated.load())
{ {
throw ThreadExitException(); throw ThreadExitException();
@@ -165,9 +168,19 @@ static void ensureAlarmWorkerRunning()
try try
{ {
constexpr uint32_t kAlarmCallbackStackSize = 0x4000u;
thread_local PS2Runtime *s_alarmStackRuntime = nullptr;
thread_local uint32_t s_alarmStackTop = 0u;
if (s_alarmStackRuntime != readyAlarm->runtime || s_alarmStackTop == 0u)
{
s_alarmStackRuntime = readyAlarm->runtime;
s_alarmStackTop = readyAlarm->runtime->reserveAsyncCallbackStack(kAlarmCallbackStackSize, 16u);
}
R5900Context callbackCtx{}; R5900Context callbackCtx{};
setRegU32(&callbackCtx, 28, readyAlarm->gp); setRegU32(&callbackCtx, 28, readyAlarm->gp);
setRegU32(&callbackCtx, 29, readyAlarm->sp); setRegU32(&callbackCtx, 29,
(s_alarmStackTop != 0u) ? s_alarmStackTop : (PS2_RAM_SIZE - 0x10u));
setRegU32(&callbackCtx, 31, 0); setRegU32(&callbackCtx, 31, 0);
setRegU32(&callbackCtx, 4, static_cast<uint32_t>(readyAlarm->id)); setRegU32(&callbackCtx, 4, static_cast<uint32_t>(readyAlarm->id));
setRegU32(&callbackCtx, 5, static_cast<uint32_t>(readyAlarm->ticks)); setRegU32(&callbackCtx, 5, static_cast<uint32_t>(readyAlarm->ticks));
@@ -358,7 +371,10 @@ static bool rpcInvokeFunction(uint8_t *rdram, R5900Context *ctx, PS2Runtime *run
} }
PS2Runtime::RecompiledFunction func = runtime->lookupFunction(pc); PS2Runtime::RecompiledFunction func = runtime->lookupFunction(pc);
func(rdram, &tmp, runtime); {
PS2Runtime::GuestExecutionScope guestExecution(runtime);
func(rdram, &tmp, runtime);
}
++steps; ++steps;
} }
@@ -304,6 +304,29 @@ static void joinAllHostThreads()
} }
} }
static void detachAllHostThreads()
{
std::vector<std::thread> workers;
{
std::lock_guard<std::mutex> lock(g_host_thread_mutex);
workers.reserve(g_hostThreads.size());
for (auto &entry : g_hostThreads)
{
workers.push_back(std::move(entry.second));
}
g_hostThreads.clear();
}
for (auto &worker : workers)
{
if (!worker.joinable())
{
continue;
}
worker.detach();
}
}
struct RpcServerState struct RpcServerState
{ {
uint32_t sid = 0; uint32_t sid = 0;
@@ -307,12 +307,15 @@ void WaitSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
} }
sema->waiters++; sema->waiters++;
sema->cv.wait(lock, [&]() {
{ PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime);
bool forced = info ? info->forceRelease.load() : false; sema->cv.wait(lock, [&]()
bool terminated = info ? info->terminated.load() : false; {
return sema->count > 0 || sema->deleted || forced || terminated; // bool forced = info ? info->forceRelease.load() : false;
}); bool terminated = info ? info->terminated.load() : false;
return sema->count > 0 || sema->deleted || forced || terminated; //
});
}
sema->waiters--; sema->waiters--;
if (sema->deleted) if (sema->deleted)
{ {
@@ -354,7 +357,7 @@ void WaitSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
<< std::endl; << std::endl;
} }
lock.unlock(); lock.unlock();
waitWhileSuspended(info); waitWhileSuspended(info, runtime);
setReturnS32(ctx, ret); setReturnS32(ctx, ret);
} }
@@ -627,7 +630,10 @@ void WaitEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
} }
info->waiters++; info->waiters++;
info->cv.wait(lock, satisfied); {
PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime);
info->cv.wait(lock, satisfied);
}
info->waiters--; info->waiters--;
if (tInfo) if (tInfo)
@@ -689,7 +695,7 @@ void WaitEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
} }
lock.unlock(); lock.unlock();
waitWhileSuspended(tInfo); waitWhileSuspended(tInfo, runtime);
setReturnS32(ctx, ret); setReturnS32(ctx, ret);
} }
@@ -54,6 +54,26 @@ static void writeGuestU64NoThrow(uint8_t *rdram, uint32_t addr, uint64_t value)
std::memcpy(dst, &value, sizeof(value)); std::memcpy(dst, &value, sizeof(value));
} }
static uint32_t getAsyncHandlerStackTop(PS2Runtime *runtime)
{
constexpr uint32_t kAsyncHandlerStackSize = 0x4000u;
thread_local PS2Runtime *s_cachedRuntime = nullptr;
thread_local uint32_t s_cachedStackTop = 0u;
if (runtime == nullptr)
{
return PS2_RAM_SIZE - 0x10u;
}
if (s_cachedRuntime != runtime || s_cachedStackTop == 0u)
{
s_cachedRuntime = runtime;
s_cachedStackTop = runtime->reserveAsyncCallbackStack(kAsyncHandlerStackSize, 16u);
}
return (s_cachedStackTop != 0u) ? s_cachedStackTop : (PS2_RAM_SIZE - 0x10u);
}
static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t cause) static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t cause)
{ {
if (!rdram || !runtime) if (!rdram || !runtime)
@@ -87,9 +107,8 @@ static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, ui
} }
handlers.push_back(info); handlers.push_back(info);
} }
std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b) { std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b)
return a.order < b.order; { return a.order < b.order; });
});
} }
for (const IrqHandlerInfo &info : handlers) for (const IrqHandlerInfo &info : handlers)
@@ -102,9 +121,8 @@ static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, ui
try try
{ {
R5900Context irqCtx{}; R5900Context irqCtx{};
const uint32_t sp = (info.sp != 0u) ? info.sp : (PS2_RAM_SIZE - 0x10u);
SET_GPR_U32(&irqCtx, 28, info.gp); SET_GPR_U32(&irqCtx, 28, info.gp);
SET_GPR_U32(&irqCtx, 29, sp); SET_GPR_U32(&irqCtx, 29, getAsyncHandlerStackTop(runtime));
SET_GPR_U32(&irqCtx, 31, 0u); SET_GPR_U32(&irqCtx, 31, 0u);
SET_GPR_U32(&irqCtx, 4, cause); SET_GPR_U32(&irqCtx, 4, cause);
SET_GPR_U32(&irqCtx, 5, info.arg); SET_GPR_U32(&irqCtx, 5, info.arg);
@@ -119,6 +137,8 @@ static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, ui
{ {
break; break;
} }
// Interrupt handlers must be able to preempt a guest thread that is
// spinning on interrupt-produced state, such as a vblank counter.
step(rdram, &irqCtx, runtime); step(rdram, &irqCtx, runtime);
} }
} }
@@ -171,9 +191,8 @@ void dispatchDmacHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t
} }
handlers.push_back(info); handlers.push_back(info);
} }
std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b) { std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b)
return a.order < b.order; { return a.order < b.order; });
});
} }
for (const IrqHandlerInfo &info : handlers) for (const IrqHandlerInfo &info : handlers)
@@ -186,9 +205,8 @@ void dispatchDmacHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t
try try
{ {
R5900Context irqCtx{}; R5900Context irqCtx{};
const uint32_t sp = (info.sp != 0u) ? info.sp : (PS2_RAM_SIZE - 0x10u);
SET_GPR_U32(&irqCtx, 28, info.gp); SET_GPR_U32(&irqCtx, 28, info.gp);
SET_GPR_U32(&irqCtx, 29, sp); SET_GPR_U32(&irqCtx, 29, getAsyncHandlerStackTop(runtime));
SET_GPR_U32(&irqCtx, 31, 0u); SET_GPR_U32(&irqCtx, 31, 0u);
SET_GPR_U32(&irqCtx, 4, cause); SET_GPR_U32(&irqCtx, 4, cause);
SET_GPR_U32(&irqCtx, 5, info.arg); SET_GPR_U32(&irqCtx, 5, info.arg);
@@ -256,7 +274,8 @@ static void interruptWorkerMain(uint8_t *rdram, PS2Runtime *runtime)
{ {
{ {
std::unique_lock<std::mutex> lock(g_irq_worker_mutex); std::unique_lock<std::mutex> lock(g_irq_worker_mutex);
if (g_irq_worker_cv.wait_until(lock, nextTick, []() { return g_irq_worker_stop.load(std::memory_order_acquire); })) if (g_irq_worker_cv.wait_until(lock, nextTick, []()
{ return g_irq_worker_stop.load(std::memory_order_acquire); }))
{ {
break; break;
} }
@@ -276,7 +295,8 @@ static void interruptWorkerMain(uint8_t *rdram, PS2Runtime *runtime)
for (int i = 0; i < ticksToProcess; ++i) for (int i = 0; i < ticksToProcess; ++i)
{ {
signalVSyncFlag(rdram); const uint64_t tickValue = signalVSyncFlag(rdram);
ps2_stubs::dispatchGsSyncVCallback(rdram, runtime, tickValue);
dispatchIntcHandlersForCause(rdram, runtime, kIntcVblankStart); dispatchIntcHandlersForCause(rdram, runtime, kIntcVblankStart);
std::this_thread::sleep_for(std::chrono::microseconds(500)); std::this_thread::sleep_for(std::chrono::microseconds(500));
dispatchIntcHandlersForCause(rdram, runtime, kIntcVblankEnd); dispatchIntcHandlersForCause(rdram, runtime, kIntcVblankEnd);
@@ -312,22 +332,43 @@ static void ensureInterruptWorkerRunning(uint8_t *rdram, PS2Runtime *runtime)
} }
} }
void EnsureVSyncWorkerRunning(uint8_t *rdram, PS2Runtime *runtime)
{
ensureInterruptWorkerRunning(rdram, runtime);
}
uint64_t GetCurrentVSyncTick()
{
std::lock_guard<std::mutex> lock(g_vsync_flag_mutex);
return g_vsync_tick_counter;
}
void stopInterruptWorker() void stopInterruptWorker()
{ {
g_irq_worker_stop.store(true, std::memory_order_release); g_irq_worker_stop.store(true, std::memory_order_release);
g_irq_worker_cv.notify_all(); g_irq_worker_cv.notify_all();
std::unique_lock<std::mutex> lock(g_irq_worker_mutex); std::unique_lock<std::mutex> lock(g_irq_worker_mutex);
g_irq_worker_cv.wait_for(lock, std::chrono::milliseconds(500), []() { g_irq_worker_cv.wait_for(lock, std::chrono::milliseconds(500), []()
return !g_irq_worker_running.load(std::memory_order_acquire); { return !g_irq_worker_running.load(std::memory_order_acquire); });
}); g_vsync_cv.notify_all();
} }
void WaitVSyncTick(uint8_t *rdram, PS2Runtime *runtime) uint64_t WaitForNextVSyncTick(uint8_t *rdram, PS2Runtime *runtime)
{ {
ensureInterruptWorkerRunning(rdram, runtime); ensureInterruptWorkerRunning(rdram, runtime);
std::unique_lock<std::mutex> lock(g_vsync_flag_mutex); std::unique_lock<std::mutex> lock(g_vsync_flag_mutex);
uint64_t current = g_vsync_tick_counter; uint64_t current = g_vsync_tick_counter;
g_vsync_cv.wait(lock, [current]() { return g_vsync_tick_counter > current; }); {
PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime);
g_vsync_cv.wait(lock, [current, runtime]()
{ return g_vsync_tick_counter > current || (runtime != nullptr && runtime->isStopRequested()); });
}
return g_vsync_tick_counter;
}
void WaitVSyncTick(uint8_t *rdram, PS2Runtime *runtime)
{
(void)WaitForNextVSyncTick(rdram, runtime);
} }
void SetVSyncFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) void SetVSyncFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
@@ -372,7 +372,7 @@ void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
throw ThreadExitException(); throw ThreadExitException();
} }
waitWhileSuspended(info); waitWhileSuspended(info, runtime);
const uint32_t pc = threadCtx->pc; const uint32_t pc = threadCtx->pc;
if (pc == 0u) if (pc == 0u)
@@ -445,7 +445,10 @@ void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
<< std::hex << pc << std::dec << std::endl; << std::hex << pc << std::dec << std::endl;
throw ThreadExitException(); throw ThreadExitException();
} }
step(rdram, threadCtx, runtime); {
PS2Runtime::GuestExecutionScope guestExecution(runtime);
step(rdram, threadCtx, runtime);
}
} }
} }
catch (const ThreadExitException &) catch (const ThreadExitException &)
@@ -606,8 +609,11 @@ void TerminateThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{ {
// Block until the target thread actually finishes unwinding and becomes dormant // Block until the target thread actually finishes unwinding and becomes dormant
std::unique_lock<std::mutex> lock(info->m); std::unique_lock<std::mutex> lock(info->m);
info->cv.wait(lock, [&]() {
{ return !info->started && info->status == THS_DORMANT; }); PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime);
info->cv.wait(lock, [&]()
{ return !info->started && info->status == THS_DORMANT; });
}
} }
setReturnS32(ctx, KE_OK); setReturnS32(ctx, KE_OK);
@@ -641,8 +647,11 @@ void SuspendThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
if (tid == g_currentThreadId) if (tid == g_currentThreadId)
{ {
std::unique_lock<std::mutex> lock(info->m); std::unique_lock<std::mutex> lock(info->m);
info->cv.wait(lock, [&]() {
{ return info->suspendCount == 0 || info->terminated.load(); }); PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime);
info->cv.wait(lock, [&]()
{ return info->suspendCount == 0 || info->terminated.load(); });
}
if (info->terminated.load()) if (info->terminated.load())
{ {
throw ThreadExitException(); throw ThreadExitException();
@@ -784,8 +793,11 @@ void SleepThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
info->waitId = 0; info->waitId = 0;
info->forceRelease = false; info->forceRelease = false;
info->cv.wait(lock, [&]() {
{ return info->wakeupCount > 0 || info->forceRelease.load() || info->terminated.load(); }); PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime);
info->cv.wait(lock, [&]()
{ return info->wakeupCount > 0 || info->forceRelease.load() || info->terminated.load(); });
}
if (info->terminated.load()) if (info->terminated.load())
{ {
@@ -820,7 +832,7 @@ void SleepThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
} }
lock.unlock(); lock.unlock();
waitWhileSuspended(info); waitWhileSuspended(info, runtime);
setReturnS32(ctx, ret); setReturnS32(ctx, ret);
} }
+10 -3
View File
@@ -838,7 +838,7 @@ void register_code_generator_tests()
t.IsTrue(generated.find("if (ctx->pc != 0xD008u) { return; }") != std::string::npos, "JALR should check return PC"); t.IsTrue(generated.find("if (ctx->pc != 0xD008u) { return; }") != std::string::npos, "JALR should check return PC");
}); });
tc.Run("backward BEQ emits label and goto (sign-extended offset)", [](TestCase &t) { tc.Run("backward BEQ yields cooperatively on sign-extended internal loop", [](TestCase &t) {
Function func; Function func;
func.name = "backward_branch"; func.name = "backward_branch";
func.start = 0x1100; func.start = 0x1100;
@@ -861,10 +861,17 @@ void register_code_generator_tests()
CodeGenerator gen({}, {}); CodeGenerator gen({}, {});
std::string generated = gen.generateFunction(func, instructions, false); std::string generated = gen.generateFunction(func, instructions, false);
printGeneratedCode("backward BEQ emits label and goto (sign-extended offset)", generated); printGeneratedCode("backward BEQ yields cooperatively on sign-extended internal loop", generated);
t.IsTrue(generated.find("label_1100:") != std::string::npos, "target should emit a label"); t.IsTrue(generated.find("label_1100:") != std::string::npos, "target should emit a label");
t.IsTrue(generated.find("goto label_1100;") != std::string::npos, "backward internal branch should goto label"); t.IsTrue(generated.find("ctx->pc = 0x1100u;") != std::string::npos,
"backward internal branch should preserve the loop target in ctx->pc");
t.IsTrue(generated.find("runtime->cooperativeGuestYield();") != std::string::npos,
"backward internal branch should yield guest execution before re-entering the loop");
t.IsTrue(generated.find("goto label_1100;") != std::string::npos,
"backward internal branch should re-enter the in-function label after yielding");
t.IsTrue(generated.find("ctx->pc = 0x1100u;\n return;") == std::string::npos,
"backward internal branch should not return to the dispatcher for a non-entry internal label");
}); });
tc.Run("branch-likely places delay slot only in taken path", [](TestCase &t) { tc.Run("branch-likely places delay slot only in taken path", [](TestCase &t) {
+471
View File
@@ -1,17 +1,25 @@
#include "MiniTest.h" #include "MiniTest.h"
#include "ps2_memory.h" #include "ps2_memory.h"
#include "ps2_runtime.h" #include "ps2_runtime.h"
#include "ps2_stubs.h"
#include "ps2_syscalls.h" #include "ps2_syscalls.h"
#include "ps2_gs_gpu.h" #include "ps2_gs_gpu.h"
#include "ps2_gs_psmt4.h"
#include <atomic>
#include <chrono>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <thread>
#include <vector> #include <vector>
using namespace ps2_syscalls; using namespace ps2_syscalls;
namespace namespace
{ {
std::atomic<uint32_t> g_gsSyncCallbackHits{0u};
std::atomic<uint32_t> g_gsSyncCallbackLastTick{0u};
void setRegU32(R5900Context &ctx, int reg, uint32_t value) void setRegU32(R5900Context &ctx, int reg, uint32_t value)
{ {
ctx.r[reg] = _mm_set_epi64x(0, static_cast<int64_t>(value)); ctx.r[reg] = _mm_set_epi64x(0, static_cast<int64_t>(value));
@@ -45,6 +53,32 @@ namespace
dst.resize(pos + sizeof(uint64_t)); dst.resize(pos + sizeof(uint64_t));
std::memcpy(dst.data() + pos, &value, sizeof(uint64_t)); std::memcpy(dst.data() + pos, &value, sizeof(uint64_t));
} }
template <typename Predicate>
bool waitUntil(Predicate pred, std::chrono::milliseconds timeout)
{
const auto deadline = std::chrono::steady_clock::now() + timeout;
while (std::chrono::steady_clock::now() < deadline)
{
if (pred())
{
return true;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
return pred();
}
void testGsSyncVCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)rdram;
(void)runtime;
g_gsSyncCallbackLastTick.store(getRegU32(ctx, 4), std::memory_order_relaxed);
g_gsSyncCallbackHits.fetch_add(1u, std::memory_order_relaxed);
ctx->pc = 0u;
}
} }
void register_ps2_gs_tests() void register_ps2_gs_tests()
@@ -133,6 +167,71 @@ void register_ps2_gs_tests()
t.Equals(currentImr, 0x3333444411112222ull, "GsGetIMR should return current GS IMR"); t.Equals(currentImr, 0x3333444411112222ull, "GsGetIMR should return current GS IMR");
}); });
tc.Run("sceGsSetDefDBuffDc seeds display envs and swap applies the selected page", [](TestCase &t)
{
PS2Runtime runtime;
t.IsTrue(runtime.memory().initialize(), "runtime memory initialize should succeed");
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
constexpr uint32_t kEnvAddr = 0x4000u;
constexpr uint32_t kDispEnvSize = 40u;
constexpr uint32_t kDBuffSize = 0x330u;
constexpr uint32_t kDispFbOffset = 16u;
constexpr uint32_t kDisplayOffset = 24u;
constexpr uint32_t kDraw01Offset = 0x60u;
constexpr uint32_t kFrame1Offset = kDraw01Offset + 0x00u;
constexpr uint32_t kFrame1AddrOffset = kDraw01Offset + 0x08u;
constexpr uint32_t kXYOffset1Offset = kDraw01Offset + 0x20u;
constexpr uint32_t kXYOffset1AddrOffset = kDraw01Offset + 0x28u;
R5900Context ctx{};
setRegU32(ctx, 4, kEnvAddr);
setRegU32(ctx, 5, 0u);
setRegU32(ctx, 6, 640u);
setRegU32(ctx, 7, 448u);
std::memset(rdram.data() + kEnvAddr, 0xCD, kDBuffSize);
ps2_stubs::sceGsSetDefDBuffDc(rdram.data(), &ctx, &runtime);
uint64_t dispfb0 = 0u;
uint64_t display0 = 0u;
uint64_t frame10 = 0u;
uint64_t frame10Addr = 0u;
uint64_t xyoffset10 = 0u;
uint64_t xyoffset10Addr = 0u;
std::memcpy(&dispfb0, rdram.data() + kEnvAddr + kDispFbOffset, sizeof(dispfb0));
std::memcpy(&display0, rdram.data() + kEnvAddr + kDisplayOffset, sizeof(display0));
std::memcpy(&frame10, rdram.data() + kEnvAddr + kFrame1Offset, sizeof(frame10));
std::memcpy(&frame10Addr, rdram.data() + kEnvAddr + kFrame1AddrOffset, sizeof(frame10Addr));
std::memcpy(&xyoffset10, rdram.data() + kEnvAddr + kXYOffset1Offset, sizeof(xyoffset10));
std::memcpy(&xyoffset10Addr, rdram.data() + kEnvAddr + kXYOffset1AddrOffset, sizeof(xyoffset10Addr));
t.Equals((dispfb0 >> 9) & 0x3Fu, 10ull, "dbuff display env should seed FBW from width");
t.Equals((display0 >> 32) & 0x0FFFull, 639ull, "dbuff display env should seed DW from width");
t.Equals((display0 >> 44) & 0x07FFull, 447ull, "dbuff display env should seed DH from height");
t.Equals((frame10 >> 16) & 0x3Full, 10ull, "dbuff draw env should seed FRAME FBW from width");
t.Equals(frame10Addr, 0x4Cull, "dbuff draw env should seed FRAME_1 register id");
t.Equals(xyoffset10 & 0xFFFFull, 0x6C00ull, "dbuff draw env should seed OFX in 12.4 fixed point");
t.Equals((xyoffset10 >> 32) & 0xFFFFull, 0x7200ull, "dbuff draw env should seed OFY in 12.4 fixed point");
t.Equals(xyoffset10Addr, 0x18ull, "dbuff draw env should seed XYOFFSET_1 register id");
dispfb0 = (dispfb0 & ~0x1FFull) | 150ull;
std::memcpy(rdram.data() + kEnvAddr + kDispFbOffset, &dispfb0, sizeof(dispfb0));
uint64_t dispfb1 = dispfb0;
dispfb1 = (dispfb1 & ~0x1FFull) | 151ull;
std::memcpy(rdram.data() + kEnvAddr + kDispEnvSize + kDispFbOffset, &dispfb1, sizeof(dispfb1));
std::memset(&ctx, 0, sizeof(ctx));
setRegU32(ctx, 4, kEnvAddr);
setRegU32(ctx, 5, 1u);
ps2_stubs::sceGsSwapDBuffDc(rdram.data(), &ctx, &runtime);
t.Equals(runtime.memory().gs().dispfb1 & 0x1FFull, 151ull,
"sceGsSwapDBuffDc should program GS to the selected display page");
t.Equals((runtime.memory().gs().display1 >> 32) & 0x0FFFull, 639ull,
"sceGsSwapDBuffDc should preserve the display width from the seeded env");
});
tc.Run("GIF PACKED A+D writes DISPFB1 and DISPLAY1 privileged registers", [](TestCase &t) tc.Run("GIF PACKED A+D writes DISPFB1 and DISPLAY1 privileged registers", [](TestCase &t)
{ {
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u); std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
@@ -157,6 +256,35 @@ void register_ps2_gs_tests()
t.Equals(regs.display1, display1, "A+D should write GS DISPLAY1"); t.Equals(regs.display1, display1, "A+D should write GS DISPLAY1");
}); });
tc.Run("PSMT4 address mapping matches Veronica Conv4to32 layout", [](TestCase &t)
{
constexpr uint32_t kBaseBlock = 0u;
constexpr uint32_t kWidth = 2u; // One 128x128 PSMT4 page.
t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 0u, 0u), 0u,
"PSMT4 origin should map to nibble offset 0");
t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 1u, 0u), 8u,
"PSMT4 x=1 should advance to the next packed nibble group");
t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 0u, 1u), 512u,
"PSMT4 second source row should land on the next CT32 row stride");
t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 0u, 2u), 33u,
"PSMT4 third source row should keep Veronica's interleaved ordering");
t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 0u, 3u), 545u,
"PSMT4 fourth source row should include both interleave and CT32 row stride");
t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 31u, 15u), 3647u,
"PSMT4 final texel in the first 32x16 block should match Veronica's block layout");
t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 32u, 0u), 4096u,
"PSMT4 x=32 should advance to the next CT32 block column");
t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 32u, 16u), 4160u,
"PSMT4 x=32,y=16 should include both block-column and block-row offsets");
t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 64u, 0u), 8192u,
"PSMT4 x=64 should advance to the third block column in the page");
t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 96u, 112u), 12736u,
"PSMT4 bottom-right block origin should match Veronica's page permutation");
t.Equals(GSPSMT4::addrPSMT4(kBaseBlock, kWidth, 127u, 127u), 16383u,
"PSMT4 final texel in a 128x128 page should land at the end of the page");
});
tc.Run("GIF REGLIST with odd register count consumes 128-bit padding before next tag", [](TestCase &t) tc.Run("GIF REGLIST with odd register count consumes 128-bit padding before next tag", [](TestCase &t)
{ {
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u); std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
@@ -448,5 +576,348 @@ void register_ps2_gs_tests()
t.Equals(out[0], static_cast<uint8_t>(0x21u), "packed nibble byte 0 should roundtrip"); t.Equals(out[0], static_cast<uint8_t>(0x21u), "packed nibble byte 0 should roundtrip");
t.Equals(out[1], static_cast<uint8_t>(0x43u), "packed nibble byte 1 should roundtrip"); t.Equals(out[1], static_cast<uint8_t>(0x43u), "packed nibble byte 1 should roundtrip");
}); });
tc.Run("GS PSMT4 host-local upload keeps position across split IMAGE packets", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
const uint64_t bitblt =
(static_cast<uint64_t>(0u) << 0) |
(static_cast<uint64_t>(1u) << 16) |
(static_cast<uint64_t>(GS_PSM_T4) << 24) |
(static_cast<uint64_t>(0u) << 32) |
(static_cast<uint64_t>(1u) << 48) |
(static_cast<uint64_t>(GS_PSM_T4) << 56);
gs.writeRegister(GS_REG_BITBLTBUF, bitblt);
gs.writeRegister(GS_REG_TRXPOS, 0ull);
gs.writeRegister(GS_REG_TRXREG, (8ull << 0) | (8ull << 32)); // 64 texels => 32 bytes
gs.writeRegister(GS_REG_TRXDIR, 0ull);
uint8_t packedSource[32] = {};
for (uint32_t i = 0; i < 32u; ++i)
{
packedSource[i] = static_cast<uint8_t>(0x10u + i);
}
std::vector<uint8_t> packetA;
appendU64(packetA, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true));
appendU64(packetA, 0ull);
packetA.insert(packetA.end(), packedSource, packedSource + 16u);
std::vector<uint8_t> packetB;
appendU64(packetB, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true));
appendU64(packetB, 0ull);
packetB.insert(packetB.end(), packedSource + 16u, packedSource + 32u);
gs.processGIFPacket(packetA.data(), static_cast<uint32_t>(packetA.size()));
gs.processGIFPacket(packetB.data(), static_cast<uint32_t>(packetB.size()));
gs.writeRegister(GS_REG_TRXDIR, 1ull);
uint8_t out[32] = {};
const uint32_t outBytes = gs.consumeLocalToHostBytes(out, sizeof(out));
t.Equals(outBytes, 32u, "split T4 IMAGE upload should fill the full packed byte range");
for (uint32_t i = 0; i < 32u; ++i)
{
t.Equals(out[i], packedSource[i], "split T4 IMAGE upload should preserve packed nibble order");
}
});
tc.Run("GS PSMT4 local-local copy respects swizzled page layout", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
constexpr uint32_t kSrcBp = 64u;
constexpr uint32_t kDstBp = 96u;
constexpr uint64_t kUploadBitblt =
(static_cast<uint64_t>(0u) << 0) |
(static_cast<uint64_t>(2u) << 16) |
(static_cast<uint64_t>(GS_PSM_T4) << 24) |
(static_cast<uint64_t>(kSrcBp) << 32) |
(static_cast<uint64_t>(2u) << 48) |
(static_cast<uint64_t>(GS_PSM_T4) << 56);
constexpr uint64_t kCopyBitblt =
(static_cast<uint64_t>(kSrcBp) << 0) |
(static_cast<uint64_t>(2u) << 16) |
(static_cast<uint64_t>(GS_PSM_T4) << 24) |
(static_cast<uint64_t>(kDstBp) << 32) |
(static_cast<uint64_t>(2u) << 48) |
(static_cast<uint64_t>(GS_PSM_T4) << 56);
constexpr uint64_t kCopyPos =
(static_cast<uint64_t>(0u) << 0) |
(static_cast<uint64_t>(0u) << 16) |
(static_cast<uint64_t>(32u) << 32) |
(static_cast<uint64_t>(16u) << 48);
constexpr uint64_t kReadBitblt =
(static_cast<uint64_t>(kDstBp) << 0) |
(static_cast<uint64_t>(2u) << 16) |
(static_cast<uint64_t>(GS_PSM_T4) << 24) |
(static_cast<uint64_t>(0u) << 32) |
(static_cast<uint64_t>(2u) << 48) |
(static_cast<uint64_t>(GS_PSM_T4) << 56);
constexpr uint64_t kReadPos =
(static_cast<uint64_t>(32u) << 0) |
(static_cast<uint64_t>(16u) << 16);
constexpr uint64_t kRect = (8ull << 0) | (4ull << 32);
const uint8_t packedSource[16] = {
0x10u, 0x32u, 0x54u, 0x76u,
0x98u, 0xBAu, 0xDCu, 0xFEu,
0x01u, 0x23u, 0x45u, 0x67u,
0x89u, 0xABu, 0xCDu, 0xEFu
};
gs.writeRegister(GS_REG_BITBLTBUF, kUploadBitblt);
gs.writeRegister(GS_REG_TRXPOS, 0ull);
gs.writeRegister(GS_REG_TRXREG, kRect);
gs.writeRegister(GS_REG_TRXDIR, 0ull);
std::vector<uint8_t> packet;
appendU64(packet, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true));
appendU64(packet, 0ull);
packet.insert(packet.end(), packedSource, packedSource + sizeof(packedSource));
gs.processGIFPacket(packet.data(), static_cast<uint32_t>(packet.size()));
gs.writeRegister(GS_REG_BITBLTBUF, kCopyBitblt);
gs.writeRegister(GS_REG_TRXPOS, kCopyPos);
gs.writeRegister(GS_REG_TRXREG, kRect);
gs.writeRegister(GS_REG_TRXDIR, 2ull);
gs.writeRegister(GS_REG_BITBLTBUF, kReadBitblt);
gs.writeRegister(GS_REG_TRXPOS, kReadPos);
gs.writeRegister(GS_REG_TRXREG, kRect);
gs.writeRegister(GS_REG_TRXDIR, 1ull);
uint8_t out[16] = {};
const uint32_t outBytes = gs.consumeLocalToHostBytes(out, sizeof(out));
t.Equals(outBytes, 16u, "PSMT4 local-local copy should preserve the full packed byte count");
for (size_t i = 0; i < sizeof(packedSource); ++i)
{
t.Equals(out[i], packedSource[i], "PSMT4 local-local copy should preserve packed nibble order");
}
});
tc.Run("GS T4 CSM1 lookup matches Veronica ClutCopy layout", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
constexpr uint32_t kTexTbp = 64u;
constexpr uint32_t kClutCbp = 128u;
constexpr uint32_t kFrameReg =
(0ull << 0) |
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kTex0 =
(static_cast<uint64_t>(kTexTbp) << 0) |
(1ull << 14) |
(static_cast<uint64_t>(GS_PSM_T4) << 20) |
(0ull << 26) |
(0ull << 30) |
(1ull << 34) |
(1ull << 35) |
(static_cast<uint64_t>(kClutCbp) << 37) |
(static_cast<uint64_t>(GS_PSM_CT32) << 51);
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_SPRITE) |
(1ull << 4) | // TME
(1ull << 8); // FST
constexpr uint32_t kExpectedColor = 0x800000FFu; // RGBA = (255,0,0,128)
constexpr uint32_t kWrongColor = 0x8000FF00u; // RGBA = (0,255,0,128)
const uint32_t texNibbleAddr = GSPSMT4::addrPSMT4(kTexTbp, 1u, 0u, 0u);
const uint32_t texByteOff = texNibbleAddr >> 1;
vram[texByteOff] = static_cast<uint8_t>((vram[texByteOff] & 0xF0u) | 0x08u);
// Veronica's ClutCopy stores logical entries 8..15 into physical slots 16..23.
std::memcpy(vram.data() + kClutCbp * 256u + 8u * 4u, &kWrongColor, sizeof(kWrongColor));
std::memcpy(vram.data() + kClutCbp * 256u + 16u * 4u, &kExpectedColor, sizeof(kExpectedColor));
gs.writeRegister(GS_REG_FRAME_1, kFrameReg);
gs.writeRegister(GS_REG_SCISSOR_1, 0ull);
gs.writeRegister(GS_REG_XYOFFSET_1, 0ull);
gs.writeRegister(GS_REG_TEST_1, 0ull);
gs.writeRegister(GS_REG_ALPHA_1, 0ull);
gs.writeRegister(GS_REG_TEX0_1, kTex0);
gs.writeRegister(GS_REG_PRIM, kPrim);
gs.writeRegister(GS_REG_RGBAQ, 0x80808080ull);
gs.writeRegister(GS_REG_UV, 0ull);
gs.writeRegister(GS_REG_XYZ2, 0ull);
gs.writeRegister(GS_REG_UV, 0ull);
gs.writeRegister(GS_REG_XYZ2, 0ull);
uint32_t pixel = 0u;
std::memcpy(&pixel, vram.data(), sizeof(pixel));
t.Equals(pixel, kExpectedColor,
"T4 CSM1 lookup should follow Veronica's swizzled CLUT layout for logical index 8");
});
tc.Run("GS alpha test AFAIL framebuffer-only still writes the pixel", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
constexpr uint64_t kFrame =
(0ull << 0) |
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kScissor =
(0ull << 0) |
(0ull << 16) |
(0ull << 32) |
(0ull << 48);
constexpr uint64_t kTest =
1ull | // ATE
(5ull << 1) | // ATST = GEQUAL
(0x80ull << 4) | // AREF
(1ull << 12); // AFAIL = FB_ONLY
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_POINT);
constexpr uint64_t kRgbaq =
(0x12ull << 0) |
(0x34ull << 8) |
(0x56ull << 16) |
(0x00ull << 24) |
(0x3F800000ull << 32); // q = 1.0f
gs.writeRegister(GS_REG_FRAME_1, kFrame);
gs.writeRegister(GS_REG_SCISSOR_1, kScissor);
gs.writeRegister(GS_REG_TEST_1, kTest);
gs.writeRegister(GS_REG_PRIM, kPrim);
gs.writeRegister(GS_REG_RGBAQ, kRgbaq);
gs.writeRegister(GS_REG_XYZ2, 0ull);
uint32_t pixel = 0u;
std::memcpy(&pixel, vram.data(), sizeof(pixel));
t.Equals(pixel, 0x00563412u,
"AFAIL=FB_ONLY should still update the framebuffer when the alpha test fails");
});
tc.Run("GS alpha test AFAIL RGB-only preserves destination alpha", [](TestCase &t)
{
std::vector<uint8_t> vram(PS2_GS_VRAM_SIZE, 0u);
GS gs;
gs.init(vram.data(), static_cast<uint32_t>(vram.size()), nullptr);
constexpr uint64_t kFrame =
(0ull << 0) |
(1ull << 16) |
(static_cast<uint64_t>(GS_PSM_CT32) << 24);
constexpr uint64_t kScissor =
(0ull << 0) |
(0ull << 16) |
(0ull << 32) |
(0ull << 48);
constexpr uint64_t kTest =
1ull | // ATE
(5ull << 1) | // ATST = GEQUAL
(0x80ull << 4) | // AREF
(3ull << 12); // AFAIL = RGB_ONLY
constexpr uint64_t kPrim =
static_cast<uint64_t>(GS_PRIM_POINT);
constexpr uint64_t kRgbaq =
(0x12ull << 0) |
(0x34ull << 8) |
(0x56ull << 16) |
(0x00ull << 24) |
(0x3F800000ull << 32); // q = 1.0f
constexpr uint32_t kExisting = 0xAB030201u;
std::memcpy(vram.data(), &kExisting, sizeof(kExisting));
gs.writeRegister(GS_REG_FRAME_1, kFrame);
gs.writeRegister(GS_REG_SCISSOR_1, kScissor);
gs.writeRegister(GS_REG_TEST_1, kTest);
gs.writeRegister(GS_REG_PRIM, kPrim);
gs.writeRegister(GS_REG_RGBAQ, kRgbaq);
gs.writeRegister(GS_REG_XYZ2, 0ull);
uint32_t pixel = 0u;
std::memcpy(&pixel, vram.data(), sizeof(pixel));
t.Equals(pixel, 0xAB563412u,
"AFAIL=RGB_ONLY should update RGB while preserving destination alpha");
});
tc.Run("sceGsSyncV waits on VBlank and reports interlaced field parity", [](TestCase &t)
{
notifyRuntimeStop();
ps2_stubs::resetGsSyncVCallbackState();
PS2Runtime runtime;
t.IsTrue(runtime.memory().initialize(), "runtime memory initialize should succeed");
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
R5900Context resetCtx{};
setRegU32(resetCtx, 4, 0u);
setRegU32(resetCtx, 5, 1u);
setRegU32(resetCtx, 6, 2u);
setRegU32(resetCtx, 7, 1u);
ps2_stubs::sceGsResetGraph(rdram.data(), &resetCtx, &runtime);
R5900Context sync0{};
ps2_stubs::sceGsSyncV(rdram.data(), &sync0, &runtime);
t.Equals(static_cast<int32_t>(getRegU32Test(sync0, 2)), 0, "first interlaced sceGsSyncV should report even field");
R5900Context sync1{};
ps2_stubs::sceGsSyncV(rdram.data(), &sync1, &runtime);
t.Equals(static_cast<int32_t>(getRegU32Test(sync1, 2)), 1, "second interlaced sceGsSyncV should report odd field");
R5900Context resetProgCtx{};
setRegU32(resetProgCtx, 4, 0u);
setRegU32(resetProgCtx, 5, 0u);
setRegU32(resetProgCtx, 6, 2u);
setRegU32(resetProgCtx, 7, 1u);
ps2_stubs::sceGsResetGraph(rdram.data(), &resetProgCtx, &runtime);
R5900Context syncProg{};
ps2_stubs::sceGsSyncV(rdram.data(), &syncProg, &runtime);
t.Equals(static_cast<int32_t>(getRegU32Test(syncProg, 2)), 1, "progressive sceGsSyncV should always return one");
runtime.requestStop();
notifyRuntimeStop();
ps2_stubs::resetGsSyncVCallbackState();
});
tc.Run("sceGsSyncVCallback uses the shared VBlank worker", [](TestCase &t)
{
notifyRuntimeStop();
ps2_stubs::resetGsSyncVCallbackState();
g_gsSyncCallbackHits.store(0u, std::memory_order_relaxed);
g_gsSyncCallbackLastTick.store(0u, std::memory_order_relaxed);
PS2Runtime runtime;
t.IsTrue(runtime.memory().initialize(), "runtime memory initialize should succeed");
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
constexpr uint32_t kCallbackAddr = 0x120000u;
runtime.registerFunction(kCallbackAddr, testGsSyncVCallback);
R5900Context callbackCtx{};
setRegU32(callbackCtx, 4, kCallbackAddr);
ps2_stubs::sceGsSyncVCallback(rdram.data(), &callbackCtx, &runtime);
t.Equals(getRegU32Test(callbackCtx, 2), 0u, "first sceGsSyncVCallback registration should return no previous callback");
const bool callbackFired = waitUntil([]() {
return g_gsSyncCallbackHits.load(std::memory_order_acquire) > 0u;
}, std::chrono::milliseconds(80));
t.IsTrue(callbackFired, "registered GS VSync callback should fire from the VBlank worker");
t.IsTrue(g_gsSyncCallbackLastTick.load(std::memory_order_acquire) > 0u,
"VSync callback should receive a positive tick value");
R5900Context clearCtx{};
setRegU32(clearCtx, 4, 0u);
ps2_stubs::sceGsSyncVCallback(rdram.data(), &clearCtx, &runtime);
t.Equals(getRegU32Test(clearCtx, 2), kCallbackAddr, "clearing sceGsSyncVCallback should return the previous callback");
runtime.requestStop();
notifyRuntimeStop();
ps2_stubs::resetGsSyncVCallbackState();
});
}); });
} }
+60
View File
@@ -100,6 +100,22 @@ void register_ps2_memory_tests()
t.Equals(mem.translateAddress(0xA0005678u), 0x00005678u, "KSEG1 should map directly to physical"); t.Equals(mem.translateAddress(0xA0005678u), 0x00005678u, "KSEG1 should map directly to physical");
t.Equals(mem.translateAddress(0x20001234u), 0x00001234u, "0x2000 uncached alias should map to RAM"); t.Equals(mem.translateAddress(0x20001234u), 0x00001234u, "0x2000 uncached alias should map to RAM");
t.Equals(mem.translateAddress(0x30105678u), 0x00105678u, "0x3010 accelerated alias should map to RAM"); t.Equals(mem.translateAddress(0x30105678u), 0x00105678u, "0x3010 accelerated alias should map to RAM");
t.Equals(mem.translateAddress(PS2_SCRATCHPAD_BASE + 0x123u), 0x123u, "scratchpad base should translate to local offset");
t.Equals(mem.translateAddress(PS2_SCRATCHPAD_ALIAS_BASE + 0x123u), 0x123u, "0xF000 scratchpad alias should translate to local offset");
});
tc.Run("scratchpad alias accesses the same bytes as base", [](TestCase &t)
{
PS2Memory mem;
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");
constexpr uint32_t kOffset = 0x140u;
constexpr uint32_t kScratchAddr = PS2_SCRATCHPAD_BASE + kOffset;
constexpr uint32_t kScratchAliasAddr = PS2_SCRATCHPAD_ALIAS_BASE + kOffset;
mem.write32(kScratchAliasAddr, 0xCAFEBABEu);
t.Equals(mem.read32(kScratchAddr), 0xCAFEBABEu, "writes through 0xF000 scratchpad alias should land in scratchpad");
t.Equals(mem.read32(kScratchAliasAddr), 0xCAFEBABEu, "reads through 0xF000 scratchpad alias should see scratchpad bytes");
}); });
tc.Run("fast memory helpers wrap safely at RAM boundary", [](TestCase &t) tc.Run("fast memory helpers wrap safely at RAM boundary", [](TestCase &t)
@@ -792,6 +808,50 @@ void register_ps2_memory_tests()
t.IsTrue(contentOk, "scratchpad GIF DMA packet bytes should match scratchpad source"); t.IsTrue(contentOk, "scratchpad GIF DMA packet bytes should match scratchpad source");
}); });
tc.Run("GIF DMA chain can source tags and payload from 0xF000 scratchpad alias", [](TestCase &t)
{
PS2Memory mem;
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");
constexpr uint32_t kGifCh = 0x1000A000u;
constexpr uint32_t kTagAlias = PS2_SCRATCHPAD_ALIAS_BASE + 0x100u;
uint8_t *scratch = mem.getScratchpad();
std::memset(scratch + 0x100u, 0, 32u);
const uint64_t endTag = makeDmaTag(1u, 7u, 0u, false);
std::memcpy(scratch + 0x100u, &endTag, sizeof(endTag));
for (uint32_t i = 0; i < 16u; ++i)
{
scratch[0x110u + i] = static_cast<uint8_t>(0xC0u + i);
}
std::vector<std::vector<uint8_t>> captured;
mem.setGifPacketCallback([&](const uint8_t *data, uint32_t sizeBytes)
{
captured.emplace_back(data, data + sizeBytes);
});
t.IsTrue(mem.writeIORegister(kGifCh + 0x30u, kTagAlias), "write TADR scratchpad alias should succeed");
t.IsTrue(mem.writeIORegister(kGifCh + 0x00u, 0x104u), "write CHCR STR|CHAIN should succeed");
mem.processPendingTransfers();
t.Equals(captured.size(), static_cast<size_t>(1u), "scratchpad alias chain should emit one packet");
t.Equals(captured[0].size(), static_cast<size_t>(16u), "scratchpad alias chain should emit one qword");
bool contentOk = true;
for (uint32_t i = 0; i < 16u; ++i)
{
if (captured[0][i] != static_cast<uint8_t>(0xC0u + i))
{
contentOk = false;
break;
}
}
t.IsTrue(contentOk, "scratchpad alias chain payload should match scratchpad bytes");
});
tc.Run("VIF1 DMA DIRECT forwards payload to GIF callback and clears channel", [](TestCase &t) tc.Run("VIF1 DMA DIRECT forwards payload to GIF callback and clears channel", [](TestCase &t)
{ {
PS2Memory mem; PS2Memory mem;
@@ -6,6 +6,7 @@
#include "ps2_runtime.h" #include "ps2_runtime.h"
#include "ps2_memory.h" #include "ps2_memory.h"
#include "ps2_syscalls.h" #include "ps2_syscalls.h"
#include "ps2_stubs.h"
#include "ps2_gs_gpu.h" #include "ps2_gs_gpu.h"
#include "ps2_runtime_macros.h" #include "ps2_runtime_macros.h"
@@ -100,6 +101,116 @@ namespace
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
} }
std::atomic<int32_t> gSerializedGuestActive{0};
std::atomic<int32_t> gSerializedGuestMaxActive{0};
std::atomic<int32_t> gCooperativeYieldEntryCount{0};
std::atomic<bool> gCooperativeYieldAllowFirstYield{false};
std::atomic<bool> gCooperativeYieldPeerRan{false};
void testSerializedGuestStep(uint8_t *, R5900Context *ctx, PS2Runtime *)
{
const int32_t active = gSerializedGuestActive.fetch_add(1, std::memory_order_acq_rel) + 1;
int32_t observedMax = gSerializedGuestMaxActive.load(std::memory_order_relaxed);
while (observedMax < active &&
!gSerializedGuestMaxActive.compare_exchange_weak(
observedMax,
active,
std::memory_order_release,
std::memory_order_relaxed))
{
}
std::this_thread::sleep_for(std::chrono::milliseconds(25));
gSerializedGuestActive.fetch_sub(1, std::memory_order_acq_rel);
if (ctx)
{
ctx->pc = 0u;
}
}
void testCooperativeGuestYieldStep(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
{
if (!ctx || !runtime)
{
return;
}
const int32_t entryIndex = gCooperativeYieldEntryCount.fetch_add(1, std::memory_order_acq_rel) + 1;
if (entryIndex == 1)
{
while (!gCooperativeYieldAllowFirstYield.load(std::memory_order_acquire))
{
std::this_thread::yield();
}
for (int attempt = 0; attempt < 32 &&
!gCooperativeYieldPeerRan.load(std::memory_order_acquire);
++attempt)
{
runtime->cooperativeGuestYield();
}
setRegU32(*ctx, 2, gCooperativeYieldPeerRan.load(std::memory_order_acquire) ? 1u : 0u);
}
else
{
gCooperativeYieldPeerRan.store(true, std::memory_order_release);
setRegU32(*ctx, 2, 2u);
}
ctx->pc = 0u;
}
constexpr uint32_t kAsyncCounterAddr = 0x2400u;
void testWaitForAsyncCounter(uint8_t *rdram, R5900Context *ctx, PS2Runtime *)
{
if (!rdram || !ctx)
{
return;
}
uint32_t counter = 0u;
do
{
std::memcpy(&counter, rdram + kAsyncCounterAddr, sizeof(counter));
if (counter == 0u)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
} while (counter == 0u);
ctx->pc = 0u;
}
void testSignalAsyncCounter(uint8_t *rdram, R5900Context *ctx, PS2Runtime *)
{
if (rdram)
{
const uint32_t counter = 1u;
std::memcpy(rdram + kAsyncCounterAddr, &counter, sizeof(counter));
}
if (ctx)
{
ctx->pc = 0u;
}
}
std::atomic<uint32_t> gAsyncCallbackObservedSp{0u};
std::atomic<uint32_t> gAsyncCallbackObservedGp{0u};
void testRecordAsyncCallbackStack(uint8_t *, R5900Context *ctx, PS2Runtime *)
{
if (!ctx)
{
return;
}
gAsyncCallbackObservedSp.store(::getRegU32(ctx, 29), std::memory_order_release);
gAsyncCallbackObservedGp.store(::getRegU32(ctx, 28), std::memory_order_release);
ctx->pc = 0u;
}
} }
void register_ps2_runtime_expansion_tests() void register_ps2_runtime_expansion_tests()
@@ -136,6 +247,218 @@ void register_ps2_runtime_expansion_tests()
} }
}); });
tc.Run("guest execution is serialized per runtime", [](TestCase &t)
{
PS2Runtime runtime;
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
gSerializedGuestActive.store(0, std::memory_order_release);
gSerializedGuestMaxActive.store(0, std::memory_order_release);
constexpr uint32_t kEntries[] = {
0x120000u,
0x130000u,
0x140000u,
0x150000u,
};
constexpr size_t kEntryCount = sizeof(kEntries) / sizeof(kEntries[0]);
R5900Context contexts[kEntryCount]{};
std::vector<std::thread> workers;
workers.reserve(kEntryCount);
for (size_t i = 0; i < kEntryCount; ++i)
{
runtime.registerFunction(kEntries[i], &testSerializedGuestStep);
contexts[i].pc = kEntries[i];
}
for (size_t i = 0; i < kEntryCount; ++i)
{
workers.emplace_back([&, i]()
{
runtime.dispatchLoop(rdram.data(), &contexts[i]);
});
}
for (std::thread &worker : workers)
{
if (worker.joinable())
{
worker.join();
}
}
t.Equals(gSerializedGuestActive.load(std::memory_order_acquire), 0,
"serialized guest dispatch should leave no active workers");
t.Equals(gSerializedGuestMaxActive.load(std::memory_order_acquire), 1,
"dispatchLoop should not execute guest code concurrently on one runtime");
});
tc.Run("cooperative guest yield lets another guest thread run without leaving the current function", [](TestCase &t)
{
PS2Runtime runtime;
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
constexpr uint32_t kFirstEntry = 0x190000u;
constexpr uint32_t kSecondEntry = 0x1A0000u;
gCooperativeYieldEntryCount.store(0, std::memory_order_release);
gCooperativeYieldAllowFirstYield.store(false, std::memory_order_release);
gCooperativeYieldPeerRan.store(false, std::memory_order_release);
runtime.registerFunction(kFirstEntry, &testCooperativeGuestYieldStep);
runtime.registerFunction(kSecondEntry, &testCooperativeGuestYieldStep);
R5900Context firstCtx{};
R5900Context secondCtx{};
firstCtx.pc = kFirstEntry;
secondCtx.pc = kSecondEntry;
std::thread firstWorker([&]()
{
runtime.dispatchLoop(rdram.data(), &firstCtx);
});
const bool firstEntered = waitUntil([&]()
{
return gCooperativeYieldEntryCount.load(std::memory_order_acquire) >= 1;
}, std::chrono::milliseconds(100));
std::thread secondWorker([&]()
{
runtime.dispatchLoop(rdram.data(), &secondCtx);
});
const bool secondContending = waitUntil([&]()
{
return runtime.guestExecutionWaiterCountForTesting() > 0u;
}, std::chrono::milliseconds(100));
gCooperativeYieldAllowFirstYield.store(true, std::memory_order_release);
if (firstWorker.joinable())
{
firstWorker.join();
}
if (secondWorker.joinable())
{
secondWorker.join();
}
t.IsTrue(firstEntered, "first guest worker should enter before yielding");
t.IsTrue(secondContending, "second guest worker should contend for guest execution before the first yields");
t.IsTrue(gCooperativeYieldPeerRan.load(std::memory_order_acquire),
"second guest worker should run while the first cooperatively yields");
t.Equals(getRegU32(&firstCtx, 2), 1u,
"first guest worker should observe that a peer ran before it resumed");
});
tc.Run("vblank intc handlers can preempt serialized guest execution", [](TestCase &t)
{
notifyRuntimeStop();
PS2Runtime runtime;
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
constexpr uint32_t kBusyEntry = 0x160000u;
constexpr uint32_t kIntcHandlerEntry = 0x170000u;
runtime.registerFunction(kBusyEntry, &testWaitForAsyncCounter);
runtime.registerFunction(kIntcHandlerEntry, &testSignalAsyncCounter);
R5900Context addCtx{};
setRegU32(addCtx, 4, 2u);
setRegU32(addCtx, 5, kIntcHandlerEntry);
setRegU32(addCtx, 6, 0u);
setRegU32(addCtx, 7, 0u);
AddIntcHandler(rdram.data(), &addCtx, &runtime);
t.IsTrue(getRegS32(addCtx, 2) > 0, "AddIntcHandler should register a VBlank handler");
R5900Context busyCtx{};
busyCtx.pc = kBusyEntry;
std::atomic<bool> workerDone{false};
std::atomic<bool> workerThrew{false};
std::thread worker([&]()
{
try
{
runtime.dispatchLoop(rdram.data(), &busyCtx);
}
catch (...)
{
workerThrew.store(true, std::memory_order_release);
}
workerDone.store(true, std::memory_order_release);
});
ps2_syscalls::EnsureVSyncWorkerRunning(rdram.data(), &runtime);
const bool finished = waitUntil([&]()
{
return workerDone.load(std::memory_order_acquire);
}, std::chrono::milliseconds(250));
if (!finished)
{
const uint32_t counter = 1u;
std::memcpy(rdram.data() + kAsyncCounterAddr, &counter, sizeof(counter));
}
if (worker.joinable())
{
worker.join();
}
runtime.requestStop();
notifyRuntimeStop();
uint32_t counter = 0u;
std::memcpy(&counter, rdram.data() + kAsyncCounterAddr, sizeof(counter));
t.IsFalse(workerThrew.load(std::memory_order_acquire),
"busy dispatch worker should not throw while VBlank handlers fire");
t.IsTrue(finished,
"VBlank interrupt handlers should run even while a guest thread is spinning");
t.Equals(counter, 1u, "VBlank handler should publish the awaited counter value");
});
tc.Run("GS async callbacks keep a dedicated stack when guest heap is exhausted", [](TestCase &t)
{
notifyRuntimeStop();
PS2Runtime runtime;
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
constexpr uint32_t kCallbackEntry = 0x180000u;
constexpr uint32_t kCallerGp = 0x0036A7F0u;
constexpr uint32_t kCallerSp = 0x00123450u;
constexpr uint32_t kAsyncStackFloor = 0x01F00000u;
runtime.configureGuestHeap(kAsyncStackFloor, kAsyncStackFloor);
runtime.registerFunction(kCallbackEntry, &testRecordAsyncCallbackStack);
ps2_stubs::resetGsSyncVCallbackState();
gAsyncCallbackObservedSp.store(0u, std::memory_order_release);
gAsyncCallbackObservedGp.store(0u, std::memory_order_release);
R5900Context registerCtx{};
registerCtx.pc = 0x00101900u;
setRegU32(registerCtx, 4, kCallbackEntry);
setRegU32(registerCtx, 28, kCallerGp);
setRegU32(registerCtx, 29, kCallerSp);
ps2_stubs::sceGsSyncVCallback(rdram.data(), &registerCtx, &runtime);
ps2_stubs::dispatchGsSyncVCallback(rdram.data(), &runtime, 1u);
const uint32_t observedSp = gAsyncCallbackObservedSp.load(std::memory_order_acquire);
const uint32_t observedGp = gAsyncCallbackObservedGp.load(std::memory_order_acquire);
t.IsTrue(observedSp != 0u, "callback should execute");
t.Equals(observedGp, kCallerGp, "callback should preserve the registered GP");
t.IsTrue(observedSp != kCallerSp, "callback should not reuse the registering thread stack");
t.IsTrue(observedSp >= kAsyncStackFloor,
"callback should switch to the reserved async stack pool");
runtime.requestStop();
notifyRuntimeStop();
});
tc.Run("multiply-add matrix writes rd only when R5900 requires it", [](TestCase &t) tc.Run("multiply-add matrix writes rd only when R5900 requires it", [](TestCase &t)
{ {
R5900Decoder decoder; R5900Decoder decoder;
@@ -251,6 +251,73 @@ void register_ps2_runtime_interrupt_tests()
cleanupRuntime(env); cleanupRuntime(env);
}); });
tc.Run("negative interrupt-safe EE syscall ids dispatch", [](TestCase &t)
{
notifyRuntimeStop();
TestEnv env;
constexpr uint32_t kEventParamAddr = 0x1200u;
constexpr uint32_t kStatusAddr = 0x1210u;
const uint32_t eventParam[3] = {
0u,
0u,
0u
};
std::memcpy(env.rdram.data() + kEventParamAddr, eventParam, sizeof(eventParam));
R5900Context createCtx{};
setRegU32(createCtx, 4, kEventParamAddr);
CreateEventFlag(env.rdram.data(), &createCtx, &env.runtime);
const int32_t eid = getRegS32(createCtx, 2);
t.IsTrue(eid > 0, "CreateEventFlag should return a valid event id");
R5900Context disableIntcCtx{};
setRegU32(disableIntcCtx, 4, 2u);
t.IsTrue(callSyscall(static_cast<uint32_t>(-0x1B), env.rdram.data(), &disableIntcCtx, &env.runtime),
"negative iDisableIntc syscall id should dispatch");
t.Equals(getRegS32(disableIntcCtx, 2), KE_OK, "negative iDisableIntc should return KE_OK");
R5900Context enableIntcCtx{};
setRegU32(enableIntcCtx, 4, 2u);
t.IsTrue(callSyscall(static_cast<uint32_t>(-0x1A), env.rdram.data(), &enableIntcCtx, &env.runtime),
"negative iEnableIntc syscall id should dispatch");
t.Equals(getRegS32(enableIntcCtx, 2), KE_OK, "negative iEnableIntc should return KE_OK");
R5900Context disableDmacCtx{};
setRegU32(disableDmacCtx, 4, 5u);
t.IsTrue(callSyscall(static_cast<uint32_t>(-0x1D), env.rdram.data(), &disableDmacCtx, &env.runtime),
"negative iDisableDmac syscall id should dispatch");
t.Equals(getRegS32(disableDmacCtx, 2), KE_OK, "negative iDisableDmac should return KE_OK");
R5900Context enableDmacCtx{};
setRegU32(enableDmacCtx, 4, 5u);
t.IsTrue(callSyscall(static_cast<uint32_t>(-0x1C), env.rdram.data(), &enableDmacCtx, &env.runtime),
"negative iEnableDmac syscall id should dispatch");
t.Equals(getRegS32(enableDmacCtx, 2), KE_OK, "negative iEnableDmac should return KE_OK");
R5900Context setEventFlagCtx{};
setRegU32(setEventFlagCtx, 4, static_cast<uint32_t>(eid));
setRegU32(setEventFlagCtx, 5, 0x6u);
t.IsTrue(callSyscall(static_cast<uint32_t>(-0x53), env.rdram.data(), &setEventFlagCtx, &env.runtime),
"negative iSetEventFlag syscall id should dispatch");
t.Equals(getRegS32(setEventFlagCtx, 2), KE_OK, "negative iSetEventFlag should return KE_OK");
R5900Context referCtx{};
setRegU32(referCtx, 4, static_cast<uint32_t>(eid));
setRegU32(referCtx, 5, kStatusAddr);
ReferEventFlagStatus(env.rdram.data(), &referCtx, &env.runtime);
t.Equals(getRegS32(referCtx, 2), KE_OK, "ReferEventFlagStatus should succeed after iSetEventFlag");
t.Equals(readGuestU32(env.rdram.data(), kStatusAddr + 12u), 0x6u,
"negative iSetEventFlag should publish the requested bits");
R5900Context deleteCtx{};
setRegU32(deleteCtx, 4, static_cast<uint32_t>(eid));
DeleteEventFlag(env.rdram.data(), &deleteCtx, &env.runtime);
cleanupRuntime(env);
});
tc.Run("WaitEventFlag blocks and wakes when SetEventFlag publishes bits", [](TestCase &t) tc.Run("WaitEventFlag blocks and wakes when SetEventFlag publishes bits", [](TestCase &t)
{ {
notifyRuntimeStop(); notifyRuntimeStop();