From cad1ca0bb554b1f6c95665a0ae2e93c42298b623 Mon Sep 17 00:00:00 2001 From: Ranieri Date: Wed, 18 Mar 2026 19:14:40 -0300 Subject: [PATCH] Feature/added execution guess gs psmt (#104) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- ps2xRecomp/src/lib/code_generator.cpp | 22 +- .../tools/ghidra/ExportPS2Functions.java | 2 +- ps2xRuntime/include/ps2_gs_gpu.h | 5 + ps2xRuntime/include/ps2_gs_psmt4.h | 84 +- ps2xRuntime/include/ps2_gs_psmt8.h | 256 ++++++ ps2xRuntime/include/ps2_gs_rasterizer.h | 5 +- ps2xRuntime/include/ps2_memory.h | 36 +- ps2xRuntime/include/ps2_runtime.h | 45 ++ ps2xRuntime/include/ps2_stubs.h | 2 +- ps2xRuntime/include/ps2_syscalls.h | 5 + ps2xRuntime/src/lib/ps2_gif_arbiter.cpp | 88 ++- ps2xRuntime/src/lib/ps2_gs_gpu.cpp | 742 +++++++++++++++--- ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp | 587 +++++++++++--- ps2xRuntime/src/lib/ps2_memory.cpp | 27 +- ps2xRuntime/src/lib/ps2_runtime.cpp | 477 +++++++++-- ps2xRuntime/src/lib/ps2_syscalls.cpp | 46 +- ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp | 59 +- ps2xRuntime/src/lib/ps2_vu1.cpp | 492 ++++++++---- .../lib/stubs/helpers/ps2_stubs_helpers.inl | 233 +++++- ps2xRuntime/src/lib/stubs/ps2_stubs_gs.inl | 287 ++++--- ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl | 5 +- .../lib/stubs/ps2_stubs_residentEvilCV.inl | 10 +- .../helpers/ps2_syscalls_helpers_runtime.inl | 26 +- .../helpers/ps2_syscalls_helpers_state.inl | 23 + .../src/lib/syscalls/ps2_syscalls_flags.inl | 24 +- .../lib/syscalls/ps2_syscalls_interrupt.inl | 75 +- .../src/lib/syscalls/ps2_syscalls_thread.inl | 30 +- ps2xTest/src/code_generator_tests.cpp | 13 +- ps2xTest/src/ps2_gs_tests.cpp | 471 +++++++++++ ps2xTest/src/ps2_memory_tests.cpp | 60 ++ ps2xTest/src/ps2_runtime_expansion_tests.cpp | 323 ++++++++ ps2xTest/src/ps2_runtime_interrupt_tests.cpp | 67 ++ 32 files changed, 3942 insertions(+), 685 deletions(-) create mode 100644 ps2xRuntime/include/ps2_gs_psmt8.h diff --git a/ps2xRecomp/src/lib/code_generator.cpp b/ps2xRecomp/src/lib/code_generator.cpp index 3fc9148..666a59e 100644 --- a/ps2xRecomp/src/lib/code_generator.cpp +++ b/ps2xRecomp/src/lib/code_generator.cpp @@ -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 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 { diff --git a/ps2xRecomp/tools/ghidra/ExportPS2Functions.java b/ps2xRecomp/tools/ghidra/ExportPS2Functions.java index af9b980..4f2987b 100644 --- a/ps2xRecomp/tools/ghidra/ExportPS2Functions.java +++ b/ps2xRecomp/tools/ghidra/ExportPS2Functions.java @@ -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 } } diff --git a/ps2xRuntime/include/ps2_gs_gpu.h b/ps2xRuntime/include/ps2_gs_gpu.h index 1623565..6eeef87 100644 --- a/ps2xRuntime/include/ps2_gs_gpu.h +++ b/ps2xRuntime/include/ps2_gs_gpu.h @@ -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(); diff --git a/ps2xRuntime/include/ps2_gs_psmt4.h b/ps2xRuntime/include/ps2_gs_psmt4.h index e0ccd4f..1e5ce35 100644 --- a/ps2xRuntime/include/ps2_gs_psmt4.h +++ b/ps2xRuntime/include/ps2_gs_psmt4.h @@ -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); + } } diff --git a/ps2xRuntime/include/ps2_gs_psmt8.h b/ps2xRuntime/include/ps2_gs_psmt8.h new file mode 100644 index 0000000..994b67b --- /dev/null +++ b/ps2xRuntime/include/ps2_gs_psmt8.h @@ -0,0 +1,256 @@ +#ifndef PS2_GS_PSMT8_H +#define PS2_GS_PSMT8_H + +#include +#include + +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 &index32X() + { + static const std::array table = [] + { + std::array 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 &index32Y() + { + static const std::array table = [] + { + std::array 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 &columnTable8() + { + static const std::array table = [] + { + std::array 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(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(index32Y()[localBlock]) * 2048u + + static_cast(index32X()[localBlock]) * 32u; + const uint32_t column = columnTable8()[(y & 0xFu) * 16u + (x & 0xFu)]; + return (page << 13) + pageOffset + blockBase + column; + } + +} + +#endif diff --git a/ps2xRuntime/include/ps2_gs_rasterizer.h b/ps2xRuntime/include/ps2_gs_rasterizer.h index 5384610..76afe07 100644 --- a/ps2xRuntime/include/ps2_gs_rasterizer.h +++ b/ps2xRuntime/include/ps2_gs_rasterizer.h @@ -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); diff --git a/ps2xRuntime/include/ps2_memory.h b/ps2xRuntime/include/ps2_memory.h index c95187b..d8cd814 100644 --- a/ps2xRuntime/include/ps2_memory.h +++ b/ps2xRuntime/include/ps2_memory.h @@ -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 setVu1MscalCallback(Vu1MscalCallback cb) { m_vu1MscalCallback = std::move(cb); } + using Vu1MscntCallback = std::function; + 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; diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h index 4480b64..a3bba0a 100644 --- a/ps2xRuntime/include/ps2_runtime.h +++ b/ps2xRuntime/include/ps2_runtime.h @@ -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 m_guestExecutionWaiters{0u}; mutable std::mutex m_guestHeapMutex; + mutable std::mutex m_asyncCallbackStackMutex; std::vector 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 m_functionTable; std::atomic m_stopRequested{false}; diff --git a/ps2xRuntime/include/ps2_stubs.h b/ps2xRuntime/include/ps2_stubs.h index 43e35cd..b9857e8 100644 --- a/ps2xRuntime/include/ps2_stubs.h +++ b/ps2xRuntime/include/ps2_stubs.h @@ -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); diff --git a/ps2xRuntime/include/ps2_syscalls.h b/ps2xRuntime/include/ps2_syscalls.h index f6122ae..45f036e 100644 --- a/ps2xRuntime/include/ps2_syscalls.h +++ b/ps2xRuntime/include/ps2_syscalls.h @@ -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); } diff --git a/ps2xRuntime/src/lib/ps2_gif_arbiter.cpp b/ps2xRuntime/src/lib/ps2_gif_arbiter.cpp index ca983d9..12f169a 100644 --- a/ps2xRuntime/src/lib/ps2_gif_arbiter.cpp +++ b/ps2xRuntime/src/lib/ps2_gif_arbiter.cpp @@ -1,6 +1,29 @@ #include "ps2_gif_arbiter.h" #include +#include #include +#include + +namespace +{ + std::atomic s_debugGifArbiterSubmitCount{0}; + std::atomic 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(tagLo & 0x7FFFu); + const uint8_t flg = static_cast((tagLo >> 58) & 0x3u); + uint32_t nreg = static_cast((tagLo >> 60) & 0xFu); + if (nreg == 0u) + nreg = 16u; + std::cout << "[gif:submit] idx=" << debugIndex + << " path=" << pathName(pathId) + << " size=" << sizeBytes + << " nloop=" << nloop + << " flg=" << static_cast(flg) + << " nreg=" << nreg + << " directhl=" << static_cast(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(tagLo & 0x7FFFu); + const uint8_t flg = static_cast((tagLo >> 58) & 0x3u); + uint32_t nreg = static_cast((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(flg) + << " nreg=" << nreg + << " directhl=" << static_cast(pkt.path2DirectHl ? 1u : 0u) + << " path3image=" << static_cast(pkt.path3Image ? 1u : 0u) + << std::endl; + } m_processFn(pkt.data.data(), static_cast(pkt.data.size())); + } } m_queue.clear(); } diff --git a/ps2xRuntime/src/lib/ps2_gs_gpu.cpp b/ps2xRuntime/src/lib/ps2_gs_gpu.cpp index 920d23f..56a2fdb 100644 --- a/ps2xRuntime/src/lib/ps2_gs_gpu.cpp +++ b/ps2xRuntime/src/lib/ps2_gs_gpu.cpp @@ -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 #include #include #include #include +#include +#include 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 s_debugGifPacketCount{0}; + std::atomic s_debugGsRegisterCount{0}; + std::atomic s_debugGsPackedVertexCount{0}; + std::atomic s_debugGsVertexKickCount{0}; + std::atomic s_debugCopyRegCount{0}; + std::atomic s_debugTexaWriteCount{0}; + std::atomic s_debugCvFontUploadCount{0}; + std::atomic 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(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(vram[off + 0u]) | + (static_cast(vram[off + 1u]) << 8) | + (static_cast(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((nibbleAddr & 1u) << 2); + return static_cast((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(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(value & 0xFFu); + vram[off + 1u] = static_cast((value >> 8) & 0xFFu); + vram[off + 2u] = static_cast((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(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(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(value & 0x0Fu); + uint8_t &dst = vram[byteOff]; + if ((nibbleAddr & 1u) != 0u) + dst = static_cast((dst & 0x0Fu) | (nibble << 4)); + else + dst = static_cast((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 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(tagLo & 0x7FFFu); + const uint8_t flg = static_cast((tagLo >> 58) & 0x3u); + uint32_t nreg = static_cast((tagLo >> 60) & 0xFu); + if (nreg == 0u) + nreg = 16u; + std::cout << "[gs:gif] idx=" << packetIndex + << " size=" << sizeBytes + << " nloop=" << nloop + << " flg=" << static_cast(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(tagLo & 0x7FFF); uint8_t flg = static_cast((tagLo >> 58) & 0x3); uint32_t nreg = static_cast((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((hi >> 4) & 0xFFFFFF); uint8_t f = static_cast((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(f) + << " kick=" << static_cast(!adk ? 1u : 0u) + << " prim=" << static_cast(m_prim.type) + << std::endl; + } GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; vtx.x = static_cast(x) / 16.0f; vtx.y = static_cast(y) / 16.0f; vtx.z = static_cast(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((lo >> 32) & 0xFFFF); uint32_t z = static_cast(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(!adk ? 1u : 0u) + << " prim=" << static_cast(m_prim.type) + << std::endl; + } GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; vtx.x = static_cast(x) / 16.0f; vtx.y = static_cast(y) / 16.0f; vtx.z = static_cast(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(lo & 0xFFFFu) + << " y=" << static_cast((lo >> 32) & 0xFFFFu) + << " kick=0" + << " prim=" << static_cast(m_prim.type) + << std::endl; + } GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; vtx.x = static_cast(lo & 0xFFFF) / 16.0f; vtx.y = static_cast((lo >> 32) & 0xFFFF) / 16.0f; vtx.z = static_cast((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((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(lo & 0xFFFFu) + << " y=" << static_cast((lo >> 32) & 0xFFFFu) + << " kick=0" + << " prim=" << static_cast(m_prim.type) + << std::endl; + } GSVertex &vtx = m_vtxQueue[m_vtxCount % kMaxVerts]; vtx.x = static_cast(lo & 0xFFFF) / 16.0f; vtx.y = static_cast((lo >> 32) & 0xFFFF) / 16.0f; vtx.z = static_cast(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(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(regAddr) + << " value=0x" << value + << std::dec + << " primCtxt=" << static_cast(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((value >> 24) & 0xFF); uint32_t qBits = static_cast((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((value >> 16) & 0xFFFF) / 16.0f; vtx.z = static_cast((value >> 32) & 0xFFFFFF); vtx.fog = static_cast((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(value & 0xFFFF) / 16.0f; vtx.y = static_cast((value >> 16) & 0xFFFF) / 16.0f; vtx.z = static_cast((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(sbw) * 64u * srcBpp; - uint32_t dstStride = static_cast(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(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(value & 0xFFFFFFFF); + uint32_t id = static_cast(value & 0xFFFFFFFF); uint32_t mask = static_cast(value >> 32); - uint32_t lo = static_cast(m_privRegs->siglblid & 0xFFFFFFFF); + uint32_t lo = static_cast(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(value & 0xFFFFFFFF); + uint32_t id = static_cast(value & 0xFFFFFFFF); uint32_t mask = static_cast(value >> 32); - uint32_t hi = static_cast(m_privRegs->siglblid >> 32); + uint32_t hi = static_cast(m_privRegs->siglblid >> 32); hi = (hi & ~mask) | (id & mask); m_privRegs->siglblid = (static_cast(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(sbw) + << " dbw=" << static_cast(dbw) + << " spsm=0x" << std::hex << static_cast(spsm) + << " dpsm=0x" << static_cast(dpsm) << std::dec + << " ss=(" << ssax << "," << ssay << ")" + << " ds=(" << dsax << "," << dsay << ")" + << " rr=(" << rrw << "," << rrh << ")" + << " formatAware=" << (formatAware ? 1 : 0) << std::endl; + } + + if (formatAware) + { + std::vector staged; + staged.reserve(static_cast(rrw) * static_cast(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(sbw) * 64u * srcBpp; + const uint32_t dstStride = static_cast(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(rrh) - 1; row >= 0; --row) + { + const uint32_t srcOff = srcBase + (ssay + static_cast(row)) * srcStride + ssax * srcBpp; + const uint32_t dstOff = dstBase + (dsay + static_cast(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(drawing ? 1u : 0u) + << " prim=" << static_cast(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(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(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((nibbleAddr & 1u) << 2); + uint8_t &b = m_vram[byteOff]; + b = static_cast((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((nibbleAddr & 1u) << 2); - uint8_t &b = m_vram[byteOff]; - b = static_cast((b & (0xF0u >> shift)) | ((nibble & 0x0Fu) << shift)); - } + writeT4Nibble(static_cast(srcLo), srcByte, srcLo, srcHi); + if ((xBefore + 1u) < rrw && m_hwregY < rrh) + { + writeT4Nibble(static_cast(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(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(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(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); diff --git a/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp b/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp index 71c39c5..f6658ee 100644 --- a/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp +++ b/ps2xRuntime/src/lib/ps2_gs_rasterizer.cpp @@ -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 #include #include #include #include +#include +#include +#include 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(((r >> 3) & 0x1Fu) | + (((g >> 3) & 0x1Fu) << 5) | + (((b >> 3) & 0x1Fu) << 10) | + ((a >= 0x40u) ? 0x8000u : 0u)); + } + + std::atomic s_debugPrimitiveCount{0}; + std::atomic s_debugPixelCount{0}; + std::atomic s_debugContext1PrimitiveCount{0}; + std::atomic s_debugFbp150PixelCount{0}; + + bool passesAlphaTest(uint64_t testReg, uint8_t alpha) + { + if ((testReg & 0x1u) == 0u) + return true; + + const uint8_t atst = static_cast((testReg >> 1) & 0x7u); + const uint8_t aref = static_cast((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((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(index); + + if (sourcePsm == GS_PSM_T4) + { + clutIndex = (static_cast(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(gs->m_prim.type) + << " tme=" << static_cast(gs->m_prim.tme) + << " abe=" << static_cast(gs->m_prim.abe) + << " fst=" << static_cast(gs->m_prim.fst) + << " ctxt=" << static_cast(gs->m_prim.ctxt) + << " fbp=" << ctx.frame.fbp + << " fbw=" << ctx.frame.fbw + << " psm=0x" << std::hex << static_cast(ctx.frame.psm) << std::dec + << " tex0=(" + << "tbp0=" << ctx.tex0.tbp0 + << " tbw=" << static_cast(ctx.tex0.tbw) + << " psm=0x" << std::hex << static_cast(ctx.tex0.psm) << std::dec + << " tw=" << static_cast(ctx.tex0.tw) + << " th=" << static_cast(ctx.tex0.th) + << " tcc=" << static_cast(ctx.tex0.tcc) + << " tfx=" << static_cast(ctx.tex0.tfx) + << " cbp=" << ctx.tex0.cbp + << " cpsm=0x" << std::hex << static_cast(ctx.tex0.cpsm) << std::dec + << " csm=" << static_cast(ctx.tex0.csm) + << " csa=" << static_cast(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(gs->m_vtxQueue[0].r) << "," + << static_cast(gs->m_vtxQueue[0].g) << "," + << static_cast(gs->m_vtxQueue[0].b) << "," + << static_cast(gs->m_vtxQueue[0].a) << ")" + << " rgba1=(" << static_cast(gs->m_vtxQueue[1].r) << "," + << static_cast(gs->m_vtxQueue[1].g) << "," + << static_cast(gs->m_vtxQueue[1].b) << "," + << static_cast(gs->m_vtxQueue[1].a) << ")" + << " rgba2=(" << static_cast(gs->m_vtxQueue[2].r) << "," + << static_cast(gs->m_vtxQueue[2].g) << "," + << static_cast(gs->m_vtxQueue[2].b) << "," + << static_cast(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(gs->m_prim.type) + << " tme=" << static_cast(gs->m_prim.tme) + << " abe=" << static_cast(gs->m_prim.abe) + << " fst=" << static_cast(gs->m_prim.fst) + << " ctxt=" << static_cast(gs->m_prim.ctxt) + << " fbp=" << ctx.frame.fbp + << " fbw=" << ctx.frame.fbw + << " psm=0x" << std::hex << static_cast(ctx.frame.psm) << std::dec + << " tex0=(" + << "tbp0=" << ctx.tex0.tbp0 + << " tbw=" << static_cast(ctx.tex0.tbw) + << " psm=0x" << std::hex << static_cast(ctx.tex0.psm) << std::dec + << " tcc=" << static_cast(ctx.tex0.tcc) + << " tfx=" << static_cast(ctx.tex0.tfx) + << " cbp=" << ctx.tex0.cbp + << " cpsm=0x" << std::hex << static_cast(ctx.tex0.cpsm) << std::dec + << " csm=" << static_cast(ctx.tex0.csm) + << " csa=" << static_cast(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(y) * stride + static_cast(x) * 4u; - if (off + 4 > gs->m_vramSize) + if (stride == 0) return; + const uint32_t bytesPerPixel = std::max(1u, bitsPerPixel(ctx.frame.psm) / 8u); + uint32_t off = fbBase + static_cast(y) * stride + static_cast(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(r) << "," + << static_cast(g) << "," + << static_cast(b) << "," + << static_cast(a) << ")" + << " fbp=" << ctx.frame.fbp + << " fbw=" << ctx.frame.fbw + << " psm=0x" << std::hex << static_cast(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(r) << "," + << static_cast(g) << "," + << static_cast(b) << "," + << static_cast(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((alphaReg >> 32) & 0xFF); + uint8_t fix = static_cast((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(r) - | (static_cast(g) << 8) - | (static_cast(b) << 16) - | (static_cast(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((pixel & ~mask) | (existing & mask)); + } + std::memcpy(gs->m_vram + off, &pixel, 2); + return; + } + + uint32_t pixel = static_cast(r) | (static_cast(g) << 8) | (static_cast(b) << 16) | (static_cast(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(texV) * stride + static_cast(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(texV) * stride + static_cast(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(texU), static_cast(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(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(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(s * invQ * static_cast(texW)); texV = static_cast(t * invQ * static_cast(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(idx), tex.cbp, tex.cpsm, tex.csa); + return lookupCLUT(gs, static_cast(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(tex.tbw) * 64u; - uint32_t off = base + static_cast(texV) * stride + static_cast(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(texU), static_cast(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(v1.x) - ofx; int y1 = static_cast(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(texW); - v0f = v0.t * static_cast(texH); - u1f = v1.s * static_cast(texW); - v1f = v1.t * static_cast(texH); + const float q0 = fabsQ(v0.q); + const float q1 = fabsQ(v1.q); + u0f = (v0.s / q0) * static_cast(texW); + v0f = (v0.t / q0) * static_cast(texH); + u1f = (v1.s / q1) * static_cast(texW); + v1f = (v1.t / q1) * static_cast(texH); } float spriteW = static_cast(x1 - x0); float spriteH = static_cast(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(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(idx), tex.cbp, tex.cpsm, tex.csa); + sampleIndexValue = readTexelPSMT4(gs, tex.tbp0, tex.tbw, tu, tv); + texel = lookupCLUT(gs, static_cast(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(tv) * stride + static_cast(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(tu), static_cast(tv)); + sampleIndexValue = (off < gs->m_vramSize) ? gs->m_vram[off] : 0u; + texel = lookupCLUT(gs, static_cast(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((texel >> 16) & 0xFF); uint8_t ta = static_cast((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(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(v0.u * w0 + v1.u * w1 + v2.u * w2); iv = static_cast(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(texel & 0xFF); uint8_t tg = static_cast((texel >> 8) & 0xFF); uint8_t tb = static_cast((texel >> 16) & 0xFF); uint8_t ta = static_cast((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; } } diff --git a/ps2xRuntime/src/lib/ps2_memory.cpp b/ps2xRuntime/src/lib/ps2_memory.cpp index 4f64b62..6712167 100644 --- a/ps2xRuntime/src/lib/ps2_memory.cpp +++ b/ps2xRuntime/src/lib/ps2_memory.cpp @@ -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(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(p.qwc) * 16ull; + uint32_t sizeBytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast(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(p.qwc) * 16ull; - uint32_t sizeBytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast(bytes64); if (srcPhys + sizeBytes > PS2_RAM_SIZE) sizeBytes = PS2_RAM_SIZE - srcPhys; if (sizeBytes > 0) diff --git a/ps2xRuntime/src/lib/ps2_runtime.cpp b/ps2xRuntime/src/lib/ps2_runtime.cpp index c08d821..3b0ed96 100644 --- a/ps2xRuntime/src/lib/ps2_runtime.cpp +++ b/ps2xRuntime/src/lib/ps2_runtime.cpp @@ -92,6 +92,7 @@ namespace }; thread_local DispatchHistory g_dispatchHistory; + thread_local std::unordered_map 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 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 &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(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((r << 3) | (r >> 2)); + dst[x * 4u + 1u] = static_cast((g << 3) | (g >> 2)); + dst[x * 4u + 2u] = static_cast((b << 3) | (b >> 2)); + dst[x * 4u + 3u] = 255u; + } + } + return true; + } + + return false; + }; + + auto analyzeScratch = [&](const std::vector &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(r) | + (static_cast(g) << 8) | + (static_cast(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(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((r << 3) | (r >> 2)); - dst[x * 4 + 1] = static_cast((g << 3) | (g >> 2)); - dst[x * 4 + 2] = static_cast((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 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 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::max(); + static uint32_t s_lastLoggedNonBlack = std::numeric_limits::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(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(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 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 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::max(); @@ -1533,7 +1800,10 @@ void PS2Runtime::dispatchLoop(uint8_t *rdram, R5900Context *ctx) const uint32_t dispatchedPc = pc; const uint32_t dispatchedRa = static_cast(_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(); diff --git a/ps2xRuntime/src/lib/ps2_syscalls.cpp b/ps2xRuntime/src/lib/ps2_syscalls.cpp index 04fe125..43cdf8b 100644 --- a/ps2xRuntime/src/lib/ps2_syscalls.cpp +++ b/ps2xRuntime/src/lib/ps2_syscalls.cpp @@ -83,6 +83,18 @@ namespace ps2_syscalls case 0x17: DisableDmac(rdram, ctx, runtime); return true; + case static_cast(-0x1A): + iEnableIntc(rdram, ctx, runtime); + return true; + case static_cast(-0x1B): + iDisableIntc(rdram, ctx, runtime); + return true; + case static_cast(-0x1C): + iEnableDmac(rdram, ctx, runtime); + return true; + case static_cast(-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(-0x2A): ChangeThreadPriority(rdram, ctx, runtime); return true; + case static_cast(-0x2A): + iChangeThreadPriority(rdram, ctx, runtime); + return true; case 0x2B: - case static_cast(-0x2C): RotateThreadReadyQueue(rdram, ctx, runtime); return true; + case static_cast(-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(-0x31): ReferThreadStatus(rdram, ctx, runtime); return true; + case static_cast(-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(-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(-0x70): GsGetIMR(rdram, ctx, runtime); return true; + case static_cast(-0x70): + iGsGetIMR(rdram, ctx, runtime); + return true; case 0x71: - case static_cast(-0x71): GsPutIMR(rdram, ctx, runtime); return true; + case static_cast(-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> semas; { std::lock_guard lock(g_sema_map_mutex); @@ -407,4 +427,14 @@ namespace ps2_syscalls g_syscall_overrides.clear(); } } + + void joinAllGuestHostThreads() + { + joinAllHostThreads(); + } + + void detachAllGuestHostThreads() + { + detachAllHostThreads(); + } } diff --git a/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp b/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp index 5e97254..115c3ff 100644 --- a/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp +++ b/ps2xRuntime/src/lib/ps2_vif1_interpreter.cpp @@ -1,7 +1,9 @@ // Based on Blackline Interactive implementation #include "ps2_memory.h" +#include #include #include +#include enum VIFCmd : uint8_t { @@ -27,6 +29,12 @@ enum VIFCmd : uint8_t VIF_DIRECTHL = 0x51, }; +namespace +{ + std::atomic s_debugVu1KickCount{0}; + std::atomic 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(opcode) + << " imm=0x" << imm + << std::dec + << " num=" << static_cast(num) + << " irq=" << static_cast(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(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; diff --git a/ps2xRuntime/src/lib/ps2_vu1.cpp b/ps2xRuntime/src/lib/ps2_vu1.cpp index 5a5af6b..ed08520 100644 --- a/ps2xRuntime/src/lib/ps2_vu1.cpp +++ b/ps2xRuntime/src/lib/ps2_vu1.cpp @@ -2,25 +2,33 @@ #include "ps2_gs_gpu.h" #include "ps2_gif_arbiter.h" #include "ps2_memory.h" +#include #include #include #include +#include #include #include +namespace +{ + std::atomic 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::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((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: diff --git a/ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl b/ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl index 642cc9f..39165dc 100644 --- a/ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl +++ b/ps2xRuntime/src/lib/stubs/helpers/ps2_stubs_helpers.inl @@ -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 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(dh & 0x07FF) << 44); } + static uint64_t makeFrame(uint32_t fbp, uint32_t fbw, uint32_t psm, uint32_t fbmsk) + { + return (static_cast(fbp & 0x1FFu) << 0) | + (static_cast(fbw & 0x3Fu) << 16) | + (static_cast(psm & 0x3Fu) << 24) | + (static_cast(fbmsk) << 32); + } + + static uint64_t makeZbuf(uint32_t zbp, uint32_t psm, bool zmsk) + { + return (static_cast(zbp & 0x1FFu) << 0) | + (static_cast(psm & 0xFu) << 24) | + (static_cast(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(static_cast(offY) & 0xFFFFu) << 36) | + (static_cast(static_cast(offX) & 0xFFFFu) << 4); + } + + static uint64_t makeScissor(int32_t width, int32_t height) + { + return (static_cast(0u) << 0) | + (static_cast(static_cast(width - 1) & 0x7FFu) << 16) | + (static_cast(0u) << 32) | + (static_cast(static_cast(height - 1) & 0x7FFu) << 48); + } + + static uint64_t makeTest(uint32_t ztest) + { + if ((ztest & 0x3u) == 0u) + { + return 0x30000ULL; + } + return (static_cast(ztest & 0x3u) << 17) | 0x10000ULL; + } + + static uint64_t makeGiftagAplusD(uint32_t nloop) + { + return (static_cast(nloop & 0x7FFFu) << 0) | + (static_cast(1u) << 15) | + (static_cast(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(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) diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_gs.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_gs.inl index b553dc1..e7256c6 100644 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_gs.inl +++ b/ps2xRuntime/src/lib/stubs/ps2_stubs_gs.inl @@ -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 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 lock(g_gs_sync_v_mutex); + if (tick <= g_gs_sync_v_base_tick) + { + return 0; + } + + return static_cast((tick - g_gs_sync_v_base_tick - 1u) & 1u); +} + void resetGsSyncVCallbackState() { - std::lock_guard 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 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 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 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(tick)); + SET_GPR_U32(&callbackCtx, 4, static_cast(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(img.x); uint32_t dsay = static_cast(img.y); + // Full messy uint64_t *q = reinterpret_cast(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(img.vram_addr) * 2048u) / 256u; uint64_t bitbltbuf = (static_cast(sbp & 0x3FFFu) << 0) | - (static_cast(fbw & 0x3Fu) << 16) | - (static_cast(img.psm & 0x3Fu) << 24) | - (static_cast(0u) << 32) | - (static_cast(1u) << 48) | - (static_cast(0u) << 56); + (static_cast(fbw & 0x3Fu) << 16) | + (static_cast(img.psm & 0x3Fu) << 24) | + (static_cast(0u) << 32) | + (static_cast(1u) << 48) | + (static_cast(0u) << 56); uint64_t trxpos = (static_cast(img.x & 0x7FFu) << 0) | (static_cast(img.y & 0x7FFu) << 16) | (static_cast(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(omode & 0xFF); g_gparam.ffmode = static_cast(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(1u, (w + 63u) / 64u); + const uint64_t pmode = makePmode(1u, 1u, 0u, 0u, 0u, 0x80u); + const uint64_t smode2 = + (static_cast(g_gparam.interlace & 0x1u) << 0) | + (static_cast(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(w); + const int32_t drawHeight = static_cast(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(static_cast(getRegU32(ctx, 2) & 0xFFFF)); + GsDrawEnv1Mem env{}; + seedGsDrawEnv1(env, + w, + h, + 0u, + fbw, + psm, + static_cast(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(static_cast(getRegU32(ctx, 6) & 0xFFFF)); + int32_t h = static_cast(static_cast(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(w) + 63u) >> 6) & 0x3FU; + sceGszbufaddr(rdram, ctx, runtime); + int32_t zbuf = static_cast(static_cast(getRegU32(ctx, 2) & 0xFFFF)); + + GsDrawEnv2Mem env{}; + seedGsDrawEnv2(env, + w, + h, + 0u, + fbw, + psm, + static_cast(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(ptr); - - words[0] = 0x1000000000008008ULL; - words[1] = 0x000000000000000EULL; - - words[2] = (static_cast(fbw) << 16) | (static_cast(psm) << 24); - words[3] = 0x4c; - - words[4] = (static_cast(zbuf) & 0xFFFFULL) | (static_cast(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(static_cast(off_y) & 0xFFFF) << 36) | - (static_cast(off_x) & 0xFFFF) * 16ULL; - words[7] = 0x18; - - words[8] = (static_cast(static_cast(h - 1) & 0xFFFF) << 48) | - (static_cast(static_cast(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(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(&db.draw01), 8u); + applyGsRegPairs(runtime, reinterpret_cast(&db.draw02), 8u); + } + else + { + applyGsRegPairs(runtime, reinterpret_cast(&db.draw11), 8u); + applyGsRegPairs(runtime, reinterpret_cast(&db.draw12), 8u); + } + + setReturnS32(ctx, static_cast(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); } diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl index 8c9dcbf..cfaffa0 100644 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl +++ b/ps2xRuntime/src/lib/stubs/ps2_stubs_misc.inl @@ -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); diff --git a/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl b/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl index 83d4597..290ef80 100644 --- a/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl +++ b/ps2xRuntime/src/lib/stubs/ps2_stubs_residentEvilCV.inl @@ -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) { diff --git a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl b/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl index 4878f8c..25bf34b 100644 --- a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl +++ b/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_runtime.inl @@ -17,7 +17,7 @@ static void throwIfTerminated(const std::shared_ptr &info) } } -static void waitWhileSuspended(const std::shared_ptr &info) +static void waitWhileSuspended(const std::shared_ptr &info, PS2Runtime *runtime = nullptr) { if (!info) return; @@ -28,8 +28,11 @@ static void waitWhileSuspended(const std::shared_ptr &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(readyAlarm->id)); setRegU32(&callbackCtx, 5, static_cast(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; } diff --git a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl b/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl index 341c121..6cb51f4 100644 --- a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl +++ b/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl @@ -304,6 +304,29 @@ static void joinAllHostThreads() } } +static void detachAllHostThreads() +{ + std::vector workers; + { + std::lock_guard 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; diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_flags.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_flags.inl index 8df2167..56bb244 100644 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_flags.inl +++ b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_flags.inl @@ -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); } diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl index 13ea66f..cc3b7ea 100644 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl +++ b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_interrupt.inl @@ -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 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 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 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 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) diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl index 78b08f3..0535259 100644 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl +++ b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_thread.inl @@ -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 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 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); } diff --git a/ps2xTest/src/code_generator_tests.cpp b/ps2xTest/src/code_generator_tests.cpp index 689684b..1bc55eb 100644 --- a/ps2xTest/src/code_generator_tests.cpp +++ b/ps2xTest/src/code_generator_tests.cpp @@ -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) { diff --git a/ps2xTest/src/ps2_gs_tests.cpp b/ps2xTest/src/ps2_gs_tests.cpp index 1b07ea8..d5c00f0 100644 --- a/ps2xTest/src/ps2_gs_tests.cpp +++ b/ps2xTest/src/ps2_gs_tests.cpp @@ -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 +#include #include #include +#include #include using namespace ps2_syscalls; namespace { + std::atomic g_gsSyncCallbackHits{0u}; + std::atomic g_gsSyncCallbackLastTick{0u}; + void setRegU32(R5900Context &ctx, int reg, uint32_t value) { ctx.r[reg] = _mm_set_epi64x(0, static_cast(value)); @@ -45,6 +53,32 @@ namespace dst.resize(pos + sizeof(uint64_t)); std::memcpy(dst.data() + pos, &value, sizeof(uint64_t)); } + + template + 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 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 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 vram(PS2_GS_VRAM_SIZE, 0u); @@ -448,5 +576,348 @@ void register_ps2_gs_tests() t.Equals(out[0], static_cast(0x21u), "packed nibble byte 0 should roundtrip"); t.Equals(out[1], static_cast(0x43u), "packed nibble byte 1 should roundtrip"); }); + + tc.Run("GS PSMT4 host-local upload keeps position across split IMAGE packets", [](TestCase &t) + { + std::vector vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + const uint64_t bitblt = + (static_cast(0u) << 0) | + (static_cast(1u) << 16) | + (static_cast(GS_PSM_T4) << 24) | + (static_cast(0u) << 32) | + (static_cast(1u) << 48) | + (static_cast(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(0x10u + i); + } + + std::vector packetA; + appendU64(packetA, makeGifTag(1u, GIF_FMT_IMAGE, 0u, true)); + appendU64(packetA, 0ull); + packetA.insert(packetA.end(), packedSource, packedSource + 16u); + + std::vector 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(packetA.size())); + gs.processGIFPacket(packetB.data(), static_cast(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 vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint32_t kSrcBp = 64u; + constexpr uint32_t kDstBp = 96u; + constexpr uint64_t kUploadBitblt = + (static_cast(0u) << 0) | + (static_cast(2u) << 16) | + (static_cast(GS_PSM_T4) << 24) | + (static_cast(kSrcBp) << 32) | + (static_cast(2u) << 48) | + (static_cast(GS_PSM_T4) << 56); + constexpr uint64_t kCopyBitblt = + (static_cast(kSrcBp) << 0) | + (static_cast(2u) << 16) | + (static_cast(GS_PSM_T4) << 24) | + (static_cast(kDstBp) << 32) | + (static_cast(2u) << 48) | + (static_cast(GS_PSM_T4) << 56); + constexpr uint64_t kCopyPos = + (static_cast(0u) << 0) | + (static_cast(0u) << 16) | + (static_cast(32u) << 32) | + (static_cast(16u) << 48); + constexpr uint64_t kReadBitblt = + (static_cast(kDstBp) << 0) | + (static_cast(2u) << 16) | + (static_cast(GS_PSM_T4) << 24) | + (static_cast(0u) << 32) | + (static_cast(2u) << 48) | + (static_cast(GS_PSM_T4) << 56); + constexpr uint64_t kReadPos = + (static_cast(32u) << 0) | + (static_cast(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 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(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 vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint32_t kTexTbp = 64u; + constexpr uint32_t kClutCbp = 128u; + constexpr uint32_t kFrameReg = + (0ull << 0) | + (1ull << 16) | + (static_cast(GS_PSM_CT32) << 24); + constexpr uint64_t kTex0 = + (static_cast(kTexTbp) << 0) | + (1ull << 14) | + (static_cast(GS_PSM_T4) << 20) | + (0ull << 26) | + (0ull << 30) | + (1ull << 34) | + (1ull << 35) | + (static_cast(kClutCbp) << 37) | + (static_cast(GS_PSM_CT32) << 51); + constexpr uint64_t kPrim = + static_cast(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((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 vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint64_t kFrame = + (0ull << 0) | + (1ull << 16) | + (static_cast(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(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 vram(PS2_GS_VRAM_SIZE, 0u); + GS gs; + gs.init(vram.data(), static_cast(vram.size()), nullptr); + + constexpr uint64_t kFrame = + (0ull << 0) | + (1ull << 16) | + (static_cast(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(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 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(getRegU32Test(sync0, 2)), 0, "first interlaced sceGsSyncV should report even field"); + + R5900Context sync1{}; + ps2_stubs::sceGsSyncV(rdram.data(), &sync1, &runtime); + t.Equals(static_cast(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(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 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(); + }); }); } diff --git a/ps2xTest/src/ps2_memory_tests.cpp b/ps2xTest/src/ps2_memory_tests.cpp index e275f04..91ce9f6 100644 --- a/ps2xTest/src/ps2_memory_tests.cpp +++ b/ps2xTest/src/ps2_memory_tests.cpp @@ -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(0xC0u + i); + } + + std::vector> 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(1u), "scratchpad alias chain should emit one packet"); + t.Equals(captured[0].size(), static_cast(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(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; diff --git a/ps2xTest/src/ps2_runtime_expansion_tests.cpp b/ps2xTest/src/ps2_runtime_expansion_tests.cpp index 7cdcf05..6632d66 100644 --- a/ps2xTest/src/ps2_runtime_expansion_tests.cpp +++ b/ps2xTest/src/ps2_runtime_expansion_tests.cpp @@ -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 gSerializedGuestActive{0}; + std::atomic gSerializedGuestMaxActive{0}; + std::atomic gCooperativeYieldEntryCount{0}; + std::atomic gCooperativeYieldAllowFirstYield{false}; + std::atomic 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 gAsyncCallbackObservedSp{0u}; + std::atomic 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 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 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 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 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 workerDone{false}; + std::atomic 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 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; diff --git a/ps2xTest/src/ps2_runtime_interrupt_tests.cpp b/ps2xTest/src/ps2_runtime_interrupt_tests.cpp index 79d4193..0dd9c6a 100644 --- a/ps2xTest/src/ps2_runtime_interrupt_tests.cpp +++ b/ps2xTest/src/ps2_runtime_interrupt_tests.cpp @@ -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(-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(-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(-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(-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(eid)); + setRegU32(setEventFlagCtx, 5, 0x6u); + t.IsTrue(callSyscall(static_cast(-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(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(eid)); + DeleteEventFlag(env.rdram.data(), &deleteCtx, &env.runtime); + + cleanupRuntime(env); + }); + tc.Run("WaitEventFlag blocks and wakes when SetEventFlag publishes bits", [](TestCase &t) { notifyRuntimeStop();