mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
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
This commit is contained in:
@@ -227,6 +227,19 @@ namespace ps2recomp
|
||||
const uint32_t branchPc = branchInst.address;
|
||||
const uint32_t delayPc = branchInst.address + 4u;
|
||||
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;
|
||||
if (branchInst.opcode == OPCODE_SPECIAL &&
|
||||
@@ -293,8 +306,7 @@ namespace ps2recomp
|
||||
|
||||
if (internalTargets.contains(target))
|
||||
{
|
||||
ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target);
|
||||
ss << fmt::format(" goto label_{:x};\n", target);
|
||||
emitInternalTarget(target, branchPc, " ");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -579,8 +591,7 @@ namespace ps2recomp
|
||||
|
||||
if (internalTargets.contains(target))
|
||||
{
|
||||
ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target);
|
||||
ss << fmt::format(" goto label_{:x};\n", target);
|
||||
emitInternalTarget(target, branchPc, " ");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -606,8 +617,7 @@ namespace ps2recomp
|
||||
ss << " if (" << branchTakenVar << ") {\n";
|
||||
if (internalTargets.contains(target))
|
||||
{
|
||||
ss << fmt::format(" ctx->pc = 0x{:X}u;\n", target);
|
||||
ss << fmt::format(" goto label_{:x};\n", target);
|
||||
emitInternalTarget(target, branchPc, " ");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -376,7 +376,7 @@ public class ExportPS2Functions extends GhidraScript {
|
||||
|
||||
MemoryBlock fromBlock = currentProgram.getMemory().getBlock(from);
|
||||
if (fromBlock == null || !fromBlock.isExecute()) {
|
||||
return true;
|
||||
continue; // lets ignore DATA/non-code refs
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -216,6 +216,10 @@ public:
|
||||
const uint8_t *lockDisplaySnapshot(uint32_t &outSize);
|
||||
void unlockDisplaySnapshot();
|
||||
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);
|
||||
|
||||
@@ -227,6 +231,7 @@ private:
|
||||
void vertexKick(bool drawing);
|
||||
|
||||
void processImageData(const uint8_t *data, uint32_t sizeBytes);
|
||||
void performLocalToLocalTransfer();
|
||||
void performLocalToHostToBuffer();
|
||||
|
||||
GSContext &activeContext();
|
||||
|
||||
@@ -6,53 +6,47 @@
|
||||
namespace GSPSMT4
|
||||
{
|
||||
|
||||
static const uint8_t blockTable4[8][4] = {
|
||||
{ 0, 2, 8, 10 },
|
||||
{ 1, 3, 9, 11 },
|
||||
{ 4, 6, 12, 14 },
|
||||
{ 5, 7, 13, 15 },
|
||||
{ 16, 18, 24, 26 },
|
||||
{ 17, 19, 25, 27 },
|
||||
{ 20, 22, 28, 30 },
|
||||
{ 21, 23, 29, 31 },
|
||||
};
|
||||
static const uint16_t columnTable4[16][32] = {
|
||||
{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},
|
||||
{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},
|
||||
{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},
|
||||
{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},
|
||||
{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},
|
||||
{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},
|
||||
{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},
|
||||
{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] = {
|
||||
{ 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 },
|
||||
{ 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 },
|
||||
{ 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 },
|
||||
{ 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 },
|
||||
{ 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 },
|
||||
{ 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 pageLocalNibbleOffset(uint32_t x, uint32_t y)
|
||||
{
|
||||
uint32_t yy = y & 0x7Fu;
|
||||
uint32_t xx = x & 0x7Fu;
|
||||
uint32_t blockBase = (((xx >> 5) & 3u) << 12) +
|
||||
(((yy >> 4) & 7u) << 6);
|
||||
return blockBase + columnTable4[yy & 15u][xx & 31u];
|
||||
}
|
||||
|
||||
inline uint32_t blockIdPSMT4(uint32_t block, uint32_t width, uint32_t x, uint32_t y)
|
||||
{
|
||||
return block + ((y >> 2) & ~0x1Fu) * (width >> 7) + ((x >> 2) & ~0x1Fu)
|
||||
+ blockTable4[(y >> 4) & 7][(x >> 5) & 3];
|
||||
}
|
||||
|
||||
inline uint32_t addrPSMT4(uint32_t block, uint32_t width, uint32_t x, uint32_t y)
|
||||
{
|
||||
uint32_t page = (block >> 5) + (y >> 7) * (width >> 1) + (x >> 7);
|
||||
uint32_t blk = block & 0x1Fu;
|
||||
uint32_t yy = y & 0x7Fu;
|
||||
uint32_t xx = x & 0x7Fu;
|
||||
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;
|
||||
}
|
||||
inline uint32_t addrPSMT4(uint32_t block, uint32_t width, uint32_t x, uint32_t y)
|
||||
{
|
||||
const uint32_t pagesPerRow = ((width >> 1u) != 0u) ? (width >> 1u) : 1u;
|
||||
const uint32_t localNibble = pageLocalNibbleOffset(x, y);
|
||||
const uint32_t localByte = localNibble >> 1u;
|
||||
const uint32_t localRow = localByte >> 8u;
|
||||
const uint32_t localColumnByte = localByte & 0xFFu;
|
||||
const uint32_t globalByte =
|
||||
block * 256u +
|
||||
(((y >> 7u) * 32u + localRow) * (pagesPerRow * 256u)) +
|
||||
((x >> 7u) * 256u + localColumnByte);
|
||||
return (globalByte << 1u) | (localNibble & 1u);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -10,10 +10,11 @@ class GSRasterizer
|
||||
public:
|
||||
void drawPrimitive(GS *gs);
|
||||
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 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 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:
|
||||
void drawSprite(GS *gs);
|
||||
|
||||
@@ -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_BASE = 0x00000000; // Physical base of RDRAM
|
||||
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_IO_BASE = 0x10000000; // Base for many I/O regs (Timers, DMAC, INTC)
|
||||
constexpr uint32_t PS2_IO_SIZE = 0x10000; // 64KB
|
||||
@@ -81,12 +82,40 @@ inline uint8_t *ps2GetScratchpadHostPtr()
|
||||
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))
|
||||
{
|
||||
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;
|
||||
offset = addr - PS2_SCRATCHPAD_BASE;
|
||||
offset = ps2ScratchpadOffset(addr);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -276,6 +305,8 @@ public:
|
||||
|
||||
using Vu1MscalCallback = std::function<void(uint32_t startPC, uint32_t itop)>;
|
||||
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; }
|
||||
const uint8_t *getVU1Code() const { return m_vu1Code; }
|
||||
@@ -344,6 +375,7 @@ public:
|
||||
GifPacketCallback m_gifPacketCallback;
|
||||
GifArbiter *m_gifArbiter = nullptr;
|
||||
Vu1MscalCallback m_vu1MscalCallback;
|
||||
Vu1MscntCallback m_vu1MscntCallback;
|
||||
|
||||
uint8_t *m_vu1Code = nullptr;
|
||||
uint8_t *m_vu1Data = nullptr;
|
||||
|
||||
@@ -378,6 +378,33 @@ public:
|
||||
|
||||
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);
|
||||
RecompiledFunction lookupFunction(uint32_t address);
|
||||
bool hasFunction(uint32_t address) const;
|
||||
@@ -409,9 +436,15 @@ public:
|
||||
void guestFree(uint32_t guestAddr);
|
||||
uint32_t guestHeapBase() const;
|
||||
uint32_t guestHeapEnd() const;
|
||||
uint32_t reserveAsyncCallbackStack(uint32_t size, uint32_t alignment = 16u);
|
||||
void dispatchLoop(uint8_t *rdram, R5900Context *ctx);
|
||||
void cooperativeGuestYield();
|
||||
void requestStop();
|
||||
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);
|
||||
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);
|
||||
void freeGuestBlockLocked(uint32_t guestAddr);
|
||||
void coalesceGuestHeapLocked();
|
||||
void enterGuestExecution();
|
||||
void leaveGuestExecution();
|
||||
uint32_t releaseGuestExecution();
|
||||
void reacquireGuestExecution(uint32_t depth);
|
||||
|
||||
void HandleIntegerOverflow(R5900Context *ctx);
|
||||
|
||||
friend class GuestExecutionScope;
|
||||
friend class GuestExecutionReleaseScope;
|
||||
|
||||
private:
|
||||
PS2Memory m_memory;
|
||||
GifArbiter m_gifArbiter;
|
||||
@@ -508,13 +548,18 @@ private:
|
||||
PSPadBackend m_padBackend;
|
||||
VU1Interpreter m_vu1;
|
||||
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_asyncCallbackStackMutex;
|
||||
std::vector<GuestHeapBlock> m_guestHeapBlocks;
|
||||
uint32_t m_guestHeapBase = 0x00100000u;
|
||||
uint32_t m_guestHeapEnd = 0x00100000u;
|
||||
uint32_t m_guestHeapLimit = PS2_RAM_SIZE;
|
||||
uint32_t m_guestHeapSuggestedBase = 0x00100000u;
|
||||
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::atomic<bool> m_stopRequested{false};
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace ps2_stubs
|
||||
PS2_TEST_HOOK_LIST(PS2_DECLARE_TEST_HOOK)
|
||||
#undef PS2_DECLARE_TEST_HOOK
|
||||
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 sndr_trans_func(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace ps2_syscalls
|
||||
void initializeGuestKernelState(uint8_t *rdram);
|
||||
void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId);
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,29 @@
|
||||
#include "ps2_gif_arbiter.h"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#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)
|
||||
: 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)
|
||||
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;
|
||||
pkt.pathId = pathId;
|
||||
pkt.path2DirectHl = (pathId == GifPathId::Path2) && path2DirectHl;
|
||||
@@ -38,23 +81,46 @@ void GifArbiter::drain()
|
||||
return;
|
||||
|
||||
std::stable_sort(m_queue.begin(), m_queue.end(),
|
||||
[](const GifArbiterPacket &a, const GifArbiterPacket &b) {
|
||||
// DIRECTHL cannot preempt PATH3 IMAGE transfers.
|
||||
if (a.path2DirectHl != b.path2DirectHl || a.path3Image != b.path3Image)
|
||||
{
|
||||
if (a.path3Image && b.path2DirectHl)
|
||||
return true;
|
||||
if (a.path2DirectHl && b.path3Image)
|
||||
return false;
|
||||
}
|
||||
return pathPriority(a.pathId) < pathPriority(b.pathId);
|
||||
});
|
||||
[](const GifArbiterPacket &a, const GifArbiterPacket &b)
|
||||
{
|
||||
// DIRECTHL cannot preempt PATH3 IMAGE transfers.
|
||||
if (a.path2DirectHl != b.path2DirectHl || a.path3Image != b.path3Image)
|
||||
{
|
||||
if (a.path3Image && b.path2DirectHl)
|
||||
return true;
|
||||
if (a.path2DirectHl && b.path3Image)
|
||||
return false;
|
||||
}
|
||||
return pathPriority(a.pathId) < pathPriority(b.pathId);
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < m_queue.size(); ++i)
|
||||
{
|
||||
auto &pkt = m_queue[i];
|
||||
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_queue.clear();
|
||||
}
|
||||
|
||||
+622
-120
@@ -1,20 +1,189 @@
|
||||
#include "ps2_gs_gpu.h"
|
||||
#include "ps2_gs_common.h"
|
||||
#include "ps2_gs_psmt4.h"
|
||||
#include "ps2_gs_psmt8.h"
|
||||
#include "ps2_memory.h"
|
||||
#include <atomic>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
static inline uint64_t loadLE64(const uint8_t *p)
|
||||
{
|
||||
uint64_t v;
|
||||
std::memcpy(&v, p, 8);
|
||||
return v;
|
||||
}
|
||||
static inline uint64_t loadLE64(const uint8_t *p)
|
||||
{
|
||||
uint64_t v;
|
||||
std::memcpy(&v, p, 8);
|
||||
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;
|
||||
@@ -36,10 +205,15 @@ void GS::reset()
|
||||
{
|
||||
std::memset(m_ctx, 0, sizeof(m_ctx));
|
||||
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_curS = 0.0f; m_curT = 0.0f;
|
||||
m_curU = 0; m_curV = 0;
|
||||
m_curS = 0.0f;
|
||||
m_curT = 0.0f;
|
||||
m_curU = 0;
|
||||
m_curV = 0;
|
||||
m_curFog = 0;
|
||||
m_prmodecont = true;
|
||||
m_bitbltbuf = {};
|
||||
@@ -68,7 +242,8 @@ GSContext &GS::activeContext()
|
||||
|
||||
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);
|
||||
m_displaySnapshot.resize(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)
|
||||
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)
|
||||
{
|
||||
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);
|
||||
uint8_t flg = static_cast<uint8_t>((tagLo >> 58) & 0x3);
|
||||
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;
|
||||
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_curT, &tBits, 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;
|
||||
}
|
||||
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);
|
||||
uint8_t f = static_cast<uint8_t>((hi >> 36) & 0xFF);
|
||||
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];
|
||||
vtx.x = static_cast<float>(x) / 16.0f;
|
||||
vtx.y = static_cast<float>(y) / 16.0f;
|
||||
vtx.z = static_cast<float>(z);
|
||||
vtx.r = m_curR; vtx.g = m_curG; 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;
|
||||
vtx.r = m_curR;
|
||||
vtx.g = m_curG;
|
||||
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);
|
||||
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);
|
||||
uint32_t z = static_cast<uint32_t>(hi & 0xFFFFFFFF);
|
||||
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];
|
||||
vtx.x = static_cast<float>(x) / 16.0f;
|
||||
vtx.y = static_cast<float>(y) / 16.0f;
|
||||
vtx.z = static_cast<float>(z);
|
||||
vtx.r = m_curR; vtx.g = m_curG; 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;
|
||||
vtx.r = m_curR;
|
||||
vtx.g = m_curG;
|
||||
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);
|
||||
break;
|
||||
}
|
||||
@@ -248,26 +483,59 @@ void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi)
|
||||
break;
|
||||
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];
|
||||
vtx.x = static_cast<float>(lo & 0xFFFF) / 16.0f;
|
||||
vtx.y = static_cast<float>((lo >> 32) & 0xFFFF) / 16.0f;
|
||||
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.q = m_curQ; vtx.s = m_curS; vtx.t = m_curT;
|
||||
vtx.u = m_curU; vtx.v = m_curV;
|
||||
vtx.r = m_curR;
|
||||
vtx.g = m_curG;
|
||||
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);
|
||||
vertexKick(false);
|
||||
break;
|
||||
}
|
||||
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];
|
||||
vtx.x = static_cast<float>(lo & 0xFFFF) / 16.0f;
|
||||
vtx.y = static_cast<float>((lo >> 32) & 0xFFFF) / 16.0f;
|
||||
vtx.z = static_cast<float>(hi & 0xFFFFFFFF);
|
||||
vtx.r = m_curR; vtx.g = m_curG; 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;
|
||||
vtx.r = m_curR;
|
||||
vtx.g = m_curG;
|
||||
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);
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
uint32_t qBits = static_cast<uint32_t>((value >> 32) & 0xFFFFFFFF);
|
||||
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;
|
||||
}
|
||||
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.z = static_cast<float>((value >> 32) & 0xFFFFFF);
|
||||
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.q = m_curQ; vtx.s = m_curS; vtx.t = m_curT;
|
||||
vtx.u = m_curU; vtx.v = m_curV;
|
||||
vtx.r = m_curR;
|
||||
vtx.g = m_curG;
|
||||
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);
|
||||
break;
|
||||
}
|
||||
@@ -350,9 +686,16 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value)
|
||||
vtx.x = static_cast<float>(value & 0xFFFF) / 16.0f;
|
||||
vtx.y = static_cast<float>((value >> 16) & 0xFFFF) / 16.0f;
|
||||
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.q = m_curQ; vtx.s = m_curS; vtx.t = m_curT;
|
||||
vtx.u = m_curU; vtx.v = m_curV; vtx.fog = m_curFog;
|
||||
vtx.r = m_curR;
|
||||
vtx.g = m_curG;
|
||||
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);
|
||||
break;
|
||||
}
|
||||
@@ -500,58 +843,7 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value)
|
||||
|
||||
if (m_trxdir == 2 && m_vram)
|
||||
{
|
||||
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;
|
||||
|
||||
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();
|
||||
}
|
||||
performLocalToLocalTransfer();
|
||||
}
|
||||
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_MIPTBP2_1:
|
||||
case GS_REG_MIPTBP2_2:
|
||||
case GS_REG_TEXA:
|
||||
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:
|
||||
{
|
||||
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 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);
|
||||
m_privRegs->siglblid = (m_privRegs->siglblid & 0xFFFFFFFF00000000ULL) | lo;
|
||||
m_privRegs->csr |= 0x1;
|
||||
@@ -603,9 +909,9 @@ void GS::writeRegister(uint8_t regAddr, uint64_t value)
|
||||
{
|
||||
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 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);
|
||||
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)
|
||||
{
|
||||
++m_vtxCount;
|
||||
++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)
|
||||
return;
|
||||
|
||||
int needed = 0;
|
||||
switch (m_prim.type)
|
||||
{
|
||||
case GS_PRIM_POINT: needed = 1; break;
|
||||
case GS_PRIM_LINE: needed = 2; break;
|
||||
case GS_PRIM_LINESTRIP: 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;
|
||||
case GS_PRIM_POINT:
|
||||
needed = 1;
|
||||
break;
|
||||
case GS_PRIM_LINE:
|
||||
needed = 2;
|
||||
break;
|
||||
case GS_PRIM_LINESTRIP:
|
||||
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)
|
||||
@@ -690,7 +1127,8 @@ void GS::processImageData(const uint8_t *data, uint32_t sizeBytes)
|
||||
uint8_t dbw = m_bitbltbuf.dbw;
|
||||
uint8_t dpsm = m_bitbltbuf.dpsm;
|
||||
|
||||
if (dbw == 0) dbw = 1;
|
||||
if (dbw == 0)
|
||||
dbw = 1;
|
||||
uint32_t base = dbp * 256u;
|
||||
uint32_t bpp = bitsPerPixel(dpsm);
|
||||
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)
|
||||
{
|
||||
uint32_t rowBytes = (rrw + 1u) / 2u;
|
||||
if (rowBytes == 0) rowBytes = 1;
|
||||
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;
|
||||
for (uint32_t x = 0; x < rrw && (srcRowOff + (x / 2u)) < sizeBytes; ++x)
|
||||
if (m_hwregY >= rrh)
|
||||
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)];
|
||||
uint32_t nibble = (x & 1u) ? ((srcByte >> 4) & 0xFu) : (srcByte & 0xFu);
|
||||
const int shift = static_cast<int>((nibbleAddr & 1u) << 2);
|
||||
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;
|
||||
uint32_t vy = dsay + y;
|
||||
uint32_t nibbleAddr = GSPSMT4::addrPSMT4(dbp, widthBlocks, vx, vy);
|
||||
uint32_t byteOff = nibbleAddr >> 1;
|
||||
while (offset < sizeBytes && m_hwregY < rrh)
|
||||
{
|
||||
const uint8_t srcByte = data[offset++];
|
||||
const uint32_t srcLo = srcByte & 0x0Fu;
|
||||
const uint32_t srcHi = (srcByte >> 4) & 0x0Fu;
|
||||
const uint32_t xBefore = m_hwregX;
|
||||
|
||||
if (byteOff < m_vramSize)
|
||||
{
|
||||
int shift = static_cast<int>((nibbleAddr & 1u) << 2);
|
||||
uint8_t &b = m_vram[byteOff];
|
||||
b = static_cast<uint8_t>((b & (0xF0u >> shift)) | ((nibble & 0x0Fu) << shift));
|
||||
}
|
||||
writeT4Nibble(static_cast<uint8_t>(srcLo), srcByte, srcLo, srcHi);
|
||||
if ((xBefore + 1u) < rrw && m_hwregY < rrh)
|
||||
{
|
||||
writeT4Nibble(static_cast<uint8_t>(srcHi), srcByte, srcLo, srcHi);
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
@@ -773,7 +1259,8 @@ void GS::processImageData(const uint8_t *data, uint32_t sizeBytes)
|
||||
else
|
||||
{
|
||||
uint32_t bytesPerPixel = bpp / 8u;
|
||||
if (bytesPerPixel == 0) bytesPerPixel = 4;
|
||||
if (bytesPerPixel == 0)
|
||||
bytesPerPixel = 4;
|
||||
uint32_t strideBytes = stridePixels * bytesPerPixel;
|
||||
uint32_t rowBytes = rrw * bytesPerPixel;
|
||||
|
||||
@@ -814,7 +1301,8 @@ void GS::performLocalToHostToBuffer()
|
||||
uint8_t sbw = m_bitbltbuf.sbw;
|
||||
uint8_t spsm = m_bitbltbuf.spsm;
|
||||
|
||||
if (sbw == 0) sbw = 1;
|
||||
if (sbw == 0)
|
||||
sbw = 1;
|
||||
uint32_t base = sbp * 256u;
|
||||
uint32_t bpp = bitsPerPixel(spsm);
|
||||
uint32_t stridePixels = static_cast<uint32_t>(sbw) * 64u;
|
||||
@@ -827,7 +1315,8 @@ void GS::performLocalToHostToBuffer()
|
||||
if (bpp == 4)
|
||||
{
|
||||
uint32_t rowBytes = (rrw + 1u) / 2u;
|
||||
if (rowBytes == 0) rowBytes = 1;
|
||||
if (rowBytes == 0)
|
||||
rowBytes = 1;
|
||||
m_localToHostBuffer.reserve(rowBytes * rrh);
|
||||
uint32_t widthBlocks = static_cast<uint32_t>(sbw);
|
||||
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)
|
||||
{
|
||||
uint32_t storageBpp = 4;
|
||||
@@ -875,7 +1376,8 @@ void GS::performLocalToHostToBuffer()
|
||||
else
|
||||
{
|
||||
uint32_t bytesPerPixel = bpp / 8u;
|
||||
if (bytesPerPixel == 0) bytesPerPixel = 4;
|
||||
if (bytesPerPixel == 0)
|
||||
bytesPerPixel = 4;
|
||||
uint32_t strideBytes = stridePixels * bytesPerPixel;
|
||||
uint32_t rowBytes = rrw * bytesPerPixel;
|
||||
m_localToHostBuffer.reserve(rowBytes * rrh);
|
||||
|
||||
@@ -2,15 +2,289 @@
|
||||
#include "ps2_gs_gpu.h"
|
||||
#include "ps2_gs_common.h"
|
||||
#include "ps2_gs_psmt4.h"
|
||||
#include "ps2_gs_psmt8.h"
|
||||
#include <atomic>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
return;
|
||||
|
||||
const AlphaTestResult alphaTest = classifyAlphaTest(ctx.test, a);
|
||||
if (!alphaTest.writeFramebuffer)
|
||||
return;
|
||||
|
||||
uint32_t fbBase = ctx.frame.fbp * 8192u;
|
||||
uint32_t stride = fbStride(ctx.frame.fbw, ctx.frame.psm);
|
||||
if (stride == 0) return;
|
||||
|
||||
uint32_t off = fbBase + static_cast<uint32_t>(y) * stride + static_cast<uint32_t>(x) * 4u;
|
||||
if (off + 4 > gs->m_vramSize)
|
||||
if (stride == 0)
|
||||
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)
|
||||
{
|
||||
uint32_t existing;
|
||||
std::memcpy(&existing, gs->m_vram + off, 4);
|
||||
uint32_t existing = 0u;
|
||||
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 dg = (existing >> 8) & 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 csel = (alphaReg >> 4) & 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 {
|
||||
if (sel == 0) return cs;
|
||||
if (sel == 1) return cd;
|
||||
auto pickRGB = [&](uint8_t sel, int cs, int cd) -> int
|
||||
{
|
||||
if (sel == 0)
|
||||
return cs;
|
||||
if (sel == 1)
|
||||
return cd;
|
||||
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));
|
||||
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));
|
||||
}
|
||||
|
||||
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;
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 stride = tbw * 64u * 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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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 byteOff = nibbleAddr >> 1;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
const uint32_t clutIndex = resolveClutIndex(index, csm, csa, sourcePsm);
|
||||
|
||||
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)
|
||||
return 0xFFFF00FFu;
|
||||
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)
|
||||
{
|
||||
uint32_t off = clutBase + (static_cast<uint32_t>(csa) * 16u + index) * 2u;
|
||||
uint32_t off = clutBase + clutIndex * 2u;
|
||||
if (off + 2 > gs->m_vramSize)
|
||||
return 0xFFFF00FFu;
|
||||
uint16_t c16;
|
||||
@@ -155,15 +520,13 @@ uint32_t GSRasterizer::lookupCLUT(GS *gs, uint8_t index, uint32_t cbp, uint8_t c
|
||||
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 &tex = ctx.tex0;
|
||||
|
||||
int texW = 1 << tex.tw;
|
||||
int texH = 1 << tex.th;
|
||||
if (texW == 0) texW = 1;
|
||||
if (texH == 0) texH = 1;
|
||||
|
||||
int texU, texV;
|
||||
if (gs->m_prim.fst)
|
||||
@@ -173,7 +536,7 @@ uint32_t GSRasterizer::sampleTexture(GS *gs, float s, float t, uint16_t u, uint1
|
||||
}
|
||||
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));
|
||||
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)
|
||||
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)
|
||||
{
|
||||
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.tbw == 0) return 0xFFFF00FFu;
|
||||
uint32_t base = tex.tbp0 * 256u;
|
||||
uint32_t stride = static_cast<uint32_t>(tex.tbw) * 64u;
|
||||
uint32_t off = base + static_cast<uint32_t>(texV) * stride + static_cast<uint32_t>(texU);
|
||||
if (off >= gs->m_vramSize) return 0xFFFF00FFu;
|
||||
if (tex.tbw == 0)
|
||||
return 0xFFFF00FFu;
|
||||
uint32_t off = GSPSMT8::addrPSMT8(tex.tbp0, tex.tbw, static_cast<uint32_t>(texU), static_cast<uint32_t>(texV));
|
||||
if (off >= gs->m_vramSize)
|
||||
return 0xFFFF00FFu;
|
||||
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;
|
||||
@@ -218,13 +584,16 @@ void GSRasterizer::drawSprite(GS *gs)
|
||||
int x1 = static_cast<int>(v1.x) - ofx;
|
||||
int y1 = static_cast<int>(v1.y) - ofy;
|
||||
|
||||
if (x0 > x1) std::swap(x0, x1);
|
||||
if (y0 > y1) std::swap(y0, y1);
|
||||
if (x0 > x1)
|
||||
std::swap(x0, x1);
|
||||
if (y0 > y1)
|
||||
std::swap(y0, y1);
|
||||
|
||||
// If the sprite rectangle is fully outside scissor, nothing should render.
|
||||
if (x1 < ctx.scissor.x0 || x0 > ctx.scissor.x1 ||
|
||||
y1 < ctx.scissor.y0 || y0 > ctx.scissor.y1)
|
||||
{
|
||||
// maybe a log here idk ?
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -237,12 +606,14 @@ void GSRasterizer::drawSprite(GS *gs)
|
||||
|
||||
if (gs->m_prim.tme)
|
||||
{
|
||||
const auto &tex = ctx.tex0;
|
||||
const auto &tex = ctx.tex0;
|
||||
int texW = 1 << tex.tw;
|
||||
int texH = 1 << tex.th;
|
||||
if (texW == 0) texW = 1;
|
||||
if (texH == 0) texH = 1;
|
||||
|
||||
if (texW == 0)
|
||||
texW = 1;
|
||||
if (texH == 0)
|
||||
texH = 1;
|
||||
|
||||
float u0f, v0f, u1f, v1f;
|
||||
if (gs->m_prim.fst)
|
||||
{
|
||||
@@ -253,16 +624,20 @@ void GSRasterizer::drawSprite(GS *gs)
|
||||
}
|
||||
else
|
||||
{
|
||||
u0f = v0.s * static_cast<float>(texW);
|
||||
v0f = v0.t * static_cast<float>(texH);
|
||||
u1f = v1.s * static_cast<float>(texW);
|
||||
v1f = v1.t * static_cast<float>(texH);
|
||||
const float q0 = fabsQ(v0.q);
|
||||
const float q1 = fabsQ(v1.q);
|
||||
u0f = (v0.s / q0) * static_cast<float>(texW);
|
||||
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 spriteH = static_cast<float>(y1 - y0);
|
||||
if (spriteW < 1.0f) spriteW = 1.0f;
|
||||
if (spriteH < 1.0f) spriteH = 1.0f;
|
||||
if (spriteW < 1.0f)
|
||||
spriteW = 1.0f;
|
||||
if (spriteH < 1.0f)
|
||||
spriteH = 1.0f;
|
||||
|
||||
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);
|
||||
|
||||
uint32_t texel;
|
||||
uint32_t sampleIndexValue = 0u;
|
||||
if (tex.psm == GS_PSM_CT32 || tex.psm == GS_PSM_CT24)
|
||||
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)
|
||||
{
|
||||
uint32_t idx = readTexelPSMT4(gs, tex.tbp0, tex.tbw, tu, tv);
|
||||
texel = lookupCLUT(gs, static_cast<uint8_t>(idx), tex.cbp, tex.cpsm, tex.csa);
|
||||
sampleIndexValue = readTexelPSMT4(gs, tex.tbp0, tex.tbw, tu, tv);
|
||||
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)
|
||||
{
|
||||
uint32_t base = tex.tbp0 * 256u;
|
||||
uint32_t tbw = tex.tbw ? tex.tbw : 1u;
|
||||
uint32_t stride = tbw * 64u;
|
||||
uint32_t off = base + static_cast<uint32_t>(tv) * stride + static_cast<uint32_t>(tu);
|
||||
uint8_t idx = (off < gs->m_vramSize) ? gs->m_vram[off] : 0;
|
||||
texel = lookupCLUT(gs, idx, tex.cbp, tex.cpsm, tex.csa);
|
||||
uint32_t off = GSPSMT8::addrPSMT8(tex.tbp0, tex.tbw ? tex.tbw : 1u,
|
||||
static_cast<uint32_t>(tu), static_cast<uint32_t>(tv));
|
||||
sampleIndexValue = (off < gs->m_vramSize) ? gs->m_vram[off] : 0u;
|
||||
texel = lookupCLUT(gs, static_cast<uint8_t>(sampleIndexValue), tex.cbp, tex.cpsm, tex.csm, tex.csa, tex.psm);
|
||||
}
|
||||
else
|
||||
texel = 0xFFFF00FFu;
|
||||
@@ -301,27 +677,8 @@ void GSRasterizer::drawSprite(GS *gs)
|
||||
uint8_t tb = static_cast<uint8_t>((texel >> 16) & 0xFF);
|
||||
uint8_t ta = static_cast<uint8_t>((texel >> 24) & 0xFF);
|
||||
|
||||
uint8_t fr, fg, fb, fa;
|
||||
if (tex.tfx == 0)
|
||||
{
|
||||
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);
|
||||
const TextureCombineResult color = combineTexture(tex, r, g, b, a, tr, tg, tb, ta);
|
||||
writePixel(gs, x, y, color.r, color.g, color.b, color.a);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -364,7 +721,8 @@ void GSRasterizer::drawTriangle(GS *gs)
|
||||
if (std::fabs(denom) < 0.001f)
|
||||
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)
|
||||
{
|
||||
@@ -373,8 +731,8 @@ void GSRasterizer::drawTriangle(GS *gs)
|
||||
{
|
||||
float px = static_cast<float>(x) + 0.5f;
|
||||
|
||||
float w0 = ((fy1 - fy2) * (px - fx2) + (fx2 - fx1) * (py - fy2)) * invDenom;
|
||||
float w1 = ((fy2 - fy0) * (px - fx2) + (fx0 - fx2) * (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)) * winding) * invAbsDenom;
|
||||
float w2 = 1.0f - w0 - w1;
|
||||
|
||||
if (w0 < 0.0f || w1 < 0.0f || w2 < 0.0f)
|
||||
@@ -390,50 +748,57 @@ void GSRasterizer::drawTriangle(GS *gs)
|
||||
}
|
||||
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)
|
||||
{
|
||||
float is, it;
|
||||
float is, it, iq;
|
||||
uint16_t iu, iv;
|
||||
if (gs->m_prim.fst)
|
||||
{
|
||||
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);
|
||||
is = 0; it = 0;
|
||||
is = 0.0f;
|
||||
it = 0.0f;
|
||||
iq = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
is = v0.s * w0 + v1.s * w1 + v2.s * w2;
|
||||
it = v0.t * w0 + v1.t * w1 + v2.t * w2;
|
||||
iu = 0; iv = 0;
|
||||
const float invQ0 = 1.0f / fabsQ(v0.q);
|
||||
const float invQ1 = 1.0f / fabsQ(v1.q);
|
||||
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 tg = static_cast<uint8_t>((texel >> 8) & 0xFF);
|
||||
uint8_t tb = static_cast<uint8_t>((texel >> 16) & 0xFF);
|
||||
uint8_t ta = static_cast<uint8_t>((texel >> 24) & 0xFF);
|
||||
|
||||
const auto &tex = ctx.tex0;
|
||||
if (tex.tfx == 0)
|
||||
{
|
||||
r = clampU8((tr * r) >> 7);
|
||||
g = clampU8((tg * g) >> 7);
|
||||
b = clampU8((tb * b) >> 7);
|
||||
a = ta;
|
||||
}
|
||||
else if (tex.tfx == 1)
|
||||
{
|
||||
r = tr; g = tg; b = tb; a = ta;
|
||||
}
|
||||
else
|
||||
{
|
||||
r = clampU8((tr * r) >> 7);
|
||||
g = clampU8((tg * g) >> 7);
|
||||
b = clampU8((tb * b) >> 7);
|
||||
a = ta;
|
||||
}
|
||||
const uint8_t shadeR = r;
|
||||
const uint8_t shadeG = g;
|
||||
const uint8_t shadeB = b;
|
||||
const uint8_t shadeA = a;
|
||||
const TextureCombineResult color = combineTexture(tex, shadeR, shadeG, shadeB, shadeA, tr, tg, tb, ta);
|
||||
|
||||
r = color.r;
|
||||
g = color.g;
|
||||
b = color.b;
|
||||
a = color.a;
|
||||
}
|
||||
|
||||
writePixel(gs, x, y, r, g, b, a);
|
||||
@@ -462,7 +827,8 @@ void GSRasterizer::drawLine(GS *gs)
|
||||
int err = dx + dy;
|
||||
|
||||
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;
|
||||
|
||||
for (;;)
|
||||
@@ -478,7 +844,10 @@ void GSRasterizer::drawLine(GS *gs)
|
||||
}
|
||||
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);
|
||||
@@ -487,8 +856,16 @@ void GSRasterizer::drawLine(GS *gs)
|
||||
break;
|
||||
|
||||
int e2 = 2 * err;
|
||||
if (e2 >= dy) { err += dy; x0 += sx; }
|
||||
if (e2 <= dx) { err += dx; y0 += sy; }
|
||||
if (e2 >= dy)
|
||||
{
|
||||
err += dy;
|
||||
x0 += sx;
|
||||
}
|
||||
if (e2 <= dx)
|
||||
{
|
||||
err += dx;
|
||||
y0 += sy;
|
||||
}
|
||||
++step;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,15 +224,14 @@ bool PS2Memory::initialize(size_t ramSize)
|
||||
|
||||
bool PS2Memory::isScratchpad(uint32_t address) const
|
||||
{
|
||||
return address >= PS2_SCRATCHPAD_BASE &&
|
||||
address < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE;
|
||||
return ps2IsScratchpadAddress(address);
|
||||
}
|
||||
|
||||
uint32_t PS2Memory::translateAddress(uint32_t virtualAddress)
|
||||
{
|
||||
if (isScratchpad(virtualAddress))
|
||||
{
|
||||
return virtualAddress - PS2_SCRATCHPAD_BASE;
|
||||
return ps2ScratchpadOffset(virtualAddress);
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (channelBase == 0x1000A000)
|
||||
m_pendingGifTransfers.push_back(pt);
|
||||
else if (channelBase == 0x10009000 && !scratch)
|
||||
else if (channelBase == 0x10009000)
|
||||
m_pendingVif1Transfers.push_back(pt);
|
||||
};
|
||||
|
||||
@@ -1029,6 +1028,13 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
|
||||
{
|
||||
enqueueTransfer(madr, qwc);
|
||||
}
|
||||
|
||||
const bool autoProcessTransfers =
|
||||
(channelBase == 0x1000A000u) ? (m_gifPacketCallback || m_gifArbiter != nullptr) : true;
|
||||
if (autoProcessTransfers)
|
||||
{
|
||||
processPendingTransfers();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -1105,9 +1111,11 @@ void PS2Memory::processPendingTransfers()
|
||||
{
|
||||
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;
|
||||
const uint64_t bytes64 = static_cast<uint64_t>(p.qwc) * 16ull;
|
||||
uint32_t sizeBytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast<uint32_t>(bytes64);
|
||||
try
|
||||
{
|
||||
srcPhys = translateAddress(p.srcAddr);
|
||||
@@ -1116,10 +1124,13 @@ void PS2Memory::processPendingTransfers()
|
||||
{
|
||||
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)
|
||||
sizeBytes = PS2_RAM_SIZE - srcPhys;
|
||||
if (sizeBytes > 0)
|
||||
|
||||
@@ -92,6 +92,7 @@ namespace
|
||||
};
|
||||
|
||||
thread_local DispatchHistory g_dispatchHistory;
|
||||
thread_local std::unordered_map<PS2Runtime *, uint32_t> g_guestExecutionDepths;
|
||||
|
||||
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)
|
||||
{
|
||||
// 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)
|
||||
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 *gsvram = rt->memory().getGSVRAM();
|
||||
|
||||
uint32_t snapSize = 0;
|
||||
const uint8_t *snapVram = rt->gs().lockDisplaySnapshot(snapSize);
|
||||
const uint8_t *vramSrc = (snapVram && snapSize > 0) ? snapVram : gsvram;
|
||||
|
||||
if (snapVram)
|
||||
auto fillScratchFromFrame = [&](uint32_t srcFbp,
|
||||
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)
|
||||
{
|
||||
uint32_t srcOff = baseBytes + y * strideBytes;
|
||||
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;
|
||||
const uint8_t *row = scratchBuf.data() + y * FB_WIDTH * 4u;
|
||||
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;
|
||||
for (uint32_t y = 0; y < height; ++y)
|
||||
if (!vramSrc)
|
||||
{
|
||||
uint32_t srcOff = baseBytes + y * strideBytes;
|
||||
uint32_t dstOff = y * FB_WIDTH * 4;
|
||||
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 = scratch.data() + dstOff;
|
||||
for (uint32_t x = 0; x < width; ++x)
|
||||
return 0u;
|
||||
}
|
||||
const uint32_t probeBaseBytes = probeFbp * 8192u;
|
||||
const uint32_t probeStrideBytes = 10u * 64u * 4u;
|
||||
uint32_t count = 0u;
|
||||
for (uint32_t py = 0; py < height; ++py)
|
||||
{
|
||||
const uint32_t srcOff = probeBaseBytes + py * probeStrideBytes;
|
||||
if (srcOff + width * 4u > PS2_GS_VRAM_SIZE)
|
||||
{
|
||||
uint16_t p = *reinterpret_cast<const uint16_t *>(src + x * 2);
|
||||
uint32_t r = (p >> 10) & 31u;
|
||||
uint32_t g = (p >> 5) & 31u;
|
||||
uint32_t b = p & 31u;
|
||||
dst[x * 4 + 0] = static_cast<uint8_t>((r << 3) | (r >> 2));
|
||||
dst[x * 4 + 1] = static_cast<uint8_t>((g << 3) | (g >> 2));
|
||||
dst[x * 4 + 2] = static_cast<uint8_t>((b << 3) | (b >> 2));
|
||||
dst[x * 4 + 3] = 255u;
|
||||
break;
|
||||
}
|
||||
const uint8_t *row = vramSrc + srcOff;
|
||||
for (uint32_t px = 0; px < width; ++px)
|
||||
{
|
||||
const uint8_t r = row[px * 4u + 0u];
|
||||
const uint8_t g = row[px * 4u + 1u];
|
||||
const uint8_t b = row[px * 4u + 2u];
|
||||
if (r != 0u || g != 0u || b != 0u)
|
||||
{
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
return count;
|
||||
};
|
||||
|
||||
std::vector<uint8_t> scratch;
|
||||
if (!fillScratchFromFrame(fbp, fbw, psm, scratch))
|
||||
{
|
||||
rt->gs().unlockDisplaySnapshot();
|
||||
Image blank = GenImageColor(FB_WIDTH, FB_HEIGHT, MAGENTA);
|
||||
@@ -425,8 +544,96 @@ static void UploadFrame(Texture2D &tex, PS2Runtime *rt)
|
||||
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();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -448,11 +655,14 @@ PS2Runtime::PS2Runtime()
|
||||
m_guestHeapLimit = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE);
|
||||
m_guestHeapSuggestedBase = kGuestHeapDefaultBase;
|
||||
m_guestHeapConfigured = false;
|
||||
m_asyncCallbackStackFloor = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE);
|
||||
m_asyncCallbackStackTop = PS2_RAM_SIZE;
|
||||
}
|
||||
|
||||
PS2Runtime::~PS2Runtime()
|
||||
{
|
||||
requestStop();
|
||||
ps2_syscalls::detachAllGuestHostThreads();
|
||||
if (IsWindowReady())
|
||||
{
|
||||
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.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.setVu1MscalCallback([this](uint32_t startPC, uint32_t itop) {
|
||||
m_vu1.execute(m_memory.getVU1Code(), PS2_VU1_CODE_SIZE,
|
||||
m_memory.getVU1Data(), PS2_VU1_DATA_SIZE,
|
||||
m_gs, &m_memory, startPC, itop, 65536);
|
||||
});
|
||||
m_memory.setVu1MscalCallback([this](uint32_t startPC, uint32_t itop)
|
||||
{ m_vu1.execute(m_memory.getVU1Code(), PS2_VU1_CODE_SIZE,
|
||||
m_memory.getVU1Data(), PS2_VU1_DATA_SIZE,
|
||||
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.reset();
|
||||
@@ -707,6 +921,12 @@ bool PS2Runtime::loadELF(const std::string &elfPath)
|
||||
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;
|
||||
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
|
||||
{
|
||||
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)
|
||||
@@ -797,9 +1029,6 @@ PS2Runtime::RecompiledFunction PS2Runtime::lookupFunction(uint32_t address)
|
||||
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)
|
||||
{
|
||||
auto parent = m_functionTable.find(0x2913B0u);
|
||||
@@ -1499,6 +1728,44 @@ uint32_t PS2Runtime::guestHeapEnd() const
|
||||
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)
|
||||
{
|
||||
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 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)
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
try
|
||||
@@ -1752,7 +2093,6 @@ void PS2Runtime::run()
|
||||
while (!isStopRequested() && g_activeThreads.load(std::memory_order_relaxed) > 0)
|
||||
{
|
||||
tick++;
|
||||
ps2_stubs::dispatchGsSyncVCallback(m_memory.getRDRAM(), this);
|
||||
if ((tick % 120) == 0)
|
||||
{
|
||||
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);
|
||||
CloseWindow();
|
||||
|
||||
|
||||
@@ -83,6 +83,18 @@ namespace ps2_syscalls
|
||||
case 0x17:
|
||||
DisableDmac(rdram, ctx, runtime);
|
||||
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 0xFC:
|
||||
SetAlarm(rdram, ctx, runtime);
|
||||
@@ -119,13 +131,17 @@ namespace ps2_syscalls
|
||||
TerminateThread(rdram, ctx, runtime);
|
||||
return true;
|
||||
case 0x29:
|
||||
case static_cast<uint32_t>(-0x2A):
|
||||
ChangeThreadPriority(rdram, ctx, runtime);
|
||||
return true;
|
||||
case static_cast<uint32_t>(-0x2A):
|
||||
iChangeThreadPriority(rdram, ctx, runtime);
|
||||
return true;
|
||||
case 0x2B:
|
||||
case static_cast<uint32_t>(-0x2C):
|
||||
RotateThreadReadyQueue(rdram, ctx, runtime);
|
||||
return true;
|
||||
case static_cast<uint32_t>(-0x2C):
|
||||
iRotateThreadReadyQueue(rdram, ctx, runtime);
|
||||
return true;
|
||||
case 0x2D:
|
||||
ReleaseWaitThread(rdram, ctx, runtime);
|
||||
return true;
|
||||
@@ -137,9 +153,11 @@ namespace ps2_syscalls
|
||||
GetThreadId(rdram, ctx, runtime);
|
||||
return true;
|
||||
case 0x30:
|
||||
case static_cast<uint32_t>(-0x31):
|
||||
ReferThreadStatus(rdram, ctx, runtime);
|
||||
return true;
|
||||
case static_cast<uint32_t>(-0x31):
|
||||
iReferThreadStatus(rdram, ctx, runtime);
|
||||
return true;
|
||||
case 0x32:
|
||||
SleepThread(rdram, ctx, runtime);
|
||||
return true;
|
||||
@@ -217,7 +235,7 @@ namespace ps2_syscalls
|
||||
case 0x52:
|
||||
SetEventFlag(rdram, ctx, runtime);
|
||||
return true;
|
||||
case 0x53:
|
||||
case static_cast<uint32_t>(-0x53):
|
||||
iSetEventFlag(rdram, ctx, runtime);
|
||||
return true;
|
||||
case 0x54:
|
||||
@@ -267,13 +285,17 @@ namespace ps2_syscalls
|
||||
FlushCache(rdram, ctx, runtime);
|
||||
return true;
|
||||
case 0x70:
|
||||
case static_cast<uint32_t>(-0x70):
|
||||
GsGetIMR(rdram, ctx, runtime);
|
||||
return true;
|
||||
case static_cast<uint32_t>(-0x70):
|
||||
iGsGetIMR(rdram, ctx, runtime);
|
||||
return true;
|
||||
case 0x71:
|
||||
case static_cast<uint32_t>(-0x71):
|
||||
GsPutIMR(rdram, ctx, runtime);
|
||||
return true;
|
||||
case static_cast<uint32_t>(-0x71):
|
||||
iGsPutIMR(rdram, ctx, runtime);
|
||||
return true;
|
||||
case 0x73:
|
||||
SetVSyncFlag(rdram, ctx, runtime);
|
||||
return true;
|
||||
@@ -352,8 +374,6 @@ namespace ps2_syscalls
|
||||
threadInfo->cv.notify_all();
|
||||
}
|
||||
|
||||
joinAllHostThreads();
|
||||
|
||||
std::vector<std::shared_ptr<SemaInfo>> semas;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sema_map_mutex);
|
||||
@@ -407,4 +427,14 @@ namespace ps2_syscalls
|
||||
g_syscall_overrides.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void joinAllGuestHostThreads()
|
||||
{
|
||||
joinAllHostThreads();
|
||||
}
|
||||
|
||||
void detachAllGuestHostThreads()
|
||||
{
|
||||
detachAllHostThreads();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Based on Blackline Interactive implementation
|
||||
#include "ps2_memory.h"
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <iostream>
|
||||
|
||||
enum VIFCmd : uint8_t
|
||||
{
|
||||
@@ -27,6 +29,12 @@ enum VIFCmd : uint8_t
|
||||
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)
|
||||
{
|
||||
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;
|
||||
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.
|
||||
vif1_regs.code = cmd;
|
||||
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
|
||||
recomputeVif1Tops();
|
||||
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)
|
||||
m_vu1MscalCallback(startPC, vif1_regs.itop);
|
||||
continue;
|
||||
@@ -141,6 +171,16 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes)
|
||||
vif1_regs.itops = vif1_regs.itop & 0x3FFu;
|
||||
vif1_regs.stat ^= (1u << 7); // toggle DBF
|
||||
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;
|
||||
}
|
||||
else if (opcode == VIF_STMASK)
|
||||
@@ -222,11 +262,20 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes)
|
||||
int bitsPerComponent = 32;
|
||||
switch (vl)
|
||||
{
|
||||
case 0: bitsPerComponent = 32; break;
|
||||
case 1: bitsPerComponent = 16; break;
|
||||
case 2: bitsPerComponent = 8; break;
|
||||
case 3: bitsPerComponent = (vn == 3) ? 4 : 16; break;
|
||||
default: break;
|
||||
case 0:
|
||||
bitsPerComponent = 32;
|
||||
break;
|
||||
case 1:
|
||||
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);
|
||||
uint32_t bytesPerVector = (bitsPerVector + 7) / 8;
|
||||
|
||||
+346
-146
@@ -2,25 +2,33 @@
|
||||
#include "ps2_gs_gpu.h"
|
||||
#include "ps2_gif_arbiter.h"
|
||||
#include "ps2_memory.h"
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::atomic<uint32_t> s_debugVu1XgkickCount{0};
|
||||
}
|
||||
|
||||
// Instruction field extraction helpers
|
||||
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 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 BC(uint32_t i) { return (uint8_t)(i & 0x3); }
|
||||
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 FD(uint32_t i) { return (uint8_t)((i >> 6) & 0x1F); }
|
||||
static inline uint8_t BC(uint32_t i) { return (uint8_t)(i & 0x3); }
|
||||
|
||||
// Lower instruction field helpers
|
||||
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 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 IMM15(uint32_t i){
|
||||
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 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 IMM15(uint32_t i)
|
||||
{
|
||||
uint32_t lo11 = i & 0x7FF;
|
||||
uint32_t hi4 = (i >> 21) & 0xF;
|
||||
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)
|
||||
{
|
||||
if (dest & 0x8) dst[0] = result[0]; // x
|
||||
if (dest & 0x4) dst[1] = result[1]; // y
|
||||
if (dest & 0x2) dst[2] = result[2]; // z
|
||||
if (dest & 0x1) dst[3] = result[3]; // w
|
||||
if (dest & 0x8)
|
||||
dst[0] = result[0]; // x
|
||||
if (dest & 0x4)
|
||||
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)
|
||||
@@ -58,10 +70,10 @@ void VU1Interpreter::applyDestAcc(const float *result, uint8_t dest)
|
||||
}
|
||||
|
||||
void VU1Interpreter::execute(uint8_t *vuCode, uint32_t codeSize,
|
||||
uint8_t *vuData, uint32_t dataSize,
|
||||
GS &gs, PS2Memory *memory,
|
||||
uint32_t startPC, uint32_t itop,
|
||||
uint32_t maxCycles)
|
||||
uint8_t *vuData, uint32_t dataSize,
|
||||
GS &gs, PS2Memory *memory,
|
||||
uint32_t startPC, uint32_t itop,
|
||||
uint32_t maxCycles)
|
||||
{
|
||||
m_state.pc = startPC;
|
||||
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,
|
||||
uint8_t *vuData, uint32_t dataSize,
|
||||
GS &gs, PS2Memory *memory,
|
||||
uint32_t itop, uint32_t maxCycles)
|
||||
uint8_t *vuData, uint32_t dataSize,
|
||||
GS &gs, PS2Memory *memory,
|
||||
uint32_t itop, uint32_t maxCycles)
|
||||
{
|
||||
m_state.ebit = false;
|
||||
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,
|
||||
uint8_t *vuData, uint32_t dataSize,
|
||||
GS &gs, PS2Memory *memory, uint32_t maxCycles)
|
||||
uint8_t *vuData, uint32_t dataSize,
|
||||
GS &gs, PS2Memory *memory, uint32_t maxCycles)
|
||||
{
|
||||
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;
|
||||
|
||||
uint32_t nextPC = m_state.pc + 8;
|
||||
if (nextPC >= codeSize) nextPC = 0;
|
||||
if (nextPC >= codeSize)
|
||||
nextPC = 0;
|
||||
m_state.pc = nextPC;
|
||||
|
||||
if (m_state.ebit)
|
||||
@@ -152,125 +165,171 @@ void VU1Interpreter::execUpper(uint32_t instr)
|
||||
// Upper opcode decoding (bits 5:0 of upper word)
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
case 0x2E: // OPMSUB
|
||||
@@ -281,12 +340,16 @@ void VU1Interpreter::execUpper(uint32_t instr)
|
||||
applyDest(vd, result, dest);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
|
||||
// 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 sop = (instr & 0x3) | ((instr >> 4) & 0x3C);
|
||||
@@ -301,95 +364,160 @@ void VU1Interpreter::execUpper(uint32_t instr)
|
||||
(void)bc2;
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
case 0x1F: // CLIP
|
||||
{
|
||||
float w = std::fabs(vt[3]);
|
||||
uint32_t flags = 0;
|
||||
if (vs[0] > +w) flags |= 0x01;
|
||||
if (vs[0] < -w) flags |= 0x02;
|
||||
if (vs[1] > +w) flags |= 0x04;
|
||||
if (vs[1] < -w) flags |= 0x08;
|
||||
if (vs[2] > +w) flags |= 0x10;
|
||||
if (vs[2] < -w) flags |= 0x20;
|
||||
if (vs[0] > +w)
|
||||
flags |= 0x01;
|
||||
if (vs[0] < -w)
|
||||
flags |= 0x02;
|
||||
if (vs[1] > +w)
|
||||
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;
|
||||
return;
|
||||
}
|
||||
@@ -403,55 +531,68 @@ void VU1Interpreter::execUpper(uint32_t instr)
|
||||
switch (funct)
|
||||
{
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
case 0x0E: // OPMULA
|
||||
@@ -475,7 +616,10 @@ void VU1Interpreter::execUpper(uint32_t instr)
|
||||
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:
|
||||
// NOP / unimplemented upper
|
||||
return;
|
||||
@@ -524,10 +668,14 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
|
||||
{
|
||||
float tmp[4];
|
||||
std::memcpy(tmp, vuData + addr, 16);
|
||||
if (dest & 0x8) tmp[0] = m_state.vf[is][0];
|
||||
if (dest & 0x4) 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];
|
||||
if (dest & 0x8)
|
||||
tmp[0] = m_state.vf[is][0];
|
||||
if (dest & 0x4)
|
||||
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);
|
||||
}
|
||||
return;
|
||||
@@ -543,13 +691,18 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
|
||||
if (addr + 16 <= dataSize)
|
||||
{
|
||||
int comp = 0;
|
||||
if (dest & 0x8) comp = 0;
|
||||
else if (dest & 0x4) comp = 1;
|
||||
else if (dest & 0x2) comp = 2;
|
||||
else comp = 3;
|
||||
if (dest & 0x8)
|
||||
comp = 0;
|
||||
else if (dest & 0x4)
|
||||
comp = 1;
|
||||
else if (dest & 0x2)
|
||||
comp = 2;
|
||||
else
|
||||
comp = 3;
|
||||
uint32_t v;
|
||||
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;
|
||||
}
|
||||
@@ -564,10 +717,14 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
|
||||
if (addr + 16 <= dataSize)
|
||||
{
|
||||
uint32_t val = (uint32_t)(uint16_t)(m_state.vi[it] & 0xFFFF);
|
||||
if (dest & 0x8) std::memcpy(vuData + addr + 0, &val, 4);
|
||||
if (dest & 0x4) 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);
|
||||
if (dest & 0x8)
|
||||
std::memcpy(vuData + addr + 0, &val, 4);
|
||||
if (dest & 0x4)
|
||||
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;
|
||||
}
|
||||
@@ -592,7 +749,8 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
|
||||
case 0x10: // FCEQ
|
||||
{
|
||||
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;
|
||||
}
|
||||
case 0x11: // FCSET
|
||||
@@ -603,19 +761,22 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
|
||||
case 0x12: // FCAND
|
||||
{
|
||||
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;
|
||||
}
|
||||
case 0x13: // FCOR
|
||||
{
|
||||
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;
|
||||
}
|
||||
case 0x14: // FSEQ
|
||||
{
|
||||
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;
|
||||
}
|
||||
case 0x15: // FSSET
|
||||
@@ -626,34 +787,39 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
|
||||
case 0x16: // FSAND
|
||||
{
|
||||
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;
|
||||
}
|
||||
case 0x17: // FSOR
|
||||
{
|
||||
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;
|
||||
}
|
||||
case 0x18: // FMAND
|
||||
{
|
||||
uint8_t it = LIT(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;
|
||||
}
|
||||
case 0x1A: // FMEQ
|
||||
{
|
||||
uint8_t it = LIT(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;
|
||||
}
|
||||
case 0x1C: // FMOR
|
||||
{
|
||||
uint8_t it = LIT(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;
|
||||
}
|
||||
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);
|
||||
int16_t imm = IMM11(instr);
|
||||
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;
|
||||
return;
|
||||
}
|
||||
@@ -685,7 +852,8 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
|
||||
uint8_t it = LIT(instr);
|
||||
uint8_t is = LIS(instr);
|
||||
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;
|
||||
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)
|
||||
{
|
||||
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);
|
||||
return;
|
||||
}
|
||||
@@ -815,20 +983,27 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
|
||||
float result[4];
|
||||
int32_t val = (int32_t)(int16_t)(m_state.vi[is] & 0xFFFF);
|
||||
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);
|
||||
return;
|
||||
}
|
||||
case 0x04: // MTIR (Move To Integer Register)
|
||||
{
|
||||
int comp = 0;
|
||||
if (dest & 0x8) comp = 0;
|
||||
else if (dest & 0x4) comp = 1;
|
||||
else if (dest & 0x2) comp = 2;
|
||||
else comp = 3;
|
||||
if (dest & 0x8)
|
||||
comp = 0;
|
||||
else if (dest & 0x4)
|
||||
comp = 1;
|
||||
else if (dest & 0x2)
|
||||
comp = 2;
|
||||
else
|
||||
comp = 3;
|
||||
uint32_t fval;
|
||||
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;
|
||||
}
|
||||
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);
|
||||
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;
|
||||
}
|
||||
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];
|
||||
std::memcpy(tmp, vuData + addr, 16);
|
||||
if (dest & 0x8) tmp[0] = m_state.vf[is][0];
|
||||
if (dest & 0x4) 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];
|
||||
if (dest & 0x8)
|
||||
tmp[0] = m_state.vf[is][0];
|
||||
if (dest & 0x4)
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
addr &= (dataSize - 1);
|
||||
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)
|
||||
{
|
||||
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;
|
||||
addr &= (dataSize - 1);
|
||||
if (addr + 16 <= dataSize)
|
||||
{
|
||||
float tmp[4];
|
||||
std::memcpy(tmp, vuData + addr, 16);
|
||||
if (dest & 0x8) tmp[0] = m_state.vf[is][0];
|
||||
if (dest & 0x4) 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];
|
||||
if (dest & 0x8)
|
||||
tmp[0] = m_state.vf[is][0];
|
||||
if (dest & 0x4)
|
||||
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);
|
||||
}
|
||||
return;
|
||||
@@ -936,7 +1123,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
}
|
||||
@@ -949,7 +1136,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
|
||||
}
|
||||
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);
|
||||
m_state.p = (len != 0.0f) ? (1.0f / len) : std::numeric_limits<float>::max();
|
||||
return;
|
||||
@@ -960,7 +1147,7 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
|
||||
return;
|
||||
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);
|
||||
return;
|
||||
}
|
||||
@@ -1035,6 +1222,17 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
|
||||
if (totalBytes == 0u)
|
||||
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 (memory)
|
||||
@@ -1059,12 +1257,14 @@ void VU1Interpreter::execLower(uint32_t instr, uint8_t *vuData, uint32_t dataSiz
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -1412,6 +1412,7 @@ namespace
|
||||
mem.writeIORegister(channelBase + 0x10u, madr);
|
||||
mem.writeIORegister(channelBase + 0x30u, tadr);
|
||||
mem.writeIORegister(channelBase + 0x00u, chcr);
|
||||
mem.processPendingTransfers();
|
||||
|
||||
std::lock_guard<std::mutex> lock(g_dmaStubMutex);
|
||||
g_dmaPendingPolls[channelBase] = 1;
|
||||
@@ -1492,6 +1493,65 @@ namespace
|
||||
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
|
||||
{
|
||||
uint16_t x;
|
||||
@@ -1503,33 +1563,14 @@ namespace
|
||||
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(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;
|
||||
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 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)
|
||||
{
|
||||
uint32_t sp = getRegU32(ctx, 29);
|
||||
@@ -1712,6 +1800,99 @@ namespace
|
||||
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 ®s = 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)
|
||||
{
|
||||
if (!runtime)
|
||||
|
||||
@@ -1,28 +1,48 @@
|
||||
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;
|
||||
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_sp = 0u;
|
||||
uint32_t g_gs_sync_v_callback_stack_base = 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;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_gs_sync_v_callback_mutex);
|
||||
g_gs_sync_v_callback_func = 0u;
|
||||
g_gs_sync_v_callback_gp = 0u;
|
||||
g_gs_sync_v_callback_sp = 0u;
|
||||
g_gs_sync_v_callback_stack_base = 0u;
|
||||
g_gs_sync_v_callback_stack_top = 0u;
|
||||
g_gs_sync_v_callback_tick = 0u;
|
||||
g_gs_sync_v_callback_bad_pc_logs = 0u;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_gs_sync_v_callback_mutex);
|
||||
g_gs_sync_v_callback_func = 0u;
|
||||
g_gs_sync_v_callback_gp = 0u;
|
||||
g_gs_sync_v_callback_sp = 0u;
|
||||
g_gs_sync_v_callback_stack_base = 0u;
|
||||
g_gs_sync_v_callback_stack_top = 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)
|
||||
{
|
||||
@@ -31,20 +51,17 @@ void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime)
|
||||
|
||||
uint32_t callback = 0u;
|
||||
uint32_t gp = 0u;
|
||||
uint32_t sp = 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);
|
||||
callback = g_gs_sync_v_callback_func;
|
||||
gp = g_gs_sync_v_callback_gp;
|
||||
sp = g_gs_sync_v_callback_sp;
|
||||
callbackStackTop = g_gs_sync_v_callback_stack_top;
|
||||
if (callback == 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
tick = ++g_gs_sync_v_callback_tick;
|
||||
}
|
||||
|
||||
if (!runtime->hasFunction(callback))
|
||||
@@ -55,14 +72,14 @@ void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime)
|
||||
if (callbackStackTop == 0u)
|
||||
{
|
||||
constexpr uint32_t kCallbackStackSize = 0x4000u;
|
||||
const uint32_t stackBase = runtime->guestMalloc(kCallbackStackSize, 16u);
|
||||
if (stackBase != 0u)
|
||||
const uint32_t stackTop = runtime->reserveAsyncCallbackStack(kCallbackStackSize, 16u);
|
||||
if (stackTop != 0u)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_gs_sync_v_callback_mutex);
|
||||
if (g_gs_sync_v_callback_stack_top == 0u)
|
||||
{
|
||||
g_gs_sync_v_callback_stack_base = stackBase;
|
||||
g_gs_sync_v_callback_stack_top = stackBase + kCallbackStackSize - 0x10u;
|
||||
g_gs_sync_v_callback_stack_base = stackTop - (kCallbackStackSize - 0x10u);
|
||||
g_gs_sync_v_callback_stack_top = stackTop;
|
||||
}
|
||||
callbackStackTop = g_gs_sync_v_callback_stack_top;
|
||||
}
|
||||
@@ -72,9 +89,9 @@ void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime)
|
||||
{
|
||||
R5900Context callbackCtx{};
|
||||
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, 4, static_cast<uint32_t>(tick));
|
||||
SET_GPR_U32(&callbackCtx, 4, static_cast<uint32_t>(callbackTick));
|
||||
callbackCtx.pc = callback;
|
||||
|
||||
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 dsay = static_cast<uint32_t>(img.y);
|
||||
|
||||
// Full messy
|
||||
uint64_t *q = reinterpret_cast<uint64_t *>(pkt);
|
||||
q[0] = 0x1000000000000004ULL;
|
||||
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;
|
||||
uint64_t bitbltbuf = (static_cast<uint64_t>(sbp & 0x3FFFu) << 0) |
|
||||
(static_cast<uint64_t>(fbw & 0x3Fu) << 16) |
|
||||
(static_cast<uint64_t>(img.psm & 0x3Fu) << 24) |
|
||||
(static_cast<uint64_t>(0u) << 32) |
|
||||
(static_cast<uint64_t>(1u) << 48) |
|
||||
(static_cast<uint64_t>(0u) << 56);
|
||||
(static_cast<uint64_t>(fbw & 0x3Fu) << 16) |
|
||||
(static_cast<uint64_t>(img.psm & 0x3Fu) << 24) |
|
||||
(static_cast<uint64_t>(0u) << 32) |
|
||||
(static_cast<uint64_t>(1u) << 48) |
|
||||
(static_cast<uint64_t>(0u) << 56);
|
||||
uint64_t trxpos = (static_cast<uint64_t>(img.x & 0x7FFu) << 0) |
|
||||
(static_cast<uint64_t>(img.y & 0x7FFu) << 16) |
|
||||
(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)
|
||||
{
|
||||
uint32_t envAddr = getRegU32(ctx, 4);
|
||||
uint8_t *ptr = getMemPtr(rdram, envAddr);
|
||||
if (!ptr)
|
||||
GsDispEnvMem env{};
|
||||
if (!readGsDispEnv(rdram, envAddr, env))
|
||||
{
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
constexpr uint32_t GIF_CHANNEL = 0x1000A000;
|
||||
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);
|
||||
applyGsDispEnv(runtime, env);
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void sceGsPutDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
uint32_t envAddr = getRegU32(ctx, 4);
|
||||
uint8_t *ptr = getMemPtr(rdram, envAddr);
|
||||
if (!ptr)
|
||||
GsRegPairMem pairs[8]{};
|
||||
if (!readGsRegPairs(rdram, envAddr, pairs, 8u))
|
||||
{
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
applyGsRegPairs(runtime, pairs, 8u);
|
||||
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.ffmode = static_cast<uint8_t>(ffmode & 0x1);
|
||||
writeGsGParamToScratch(runtime);
|
||||
resetGsSyncVState();
|
||||
|
||||
uint64_t pmode = makePmode(1, 0, 0, 0, 0, 0x80);
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -429,6 +490,60 @@ void sceGsSetDefDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
sceGszbufaddr(rdram, ctx, runtime);
|
||||
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);
|
||||
if (!ptr)
|
||||
{
|
||||
@@ -436,48 +551,10 @@ void sceGsSetDefDrawEnv(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t *const words = reinterpret_cast<uint64_t *>(ptr);
|
||||
|
||||
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;
|
||||
|
||||
std::memcpy(ptr, &env, sizeof(env));
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
static int cur = 0;
|
||||
cur ^= 1;
|
||||
setReturnS32(ctx, cur);
|
||||
const uint32_t envAddr = getRegU32(ctx, 4);
|
||||
const uint32_t which = getRegU32(ctx, 5) & 1u;
|
||||
|
||||
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)
|
||||
@@ -577,13 +674,18 @@ void sceGsSyncPath(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)rdram;
|
||||
|
||||
const uint32_t newCallback = getRegU32(ctx, 4);
|
||||
const uint32_t callerPc = ctx ? ctx->pc : 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;
|
||||
}
|
||||
|
||||
if (newCallback != 0u)
|
||||
{
|
||||
ps2_syscalls::EnsureVSyncWorkerRunning(rdram, runtime);
|
||||
}
|
||||
|
||||
setReturnU32(ctx, oldCallback);
|
||||
}
|
||||
|
||||
|
||||
@@ -1645,7 +1645,10 @@ void sceIpuInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
if (setD4)
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -128,7 +128,10 @@ void syMalloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
const uint32_t returnPc = getRegU32(ctx, 31);
|
||||
PS2Runtime::RecompiledFunction syMallocFn = runtime->lookupFunction(kCvSyMallocAddr);
|
||||
ctx->pc = kCvSyMallocAddr;
|
||||
syMallocFn(rdram, ctx, runtime);
|
||||
{
|
||||
PS2Runtime::GuestExecutionScope guestExecution(runtime);
|
||||
syMallocFn(rdram, ctx, runtime);
|
||||
}
|
||||
|
||||
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);
|
||||
PS2Runtime::RecompiledFunction sdrInit = runtime->lookupFunction(kSdrInitAddr);
|
||||
ctx->pc = kSdrInitAddr;
|
||||
sdrInit(rdram, ctx, runtime);
|
||||
{
|
||||
PS2Runtime::GuestExecutionScope guestExecution(runtime);
|
||||
sdrInit(rdram, ctx, runtime);
|
||||
}
|
||||
|
||||
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)
|
||||
return;
|
||||
@@ -28,8 +28,11 @@ static void waitWhileSuspended(const std::shared_ptr<ThreadInfo> &info)
|
||||
info->status = THS_SUSPEND;
|
||||
info->waitType = TSW_NONE;
|
||||
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())
|
||||
{
|
||||
throw ThreadExitException();
|
||||
@@ -165,9 +168,19 @@ static void ensureAlarmWorkerRunning()
|
||||
|
||||
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{};
|
||||
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, 4, static_cast<uint32_t>(readyAlarm->id));
|
||||
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);
|
||||
func(rdram, &tmp, runtime);
|
||||
{
|
||||
PS2Runtime::GuestExecutionScope guestExecution(runtime);
|
||||
func(rdram, &tmp, runtime);
|
||||
}
|
||||
++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
|
||||
{
|
||||
uint32_t sid = 0;
|
||||
|
||||
@@ -307,12 +307,15 @@ void WaitSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
}
|
||||
|
||||
sema->waiters++;
|
||||
sema->cv.wait(lock, [&]()
|
||||
{
|
||||
bool forced = info ? info->forceRelease.load() : false;
|
||||
bool terminated = info ? info->terminated.load() : false;
|
||||
return sema->count > 0 || sema->deleted || forced || terminated; //
|
||||
});
|
||||
{
|
||||
PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime);
|
||||
sema->cv.wait(lock, [&]()
|
||||
{
|
||||
bool forced = info ? info->forceRelease.load() : false;
|
||||
bool terminated = info ? info->terminated.load() : false;
|
||||
return sema->count > 0 || sema->deleted || forced || terminated; //
|
||||
});
|
||||
}
|
||||
sema->waiters--;
|
||||
if (sema->deleted)
|
||||
{
|
||||
@@ -354,7 +357,7 @@ void WaitSema(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
<< std::endl;
|
||||
}
|
||||
lock.unlock();
|
||||
waitWhileSuspended(info);
|
||||
waitWhileSuspended(info, runtime);
|
||||
setReturnS32(ctx, ret);
|
||||
}
|
||||
|
||||
@@ -627,7 +630,10 @@ void WaitEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
}
|
||||
|
||||
info->waiters++;
|
||||
info->cv.wait(lock, satisfied);
|
||||
{
|
||||
PS2Runtime::GuestExecutionReleaseScope releaseGuestExecution(runtime);
|
||||
info->cv.wait(lock, satisfied);
|
||||
}
|
||||
info->waiters--;
|
||||
|
||||
if (tInfo)
|
||||
@@ -689,7 +695,7 @@ void WaitEventFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
waitWhileSuspended(tInfo);
|
||||
waitWhileSuspended(tInfo, runtime);
|
||||
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));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!rdram || !runtime)
|
||||
@@ -87,9 +107,8 @@ static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, ui
|
||||
}
|
||||
handlers.push_back(info);
|
||||
}
|
||||
std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b) {
|
||||
return a.order < b.order;
|
||||
});
|
||||
std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b)
|
||||
{ return a.order < b.order; });
|
||||
}
|
||||
|
||||
for (const IrqHandlerInfo &info : handlers)
|
||||
@@ -102,9 +121,8 @@ static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, ui
|
||||
try
|
||||
{
|
||||
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, 29, sp);
|
||||
SET_GPR_U32(&irqCtx, 29, getAsyncHandlerStackTop(runtime));
|
||||
SET_GPR_U32(&irqCtx, 31, 0u);
|
||||
SET_GPR_U32(&irqCtx, 4, cause);
|
||||
SET_GPR_U32(&irqCtx, 5, info.arg);
|
||||
@@ -119,6 +137,8 @@ static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, ui
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -171,9 +191,8 @@ void dispatchDmacHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t
|
||||
}
|
||||
handlers.push_back(info);
|
||||
}
|
||||
std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b) {
|
||||
return a.order < b.order;
|
||||
});
|
||||
std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b)
|
||||
{ return a.order < b.order; });
|
||||
}
|
||||
|
||||
for (const IrqHandlerInfo &info : handlers)
|
||||
@@ -186,9 +205,8 @@ void dispatchDmacHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t
|
||||
try
|
||||
{
|
||||
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, 29, sp);
|
||||
SET_GPR_U32(&irqCtx, 29, getAsyncHandlerStackTop(runtime));
|
||||
SET_GPR_U32(&irqCtx, 31, 0u);
|
||||
SET_GPR_U32(&irqCtx, 4, cause);
|
||||
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);
|
||||
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;
|
||||
}
|
||||
@@ -276,7 +295,8 @@ static void interruptWorkerMain(uint8_t *rdram, PS2Runtime *runtime)
|
||||
|
||||
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);
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(500));
|
||||
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()
|
||||
{
|
||||
g_irq_worker_stop.store(true, std::memory_order_release);
|
||||
g_irq_worker_cv.notify_all();
|
||||
std::unique_lock<std::mutex> lock(g_irq_worker_mutex);
|
||||
g_irq_worker_cv.wait_for(lock, std::chrono::milliseconds(500), []() {
|
||||
return !g_irq_worker_running.load(std::memory_order_acquire);
|
||||
});
|
||||
g_irq_worker_cv.wait_for(lock, std::chrono::milliseconds(500), []()
|
||||
{ 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);
|
||||
std::unique_lock<std::mutex> lock(g_vsync_flag_mutex);
|
||||
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)
|
||||
|
||||
@@ -372,7 +372,7 @@ void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
throw ThreadExitException();
|
||||
}
|
||||
|
||||
waitWhileSuspended(info);
|
||||
waitWhileSuspended(info, runtime);
|
||||
|
||||
const uint32_t pc = threadCtx->pc;
|
||||
if (pc == 0u)
|
||||
@@ -445,7 +445,10 @@ void StartThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
<< std::hex << pc << std::dec << std::endl;
|
||||
throw ThreadExitException();
|
||||
}
|
||||
step(rdram, threadCtx, runtime);
|
||||
{
|
||||
PS2Runtime::GuestExecutionScope guestExecution(runtime);
|
||||
step(rdram, threadCtx, runtime);
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
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);
|
||||
@@ -641,8 +647,11 @@ void SuspendThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
if (tid == g_currentThreadId)
|
||||
{
|
||||
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())
|
||||
{
|
||||
throw ThreadExitException();
|
||||
@@ -784,8 +793,11 @@ void SleepThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
info->waitId = 0;
|
||||
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())
|
||||
{
|
||||
@@ -820,7 +832,7 @@ void SleepThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
waitWhileSuspended(info);
|
||||
waitWhileSuspended(info, runtime);
|
||||
setReturnS32(ctx, ret);
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
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;
|
||||
func.name = "backward_branch";
|
||||
func.start = 0x1100;
|
||||
@@ -861,10 +861,17 @@ void register_code_generator_tests()
|
||||
|
||||
CodeGenerator gen({}, {});
|
||||
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("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) {
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
#include "MiniTest.h"
|
||||
#include "ps2_memory.h"
|
||||
#include "ps2_runtime.h"
|
||||
#include "ps2_stubs.h"
|
||||
#include "ps2_syscalls.h"
|
||||
#include "ps2_gs_gpu.h"
|
||||
#include "ps2_gs_psmt4.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using namespace ps2_syscalls;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::atomic<uint32_t> g_gsSyncCallbackHits{0u};
|
||||
std::atomic<uint32_t> g_gsSyncCallbackLastTick{0u};
|
||||
|
||||
void setRegU32(R5900Context &ctx, int reg, uint32_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));
|
||||
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()
|
||||
@@ -133,6 +167,71 @@ void register_ps2_gs_tests()
|
||||
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)
|
||||
{
|
||||
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");
|
||||
});
|
||||
|
||||
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)
|
||||
{
|
||||
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[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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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(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(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)
|
||||
@@ -792,6 +808,50 @@ void register_ps2_memory_tests()
|
||||
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)
|
||||
{
|
||||
PS2Memory mem;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "ps2_runtime.h"
|
||||
#include "ps2_memory.h"
|
||||
#include "ps2_syscalls.h"
|
||||
#include "ps2_stubs.h"
|
||||
#include "ps2_gs_gpu.h"
|
||||
#include "ps2_runtime_macros.h"
|
||||
|
||||
@@ -100,6 +101,116 @@ namespace
|
||||
|
||||
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()
|
||||
@@ -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(), ®isterCtx, &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)
|
||||
{
|
||||
R5900Decoder decoder;
|
||||
|
||||
@@ -251,6 +251,73 @@ void register_ps2_runtime_interrupt_tests()
|
||||
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)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
|
||||
Reference in New Issue
Block a user