diff --git a/core/Decoder/Png/CMakeLists.txt b/core/Decoder/Png/CMakeLists.txt index 2c4d268b3..6a86166c2 100644 --- a/core/Decoder/Png/CMakeLists.txt +++ b/core/Decoder/Png/CMakeLists.txt @@ -16,6 +16,7 @@ function(add_png_decoder target runtimeTarget) if(BUILD_TESTING) add_test_executable(${target}_tests ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/tests/Png.cpp) + target_include_directories(${target}_tests SYSTEM PRIVATE ${PROJECT_SOURCE_DIR}/3rdparty/stb) target_link_libraries(${target}_tests PRIVATE ${target}) configure_windows_unwind(${target}_tests) add_test(NAME ${target} COMMAND ${target}_tests) diff --git a/core/Decoder/Png/include/Decoder/Png.hpp b/core/Decoder/Png/include/Decoder/Png.hpp index 051b5f230..175341762 100644 --- a/core/Decoder/Png/include/Decoder/Png.hpp +++ b/core/Decoder/Png/include/Decoder/Png.hpp @@ -35,9 +35,16 @@ std::optional
ParseHeader(std::span png); std::optional Decode(std::span png); +inline constexpr std::uint8_t FILTER_NONE = 1 << 0; +inline constexpr std::uint8_t FILTER_SUB = 1 << 1; +inline constexpr std::uint8_t FILTER_UP = 1 << 2; +inline constexpr std::uint8_t FILTER_AVERAGE = 1 << 3; +inline constexpr std::uint8_t FILTER_PAETH = 1 << 4; +inline constexpr std::uint8_t FILTER_ALL = FILTER_NONE | FILTER_SUB | FILTER_UP | FILTER_AVERAGE | FILTER_PAETH; + struct EncodeOptions { int compressionLevel = 8; - int filter = -1; + std::uint8_t filters = FILTER_ALL; }; std::vector Encode(std::span pixels, std::uint32_t width, std::uint32_t height, diff --git a/core/Decoder/Png/src/Png.cpp b/core/Decoder/Png/src/Png.cpp index 7e8d40b8d..d137df164 100644 --- a/core/Decoder/Png/src/Png.cpp +++ b/core/Decoder/Png/src/Png.cpp @@ -4,7 +4,8 @@ #include #include #include -#include +#include +#include #include #define STB_IMAGE_WRITE_STATIC @@ -26,6 +27,17 @@ constexpr std::array SIGNATURE = {0x89, 0x50, 0x4E, 0x47, 0x0D, constexpr std::size_t CHUNK_OVERHEAD = 12; constexpr std::size_t IHDR_SIZE = 13; constexpr std::uint32_t MAX_DIMENSION = 0x7FFFFFFF; +constexpr std::uint8_t FILTER_TYPE_COUNT = 5; +constexpr std::array COLOR_TYPE_BY_CHANNELS = {0, 0, 4, 2, 6}; +constexpr std::array CRC_TABLE = [] { + std::array table{}; + for (std::uint32_t index = 0; index < table.size(); ++index) { + std::uint32_t value = index; + for (int bit = 0; bit < 8; ++bit) value = (value >> 1) ^ ((value & 1) != 0 ? 0xEDB88320u : 0u); + table[index] = value; + } + return table; +}(); std::uint32_t readBigEndian32(const std::uint8_t* bytes) { return static_cast(bytes[0]) << 24 | static_cast(bytes[1]) << 16 @@ -51,10 +63,57 @@ bool isValidFormat(std::uint8_t bitDepth, std::uint8_t colorType) { } } -void appendBytes(void* context, void* data, int size) { - auto* output = static_cast*>(context); - const auto* bytes = static_cast(data); - output->insert(output->end(), bytes, bytes + size); +void appendBigEndian32(std::vector& output, std::uint32_t value) { + for (int shift = 24; shift >= 0; shift -= 8) output.push_back(static_cast(value >> shift)); +} + +void appendChunk(std::vector& output, const char* type, std::span data) { + appendBigEndian32(output, static_cast(data.size())); + const std::size_t checkedFrom = output.size(); + output.insert(output.end(), type, type + 4); + output.insert(output.end(), data.begin(), data.end()); + std::uint32_t crc = 0xFFFFFFFFu; + for (std::size_t i = checkedFrom; i < output.size(); ++i) crc = (crc >> 8) ^ CRC_TABLE[(crc ^ output[i]) & 0xFF]; + appendBigEndian32(output, ~crc); +} + +int paethPredictor(int left, int up, int upLeft) { + const int estimate = left + up - upLeft; + const int leftDistance = std::abs(estimate - left); + const int upDistance = std::abs(estimate - up); + const int upLeftDistance = std::abs(estimate - upLeft); + if (leftDistance <= upDistance && leftDistance <= upLeftDistance) return left; + return upDistance <= upLeftDistance ? up : upLeft; +} + +std::uint64_t filterRow(const std::uint8_t* row, const std::uint8_t* previousRow, std::size_t rowSize, std::size_t pixelSize, + std::uint8_t filterType, std::uint8_t* filtered) { + std::uint64_t cost = 0; + for (std::size_t i = 0; i < rowSize; ++i) { + const int left = i >= pixelSize ? row[i - pixelSize] : 0; + const int up = previousRow ? previousRow[i] : 0; + const int upLeft = previousRow && i >= pixelSize ? previousRow[i - pixelSize] : 0; + int prediction = 0; + switch (filterType) { + case 1: + prediction = left; + break; + case 2: + prediction = up; + break; + case 3: + prediction = (left + up) >> 1; + break; + case 4: + prediction = paethPredictor(left, up, upLeft); + break; + default: + break; + } + filtered[i] = static_cast(row[i] - prediction); + cost += filtered[i] < 0x80 ? filtered[i] : 0x100u - filtered[i]; + } + return cost; } } // namespace @@ -105,25 +164,50 @@ std::optional Decode(std::span png) { std::vector Encode(std::span pixels, std::uint32_t width, std::uint32_t height, std::uint32_t channels, EncodeOptions options) { if (channels < 1 || channels > 4) throw std::invalid_argument("Png::Encode: channels must be 1-4"); - if (options.compressionLevel < 0 || options.compressionLevel > 9 || options.filter < -1 || options.filter > 4) { + if (options.compressionLevel < 0 || options.compressionLevel > 9 || options.filters == 0 || (options.filters & ~FILTER_ALL) != 0) { throw std::invalid_argument("Png::Encode: unsupported options"); } - if (width == 0 || height == 0 || static_cast(width) * channels > INT_MAX || height > INT_MAX) { + const std::uint64_t rowSize = static_cast(width) * channels; + if (width == 0 || height == 0 || rowSize > INT_MAX || (rowSize + 1) * height > INT_MAX) { throw std::invalid_argument("Png::Encode: unsupported image size"); } - if (pixels.size() < static_cast(width) * height * channels) { + if (pixels.size() < rowSize * height) { throw std::invalid_argument("Png::Encode: pixel buffer is too small"); } - std::vector output; - const int stride = static_cast(width * channels); - static std::mutex optionsMutex; - std::lock_guard lock(optionsMutex); - stbi_write_png_compression_level = options.compressionLevel; - stbi_write_force_png_filter = options.filter; - const int written = stbi_write_png_to_func(appendBytes, &output, static_cast(width), static_cast(height), - static_cast(channels), pixels.data(), stride); - if (written == 0) throw std::runtime_error("Png::Encode: encoding failed"); + std::vector filtered((rowSize + 1) * height); + std::vector candidate(rowSize); + for (std::uint32_t y = 0; y < height; ++y) { + const std::uint8_t* row = pixels.data() + rowSize * y; + const std::uint8_t* previousRow = y == 0 ? nullptr : row - rowSize; + std::uint8_t* filteredRow = filtered.data() + (rowSize + 1) * y; + std::uint64_t bestCost = UINT64_MAX; + for (std::uint8_t filterType = 0; filterType < FILTER_TYPE_COUNT; ++filterType) { + if ((options.filters & (1u << filterType)) == 0) continue; + const std::uint64_t cost =filterRow(row, previousRow, rowSize, channels, filterType, candidate.data()); + if (cost >= bestCost) continue; + bestCost = cost; + filteredRow[0] = filterType; + std::copy(candidate.begin(), candidate.end(), filteredRow + 1); + } + } + + int compressedSize = 0; + const std::unique_ptr compressed( + stbi_zlib_compress(filtered.data(), static_cast(filtered.size()), &compressedSize, options.compressionLevel), + [](unsigned char* memory) { STBIW_FREE(memory); }); + if (!compressed) throw std::runtime_error("Png::Encode: encoding failed"); + + std::vector header; + appendBigEndian32(header, width); + appendBigEndian32(header, height); + header.insert(header.end(), {8, COLOR_TYPE_BY_CHANNELS[channels], 0, 0, 0}); + + std::vector output(SIGNATURE.begin(), SIGNATURE.end()); + output.reserve(SIGNATURE.size() + 3 * CHUNK_OVERHEAD + IHDR_SIZE + static_cast(compressedSize)); + appendChunk(output, "IHDR", header); + appendChunk(output, "IDAT", std::span(compressed.get(), static_cast(compressedSize))); + appendChunk(output, "IEND", {}); return output; } diff --git a/core/Decoder/Png/tests/Png.cpp b/core/Decoder/Png/tests/Png.cpp index 9129f03dc..7287ed5b3 100644 --- a/core/Decoder/Png/tests/Png.cpp +++ b/core/Decoder/Png/tests/Png.cpp @@ -1,10 +1,17 @@ #include "Decoder/Png.hpp" +#include #include #include #include #include +#define STB_IMAGE_STATIC +#define STB_IMAGE_IMPLEMENTATION +#define STBI_ONLY_PNG +#define STBI_NO_STDIO +#include "stb_image.h" + static void Require(bool value) { if (!value) std::abort(); } template @@ -84,11 +91,63 @@ static void RequireRoundTrip(std::uint32_t channels, Decoder::Png::ColorType col } } +static std::vector RowFilters(const std::vector& png, std::uint32_t height, std::uint32_t rowSize) { + std::vector stream; + for (std::size_t offset = 8; offset + 12 <= png.size();) { + const std::size_t length = static_cast(png[offset]) << 24 | static_cast(png[offset + 1]) << 16 + | static_cast(png[offset + 2]) << 8 | png[offset + 3]; + Require(length <= png.size() - offset - 12); + const std::uint8_t* data = png.data() + offset + 8; + if (std::equal(data - 4, data, "IDAT")) stream.insert(stream.end(), data, data + length); + offset += 12 + length; + } + + int size = 0; + char* rows = stbi_zlib_decode_malloc(reinterpret_cast(stream.data()), static_cast(stream.size()), &size); + Require(rows && static_cast(size) == static_cast(height) * (rowSize + 1)); + std::vector filters; + for (std::uint32_t y = 0; y < height; ++y) filters.push_back(static_cast(rows[y * (rowSize + 1)])); + stbi_image_free(rows); + return filters; +} + +static void RequireFilterSets(std::uint32_t channels) { + constexpr std::uint32_t width = 16; + constexpr std::uint32_t height = 6; + const std::uint32_t rowSize = width * channels; + std::vector pixels(rowSize * height); + std::uint32_t noise = 12345; + for (std::uint32_t y = 0; y < height; ++y) { + for (std::uint32_t i = 0; i < rowSize; ++i) { + noise = noise * 1103515245u + 12345u; + pixels[y * rowSize + i] = y == 2 ? static_cast(noise >> 16) : static_cast(i / channels * 8); + } + } + std::copy_n(pixels.begin() + 2 * rowSize, rowSize, pixels.begin() + 3 * rowSize); + + for (std::uint8_t filters = 1; filters <= Decoder::Png::FILTER_ALL; ++filters) { + const std::vector png = Decoder::Png::Encode(pixels, width, height, channels, {6, filters}); + for (const std::uint8_t filter : RowFilters(png, height, rowSize)) Require(filter < 5 && (filters >> filter & 1) != 0); + + const auto image = Decoder::Png::Decode(png); + Require(image.has_value() && image->width == width && image->height == height); + for (std::size_t pixel = 0; pixel < width * height; ++pixel) { + Require(image->pixels[pixel * 4] == pixels[pixel * channels]); + Require(image->pixels[pixel * 4 + 3] == (channels % 2 == 0 ? pixels[pixel * channels + channels - 1] : 255)); + } + } + + const std::vector subOrUp = RowFilters( + Decoder::Png::Encode(pixels, width, height, channels, {6, Decoder::Png::FILTER_SUB | Decoder::Png::FILTER_UP}), height, rowSize); + Require(subOrUp[0] == 1 && subOrUp[1] == 2 && subOrUp[3] == 2 && subOrUp[4] == 1 && subOrUp[5] == 2); +} + int main() { RequireRoundTrip(1, Decoder::Png::ColorType::Grayscale); RequireRoundTrip(2, Decoder::Png::ColorType::GrayscaleAlpha); RequireRoundTrip(3, Decoder::Png::ColorType::Rgb); RequireRoundTrip(4, Decoder::Png::ColorType::Rgba); + for (const std::uint32_t channels : {1, 2, 3, 4}) RequireFilterSets(channels); const auto paletteHeader = Decoder::Png::ParseHeader(PALETTE_TRNS); Require(paletteHeader.has_value()); @@ -140,13 +199,14 @@ int main() { Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 5); })); Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 0, 4, 4); })); Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 5, 4); })); - Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 4, {10, -1}); })); - Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 4, {8, 5}); })); + Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 4, {10, Decoder::Png::FILTER_ALL}); })); + Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 4, {8, 0}); })); + Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 4, {8, Decoder::Png::FILTER_ALL + 1}); })); std::vector gradient(64 * 64 * 3); for (std::size_t i = 0; i < gradient.size(); ++i) gradient[i] = static_cast(i / 3 % 64 * 4); - const auto stored = Decoder::Png::Encode(gradient, 64, 64, 3, {0, 0}); - const auto compressed = Decoder::Png::Encode(gradient, 64, 64, 3, {9, 1}); + const auto stored = Decoder::Png::Encode(gradient, 64, 64, 3, {0, Decoder::Png::FILTER_NONE}); + const auto compressed = Decoder::Png::Encode(gradient, 64, 64, 3, {9, Decoder::Png::FILTER_SUB}); Require(compressed.size() < stored.size()); for (const auto& png : {stored, compressed}) { const auto image = Decoder::Png::Decode(png); diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 53ec753d7..510110335 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -170,6 +170,16 @@ if(BUILD_TESTING) add_test(NAME host_thread_local COMMAND host_thread_local_tests) set_tests_properties(host_thread_local PROPERTIES TIMEOUT 15) + add_test_executable(guest_np_cpp_web_api_intrusive_ptr_tests tests/GuestNpCppWebApiIntrusivePtr.cpp) + target_include_directories(guest_np_cpp_web_api_intrusive_ptr_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_np_cpp_web_api_intrusive_ptr_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_intrusive_ptr_tests) + add_test(NAME guest_np_cpp_web_api_intrusive_ptr COMMAND guest_np_cpp_web_api_intrusive_ptr_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_intrusive_ptr PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(savedata_core_write_replacement_tests tests/SaveDataWriteReplacement.cpp) target_include_directories(savedata_core_write_replacement_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(savedata_core_write_replacement_tests PRIVATE libSceSaveData) diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index def7785d9..f58476bec 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -389,6 +389,18 @@ struct AjmDecAt9ConfigDataInfo { std::uint32_t superframe_size; }; +struct AjmDecMp3ParseFrame { + std::uint64_t frame_size; + std::uint32_t num_channels; + std::uint32_t samples_per_channel; + std::uint32_t bitrate; + std::uint32_t sample_rate; + std::uint32_t encoder_delay; + std::uint32_t num_frames; + std::uint32_t total_samples; + std::uint32_t ofl_type; +}; + using AudioOut2ContextHandle = std::uint64_t; using AudioOut2PortHandle = std::uint64_t; using AudioOut2UserHandle = std::uintptr_t; diff --git a/core/libs/prx/libSceAgc/Acb/src/Memory.cpp b/core/libs/prx/libSceAgc/Acb/src/Memory.cpp index c2b181de8..1a93bdbc2 100644 --- a/core/libs/prx/libSceAgc/Acb/src/Memory.cpp +++ b/core/libs/prx/libSceAgc/Acb/src/Memory.cpp @@ -47,14 +47,7 @@ std::uint32_t APS5_VABI sceAgcAcbAtomicGdsGetSize() { } std::uint32_t* APS5_VABI sceAgcAcbPrimeUtcl2(CommandBuffer* buf, const volatile void* address, std::uint32_t sizeInBytes) { - const auto guestAddress = reinterpret_cast(address); - auto* packet = Agc::Command::Allocate(buf, 5, __func__); - packet[0] = Agc::Command::Header(0x10u, 5); - packet[1] = 0; - packet[2] = static_cast(guestAddress); - packet[3] = static_cast(guestAddress >> 32u); - packet[4] = sizeInBytes; - return packet; + return Agc::Command::WritePrimeUtcl2(buf, address, sizeInBytes, __func__); } std::uint32_t APS5_VABI sceAgcAcbPrimeUtcl2GetSize() { diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index 1ac5e1b6b..f1822c8ce 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -30,6 +30,7 @@ add_library(libSceAgc SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/DcbState/src/Registers.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DcbState/src/Workload.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/PacketInfo.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Platform.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/RegisterDefaults.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/ShaderFusion.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Suspend.cpp @@ -69,9 +70,23 @@ target_link_libraries(agc_command_tests PRIVATE libSceAgc libc) configure_windows_unwind(agc_command_tests) configure_windows_unwind(libSceAgc) +add_test_executable(agc_platform_tests EXCLUDE_FROM_ALL tests/Platform.cpp) +target_include_directories(agc_platform_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_platform_tests PRIVATE libSceAgc libc) +configure_windows_unwind(agc_platform_tests) +if(BUILD_TESTING) + set_target_properties(agc_platform_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) + add_test(NAME agc_platform_tests COMMAND agc_platform_tests) + if(WIN32) + set_tests_properties(agc_platform_tests PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_register_command_tests EXCLUDE_FROM_ALL tests/RegisterCommands.cpp Command/src/Packet.cpp + Cb/src/Registers.cpp DcbState/src/Registers.cpp Patch/src/IndirectRegisters.cpp ) @@ -120,6 +135,21 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_interpolant_mapping_tests EXCLUDE_FROM_ALL tests/InterpolantMapping.cpp) +target_include_directories(agc_interpolant_mapping_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_interpolant_mapping_tests PRIVATE libSceAgc libc) +set_target_properties(agc_interpolant_mapping_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_interpolant_mapping_tests) + +if(BUILD_TESTING) + set_target_properties(agc_interpolant_mapping_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_interpolant_mapping COMMAND agc_interpolant_mapping_tests) + if(WIN32) + set_tests_properties(agc_interpolant_mapping PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + if(BUILD_TESTING) add_test_executable(guest_shader_link_tests ${LIBS_INCLUDE_DIR}/tests/GuestShaderLink.cpp) target_include_directories(guest_shader_link_tests PRIVATE ${LIBS_INCLUDE_DIR}) @@ -150,3 +180,23 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +add_test_executable(agc_prime_utcl2_tests EXCLUDE_FROM_ALL + tests/PrimeUtcl2.cpp + Command/src/Packet.cpp + Command/src/Memory.cpp + Acb/src/Memory.cpp + DcbFlow/src/Memory.cpp + DcbState/src/Display.cpp +) +target_include_directories(agc_prime_utcl2_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_prime_utcl2_tests PRIVATE libc) +configure_windows_unwind(agc_prime_utcl2_tests) +if(BUILD_TESTING) + set_target_properties(agc_prime_utcl2_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) + add_test(NAME agc_prime_utcl2_tests COMMAND agc_prime_utcl2_tests) + if(WIN32) + set_tests_properties(agc_prime_utcl2_tests PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgc/Cb/src/Registers.cpp b/core/libs/prx/libSceAgc/Cb/src/Registers.cpp index 29b68fe01..8e4d72acd 100644 --- a/core/libs/prx/libSceAgc/Cb/src/Registers.cpp +++ b/core/libs/prx/libSceAgc/Cb/src/Registers.cpp @@ -6,6 +6,16 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +std::uint32_t RegisterListSize(std::uint32_t numRegs, const char* function) { + constexpr std::uint32_t bytesPerRegister = 3u * sizeof(std::uint32_t); + Agc::Command::Require(numRegs <= 0xffffffffu / bytesPerRegister, function, "register list size overflow"); + return numRegs * bytesPerRegister; +} + +} + extern "C" { std::uint32_t* APS5_VABI sceAgcCbSetUcRegisterRangeDirect(CommandBuffer* buf, std::uint32_t offset, const std::uint32_t* values, std::uint32_t numValues) { @@ -29,9 +39,7 @@ std::uint32_t* APS5_VABI sceAgcCbSetShRegistersDirect(CommandBuffer* buf, const } std::uint32_t APS5_VABI sceAgcCbSetShRegistersDirectGetSize(std::uint32_t numRegs) { - (void)numRegs; - NotImplemented_nid_no_patch(__func__); - return 0; + return RegisterListSize(numRegs, __func__); } std::uint32_t* APS5_VABI sceAgcCbSetUcRegistersDirect(CommandBuffer* buf, const volatile ShaderRegister* regs, std::uint32_t numRegs) { @@ -39,9 +47,7 @@ std::uint32_t* APS5_VABI sceAgcCbSetUcRegistersDirect(CommandBuffer* buf, const } std::uint32_t APS5_VABI sceAgcCbSetUcRegistersDirectGetSize(std::uint32_t numRegs) { - (void)numRegs; - NotImplemented_nid_no_patch(__func__); - return 0; + return RegisterListSize(numRegs, __func__); } } diff --git a/core/libs/prx/libSceAgc/Command/include/Memory.hpp b/core/libs/prx/libSceAgc/Command/include/Memory.hpp index 9c19b6599..9c5fe2edf 100644 --- a/core/libs/prx/libSceAgc/Command/include/Memory.hpp +++ b/core/libs/prx/libSceAgc/Command/include/Memory.hpp @@ -8,6 +8,7 @@ namespace Agc::Command { std::uint32_t* WriteDma(CommandBuffer* buffer, bool compute, std::uint8_t engine, std::uint8_t dst, std::uint8_t dstCachePolicy, std::uint64_t dstAddress, std::uint8_t src, std::uint8_t srcCachePolicy, std::uint64_t srcAddress, std::uint32_t numBytes, std::uint8_t waitForPrevious, std::uint8_t writeConfirm, std::uint8_t blockEngine, const char* function); std::uint32_t* WriteCopyData(CommandBuffer* buffer, bool compute, std::uint8_t dst, std::uint8_t dstCachePolicy, std::uint64_t dstAddress, std::uint8_t src, std::uint8_t srcCachePolicy, std::uint64_t srcAddressOrImmediate, std::uint8_t itemSize, std::uint8_t writeConfirm, const char* function); std::uint32_t* WriteAtomicMem(CommandBuffer* buffer, std::uint8_t atomicOp, std::uint8_t command, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t srcData, std::uint64_t compareData, std::uint16_t loopInterval, const char* function); +std::uint32_t* WritePrimeUtcl2(CommandBuffer* buffer, const volatile void* address, std::uint32_t sizeInBytes, const char* function); std::uint32_t* WriteData(CommandBuffer* buffer, bool compute, std::uint8_t dst, std::uint8_t cachePolicy, std::uint64_t address, const void* data, std::uint32_t count, std::uint8_t increment, std::uint8_t writeConfirm, const char* function); std::uint32_t* WriteWait(CommandBuffer* buffer, std::uint8_t size, std::uint8_t compareFunction, std::uint8_t operation, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t reference, std::uint64_t mask, std::uint32_t pollCycles, const char* function); std::uint32_t* ValidateWait(std::uint32_t* packet, const char* function); diff --git a/core/libs/prx/libSceAgc/Command/src/Memory.cpp b/core/libs/prx/libSceAgc/Command/src/Memory.cpp index 86d799379..15d897300 100644 --- a/core/libs/prx/libSceAgc/Command/src/Memory.cpp +++ b/core/libs/prx/libSceAgc/Command/src/Memory.cpp @@ -49,6 +49,11 @@ std::uint32_t* WriteAtomicMem(CommandBuffer* buffer, std::uint8_t atomicOp, std: return Emit(buffer, 0x1eu, {atomicOp | (static_cast(command) << 8u) | (static_cast(cachePolicy) << 25u), static_cast(guestAddress), static_cast(guestAddress >> 32u), static_cast(srcData), static_cast(srcData >> 32u), static_cast(compareData), static_cast(compareData >> 32u), loopInterval}, function); } +std::uint32_t* WritePrimeUtcl2(CommandBuffer* buffer, const volatile void* address, std::uint32_t sizeInBytes, const char* function) { + const auto guestAddress = reinterpret_cast(address); + return Emit(buffer, 0x10u, {0, static_cast(guestAddress), static_cast(guestAddress >> 32u), sizeInBytes}, function); +} + std::uint32_t* WriteData(CommandBuffer* buffer, bool compute, std::uint8_t dst, std::uint8_t cachePolicy, std::uint64_t address, const void* data, std::uint32_t count, std::uint8_t increment, std::uint8_t writeConfirm, const char* function) { Require(data != nullptr && count != 0 && count <= 0x3ffdu, function, "invalid write data payload"); CheckBits(dst, compute ? 0xfu : 0x1fu, function); diff --git a/core/libs/prx/libSceAgc/DcbState/src/Display.cpp b/core/libs/prx/libSceAgc/DcbState/src/Display.cpp index 9b379b97d..f9594cfe8 100644 --- a/core/libs/prx/libSceAgc/DcbState/src/Display.cpp +++ b/core/libs/prx/libSceAgc/DcbState/src/Display.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgc/DcbState/include/Display.hpp" +#include "prx/libSceAgc/Command/include/Memory.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" #include @@ -21,16 +22,11 @@ uint32_t* APS5_VABI sceAgcDcbSetFlip(CommandBuffer* buf, uint32_t video_out_hand } uint32_t* APS5_VABI sceAgcDcbPrimeUtcl2(CommandBuffer* buf, const volatile void* address, uint32_t size_in_bytes) { - (void)buf; - (void)address; - (void)size_in_bytes; - NotImplemented_nid_no_patch(__func__); - return nullptr; + return Agc::Command::WritePrimeUtcl2(buf, address, size_in_bytes, __func__); } std::uint32_t APS5_VABI sceAgcDcbPrimeUtcl2GetSize() { - NotImplemented_nid_no_patch(__func__); - return 0; + return 20; } } diff --git a/core/libs/prx/libSceAgc/Misc/include/Platform.hpp b/core/libs/prx/libSceAgc/Misc/include/Platform.hpp new file mode 100644 index 000000000..fd07f976a --- /dev/null +++ b/core/libs/prx/libSceAgc/Misc/include/Platform.hpp @@ -0,0 +1,4 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_PLATFORM_HPP +#define CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_PLATFORM_HPP + +#endif diff --git a/core/libs/prx/libSceAgc/Misc/src/Platform.cpp b/core/libs/prx/libSceAgc/Misc/src/Platform.cpp new file mode 100644 index 000000000..de162d3a7 --- /dev/null +++ b/core/libs/prx/libSceAgc/Misc/src/Platform.cpp @@ -0,0 +1,17 @@ +#include "prx/libSceAgc/Misc/include/Platform.hpp" +#include "prx/libSceAgc/Command/include/Packet.hpp" + +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceAgcGetIsTrinityMode(bool* isTrinityMode) { + Agc::Command::CheckAddress(reinterpret_cast(isTrinityMode), alignof(bool), __func__); + *isTrinityMode = false; + return 0; +} + +} diff --git a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp index 515458391..5ce4d1555 100644 --- a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp +++ b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp @@ -8,11 +8,48 @@ #include "prx/libSceAgc/Shader/include/ShaderUtils.hpp" #include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" -extern "C" { +namespace { -APS5_EXPORT("HV4j+E0MBHE", sceAgcCreateInterpolantMapping); -int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) { - constexpr auto fn = __func__; +constexpr std::uint32_t InterpolantRegisterCount = 32; +constexpr std::uint32_t InterpolantF16HiOnly = 2; + +using CreateInterpolantValueFn = std::uint32_t (*)(std::uint32_t psWord, const ShaderSemantic* gsSemantic); + +std::uint32_t CreateInterpolantValue(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + const std::uint32_t value = ((psWord & 0x00300000u) != 0) + ? CreateInterpolantF16Value(psWord, gsSemantic) + : CreateInterpolantNonF16Value(psWord, gsSemantic); + + return (gsSemantic == nullptr) + ? CreateInterpolantDefaultValue(value, psWord) + : CreateInterpolantMappingValue(value, psWord, ShaderSemanticWord(*gsSemantic)); +} + +std::uint32_t CreateInterpolantF16HiValue(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + std::uint32_t value = ((psWord << 4u) & 0x03000000u) | 0x00080000u; + if (gsSemantic == nullptr || (psWord & ShaderSemanticWord(*gsSemantic) & 0x00200000u) == 0) { + value |= 0x00000020u; + } + value = ApplyInterpolantDefaultValueHi(value, psWord); + value |= ((psWord >> 30u) & 0x3u) << 8u; + if (gsSemantic == nullptr) { + return value; + } + + value |= (ShaderSemanticWord(*gsSemantic) >> 8u) & 0x1Fu; + if ((psWord & 0x01400000u) != 0) { + value |= 0x00000400u; + } + return value; +} + +std::uint32_t CreateInterpolantValueSplitF16(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + return (((psWord >> 20u) & 0x3u) == InterpolantF16HiOnly) + ? CreateInterpolantF16HiValue(psWord, gsSemantic) + : CreateInterpolantValue(psWord, gsSemantic); +} + +int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* gs, const Shader* ps, CreateInterpolantValueFn createValue) { if (regs == nullptr) { throw std::runtime_error(std::string(fn) + ": regs is null"); } @@ -26,6 +63,10 @@ int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* throw std::runtime_error(std::string(fn) + ": ps->input_semantics is null but num_input_semantics != 0"); } + if (ps->num_input_semantics > InterpolantRegisterCount) { + throw std::runtime_error(std::string(fn) + ": ps->num_input_semantics exceeds the interpolant register count"); + } + if (gs == nullptr) { throw std::runtime_error(std::string(fn) + ": gs is null"); } @@ -37,17 +78,7 @@ int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* for (std::uint32_t i = 0; i < ps->num_input_semantics; ++i) { const ShaderSemantic& psSemantic = ps->input_semantics[i]; const ShaderSemantic* gsSemantic = FindOutputSemantic(gs, psSemantic.semantic); - const std::uint32_t psWord = ShaderSemanticWord(psSemantic); - - std::uint32_t value = ((psWord & 0x00300000u) != 0) - ? CreateInterpolantF16Value(psWord, gsSemantic) - : CreateInterpolantNonF16Value(psWord, gsSemantic); - - value = (gsSemantic == nullptr) - ? CreateInterpolantDefaultValue(value, psWord) - : CreateInterpolantMappingValue(value, psWord, ShaderSemanticWord(*gsSemantic)); - - SetInterpolantRegister(regs, i, value); + SetInterpolantRegister(regs, i, createValue(ShaderSemanticWord(psSemantic), gsSemantic)); } FillIdentityInterpolants(regs, ps->num_input_semantics); @@ -55,3 +86,17 @@ int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* } } + +extern "C" { + +APS5_EXPORT("HV4j+E0MBHE", sceAgcCreateInterpolantMapping); +int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) { + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValue); +} + +APS5_EXPORT("dbOlWdppb4o", sceAgcUnknownCreateInterpolantMapping); +int APS5_VABI sceAgcUnknownCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) { + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueSplitF16); +} + +} diff --git a/core/libs/prx/libSceAgc/Unimplemented.cpp b/core/libs/prx/libSceAgc/Unimplemented.cpp index e53a08cb3..dea38a6a4 100644 --- a/core/libs/prx/libSceAgc/Unimplemented.cpp +++ b/core/libs/prx/libSceAgc/Unimplemented.cpp @@ -70,10 +70,6 @@ int APS5_VABI sceAgcGetGsOversubscription() { return 0; } -int APS5_VABI sceAgcGetIsTrinityMode() { - return 0; -} - int APS5_VABI sceAgcGetSemaphoreLabel() { NotImplemented_nid_no_patch(__func__); return 0; @@ -84,8 +80,7 @@ int APS5_VABI sceAgcSetAmmSemaphoreMemory() { return 0; } -APS5_EXPORT("6nths4DHNrs", sceAgcUnknown_6nths4DHNrs); -int APS5_VABI sceAgcUnknown_6nths4DHNrs() { +int APS5_VABI sceAgcCbMemsetExclusive() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -96,38 +91,32 @@ int APS5_VABI sceAgcUnknown_7Wa3aeJgeVU() { return 0; } -APS5_EXPORT("EJBA4dbmvfg", sceAgcUnknown_EJBA4dbmvfg); -int APS5_VABI sceAgcUnknown_EJBA4dbmvfg() { +int APS5_VABI sceAgcAsyncWriteDataPatchSetDst() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("ICkECTBxrMw", sceAgcUnknown_ICkECTBxrMw); -int APS5_VABI sceAgcUnknown_ICkECTBxrMw() { +int APS5_VABI sceAgcGetGsPrimPayload() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("RTpj-tIlvZc", sceAgcUnknown_RTpj_MtIlvZc); -int APS5_VABI sceAgcUnknown_RTpj_MtIlvZc() { +int APS5_VABI sceAgcSetShaderInstrumentation() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("SwI6QxqwAC0", sceAgcUnknown_SwI6QxqwAC0); -int APS5_VABI sceAgcUnknown_SwI6QxqwAC0() { +int APS5_VABI sceAgcGetShaderInstrumentation() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("d4NZIlguzv0", sceAgcUnknown_d4NZIlguzv0); -int APS5_VABI sceAgcUnknown_d4NZIlguzv0() { +int APS5_VABI sceAgcAsyncWriteDataPatchSetAddressOrOffset() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("eAy8eGNsCuU", sceAgcUnknown_eAy8eGNsCuU); -int APS5_VABI sceAgcUnknown_eAy8eGNsCuU() { +int APS5_VABI sceAgcWriteDataPatchSetCachePolicy() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -138,14 +127,12 @@ int APS5_VABI sceAgcUnknown_rP5xLdOf26k() { return 0; } -APS5_EXPORT("tmy-+rBpspY", sceAgcUnknown_tmy_M_PrBpspY); -int APS5_VABI sceAgcUnknown_tmy_M_PrBpspY() { +int APS5_VABI sceAgcWriteDataPatchSetDst() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("y5K5tPktiL8", sceAgcUnknown_y5K5tPktiL8); -int APS5_VABI sceAgcUnknown_y5K5tPktiL8() { +int APS5_VABI sceAgcAsyncWriteDataPatchSetCachePolicy() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp new file mode 100644 index 000000000..bee89bb8d --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp @@ -0,0 +1,134 @@ +#include "SceShaders.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" + +#include +#include +#include +#include +#include + +extern "C" int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps); +extern "C" int APS5_VABI sceAgcUnknownCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps); + +namespace { + +using Registers = std::array; + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +void expectFailure(TAction action) { + try { + action(); + } catch (const std::exception& error) { + check(error.what()[0] != '\0', "empty exception message"); + return; + } + throw std::runtime_error("expected an exception"); +} + +Registers filled() { + Registers regs{}; + std::memset(regs.data(), 0xcc, sizeof(regs)); + return regs; +} + +ShaderSemantic semantic(std::uint32_t id, std::uint32_t hardwareMapping, std::uint32_t f16) { + ShaderSemantic value{}; + value.semantic = id; + value.hardware_mapping = hardwareMapping; + value.is_f16 = f16; + return value; +} + +void checkIdentity(const Registers& regs, std::uint32_t first, const char* message) { + for (std::uint32_t i = first; i < regs.size(); ++i) { + check(regs[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i && regs[i].value == i, message); + } +} + +void testIdentity() { + auto regs = filled(); + check(sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, nullptr) == 0, "mapping without shaders failed"); + checkIdentity(regs, 0, "mapping without a pixel shader is not the identity"); + Shader ps{}; + regs = filled(); + check(sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, &ps) == 0, "mapping without inputs failed"); + checkIdentity(regs, 0, "mapping without inputs is not the identity"); +} + +void testMapping() { + std::array inputs{ + semantic(1, 0, 2), semantic(2, 0, 2), semantic(5, 0, 2), semantic(3, 0, 0), semantic(4, 0, 3)}; + inputs[0].is_flat_shaded = 1; + inputs[0].default_value = 1; + inputs[0].default_value_hi = 3; + inputs[1].default_value_hi = 1; + inputs[2].is_flat_shaded = 1; + inputs[2].default_value_hi = 2; + inputs[3].is_flat_shaded = 1; + inputs[3].default_value = 2; + std::array outputs{semantic(1, 7, 3), semantic(2, 4, 1), semantic(3, 9, 0), semantic(4, 2, 1)}; + Shader gs{}; + gs.output_semantics = outputs.data(); + gs.num_output_semantics = static_cast(outputs.size()); + Shader ps{}; + ps.input_semantics = inputs.data(); + ps.num_input_semantics = static_cast(inputs.size()); + + auto split = filled(); + check(sceAgcUnknownCreateInterpolantMapping(split.data(), &gs, &ps) == 0, "split f16 mapping failed"); + for (std::uint32_t i = 0; i < inputs.size(); ++i) { + check(split[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i, "interpolant register offset changed"); + } + check(split[0].value == 0x02680707u, "high f16 half provided by the gs is not mapped"); + check(split[1].value == 0x02280124u, "high f16 half missing from the gs does not use its default"); + check(split[2].value == 0x02480220u, "unmatched high f16 half does not use its default"); + check(split[3].value == 0x00000609u, "32-bit interpolant mapping changed"); + check(split[4].value == 0x03180002u, "full f16 interpolant mapping changed"); + checkIdentity(split, static_cast(inputs.size()), "unused interpolants are not the identity"); + + auto regular = filled(); + check(sceAgcCreateInterpolantMapping(regular.data(), &gs, &ps) == 0, "mapping failed"); + check(regular[3].value == split[3].value && regular[4].value == split[4].value, "mappings differ outside the high f16 half mode"); + checkIdentity(regular, static_cast(inputs.size()), "unused interpolants are not the identity"); +} + +void testRejections() { + std::array inputs{}; + Shader gs{}; + Shader ps{}; + ps.input_semantics = inputs.data(); + ps.num_input_semantics = static_cast(inputs.size()); + std::array regs{}; + std::memset(regs.data(), 0xcc, sizeof(regs)); + const auto saved = regs; + expectFailure([&] { sceAgcCreateInterpolantMapping(regs.data(), &gs, &ps); }); + expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(regs.data(), &gs, &ps); }); + check(std::memcmp(regs.data(), saved.data(), sizeof(regs)) == 0, "rejected mapping wrote registers"); + ps.num_input_semantics = 1; + expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, &ps); }); + expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(nullptr, &gs, &ps); }); +} + +} + +int main() { + try { + testIdentity(); + testMapping(); + testRejections(); + LibcRunShutdown_nid_postfix(); + std::puts("AGC interpolant mapping tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + try { LibcRunShutdown_nid_postfix(); } + catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); } + return 1; + } +} diff --git a/core/libs/prx/libSceAgc/tests/Platform.cpp b/core/libs/prx/libSceAgc/tests/Platform.cpp new file mode 100644 index 000000000..daa0634cf --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/Platform.cpp @@ -0,0 +1,53 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/Shutdown.hpp" + +#include +#include +#include + +extern "C" int APS5_VABI sceAgcGetIsTrinityMode(bool* isTrinityMode); + +namespace { + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +void expectFailure(TAction action) { + try { + action(); + } catch (const std::runtime_error& error) { + check(error.what()[0] != '\0', "empty exception message"); + return; + } + throw std::runtime_error("expected an exception"); +} + +void testTrinityMode() { + std::array flags{true, true, true}; + check(sceAgcGetIsTrinityMode(&flags[1]) == 0, "Trinity mode query did not return 0"); + check(!flags[1], "base PS5 GPU reported as Trinity"); + check(flags[0] && flags[2], "Trinity mode query wrote past its one-byte flag"); +} + +void testRejections() { + expectFailure([] { sceAgcGetIsTrinityMode(nullptr); }); +} + +} + +int main() { + try { + testTrinityMode(); + testRejections(); + LibcRunShutdown_nid_postfix(); + std::puts("AGC platform tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + try { LibcRunShutdown_nid_postfix(); } + catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); } + return 1; + } +} diff --git a/core/libs/prx/libSceAgc/tests/PrimeUtcl2.cpp b/core/libs/prx/libSceAgc/tests/PrimeUtcl2.cpp new file mode 100644 index 000000000..b1e702d24 --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/PrimeUtcl2.cpp @@ -0,0 +1,99 @@ +#include "prx/libSceAgc/Command/include/Packet.hpp" + +#include +#include +#include +#include +#include +#include + +extern "C" { +std::uint32_t* APS5_VABI sceAgcAcbPrimeUtcl2(CommandBuffer*, const volatile void*, std::uint32_t); +std::uint32_t APS5_VABI sceAgcAcbPrimeUtcl2GetSize(); +std::uint32_t* APS5_VABI sceAgcDcbPrimeUtcl2(CommandBuffer*, const volatile void*, std::uint32_t); +std::uint32_t APS5_VABI sceAgcDcbPrimeUtcl2GetSize(); +} + +namespace { + +constexpr std::uint32_t Sentinel = 0xabcdef01u; +constexpr std::uint64_t Address = 0x0000112233440000ull; +constexpr std::uint32_t SizeInBytes = 0x00345000u; + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +void expectFailure(TAction action) { + try { + action(); + } catch (const std::runtime_error&) { + return; + } + throw std::runtime_error("expected invalid input to fail"); +} + +struct Storage { + std::array words{}; + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; + + Storage() { + words.fill(Sentinel); + } + + void limitTo(std::uint32_t count) { + buffer.cursor_down = buffer.cursor_up + count; + } +}; + +std::uint32_t* writeCompute(CommandBuffer* buffer) { + return sceAgcAcbPrimeUtcl2(buffer, reinterpret_cast(Address), SizeInBytes); +} + +std::uint32_t* writeDraw(CommandBuffer* buffer) { + return sceAgcDcbPrimeUtcl2(buffer, reinterpret_cast(Address), SizeInBytes); +} + +template +void testCommand(TWriter writer, TSize size) { + const std::array expected{0xc0031000u, 0u, 0x33440000u, 0x1122u, SizeInBytes}; + const auto count = static_cast(expected.size()); + check(size() == count * sizeof(std::uint32_t), "size query does not match the prime packet"); + + Storage storage; + auto* packet = writer(&storage.buffer); + check(packet == storage.words.data() && std::equal(expected.begin(), expected.end(), packet), "incorrect prime packet"); + check(storage.buffer.cursor_up == packet + size() / sizeof(std::uint32_t), "cursor advance does not match the size query"); + check(std::all_of(storage.words.begin() + count, storage.words.end(), [](std::uint32_t word) { return word == Sentinel; }), "prime command overwrote following words"); + + auto* second = writer(&storage.buffer); + check(second == packet + count && std::equal(expected.begin(), expected.end(), second), "second prime packet does not follow the first"); + + Storage exact; + exact.limitTo(size() / sizeof(std::uint32_t)); + check(writer(&exact.buffer) == exact.words.data() && exact.buffer.cursor_up == exact.buffer.cursor_down, "prime packet does not fill the queried size"); + + Storage shortBuffer; + shortBuffer.limitTo(size() / sizeof(std::uint32_t) - 1u); + const auto before = shortBuffer.words; + expectFailure([&] { writer(&shortBuffer.buffer); }); + check(shortBuffer.words == before && shortBuffer.buffer.cursor_up == shortBuffer.words.data(), "failed prime write modified the buffer"); + + expectFailure([&] { writer(nullptr); }); +} + +} + +int main() { + try { + testCommand(writeCompute, sceAgcAcbPrimeUtcl2GetSize); + testCommand(writeDraw, sceAgcDcbPrimeUtcl2GetSize); + check(sceAgcAcbPrimeUtcl2GetSize() == sceAgcDcbPrimeUtcl2GetSize(), "compute and draw prime sizes differ"); + std::puts("AGC prime UTCL2 tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp b/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp index e40249ef6..ffe1e8690 100644 --- a/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp +++ b/core/libs/prx/libSceAgc/tests/RegisterCommands.cpp @@ -21,6 +21,10 @@ std::uint32_t APS5_VABI sceAgcDcbSetUcRegistersIndirectGetSize(std::uint32_t); int APS5_VABI sceAgcSetCxRegIndirectPatchSetNumRegisters(std::uint32_t*, std::uint32_t); int APS5_VABI sceAgcSetShRegIndirectPatchSetNumRegisters(std::uint32_t*, std::uint32_t); int APS5_VABI sceAgcSetUcRegIndirectPatchSetNumRegisters(std::uint32_t*, std::uint32_t); +std::uint32_t* APS5_VABI sceAgcCbSetShRegistersDirect(CommandBuffer*, const volatile ShaderRegister*, std::uint32_t); +std::uint32_t* APS5_VABI sceAgcCbSetUcRegistersDirect(CommandBuffer*, const volatile ShaderRegister*, std::uint32_t); +std::uint32_t APS5_VABI sceAgcCbSetShRegistersDirectGetSize(std::uint32_t); +std::uint32_t APS5_VABI sceAgcCbSetUcRegistersDirectGetSize(std::uint32_t); } namespace { @@ -101,12 +105,60 @@ void testIndirect() { } } +void testDirectList() { + constexpr std::uint32_t sentinel = 0xabcdef01u; + constexpr std::uint32_t wordSize = sizeof(std::uint32_t); + const std::array writers{sceAgcCbSetShRegistersDirect, sceAgcCbSetUcRegistersDirect}; + const std::array sizes{sceAgcCbSetShRegistersDirectGetSize, sceAgcCbSetUcRegistersDirectGetSize}; + const std::array opcodes{0x7600u, 0x7900u}; + const std::array scattered{{{0x12u, 1}, {0x11u, 2}, {0x20u, 3}, {0xffffu, 4}}}; + const std::array adjacent{{{0x10u, 5}, {0x11u, 6}, {0x12u, 7}, {0x13u, 8}}}; + const std::array paired{{{0x10u, 5}, {0x11u, 6}, {0x20u, 7}, {0x21u, 8}}}; + for (std::size_t i = 0; i < writers.size(); ++i) { + const auto single = 0xc0010000u | opcodes[i]; + for (std::uint32_t count = 1; count <= scattered.size(); ++count) { + const auto words = sizes[i](count) / wordSize; + Storage storage; + check(sizes[i](count) % wordSize == 0 && words != 0 && words <= storage.words.size(), "register list size is not a usable word count"); + storage.words.fill(sentinel); + storage.buffer.cursor_down = storage.buffer.cursor_up + words; + auto* packet = writers[i](&storage.buffer, scattered.data(), count); + check(packet == storage.words.data() && storage.buffer.cursor_up == storage.buffer.cursor_down, "separate registers do not fill the queried size"); + for (std::uint32_t reg = 0; reg < count; ++reg) { + const std::array expected{single, scattered[reg].offset, scattered[reg].value}; + check(std::equal(expected.begin(), expected.end(), packet + reg * expected.size()), "incorrect register list packet"); + } + check(std::all_of(storage.words.begin() + words, storage.words.end(), [](std::uint32_t word) { return word == sentinel; }), "register list overwrote following words"); + Storage shortStorage; + shortStorage.buffer.cursor_down = shortStorage.buffer.cursor_up + words - 1u; + expectFailure([&] { writers[i](&shortStorage.buffer, scattered.data(), count); }); + } + const auto limit = sizes[i](4) / wordSize; + Storage merged; + merged.buffer.cursor_down = merged.buffer.cursor_up + limit; + const std::array expectedMerged{0xc0040000u | opcodes[i], 0x10u, 5, 6, 7, 8}; + auto* packet = writers[i](&merged.buffer, adjacent.data(), 4); + check(std::equal(expectedMerged.begin(), expectedMerged.end(), packet) && merged.buffer.cursor_up == packet + expectedMerged.size(), "adjacent registers were not merged into one range"); + Storage split; + split.buffer.cursor_down = split.buffer.cursor_up + limit; + const std::array expectedSplit{0xc0020000u | opcodes[i], 0x10u, 5, 6, 0xc0020000u | opcodes[i], 0x20u, 7, 8}; + packet = writers[i](&split.buffer, paired.data(), 4); + check(std::equal(expectedSplit.begin(), expectedSplit.end(), packet) && split.buffer.cursor_up == packet + expectedSplit.size(), "register pairs were not written as two ranges"); + check(sizes[i](0) == 0, "an empty register list has a size"); + check(sizes[i](0x15555555u) == 0xfffffffcu, "largest register list size mismatch"); + for (const auto count : {0x15555556u, 0x40000000u, 0xffffffffu}) { + expectFailure([&] { sizes[i](count); }); + } + } +} + } int main() { try { testDirect(); testIndirect(); + testDirectList(); std::puts("AGC register command tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index dd97d08ab..b373847f0 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -279,6 +279,11 @@ target_link_libraries(agc_dpp_write_tests PRIVATE shader_recompiler_agc_driver l set_target_properties(agc_dpp_write_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_dpp_write_tests) +add_test_executable(agc_masked_select_tests EXCLUDE_FROM_ALL tests/MaskedSelect.cpp) +target_link_libraries(agc_masked_select_tests PRIVATE shader_recompiler_agc_driver libc) +set_target_properties(agc_masked_select_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_masked_select_tests) + add_test_executable(agc_vop1_control_decode_tests EXCLUDE_FROM_ALL tests/Vop1ControlDecode.cpp) target_link_libraries(agc_vop1_control_decode_tests PRIVATE shader_recompiler_agc_driver libc) set_target_properties(agc_vop1_control_decode_tests PROPERTIES CXX_EXTENSIONS OFF) @@ -498,7 +503,7 @@ foreach(agcExecutionTestSource IN LISTS agcExecutionTestSources) endforeach() if(BUILD_TESTING) - foreach(agcTest IN ITEMS agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_dpp_write_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_shader_disk_cache_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests ${agcExecutionTests}) + foreach(agcTest IN ITEMS agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_dpp_write_tests agc_masked_select_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_shader_disk_cache_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests ${agcExecutionTests}) string(REGEX REPLACE "_tests$" "" agcTestName "${agcTest}") set_target_properties(${agcTest} PROPERTIES EXCLUDE_FROM_ALL OFF) add_test(NAME ${agcTestName} COMMAND ${agcTest}) @@ -681,6 +686,14 @@ if(BUILD_TESTING) set_tests_properties(ray_tracing_miss PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(wave32_wide_subgroup_tests ${LIBS_INCLUDE_DIR}/tests/Wave32WideSubgroup.cpp) + target_link_libraries(wave32_wide_subgroup_tests PRIVATE shader_recompiler_agc_driver libc) + configure_windows_unwind(wave32_wide_subgroup_tests) + add_test(NAME wave32_wide_subgroup COMMAND wave32_wide_subgroup_tests) + if(WIN32) + set_tests_properties(wave32_wide_subgroup PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(vector_compare_i16_u16_tests ${LIBS_INCLUDE_DIR}/tests/VectorCompareI16U16.cpp) target_link_libraries(vector_compare_i16_u16_tests PRIVATE shader_recompiler_agc_driver libc) configure_windows_unwind(vector_compare_i16_u16_tests) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index 00f21aa66..cf2941e62 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -128,7 +128,7 @@ private: static std::array& writeHistory(); static std::size_t& writeCursor(); static void dumpPackets(std::span commands, const std::uint32_t* guest = nullptr); - static void validate(std::span commands, std::uint32_t queue, const std::uint32_t* guest = nullptr); + static void validate(const Submission& submission, const std::uint32_t* guest = nullptr); static void reportSkip(const char* kind, const std::string& what); static std::string dumpRequest(std::uint64_t address, const ShaderRecompiler::RecompileRequest& request); static bool matchesFillKernel(std::span code, const std::vector& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp index ed7da06d9..cdc9f1683 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp @@ -25,6 +25,7 @@ struct Submission { std::shared_ptr shaders; std::map> flips; std::map> renderingWaits; + std::map conditionalEnds; bool suspend = false; bool waitFree = false; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp index 7511f50e0..6189cbfec 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp @@ -39,6 +39,8 @@ using ShaderRegistry = std::map SourceHandleFor(const ShaderSnapshot& snapshot, std::size_t codeOffset, std::uint64_t deviceSerial, const ShaderRecompiler::RecompileRequest& request, bool bypass, const std::string** poisoned = nullptr); } diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp index 3add10cd8..c413e357e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp @@ -96,6 +96,8 @@ bool WaitSatisfiedUnchecked(std::span packet); // holds will store), without reading memory. bool WaitComparesValue(std::span packet, std::uint64_t value); std::size_t WaitAwaitedBytes(std::span packet); +std::size_t ConditionalWords(std::span packet); +std::uint32_t ReadCondition(std::span packet); // A label write (RELEASE_MEM with a data select, WRITE_DATA to memory): the destination and the // bytes it stores, so the write can be recorded on the GPU behind the work it signals. Packets // without a memory destination decode to nothing. No allocation per label (tens of thousands per diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp index 56ae16a72..42a10f51d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" @@ -15,6 +16,8 @@ std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Su return (static_cast(readRegister(queue.shader, base)) << 8u) | (static_cast(high) << 40u); }; const auto prepare = [&](std::uint64_t address, std::uint8_t type, Stage stage, std::uint32_t rsrc2, std::uint32_t userDataBase) { + const bool nullPixel = address == 0 && stage == Stage::Fragment; + if (nullPixel) address = NullPixelProgramAddress(); auto it = submission.shaders->upper_bound(address); require(it != submission.shaders->begin(), "graphics program does not belong to a registered shader"); --it; @@ -22,7 +25,7 @@ std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Su require(address - snapshot.codeAddress < snapshot.code.size() * sizeof(std::uint32_t), "graphics program is outside registered shader code"); require(snapshot.type == type, "graphics program refers to an incompatible shader binary type"); Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, rsrc2); - const auto resources = readRegister(queue.shader, rsrc2); + const auto resources = nullPixel && !queue.shader.contains(rsrc2) ? 0u : readRegister(queue.shader, rsrc2); const auto userCount = ((resources >> 1u) & 0x1fu) | (((resources >> 27u) & 1u) << 5u); require(userCount <= 32, "graphics user SGPR count exceeds the register bank"); const auto codeOffset = static_cast((address - snapshot.codeAddress) / sizeof(std::uint32_t)); @@ -86,9 +89,14 @@ std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Su } else { append(0xc8, 2, Stage::Vertex, 0x8b, 0x8c, Role::Main); } + const bool nullPixel = Graphics::PixelProgramUnset(queue); + if (nullPixel) { + const auto rejection = Graphics::NullPixelProgramRejection(queue); + require(rejection.empty(), rejection.c_str()); + } append(0x008, 1, Stage::Fragment, 0x00b, 0x00c, Role::Fragment); programs.back().firstUserSgpr = 0; - product->pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(graphics)); + product->pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(graphics), nullPixel); return product; } } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp index f4184ee0f..27d567cd6 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp @@ -54,7 +54,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) { const auto& s = a.state; const auto& t = b.state; const auto sameColor = [](const Graphics::ColorTarget& x, const Graphics::ColorTarget& y) { - return x.address == y.address && x.extent.width == y.extent.width && x.extent.height == y.extent.height && x.format == y.format && x.bytes == y.bytes && x.componentMapping == y.componentMapping && x.tileMode == y.tileMode && x.elementBytes == y.elementBytes && x.dccAddress == y.dccAddress && x.dccAlphaOnMsb == y.dccAlphaOnMsb && x.slot == y.slot; + return x.address == y.address && x.extent.width == y.extent.width && x.extent.height == y.extent.height && x.format == y.format && x.bytes == y.bytes && x.componentMapping == y.componentMapping && x.tileMode == y.tileMode && x.elementBytes == y.elementBytes && x.dccAddress == y.dccAddress && x.dccAlphaOnMsb == y.dccAlphaOnMsb && x.slot == y.slot && x.exportIndex == y.exportIndex; }; const auto sameBlend = [](const VkPipelineColorBlendAttachmentState& x, const VkPipelineColorBlendAttachmentState& y) { return x.blendEnable == y.blendEnable && x.srcColorBlendFactor == y.srcColorBlendFactor && x.dstColorBlendFactor == y.dstColorBlendFactor && x.colorBlendOp == y.colorBlendOp && x.srcAlphaBlendFactor == y.srcAlphaBlendFactor && x.dstAlphaBlendFactor == y.dstAlphaBlendFactor && x.alphaBlendOp == y.alphaBlendOp && x.colorWriteMask == y.colorWriteMask; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp index baa341ee2..2d614b5b4 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferFill.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "ThreadOwned.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include @@ -192,7 +193,8 @@ bool Driver::fillBuffer(QueueState& queue, std::uint32_t queueId, std::spanWaitIdle(); - static thread_local std::vector block; + static thread_local std::vector* blockSlot = nullptr; + auto& block = ShaderRecompiler::ThreadOwned(blockSlot); const auto chunk = std::min(bytes, 1u << 20u); block.resize(chunk); for (std::size_t at = 0; at < chunk; at += 16) std::memcpy(block.data() + at, pattern.data(), 16); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/CaptureValidation.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/CaptureValidation.cpp index 852c8d12c..2eb194129 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/CaptureValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/CaptureValidation.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "ThreadOwned.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include #include @@ -14,7 +15,8 @@ bool Driver::captureStable(std::span captu double waited = 0; PendingView pending; pending.Load(); - thread_local std::vector known; + thread_local std::vector* knownSlot = nullptr; + auto& known = ShaderRecompiler::ThreadOwned(knownSlot); for (const auto& region : captured) { if (!stable) break; known.resize(region.bytes.size()); @@ -58,7 +60,8 @@ bool Driver::captureStable(std::span captu bool Driver::validateCaptured(std::uint64_t program, std::uint32_t queue, std::span captured, const ShaderRecompiler::RecompileResult& compiled, bool inPlace, const PendingView& view, bool* unmapped, std::optional* sampling, const DataMask* data) { using Policy = ShaderMemory::PendingWrite; - thread_local std::vector pending; + thread_local std::vector* pendingSlot = nullptr; + auto& pending = ShaderRecompiler::ThreadOwned(pendingSlot); pending.clear(); for (std::size_t i = 0; i < captured.size(); ++i) { const auto& region = captured[i]; @@ -66,7 +69,8 @@ bool Driver::validateCaptured(std::uint64_t program, std::uint32_t queue, std::s } const auto compareMasked = [&](const ShaderRecompiler::MemoryRegion& region, std::size_t first) { - thread_local std::vector live; + thread_local std::vector* liveSlot = nullptr; + auto& live = ShaderRecompiler::ThreadOwned(liveSlot); live.resize(region.bytes.size()); const auto copied = GuestMemory::CopyMapped(region.guestAddress, live); if (copied != GuestMemory::Compare::Equal) return copied; @@ -84,9 +88,12 @@ bool Driver::validateCaptured(std::uint64_t program, std::uint32_t queue, std::s }; static constexpr std::size_t NoKnownValue = std::numeric_limits::max(); - thread_local std::vector policies; - thread_local std::vector knownOffsets; - thread_local std::vector knownBytes; + thread_local std::vector* policiesSlot = nullptr; + thread_local std::vector* knownOffsetsSlot = nullptr; + thread_local std::vector* knownBytesSlot = nullptr; + auto& policies = ShaderRecompiler::ThreadOwned(policiesSlot); + auto& knownOffsets = ShaderRecompiler::ThreadOwned(knownOffsetsSlot); + auto& knownBytes = ShaderRecompiler::ThreadOwned(knownBytesSlot); policies.assign(pending.size(), Policy::None); knownOffsets.assign(pending.size(), NoKnownValue); knownBytes.clear(); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp index 144ce3aaa..bf0f11838 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp @@ -165,6 +165,10 @@ ShaderMemory::PendingWrite Driver::classifyPendingWrite(std::uint64_t address, s } const auto first = address & ~3ull; const auto limit = address + bytes; + if (Graphics::Recorder::WideLabelIn(first, static_cast(limit - first))) { + reason = &ValidateCounters::syncedLabel; + return Policy::Sync; + } for (std::uint64_t dword = first; dword < limit; dword += 4) { if (Graphics::Recorder::LookupLabelValue(dword, 4, 0).has_value()) { reason = &ValidateCounters::syncedLabel; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp index 492086cc3..dd7c023ba 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp @@ -47,8 +47,12 @@ void Driver::dumpPackets(std::span commands, const std::uin } } -void Driver::validate(std::span commands, std::uint32_t queue, const std::uint32_t* guest) { +void Driver::validate(const Submission& submission, const std::uint32_t* guest) { + const std::span commands = submission.commands; + const auto queue = submission.queue; + std::vector guarded; for (std::size_t cursor = 0; cursor < commands.size();) { + std::erase_if(guarded, [cursor](std::size_t end) { return end <= cursor; }); const auto header = commands[cursor]; if (Pm4::FillerPacket(header)) { ++cursor; continue; } if ((header & 0xc0000000u) != 0xc0000000u) { @@ -65,6 +69,12 @@ void Driver::validate(std::span commands, std::uint32_t que require(count <= commands.size() - cursor, "truncated PM4 packet"); try { Pm4::Validate(commands.subspan(cursor, count), queue); + if (header == FlipPacketHeader && !guarded.empty()) throw std::runtime_error("a flip inside a conditional execution range (conditional flip reservation) is not implemented"); + if (((header >> 8u) & 0xffu) == 0x22u) { + const auto end = submission.conditionalEnds.find(cursor); + if (end == submission.conditionalEnds.end()) throw std::runtime_error("conditional execution range is not made of whole packets of its command buffer"); + guarded.push_back(end->second); + } } catch (const std::exception& error) { dumpPackets(commands, guest); throw std::runtime_error("AGC driver: " + Pm4::Name(header) + " at DWORD " + std::to_string(cursor) + ": " + error.what()); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp index 3ebdecd90..06dcb02cb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "ThreadOwned.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/DeferredLabels.hpp" @@ -97,7 +98,8 @@ void Driver::execute(const Submission& submission) { static const bool profilePackets = std::getenv("APS5_PROFILE_DRAW") != nullptr; - thread_local PacketProfile packetProfile; + thread_local PacketProfile* packetProfileSlot = nullptr; + auto& packetProfile = ShaderRecompiler::ThreadOwned(packetProfileSlot); ++packetProfile.submissions; bumpEpoch(&EpochBumps::submissions); @@ -109,6 +111,7 @@ void Driver::execute(const Submission& submission) { const auto count = Pm4::PacketWords(header); const auto packet = std::span(submission.commands).subspan(cursor, count); const auto opcode = (header >> 8u) & 0xffu; + auto nextCursor = cursor + count; std::shared_lock deviceUse(deviceReplacement, std::defer_lock); if (opcode != 0x3c && opcode != 0x93 && header != RenderingWaitPacketHeader && header != FlipPacketHeader) deviceUse.lock(); @@ -270,6 +273,11 @@ void Driver::execute(const Submission& submission) { } }); }); finishDrawPacket(drawn); + } else if (opcode == 0x22) { + recordQueuedLabelsBeforeRead(submission.queue); + const auto condition = Pm4::ReadCondition(packet); + if (condition == 0) nextCursor = submission.conditionalEnds.at(cursor); + if (traceGpu) std::fprintf(stderr, "[gpu] %.1f queue 0x%x COND_EXEC at DWORD %zu reads 0x%x at 0x%llx: %s %zu dwords\n", TraceMs(), submission.queue, cursor, condition, static_cast(packet[1] | (static_cast(packet[2]) << 32u)), condition == 0 ? "skips" : "executes", submission.conditionalEnds.at(cursor) - cursor - count); } else if (sampleDump && !wroteOnGpu) { dumpSampleCounters(packet[2] | (static_cast(packet[3]) << 32u)); } else if (opcode != 0x42 && opcode != 0x46 && opcode != 0x58) { @@ -282,7 +290,7 @@ void Driver::execute(const Submission& submission) { if (endOfPipeInterrupt && !interruptDeferred) AgcDriverDeliverEopInterrupt(submission.queue); } if (drawPacket || (sampleDump && wroteOnGpu)) Graphics::Recorder::CountRecordedWork(); - cursor += count; + cursor = nextCursor; } static const bool submitAtEnd = std::getenv("APS5_SUBMIT_AT_END") != nullptr; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp index 4dbe52c02..edd9b5b70 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp @@ -11,6 +11,7 @@ namespace AgcDriver::DriverDetail { void Driver::copyCommands(Submission& submission, const std::uint32_t* guest, std::size_t words) { submission.commands.clear(); + submission.conditionalEnds.clear(); std::size_t budget = std::size_t{1} << 26u; copySegment(submission, guest, words, budget); } @@ -18,34 +19,57 @@ void Driver::copyCommands(Submission& submission, const std::uint32_t* guest, st bool Driver::copySegment(Submission& submission, const std::uint32_t* guest, std::size_t words, std::size_t& budget) { require(words <= budget, "command buffer jumps exceed the copy limit (a jump loop?)"); budget -= words; + std::vector> guarded; + const auto reach = [&](std::size_t cursor, std::size_t next) { + std::erase_if(guarded, [&](const auto& range) { + if (range.first != cursor) return false; + submission.conditionalEnds.emplace(range.second, submission.commands.size()); + return true; + }); + for (const auto& range : guarded) require(range.first >= next, "conditional execution range ends inside a packet"); + }; for (std::size_t cursor = 0; cursor < words;) { const auto header = guest[cursor]; - if (Pm4::FillerPacket(header)) { submission.commands.push_back(header); ++cursor; continue; } + if (Pm4::FillerPacket(header)) { + reach(cursor, cursor + 1); + submission.commands.push_back(header); + ++cursor; + continue; + } const auto count = (header & 0xc0000000u) == 0xc0000000u ? Pm4::PacketWords(header) : words - cursor; if ((header & 0xc0000000u) != 0xc0000000u || count > words - cursor) { submission.commands.insert(submission.commands.end(), guest + cursor, guest + words); return false; } + reach(cursor, cursor + count); const auto opcode = (header >> 8u) & 0xffu; if (opcode == 0x3fu) { require(count == 4, "invalid INDIRECT_BUFFER size"); const auto* target = reinterpret_cast(static_cast(guest[cursor + 1] & ~3u) | (static_cast(guest[cursor + 2] & 0xffffu) << 32u)); const std::size_t targetWords = guest[cursor + 3] & 0xfffffu; const bool chain = (guest[cursor + 3] & (1u << 20u)) != 0; + require(!chain || guarded.empty(), "a chained INDIRECT_BUFFER inside a conditional execution range is not implemented"); GuestMemory::CheckRange(target, targetWords * sizeof(std::uint32_t), alignof(std::uint32_t)); - if (copySegment(submission, target, targetWords, budget)) return true; + if (copySegment(submission, target, targetWords, budget)) { + require(guarded.empty(), "a REWIND inside a conditional execution range is not implemented"); + return true; + } if (chain) return false; cursor += count; continue; } + if (opcode == 0x22u && count == 5) guarded.emplace_back(cursor + count + Pm4::ConditionalWords(std::span(guest + cursor, count)), submission.commands.size()); submission.commands.insert(submission.commands.end(), guest + cursor, guest + cursor + count); cursor += count; if (opcode == 0x59u) { + require(guarded.empty(), "a REWIND inside a conditional execution range is not implemented"); submission.rewindTail = guest + cursor; submission.rewindWords = words - cursor; return true; } } + reach(words, words); + require(guarded.empty(), "conditional execution range exceeds its command buffer"); return false; } @@ -104,7 +128,7 @@ void Driver::executeRewindTail(const Submission& stalled) { Submission tail{}; tail.queue = stalled.queue; copyCommands(tail, stalled.rewindTail, stalled.rewindWords); - validate(tail.commands, tail.queue, stalled.rewindTail); + validate(tail, stalled.rewindTail); waitForFlipRoom(tail); { std::lock_guard lock(mutex); @@ -135,7 +159,7 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue) { copyCommands(submission, descriptor.addr, descriptor.dw_num); } const auto copied = profile ? std::chrono::steady_clock::now() : start; - validate(submission.commands, queue, descriptor.addr); + validate(submission, descriptor.addr); waitForFlipRoom(submission); static const bool trace = std::getenv("APS5_TRACE_GPU") != nullptr; if (trace) std::fprintf(stderr, "[gpu] %.1f submit queue=0x%x dwords=%zu at %p\n", TraceMs(), queue, submission.commands.size(), static_cast(descriptor.addr)); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index 1c5e6123d..abd485168 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -77,6 +77,22 @@ std::shared_ptr SourceHandleFor(const Shad return handle; } +alignas(256) static const std::uint32_t NullPixelCode[64] = {0xbf810000u}; +static const Shader NullPixelShader = [] { + Shader shader{}; + shader.file_header = 0x34333231u; + shader.version = 0x18u; + shader.code = NullPixelCode; + shader.header_size = sizeof(Shader); + shader.shader_size = sizeof(NullPixelCode); + shader.type = 1; + return shader; +}(); + +std::uint64_t NullPixelProgramAddress() { + return reinterpret_cast(NullPixelCode); +} + void Driver::RegisterShader(const Shader* shader) { CheckFailure(); GuestMemory::CheckRange(shader, sizeof(Shader), alignof(Shader)); @@ -107,6 +123,13 @@ void Driver::RegisterShader(const Shader* shader) { if (shaders == nullptr) shaders = std::make_shared(); else if (shaders.use_count() != 1) shaders = std::make_shared(*shaders); shaders->insert_or_assign(address, std::make_shared(std::move(snapshot))); + if (shaders->find(NullPixelProgramAddress()) == shaders->end()) { + ShaderSnapshot null{NullPixelProgramAddress(), reinterpret_cast(&NullPixelShader), NullPixelShader.type, {}, {}}; + null.code.assign(std::begin(NullPixelCode), std::end(NullPixelCode)); + null.header.resize(sizeof(Shader)); + std::memcpy(null.header.data(), &NullPixelShader, sizeof(Shader)); + shaders->insert_or_assign(NullPixelProgramAddress(), std::make_shared(std::move(null))); + } } } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp index e8be75566..395c2ef2e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp @@ -2,13 +2,14 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/DeferredLabels.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "ThreadOwned.hpp" #include namespace AgcDriver::DriverDetail { DeferredLabels& deferredLabels() { - static thread_local DeferredLabels deferred; - return deferred; + static thread_local DeferredLabels* deferred = nullptr; + return ShaderRecompiler::ThreadOwned(deferred); } bool DeferLabels() { @@ -91,7 +92,8 @@ void Driver::recordDeferredLabels(VulkanDevice* localDevice, std::uint32_t queue auto& deferred = deferredLabels(); if (deferred.labels.empty()) return; - static thread_local std::vector labels; + static thread_local std::vector* recording = nullptr; + auto& labels = ShaderRecompiler::ThreadOwned(recording); labels.clear(); labels.swap(deferred.labels); struct Clear { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index 0af8293e2..291369842 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "ThreadOwned.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" @@ -169,8 +170,8 @@ struct ThreadMemoryCounters { MemoryCounter& CounterFor(MemoryCounterKind kind) { if (SharedMemoryCounters()) return Profile().shared.counters[kind]; - thread_local ThreadMemoryCounters thread; - return thread.counters.counters[kind]; + thread_local ThreadMemoryCounters* thread = nullptr; + return ShaderRecompiler::ThreadOwned(thread).counters.counters[kind]; } // Every thread's counters, the retired and the shared ones summed: calls, bytes, nanoseconds per kind. diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 9dfdfad7b..a07acee1a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -166,7 +166,7 @@ std::string_view UnsupportedReason(std::uint32_t header) { } } switch (opcode) { - case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x26: case 0x27: + case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x22: case 0x26: case 0x27: case 0x2a: case 0x2d: case 0x2f: case 0x35: case 0x37: case 0x40: case 0x42: case 0x46: case 0x50: case 0x58: case 0x63: case 0x64: case 0x69: case 0x76: case 0x79: case 0x7a: case 0x81: case 0x83: case 0x9f: return {}; @@ -176,7 +176,6 @@ std::string_view UnsupportedReason(std::uint32_t header) { case 0x3a: case 0x8d: return "graphics draw, shader stages and guest render-target materialization are not implemented"; case 0x20: return "GPU query predication is not implemented"; - case 0x22: return "conditional command execution and conditional flip reservation are not implemented"; case 0x33: case 0x3f: return "nested command buffers, branching and nested flip reservation are not implemented"; case 0x3c: case 0x93: return {}; case 0x39: @@ -194,8 +193,11 @@ std::string_view UnsupportedReason(std::uint32_t header) { } } +constexpr std::uint32_t ConditionCachePolicy = 3u << 25u; +constexpr std::uint32_t ConditionalWordsMask = 0x3fffu; constexpr std::uint32_t DmaSourceCachePolicy = 3u << 13u; constexpr std::uint32_t DmaDestinationCachePolicy = 3u << 25u; +constexpr std::uint32_t WriteDataCachePolicy = 3u << 25u; void Validate(std::span packet, std::uint32_t queue) { require(!packet.empty(), "truncated PM4 header"); @@ -296,6 +298,13 @@ void Validate(std::span packet, std::uint32_t queue) { require(packet.size() == 3 || packet.size() == 4, "invalid indirect dispatch size"); require((packet.back() & ~0xa020u) == 0x41u, "indirect dispatch modifiers are not implemented"); break; + case 0x22: + size(5); + require((packet[1] & 3u) == 0, "COND_EXEC reserved address bits are not implemented"); + require(packet[2] <= 0xffffu, "COND_EXEC address bits above 48 are not implemented"); + require((packet[3] & ~(queue == 0 ? 0u : ConditionCachePolicy)) == 0, "COND_EXEC reserved control fields are not implemented"); + require((packet[4] & ~ConditionalWordsMask) == 0, "COND_EXEC reserved count bits are not implemented"); + break; case 0x42: size(2); require(packet[1] == 0, "unsupported PFP_SYNC_ME payload"); break; case 0x46: { require((packet[1] & ~0x73fu) == 0, "unsupported EVENT_WRITE flags or reserved bits"); @@ -380,7 +389,7 @@ void Validate(std::span packet, std::uint32_t queue) { break; case 0x37: { require(packet.size() >= 5, "WRITE_DATA has no data"); - require((packet[1] & ~0x40110f00u) == 0, "WRITE_DATA engine, cache or reserved fields are not implemented"); + require((packet[1] & ~(0x40110f00u | WriteDataCachePolicy)) == 0, "WRITE_DATA engine or reserved fields are not implemented"); const auto destination = (packet[1] >> 8u) & 0xfu; require(destination == 1 || destination == 2 || (queue != 0 && destination == 5), "WRITE_DATA register or GDS destination is not implemented"); require((packet[2] & 3u) == 0, "misaligned WRITE_DATA destination"); @@ -462,6 +471,19 @@ std::size_t WaitAwaitedBytes(std::span packet) { return wide && (packet.size() < 8 || packet[7] != 0) ? 8 : 4; } +std::size_t ConditionalWords(std::span packet) { + require(packet.size() == 5 && ((packet[0] >> 8u) & 0xffu) == 0x22u, "expected COND_EXEC packet"); + return packet[4] & ConditionalWordsMask; +} + +std::uint32_t ReadCondition(std::span packet) { + require(packet.size() == 5 && ((packet[0] >> 8u) & 0xffu) == 0x22u, "expected COND_EXEC packet"); + std::uint32_t value = 0; + const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::Wait); + GuestMemory::Read(address(packet[1], packet[2]), std::as_writable_bytes(std::span(&value, 1)), 4); + return value; +} + std::optional DecodeLabelWrite(std::span packet) { const auto opcode = (packet[0] >> 8u) & 0xffu; if (opcode == 0x49 && packet.size() >= 7) { @@ -537,7 +559,7 @@ bool UsesGpuCacheBarrier(std::span packet) { bool AccessesMemory(std::uint32_t header) { switch ((header >> 8u) & 0xffu) { - case 0x27: case 0x3c: case 0x93: case 0x49: case 0x16: case 0x2d: case 0x35: case 0x24: case 0x25: case 0x2c: case 0x38: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true; + case 0x22: case 0x27: case 0x3c: case 0x93: case 0x49: case 0x16: case 0x2d: case 0x35: case 0x24: case 0x25: case 0x2c: case 0x38: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true; default: return false; } } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index cc606ac60..b040bbb41 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -191,6 +191,7 @@ struct VulkanDevice::State { bool primitiveListRestart = false; bool depthClipControl = false; bool imageViewMinLod = false; + bool pipelineExecutableInfo = false; bool maintenance8 = false; bool depthClamp = false; bool depthBounds = false; @@ -802,7 +803,6 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u state->capabilities.push_back(5283); state->spirvExtensions.push_back("SPV_EXT_mesh_shader"); } - APS5_LOG_OUT("Vulkan features tessellationAvailable=%u mesh=%u depthClip=%u depthRangeUnrestricted=%u", static_cast(state->tessellationShader), static_cast(state->meshShader), static_cast(state->depthClipControl), static_cast(state->depthRangeUnrestricted)); const float priority = 1.0f; VkDeviceQueueCreateInfo queueInfo{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; queueInfo.queueFamilyIndex = family; @@ -823,6 +823,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u enabled.tessellationShader = available.tessellationShader; state->tessellationShader = enabled.tessellationShader == VK_TRUE; if (state->tessellationShader) state->capabilities.push_back(3); + APS5_LOG_OUT("Vulkan features tessellationAvailable=%u mesh=%u depthClip=%u depthRangeUnrestricted=%u", static_cast(state->tessellationShader), static_cast(state->meshShader), static_cast(state->depthClipControl), static_cast(state->depthRangeUnrestricted)); require(available.samplerAnisotropy && available.textureCompressionBC, "device lacks sampler anisotropy or BC texture compression support required for texture sampling"); enabled.samplerAnisotropy = VK_TRUE; enabled.textureCompressionBC = VK_TRUE; @@ -957,6 +958,20 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u } else { std::fprintf(stderr, "[gpu] timeline semaphores unavailable or disabled; drains wait under the GPU mutex\n"); } + VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR pipelineFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_EXECUTABLE_PROPERTIES_FEATURES_KHR}; + const auto pipelineStats = std::getenv("APS5_PIPELINE_STATS"); + if (pipelineStats && std::string_view(pipelineStats) == "1") { + Graphics::Require(hasExtension(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME), "APS5_PIPELINE_STATS requires VK_KHR_pipeline_executable_properties"); + VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &pipelineFeatures}; + state->InstanceFunction("vkGetPhysicalDeviceFeatures2")(selected, &features); + Graphics::Require(pipelineFeatures.pipelineExecutableInfo == VK_TRUE, "APS5_PIPELINE_STATS requires pipelineExecutableInfo"); + state->pipelineExecutableInfo = true; + deviceExtensions.push_back(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); + deviceInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + deviceInfo.ppEnabledExtensionNames = deviceExtensions.data(); + pipelineFeatures.pNext = byteFeatures.pNext; + byteFeatures.pNext = &pipelineFeatures; + } bdaFeatures.pNext = &byteFeatures; deviceInfo.pNext = &bdaFeatures; check(state->InstanceFunction("vkCreateDevice")(selected, &deviceInfo, nullptr, &state->device), "vkCreateDevice"); @@ -2305,6 +2320,7 @@ Graphics::Context VulkanDevice::buildContext() const { context.descriptorIndexing = state->descriptorIndexing; context.primitiveListRestart = state->primitiveListRestart; context.imageViewMinLod = state->imageViewMinLod; + context.pipelineExecutableInfo = state->pipelineExecutableInfo; return context; } @@ -3309,6 +3325,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec layoutInfo.pPushConstantRanges = pushStages != 0 ? &push : nullptr; check(state->DeviceFunction("vkCreatePipelineLayout")(state->device, &layoutInfo, nullptr, &objects->layout), "vkCreatePipelineLayout"); VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; + pipelineInfo.flags = context.pipelineExecutableInfo ? VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR : 0; pipelineInfo.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; pipelineInfo.stage.module = objects->module; @@ -3319,6 +3336,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec pipelineInfo.layout = objects->layout; if (profile && shader.spirv.size() > 100000) std::fprintf(stderr, "[dispatch] creating a pipeline for %zu SPIR-V words (program 0x%llx)\n", shader.spirv.size(), static_cast(programAddress)); check(state->DeviceFunction("vkCreateComputePipelines")(state->device, context.pipelineCache, 1, &pipelineInfo, nullptr, &objects->pipeline), "vkCreateComputePipelines"); + Graphics::LogPipelineStatistics_nid_no_patch(context, objects->pipeline); timing.Mark("pipeline_create"); if (pipelineKey != 0) { // Find-or-insert: objects another dispatch of the variant mapped meanwhile serve this diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp index 99f77ec86..238c05424 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp @@ -135,6 +135,7 @@ struct Context { bool descriptorIndexing = false; bool primitiveListRestart = false; bool imageViewMinLod = false; + bool pipelineExecutableInfo = false; template TFunction Function(const char* name) const { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp index b09d3168c..84583699b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp @@ -61,6 +61,7 @@ struct GuestTextureResource { std::uint64_t dccAddress = 0; bool dccAlphaOnMsb = false; std::uint32_t minLod = 0; + std::uint32_t allocatedMipCount = 0; }; float EffectiveMinLod(const GuestTextureResource& resource); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp index 6f54f0fff..fe55c0dce 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp @@ -8,6 +8,8 @@ namespace AgcDriver::Graphics { struct VertexInputLayout; +void LogPipelineStatistics_nid_no_patch(const Context& context, VkPipeline pipeline); + // The attachments of one render pass instance. Work recorded with it references the handle until // the batch completes, so a recorded draw keeps the object (Recorder::Keep) like its pipeline. class Framebuffer { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index 6e3c1ddca..5c23f7365 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -235,6 +235,7 @@ public: // Entries leave the table with their batch (a memory bound only). The note also enters the // label's range as a pending write of the open batch (the flush hook syncs CPU reads), marked // as the label's own so it does not count as an overwrite of the entry. + static constexpr std::size_t LabelTableBytes = 64; void NoteLabel(std::uint64_t address, std::span bytes, std::uint64_t stamp, std::uint32_t queue); // Late rule: an entry whose stamp is not newer than the wait's submission is still the value // memory will hold, unless a CPU store touched the dword since it was recorded (the table @@ -285,6 +286,7 @@ public: static std::optional LookupLabel(std::uint64_t address, std::size_t bytes, std::uint64_t afterStamp, LabelRefusal* refusal = nullptr); // The value alone, for callers asking whether any label is pending in a dword (afterStamp 0). static std::optional LookupLabelValue(std::uint64_t address, std::size_t bytes, std::uint64_t afterStamp); + static bool WideLabelIn(std::uint64_t address, std::size_t bytes); // A label a queue worker decoded but has not recorded yet (Driver.cpp DeferredLabels): it // enters the table with no batch, and only a lookup made on the noting thread (a WAIT_REG_MEM // of the same queue) takes its value, since that queue's later work follows the label in queue @@ -520,6 +522,7 @@ private: std::uint64_t readGeneration = 0; // Dword addresses this batch noted in the label table (removed when it finishes). std::vector labelDwords; + std::vector::iterator> wideLabels; // Labels stored by this batch's completion actions (see AfterCompletions); subtracted from // the lock-free pending count when the batch finishes, whether or not its completions ran. std::uint32_t completionLabelCount = 0; @@ -641,6 +644,13 @@ private: PFN_vkEndCommandBuffer endCommandBuffer = nullptr; PFN_vkQueueSubmit queueSubmit = nullptr; PFN_vkCmdPipelineBarrier cmdPipelineBarrier = nullptr; + PFN_vkCmdBeginQuery cmdBeginQuery = nullptr; + PFN_vkCmdEndQuery cmdEndQuery = nullptr; + PFN_vkCmdResetQueryPool cmdResetQueryPool = nullptr; + PFN_vkCmdCopyQueryPoolResults cmdCopyQueryPoolResults = nullptr; + PFN_vkCmdBindPipeline cmdBindPipeline = nullptr; + PFN_vkCmdPushConstants cmdPushConstants = nullptr; + PFN_vkCmdDispatch cmdDispatch = nullptr; template TFunction function(TFunction resolved, const char* name) const { return resolved != nullptr ? resolved : context.Function(name); @@ -709,6 +719,9 @@ private: // The queued entries (batch always null), by dword; a lookup on the noting queue's thread // prefers them (its newest store in program order), every other lookup sees `labels` alone. std::map queuedLabels; + std::multimap wideLabels; + std::uint64_t wideLabelBytes = 0; + bool wideLabelInLocked(std::uint64_t address, std::size_t bytes) const; // labels.size(), readable without the table mutex: a noted write skips the mutex while the // table is empty (most of the time between label groups). std::atomic recordedLabels{0}; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp index df16329a9..d54fba676 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp @@ -10,7 +10,7 @@ namespace AgcDriver::Graphics { -ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& context, const std::array& exportMappings); +ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& context, const std::array& exportMappings, bool nullProgram = false); ShaderRecompiler::ShaderComputeStageInfo DecodeComputeStageInfo(const Registers& shader); // A guest range DecodeVertexStageInfo read (an attribute word, a vertex V#) with the bytes as read: // the draw cache validates them by value with the stage's capture (design_cpu_final rule RD). diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 205551744..69d086ca4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -411,6 +411,7 @@ private: std::vector storageKeys; std::vector storageFirstLayer; std::vector storageWritten; + std::vector storageAtomic; std::vector> samplers; bool reusable = false; std::vector directRegions; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index a32309b7c..8367436d9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -50,6 +50,7 @@ struct ColorTarget { std::uint32_t slot = 0; std::uint32_t depth = 1; std::uint32_t depthSlice = 0; + std::uint32_t exportIndex = 0; }; struct DepthTarget { @@ -110,6 +111,8 @@ std::optional DecodeColorMetadataPass(const QueueState& queue // this precheck covers, evaluated without exceptions before the draw is decoded; empty when they // pass (DecodeState still checks everything). A register a rule needs that is absent is no verdict. std::string DrawRejection(const QueueState& queue, bool indexed); +bool PixelProgramUnset(const QueueState& queue); +std::string NullPixelProgramRejection(const QueueState& queue); // The recording facade of the draw decoders (design_cpu_final M8, step 8a): every register read // of DecodeShaderStages, DecodeState, DrawRejection, DecodePixelStageInfo and Driver::draw's diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index d21498788..198939b7b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -114,6 +114,7 @@ public: // so successive mip writes of a chain share one image and one write-back. VkImageView View(std::uint32_t mip); VkImageView FirstLayerView(std::uint32_t mip); + VkImageView AtomicView(std::uint32_t mip, bool firstLayer); // Render targets live in the same images: draws attach mip 0 through a view of the color // buffer's format and mark the image dirty like a storage write. bool Attachable() const { return attachable; } @@ -308,6 +309,27 @@ private: // The windows moving the surface-relative runs: whole tile blocks of thin tiled mips, a tail // mip whole (its block holds every tail level), in slice order. std::vector sliceWindows(std::span> runs) const; + // The bytes of the surface-relative runs no element of the surface holds (the padding of partly + // covered tile blocks and of linear rows, tail blocks, the bytes between mips; every byte of a + // thick surface), ascending: a write-back keeps the guest's bytes there. + std::vector> uncoveredBytes(std::span> runs) const; + // The seed of a write-back's tiled scratch where its copies would carry bytes no element holds: + // those bytes' current contents (a fresh unit shadow's, else the import's), copied into the + // scratch before the retile, so the copies out of the scratch store them unchanged. `copied` + // lists the surface-relative [begin, end) ranges the write-back copies out of the scratch, each + // with the scratch offset of its begin. + struct CopiedBytes { + std::uint64_t begin; + std::uint64_t end; + std::uint64_t scratch; + }; + struct PaddingSeeds { + std::vector>> copies; + std::vector> slabs; + // Guest ranges read from the import. + std::vector> importReads; + }; + PaddingSeeds paddingSeeds(const HostImport& import, std::vector copied) const; // The direct upload and GPU-direct write-back of the runs through their windows (block units); // both return the bytes moved. The upload reads each unit from its unit shadow while fresh, // else from the import (`discard`: the image has no layout yet, a whole-surface first upload). @@ -373,7 +395,7 @@ private: bool clearByKeysFill(DccKeys keys, std::uint8_t key); bool overlaps(std::uint64_t address, std::size_t bytes) const; bool pendingUnitInside(std::uint64_t address, std::size_t bytes) const; - VkImageView createView(std::uint32_t mip, bool firstLayer = false) const; + VkImageView createView(std::uint32_t mip, bool firstLayer, VkFormat format) const; void release() noexcept; Context context; @@ -421,6 +443,7 @@ private: std::uint32_t defaultMip = 0; std::map extraViews; std::map firstLayerViews; + std::map, VkImageView> atomicViews; bool attachable = false; std::map, VkImageView> attachmentViews; VkFormat storageFormat = VK_FORMAT_UNDEFINED; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp index 691baa8aa..b83d4f4f8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace AgcDriver::Graphics { @@ -34,10 +35,15 @@ std::array ThickBlockExtent(TextureTileMode tileMode, std::uin // (the detiler's blockExtent). A mip's tiled bytes are its blocks in row-major order, blocksPerRow per // row (TileMipLayout), except the tail mips, which share one block. Throws for linear tiling. std::array ThinBlockLayout(TextureTileMode tileMode, std::uint32_t bytesPerElement); +// The bytes of a thin mip (mip-relative [begin, end), ascending and merged) that hold one of its +// elements each: its whole tile blocks (every element of the block inside the mip), or its rows +// without the pitch padding when linear. A tail mip has none (its block also holds the other tail +// levels and their padding). A retile of the mip writes every byte of these and no other. +std::vector> CoveredMipBytes(TextureTileMode tileMode, std::uint32_t bytesPerElement, const TileMipLayout& mip); -// 3D surface. Each slab holds the whole mip chain, smallest level first. Depth slice z of a level is detiled -// from slab z / blockDepth (slabBytes apart) with the swizzle's slice input set to z, into linear -// slices sliceLinearBytes apart. +// 3D surface. Each slab holds the whole mip chain: the mip tail block, if any, then the other levels from the +// smallest to level 0. Depth slice z of a level is detiled from slab z / blockDepth (slabBytes apart) with the +// swizzle's slice input set to z, into linear slices sliceLinearBytes apart. struct ThickLayout { std::vector mips; std::uint32_t depth; @@ -68,6 +74,7 @@ struct SurfaceGeometry { bool HasLayer(std::uint32_t level, std::uint32_t layer) const { return imageDepth <= 1 || layer < std::max(imageDepth >> level, 1u); } }; SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor); +bool LevelsFitAllocation(const GuestTextureResource& surface, std::uint32_t levels); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp index b69b285fc..7748a0e9d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp @@ -100,7 +100,8 @@ bool AnyShadowedOverlaps(std::span // from one buffer (a slab while the unit is fresh, else the import) at `offset` (byte x of the run // is at offset + (x - begin)), split at unit freshness and slab boundaries with one tracker query // over the span. A tail block (surface-relative, every unit of it) must have one source: a mixed -// one is published (TailMip) and read from the import. +// one is published (TailMip) and read from the import. `countReads` false: not counted as detiled +// bytes (a write-back reading the current bytes of its padding). struct ShadowRun { std::uint64_t begin; std::uint64_t end; @@ -109,7 +110,7 @@ struct ShadowRun { bool shadow; std::shared_ptr slab; }; -std::vector ShadowSources(const Context& context, const HostImport& import, std::uint64_t surfaceBase, std::span> runs, std::span> tailBlocks); +std::vector ShadowSources(const Context& context, const HostImport& import, std::uint64_t surfaceBase, std::span> runs, std::span> tailBlocks, bool countReads = true); // The slab destination of a retile copy [begin, end) (guest addresses inside one slab: the caller // splits at SlabBoundary), making the slab under the budget; nullopt when refused (the caller writes diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp index 22bc67a98..605809a50 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp @@ -262,7 +262,7 @@ void BdaResources::CheckFault() const { ShaderRecompiler::BdaAbi::Range range{}; std::memcpy(&range, bytes.data() + sizeof(header) + index * sizeof(range), sizeof(range)); if (report.address < range.begin || report.address >= range.end) continue; - message << std::hex << "; the store hit 0x" << range.begin << "+0x" << range.end - range.begin << ", read-only in the BDA table: stores through GPU-selected descriptors reach only writable ranges imported in place, not ones served by a mirror or a copy (past APS5_HOST_IMPORT_MIB, or refused by the driver)"; + message << std::hex << "; the store hit 0x" << range.begin << "+0x" << range.end - range.begin << ", read-only in the BDA table: stores through GPU-selected descriptors and FLAT/GLOBAL addresses reach only writable ranges imported in place, not ones served by a mirror or a copy (past APS5_HOST_IMPORT_MIB, or refused by the driver)"; break; } } @@ -276,7 +276,7 @@ namespace AgcDriver::Graphics { void BdaResources::markWrittenPages() const { namespace Abi = ShaderRecompiler::BdaAbi; auto* words = reinterpret_cast(fault->Bytes().data()); - Require(words[Abi::WrittenOverflowWord] == 0, "more than " + std::to_string(Abi::WrittenPageSlots) + " pages stored to through GPU-selected buffer descriptors in one use are not implemented"); + Require(words[Abi::WrittenOverflowWord] == 0, "more than " + std::to_string(Abi::WrittenPageSlots) + " pages stored to through the BDA table in one use are not implemented"); bool any = false; for (std::uint32_t slot = 0; slot < Abi::WrittenPageSlots; ++slot) { const auto page = words[Abi::WrittenSlotsWord + slot]; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 3ca3ffd1f..75d874fec 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -1312,7 +1312,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam for (const auto view : record.targetViews) mix(reinterpret_cast(view)); if (state.blends.size() != state.colors.size()) { mix(state.blends.size()); - for (const auto& color : state.colors) mix(color.slot); + for (const auto& color : state.colors) mix(color.exportIndex); } mix(state.renderExtent.width); mix(state.renderExtent.height); @@ -1558,7 +1558,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& } } timer.phase(PhaseReadTarget); - binding.target = std::make_unique(context, color, state.blends.at(color.slot).blendEnable != 0); + binding.target = std::make_unique(context, color, state.blends.at(color.exportIndex).blendEnable != 0); targetViews.push_back(binding.target->View()); } if (state.depth) targetViews.push_back(DepthSurfaceView(context, *state.depth)); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index da3e6332a..9c08d1a48 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "ThreadOwned.hpp" #include "prx/libSceAgcDriver/Graphics/include/BdaResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" @@ -671,7 +672,8 @@ void refreshMirror(ImageMirror& mirror, std::uint64_t address, std::uint64_t byt void refreshHeapMirrors(std::vector& mirrors, std::vector& blocks) { constexpr std::uint64_t block = 65536; std::sort(mirrors.begin(), mirrors.end(), [](const ImageMirror* left, const ImageMirror* right) { return left->base < right->base; }); - thread_local std::vector changed; + thread_local std::vector* changedSlot = nullptr; + auto& changed = ShaderRecompiler::ThreadOwned(changedSlot); for (std::size_t first = 0; first < mirrors.size();) { auto last = first + 1; while (last < mirrors.size() && mirrors[last]->base == mirrors[last - 1]->base + mirrors[last - 1]->bytes) ++last; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp index fc8dfba9a..a9954b1da 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp @@ -1,6 +1,7 @@ #include #include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" #include "prx/libSceAgcDriver/Graphics/include/Context.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" #include #include #include @@ -129,10 +130,9 @@ GuestTextureResource DecodeTextureResource(std::span words) } catch (const std::exception& error) { throw std::runtime_error(std::string(error.what()) + describe()); } - Require(baseLevel <= maxMip, "guest texture descriptor starts past the surface's last mip level" + describe()); // Views may name levels past MAX_MIP (a 512x512 view through 1x1 over a 9-level surface); the // hardware never addresses them, so the view ends at the surface's last level. - lastLevel = std::min(lastLevel, maxMip); + if (baseLevel <= maxMip) lastLevel = std::min(lastLevel, maxMip); // XOR swizzles fold a pipe/bank XOR into the low address bits; only unmodified 64 KiB bases are modeled. Require(XorSwizzleMode(tileMode) == 0 || (baseAddress & 0xffffu) == 0, "guest texture descriptor combines an XOR swizzle with a pipe/bank XOR base which is not implemented"); @@ -164,6 +164,7 @@ GuestTextureResource DecodeTextureResource(std::span words) result.depthOrLastArray = depth; result.baseArray = baseArray; result.mipCount = maxMip + 1u; + result.allocatedMipCount = maxMip + 1u; result.baseLevel = baseLevel; result.lastLevel = lastLevel; result.tileMode = tileMode; @@ -176,6 +177,10 @@ GuestTextureResource DecodeTextureResource(std::span words) result.dccAddress = metaCompress ? metaAddr << 8u : 0u; result.dccAlphaOnMsb = dccAlphaPos; result.minLod = minLod; + if (baseLevel > maxMip) { + Require(LevelsFitAllocation(result, lastLevel + 1u), "guest texture descriptor starts past the surface's last mip level at levels that would move the surface's own" + describe()); + result.mipCount = lastLevel + 1u; + } return result; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index 718587525..3d56df8c9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -14,6 +14,51 @@ namespace AgcDriver::Graphics { +void LogPipelineStatistics_nid_no_patch(const Context& context, VkPipeline pipeline) { + if (!context.pipelineExecutableInfo) return; + const auto getProperties = context.Function("vkGetPipelineExecutablePropertiesKHR"); + const auto getStatistics = context.Function("vkGetPipelineExecutableStatisticsKHR"); + VkPipelineInfoKHR pipelineInfo{VK_STRUCTURE_TYPE_PIPELINE_INFO_KHR}; + pipelineInfo.pipeline = pipeline; + std::uint32_t count = 0; + Check(getProperties(context.device, &pipelineInfo, &count, nullptr), "vkGetPipelineExecutablePropertiesKHR count"); + std::vector properties(count, {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_PROPERTIES_KHR}); + Check(getProperties(context.device, &pipelineInfo, &count, properties.data()), "vkGetPipelineExecutablePropertiesKHR"); + properties.resize(count); + for (std::uint32_t index = 0; index < properties.size(); ++index) { + VkPipelineExecutableInfoKHR executable{VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INFO_KHR}; + executable.pipeline = pipeline; + executable.executableIndex = index; + count = 0; + Check(getStatistics(context.device, &executable, &count, nullptr), "vkGetPipelineExecutableStatisticsKHR count"); + std::vector statistics(count, {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_STATISTIC_KHR}); + Check(getStatistics(context.device, &executable, &count, statistics.data()), "vkGetPipelineExecutableStatisticsKHR"); + statistics.resize(count); + const auto& property = properties[index]; + std::fprintf(stderr, "[pipeline-stats] executable=%u stages=0x%x subgroup=%u name=%s\n", index, property.stages, property.subgroupSize, property.name); + for (const auto& statistic : statistics) { + std::fprintf(stderr, "[pipeline-stats] executable=%u %s=", index, statistic.name); + switch (statistic.format) { + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_BOOL32_KHR: + std::fprintf(stderr, "%s", statistic.value.b32 ? "true" : "false"); + break; + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_INT64_KHR: + std::fprintf(stderr, "%lld", static_cast(statistic.value.i64)); + break; + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_UINT64_KHR: + std::fprintf(stderr, "%llu", static_cast(statistic.value.u64)); + break; + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_FLOAT64_KHR: + std::fprintf(stderr, "%.17g", statistic.value.f64); + break; + default: + throw std::runtime_error("unsupported pipeline statistic format"); + } + std::fprintf(stderr, " (%s)\n", statistic.description); + } + } +} + Framebuffer::Framebuffer(const Context& context, VkRenderPass renderPass, std::span targets, VkExtent2D extent) : context(context) { // Cached objects outlive their device's teardown; they must not keep its buffer pool alive past it. this->context.bufferPool.reset(); @@ -49,7 +94,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput // A cached pipeline may outlive its device's teardown (see ClearCachedPipelines); it must not keep // the buffer pool, which is reset with the device, alive past it. this->context.bufferPool.reset(); - Require(state.blends.size() == (state.colors.empty() ? 0u : state.colors.back().slot + 1u) && state.colors.size() <= state.blends.size(), "blend states do not match decoded color state"); + Require(state.blends.size() == (state.colors.empty() ? 0u : state.colors.back().exportIndex + 1u) && state.colors.size() <= state.blends.size(), "blend states do not match decoded color state"); Require(state.blends.size() <= context.limits.maxColorAttachments, "color targets exceed device attachment limits"); Require(state.hasColorTarget || (context.limits.framebufferNoAttachmentsSampleCounts & VK_SAMPLE_COUNT_1_BIT) != 0, "device does not support single-sample rendering without attachments"); Require(!depthBounds || context.depthBounds, "device does not support the depth bounds test"); @@ -106,7 +151,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput color.initialLayout = attachmentLayout; color.finalLayout = attachmentLayout; colors.push_back(color); - references.at(state.colors[index].slot) = {index, attachmentLayout}; + references.at(state.colors[index].exportIndex) = {index, attachmentLayout}; } VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; @@ -177,6 +222,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput blend.pAttachments = state.blends.empty() ? nullptr : state.blends.data(); std::copy(state.blendConstants.begin(), state.blendConstants.end(), blend.blendConstants); VkGraphicsPipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; + pipelineInfo.flags = context.pipelineExecutableInfo ? VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR : 0; pipelineInfo.stageCount = static_cast(stages.size()); pipelineInfo.pStages = stages.data(); VkPipelineTessellationStateCreateInfo tessellation{VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO}; @@ -195,6 +241,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput pipelineInfo.layout = layout; pipelineInfo.renderPass = renderPass; Check(context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines"); + LogPipelineStatistics_nid_no_patch(context, pipeline); } catch (...) { release(); throw; @@ -355,7 +402,7 @@ std::vector pipelineKey(const Context& context, const State& state, c append(key, state.colors.size()); for (const auto& color : state.colors) append(key, color.format); if (state.blends.size() != state.colors.size()) { - for (const auto& color : state.colors) append(key, color.slot); + for (const auto& color : state.colors) append(key, color.exportIndex); } append(key, state.depth.has_value()); if (state.depth) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index 474ac0976..624821852 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -1074,6 +1074,13 @@ Recorder::Recorder(const Context& context, bool timelineSemaphores) : context(co endCommandBuffer = context.Function("vkEndCommandBuffer"); queueSubmit = context.Function("vkQueueSubmit"); cmdPipelineBarrier = context.Function("vkCmdPipelineBarrier"); + cmdBeginQuery = context.Function("vkCmdBeginQuery"); + cmdEndQuery = context.Function("vkCmdEndQuery"); + cmdResetQueryPool = context.Function("vkCmdResetQueryPool"); + cmdCopyQueryPoolResults = context.Function("vkCmdCopyQueryPoolResults"); + cmdBindPipeline = context.Function("vkCmdBindPipeline"); + cmdPushConstants = context.Function("vkCmdPushConstants"); + cmdDispatch = context.Function("vkCmdDispatch"); } if (!timelineSemaphores) return; // The timeline starts at 0 and every Submit signals its serial (1, 2, ...): a value that only @@ -1208,6 +1215,20 @@ std::optional Recorder::LookupLabel(std::uint64_t address, s return labelTableOwner->lookupLabel(address, bytes, afterStamp, refusal); } +bool Recorder::WideLabelIn(std::uint64_t address, std::size_t bytes) { + std::lock_guard tableLock(labelTableMutex); + return labelTableOwner != nullptr && labelTableOwner->wideLabelInLocked(address, bytes); +} + +bool Recorder::wideLabelInLocked(std::uint64_t address, std::size_t bytes) const { + if (wideLabels.empty() || bytes == 0) return false; + const auto end = address + bytes; + for (auto it = wideLabels.lower_bound(address >= wideLabelBytes ? address - wideLabelBytes + 1 : 0); it != wideLabels.end() && it->first < end; ++it) { + if (it->second > address) return true; + } + return false; +} + std::optional Recorder::LookupLabelValue(std::uint64_t address, std::size_t bytes, std::uint64_t afterStamp) { const auto hit = LookupLabel(address, bytes, afterStamp); if (!hit.has_value()) return std::nullopt; @@ -1837,7 +1858,7 @@ std::uint32_t Recorder::beginTiming(std::uint64_t key) { return NoTiming; } } - context.Function("vkCmdResetQueryPool")(commands, open->queries, 0, queryCount); + function(cmdResetQueryPool, "vkCmdResetQueryPool")(commands, open->queries, 0, queryCount); } if (open->timedKeys.size() >= queryCount / 2) { timingDropped.fetch_add(1, std::memory_order_relaxed); @@ -1926,7 +1947,7 @@ bool Recorder::gpuSampleCounter() { void Recorder::endSamples(Batch& batch) { if (!batch.sampleActive) return; - context.Function("vkCmdEndQuery")(batch.commands, batch.samples, 0); + function(cmdEndQuery, "vkCmdEndQuery")(batch.commands, batch.samples, 0); batch.sampleActive = false; if (sampleCounterState > 0 && batch.samplesDrawn) pendingSamples.push_back({batch.samplePool, batch.samples}); batch.samplesDrawn = false; @@ -1934,10 +1955,10 @@ void Recorder::endSamples(Batch& batch) { void Recorder::foldSamples(Batch& batch, VkDeviceAddress target) { const auto commands = batch.commands; - const auto copy = context.Function("vkCmdCopyQueryPoolResults"); - const auto bind = context.Function("vkCmdBindPipeline"); - const auto push = context.Function("vkCmdPushConstants"); - const auto dispatch = context.Function("vkCmdDispatch"); + const auto copy = function(cmdCopyQueryPoolResults, "vkCmdCopyQueryPoolResults"); + const auto bind = function(cmdBindPipeline, "vkCmdBindPipeline"); + const auto push = function(cmdPushConstants, "vkCmdPushConstants"); + const auto dispatch = function(cmdDispatch, "vkCmdDispatch"); std::size_t done = 0; do { const auto count = static_cast(std::min(SampleFoldCapacity, pendingSamples.size() - done)); @@ -2011,15 +2032,15 @@ bool Recorder::RecordMeshArguments(VkCommandBuffer commands, VkDeviceAddress rec } if (meshArgumentState < 0) return false; recordBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); - context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, meshArgumentPipeline); + function(cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, meshArgumentPipeline); struct { VkDeviceAddress record; VkDeviceAddress arguments; std::array rules; } parameters{record, arguments, {rules[0], rules[1], rules[2], rules[3], rules[4], rules[5], rules[6], 0u}}; static_assert(sizeof(parameters) == PushBytes); - context.Function("vkCmdPushConstants")(commands, meshArgumentLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, PushBytes, ¶meters); - context.Function("vkCmdDispatch")(commands, 1, 1, 1); + function(cmdPushConstants, "vkCmdPushConstants")(commands, meshArgumentLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, PushBytes, ¶meters); + function(cmdDispatch, "vkCmdDispatch")(commands, 1, 1, 1); recordBarrier(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_MESH_SHADER_BIT_EXT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_SHADER_READ_BIT); CountBarriers(CommandClass::Draw, 2); return true; @@ -2049,8 +2070,8 @@ void Recorder::beginSamples(Batch& batch) { throw; } } - context.Function("vkCmdResetQueryPool")(batch.commands, batch.samples, 0, 1); - context.Function("vkCmdBeginQuery")(batch.commands, batch.samples, 0, context.occlusionQueryPrecise ? VK_QUERY_CONTROL_PRECISE_BIT : 0u); + function(cmdResetQueryPool, "vkCmdResetQueryPool")(batch.commands, batch.samples, 0, 1); + function(cmdBeginQuery, "vkCmdBeginQuery")(batch.commands, batch.samples, 0, context.occlusionQueryPrecise ? VK_QUERY_CONTROL_PRECISE_BIT : 0u); batch.sampleActive = true; batch.samplesDrawn = false; } @@ -2501,7 +2522,16 @@ void Recorder::NoteLabel(std::uint64_t address, std::span bytes ensureOpen(); // The table entry before the write note: the note bumps the write generation a poller // watches, and a poller that sees the bump then finds the label without the GPU mutex. - noteLabelOn(*open, address, bytes, stamp, queue); + const bool tabled = bytes.size() <= LabelTableBytes; + if (tabled) { + noteLabelOn(*open, address, bytes, stamp, queue); + } else { + std::lock_guard tableLock(labelTableMutex); + labels.erase(labels.lower_bound(address), labels.lower_bound(address + bytes.size())); + recordedLabels.store(labels.size(), std::memory_order_relaxed); + open->wideLabels.push_back(wideLabels.emplace(address, address + bytes.size())); + wideLabelBytes = std::max(wideLabelBytes, bytes.size()); + } if (activeRecorder == this && pendingLabelSince.load(std::memory_order_relaxed) == NoPendingLabel) { pendingLabelSince.store(std::chrono::steady_clock::now().time_since_epoch().count(), std::memory_order_release); } @@ -2524,6 +2554,7 @@ bool Recorder::PendingLabelIn(std::uint64_t address, std::size_t bytes) const { if (bytes == 0) return false; const auto end = address + bytes; if (const auto first = labels.lower_bound(address); first != labels.end() && first->first < end) return true; + if (wideLabelInLocked(address, bytes)) return true; // A queued label of another queue is unordered against the caller on hardware (nothing that // queue recorded could have satisfied a wait yet); it is still reported, since the table mutex // is held anyway and a caller deciding a CPU store wants the conservative answer. @@ -3256,15 +3287,17 @@ void Recorder::finish(std::unique_ptr batch, bool wait, int source) { } // The batch's label entries leave the table (a later label to the same dword already replaced // its entry and belongs to another batch). Correctness never depended on this removal. - if (!batch->labelDwords.empty()) { + if (!batch->labelDwords.empty() || !batch->wideLabels.empty()) { std::lock_guard tableLock(labelTableMutex); for (const auto dword : batch->labelDwords) { const auto found = labels.find(dword); if (found != labels.end() && found->second.batch == batch.get()) labels.erase(found); } + for (const auto entry : batch->wideLabels) wideLabels.erase(entry); recordedLabels.store(labels.size(), std::memory_order_relaxed); } batch->labelDwords.clear(); + batch->wideLabels.clear(); release(*batch); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp index 788458ce7..44ee5932f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp @@ -25,6 +25,7 @@ constexpr std::uint32_t spiPsInputAddr = 0x1B4; constexpr std::uint32_t spiPsInControl = 0x1B6; constexpr std::uint32_t dbShaderControl = 0x203; constexpr std::uint32_t spiShaderColFormat = 0x1C5; +constexpr std::uint32_t defaultPixelInputs = 0x2u; std::uint32_t read(const Registers& registers, std::uint32_t offset, RegisterBank bank) { NoteRegisterRead(bank, offset); @@ -37,6 +38,12 @@ std::uint32_t read(const Registers& registers, std::uint32_t offset, RegisterBan return it->second; } +std::uint32_t readOr(const Registers& registers, std::uint32_t offset, RegisterBank bank, std::uint32_t fallback) { + NoteRegisterRead(bank, offset); + const auto it = registers.find(offset); + return it == registers.end() ? fallback : it->second; +} + template T _readHeaderPod(std::span header, std::uint64_t headerAddress, const void* pointer) { if (pointer == nullptr) throw std::runtime_error("AGC graphics: null AGC header pointer"); const auto address = reinterpret_cast(pointer); @@ -84,14 +91,14 @@ ShaderRecompiler::ShaderComputeStageInfo DecodeComputeStageInfo(const Registers& }; } -ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& context, const std::array& exportMappings) { +ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& context, const std::array& exportMappings, bool nullProgram) { const auto inControl = read(context, spiPsInControl, RegisterBank::Context); const auto inputNum = inControl & 0x3Fu; if (inputNum > 32u) { throw std::runtime_error("AGC graphics: SPI_PS_IN_CONTROL input count exceeds 32"); } - const auto ena = read(context, spiPsInputEna, RegisterBank::Context); - const auto addr = read(context, spiPsInputAddr, RegisterBank::Context); + const auto ena = nullProgram ? readOr(context, spiPsInputEna, RegisterBank::Context, defaultPixelInputs) : read(context, spiPsInputEna, RegisterBank::Context); + const auto addr = nullProgram ? readOr(context, spiPsInputAddr, RegisterBank::Context, defaultPixelInputs) : read(context, spiPsInputAddr, RegisterBank::Context); const auto activeInputs = ena & addr; using ShaderRecompiler::PixelInput; using ShaderRecompiler::PixelInputBit; @@ -114,7 +121,7 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con if (((shaderControl >> 13u) & 0x3u) != 0) { throw std::runtime_error("AGC graphics: DB_SHADER_CONTROL.CONSERVATIVE_Z_EXPORT is unsupported"); } - const auto colFormat = read(context, spiShaderColFormat, RegisterBank::Context); + const auto colFormat = nullProgram ? readOr(context, spiShaderColFormat, RegisterBank::Context, 0u) : read(context, spiShaderColFormat, RegisterBank::Context); std::array targetOutputMode{}; for (std::uint32_t i = 0; i < 8u; ++i) { targetOutputMode[i] = static_cast((colFormat >> (4u * i)) & 0xFu); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 5169e3ef8..c1fff993b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" +#include "ThreadOwned.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" @@ -186,14 +187,19 @@ struct LookupRecord { const StorageTexture* source; }; -thread_local std::vector lookupLog; +thread_local std::vector* lookupLogSlot = nullptr; + +std::vector& lookupLog() { + return ShaderRecompiler::ThreadOwned(lookupLogSlot); +} void logLookup(const LookupRecord& record) { // The bound drops the oldest half, never everything: one build's lookups (a few dozen at most) // are the newest records, and a capture must find them even when the bound trips mid-build. constexpr std::size_t bound = 1024; - if (lookupLog.size() >= bound) lookupLog.erase(lookupLog.begin(), lookupLog.begin() + bound / 2); - lookupLog.push_back(record); + auto& log = lookupLog(); + if (log.size() >= bound) log.erase(log.begin(), log.begin() + bound / 2); + log.push_back(record); } std::shared_ptr cachedStorageTexture(const Context& context, std::span words, const GuestTextureResource& resource, std::uint32_t mip, std::uint64_t guestBytes = 0); @@ -511,22 +517,64 @@ std::array SurfaceKey(const Context& context, const GuestTextu return {static_cast(resource.baseAddress), static_cast(resource.baseAddress >> 32u), resource.width, resource.height, (resource.depthOrLastArray << 16u) | (resource.mipCount & 0xffffu), (static_cast(resource.tileMode) << 12u) | (static_cast(resource.dimension) << 20u), resource.baseArray, static_cast(StorageFormatForGuest(context, resource.format))}; } +struct ExtendedSurfaces { + std::mutex mutex; + std::map, std::uint32_t> levels; + std::atomic any{false}; +}; + +ExtendedSurfaces& ExtendedChains() { + static ExtendedSurfaces surfaces; + return surfaces; +} + +std::uint32_t AllocatedLevels(const GuestTextureResource& resource) { + return resource.allocatedMipCount != 0 ? resource.allocatedMipCount : resource.mipCount; +} + +GuestTextureResource StorageSurface(const Context& context, const GuestTextureResource& viewed) { + auto& surfaces = ExtendedChains(); + const bool extended = viewed.mipCount > AllocatedLevels(viewed); + if (!extended && !surfaces.any.load(std::memory_order_acquire)) return viewed; + auto surface = viewed; + surface.mipCount = AllocatedLevels(viewed); + const auto identity = SurfaceKey(context, surface); + std::lock_guard lock(surfaces.mutex); + if (extended) { + auto& levels = surfaces.levels[identity]; + levels = std::max(levels, viewed.mipCount); + surfaces.any.store(true, std::memory_order_release); + surface.mipCount = levels; + } else if (const auto it = surfaces.levels.find(identity); it != surfaces.levels.end()) { + surface.mipCount = it->second; + } else { + surface.mipCount = viewed.mipCount; + } + return surface; +} + // `guestBytes` is the surface size when the caller described the surface already (0: described here). -std::shared_ptr cachedStorageTexture(const Context& context, std::span words, const GuestTextureResource& resource, std::uint32_t mip, std::uint64_t guestBytes) { - if (DepthSurfaceAt(resource.baseAddress)) { +std::shared_ptr cachedStorageTexture(const Context& context, std::span words, const GuestTextureResource& viewed, std::uint32_t mip, std::uint64_t guestBytes) { + if (DepthSurfaceAt(viewed.baseAddress)) { char text[112]; - std::snprintf(text, sizeof(text), "AGC graphics: storage image access to depth/stencil surface 0x%llx is not implemented", static_cast(resource.baseAddress)); + std::snprintf(text, sizeof(text), "AGC graphics: storage image access to depth/stencil surface 0x%llx is not implemented", static_cast(viewed.baseAddress)); throw std::runtime_error(text); } static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr; - if (disabled) return std::make_shared(context, *context.detiler, resource, mip); + if (disabled) return std::make_shared(context, *context.detiler, viewed, mip); static_cast(words); const bool profile = LookupOutcomes::Profiled(); auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; auto& counters = TextureCounts(); + const auto resource = StorageSurface(context, viewed); const StorageKey key{context.device, SurfaceKey(context, resource)}; auto& cache = StorageTextures(); std::lock_guard lock(cache.mutex); + if (resource.mipCount > AllocatedLevels(resource)) { + auto allocated = resource; + allocated.mipCount = AllocatedLevels(resource); + if (const auto found = findStorage(cache, {context.device, SurfaceKey(context, allocated)}); found != cache.entries.end()) evictStorage(cache, found); + } auto it = findStorage(cache, key); if (it != cache.entries.end() && MetadataMoved(*it->texture, resource)) { evictStorage(cache, it); @@ -793,7 +841,8 @@ struct ThreadBuildProfile { }; ThreadBuildProfile& ThreadBuilds() { - thread_local ThreadBuildProfile profile; + thread_local ThreadBuildProfile* profileSlot = nullptr; + auto& profile = ShaderRecompiler::ThreadOwned(profileSlot); return profile; } @@ -1007,7 +1056,7 @@ void ShaderResources::buildComplete() { break; case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: write.pImageInfo = images.data() + images.size(); - for (const auto index : binding.imageAllocations) images.push_back({VK_NULL_HANDLE, storageFirstLayer[index] ? storageTextures[index]->FirstLayerView(storageMips[index]) : storageTextures[index]->View(storageMips[index]), VK_IMAGE_LAYOUT_GENERAL}); + for (const auto index : binding.imageAllocations) images.push_back({VK_NULL_HANDLE, storageAtomic[index] ? storageTextures[index]->AtomicView(storageMips[index], storageFirstLayer[index]) : storageFirstLayer[index] ? storageTextures[index]->FirstLayerView(storageMips[index]) : storageTextures[index]->View(storageMips[index]), VK_IMAGE_LAYOUT_GENERAL}); break; case VK_DESCRIPTOR_TYPE_SAMPLER: write.pImageInfo = images.data() + images.size(); @@ -1152,6 +1201,7 @@ std::vector ShaderResources::ContentKey(const CompiledShader& sha packBits(binding.imageWritten); packBits(binding.samplerDepthCompare); packBits(binding.imageDepthCompare); + packBits(binding.imageAtomic); // Read-only elements are bound without a write set: an object built for one written set // must not serve a build with another (the variant implies it, this makes it explicit). packBits(binding.bufferWritten); @@ -1310,7 +1360,8 @@ bool EpochRevalidate() { // ValidatedSurface); the last record of an object is the freshest. void ShaderResources::captureValidation() { const auto record = [](const void* object) -> const LookupRecord* { - for (auto it = lookupLog.rbegin(); it != lookupLog.rend(); ++it) { + const auto& log = lookupLog(); + for (auto it = log.rbegin(); it != log.rend(); ++it) { if (it->object == object) return &*it; } return nullptr; @@ -1319,7 +1370,7 @@ void ShaderResources::captureValidation() { for (std::size_t i = 0; i < textures.size(); ++i) { if (const auto* found = record(textures[i].get())) validatedTextures[i] = {found->resource, found->bytes, found->keys, found->generation, 0, found->source, true}; } - lookupLog.clear(); + lookupLog().clear(); } // Proves every texture and storage image still current without repeating its lookup: exactly the @@ -1350,12 +1401,17 @@ bool ShaderResources::fastRevalidate(std::uint64_t serialBefore, std::span queries; - thread_local std::vector pending; + thread_local std::vector* queriesSlot = nullptr; + auto& queries = ShaderRecompiler::ThreadOwned(queriesSlot); + thread_local std::vector* pendingSlot = nullptr; + auto& pending = ShaderRecompiler::ThreadOwned(pendingSlot); // The element each pending query stands for, in query order. - thread_local std::vector owners; - thread_local std::vector images; - thread_local std::vector scannedKeys; + thread_local std::vector* ownersSlot = nullptr; + auto& owners = ShaderRecompiler::ThreadOwned(ownersSlot); + thread_local std::vector* imagesSlot = nullptr; + auto& images = ShaderRecompiler::ThreadOwned(imagesSlot); + thread_local std::vector* scannedKeysSlot = nullptr; + auto& scannedKeys = ShaderRecompiler::ThreadOwned(scannedKeysSlot); queries.clear(); pending.clear(); owners.clear(); @@ -1628,7 +1684,7 @@ bool ShaderResources::Revalidate(std::span shaders, ProofR // they must hand back the very objects the set's views belong to. The build appended them stage // by stage, binding by binding, so the walk repeats that order. const auto fullWalk = [&] { - lookupLog.clear(); + lookupLog().clear(); std::size_t textureIndex = 0; std::size_t storageIndex = 0; for (const auto& shader : shaders) { @@ -1664,8 +1720,10 @@ bool ShaderResources::Revalidate(std::span shaders, ProofR captureValidation(); return true; }; - thread_local std::vector overlapping; - thread_local std::vector refreshed; + thread_local std::vector* overlappingSlot = nullptr; + auto& overlapping = ShaderRecompiler::ThreadOwned(overlappingSlot); + thread_local std::vector* refreshedSlot = nullptr; + auto& refreshed = ShaderRecompiler::ThreadOwned(refreshedSlot); refreshed.clear(); FastFail reason = FastFail::Count; bool accepted = false; @@ -1711,7 +1769,8 @@ bool ShaderResources::Revalidate(std::span shaders, ProofR // The walk after the refresh, or beside a proof that accepted a foreign overlap, must // find every object in place and current: another object, or an upload (a moved // content version), is a decision the proof got wrong. - thread_local std::vector> versions; + thread_local std::vector>* versionsSlot = nullptr; + auto& versions = ShaderRecompiler::ThreadOwned(versionsSlot); versions.clear(); for (const auto& surface : validatedTextures) { if (surface.source != nullptr) versions.emplace_back(surface.source, surface.source->Version()); @@ -1744,7 +1803,8 @@ bool ShaderResources::Revalidate(std::span shaders, ProofR }; if (EpochRevalidate()) { if (!directRegions.empty() && !(pendingSerialSeen != 0 && pendingSerialSeen == StorageTexture::PendingSerial())) { - thread_local std::vector regions; + thread_local std::vector* regionsSlot = nullptr; + auto& regions = ShaderRecompiler::ThreadOwned(regionsSlot); regions.clear(); for (const auto& region : directRegions) regions.push_back({region.begin, region.end, nullptr, nullptr, false}); StorageTexture::ScanPending(regions); @@ -2505,6 +2565,7 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind storageFirstLayer.push_back(firstLayer); // Images the shader only reads have nothing to store back. storageWritten.push_back(element >= binding.imageWritten.size() || binding.imageWritten[element]); + storageAtomic.push_back(element < binding.imageAtomic.size() && binding.imageAtomic[element]); describedRanges.push_back({"storage", resource.baseAddress, guestBytes, resource.width, resource.height, resource.format, static_cast(resource.tileMode), resource.dccAddress}); item.imageAllocations.push_back(storageTextures.size() - 1); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 5214fd141..c8d156bf5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -70,7 +70,7 @@ constexpr std::uint32_t ScanModeMask = ~2u; constexpr std::uint32_t ScanControlMask = ~0x06003fffu; constexpr std::uint32_t ScreenOffsetMask = ~0x01ff01ffu; // Bits 26/27 (ZCLIP_NEAR/FAR_DISABLE) become depth clamping; bit 19 selects the [0, 1] clip space. -constexpr std::uint32_t ClipControlMask = ~(0x80000u | 0x0c000000u); +constexpr std::uint32_t ClipControlMask = ~(0x80000u | 0x01000000u | 0x0c000000u); // Debug aid: APS5_IGNORE_DEPTH_TEST=1 renders depth- and stencil-tested draws without a depth // target as if their tests always passed (wrong occlusion, but the draws run), so stages that @@ -501,28 +501,34 @@ State DecodeState(const QueueState& queue) { // matters where the shader exports. const auto targetMask = read(cx, 0x8e) & shaderMask; APS5_LOG_OUT_DEBUG("CB_TARGET_MASK=0x%x CB_SHADER_MASK=0x%x", targetMask, shaderMask); - std::uint32_t slotCount = 0; + std::vector exportSlots; for (std::uint32_t slot = 0; slot < 8; ++slot) { - if (((targetMask >> (4u * slot)) & 0xfu) != 0) slotCount = slot + 1; + if (((shaderMask >> (4u * slot)) & 0xfu) != 0) exportSlots.push_back(slot); } const auto written = [&](std::uint32_t slot) { return ((targetMask >> (4u * slot)) & 0xfu) != 0; }; - result.hasColorTarget = slotCount != 0; - APS5_LOG_OUT_DEBUG("hasColorTarget=%u slots=%u", result.hasColorTarget ? 1u : 0u, slotCount); + std::uint32_t exportCount = 0; + for (std::uint32_t index = 0; index < exportSlots.size(); ++index) { + if (written(exportSlots[index])) exportCount = index + 1; + } + result.hasColorTarget = exportCount != 0; + APS5_LOG_OUT_DEBUG("hasColorTarget=%u exports=%u", result.hasColorTarget ? 1u : 0u, exportCount); // CB_COLOR_CONTROL mode 0 disables color writes, which only matters when a target is written. if (const auto colorControl = read(cx, 0x202); !colorControlSupported(colorControl, result.hasColorTarget)) throw std::runtime_error(colorControlMessage(colorControl)); zero(cx, 0x1c4, ~0u, "depth or sample-mask export"); - const auto exportFormat = read(cx, 0x1c5); + const auto exportFormat = result.hasColorTarget ? read(cx, 0x1c5) : 0u; APS5_LOG_OUT_DEBUG("Export format=%u", exportFormat); // SPI_SHADER_POS_FORMAT: POS0 must be a 4-component position; later vectors carry the misc/clip // exports that PA_CL_VS_OUT_CNTL validation above already limits to ignored layer/viewport data. Require((read(cx, 0x1c3) & 0xfu) == 4, "additional position exports are unsupported"); - for (std::uint32_t slot = 0; slot < slotCount; ++slot) { + for (std::uint32_t index = 0; index < exportCount; ++index) { + const auto slot = exportSlots[index]; if (!written(slot)) continue; // Export formats only matter for the targets the draw writes. - const auto slotExport = (exportFormat >> (4u * slot)) & 0xfu; + const auto slotExport = (exportFormat >> (4u * index)) & 0xfu; if (slotExport == 0 || slotExport == 7 || slotExport == 8 || slotExport > 9) throw std::runtime_error("AGC graphics: color export format " + std::to_string(slotExport) + " is unsupported"); - const auto color = DecodeColorBuffer(cx, slot); + auto color = DecodeColorBuffer(cx, slot); + color.exportIndex = index; APS5_LOG_OUT_DEBUG("Color %u address=0x%llx extent=%ux%u bytes=%llu VkFormat=%u", slot, static_cast(color.address), color.extent.width, color.extent.height, static_cast(color.bytes), static_cast(color.format)); if (result.colors.empty()) { result.renderExtent = color.extent; @@ -566,7 +572,7 @@ State DecodeState(const QueueState& queue) { intersect(result.scissor, cx, 0x90, false); if ((read(cx, 0x292) & 2u) != 0) intersect(result.scissor, cx, 0x94, false); APS5_LOG_OUT_DEBUG("Scissor offset=(%d,%d) extent=%ux%u", result.scissor.offset.x, result.scissor.offset.y, result.scissor.extent.width, result.scissor.extent.height); - result.blends.assign(slotCount, VkPipelineColorBlendAttachmentState{}); + result.blends.assign(exportCount, VkPipelineColorBlendAttachmentState{}); for (const auto& color : result.colors) { const auto slot = color.slot; const auto blend = read(cx, 0x1e0 + slot); @@ -595,9 +601,9 @@ State DecodeState(const QueueState& queue) { } for (std::uint32_t i = 0; i < 4; ++i) result.blendConstants[i] = readFloat(cx, 0x105 + i); } - result.blends[slot] = state; + result.blends[color.exportIndex] = state; } - if (!result.colors.empty()) result.blend = result.blends[result.colors.front().slot]; + if (!result.colors.empty()) result.blend = result.blends[result.colors.front().exportIndex]; APS5_LOG_OUT_DEBUG("DecodeState done colorTarget=%u render=%ux%u topology=%u", result.hasColorTarget ? 1u : 0u, result.renderExtent.width, result.renderExtent.height, static_cast(result.topology)); return result; } @@ -606,7 +612,7 @@ std::array ExportMappings(const State& state) { std::array mappings{}; mappings.fill(0xe4u); for (const auto& color : state.colors) { - if (color.slot < mappings.size()) mappings[color.slot] = color.componentMapping; + if (color.exportIndex < mappings.size()) mappings[color.exportIndex] = color.componentMapping; } return mappings; } @@ -770,8 +776,7 @@ std::string DrawRejection(const QueueState& queue, bool indexed) { std::uint32_t targetMask = 0, shaderMask = 0; if (value(cx, 0x8e, targetMask) && value(cx, 0x8f, shaderMask) && value(cx, 0x202, word) && !colorControlSupported(word, (targetMask & shaderMask) != 0)) return colorControlMessage(word); if (auto reason = nonzero(cx, 0x1c4, ~0u, "depth or sample-mask export"); !reason.empty()) return reason; - // The pixel stage decode (ShaderInputState.cpp) reads these after DecodeState and the program - // prepare; a bank without them fails there with this message. + if (PixelProgramUnset(queue)) return NullPixelProgramRejection(queue); for (const auto offset : {0x1b3u, 0x1b4u, 0x1c5u}) { if (find(cx, offset) != cx.end()) continue; char text[64]; @@ -781,6 +786,20 @@ std::string DrawRejection(const QueueState& queue, bool indexed) { return {}; } +bool PixelProgramUnset(const QueueState& queue) { + const auto low = find(queue.shader, 0x008, RegisterBank::Shader); + const auto high = find(queue.shader, 0x009, RegisterBank::Shader); + return low != queue.shader.end() && high != queue.shader.end() && low->second == 0 && high->second == 0; +} + +std::string NullPixelProgramRejection(const QueueState& queue) { + const auto targetMask = find(queue.context, 0x8e); + const auto shaderMask = find(queue.context, 0x8f); + if (targetMask == queue.context.end() || shaderMask == queue.context.end()) return "AGC graphics: a draw without a pixel program needs CB_TARGET_MASK and CB_SHADER_MASK"; + if ((targetMask->second & shaderMask->second) != 0) return "AGC graphics: a draw without a pixel program writes color"; + return {}; +} + std::vector*& RegisterReadLog() { static thread_local std::vector* log = nullptr; return log; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index ea69132a0..2bb762263 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -691,7 +691,7 @@ StorageTexture::StorageTexture(const Context& context, TextureDetiler& detiler, uploadReason = "first"; upload(); defaultMip = mipLevel; - view = createView(mipLevel); + view = createView(mipLevel, false, storageFormat); { auto& live = Live(); std::lock_guard lock(live.mutex); @@ -942,15 +942,18 @@ bool AdjacentGenerationEnabled() { } -VkImageView StorageTexture::createView(std::uint32_t mip, bool firstLayer) const { +VkImageView StorageTexture::createView(std::uint32_t mip, bool firstLayer, VkFormat format) const { Require(mip < descriptor.mipCount, "storage texture mip level is outside the texture"); Require(!firstLayer || descriptor.dimension == TextureDimension::k2DArray, "a first-layer storage view needs a 2D array surface"); const auto viewLayerCount = firstLayer ? 1u : geometry.imageLayers - descriptor.baseArray; + VkImageViewUsageCreateInfo usage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO}; + usage.usage = VK_IMAGE_USAGE_STORAGE_BIT; VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + viewInfo.pNext = format == storageFormat ? nullptr : &usage; viewInfo.image = image; // Storage views address one mip; cube faces are written as array layers. viewInfo.viewType = firstLayer ? VK_IMAGE_VIEW_TYPE_2D : descriptor.dimension == TextureDimension::k1D ? VK_IMAGE_VIEW_TYPE_1D : descriptor.dimension == TextureDimension::k2D ? VK_IMAGE_VIEW_TYPE_2D : descriptor.dimension == TextureDimension::k3D ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D_ARRAY; - viewInfo.format = storageFormat; + viewInfo.format = format; viewInfo.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, mip, 1u, descriptor.baseArray, viewLayerCount}; VkImageView created = VK_NULL_HANDLE; @@ -984,7 +987,7 @@ VkImageView StorageTexture::View(std::uint32_t mip) { if (mip == defaultMip) return view; const auto found = extraViews.find(mip); if (found != extraViews.end()) return found->second; - const auto created = createView(mip); + const auto created = createView(mip, false, storageFormat); extraViews.emplace(mip, created); return created; } @@ -992,11 +995,21 @@ VkImageView StorageTexture::View(std::uint32_t mip) { VkImageView StorageTexture::FirstLayerView(std::uint32_t mip) { const auto found = firstLayerViews.find(mip); if (found != firstLayerViews.end()) return found->second; - const auto created = createView(mip, true); + const auto created = createView(mip, true, storageFormat); firstLayerViews.emplace(mip, created); return created; } +VkImageView StorageTexture::AtomicView(std::uint32_t mip, bool firstLayer) { + if (storageFormat == VK_FORMAT_R32_UINT) return firstLayer ? FirstLayerView(mip) : View(mip); + Require(storageFormat == VK_FORMAT_R32_SINT || storageFormat == VK_FORMAT_R32_SFLOAT, "storage image atomics need a surface of one 32-bit component"); + const auto found = atomicViews.find({mip, firstLayer}); + if (found != atomicViews.end()) return found->second; + const auto created = createView(mip, firstLayer, VK_FORMAT_R32_UINT); + atomicViews.emplace(std::pair{mip, firstLayer}, created); + return created; +} + const char* LookupOutcomes::Name(Kind kind) { static constexpr const char* names[Count] = {"sampled fast hit", "sampled fast miss", "sampled hit view", "sampled hit cleared view", "sampled hit snapshot", "sampled made view", "sampled made snapshot", "storage hit", "storage made", "refresh unchanged", "refresh compared", "upload direct", "upload cpu", "upload clear", "dcc scan", "pending flush"}; return kind < Count ? names[kind] : "?"; @@ -1617,6 +1630,83 @@ std::vector StorageTexture::sliceWindows(std::span< return windows; } +std::vector> StorageTexture::uncoveredBytes(std::span> runs) const { + std::vector> covered; + if (!geometry.thick) { + const auto elementBytes = BytesPerElement(descriptor.format); + for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) { + for (std::uint32_t level = 0; level < descriptor.mipCount; ++level) { + if (!geometry.HasLayer(level, layer)) continue; + const auto sliceBegin = geometry.GuestLayerOffset(layer) + mips[level].tiledOffset; + for (const auto& [begin, end] : CoveredMipBytes(descriptor.tileMode, elementBytes, mips[level])) covered.emplace_back(sliceBegin + begin, sliceBegin + end); + } + } + std::sort(covered.begin(), covered.end()); + } + std::vector> sorted(runs.begin(), runs.end()); + std::sort(sorted.begin(), sorted.end()); + std::vector> uncovered; + const auto add = [&](std::uint64_t begin, std::uint64_t end) { + if (begin >= end) return; + if (!uncovered.empty() && uncovered.back().second >= begin) uncovered.back().second = std::max(uncovered.back().second, end); + else uncovered.emplace_back(begin, end); + }; + std::size_t next = 0; + for (const auto& [runBegin, runEnd] : sorted) { + while (next < covered.size() && covered[next].second <= runBegin) ++next; + auto at = runBegin; + for (auto i = next; i < covered.size() && covered[i].first < runEnd; ++i) { + add(at, std::min(covered[i].first, runEnd)); + at = std::max(at, covered[i].second); + } + add(at, runEnd); + } + return uncovered; +} + +StorageTexture::PaddingSeeds StorageTexture::paddingSeeds(const HostImport& import, std::vector copied) const { + PaddingSeeds seeds; + std::sort(copied.begin(), copied.end(), [](const CopiedBytes& a, const CopiedBytes& b) { return a.begin < b.begin; }); + std::vector> runs; + for (const auto& range : copied) runs.emplace_back(range.begin, range.end); + const auto uncovered = uncoveredBytes(runs); + if (uncovered.empty()) return seeds; + std::vector pieces; + std::vector> pieceRuns; + std::size_t next = 0; + for (const auto& [begin, end] : uncovered) { + while (next < copied.size() && copied[next].end <= begin) ++next; + for (auto i = next; i < copied.size() && copied[i].begin < end; ++i) { + const auto from = std::max(begin, copied[i].begin); + const auto to = std::min(end, copied[i].end); + if (from >= to) continue; + pieces.push_back({from, to, copied[i].scratch + (from - copied[i].begin)}); + pieceRuns.emplace_back(from, to); + } + } + if (pieces.empty()) return seeds; + const auto sources = ShadowSources(context, import, descriptor.baseAddress, pieceRuns, {}, false); + std::size_t first = 0; + for (const auto& source : sources) { + while (first < pieces.size() && pieces[first].end <= source.begin) ++first; + for (auto i = first; i < pieces.size() && pieces[i].begin < source.end; ++i) { + const auto from = std::max(source.begin, pieces[i].begin); + const auto to = std::min(source.end, pieces[i].end); + if (from >= to) continue; + auto target = std::find_if(seeds.copies.begin(), seeds.copies.end(), [&](const auto& entry) { return entry.first == source.buffer; }); + if (target == seeds.copies.end()) target = seeds.copies.insert(seeds.copies.end(), {source.buffer, {}}); + target->second.push_back({source.offset + (from - source.begin), pieces[i].scratch + (from - pieces[i].begin), to - from}); + if (!source.shadow) { + const auto guestBegin = descriptor.baseAddress + from; + if (!seeds.importReads.empty() && seeds.importReads.back().second == guestBegin) seeds.importReads.back().second = descriptor.baseAddress + to; + else seeds.importReads.emplace_back(guestBegin, descriptor.baseAddress + to); + } + } + if (source.shadow && std::find(seeds.slabs.begin(), seeds.slabs.end(), source.slab) == seeds.slabs.end()) seeds.slabs.push_back(source.slab); + } + return seeds; +} + std::uint64_t StorageTexture::uploadWindows(const HostImport& import, std::span> runs, bool discard) { const auto elementBytes = BytesPerElement(descriptor.format); // Each run's pieces by source (a unit shadow's slab while fresh, else the import), the tail @@ -1808,6 +1898,17 @@ std::uint64_t StorageTexture::writeBackWindows(const HostImport& import, std::sp } } std::sort(seeds.begin(), seeds.end(), [](const ShadowedRange& a, const ShadowedRange& b) { return a.begin < b.begin; }); + std::vector copied; + for (std::size_t i = 0; i < windows.size(); ++i) { + const auto& window = windows[i]; + if (i != 0 && scratchPositions[i] == scratchPositions[i - 1]) continue; + for (const auto& [from, to] : runs) { + const auto begin = std::max(from, window.tiledBegin); + const auto end = std::min(to, window.tiledEnd); + if (begin < end) copied.push_back({begin, end, scratchPositions[i] + (begin - window.tiledBegin)}); + } + } + const auto padding = paddingSeeds(import, std::move(copied)); // A queued label or key store inside a seeded unit lands before the seed copies the import. auto flushBegin = firstStored; auto flushEnd = lastStored; @@ -1816,7 +1917,7 @@ std::uint64_t StorageTexture::writeBackWindows(const HostImport& import, std::sp flushEnd = std::max(flushEnd, seed.end); } auto linear = std::make_shared(context, static_cast(linearTotal), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); - auto tiledScratch = std::make_shared(context, static_cast(scratchTotal), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + auto tiledScratch = std::make_shared(context, static_cast(scratchTotal), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); detiler.BeginBatch(); auto* recorder = Recorder::Active(); std::unique_ptr batch; @@ -1831,12 +1932,14 @@ std::uint64_t StorageTexture::writeBackWindows(const HostImport& import, std::sp recorder->Keep(linear); recorder->Keep(tiledScratch); for (const auto& pieces : slabPieces) recorder->Keep(pieces.slab); + for (const auto& slab : padding.slabs) recorder->Keep(slab); if (auto self = weak_from_this().lock()) recorder->Keep(std::move(self)); Recorder::CountBarriers(Recorder::CommandClass::StorageWriteBack, 4); if (Recorder::BarrierValidate()) { const std::pair read{image, false}; std::vector> seedReads; for (const auto& seed : seeds) seedReads.emplace_back(seed.begin, seed.end); + seedReads.insert(seedReads.end(), padding.importReads.begin(), padding.importReads.end()); recorder->NoteAccess(Recorder::CommandClass::StorageWriteBack, Recorder::Access{seedReads, imported, std::span(&read, 1), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT}); } } else { @@ -1853,7 +1956,7 @@ std::uint64_t StorageTexture::writeBackWindows(const HostImport& import, std::sp toSource.image = image; toSource.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, descriptor.mipCount, 0, geometry.imageLayers}; context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toSource); - if (!seeds.empty()) { + if (!seeds.empty() || !padding.copies.empty()) { // The seeded units' import bytes (every earlier writer of them, host stores included) // precede the seed copies; the previous command's trailing barrier may have covered that. constexpr VkAccessFlags transferAccess = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; @@ -1869,6 +1972,9 @@ std::uint64_t StorageTexture::writeBackWindows(const HostImport& import, std::sp context.Resolved(&DeviceFunctions::cmdCopyBuffer, "vkCmdCopyBuffer")(commands, import.buffer, slab->buffer, 1, &seed); NoteShadowSeed(begin, end); } + // The padding's current bytes, under the retile that follows (the import-ready barrier + // orders the scratch writes). + for (const auto& [source, copies] : padding.copies) context.Resolved(&DeviceFunctions::cmdCopyBuffer, "vkCmdCopyBuffer")(commands, source, tiledScratch->Handle(), static_cast(copies.size()), copies.data()); } std::vector regions; for (std::size_t i = 0; i < windows.size(); ++i) { @@ -3224,7 +3330,10 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { // untouched blocks are copied into the imported bytes in place, recorded behind the work that // produced the image; nothing crosses to the CPU. auto linear = std::make_shared(context, static_cast(sliceLinearBytes * arrayLayers), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); - auto tiledScratch = std::make_shared(context, static_cast(guestBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + auto tiledScratch = std::make_shared(context, static_cast(guestBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); + std::vector copied; + for (const auto& [from, to] : keep) copied.push_back({from - descriptor.baseAddress, to - descriptor.baseAddress, from - descriptor.baseAddress}); + const auto padding = paddingSeeds(*import, std::move(copied)); detiler.BeginBatch(); auto* recorder = Recorder::Active(); std::unique_ptr batch; @@ -3239,12 +3348,13 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { timing = recorder->BeginGpuTiming(Recorder::CommandClass::StorageWriteBack); recorder->Keep(linear); recorder->Keep(tiledScratch); + for (const auto& slab : padding.slabs) recorder->Keep(slab); // The image itself must outlive the recorded retile: the cache may evict it right after. if (auto self = weak_from_this().lock()) recorder->Keep(std::move(self)); - Recorder::CountBarriers(Recorder::CommandClass::StorageWriteBack, 4); + Recorder::CountBarriers(Recorder::CommandClass::StorageWriteBack, padding.copies.empty() ? 4 : 5); if (Recorder::BarrierValidate()) { const std::pair read{image, false}; - recorder->NoteAccess(Recorder::CommandClass::StorageWriteBack, Recorder::Access{{}, keep, std::span(&read, 1), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT}); + recorder->NoteAccess(Recorder::CommandClass::StorageWriteBack, Recorder::Access{padding.importReads, keep, std::span(&read, 1), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT}); } } else { batch = std::make_unique(context); @@ -3264,6 +3374,13 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { toSource.image = image; toSource.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, descriptor.mipCount, 0, geometry.imageLayers}; context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toSource); + if (!padding.copies.empty()) { + // The current bytes of the copied ranges no element holds go under the retile (every + // earlier writer of the import, host stores included, first; the import-ready barrier + // below orders the scratch writes). + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + for (const auto& [source, copies] : padding.copies) context.Function("vkCmdCopyBuffer")(commands, source, tiledScratch->Handle(), static_cast(copies.size()), copies.data()); + } const auto regions = CopyRegions(storedLayers); context.Function("vkCmdCopyImageToBuffer")(commands, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, linear->Handle(), static_cast(regions.size()), regions.data()); const auto linearRead = WholeBufferBarrier(linear->Handle(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); @@ -3455,6 +3572,8 @@ void StorageTexture::release() noexcept { extraViews.clear(); for (const auto& [mip, extra] : firstLayerViews) context.Function("vkDestroyImageView")(context.device, extra, nullptr); firstLayerViews.clear(); + for (const auto& [key, atomic] : atomicViews) context.Function("vkDestroyImageView")(context.device, atomic, nullptr); + atomicViews.clear(); for (const auto& [format, attachment] : attachmentViews) context.Function("vkDestroyImageView")(context.device, attachment, nullptr); attachmentViews.clear(); if (view) context.Function("vkDestroyImageView")(context.device, view, nullptr); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp index 0de68a1d5..a25632749 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp @@ -255,6 +255,30 @@ std::array ThinBlockLayout(TextureTileMode tileMode, std::uint return {block.blockSize, block.blockWidth, block.blockHeight}; } +std::vector> CoveredMipBytes(TextureTileMode tileMode, std::uint32_t bytesPerElement, const TileMipLayout& mip) { + std::vector> covered; + if (mip.tail || mip.width == 0 || mip.height == 0) return covered; + const auto add = [&](std::uint64_t begin, std::uint64_t end) { + end = std::min(end, mip.tiledSize); + if (begin >= end) return; + if (!covered.empty() && covered.back().second == begin) covered.back().second = end; + else covered.emplace_back(begin, end); + }; + if (tileMode == TextureTileMode::kLinear) { + const auto rowBytes = static_cast(mip.width) * bytesPerElement; + for (std::uint32_t row = 0; row < mip.height; ++row) add(static_cast(row) * mip.pitchBytes, static_cast(row) * mip.pitchBytes + rowBytes); + return covered; + } + const auto block = ThinBlockLayout(tileMode, bytesPerElement); + const auto wholeColumns = std::min(mip.width / block[1], mip.blocksPerRow); + const auto wholeRows = mip.height / block[2]; + for (std::uint32_t row = 0; row < wholeRows && wholeColumns != 0; ++row) { + const auto first = static_cast(row) * mip.blocksPerRow; + add(first * block[0], (first + wholeColumns) * block[0]); + } + return covered; +} + ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t depth, std::uint32_t mipCount) { Require(width != 0 && height != 0 && depth != 0, "cannot compute layout for a zero-sized 3D texture"); Require(mipCount != 0 && mipCount <= 16u, "3D texture mip count is out of range"); @@ -265,6 +289,8 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s result.mips.resize(mipCount); std::array block{CalcLinearBlockWidth(bytesPerElement), 1u, 1u}; std::uint32_t blockBytes = 0; + std::uint32_t maxTailLevels = 0; + auto firstTailLevel = mipCount; if (tileMode != TextureTileMode::kLinear) { block = ThickBlockExtent(tileMode, bytesPerElement); blockBytes = tileMode == TextureTileMode::kStandard4KB ? 4096u : 65536u; @@ -272,18 +298,22 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s const auto tailWidth = log2BlockBytes % 3u == 1u ? block[0] >> 1u : block[0]; const auto tailHeight = log2BlockBytes % 3u == 0u ? block[1] >> 1u : block[1]; const auto effectiveLog2 = log2BlockBytes - (log2BlockBytes - 8u) / 3u; - const auto maxTailLevels = effectiveLog2 <= 11u ? 1u + (1u << (effectiveLog2 - 9u)) : effectiveLog2 - 4u; - for (std::uint32_t level = 0; mipCount > 1 && level < mipCount; ++level) - Require(std::max(width >> level, 1u) > tailWidth || std::max(height >> level, 1u) > tailHeight || mipCount - level > maxTailLevels, "3D texture mip tails are not implemented"); + maxTailLevels = effectiveLog2 <= 11u ? 1u + (1u << (effectiveLog2 - 9u)) : effectiveLog2 - 4u; + for (std::uint32_t level = 0; mipCount > 1 && level < mipCount; ++level) { + if (ShiftCeil(width, level) <= tailWidth && ShiftCeil(height, level) <= tailHeight && mipCount - level <= maxTailLevels) { + firstTailLevel = level; + break; + } + } } result.blockDepth = block[2]; - std::uint64_t tiledOffset = 0; + std::uint64_t tiledOffset = firstTailLevel < mipCount ? blockBytes : 0u; std::uint64_t linearOffset = 0; for (auto level = mipCount; level-- > 0;) { auto& mip = result.mips[level]; mip.width = std::max(width >> level, 1u); mip.height = std::max(height >> level, 1u); - mip.tail = false; + mip.tail = level >= firstTailLevel; mip.tailX = 0; mip.tailY = 0; const bool tiled = tileMode != TextureTileMode::kLinear; @@ -291,17 +321,34 @@ ThickLayout ComputeThickLayout(TextureTileMode tileMode, std::uint32_t format, s const auto levelHeight = tiled ? std::max((height + (1u << level) - 1u) >> level, 1u) : mip.height; const auto paddedWidth = AlignUp(levelWidth, block[0]); mip.pitchBytes = paddedWidth * bytesPerElement; - if (!tiled) { + if (mip.tail) { + constexpr std::uint32_t microBlock[5][2] = {{8, 4}, {4, 4}, {4, 4}, {4, 2}, {2, 2}}; + const auto element = static_cast(std::countr_zero(bytesPerElement)); + const auto slot = maxTailLevels - 1u - (level - firstTailLevel); + const auto slotOffset = slot > 6u ? 16u << slot : slot << 8u; + for (std::uint32_t bit = 0; bit < 6u; ++bit) { + mip.tailX |= ((slotOffset >> (9u + 2u * bit)) & 1u) << bit; + mip.tailY |= ((slotOffset >> (8u + 2u * bit)) & 1u) << bit; + } + mip.tailX *= microBlock[element][0]; + mip.tailY *= microBlock[element][1]; + mip.blocksPerRow = 1u; + mip.pitchBytes = block[0] * bytesPerElement; + mip.tiledOffset = 0; + mip.tiledSize = blockBytes; + } else if (!tiled) { mip.blocksPerRow = paddedWidth; mip.tiledSize = static_cast(mip.pitchBytes) * mip.height; } else { mip.blocksPerRow = paddedWidth / block[0]; mip.tiledSize = static_cast(mip.blocksPerRow) * (AlignUp(levelHeight, block[1]) / block[1]) * blockBytes; } + if (!mip.tail) { + mip.tiledOffset = tiledOffset; + tiledOffset += mip.tiledSize; + } mip.linearSize = static_cast(mip.pitchBytes) * mip.height; - mip.tiledOffset = tiledOffset; mip.linearOffset = linearOffset; - tiledOffset += mip.tiledSize; linearOffset += mip.linearSize; } result.slabBytes = tiledOffset; @@ -345,6 +392,25 @@ SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor) { return geometry; } +bool LevelsFitAllocation(const GuestTextureResource& surface, std::uint32_t levels) { + Require(levels >= surface.mipCount && levels <= 16u, "a view's mip chain must cover the surface's levels and at most 16"); + auto view = surface; + view.mipCount = levels; + const auto allocated = DescribeSurface(surface); + const auto extended = DescribeSurface(view); + if (allocated.guestBytes != extended.guestBytes || allocated.layerBytes != extended.layerBytes || allocated.layers != extended.layers) return false; + for (std::uint32_t level = 0; level < surface.mipCount; ++level) { + const auto& before = allocated.mips[level]; + const auto& after = extended.mips[level]; + if (before.tiledOffset != after.tiledOffset || before.tiledSize != after.tiledSize || before.blocksPerRow != after.blocksPerRow || before.tail != after.tail || before.tailX != after.tailX || before.tailY != after.tailY) return false; + } + for (auto level = surface.mipCount; level < levels; ++level) { + const auto& mip = extended.mips[level]; + if (mip.tiledOffset + mip.tiledSize > extended.layerBytes) return false; + } + return true; +} + std::vector ComputeElementMipLayout(TextureTileMode tileMode, std::uint32_t bytesPerElement, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount) { Require(width != 0 && height != 0 && mipCount != 0 && mipCount <= 16u, "invalid surface mip chain"); if (tileMode == TextureTileMode::kLinear) return ComputeLinearMipLayout(bytesPerElement, 1u, 1u, width, height, mipCount); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp index efa6a44bf..f15aaf502 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp @@ -592,12 +592,12 @@ std::optional ShadowDestinationFor(const Context& context, co return destination; } -std::vector ShadowSources(const Context& context, const HostImport& import, std::uint64_t surfaceBase, std::span> runs, std::span> tailBlocks) { +std::vector ShadowSources(const Context& context, const HostImport& import, std::uint64_t surfaceBase, std::span> runs, std::span> tailBlocks, bool countReads) { std::vector result; if (runs.empty()) return result; const auto importPiece = [&](std::uint64_t begin, std::uint64_t end) { result.push_back({begin, end, import.buffer, surfaceBase + begin - import.base, false, nullptr}); - Stats().detiledImportBytes.fetch_add(end - begin, std::memory_order_relaxed); + if (countReads) Stats().detiledImportBytes.fetch_add(end - begin, std::memory_order_relaxed); }; std::shared_ptr shadow; if (UnitShadowEnabled()) { @@ -608,7 +608,7 @@ std::vector ShadowSources(const Context& context, const HostImport& i } if (shadow == nullptr) { for (const auto& [begin, end] : runs) importPiece(begin, end); - Stats().detiledImport.fetch_add(1, std::memory_order_relaxed); + if (countReads) Stats().detiledImport.fetch_add(1, std::memory_order_relaxed); return result; } const auto first = shadow->UnitOf(surfaceBase + runs.front().first); @@ -663,6 +663,7 @@ std::vector ShadowSources(const Context& context, const HostImport& i (isFresh ? shadowBytes : importBytes) += pieceEnd - pieceBegin; } } + if (!countReads) shadowBytes = importBytes = 0; if (shadowBytes != 0) { Stats().detiledShadow.fetch_add(1, std::memory_order_relaxed); Stats().detiledShadowBytes.fetch_add(shadowBytes, std::memory_order_relaxed); diff --git a/core/libs/prx/libSceAgcDriver/Unimplemented.cpp b/core/libs/prx/libSceAgcDriver/Unimplemented.cpp index 8166a546f..775fcced3 100644 --- a/core/libs/prx/libSceAgcDriver/Unimplemented.cpp +++ b/core/libs/prx/libSceAgcDriver/Unimplemented.cpp @@ -85,26 +85,22 @@ int APS5_VABI sceAgcDriverUnregisterWorkloadStream() { return 0; } -APS5_EXPORT("+iAOE3jCnkk", sceAgcDriverUnknown__PiAOE3jCnkk); -int APS5_VABI sceAgcDriverUnknown__PiAOE3jCnkk() { +int APS5_VABI sceAgcDriverSetSubmitValidationMode() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("ICaGtkEIXTk", sceAgcDriverUnknown_ICaGtkEIXTk); -int APS5_VABI sceAgcDriverUnknown_ICaGtkEIXTk() { +int APS5_VABI sceAgcDriverGetSubmitValidationConfig() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("WHIOMbb+iIU", sceAgcDriverUnknown_WHIOMbb_PiIU); -int APS5_VABI sceAgcDriverUnknown_WHIOMbb_PiIU() { +int APS5_VABI sceAgcDriverGetSubmitValidationMode() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("chJWZcNSzjk", sceAgcDriverUnknown_chJWZcNSzjk); -int APS5_VABI sceAgcDriverUnknown_chJWZcNSzjk() { +int APS5_VABI sceAgcDriverSetSubmitValidationConfig() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceAgcDriver/tests/Flip.cpp b/core/libs/prx/libSceAgcDriver/tests/Flip.cpp index 9beb24074..596974c90 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Flip.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Flip.cpp @@ -114,6 +114,12 @@ void testFlipAndBoundary() { Packet rejected{rollback.data(), 12, 0, {}}; expectFailure([&] { sceAgcDriverSubmitDcb(&rejected); }); check(output->state->alive == 0 && output->state->ready == 0, "rejected submission retained or executed a reservation"); + alignas(4) static std::uint32_t condition = 1; + const auto conditionAddress = reinterpret_cast(&condition); + std::array guarded{0xc0032200, static_cast(conditionAddress), static_cast(conditionAddress >> 32u), 0, 6, 0xc004105c, 7, 0, 1, 0, 0}; + Packet conditionalFlip{guarded.data(), 11, 0, {}}; + check(expectFailure([&] { sceAgcDriverSubmitDcb(&conditionalFlip); }).find("a flip inside a conditional execution range") != std::string::npos, "a flip inside a COND_EXEC range was not rejected"); + check(output->state->alive == 0 && output->state->ready == 0, "a rejected conditional flip retained or executed a reservation"); { std::lock_guard lock(output->state->mutex); output->state->block = true; diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 249a2a721..75b240db0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -191,6 +191,25 @@ void stateTests() { log.clear(); static_cast(AgcDriver::Graphics::DecodeState(queue)); Require(log.empty(), "the register facade recorded without a log"); + queue = makeState(); + queue.context.erase(0x1b3); + queue.context.erase(0x1b4); + queue.context.erase(0x1c5); + queue.shader[0x008] = 0x100; + queue.shader[0x009] = 0; + Require(!AgcDriver::Graphics::PixelProgramUnset(queue), "a pixel program address was read as unset"); + Require(AgcDriver::Graphics::DrawRejection(queue, true).find("missing register at DWORD 0x1b3") != std::string::npos, "a real pixel program without SPI_PS_INPUT_ENA was accepted"); + expectFailure([&] { AgcDriver::Graphics::DecodePixelStageInfo(queue.context, std::array{0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u}); }, "missing register"); + queue.shader[0x008] = 0; + Require(AgcDriver::Graphics::PixelProgramUnset(queue), "a zero pixel program address was not read as unset"); + Require(AgcDriver::Graphics::DrawRejection(queue, true).find("writes color") != std::string::npos, "a draw without a pixel program that writes color was accepted"); + queue.context[0x8e] = 0; + Require(AgcDriver::Graphics::DrawRejection(queue, true).empty(), "a depth-only draw without a pixel program was rejected"); + state = AgcDriver::Graphics::DecodeState(queue); + Require(!state.hasColorTarget, "a depth-only draw without a pixel program decoded a color target"); + const auto unset = AgcDriver::Graphics::DecodePixelStageInfo(queue.context, AgcDriver::Graphics::ExportMappings(state), true); + Require(unset.inputAddr == ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveCenter), "unset pixel inputs of the null program did not read as PERSP_CENTER_ENA"); + for (const auto mode : unset.targetOutputMode) Require(mode == 0, "an unset SPI_SHADER_COL_FORMAT exported a color"); } void hardwareScreenOffsetTests() { @@ -449,6 +468,36 @@ void DisabledColorTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "missing register"); } +void CompactedExportTests() { + alignas(256) static std::array slotFourMemory{}; + const auto slotFour = reinterpret_cast(slotFourMemory.data()); + auto queue = makeState(); + for (const auto offset : {0x31bu, 0x31cu, 0x31du}) queue.context[offset + 4u * 0xfu] = queue.context.at(offset); + for (const auto offset : {0x3b0u, 0x3b8u}) queue.context[offset + 4u] = queue.context.at(offset); + queue.context[0x318 + 4u * 0xfu] = static_cast(slotFour >> 8u); + queue.context[0x390 + 4u] = static_cast(slotFour >> 40u); + queue.context[0x1e4] = 0x40010001u; + for (std::uint32_t i = 0; i < 4; ++i) queue.context[0x105 + i] = 0; + queue.context[0x8e] = 0x3000fu; + queue.context[0x8f] = 0xf000fu; + queue.context[0x1c5] = 0x44u; + auto state = AgcDriver::Graphics::DecodeState(queue); + Require(state.colors.size() == 2 && state.blends.size() == 2, "two compacted exports did not give two attachments"); + Require(state.colors[0].slot == 0 && state.colors[0].exportIndex == 0 && state.colors[0].address == reinterpret_cast(colorMemory.data()), "export 0 did not reach MRT slot 0"); + Require(state.colors[1].slot == 4 && state.colors[1].exportIndex == 1 && state.colors[1].address == slotFour, "export 1 did not reach MRT slot 4, the second slot CB_SHADER_MASK enables"); + Require(!state.blends[0].blendEnable && state.blends[1].blendEnable && state.blends[1].colorWriteMask == (VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT), "export 1 did not take MRT slot 4's blend control and target mask"); + Require(AgcDriver::Graphics::ExportMappings(state)[1] == state.colors[1].componentMapping, "export 1 did not take MRT slot 4's component mapping"); + queue.context[0x1c5] = 0x90009u; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "color export format 0"); + queue.context[0x8e] = 0xf000fu; + queue.context[0x8f] = 0xf0f0fu; + queue.context[0x1c5] = 0x999u; + state = AgcDriver::Graphics::DecodeState(queue); + Require(state.colors.size() == 2 && state.blends.size() == 3, "a write-masked export between written ones changed the attachment count"); + Require(state.colors[0].exportIndex == 0 && state.colors[1].slot == 4 && state.colors[1].exportIndex == 2, "export 2 did not reach MRT slot 4, the third slot CB_SHADER_MASK enables"); + Require(state.blends[1].colorWriteMask == 0 && !state.blends[1].blendEnable && state.blends[2].blendEnable, "the write-masked export 1 was not left unused"); +} + void DepthStencilTests() { auto queue = makeState(); queue.context[0x000] = 0; @@ -679,9 +728,23 @@ void DepthClipTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "inverted viewport depth clamp"); queue.context[0xb4] = 0; for (std::uint32_t bit = 0; bit < 32; ++bit) { - if (bit == 19 || bit == 26 || bit == 27) continue; - queue.context[0x204] = 1u << bit; - expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "PA_CL_CLIP_CNTL"); + if (bit == 19 || bit == 24 || bit == 26 || bit == 27) continue; + for (const auto linearBit : {0u, 0x01000000u}) { + queue.context[0x204] = (1u << bit) | linearBit; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "PA_CL_CLIP_CNTL"); + Require(AgcDriver::Graphics::DrawRejection(queue, false).find("PA_CL_CLIP_CNTL") != std::string::npos, "the precheck accepted an unsupported PA_CL_CLIP_CNTL bit"); + } + } + queue = makeState(); + queue.context[0x1b3] = 2; + queue.context[0x1b4] = 2; + for (const auto clip : {0x00080000u, 0x00000000u, 0x0c080000u, 0x04000000u}) { + queue.context[0x204] = clip; + const auto plain = AgcDriver::Graphics::DecodeState(queue); + queue.context[0x204] = clip | 0x01000000u; + const auto linearClip = AgcDriver::Graphics::DecodeState(queue); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "the precheck rejected DX_LINEAR_ATTR_CLIP_ENA"); + Require(linearClip.negativeOneToOne == plain.negativeOneToOne && linearClip.depthClamp == plain.depthClamp && linearClip.viewport.minDepth == plain.viewport.minDepth && linearClip.viewport.maxDepth == plain.viewport.maxDepth, "DX_LINEAR_ATTR_CLIP_ENA changed the clip space, depth clamping or depth range"); } } @@ -1860,6 +1923,7 @@ int main() { DepthStencilTests(); DepthBoundsBiasTests(); DisabledColorTests(); + CompactedExportTests(); metadataPassTests(); ShaderStageTests(); PixelInputLayoutTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp index 092a548c0..87e25e28a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp @@ -1,5 +1,6 @@ #include "GraphicsTests.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" #include #include #include @@ -269,6 +270,57 @@ void RunGuestTextureResourceTests() { const auto partial = DecodeTextureResource(pack(partialMips)); Require(partial.lastLevel == 1 && partial.mipCount == 3, "a view over part of the mip chain decoded wrongly"); + Fields pastLast = base; + pastLast.base40 = 0x55ea000ull; + pastLast.format = 71; + pastLast.width = 1920; + pastLast.height = 1080; + pastLast.tileModeRaw = 0x1b; + pastLast.maxMip = 5; + pastLast.baseLevel = 6; + pastLast.lastLevel = 6; + const auto tailView = DecodeTextureResource(pack(pastLast)); + Require(tailView.baseLevel == 6 && tailView.lastLevel == 6 && tailView.mipCount == 7, "a view one level past the last mip must address that level of the chain"); + auto allocated = tailView; + allocated.mipCount = 6; + const auto allocatedSurface = DescribeSurface(allocated); + const auto viewSurface = DescribeSurface(tailView); + Require(allocatedSurface.guestBytes == 0x1640000u && viewSurface.guestBytes == 0x1640000u, "a 1920x1080 64 bpp SW_64KB_R_X chain must take addrlib's 0x1640000 bytes with 6 and with 7 levels"); + constexpr std::array addrlibOffsets{0x650000u, 0x1d0000u, 0x90000u, 0x30000u, 0x10000u, 0u, 0u}; + for (std::uint32_t level = 0; level < 7; ++level) { + Require(viewSurface.mips[level].tiledOffset == addrlibOffsets[level] && viewSurface.mips[level].tail == (level >= 5), "the 7-level chain must place each level at its addrlib offset"); + if (level < 6) Require(allocatedSurface.mips[level].tiledOffset == addrlibOffsets[level] && allocatedSurface.mips[level].tail == viewSurface.mips[level].tail && allocatedSurface.mips[level].tailX == viewSurface.mips[level].tailX && allocatedSurface.mips[level].tailY == viewSurface.mips[level].tailY, "the level past the last mip must not move the allocated levels"); + } + Require(viewSurface.mips[5].tailX == 64 && viewSurface.mips[5].tailY == 0, "the last allocated level must sit in its addrlib tail slot"); + Require(viewSurface.mips[6].tailX == 0 && viewSurface.mips[6].tailY == 32, "the level past the last mip must sit in its addrlib tail slot"); + pastLast.lastLevel = 8; + const auto deeperView = DecodeTextureResource(pack(pastLast)); + Require(deeperView.mipCount == 9, "a view past the last mip must cover every level it names"); + const auto deeperSurface = DescribeSurface(deeperView); + Require(deeperSurface.guestBytes == 0x1640000u && deeperSurface.mips[7].tail && deeperSurface.mips[7].tailX == 32 && deeperSurface.mips[7].tailY == 0 && deeperSurface.mips[8].tail && deeperSurface.mips[8].tailX == 0 && deeperSurface.mips[8].tailY == 16, "levels 7 and 8 must sit in their addrlib tail slots"); + + Fields pastLinear = base; + pastLinear.maxMip = 1; + pastLinear.baseLevel = 2; + pastLinear.lastLevel = 2; + rejectFields(pastLinear, "would move the surface's own"); + auto linearChain = partial; + linearChain.mipCount = 2; + const auto linearAllocated = DescribeSurface(linearChain).guestBytes; + linearChain.mipCount = 3; + Require(linearAllocated == 0x1800u && DescribeSurface(linearChain).guestBytes == 0x1c00u, "a third level must grow a 16x16 32 bpp linear chain from addrlib's 0x1800 to 0x1c00 bytes"); + + Fields pastUntailed = pastLast; + pastUntailed.maxMip = 0; + pastUntailed.baseLevel = 1; + pastUntailed.lastLevel = 1; + rejectFields(pastUntailed, "would move the surface's own"); + auto untailedChain = tailView; + untailedChain.mipCount = 1; + const auto untailedAllocated = DescribeSurface(untailedChain).guestBytes; + untailedChain.mipCount = 2; + Require(untailedAllocated == 0xff0000u && DescribeSurface(untailedChain).guestBytes == 0x1470000u, "a second level must grow a 1920x1080 64 bpp SW_64KB_R_X surface from addrlib's 0xff0000 to 0x1470000 bytes"); + std::array shortWords{}; reject([&] { DecodeTextureResource(shortWords); }, "8 dwords"); diff --git a/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp b/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp new file mode 100644 index 000000000..119f87925 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp @@ -0,0 +1,378 @@ +#include "IntermediateRepresentation/IrProgram.hpp" +#include "Optimization/MaskedSelectEliminator.hpp" +#include "RdnaDecoder/RdnaInstruction.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace ShaderRecompiler; + +namespace { + +struct Builder { + IrProgram program; + IrBlock* block = nullptr; + + Builder() { + block = &newBlock(); + program.SetEntryBlock(*block); + } + + IrBlock& newBlock() { + auto& created = program.CreateBlock(); + program.BlockOrder().push_back(&created); + return created; + } + + IrValue& constant(std::uint32_t value) { + auto& created = program.CreateValue(IrOpcode::Void, IrType::U32); + created.SetImmediateU32(value); + return created; + } + + IrValue& emit(IrOpcode opcode, IrType type, std::initializer_list arguments, std::uint64_t flags = 0) { + auto& created = program.CreateValue(opcode, type, flags); + for (auto* argument : arguments) created.AddArgument(argument); + block->AppendInstruction(&created); + return created; + } + + IrValue& lane() { + return emit(IrOpcode::LaneId, IrType::U32, {}); + } + + IrValue& mask(std::uint32_t lanes) { + return emit(IrOpcode::ULessThan32, IrType::Bool, {&lane(), &constant(lanes)}); + } + + IrValue& select(IrValue& condition, IrValue& active, IrValue& inactive) { + return emit(IrOpcode::SelectU32, IrType::U32, {&condition, &active, &inactive}); + } + + IrValue& add(IrValue& value, std::uint32_t immediate) { + return emit(IrOpcode::IAdd32, IrType::U32, {&value, &constant(immediate)}); + } + + IrValue& logicalAnd(IrValue& left, IrValue& right) { + return emit(IrOpcode::LogicalAnd, IrType::Bool, {&left, &right}); + } + + IrValue& threadBit(IrValue& low, IrValue& high) { + auto& laneId = lane(); + auto& word = select(emit(IrOpcode::ULessThan32, IrType::Bool, {&laneId, &constant(32u)}), low, high); + auto& shift = emit(IrOpcode::BitwiseAnd32, IrType::U32, {&laneId, &constant(31u)}); + auto& shifted = emit(IrOpcode::ShiftRightLogical32, IrType::U32, {&word, &shift}); + auto& bit = emit(IrOpcode::BitwiseAnd32, IrType::U32, {&shifted, &constant(1u)}); + return emit(IrOpcode::INotEqual32, IrType::Bool, {&bit, &constant(0u)}); + } + + IrValue& ballotBit(IrValue& predicate) { + auto& ballot = emit(IrOpcode::Ballot, IrType::U32x4, {&predicate}); + auto& low = emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &constant(0u)}); + auto& high = emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &constant(1u)}); + return threadBit(low, high); + } + + void keep(IrValue& value) { + (void)emit(IrOpcode::ReferenceU32, IrType::Void, {&value}); + } + + std::uint32_t eliminate() { + return MaskedSelectEliminator{}.Eliminate(program).removedSelects; + } +}; + +bool removed(const IrValue& value) { + return value.Parent() == nullptr; +} + +bool expect(const char* name, bool condition) { + if (!condition) std::fprintf(stderr, "%s\n", name); + return condition; +} + +bool run(const char* name, const std::function& test) { + try { + return expect(name, test()); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s: %s\n", name, error.what()); + return false; + } +} + +} + +int main() { + bool passed = true; + + passed &= run("a write read only under its own exec must lose its select", [] { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& next = b.select(exec, b.add(written, 2u), old); + b.keep(next); + return b.eliminate() == 1u && removed(written) && !removed(next); + }); + + passed &= run("a write read under a wider exec must keep its select", [] { + Builder b; + auto& wide = b.mask(32u); + auto& exec = b.logicalAnd(wide, b.mask(16u)); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& next = b.select(wide, b.add(written, 2u), old); + b.keep(next); + return b.eliminate() == 0u && !removed(written); + }); + + passed &= run("a write read under a narrower exec must lose its select", [] { + Builder b; + auto& exec = b.mask(32u); + auto& narrow = b.logicalAnd(exec, b.mask(16u)); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& next = b.select(narrow, b.add(written, 2u), old); + b.keep(next); + return b.eliminate() == 1u && removed(written); + }); + + passed &= run("a write whose old lanes flow into the next write must keep its select", [] { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& next = b.select(exec, b.add(old, 2u), written); + b.keep(next); + return b.eliminate() == 0u && !removed(written); + }); + + passed &= run("a chain of writes under one exec must collapse once the last one goes", [] { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& first = b.select(exec, b.add(old, 1u), old); + auto& second = b.select(exec, b.add(first, 2u), first); + auto& sink = b.select(exec, b.add(second, 3u), old); + b.keep(sink); + return b.eliminate() == 2u && removed(first) && removed(second) && !removed(sink); + }); + + passed &= run("cross-lane reads must keep the select", [] { + bool ok = true; + for (const IrOpcode opcode : {IrOpcode::ReadFirstLane, IrOpcode::DppMoveU32, IrOpcode::BpermuteU32, IrOpcode::Permlane16U32, IrOpcode::SwizzleU32}) { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& read = b.emit(opcode, IrType::U32, {&written, &exec}); + b.keep(b.select(exec, read, old)); + ok &= b.eliminate() == 0u && !removed(written); + } + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& ballot = b.emit(IrOpcode::Ballot, IrType::U32x4, {&b.emit(IrOpcode::INotEqual32, IrType::Bool, {&written, &b.constant(0u)})}); + b.keep(b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &b.constant(0u)})); + return ok && b.eliminate() == 0u && !removed(written); + }); + + passed &= run("a branch condition derived from the write keeps the select", [] { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& taken = b.emit(IrOpcode::INotEqual32, IrType::Bool, {&written, &b.constant(0u)}); + (void)b.emit(IrOpcode::Reference, IrType::Void, {&taken}); + return b.eliminate() == 0u && !removed(written); + }); + + passed &= run("a compare masked by the same exec must not keep the select", [] { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& compare = b.logicalAnd(exec, b.emit(IrOpcode::INotEqual32, IrType::Bool, {&written, &b.constant(0u)})); + auto& ballot = b.emit(IrOpcode::Ballot, IrType::U32x4, {&compare}); + b.keep(b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&ballot, &b.constant(0u)})); + return b.eliminate() == 1u && removed(written); + }); + + passed &= run("an exec rebuilt from a ballot of a narrower mask implies the write's exec", [] { + Builder b; + auto& exec = b.mask(48u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& later = b.ballotBit(b.logicalAnd(exec, b.mask(40u))); + auto& next = b.select(later, b.add(written, 2u), old); + b.keep(next); + return b.eliminate() == 1u && removed(written); + }); + + passed &= run("an exec rebuilt from a ballot of a wider mask keeps the select", [] { + Builder b; + auto& wide = b.mask(48u); + auto& exec = b.logicalAnd(wide, b.mask(40u)); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& later = b.ballotBit(wide); + b.keep(b.select(later, b.add(written, 2u), old)); + return b.eliminate() == 0u && !removed(written); + }); + + passed &= run("a select under the full wave's exec must go whatever reads it", [] { + Builder b; + auto& full = b.threadBit(b.constant(0xffffffffu), b.constant(0xffffffffu)); + auto& old = b.lane(); + auto& written = b.select(full, b.add(old, 1u), old); + b.keep(written); + return b.eliminate() != 0u && removed(written); + }); + + passed &= run("a partial wave's exec is not the full wave", [] { + Builder b; + auto& partial = b.threadBit(b.constant(0xffffffffu), b.constant(0x0000ffffu)); + auto& old = b.lane(); + auto& written = b.select(partial, b.add(old, 1u), old); + b.keep(written); + return b.eliminate() == 0u && !removed(written); + }); + + const auto diamond = [](bool wideRead) { + Builder b; + auto& wide = b.mask(48u); + auto& exec = b.logicalAnd(wide, b.mask(16u)); + auto& old = b.lane(); + IrBlock& head = *b.block; + IrBlock& body = b.newBlock(); + IrBlock& merge = b.newBlock(); + head.AddBranch(&body); + head.AddBranch(&merge); + body.AddBranch(&merge); + b.block = &body; + auto& written = b.select(exec, b.add(old, 1u), old); + b.block = &merge; + auto& phi = b.program.CreateValue(IrOpcode::Phi, IrType::U32); + merge.AppendInstruction(&phi); + phi.AddPhiOperand(&head, &old); + phi.AddPhiOperand(&body, &written); + b.keep(b.select(wideRead ? wide : exec, b.add(phi, 2u), old)); + return std::pair{b.eliminate(), removed(written)}; + }; + + passed &= run("a write merged by a phi and read under its own exec must lose its select", [&] { + const auto [count, gone] = diamond(false); + return count == 1u && gone; + }); + + passed &= run("a write merged by a phi and read under the wider exec must keep its select", [&] { + const auto [count, gone] = diamond(true); + return count == 0u && !gone; + }); + + passed &= run("a write in a loop must keep its select", [] { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + IrBlock& head = *b.block; + IrBlock& loop = b.newBlock(); + IrBlock& exit = b.newBlock(); + head.AddBranch(&loop); + loop.AddBranch(&loop); + loop.AddBranch(&exit); + b.block = &loop; + auto& written = b.select(exec, b.add(old, 1u), old); + auto& next = b.select(exec, b.add(written, 2u), old); + b.keep(next); + return b.eliminate() == 0u && !removed(written); + }); + + passed &= run("a write read inside a later loop must keep its select", [] { + Builder b; + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + IrBlock& head = *b.block; + IrBlock& loop = b.newBlock(); + head.AddBranch(&loop); + loop.AddBranch(&loop); + b.block = &loop; + b.keep(b.select(exec, b.add(written, 2u), old)); + return b.eliminate() == 0u && !removed(written); + }); + + const auto sample = [](IrShaderStage stage, std::uint32_t sampleFlags) { + Builder b; + b.program.Resources().stage = stage; + MemoryInfo memory; + memory.kind = ResourceKind::Image; + memory.imageSampleFlags = sampleFlags; + b.program.Resources().memoryInfo.push_back(memory); + auto& exec = b.mask(16u); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + auto& address = b.emit(IrOpcode::MakeImageAddress, IrType::ImageAddress, {&written, &old}); + auto& image = b.emit(IrOpcode::GetImageResource, IrType::ImageResource, {}); + auto& sampler = b.emit(IrOpcode::GetSamplerResource, IrType::SamplerResource, {}); + MemoryFlags flags{0u, 0u}; + std::uint64_t bits = 0; + std::memcpy(&bits, &flags, sizeof(flags)); + auto& sampled = b.emit(IrOpcode::ImageSampleRaw, IrType::U32x4, {&image, &sampler, &address}, bits); + b.keep(b.select(exec, b.emit(IrOpcode::CompositeExtractU32x4, IrType::U32, {&sampled, &b.constant(0u)}), old)); + b.eliminate(); + return removed(written); + }; + + passed &= run("an implicit-LOD pixel sample reads its quad's coordinates and keeps the select", [&] { + return !sample(IrShaderStage::Pixel, 0u); + }); + + passed &= run("an explicit-LOD sample reads only its lane and drops the select", [&] { + return sample(IrShaderStage::Pixel, RdnaImageSampleFlagLevelZero) && sample(IrShaderStage::Mesh, 0u); + }); + + const auto guarded = [](IrOpcode opcode, bool sameExec, bool asActive) { + Builder b; + auto& wide = b.mask(48u); + auto& exec = b.logicalAnd(wide, b.mask(16u)); + auto& old = b.lane(); + auto& written = b.select(exec, b.add(old, 1u), old); + IrValue& active = asActive ? b.emit(IrOpcode::INotEqual32, IrType::Bool, {&written, &b.constant(0u)}) : sameExec ? exec : wide; + switch (opcode) { + case IrOpcode::WriteSharedU32: + b.emit(opcode, IrType::Void, {&old, &written, &active}); + break; + case IrOpcode::LoadBufferU32: + b.keep(b.select(exec, b.emit(opcode, IrType::U32, {&b.emit(IrOpcode::GetBufferResource, IrType::BufferResource, {}), &written, &b.constant(0u), &b.constant(0u), &active}), old)); + break; + default: + b.emit(opcode, IrType::Void, {&b.emit(IrOpcode::CompositeConstructU32x4, IrType::U32x4, {&written, &old, &old, &old}), &active}); + break; + } + b.eliminate(); + return removed(written); + }; + + passed &= run("a store, load or export whose exec implies the write's exec drops the select", [&] { + return guarded(IrOpcode::WriteSharedU32, true, false) && guarded(IrOpcode::LoadBufferU32, true, false) && guarded(IrOpcode::SetAttribute, true, false); + }); + + passed &= run("a store, load or export under a wider exec keeps the select", [&] { + return !guarded(IrOpcode::WriteSharedU32, false, false) && !guarded(IrOpcode::LoadBufferU32, false, false) && !guarded(IrOpcode::SetAttribute, false, false); + }); + + passed &= run("a value used as a store's exec keeps the select", [&] { + return !guarded(IrOpcode::WriteSharedU32, true, true); + }); + + passed &= run("an atomic keeps the select", [&] { + return !guarded(IrOpcode::SharedAtomicIAdd32, true, false); + }); + + return passed ? 0 : 1; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 4c559fc5f..2efbe1ab5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -2,12 +2,16 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libSceAgcDriver/Submit/include/Acb.hpp" #include "prx/libc/include/Shutdown.hpp" +#include +#include #include #include #include #include #include +#include #include #ifdef _WIN32 #ifndef NOMINMAX @@ -290,6 +294,9 @@ void testMemory() { check(data[0] == 22 && data[1] == 12, "WRITE_DATA fixed destination failed"); execute(state, makePacket(0x37, {0x40000100, low(data.data()), high(data.data()), 41, 42})); check(data[0] == 41 && data[1] == 42, "WRITE_DATA from the PFP failed"); + execute(state, makePacket(0x37, {0x04100200, low(data.data()), high(data.data()), 61, 62})); + check(data[0] == 61 && data[1] == 62, "WRITE_DATA with a cache policy failed"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x37, {0x08000100, low(data.data()), high(data.data()), 71}), 0); }, "reserved"); expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x37, {0x80000100, low(data.data()), high(data.data()), 51}), 0); }, "engine"); execute(state, makePacket(0x81, {4, 31, 32})); execute(state, makePacket(0x83, {4, 2, low(data.data()), high(data.data())})); @@ -405,6 +412,60 @@ void testMemorySynchronization() { check(memory.destination == 42, "failed synchronization changed the destination"); } +void testConditionalValidation() { + alignas(4) static std::uint32_t condition = 0; + const auto valid = makePacket(0x22, {low(&condition), high(&condition), 0, 0x3fff}); + check(AgcDriver::Pm4::UnsupportedReason(valid[0]).empty() && AgcDriver::Pm4::AccessesMemory(valid[0]), "COND_EXEC is rejected or does not synchronize guest memory"); + AgcDriver::Pm4::Validate(valid, 0); + AgcDriver::Pm4::Validate(valid, 0x20); + check(AgcDriver::Pm4::ConditionalWords(valid) == 0x3fff, "COND_EXEC count decoded wrong"); + check(AgcDriver::Pm4::ConditionalWords(makePacket(0x22, {low(&condition), high(&condition), 0, 0})) == 0, "empty COND_EXEC range decoded wrong"); + AgcDriver::Pm4::Validate(makePacket(0x22, {low(&condition), high(&condition), 3u << 25u, 5}), 0x20); + const auto invalidWord = [&](std::size_t word, std::uint32_t value, std::uint32_t queue, const char* text) { + auto packet = valid; + packet[word] = value; + expectFailure([&] { AgcDriver::Pm4::Validate(packet, queue); }, text); + }; + invalidWord(1, low(&condition) | 1u, 0, "reserved address bits"); + invalidWord(1, low(&condition) | 2u, 0x20, "reserved address bits"); + invalidWord(2, 0x10000u, 0, "above 48"); + invalidWord(3, 3u << 25u, 0, "reserved control fields"); + invalidWord(3, 1u, 0x20, "reserved control fields"); + invalidWord(3, 1u << 27u, 0x20, "reserved control fields"); + invalidWord(4, 0x4000u, 0, "reserved count bits"); + invalidWord(4, 0x80000005u, 0x20, "reserved count bits"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0}), 0); }, "packet size"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0, 0, 0}), 0); }, "packet size"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0, 0}, 1), 0); }, "header flags"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x22, {0, 0, 0, 0}, 2), 0x20); }, "header flags"); + expectFailure([] { AgcDriver::Pm4::ConditionalWords(makePacket(0x37, {0x100, 0, 0, 0})); }, "expected COND_EXEC"); + AgcDriver::QueueState state; + expectFailure([&] { AgcDriver::Pm4::Execute(valid, state); }, "driver execution"); +} + +void testConditionReadSynchronization() { + alignas(4) static std::uint32_t condition = 0; + static std::size_t flushed = 0; + condition = 0; + flushed = 0; + struct FlushHookReset { + ~FlushHookReset() { AgcDriver::GuestMemory::SetFlushHook(nullptr); } + } reset; + AgcDriver::GuestMemory::SetFlushHook([](std::uint64_t address, std::size_t bytes) { + check(address == reinterpret_cast(&condition) && bytes == sizeof(condition), "COND_EXEC synchronized an unrelated range"); + condition = 0x80; + ++flushed; + }); + const auto packet = makePacket(0x22, {low(&condition), high(&condition), 0, 5}); + check(AgcDriver::Pm4::ReadCondition(packet) == 0x80 && flushed == 1, "COND_EXEC read its condition before the GPU work that writes it"); + AgcDriver::GuestMemory::SetFlushHook([](std::uint64_t, std::size_t) { + throw std::runtime_error("condition synchronization failed"); + }); + expectFailure([&] { AgcDriver::Pm4::ReadCondition(packet); }, "condition synchronization failed"); + AgcDriver::GuestMemory::SetFlushHook(nullptr); + expectFailure([] { AgcDriver::Pm4::ReadCondition(makePacket(0x22, {0x1000, 0, 0, 5})); }, "guest"); +} + void testWriteChangedKeepsUntouchedBytes() { alignas(256) static std::uint8_t guest[256]; std::memset(guest, 0, sizeof(guest)); @@ -541,6 +602,97 @@ void testDriverSubmission() { check(destination[0] == 0, "rejected submission executed a prefix"); } +std::vector joinPackets(std::initializer_list> packets) { + std::vector words; + for (const auto& packet : packets) words.insert(words.end(), packet.begin(), packet.end()); + return words; +} + +std::vector writeWord(std::uint32_t& target, std::uint32_t value) { + return makePacket(0x37, {0x00100200, low(&target), high(&target), value}); +} + +std::vector conditional(const std::uint32_t& condition, std::uint32_t words, std::uint32_t control = 0) { + return makePacket(0x22, {low(&condition), high(&condition), control, words}); +} + +std::vector indirectBuffer(const std::vector& target, bool chain = false) { + return makePacket(0x3f, {low(target.data()), high(target.data()), static_cast(target.size()) | (chain ? 1u << 20u : 0u)}); +} + +void submitWords(std::vector& words, std::uint32_t queue = 0) { + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + check((queue == 0 ? sceAgcDriverSubmitDcb(&packet) : sceAgcDriverSubmitAcb(queue, &packet)) == 0, "conditional submission failed"); +} + +void testConditionalSubmission() { + alignas(8) static std::uint32_t zero = 0, one = 1, condition = 0; + static std::array results{}; + results.fill(0); + const auto run = [](std::vector words, std::uint32_t queue = 0) { + submitWords(words, queue); + AgcDriverWaitIdle_nid_postfix(); + }; + run(joinPackets({conditional(one, 5), writeWord(results[0], 11), writeWord(results[1], 12)})); + check(results[0] == 11 && results[1] == 12, "COND_EXEC skipped a range whose condition is set"); + run(joinPackets({conditional(zero, 6), {0x80000000u}, writeWord(results[0], 21), writeWord(results[1], 22)})); + check(results[0] == 11 && results[1] == 22, "COND_EXEC did not skip exactly its range when the condition is zero"); + run(joinPackets({conditional(zero, 0), writeWord(results[2], 23)})); + check(results[2] == 23, "an empty COND_EXEC range skipped the next packet"); + alignas(4) static std::uint32_t never = 0; + never = 0; + auto skippedWait = joinPackets({conditional(zero, 9), makePacket(0x3c, {0x13, low(&never), high(&never), 1, 0xffffffffu, 0x190}), makePacket(0x10, {0}, 0x30), writeWord(results[14], 24)}); + submitWords(skippedWait); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + while (std::atomic_ref(results[14]).load() != 24 && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + const bool skipped = std::atomic_ref(results[14]).load() == 24; + std::atomic_ref(never).store(1); + AgcDriverWaitIdle_nid_postfix(); + check(skipped, "a WAIT_REG_MEM inside a skipped COND_EXEC range was executed"); + + condition = 1; + run(joinPackets({writeWord(condition, 0), conditional(condition, 5), writeWord(results[0], 31), writeWord(results[1], 32)})); + check(condition == 0 && results[0] == 11 && results[1] == 32, "COND_EXEC read its condition before an earlier packet of its queue stored it"); + run(joinPackets({writeWord(condition, 9), conditional(condition, 5), writeWord(results[0], 41)})); + check(results[0] == 41, "COND_EXEC skipped a range whose condition an earlier packet of its queue set"); + condition = 0; + run(joinPackets({conditional(condition, 5, 3u << 25u), writeWord(results[3], 42), writeWord(condition, 1), conditional(condition, 5, 1u << 25u), writeWord(results[4], 43)}), 0x20); + check(results[3] == 0 && results[4] == 43, "compute queue COND_EXEC with a cache policy evaluated wrong"); + + run(joinPackets({conditional(one, 10), conditional(zero, 10), writeWord(results[5], 51), writeWord(results[6], 52), writeWord(results[7], 53)})); + check(results[5] == 0 && results[6] == 0 && results[7] == 53, "a nested COND_EXEC range reaching past the outer range was not skipped exactly"); + run(joinPackets({conditional(zero, 10), conditional(one, 10), writeWord(results[5], 54), writeWord(results[6], 55), writeWord(results[7], 56)})); + check(results[5] == 0 && results[6] == 55 && results[7] == 56, "a skipped COND_EXEC range did not skip the COND_EXEC inside it"); + + static std::vector target; + target = joinPackets({writeWord(results[8], 61), conditional(zero, 5), writeWord(results[9], 62), writeWord(results[10], 63)}); + run(joinPackets({conditional(zero, 4), indirectBuffer(target), writeWord(results[11], 64)})); + check(results[8] == 0 && results[10] == 0 && results[11] == 64, "COND_EXEC over an INDIRECT_BUFFER did not skip the whole buffer"); + run(joinPackets({conditional(one, 4), indirectBuffer(target), writeWord(results[11], 65)})); + check(results[8] == 61 && results[9] == 0 && results[10] == 63 && results[11] == 65, "COND_EXEC over an INDIRECT_BUFFER or inside one evaluated wrong"); + + const auto rejected = [](std::vector words, const char* text) { + results[12] = 0; + expectFailure([&] { submitWords(words); }, text); + AgcDriverWaitIdle_nid_postfix(); + check(results[12] == 0, "a rejected conditional submission executed a prefix"); + }; + const auto sentinel = writeWord(results[12], 1); + rejected(joinPackets({sentinel, conditional(one, 3), writeWord(results[13], 1)}), "ends inside a packet"); + rejected(joinPackets({sentinel, conditional(one, 6), writeWord(results[13], 1)}), "exceeds its command buffer"); + rejected(joinPackets({sentinel, conditional(one, 6), {0xc004105cu, 7, 0, 1, 0, 0}}), "a flip inside a conditional execution range"); + rejected(joinPackets({sentinel, conditional(one, 2), makePacket(0x59, {0x80000000u})}), "REWIND inside a conditional execution range"); + rejected(joinPackets({sentinel, conditional(one, 4), indirectBuffer(target, true)}), "chained INDIRECT_BUFFER"); + rejected(joinPackets({sentinel, conditional(one, 2), {0x40000000u, 0}}), "whole packets"); + rejected(joinPackets({sentinel, makePacket(0x22, {low(&one), high(&one), 0, 0x4005}), writeWord(results[13], 1)}), "reserved count bits"); + static std::vector unaligned, overlong; + unaligned = joinPackets({conditional(one, 3), writeWord(results[13], 1)}); + overlong = joinPackets({conditional(one, 10), writeWord(results[13], 1)}); + rejected(joinPackets({sentinel, indirectBuffer(unaligned)}), "ends inside a packet"); + rejected(joinPackets({sentinel, indirectBuffer(overlong), writeWord(results[13], 1)}), "exceeds its command buffer"); + check(results[13] == 0, "a rejected conditional submission executed a guarded packet"); +} + void testAsyncMemoryFailure() { auto commands = makePacket(0x37, {0x100, 0x1000, 0, 1}); Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; @@ -572,11 +724,14 @@ int main(int argc, char** argv) { testCopies(); testMemoryCopyDecode(); testMemorySynchronization(); + testConditionalValidation(); + testConditionReadSynchronization(); testEventWrite(); testAcquireMem(); testDriverSubmission(); + testConditionalSubmission(); LibcRunShutdown_nid_postfix(); - std::puts("PM4 catalog, registers, state, memory and submission tests passed"); + std::puts("PM4 catalog, registers, state, memory, conditional execution and submission tests passed"); return 0; } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 732e684a1..795db774c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -429,6 +429,38 @@ void lateLabelTests(Recorder& recorder) { Require(!Recorder::LookupLabel(a, 4, 5).has_value() && !Recorder::LookupLabel(b, 8, 5).has_value() && !recorder.PendingLabelIn(0x70000, 0x400), "(7) entries outlived their batch"); } +void largeLabelTests(Recorder& recorder) { + constexpr std::uint64_t slot = 0x71000, small = slot + 0x10, outside = slot - 4, wide = 0x72000; + const std::array one{std::byte{1}, std::byte{0}, std::byte{0}, std::byte{0}}; + std::array clear{}; + std::array limit{}; + limit.fill(std::byte{3}); + recorder.Sync(); + Require(recorder.PendingLabels() == 0 && !Recorder::PendingLabelSince().has_value() && !Recorder::WideLabelIn(0, ~0ull), "the table is not empty before the large label tests"); + recorder.NoteLabel(small, one, 100, 0); + recorder.NoteLabel(outside, one, 100, 0); + Recorder::CloseLabelGroup(AgcDriver::GuestMemory::TrackerGeneration()); + Require(recorder.PendingLabels() == 2 && Recorder::LookupLabel(small, 4, 50).has_value(), "4-byte labels did not enter the table"); + recorder.NoteLabel(slot, clear, 101, 0); + Recorder::CloseLabelGroup(AgcDriver::GuestMemory::TrackerGeneration()); + Require(recorder.PendingLabels() == 1 && Recorder::LookupLabel(outside, 4, 50).has_value(), "a label larger than the table bound entered dwords or removed one outside its range"); + Require(!Recorder::LookupLabel(small, 4, 50).has_value() && !Recorder::LookupLabel(slot, 8, 0).has_value(), "an older label under a larger one still composes the value"); + Require(Recorder::WideLabelIn(slot, 4) && Recorder::WideLabelIn(slot + clear.size() - 4, 4) && Recorder::WideLabelIn(slot - 0x100, 0x104) && !Recorder::WideLabelIn(slot - 0x100, 0x100) && !Recorder::WideLabelIn(slot + clear.size(), 4), "a large label's range is wrong"); + Require(recorder.PendingLabelIn(slot + 0x20, 0x20) && recorder.PendingWriteOverlaps(slot, clear.size()), "a large label is not pending"); + Require(Recorder::PendingLabelSince().has_value(), "a large label did not start the label flush deadline"); + recorder.NoteLabel(small, one, 102, 0); + Recorder::CloseLabelGroup(AgcDriver::GuestMemory::TrackerGeneration()); + const auto later = Recorder::LookupLabel(small, 4, 101); + Require(later.has_value() && later->value == 1 && later->stamp == 102, "a label after a large one is not served"); + recorder.NoteLabel(wide, limit, 103, 0); + Recorder::CloseLabelGroup(AgcDriver::GuestMemory::TrackerGeneration()); + Require(recorder.PendingLabels() == 2 + Recorder::LabelTableBytes / 4 && !Recorder::WideLabelIn(wide, limit.size()), "a label at the table bound did not enter every dword"); + const auto bound = Recorder::LookupLabel(wide + Recorder::LabelTableBytes - 8, 8, 0); + Require(bound.has_value() && bound->value == 0x0303030303030303ull && bound->stamp == 103, "a label at the table bound is not served"); + recorder.Sync(); + Require(recorder.PendingLabels() == 0 && !Recorder::WideLabelIn(0, ~0ull) && !recorder.PendingLabelIn(slot, clear.size()) && !recorder.PendingWriteOverlaps(slot, clear.size()), "large label tests left entries, ranges or writes behind"); +} + // (6) GuestMemory::UnchangedSinceCollected: false outside the watched arena, and around a // MarkWritten or a CPU write of the block inside it. void unchangedSinceTests() { @@ -768,6 +800,91 @@ void movedMetadataTests(const Device& device, Recorder& recorder) { Require(holds(*back, {0x55, 0x55, 0x55, 0x55}), "the image remade under the first keys does not hold the stored texels"); } +void viewPastLastMipTests(const Device& device, Recorder& recorder) { + const auto& base = device.GetContext(); + if (base.hostImportAlignment == 0) { + std::cout << "host imports unavailable: views past the last mip not tested\n"; + return; + } + constexpr std::uint32_t side = 256; + constexpr std::size_t bytes = 0x60000; +#ifdef _WIN32 + void* block = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); +#else + void* block = std::aligned_alloc(65536, bytes); +#endif + Require(block != nullptr, "cannot allocate the past-last-mip block"); + auto* texels = static_cast(block); + const auto address = reinterpret_cast(block); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, bytes, true, true); + } + struct Unregister { + const Context& context; + void* block; + std::uint64_t address; + ~Unregister() { + ClearCachedTextures(context.device); + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + HostImportFor(context, address, bytes); + } + } unregister{base, block, address}; + if (HostImportFor(base, address, bytes) == nullptr) { + std::cout << "host import of the past-last-mip block refused: views past the last mip not tested\n"; + return; + } + TextureDetiler detiler(base); + auto context = base; + context.detiler = &detiler; + const auto view = [&](std::uint32_t level) { + const std::array words{ + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (56u << 20u) | (((side - 1u) & 3u) << 30u), + ((side - 1u) >> 2u) | ((side - 1u) << 14u), + 0xfacu | (level << 12u) | (level << 16u) | (0x1bu << 20u) | (9u << 28u), + 0u, + 2u << 4u, + 0u, + 0u, + }; + return DecodeTextureResource(words); + }; + const auto texel = [&](const StorageTexture& image, std::uint32_t level) { + Buffer readback(context, 4, VK_BUFFER_USAGE_TRANSFER_DST_BIT); + const auto commands = recorder.Commands(); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + VkBufferImageCopy copy{}; + copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, 1}; + copy.imageExtent = {1, 1, 1}; + context.Function("vkCmdCopyImageToBuffer")(commands, image.Image(), VK_IMAGE_LAYOUT_GENERAL, readback.Handle(), 1, ©); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + std::uint32_t value = 0; + std::memcpy(&value, readback.Bytes().data(), sizeof(value)); + return value; + }; + std::memset(texels, 0x55, bytes); + std::memset(texels + 0x8400, 0x66, 4); + std::memset(texels + 0x4800, 0x77, 4); + const auto allocatedView = view(2); + const auto pastView = view(3); + Require(allocatedView.mipCount == 3 && pastView.mipCount == 4 && DescribeSurface(pastView).guestBytes == bytes, "a 256x256 32 bpp SW_64KB_R_X surface viewed past MAX_MIP 2 decoded wrongly"); + const auto allocated = CachedStorageSurface(context, allocatedView); + Require(allocated->Descriptor().mipCount == 3, "the allocated levels' storage image has an unexpected level count"); + const auto past = CachedStorageSurface(context, pastView); + Require(past->Descriptor().mipCount == 4, "the storage image of a view past the last mip lacks the level it names"); + Require(!StorageImageCached(context, allocated.get()), "the allocated levels' image is still the surface's after a view past its last mip"); + Require(CachedStorageSurface(context, allocatedView) == past, "a view of the allocated levels took another image than the view past the last mip"); + Require(texel(*past, 2) == 0x66666666u, "level 2 was not read from its addrlib tail slot"); + Require(texel(*past, 3) == 0x77777777u, "the level past the last mip was not read from its addrlib tail slot"); +} + void resourceReadTests(const Device& device, Recorder& recorder) { const auto& context = device.GetContext(); if (context.hostImportAlignment == 0) { @@ -1700,6 +1817,143 @@ void staleGenerationTests(const Device& device, Recorder& recorder) { #endif } +// A write-back stores the texels of an image and leaves every other byte of the surface's memory +// as it was: the padding of the tile blocks the surface covers only partly (its last block column +// and row), the tail block of a mip chain, the bytes between the mips and the pitch padding of a +// linear surface. Another surface over the memory, or the CPU, reads those bytes. Each case clears +// every mip of an image over memory holding a pattern no texel equals, stores it and reads the +// memory back (watched memory through the flush hook, after a store into the import's unit shadow): +// each 4-byte word is the clear texel or the pattern, and the texels are exactly the surface's. +// The 64 KiB-aligned tiled cases store through block windows, the others whole layers. +void writeBackPaddingTests(const Device& device, Recorder& recorder, bool watched) { + const auto& base = device.GetContext(); + if (base.hostImportAlignment == 0) { + std::cout << "host imports unavailable: write-back padding not tested\n"; + return; + } + struct Case { + const char* name; + TextureTileMode tileMode; + std::uint32_t width; + std::uint32_t height; + std::uint32_t mipCount; + std::uint64_t offset; + }; + constexpr Case cases[] = { + {"64 KiB R_X 200x150", TextureTileMode::kR64KBX, 200, 150, 1, 0}, + {"64 KiB R_X 200x150, 3 mips", TextureTileMode::kR64KBX, 200, 150, 3, 0}, + {"64 KiB R_X 520x260, 6 mips", TextureTileMode::kR64KBX, 520, 260, 6, 0}, + {"64 KiB R_X 200x150 off a 64 KiB boundary, 3 mips", TextureTileMode::kR64KBX, 200, 150, 3, 4096}, + {"4 KiB S 100x70, 2 mips", TextureTileMode::kStandard4KB, 100, 70, 2, 0}, + {"linear 200x150", TextureTileMode::kLinear, 200, 150, 1, 0}, + }; + const auto pattern = [](std::size_t i) { return static_cast(i * 131u + 7u); }; + constexpr std::array red{255, 0, 0, 255}; + std::string failures; + for (const auto& test : cases) { + const std::string what = std::string(watched ? "(watched) " : "") + test.name; + GuestTextureResource resource{}; + resource.width = test.width; + resource.height = test.height; + resource.mipCount = test.mipCount; + resource.tileMode = test.tileMode; + resource.dimension = TextureDimension::k2D; + resource.format = 56; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + const auto geometry = DescribeSurface(resource); + const auto surfaceBytes = static_cast(geometry.guestBytes); + std::size_t texelCount = 0; + for (const auto& mip : geometry.mips) texelCount += static_cast(mip.width) * mip.height; + // The surface, then a 64 KiB unit past it that no store may reach. + const auto bytes = (static_cast(test.offset) + surfaceBytes + 65535) / 65536 * 65536 + 65536; + void* block = nullptr; + if (watched) { + block = AllocateWatched(bytes, 65536); + if (block == nullptr) { + std::cout << "no write watching: write-back padding in watched memory not tested\n"; + return; + } + } else { +#ifdef _WIN32 + block = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); +#else + block = std::aligned_alloc(65536, bytes); +#endif + } + Require(block != nullptr, "cannot allocate the write-back padding block"); + auto* memory = static_cast(block); + for (std::size_t i = 0; i < bytes; ++i) memory[i] = pattern(i); + const auto blockAddress = reinterpret_cast(block); + const auto address = blockAddress + test.offset; + { + GuestAllocations::Mutation mutation; + mutation.Add(block, bytes, true, true); + } + struct Unregister { + const Context& context; + void* block; + std::uint64_t address; + std::size_t bytes; + bool watched; + ~Unregister() { + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + HostImportFor(context, address, bytes); + if (watched) ReleaseWatched(block, bytes); + } + } unregister{base, block, blockAddress, bytes, watched}; + if (HostImportFor(base, blockAddress, bytes) == nullptr) { + std::cout << "host import of the write-back padding block refused: write-back padding not tested\n"; + return; + } + TextureDetiler detiler(base); + auto context = base; + context.detiler = &detiler; + resource.baseAddress = address; + { + auto image = std::make_shared(context, detiler, resource, 0); + const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, test.mipCount, 0, 1}; + const auto commands = recorder.Commands(); + recorder.Keep(image); + const VkClearColorValue value{{1.0f, 0.0f, 0.0f, 1.0f}}; + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); + context.Function("vkCmdClearColorImage")(commands, image->Image(), VK_IMAGE_LAYOUT_GENERAL, &value, 1, &range); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + image->MarkDirty(); + image->WriteBack(); + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + std::vector read(bytes); + if (watched) AgcDriver::GuestMemory::Read(blockAddress, read); + else std::memcpy(read.data(), memory, bytes); + std::size_t texels = 0, garbage = 0, outside = 0; + for (std::size_t i = 0; i < bytes; i += 4) { + const bool inside = i >= test.offset && i < test.offset + surfaceBytes; + bool isTexel = inside, isPattern = true; + for (std::size_t c = 0; c < 4; ++c) { + const auto byte = std::to_integer(read[i + c]); + isTexel = isTexel && byte == red[c]; + isPattern = isPattern && byte == pattern(i + c); + } + if (isTexel) ++texels; + else if (!isPattern) ++(inside ? garbage : outside); + } + if (garbage != 0 || outside != 0 || texels != texelCount) { + failures += what + ": the write-back stored " + std::to_string(texels) + " texels of " + std::to_string(texelCount) + " and changed " + std::to_string(garbage) + " words no texel holds (" + std::to_string(outside) + " past the surface)\n"; + } + } + recorder.Sync(); + } + if (!failures.empty()) throw std::runtime_error("write-back padding:\n" + failures); + std::cout << "write-back padding" << (watched ? " (watched)" : "") << ": ok\n"; +} + void importWindowTests(const Device& device, Recorder& recorder) { using namespace AgcDriver::GuestMemory; const auto& base = device.GetContext(); @@ -2319,6 +2573,44 @@ void firstLayerViewTests(const Device& device, Recorder& recorder) { expectRed(program.Red(flatTexture.View(), flatTexture.Layout(), 0.0f), 0x20 / 255.0f, "a 2D texture over the surface does not read its first layer"); } +void atomicViewTests(const Device& device, Recorder& recorder) { + const auto& context = device.GetContext(); + TextureDetiler detiler(context); + auto withDetiler = context; + withDetiler.detiler = &detiler; + for (const std::uint32_t format : {20u, 21u, 22u, 56u}) { + GuestTextureResource resource{}; + resource.width = 64; + resource.height = 4; + resource.mipCount = 1; + resource.tileMode = TextureTileMode::kLinear; + resource.dimension = TextureDimension::k2D; + resource.format = format; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + const auto geometry = DescribeSurface(resource); + std::vector memory(static_cast(geometry.guestBytes) + 256); + resource.baseAddress = (reinterpret_cast(memory.data()) + 255) & ~std::uintptr_t{255}; + auto image = std::make_shared(withDetiler, detiler, resource, 0); + recorder.Keep(image); + if (format == 56u) { + bool refused = false; + try { + static_cast(image->AtomicView(0, false)); + } catch (const std::exception&) { + refused = true; + } + Require(refused, "an 8_8_8_8 storage image has an atomic view"); + continue; + } + const auto atomic = image->AtomicView(0, false); + Require(atomic != VK_NULL_HANDLE && atomic == image->AtomicView(0, false), "atomic storage views are not reused"); + Require((atomic == image->View(0)) == (format == 20u), "only a 32_UINT storage image serves atomics through its own view"); + } +} + void keysFillTests(const Device& device, Recorder& recorder) { const auto& base = device.GetContext(); if (base.hostImportAlignment == 0) { @@ -2444,6 +2736,7 @@ int main() { batchStampTests(recorder); labelTests(recorder); lateLabelTests(recorder); + largeLabelTests(recorder); unchangedSinceTests(); closeRaceTests(device, recorder); keyProofTests(device, recorder); @@ -2455,10 +2748,13 @@ int main() { RunResidentPresentTests(device.GetContext()); storeRunTests(device, recorder); movedMetadataTests(device, recorder); + viewPastLastMipTests(device, recorder); keysFillTests(device, recorder); unitShadowTests(device, recorder); storageRefreshTests(device, recorder, false); storageRefreshTests(device, recorder, true); + writeBackPaddingTests(device, recorder, false); + writeBackPaddingTests(device, recorder, true); importWatchTests(device); staleGenerationTests(device, recorder); importWindowTests(device, recorder); @@ -2466,6 +2762,7 @@ int main() { dataRefreshTests(device, recorder); minLodTests(device, recorder); firstLayerViewTests(device, recorder); + atomicViewTests(device, recorder); metadataPassTests(device, recorder); pendingKeyStoreTests(device, recorder); sampleDumpTests(device, recorder); diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index 55e79fbc5..20dd3e202 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -31,7 +31,7 @@ void setEnvironment(const char* name, const std::string& value) { } bool sameBinding(const DescriptorBinding& left, const DescriptorBinding& right) { - return left.kind == right.kind && left.role == right.role && left.descriptorSet == right.descriptorSet && left.binding == right.binding && left.count == right.count && left.guestDescriptor == right.guestDescriptor && left.readOnly == right.readOnly && left.imageShape == right.imageShape && left.samplerDepthCompare == right.samplerDepthCompare && left.imageWritten == right.imageWritten && left.imageDepthCompare == right.imageDepthCompare && left.bufferAtomic == right.bufferAtomic && left.bufferWritten == right.bufferWritten; + return left.kind == right.kind && left.role == right.role && left.descriptorSet == right.descriptorSet && left.binding == right.binding && left.count == right.count && left.guestDescriptor == right.guestDescriptor && left.readOnly == right.readOnly && left.imageShape == right.imageShape && left.samplerDepthCompare == right.samplerDepthCompare && left.imageWritten == right.imageWritten && left.imageDepthCompare == right.imageDepthCompare && left.imageAtomic == right.imageAtomic && left.bufferAtomic == right.bufferAtomic && left.bufferWritten == right.bufferWritten; } bool sameBindings(const std::vector& left, const std::vector& right) { @@ -92,6 +92,7 @@ DescriptorBinding sampleBinding(std::uint32_t seed) { binding.samplerDepthCompare = {true, false, true}; binding.imageDepthCompare = {false, true, false}; binding.imageWritten = {false, true, true}; + binding.imageAtomic = {false, true, false}; binding.bufferAtomic = {true}; binding.bufferWritten = {false, false, true, true, false}; return binding; diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index fd8aa6493..a65c7d9e5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -893,6 +893,146 @@ void verifyTwoLaneUniformValues() { require(multiplies(32u) == oneLane, "two-lane uniform values: a two-lane invocation computes a scalar value once per lane"); } +void verifyFunctionLdsBound() { + using namespace ShaderRecompiler; + const auto build = [](const auto& body) { + IrProgram program; + program.Resources().stage = IrShaderStage::Pixel; + for (const std::uint32_t offset : {0u, 256u, 512u, 0xfffffff0u}) { + MemoryInfo lds; + lds.kind = ResourceKind::Lds; + lds.offset = offset; + program.Resources().memoryInfo.push_back(lds); + } + auto& block = program.CreateBlock(); + program.SetEntryBlock(block); + program.BlockOrder().push_back(&block); + const auto emit = [&](IrOpcode opcode, IrType type, std::initializer_list arguments, std::uint32_t memory = ~0u) -> IrValue& { + std::uint64_t bits = 0; + if (memory != ~0u) { + MemoryFlags flags{memory, 0u}; + std::memcpy(&bits, &flags, sizeof(flags)); + } + auto& value = program.CreateValue(opcode, type, bits); + for (auto* argument : arguments) value.AddArgument(argument); + block.AppendInstruction(&value); + return value; + }; + const auto constant = [&](std::uint32_t immediate) -> IrValue& { + auto& value = program.CreateValue(IrOpcode::Void, IrType::U32); + value.SetImmediateU32(immediate); + return value; + }; + auto& active = program.CreateValue(IrOpcode::Void, IrType::U1); + active.SetImmediateBool(true); + body(program, block, emit, constant, active); + return std::pair{FunctionLdsDwords(program), AnalyzeProgramRequirements(program)}; + }; + + const auto [laneSlots, laneRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + auto& lane = emit(IrOpcode::LaneId, IrType::U32, {}); + auto& address = emit(IrOpcode::ShiftLeftLogical32, IrType::U32, {&lane, &constant(2u)}); + for (const std::uint32_t memory : {0u, 1u, 2u}) emit(IrOpcode::WriteSharedU32, IrType::Void, {&address, &lane, &active}, memory); + emit(IrOpcode::LoadSharedU32, IrType::U32, {&address, &active}, 2u); + }); + require(laneSlots == 192u, "function LDS: lane-strided slots up to byte 767 must take 192 dwords"); + require(laneRequirements.functionLds && laneRequirements.functionLdsDwords == 192u, "function LDS: the requirements do not carry the bounded size"); + { + std::vector rebased; + for (const auto& [inst, address] : laneRequirements.functionLdsAddresses) rebased.push_back(address); + std::sort(rebased.begin(), rebased.end()); + require(rebased == std::vector{0u, 0u, 0u, 0u}, "function LDS: lane-strided slots must be addressed without the lane term"); + } + + const auto [mixed, mixedRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + auto& lane = emit(IrOpcode::LaneId, IrType::U32, {}); + auto& address = emit(IrOpcode::ShiftLeftLogical32, IrType::U32, {&lane, &constant(2u)}); + emit(IrOpcode::WriteSharedU32, IrType::Void, {&address, &lane, &active}, 0u); + emit(IrOpcode::LoadSharedU32, IrType::U32, {&constant(8u), &active}, 0u); + }); + require(mixed == 64u && mixedRequirements.functionLdsAddresses.empty(), "function LDS: a constant address beside lane-strided ones must keep the lane term"); + + const auto [strides, stridesRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + auto& lane = emit(IrOpcode::LaneId, IrType::U32, {}); + auto& four = emit(IrOpcode::ShiftLeftLogical32, IrType::U32, {&lane, &constant(2u)}); + auto& eight = emit(IrOpcode::IMul32, IrType::U32, {&lane, &constant(8u)}); + emit(IrOpcode::WriteSharedU32, IrType::Void, {&four, &lane, &active}, 0u); + emit(IrOpcode::LoadSharedU32, IrType::U32, {&eight, &active}, 0u); + }); + require(strides == 128u && stridesRequirements.functionLdsAddresses.empty(), "function LDS: two lane strides must keep the lane term"); + + const auto [halfWord, halfWordRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + auto& lane = emit(IrOpcode::LaneId, IrType::U32, {}); + auto& address = emit(IrOpcode::ShiftLeftLogical32, IrType::U32, {&lane, &constant(1u)}); + emit(IrOpcode::WriteSharedU32, IrType::Void, {&address, &lane, &active}, 0u); + }); + require(halfWord == 64u && halfWordRequirements.functionLdsAddresses.empty(), "function LDS: a lane stride that is not whole dwords must keep the lane term"); + + const auto [summed, summedRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + auto& lane = emit(IrOpcode::LaneId, IrType::U32, {}); + auto& scaled = emit(IrOpcode::IMul32, IrType::U32, {&constant(16u), &lane}); + auto& low = emit(IrOpcode::IAdd32, IrType::U32, {&scaled, &constant(4u)}); + auto& other = emit(IrOpcode::LaneId, IrType::U32, {}); + auto& shifted = emit(IrOpcode::ShiftLeftLogical32, IrType::U32, {&other, &constant(4u)}); + auto& high = emit(IrOpcode::IAdd32, IrType::U32, {&constant(1024u), &shifted}); + emit(IrOpcode::WriteSharedU32x2, IrType::Void, {&low, &lane, &lane, &active}, 0u); + emit(IrOpcode::LoadSharedU32x4, IrType::U32x4, {&high, &active}, 1u); + }); + { + std::vector rebased; + for (const auto& [inst, address] : summedRequirements.functionLdsAddresses) rebased.push_back(address); + std::sort(rebased.begin(), rebased.end()); + require(rebased == std::vector{4u, 1024u}, "function LDS: lane ids of separate instructions with one stride must share the rebase"); + require(summedRequirements.functionLdsDwords == 384u && summed == 576u, "function LDS: the rebased array must hold the rebased addresses"); + } + + const auto [wide, wideRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + auto& lane = emit(IrOpcode::LaneId, IrType::U32, {}); + auto& masked = emit(IrOpcode::BitwiseAnd32, IrType::U32, {&lane, &constant(7u)}); + auto& scaled = emit(IrOpcode::IMul32, IrType::U32, {&masked, &constant(16u)}); + auto& chosen = emit(IrOpcode::SelectU32, IrType::U32, {&active, &scaled, &constant(0x100u)}); + emit(IrOpcode::LoadSharedU32x4, IrType::U32x4, {&chosen, &active}, 0u); + }); + require(wide == 128u, "function LDS: a 4-dword access at byte 0x100 must take 68 dwords, rounded to 128"); + require(wideRequirements.functionLdsDwords == 128u, "function LDS: the requirements do not carry the wide access's size"); + require(wideRequirements.functionLdsAddresses.empty(), "function LDS: a selected address must keep the lane term"); + + const auto [unbounded, unboundedRequirements] = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + auto& user = emit(IrOpcode::GetUserData, IrType::U32, {&constant(0u)}); + emit(IrOpcode::WriteSharedU32, IrType::Void, {&user, &user, &active}, 0u); + }); + require(unbounded == FunctionLdsDwordLimit && unboundedRequirements.functionLdsDwords == FunctionLdsDwordLimit, "function LDS: an address without a bound must keep the full array"); + + const auto wrapping = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + emit(IrOpcode::LoadSharedU32, IrType::U32, {&constant(0x20u), &active}, 3u); + }).first; + require(wrapping == FunctionLdsDwordLimit, "function LDS: an offset that can wrap the address must keep the full array"); + + const auto loop = build([](IrProgram& program, IrBlock& block, auto& emit, auto& constant, IrValue& active) { + auto& phi = program.CreateValue(IrOpcode::Phi, IrType::U32); + block.AppendInstruction(&phi); + auto& next = emit(IrOpcode::IAdd32, IrType::U32, {&phi, &constant(4u)}); + phi.AddPhiOperand(&block, &constant(0u)); + phi.AddPhiOperand(&block, &next); + emit(IrOpcode::WriteSharedU32, IrType::Void, {&phi, &next, &active}, 0u); + }).first; + require(loop == FunctionLdsDwordLimit, "function LDS: a loop-carried address must keep the full array"); + + const auto joined = build([](IrProgram& program, IrBlock& block, auto& emit, auto& constant, IrValue& active) { + auto& phi = program.CreateValue(IrOpcode::Phi, IrType::U32); + block.AppendInstruction(&phi); + phi.AddPhiOperand(&block, &constant(0x40u)); + phi.AddPhiOperand(&block, &constant(0x3fcu)); + emit(IrOpcode::WriteSharedU32, IrType::Void, {&phi, &constant(1u), &active}, 0u); + }).first; + require(joined == 256u, "function LDS: a phi of bounded addresses must take its largest"); + + const auto unsized = build([](IrProgram&, IrBlock&, auto& emit, auto& constant, IrValue& active) { + emit(IrOpcode::LoadShared, IrType::U32, {&constant(0u), &active}, 0u); + }).first; + require(unsized == FunctionLdsDwordLimit, "function LDS: an access without a known width must keep the full array"); +} + int main() { try { using namespace ShaderRecompiler; @@ -908,6 +1048,7 @@ int main() { verifyComputedTexelOffsets(); verifyWaveUniformValues(); verifyTwoLaneUniformValues(); + verifyFunctionLdsBound(); #if ANYPS5_ENABLE_SPIRV_TOOLS const std::vector minimalSpirv{ 0x07230203u, 0x00010000u, 0u, 5u, 0u, diff --git a/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp index 37b3b1a95..3058cc098 100644 --- a/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp @@ -1,7 +1,10 @@ #include "GraphicsTests.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureSwizzleEquations.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" #include #include +#include +#include #include #include #include @@ -22,6 +25,57 @@ void reject(TAction action, std::string_view reason) { throw std::runtime_error(std::string("expected texture tiling rejection: ") + std::string(reason)); } +struct ElementAddress { + std::uint32_t mip; + std::uint32_t x; + std::uint32_t y; + std::uint32_t z; + std::uint64_t address; +}; + +std::uint64_t equationOffset(const TextureSwizzleEquation& equation, std::uint32_t x, std::uint32_t y, std::uint32_t z) { + std::uint64_t offset = 0; + for (std::uint32_t bit = 0; bit < 16; ++bit) { + const auto mask = equation.bits[bit]; + const auto selected = (x & (mask & 0xfffu)) ^ ((y << 12) & (mask & 0xfff000u)) ^ ((z << 24) & (mask & 0xff000000u)); + offset |= static_cast(std::popcount(selected) & 1u) << bit; + } + return offset; +} + +GuestTextureResource thickVolume(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t depth, std::uint32_t mipCount) { + GuestTextureResource resource{}; + resource.width = width; + resource.height = height; + resource.depthOrLastArray = depth - 1u; + resource.mipCount = mipCount; + resource.tileMode = tileMode; + resource.dimension = TextureDimension::k3D; + resource.format = format; + return resource; +} + +void requireThickAddresses(const GuestTextureResource& resource, std::uint32_t bytesPerElement, std::uint32_t firstTailLevel, std::uint64_t guestBytes, std::span expected, const std::string& what) { + const auto geometry = DescribeSurface(resource); + Require(geometry.thick && geometry.mips.size() == resource.mipCount && geometry.guestBytes == guestBytes, what + ": guest size differs from addrlib"); + for (std::uint32_t level = 0; level < resource.mipCount; ++level) Require(geometry.mips[level].tail == (level >= firstTailLevel), what + ": mip " + std::to_string(level) + " is on the wrong side of the mip tail"); + const auto blockBytes = resource.tileMode == TextureTileMode::kStandard4KB ? 4096u : 65536u; + const auto* equation = FindTextureSwizzleEquation(resource.tileMode == TextureTileMode::kStandard4KB ? 0x105u : 0x109u, bytesPerElement); + Require(equation != nullptr, what + ": missing thick swizzle equation"); + const auto block = ThickBlockExtent(resource.tileMode, bytesPerElement); + for (const auto& element : expected) { + Require(geometry.HasLayer(element.mip, element.z), what + ": sample slice lies outside its level"); + const auto& mip = geometry.mips.at(element.mip); + auto address = geometry.GuestLayerOffset(element.z) + mip.tiledOffset; + if (mip.tail) { + address += equationOffset(*equation, element.x + mip.tailX, element.y + mip.tailY, element.z); + } else { + address += (static_cast(element.y / block[1]) * mip.blocksPerRow + element.x / block[0]) * blockBytes + equationOffset(*equation, element.x, element.y, element.z); + } + Require(address == element.address, what + ": mip " + std::to_string(element.mip) + " element (" + std::to_string(element.x) + ", " + std::to_string(element.y) + ", " + std::to_string(element.z) + ") detiles from " + std::to_string(address) + " instead of addrlib's " + std::to_string(element.address)); + } +} + } void RunTextureTilingTests() { @@ -142,11 +196,72 @@ void RunTextureTilingTests() { const auto odd = DescribeSurface(volume); Require(odd.mips[1].width == 16 && odd.mips[1].blocksPerRow == 3 && odd.mips[1].tiledSize == 12288, "a 3D level must be padded from its size rounded up, as addrlib does"); Require(odd.mips[0].blocksPerRow == 5 && odd.mips[0].tiledOffset == 12288 && odd.mips[0].tiledSize == 40960, "3D mip 0 of a non-power-of-two volume changed"); - volume.width = 8; - volume.height = 8; - volume.depthOrLastArray = 7; - volume.mipCount = 2; - reject([&] { DescribeSurface(volume); }, "3D texture mip tails are not implemented"); + } + + { + constexpr ElementAddress chainInTail[] = {{0, 0, 0, 0, 0x800}, {0, 7, 7, 7, 0xffc}, {0, 4, 2, 4, 0xe20}, {0, 2, 7, 0, 0x968}, {0, 7, 0, 7, 0xed4}, {1, 0, 0, 0, 0x300}, {1, 3, 3, 3, 0x3fc}, {1, 2, 1, 2, 0x3c8}, {1, 1, 3, 0, 0x32c}, {1, 3, 0, 3, 0x3d4}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard4KB, 56, 8, 8, 8, 2), 4, 0, 4096, chainInTail, "SW_4KB_S 32 bpp 8x8x8, 2 levels"); + constexpr ElementAddress unevenTail[] = {{0, 0, 0, 0, 0x6000}, {0, 32, 19, 11, 0x1f0b8}, {0, 16, 6, 6, 0x85a0}, {0, 11, 19, 0, 0xc06c}, {0, 32, 0, 11, 0x1a090}, {1, 0, 0, 0, 0x3000}, {1, 15, 9, 5, 0x4e5c}, {1, 8, 3, 3, 0x40b8}, {1, 5, 9, 0, 0x3a0c}, {1, 15, 0, 5, 0x4654}, {2, 0, 0, 0, 0x1000}, {2, 7, 4, 2, 0x13c4}, {2, 4, 1, 1, 0x1218}, {2, 2, 4, 0, 0x1140}, {2, 7, 0, 2, 0x12c4}, {3, 0, 0, 0, 0x800}, {3, 3, 1, 0, 0x84c}, {3, 2, 0, 0, 0x840}, {3, 1, 1, 0, 0x80c}, {3, 3, 0, 0, 0x844}, {4, 0, 0, 0, 0x300}, {4, 1, 0, 0, 0x304}, {4, 0, 0, 0, 0x300}, {5, 0, 0, 0, 0x200}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard4KB, 56, 33, 20, 12, 6), 4, 3, 131072, unevenTail, "SW_4KB_S 32 bpp 33x20x12, 6 levels"); + constexpr ElementAddress wideTail[] = {{0, 0, 0, 0, 0x20000}, {0, 39, 23, 19, 0xb0bf8}, {0, 20, 8, 10, 0x2e280}, {0, 13, 23, 0, 0x41b28}, {0, 39, 0, 19, 0x902d8}, {1, 0, 0, 0, 0x10000}, {1, 19, 11, 9, 0x1e178}, {1, 10, 4, 5, 0x11c50}, {1, 6, 11, 0, 0x14360}, {1, 19, 0, 9, 0x1a058}, {2, 0, 0, 0, 0x8000}, {2, 9, 5, 4, 0x9c28}, {2, 5, 2, 2, 0x8388}, {2, 3, 5, 0, 0x8868}, {2, 9, 0, 4, 0x9408}, {3, 0, 0, 0, 0x4000}, {3, 4, 2, 1, 0x4310}, {3, 2, 1, 1, 0x4070}, {3, 1, 2, 0, 0x4108}, {3, 4, 0, 1, 0x4210}, {4, 0, 0, 0, 0x1000}, {4, 1, 0, 0, 0x1008}, {5, 0, 0, 0, 0xa00}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard64KB, 71, 40, 24, 20, 6), 8, 2, 786432, wideTail, "SW_64KB_S 64 bpp 40x24x20, 6 levels"); + constexpr ElementAddress byteTail[] = {{0, 0, 0, 0, 0x20000}, {0, 99, 59, 69, 0x11c8af}, {0, 50, 20, 35, 0x8d116}, {0, 33, 59, 0, 0x4c829}, {0, 99, 0, 69, 0xf8087}, {1, 0, 0, 0, 0x10000}, {1, 49, 29, 34, 0x7d919}, {1, 25, 10, 17, 0x13a25}, {1, 16, 29, 0, 0x15908}, {1, 49, 0, 34, 0x79011}, {2, 0, 0, 0, 0x8000}, {2, 24, 14, 16, 0xbb20}, {2, 12, 5, 8, 0x8748}, {2, 8, 14, 0, 0x8b20}, {2, 24, 0, 16, 0xb200}, {3, 0, 0, 0, 0x4000}, {3, 11, 6, 7, 0x43b7}, {3, 6, 2, 4, 0x40e2}, {3, 4, 6, 0, 0x4160}, {3, 11, 0, 7, 0x4297}, {4, 0, 0, 0, 0x1000}, {4, 5, 2, 3, 0x1075}, {4, 3, 1, 2, 0x101b}, {4, 2, 2, 0, 0x1022}, {4, 5, 0, 3, 0x1055}, {5, 0, 0, 0, 0xa00}, {5, 2, 0, 1, 0xa06}, {5, 1, 0, 0, 0xa01}, {6, 0, 0, 0, 0x900}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard64KB, 1, 100, 60, 70, 7), 1, 2, 1179648, byteTail, "SW_64KB_S 8 bpp 100x60x70, 7 levels"); + constexpr ElementAddress wideElementTail[] = {{0, 0, 0, 0, 0x3000}, {0, 8, 4, 2, 0x5880}, {0, 4, 1, 1, 0x4030}, {0, 3, 4, 0, 0x3a40}, {0, 8, 0, 2, 0x5080}, {1, 0, 0, 0, 0x1000}, {1, 3, 1, 0, 0x1260}, {1, 2, 0, 0, 0x1200}, {1, 1, 1, 0, 0x1060}, {1, 3, 0, 0, 0x1240}, {2, 0, 0, 0, 0x800}, {2, 1, 0, 0, 0x840}, {3, 0, 0, 0, 0x300}}; + requireThickAddresses(thickVolume(TextureTileMode::kStandard4KB, 77, 9, 5, 3, 4), 16, 2, 24576, wideElementTail, "SW_4KB_S 128 bpp 9x5x3, 4 levels"); + } + + { + // CoveredMipBytes against the XOR address equations: every element slot of a covered range + // holds exactly one element of the mip, and every tile block left out holds a slot no + // element does (the bytes a write-back must keep). + constexpr std::array modes{TextureTileMode::kZ64KBX, TextureTileMode::kS64KBX, TextureTileMode::kD64KBX, TextureTileMode::kR64KBX}; + constexpr std::array, 6> sizes{{{200, 150}, {1920, 1080}, {2432, 1368}, {960, 540}, {256, 128}, {300, 1}}}; + for (const auto mode : modes) { + for (std::uint32_t bytesPerElement = 1; bytesPerElement <= 16; bytesPerElement *= 2) { + const auto* equation = FindTextureSwizzleEquation(XorSwizzleMode(mode), bytesPerElement); + Require(equation != nullptr, "missing XOR swizzle equation"); + const auto block = ThinBlockLayout(mode, bytesPerElement); + for (const auto& [width, height] : sizes) { + if (static_cast(width) * height > (1u << 20) && mode != TextureTileMode::kR64KBX) continue; + const auto what = "XOR mode " + std::to_string(XorSwizzleMode(mode)) + ", " + std::to_string(bytesPerElement) + " bytes, " + std::to_string(width) + "x" + std::to_string(height); + for (const auto& mip : ComputeElementMipLayout(mode, bytesPerElement, width, height, 3)) { + const auto covered = CoveredMipBytes(mode, bytesPerElement, mip); + if (mip.tail) { + Require(covered.empty(), what + ": a tail mip has covered bytes"); + continue; + } + std::vector held(static_cast(mip.tiledSize / bytesPerElement), 0); + for (std::uint32_t y = 0; y < mip.height; ++y) { + for (std::uint32_t x = 0; x < mip.width; ++x) { + const auto offset = (static_cast(y / block[2]) * mip.blocksPerRow + x / block[1]) * block[0] + equationOffset(*equation, x, y, 0); + Require(offset % bytesPerElement == 0 && offset < mip.tiledSize, what + ": an element lies outside the mip"); + held[static_cast(offset / bytesPerElement)] += 1; + } + } + std::vector inCovered(static_cast(mip.tiledSize / block[0]), false); + std::uint64_t previous = 0; + for (const auto& [begin, end] : covered) { + Require(begin >= previous && begin < end && end <= mip.tiledSize && begin % block[0] == 0 && end % block[0] == 0, what + ": covered ranges are not ascending whole blocks"); + previous = end; + for (auto slot = begin / bytesPerElement; slot < end / bytesPerElement; ++slot) Require(held[static_cast(slot)] == 1, what + ": a covered byte holds no element or several"); + for (auto at = begin; at < end; at += block[0]) inCovered[static_cast(at / block[0])] = true; + } + for (std::size_t index = 0; index < inCovered.size(); ++index) { + if (inCovered[index]) continue; + const auto first = index * block[0] / bytesPerElement; + const auto last = (index + 1) * block[0] / bytesPerElement; + Require(std::any_of(held.begin() + static_cast(first), held.begin() + static_cast(last), [](std::uint8_t count) { return count == 0; }), what + ": a block every byte of which holds an element is left out"); + } + } + } + } + } + const auto linear = ComputeElementMipLayout(TextureTileMode::kLinear, 4, 200, 3, 1).front(); + const auto rows = CoveredMipBytes(TextureTileMode::kLinear, 4, linear); + Require(linear.pitchBytes > 800 && rows.size() == 3 && rows[1].first == linear.pitchBytes && rows[1].second == linear.pitchBytes + 800, "linear covered bytes are not the rows without their pitch padding"); + const auto dense = ComputeElementMipLayout(TextureTileMode::kLinear, 4, 256, 3, 1).front(); + Require(dense.pitchBytes == 1024 && CoveredMipBytes(TextureTileMode::kLinear, 4, dense) == std::vector>{{0, 3072}}, "linear rows without padding are not one range"); } reject([] { ComputeSurfaceSize({}, 1); }, "empty mip chain"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp new file mode 100644 index 000000000..aab4cf664 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicCsub.cpp @@ -0,0 +1,200 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 8; +constexpr std::uint32_t Written = 5; +constexpr std::uint32_t SharedWords = 2; +constexpr std::uint32_t Untouched = 0xdeadbeefu; +constexpr std::uint32_t MaskedExecLo = 0x55555555u; +constexpr std::uint32_t SharedPartial = 0x800186a0u; +constexpr std::uint32_t SharedExhausted = 1000u; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34040085, 0xe0301000, 0x80000401, 0xe0301004, 0x80000501, 0xe0301008, 0x80000601, + 0xbf8c3f70, 0x7e140280, 0x461414c1, 0x481414c1, 0x7e0e0304, 0xe0d05000, 0x80010702, 0x7e100304, + 0xbefe03ff, 0x55555555, 0xe0d05004, 0x80010802, 0xbefe03c1, 0x7e120305, 0xe0d04800, 0x80010900, + 0x7e160306, 0xe0d04804, 0x80010b00, 0xbf8c3f70, 0xe0701008, 0x80010702, 0xe070100c, 0x80010802, + 0xe0701010, 0x80010a02, 0xbf810000, +}; + +alignas(256) constexpr std::array NoReturnCode{ + 0xe0d01000, 0x80010702, 0xbf810000, +}; + +struct Workgroup { + std::uint32_t threads; + std::uint32_t waveSize; + + [[nodiscard]] std::string Name() const { + return "wave" + std::to_string(waveSize) + " " + std::to_string(threads) + " threads"; + } +}; + +constexpr std::array Workgroups{{{32, 32}, {64, 32}, {64, 64}}}; + +struct Row { + std::uint32_t memory; + std::uint32_t subtrahend; +}; + +constexpr std::array Edges{{ + {0u, 0u}, {0u, 1u}, {1u, 0u}, {1u, 1u}, + {5u, 7u}, {7u, 5u}, {0xffffffffu, 1u}, {1u, 0xffffffffu}, + {0x80000000u, 0x7fffffffu}, {0x7fffffffu, 0x80000000u}, {0xffffffffu, 0xffffffffu}, {0x80000000u, 0x80000000u}, + {0x80000001u, 0x80000000u}, {0u, 0xffffffffu}, {0xffffffffu, 0u}, {0x12345678u, 0x12345679u}, +}}; + +Row RowOf(std::uint32_t tid) { + if (tid < Edges.size()) return Edges[tid]; + const std::uint32_t memory = (tid + 1u) * 0x9e3779b9u; + const std::uint32_t subtrahend = (tid + 1u) * 0x85ebca6bu; + return {memory, (tid & 3u) == 0u ? subtrahend >> 8u : subtrahend}; +} + +std::uint32_t SharedPartialStep(std::uint32_t tid) { return tid + 1u; } +std::uint32_t SharedExhaustedStep(std::uint32_t tid) { return tid + 100u; } + +std::uint32_t SubtractClamped(std::uint32_t memory, std::uint32_t subtrahend) { + return memory >= subtrahend ? memory - subtrahend : 0u; +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::vector UserData() { + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + return userData; +} + +ShaderRecompiler::RecompileResult Recompile(const AgcDriver::VulkanDevice& device, std::span code, const Workgroup& workgroup, std::span userData) { + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{workgroup.threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {workgroup.waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device, const Workgroup& workgroup) { + Input.fill(0u); + Output.fill(Untouched); + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) { + const Row row = RowOf(tid); + Input[tid * Inputs] = row.subtrahend; + Input[tid * Inputs + 1u] = SharedPartialStep(tid); + Input[tid * Inputs + 2u] = SharedExhaustedStep(tid); + Output[tid * Results] = row.memory; + Output[tid * Results + 1u] = row.memory; + } + Output[MaxThreads * Results] = SharedPartial; + Output[MaxThreads * Results + 1u] = SharedExhausted; + const auto userData = UserData(); + const std::span code(Code); + const auto result = Recompile(device, code, workgroup, userData); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(const Workgroup& workgroup) { + constexpr std::array names{"memory", "memory under a partial exec mask", "returned value", "data register under a partial exec mask"}; + std::uint32_t partialTotal = 0; + std::uint32_t exhaustedTotal = 0; + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) { + const Row row = RowOf(tid); + const auto* out = &Output[tid * Results]; + const auto where = "buffer atomic csub: " + workgroup.Name() + " thread " + std::to_string(tid); + if (tid >= workgroup.threads) { + Require(out[0] == row.memory && out[1] == row.memory, where + " is outside the workgroup but its memory changed"); + for (std::uint32_t j = 2; j < Results; ++j) { + Require(out[j] == Untouched, where + " is outside the workgroup but result " + std::to_string(j) + " was written"); + } + continue; + } + partialTotal += SharedPartialStep(tid); + exhaustedTotal += SharedExhaustedStep(tid); + const auto lane = out[4]; + Require(lane < workgroup.waveSize, where + " reports lane " + std::to_string(lane)); + const bool active = lane >= 32u || ((MaskedExecLo >> lane) & 1u) != 0u; + const std::uint32_t clamped = SubtractClamped(row.memory, row.subtrahend); + const std::array expected{clamped, active ? clamped : row.memory, row.memory, active ? row.memory : row.subtrahend}; + for (std::uint32_t j = 0; j < expected.size(); ++j) { + Require(out[j] == expected[j], where + " lane " + std::to_string(lane) + " " + names[j] + " is " + Hex(out[j]) + ", expected " + Hex(expected[j])); + } + for (std::uint32_t j = Written; j < Results; ++j) { + Require(out[j] == Untouched, where + " result " + std::to_string(j) + " was written"); + } + } + const std::array shared{SubtractClamped(SharedPartial, partialTotal), SubtractClamped(SharedExhausted, exhaustedTotal)}; + constexpr std::array sharedNames{"dword shared by all lanes", "dword exhausted by all lanes"}; + Require(shared[0] != 0u && shared[1] == 0u, "buffer atomic csub: " + workgroup.Name() + " does not cover both shared outcomes"); + for (std::uint32_t j = 0; j < SharedWords; ++j) { + const auto actual = Output[MaxThreads * Results + j]; + Require(actual == shared[j], "buffer atomic csub: " + workgroup.Name() + " " + sharedNames[j] + " is " + Hex(actual) + ", expected " + Hex(shared[j])); + } +} + +void CheckNoReturnRejected(const AgcDriver::VulkanDevice& device) { + const auto userData = UserData(); + const std::string reason = "buffer_atomic_csub without glc is not supported"; + std::string failure; + try { + static_cast(Recompile(device, NoReturnCode, Workgroups[0], userData)); + } catch (const std::exception& error) { + failure = error.what(); + } + Require(failure.find(reason) != std::string::npos, "buffer atomic csub: expected '" + reason + "', got '" + failure + "'"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + for (const auto& workgroup : Workgroups) { + Run(*device, workgroup); + Check(workgroup); + } + CheckNoReturnRejected(*device); + std::puts("buffer atomic csub tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicsOrSwap64.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicsOrSwap64.cpp new file mode 100644 index 000000000..be03cdc3b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/BufferAtomicsOrSwap64.cpp @@ -0,0 +1,296 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 128; +constexpr std::uint32_t Checked = 28; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060089, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe880304, 0xbe890305, 0xbe8a03ff, + 0x00002134, 0xbe8b03ff, 0x31016fac, 0xe0741100, 0x80010403, 0xe0741108, 0x80010403, 0xe0741110, + 0x80010403, 0xe0741118, 0x80010403, 0xe0741120, 0x80010403, 0xe0741128, 0x80010403, 0xe0741130, + 0x80010403, 0xe0741138, 0x80010403, 0xbf8c3f70, 0x7e140306, 0x7e160307, 0x7e180306, 0x7e1a0307, + 0x7e1c0306, 0x7e1e0307, 0x7e200306, 0x7e220307, 0x7e240306, 0x7e260307, 0x7e280306, 0x7e2a0307, + 0x7e500306, 0x7e520307, 0xe1685100, 0x80010a03, 0xe1681108, 0x80012803, 0xe1405110, 0x80010c03, + 0xe1401118, 0x80012803, 0x36a00e83, 0x7d8aa080, 0xbea83c6a, 0xe1685120, 0x80010e03, 0xe1405128, + 0x80011003, 0xbefe03c1, 0xe1685130, 0x80021203, 0xe1405138, 0x80021403, 0xbf8c3f70, 0xe034d100, + 0x80011603, 0xe034d108, 0x80011803, 0xe034d110, 0x80011a03, 0xe034d118, 0x80011c03, 0xe034d120, + 0x80011e03, 0xe034d128, 0x80012003, 0xe034d130, 0x80012203, 0xe034d138, 0x80012403, 0xbf8c3f70, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xe0781040, 0x80011a03, 0xe0781050, 0x80011e03, 0xe0781060, 0x80012203, 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060089, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe880304, 0xbe890305, 0xbe8a03ff, + 0x00004134, 0xbe8b03ff, 0x31016fac, 0xe0741100, 0x80010403, 0xe0741108, 0x80010403, 0xe0741110, + 0x80010403, 0xe0741118, 0x80010403, 0xe0741120, 0x80010403, 0xe0741128, 0x80010403, 0xe0741130, + 0x80010403, 0xe0741138, 0x80010403, 0xbf8c3f70, 0x7e140306, 0x7e160307, 0x7e180306, 0x7e1a0307, + 0x7e1c0306, 0x7e1e0307, 0x7e200306, 0x7e220307, 0x7e240306, 0x7e260307, 0x7e280306, 0x7e2a0307, + 0x7e500306, 0x7e520307, 0xe1685100, 0x80010a03, 0xe1681108, 0x80012803, 0xe1405110, 0x80010c03, + 0xe1401118, 0x80012803, 0x36a00e83, 0x7d8aa080, 0xbea8246a, 0xe1685120, 0x80010e03, 0xe1405128, + 0x80011003, 0xbefe04c1, 0xe1685130, 0x80021203, 0xe1405138, 0x80021403, 0xbf8c3f70, 0xe034d100, + 0x80011603, 0xe034d108, 0x80011803, 0xe034d110, 0x80011a03, 0xe034d118, 0x80011c03, 0xe034d120, + 0x80011e03, 0xe034d128, 0x80012003, 0xe034d130, 0x80012203, 0xe034d138, 0x80012403, 0xbf8c3f70, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xe0781040, 0x80011a03, 0xe0781050, 0x80011e03, 0xe0781060, 0x80012203, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x6a6ae768u, 0x89abcdefu, 0x466de486u, 0x00ff00ffu}, + {0x00ff00ffu, 0x00ff00ffu, 0x80000000u, 0x2e09e4b8u}, + {0x2be38eccu, 0xdd1775a9u, 0xab195b47u, 0xe88f40a2u}, + {0xe9a462ceu, 0x00000000u, 0x2b1141d6u, 0xac0e36d5u}, + {0x64ffc604u, 0xbcfdc56au, 0x2af2003cu, 0x8f4c6fc3u}, + {0x70ce7993u, 0x00ff00ffu, 0x80000000u, 0x7b05bc1au}, + {0x00000001u, 0xba30a818u, 0x6e325d76u, 0x7767929du}, + {0x7e974317u, 0x00ff00ffu, 0xddd59451u, 0xdf30a0eau}, + {0xffffffffu, 0x159840eau, 0xe41c70dbu, 0x88d1f52cu}, + {0x89abcdefu, 0x00000000u, 0x6dd9de7bu, 0x93cd7e6fu}, + {0x7fffffffu, 0x6d924e06u, 0x379cf45fu, 0x5a176daau}, + {0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xdd343334u}, + {0xf4c65508u, 0x774afa8fu, 0x4bf01f77u, 0x80000000u}, + {0x0680d87fu, 0xe2aeba83u, 0x0cae8376u, 0x21027ce8u}, + {0x12345678u, 0x24483dacu, 0x343089c1u, 0xfe3a9921u}, + {0x9dd441ebu, 0x7fffffffu, 0x3f250fabu, 0x5a25508fu}, + {0x80000000u, 0x940c48d1u, 0x00000001u, 0x12345678u}, + {0x06a06e39u, 0xdca4271cu, 0x89abcdefu, 0x80000000u}, + {0x00ff00ffu, 0xdadef6e0u, 0xdfd0b7a2u, 0xffffffffu}, + {0x436d4bd7u, 0xb4373875u, 0xc71d5b33u, 0x63b433c8u}, + {0xe1a9611du, 0xdd70b68bu, 0xc3e41dfbu, 0x80000000u}, + {0x00ff00ffu, 0x00ff00ffu, 0x2a2b5f67u, 0xf2d9cc0du}, + {0x89abcdefu, 0x38573260u, 0x5b120ed1u, 0x49e07752u}, + {0xf60106b7u, 0xd61d1405u, 0xc58ccccdu, 0xbce22306u}, + {0x7fffffffu, 0x80000000u, 0x15452b7bu, 0xb8054218u}, + {0x12345678u, 0x00000001u, 0xa7387a4cu, 0xffffffffu}, + {0x00000000u, 0x90322d5bu, 0x7ee058dau, 0x89abcdefu}, + {0x7fffffffu, 0x93b8f2e0u, 0x8245e09du, 0x168b95f5u}, + {0x80000000u, 0x4ec04618u, 0x44b6a55du, 0x89abcdefu}, + {0x80a3acc4u, 0x7fffffffu, 0x1f016769u, 0xac7c83b9u}, + {0x725f6ff6u, 0x00000000u, 0x45040843u, 0x00ff00ffu}, + {0x50d14398u, 0x7f767333u, 0x2369eadcu, 0x00000001u}, + {0xac8c599bu, 0x7fffffffu, 0xd8f36844u, 0x4761f31au}, + {0xd078019fu, 0x60ac3d38u, 0x7fffffffu, 0x20b5d1d4u}, + {0x2925feb3u, 0x00000001u, 0x7fffffffu, 0xb16614f4u}, + {0x86d5360fu, 0xfaf40417u, 0xd4424852u, 0x89631325u}, + {0xffffffffu, 0xde274210u, 0xe96e12c9u, 0x65305d1cu}, + {0x1043c4f3u, 0x8a860eeau, 0xe9579523u, 0x7563acb0u}, + {0x310b55c7u, 0x12345678u, 0x12345678u, 0x96188707u}, + {0x80000000u, 0xbcabb1bau, 0x12345678u, 0xffffffffu}, + {0x72fdad03u, 0x00000001u, 0x028f3b02u, 0x6c60da42u}, + {0xe473cdc9u, 0x75eb16c7u, 0x12345678u, 0xe0d59f3cu}, + {0x89abcdefu, 0x74102553u, 0x1cb3e931u, 0xe536aae9u}, + {0x4d126f38u, 0x7d42a12au, 0x50692ce5u, 0xa68132ddu}, + {0x744e3f0eu, 0x7fffffffu, 0xde298785u, 0x80000000u}, + {0xad554ba1u, 0x7c5153d0u, 0xeaf8edcfu, 0xc8aab92fu}, + {0xf8f008c5u, 0x0f119987u, 0x1bd39c7cu, 0x5dc52692u}, + {0xd03b304du, 0x00ff00ffu, 0x89abcdefu, 0x991132c0u}, + {0xa92c8f72u, 0x07b9077du, 0x66546794u, 0xf6a5f7c1u}, + {0xb5691c18u, 0x5d6c710au, 0x7ae6b997u, 0xbfe4abb0u}, + {0xc9570a1cu, 0xefa96b6eu, 0x00000000u, 0xcd12bfb3u}, + {0x00000000u, 0x37eadcd2u, 0x3a6d60aau, 0x89abcdefu}, + {0x114a2d32u, 0x00000000u, 0x89abcdefu, 0x12345678u}, + {0x14090b01u, 0xffffffffu, 0x0eb083edu, 0x1a0caeefu}, + {0x12345678u, 0x4521d7efu, 0x00000001u, 0x80000000u}, + {0xefa94a8eu, 0x561bfb20u, 0x19b0c2f0u, 0x69b1111du}, + {0xbcf7629fu, 0xaf1de605u, 0x41622456u, 0x00000000u}, + {0xd47a46a9u, 0x12da9645u, 0x7ded0e8du, 0x00000000u}, + {0x697d50dbu, 0x89abcdefu, 0xdeacacaau, 0xcf93dd30u}, + {0x00ff00ffu, 0xb6a48819u, 0x1f752ee6u, 0x80000000u}, + {0x12345678u, 0xbb36bb87u, 0x0888385bu, 0x5cb6c6d7u}, + {0xd24df64du, 0x9595f0c7u, 0x00000000u, 0x7fffffffu}, + {0xffffffffu, 0x00ff00ffu, 0x00000001u, 0xfe7ffe21u}, + {0xdd6537d2u, 0x6c2ab542u, 0xffffffffu, 0xde5ffb8au} +}; + +constexpr std::uint32_t Expected32[32][28] = { + {0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6e6fe7eeu, 0x89ffcdffu, 0x6e6fe7eeu, 0x89ffcdffu, 0x466de486u, 0x00ff00ffu, 0x466de486u, 0x00ff00ffu, 0x6e6fe7eeu, 0x89ffcdffu, 0x466de486u, 0x00ff00ffu, 0x6e6fe7eeu, 0x89ffcdffu, 0x466de486u, 0x00ff00ffu}, + {0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x80000000u, 0x2e09e4b8u, 0x80000000u, 0x2e09e4b8u, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x80ff00ffu, 0x2effe4ffu, 0x80ff00ffu, 0x2effe4ffu, 0x80000000u, 0x2e09e4b8u, 0x80000000u, 0x2e09e4b8u, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x80ff00ffu, 0x2effe4ffu, 0x80000000u, 0x2e09e4b8u}, + {0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0xabfbdfcfu, 0xfd9f75abu, 0xabfbdfcfu, 0xfd9f75abu, 0xab195b47u, 0xe88f40a2u, 0xab195b47u, 0xe88f40a2u, 0xabfbdfcfu, 0xfd9f75abu, 0xab195b47u, 0xe88f40a2u, 0xabfbdfcfu, 0xfd9f75abu, 0xab195b47u, 0xe88f40a2u}, + {0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xebb563deu, 0xac0e36d5u, 0xebb563deu, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u, 0xebb563deu, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u, 0xebb563deu, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u}, + {0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x6effc63cu, 0xbffdefebu, 0x6effc63cu, 0xbffdefebu, 0x2af2003cu, 0x8f4c6fc3u, 0x2af2003cu, 0x8f4c6fc3u, 0x6effc63cu, 0xbffdefebu, 0x2af2003cu, 0x8f4c6fc3u, 0x6effc63cu, 0xbffdefebu, 0x2af2003cu, 0x8f4c6fc3u}, + {0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0xf0ce7993u, 0x7bffbcffu, 0xf0ce7993u, 0x7bffbcffu, 0x80000000u, 0x7b05bc1au, 0x80000000u, 0x7b05bc1au, 0xf0ce7993u, 0x7bffbcffu, 0x80000000u, 0x7b05bc1au, 0xf0ce7993u, 0x7bffbcffu, 0x80000000u, 0x7b05bc1au}, + {0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x6e325d77u, 0xff77ba9du, 0x6e325d77u, 0xff77ba9du, 0x6e325d76u, 0x7767929du, 0x6e325d76u, 0x7767929du, 0x6e325d77u, 0xff77ba9du, 0x6e325d76u, 0x7767929du, 0x6e325d77u, 0xff77ba9du, 0x6e325d76u, 0x7767929du}, + {0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0xffd7d757u, 0xdfffa0ffu, 0xffd7d757u, 0xdfffa0ffu, 0xddd59451u, 0xdf30a0eau, 0xddd59451u, 0xdf30a0eau, 0xffd7d757u, 0xdfffa0ffu, 0xddd59451u, 0xdf30a0eau, 0xffd7d757u, 0xdfffa0ffu, 0xddd59451u, 0xdf30a0eau}, + {0xffffffffu, 0x159840eau, 0xffffffffu, 0x159840eau, 0xe41c70dbu, 0x88d1f52cu, 0xe41c70dbu, 0x88d1f52cu, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x9dd9f5eeu, 0xffffffffu, 0x9dd9f5eeu, 0xe41c70dbu, 0x88d1f52cu, 0xe41c70dbu, 0x88d1f52cu, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x9dd9f5eeu, 0xe41c70dbu, 0x88d1f52cu}, + {0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0xedfbdfffu, 0x93cd7e6fu, 0xedfbdfffu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu, 0xedfbdfffu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu, 0xedfbdfffu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu}, + {0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x7f976faeu, 0x7fffffffu, 0x7f976faeu, 0x379cf45fu, 0x5a176daau, 0x379cf45fu, 0x5a176daau, 0x7fffffffu, 0x7f976faeu, 0x379cf45fu, 0x5a176daau, 0x7fffffffu, 0x7f976faeu, 0x379cf45fu, 0x5a176daau}, + {0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfd3eb3bdu, 0x89abcdefu, 0xfd3eb3bdu, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfd3eb3bdu, 0x89abcdefu, 0xdd343334u}, + {0xf4c65508u, 0x774afa8fu, 0xf4c65508u, 0x774afa8fu, 0x4bf01f77u, 0x80000000u, 0x4bf01f77u, 0x80000000u, 0xf4c65508u, 0x774afa8fu, 0xf4c65508u, 0x774afa8fu, 0xfff65f7fu, 0xf74afa8fu, 0xfff65f7fu, 0xf74afa8fu, 0x4bf01f77u, 0x80000000u, 0x4bf01f77u, 0x80000000u, 0xf4c65508u, 0x774afa8fu, 0xf4c65508u, 0x774afa8fu, 0xfff65f7fu, 0xf74afa8fu, 0x4bf01f77u, 0x80000000u}, + {0x0680d87fu, 0xe2aeba83u, 0x0680d87fu, 0xe2aeba83u, 0x0cae8376u, 0x21027ce8u, 0x0cae8376u, 0x21027ce8u, 0x0680d87fu, 0xe2aeba83u, 0x0680d87fu, 0xe2aeba83u, 0x0eaedb7fu, 0xe3aefeebu, 0x0eaedb7fu, 0xe3aefeebu, 0x0cae8376u, 0x21027ce8u, 0x0cae8376u, 0x21027ce8u, 0x0680d87fu, 0xe2aeba83u, 0x0680d87fu, 0xe2aeba83u, 0x0eaedb7fu, 0xe3aefeebu, 0x0cae8376u, 0x21027ce8u}, + {0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x3634dff9u, 0xfe7abdadu, 0x3634dff9u, 0xfe7abdadu, 0x343089c1u, 0xfe3a9921u, 0x343089c1u, 0xfe3a9921u, 0x3634dff9u, 0xfe7abdadu, 0x343089c1u, 0xfe3a9921u, 0x3634dff9u, 0xfe7abdadu, 0x343089c1u, 0xfe3a9921u}, + {0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0xbff54febu, 0x7fffffffu, 0xbff54febu, 0x7fffffffu, 0x3f250fabu, 0x5a25508fu, 0x3f250fabu, 0x5a25508fu, 0xbff54febu, 0x7fffffffu, 0x3f250fabu, 0x5a25508fu, 0xbff54febu, 0x7fffffffu, 0x3f250fabu, 0x5a25508fu}, + {0x80000000u, 0x940c48d1u, 0x80000000u, 0x940c48d1u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000001u, 0x963c5ef9u, 0x80000001u, 0x963c5ef9u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x80000000u, 0x940c48d1u, 0x80000000u, 0x940c48d1u, 0x80000000u, 0x940c48d1u, 0x80000000u, 0x940c48d1u}, + {0x06a06e39u, 0xdca4271cu, 0x06a06e39u, 0xdca4271cu, 0x89abcdefu, 0x80000000u, 0x89abcdefu, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x8fabefffu, 0xdca4271cu, 0x8fabefffu, 0xdca4271cu, 0x89abcdefu, 0x80000000u, 0x89abcdefu, 0x80000000u, 0x06a06e39u, 0xdca4271cu, 0x06a06e39u, 0xdca4271cu, 0x06a06e39u, 0xdca4271cu, 0x06a06e39u, 0xdca4271cu}, + {0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdfffb7ffu, 0xffffffffu, 0xdfffb7ffu, 0xffffffffu, 0xdfd0b7a2u, 0xffffffffu, 0xdfd0b7a2u, 0xffffffffu, 0xdfffb7ffu, 0xffffffffu, 0xdfd0b7a2u, 0xffffffffu, 0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u}, + {0x436d4bd7u, 0xb4373875u, 0x436d4bd7u, 0xb4373875u, 0xc71d5b33u, 0x63b433c8u, 0xc71d5b33u, 0x63b433c8u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xc77d5bf7u, 0xf7b73bfdu, 0xc77d5bf7u, 0xf7b73bfdu, 0xc71d5b33u, 0x63b433c8u, 0xc71d5b33u, 0x63b433c8u, 0x436d4bd7u, 0xb4373875u, 0x436d4bd7u, 0xb4373875u, 0x436d4bd7u, 0xb4373875u, 0x436d4bd7u, 0xb4373875u}, + {0xe1a9611du, 0xdd70b68bu, 0xe1a9611du, 0xdd70b68bu, 0xc3e41dfbu, 0x80000000u, 0xc3e41dfbu, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xe3ed7dffu, 0xdd70b68bu, 0xe3ed7dffu, 0xdd70b68bu, 0xc3e41dfbu, 0x80000000u, 0xc3e41dfbu, 0x80000000u, 0xe1a9611du, 0xdd70b68bu, 0xe1a9611du, 0xdd70b68bu, 0xe1a9611du, 0xdd70b68bu, 0xe1a9611du, 0xdd70b68bu}, + {0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x2aff5fffu, 0xf2ffccffu, 0x2aff5fffu, 0xf2ffccffu, 0x2a2b5f67u, 0xf2d9cc0du, 0x2a2b5f67u, 0xf2d9cc0du, 0x2aff5fffu, 0xf2ffccffu, 0x2a2b5f67u, 0xf2d9cc0du, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu}, + {0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdbbbcfffu, 0x79f77772u, 0xdbbbcfffu, 0x79f77772u, 0x5b120ed1u, 0x49e07752u, 0x5b120ed1u, 0x49e07752u, 0xdbbbcfffu, 0x79f77772u, 0x5b120ed1u, 0x49e07752u, 0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u}, + {0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xf78dceffu, 0xfeff3707u, 0xf78dceffu, 0xfeff3707u, 0xc58ccccdu, 0xbce22306u, 0xc58ccccdu, 0xbce22306u, 0xf78dceffu, 0xfeff3707u, 0xc58ccccdu, 0xbce22306u, 0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u}, + {0x7fffffffu, 0x80000000u, 0x7fffffffu, 0x80000000u, 0x15452b7bu, 0xb8054218u, 0x15452b7bu, 0xb8054218u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fffffffu, 0xb8054218u, 0x7fffffffu, 0xb8054218u, 0x15452b7bu, 0xb8054218u, 0x15452b7bu, 0xb8054218u, 0x7fffffffu, 0x80000000u, 0x7fffffffu, 0x80000000u, 0x7fffffffu, 0x80000000u, 0x7fffffffu, 0x80000000u}, + {0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xb73c7e7cu, 0xffffffffu, 0xb73c7e7cu, 0xffffffffu, 0xa7387a4cu, 0xffffffffu, 0xa7387a4cu, 0xffffffffu, 0xb73c7e7cu, 0xffffffffu, 0xa7387a4cu, 0xffffffffu, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u}, + {0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ee058dau, 0x99bbedffu, 0x7ee058dau, 0x99bbedffu, 0x7ee058dau, 0x89abcdefu, 0x7ee058dau, 0x89abcdefu, 0x7ee058dau, 0x99bbedffu, 0x7ee058dau, 0x89abcdefu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu}, + {0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x97bbf7f5u, 0xffffffffu, 0x97bbf7f5u, 0x8245e09du, 0x168b95f5u, 0x8245e09du, 0x168b95f5u, 0xffffffffu, 0x97bbf7f5u, 0x8245e09du, 0x168b95f5u, 0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u}, + {0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xc4b6a55du, 0xcfebcfffu, 0xc4b6a55du, 0xcfebcfffu, 0x44b6a55du, 0x89abcdefu, 0x44b6a55du, 0x89abcdefu, 0xc4b6a55du, 0xcfebcfffu, 0x44b6a55du, 0x89abcdefu, 0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u}, + {0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x9fa3efedu, 0xffffffffu, 0x9fa3efedu, 0xffffffffu, 0x1f016769u, 0xac7c83b9u, 0x1f016769u, 0xac7c83b9u, 0x9fa3efedu, 0xffffffffu, 0x1f016769u, 0xac7c83b9u, 0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu}, + {0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x775f6ff7u, 0x00ff00ffu, 0x775f6ff7u, 0x00ff00ffu, 0x45040843u, 0x00ff00ffu, 0x45040843u, 0x00ff00ffu, 0x775f6ff7u, 0x00ff00ffu, 0x45040843u, 0x00ff00ffu, 0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u}, + {0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x73f9ebdcu, 0x7f767333u, 0x73f9ebdcu, 0x7f767333u, 0x2369eadcu, 0x00000001u, 0x2369eadcu, 0x00000001u, 0x73f9ebdcu, 0x7f767333u, 0x2369eadcu, 0x00000001u, 0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u} +}; +constexpr std::uint32_t Expected64[64][28] = { + {0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6a6ae768u, 0x89abcdefu, 0x6e6fe7eeu, 0x89ffcdffu, 0x6e6fe7eeu, 0x89ffcdffu, 0x466de486u, 0x00ff00ffu, 0x466de486u, 0x00ff00ffu, 0x6e6fe7eeu, 0x89ffcdffu, 0x466de486u, 0x00ff00ffu, 0x6e6fe7eeu, 0x89ffcdffu, 0x466de486u, 0x00ff00ffu}, + {0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x80000000u, 0x2e09e4b8u, 0x80000000u, 0x2e09e4b8u, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x80ff00ffu, 0x2effe4ffu, 0x80ff00ffu, 0x2effe4ffu, 0x80000000u, 0x2e09e4b8u, 0x80000000u, 0x2e09e4b8u, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x80ff00ffu, 0x2effe4ffu, 0x80000000u, 0x2e09e4b8u}, + {0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0x2be38eccu, 0xdd1775a9u, 0xabfbdfcfu, 0xfd9f75abu, 0xabfbdfcfu, 0xfd9f75abu, 0xab195b47u, 0xe88f40a2u, 0xab195b47u, 0xe88f40a2u, 0xabfbdfcfu, 0xfd9f75abu, 0xab195b47u, 0xe88f40a2u, 0xabfbdfcfu, 0xfd9f75abu, 0xab195b47u, 0xe88f40a2u}, + {0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xe9a462ceu, 0x00000000u, 0xebb563deu, 0xac0e36d5u, 0xebb563deu, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u, 0xebb563deu, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u, 0xebb563deu, 0xac0e36d5u, 0x2b1141d6u, 0xac0e36d5u}, + {0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x64ffc604u, 0xbcfdc56au, 0x6effc63cu, 0xbffdefebu, 0x6effc63cu, 0xbffdefebu, 0x2af2003cu, 0x8f4c6fc3u, 0x2af2003cu, 0x8f4c6fc3u, 0x6effc63cu, 0xbffdefebu, 0x2af2003cu, 0x8f4c6fc3u, 0x6effc63cu, 0xbffdefebu, 0x2af2003cu, 0x8f4c6fc3u}, + {0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0x70ce7993u, 0x00ff00ffu, 0xf0ce7993u, 0x7bffbcffu, 0xf0ce7993u, 0x7bffbcffu, 0x80000000u, 0x7b05bc1au, 0x80000000u, 0x7b05bc1au, 0xf0ce7993u, 0x7bffbcffu, 0x80000000u, 0x7b05bc1au, 0xf0ce7993u, 0x7bffbcffu, 0x80000000u, 0x7b05bc1au}, + {0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x00000001u, 0xba30a818u, 0x6e325d77u, 0xff77ba9du, 0x6e325d77u, 0xff77ba9du, 0x6e325d76u, 0x7767929du, 0x6e325d76u, 0x7767929du, 0x6e325d77u, 0xff77ba9du, 0x6e325d76u, 0x7767929du, 0x6e325d77u, 0xff77ba9du, 0x6e325d76u, 0x7767929du}, + {0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0x7e974317u, 0x00ff00ffu, 0xffd7d757u, 0xdfffa0ffu, 0xffd7d757u, 0xdfffa0ffu, 0xddd59451u, 0xdf30a0eau, 0xddd59451u, 0xdf30a0eau, 0xffd7d757u, 0xdfffa0ffu, 0xddd59451u, 0xdf30a0eau, 0xffd7d757u, 0xdfffa0ffu, 0xddd59451u, 0xdf30a0eau}, + {0xffffffffu, 0x159840eau, 0xffffffffu, 0x159840eau, 0xe41c70dbu, 0x88d1f52cu, 0xe41c70dbu, 0x88d1f52cu, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x9dd9f5eeu, 0xffffffffu, 0x9dd9f5eeu, 0xe41c70dbu, 0x88d1f52cu, 0xe41c70dbu, 0x88d1f52cu, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x159840eau, 0xffffffffu, 0x9dd9f5eeu, 0xe41c70dbu, 0x88d1f52cu}, + {0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0x89abcdefu, 0x00000000u, 0xedfbdfffu, 0x93cd7e6fu, 0xedfbdfffu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu, 0xedfbdfffu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu, 0xedfbdfffu, 0x93cd7e6fu, 0x6dd9de7bu, 0x93cd7e6fu}, + {0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x6d924e06u, 0x7fffffffu, 0x7f976faeu, 0x7fffffffu, 0x7f976faeu, 0x379cf45fu, 0x5a176daau, 0x379cf45fu, 0x5a176daau, 0x7fffffffu, 0x7f976faeu, 0x379cf45fu, 0x5a176daau, 0x7fffffffu, 0x7f976faeu, 0x379cf45fu, 0x5a176daau}, + {0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfd3eb3bdu, 0x89abcdefu, 0xfd3eb3bdu, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xdd343334u, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfc3eb39du, 0x89abcdefu, 0xfd3eb3bdu, 0x89abcdefu, 0xdd343334u}, + {0xf4c65508u, 0x774afa8fu, 0xf4c65508u, 0x774afa8fu, 0x4bf01f77u, 0x80000000u, 0x4bf01f77u, 0x80000000u, 0xf4c65508u, 0x774afa8fu, 0xf4c65508u, 0x774afa8fu, 0xfff65f7fu, 0xf74afa8fu, 0xfff65f7fu, 0xf74afa8fu, 0x4bf01f77u, 0x80000000u, 0x4bf01f77u, 0x80000000u, 0xf4c65508u, 0x774afa8fu, 0xf4c65508u, 0x774afa8fu, 0xfff65f7fu, 0xf74afa8fu, 0x4bf01f77u, 0x80000000u}, + {0x0680d87fu, 0xe2aeba83u, 0x0680d87fu, 0xe2aeba83u, 0x0cae8376u, 0x21027ce8u, 0x0cae8376u, 0x21027ce8u, 0x0680d87fu, 0xe2aeba83u, 0x0680d87fu, 0xe2aeba83u, 0x0eaedb7fu, 0xe3aefeebu, 0x0eaedb7fu, 0xe3aefeebu, 0x0cae8376u, 0x21027ce8u, 0x0cae8376u, 0x21027ce8u, 0x0680d87fu, 0xe2aeba83u, 0x0680d87fu, 0xe2aeba83u, 0x0eaedb7fu, 0xe3aefeebu, 0x0cae8376u, 0x21027ce8u}, + {0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x12345678u, 0x24483dacu, 0x3634dff9u, 0xfe7abdadu, 0x3634dff9u, 0xfe7abdadu, 0x343089c1u, 0xfe3a9921u, 0x343089c1u, 0xfe3a9921u, 0x3634dff9u, 0xfe7abdadu, 0x343089c1u, 0xfe3a9921u, 0x3634dff9u, 0xfe7abdadu, 0x343089c1u, 0xfe3a9921u}, + {0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0x9dd441ebu, 0x7fffffffu, 0xbff54febu, 0x7fffffffu, 0xbff54febu, 0x7fffffffu, 0x3f250fabu, 0x5a25508fu, 0x3f250fabu, 0x5a25508fu, 0xbff54febu, 0x7fffffffu, 0x3f250fabu, 0x5a25508fu, 0xbff54febu, 0x7fffffffu, 0x3f250fabu, 0x5a25508fu}, + {0x80000000u, 0x940c48d1u, 0x80000000u, 0x940c48d1u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x80000000u, 0x940c48d1u, 0x80000000u, 0x940c48d1u, 0x80000001u, 0x963c5ef9u, 0x80000001u, 0x963c5ef9u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x80000000u, 0x940c48d1u, 0x80000000u, 0x940c48d1u, 0x80000001u, 0x963c5ef9u, 0x00000001u, 0x12345678u}, + {0x06a06e39u, 0xdca4271cu, 0x06a06e39u, 0xdca4271cu, 0x89abcdefu, 0x80000000u, 0x89abcdefu, 0x80000000u, 0x06a06e39u, 0xdca4271cu, 0x06a06e39u, 0xdca4271cu, 0x8fabefffu, 0xdca4271cu, 0x8fabefffu, 0xdca4271cu, 0x89abcdefu, 0x80000000u, 0x89abcdefu, 0x80000000u, 0x06a06e39u, 0xdca4271cu, 0x06a06e39u, 0xdca4271cu, 0x8fabefffu, 0xdca4271cu, 0x89abcdefu, 0x80000000u}, + {0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0x00ff00ffu, 0xdadef6e0u, 0xdfffb7ffu, 0xffffffffu, 0xdfffb7ffu, 0xffffffffu, 0xdfd0b7a2u, 0xffffffffu, 0xdfd0b7a2u, 0xffffffffu, 0xdfffb7ffu, 0xffffffffu, 0xdfd0b7a2u, 0xffffffffu, 0xdfffb7ffu, 0xffffffffu, 0xdfd0b7a2u, 0xffffffffu}, + {0x436d4bd7u, 0xb4373875u, 0x436d4bd7u, 0xb4373875u, 0xc71d5b33u, 0x63b433c8u, 0xc71d5b33u, 0x63b433c8u, 0x436d4bd7u, 0xb4373875u, 0x436d4bd7u, 0xb4373875u, 0xc77d5bf7u, 0xf7b73bfdu, 0xc77d5bf7u, 0xf7b73bfdu, 0xc71d5b33u, 0x63b433c8u, 0xc71d5b33u, 0x63b433c8u, 0x436d4bd7u, 0xb4373875u, 0x436d4bd7u, 0xb4373875u, 0xc77d5bf7u, 0xf7b73bfdu, 0xc71d5b33u, 0x63b433c8u}, + {0xe1a9611du, 0xdd70b68bu, 0xe1a9611du, 0xdd70b68bu, 0xc3e41dfbu, 0x80000000u, 0xc3e41dfbu, 0x80000000u, 0xe1a9611du, 0xdd70b68bu, 0xe1a9611du, 0xdd70b68bu, 0xe3ed7dffu, 0xdd70b68bu, 0xe3ed7dffu, 0xdd70b68bu, 0xc3e41dfbu, 0x80000000u, 0xc3e41dfbu, 0x80000000u, 0xe1a9611du, 0xdd70b68bu, 0xe1a9611du, 0xdd70b68bu, 0xe3ed7dffu, 0xdd70b68bu, 0xc3e41dfbu, 0x80000000u}, + {0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x2aff5fffu, 0xf2ffccffu, 0x2aff5fffu, 0xf2ffccffu, 0x2a2b5f67u, 0xf2d9cc0du, 0x2a2b5f67u, 0xf2d9cc0du, 0x2aff5fffu, 0xf2ffccffu, 0x2a2b5f67u, 0xf2d9cc0du, 0x2aff5fffu, 0xf2ffccffu, 0x2a2b5f67u, 0xf2d9cc0du}, + {0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0x89abcdefu, 0x38573260u, 0xdbbbcfffu, 0x79f77772u, 0xdbbbcfffu, 0x79f77772u, 0x5b120ed1u, 0x49e07752u, 0x5b120ed1u, 0x49e07752u, 0xdbbbcfffu, 0x79f77772u, 0x5b120ed1u, 0x49e07752u, 0xdbbbcfffu, 0x79f77772u, 0x5b120ed1u, 0x49e07752u}, + {0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0xf60106b7u, 0xd61d1405u, 0xf78dceffu, 0xfeff3707u, 0xf78dceffu, 0xfeff3707u, 0xc58ccccdu, 0xbce22306u, 0xc58ccccdu, 0xbce22306u, 0xf78dceffu, 0xfeff3707u, 0xc58ccccdu, 0xbce22306u, 0xf78dceffu, 0xfeff3707u, 0xc58ccccdu, 0xbce22306u}, + {0x7fffffffu, 0x80000000u, 0x7fffffffu, 0x80000000u, 0x15452b7bu, 0xb8054218u, 0x15452b7bu, 0xb8054218u, 0x7fffffffu, 0x80000000u, 0x7fffffffu, 0x80000000u, 0x7fffffffu, 0xb8054218u, 0x7fffffffu, 0xb8054218u, 0x15452b7bu, 0xb8054218u, 0x15452b7bu, 0xb8054218u, 0x7fffffffu, 0x80000000u, 0x7fffffffu, 0x80000000u, 0x7fffffffu, 0xb8054218u, 0x15452b7bu, 0xb8054218u}, + {0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0x12345678u, 0x00000001u, 0xb73c7e7cu, 0xffffffffu, 0xb73c7e7cu, 0xffffffffu, 0xa7387a4cu, 0xffffffffu, 0xa7387a4cu, 0xffffffffu, 0xb73c7e7cu, 0xffffffffu, 0xa7387a4cu, 0xffffffffu, 0xb73c7e7cu, 0xffffffffu, 0xa7387a4cu, 0xffffffffu}, + {0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x00000000u, 0x90322d5bu, 0x7ee058dau, 0x99bbedffu, 0x7ee058dau, 0x99bbedffu, 0x7ee058dau, 0x89abcdefu, 0x7ee058dau, 0x89abcdefu, 0x7ee058dau, 0x99bbedffu, 0x7ee058dau, 0x89abcdefu, 0x7ee058dau, 0x99bbedffu, 0x7ee058dau, 0x89abcdefu}, + {0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0x7fffffffu, 0x93b8f2e0u, 0xffffffffu, 0x97bbf7f5u, 0xffffffffu, 0x97bbf7f5u, 0x8245e09du, 0x168b95f5u, 0x8245e09du, 0x168b95f5u, 0xffffffffu, 0x97bbf7f5u, 0x8245e09du, 0x168b95f5u, 0xffffffffu, 0x97bbf7f5u, 0x8245e09du, 0x168b95f5u}, + {0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0x80000000u, 0x4ec04618u, 0xc4b6a55du, 0xcfebcfffu, 0xc4b6a55du, 0xcfebcfffu, 0x44b6a55du, 0x89abcdefu, 0x44b6a55du, 0x89abcdefu, 0xc4b6a55du, 0xcfebcfffu, 0x44b6a55du, 0x89abcdefu, 0xc4b6a55du, 0xcfebcfffu, 0x44b6a55du, 0x89abcdefu}, + {0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x80a3acc4u, 0x7fffffffu, 0x9fa3efedu, 0xffffffffu, 0x9fa3efedu, 0xffffffffu, 0x1f016769u, 0xac7c83b9u, 0x1f016769u, 0xac7c83b9u, 0x9fa3efedu, 0xffffffffu, 0x1f016769u, 0xac7c83b9u, 0x9fa3efedu, 0xffffffffu, 0x1f016769u, 0xac7c83b9u}, + {0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x725f6ff6u, 0x00000000u, 0x775f6ff7u, 0x00ff00ffu, 0x775f6ff7u, 0x00ff00ffu, 0x45040843u, 0x00ff00ffu, 0x45040843u, 0x00ff00ffu, 0x775f6ff7u, 0x00ff00ffu, 0x45040843u, 0x00ff00ffu, 0x775f6ff7u, 0x00ff00ffu, 0x45040843u, 0x00ff00ffu}, + {0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x50d14398u, 0x7f767333u, 0x73f9ebdcu, 0x7f767333u, 0x73f9ebdcu, 0x7f767333u, 0x2369eadcu, 0x00000001u, 0x2369eadcu, 0x00000001u, 0x73f9ebdcu, 0x7f767333u, 0x2369eadcu, 0x00000001u, 0x73f9ebdcu, 0x7f767333u, 0x2369eadcu, 0x00000001u}, + {0xac8c599bu, 0x7fffffffu, 0xac8c599bu, 0x7fffffffu, 0xac8c599bu, 0x7fffffffu, 0xac8c599bu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfcff79dfu, 0x7fffffffu, 0xfcff79dfu, 0x7fffffffu, 0xd8f36844u, 0x4761f31au, 0xd8f36844u, 0x4761f31au, 0xfcff79dfu, 0x7fffffffu, 0xd8f36844u, 0x4761f31au, 0xac8c599bu, 0x7fffffffu, 0xac8c599bu, 0x7fffffffu}, + {0xd078019fu, 0x60ac3d38u, 0xd078019fu, 0x60ac3d38u, 0x7fffffffu, 0x20b5d1d4u, 0x7fffffffu, 0x20b5d1d4u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x60bdfdfcu, 0xffffffffu, 0x60bdfdfcu, 0x7fffffffu, 0x20b5d1d4u, 0x7fffffffu, 0x20b5d1d4u, 0xd078019fu, 0x60ac3d38u, 0xd078019fu, 0x60ac3d38u, 0xd078019fu, 0x60ac3d38u, 0xd078019fu, 0x60ac3d38u}, + {0x2925feb3u, 0x00000001u, 0x2925feb3u, 0x00000001u, 0x7fffffffu, 0xb16614f4u, 0x7fffffffu, 0xb16614f4u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fffffffu, 0xb16614f5u, 0x7fffffffu, 0xb16614f5u, 0x7fffffffu, 0xb16614f4u, 0x7fffffffu, 0xb16614f4u, 0x2925feb3u, 0x00000001u, 0x2925feb3u, 0x00000001u, 0x2925feb3u, 0x00000001u, 0x2925feb3u, 0x00000001u}, + {0x86d5360fu, 0xfaf40417u, 0x86d5360fu, 0xfaf40417u, 0x86d5360fu, 0xfaf40417u, 0x86d5360fu, 0xfaf40417u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xd6d77e5fu, 0xfbf71737u, 0xd6d77e5fu, 0xfbf71737u, 0xd4424852u, 0x89631325u, 0xd4424852u, 0x89631325u, 0xd6d77e5fu, 0xfbf71737u, 0xd4424852u, 0x89631325u, 0x86d5360fu, 0xfaf40417u, 0x86d5360fu, 0xfaf40417u}, + {0xffffffffu, 0xde274210u, 0xffffffffu, 0xde274210u, 0xe96e12c9u, 0x65305d1cu, 0xe96e12c9u, 0x65305d1cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xff375f1cu, 0xffffffffu, 0xff375f1cu, 0xe96e12c9u, 0x65305d1cu, 0xe96e12c9u, 0x65305d1cu, 0xffffffffu, 0xde274210u, 0xffffffffu, 0xde274210u, 0xffffffffu, 0xde274210u, 0xffffffffu, 0xde274210u}, + {0x1043c4f3u, 0x8a860eeau, 0x1043c4f3u, 0x8a860eeau, 0xe9579523u, 0x7563acb0u, 0xe9579523u, 0x7563acb0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xf957d5f3u, 0xffe7aefau, 0xf957d5f3u, 0xffe7aefau, 0xe9579523u, 0x7563acb0u, 0xe9579523u, 0x7563acb0u, 0x1043c4f3u, 0x8a860eeau, 0x1043c4f3u, 0x8a860eeau, 0x1043c4f3u, 0x8a860eeau, 0x1043c4f3u, 0x8a860eeau}, + {0x310b55c7u, 0x12345678u, 0x310b55c7u, 0x12345678u, 0x310b55c7u, 0x12345678u, 0x310b55c7u, 0x12345678u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x333f57ffu, 0x963cd77fu, 0x333f57ffu, 0x963cd77fu, 0x12345678u, 0x96188707u, 0x12345678u, 0x96188707u, 0x333f57ffu, 0x963cd77fu, 0x12345678u, 0x96188707u, 0x310b55c7u, 0x12345678u, 0x310b55c7u, 0x12345678u}, + {0x80000000u, 0xbcabb1bau, 0x80000000u, 0xbcabb1bau, 0x80000000u, 0xbcabb1bau, 0x80000000u, 0xbcabb1bau, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x92345678u, 0xffffffffu, 0x92345678u, 0xffffffffu, 0x12345678u, 0xffffffffu, 0x12345678u, 0xffffffffu, 0x92345678u, 0xffffffffu, 0x12345678u, 0xffffffffu, 0x80000000u, 0xbcabb1bau, 0x80000000u, 0xbcabb1bau}, + {0x72fdad03u, 0x00000001u, 0x72fdad03u, 0x00000001u, 0x72fdad03u, 0x00000001u, 0x72fdad03u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x72ffbf03u, 0x6c60da43u, 0x72ffbf03u, 0x6c60da43u, 0x028f3b02u, 0x6c60da42u, 0x028f3b02u, 0x6c60da42u, 0x72ffbf03u, 0x6c60da43u, 0x028f3b02u, 0x6c60da42u, 0x72fdad03u, 0x00000001u, 0x72fdad03u, 0x00000001u}, + {0xe473cdc9u, 0x75eb16c7u, 0xe473cdc9u, 0x75eb16c7u, 0x12345678u, 0xe0d59f3cu, 0x12345678u, 0xe0d59f3cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xf677dff9u, 0xf5ff9fffu, 0xf677dff9u, 0xf5ff9fffu, 0x12345678u, 0xe0d59f3cu, 0x12345678u, 0xe0d59f3cu, 0xe473cdc9u, 0x75eb16c7u, 0xe473cdc9u, 0x75eb16c7u, 0xe473cdc9u, 0x75eb16c7u, 0xe473cdc9u, 0x75eb16c7u}, + {0x89abcdefu, 0x74102553u, 0x89abcdefu, 0x74102553u, 0x89abcdefu, 0x74102553u, 0x89abcdefu, 0x74102553u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x9dbbedffu, 0xf536affbu, 0x9dbbedffu, 0xf536affbu, 0x1cb3e931u, 0xe536aae9u, 0x1cb3e931u, 0xe536aae9u, 0x9dbbedffu, 0xf536affbu, 0x1cb3e931u, 0xe536aae9u, 0x89abcdefu, 0x74102553u, 0x89abcdefu, 0x74102553u}, + {0x4d126f38u, 0x7d42a12au, 0x4d126f38u, 0x7d42a12au, 0x4d126f38u, 0x7d42a12au, 0x4d126f38u, 0x7d42a12au, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x5d7b6ffdu, 0xffc3b3ffu, 0x5d7b6ffdu, 0xffc3b3ffu, 0x50692ce5u, 0xa68132ddu, 0x50692ce5u, 0xa68132ddu, 0x5d7b6ffdu, 0xffc3b3ffu, 0x50692ce5u, 0xa68132ddu, 0x4d126f38u, 0x7d42a12au, 0x4d126f38u, 0x7d42a12au}, + {0x744e3f0eu, 0x7fffffffu, 0x744e3f0eu, 0x7fffffffu, 0xde298785u, 0x80000000u, 0xde298785u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfe6fbf8fu, 0xffffffffu, 0xfe6fbf8fu, 0xffffffffu, 0xde298785u, 0x80000000u, 0xde298785u, 0x80000000u, 0x744e3f0eu, 0x7fffffffu, 0x744e3f0eu, 0x7fffffffu, 0x744e3f0eu, 0x7fffffffu, 0x744e3f0eu, 0x7fffffffu}, + {0xad554ba1u, 0x7c5153d0u, 0xad554ba1u, 0x7c5153d0u, 0xad554ba1u, 0x7c5153d0u, 0xad554ba1u, 0x7c5153d0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xeffdefefu, 0xfcfbfbffu, 0xeffdefefu, 0xfcfbfbffu, 0xeaf8edcfu, 0xc8aab92fu, 0xeaf8edcfu, 0xc8aab92fu, 0xeffdefefu, 0xfcfbfbffu, 0xeaf8edcfu, 0xc8aab92fu, 0xad554ba1u, 0x7c5153d0u, 0xad554ba1u, 0x7c5153d0u}, + {0xf8f008c5u, 0x0f119987u, 0xf8f008c5u, 0x0f119987u, 0xf8f008c5u, 0x0f119987u, 0xf8f008c5u, 0x0f119987u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfbf39cfdu, 0x5fd5bf97u, 0xfbf39cfdu, 0x5fd5bf97u, 0x1bd39c7cu, 0x5dc52692u, 0x1bd39c7cu, 0x5dc52692u, 0xfbf39cfdu, 0x5fd5bf97u, 0x1bd39c7cu, 0x5dc52692u, 0xf8f008c5u, 0x0f119987u, 0xf8f008c5u, 0x0f119987u}, + {0xd03b304du, 0x00ff00ffu, 0xd03b304du, 0x00ff00ffu, 0x89abcdefu, 0x991132c0u, 0x89abcdefu, 0x991132c0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xd9bbfdefu, 0x99ff32ffu, 0xd9bbfdefu, 0x99ff32ffu, 0x89abcdefu, 0x991132c0u, 0x89abcdefu, 0x991132c0u, 0xd03b304du, 0x00ff00ffu, 0xd03b304du, 0x00ff00ffu, 0xd03b304du, 0x00ff00ffu, 0xd03b304du, 0x00ff00ffu}, + {0xa92c8f72u, 0x07b9077du, 0xa92c8f72u, 0x07b9077du, 0xa92c8f72u, 0x07b9077du, 0xa92c8f72u, 0x07b9077du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xef7ceff6u, 0xf7bdf7fdu, 0xef7ceff6u, 0xf7bdf7fdu, 0x66546794u, 0xf6a5f7c1u, 0x66546794u, 0xf6a5f7c1u, 0xef7ceff6u, 0xf7bdf7fdu, 0x66546794u, 0xf6a5f7c1u, 0xa92c8f72u, 0x07b9077du, 0xa92c8f72u, 0x07b9077du}, + {0xb5691c18u, 0x5d6c710au, 0xb5691c18u, 0x5d6c710au, 0x7ae6b997u, 0xbfe4abb0u, 0x7ae6b997u, 0xbfe4abb0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffefbd9fu, 0xffecfbbau, 0xffefbd9fu, 0xffecfbbau, 0x7ae6b997u, 0xbfe4abb0u, 0x7ae6b997u, 0xbfe4abb0u, 0xb5691c18u, 0x5d6c710au, 0xb5691c18u, 0x5d6c710au, 0xb5691c18u, 0x5d6c710au, 0xb5691c18u, 0x5d6c710au}, + {0xc9570a1cu, 0xefa96b6eu, 0xc9570a1cu, 0xefa96b6eu, 0xc9570a1cu, 0xefa96b6eu, 0xc9570a1cu, 0xefa96b6eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xc9570a1cu, 0xefbbffffu, 0xc9570a1cu, 0xefbbffffu, 0x00000000u, 0xcd12bfb3u, 0x00000000u, 0xcd12bfb3u, 0xc9570a1cu, 0xefbbffffu, 0x00000000u, 0xcd12bfb3u, 0xc9570a1cu, 0xefa96b6eu, 0xc9570a1cu, 0xefa96b6eu}, + {0x00000000u, 0x37eadcd2u, 0x00000000u, 0x37eadcd2u, 0x00000000u, 0x37eadcd2u, 0x00000000u, 0x37eadcd2u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3a6d60aau, 0xbfebddffu, 0x3a6d60aau, 0xbfebddffu, 0x3a6d60aau, 0x89abcdefu, 0x3a6d60aau, 0x89abcdefu, 0x3a6d60aau, 0xbfebddffu, 0x3a6d60aau, 0x89abcdefu, 0x00000000u, 0x37eadcd2u, 0x00000000u, 0x37eadcd2u}, + {0x114a2d32u, 0x00000000u, 0x114a2d32u, 0x00000000u, 0x89abcdefu, 0x12345678u, 0x89abcdefu, 0x12345678u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x99ebedffu, 0x12345678u, 0x99ebedffu, 0x12345678u, 0x89abcdefu, 0x12345678u, 0x89abcdefu, 0x12345678u, 0x114a2d32u, 0x00000000u, 0x114a2d32u, 0x00000000u, 0x114a2d32u, 0x00000000u, 0x114a2d32u, 0x00000000u}, + {0x14090b01u, 0xffffffffu, 0x14090b01u, 0xffffffffu, 0x14090b01u, 0xffffffffu, 0x14090b01u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x1eb98bedu, 0xffffffffu, 0x1eb98bedu, 0xffffffffu, 0x0eb083edu, 0x1a0caeefu, 0x0eb083edu, 0x1a0caeefu, 0x1eb98bedu, 0xffffffffu, 0x0eb083edu, 0x1a0caeefu, 0x14090b01u, 0xffffffffu, 0x14090b01u, 0xffffffffu}, + {0x12345678u, 0x4521d7efu, 0x12345678u, 0x4521d7efu, 0x00000001u, 0x80000000u, 0x00000001u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x12345679u, 0xc521d7efu, 0x12345679u, 0xc521d7efu, 0x00000001u, 0x80000000u, 0x00000001u, 0x80000000u, 0x12345678u, 0x4521d7efu, 0x12345678u, 0x4521d7efu, 0x12345678u, 0x4521d7efu, 0x12345678u, 0x4521d7efu}, + {0xefa94a8eu, 0x561bfb20u, 0xefa94a8eu, 0x561bfb20u, 0xefa94a8eu, 0x561bfb20u, 0xefa94a8eu, 0x561bfb20u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffb9cafeu, 0x7fbbfb3du, 0xffb9cafeu, 0x7fbbfb3du, 0x19b0c2f0u, 0x69b1111du, 0x19b0c2f0u, 0x69b1111du, 0xffb9cafeu, 0x7fbbfb3du, 0x19b0c2f0u, 0x69b1111du, 0xefa94a8eu, 0x561bfb20u, 0xefa94a8eu, 0x561bfb20u}, + {0xbcf7629fu, 0xaf1de605u, 0xbcf7629fu, 0xaf1de605u, 0x41622456u, 0x00000000u, 0x41622456u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfdf766dfu, 0xaf1de605u, 0xfdf766dfu, 0xaf1de605u, 0x41622456u, 0x00000000u, 0x41622456u, 0x00000000u, 0xbcf7629fu, 0xaf1de605u, 0xbcf7629fu, 0xaf1de605u, 0xbcf7629fu, 0xaf1de605u, 0xbcf7629fu, 0xaf1de605u}, + {0xd47a46a9u, 0x12da9645u, 0xd47a46a9u, 0x12da9645u, 0x7ded0e8du, 0x00000000u, 0x7ded0e8du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfdff4eadu, 0x12da9645u, 0xfdff4eadu, 0x12da9645u, 0x7ded0e8du, 0x00000000u, 0x7ded0e8du, 0x00000000u, 0xd47a46a9u, 0x12da9645u, 0xd47a46a9u, 0x12da9645u, 0xd47a46a9u, 0x12da9645u, 0xd47a46a9u, 0x12da9645u}, + {0x697d50dbu, 0x89abcdefu, 0x697d50dbu, 0x89abcdefu, 0xdeacacaau, 0xcf93dd30u, 0xdeacacaau, 0xcf93dd30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffdfcfbu, 0xcfbbddffu, 0xfffdfcfbu, 0xcfbbddffu, 0xdeacacaau, 0xcf93dd30u, 0xdeacacaau, 0xcf93dd30u, 0x697d50dbu, 0x89abcdefu, 0x697d50dbu, 0x89abcdefu, 0x697d50dbu, 0x89abcdefu, 0x697d50dbu, 0x89abcdefu}, + {0x00ff00ffu, 0xb6a48819u, 0x00ff00ffu, 0xb6a48819u, 0x1f752ee6u, 0x80000000u, 0x1f752ee6u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x1fff2effu, 0xb6a48819u, 0x1fff2effu, 0xb6a48819u, 0x1f752ee6u, 0x80000000u, 0x1f752ee6u, 0x80000000u, 0x00ff00ffu, 0xb6a48819u, 0x00ff00ffu, 0xb6a48819u, 0x00ff00ffu, 0xb6a48819u, 0x00ff00ffu, 0xb6a48819u}, + {0x12345678u, 0xbb36bb87u, 0x12345678u, 0xbb36bb87u, 0x12345678u, 0xbb36bb87u, 0x12345678u, 0xbb36bb87u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x1abc7e7bu, 0xffb6ffd7u, 0x1abc7e7bu, 0xffb6ffd7u, 0x0888385bu, 0x5cb6c6d7u, 0x0888385bu, 0x5cb6c6d7u, 0x1abc7e7bu, 0xffb6ffd7u, 0x0888385bu, 0x5cb6c6d7u, 0x12345678u, 0xbb36bb87u, 0x12345678u, 0xbb36bb87u}, + {0xd24df64du, 0x9595f0c7u, 0xd24df64du, 0x9595f0c7u, 0xd24df64du, 0x9595f0c7u, 0xd24df64du, 0x9595f0c7u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xd24df64du, 0xffffffffu, 0xd24df64du, 0xffffffffu, 0x00000000u, 0x7fffffffu, 0x00000000u, 0x7fffffffu, 0xd24df64du, 0xffffffffu, 0x00000000u, 0x7fffffffu, 0xd24df64du, 0x9595f0c7u, 0xd24df64du, 0x9595f0c7u}, + {0xffffffffu, 0x00ff00ffu, 0xffffffffu, 0x00ff00ffu, 0xffffffffu, 0x00ff00ffu, 0xffffffffu, 0x00ff00ffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xfefffeffu, 0xffffffffu, 0xfefffeffu, 0x00000001u, 0xfe7ffe21u, 0x00000001u, 0xfe7ffe21u, 0xffffffffu, 0xfefffeffu, 0x00000001u, 0xfe7ffe21u, 0xffffffffu, 0x00ff00ffu, 0xffffffffu, 0x00ff00ffu}, + {0xdd6537d2u, 0x6c2ab542u, 0xdd6537d2u, 0x6c2ab542u, 0xdd6537d2u, 0x6c2ab542u, 0xdd6537d2u, 0x6c2ab542u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xfe7fffcau, 0xffffffffu, 0xfe7fffcau, 0xffffffffu, 0xde5ffb8au, 0xffffffffu, 0xde5ffb8au, 0xffffffffu, 0xfe7fffcau, 0xffffffffu, 0xde5ffb8au, 0xdd6537d2u, 0x6c2ab542u, 0xdd6537d2u, 0x6c2ab542u} +}; + +constexpr const char* Names[Checked] = { + "buffer_atomic_or_x2 returned lo", "buffer_atomic_or_x2 returned hi", "buffer_atomic_swap_x2 returned lo", "buffer_atomic_swap_x2 returned hi", + "buffer_atomic_or_x2 returned lo (exec-masked)", "buffer_atomic_or_x2 returned hi (exec-masked)", "buffer_atomic_swap_x2 returned lo (exec-masked)", "buffer_atomic_swap_x2 returned hi (exec-masked)", + "buffer_atomic_or_x2 returned lo (V# end)", "buffer_atomic_or_x2 returned hi (V# end)", "buffer_atomic_swap_x2 returned lo (V# end)", "buffer_atomic_swap_x2 returned hi (V# end)", + "or_x2 memory lo", "or_x2 memory hi", "or_x2 (no return) memory lo", "or_x2 (no return) memory hi", + "swap_x2 memory lo", "swap_x2 memory hi", "swap_x2 (no return) memory lo", "swap_x2 (no return) memory hi", + "or_x2 (exec-masked) memory lo", "or_x2 (exec-masked) memory hi", "swap_x2 (exec-masked) memory lo", "swap_x2 (exec-masked) memory hi", + "or_x2 (V# end) memory lo", "or_x2 (V# end) memory hi", "swap_x2 (V# end) memory lo", "swap_x2 (V# end) memory hi", +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x31016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + Input.fill(0u); + for (std::uint32_t tid = 0; tid < waveSize; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), waveSize * Inputs * 4u); + const auto output = BufferDescriptor(Output.data(), waveSize * Results * 4u); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked], const char* const (&names)[Checked], const char* run) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], std::string("buffer 64-bit or/swap atomics ") + run + ": lane " + std::to_string(tid) + " " + names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device, Wave32Code, 32, device->Target()); + Check(Expected32, Names, "wave32"); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64, Names, "wave64"); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64, Names, "wave64 split"); + std::puts("buffer 64-bit or/swap atomics tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Bvh64IntersectRay.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Bvh64IntersectRay.cpp new file mode 100644 index 000000000..d0a599877 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Bvh64IntersectRay.cpp @@ -0,0 +1,304 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Record = 16; +constexpr std::uint32_t Forms = 4; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint64_t NodeBytes = 64; +constexpr std::uint32_t LastNode = 6; +constexpr std::uint32_t Invalid = 0xffffffffu; +constexpr std::uint32_t BoxSort = 0x80000000u; +constexpr std::uint32_t TriangleId = 0x00abc123u; +constexpr std::array Box32Children{0x1000u, 0x1001u, 0x1002u, 0x1003u}; +constexpr std::array Box16Children{0x2000u, 0x2001u, 0x2002u, 0x2003u}; +constexpr std::array PastEndChildren{0x3000u, 0x3001u, 0x3002u, 0x3003u}; +constexpr std::array FormNames{"image_bvh64_intersect_ray", "image_bvh_intersect_ray", "image_bvh64_intersect_ray a16", "image_bvh_intersect_ray a16"}; + +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +constexpr std::array MakeCode() { + std::array code{}; + std::size_t at = 0; + code[at++] = 0x34020086u; + for (std::uint32_t dword = 0; dword < Record; ++dword) { + code[at++] = 0xe0301000u | (dword * 4u); + code[at++] = 0x80000001u | ((4u + dword) << 8u); + } + code[at++] = 0xbf8c3f70u; + code[at++] = 0xf19c9f01u; + code[at++] = 0x00031405u; + code[at++] = 0xf1989f07u; + code[at++] = 0x00021804u; + code[at++] = 0x0a090807u; + code[at++] = 0x0e0d0c0bu; + code[at++] = 0x0000100fu; + code[at++] = 0xf19c9f05u; + code[at++] = 0x40031c05u; + code[at++] = 0x09080706u; + code[at++] = 0x1312110au; + code[at++] = 0xf1989f05u; + code[at++] = 0x40022004u; + code[at++] = 0x0a090807u; + code[at++] = 0x00131211u; + for (std::uint32_t dword = 0; dword < Record; ++dword) { + code[at++] = 0xe0701000u | (dword * 4u); + code[at++] = 0x80010001u | ((20u + dword) << 8u); + } + code[at++] = 0xbf810000u; + return code; +} + +alignas(256) constexpr std::array Code = MakeCode(); + +struct Ray { + float extent; + std::array origin; + std::array direction; + std::array inverse; +}; + +struct Case { + std::uint32_t node; + Ray ray; + std::array expected; +}; + +constexpr float Infinity = std::numeric_limits::infinity(); +constexpr std::array Diagonal{0.25f, 0.5f, 1.0f}; +constexpr std::array DiagonalInverse{4.0f, 2.0f, 1.0f}; +constexpr Ray Long{100.0f, {0.0f, 0.0f, 0.0f}, Diagonal, DiagonalInverse}; +constexpr Ray Short{2.0f, {0.0f, 0.0f, 0.0f}, Diagonal, DiagonalInverse}; +constexpr Ray Forward{100.0f, {0.25f, 0.25f, 0.0f}, {0.0f, 0.0f, 1.0f}, {Infinity, Infinity, 1.0f}}; + +constexpr std::uint32_t Pointer(std::uint32_t node, std::uint32_t type) { + return (node << 3u) | type; +} + +constexpr std::array Cases{{ + {Pointer(2, 5), Long, {Box32Children[2], Box32Children[0], Invalid, Invalid}}, + {Pointer(4, 4), Long, {Box16Children[2], Box16Children[0], Invalid, Invalid}}, + {Pointer(1, 0), Forward, {0xc0a00000u, 0xbf800000u, TriangleId, 1u}}, + {Pointer(1, 1), Forward, {0x7f800000u, 0x3f800000u, TriangleId, 0u}}, + {Pointer(1, 7), Long, {Invalid, Invalid, Invalid, Invalid}}, + {Pointer(6, 5), Long, {Invalid, Invalid, Invalid, Invalid}}, + {Pointer(7, 0), Forward, {Invalid, Invalid, Invalid, Invalid}}, + {Pointer(2, 5), Short, {Box32Children[2], Invalid, Invalid, Invalid}}, +}}; + +constexpr std::array, 4> Boxes{{ + {1.0f, 2.0f, 4.0f, 2.0f, 4.0f, 8.0f}, + {10.0f, 0.0f, 0.0f, 11.0f, 1.0f, 1.0f}, + {0.25f, 0.5f, 1.0f, 0.5f, 1.0f, 2.0f}, + {-2.0f, -4.0f, -8.0f, -1.0f, -2.0f, -4.0f}, +}}; + +constexpr std::array TriangleVertices{0.0f, 0.0f, 5.0f, 1.0f, 0.0f, 5.0f, 0.0f, 1.0f, 5.0f, 1.0f, 1.0f, 5.0f}; + +std::uint32_t Bits(float value) { + return std::bit_cast(value); +} + +std::uint32_t Half(float value) { + const auto bits = Bits(value); + const auto sign = (bits >> 16u) & 0x8000u; + const auto exponent = (bits >> 23u) & 0xffu; + const auto mantissa = bits & 0x7fffffu; + if (exponent == 0u && mantissa == 0u) return sign; + if (exponent == 0xffu && mantissa == 0u) return sign | 0x7c00u; + Require(exponent >= 113u && exponent <= 142u && (mantissa & 0x1fffu) == 0u, "bvh64 intersect ray: a test value is not a float16"); + return sign | ((exponent - 112u) << 10u) | (mantissa >> 13u); +} + +class GuestBlock { +public: + GuestBlock() { + constexpr std::uintptr_t hint = std::uintptr_t{1} << 40u; +#ifdef _WIN32 + for (std::uintptr_t attempt = 0; block == nullptr && attempt < 16u; ++attempt) { + block = static_cast(VirtualAlloc(reinterpret_cast(hint + (attempt << 32u)), BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + } + if (block == nullptr) block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + void* mapped = mmap(reinterpret_cast(hint), BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + block = mapped == MAP_FAILED ? nullptr : static_cast(mapped); +#endif + Require(block != nullptr, "bvh64 intersect ray: cannot allocate the guest block"); + std::memset(block, 0, BlockBytes); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + munmap(block, BlockBytes); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint64_t Address() const { return static_cast(reinterpret_cast(block)); } + + void Write(std::uint32_t node, std::uint32_t dword, std::uint32_t value) { + std::memcpy(block + node * NodeBytes + dword * 4u, &value, sizeof(value)); + } + +private: + std::uint8_t* block = nullptr; +}; + +void WriteBox32(GuestBlock& guest, std::uint32_t node, const std::array& children) { + for (std::uint32_t child = 0; child < 4u; ++child) { + guest.Write(node, child, children[child]); + for (std::uint32_t index = 0; index < 6u; ++index) guest.Write(node, 4u + child * 6u + index, Bits(Boxes[child][index])); + } +} + +void WriteBox16(GuestBlock& guest, std::uint32_t node, const std::array& children) { + for (std::uint32_t child = 0; child < 4u; ++child) { + guest.Write(node, child, children[child]); + for (std::uint32_t index = 0; index < 6u; index += 2u) { + const auto position = child * 6u + index; + guest.Write(node, 4u + position / 2u, Half(Boxes[child][index]) | (Half(Boxes[child][index + 1u]) << 16u)); + } + } +} + +void WriteTriangles(GuestBlock& guest, std::uint32_t node) { + for (std::uint32_t index = 0; index < TriangleVertices.size(); ++index) guest.Write(node, index, Bits(TriangleVertices[index])); + guest.Write(node, 15u, TriangleId); +} + +void FillNodes(GuestBlock& guest) { + WriteTriangles(guest, 1u); + WriteBox32(guest, 2u, Box32Children); + WriteBox16(guest, 4u, Box16Children); + WriteBox32(guest, 6u, PastEndChildren); + WriteTriangles(guest, 7u); +} + +void FillInput(std::uint64_t nodeBias) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& test = Cases[tid % Cases.size()]; + const auto& ray = test.ray; + const std::uint64_t node64 = nodeBias + test.node; + auto* words = &Input[tid * Record]; + words[0] = test.node; + words[1] = static_cast(node64); + words[2] = static_cast(node64 >> 32u); + words[3] = Bits(ray.extent); + for (std::uint32_t axis = 0; axis < 3u; ++axis) { + words[4u + axis] = Bits(ray.origin[axis]); + words[7u + axis] = Bits(ray.direction[axis]); + words[10u + axis] = Bits(ray.inverse[axis]); + } + words[13] = Half(ray.direction[0]) | (Half(ray.direction[1]) << 16u); + words[14] = Half(ray.direction[2]) | (Half(ray.inverse[0]) << 16u); + words[15] = Half(ray.inverse[1]) | (Half(ray.inverse[2]) << 16u); + } +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::array BvhDescriptor(std::uint64_t base, std::uint64_t lastNode) { + return {static_cast(base >> 8u), static_cast((base >> 40u) & 0xffu) | BoxSort, static_cast(lastNode), static_cast((lastNode >> 32u) & 0x3ffu)}; +} + +void Run(AgcDriver::VulkanDevice& device, const GuestBlock& guest, bool addressInPointer) { + const auto address = guest.Address(); + FillInput(addressInPointer ? address >> 3u : 0u); + Output.fill(0xdeadbeefu); + std::vector userData(16, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + const auto narrow = BvhDescriptor(address, LastNode); + const auto wide = addressInPointer ? BvhDescriptor(0u, address / NodeBytes + LastNode) : narrow; + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + std::copy(narrow.begin(), narrow.end(), userData.begin() + 8); + std::copy(wide.begin(), wide.end(), userData.begin() + 12); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(bool addressInPointer) { + const std::string mode = addressInPointer ? "address in the node pointer" : "base in the descriptor"; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Cases[tid % Cases.size()].expected; + for (std::uint32_t form = 0; form < Forms; ++form) { + for (std::uint32_t dword = 0; dword < 4u; ++dword) { + const auto actual = Output[tid * Record + form * 4u + dword]; + Require(actual == expected[dword], std::string(FormNames[form]) + ", " + mode + ": thread " + std::to_string(tid) + " dword " + std::to_string(dword) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[dword])); + } + } + } +} + +} + +int main() { + try { + if (ShaderRecompiler::RayTracingStrict() || ShaderRecompiler::RayTracingMiss()) { + std::puts("skipped, APS5_RAYTRACING replaces the node test"); + return VulkanTestSkipped; + } + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + Require((guest.Address() >> 35u) != 0u, "bvh64 intersect ray: the guest block is not above 32 GiB, the node pointer would fit 32 bits"); + FillNodes(guest); + for (const bool addressInPointer : {true, false}) { + Run(*device, guest, addressInPointer); + Check(addressInPointer); + } + std::puts("bvh64 intersect ray tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DivisionResultModifiers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DivisionResultModifiers.cpp new file mode 100644 index 000000000..d6ac16fc2 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DivisionResultModifiers.cpp @@ -0,0 +1,206 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x36100e81, 0x7d8a1080, 0xd56f800a, + 0x041a0b04, 0xd55f800b, 0x041a0b04, 0xd55f820c, 0x241a0b04, 0xd56f800d, 0x441a0905, 0xd570800e, + 0x04120d04, 0xd5700010, 0x0c120d06, 0xd5608012, 0x04120d04, 0xd5600014, 0x1c1a0906, 0xd5608016, + 0x141a0906, 0xd5608018, 0x24120d04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x32780000u, 0x7d2e1601u}, + {0x00000000u, 0x00800000u, 0x3ec00000u, 0x9ffbf25eu}, + {0x00000000u, 0x3e8e7000u, 0x8800759cu, 0x7e37e43cu}, + {0x00800000u, 0x40d00000u, 0xca667aa1u, 0x0f7a9d5bu}, + {0x32e94401u, 0x5f800000u, 0x3f800000u, 0xded37902u}, + {0x00000000u, 0x80000000u, 0x3f800000u, 0x0406ef86u}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff00000u, 0xc2f8f359u, 0x81a56e1fu}, + {0x3f800000u, 0x2232f1b4u, 0x7f7fffffu, 0x998ad4a5u}, + {0x40000000u, 0xb6e038d3u, 0xf4787e84u, 0x37112fe1u}, + {0x40780000u, 0xbf800000u, 0x32680000u, 0x1bdea0a2u}, + {0x3f800000u, 0x5f800000u, 0xbeb80000u, 0xc009870du}, + {0x7f800000u, 0xff780000u, 0xc0900000u, 0xde1756c4u}, + {0x00000000u, 0x3ff00000u, 0x00000000u, 0x00100000u}, + {0x00000000u, 0x3ff00000u, 0x00000000u, 0xbff00000u}, + {0x3f680000u, 0x80f80000u, 0x80800000u, 0x9f8fe465u}, + {0x1f800000u, 0xc0200000u, 0xbf280000u, 0xb1fee08fu}, + {0x41e3bd75u, 0x7f800000u, 0xbf800000u, 0xece56e40u}, + {0xbef80000u, 0x40000000u, 0x40400000u, 0xfc972e59u}, + {0x00000000u, 0x001e1000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x3c400000u, 0xbf800000u, 0xc8c6df8eu}, + {0x00000000u, 0xbff00000u, 0x00000000u, 0x00000000u}, + {0x47e2cc36u, 0x00800000u, 0x00000000u, 0x66263f9fu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u}, + {0x00000000u, 0x00000000u, 0xc2f8f359u, 0x81a56e1fu}, + {0x00000000u, 0x3f400000u, 0x80000000u, 0xa8333fe6u}, + {0x00000000u, 0x40080000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x7ff80000u, 0xc2f8f359u, 0x81a56e1fu}, + {0x00000000u, 0xff7fffffu, 0x80000000u, 0xd6ee400au}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00100000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x06420000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x12780000u, 0x3f800000u, 0x3f800000u, 0x12780000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u}, + {0x3ec00000u, 0x00000000u, 0x00000000u, 0x3ec00000u, 0x00000000u, 0x00800000u, 0x2abc6ea9u, 0x00801868u, 0x00000000u, 0x00000000u, 0x3ec00000u, 0x9ffbf25eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x3e8e7000u, 0x8800759cu, 0x7e37e43cu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3e8e7000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00800000u, 0x48d00000u, 0x00000000u, 0x3ff00000u, 0xca667aa1u, 0x0f7a9d5bu, 0xca667aa1u, 0x0f7a9d5bu, 0x00000000u, 0x3ff00000u}, + {0x3f800000u, 0x32e94401u, 0x32e94401u, 0x00000000u, 0x00000000u, 0x00000000u, 0x88d0c0dcu, 0x7db7b2f8u, 0x00000000u, 0x00000000u, 0x3f800000u, 0xded37902u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x1a32f1b4u, 0x3f800000u, 0x1a32f1b4u, 0x00000000u, 0x00000000u, 0x7f7fffffu, 0x998ad4a5u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0xaee038d3u, 0x00000000u, 0x00000000u, 0xf4787e84u, 0xb7112fe1u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x40780000u, 0xbf800000u, 0x00000000u, 0x00000000u, 0x32680000u, 0x9bdea0a2u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x67800000u, 0x00000000u, 0x00000000u, 0xbeb80000u, 0xc009870du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3ff00000u, 0x7f7ffffeu, 0xff780000u, 0x00000000u, 0x3ff00000u, 0xc0900000u, 0x5e1756c4u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00100000u, 0x00000000u, 0x00100000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3f680000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x3f680000u, 0x00f80000u, 0x80800000u, 0x1f8fe465u, 0x80800000u, 0x1f8fe465u, 0x3f680000u, 0x00f80000u}, + {0x00000000u, 0x1f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x1f800000u, 0xc8200000u, 0x00000000u, 0x3ff00000u, 0xbf280000u, 0x31fee08fu, 0xbf280000u, 0x31fee08fu, 0x00000000u, 0x3ff00000u}, + {0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xbf800000u, 0xece56e40u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x3ef80000u, 0x3ef80000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x40400000u, 0xfc972e59u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x002e1000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x001e1000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x001e1000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x1759d00au, 0x51a0597eu, 0x00000000u, 0x00000000u, 0xbf800000u, 0xc8c6df8eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x63f5b02cu, 0x1eb63f9fu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x66263f9fu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f400000u, 0x00000000u, 0x3f400000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xa8333fe6u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x3ff00000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0xff7fffffu, 0x00000000u, 0x3ff00000u, 0x80000000u, 0x56ee400au, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u} +}; +constexpr std::uint32_t Checked[32] = {0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xfff0u, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu}; +constexpr const char* Names[16] = { + "v_div_fmas_f32 v10, v4, v5, v6 clamp", + "v_div_fixup_f32 v11, v4, v5, v6 clamp", + "v_div_fixup_f32 v12, -v4, |v5|, v6 clamp", + "v_div_fmas_f32 v13, v5, -v4, v6 clamp", + "v_div_fmas_f64 v[14:15], v[4:5], v[6:7], v[4:5] clamp lo", + "v_div_fmas_f64 v[14:15], v[4:5], v[6:7], v[4:5] clamp hi", + "v_div_fmas_f64 v[16:17], v[6:7], v[6:7], v[4:5] mul:2 lo", + "v_div_fmas_f64 v[16:17], v[6:7], v[6:7], v[4:5] mul:2 hi", + "v_div_fixup_f64 v[18:19], v[4:5], v[6:7], v[4:5] clamp lo", + "v_div_fixup_f64 v[18:19], v[4:5], v[6:7], v[4:5] clamp hi", + "v_div_fixup_f64 v[20:21], v[6:7], v[4:5], v[6:7] div:2 lo", + "v_div_fixup_f64 v[20:21], v[6:7], v[4:5], v[6:7] div:2 hi", + "v_div_fixup_f64 v[22:23], v[6:7], v[4:5], v[6:7] clamp mul:4 lo", + "v_div_fixup_f64 v[22:23], v[6:7], v[4:5], v[6:7] clamp mul:4 hi", + "v_div_fixup_f64 v[24:25], -v[4:5], v[6:7], v[4:5] clamp lo", + "v_div_fixup_f64 v[24:25], -v[4:5], v[6:7], v[4:5] clamp hi", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("division result modifiers: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span code) { + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + const std::span code(Code); + const auto result = Compile(device, code); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckF32OmodRefused(AgcDriver::VulkanDevice& device) { + for (const std::uint32_t opcode : {0x15fu, 0x16fu}) { + alignas(256) const std::array code{0xd400000au | (opcode << 16u), 0x0c1a0b04u, 0xbf810000u}; + std::string refusal; + try { + static_cast(Compile(device, code)); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("VOP3 source modifiers are not implemented") != std::string::npos, "VOP3 opcode " + Hex(opcode) + " with omod was not refused"); + } +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + if ((Checked[tid] >> i) & 1u) Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + CheckF32OmodRefused(*device); + std::puts("division result modifiers tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16InactiveSource.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16InactiveSource.cpp new file mode 100644 index 000000000..1350e1ff2 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16InactiveSource.cpp @@ -0,0 +1,287 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 15; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0xbefe03ff, 0x33cc0ff3, 0x7e1402fa, 0xff004b04, 0x7e1602fa, + 0xff044b04, 0x7e1802fa, 0xff084b04, 0x7e1a02fa, 0xff011104, 0x7e1c02fa, 0xff051104, 0x7e1e02fa, + 0xff091104, 0x7e2002fa, 0xff0d1104, 0x7e2202fa, 0x5a014004, 0x4a240afa, 0xff004b04, 0x4a260afa, + 0xff044b04, 0x4a280afa, 0xff084b04, 0x4a2a0afa, 0xff011104, 0x4a2c0afa, 0xff051104, 0x4a2e0afa, + 0xff091104, 0x4a300afa, 0xff0d1104, 0xbefe03c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, + 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0xbefe03ff, 0x33cc0ff3, 0xbeff03ff, 0xc3a5960f, 0x7e1402fa, + 0xff004b04, 0x7e1602fa, 0xff044b04, 0x7e1802fa, 0xff084b04, 0x7e1a02fa, 0xff011104, 0x7e1c02fa, + 0xff051104, 0x7e1e02fa, 0xff091104, 0x7e2002fa, 0xff0d1104, 0x7e2202fa, 0x5a014004, 0x4a240afa, + 0xff004b04, 0x4a260afa, 0xff044b04, 0x4a280afa, 0xff084b04, 0x4a2a0afa, 0xff011104, 0x4a2c0afa, + 0xff051104, 0x4a2e0afa, 0xff091104, 0x4a300afa, 0xff0d1104, 0xbefe04c1, 0xe0781000, 0x80010a03, + 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x1e7ea419u, 0x51c9bc70u, 0x00000000u, 0x00000000u}, + {0x80a4df5au, 0xf38b2ffcu, 0x00000000u, 0x00000000u}, + {0x8306d03bu, 0xa5aec797u, 0x00000000u, 0x00000000u}, + {0xdc28ff90u, 0xf3f49249u, 0x00000000u, 0x00000000u}, + {0x1a466884u, 0xe255accbu, 0x00000000u, 0x00000000u}, + {0x39292d22u, 0xe5121482u, 0x00000000u, 0x00000000u}, + {0x99dd251du, 0x9f199504u, 0x00000000u, 0x00000000u}, + {0x8e7aa6e9u, 0x6bad6be2u, 0x00000000u, 0x00000000u}, + {0xc88b2875u, 0x9293de8fu, 0x00000000u, 0x00000000u}, + {0x8c3d5f16u, 0xd7a7a3ccu, 0x00000000u, 0x00000000u}, + {0xbb049a79u, 0xc6cd75e9u, 0x00000000u, 0x00000000u}, + {0xc4a334bfu, 0x7dabe929u, 0x00000000u, 0x00000000u}, + {0xc0433cbdu, 0xc5e818fau, 0x00000000u, 0x00000000u}, + {0x96263ae6u, 0x70eb9a0au, 0x00000000u, 0x00000000u}, + {0x3d6c51e3u, 0x00a61f93u, 0x00000000u, 0x00000000u}, + {0x9d3c7decu, 0x14aa4e71u, 0x00000000u, 0x00000000u}, + {0x1c593af5u, 0x49889310u, 0x00000000u, 0x00000000u}, + {0xd11745adu, 0x1919e93au, 0x00000000u, 0x00000000u}, + {0x731c9452u, 0x02f0ee99u, 0x00000000u, 0x00000000u}, + {0xd0944996u, 0xe4163207u, 0x00000000u, 0x00000000u}, + {0xaf67d461u, 0x7d836e77u, 0x00000000u, 0x00000000u}, + {0xadec6f11u, 0x5071950eu, 0x00000000u, 0x00000000u}, + {0x35d14880u, 0x65b00a2du, 0x00000000u, 0x00000000u}, + {0x406329bcu, 0x59001ac9u, 0x00000000u, 0x00000000u}, + {0xfadb8908u, 0xffb88309u, 0x00000000u, 0x00000000u}, + {0xede7bd0cu, 0x5b4c8012u, 0x00000000u, 0x00000000u}, + {0xce1d62e0u, 0x60581ccau, 0x00000000u, 0x00000000u}, + {0xffe976abu, 0xbef7e268u, 0x00000000u, 0x00000000u}, + {0x837a7935u, 0xa341738cu, 0x00000000u, 0x00000000u}, + {0x1389b372u, 0xb91dddd9u, 0x00000000u, 0x00000000u}, + {0x57241955u, 0x16f44881u, 0x00000000u, 0x00000000u}, + {0x8eb5140fu, 0x89be9c1cu, 0x00000000u, 0x00000000u}, + {0x4ac87fc0u, 0x49a047dcu, 0x00000000u, 0x00000000u}, + {0x751b4c83u, 0x24114258u, 0x00000000u, 0x00000000u}, + {0xe060a724u, 0xa51149bbu, 0x00000000u, 0x00000000u}, + {0xb437bdb5u, 0xb766ff10u, 0x00000000u, 0x00000000u}, + {0x93d20470u, 0x4f1a8a1du, 0x00000000u, 0x00000000u}, + {0x06555096u, 0xd9090b89u, 0x00000000u, 0x00000000u}, + {0xb5fb12e0u, 0x5e6c9992u, 0x00000000u, 0x00000000u}, + {0x5d111a9du, 0x7608d942u, 0x00000000u, 0x00000000u}, + {0x6c167229u, 0x17371472u, 0x00000000u, 0x00000000u}, + {0x66160227u, 0xe41686cdu, 0x00000000u, 0x00000000u}, + {0x95f19a51u, 0x8df05f25u, 0x00000000u, 0x00000000u}, + {0x7f9ac098u, 0xde0279e1u, 0x00000000u, 0x00000000u}, + {0x1dcf884cu, 0x6ce9da66u, 0x00000000u, 0x00000000u}, + {0x81a0ba05u, 0xcd73de00u, 0x00000000u, 0x00000000u}, + {0xc0099ebau, 0xdde3a361u, 0x00000000u, 0x00000000u}, + {0x99a46df0u, 0xe01c683eu, 0x00000000u, 0x00000000u}, + {0x7db4304du, 0x65ed0de4u, 0x00000000u, 0x00000000u}, + {0xeab06e9bu, 0x864fa3f3u, 0x00000000u, 0x00000000u}, + {0x423674a6u, 0xd82fb0f1u, 0x00000000u, 0x00000000u}, + {0x6b82e6c9u, 0x90624fe3u, 0x00000000u, 0x00000000u}, + {0xed1d9d16u, 0xfb759e0fu, 0x00000000u, 0x00000000u}, + {0x7aac319fu, 0x8350a73fu, 0x00000000u, 0x00000000u}, + {0x849acfb5u, 0xce1fd3d9u, 0x00000000u, 0x00000000u}, + {0x067f0cfcu, 0x93550840u, 0x00000000u, 0x00000000u}, + {0x397a7623u, 0xc40d31b5u, 0x00000000u, 0x00000000u}, + {0x2106119eu, 0xd1711cbdu, 0x00000000u, 0x00000000u}, + {0x0c83e786u, 0xf675299bu, 0x00000000u, 0x00000000u}, + {0xb464c554u, 0x85f184e0u, 0x00000000u, 0x00000000u}, + {0xe306f0c4u, 0xaba4c70eu, 0x00000000u, 0x00000000u}, + {0x1a42721eu, 0xb7a1774fu, 0x00000000u, 0x00000000u}, + {0x9f4fb02bu, 0x6dedc86au, 0x00000000u, 0x00000000u}, + {0x78549014u, 0x2475263cu, 0x00000000u, 0x00000000u} +}; + +constexpr std::uint32_t Expected32[32][15] = { + {0xdeaddeadu, 0xdc28ff90u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x00000000u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x2df2bc00u, 0x51c9bc70u, 0xdeaddeadu, 0xdeaddeadu, 0x51c9bc70u, 0x51c9bc70u}, + {0xdeaddeadu, 0x8306d03bu, 0x00000000u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0xdeaddeadu, 0xdeaddeadu, 0x76920037u, 0xf38b2ffcu, 0x1209d415u, 0x1209d415u, 0x1209d415u, 0x1209d415u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0xdeaddeadu, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u, 0xc4a334bfu, 0x70d053b4u, 0x70d053b4u, 0x70d053b4u, 0xdeaddeadu, 0xbe7eac5bu, 0xe255accbu, 0xbe7eac5bu}, + {0x99dd251du, 0x99dd251du, 0x99dd251du, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0xbb049a79u, 0x7eef399fu, 0x7eef399fu, 0x7eef399fu, 0xff587d06u, 0xff587d06u, 0xff587d06u, 0xff587d06u}, + {0x1a466884u, 0x1a466884u, 0x1a466884u, 0x39292d22u, 0x39292d22u, 0x39292d22u, 0x39292d22u, 0x8c3d5f16u, 0xb95ffd88u, 0xb95ffd88u, 0xb95ffd88u, 0xd842c226u, 0xd842c226u, 0xd842c226u, 0xd842c226u}, + {0x39292d22u, 0x39292d22u, 0x39292d22u, 0x99dd251du, 0x99dd251du, 0x99dd251du, 0x99dd251du, 0xc88b2875u, 0xa4d69904u, 0xa4d69904u, 0xa4d69904u, 0x058a90ffu, 0x058a90ffu, 0x058a90ffu, 0x058a90ffu}, + {0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0xdeaddeadu, 0x5737134eu, 0x5737134eu, 0x5737134eu, 0x210e8578u, 0x210e8578u, 0x210e8578u, 0x210e8578u}, + {0xbb049a79u, 0xbb049a79u, 0xbb049a79u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xdeaddeadu, 0x92ac3e45u, 0x92ac3e45u, 0x92ac3e45u, 0xa032cc41u, 0xa032cc41u, 0xa032cc41u, 0xa032cc41u}, + {0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0xdeaddeadu, 0x8f589e5eu, 0x8f589e5eu, 0x8f589e5eu, 0x530ad4ffu, 0x530ad4ffu, 0x530ad4ffu, 0x530ad4ffu}, + {0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0xbb049a79u, 0xbb049a79u, 0xbb049a79u, 0xbb049a79u, 0xdeaddeadu, 0x09e9483fu, 0x09e9483fu, 0x09e9483fu, 0x38b083a2u, 0x38b083a2u, 0x38b083a2u, 0x38b083a2u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0x1c593af5u, 0x00000000u, 0xdeaddeadu, 0xd11745adu, 0x00000000u, 0xd11745adu, 0xdeaddeadu, 0xdeaddeadu, 0x1f4a298eu, 0x02f0ee99u, 0xdeaddeadu, 0xd4083446u, 0x02f0ee99u, 0xd4083446u}, + {0xdeaddeadu, 0xd11745adu, 0x00000000u, 0x731c9452u, 0x731c9452u, 0x731c9452u, 0x731c9452u, 0xdeaddeadu, 0xdeaddeadu, 0xb52d77b4u, 0xe4163207u, 0x5732c659u, 0x5732c659u, 0x5732c659u, 0x5732c659u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xaf67d461u, 0x00000000u, 0xdeaddeadu, 0xadec6f11u, 0x00000000u, 0xadec6f11u, 0xdeaddeadu, 0xdeaddeadu, 0x1517de8eu, 0x65b00a2du, 0xdeaddeadu, 0x139c793eu, 0x65b00a2du, 0x139c793eu}, + {0xdeaddeadu, 0xadec6f11u, 0x00000000u, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0xdeaddeadu, 0xdeaddeadu, 0x06ec89dau, 0x59001ac9u, 0x8ed16349u, 0x8ed16349u, 0x8ed16349u, 0x8ed16349u}, + {0xdeaddeadu, 0xffe976abu, 0x00000000u, 0x406329bcu, 0x406329bcu, 0x406329bcu, 0x406329bcu, 0xdeaddeadu, 0xdeaddeadu, 0xffa1f9b4u, 0xffb88309u, 0x401bacc5u, 0x401bacc5u, 0x401bacc5u, 0x401bacc5u}, + {0xdeaddeadu, 0xce1d62e0u, 0x00000000u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xdeaddeadu, 0xdeaddeadu, 0x2969e2f2u, 0x5b4c8012u, 0x5628091au, 0x5628091au, 0x5628091au, 0x5628091au}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0x8eb5140fu, 0x00000000u, 0xdeaddeadu, 0xffe976abu, 0x00000000u, 0xffe976abu, 0xdeaddeadu, 0xdeaddeadu, 0x31f6879bu, 0xa341738cu, 0xdeaddeadu, 0xa32aea37u, 0xa341738cu, 0xa32aea37u}, + {0xdeaddeadu, 0x57241955u, 0x00000000u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0xdeaddeadu, 0xdeaddeadu, 0x1041f72eu, 0xb91dddd9u, 0x3c98570eu, 0x3c98570eu, 0x3c98570eu, 0x3c98570eu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +constexpr std::uint32_t Expected64[64][15] = { + {0xdeaddeadu, 0xdc28ff90u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x00000000u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x2df2bc00u, 0x51c9bc70u, 0xdeaddeadu, 0xdeaddeadu, 0x51c9bc70u, 0x51c9bc70u}, + {0xdeaddeadu, 0x8306d03bu, 0x00000000u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0xdeaddeadu, 0xdeaddeadu, 0x76920037u, 0xf38b2ffcu, 0x1209d415u, 0x1209d415u, 0x1209d415u, 0x1209d415u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0xdeaddeadu, 0xdc28ff90u, 0x00000000u, 0xdc28ff90u, 0xc4a334bfu, 0x70d053b4u, 0x70d053b4u, 0x70d053b4u, 0xdeaddeadu, 0xbe7eac5bu, 0xe255accbu, 0xbe7eac5bu}, + {0x99dd251du, 0x99dd251du, 0x99dd251du, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0x1a466884u, 0xbb049a79u, 0x7eef399fu, 0x7eef399fu, 0x7eef399fu, 0xff587d06u, 0xff587d06u, 0xff587d06u, 0xff587d06u}, + {0x1a466884u, 0x1a466884u, 0x1a466884u, 0x39292d22u, 0x39292d22u, 0x39292d22u, 0x39292d22u, 0x8c3d5f16u, 0xb95ffd88u, 0xb95ffd88u, 0xb95ffd88u, 0xd842c226u, 0xd842c226u, 0xd842c226u, 0xd842c226u}, + {0x39292d22u, 0x39292d22u, 0x39292d22u, 0x99dd251du, 0x99dd251du, 0x99dd251du, 0x99dd251du, 0xc88b2875u, 0xa4d69904u, 0xa4d69904u, 0xa4d69904u, 0x058a90ffu, 0x058a90ffu, 0x058a90ffu, 0x058a90ffu}, + {0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0xdeaddeadu, 0x5737134eu, 0x5737134eu, 0x5737134eu, 0x210e8578u, 0x210e8578u, 0x210e8578u, 0x210e8578u}, + {0xbb049a79u, 0xbb049a79u, 0xbb049a79u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0xdeaddeadu, 0x92ac3e45u, 0x92ac3e45u, 0x92ac3e45u, 0xa032cc41u, 0xa032cc41u, 0xa032cc41u, 0xa032cc41u}, + {0xc88b2875u, 0xc88b2875u, 0xc88b2875u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0xdeaddeadu, 0x8f589e5eu, 0x8f589e5eu, 0x8f589e5eu, 0x530ad4ffu, 0x530ad4ffu, 0x530ad4ffu, 0x530ad4ffu}, + {0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0xbb049a79u, 0xbb049a79u, 0xbb049a79u, 0xbb049a79u, 0xdeaddeadu, 0x09e9483fu, 0x09e9483fu, 0x09e9483fu, 0x38b083a2u, 0x38b083a2u, 0x38b083a2u, 0x38b083a2u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0x1c593af5u, 0x00000000u, 0xdeaddeadu, 0xd11745adu, 0x00000000u, 0xd11745adu, 0xdeaddeadu, 0xdeaddeadu, 0x1f4a298eu, 0x02f0ee99u, 0xdeaddeadu, 0xd4083446u, 0x02f0ee99u, 0xd4083446u}, + {0xdeaddeadu, 0xd11745adu, 0x00000000u, 0x731c9452u, 0x731c9452u, 0x731c9452u, 0x731c9452u, 0xdeaddeadu, 0xdeaddeadu, 0xb52d77b4u, 0xe4163207u, 0x5732c659u, 0x5732c659u, 0x5732c659u, 0x5732c659u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xaf67d461u, 0x00000000u, 0xdeaddeadu, 0xadec6f11u, 0x00000000u, 0xadec6f11u, 0xdeaddeadu, 0xdeaddeadu, 0x1517de8eu, 0x65b00a2du, 0xdeaddeadu, 0x139c793eu, 0x65b00a2du, 0x139c793eu}, + {0xdeaddeadu, 0xadec6f11u, 0x00000000u, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0xdeaddeadu, 0xdeaddeadu, 0x06ec89dau, 0x59001ac9u, 0x8ed16349u, 0x8ed16349u, 0x8ed16349u, 0x8ed16349u}, + {0xdeaddeadu, 0xffe976abu, 0x00000000u, 0x406329bcu, 0x406329bcu, 0x406329bcu, 0x406329bcu, 0xdeaddeadu, 0xdeaddeadu, 0xffa1f9b4u, 0xffb88309u, 0x401bacc5u, 0x401bacc5u, 0x401bacc5u, 0x401bacc5u}, + {0xdeaddeadu, 0xce1d62e0u, 0x00000000u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xdeaddeadu, 0xdeaddeadu, 0x2969e2f2u, 0x5b4c8012u, 0x5628091au, 0x5628091au, 0x5628091au, 0x5628091au}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0x8eb5140fu, 0x00000000u, 0xdeaddeadu, 0xffe976abu, 0x00000000u, 0xffe976abu, 0xdeaddeadu, 0xdeaddeadu, 0x31f6879bu, 0xa341738cu, 0xdeaddeadu, 0xa32aea37u, 0xa341738cu, 0xa32aea37u}, + {0xdeaddeadu, 0x57241955u, 0x00000000u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0xdeaddeadu, 0xdeaddeadu, 0x1041f72eu, 0xb91dddd9u, 0x3c98570eu, 0x3c98570eu, 0x3c98570eu, 0x3c98570eu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb437bdb5u, 0xb437bdb5u, 0xb437bdb5u, 0xdeaddeadu, 0xdeaddeadu, 0x00000000u, 0x00000000u, 0xdeaddeadu, 0xfdd80591u, 0xfdd80591u, 0xfdd80591u, 0xdeaddeadu, 0xdeaddeadu, 0x49a047dcu, 0x49a047dcu}, + {0xe060a724u, 0xe060a724u, 0xe060a724u, 0x4ac87fc0u, 0x4ac87fc0u, 0x4ac87fc0u, 0x4ac87fc0u, 0xdeaddeadu, 0x0471e97cu, 0x0471e97cu, 0x0471e97cu, 0x6ed9c218u, 0x6ed9c218u, 0x6ed9c218u, 0x6ed9c218u}, + {0x4ac87fc0u, 0x4ac87fc0u, 0x4ac87fc0u, 0x751b4c83u, 0x751b4c83u, 0x751b4c83u, 0x751b4c83u, 0xdeaddeadu, 0xefd9c97bu, 0xefd9c97bu, 0xefd9c97bu, 0x1a2c963eu, 0x1a2c963eu, 0x1a2c963eu, 0x1a2c963eu}, + {0x751b4c83u, 0x751b4c83u, 0x751b4c83u, 0xe060a724u, 0xe060a724u, 0xe060a724u, 0xe060a724u, 0xdeaddeadu, 0x2c824b93u, 0x2c824b93u, 0x2c824b93u, 0x97c7a634u, 0x97c7a634u, 0x97c7a634u, 0x97c7a634u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x95f19a51u, 0x95f19a51u, 0x95f19a51u, 0xdeaddeadu, 0x6c167229u, 0x00000000u, 0x6c167229u, 0xdeaddeadu, 0x7a08211eu, 0x7a08211eu, 0x7a08211eu, 0xdeaddeadu, 0x502cf8f6u, 0xe41686cdu, 0x502cf8f6u}, + {0xdeaddeadu, 0x6c167229u, 0x00000000u, 0x66160227u, 0x66160227u, 0x66160227u, 0x66160227u, 0xdeaddeadu, 0xdeaddeadu, 0xfa06d14eu, 0x8df05f25u, 0xf406614cu, 0xf406614cu, 0xf406614cu, 0xf406614cu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x99a46df0u, 0x99a46df0u, 0x99a46df0u, 0xdeaddeadu, 0x7f9ac098u, 0x00000000u, 0x7f9ac098u, 0xb437bdb5u, 0x068e4856u, 0x068e4856u, 0x068e4856u, 0xdeaddeadu, 0xec849afeu, 0x6ce9da66u, 0xec849afeu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0x81a0ba05u, 0x00000000u, 0xdeaddeadu, 0xc0099ebau, 0x00000000u, 0xc0099ebau, 0x4ac87fc0u, 0xdeaddeadu, 0x61bd2243u, 0xe01c683eu, 0xdeaddeadu, 0xa02606f8u, 0xe01c683eu, 0xa02606f8u}, + {0xdeaddeadu, 0x6b82e6c9u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x00000000u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0xd16ff4adu, 0x65ed0de4u, 0xdeaddeadu, 0xdeaddeadu, 0x65ed0de4u, 0x65ed0de4u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x7db4304du, 0x7db4304du, 0x7db4304du, 0xdeaddeadu, 0xeab06e9bu, 0x00000000u, 0xeab06e9bu, 0xdeaddeadu, 0x55e3e13eu, 0x55e3e13eu, 0x55e3e13eu, 0xdeaddeadu, 0xc2e01f8cu, 0xd82fb0f1u, 0xc2e01f8cu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0x849acfb5u, 0x00000000u, 0xdeaddeadu, 0xed1d9d16u, 0x00000000u, 0xed1d9d16u, 0xdeaddeadu, 0xdeaddeadu, 0x07eb76f4u, 0x8350a73fu, 0xdeaddeadu, 0x706e4455u, 0x8350a73fu, 0x706e4455u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x7aac319fu, 0x7aac319fu, 0x7aac319fu, 0xdeaddeadu, 0x849acfb5u, 0x00000000u, 0x849acfb5u, 0xdeaddeadu, 0x0e0139dfu, 0x0e0139dfu, 0x0e0139dfu, 0xdeaddeadu, 0x17efd7f5u, 0x93550840u, 0x17efd7f5u}, + {0xdeaddeadu, 0xb464c554u, 0x00000000u, 0x067f0cfcu, 0x067f0cfcu, 0x067f0cfcu, 0x067f0cfcu, 0xdeaddeadu, 0xdeaddeadu, 0x7871f709u, 0xc40d31b5u, 0xca8c3eb1u, 0xca8c3eb1u, 0xca8c3eb1u, 0xca8c3eb1u}, + {0xdeaddeadu, 0x0c83e786u, 0x00000000u, 0x397a7623u, 0x397a7623u, 0x397a7623u, 0x397a7623u, 0xdeaddeadu, 0xdeaddeadu, 0xddf50443u, 0xd1711cbdu, 0x0aeb92e0u, 0x0aeb92e0u, 0x0aeb92e0u, 0x0aeb92e0u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xe306f0c4u, 0x00000000u, 0xdeaddeadu, 0x1a42721eu, 0x00000000u, 0x1a42721eu, 0xdeaddeadu, 0xdeaddeadu, 0x50f4b92eu, 0x6dedc86au, 0xdeaddeadu, 0x88303a88u, 0x6dedc86au, 0x88303a88u}, + {0xdeaddeadu, 0x1a42721eu, 0x00000000u, 0x9f4fb02bu, 0x9f4fb02bu, 0x9f4fb02bu, 0x9f4fb02bu, 0xdeaddeadu, 0xdeaddeadu, 0x3eb7985au, 0x2475263cu, 0xc3c4d667u, 0xc3c4d667u, 0xc3c4d667u, 0xc3c4d667u} +}; + +constexpr const char* Names[Checked] = {"mov_quad_perm", "mov_quad_perm_fi", "mov_quad_perm_bc", "mov_row_shr", "mov_row_shr_fi", "mov_row_shr_bc", "mov_row_shr_bc_fi", "mov_row_mirror_masks", "add_quad_perm", "add_quad_perm_fi", "add_quad_perm_bc", "add_row_shr", "add_row_shr_fi", "add_row_shr_bc", "add_row_shr_bc_fi"}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked]) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "dpp16 inactive source wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64); + std::puts("dpp16 inactive source tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16Operands.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16Operands.cpp new file mode 100644 index 000000000..7c144a245 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16Operands.cpp @@ -0,0 +1,182 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e500304, 0x7e520305, 0x7e140306, + 0x7e1602ff, 0xdeaddead, 0x7e5802ff, 0xdeaddead, 0x7e5a02ff, 0xdeaddead, 0x7e5c02ff, 0xdeaddead, + 0x7e5e02ff, 0xdeaddead, 0x7e6002ff, 0xdeaddead, 0x7e6202ff, 0xdeaddead, 0xbefe03c1, 0xbefc0381, + 0x78140afa, 0xff004b04, 0x7e1686fa, 0xff004b28, 0x7e5888fa, 0xff004b28, 0x7e5c84fa, 0xff004b28, + 0xbefc03ff, 0x00010001, 0x7e6090fa, 0xff004b28, 0xbefe03c1, 0x7e18032d, 0x7e1a032f, 0x7e1c0331, + 0x7e500304, 0x7e520305, 0x7e1e0306, 0x7e2002ff, 0xdeaddead, 0x7e5802ff, 0xdeaddead, 0x7e5a02ff, + 0xdeaddead, 0x7e5c02ff, 0xdeaddead, 0x7e5e02ff, 0xdeaddead, 0x7e6002ff, 0xdeaddead, 0x7e6202ff, + 0xdeaddead, 0xbefe03c1, 0xbefc0381, 0x781e0afa, 0xff011104, 0x7e2086fa, 0xff011128, 0x7e5888fa, + 0xff011128, 0x7e5c84fa, 0xff011128, 0xbefc03ff, 0x00010001, 0x7e6090fa, 0xff011128, 0xbefe03c1, + 0x7e22032d, 0x7e24032f, 0x7e260331, 0x7e500304, 0x7e520305, 0x7e280306, 0x7e2a02ff, 0xdeaddead, + 0x7e5802ff, 0xdeaddead, 0x7e5a02ff, 0xdeaddead, 0x7e5c02ff, 0xdeaddead, 0x7e5e02ff, 0xdeaddead, + 0x7e6002ff, 0xdeaddead, 0x7e6202ff, 0xdeaddead, 0xbefe03ff, 0x33cc0ff3, 0xbefc0381, 0x78280afa, + 0x5a00b104, 0x7e2a86fa, 0x5a00b128, 0x7e5888fa, 0x5a00b128, 0x7e5c84fa, 0x5a00b128, 0xbefc03ff, + 0x00010001, 0x7e6090fa, 0x5a00b128, 0xbefe03c1, 0x7e2c032d, 0x7e2e032f, 0x7e300331, 0xd5780019, + 0x04c25d2c, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +constexpr const char* Names[] = {"quad_perm_pk_fmac_f16", "quad_perm_movrels", "quad_perm_movrelsd", "quad_perm_movreld", "quad_perm_movrelsd_2", "row_shr_pk_fmac_f16", "row_shr_movrels", "row_shr_movrelsd", "row_shr_movreld", "row_shr_movrelsd_2", "masks_exec_pk_fmac_f16", "masks_exec_movrels", "masks_exec_movrelsd", "masks_exec_movreld", "masks_exec_movrelsd_2", "relative_bases"}; +constexpr std::uint32_t Rows[32][4] = { + {0x4000c4abu, 0x00000000u, 0x4a3d4000u, 0xe255accbu}, + {0x35558000u, 0x80000000u, 0x2e668000u, 0x8c3d5f16u}, + {0xb1c73f01u, 0x80003f01u, 0x35553c00u, 0x9d3c7decu}, + {0x40004000u, 0x42484000u, 0x3f013c00u, 0xd0944996u}, + {0x4a3d3f01u, 0x4a3dc4abu, 0x35552e66u, 0x406329bcu}, + {0xc4abc4abu, 0x2e66b1c7u, 0x00004a3du, 0x1389b372u}, + {0xb1c7c4abu, 0x40000000u, 0x00004248u, 0x49a047dcu}, + {0x3f01b800u, 0x4a3db1c7u, 0xb1c78000u, 0x4f1a8a1du}, + {0x3c00b1c7u, 0xc4abc4abu, 0x3f012e66u, 0x17371472u}, + {0x2e668000u, 0x00003f01u, 0x40002e66u, 0x81a0ba05u}, + {0x80003f01u, 0x2e660000u, 0x42482e66u, 0x90624fe3u}, + {0x3f010000u, 0x00003c00u, 0x80003555u, 0xc40d31b5u}, + {0xb8003c00u, 0xb1c70000u, 0x4a3d4000u, 0xb7a1774fu}, + {0x80002e66u, 0x3f01b800u, 0x3f013555u, 0xd9814559u}, + {0x40004a3du, 0xb1c73f01u, 0x4a3d3f01u, 0xb6eafff5u}, + {0x00004248u, 0x00002e66u, 0x4a3d4248u, 0x8c53765fu}, + {0x4a3d0000u, 0xb1c73f01u, 0x2e662e66u, 0x33d91808u}, + {0x42484248u, 0x3c003c00u, 0xb8004a3du, 0xe1cd7be8u}, + {0x80003f01u, 0x00003f01u, 0xc4ab3555u, 0x42969a50u}, + {0xb1c73f01u, 0x3f010000u, 0x3f012e66u, 0x8ece78b0u}, + {0x4a3d3c00u, 0x4248c4abu, 0x3f014000u, 0x703e53cfu}, + {0xb1c7b1c7u, 0x00004248u, 0x3f01b800u, 0xf79edc82u}, + {0x2e66b1c7u, 0x3f014000u, 0x4a3d4248u, 0xe73c49eau}, + {0x3f01c4abu, 0x40003555u, 0xb1c72e66u, 0x2577c324u}, + {0x3f013c00u, 0x40003c00u, 0x3f018000u, 0x8bed5b65u}, + {0x40008000u, 0x35553555u, 0x40008000u, 0x0b6ec6f3u}, + {0x00004248u, 0x4a3d4a3du, 0xc4abb800u, 0x01504585u}, + {0xc4ab4a3du, 0x80008000u, 0x3f01b1c7u, 0xb870e4e1u}, + {0xb8000000u, 0x35553f01u, 0x8000b800u, 0xf551abfdu}, + {0x8000b1c7u, 0x00004248u, 0x40002e66u, 0xab266981u}, + {0xb800b800u, 0x80004248u, 0x00002e66u, 0x63f1bbbcu}, + {0xb8000000u, 0x35552e66u, 0x3c004000u, 0x63ac4f14u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x4a3d4000u, 0x42484000u, 0x42484000u, 0x40004000u, 0x42484000u, 0x4a3d4000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0x4a3d4000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x2e660000u, 0x80003f01u, 0x80003f01u, 0xb1c73f01u, 0x80003f01u, 0x2e668000u, 0x00000000u, 0x00000000u, 0x4000c4abu, 0x00000000u, 0x2e668000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3555c72cu, 0x00000000u, 0x00000000u, 0x4000c4abu, 0x00000000u, 0x35553c00u, 0x80000000u, 0x80000000u, 0x35558000u, 0x80000000u, 0x35553c00u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x41983c00u, 0x80000000u, 0x80000000u, 0x35558000u, 0x80000000u, 0x3cbc4480u, 0x80003f01u, 0x80003f01u, 0xb1c73f01u, 0x80003f01u, 0x3f013c00u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x4d8b40deu, 0x4a3db1c7u, 0x4a3db1c7u, 0x3f01b800u, 0x4a3db1c7u, 0x4e52c89eu, 0x42484000u, 0x42484000u, 0x40004000u, 0x42484000u, 0xd33d4d79u, 0x2e66b1c7u, 0x2e66b1c7u, 0xc4abc4abu, 0x2e66b1c7u, 0xdeaddeadu}, + {0xa49f4aa9u, 0x40000000u, 0x40000000u, 0xb1c7c4abu, 0x40000000u, 0x3cfd4a15u, 0x4a3dc4abu, 0x4a3dc4abu, 0x4a3d3f01u, 0x4a3dc4abu, 0x3cfd4a15u, 0x4a3dc4abu, 0x4a3dc4abu, 0x4a3d3f01u, 0x4a3dc4abu, 0xdeaddeadu}, + {0x4e3d4248u, 0x4a3dc4abu, 0x4a3dc4abu, 0x4a3d3f01u, 0x4a3dc4abu, 0xc8ab4248u, 0x2e66b1c7u, 0x2e66b1c7u, 0xc4abc4abu, 0x2e66b1c7u, 0x43014248u, 0x4a3db1c7u, 0x4a3db1c7u, 0x3f01b800u, 0x4a3db1c7u, 0xdeaddeadu}, + {0xd34d3abeu, 0x2e66b1c7u, 0x2e66b1c7u, 0xc4abc4abu, 0x2e66b1c7u, 0xc0de3abeu, 0x40000000u, 0x40000000u, 0xb1c7c4abu, 0x40000000u, 0xc0de3abeu, 0x40000000u, 0x40000000u, 0xb1c7c4abu, 0x40000000u, 0xdeaddeadu}, + {0xc66c2e66u, 0x00003c00u, 0x00003c00u, 0x3f010000u, 0x00003c00u, 0xc66c40deu, 0x4a3db1c7u, 0x4a3db1c7u, 0x3f01b800u, 0x4a3db1c7u, 0x3f012e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x40004255u, 0x2e660000u, 0x2e660000u, 0x80003f01u, 0x2e660000u, 0x4000b2ebu, 0xc4abc4abu, 0xc4abc4abu, 0x3c00b1c7u, 0xc4abc4abu, 0x40002e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x427b2e66u, 0xc4abc4abu, 0xc4abc4abu, 0x3c00b1c7u, 0xc4abc4abu, 0x424d2e66u, 0x00003f01u, 0x00003f01u, 0x2e668000u, 0x00003f01u, 0x42482e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x00003555u, 0x00003f01u, 0x00003f01u, 0x2e668000u, 0x00003f01u, 0x8000402bu, 0x2e660000u, 0x2e660000u, 0x80003f01u, 0x2e660000u, 0x80003555u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x4a3d4000u, 0x00002e66u, 0x00002e66u, 0x00004248u, 0x00002e66u, 0x4a154000u, 0x00003c00u, 0x00003c00u, 0x3f010000u, 0x00003c00u, 0x4a3d4000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x4541c5e8u, 0xb1c73f01u, 0xb1c73f01u, 0x40004a3du, 0xb1c73f01u, 0x3b01b156u, 0xb1c70000u, 0xb1c70000u, 0xb8003c00u, 0xb1c70000u, 0x3f013555u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x4a494301u, 0xb1c70000u, 0xb1c70000u, 0xb8003c00u, 0xb1c70000u, 0x4a3d3fb4u, 0x3f01b800u, 0x3f01b800u, 0x80002e66u, 0x3f01b800u, 0x4a3d3f01u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x4a3d424du, 0x3f01b800u, 0x3f01b800u, 0x80002e66u, 0x3f01b800u, 0x4a3d4463u, 0xb1c73f01u, 0xb1c73f01u, 0x40004a3du, 0xb1c73f01u, 0x4a3d4248u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x303e4255u, 0x3f010000u, 0x3f010000u, 0xb1c73f01u, 0x3f010000u, 0x2e662e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0x2e662e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb8004b1du, 0x00003f01u, 0x00003f01u, 0x80003f01u, 0x00003f01u, 0x49fd4a3du, 0xb1c73f01u, 0xb1c73f01u, 0x4a3d0000u, 0xb1c73f01u, 0xb8004a3du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc4ab3555u, 0xb1c73f01u, 0xb1c73f01u, 0x4a3d0000u, 0xb1c73f01u, 0xc4ab45d5u, 0x3c003c00u, 0x3c003c00u, 0x42484248u, 0x3c003c00u, 0xc4ab3555u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x47402e66u, 0x3c003c00u, 0x3c003c00u, 0x42484248u, 0x3c003c00u, 0x3f012e66u, 0x00003f01u, 0x00003f01u, 0x80003f01u, 0x00003f01u, 0x3f012e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x47404df3u, 0x40003555u, 0x40003555u, 0x3f01c4abu, 0x40003555u, 0x3cbcc62cu, 0x3f010000u, 0x3f010000u, 0xb1c73f01u, 0x3f010000u, 0x3f014000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3f01bc45u, 0x3f014000u, 0x3f014000u, 0x2e66b1c7u, 0x3f014000u, 0x3f014148u, 0x4248c4abu, 0x4248c4abu, 0x4a3d3c00u, 0x4248c4abu, 0x3f01b800u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x504a4524u, 0x4248c4abu, 0x4248c4abu, 0x4a3d3c00u, 0x4248c4abu, 0x4a15418fu, 0x00004248u, 0x00004248u, 0xb1c7b1c7u, 0x00004248u, 0x4a3d4248u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb8552918u, 0x00004248u, 0x00004248u, 0xb1c7b1c7u, 0x00004248u, 0x24f82918u, 0x3f014000u, 0x3f014000u, 0x2e66b1c7u, 0x3f014000u, 0xb1c72e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc7964a3du, 0x80008000u, 0x80008000u, 0xc4ab4a3du, 0x80008000u, 0x4541c4abu, 0x40003555u, 0x40003555u, 0x3f01c4abu, 0x40003555u, 0x3f018000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x40003c30u, 0x4a3d4a3du, 0x4a3d4a3du, 0x00004248u, 0x4a3d4a3du, 0x412b3555u, 0x40003c00u, 0x40003c00u, 0x3f013c00u, 0x40003c00u, 0x40008000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x4c4b49fdu, 0x40003c00u, 0x40003c00u, 0x3f013c00u, 0x40003c00u, 0x4d12b800u, 0x35553555u, 0x35553555u, 0x40008000u, 0x35553555u, 0xc4abb800u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3f01b1c7u, 0x35553555u, 0x35553555u, 0x40008000u, 0x35553555u, 0x3f01b1c7u, 0x4a3d4a3du, 0x4a3d4a3du, 0x00004248u, 0x4a3d4a3du, 0x3f01b1c7u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb155b800u, 0x35552e66u, 0x35552e66u, 0xb8000000u, 0x35552e66u, 0xbe394d56u, 0x80008000u, 0x80008000u, 0xc4ab4a3du, 0x80008000u, 0x8000b800u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x4000bde2u, 0x80004248u, 0x80004248u, 0xb800b800u, 0x80004248u, 0x40002e66u, 0x35553f01u, 0x35553f01u, 0xb8000000u, 0x35553f01u, 0x40002e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x00002e66u, 0x35553f01u, 0x35553f01u, 0xb8000000u, 0x35553f01u, 0x0000b779u, 0x00004248u, 0x00004248u, 0x8000b1c7u, 0x00004248u, 0x00002e66u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3c003feeu, 0x00004248u, 0x00004248u, 0x8000b1c7u, 0x00004248u, 0x3aab3fcdu, 0x80004248u, 0x80004248u, 0xb800b800u, 0x80004248u, 0x3c004000u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +void Fill(std::uint32_t tid, std::uint32_t* words) { + for (std::uint32_t i = 0; i < 4; ++i) words[i] = Rows[tid][i]; +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("dpp16 operands: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < Threads) { + std::printf("skipped, the device's subgroups are narrower than a wave (%u lanes)\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Run(*device); + Check(); + std::puts("dpp16 operands tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16RowShareXmask.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16RowShareXmask.cpp new file mode 100644 index 000000000..0fd6a6861 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp16RowShareXmask.cpp @@ -0,0 +1,289 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xbefe03ff, 0x33cc0ff3, 0x7e1402fa, + 0xff015004, 0x7e1602fa, 0xff055504, 0x7e1802fa, 0xff095c04, 0x7e1a02fa, 0xff0d5804, 0x7e1c02fa, + 0x5a095f04, 0x7e1e02fa, 0xff016104, 0x7e2002fa, 0xff056604, 0x7e2202fa, 0xff096f04, 0x7e2402fa, + 0xa5016a04, 0x4a260afa, 0xff015304, 0x4a280afa, 0xff095c04, 0x4a2a0afa, 0x69055404, 0x4a2c0afa, + 0xff016404, 0x4c2e0afa, 0xff0d6d04, 0x20300efa, 0xff095406, 0x20320efa, 0xff056906, 0xbefe03c1, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xbefe03ff, 0x33cc0ff3, 0xbeff03ff, + 0xc3a5960f, 0x7e1402fa, 0xff015004, 0x7e1602fa, 0xff055504, 0x7e1802fa, 0xff095c04, 0x7e1a02fa, + 0xff0d5804, 0x7e1c02fa, 0x5a095f04, 0x7e1e02fa, 0xff016104, 0x7e2002fa, 0xff056604, 0x7e2202fa, + 0xff096f04, 0x7e2402fa, 0xa5016a04, 0x4a260afa, 0xff015304, 0x4a280afa, 0xff095c04, 0x4a2a0afa, + 0x69055404, 0x4a2c0afa, 0xff016404, 0x4c2e0afa, 0xff0d6d04, 0x20300efa, 0xff095406, 0x20320efa, + 0xff056906, 0xbefe04c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, + 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x6ecd1cd2u, 0x4bef5ab2u, 0xc1f00000u, 0xc0c00000u}, + {0x6a003be9u, 0xe12916b9u, 0x422a0000u, 0x41fc0000u}, + {0xe8ace817u, 0x43f615feu, 0x42600000u, 0xc21e0000u}, + {0x4e3aa244u, 0x2d47d909u, 0x42540000u, 0xc2220000u}, + {0x68060c75u, 0xd69d14d5u, 0x41940000u, 0xc1d40000u}, + {0x5af9fb5au, 0x54393673u, 0x42440000u, 0xc1080000u}, + {0xad52f58fu, 0x3c3ff249u, 0x3fc00000u, 0xc26c0000u}, + {0xcb826265u, 0xd06f4fc2u, 0x425c0000u, 0xc2420000u}, + {0xc99d74ceu, 0xcb3eacf5u, 0x41ac0000u, 0x42320000u}, + {0x9c7fb5dbu, 0xa5e8a4bau, 0xc2340000u, 0xc24a0000u}, + {0x1e7cc79eu, 0x3197b5a3u, 0x41c00000u, 0x42420000u}, + {0x099b18b9u, 0xc7d8a2a5u, 0xc26a0000u, 0xc1980000u}, + {0x1504efdfu, 0x7e04a9d5u, 0xc2520000u, 0x422a0000u}, + {0xf200add6u, 0xd20e47a3u, 0xc1680000u, 0xc1380000u}, + {0xe31908c0u, 0x60de8b85u, 0xc20c0000u, 0x421a0000u}, + {0xf1c81bb0u, 0xa39b139du, 0x40000000u, 0xc2300000u}, + {0xdd7dd010u, 0x93d28ae8u, 0xc1f80000u, 0xc2460000u}, + {0xea239e60u, 0x1136bab0u, 0xc0900000u, 0xc1840000u}, + {0x4a248cbdu, 0x2cde59ccu, 0xc1380000u, 0xc1c40000u}, + {0x06734af7u, 0xfc5d75e7u, 0x40b00000u, 0xc2200000u}, + {0x76646c94u, 0x33cb3f0cu, 0xc25e0000u, 0xc2080000u}, + {0xc3f02221u, 0xba35f124u, 0xc2600000u, 0x424e0000u}, + {0xb955e2e5u, 0xb0a01d78u, 0xc1a40000u, 0x41300000u}, + {0xad47bf7du, 0x7e37ee41u, 0xc2380000u, 0x41e80000u}, + {0x4467b3a9u, 0xc243faa9u, 0x42400000u, 0x42280000u}, + {0xee39a32fu, 0x5d27404bu, 0xc1e00000u, 0xc2020000u}, + {0x362b62e4u, 0x40e07905u, 0x425e0000u, 0xc2640000u}, + {0x3597987bu, 0x9f2dda53u, 0xc1280000u, 0xc0200000u}, + {0x8cdfd404u, 0xaa825860u, 0x42700000u, 0x41940000u}, + {0x2883e9fdu, 0x6fcb4509u, 0xc1bc0000u, 0xc2700000u}, + {0xa64c74f6u, 0x6c225e76u, 0x41c40000u, 0xc22a0000u}, + {0xc9c4946du, 0x27f8b552u, 0x41300000u, 0xbf000000u}, + {0xec1dd6b9u, 0x37eae67cu, 0x42300000u, 0xc1fc0000u}, + {0x080a718au, 0xdd3a87b9u, 0xc1480000u, 0x426c0000u}, + {0x4d7d2f79u, 0x242b5b96u, 0xc1b80000u, 0xc25e0000u}, + {0xda0d5bd5u, 0xbd1eab24u, 0x41f40000u, 0xc2060000u}, + {0x30aa4ea5u, 0x908d264eu, 0xc0800000u, 0xc0c00000u}, + {0x4d4b8ec9u, 0x59f9bbd9u, 0xc1c40000u, 0x40c00000u}, + {0x008d7334u, 0x81e7a48bu, 0x3f800000u, 0x423a0000u}, + {0xa985a1d6u, 0x6454c697u, 0xc2300000u, 0xc22a0000u}, + {0x6945a98eu, 0x56efdfffu, 0x425e0000u, 0x41200000u}, + {0x543a17e0u, 0x9680cbfau, 0xc2360000u, 0xc20e0000u}, + {0x0e1e5311u, 0x8dbd6564u, 0xc2480000u, 0xc24c0000u}, + {0xd6c814a2u, 0x7f2c1289u, 0xc0b00000u, 0x41e00000u}, + {0x248db44au, 0xc60fd986u, 0x42400000u, 0xc2160000u}, + {0xd6c5e906u, 0x10117cecu, 0xc1f80000u, 0x41a80000u}, + {0x324909c5u, 0xdcd7b8c2u, 0x42080000u, 0x42220000u}, + {0xf995627fu, 0xc6e555f6u, 0xc2260000u, 0xc1800000u}, + {0x735c003eu, 0xc54e7632u, 0x41900000u, 0xc25a0000u}, + {0xfc3effbbu, 0x46c20a66u, 0xc1ec0000u, 0xc20a0000u}, + {0x0986feb5u, 0x040b21dfu, 0xc2180000u, 0x3f800000u}, + {0xacca236du, 0x7cfef2f2u, 0x40f00000u, 0x41cc0000u}, + {0x00bd61d8u, 0x5654123eu, 0x41400000u, 0xc1100000u}, + {0xe319a18bu, 0x5d8bc32du, 0x41100000u, 0xc2480000u}, + {0x6583147du, 0x72ce7a51u, 0x41780000u, 0x426e0000u}, + {0x49981da9u, 0x9b4ba109u, 0xc1a40000u, 0xc1e00000u}, + {0xe341cf98u, 0x3ec5433cu, 0xc0000000u, 0xc23c0000u}, + {0xc861e665u, 0xda64e2ddu, 0xc1f00000u, 0x41900000u}, + {0x8c31f3d4u, 0x807eed38u, 0xc1300000u, 0xc24e0000u}, + {0xf5411da7u, 0x6886f7e2u, 0xc1580000u, 0x420e0000u}, + {0x56aecd4cu, 0x7cfe2052u, 0xc23a0000u, 0x423a0000u}, + {0x29fc3f6du, 0x843d8b71u, 0x41d40000u, 0xbfc00000u}, + {0xf14c9634u, 0x6de904e9u, 0xc1c40000u, 0xc1180000u}, + {0x0524b659u, 0xb07d24c5u, 0xc22e0000u, 0xc23a0000u} +}; + +constexpr std::uint32_t Expected32[32][16] = { + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6a003be9u, 0xad52f58fu, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x4bef5ab2u, 0xdeaddeadu, 0xb3f56727u, 0xa6115324u, 0x41940000u, 0xc0c00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6ecd1cd2u, 0xcb826265u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0xe12916b9u, 0xdeaddeadu, 0x3c231213u, 0x33dbd926u, 0x41fc0000u, 0x41fc0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x5af9fb5au, 0xe8ace817u, 0x099b18b9u, 0xdeaddeadu, 0xdeaddeadu, 0xd69d14d5u, 0xdeaddeadu, 0x456a31a7u, 0xc5e2a106u, 0x41940000u, 0xc1680000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x68060c75u, 0x4e3aa244u, 0x1e7cc79eu, 0xdeaddeadu, 0xdeaddeadu, 0x54393673u, 0xdeaddeadu, 0xbe39725cu, 0x75643e5bu, 0x41940000u, 0xc1080000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xcb826265u, 0x6ecd1cd2u, 0x9c7fb5dbu, 0xdeaddeadu, 0xdeaddeadu, 0x3c3ff249u, 0xdeaddeadu, 0xdeaddeadu, 0xcd5b2670u, 0x41940000u, 0x40000000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xad52f58fu, 0x6a003be9u, 0xc99d74ceu, 0xdeaddeadu, 0xdeaddeadu, 0xd06f4fc2u, 0xdeaddeadu, 0xdeaddeadu, 0x4e0d77dcu, 0x41940000u, 0xc20c0000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x9c7fb5dbu, 0xe31908c0u, 0xcb826265u, 0xdeaddeadu, 0xdeaddeadu, 0xcb3eacf5u, 0xdeaddeadu, 0xdeaddeadu, 0x8fbb4e65u, 0x42320000u, 0x42320000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0xc99d74ceu, 0xf1c81bb0u, 0xad52f58fu, 0xdeaddeadu, 0xdeaddeadu, 0xa5e8a4bau, 0xdeaddeadu, 0xdeaddeadu, 0xc21d67bbu, 0x41940000u, 0xc1f00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x099b18b9u, 0x1504efdfu, 0x5af9fb5au, 0xdeaddeadu, 0xdeaddeadu, 0x3197b5a3u, 0xdeaddeadu, 0xdeaddeadu, 0x99eaacc2u, 0x42420000u, 0x42540000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x1e7cc79eu, 0xf200add6u, 0x68060c75u, 0xdeaddeadu, 0xdeaddeadu, 0xc7d8a2a5u, 0xdeaddeadu, 0xdeaddeadu, 0xe57a52eau, 0x41940000u, 0x42600000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x06734af7u, 0x76646c94u, 0x2883e9fdu, 0x4467b3a9u, 0x3351a4c3u, 0xb9be2dd0u, 0xa342c660u, 0xe6343cb1u, 0x9ce63aa1u, 0x00000000u, 0xc1280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4a248cbdu, 0xc3f02221u, 0x8cdfd404u, 0xee39a32fu, 0x02d0c0deu, 0x893d49ebu, 0x72c1e27bu, 0xa9a53564u, 0xa9eeff0fu, 0x00000000u, 0x425e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xad47bf7du, 0xdd7dd010u, 0xee39a32fu, 0xdeaddeadu, 0xb713686fu, 0x3d7ff17cu, 0xdeaddeadu, 0xfac4aa35u, 0x84f77b03u, 0x41300000u, 0x41300000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xb955e2e5u, 0xea239e60u, 0x4467b3a9u, 0xdeaddeadu, 0x84ab3938u, 0x0b17c245u, 0xdeaddeadu, 0x84ab3938u, 0xb7f374a3u, 0x41e80000u, 0x41e80000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xee39a32fu, 0xa64c74f6u, 0xad47bf7du, 0x4a248cbdu, 0xc8b745a0u, 0x4f23ceadu, 0xdeaddeadu, 0x4f23ceadu, 0x01ac2778u, 0x42280000u, 0x42280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4467b3a9u, 0xc9c4946du, 0xb955e2e5u, 0x06734af7u, 0x639a8b42u, 0xea07144fu, 0xdeaddeadu, 0x85ab2a48u, 0x193d2c49u, 0x00000000u, 0xc1f80000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x2883e9fdu, 0x362b62e4u, 0x06734af7u, 0xdeaddeadu, 0xb0f5a357u, 0x37622c64u, 0x20e6c4f4u, 0xeeea0c09u, 0x3fa14600u, 0x41940000u, 0x41940000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x8cdfd404u, 0x3597987bu, 0x4a248cbdu, 0xdeaddeadu, 0x763e9000u, 0xfcab190du, 0xe62fb19du, 0x5e04e838u, 0x6db28b07u, 0x00000000u, 0xc25e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +constexpr std::uint32_t Expected64[64][16] = { + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6a003be9u, 0xad52f58fu, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x4bef5ab2u, 0xdeaddeadu, 0xb3f56727u, 0xa6115324u, 0x41940000u, 0xc0c00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x6ecd1cd2u, 0xcb826265u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0xe12916b9u, 0xdeaddeadu, 0x3c231213u, 0x33dbd926u, 0x41fc0000u, 0x41fc0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x5af9fb5au, 0xe8ace817u, 0x099b18b9u, 0xdeaddeadu, 0xdeaddeadu, 0xd69d14d5u, 0xdeaddeadu, 0x456a31a7u, 0xc5e2a106u, 0x41940000u, 0xc1680000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0x68060c75u, 0x4e3aa244u, 0x1e7cc79eu, 0xdeaddeadu, 0xdeaddeadu, 0x54393673u, 0xdeaddeadu, 0xbe39725cu, 0x75643e5bu, 0x41940000u, 0xc1080000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xcb826265u, 0x6ecd1cd2u, 0x9c7fb5dbu, 0xdeaddeadu, 0xdeaddeadu, 0x3c3ff249u, 0xdeaddeadu, 0xdeaddeadu, 0xcd5b2670u, 0x41940000u, 0x40000000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0x00000000u, 0xad52f58fu, 0x6a003be9u, 0xc99d74ceu, 0xdeaddeadu, 0xdeaddeadu, 0xd06f4fc2u, 0xdeaddeadu, 0xdeaddeadu, 0x4e0d77dcu, 0x41940000u, 0xc20c0000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x9c7fb5dbu, 0xe31908c0u, 0xcb826265u, 0xdeaddeadu, 0xdeaddeadu, 0xcb3eacf5u, 0xdeaddeadu, 0xdeaddeadu, 0x8fbb4e65u, 0x42320000u, 0x42320000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0xc99d74ceu, 0xf1c81bb0u, 0xad52f58fu, 0xdeaddeadu, 0xdeaddeadu, 0xa5e8a4bau, 0xdeaddeadu, 0xdeaddeadu, 0xc21d67bbu, 0x41940000u, 0xc1f00000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x099b18b9u, 0x1504efdfu, 0x5af9fb5au, 0xdeaddeadu, 0xdeaddeadu, 0x3197b5a3u, 0xdeaddeadu, 0xdeaddeadu, 0x99eaacc2u, 0x42420000u, 0x42540000u}, + {0x6ecd1cd2u, 0x5af9fb5au, 0x00000000u, 0xc99d74ceu, 0xdeaddeadu, 0x1e7cc79eu, 0xf200add6u, 0x68060c75u, 0xdeaddeadu, 0xdeaddeadu, 0xc7d8a2a5u, 0xdeaddeadu, 0xdeaddeadu, 0xe57a52eau, 0x41940000u, 0x42600000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x06734af7u, 0x76646c94u, 0x2883e9fdu, 0x4467b3a9u, 0x3351a4c3u, 0xb9be2dd0u, 0xa342c660u, 0xe6343cb1u, 0x9ce63aa1u, 0x00000000u, 0xc1280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4a248cbdu, 0xc3f02221u, 0x8cdfd404u, 0xee39a32fu, 0x02d0c0deu, 0x893d49ebu, 0x72c1e27bu, 0xa9a53564u, 0xa9eeff0fu, 0x00000000u, 0x425e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xad47bf7du, 0xdd7dd010u, 0xee39a32fu, 0xdeaddeadu, 0xb713686fu, 0x3d7ff17cu, 0xdeaddeadu, 0xfac4aa35u, 0x84f77b03u, 0x41300000u, 0x41300000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xb955e2e5u, 0xea239e60u, 0x4467b3a9u, 0xdeaddeadu, 0x84ab3938u, 0x0b17c245u, 0xdeaddeadu, 0x84ab3938u, 0xb7f374a3u, 0x41e80000u, 0x41e80000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0xee39a32fu, 0xa64c74f6u, 0xad47bf7du, 0x4a248cbdu, 0xc8b745a0u, 0x4f23ceadu, 0xdeaddeadu, 0x4f23ceadu, 0x01ac2778u, 0x42280000u, 0x42280000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x4467b3a9u, 0xc9c4946du, 0xb955e2e5u, 0x06734af7u, 0x639a8b42u, 0xea07144fu, 0xdeaddeadu, 0x85ab2a48u, 0x193d2c49u, 0x00000000u, 0xc1f80000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x2883e9fdu, 0x362b62e4u, 0x06734af7u, 0xdeaddeadu, 0xb0f5a357u, 0x37622c64u, 0x20e6c4f4u, 0xeeea0c09u, 0x3fa14600u, 0x41940000u, 0x41940000u}, + {0xdeaddeadu, 0xc3f02221u, 0x8cdfd404u, 0x4467b3a9u, 0xdeaddeadu, 0x8cdfd404u, 0x3597987bu, 0x4a248cbdu, 0xdeaddeadu, 0x763e9000u, 0xfcab190du, 0xe62fb19du, 0x5e04e838u, 0x6db28b07u, 0x00000000u, 0xc25e0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0x080a718au, 0x008d7334u, 0xf995627fu, 0xdeaddeadu, 0x11f84251u, 0x5c789ac6u, 0x68953521u, 0xdeaddeadu, 0x9edb028au, 0x00000000u, 0xc1fc0000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xec1dd6b9u, 0xa985a1d6u, 0x00000000u, 0xdeaddeadu, 0xb747e38eu, 0x01c83c03u, 0x0de4d65eu, 0xdeaddeadu, 0x47532c91u, 0x426c0000u, 0x426c0000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xda0d5bd5u, 0x30aa4ea5u, 0x00000000u, 0xdeaddeadu, 0xfe38b76bu, 0x48b90fe0u, 0x54d5aa3bu, 0xdeaddeadu, 0xd56a06e9u, 0x00000000u, 0xc0b00000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0x4d7d2f79u, 0x4d4b8ec9u, 0x248db44au, 0xdeaddeadu, 0x972c06f9u, 0xe1ac5f6eu, 0xedc8f9c9u, 0xdeaddeadu, 0x752a5ea1u, 0x00000000u, 0xc2060000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xdeaddeadu, 0xf995627fu, 0x00000000u, 0xdeaddeadu, 0x708e27cfu, 0xbb0e8044u, 0xdeaddeadu, 0xdeaddeadu, 0x9a2982abu, 0x00000000u, 0x42300000u}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xdeaddeadu, 0xdeaddeadu, 0x248db44au, 0x00000000u, 0xdeaddeadu, 0x67cac139u, 0xb24b19aeu, 0xdeaddeadu, 0xdeaddeadu, 0x1bc83c72u, 0x00000000u, 0x41f40000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xf995627fu, 0xdeaddeadu, 0x0e1e5311u, 0xda0d5bd5u, 0xdeaddeadu, 0xa01d355bu, 0xea9d8dd0u, 0xf6ba282bu, 0xdeaddeadu, 0x41fa9804u, 0x00000000u, 0xc1c40000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec1dd6b9u, 0x4d4b8ec9u, 0x248db44au, 0x6945a98eu, 0xf995627fu, 0xdeaddeadu, 0x543a17e0u, 0xec1dd6b9u, 0xdeaddeadu, 0xa0f2b1cbu, 0xeb730a40u, 0xf78fa49bu, 0xdeaddeadu, 0x8697d983u, 0x00000000u, 0x3f800000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0x6583147du, 0x0524b659u, 0xdeaddeadu, 0xdeaddeadu, 0xc54e7632u, 0xdeaddeadu, 0xdeaddeadu, 0x64adc93bu, 0x00000000u, 0xc1f00000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0x00bd61d8u, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0x040b21dfu, 0xdeaddeadu, 0xdeaddeadu, 0x0119947au, 0x3f800000u, 0x3f800000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0xacca236du, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x5d8bc32du, 0xdeaddeadu, 0xdeaddeadu, 0x85b60c6bu, 0x00000000u, 0xc23a0000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0xfc3effbbu, 0xe341cf98u, 0xdeaddeadu, 0xdeaddeadu, 0x9b4ba109u, 0xdeaddeadu, 0xdeaddeadu, 0xf0e652cbu, 0x00000000u, 0xc1c40000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xc861e665u, 0xf14c9634u, 0x49981da9u, 0x0986feb5u, 0xdeaddeadu, 0x3ec5433cu, 0xdeaddeadu, 0xdeaddeadu, 0xa4545e4fu, 0x00000000u, 0xc1ec0000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xe341cf98u, 0x0524b659u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0xda64e2ddu, 0xdeaddeadu, 0xdeaddeadu, 0x26587efbu, 0x41900000u, 0x41900000u}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0x0524b659u, 0xe341cf98u, 0x00000000u, 0xdeaddeadu, 0xdeaddeadu, 0x6de904e9u, 0xdeaddeadu, 0xdeaddeadu, 0x3ee11e84u, 0x00000000u, 0xc1180000u}, + {0x735c003eu, 0xe319a18bu, 0x00000000u, 0xe341cf98u, 0xdeaddeadu, 0xf14c9634u, 0xc861e665u, 0x735c003eu, 0xdeaddeadu, 0xdeaddeadu, 0xb07d24c5u, 0xdeaddeadu, 0xdeaddeadu, 0x5909d9f0u, 0x00000000u, 0x41780000u} +}; + +constexpr const char* Names[Checked] = {"mov_row_share0", "mov_row_share5_fi", "mov_row_share12_bc", "mov_row_share8_bc_fi", "mov_row_share15_masks", "mov_row_xmask1", "mov_row_xmask6_fi", "mov_row_xmask15_bc", "mov_row_xmask10_masks", "add_row_share3", "add_row_share12_bc", "add_row_share4_fi_masks", "add_row_xmask4", "sub_row_xmask13_bc_fi", "max_f32_row_share4_bc", "max_f32_row_xmask9_fi"}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked]) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "dpp16 row share xmask wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64); + std::puts("dpp16 row share xmask tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Dpp8.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp8.cpp new file mode 100644 index 000000000..61d69c415 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Dpp8.cpp @@ -0,0 +1,285 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 12; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e1402e9, 0x78afc504, 0x7e1602ea, + 0x05397704, 0x4a180ae9, 0x00024904, 0x061a0ee9, 0x97705306, 0x7e1c0306, 0x561c0ee9, 0xa5602406, + 0x7d880b04, 0x021e0ae9, 0x78afc504, 0x7e200ae9, 0x97705304, 0x50220ae9, 0x05397704, 0x7e1202ff, + 0x12345678, 0xbefe03ff, 0x5a5af0f3, 0x7e2402e9, 0x78afc504, 0x7e2602ea, 0x78afc504, 0x4c280ae9, + 0xa5602404, 0x7e2a02e9, 0x05397709, 0xbefe03c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, + 0xe0781020, 0x80011203, 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e1402e9, 0x78afc504, 0x7e1602ea, + 0x05397704, 0x4a180ae9, 0x00024904, 0x061a0ee9, 0x97705306, 0x7e1c0306, 0x561c0ee9, 0xa5602406, + 0x7d880b04, 0x021e0ae9, 0x78afc504, 0x7e200ae9, 0x97705304, 0x50220ae9, 0x05397704, 0x7e1202ff, + 0x12345678, 0xbefe03ff, 0x5a5af0f3, 0xbeff03ff, 0xc3a5960f, 0x7e2402e9, 0x78afc504, 0x7e2602ea, + 0x78afc504, 0x4c280ae9, 0xa5602404, 0x7e2a02e9, 0x05397709, 0xbefe04c1, 0xe0781000, 0x80010a03, + 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x1e7ea419u, 0x51c9bc70u, 0x42800000u, 0xc2180000u}, + {0xe255accbu, 0x39292d22u, 0x422c0000u, 0x42740000u}, + {0xc0433cbdu, 0xc5e818fau, 0x42400000u, 0xc0400000u}, + {0x00a61f93u, 0x9d3c7decu, 0xc2300000u, 0xc2100000u}, + {0x49889310u, 0xd11745adu, 0xc21c0000u, 0x424c0000u}, + {0x02f0ee99u, 0xd0944996u, 0x42740000u, 0x41800000u}, + {0x35d14880u, 0x65b00a2du, 0x00000000u, 0x41c80000u}, + {0xfadb8908u, 0xffb88309u, 0x41d80000u, 0x42000000u}, + {0xffe976abu, 0xbef7e268u, 0xc2340000u, 0x41b80000u}, + {0x16f44881u, 0x8eb5140fu, 0x41200000u, 0x41100000u}, + {0x751b4c83u, 0x24114258u, 0x41700000u, 0xc2680000u}, + {0xd9090b89u, 0xb5fb12e0u, 0x41f00000u, 0x41e80000u}, + {0x7608d942u, 0x6c167229u, 0xc2240000u, 0x42180000u}, + {0xe41686cdu, 0x95f19a51u, 0x427c0000u, 0xc20c0000u}, + {0x6ce9da66u, 0x81a0ba05u, 0x42740000u, 0x42140000u}, + {0xeab06e9bu, 0x864fa3f3u, 0x40000000u, 0x422c0000u}, + {0x90624fe3u, 0xed1d9d16u, 0x42680000u, 0xc2680000u}, + {0x93550840u, 0x397a7623u, 0xc1f80000u, 0xc2500000u}, + {0xf675299bu, 0xb464c554u, 0xc2180000u, 0x42340000u}, + {0x78549014u, 0x2475263cu, 0x426c0000u, 0xc0c00000u}, + {0xd9814559u, 0xe2cbbb93u, 0xc2040000u, 0x42500000u}, + {0xac9f21dfu, 0x782afe6bu, 0x00000000u, 0x42180000u}, + {0xab74fe57u, 0xff8b2a6au, 0x41600000u, 0x42800000u}, + {0xb2c55523u, 0x7a98b9acu, 0x42180000u, 0x42380000u}, + {0x33d91808u, 0x4b5c1a95u, 0x40800000u, 0xc2780000u}, + {0x0879d955u, 0x265c28eau, 0x42540000u, 0x42740000u}, + {0x5c24c1aeu, 0xebe80fa9u, 0xc1500000u, 0x40000000u}, + {0xb1515fffu, 0x7c4e4248u, 0x425c0000u, 0x427c0000u}, + {0xced66b44u, 0x6f29a9f1u, 0xc2600000u, 0x41200000u}, + {0x511070a7u, 0x7528ea79u, 0xc21c0000u, 0x42400000u}, + {0xb07822dbu, 0xb76dc0c3u, 0x40000000u, 0x424c0000u}, + {0xec0643b1u, 0xf71bd135u, 0xc1900000u, 0x42300000u}, + {0xd7aa2208u, 0xba5c875cu, 0x42680000u, 0xc2400000u}, + {0xa60741f3u, 0x44371ff3u, 0x427c0000u, 0x41f00000u}, + {0x18bee074u, 0x31d06d9cu, 0x42240000u, 0xc1d80000u}, + {0x730b89dcu, 0xea9e7ab5u, 0xc2740000u, 0xc2240000u}, + {0x000e05d0u, 0x7383d496u, 0xc22c0000u, 0xc1600000u}, + {0x3f724926u, 0x1eedfcfcu, 0xc2540000u, 0x40800000u}, + {0xab215122u, 0xc1f6192bu, 0x41d80000u, 0xc1c80000u}, + {0x01504585u, 0x503dc86bu, 0x42740000u, 0xc1f00000u}, + {0x8bb9c912u, 0x3911c9b0u, 0x42640000u, 0xc1f80000u}, + {0xf551abfdu, 0x991f77c9u, 0x41200000u, 0xc22c0000u}, + {0x6af38f6cu, 0xab266981u, 0xc1c00000u, 0xc1f00000u}, + {0xc88f63d6u, 0xc84c1884u, 0x41600000u, 0x42240000u}, + {0x63f1bbbcu, 0xced8c284u, 0xc1d00000u, 0xc1600000u}, + {0x6041f10eu, 0x039e257au, 0xc2140000u, 0x420c0000u}, + {0xe344f1c3u, 0x12cc86e3u, 0xc24c0000u, 0x41c00000u}, + {0xa5721c6au, 0x5a789b32u, 0xc2680000u, 0xc2680000u}, + {0x82a8c712u, 0x79dcbc81u, 0x41980000u, 0x41500000u}, + {0x733c6676u, 0x5377899au, 0xc2040000u, 0x427c0000u}, + {0xf2e36355u, 0x7771f894u, 0xc1a80000u, 0xc1c80000u}, + {0x975cef7au, 0x850567c2u, 0xc1400000u, 0x423c0000u}, + {0x9f93693fu, 0x561f8bfeu, 0x42540000u, 0xc0a00000u}, + {0xadb21095u, 0xb5fc684au, 0xc23c0000u, 0x42180000u}, + {0x08be3b07u, 0x320be3f8u, 0xc1a00000u, 0xc2540000u}, + {0xa5f27419u, 0x8b75d02au, 0xc2500000u, 0x424c0000u}, + {0xde3b7dd3u, 0xc134a79bu, 0xc1800000u, 0x426c0000u}, + {0xb600860cu, 0xadbafed0u, 0xc26c0000u, 0xc1f80000u}, + {0xa5497863u, 0x87a9ec13u, 0xc1100000u, 0xc1e80000u}, + {0x290f5d0fu, 0xa945e7bbu, 0xc1880000u, 0x420c0000u}, + {0x45d6ac42u, 0x6ce4048fu, 0x42280000u, 0xc2300000u}, + {0x62186f4bu, 0x4e0e8896u, 0xc1880000u, 0x41c00000u}, + {0x2cb8cefcu, 0x43710bafu, 0x42580000u, 0x427c0000u}, + {0xffce6973u, 0xc7be5613u, 0x42280000u, 0x41100000u} +}; + +constexpr std::uint32_t Expected32[32][12] = { + {0x02f0ee99u, 0xfadb8908u, 0x341f693bu, 0xc2a40000u, 0x44c14000u, 0x02f0ee99u, 0x4b261f93u, 0x4ca54578u, 0x02f0ee99u, 0x02f0ee99u, 0xf7bed6a0u, 0x12345678u}, + {0x1e7ea419u, 0x35d14880u, 0x1b7ed9edu, 0x42da0000u, 0xc5120000u, 0x39292d22u, 0xce7ef30du, 0x6efa75a3u, 0x1e7ea419u, 0x1e7ea419u, 0x105f65eeu, 0x12345678u}, + {0xfadb8908u, 0x02f0ee99u, 0xa83dc5c5u, 0x42200000u, 0xc3100000u, 0xfadb8908u, 0xcded529au, 0xc8d90793u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xfadb8908u, 0x49889310u, 0x7f922ab7u, 0x41e00000u, 0xc512c000u, 0xfadb8908u, 0x4df3f521u, 0xe6c510fcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc0433cbdu, 0x00a61f93u, 0xef95e9c6u, 0x429c0000u, 0xc21c0000u, 0xc0433cbdu, 0xcca48edfu, 0xd1bd6540u, 0x00000000u, 0xc0433cbdu, 0x64ba02d3u, 0x00000000u}, + {0xe255accbu, 0xc0433cbdu, 0xef12edafu, 0x41800000u, 0x444f4000u, 0xe255accbu, 0x4e574522u, 0x90d78653u, 0xe255accbu, 0xe255accbu, 0x2f6bb66au, 0x00000000u}, + {0x35d14880u, 0xe255accbu, 0x842eae46u, 0x42ac0000u, 0x44866000u, 0x35d14880u, 0x4c3c3ba6u, 0x4805b6f8u, 0x35d14880u, 0x35d14880u, 0x7ca5a29eu, 0x12345678u}, + {0x00a61f93u, 0x1e7ea419u, 0x1e372722u, 0xc0e00000u, 0x44f76000u, 0x00a61f93u, 0x4e931126u, 0x1e372722u, 0x00000000u, 0x00a61f93u, 0x03386b90u, 0x12345678u}, + {0xe41686cdu, 0xeab06e9bu, 0xd5ec2ae9u, 0x42540000u, 0xc4770000u, 0xbef7e268u, 0xce1bdbd2u, 0xa9a85104u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xffe976abu, 0x6ce9da66u, 0xa5a95c90u, 0x41c00000u, 0xc3b38000u, 0xffe976abu, 0x4eea3699u, 0xfb9eee75u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xeab06e9bu, 0xe41686cdu, 0x3b058ad9u, 0xc2400000u, 0x45241000u, 0x24114258u, 0x4db7a244u, 0x0827c926u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xeab06e9bu, 0x7608d942u, 0xccef5b61u, 0xc1800000u, 0xc49f6000u, 0xb5fb12e0u, 0xc9b44aa8u, 0x2c03ec23u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x751b4c83u, 0xd9090b89u, 0x6bffe8d4u, 0x42200000u, 0x450e5000u, 0x6c167229u, 0xcdaa7c8bu, 0x451f7db3u, 0x00000000u, 0x751b4c83u, 0x00d3683du, 0x00000000u}, + {0x16f44881u, 0x751b4c83u, 0x95db10fcu, 0x41d00000u, 0xc3e70000u, 0x95f19a51u, 0x4ed9d3b5u, 0x0b0ce6d5u, 0x00000000u, 0x16f44881u, 0x6a0e65afu, 0x00000000u}, + {0x6ce9da66u, 0x16f44881u, 0x818a30b0u, 0x42c80000u, 0x43d78000u, 0x6ce9da66u, 0xcddf4bcau, 0x98950286u, 0x6ce9da66u, 0x6ce9da66u, 0x7e5f45fbu, 0x00000000u}, + {0xd9090b89u, 0xffe976abu, 0x86391a9eu, 0x40000000u, 0x45297000u, 0x864fa3f3u, 0x4eec11b3u, 0x86391a9fu, 0x00000000u, 0xd9090b89u, 0x5dc6e2dau, 0x00000000u}, + {0xac9f21dfu, 0xb2c55523u, 0x8072a556u, 0x3f800000u, 0x44f68000u, 0xac9f21dfu, 0x4ef0a920u, 0x9fe2f239u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x90624fe3u, 0xab74fe57u, 0xcccf7e63u, 0xc2b40000u, 0x44d2a000u, 0x397a7623u, 0xcd18ad66u, 0xe4ef747bu, 0x00000000u, 0x90624fe3u, 0xa006cf36u, 0x12345678u}, + {0xb2c55523u, 0xac9f21dfu, 0x47b9cd94u, 0x41600000u, 0x4520c000u, 0xb464c554u, 0xced955f0u, 0x6103e734u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb2c55523u, 0xd9814559u, 0xb7ca2e7cu, 0x42500000u, 0xc3908000u, 0x2475263cu, 0xcedf3b60u, 0xfdf66b96u, 0x00000000u, 0xb2c55523u, 0xdb8ad9c4u, 0x12345678u}, + {0xf675299bu, 0x78549014u, 0x732e0b76u, 0x42b40000u, 0x442dc000u, 0xf675299bu, 0xce9a7556u, 0x5b204ba7u, 0x00000000u, 0xf675299bu, 0xc8a942c4u, 0x12345678u}, + {0x93550840u, 0xf675299bu, 0x088d4e4eu, 0x42500000u, 0xc4b48000u, 0x782afe6bu, 0xcea91603u, 0x6ea02807u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xab74fe57u, 0x93550840u, 0x8fed7a4du, 0x42800000u, 0xc4f64000u, 0xab74fe57u, 0xcea6c1bcu, 0x92e032aau, 0xab74fe57u, 0xab74fe57u, 0x93c9ddd6u, 0x12345678u}, + {0x78549014u, 0x90624fe3u, 0x0afb098fu, 0x41500000u, 0x42180000u, 0x7a98b9acu, 0xce19faebu, 0x0afb0990u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x511070a7u, 0xec0643b1u, 0x53d5f3eau, 0xc0e00000u, 0x45594000u, 0x511070a7u, 0xce9d5d40u, 0x37625e46u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x33d91808u, 0xb07822dbu, 0x2ed6023fu, 0x42400000u, 0xc5523000u, 0x33d91808u, 0x4eb84983u, 0xd6d44bc5u, 0x00000000u, 0x33d91808u, 0xa87a425au, 0x12345678u}, + {0xec0643b1u, 0x511070a7u, 0xf461e8feu, 0x425c0000u, 0xc0a00000u, 0xec0643b1u, 0x4d079d95u, 0x3cf88050u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec0643b1u, 0xced66b44u, 0x84c81b9du, 0x42860000u, 0x43998000u, 0x7c4e4248u, 0x4e4f6460u, 0x4b24ad8du, 0x00000000u, 0xec0643b1u, 0x83b1bdb8u, 0x12345678u}, + {0x5c24c1aeu, 0xb1515fffu, 0xa302c1f9u, 0xc1000000u, 0xc2100000u, 0x6f29a9f1u, 0xcd9fcde2u, 0x207b09f1u, 0x00000000u, 0x5c24c1aeu, 0x414e78eau, 0x12345678u}, + {0x0879d955u, 0x5c24c1aeu, 0xa9020281u, 0x42480000u, 0xc425c000u, 0x0879d955u, 0xce9f0fbau, 0xd14dac27u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb07822dbu, 0x0879d955u, 0xeb46d8cbu, 0x41400000u, 0x45291000u, 0xb07822dbu, 0x4ea220e1u, 0xbfe79a18u, 0xb07822dbu, 0xb07822dbu, 0x510c1892u, 0x12345678u}, + {0xb1515fffu, 0x33d91808u, 0x2af4e93du, 0xc1400000u, 0xc4d8c000u, 0xb1515fffu, 0xce44a653u, 0x2af4e93du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +constexpr std::uint32_t Expected64[64][12] = { + {0x02f0ee99u, 0xfadb8908u, 0x341f693bu, 0xc2a40000u, 0x44c14000u, 0x02f0ee99u, 0x4b261f93u, 0x4ca54578u, 0x02f0ee99u, 0x02f0ee99u, 0xf7bed6a0u, 0x12345678u}, + {0x1e7ea419u, 0x35d14880u, 0x1b7ed9edu, 0x42da0000u, 0xc5120000u, 0x39292d22u, 0xce7ef30du, 0x6efa75a3u, 0x1e7ea419u, 0x1e7ea419u, 0x105f65eeu, 0x12345678u}, + {0xfadb8908u, 0x02f0ee99u, 0xa83dc5c5u, 0x42200000u, 0xc3100000u, 0xfadb8908u, 0xcded529au, 0xc8d90793u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xfadb8908u, 0x49889310u, 0x7f922ab7u, 0x41e00000u, 0xc512c000u, 0xfadb8908u, 0x4df3f521u, 0xe6c510fcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc0433cbdu, 0x00a61f93u, 0xef95e9c6u, 0x429c0000u, 0xc21c0000u, 0xc0433cbdu, 0xcca48edfu, 0xd1bd6540u, 0x00000000u, 0xc0433cbdu, 0x64ba02d3u, 0x00000000u}, + {0xe255accbu, 0xc0433cbdu, 0xef12edafu, 0x41800000u, 0x444f4000u, 0xe255accbu, 0x4e574522u, 0x90d78653u, 0xe255accbu, 0xe255accbu, 0x2f6bb66au, 0x00000000u}, + {0x35d14880u, 0xe255accbu, 0x842eae46u, 0x42ac0000u, 0x44866000u, 0x35d14880u, 0x4c3c3ba6u, 0x4805b6f8u, 0x35d14880u, 0x35d14880u, 0x7ca5a29eu, 0x12345678u}, + {0x00a61f93u, 0x1e7ea419u, 0x1e372722u, 0xc0e00000u, 0x44f76000u, 0x00a61f93u, 0x4e931126u, 0x1e372722u, 0x00000000u, 0x00a61f93u, 0x03386b90u, 0x12345678u}, + {0xe41686cdu, 0xeab06e9bu, 0xd5ec2ae9u, 0x42540000u, 0xc4770000u, 0xbef7e268u, 0xce1bdbd2u, 0xa9a85104u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xffe976abu, 0x6ce9da66u, 0xa5a95c90u, 0x41c00000u, 0xc3b38000u, 0xffe976abu, 0x4eea3699u, 0xfb9eee75u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xeab06e9bu, 0xe41686cdu, 0x3b058ad9u, 0xc2400000u, 0x45241000u, 0x24114258u, 0x4db7a244u, 0x0827c926u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xeab06e9bu, 0x7608d942u, 0xccef5b61u, 0xc1800000u, 0xc49f6000u, 0xb5fb12e0u, 0xc9b44aa8u, 0x2c03ec23u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x751b4c83u, 0xd9090b89u, 0x6bffe8d4u, 0x42200000u, 0x450e5000u, 0x6c167229u, 0xcdaa7c8bu, 0x451f7db3u, 0x00000000u, 0x751b4c83u, 0x00d3683du, 0x00000000u}, + {0x16f44881u, 0x751b4c83u, 0x95db10fcu, 0x41d00000u, 0xc3e70000u, 0x95f19a51u, 0x4ed9d3b5u, 0x0b0ce6d5u, 0x00000000u, 0x16f44881u, 0x6a0e65afu, 0x00000000u}, + {0x6ce9da66u, 0x16f44881u, 0x818a30b0u, 0x42c80000u, 0x43d78000u, 0x6ce9da66u, 0xcddf4bcau, 0x98950286u, 0x6ce9da66u, 0x6ce9da66u, 0x7e5f45fbu, 0x00000000u}, + {0xd9090b89u, 0xffe976abu, 0x86391a9eu, 0x40000000u, 0x45297000u, 0x864fa3f3u, 0x4eec11b3u, 0x86391a9fu, 0x00000000u, 0xd9090b89u, 0x5dc6e2dau, 0x00000000u}, + {0xac9f21dfu, 0xb2c55523u, 0x8072a556u, 0x3f800000u, 0x44f68000u, 0xac9f21dfu, 0x4ef0a920u, 0x9fe2f239u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x90624fe3u, 0xab74fe57u, 0xcccf7e63u, 0xc2b40000u, 0x44d2a000u, 0x397a7623u, 0xcd18ad66u, 0xe4ef747bu, 0x00000000u, 0x90624fe3u, 0xa006cf36u, 0x12345678u}, + {0xb2c55523u, 0xac9f21dfu, 0x47b9cd94u, 0x41600000u, 0x4520c000u, 0xb464c554u, 0xced955f0u, 0x6103e734u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb2c55523u, 0xd9814559u, 0xb7ca2e7cu, 0x42500000u, 0xc3908000u, 0x2475263cu, 0xcedf3b60u, 0xfdf66b96u, 0x00000000u, 0xb2c55523u, 0xdb8ad9c4u, 0x12345678u}, + {0xf675299bu, 0x78549014u, 0x732e0b76u, 0x42b40000u, 0x442dc000u, 0xf675299bu, 0xce9a7556u, 0x5b204ba7u, 0x00000000u, 0xf675299bu, 0xc8a942c4u, 0x12345678u}, + {0x93550840u, 0xf675299bu, 0x088d4e4eu, 0x42500000u, 0xc4b48000u, 0x782afe6bu, 0xcea91603u, 0x6ea02807u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xab74fe57u, 0x93550840u, 0x8fed7a4du, 0x42800000u, 0xc4f64000u, 0xab74fe57u, 0xcea6c1bcu, 0x92e032aau, 0xab74fe57u, 0xab74fe57u, 0x93c9ddd6u, 0x12345678u}, + {0x78549014u, 0x90624fe3u, 0x0afb098fu, 0x41500000u, 0x42180000u, 0x7a98b9acu, 0xce19faebu, 0x0afb0990u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x511070a7u, 0xec0643b1u, 0x53d5f3eau, 0xc0e00000u, 0x45594000u, 0x511070a7u, 0xce9d5d40u, 0x37625e46u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x33d91808u, 0xb07822dbu, 0x2ed6023fu, 0x42400000u, 0xc5523000u, 0x33d91808u, 0x4eb84983u, 0xd6d44bc5u, 0x00000000u, 0x33d91808u, 0xa87a425au, 0x12345678u}, + {0xec0643b1u, 0x511070a7u, 0xf461e8feu, 0x425c0000u, 0xc0a00000u, 0xec0643b1u, 0x4d079d95u, 0x3cf88050u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xec0643b1u, 0xced66b44u, 0x84c81b9du, 0x42860000u, 0x43998000u, 0x7c4e4248u, 0x4e4f6460u, 0x4b24ad8du, 0x00000000u, 0xec0643b1u, 0x83b1bdb8u, 0x12345678u}, + {0x5c24c1aeu, 0xb1515fffu, 0xa302c1f9u, 0xc1000000u, 0xc2100000u, 0x6f29a9f1u, 0xcd9fcde2u, 0x207b09f1u, 0x00000000u, 0x5c24c1aeu, 0x414e78eau, 0x12345678u}, + {0x0879d955u, 0x5c24c1aeu, 0xa9020281u, 0x42480000u, 0xc425c000u, 0x0879d955u, 0xce9f0fbau, 0xd14dac27u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb07822dbu, 0x0879d955u, 0xeb46d8cbu, 0x41400000u, 0x45291000u, 0xb07822dbu, 0x4ea220e1u, 0xbfe79a18u, 0xb07822dbu, 0xb07822dbu, 0x510c1892u, 0x12345678u}, + {0xb1515fffu, 0x33d91808u, 0x2af4e93du, 0xc1400000u, 0xc4d8c000u, 0xb1515fffu, 0xce44a653u, 0x2af4e93du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3f724926u, 0x01504585u, 0x6063c94fu, 0xc2da0000u, 0x4504a000u, 0xba5c875cu, 0x4ee61714u, 0xbbaccce2u, 0x00000000u, 0x3f724926u, 0x45a378a4u, 0x00000000u}, + {0xd7aa2208u, 0xab215122u, 0xea3e61e6u, 0x428e0000u, 0xc4996000u, 0x44371ff3u, 0x4dc5f704u, 0xef587116u, 0xd7aa2208u, 0xd7aa2208u, 0xbbc8e00du, 0x00000000u}, + {0x01504585u, 0x3f724926u, 0xd7d7af8fu, 0x42100000u, 0xc4bea000u, 0x01504585u, 0xceb3f17cu, 0x7142b6c2u, 0x00000000u, 0x01504585u, 0xa5d9b46cu, 0x00000000u}, + {0x01504585u, 0x000e05d0u, 0x90a5bca8u, 0x41880000u, 0xc5187000u, 0x01504585u, 0xce215778u, 0xeaac8085u, 0x00000000u, 0x01504585u, 0xed0ba753u, 0x00000000u}, + {0x18bee074u, 0x730b89dcu, 0x4b2df69eu, 0x423c0000u, 0xc3d28000u, 0x18bee074u, 0x4ba822c2u, 0xe68f5e72u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xa60741f3u, 0x18bee074u, 0xf6981f04u, 0x41f80000u, 0x42de0000u, 0x1eedfcfcu, 0xcea9bd5eu, 0x37acdd71u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xab215122u, 0xa60741f3u, 0x99a03b33u, 0xc29c0000u, 0xc4c18000u, 0xab215122u, 0x4e7dc925u, 0x67fd5b1eu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x730b89dcu, 0xd7aa2208u, 0x27e7ea73u, 0xc2920000u, 0x44ce6000u, 0x730b89dcu, 0x49605d00u, 0x27e7ea73u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6041f10eu, 0xa5721c6au, 0x2e6375adu, 0xc1880000u, 0x4457c000u, 0x3911c9b0u, 0xce5dc271u, 0xde83e61bu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8bb9c912u, 0xe344f1c3u, 0x8e7123c6u, 0xc2860000u, 0x448d0000u, 0x991f77c9u, 0x4ed5e71fu, 0x7c64698du, 0x00000000u, 0x8bb9c912u, 0xcad243f3u, 0x00000000u}, + {0xa5721c6au, 0x6041f10eu, 0xa078157eu, 0xc1a00000u, 0xc4d8c000u, 0xa5721c6au, 0xcd2ae540u, 0x0b685a8fu, 0xa5721c6au, 0xa5721c6au, 0x54d9967fu, 0x00000000u}, + {0xa5721c6au, 0x63f1bbbcu, 0xbd9dc481u, 0x42c40000u, 0x4512f000u, 0xc84c1884u, 0xcee88c6eu, 0x2c3dd441u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6af38f6cu, 0xc88f63d6u, 0x5a928b96u, 0xc2900000u, 0x442c0000u, 0x6af38f6cu, 0xceb51bc7u, 0x9768265au, 0x6af38f6cu, 0x6af38f6cu, 0x31273d7cu, 0x00000000u}, + {0xf551abfdu, 0x6af38f6cu, 0x8f57ee8cu, 0xc1800000u, 0xc45b4000u, 0x039e257au, 0xcde5d872u, 0x6e91b4e7u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xe344f1c3u, 0xf551abfdu, 0x9e864ff5u, 0xc1500000u, 0x433d0000u, 0x12cc86e3u, 0x4ec083e2u, 0x081e32e1u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc88f63d6u, 0x8bb9c912u, 0xe6326444u, 0xc2a80000u, 0x45028000u, 0x5a789b32u, 0x4ec7e377u, 0xe6326445u, 0x00000000u, 0xc88f63d6u, 0xa58764ceu, 0x00000000u}, + {0xadb21095u, 0xa5f27419u, 0xed1922f7u, 0x3f800000u, 0x44310000u, 0x79dcbc81u, 0xced14621u, 0x1fcf309bu, 0xadb21095u, 0xadb21095u, 0x8623437fu, 0x12345678u}, + {0x82a8c712u, 0x08be3b07u, 0xc6b3f010u, 0x42280000u, 0x454ea000u, 0x5377899au, 0xcd51c9cbu, 0x5c35c4a2u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xa5f27419u, 0xadb21095u, 0xeaae5f0au, 0xc2680000u, 0xc3f80000u, 0x7771f894u, 0x4ee678cdu, 0x2524092au, 0xa5f27419u, 0xa5f27419u, 0x0b36ce7eu, 0x12345678u}, + {0xa5f27419u, 0x9f93693fu, 0xf841ce38u, 0x42840000u, 0x445c4000u, 0x850567c2u, 0xcefaae72u, 0x2498d102u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xf2e36355u, 0x975cef7au, 0xd8c85310u, 0xc2640000u, 0x43190000u, 0x561f8bfeu, 0xceb41b18u, 0xed7c7b79u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x733c6676u, 0xf2e36355u, 0x38a52f5cu, 0x41900000u, 0xc4534000u, 0x733c6676u, 0x4d0be3b0u, 0xa8dfcb9fu, 0x00000000u, 0x733c6676u, 0x3ce6fb0bu, 0x12345678u}, + {0x08be3b07u, 0x733c6676u, 0xb4b4ab0au, 0xc2c80000u, 0x44d82000u, 0x08be3b07u, 0xcea49bdfu, 0xa5484a6eu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x975cef7au, 0x82a8c712u, 0x0e1e973cu, 0x42d00000u, 0xc5191000u, 0x8b75d02au, 0xcec0d92eu, 0x0e1e973du, 0x00000000u, 0x975cef7au, 0x223c406bu, 0x12345678u}, + {0x62186f4bu, 0xffce6973u, 0x77352da7u, 0x42280000u, 0x4519e000u, 0xc134a79bu, 0x4e243d74u, 0xc103110fu, 0x00000000u, 0x62186f4bu, 0x3ecb5865u, 0x12345678u}, + {0xde3b7dd3u, 0x2cb8cefcu, 0x63bb84dcu, 0xc2200000u, 0xc4aa2000u, 0xadbafed0u, 0xceb56d0fu, 0xda73cdcdu, 0xde3b7dd3u, 0xde3b7dd3u, 0x52450130u, 0x12345678u}, + {0xffce6973u, 0x62186f4bu, 0x3daa721fu, 0xc2b00000u, 0x43e38000u, 0x87a9ec13u, 0xce93fef4u, 0xe9c25b5fu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xffce6973u, 0x45d6ac42u, 0x5f466dc7u, 0x41980000u, 0xc4104000u, 0xffce6973u, 0xce071209u, 0xef1c93fdu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xa5497863u, 0x290f5d0fu, 0x4b1f8262u, 0xc0000000u, 0xc511e000u, 0xa5497863u, 0xca465a34u, 0x95f3619eu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb600860cu, 0xa5497863u, 0x2c4a0669u, 0x429c0000u, 0xc3690000u, 0x4e0e8896u, 0x4e32e33cu, 0xf35800fau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x2cb8cefcu, 0xb600860cu, 0x21ac8982u, 0x42380000u, 0xc564f000u, 0x2cb8cefcu, 0x4ec430dfu, 0xf97191bbu, 0x2cb8cefcu, 0x2cb8cefcu, 0x728f7a5du, 0x12345678u}, + {0x290f5d0fu, 0xde3b7dd3u, 0xa5f9d3e6u, 0x424c0000u, 0xc2de0000u, 0xc7be5613u, 0x4e8bad59u, 0xa5f9d3e7u, 0x00000000u, 0x290f5d0fu, 0x3841a9edu, 0x12345678u} +}; + +constexpr const char* Names[Checked] = {"mov", "mov_fi", "add_nc_u32", "add_f32", "fmac_f32", "cndmask", "cvt_f32_i32", "add_co_ci_u32", "mov_inactive", "mov_inactive_fi", "sub_nc_u32_inactive", "mov_uniform_inactive"}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked]) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "dpp8 wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64); + std::puts("dpp8 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DppNoForm.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DppNoForm.cpp new file mode 100644 index 000000000..5f94f7849 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DppNoForm.cpp @@ -0,0 +1,97 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +struct NoFormOpcode { + bool vop2; + std::uint32_t opcode; + const char* name; +}; + +constexpr std::array NoFormOpcodes{{ + {false, 0x02u, "v_readfirstlane_b32"}, {false, 0x03u, "v_cvt_i32_f64"}, {false, 0x04u, "v_cvt_f64_i32"}, + {false, 0x0fu, "v_cvt_f32_f64"}, {false, 0x10u, "v_cvt_f64_f32"}, {false, 0x15u, "v_cvt_u32_f64"}, + {false, 0x16u, "v_cvt_f64_u32"}, {false, 0x17u, "v_trunc_f64"}, {false, 0x18u, "v_ceil_f64"}, + {false, 0x19u, "v_rndne_f64"}, {false, 0x1au, "v_floor_f64"}, {false, 0x3cu, "v_frexp_exp_i32_f64"}, + {false, 0x3du, "v_frexp_mant_f64"}, {false, 0x3eu, "v_fract_f64"}, {false, 0x65u, "v_swap_b32"}, + {false, 0x68u, "v_swaprel_b32"}, {true, 0x06u, "v_mac_legacy_f32"}, {true, 0x20u, "v_madmk_f32"}, + {true, 0x21u, "v_madak_f32"}, {true, 0x2cu, "v_fmamk_f32"}, {true, 0x2du, "v_fmaak_f32"}, + {true, 0x37u, "v_fmamk_f16"}, {true, 0x38u, "v_fmaak_f16"}, +}}; + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +auto Compile(AgcDriver::VulkanDevice& device, std::span code) { + std::vector userData(8, 0u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{32, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void CheckRefused(AgcDriver::VulkanDevice& device, std::uint32_t word0, std::uint32_t word1, const std::string& reason, const std::string& what) { + alignas(256) const std::array code{word0, word1, 0x3c003c00u, 0xbf810000u}; + std::string refusal; + try { + static_cast(Compile(device, code)); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find(reason) != std::string::npos, what + " " + Hex(word0) + " was not refused"); +} + +void CheckAccepted(AgcDriver::VulkanDevice& device, std::uint32_t word0, std::uint32_t word1) { + alignas(256) const std::array code{word0, word1, 0xbf810000u}; + static_cast(Compile(device, code)); +} + +std::uint32_t Encode(const NoFormOpcode& op, std::uint32_t form) { + return op.vop2 ? (op.opcode << 25u) | (10u << 17u) | (5u << 9u) | form : 0x7e000000u | (10u << 17u) | (op.opcode << 9u) | form; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + constexpr std::uint32_t Dpp16 = 0xff00b104u; + constexpr std::uint32_t Dpp8 = 0x00fac604u; + for (const auto& op : NoFormOpcodes) { + const std::string name = op.name; + CheckRefused(*device, Encode(op, 0xfau), Dpp16, "DPP modifier is not supported for opcode", name + " with DPP16"); + CheckRefused(*device, Encode(op, 0xe9u), Dpp8, "DPP8 modifier is not supported for opcode", name + " with DPP8"); + CheckRefused(*device, Encode(op, 0xeau), Dpp8, "DPP8 modifier is not supported for opcode", name + " with DPP8 fi:1"); + } + CheckAccepted(*device, 0x7e1402fau, Dpp16); + CheckAccepted(*device, 0x4a140afau, Dpp16); + std::puts("dpp no form tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp new file mode 100644 index 000000000..4ef9a3949 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DsSwizzle.cpp @@ -0,0 +1,289 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xd8d4001f, 0x0a000004, 0xd8d4201f, + 0x0b000004, 0xd8d400b8, 0x0c000004, 0xd8d47c1f, 0x0d000004, 0xd8d42493, 0x0e000004, 0xd8d4804b, + 0x0f000004, 0xd8d4bf4b, 0x10000004, 0xd8d4c020, 0x11000004, 0xd8d4c460, 0x12000004, 0xd8d4c3e0, + 0x13000004, 0xd8d4c4ac, 0x14000004, 0xd8d4d8e0, 0x15000004, 0xbefe03ff, 0x33cc0ff3, 0xd8d4201f, + 0x16000004, 0xd8d4804b, 0x17000004, 0xd8d4c460, 0x18000004, 0xd8d400b8, 0x19000004, 0xbf8cc07f, + 0xbefe03c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xd8d4001f, 0x0a000004, 0xd8d4201f, + 0x0b000004, 0xd8d400b8, 0x0c000004, 0xd8d47c1f, 0x0d000004, 0xd8d42493, 0x0e000004, 0xd8d4804b, + 0x0f000004, 0xd8d4bf4b, 0x10000004, 0xd8d4c020, 0x11000004, 0xd8d4c460, 0x12000004, 0xd8d4c3e0, + 0x13000004, 0xd8d4c4ac, 0x14000004, 0xd8d4d8e0, 0x15000004, 0xbefe03ff, 0x33cc0ff3, 0xbeff03ff, + 0xc3a5960f, 0xd8d4201f, 0x16000004, 0xd8d4804b, 0x17000004, 0xd8d4c460, 0x18000004, 0xd8d400b8, + 0x19000004, 0xbf8cc07f, 0xbefe04c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x1e7ea419u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x51c9bc70u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80a4df5au, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xf38b2ffcu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8306d03bu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xa5aec797u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xdc28ff90u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xf3f49249u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1a466884u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xe255accbu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x39292d22u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xe5121482u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x99dd251du, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x9f199504u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8e7aa6e9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x6bad6be2u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc88b2875u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x9293de8fu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8c3d5f16u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd7a7a3ccu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xbb049a79u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc6cd75e9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc4a334bfu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x7dabe929u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc0433cbdu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc5e818fau, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x96263ae6u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x70eb9a0au, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3d6c51e3u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00a61f93u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x9d3c7decu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x14aa4e71u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1c593af5u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x49889310u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd11745adu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1919e93au, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x731c9452u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x02f0ee99u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd0944996u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xe4163207u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xaf67d461u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x7d836e77u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xadec6f11u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x5071950eu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x35d14880u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x65b00a2du, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x406329bcu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x59001ac9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xfadb8908u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffb88309u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xede7bd0cu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x5b4c8012u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xce1d62e0u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x60581ccau, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffe976abu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xbef7e268u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x837a7935u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xa341738cu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1389b372u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xb91dddd9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x57241955u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x16f44881u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8eb5140fu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x89be9c1cu, 0x00000000u, 0x00000000u, 0x00000000u} +}; + +constexpr std::uint32_t Expected32[32][16] = { + {0x1e7ea419u, 0x1a466884u, 0xa5aec797u, 0x14aa4e71u, 0x9f199504u, 0xf38b2ffcu, 0xf38b2ffcu, 0x51c9bc70u, 0x00a61f93u, 0x14aa4e71u, 0xd7a7a3ccu, 0xf3f49249u, 0x1a466884u, 0x00000000u, 0x00a61f93u, 0xa5aec797u}, + {0x51c9bc70u, 0xe255accbu, 0xa5aec797u, 0x9d3c7decu, 0x99dd251du, 0x80a4df5au, 0x80a4df5au, 0x80a4df5au, 0x9d3c7decu, 0x1e7ea419u, 0xc88b2875u, 0x1a466884u, 0xe255accbu, 0x00000000u, 0x00000000u, 0xa5aec797u}, + {0x80a4df5au, 0x39292d22u, 0xa5aec797u, 0x00a61f93u, 0x6bad6be2u, 0x1e7ea419u, 0x1e7ea419u, 0xf38b2ffcu, 0x14aa4e71u, 0x51c9bc70u, 0x9293de8fu, 0xe255accbu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xf38b2ffcu, 0xe5121482u, 0xa5aec797u, 0x3d6c51e3u, 0x8e7aa6e9u, 0x51c9bc70u, 0x51c9bc70u, 0x8306d03bu, 0x1e7ea419u, 0x80a4df5au, 0x8c3d5f16u, 0x39292d22u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8306d03bu, 0x99dd251du, 0xa5aec797u, 0x70eb9a0au, 0x9f199504u, 0xf3f49249u, 0xf3f49249u, 0xa5aec797u, 0x51c9bc70u, 0xf38b2ffcu, 0x7dabe929u, 0xe5121482u, 0x00000000u, 0xf3f49249u, 0x51c9bc70u, 0xa5aec797u}, + {0xa5aec797u, 0x9f199504u, 0xa5aec797u, 0x96263ae6u, 0x99dd251du, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0x80a4df5au, 0x8306d03bu, 0x8306d03bu, 0x99dd251du, 0x00000000u, 0xdc28ff90u, 0x00000000u, 0xa5aec797u}, + {0xdc28ff90u, 0x8e7aa6e9u, 0xa5aec797u, 0xc5e818fau, 0x6bad6be2u, 0x8306d03bu, 0x8306d03bu, 0xf3f49249u, 0xf38b2ffcu, 0xa5aec797u, 0xa5aec797u, 0x9f199504u, 0x00000000u, 0x8306d03bu, 0x00000000u, 0xa5aec797u}, + {0xf3f49249u, 0x6bad6be2u, 0xa5aec797u, 0xc0433cbdu, 0x8e7aa6e9u, 0xa5aec797u, 0xa5aec797u, 0x1a466884u, 0x8306d03bu, 0xdc28ff90u, 0xdc28ff90u, 0x8e7aa6e9u, 0x00000000u, 0xa5aec797u, 0x8306d03bu, 0xa5aec797u}, + {0x1a466884u, 0x1e7ea419u, 0x9f199504u, 0x7dabe929u, 0x9f199504u, 0xe5121482u, 0xe5121482u, 0xe255accbu, 0xa5aec797u, 0xf3f49249u, 0xe5121482u, 0x6bad6be2u, 0x1e7ea419u, 0xe5121482u, 0xa5aec797u, 0x00000000u}, + {0xe255accbu, 0x51c9bc70u, 0x9f199504u, 0xc4a334bfu, 0x99dd251du, 0x39292d22u, 0x39292d22u, 0x39292d22u, 0xdc28ff90u, 0x1a466884u, 0x1a466884u, 0xc88b2875u, 0x51c9bc70u, 0x39292d22u, 0xdc28ff90u, 0x00000000u}, + {0x39292d22u, 0x80a4df5au, 0x9f199504u, 0xc6cd75e9u, 0x6bad6be2u, 0x1a466884u, 0x1a466884u, 0xe5121482u, 0xf3f49249u, 0xe255accbu, 0xe255accbu, 0x9293de8fu, 0x00000000u, 0x1a466884u, 0xf3f49249u, 0x00000000u}, + {0xe5121482u, 0xf38b2ffcu, 0x9f199504u, 0xbb049a79u, 0x8e7aa6e9u, 0xe255accbu, 0xe255accbu, 0x99dd251du, 0x1a466884u, 0x39292d22u, 0x39292d22u, 0x8c3d5f16u, 0x00000000u, 0xe255accbu, 0x1a466884u, 0x00000000u}, + {0x99dd251du, 0x8306d03bu, 0x9f199504u, 0xd7a7a3ccu, 0x9f199504u, 0x6bad6be2u, 0x6bad6be2u, 0x9f199504u, 0xe255accbu, 0xe5121482u, 0x6bad6be2u, 0xd7a7a3ccu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9f199504u, 0xa5aec797u, 0x9f199504u, 0x8c3d5f16u, 0x99dd251du, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x39292d22u, 0x99dd251du, 0x99dd251du, 0xbb049a79u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0xdc28ff90u, 0x9f199504u, 0x9293de8fu, 0x6bad6be2u, 0x99dd251du, 0x99dd251du, 0x6bad6be2u, 0xe5121482u, 0x9f199504u, 0x9f199504u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6bad6be2u, 0xf3f49249u, 0x9f199504u, 0xc88b2875u, 0x8e7aa6e9u, 0x9f199504u, 0x9f199504u, 0xc88b2875u, 0x99dd251du, 0x8e7aa6e9u, 0x8e7aa6e9u, 0xc4a334bfu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc88b2875u, 0xc0433cbdu, 0xc6cd75e9u, 0x6bad6be2u, 0x00a61f93u, 0xd7a7a3ccu, 0xd7a7a3ccu, 0x9293de8fu, 0x9f199504u, 0x6bad6be2u, 0xf38b2ffcu, 0x7dabe929u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9293de8fu, 0xc5e818fau, 0xc6cd75e9u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8e7aa6e9u, 0xc88b2875u, 0x1e7ea419u, 0xc0433cbdu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8c3d5f16u, 0x96263ae6u, 0xc6cd75e9u, 0x9f199504u, 0x14aa4e71u, 0xc88b2875u, 0xc88b2875u, 0xd7a7a3ccu, 0x6bad6be2u, 0x9293de8fu, 0x51c9bc70u, 0xc5e818fau, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd7a7a3ccu, 0x70eb9a0au, 0xc6cd75e9u, 0x99dd251du, 0x9d3c7decu, 0x9293de8fu, 0x9293de8fu, 0xbb049a79u, 0xc88b2875u, 0x8c3d5f16u, 0x80a4df5au, 0x96263ae6u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xbb049a79u, 0x3d6c51e3u, 0xc6cd75e9u, 0xe5121482u, 0x00a61f93u, 0x7dabe929u, 0x7dabe929u, 0xc6cd75e9u, 0x9293de8fu, 0xd7a7a3ccu, 0xf3f49249u, 0x70eb9a0au, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc6cd75e9u, 0x00a61f93u, 0xc6cd75e9u, 0x39292d22u, 0x3d6c51e3u, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0x8c3d5f16u, 0xbb049a79u, 0xbb049a79u, 0x3d6c51e3u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc4a334bfu, 0x9d3c7decu, 0xc6cd75e9u, 0xe255accbu, 0x14aa4e71u, 0xbb049a79u, 0xbb049a79u, 0x7dabe929u, 0xd7a7a3ccu, 0xc6cd75e9u, 0xc6cd75e9u, 0x00a61f93u, 0x00000000u, 0x00000000u, 0xd7a7a3ccu, 0x00000000u}, + {0x7dabe929u, 0x14aa4e71u, 0xc6cd75e9u, 0x1a466884u, 0x9d3c7decu, 0xc6cd75e9u, 0xc6cd75e9u, 0xc0433cbdu, 0xbb049a79u, 0xc4a334bfu, 0xc4a334bfu, 0x9d3c7decu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc0433cbdu, 0xc88b2875u, 0x00a61f93u, 0xf3f49249u, 0x00a61f93u, 0x70eb9a0au, 0x70eb9a0au, 0xc5e818fau, 0xc6cd75e9u, 0x7dabe929u, 0x70eb9a0au, 0x14aa4e71u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00a61f93u}, + {0xc5e818fau, 0x9293de8fu, 0x00a61f93u, 0xdc28ff90u, 0x3d6c51e3u, 0x96263ae6u, 0x96263ae6u, 0x96263ae6u, 0xc4a334bfu, 0xc0433cbdu, 0xc0433cbdu, 0x1e7ea419u, 0x00000000u, 0x00000000u, 0xc4a334bfu, 0x00a61f93u}, + {0x96263ae6u, 0x8c3d5f16u, 0x00a61f93u, 0xa5aec797u, 0x14aa4e71u, 0xc0433cbdu, 0xc0433cbdu, 0x70eb9a0au, 0x7dabe929u, 0xc5e818fau, 0xc5e818fau, 0x51c9bc70u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x70eb9a0au, 0xd7a7a3ccu, 0x00a61f93u, 0x8306d03bu, 0x9d3c7decu, 0xc5e818fau, 0xc5e818fau, 0x3d6c51e3u, 0xc0433cbdu, 0x96263ae6u, 0x96263ae6u, 0x80a4df5au, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3d6c51e3u, 0xbb049a79u, 0x00a61f93u, 0xf38b2ffcu, 0x00a61f93u, 0x14aa4e71u, 0x14aa4e71u, 0x00a61f93u, 0xc5e818fau, 0x70eb9a0au, 0x14aa4e71u, 0xf38b2ffcu, 0x00000000u, 0x00000000u, 0xc5e818fau, 0x00a61f93u}, + {0x00a61f93u, 0xc6cd75e9u, 0x00a61f93u, 0x80a4df5au, 0x3d6c51e3u, 0x9d3c7decu, 0x9d3c7decu, 0x9d3c7decu, 0x96263ae6u, 0x3d6c51e3u, 0x3d6c51e3u, 0x8306d03bu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00a61f93u}, + {0x9d3c7decu, 0xc4a334bfu, 0x00a61f93u, 0x51c9bc70u, 0x14aa4e71u, 0x3d6c51e3u, 0x3d6c51e3u, 0x14aa4e71u, 0x70eb9a0au, 0x00a61f93u, 0x00a61f93u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x14aa4e71u, 0x7dabe929u, 0x00a61f93u, 0x1e7ea419u, 0x9d3c7decu, 0x00a61f93u, 0x00a61f93u, 0x1e7ea419u, 0x3d6c51e3u, 0x9d3c7decu, 0x9d3c7decu, 0xdc28ff90u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +constexpr std::uint32_t Expected64[64][16] = { + {0x1e7ea419u, 0x1a466884u, 0xa5aec797u, 0x14aa4e71u, 0x9f199504u, 0xf38b2ffcu, 0xf38b2ffcu, 0x51c9bc70u, 0x00a61f93u, 0x14aa4e71u, 0xd7a7a3ccu, 0xf3f49249u, 0x1a466884u, 0x00000000u, 0x00a61f93u, 0xa5aec797u}, + {0x51c9bc70u, 0xe255accbu, 0xa5aec797u, 0x9d3c7decu, 0x99dd251du, 0x80a4df5au, 0x80a4df5au, 0x80a4df5au, 0x9d3c7decu, 0x1e7ea419u, 0xc88b2875u, 0x1a466884u, 0xe255accbu, 0x00000000u, 0x00000000u, 0xa5aec797u}, + {0x80a4df5au, 0x39292d22u, 0xa5aec797u, 0x00a61f93u, 0x6bad6be2u, 0x1e7ea419u, 0x1e7ea419u, 0xf38b2ffcu, 0x14aa4e71u, 0x51c9bc70u, 0x9293de8fu, 0xe255accbu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xf38b2ffcu, 0xe5121482u, 0xa5aec797u, 0x3d6c51e3u, 0x8e7aa6e9u, 0x51c9bc70u, 0x51c9bc70u, 0x8306d03bu, 0x1e7ea419u, 0x80a4df5au, 0x8c3d5f16u, 0x39292d22u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8306d03bu, 0x99dd251du, 0xa5aec797u, 0x70eb9a0au, 0x9f199504u, 0xf3f49249u, 0xf3f49249u, 0xa5aec797u, 0x51c9bc70u, 0xf38b2ffcu, 0x7dabe929u, 0xe5121482u, 0x00000000u, 0xf3f49249u, 0x51c9bc70u, 0xa5aec797u}, + {0xa5aec797u, 0x9f199504u, 0xa5aec797u, 0x96263ae6u, 0x99dd251du, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0x80a4df5au, 0x8306d03bu, 0x8306d03bu, 0x99dd251du, 0x00000000u, 0xdc28ff90u, 0x00000000u, 0xa5aec797u}, + {0xdc28ff90u, 0x8e7aa6e9u, 0xa5aec797u, 0xc5e818fau, 0x6bad6be2u, 0x8306d03bu, 0x8306d03bu, 0xf3f49249u, 0xf38b2ffcu, 0xa5aec797u, 0xa5aec797u, 0x9f199504u, 0x00000000u, 0x8306d03bu, 0x00000000u, 0xa5aec797u}, + {0xf3f49249u, 0x6bad6be2u, 0xa5aec797u, 0xc0433cbdu, 0x8e7aa6e9u, 0xa5aec797u, 0xa5aec797u, 0x1a466884u, 0x8306d03bu, 0xdc28ff90u, 0xdc28ff90u, 0x8e7aa6e9u, 0x00000000u, 0xa5aec797u, 0x8306d03bu, 0xa5aec797u}, + {0x1a466884u, 0x1e7ea419u, 0x9f199504u, 0x7dabe929u, 0x9f199504u, 0xe5121482u, 0xe5121482u, 0xe255accbu, 0xa5aec797u, 0xf3f49249u, 0xe5121482u, 0x6bad6be2u, 0x1e7ea419u, 0xe5121482u, 0xa5aec797u, 0x00000000u}, + {0xe255accbu, 0x51c9bc70u, 0x9f199504u, 0xc4a334bfu, 0x99dd251du, 0x39292d22u, 0x39292d22u, 0x39292d22u, 0xdc28ff90u, 0x1a466884u, 0x1a466884u, 0xc88b2875u, 0x51c9bc70u, 0x39292d22u, 0xdc28ff90u, 0x00000000u}, + {0x39292d22u, 0x80a4df5au, 0x9f199504u, 0xc6cd75e9u, 0x6bad6be2u, 0x1a466884u, 0x1a466884u, 0xe5121482u, 0xf3f49249u, 0xe255accbu, 0xe255accbu, 0x9293de8fu, 0x00000000u, 0x1a466884u, 0xf3f49249u, 0x00000000u}, + {0xe5121482u, 0xf38b2ffcu, 0x9f199504u, 0xbb049a79u, 0x8e7aa6e9u, 0xe255accbu, 0xe255accbu, 0x99dd251du, 0x1a466884u, 0x39292d22u, 0x39292d22u, 0x8c3d5f16u, 0x00000000u, 0xe255accbu, 0x1a466884u, 0x00000000u}, + {0x99dd251du, 0x8306d03bu, 0x9f199504u, 0xd7a7a3ccu, 0x9f199504u, 0x6bad6be2u, 0x6bad6be2u, 0x9f199504u, 0xe255accbu, 0xe5121482u, 0x6bad6be2u, 0xd7a7a3ccu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9f199504u, 0xa5aec797u, 0x9f199504u, 0x8c3d5f16u, 0x99dd251du, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x8e7aa6e9u, 0x39292d22u, 0x99dd251du, 0x99dd251du, 0xbb049a79u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0xdc28ff90u, 0x9f199504u, 0x9293de8fu, 0x6bad6be2u, 0x99dd251du, 0x99dd251du, 0x6bad6be2u, 0xe5121482u, 0x9f199504u, 0x9f199504u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6bad6be2u, 0xf3f49249u, 0x9f199504u, 0xc88b2875u, 0x8e7aa6e9u, 0x9f199504u, 0x9f199504u, 0xc88b2875u, 0x99dd251du, 0x8e7aa6e9u, 0x8e7aa6e9u, 0xc4a334bfu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc88b2875u, 0xc0433cbdu, 0xc6cd75e9u, 0x6bad6be2u, 0x00a61f93u, 0xd7a7a3ccu, 0xd7a7a3ccu, 0x9293de8fu, 0x9f199504u, 0x6bad6be2u, 0xf38b2ffcu, 0x7dabe929u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9293de8fu, 0xc5e818fau, 0xc6cd75e9u, 0x8e7aa6e9u, 0x3d6c51e3u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8c3d5f16u, 0x8e7aa6e9u, 0xc88b2875u, 0x1e7ea419u, 0xc0433cbdu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8c3d5f16u, 0x96263ae6u, 0xc6cd75e9u, 0x9f199504u, 0x14aa4e71u, 0xc88b2875u, 0xc88b2875u, 0xd7a7a3ccu, 0x6bad6be2u, 0x9293de8fu, 0x51c9bc70u, 0xc5e818fau, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd7a7a3ccu, 0x70eb9a0au, 0xc6cd75e9u, 0x99dd251du, 0x9d3c7decu, 0x9293de8fu, 0x9293de8fu, 0xbb049a79u, 0xc88b2875u, 0x8c3d5f16u, 0x80a4df5au, 0x96263ae6u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xbb049a79u, 0x3d6c51e3u, 0xc6cd75e9u, 0xe5121482u, 0x00a61f93u, 0x7dabe929u, 0x7dabe929u, 0xc6cd75e9u, 0x9293de8fu, 0xd7a7a3ccu, 0xf3f49249u, 0x70eb9a0au, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc6cd75e9u, 0x00a61f93u, 0xc6cd75e9u, 0x39292d22u, 0x3d6c51e3u, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0x8c3d5f16u, 0xbb049a79u, 0xbb049a79u, 0x3d6c51e3u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc4a334bfu, 0x9d3c7decu, 0xc6cd75e9u, 0xe255accbu, 0x14aa4e71u, 0xbb049a79u, 0xbb049a79u, 0x7dabe929u, 0xd7a7a3ccu, 0xc6cd75e9u, 0xc6cd75e9u, 0x00a61f93u, 0x00000000u, 0x00000000u, 0xd7a7a3ccu, 0x00000000u}, + {0x7dabe929u, 0x14aa4e71u, 0xc6cd75e9u, 0x1a466884u, 0x9d3c7decu, 0xc6cd75e9u, 0xc6cd75e9u, 0xc0433cbdu, 0xbb049a79u, 0xc4a334bfu, 0xc4a334bfu, 0x9d3c7decu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc0433cbdu, 0xc88b2875u, 0x00a61f93u, 0xf3f49249u, 0x00a61f93u, 0x70eb9a0au, 0x70eb9a0au, 0xc5e818fau, 0xc6cd75e9u, 0x7dabe929u, 0x70eb9a0au, 0x14aa4e71u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00a61f93u}, + {0xc5e818fau, 0x9293de8fu, 0x00a61f93u, 0xdc28ff90u, 0x3d6c51e3u, 0x96263ae6u, 0x96263ae6u, 0x96263ae6u, 0xc4a334bfu, 0xc0433cbdu, 0xc0433cbdu, 0x1e7ea419u, 0x00000000u, 0x00000000u, 0xc4a334bfu, 0x00a61f93u}, + {0x96263ae6u, 0x8c3d5f16u, 0x00a61f93u, 0xa5aec797u, 0x14aa4e71u, 0xc0433cbdu, 0xc0433cbdu, 0x70eb9a0au, 0x7dabe929u, 0xc5e818fau, 0xc5e818fau, 0x51c9bc70u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x70eb9a0au, 0xd7a7a3ccu, 0x00a61f93u, 0x8306d03bu, 0x9d3c7decu, 0xc5e818fau, 0xc5e818fau, 0x3d6c51e3u, 0xc0433cbdu, 0x96263ae6u, 0x96263ae6u, 0x80a4df5au, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3d6c51e3u, 0xbb049a79u, 0x00a61f93u, 0xf38b2ffcu, 0x00a61f93u, 0x14aa4e71u, 0x14aa4e71u, 0x00a61f93u, 0xc5e818fau, 0x70eb9a0au, 0x14aa4e71u, 0xf38b2ffcu, 0x00000000u, 0x00000000u, 0xc5e818fau, 0x00a61f93u}, + {0x00a61f93u, 0xc6cd75e9u, 0x00a61f93u, 0x80a4df5au, 0x3d6c51e3u, 0x9d3c7decu, 0x9d3c7decu, 0x9d3c7decu, 0x96263ae6u, 0x3d6c51e3u, 0x3d6c51e3u, 0x8306d03bu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00a61f93u}, + {0x9d3c7decu, 0xc4a334bfu, 0x00a61f93u, 0x51c9bc70u, 0x14aa4e71u, 0x3d6c51e3u, 0x3d6c51e3u, 0x14aa4e71u, 0x70eb9a0au, 0x00a61f93u, 0x00a61f93u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x14aa4e71u, 0x7dabe929u, 0x00a61f93u, 0x1e7ea419u, 0x9d3c7decu, 0x00a61f93u, 0x00a61f93u, 0x1e7ea419u, 0x3d6c51e3u, 0x9d3c7decu, 0x9d3c7decu, 0xdc28ff90u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x1c593af5u, 0xaf67d461u, 0x02f0ee99u, 0x89be9c1cu, 0x65b00a2du, 0x1919e93au, 0x1919e93au, 0x49889310u, 0x16f44881u, 0x89be9c1cu, 0x5b4c8012u, 0xe4163207u, 0x00000000u, 0x1919e93au, 0x00000000u, 0x00000000u}, + {0x49889310u, 0x7d836e77u, 0x02f0ee99u, 0x8eb5140fu, 0x35d14880u, 0xd11745adu, 0xd11745adu, 0xd11745adu, 0x8eb5140fu, 0x1c593af5u, 0xfadb8908u, 0xaf67d461u, 0x7d836e77u, 0xd11745adu, 0x8eb5140fu, 0x00000000u}, + {0xd11745adu, 0xadec6f11u, 0x02f0ee99u, 0x16f44881u, 0x59001ac9u, 0x1c593af5u, 0x1c593af5u, 0x1919e93au, 0x89be9c1cu, 0x49889310u, 0xffb88309u, 0x7d836e77u, 0xadec6f11u, 0x1c593af5u, 0x89be9c1cu, 0x00000000u}, + {0x1919e93au, 0x5071950eu, 0x02f0ee99u, 0x57241955u, 0x406329bcu, 0x49889310u, 0x49889310u, 0x731c9452u, 0x1c593af5u, 0xd11745adu, 0xede7bd0cu, 0xadec6f11u, 0x00000000u, 0x49889310u, 0x1c593af5u, 0x00000000u}, + {0x731c9452u, 0x35d14880u, 0x02f0ee99u, 0xb91dddd9u, 0x65b00a2du, 0xe4163207u, 0xe4163207u, 0x02f0ee99u, 0x49889310u, 0x1919e93au, 0xbef7e268u, 0x5071950eu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x02f0ee99u, 0x65b00a2du, 0x02f0ee99u, 0x1389b372u, 0x35d14880u, 0xd0944996u, 0xd0944996u, 0xd0944996u, 0xd11745adu, 0x731c9452u, 0x731c9452u, 0x35d14880u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xd0944996u, 0x406329bcu, 0x02f0ee99u, 0xa341738cu, 0x59001ac9u, 0x731c9452u, 0x731c9452u, 0xe4163207u, 0x1919e93au, 0x02f0ee99u, 0x02f0ee99u, 0x65b00a2du, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xe4163207u, 0x59001ac9u, 0x02f0ee99u, 0x837a7935u, 0x406329bcu, 0x02f0ee99u, 0x02f0ee99u, 0xaf67d461u, 0x731c9452u, 0xd0944996u, 0xd0944996u, 0x406329bcu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xaf67d461u, 0x1c593af5u, 0x65b00a2du, 0xbef7e268u, 0x65b00a2du, 0x5071950eu, 0x5071950eu, 0x7d836e77u, 0x02f0ee99u, 0xe4163207u, 0x5071950eu, 0x59001ac9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x7d836e77u, 0x49889310u, 0x65b00a2du, 0xffe976abu, 0x35d14880u, 0xadec6f11u, 0xadec6f11u, 0xadec6f11u, 0xd0944996u, 0xaf67d461u, 0xaf67d461u, 0xfadb8908u, 0x49889310u, 0xadec6f11u, 0x00000000u, 0x00000000u}, + {0xadec6f11u, 0xd11745adu, 0x65b00a2du, 0x60581ccau, 0x59001ac9u, 0xaf67d461u, 0xaf67d461u, 0x5071950eu, 0xe4163207u, 0x7d836e77u, 0x7d836e77u, 0xffb88309u, 0xd11745adu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x5071950eu, 0x1919e93au, 0x65b00a2du, 0xce1d62e0u, 0x406329bcu, 0x7d836e77u, 0x7d836e77u, 0x35d14880u, 0xaf67d461u, 0xadec6f11u, 0xadec6f11u, 0xede7bd0cu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x35d14880u, 0x731c9452u, 0x65b00a2du, 0x5b4c8012u, 0x65b00a2du, 0x59001ac9u, 0x59001ac9u, 0x65b00a2du, 0x7d836e77u, 0x5071950eu, 0x59001ac9u, 0x5b4c8012u, 0x00000000u, 0x59001ac9u, 0x7d836e77u, 0x00000000u}, + {0x65b00a2du, 0x02f0ee99u, 0x65b00a2du, 0xede7bd0cu, 0x35d14880u, 0x406329bcu, 0x406329bcu, 0x406329bcu, 0xadec6f11u, 0x35d14880u, 0x35d14880u, 0xce1d62e0u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x406329bcu, 0xd0944996u, 0x65b00a2du, 0xffb88309u, 0x59001ac9u, 0x35d14880u, 0x35d14880u, 0x59001ac9u, 0x5071950eu, 0x65b00a2du, 0x65b00a2du, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x59001ac9u, 0xe4163207u, 0x65b00a2du, 0xfadb8908u, 0x406329bcu, 0x65b00a2du, 0x65b00a2du, 0xfadb8908u, 0x35d14880u, 0x406329bcu, 0x406329bcu, 0xffe976abu, 0x00000000u, 0x00000000u, 0x35d14880u, 0x00000000u}, + {0xfadb8908u, 0x837a7935u, 0x60581ccau, 0x59001ac9u, 0x16f44881u, 0x5b4c8012u, 0x5b4c8012u, 0xffb88309u, 0x65b00a2du, 0x59001ac9u, 0x1919e93au, 0xbef7e268u, 0x837a7935u, 0x00000000u, 0x00000000u, 0x60581ccau}, + {0xffb88309u, 0xa341738cu, 0x60581ccau, 0x406329bcu, 0x57241955u, 0xede7bd0cu, 0xede7bd0cu, 0xede7bd0cu, 0x406329bcu, 0xfadb8908u, 0x1c593af5u, 0x837a7935u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xede7bd0cu, 0x1389b372u, 0x60581ccau, 0x65b00a2du, 0x89be9c1cu, 0xfadb8908u, 0xfadb8908u, 0x5b4c8012u, 0x59001ac9u, 0xffb88309u, 0x49889310u, 0xa341738cu, 0x00000000u, 0xfadb8908u, 0x59001ac9u, 0x60581ccau}, + {0x5b4c8012u, 0xb91dddd9u, 0x60581ccau, 0x35d14880u, 0x8eb5140fu, 0xffb88309u, 0xffb88309u, 0xce1d62e0u, 0xfadb8908u, 0xede7bd0cu, 0xd11745adu, 0x1389b372u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xce1d62e0u, 0x57241955u, 0x60581ccau, 0x5071950eu, 0x16f44881u, 0xbef7e268u, 0xbef7e268u, 0x60581ccau, 0xffb88309u, 0x5b4c8012u, 0xe4163207u, 0xb91dddd9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x60581ccau, 0x16f44881u, 0x60581ccau, 0xadec6f11u, 0x57241955u, 0xffe976abu, 0xffe976abu, 0xffe976abu, 0xede7bd0cu, 0xce1d62e0u, 0xce1d62e0u, 0x57241955u, 0x00000000u, 0x00000000u, 0xede7bd0cu, 0x60581ccau}, + {0xffe976abu, 0x8eb5140fu, 0x60581ccau, 0x7d836e77u, 0x89be9c1cu, 0xce1d62e0u, 0xce1d62e0u, 0xbef7e268u, 0x5b4c8012u, 0x60581ccau, 0x60581ccau, 0x16f44881u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xbef7e268u, 0x89be9c1cu, 0x60581ccau, 0xaf67d461u, 0x8eb5140fu, 0x60581ccau, 0x60581ccau, 0x837a7935u, 0xce1d62e0u, 0xffe976abu, 0xffe976abu, 0x8eb5140fu, 0x89be9c1cu, 0x60581ccau, 0x00000000u, 0x60581ccau}, + {0x837a7935u, 0xfadb8908u, 0x16f44881u, 0xe4163207u, 0x16f44881u, 0xb91dddd9u, 0xb91dddd9u, 0xa341738cu, 0x60581ccau, 0xbef7e268u, 0xb91dddd9u, 0x89be9c1cu, 0xfadb8908u, 0x00000000u, 0x60581ccau, 0x00000000u}, + {0xa341738cu, 0xffb88309u, 0x16f44881u, 0xd0944996u, 0x57241955u, 0x1389b372u, 0x1389b372u, 0x1389b372u, 0xffe976abu, 0x837a7935u, 0x837a7935u, 0x1c593af5u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1389b372u, 0xede7bd0cu, 0x16f44881u, 0x02f0ee99u, 0x89be9c1cu, 0x837a7935u, 0x837a7935u, 0xb91dddd9u, 0xbef7e268u, 0xa341738cu, 0xa341738cu, 0x49889310u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb91dddd9u, 0x5b4c8012u, 0x16f44881u, 0x731c9452u, 0x8eb5140fu, 0xa341738cu, 0xa341738cu, 0x57241955u, 0x837a7935u, 0x1389b372u, 0x1389b372u, 0xd11745adu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x57241955u, 0xce1d62e0u, 0x16f44881u, 0x1919e93au, 0x16f44881u, 0x89be9c1cu, 0x89be9c1cu, 0x16f44881u, 0xa341738cu, 0xb91dddd9u, 0x89be9c1cu, 0x1919e93au, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x16f44881u, 0x60581ccau, 0x16f44881u, 0xd11745adu, 0x57241955u, 0x8eb5140fu, 0x8eb5140fu, 0x8eb5140fu, 0x1389b372u, 0x57241955u, 0x57241955u, 0x731c9452u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8eb5140fu, 0xffe976abu, 0x16f44881u, 0x49889310u, 0x89be9c1cu, 0x57241955u, 0x57241955u, 0x89be9c1cu, 0xb91dddd9u, 0x16f44881u, 0x16f44881u, 0x02f0ee99u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x89be9c1cu, 0xbef7e268u, 0x16f44881u, 0x1c593af5u, 0x8eb5140fu, 0x16f44881u, 0x16f44881u, 0x1c593af5u, 0x57241955u, 0x8eb5140fu, 0x8eb5140fu, 0xd0944996u, 0xbef7e268u, 0x00000000u, 0x00000000u, 0x00000000u} +}; + +constexpr const char* Names[Checked] = {"bitmask_identity", "bitmask_swap8", "bitmask_broadcast", "bitmask_reverse", "bitmask_mixed", "quad_perm", "quad_perm_high_bits", "rotate_left_1", "rotate_right_3", "rotate_left_31", "rotate_masked", "rotate_high_bits", "inactive_swap8", "inactive_quad_perm", "inactive_rotate", "inactive_broadcast"}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked]) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "ds_swizzle wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64); + std::puts("ds_swizzle tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxNan.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxNan.cpp new file mode 100644 index 000000000..b9e7244d8 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxNan.cpp @@ -0,0 +1,372 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x1e140b04, 0x20160b04, 0xd551000c, + 0x041a0b04, 0xd554000d, 0x041a0b04, 0xd557000e, 0x041a0b04, 0xd50f000f, 0x02020b04, 0xd5100010, + 0x02020b04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xbf810000, +}; + +constexpr std::uint32_t Rows[128][4] = { + {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x7f800001u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0xff800000u, 0x00000000u}, + {0x00000000u, 0xc0400000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x7f800000u, 0xff812345u, 0x00000000u}, + {0x00000000u, 0xff800000u, 0x7fc12345u, 0x00000000u}, + {0x00000000u, 0x7fc12345u, 0xc0400000u, 0x00000000u}, + {0x00000000u, 0xffd54321u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x7f800001u, 0xffd54321u, 0x00000000u}, + {0x00000000u, 0xff812345u, 0x7f800000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x7f800000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x3f800000u, 0x7f800001u, 0x00000000u}, + {0x80000000u, 0xc0400000u, 0xff800000u, 0x00000000u}, + {0x80000000u, 0x7f800000u, 0x3f800000u, 0x00000000u}, + {0x80000000u, 0xff800000u, 0xff812345u, 0x00000000u}, + {0x80000000u, 0x7fc12345u, 0x7fc12345u, 0x00000000u}, + {0x80000000u, 0xffd54321u, 0xc0400000u, 0x00000000u}, + {0x80000000u, 0x7f800001u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0xff812345u, 0xffd54321u, 0x00000000u}, + {0x3f800000u, 0x00000000u, 0xffd54321u, 0x00000000u}, + {0x3f800000u, 0x80000000u, 0x7f800000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0x80000000u, 0x00000000u}, + {0x3f800000u, 0xc0400000u, 0x7f800001u, 0x00000000u}, + {0x3f800000u, 0x7f800000u, 0xff800000u, 0x00000000u}, + {0x3f800000u, 0xff800000u, 0x3f800000u, 0x00000000u}, + {0x3f800000u, 0x7fc12345u, 0xff812345u, 0x00000000u}, + {0x3f800000u, 0xffd54321u, 0x7fc12345u, 0x00000000u}, + {0x3f800000u, 0x7f800001u, 0xc0400000u, 0x00000000u}, + {0x3f800000u, 0xff812345u, 0x00000000u, 0x00000000u}, + {0xc0400000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc0400000u, 0x80000000u, 0xffd54321u, 0x00000000u}, + {0xc0400000u, 0x3f800000u, 0x7f800000u, 0x00000000u}, + {0xc0400000u, 0xc0400000u, 0x80000000u, 0x00000000u}, + {0xc0400000u, 0x7f800000u, 0x7f800001u, 0x00000000u}, + {0xc0400000u, 0xff800000u, 0xff800000u, 0x00000000u}, + {0xc0400000u, 0x7fc12345u, 0x3f800000u, 0x00000000u}, + {0xc0400000u, 0xffd54321u, 0xff812345u, 0x00000000u}, + {0xc0400000u, 0x7f800001u, 0x7fc12345u, 0x00000000u}, + {0xc0400000u, 0xff812345u, 0xc0400000u, 0x00000000u}, + {0x7f800000u, 0x00000000u, 0xc0400000u, 0x00000000u}, + {0x7f800000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x7f800000u, 0x3f800000u, 0xffd54321u, 0x00000000u}, + {0x7f800000u, 0xc0400000u, 0x7f800000u, 0x00000000u}, + {0x7f800000u, 0x7f800000u, 0x80000000u, 0x00000000u}, + {0x7f800000u, 0xff800000u, 0x7f800001u, 0x00000000u}, + {0x7f800000u, 0x7fc12345u, 0xff800000u, 0x00000000u}, + {0x7f800000u, 0xffd54321u, 0x3f800000u, 0x00000000u}, + {0x7f800000u, 0x7f800001u, 0xff812345u, 0x00000000u}, + {0x7f800000u, 0xff812345u, 0x7fc12345u, 0x00000000u}, + {0xff800000u, 0x00000000u, 0x7fc12345u, 0x00000000u}, + {0xff800000u, 0x80000000u, 0xc0400000u, 0x00000000u}, + {0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u}, + {0xff800000u, 0xc0400000u, 0xffd54321u, 0x00000000u}, + {0xff800000u, 0x7f800000u, 0x7f800000u, 0x00000000u}, + {0xff800000u, 0xff800000u, 0x80000000u, 0x00000000u}, + {0xff800000u, 0x7fc12345u, 0x7f800001u, 0x00000000u}, + {0xff800000u, 0xffd54321u, 0xff800000u, 0x00000000u}, + {0xff800000u, 0x7f800001u, 0x3f800000u, 0x00000000u}, + {0xff800000u, 0xff812345u, 0xff812345u, 0x00000000u}, + {0x7fc12345u, 0x00000000u, 0xff812345u, 0x00000000u}, + {0x7fc12345u, 0x80000000u, 0x7fc12345u, 0x00000000u}, + {0x7fc12345u, 0x3f800000u, 0xc0400000u, 0x00000000u}, + {0x7fc12345u, 0xc0400000u, 0x00000000u, 0x00000000u}, + {0x7fc12345u, 0x7f800000u, 0xffd54321u, 0x00000000u}, + {0x7fc12345u, 0xff800000u, 0x7f800000u, 0x00000000u}, + {0x7fc12345u, 0x7fc12345u, 0x80000000u, 0x00000000u}, + {0x7fc12345u, 0xffd54321u, 0x7f800001u, 0x00000000u}, + {0x7fc12345u, 0x7f800001u, 0xff800000u, 0x00000000u}, + {0x7fc12345u, 0xff812345u, 0x3f800000u, 0x00000000u}, + {0xffd54321u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0xffd54321u, 0x80000000u, 0xff812345u, 0x00000000u}, + {0xffd54321u, 0x3f800000u, 0x7fc12345u, 0x00000000u}, + {0xffd54321u, 0xc0400000u, 0xc0400000u, 0x00000000u}, + {0xffd54321u, 0x7f800000u, 0x00000000u, 0x00000000u}, + {0xffd54321u, 0xff800000u, 0xffd54321u, 0x00000000u}, + {0xffd54321u, 0x7fc12345u, 0x7f800000u, 0x00000000u}, + {0xffd54321u, 0xffd54321u, 0x80000000u, 0x00000000u}, + {0xffd54321u, 0x7f800001u, 0x7f800001u, 0x00000000u}, + {0xffd54321u, 0xff812345u, 0xff800000u, 0x00000000u}, + {0x7f800001u, 0x00000000u, 0xff800000u, 0x00000000u}, + {0x7f800001u, 0x80000000u, 0x3f800000u, 0x00000000u}, + {0x7f800001u, 0x3f800000u, 0xff812345u, 0x00000000u}, + {0x7f800001u, 0xc0400000u, 0x7fc12345u, 0x00000000u}, + {0x7f800001u, 0x7f800000u, 0xc0400000u, 0x00000000u}, + {0x7f800001u, 0xff800000u, 0x00000000u, 0x00000000u}, + {0x7f800001u, 0x7fc12345u, 0xffd54321u, 0x00000000u}, + {0x7f800001u, 0xffd54321u, 0x7f800000u, 0x00000000u}, + {0x7f800001u, 0x7f800001u, 0x80000000u, 0x00000000u}, + {0x7f800001u, 0xff812345u, 0x7f800001u, 0x00000000u}, + {0xff812345u, 0x00000000u, 0x7f800001u, 0x00000000u}, + {0xff812345u, 0x80000000u, 0xff800000u, 0x00000000u}, + {0xff812345u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0xff812345u, 0xc0400000u, 0xff812345u, 0x00000000u}, + {0xff812345u, 0x7f800000u, 0x7fc12345u, 0x00000000u}, + {0xff812345u, 0xff800000u, 0xc0400000u, 0x00000000u}, + {0xff812345u, 0x7fc12345u, 0x00000000u, 0x00000000u}, + {0xff812345u, 0xffd54321u, 0xffd54321u, 0x00000000u}, + {0xff812345u, 0x7f800001u, 0x7f800000u, 0x00000000u}, + {0xff812345u, 0xff812345u, 0x80000000u, 0x00000000u}, + {0x3f800000u, 0x40200000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x00800000u, 0x3f800000u, 0x00000000u}, + {0xbf800000u, 0xc0400000u, 0x00000000u, 0x00000000u}, + {0xc0400000u, 0xbf800000u, 0x3f800000u, 0x00000000u}, + {0x7fc00000u, 0x7fc12345u, 0x00000000u, 0x00000000u}, + {0xffc00000u, 0xffd54321u, 0x00000000u, 0x00000000u}, + {0x7f800001u, 0x7fa00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xbf800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xffc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xff812345u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0xbf800000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x7f800000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0xff800000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x7fc00000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0xffc00000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0xff812345u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u} +}; +constexpr std::uint32_t Expected[128][7] = { + {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0xc0400000u, 0x00000000u, 0xc0400000u, 0x3f800000u, 0x00000000u, 0xc0400000u, 0x00000000u}, + {0x00000000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x7f800000u}, + {0xff800000u, 0x00000000u, 0xff800000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xc0400000u, 0x00000000u, 0xc0400000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x7f800000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x80000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x80000000u, 0x80000000u, 0x3f800000u}, + {0xc0400000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xc0400000u, 0xc0400000u, 0x80000000u}, + {0x80000000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x3f800000u, 0x80000000u, 0x7f800000u}, + {0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xff800000u, 0xff800000u, 0x80000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x80000000u, 0x80000000u, 0xc0400000u, 0x80000000u, 0xc0400000u, 0x80000000u, 0x80000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x80000000u, 0x3f800000u, 0x80000000u, 0x7f800000u, 0x3f800000u, 0x80000000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0x3f800000u, 0xc0400000u, 0x3f800000u, 0xc0400000u, 0xc0400000u, 0x3f800000u}, + {0x3f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x3f800000u, 0x3f800000u, 0x7f800000u}, + {0xff800000u, 0x3f800000u, 0xff800000u, 0x3f800000u, 0x3f800000u, 0xff800000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0xc0400000u, 0x3f800000u, 0xc0400000u, 0x3f800000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0x00000000u, 0xc0400000u, 0x00000000u, 0x00000000u, 0xc0400000u, 0x00000000u}, + {0xc0400000u, 0x80000000u, 0xc0400000u, 0x80000000u, 0xc0400000u, 0xc0400000u, 0x80000000u}, + {0xc0400000u, 0x3f800000u, 0xc0400000u, 0x7f800000u, 0x3f800000u, 0xc0400000u, 0x3f800000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0x80000000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0xc0400000u, 0x7f800000u, 0xc0400000u, 0x7f800000u, 0xc0400000u, 0xc0400000u, 0x7f800000u}, + {0xff800000u, 0xc0400000u, 0xff800000u, 0xc0400000u, 0xff800000u, 0xff800000u, 0xc0400000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0x3f800000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0x00000000u, 0x7f800000u, 0xc0400000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x7f800000u}, + {0x80000000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x00000000u, 0x80000000u, 0x7f800000u}, + {0x3f800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u, 0x3f800000u, 0x3f800000u, 0x7f800000u}, + {0xc0400000u, 0x7f800000u, 0xc0400000u, 0x7f800000u, 0x7f800000u, 0xc0400000u, 0x7f800000u}, + {0x7f800000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0xff800000u, 0x7f800000u}, + {0x7f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x7f800000u}, + {0x7f800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u, 0x7f800000u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0x00000000u, 0xff800000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x00000000u}, + {0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xc0400000u, 0xff800000u, 0x80000000u}, + {0xff800000u, 0x3f800000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0xff800000u, 0x3f800000u}, + {0xff800000u, 0xc0400000u, 0xff800000u, 0xc0400000u, 0xff800000u, 0xff800000u, 0xc0400000u}, + {0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x80000000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x3f800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x3f800000u, 0x3f800000u, 0xc0400000u, 0x3f800000u, 0xc0400000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0x00000000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0x7fc12345u, 0x7fc12345u, 0x80000000u, 0x80000000u, 0x80000000u, 0x7fc12345u, 0x7fc12345u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u}, + {0x7fc12345u, 0x7fc12345u, 0xff800000u, 0xff800000u, 0xff800000u, 0x7fc12345u, 0x7fc12345u}, + {0x7fc12345u, 0x7fc12345u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x7fc12345u, 0x7fc12345u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0x7f800000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xffd54321u, 0xffd54321u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0xffd54321u, 0xffd54321u}, + {0xffd54321u, 0xffd54321u, 0x80000000u, 0x80000000u, 0x80000000u, 0xffd54321u, 0xffd54321u}, + {0xffd54321u, 0xffd54321u, 0xffd54321u, 0xffd54321u, 0xffd54321u, 0xffd54321u, 0xffd54321u}, + {0xffd54321u, 0xffd54321u, 0xff800000u, 0xff800000u, 0xff800000u, 0xffd54321u, 0xffd54321u}, + {0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x3f800000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0x7f800000u, 0x7f800000u, 0xc0400000u, 0x7f800000u, 0xc0400000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x00000000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u}, + {0x7f800001u, 0x7f800001u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800001u, 0x7f800001u}, + {0x7f800001u, 0x7f800001u, 0x80000000u, 0x80000000u, 0x80000000u, 0x7f800001u, 0x7f800001u}, + {0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0x80000000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xc0400000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xff812345u, 0xff812345u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff812345u, 0xff812345u}, + {0xff812345u, 0xff812345u, 0xff812345u, 0xff812345u, 0xff812345u, 0xff812345u, 0xff812345u}, + {0xff812345u, 0xff812345u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0xff812345u, 0xff812345u}, + {0xff812345u, 0xff812345u, 0x80000000u, 0x80000000u, 0x80000000u, 0xff812345u, 0xff812345u}, + {0x3f800000u, 0x40200000u, 0x00000000u, 0x40200000u, 0x3f800000u, 0x3f800000u, 0x40200000u}, + {0x00800000u, 0x3f800000u, 0x00800000u, 0x3f800000u, 0x3f800000u, 0x00800000u, 0x3f800000u}, + {0xc0400000u, 0xbf800000u, 0xc0400000u, 0x00000000u, 0xbf800000u, 0xc0400000u, 0xbf800000u}, + {0xc0400000u, 0xbf800000u, 0xc0400000u, 0x3f800000u, 0xbf800000u, 0xc0400000u, 0xbf800000u}, + {0x7fc00000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u, 0x7fc00000u}, + {0xffc00000u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc00000u, 0xffc00000u}, + {0x7f800001u, 0x7f800001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f800001u, 0x7f800001u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x3f800000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x7f800000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x00000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u} +}; +constexpr std::uint32_t Chunks = 4; +std::uint32_t Chunk = 0; +constexpr const char* Names[7] = { + "v_min_f32 v10, v4, v5", + "v_max_f32 v11, v4, v5", + "v_min3_f32 v12, v4, v5, v6", + "v_max3_f32 v13, v4, v5, v6", + "v_med3_f32 v14, v4, v5, v6", + "v_min_f32_e64 v15, v4, v5", + "v_max_f32_e64 v16, v4, v5", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[Chunk * Threads + tid]), std::end(Rows[Chunk * Threads + tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f32 min max nan: row ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 7; ++i) { + Expect(Chunk * Threads + tid, out[i], Expected[Chunk * Threads + tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + for (Chunk = 0; Chunk < Chunks; ++Chunk) { + Run(*device); + Check(); + } + std::puts("f32 min max nan tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32OutputModifier.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32OutputModifier.cpp new file mode 100644 index 000000000..fe668ec80 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32OutputModifier.cpp @@ -0,0 +1,191 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd508000a, 0x0a020b04, 0xd508000b, + 0x1a020b04, 0xd508000c, 0x12020b04, 0xd503000d, 0x0a020b04, 0xd503000e, 0x1a020b04, 0xd504000f, + 0x32020b04, 0xd54b0010, 0x0c1a0b04, 0xd54b0011, 0x1c1a0b04, 0xd50f0012, 0x1a020b04, 0xd5100013, + 0x0a020d04, 0xd5a40014, 0x1a010104, 0xd5850015, 0x12010104, 0xd5088016, 0x0a020b04, 0xd5088017, + 0x1a020b04, 0xd5038018, 0x12020d04, 0xd54b0019, 0x341a0b04, 0xe0781000, 0x80010a03, 0xe0781010, + 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x80000000u, 0x3f800000u, 0x80000000u, 0x00000000u}, + {0x80800000u, 0x3f800000u, 0x80800000u, 0x00000000u}, + {0x00ffffffu, 0x3f800000u, 0x80ffffffu, 0x00000000u}, + {0x7f7fffffu, 0x40000000u, 0x7f7fffffu, 0x00000000u}, + {0x7fc12345u, 0xffc00000u, 0xff800001u, 0x00000000u}, + {0x7fc12345u, 0x3f800000u, 0x7fc12345u, 0x00000000u}, + {0x7f800001u, 0x40000000u, 0x7f800001u, 0x00000000u}, + {0xff812345u, 0xbf800000u, 0xff812345u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u}, + {0x3f800000u, 0xff000000u, 0x00000000u, 0x00000000u}, + {0x3f000000u, 0xbf800000u, 0x7f800000u, 0x00000000u}, + {0xff800000u, 0x3f000000u, 0x7fc00000u, 0x00000000u}, + {0xff000000u, 0xbf800000u, 0xbf800000u, 0x00000000u}, + {0x3f800000u, 0x01000000u, 0x3f000000u, 0x00000000u}, + {0x00000000u, 0x00800000u, 0x00800000u, 0x00000000u}, + {0x00000000u, 0x80800000u, 0x80800000u, 0x00000000u}, + {0x7f7fffffu, 0x3f000000u, 0x00000000u, 0x00000000u}, + {0x7f800000u, 0x7f800000u, 0x00000000u, 0x00000000u}, + {0x3f000000u, 0xff000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0xff000000u, 0x7f7fffffu, 0x7fc00000u, 0x00000000u}, + {0x00000000u, 0x01000000u, 0x01000000u, 0x00000000u}, + {0x7fc00000u, 0x7f7fffffu, 0x00000000u, 0x00000000u}, + {0xbf800000u, 0x01000000u, 0x00000000u, 0x00000000u}, + {0x80800000u, 0x80800000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xffc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc12345u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800000u, 0x00000000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x3f000000u, 0xc0800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xd0000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x81000000u, 0x80000000u, 0x81800000u, 0x40000000u, 0x3f000000u, 0xc0800000u, 0x81800000u, 0x80800000u, 0x80000000u, 0x81000000u, 0xbf000000u, 0xcfff0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x017fffffu, 0x00000000u, 0x01ffffffu, 0x40000000u, 0x3f000000u, 0xc0800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x017fffffu, 0x00000000u, 0x4c7fffffu, 0x017fffffu, 0x00000000u, 0x00000000u, 0x827fffffu}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7effffffu, 0xff800000u, 0x7f800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u, 0x7effffffu, 0x4fff0000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0xff800000u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x4fff8247u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc12345u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x7fc12345u, 0x3f000000u, 0x7fc12345u, 0x7fc12345u, 0x4fff8247u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc12345u}, + {0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0xff800001u, 0x7f800001u, 0x7f800001u, 0x3f800000u, 0x7f800001u, 0x7f800001u, 0x4fff0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800001u}, + {0xff812345u, 0xff812345u, 0xff812345u, 0xff812345u, 0xff812345u, 0x7f812345u, 0xff812345u, 0xff812345u, 0xbf000000u, 0xff812345u, 0xff812345u, 0xcbfdb976u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f812345u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800000u}, + {0xff800000u, 0xfe800000u, 0xff800000u, 0xff800000u, 0xfe800000u, 0x7f800000u, 0xff800000u, 0xfe800000u, 0xfe800000u, 0x40000000u, 0x3f000000u, 0x4f7e0000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x7f800000u}, + {0xbf800000u, 0xbe800000u, 0xc0000000u, 0xbf800000u, 0xbe800000u, 0x40000000u, 0x7f800000u, 0x7f800000u, 0xbf000000u, 0x7f800000u, 0x00000000u, 0x4f7c0000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0x7f800000u, 0x7fc00000u, 0x7fc00000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xcc000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u}, + {0x7f800000u, 0x7e800000u, 0x7f800000u, 0xff800000u, 0xfe800000u, 0x7f800000u, 0x7f800000u, 0x7e800000u, 0xfe800000u, 0xc0000000u, 0xfe800000u, 0xcc800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0xff800000u}, + {0x01800000u, 0x00800000u, 0x02000000u, 0x40000000u, 0x3f000000u, 0xc0800000u, 0x3f800000u, 0x3e800000u, 0x00800000u, 0x40000000u, 0x3f000000u, 0x4f7e0000u, 0x01800000u, 0x00800000u, 0x3f800000u, 0x40000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00000000u, 0x81800000u, 0x01000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x01800000u, 0x01800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x81000000u, 0x80000000u, 0x01800000u, 0x81000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x81800000u}, + {0x7f7fffffu, 0x7e7fffffu, 0x7f800000u, 0x7f800000u, 0x7effffffu, 0xff800000u, 0x7f7fffffu, 0x7e7fffffu, 0x3e800000u, 0x7f800000u, 0x7effffffu, 0x4fff0000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0xff800000u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x4fff0000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0xff800000u}, + {0xff000000u, 0xfe000000u, 0xff800000u, 0xff800000u, 0xfe800000u, 0x7f800000u, 0xff000000u, 0xfe000000u, 0xfe800000u, 0x3f800000u, 0x00000000u, 0x4f7c0000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x7f800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xd0000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x7f7ffffeu, 0x7e7ffffeu, 0xff800000u, 0x7fc00000u, 0x7fc00000u, 0xfe800000u, 0xff800000u, 0xfe800000u, 0xcc800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x01800000u, 0x00800000u, 0x82000000u, 0x01800000u, 0x00800000u, 0x00000000u, 0x01800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x02000000u, 0x02000000u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0xffc00000u, 0x7fc00000u, 0x7fc00000u, 0x7effffffu, 0x00000000u, 0x7fc00000u, 0x4fff8000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc00000u}, + {0x81800000u, 0x80800000u, 0x82000000u, 0xc0000000u, 0xbf000000u, 0x40800000u, 0x81800000u, 0x80800000u, 0xbf000000u, 0x00000000u, 0xbf000000u, 0xcf810000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x02000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x81800000u, 0x80800000u, 0x02000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xbf000000u, 0xcfff0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc00000u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f800001u, 0x7f800001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f800001u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc12345u, 0x7fc12345u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc12345u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f800000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x7f800000u} +}; +constexpr std::uint32_t Checked[32] = {0xffffu, 0xffffu, 0xffffu, 0x7f3fu, 0x7800u, 0x7b00u, 0x7b00u, 0x7b00u, 0xffffu, 0xffffu, 0xffffu, 0x7f3fu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0x7f3fu, 0xffffu, 0xffffu, 0x7f3fu, 0xffffu, 0x7b00u, 0xffffu, 0x7f3fu, 0xffffu, 0xffffu, 0x7f3fu, 0x7f3fu, 0x7f3fu, 0x7f3fu, 0xffffu}; +constexpr std::uint32_t AnyNan[32] = {0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x84ffu, 0x84ffu, 0x84ffu, 0x84ffu, 0x0000u, 0x0000u, 0x0000u, 0x80c0u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x80c0u, 0x0000u, 0x84ffu, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x80c0u, 0x80c0u, 0x80c0u, 0x80c0u, 0x0000u}; +constexpr const char* Names[16] = { + "v_mul_f32_e64 v10, v4, v5 mul:2", + "v_mul_f32_e64 v11, v4, v5 div:2", + "v_mul_f32_e64 v12, v4, v5 mul:4", + "v_add_f32_e64 v13, v4, v5 mul:2", + "v_add_f32_e64 v14, v4, v5 div:2", + "v_sub_f32_e64 v15, -v4, v5 mul:4", + "v_fma_f32 v16, v4, v5, v6 mul:2", + "v_fma_f32 v17, v4, v5, v6 div:2", + "v_min_f32_e64 v18, v4, v5 div:2", + "v_max_f32_e64 v19, v4, v6 mul:2", + "v_floor_f32_e64 v20, v4 div:2", + "v_cvt_f32_i32_e64 v21, v4 mul:4", + "v_mul_f32_e64 v22, v4, v5 clamp mul:2", + "v_mul_f32_e64 v23, v4, v5 clamp div:2", + "v_add_f32_e64 v24, v4, v6 clamp mul:4", + "v_fma_f32 v25, -v4, v5, v6 mul:4", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f32 output modifier: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + if ((Checked[tid] >> i) & 1u) Expect(tid, out[i], Expected[tid][i], Names[i]); + if ((AnyNan[tid] >> i) & 1u) Require((out[i] & 0x7fffffffu) > 0x7f800000u, std::string("f32 output modifier: lane ") + std::to_string(tid) + " " + Names[i] + " is " + Hex(out[i]) + ", expected a NaN"); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("f32 output modifier tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32ResultClamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32ResultClamp.cpp new file mode 100644 index 000000000..9833bcc22 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32ResultClamp.cpp @@ -0,0 +1,189 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1e0306, 0xd503800a, 0x02020b04, + 0xd504800b, 0x02020b04, 0xd508800c, 0x02020b04, 0xd508800d, 0x22020b04, 0xd54b800e, 0x041a0b04, + 0xd52b800f, 0x02020b04, 0xd50f8010, 0x02020b04, 0xd5108011, 0x22020b04, 0xd5578012, 0x041a0b04, + 0xd5a48013, 0x02010104, 0xd5a08014, 0x02010104, 0xd5858015, 0x02010104, 0xd5a18016, 0x22010104, + 0xd5a38017, 0x02010104, 0xd5548018, 0x041a0b04, 0xd5a28019, 0x22010104, 0xe0781000, 0x80010a03, + 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x7f800000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0xbf000000u, 0x3f800000u, 0xff800000u, 0x00000000u}, + {0xff800000u, 0xff800000u, 0x80000000u, 0x00000000u}, + {0x3f800000u, 0x3f000000u, 0x3f800001u, 0x00000000u}, + {0xc0200000u, 0x00000000u, 0x3f000000u, 0x00000000u}, + {0x40000000u, 0xbf800000u, 0x3f000000u, 0x00000000u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x40000000u, 0x3f800000u, 0xbf800000u, 0x00000000u}, + {0xbf800000u, 0x7f800000u, 0x7f800000u, 0x00000000u}, + {0x7fc00000u, 0xff800000u, 0x7fc00000u, 0x00000000u}, + {0x7f800000u, 0x3f800001u, 0x00000000u, 0x00000000u}, + {0xbf800000u, 0xbf800000u, 0x7fc00000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x7fc00000u, 0x7f800000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0xbf800000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xffc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc12345u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f800001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xbf000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f7fffffu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xbf800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xc0200000u, 0x00000000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x3f000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x3f800000u, 0x3f000000u, 0x3f000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x3f000000u, 0x3f000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x3f000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f7fffffu, 0x3f7fffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f7fffffu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u} +}; +constexpr std::uint32_t Checked[32] = {0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xfeffu, 0xffffu, 0xffffu, 0xffffu, 0xfeffu, 0xffffu, 0xfeffu, 0xffffu, 0xfeffu, 0xffffu, 0xffffu, 0xfeffu, 0xfeffu, 0xbeffu, 0xfeffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu}; +constexpr const char* Names[16] = { + "v_add_f32_e64 v10, v4, v5 clamp", + "v_sub_f32_e64 v11, v4, v5 clamp", + "v_mul_f32_e64 v12, v4, v5 clamp", + "v_mul_f32_e64 v13, -v4, v5 clamp", + "v_fma_f32 v14, v4, v5, v6 clamp", + "v_fmac_f32_e64 v15, v4, v5 clamp", + "v_min_f32_e64 v16, v4, v5 clamp", + "v_max_f32_e64 v17, -v4, v5 clamp", + "v_med3_f32 v18, v4, v5, v6 clamp", + "v_floor_f32_e64 v19, v4 clamp", + "v_fract_f32_e64 v20, v4 clamp", + "v_cvt_f32_i32_e64 v21, v4 clamp", + "v_trunc_f32_e64 v22, -v4 clamp", + "v_rndne_f32_e64 v23, v4 clamp", + "v_max3_f32 v24, v4, v5, v6 clamp", + "v_ceil_f32_e64 v25, -v4 clamp", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f32 result clamp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + if ((Checked[tid] >> i) & 1u) Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("f32 result clamp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F64Division.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F64Division.cpp new file mode 100644 index 000000000..634a3ed4e --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F64Division.cpp @@ -0,0 +1,172 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd56e6a0a, 0x041a0904, 0xd5010018, + 0x01a90280, 0xd56e6a0c, 0x041a0906, 0xd5010019, 0x01a90480, 0x38303318, 0xbeea03c1, 0xd570000e, + 0x04120d04, 0xd5700010, 0x041a0d04, 0xd5600012, 0x04120d04, 0xd5600014, 0x041a0906, 0xd5600016, + 0x041a08ff, 0x7ff80000, 0x7e320280, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000001u, 0x7ff00000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x7ff40000u, 0x00000000u, 0x3ff00000u}, + {0xe61541b6u, 0xcdde1df6u, 0x652b0ed7u, 0xfddc36e5u}, + {0xc3d4a267u, 0x03d31de5u, 0x08426379u, 0xb3cb953cu}, + {0xceb52fc3u, 0xb3648a3fu, 0xd8fe4338u, 0x8367c969u}, + {0x131c99fcu, 0x7f65f30fu, 0x888c6e8bu, 0xcf72c7f8u}, + {0x00000000u, 0x7fd00000u, 0x00000000u, 0x3ca00000u}, + {0xffffffffu, 0x7fefffffu, 0x00000000u, 0x3ca00000u}, + {0x00000000u, 0x7fd00000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x7fc00000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x00100000u, 0x00000000u, 0x03500000u}, + {0x00000000u, 0x00100000u, 0x00000000u, 0x03600000u}, + {0x8a41dfb1u, 0xbffc3c7cu, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xbff00000u, 0x00000000u, 0xfff00000u}, + {0x00000000u, 0x7ff00000u, 0xffffffffu, 0x7fefffffu}, + {0x00000001u, 0xfff80000u, 0x00000000u, 0x7ff80000u}, + {0x00000000u, 0x00000000u, 0x00000001u, 0xfff80000u}, + {0x00000000u, 0x3ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00100000u}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0xffefffffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u}, + {0x00000000u, 0x00000000u, 0x00000001u, 0x7ff00000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000001u, 0x7ff00000u, 0x00000000u, 0x3ff00000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0x7ff40000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000003u, 0x00000000u}, + {0xe61541b6u, 0xd5de1df6u, 0x652b0ed7u, 0xfddc36e5u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0xe61541b6u, 0x4dde1df6u, 0x652b0ed7u, 0x7ddc36e5u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0xc3d4a267u, 0x03d31de5u, 0x08426379u, 0xb3cb953cu, 0x00000000u, 0x00000000u, 0x08426379u, 0xabcb953cu, 0xc3d4a267u, 0x83d31de5u, 0x08426379u, 0xb3cb953cu, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u}, + {0xceb52fc3u, 0xb3648a3fu, 0xd8fe4338u, 0x8b67c969u, 0xceb52fc3u, 0xab648a3fu, 0x00000000u, 0x80000000u, 0xceb52fc3u, 0x33648a3fu, 0xd8fe4338u, 0x0367c969u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0x131c99fcu, 0x7f65f30fu, 0x888c6e8bu, 0xcf72c7f8u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x131c99fcu, 0xff65f30fu, 0x888c6e8bu, 0xcf72c7f8u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x77d00000u, 0x00000000u, 0x3ca00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x74800000u, 0x00000000u, 0x7fd00000u, 0x00000000u, 0x3ca00000u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0xffffffffu, 0x77efffffu, 0x00000000u, 0x3ca00000u, 0x00000000u, 0x7ff00000u, 0xffffffffu, 0x749fffffu, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0x77d00000u, 0xffffffffu, 0x77efffffu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7fd00000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x7fc00000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7fc00000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x08100000u, 0x00000000u, 0x0b500000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00100000u, 0x00000000u, 0x03500000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00100000u, 0x00000000u, 0x03600000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00100000u, 0x00000000u, 0x03600000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x8a41dfb1u, 0xc7fc3c7cu, 0xffffffffu, 0x7fefffffu, 0x8a41dfb0u, 0xf7fc3c7cu, 0x00000000u, 0xfff00000u, 0x8a41dfb1u, 0xbffc3c7cu, 0xffffffffu, 0xffefffffu, 0x00000000u, 0xfff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xc7f00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0x7ff00000u, 0xffffffffu, 0x77efffffu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000001u, 0xfff80000u, 0x00000000u, 0x7ff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x37f00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x37f00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xb7f00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000003u, 0x00000000u} +}; +constexpr const char* Names[7] = {"div_scale denominator", "div_scale numerator", "div_fmas a*b+a", "div_fmas a*b+b", "div_fixup a", "div_fixup b", "div_fixup nan"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f64 division: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 7; ++i) { + Expect(tid, out[2 * i], Expected[tid][2 * i], Names[i]); + Expect(tid, out[2 * i + 1], Expected[tid][2 * i + 1], Names[i]); + } + Expect(tid, out[14], Expected[tid][14], "div_scale vcc"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("f64 division tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F64DivisionModifiers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F64DivisionModifiers.cpp new file mode 100644 index 000000000..645f47a10 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F64DivisionModifiers.cpp @@ -0,0 +1,172 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd56e6a0a, 0x641a0904, 0xd5010018, + 0x01a90280, 0xd56e6a0c, 0x841a0906, 0xd5010019, 0x01a90480, 0x38303318, 0xbeea03c1, 0xd570060e, + 0xa4120d04, 0xd5700010, 0x04120d06, 0xbeea0380, 0xd5700012, 0x041a0d04, 0xd5600514, 0x64120d04, + 0x36320881, 0x7d8a3280, 0xd5700016, 0x041a0906, 0x7e320280, 0xe0781000, 0x80010a03, 0xe0781010, + 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000001u, 0x7ff00000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x7ff40000u, 0x00000000u, 0x3ff00000u}, + {0xe61541b6u, 0xcdde1df6u, 0x652b0ed7u, 0xfddc36e5u}, + {0xc3d4a267u, 0x03d31de5u, 0x08426379u, 0xb3cb953cu}, + {0xceb52fc3u, 0xb3648a3fu, 0xd8fe4338u, 0x8367c969u}, + {0x131c99fcu, 0x7f65f30fu, 0x888c6e8bu, 0xcf72c7f8u}, + {0x00000000u, 0x7fd00000u, 0x00000000u, 0x3ca00000u}, + {0xffffffffu, 0x7fefffffu, 0x00000000u, 0x3ca00000u}, + {0x00000000u, 0x7fd00000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x7fc00000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x00100000u, 0x00000000u, 0x03500000u}, + {0x00000000u, 0x00100000u, 0x00000000u, 0x03600000u}, + {0x00000000u, 0x7ff00000u, 0x00000001u, 0xfff80000u}, + {0xffffffffu, 0xffefffffu, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00100000u, 0x00000000u, 0x80000000u}, + {0x00000001u, 0xfff80000u, 0x00000000u, 0x7ff80000u}, + {0x00000000u, 0x3ff00000u, 0x00000000u, 0xfff00000u}, + {0x00000000u, 0xbff00000u, 0xffffffffu, 0x7fefffffu}, + {0xb0894f5au, 0x80834975u, 0x4c867062u, 0x0376ec15u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00100000u}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0x7fefffffu}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0xffefffffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7ff80000u}, + {0x00000000u, 0x00000000u, 0x00000001u, 0xfff80000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000001u, 0xfff00000u, 0x00000000u, 0x3ff00000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff40000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0xfffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000003u, 0x00000000u}, + {0xe61541b6u, 0x55de1df6u, 0x652b0ed7u, 0xfddc36e5u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0xe61541b6u, 0x4dde1df6u, 0x00000000u, 0x7ff00000u, 0x00000003u, 0x00000000u}, + {0xc3d4a267u, 0x83d31de5u, 0x08426379u, 0xb3cb953cu, 0x00000000u, 0x80000000u, 0x456d9116u, 0x1fa7c68du, 0x08426379u, 0xb3cb953cu, 0xc3d4a267u, 0x03d31de5u, 0x08426379u, 0xabcb953cu, 0x00000000u, 0x00000000u}, + {0xceb52fc3u, 0x33648a3fu, 0xd8fe4338u, 0x8b67c969u, 0xceb52fc3u, 0xab648a3fu, 0xceb52fc3u, 0xab648a3fu, 0xd8fe4338u, 0x8367c969u, 0xceb52fc3u, 0x33648a3fu, 0x00000000u, 0x80000000u, 0x00000003u, 0x00000000u}, + {0x131c99fcu, 0xff65f30fu, 0x888c6e8bu, 0xcf72c7f8u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff00000u, 0x131c99fcu, 0x7f65f30fu, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xf7d00000u, 0x00000000u, 0x3ca00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7c800000u, 0x00000000u, 0xffd00000u, 0x00000000u, 0x7c800000u, 0x00000003u, 0x00000000u}, + {0xffffffffu, 0xf7efffffu, 0x00000000u, 0x3ca00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0xffffffffu, 0x7c9fffffu, 0xffffffffu, 0xffefffffu, 0xffffffffu, 0x749fffffu, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xf7d00000u, 0xffffffffu, 0x77efffffu, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xffd00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xffc00000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xffc00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x88100000u, 0x00000000u, 0x0b500000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x03500000u, 0x00000000u, 0x80100000u, 0x00000000u, 0x03500000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x80100000u, 0x00000000u, 0x03600000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x03600000u, 0x00000000u, 0x80100000u, 0x00000000u, 0x03600000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff00000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0xfff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x37f00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3ff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000001u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xc7f00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0x47f00000u, 0xffffffffu, 0x7fefffffu, 0xffffffffu, 0x77efffffu, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x00000000u}, + {0xb0894f5au, 0x00834975u, 0x4c867062u, 0x0376ec15u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x4c867062u, 0x0376ec15u, 0xb0894f5au, 0x80834975u, 0x4c867062u, 0x0376ec15u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x37f00000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3ff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x37f00000u, 0x00000000u, 0xbff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbff00000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00100000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00100000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x7ff00000u, 0xffffffffu, 0x7fefffffu, 0x00000000u, 0x80000000u, 0xffffffffu, 0x7fefffffu, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x7ff00000u, 0xffffffffu, 0xffefffffu, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffefffffu, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xfff80000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0x7ff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x7ff80000u, 0x00000003u, 0x00000000u}, + {0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0xfff80000u, 0x00000003u, 0x00000000u} +}; +constexpr const char* Names[7] = {"div_scale -a", "div_scale -b", "div_fmas modifiers", "div_fmas b*b+a", "div_fmas vcc 0", "div_fixup modifiers", "div_fmas vcc per lane"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f64 division modifiers: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 7; ++i) { + Expect(tid, out[2 * i], Expected[tid][2 * i], Names[i]); + Expect(tid, out[2 * i + 1], Expected[tid][2 * i + 1], Names[i]); + } + Expect(tid, out[14], Expected[tid][14], "div_scale vcc"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("f64 division modifiers tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F64Transcendental.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F64Transcendental.cpp new file mode 100644 index 000000000..f4e6cc55e --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F64Transcendental.cpp @@ -0,0 +1,190 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e145f04, 0x7e186304, 0x7e1c6904, + 0xd5740010, 0x02020d04, 0xd5740012, 0x02010104, 0xd5740114, 0x2a020d04, 0xd5af8016, 0x02010104, + 0xd5b40118, 0x22010104, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, + 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000001u, 0x7ff00000u, 0xcad921b4u, 0x00000000u}, + {0x00000000u, 0x7ff40000u, 0x0000001fu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000025u, 0x80000000u}, + {0xe0000000u, 0x47efffffu, 0x0000001eu, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x0000001bu, 0x00000000u}, + {0x00000000u, 0xbff00000u, 0x0000001au, 0x00000000u}, + {0x00000000u, 0x7ff00000u, 0x01c32149u, 0x00000000u}, + {0x00000000u, 0xfff00000u, 0x00000012u, 0x00000000u}, + {0x00000001u, 0xfff80000u, 0x00000012u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3ceb3ffdu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x8b529b4au, 0x3ff00000u}, + {0x00000000u, 0x00000000u, 0x00000008u, 0xbff00000u}, + {0x00000000u, 0x00000000u, 0x5eb561a4u, 0x3fe00000u}, + {0x00000000u, 0x00000000u, 0x00000026u, 0xbfe00000u}, + {0x00000000u, 0x00000000u, 0x795b929eu, 0x3ff80000u}, + {0x00000000u, 0x00000000u, 0x00000028u, 0x40040000u}, + {0x00000000u, 0x00000000u, 0x94b2b8fdu, 0xc0040000u}, + {0x00000000u, 0x00000000u, 0x00000004u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x9b08923du, 0x800fffffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00100000u}, + {0x00000000u, 0x00000000u, 0xe8a8529fu, 0x7fefffffu}, + {0x00000000u, 0x00000000u, 0x0000001eu, 0xffefffffu}, + {0x00000000u, 0x00000000u, 0x42650644u, 0x7ff00000u}, + {0x00000000u, 0x00000000u, 0x00000023u, 0xfff00000u}, + {0x00000000u, 0x00000000u, 0x3bfd1d33u, 0x7ff80000u}, + {0x00000000u, 0x00000000u, 0x0000000cu, 0xfff80000u}, + {0x00000000u, 0x00000000u, 0xfee29476u, 0x7ff00000u}, + {0x00000000u, 0x00000000u, 0x0000001eu, 0x7ff40000u}, + {0x00000000u, 0x00000000u, 0x8a7d43b5u, 0x41e00000u}, + {0x00000000u, 0x00000000u, 0x00000023u, 0xc1e00000u}, + {0x00000000u, 0x00000000u, 0x79f248b0u, 0xc1e00000u}, + {0x00000000u, 0x00000000u, 0x00000019u, 0x41dfffffu} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000001u, 0x7ff80000u, 0x00000000u, 0x00000000u, 0xf5f2f8bdu, 0x0b43dd63u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0xfff80000u}, + {0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x7ffc0000u, 0x00000000u, 0x00000000u, 0xf5f2f8bdu, 0x0b43dd63u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffc0000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x2126e970u, 0x2f42371du, 0x6dc9c882u, 0x3fe45f30u, 0x2126e970u, 0x2f42371du, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x1af80000u, 0x37f00000u, 0x039e0000u, 0x3bf00000u, 0xed080000u, 0x43efffffu, 0x00000000u, 0x00000000u, 0xfa9a6ee0u, 0x3b43abe8u, 0x00000000u, 0x00000000u, 0x1af80000u, 0x37f00000u, 0x00000000u, 0xfff80000u}, + {0x00000000u, 0xfff00000u, 0x00000000u, 0xfff00000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0xbff00000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xf5f2f8bdu, 0x0b43dd63u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0xfff80000u, 0x00000000u, 0x00000000u, 0xf5f2f8bdu, 0x0b43dd63u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfff80000u}, + {0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000001u, 0xfff80000u, 0x00000000u, 0x00000000u, 0xf5f2f8bdu, 0x0b43dd63u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0xfff80000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x2fbf209cu, 0x1ec8135au, 0x6dc9c882u, 0x3fe45f30u, 0x2fbf209cu, 0x1ec8135au, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x74411af8u, 0x2544d39fu, 0x6dc9c882u, 0x3fe45f30u, 0x74411af8u, 0x2544d39fu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xde2b0db8u, 0x328d5ef5u, 0x6dc9c882u, 0x3fe45f30u, 0xde2b0db8u, 0x328d5ef5u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xa8274600u, 0x2b9924bbu, 0x6dc9c882u, 0x3fe45f30u, 0xa8274600u, 0x2b9924bbu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x74411af8u, 0x2544d39fu, 0x6dc9c882u, 0x3fe45f30u, 0x74411af8u, 0x2544d39fu, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xde2b0db8u, 0x328d5ef5u, 0x6dc9c882u, 0x3fe45f30u, 0xde2b0db8u, 0x328d5ef5u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x6dc9c882u, 0x3fe45f30u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xde2b0db8u, 0x328d5ef5u, 0x6dc9c882u, 0x3fe45f30u, 0xde2b0db8u, 0x328d5ef5u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x041fe516u, 0x35f3c439u, 0x6dc9c882u, 0x3fe45f30u, 0x041fe516u, 0x35f3c439u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xd0739f78u, 0x00ff17b3u, 0x6dc9c882u, 0x3fe45f30u, 0xd0739f78u, 0x00ff17b3u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x7e2ef7e4u, 0x18127211u, 0x6dc9c882u, 0x3fe45f30u, 0x7e2ef7e4u, 0x18127211u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x041fe516u, 0x35f3c439u, 0x6dc9c882u, 0x3fe45f30u, 0x041fe516u, 0x35f3c439u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0xf2c26dd2u, 0x0adfb3c9u, 0x6dc9c882u, 0x3fe45f30u, 0xf2c26dd2u, 0x0adfb3c9u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x7ff00000u, 0x00000000u, 0x7ff00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6dc9c882u, 0x3fe45f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ff00000u, 0x00000000u, 0x80000000u} +}; +constexpr const char* Names[8] = {"rcp", "rsq", "sqrt", "trig preop", "trig preop inline", "trig preop abs neg omod", "rcp clamp", "sqrt abs neg"}; +constexpr bool Approximate[8] = {true, true, true, false, false, false, true, true}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +double F64(const std::uint32_t* words) { + return std::bit_cast(static_cast(words[1]) << 32u | words[0]); +} + +bool Normal(const std::uint32_t* words) { + const auto exponent = (words[1] >> 20u) & 0x7ffu; + return exponent != 0u && exponent != 0x7ffu; +} + +void ExpectNear(std::uint32_t tid, const std::uint32_t* actual, const std::uint32_t* expected, const char* name) { + const auto error = std::abs(F64(actual) - F64(expected)) / std::abs(F64(expected)); + Require(error <= 0x1p-23, std::string("f64 transcendental: lane ") + std::to_string(tid) + " " + name + " is " + std::to_string(F64(actual)) + ", expected " + std::to_string(F64(expected))); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f64 transcendental: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 8; ++i) { + const std::uint32_t* expected = &Expected[tid][2 * i]; + if (Approximate[i] && Normal(expected)) { + ExpectNear(tid, out + 2 * i, expected, Names[i]); + continue; + } + Expect(tid, out[2 * i], expected[0], Names[i]); + Expect(tid, out[2 * i + 1], expected[1], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("f64 transcendental tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp new file mode 100644 index 000000000..717f9f028 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatCacheBits.cpp @@ -0,0 +1,177 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 4; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array FlatCacheBitsCode{ + 0x34020084, 0x34040084, 0xd70f6a1e, 0x02020208, 0x7e3e0209, 0x503e3e80, 0xd70f6a1e, 0x02023c8c, + 0x503e3e80, 0xdc379000, 0x0a080001, 0xdc331000, 0x0d7d001e, 0xbf8c0070, 0xdc739004, 0x00080d01, + 0xdccb9008, 0x0c080a01, 0xdc731000, 0x007d0b1e, 0xbf8c0070, 0xe0781000, 0x80010a02, 0xbf810000 +}; + +struct Row { + std::array memory; + std::array results; + std::array final; +}; + +constexpr std::array Rows{{ + {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x7fffffff, 0x00000081, 0x0000007f}}, + {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x80ff7f01, 0x8000007f, 0x000000ff}}, + {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x000000ff, 0xdeadbf6f, 0x80000000}}, + {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0xdeadbeef, 0x00000100, 0x12345678}}, + {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x00000080, 0x800000fe, 0x80ff7f01}}, + {{0x80000000, 0x00000001, 0xffffffff, 0x12345678}, {0x80000000, 0x00000001, 0xffffffff, 0x12345678}, {0x80000000, 0x12345678, 0x7fffffff, 0x00000001}}, + {{0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f}, {0xffffffff, 0x00000080, 0x80ff7f01, 0x0000007f}, {0xffffffff, 0x0000007f, 0x80ff7f00, 0x00000080}}, + {{0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff}, {0x12345678, 0x7fffffff, 0x0000007f, 0xffffffff}, {0x12345678, 0xffffffff, 0x123456f7, 0x7fffffff}}, + {{0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001}, {0xdeadbeef, 0xffffffff, 0x7fffffff, 0x00000001}, {0xdeadbeef, 0x00000001, 0x5eadbeee, 0xffffffff}}, + {{0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000}, {0x80ff7f01, 0xdeadbeef, 0x12345678, 0x80000000}, {0x80ff7f01, 0x80000000, 0x9333d579, 0xdeadbeef}}, + {{0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x00000000, 0x00000000, 0x00000000, 0x00000000}, {0x00000000, 0x00000000, 0x00000000, 0x00000000}}, + {{0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x0000007f, 0x00000080, 0x7fffffff}, {0x00000001, 0x7fffffff, 0x00000081, 0x0000007f}}, + {{0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x000000ff, 0x80000000, 0x80ff7f01}, {0x0000007f, 0x80ff7f01, 0x8000007f, 0x000000ff}}, + {{0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x80000000, 0xdeadbeef, 0x000000ff}, {0x00000080, 0x000000ff, 0xdeadbf6f, 0x80000000}}, + {{0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0x12345678, 0x00000001, 0xdeadbeef}, {0x000000ff, 0xdeadbeef, 0x00000100, 0x12345678}}, + {{0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x80ff7f01, 0x000000ff, 0x00000080}, {0x7fffffff, 0x00000080, 0x800000fe, 0x80ff7f01}}, + {{0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30}, {0x2265b1f5, 0x91b7584a, 0xd8f16adf, 0xcd613e30}, {0x2265b1f5, 0xcd613e30, 0xfb571cd4, 0x91b7584a}}, + {{0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89}, {0xc386bbc4, 0x1027c4d1, 0x414c343c, 0x1e2feb89}, {0xc386bbc4, 0x1e2feb89, 0x04d2f000, 0x1027c4d1}}, + {{0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061}, {0x7ed4d57b, 0xc2ce6f44, 0x7311d8a3, 0x78e51061}, {0x7ed4d57b, 0x78e51061, 0xf1e6ae1e, 0xc2ce6f44}}, + {{0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d}, {0xa6cecc1b, 0x612e7696, 0xc9e9c616, 0x35bf992d}, {0xa6cecc1b, 0x35bf992d, 0x70b89231, 0x612e7696}}, + {{0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6}, {0x18072e8c, 0x7ce42c82, 0x0741c7a8, 0xe4b06ce6}, {0x18072e8c, 0xe4b06ce6, 0x1f48f634, 0x7ce42c82}}, + {{0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76}, {0xd5f4b3b2, 0x63ca828d, 0x6ec9d286, 0x9b810e76}, {0xd5f4b3b2, 0x9b810e76, 0x44be8638, 0x63ca828d}}, + {{0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58}, {0xc324c985, 0xc4647159, 0x008a05a6, 0xb2221a58}, {0xc324c985, 0xb2221a58, 0xc3aecf2b, 0xc4647159}}, + {{0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35}, {0x7204e52d, 0x442e3d43, 0xb8b6d8fe, 0xcd447e35}, {0x7204e52d, 0xcd447e35, 0x2abbbe2b, 0x442e3d43}}, + {{0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f}, {0x3a902931, 0x9755d4c1, 0xf1fd42a2, 0x1a2b8f1f}, {0x3a902931, 0x1a2b8f1f, 0x2c8d6bd3, 0x9755d4c1}}, + {{0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3}, {0xe6c3f339, 0x51431193, 0x07d4bedc, 0x05b6e6e3}, {0xe6c3f339, 0x05b6e6e3, 0xee98b215, 0x51431193}}, + {{0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f}, {0x06839eb9, 0xa648a7dd, 0x8a9a021e, 0x025b413f}, {0x06839eb9, 0x025b413f, 0x911da0d7, 0xa648a7dd}}, + {{0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9}, {0xf06c144a, 0xe1988ad9, 0x619699cf, 0xafbd67f9}, {0xf06c144a, 0xafbd67f9, 0x5202ae19, 0xe1988ad9}}, + {{0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0}, {0x37730edf, 0xf8130c42, 0x6c0fd4f5, 0xb9d179e0}, {0x37730edf, 0xb9d179e0, 0xa382e3d4, 0xf8130c42}}, + {{0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f}, {0x076f3787, 0x8712b8bc, 0x38c0c8fd, 0xc381e88f}, {0x076f3787, 0xc381e88f, 0x40300084, 0x8712b8bc}}, + {{0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a}, {0x701966a0, 0xf06d3fef, 0x7eed8d14, 0x8d88348a}, {0x701966a0, 0x8d88348a, 0xef06f3b4, 0xf06d3fef}}, + {{0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d}, {0x3bab6c39, 0x587fd280, 0x3b1a11df, 0xad45f23d}, {0x3bab6c39, 0xad45f23d, 0x76c57e18, 0x587fd280}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "flat cache bits: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(FlatCacheBitsCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + std::memset(guest.Data(), Fill, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid % Rows.size()].memory.data(), 16u); + } + Output.fill(Sentinel); + Dispatch(device, waveSize, guest.Data()); + const auto wave = "flat cache bits: wave" + std::to_string(waveSize) + " lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = Rows[tid % Rows.size()]; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == row.results[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(row.results[result])); + } + std::array memory{}; + std::memcpy(memory.data(), guest.Data() + tid * 16u, 16u); + for (std::uint32_t dword = 0; dword < 4u; ++dword) { + Require(memory[dword] == row.final[dword], wave + std::to_string(tid) + " memory dword " + std::to_string(dword) + " is " + Hex(memory[dword]) + ", expected " + Hex(row.final[dword])); + } + } + for (std::size_t offset = Threads * 16u; offset < BlockBytes; ++offset) { + Require(guest.Data()[offset] == Fill, wave + "store outside the rows changed byte " + std::to_string(offset)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + Run(*device, guest, 32); + std::puts("flat cache bits tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatD16Loads.cpp similarity index 72% rename from core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoads.cpp rename to core/libs/prx/libSceAgcDriver/tests/execution/FlatD16Loads.cpp index 3ca6fa1b2..3dfd1167e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoads.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatD16Loads.cpp @@ -31,31 +31,18 @@ constexpr std::size_t BlockBytes = 65536; constexpr std::size_t RegionOffset = Threads * 16u; alignas(256) std::array Output{}; -alignas(256) constexpr std::array GlobalLoadsCode{ +alignas(256) constexpr std::array FlatD16LoadsCode{ 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, - 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, - 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, - 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, - 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x5a5a0101, 0x7e1802ff, 0x5a5a0202, 0x7e1a02ff, 0x5a5a0303, 0x7e1c02ff, 0x5a5a0404, 0x7e1e02ff, + 0x5a5a0505, 0x7e2002ff, 0x5a5a0606, 0x7e2202ff, 0x5a5a0707, 0x7e2402ff, 0x5a5a0808, 0x7e2602ff, + 0x5a5a0909, 0x7e2802ff, 0x5a5a0a0a, 0x7e2a02ff, 0x5a5a0b0b, 0x7e2c02ff, 0x5a5a0c0c, 0x7e2e02ff, + 0x5a5a0d0d, 0x7e3002ff, 0x5a5a0e0e, 0x7e3202ff, 0x5a5a0f0f, 0x4a4008ff, 0x00000a00, 0x4a420eff, 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, - 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc208800, 0x0a080020, 0xdc2487ff, - 0x0b080020, 0xdc288ffd, 0x0c080020, 0xdc2c8001, 0x0d7d0022, 0xdc308fff, 0x0e080020, 0xdc348ffa, - 0x0f7d0022, 0xdc3c8003, 0x11080020, 0xdc3887f8, 0x147d0022, 0xdc308000, 0x18080021, 0xdc208800, - 0x197d0022, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, - 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 -}; - -alignas(256) constexpr std::array FlatLoadsCode{ - 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, - 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, - 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, - 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, - 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, - 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, - 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc200000, 0x0a7d0024, 0xdc2407ff, - 0x0b7d0024, 0xdc280001, 0x0c7d0024, 0xdc2c0003, 0x0d7d0024, 0xdc300002, 0x0e7d0024, 0xdc340001, - 0x0f7d0024, 0xdc3c0007, 0x117d0024, 0xdc3807f1, 0x147d0024, 0xdc300400, 0x187d0024, 0xdc2807ff, - 0x197d0024, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc800000, 0x0a7d0024, 0xdc8407ff, + 0x0b7d0024, 0xdc880001, 0x0c7d0024, 0xdc8c0003, 0x0d7d0024, 0xdc900001, 0x0e7d0024, 0xdc9407fe, + 0x0f7d0024, 0xdc900005, 0x107d0024, 0xdc8c0407, 0x107d0024, 0xdc840400, 0x117d0024, 0xdc880009, + 0x117d0024, 0xdc940007, 0x127d0024, 0xdc8007f8, 0x127d0024, 0xdc8807ff, 0x137d0024, 0xdc90000c, + 0x137d0024, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 }; @@ -448,43 +435,42 @@ constexpr std::array Region{ struct Row { std::array input; - std::array global; - std::array flat; + std::array expected; }; constexpr std::array Rows{{ - {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x000000d2, 0x00000029, 0x0000ebf6, 0x00004397, 0x4397357a, 0xf6804124, 0x97357aeb, 0x513c819d, 0x2e32095f, 0xefa22967, 0x6595f862, 0x295aec03, 0x523df3dc, 0x457d538e, 0x6526f2d2, 0x000000d2}, {0x000000d2, 0x0000007a, 0x000026f2, 0xffffa665, 0x0ca66526, 0xa66526f2, 0x89d2120c, 0x5d1889d2, 0x81e80e95, 0x6f160936, 0xe5522840, 0xd0e7469d, 0x80412474, 0x357aebf6, 0xfe1ec477, 0x0000357a}}, - {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x000000f2, 0xffffffdc, 0x00007aeb, 0xffff9d43, 0x9d439735, 0xebf68041, 0x4397357a, 0x5f513c81, 0x672e3209, 0x47efa229, 0x036595f8, 0xdc295aec, 0x8e523df3, 0x48457d53, 0xa66526f2, 0x000000f2}, {0x000000f2, 0x00000035, 0x00006526, 0x00000ca6, 0x120ca665, 0x0ca66526, 0x1889d212, 0x955d1889, 0x3681e80e, 0x6b6f1609, 0x9de55228, 0x74d0e746, 0xf6804124, 0x97357aeb, 0xe4fe1ec4, 0x00009735}}, - {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x00000026, 0xfffffff3, 0x0000357a, 0xffff819d, 0x819d4397, 0x7aebf680, 0x9d439735, 0x095f513c, 0x29672e32, 0xad47efa2, 0xec036595, 0xf3dc295a, 0x538e523d, 0xe048457d, 0x0ca66526, 0x00000026}, {0x000000a4, 0x0000005a, 0x0000449e, 0xffffadea, 0x9fadea44, 0xadea449e, 0x8aa0459f, 0xecce8aa0, 0x40199f09, 0x6f03851f, 0x97578278, 0x3c61fe8b, 0xbecaa1d3, 0xb65a60de, 0x37307a70, 0x0000b65a}}, - {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x00000065, 0x0000003d, 0x00009735, 0x00003c81, 0x3c819d43, 0x357aebf6, 0x819d4397, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x5aec0365, 0x3df3dc29, 0x7d538e52, 0xfce04845, 0x120ca665, 0x00000065}, {0x000000dd, 0x00000060, 0x00009ea4, 0xffffea44, 0xadea449e, 0xea449ea4, 0xa0459fad, 0xce8aa045, 0x199f09ec, 0x03851f40, 0x578278da, 0x61fe8b97, 0xcaa1d33c, 0x5a60debe, 0x307a70d9, 0x00005a60}}, - {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x00000077, 0x00000072, 0x00007ea7, 0x00003154, 0x31541eca, 0xa734f958, 0x541eca7e, 0x204cb651, 0x16a2f996, 0x660affc8, 0x30d907a2, 0x72bdad38, 0xa92515b0, 0x948e5f94, 0xa2731cb1, 0x00000077}, {0x00000026, 0xffffff97, 0x0000a665, 0x0000120c, 0xd2120ca6, 0x120ca665, 0x5d1889d2, 0x0e955d18, 0x093681e8, 0x116b6f16, 0x469de552, 0x2474d0e7, 0xebf68041, 0x4397357a, 0x8ce4fe1e, 0x00004397}}, - {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x00000049, 0xffffffad, 0x000034f9, 0x00001eca, 0x1eca7ea7, 0xf95881df, 0xca7ea734, 0xb6513154, 0xf996204c, 0xffc816a2, 0x07a2e287, 0xad3830d9, 0x15b072bd, 0x5f94a925, 0x1cb172ff, 0x00000049}, {0x0000007a, 0x0000005a, 0x00009735, 0xffff9d43, 0x819d4397, 0x9d439735, 0x5f513c81, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x553e074f, 0x8b833038, 0x6595f862, 0x295aec03, 0x5131541e, 0x0000295a}}, - {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x000000c6, 0xffffff91, 0x00002075, 0xffffb0c1, 0xb0c19888, 0x75f13ba1, 0xc1988820, 0x08a59ac0, 0x85539459, 0x73d7de27, 0x62c3c5bc, 0x91b7a807, 0x63034f25, 0x2db116b5, 0xdc295aec, 0x000000c6}, {0x00000097, 0xffffffdc, 0x00009d43, 0x00003c81, 0x513c819d, 0x3c819d43, 0x32095f51, 0x672e3209, 0x47efa229, 0x60e553ad, 0x3038553e, 0xf8628b83, 0xec036595, 0xf3dc295a, 0x4cb65131, 0x0000f3dc}}, - {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x000000f1, 0x00000054, 0x00001873, 0xffffdf25, 0xdf25a310, 0x7326b53e, 0x25a31018, 0x6645c8aa, 0x02f743cf, 0x8fd2a50e, 0x3b946093, 0x54ebd216, 0x9bd7f852, 0x53f53ed6, 0x7dee2657, 0x000000f1}, {0x00000000, 0x00000058, 0x00003386, 0xffffc349, 0xc0c34933, 0xc3493386, 0x010dfac0, 0x7d59010d, 0xcbb17d18, 0x7ef72fd3, 0x1ff610a6, 0x59e9d36c, 0x30633e8d, 0x3c584877, 0xb4de5e8e, 0x00003c58}}, - {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x000000a8, 0x0000002d, 0x0000a25c, 0xffff9c05, 0x9c0507a5, 0x5ca32e13, 0x0507a5a2, 0xeebcae0b, 0xfbcf54ff, 0x7c7b8218, 0x02701056, 0x2dd3f4aa, 0x602ab9e7, 0xcd71014b, 0xf610a667, 0x000000a8}, {0x00000021, 0xffffff8b, 0x0000d19a, 0xffff82a1, 0xc782a1d1, 0x82a1d19a, 0xb41ce3c7, 0x3b5db41c, 0x7ce0e57f, 0x00280664, 0x300b011b, 0x9b79ebd2, 0xc90fa8c4, 0x9c8be880, 0x401ae5e1, 0x00009c8b}}, - {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x000000d7, 0xffffffb6, 0x00004159, 0xffffb70c, 0xb70c48ee, 0x5905426c, 0x0c48ee41, 0x52e95ec2, 0x809af6c4, 0x9e49d979, 0x693895b7, 0xb6e0664f, 0xdeca057c, 0x2b2c163e, 0x3e052289, 0x000000d7}, {0x000000fa, 0x0000003d, 0x000017c8, 0xffffe6ab, 0xe4e6ab17, 0xe6ab17c8, 0xa2c98ce4, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x5bd3a52f, 0x225e3a51, 0xd65eeb8d, 0x0e3d40d4, 0x3f5aefae, 0x00000e3d}}, - {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x0000003b, 0x00000015, 0x00007b82, 0x000024e5, 0x24e5c17c, 0x8218fbcf, 0xe5c17c7b, 0x6ab73608, 0x185620a0, 0x575da8dc, 0x59ac90cd, 0x15783291, 0x7584528a, 0xe888f86d, 0xdebaea1d, 0x0000003b}, {0x0000008d, 0x00000074, 0x0000538b, 0xfffffa29, 0x6dfa2953, 0xfa29538b, 0xfc1ae26d, 0x4312fc1a, 0x1835159f, 0xb5fd7f6b, 0x4506273f, 0x554bd692, 0x7d422acd, 0xe774511b, 0xda660308, 0x0000e774}}, - {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x00000021, 0x00000071, 0x00007769, 0xffff8cc2, 0x8cc24bff, 0x69d0cbd1, 0xc24bff77, 0xd5c720a6, 0xd9c85a78, 0x60b4443a, 0xf6724fb3, 0x7153291c, 0x4a5c9177, 0x0b128e2b, 0x7f9859b1, 0x00000021}, {0x0000005d, 0x00000066, 0x00007957, 0xffff86c7, 0x8d86c779, 0x86c77957, 0x35e9c58d, 0x6f4835e9, 0x8d406c57, 0x4a4ad30d, 0x528a1578, 0xf86d7584, 0xdda0e888, 0xf666f927, 0xb19c74d6, 0x0000f666}}, - {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x00000011, 0x00000016, 0x0000b53e, 0x00001018, 0x10187326, 0x3ecdef03, 0x187326b5, 0xaadf25a3, 0xcf6645c8, 0x0e02f743, 0x936f25cd, 0x163b9460, 0x5254ebd2, 0xd69bd7f8, 0x224cb259, 0x00000011}, {0x00000010, 0x0000006e, 0x0000fc1c, 0xffffc9eb, 0x3ac9ebfc, 0xc9ebfc1c, 0x1ae0f83a, 0x43151ae0, 0xc7e70a45, 0x21c1452e, 0xc6e0e1a7, 0xda95b3a4, 0xa42ead3a, 0x506e741f, 0x3c3e2f3f, 0x0000506e}}, - {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x99571c29, 0x00000065}, {0x00000015, 0xffffffbe, 0x000078a4, 0xffffe536, 0x26e53678, 0xe53678a4, 0x6d03a026, 0x02016d03, 0xfc1fabaf, 0x3716dbf7, 0xfd9f16e3, 0x1d9033df, 0x5adebaea, 0xd7be5d2b, 0x936f25cd, 0x0000d7be}}, - {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x00000053, 0x00000067, 0x00004431, 0xffffd3b4, 0xd3b4c14d, 0x31813cb2, 0xb4c14d44, 0xb9279ed7, 0xb93ff927, 0x59859a53, 0xb7d12049, 0x675b4866, 0xc676e8d8, 0xdca0e1a0, 0x034ab01d, 0x00000053}, {0x000000a0, 0x0000006b, 0x00004ec7, 0xffffeba6, 0x4eeba64e, 0xeba64ec7, 0x6c3f954e, 0xa8636c3f, 0x7080725e, 0x9e00052d, 0x871b5d02, 0x6e53112f, 0x05ab81b3, 0xf86b2339, 0x6b431e8e, 0x0000f86b}}, - {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x000000fd, 0x00000072, 0x0000d4a3, 0x000069d0, 0x69d0cbd1, 0xa3aa5a25, 0xd0cbd1d4, 0xc24bff77, 0xc720a68c, 0xc85a78d5, 0x324d47e5, 0x724fb31c, 0x53291cf6, 0x5c917771, 0x15602efe, 0x000000fd}, {0x000000c8, 0xffffffd3, 0x00003527, 0x000067e7, 0xca67e735, 0x67e73527, 0x9c2a88ca, 0xb0e49c2a, 0x17c8fa9b, 0x8ce4e6ab, 0x1a2baeed, 0x4fd4b8c8, 0x61539dbe, 0x5bd3a52f, 0xd052e9fc, 0x00005bd3}}, - {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x00000086, 0xfffffffe, 0x000070d6, 0xffff8e1d, 0x8e1de1ab, 0xd6bd1487, 0x1de1ab70, 0x3b6b431e, 0x7e7923d3, 0x777b0e8e, 0x6c7e37f1, 0xfe28bb7f, 0xa0949a4c, 0x3ab02414, 0x65d656c0, 0x00000086}, {0x000000cd, 0xfffffffd, 0x0000e9fc, 0xffffd052, 0x66d052e9, 0xd052e9fc, 0x0d8fd866, 0x84530d8f, 0xefaef525, 0x6cde3f5a, 0x68e162c2, 0xffe524cd, 0x9b3d01a2, 0x41fded80, 0x3a515bd3, 0x000041fd}}, - {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x00000019, 0x00000057, 0x00005567, 0xffffdf45, 0xdf45fb80, 0x677ed35a, 0x45fb8055, 0x34f95881, 0x1eca7ea7, 0xb6513154, 0xa5a87b5e, 0x57dd4c23, 0x07a2e287, 0xad3830d9, 0xd14b0cf5, 0x00000019}, {0x000000fc, 0xffffffb0, 0x0000267e, 0xffffb948, 0x9eb94826, 0xb948267e, 0x0b25a49e, 0xd5d30b25, 0xa083e4b7, 0xcfb3bb6d, 0x5873c62f, 0xe73527c8, 0x2a88ca67, 0x9bb0e49c, 0xf83f261f, 0x00009bb0}}, - {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x0000009e, 0xffffff91, 0x000017c8, 0xffff8ce4, 0x8ce4e6ab, 0xc8fa9bb0, 0xe4e6ab17, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x3d40d4d6, 0x911b0a0e, 0xd1eba0cd, 0xe767b4ff, 0x1d426570, 0x0000009e}, {0x00000079, 0xffffffba, 0x000069c5, 0x00001a8c, 0x151a8c69, 0x1a8c69c5, 0x3678a415, 0x26e53678, 0x016d03a0, 0x1fabaf02, 0x6d22b51e, 0x9f16e3e8, 0x9033dffd, 0xdebaea1d, 0x892f4c71, 0x0000deba}}, - {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x79fec2ec, 0x00000065}, {0x00000013, 0x00000033, 0x0000835f, 0x00002ddd, 0x722ddd83, 0x2ddd835f, 0xc6429a72, 0xaac7c642, 0xa31b01f2, 0x599eb598, 0x2c46c27e, 0xab9c2334, 0x31cfa0b5, 0x02334e95, 0x19598433, 0x00000233}}, - {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x000000a2, 0x0000001c, 0x00005a25, 0xffffd1d4, 0xd1d4a3aa, 0x25f6d039, 0xd4a3aa5a, 0x7769d0cb, 0x8cc24bff, 0xd5c720a6, 0xe50f0b34, 0x1c324d47, 0xf6724fb3, 0x7153291c, 0x524da73e, 0x000000a2}, {0x000000ca, 0x0000001b, 0x00006436, 0xffff959f, 0x13959f64, 0x959f6436, 0x2be99013, 0x08252be9, 0x9f1b1c06, 0xfa5829ed, 0x24ff8388, 0x9a693293, 0x8428251f, 0x071b82c2, 0x1588587f, 0x0000071b}}, - {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x0000008c, 0x00000017, 0x00006d7c, 0xffffa815, 0xa815a39f, 0x7c7a524d, 0x15a39f6d, 0xed275ce5, 0x0e62da4d, 0xe790d315, 0x528bc830, 0x17eb5b02, 0x9da049a4, 0xb3a7bbef, 0x660a3790, 0x0000008c}, {0x00000088, 0x0000000d, 0x0000aae4, 0xffffe110, 0xdee110aa, 0xe110aae4, 0xb6eac1de, 0x62f1b6ea, 0x34343f1b, 0xf308081c, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x380d0562, 0xe242e585, 0x0000380d}}, - {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x00000047, 0x0000000d, 0x0000d463, 0x00001bf9, 0x1bf9e87d, 0x6302c753, 0xf9e87dd4, 0x378b4009, 0xf3c8b729, 0x598433f0, 0xa0d6d4b0, 0x0d8273a0, 0x6d34b2ad, 0xedd883ac, 0xaedff3c7, 0x00000047}, {0x00000013, 0xffffffef, 0x0000ea6b, 0xffffa06b, 0xb2a06bea, 0xa06bea6b, 0x945aacb2, 0x1b43945a, 0x66bd4d39, 0xf886f4f0, 0x7cb4af22, 0xb43c7bab, 0xb183013d, 0xa4ef2271, 0x72ee855b, 0x0000a4ef}}, - {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, - {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x000000c7, 0xffffffdb, 0x0000b20a, 0x0000100b, 0x100bbc11, 0x0aa2a4b1, 0x0bbc11b2, 0x5dc397db, 0xd1f4d133, 0x10aae488, 0x082b465a, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x92b36eda, 0x000000c7}, {0x000000af, 0x00000015, 0x0000fb40, 0xffffad6d, 0x2fad6dfb, 0xad6dfb40, 0xce007b2f, 0xccb8ce00, 0xa73e5b47, 0x7c7a524d, 0x4a5c9177, 0x0b128e2b, 0xfafd7702, 0xbf157cc0, 0xde591a71, 0x0000bf15}}, - {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x00000025, 0x0000002b, 0x00006fd8, 0x00001477, 0x1477e9be, 0xd8600189, 0x77e9be6f, 0x2c73a7bd, 0x421aff39, 0x1f09a43b, 0x87b15796, 0x2b17d0bf, 0xfa5d515c, 0x834fd313, 0x63779354, 0x00000025}, {0x00000073, 0x0000002f, 0x0000c858, 0x00003527, 0xe73527c8, 0x3527c858, 0x88ca67e7, 0x9c2a88ca, 0xfa9bb0e4, 0xe6ab17c8, 0xaeede5d8, 0xb8c81a2b, 0x9dbe4fd4, 0xa52f6153, 0xe9fccd90, 0x0000a52f}}, - {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x000000c2, 0xffffffd1, 0x0000e2a4, 0x00003181, 0x31813cb2, 0xa4765520, 0x813cb2e2, 0xb4c14d44, 0x279ed7d3, 0x3ff927b9, 0x164fb710, 0xd1204939, 0x5b4866b7, 0x76e8d867, 0x764984b5, 0x000000c2}, {0x0000009d, 0xffffffde, 0x000023c0, 0xffffdea4, 0x49dea423, 0xdea423c0, 0xe90c7c49, 0x8cede90c, 0x6a782b20, 0x414c4857, 0x6d03a026, 0xabaf0201, 0xdbf7fc1f, 0x1fde3716, 0xa50e02f7, 0x00001fde}}, - {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x00000049, 0xfffffff9, 0x00001856, 0x0000575d, 0x575da8dc, 0x5620a06a, 0x5da8dc18, 0x8d86c779, 0x4835e9c5, 0x406c576f, 0xe888f86d, 0xf927dda0, 0x9e9bf666, 0x0fcfcf14, 0x1d9033df, 0x00000049}, {0x000000fe, 0xffffff9a, 0x0000602e, 0x00000715, 0x0d071560, 0x0715602e, 0x5e8e080d, 0xb4de5e8e, 0xd8f27c75, 0xdc10e5e8, 0xa02ac205, 0x05b43c44, 0x72660c37, 0x489ae433, 0x30633e8d, 0x0000489a}}, - {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a00d2, 0x007a0101, 0x5a5afff2, 0x00650303, 0x5a5a26f2, 0x7aeb0505, 0x004f120c, 0x00770018, 0x89d200d0, 0x5a5ae80e, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a00f2, 0x00350101, 0x5a5a0026, 0xffa60303, 0x5a5a6526, 0x357a0505, 0xfffad212, 0x00c4005d, 0x18890074, 0x5a5a81e8, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a00a4, 0x005a0101, 0x5a5aff9e, 0xffea0303, 0x5a5a449e, 0x5a600505, 0x0008459f, 0x0070ffce, 0x8aa0003c, 0x5a5a199f, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a00dd, 0x00600101, 0x5a5affa4, 0x00440303, 0x5a5a9ea4, 0x60de0505, 0x00009fad, 0x00d9ff8a, 0xa0450061, 0x5a5a9f09, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0026, 0x00970101, 0x5a5a0065, 0x000c0303, 0x5a5aa665, 0x97350505, 0x001589d2, 0x001eff95, 0x5d180024, 0x5a5a3681, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0101, 0x5a5a0202, 0x5a5a0303, 0x5a5a0404, 0x5a5a0505, 0x5a5a0606, 0x5a5a0707, 0x5a5a0808, 0x5a5a0909, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a007a, 0x005a0101, 0x5a5a0035, 0x00430303, 0x5a5a9735, 0x5aec0505, 0xff963c81, 0x001e0009, 0x5f51008b, 0x5a5a2967, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0097, 0x00dc0101, 0x5a5a0043, 0xff810303, 0x5a5a9d43, 0xdc290505, 0xffa25f51, 0x0031002e, 0x320900f8, 0x5a5aefa2, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x00580101, 0x5a5aff86, 0x00490303, 0x5a5a3386, 0x58480505, 0xffd8fac0, 0x008e0059, 0x010d0059, 0x5a5ab17d, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0021, 0x008b0101, 0x5a5aff9a, 0xffa10303, 0x5a5ad19a, 0x8be80505, 0xffdbe3c7, 0x00e1005d, 0xb41c009b, 0x5a5ae0e5, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a00fa, 0x003d0101, 0x5a5affc8, 0xffab0303, 0x5a5a17c8, 0x3d400505, 0xffa18ce4, 0x00aeffd6, 0xa2c90022, 0x5a5ab17e, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a008d, 0x00740101, 0x5a5aff8b, 0x00290303, 0x5a5a538b, 0x74510505, 0x0038e26d, 0x00080012, 0xfc1a0055, 0x5a5a3515, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0101, 0x5a5a0202, 0x5a5a0303, 0x5a5a0404, 0x5a5a0505, 0x5a5a0606, 0x5a5a0707, 0x5a5a0808, 0x5a5a0909, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a005d, 0x00660101, 0x5a5a0057, 0xffc70303, 0x5a5a7957, 0x66f90505, 0x0062c58d, 0x00d60048, 0x35e900f8, 0x5a5a406c, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0010, 0x006e0101, 0x5a5a001c, 0xffeb0303, 0x5a5afc1c, 0x6e740505, 0xffe8f83a, 0x003f0015, 0x1ae000da, 0x5a5ae70a, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0015, 0x00be0101, 0x5a5affa4, 0x00360303, 0x5a5a78a4, 0xbe5d0505, 0x0016a026, 0x00cd0001, 0x6d03001d, 0x5a5a1fab, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a00a0, 0x006b0101, 0x5a5affc7, 0xffa60303, 0x5a5a4ec7, 0x6b230505, 0x0079954e, 0x008e0063, 0x6c3f006e, 0x5a5a8072, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a00c8, 0x00d30101, 0x5a5a0027, 0xffe70303, 0x5a5a3527, 0xd3a50505, 0x000d88ca, 0x00fcffe4, 0x9c2a004f, 0x5a5ac8fa, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a00cd, 0x00fd0101, 0x5a5afffc, 0x00520303, 0x5a5ae9fc, 0xfded0505, 0xffebd866, 0x00d30053, 0x0d8f00ff, 0x5a5aaef5, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0101, 0x5a5a0202, 0x5a5a0303, 0x5a5a0404, 0x5a5a0505, 0x5a5a0606, 0x5a5a0707, 0x5a5a0808, 0x5a5a0909, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a00fc, 0x00b00101, 0x5a5a007e, 0x00480303, 0x5a5a267e, 0xb0e40505, 0x0031a49e, 0x001fffd3, 0x0b2500e7, 0x5a5a83e4, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0079, 0x00ba0101, 0x5a5affc5, 0xff8c0303, 0x5a5a69c5, 0xbaea0505, 0x006fa415, 0x0071ffe5, 0x3678009f, 0x5a5a6d03, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0013, 0x00330101, 0x5a5a005f, 0xffdd0303, 0x5a5a835f, 0x334e0505, 0xff8a9a72, 0x0033ffc7, 0xc64200ab, 0x5a5a1b01, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a00ca, 0x001b0101, 0x5a5a0036, 0xff9f0303, 0x5a5a6436, 0x1b820505, 0xffcc9013, 0x007f0025, 0x2be9009a, 0x5a5a1b1c, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0088, 0x000d0101, 0x5a5affe4, 0x00100303, 0x5a5aaae4, 0x0d050505, 0x007ac1de, 0x0085fff1, 0xb6ea0010, 0x5a5a343f, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0013, 0x00ef0101, 0x5a5a006b, 0x006b0303, 0x5a5aea6b, 0xef220505, 0x001eacb2, 0x005b0043, 0x945a00b4, 0x5a5abd4d, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0101, 0x5a5a0202, 0x5a5a0303, 0x5a5a0404, 0x5a5a0505, 0x5a5a0606, 0x5a5a0707, 0x5a5a0808, 0x5a5a0909, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a00af, 0x00150101, 0x5a5a0040, 0x006d0303, 0x5a5afb40, 0x157c0505, 0x000b7b2f, 0x0071ffb8, 0xce00000b, 0x5a5a3e5b, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0073, 0x002f0101, 0x5a5a0058, 0x00270303, 0x5a5ac858, 0x2f610505, 0xffd867e7, 0x0090002a, 0x88ca00b8, 0x5a5a9bb0, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a009d, 0x00de0101, 0x5a5affc0, 0xffa40303, 0x5a5a23c0, 0xde370505, 0x00597c49, 0x00f7ffed, 0xe90c00ab, 0x5a5a782b, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a00fe, 0x009a0101, 0x5a5a002e, 0x00150303, 0x5a5a602e, 0x9ae40505, 0x003c080d, 0x008dffde, 0x5e8e0005, 0x5a5af27c, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0101, 0x5a5a0202, 0x5a5a0303, 0x5a5a0404, 0x5a5a0505, 0x5a5a0606, 0x5a5a0707, 0x5a5a0808, 0x5a5a0909, 0x5a5a0a0a, 0x5a5a0b0b, 0x5a5a0c0c, 0x5a5a0d0d, 0x5a5a0e0e, 0x5a5a0f0f}} }}; class GuestBlock { @@ -495,7 +481,7 @@ public: #else block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); #endif - Require(block != nullptr, "flat global loads: cannot allocate the guest block"); + Require(block != nullptr, "flat d16 loads: cannot allocate the guest block"); GuestAllocations::Mutation().Add(block, BlockBytes, true, false); } @@ -549,12 +535,12 @@ void Dispatch(AgcDriver::VulkanDevice& device, std::span co device.WaitIdle(); } -void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code, bool flat) { +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest) { Output.fill(Sentinel); - Dispatch(device, code, guest.Data()); - const auto wave = std::string(flat ? "flat" : "global") + " loads: lane "; + Dispatch(device, FlatD16LoadsCode, guest.Data()); + const std::string wave = "flat d16 loads: lane "; for (std::uint32_t tid = 0; tid < Threads; ++tid) { - const auto& expected = flat ? Rows[tid].flat : Rows[tid].global; + const auto& expected = Rows[tid].expected; for (std::uint32_t result = 0; result < Results; ++result) { const auto actual = Output[tid * Results + result]; Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); @@ -574,9 +560,8 @@ int main() { std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); } std::memcpy(guest.Data() + RegionOffset, Region.data(), Region.size()); - Run(*device, guest, GlobalLoadsCode, false); - Run(*device, guest, FlatLoadsCode, true); - std::puts("flat global loads tests passed"); + Run(*device, guest); + std::puts("flat d16 loads tests passed"); return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoadsRegion.hpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoadsRegion.hpp new file mode 100644 index 000000000..8a6272e69 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatGlobalLoadsRegion.hpp @@ -0,0 +1,394 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_EXECUTION_FLATGLOBALLOADSREGION_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_EXECUTION_FLATGLOBALLOADSREGION_HPP + +#include +#include + +inline constexpr std::array FlatGlobalLoadsRegion{ + 0xd2, 0xf2, 0x26, 0x65, 0xa6, 0x0c, 0x12, 0xd2, 0x89, 0x18, 0x5d, 0x95, 0x0e, 0xe8, 0x81, 0x36, + 0x09, 0x16, 0x6f, 0x6b, 0x11, 0x3d, 0x17, 0x8d, 0x6c, 0x0f, 0xd3, 0x90, 0x1f, 0xf2, 0x39, 0xa1, + 0xa0, 0x95, 0xf2, 0x0f, 0x93, 0x95, 0x65, 0x0c, 0xf9, 0x38, 0x0b, 0x8e, 0xdb, 0x22, 0x4a, 0x6b, + 0x24, 0x8a, 0x1e, 0x92, 0x4e, 0x8f, 0xd0, 0xae, 0x2e, 0x1a, 0x94, 0x92, 0xa3, 0x30, 0x5f, 0x18, + 0x8c, 0xb6, 0x10, 0x90, 0x0f, 0x9e, 0x34, 0x7f, 0xae, 0x88, 0x6d, 0xc6, 0x50, 0x77, 0x95, 0xec, + 0x74, 0x5c, 0x4c, 0x3f, 0xcb, 0x2e, 0xb2, 0xc7, 0x3e, 0x14, 0x93, 0x4c, 0x86, 0x7e, 0xe0, 0x57, + 0xba, 0xf2, 0x49, 0x9b, 0xfa, 0x12, 0x1e, 0x83, 0x6b, 0x2a, 0xc1, 0x57, 0x26, 0xee, 0x7d, 0x6b, + 0x0a, 0xf6, 0xab, 0x13, 0xc3, 0x8e, 0x92, 0xca, 0xe0, 0xd1, 0x50, 0x57, 0xb1, 0x59, 0x98, 0x7f, + 0x94, 0xcc, 0x74, 0x11, 0xd7, 0x17, 0xf1, 0x45, 0x79, 0xb2, 0xaa, 0x10, 0x0f, 0xbb, 0xb3, 0x4f, + 0xa5, 0x93, 0xfe, 0xae, 0xd2, 0x72, 0x48, 0xb7, 0x62, 0xe3, 0xab, 0x58, 0x05, 0xf0, 0x76, 0x5a, + 0x2b, 0x9c, 0x1d, 0x7e, 0x0f, 0x37, 0xc4, 0x49, 0x21, 0xbd, 0x3f, 0x65, 0x64, 0xea, 0xdf, 0x7f, + 0x14, 0x2a, 0x72, 0x66, 0x8c, 0x47, 0xe2, 0x23, 0xd1, 0x6e, 0xdd, 0x8c, 0x47, 0xb4, 0x6a, 0xfc, + 0x5b, 0xae, 0xe2, 0x61, 0xf5, 0x3b, 0x26, 0x15, 0x2d, 0x26, 0x3b, 0xa8, 0x3b, 0x03, 0x7c, 0xd4, + 0x96, 0x2e, 0x43, 0x48, 0x01, 0x25, 0x6b, 0x88, 0x5e, 0x9c, 0x90, 0x51, 0xf3, 0x20, 0xb0, 0xdb, + 0x83, 0xf3, 0x9e, 0xa7, 0xad, 0xbd, 0x0d, 0x74, 0xe6, 0xde, 0xc7, 0xf3, 0xdf, 0xae, 0xcc, 0x8f, + 0x64, 0x65, 0x66, 0x64, 0x1a, 0x7b, 0xa2, 0x66, 0x0f, 0x30, 0x11, 0xfc, 0x35, 0x70, 0x29, 0x1c, + 0x57, 0x99, 0x0d, 0x1a, 0x00, 0x91, 0x26, 0x89, 0x19, 0xf2, 0x5d, 0x9d, 0x06, 0x12, 0xdf, 0x35, + 0x9d, 0x60, 0x26, 0xa2, 0x40, 0xf4, 0x58, 0x9a, 0x5d, 0x79, 0x1f, 0x1d, 0xd9, 0x7c, 0xfe, 0xfa, + 0x77, 0x7a, 0x7b, 0xcf, 0x15, 0x24, 0x1a, 0xbf, 0x57, 0xbd, 0x43, 0x7a, 0xd4, 0xb1, 0x29, 0x84, + 0x05, 0x34, 0xf3, 0xf3, 0x87, 0x5c, 0x25, 0xb0, 0x8b, 0xea, 0x06, 0xc2, 0x87, 0x4c, 0xfa, 0xa4, + 0xdd, 0x17, 0xb2, 0xd8, 0x42, 0x84, 0x5d, 0xe8, 0x2a, 0x5b, 0xc5, 0x39, 0x88, 0x8a, 0xc7, 0x80, + 0x54, 0xa2, 0x39, 0x9c, 0xcf, 0xc9, 0xfc, 0xc2, 0xda, 0x31, 0xce, 0x3d, 0xd1, 0x66, 0xbd, 0xcd, + 0x3a, 0x33, 0x84, 0x7e, 0x5b, 0xbb, 0x07, 0xfd, 0x07, 0xca, 0x47, 0x78, 0x42, 0x31, 0xb1, 0x9a, + 0xf4, 0x58, 0x72, 0xce, 0xef, 0xb9, 0xfc, 0x59, 0xf4, 0xf9, 0x5d, 0x14, 0x38, 0x1a, 0x3a, 0x78, + 0x32, 0x56, 0x34, 0x7b, 0x9f, 0xfc, 0xe6, 0x9c, 0xd7, 0x00, 0x7a, 0xe8, 0xa7, 0x58, 0xcc, 0xa4, + 0x15, 0xd5, 0xa9, 0x1e, 0xe8, 0x63, 0xc8, 0xb6, 0xc0, 0x33, 0x7a, 0xe3, 0x2d, 0x6f, 0xca, 0xa2, + 0x55, 0x16, 0xcd, 0xf2, 0xf8, 0xb8, 0x65, 0x76, 0x66, 0xbe, 0xf2, 0x15, 0xb9, 0x28, 0x2b, 0xfe, + 0x20, 0x07, 0x26, 0x97, 0xe7, 0x77, 0xce, 0xa7, 0x25, 0x9c, 0xd3, 0x98, 0xfa, 0x79, 0xa8, 0xef, + 0x59, 0x27, 0x8c, 0x8c, 0x21, 0x05, 0x03, 0xcc, 0xf8, 0xb9, 0xa6, 0x1a, 0x86, 0xbf, 0xef, 0x23, + 0x6f, 0xfc, 0xdf, 0x31, 0xd3, 0xdf, 0x36, 0x07, 0x40, 0x36, 0x4a, 0x80, 0x3d, 0xc3, 0x96, 0x53, + 0x42, 0x8b, 0x6b, 0xd5, 0x21, 0x8f, 0xe8, 0xbd, 0x5a, 0xe5, 0x75, 0xa9, 0x95, 0xd0, 0xe7, 0x84, + 0x6b, 0xd3, 0xea, 0xe0, 0x80, 0x21, 0x88, 0x26, 0x86, 0x82, 0x04, 0xdf, 0x70, 0xc6, 0x2e, 0x9b, + 0x01, 0xc6, 0xcc, 0x26, 0x2c, 0x24, 0x79, 0x9e, 0xb9, 0x1e, 0x8e, 0x0f, 0x53, 0xae, 0x84, 0x87, + 0x8e, 0x7b, 0xc8, 0xc6, 0x1b, 0xe2, 0x8f, 0x0e, 0x3f, 0x30, 0x46, 0x0a, 0xc5, 0x19, 0x81, 0x73, + 0x8f, 0x07, 0xc2, 0xe4, 0xe9, 0x10, 0x71, 0x53, 0x9c, 0xf9, 0x81, 0x9b, 0x83, 0x33, 0xb1, 0x46, + 0x73, 0x82, 0x88, 0xce, 0x7a, 0x81, 0xf1, 0x3f, 0xb2, 0x85, 0xe0, 0xe0, 0xf1, 0xed, 0x42, 0xec, + 0x8f, 0xe4, 0xf1, 0x33, 0xd7, 0x72, 0xa3, 0x6a, 0x1f, 0x64, 0x71, 0x50, 0x12, 0xab, 0x3d, 0x6d, + 0x12, 0x36, 0xab, 0x4d, 0xc8, 0x1f, 0xe5, 0xc6, 0x27, 0xf0, 0xb7, 0xa4, 0xa9, 0x5d, 0x24, 0x40, + 0xe2, 0x23, 0xf7, 0x77, 0x38, 0xbf, 0xf3, 0x18, 0x65, 0xe2, 0x7c, 0x29, 0xfd, 0xaa, 0xd5, 0x39, + 0x29, 0xb4, 0x6e, 0xfe, 0x83, 0x67, 0x56, 0x6b, 0x32, 0x5b, 0x51, 0x17, 0xb8, 0x5d, 0x04, 0x56, + 0x8d, 0x75, 0x70, 0xb4, 0x04, 0x62, 0x54, 0x84, 0x9f, 0x4b, 0x83, 0xf5, 0x10, 0x1c, 0xfc, 0xeb, + 0xc9, 0x3a, 0xf8, 0xe0, 0x1a, 0x15, 0x43, 0x45, 0x0a, 0xe7, 0xc7, 0x2e, 0x45, 0xc1, 0x21, 0xd1, + 0x6c, 0xd9, 0xe9, 0xad, 0xd1, 0xf2, 0x42, 0xe7, 0x26, 0x89, 0xeb, 0x83, 0x92, 0x7e, 0xb3, 0x53, + 0x16, 0x47, 0x0e, 0xcc, 0xb0, 0x2e, 0x6c, 0xe5, 0x12, 0x44, 0xf0, 0x04, 0xa2, 0x16, 0xcd, 0x42, + 0x15, 0x9b, 0xdb, 0x38, 0x11, 0x43, 0xdc, 0x1f, 0x74, 0x02, 0x56, 0xfe, 0x8d, 0x6a, 0xed, 0xea, + 0x44, 0x9f, 0x21, 0x0b, 0x86, 0xb5, 0x3d, 0xf0, 0x1c, 0xf8, 0x29, 0x43, 0x0c, 0x2e, 0x33, 0xee, + 0x4f, 0xa0, 0x4e, 0x87, 0xc2, 0x34, 0x4a, 0x72, 0x80, 0xac, 0x2d, 0x45, 0x58, 0xcd, 0x04, 0xfe, + 0x40, 0x09, 0x03, 0x04, 0xbb, 0x81, 0x8d, 0xfa, 0x30, 0x83, 0x79, 0x3e, 0xef, 0x72, 0x1b, 0xa8, + 0xd1, 0xa6, 0x6e, 0xa8, 0x7e, 0x8b, 0xd5, 0xe3, 0xe4, 0xf8, 0x81, 0x4e, 0xb0, 0x37, 0xfb, 0x3a, + 0x57, 0x32, 0xd5, 0xe1, 0xb4, 0xba, 0xa2, 0x23, 0x67, 0xfd, 0x58, 0xfb, 0x0d, 0xd6, 0x21, 0x03, + 0x12, 0xa0, 0xbd, 0xe1, 0x41, 0x6e, 0x29, 0x0e, 0x15, 0xaa, 0xd7, 0x61, 0xde, 0x81, 0xab, 0xf8, + 0x48, 0x99, 0x3e, 0xb1, 0x4b, 0x0b, 0x75, 0x2f, 0x28, 0x44, 0x72, 0x00, 0x43, 0x5d, 0xf6, 0x54, + 0xf8, 0xfc, 0x8c, 0x52, 0x3e, 0x08, 0xf7, 0xe1, 0x4f, 0x37, 0x5b, 0x2e, 0x00, 0x55, 0x61, 0x15, + 0x79, 0x47, 0x80, 0xa7, 0x33, 0x3f, 0x81, 0xc6, 0x01, 0x17, 0x43, 0xd1, 0x16, 0x24, 0x66, 0x96, + 0x0a, 0x64, 0x05, 0x4c, 0x4d, 0xa1, 0x3b, 0x15, 0x95, 0xf5, 0x87, 0xda, 0xc0, 0x27, 0xa8, 0xe4, + 0xb7, 0xc8, 0xe1, 0x98, 0x63, 0xc3, 0x53, 0xb8, 0xfc, 0x7e, 0x26, 0x48, 0xb9, 0x9e, 0xa4, 0x25, + 0x0b, 0xd3, 0xd5, 0xb7, 0xe4, 0x83, 0xa0, 0x6d, 0xbb, 0xb3, 0xcf, 0x81, 0x23, 0xe8, 0x86, 0xc0, + 0x81, 0x91, 0xd5, 0xd0, 0xcd, 0x04, 0xd3, 0xaf, 0x95, 0xcc, 0xe4, 0xb6, 0xae, 0xf4, 0xb1, 0xa4, + 0x3a, 0x15, 0x07, 0x0a, 0x22, 0xa3, 0x5c, 0xf5, 0x1a, 0x60, 0xd5, 0x73, 0x8e, 0x0c, 0xa0, 0x04, + 0xa0, 0x88, 0xae, 0x3e, 0x7d, 0x43, 0x00, 0x74, 0xcc, 0x11, 0xbf, 0xee, 0x80, 0xe5, 0x89, 0x17, + 0xa8, 0x86, 0x10, 0xbe, 0xbc, 0x79, 0x40, 0xcf, 0x13, 0xd8, 0xc3, 0x3c, 0xba, 0xc1, 0x34, 0x3b, + 0xbd, 0xa6, 0xf9, 0x75, 0x7e, 0xd8, 0x61, 0x13, 0x7a, 0xe9, 0xaf, 0x49, 0xc4, 0x0b, 0x9d, 0xa1, + 0xa4, 0x32, 0x13, 0x99, 0x25, 0x54, 0x41, 0xa6, 0xbe, 0xb1, 0x4d, 0x9f, 0x91, 0x22, 0x03, 0x7b, + 0x0f, 0x7c, 0x44, 0xf8, 0xac, 0x19, 0xb1, 0x37, 0xac, 0x7d, 0x4a, 0xb5, 0x84, 0x49, 0x76, 0x77, + 0x77, 0xc4, 0x1e, 0xfe, 0xe4, 0x8c, 0x33, 0x4f, 0xfa, 0x15, 0xef, 0x79, 0x04, 0x4a, 0x75, 0x13, + 0xd1, 0x81, 0xf7, 0xfe, 0x73, 0xfe, 0x44, 0x63, 0x35, 0xea, 0xf2, 0xee, 0x35, 0x13, 0x94, 0x17, + 0x24, 0xbf, 0x86, 0x43, 0xf3, 0x5c, 0x21, 0x9a, 0xd1, 0xa1, 0x82, 0xc7, 0xe3, 0x1c, 0xb4, 0x5d, + 0x3b, 0x7f, 0xe5, 0xe0, 0x7c, 0x64, 0x06, 0x28, 0x00, 0xf3, 0x7d, 0xae, 0x73, 0x67, 0x4d, 0xba, + 0x24, 0x6a, 0x58, 0x60, 0x50, 0x1e, 0xd7, 0x54, 0x00, 0x53, 0xc0, 0x56, 0xd6, 0x65, 0x1e, 0xf0, + 0xed, 0x32, 0xb6, 0x03, 0xe6, 0xbd, 0x4a, 0x40, 0x5f, 0x10, 0x64, 0x63, 0xff, 0xde, 0x96, 0x13, + 0x5c, 0xec, 0x6d, 0xc1, 0x46, 0xda, 0x0c, 0x47, 0x1a, 0x0d, 0xd5, 0xa9, 0x49, 0xa2, 0xef, 0x26, + 0x3f, 0xf8, 0x44, 0x6f, 0x82, 0x50, 0x30, 0xc5, 0x5f, 0xc8, 0xf4, 0x6d, 0xe2, 0x07, 0xcf, 0xc2, + 0xa1, 0x66, 0xe9, 0xe0, 0xf0, 0x8d, 0x8c, 0x34, 0xb8, 0x14, 0x0c, 0xee, 0xbb, 0x69, 0x73, 0x9d, + 0xc0, 0x23, 0xa4, 0xde, 0x49, 0x7c, 0x0c, 0xe9, 0xed, 0x8c, 0x20, 0x2b, 0x78, 0x6a, 0x57, 0x48, + 0x4c, 0x41, 0xbd, 0xbd, 0xf9, 0xa7, 0x42, 0x67, 0xa7, 0x3d, 0x4d, 0x7b, 0x8e, 0xab, 0x64, 0x1e, + 0x2a, 0xa4, 0x29, 0x13, 0x35, 0x80, 0xe7, 0xcf, 0x7f, 0x8c, 0x38, 0x73, 0xe8, 0x55, 0xff, 0xc2, + 0x73, 0x6d, 0x23, 0x8c, 0x31, 0x3e, 0x17, 0x2c, 0x57, 0x8e, 0x17, 0x51, 0x3d, 0x5e, 0x42, 0xcf, + 0x91, 0x33, 0xe3, 0x05, 0xbf, 0xde, 0x69, 0x62, 0x69, 0xbe, 0x86, 0x35, 0x60, 0x45, 0x56, 0xc0, + 0x0f, 0x7f, 0x47, 0x93, 0xf7, 0x5c, 0x20, 0xaf, 0x80, 0x87, 0xa1, 0xca, 0xdc, 0xd9, 0x37, 0x17, + 0x45, 0xe5, 0x3f, 0x62, 0x66, 0xa5, 0x72, 0x6e, 0xf4, 0x4f, 0xd9, 0xd0, 0xdf, 0xf7, 0x05, 0x20, + 0x08, 0x6c, 0xb5, 0xc3, 0xe5, 0xcd, 0x79, 0xf7, 0x96, 0x7d, 0x00, 0x12, 0x64, 0xee, 0xed, 0xed, + 0xd3, 0x87, 0xda, 0x77, 0xf8, 0x72, 0x3f, 0xc8, 0x1b, 0x39, 0x27, 0x26, 0x85, 0xf8, 0xae, 0x1b, + 0xf1, 0xd3, 0xb8, 0xb3, 0xa5, 0xd8, 0xc3, 0xe5, 0x75, 0x15, 0x8d, 0xc6, 0x0a, 0x00, 0xc8, 0x20, + 0x3b, 0x91, 0xeb, 0x09, 0xa5, 0xb7, 0x4d, 0xf6, 0x20, 0xa0, 0x40, 0x87, 0xa2, 0x6f, 0xb2, 0xc3, + 0x1c, 0x19, 0x12, 0x4c, 0x86, 0xf1, 0x95, 0x31, 0x63, 0x42, 0x39, 0xca, 0x99, 0x00, 0x82, 0x89, + 0x4d, 0xff, 0x75, 0x47, 0xf5, 0x50, 0xa5, 0xd6, 0xe2, 0x3e, 0x79, 0x86, 0x3c, 0x8c, 0x3f, 0x07, + 0xf5, 0x69, 0xb4, 0xa6, 0x4e, 0x0e, 0x05, 0x31, 0x7f, 0xe2, 0xac, 0xa5, 0x6b, 0x14, 0x41, 0x3a, + 0xaa, 0x6c, 0xec, 0x5e, 0x3a, 0x7e, 0x08, 0xb2, 0x56, 0xb7, 0x6b, 0x5c, 0xae, 0x65, 0x32, 0x01, + 0xcc, 0x4a, 0xbd, 0xd8, 0x81, 0x11, 0x34, 0x7e, 0xf8, 0x33, 0x4f, 0xc4, 0xd1, 0x31, 0x3b, 0x77, + 0x38, 0x43, 0xc2, 0xe3, 0x4b, 0x1b, 0xf3, 0x9f, 0x7e, 0x9c, 0x2f, 0xe5, 0x39, 0x7c, 0x6a, 0xe9, + 0xaa, 0x0e, 0xf2, 0x98, 0x25, 0xec, 0x64, 0x0d, 0x36, 0x06, 0xf9, 0x98, 0x24, 0x6a, 0x0d, 0xb5, + 0x0f, 0x2f, 0x64, 0x73, 0xe5, 0xb6, 0xe2, 0x50, 0xbb, 0x1c, 0xff, 0x14, 0xee, 0x2a, 0x54, 0x30, + 0x2f, 0xa7, 0xef, 0x86, 0xbf, 0x77, 0x08, 0x4f, 0xaa, 0xb9, 0x60, 0xd6, 0x5f, 0xfc, 0x54, 0x71, + 0x2b, 0x1b, 0x00, 0x14, 0x47, 0x14, 0x59, 0x6b, 0xf4, 0xe2, 0x1f, 0x8f, 0xf6, 0xc2, 0x35, 0x61, + 0x5b, 0xc4, 0xd2, 0x4f, 0xd2, 0xcd, 0x6e, 0x16, 0x0c, 0xb4, 0x79, 0x32, 0x5f, 0x8a, 0xeb, 0x72, + 0x31, 0x52, 0x5d, 0xbc, 0xe5, 0x79, 0x07, 0xa1, 0x69, 0x3f, 0xcf, 0xa0, 0xc4, 0x67, 0x0a, 0x60, + 0x08, 0x76, 0x10, 0xcd, 0xeb, 0x0f, 0x41, 0x31, 0xbf, 0x10, 0xe6, 0x9b, 0x56, 0x5c, 0x45, 0x55, + 0xf5, 0xf4, 0x9d, 0x0b, 0x43, 0xbf, 0xb7, 0xb0, 0x51, 0xec, 0x46, 0x4c, 0x00, 0xb8, 0xc1, 0x98, + 0xea, 0xce, 0xa2, 0xf2, 0xf1, 0x10, 0x06, 0xd3, 0x3b, 0x1b, 0x79, 0xb7, 0xf4, 0x77, 0xf4, 0xc6, + 0x62, 0xca, 0x40, 0xe9, 0x6e, 0xd0, 0x7e, 0x21, 0xed, 0x7f, 0x2e, 0x02, 0xcd, 0xee, 0xbd, 0x4d, + 0xd2, 0xb1, 0xc5, 0x26, 0x9b, 0x3c, 0x53, 0xdc, 0x51, 0x75, 0x5c, 0xc8, 0xc8, 0x98, 0x14, 0x83, + 0x32, 0x64, 0xc0, 0x28, 0x3f, 0x68, 0x10, 0xa6, 0x08, 0x7b, 0x8d, 0x8b, 0x53, 0x29, 0xfa, 0x6d, + 0xe2, 0x1a, 0xfc, 0x12, 0x43, 0x9f, 0x15, 0x35, 0x18, 0x6b, 0x7f, 0xfd, 0xb5, 0xf8, 0x72, 0x2c, + 0x3b, 0xa2, 0x6a, 0x75, 0x9e, 0xe4, 0xac, 0x3c, 0xbf, 0x89, 0xd8, 0xc6, 0xaa, 0xc2, 0x1f, 0xc7, + 0xd7, 0x4b, 0x4b, 0x47, 0x91, 0x44, 0x5f, 0x41, 0xbc, 0x42, 0x32, 0x70, 0x3f, 0x2f, 0x3e, 0x3c, + 0x27, 0x48, 0xe2, 0xe8, 0x94, 0x30, 0x53, 0x10, 0x65, 0x40, 0xfe, 0x3e, 0x81, 0x86, 0x3b, 0xa6, + 0xce, 0x19, 0xa7, 0x76, 0xfd, 0x09, 0x1a, 0x01, 0x79, 0xe2, 0xd1, 0x3b, 0xd7, 0x72, 0xea, 0x5f, + 0x0a, 0xe0, 0x4b, 0x3b, 0x1e, 0x0c, 0x30, 0x99, 0xf9, 0xd3, 0x95, 0x31, 0xee, 0x13, 0x5f, 0x83, + 0xdd, 0x2d, 0x72, 0x9a, 0x42, 0xc6, 0xc7, 0xaa, 0xf2, 0x01, 0x1b, 0xa3, 0x98, 0xb5, 0x9e, 0x59, + 0x37, 0x09, 0xde, 0x57, 0x24, 0x0b, 0x34, 0xff, 0x41, 0x09, 0x99, 0xbb, 0xa6, 0xe9, 0x34, 0xd0, + 0x02, 0xd1, 0x53, 0x68, 0xad, 0x5f, 0x2f, 0x9e, 0x4f, 0x13, 0x34, 0x08, 0xcb, 0x7e, 0x8c, 0x7b, + 0x10, 0x68, 0x19, 0xcb, 0x65, 0xa9, 0x8c, 0x27, 0xa3, 0x88, 0x17, 0xa7, 0x29, 0x65, 0xb2, 0x45, + 0x68, 0xfc, 0x48, 0xaa, 0x4e, 0x6a, 0xf4, 0x0d, 0x4f, 0xbe, 0x91, 0xe2, 0x5b, 0x6a, 0x6a, 0x04, + 0xdd, 0xc4, 0xff, 0xcd, 0x5d, 0xa4, 0x32, 0x64, 0xba, 0x67, 0x34, 0xf1, 0x01, 0x6f, 0xe6, 0x28, + 0x6c, 0x1d, 0xd2, 0x17, 0x67, 0x93, 0xe2, 0x5d, 0x75, 0xc5, 0x29, 0x21, 0x03, 0x0d, 0x8d, 0x24, + 0xa4, 0xce, 0xe8, 0xe5, 0x16, 0x92, 0x9f, 0xed, 0x5e, 0xbc, 0x81, 0x2b, 0x25, 0x59, 0x48, 0x29, + 0x85, 0x2b, 0xec, 0x11, 0x1b, 0x62, 0x7d, 0xc0, 0xce, 0xca, 0xf7, 0xce, 0x32, 0x4d, 0x20, 0xd6, + 0xf1, 0x0b, 0xf9, 0xe9, 0x7b, 0x50, 0x0d, 0x9b, 0xed, 0xa2, 0x63, 0x16, 0xe7, 0xb6, 0x9e, 0xb0, + 0xd3, 0xe4, 0x29, 0xa3, 0xc9, 0xdb, 0x38, 0x9e, 0x67, 0x9d, 0xd8, 0x32, 0xd4, 0x79, 0x2e, 0x90, + 0x37, 0x0a, 0x66, 0xf0, 0x84, 0x28, 0x62, 0x5b, 0x1f, 0x26, 0x3f, 0xf8, 0xb9, 0xd0, 0xe5, 0x31, + 0x0a, 0xe2, 0x8f, 0xd7, 0xc1, 0xac, 0x09, 0xaa, 0xd6, 0x52, 0x1e, 0x63, 0x99, 0x74, 0x8c, 0xd9, + 0xa0, 0xc7, 0x4e, 0xa6, 0xeb, 0x4e, 0x95, 0x3f, 0x6c, 0x63, 0xa8, 0x5e, 0x72, 0x80, 0x70, 0x2d, + 0x05, 0x00, 0x9e, 0xfc, 0x7d, 0x77, 0x3c, 0x72, 0xc3, 0x9e, 0xc7, 0xd1, 0x75, 0xd6, 0x2d, 0xcf, + 0x79, 0x66, 0x1b, 0x11, 0x20, 0x5b, 0x6e, 0x5d, 0x17, 0xcd, 0x71, 0x81, 0x82, 0xa8, 0x0a, 0x0a, + 0xa2, 0x21, 0x15, 0xec, 0xbb, 0x50, 0xc7, 0xb8, 0x82, 0x14, 0x0d, 0xc0, 0x81, 0xe5, 0x60, 0xa7, + 0xf3, 0xc8, 0x22, 0x06, 0xdb, 0x10, 0xff, 0x9d, 0xbb, 0xb1, 0xd0, 0x1c, 0x31, 0x21, 0xfb, 0xe2, + 0x7d, 0x49, 0xf4, 0xcf, 0xea, 0xcb, 0x2a, 0xaf, 0xc9, 0xb8, 0xee, 0x38, 0x10, 0xd5, 0x59, 0x9c, + 0xc1, 0x40, 0x28, 0x52, 0xe5, 0x9d, 0x46, 0xe7, 0xd0, 0x74, 0x24, 0x41, 0x80, 0xf6, 0xeb, 0x7a, + 0x35, 0x97, 0x43, 0x9d, 0x81, 0x3c, 0x51, 0x5f, 0x09, 0x32, 0x2e, 0x67, 0x29, 0xa2, 0xef, 0x47, + 0xad, 0x53, 0xe5, 0x60, 0x2b, 0xca, 0xc8, 0x43, 0x1d, 0xc4, 0x87, 0x0c, 0xa2, 0xdb, 0x5c, 0xf7, + 0xdf, 0x73, 0x8e, 0x85, 0x94, 0xb0, 0xe1, 0xe5, 0x1a, 0x40, 0xfe, 0x89, 0xa1, 0xdb, 0x64, 0xbc, + 0xcc, 0x5f, 0x43, 0x60, 0xfd, 0x5e, 0x93, 0x25, 0x5c, 0x54, 0xc3, 0x14, 0x71, 0x3a, 0x2d, 0x9d, + 0xbe, 0xf5, 0x0c, 0x4b, 0xd1, 0x84, 0x40, 0x4f, 0xa3, 0xf7, 0xfb, 0xde, 0x95, 0xed, 0xa9, 0xe5, + 0x50, 0xbb, 0x00, 0xbf, 0x08, 0x38, 0xa6, 0x4a, 0x9d, 0xa0, 0x6e, 0x6a, 0x83, 0x5d, 0xe5, 0x0c, + 0x21, 0x7d, 0x3a, 0x9c, 0xa7, 0x0b, 0x05, 0x0d, 0x00, 0x91, 0x5a, 0x4d, 0x1b, 0x85, 0x5b, 0x88, + 0x39, 0x69, 0x95, 0x4d, 0x96, 0x22, 0x34, 0x5d, 0x9f, 0xd4, 0x79, 0x28, 0x22, 0x03, 0xef, 0xcd, + 0x3e, 0xb5, 0x26, 0x73, 0x18, 0x10, 0xa3, 0x25, 0xdf, 0xaa, 0xc8, 0x45, 0x66, 0xcf, 0x43, 0xf7, + 0x02, 0x0e, 0xa5, 0xd2, 0x8f, 0xe4, 0x59, 0x98, 0xa5, 0x94, 0x71, 0x9a, 0xef, 0x84, 0xbb, 0x7e, + 0x3f, 0x2a, 0xe7, 0x00, 0x0b, 0x0f, 0x88, 0x06, 0x67, 0x2f, 0x3c, 0x28, 0x0e, 0xe9, 0xc7, 0x1a, + 0x03, 0x9c, 0x8d, 0xa8, 0xf0, 0x32, 0x24, 0xe9, 0x33, 0x84, 0x9b, 0xa4, 0x81, 0xa5, 0xa4, 0x6a, + 0xd0, 0x9c, 0x2c, 0x82, 0x4f, 0x10, 0x4c, 0xa0, 0x0c, 0xfe, 0xe3, 0xb9, 0xc8, 0x7a, 0xb7, 0x89, + 0x01, 0x60, 0xd8, 0x6f, 0xbe, 0xe9, 0x77, 0x14, 0xbd, 0xa7, 0x73, 0x2c, 0x39, 0xff, 0x1a, 0x42, + 0x3b, 0xa4, 0x09, 0x1f, 0x55, 0xe4, 0xbf, 0xec, 0xb1, 0xf1, 0xd8, 0x43, 0xb6, 0x0d, 0x44, 0xa2, + 0x8d, 0xad, 0x6f, 0xaf, 0xc9, 0xea, 0x85, 0xf8, 0x43, 0x4b, 0xa4, 0xed, 0xf7, 0xe4, 0x37, 0x15, + 0xe1, 0x81, 0x03, 0x2b, 0x42, 0xe7, 0x3c, 0xd7, 0xbe, 0x33, 0xf1, 0x28, 0xbf, 0xea, 0x53, 0x31, + 0xe1, 0x63, 0x54, 0x99, 0x3d, 0x61, 0xe8, 0xda, 0xa1, 0xeb, 0xb1, 0xfb, 0xaa, 0xd7, 0xfa, 0x89, + 0x78, 0x78, 0xd6, 0x87, 0xb2, 0x01, 0xdb, 0x06, 0x6f, 0xf4, 0xb9, 0x3b, 0x92, 0xe2, 0x4e, 0xca, + 0x36, 0x64, 0x9f, 0x95, 0x13, 0x90, 0xe9, 0x2b, 0x25, 0x08, 0x06, 0x1c, 0x1b, 0x9f, 0xed, 0x29, + 0x58, 0xfa, 0x24, 0xb3, 0x07, 0x07, 0x0a, 0x23, 0xb1, 0xa4, 0xa2, 0x0a, 0xb2, 0x11, 0xbc, 0x0b, + 0x10, 0xdb, 0x97, 0xc3, 0x5d, 0x33, 0xd1, 0xf4, 0xd1, 0x88, 0xe4, 0xaa, 0x10, 0xe1, 0xde, 0xc1, + 0xea, 0xb6, 0xf1, 0x62, 0x1b, 0x3f, 0x34, 0x34, 0x1c, 0x08, 0x08, 0xf3, 0xd9, 0xe9, 0xcf, 0xc0, + 0xa2, 0x16, 0xd3, 0xc0, 0xa1, 0xa1, 0x49, 0x7a, 0x19, 0xa1, 0x19, 0xca, 0xc1, 0xa5, 0x34, 0x4b, + 0x51, 0x56, 0x6c, 0x42, 0x05, 0x59, 0x41, 0xee, 0x48, 0x0c, 0xb7, 0xc2, 0x5e, 0xe9, 0x52, 0xc4, + 0xf6, 0x9a, 0x80, 0x79, 0xd9, 0x49, 0x9e, 0xbe, 0x07, 0xc9, 0x69, 0x07, 0x6f, 0x84, 0xc5, 0x19, + 0x58, 0x78, 0xb4, 0x0c, 0x89, 0x90, 0x37, 0xb6, 0xdc, 0xd3, 0x17, 0x93, 0xd1, 0x49, 0x2b, 0x6f, + 0x00, 0x86, 0x33, 0x49, 0xc3, 0xc0, 0xfa, 0x0d, 0x01, 0x59, 0x7d, 0x18, 0x7d, 0xb1, 0xcb, 0xd3, + 0x2f, 0xf7, 0x7e, 0x97, 0x58, 0xf5, 0xd4, 0x83, 0x42, 0x93, 0xf1, 0x28, 0x48, 0xd0, 0x36, 0xf0, + 0xb3, 0x3b, 0x7f, 0x2a, 0x1c, 0xf0, 0xa2, 0xc4, 0x14, 0x7d, 0xc9, 0xfd, 0xb2, 0x8f, 0xc9, 0x1a, + 0xa0, 0x53, 0x5b, 0x18, 0x66, 0xed, 0x65, 0xe4, 0xe3, 0xbe, 0x16, 0x6c, 0xe3, 0xa5, 0x06, 0x5f, + 0x34, 0x4d, 0x43, 0x6d, 0xe6, 0x8b, 0x80, 0x2b, 0x61, 0xfb, 0xe2, 0xa1, 0x3b, 0xf1, 0x75, 0x20, + 0x88, 0x98, 0xc1, 0xb0, 0xc0, 0x9a, 0xa5, 0x08, 0x59, 0x94, 0x53, 0x85, 0x27, 0xde, 0xd7, 0x73, + 0xa9, 0x8d, 0xbd, 0x52, 0x2b, 0x76, 0x70, 0xb0, 0xc5, 0x41, 0x94, 0x3b, 0x20, 0x55, 0x76, 0xa4, + 0xe2, 0xb2, 0x3c, 0x81, 0x31, 0x44, 0x4d, 0xc1, 0xb4, 0xd3, 0xd7, 0x9e, 0x27, 0xb9, 0x27, 0xf9, + 0x3f, 0xb9, 0x53, 0x9a, 0x85, 0x59, 0x29, 0x3c, 0x53, 0xf4, 0x30, 0xc2, 0xf9, 0xf4, 0xba, 0xfe, + 0x1a, 0x2a, 0xf6, 0xa8, 0x1a, 0x32, 0x62, 0x26, 0xfb, 0x25, 0xcb, 0x4d, 0xbb, 0x4c, 0x6f, 0x46, + 0x32, 0x1b, 0xa3, 0xe9, 0x1b, 0x47, 0x34, 0xe2, 0x63, 0x76, 0x08, 0x03, 0x66, 0xda, 0xca, 0x6f, + 0xb1, 0x38, 0x80, 0xfb, 0xa1, 0x4b, 0x76, 0x05, 0x24, 0x41, 0x9a, 0xbc, 0x67, 0x01, 0xbd, 0x3e, + 0xe8, 0xda, 0x6e, 0xb3, 0x92, 0x96, 0xbf, 0xa5, 0x6b, 0xd8, 0x3a, 0xaa, 0xb8, 0xa7, 0xe1, 0xe0, + 0xc6, 0xa4, 0xb3, 0x95, 0xda, 0x3a, 0xad, 0x2e, 0xa4, 0x1f, 0x74, 0x6e, 0x50, 0x42, 0xa0, 0xb3, + 0x19, 0xe5, 0x6b, 0x3e, 0xc8, 0x66, 0xb6, 0xb6, 0xa1, 0x28, 0x40, 0xd9, 0xec, 0x7b, 0x74, 0x05, + 0x9f, 0xdb, 0x68, 0x84, 0xac, 0xa9, 0xee, 0xdf, 0x2e, 0xe4, 0xa7, 0x53, 0xc7, 0x02, 0x63, 0xd4, + 0x7d, 0xe8, 0xf9, 0x1b, 0x09, 0x40, 0x8b, 0x37, 0x29, 0xb7, 0xc8, 0xf3, 0xf0, 0x33, 0x84, 0x59, + 0x19, 0xd8, 0x93, 0x74, 0x8a, 0x34, 0xb7, 0x79, 0x83, 0x04, 0xa3, 0xca, 0xd4, 0x5e, 0x85, 0x57, + 0x69, 0xbd, 0xf2, 0x74, 0x35, 0xfd, 0xaf, 0x2f, 0x64, 0x83, 0xc3, 0xee, 0x1f, 0xba, 0xfc, 0x9d, + 0x5b, 0xa3, 0x0e, 0x40, 0x46, 0x61, 0x66, 0x0f, 0x03, 0x13, 0x6b, 0xea, 0x6b, 0xa0, 0xb2, 0xac, + 0x5a, 0x94, 0x43, 0x1b, 0x39, 0x4d, 0xbd, 0x66, 0xf0, 0xf4, 0x86, 0xf8, 0x38, 0xfe, 0xcd, 0xf5, + 0x64, 0x76, 0x36, 0x2a, 0x21, 0xed, 0xc6, 0x11, 0xcf, 0xcc, 0xa2, 0x31, 0x78, 0xa4, 0x8f, 0xb8, + 0x39, 0xd0, 0xf6, 0x25, 0x5a, 0xaa, 0xa3, 0xd4, 0xd1, 0xcb, 0xd0, 0x69, 0x77, 0xff, 0x4b, 0xc2, + 0x8c, 0xa6, 0x20, 0xc7, 0xd5, 0x78, 0x5a, 0xc8, 0xd9, 0x3a, 0x44, 0xb4, 0x60, 0xaf, 0x40, 0xfb, + 0x6d, 0xad, 0x2f, 0x7b, 0x00, 0xce, 0xb8, 0xcc, 0x47, 0x5b, 0x3e, 0xa7, 0x4d, 0x52, 0x7a, 0x7c, + 0x6d, 0x9f, 0xa3, 0x15, 0xa8, 0xe5, 0x5c, 0x27, 0xed, 0x4d, 0xda, 0x62, 0x0e, 0x15, 0xd3, 0x90, + 0xe7, 0x53, 0xc8, 0xf1, 0x23, 0x87, 0xd4, 0x58, 0xa2, 0x95, 0x03, 0xa8, 0x02, 0x35, 0xf3, 0x12, + 0xa7, 0x4b, 0x40, 0x9b, 0x19, 0x94, 0x24, 0xda, 0x3b, 0x2f, 0xc6, 0x73, 0x58, 0xc8, 0x27, 0x35, + 0xe7, 0x67, 0xca, 0x88, 0x2a, 0x9c, 0xe4, 0xb0, 0x9b, 0xfa, 0xc8, 0x17, 0xab, 0xe6, 0xe4, 0x8c, + 0xc9, 0xa2, 0xd6, 0x4c, 0x32, 0x7e, 0xb1, 0x36, 0x87, 0x14, 0xbd, 0xd6, 0x70, 0xab, 0xe1, 0x1d, + 0x8e, 0x1e, 0x43, 0x6b, 0x3b, 0xd3, 0x23, 0x79, 0x7e, 0x8e, 0x0e, 0x7b, 0x77, 0xe7, 0x24, 0xb3, + 0x7d, 0x3f, 0x7f, 0x2a, 0x8a, 0x99, 0xdc, 0xbc, 0x01, 0x29, 0xd7, 0x52, 0x77, 0xb2, 0x90, 0x7f, + 0xaa, 0x4b, 0xd7, 0x77, 0x5f, 0x6d, 0x6b, 0xff, 0xf5, 0xad, 0x13, 0x2e, 0xa3, 0x5c, 0xa2, 0xa5, + 0x07, 0x05, 0x9c, 0x0b, 0xae, 0xbc, 0xee, 0xff, 0x54, 0xcf, 0xfb, 0x18, 0x82, 0x7b, 0x7c, 0xc1, + 0xe5, 0x24, 0x08, 0x36, 0xb7, 0x6a, 0xa0, 0x20, 0x56, 0x18, 0xdc, 0xa8, 0x5d, 0x57, 0x79, 0xc7, + 0x86, 0x8d, 0xc5, 0xe9, 0x35, 0x48, 0x6f, 0x57, 0x6c, 0x40, 0x8d, 0x0d, 0xd3, 0x4a, 0x4a, 0x5a, + 0xd3, 0x7e, 0x67, 0x55, 0x80, 0xfb, 0x45, 0xdf, 0x81, 0x58, 0xf9, 0x34, 0xa7, 0x7e, 0xca, 0x1e, + 0x54, 0x31, 0x51, 0xb6, 0x4c, 0x20, 0x96, 0xf9, 0xa2, 0x16, 0xc8, 0xff, 0x0a, 0x66, 0xb9, 0x8d, + 0xe2, 0x67, 0x8b, 0x92, 0x0c, 0x66, 0x4c, 0x1b, 0x01, 0x0b, 0x30, 0xd2, 0xeb, 0x79, 0x9b, 0xc4, + 0xa8, 0x0f, 0xc9, 0x80, 0xe8, 0x8b, 0x9c, 0x60, 0x9d, 0x25, 0xa0, 0xac, 0xb2, 0xb0, 0x98, 0xe0, + 0xae, 0x15, 0x36, 0x0a, 0xaa, 0xa2, 0x75, 0xa0, 0xc3, 0x2c, 0x19, 0xa9, 0x2e, 0xde, 0x09, 0x6b, + 0xc6, 0x19, 0xea, 0xee, 0xa7, 0x03, 0x5e, 0xdf, 0xd2, 0x23, 0xc9, 0x4f, 0x8f, 0xb5, 0x42, 0xdc, + 0x4d, 0x2f, 0x6b, 0x08, 0x51, 0x05, 0x6e, 0x90, 0xa4, 0x94, 0xef, 0xe9, 0x0d, 0x7f, 0x91, 0x85, + 0x0a, 0xd3, 0x1e, 0xc6, 0xcf, 0x6b, 0x93, 0xb2, 0xeb, 0x67, 0x72, 0x11, 0x03, 0xae, 0x63, 0x98, + 0x97, 0xfe, 0xf0, 0xa8, 0xfb, 0x27, 0x79, 0xc5, 0x69, 0x8c, 0x1a, 0x15, 0xa4, 0x78, 0x36, 0xe5, + 0x26, 0xa0, 0x03, 0x6d, 0x01, 0x02, 0xaf, 0xab, 0x1f, 0xfc, 0xf7, 0xdb, 0x16, 0x37, 0xde, 0x1f, + 0x21, 0x78, 0x04, 0x46, 0xb8, 0x91, 0x3e, 0x73, 0xbb, 0xbe, 0x2f, 0xec, 0x0c, 0x5d, 0xc6, 0xbf, + 0xb6, 0xb1, 0xdb, 0x25, 0xba, 0xc2, 0x15, 0x4b, 0xa0, 0x8e, 0xb5, 0x7f, 0x75, 0xab, 0xee, 0xe3, + 0x41, 0xe9, 0xf6, 0x0d, 0xb7, 0x08, 0x02, 0x0f, 0x03, 0xe2, 0xa6, 0xaf, 0xd1, 0x9e, 0x14, 0x63, + 0x4f, 0x4f, 0xba, 0x99, 0x2a, 0xf5, 0xdc, 0xd5, 0x7c, 0x9b, 0x0f, 0x50, 0x5e, 0xf2, 0x93, 0xba, + 0x70, 0x78, 0xad, 0x2a, 0x25, 0xf7, 0xcc, 0x1d, 0x5c, 0xf4, 0xa5, 0x29, 0xa1, 0xcd, 0x6a, 0x7a, + 0x62, 0xc7, 0xc9, 0x73, 0xf1, 0x45, 0xc8, 0xc1, 0x91, 0x55, 0x4a, 0x47, 0x0f, 0x9f, 0xf9, 0xa6, + 0xb4, 0xcd, 0xd3, 0x99, 0x55, 0xde, 0x9b, 0xb9, 0xfa, 0x03, 0xd4, 0x26, 0x99, 0xd5, 0x4f, 0x95, + 0x6d, 0xf9, 0xe3, 0x3f, 0x60, 0x63, 0xaf, 0x60, 0x9a, 0xc5, 0xe5, 0x3b, 0xce, 0x73, 0x48, 0xb0, + 0x00, 0x52, 0x43, 0x44, 0x6c, 0x28, 0x96, 0xeb, 0xd0, 0xc3, 0xe3, 0xc8, 0x0a, 0x49, 0xd5, 0x24, + 0xcf, 0xe3, 0xde, 0xfe, 0x92, 0x25, 0x46, 0xf9, 0xd9, 0xcc, 0xce, 0x8c, 0xaf, 0xc6, 0xe9, 0x7f, + 0x58, 0x88, 0x15, 0x8a, 0x8d, 0x7c, 0xcc, 0x61, 0x33, 0xc9, 0xc0, 0xb8, 0xee, 0xfb, 0x3b, 0x4f, + 0x9b, 0x0e, 0xad, 0xe5, 0x77, 0xb5, 0x34, 0xed, 0x41, 0x96, 0xc0, 0x02, 0xca, 0x62, 0x75, 0x8a, + 0x16, 0x89, 0xce, 0x5a, 0xc5, 0x10, 0x3b, 0x65, 0x94, 0x85, 0xe5, 0x42, 0xe2, 0xd5, 0x85, 0x52, + 0x7a, 0x81, 0x96, 0x33, 0x30, 0x36, 0x31, 0x17, 0x2e, 0xce, 0xb3, 0x4a, 0x5c, 0x93, 0x90, 0x5b, + 0x67, 0xc7, 0x84, 0xdb, 0x26, 0x3f, 0x0b, 0xec, 0xff, 0x7e, 0x5f, 0xdd, 0x1b, 0x5f, 0xa1, 0x76, + 0xc9, 0x14, 0x27, 0x50, 0x98, 0x07, 0x58, 0x47, 0x84, 0x9b, 0x05, 0x18, 0x08, 0x34, 0xfd, 0xde, + 0xdd, 0x90, 0x7c, 0x96, 0x91, 0x36, 0x42, 0xec, 0xc7, 0x47, 0x6d, 0x18, 0xf2, 0x72, 0xc4, 0x97, + 0xd1, 0x9b, 0xf6, 0x21, 0xc1, 0xd7, 0x09, 0x56, 0x33, 0xfe, 0x2e, 0x60, 0x15, 0x07, 0x0d, 0x08, + 0x8e, 0x5e, 0xde, 0xb4, 0x75, 0x7c, 0xf2, 0xd8, 0xe8, 0xe5, 0x10, 0xdc, 0x99, 0xa3, 0x65, 0xec, + 0x1e, 0xb4, 0xf5, 0x17, 0x41, 0x51, 0x90, 0x3b, 0xa4, 0x16, 0xf4, 0xeb, 0xab, 0x81, 0x64, 0x2e, + 0x72, 0xd9, 0x28, 0x5e, 0xf7, 0x3c, 0xfd, 0xb8, 0x38, 0x2c, 0x09, 0xf1, 0x41, 0xf0, 0x5a, 0x0f, + 0xe7, 0x8d, 0xe7, 0x07, 0xd6, 0xeb, 0x0c, 0x42, 0xc9, 0x83, 0xb5, 0xbd, 0xa5, 0xc2, 0xfc, 0x7b, + 0x0e, 0x19, 0x25, 0x51, 0xc1, 0x01, 0xf0, 0x32, 0xad, 0xbf, 0x4c, 0x96, 0x97, 0x70, 0xc2, 0xa7, + 0x1a, 0x78, 0x52, 0x5f, 0x41, 0xe3, 0x1f, 0x5f, 0x7b, 0x61, 0x2b, 0x70, 0x3d, 0xce, 0x24, 0xea, + 0xad, 0xe4, 0x03, 0x77, 0xb7, 0xe9, 0x31, 0xcc, 0x09, 0x28, 0xed, 0xd5, 0x38, 0x13, 0xef, 0x9e, + 0xdd, 0x5f, 0xe3, 0xbf, 0x23, 0xc7, 0x72, 0xf5, 0x18, 0xed, 0xed, 0x62, 0xd7, 0x05, 0xa0, 0x13, + 0x73, 0xf8, 0x56, 0x52, 0xd2, 0x3b, 0x7a, 0x1d, 0xa0, 0x5d, 0x24, 0x54, 0x38, 0xbc, 0x0e, 0x2e, + 0xb6, 0x73, 0x8d, 0xe3, 0x25, 0x70, 0xde, 0x26, 0x44, 0x6b, 0x69, 0x3f, 0x27, 0x06, 0x45, 0x92, + 0xd6, 0x4b, 0x55, 0xcd, 0x2a, 0x42, 0x7d, 0x1b, 0x51, 0x74, 0xe7, 0x7b, 0x1d, 0x27, 0xfa, 0x83, + 0x0e, 0xa1, 0xe5, 0xc9, 0xab, 0xec, 0xb6, 0x8f, 0x7a, 0xd5, 0x49, 0x1e, 0x41, 0xc1, 0x33, 0xf8, + 0x5d, 0x6e, 0xfd, 0x42, 0xff, 0x3d, 0xec, 0x3c, 0x18, 0x63, 0x4a, 0x6a, 0xe5, 0x29, 0x0e, 0xd5, + 0xb9, 0xfa, 0x4b, 0x24, 0xfa, 0xa3, 0x04, 0x71, 0xce, 0x81, 0x57, 0x82, 0x23, 0x71, 0x00, 0xca, + 0xd5, 0xf1, 0x86, 0x49, 0x2f, 0x5c, 0x6f, 0x0a, 0xe9, 0x68, 0x37, 0x46, 0x92, 0x2e, 0x23, 0xd7, + 0x2e, 0x85, 0xc5, 0x3a, 0xb6, 0x2c, 0x32, 0x99, 0x14, 0xd4, 0x16, 0xe3, 0x9b, 0xbb, 0x7e, 0xc2, + 0x46, 0x2c, 0x34, 0x23, 0x9c, 0xab, 0xb5, 0xa0, 0xcf, 0x31, 0x95, 0x4e, 0x33, 0x02, 0x10, 0xb1, + 0xbb, 0x85, 0x68, 0xd7, 0xb8, 0xea, 0x0e, 0x84, 0xcf, 0x58, 0x55, 0x48, 0xd7, 0xa3, 0xdd, 0xf2, + 0x7e, 0x17, 0x03, 0x68, 0xe9, 0xc3, 0x7a, 0x22, 0xdf, 0xaa, 0x44, 0x3f, 0x2f, 0x90, 0xd4, 0xfc, + 0x5d, 0x09, 0x29, 0xb3, 0x5f, 0x93, 0x98, 0xdb, 0x01, 0x5b, 0x85, 0xee, 0x72, 0xf7, 0x84, 0x12, + 0x1e, 0x5b, 0xb6, 0x3e, 0xd1, 0xd4, 0xdd, 0xe9, 0x52, 0xc7, 0xb6, 0xde, 0x61, 0x93, 0xc0, 0xe5, + 0x0f, 0x4a, 0xdf, 0x1b, 0xf4, 0xbb, 0x7e, 0x72, 0x83, 0x06, 0x87, 0xcd, 0x89, 0x22, 0x05, 0x3e, + 0xf7, 0x16, 0x39, 0x9e, 0x2e, 0x2a, 0x1a, 0x4f, 0x40, 0x8e, 0xd1, 0xf4, 0x07, 0x04, 0x18, 0xed, + 0xb2, 0xbd, 0x31, 0x42, 0x04, 0xd6, 0x99, 0xa3, 0x93, 0x76, 0x85, 0x3d, 0xb3, 0x71, 0x1a, 0x59, + 0xde, 0x18, 0xb7, 0x2d, 0x0b, 0x45, 0x1f, 0x77, 0x7e, 0x95, 0x80, 0xc2, 0x47, 0x1c, 0x1f, 0x1f, + 0x67, 0xe2, 0x23, 0x8a, 0x97, 0x3a, 0xdc, 0x3a, 0x25, 0xab, 0x92, 0x76, 0xbf, 0x65, 0x2a, 0xf2, + 0xd3, 0x04, 0xf0, 0xa2, 0x63, 0xb1, 0x6b, 0x98, 0xd6, 0x9a, 0x86, 0x09, 0x65, 0xf8, 0xf0, 0x0d, + 0xc6, 0x5c, 0x56, 0x66, 0x3d, 0xd6, 0x55, 0xb7, 0x6f, 0xd7, 0xfb, 0x90, 0xcd, 0xfc, 0xe9, 0x52, + 0xd0, 0x66, 0xd8, 0x8f, 0x0d, 0x53, 0x84, 0x25, 0xf5, 0xae, 0xef, 0x5a, 0x3f, 0xde, 0x6c, 0xa9, + 0xa1, 0x02, 0x5d, 0x1b, 0x87, 0x2f, 0x11, 0x53, 0x6e, 0xb3, 0x81, 0xab, 0x05, 0x39, 0x23, 0x6b, + 0xf8, 0x65, 0xc6, 0xff, 0xef, 0x74, 0xa2, 0x0b, 0xcf, 0xfa, 0xe2, 0xf9, 0xe2, 0x0a, 0x08, 0xdd, + 0xa4, 0x9e, 0x44, 0xea, 0xad, 0x9f, 0x45, 0xa0, 0x8a, 0xce, 0xec, 0x09, 0x9f, 0x19, 0x40, 0x1f, + 0x85, 0x03, 0x6f, 0x3c, 0xf3, 0x0a, 0x49, 0x1c, 0x4e, 0x58, 0xa5, 0x2a, 0x1e, 0x0f, 0x98, 0xf5, + 0xf4, 0xeb, 0x83, 0xe6, 0x44, 0x15, 0x77, 0x97, 0x88, 0xee, 0x25, 0x70, 0x1f, 0x82, 0x21, 0xe2, + 0x4b, 0xea, 0x68, 0x93, 0x49, 0x46, 0x3e, 0xbc, 0x16, 0xbd, 0x8b, 0x49, 0xd6, 0x74, 0x9c, 0xb1, + 0x91, 0x38, 0xa6, 0x62, 0x33, 0x8c, 0xb5, 0x5d, 0x75, 0xe4, 0x8c, 0x4d, 0x9c, 0x7a, 0x78, 0xd1, + 0x4f, 0x07, 0x3e, 0x55, 0x38, 0x30, 0x83, 0x8b, 0x62, 0xf8, 0x95, 0x65, 0x03, 0xec, 0x5a, 0x29, + 0xdc, 0xf3, 0x3d, 0x52, 0x8e, 0x53, 0x7d, 0x45, 0x48, 0xe0, 0xfc, 0x37, 0x4b, 0x0e, 0xc5, 0x05, + 0x28, 0x8d, 0x11, 0x9b, 0xdf, 0x59, 0x70, 0xa8, 0x0f, 0x84, 0x63, 0xd5, 0x70, 0x5a, 0xbc, 0xc3, + 0x1b, 0x85, 0x39, 0xfd, 0xf5, 0xad, 0xbd, 0xef, 0x27, 0x6a, 0x56, 0xab, 0x5a, 0x23, 0xac, 0x33, + 0x9d, 0x9c, 0xd9, 0x46, 0xd2, 0xd6, 0x84, 0x18, 0xbd, 0xdb, 0xbe, 0xec, 0xc2, 0xfe, 0x79, 0x44, + 0xc8, 0xa1, 0xb5, 0xa1, 0xea, 0xb4, 0x20, 0x69, 0xde, 0x1a, 0x01, 0xe9, 0xc4, 0x8c, 0x95, 0x1e, + 0x7f, 0x65, 0xf6, 0xfe, 0x92, 0x26, 0x6a, 0xd9, 0xc8, 0x47, 0xdf, 0x9f, 0x9b, 0x1c, 0x61, 0xda, + 0x73, 0xb1, 0x75, 0x49, 0xb9, 0x5a, 0x4a, 0x5a, 0x64, 0x86, 0x8e, 0x98, 0x62, 0xa5, 0x52, 0x01, + 0xc9, 0xbe, 0xd9, 0xfd, 0x7f, 0x61, 0x71, 0x4c, 0x2f, 0x89, 0x4d, 0xcd, 0x25, 0x6f, 0x93, 0x60, + 0x94, 0x3b, 0x16, 0xd2, 0xeb, 0x54, 0x52, 0xf8, 0xd7, 0x9b, 0xd6, 0x3e, 0xf5, 0x53, 0x34, 0xf8, + 0x6d, 0xe4, 0xe9, 0xf4, 0x02, 0x06, 0x0c, 0x41, 0x90, 0xe5, 0x7f, 0x4c, 0xeb, 0x89, 0xc6, 0x4f, + 0x89, 0x9e, 0xff, 0x6f, 0x84, 0xd3, 0x84, 0xba, 0xaf, 0x6e, 0x63, 0x76, 0xdb, 0x0a, 0x98, 0xad, + 0x59, 0x73, 0xf2, 0x02, 0xad, 0x11, 0x86, 0x3a, 0x19, 0x68, 0x5f, 0x80, 0x66, 0xa6, 0x8f, 0xed, + 0x92, 0x27, 0xe1, 0x30, 0xf6, 0x6b, 0x7c, 0x66, 0x70, 0xc4, 0x9f, 0xe6, 0xff, 0x96, 0x57, 0xb1, + 0x87, 0xbf, 0xd0, 0x17, 0x2b, 0x5c, 0x51, 0x5d, 0xfa, 0x13, 0xd3, 0x4f, 0x83, 0x2c, 0x1c, 0xa7, + 0xe4, 0x4b, 0xb0, 0x57, 0xd2, 0xef, 0xfd, 0x82, 0xe3, 0xf8, 0x6b, 0xa1, 0x28, 0x86, 0x4a, 0xd0, + 0x82, 0x35, 0x81, 0xe4, 0x30, 0x69, 0x2e, 0x0f, 0xa1, 0x90, 0x9a, 0x1b, 0x5a, 0x91, 0xfe, 0xa1, + 0xa2, 0xb9, 0x0a, 0xb1, 0x69, 0x02, 0xc9, 0x00, 0x4e, 0xb5, 0xb0, 0x8d, 0x01, 0xea, 0x4d, 0x65, + 0xd7, 0x19, 0x96, 0x03, 0xab, 0x07, 0x32, 0x2c, 0x7f, 0xc4, 0x8d, 0x91, 0x44, 0xdf, 0xa5, 0xe5, + 0x88, 0x83, 0xff, 0x24, 0x93, 0x32, 0x69, 0x9a, 0x1f, 0x25, 0x28, 0x84, 0xc2, 0x82, 0x1b, 0x07, + 0x19, 0x13, 0x2b, 0xf2, 0x85, 0x7d, 0xd2, 0x77, 0x9c, 0x6e, 0xce, 0xcc, 0x0f, 0xa6, 0x03, 0xaf, + 0xc5, 0x94, 0x52, 0x24, 0xb7, 0x3c, 0x5a, 0x46, 0x2b, 0x08, 0x44, 0xa0, 0x19, 0xdb, 0xe7, 0xf2, + 0x95, 0x10, 0x59, 0x31, 0x73, 0x9f, 0x62, 0x05, 0x0d, 0x38, 0xe3, 0x65, 0x95, 0xc3, 0xf5, 0x0b, + 0x70, 0x0d, 0x9e, 0x3d, 0x3f, 0x39, 0x0b, 0x28, 0xee, 0x96, 0xda, 0x2c, 0x50, 0x01, 0xe6, 0xdd, + 0xd0, 0x74, 0x4d, 0x6b, 0x9a, 0x40, 0xf5, 0xe3, 0x7e, 0xfa, 0xf3, 0x11, 0x3e, 0xad, 0x63, 0xac, + 0xb7, 0x95, 0x38, 0x69, 0x4f, 0x66, 0xe0, 0xb6, 0x7c, 0x05, 0xca, 0xde, 0x3e, 0x16, 0x2c, 0x2b, + 0x5b, 0x61, 0x2f, 0x01, 0xf8, 0xe1, 0x4a, 0x65, 0x8f, 0x5c, 0x1d, 0x55, 0x88, 0xdf, 0x62, 0x55, + 0x67, 0xa6, 0x10, 0xf6, 0x1f, 0x6c, 0xd3, 0xe9, 0x59, 0x8d, 0x3e, 0x63, 0x30, 0x77, 0x48, 0x58, + 0x3c, 0x6f, 0x08, 0x47, 0xaa, 0x06, 0x57, 0xce, 0x27, 0x3d, 0xb4, 0x21, 0x17, 0x32, 0x45, 0x8b, + 0xd5, 0xc9, 0x20, 0x8e, 0x71, 0x77, 0xd6, 0xcb, 0xce, 0x3d, 0x28, 0x5e, 0x5a, 0x37, 0xb8, 0xe7, + 0x60, 0xa1, 0xf5, 0x94, 0x35, 0x4c, 0xf3, 0x79, 0x81, 0x34, 0x3a, 0xdb, 0x73, 0xac, 0x21, 0xf1, + 0xb4, 0xff, 0x42, 0x98, 0xe6, 0x70, 0x96, 0xfd, 0x5e, 0x88, 0x3f, 0x67, 0x9b, 0x82, 0x36, 0x20, + 0xdf, 0xc0, 0x1f, 0xad, 0x83, 0x17, 0x8a, 0xda, 0x45, 0xbc, 0xc5, 0xc3, 0x62, 0x07, 0xa8, 0xb7, + 0x91, 0x25, 0x4f, 0x03, 0x63, 0xb5, 0x16, 0xb1, 0x2d, 0xc6, 0xd9, 0x3b, 0x52, 0x30, 0xa9, 0xe4, + 0x1b, 0x11, 0x8f, 0xe9, 0x5c, 0xce, 0x80, 0xc2, 0x4c, 0x31, 0x10, 0xb7, 0x4f, 0x16, 0x39, 0x49, + 0x20, 0xd1, 0xb7, 0x66, 0x48, 0x5b, 0x67, 0xd8, 0xe8, 0x76, 0xc6, 0xa0, 0xe1, 0xa0, 0xdc, 0xdc, + 0xa1, 0xef, 0x46, 0x2d, 0x07, 0x5d, 0xad, 0xcc, 0xa9, 0xb0, 0x59, 0xe5, 0x69, 0x06, 0xa8, 0xb4, + 0xb3, 0x76, 0x3f, 0xff, 0xd8, 0x66, 0x5a, 0xe7, 0xa0, 0x19, 0x2e, 0x4a, 0x1d, 0x45, 0xe9, 0x9b, + 0xbb, 0x38, 0xb6, 0xad, 0x0a, 0x67, 0x0a, 0x9b, 0x29, 0x6e, 0x32, 0xc1, 0x4d, 0x27, 0x61, 0xbd, + 0x0a, 0x8d, 0x4f, 0xa1, 0xa3, 0xf1, 0x2d, 0x90, 0xd6, 0x3a, 0x91, 0x7f, 0xb7, 0x85, 0x41, 0xec, + 0x6f, 0xab, 0xaf, 0x93, 0x59, 0xef, 0x00, 0x1c, 0xd5, 0xc3, 0xc6, 0xa7, 0x49, 0xe6, 0x0a, 0xe0, + 0xda, 0x95, 0x9b, 0xb2, 0x0c, 0xf9, 0x3e, 0xae, 0x1c, 0x09, 0xca, 0x51, 0x35, 0xc6, 0xea, 0x58, + 0xbf, 0xe9, 0x16, 0x6a, 0xb1, 0xbe, 0x64, 0xff, 0xbf, 0x9d, 0xd4, 0x38, 0x47, 0x86, 0x17, 0x59, + 0xf2, 0xf3, 0x6c, 0x71, 0xee, 0x57, 0xb1, 0x80, 0xbd, 0xb0, 0xd4, 0xd6, 0xa0, 0xa0, 0x73, 0x82, + 0x0d, 0xad, 0xb2, 0x34, 0x6d, 0xac, 0x83, 0xd8, 0xed, 0xc7, 0x20, 0x7d, 0xc3, 0x30, 0x0b, 0xf3, + 0xb3, 0xd3, 0xce, 0x8f, 0x42, 0x2c, 0x8b, 0x29, 0xf8, 0xc7, 0xa3, 0x3c, 0x8b, 0x42, 0x3f, 0xf6, + 0x0f, 0x2b, 0x5b, 0x58, 0x69, 0x17, 0x33, 0xa2, 0x4f, 0x23, 0x22, 0xaf, 0xb4, 0x7c, 0xab, 0x7b, + 0x3c, 0xb4, 0x3d, 0x01, 0x83, 0xb1, 0x71, 0x22, 0xef, 0xa4, 0x59, 0xb2, 0x4c, 0x22, 0xe2, 0xb5, + 0x24, 0x96, 0x90, 0x3d, 0x55, 0xa1, 0xd0, 0x1e, 0x8c, 0x6c, 0xc2, 0xf0, 0x2b, 0xad, 0xaa, 0x27, + 0x99, 0xfa, 0x76, 0xd6, 0xc4, 0x67, 0xd4, 0x34, 0x1d, 0xb0, 0x4a, 0x03, 0x5c, 0x7c, 0x34, 0x0b, + 0x0f, 0xe5, 0x47, 0x4d, 0x32, 0x1c, 0xb3, 0x4f, 0x72, 0xf6, 0x1c, 0x29, 0x53, 0x71, 0x77, 0x91, + 0x5c, 0x4a, 0x2b, 0x8e, 0x12, 0x0b, 0x02, 0x77, 0xfd, 0xfa, 0xc0, 0x7c, 0x15, 0xbf, 0xb7, 0x54, + 0xfa, 0xbd, 0x90, 0x43, 0x1b, 0xa5, 0x7d, 0xf4, 0x6f, 0x7d, 0x30, 0xc8, 0x8b, 0x52, 0x02, 0x5b, + 0xeb, 0x17, 0xa4, 0x49, 0xa0, 0x9d, 0xef, 0xbb, 0xa7, 0xb3, 0x40, 0xa7, 0x3e, 0x14, 0x23, 0xbf, + 0x07, 0x06, 0xc6, 0xe5, 0xd6, 0x25, 0x4b, 0x5e, 0x2f, 0xf6, 0xa3, 0x86, 0xd8, 0xe5, 0xed, 0xae, + 0x2b, 0x1a, 0xc8, 0xb8, 0xd4, 0x4f, 0xbe, 0x9d, 0x53, 0x61, 0x2f, 0xa5, 0xd3, 0x5b, 0x51, 0x3a, + 0x5e, 0x22, 0x8d, 0xeb, 0x5e, 0xd6, 0xd4, 0x40, 0x3d, 0x0e, 0x0a, 0x1b, 0x91, 0xcd, 0xa0, 0xeb, + 0xd1, 0xff, 0xb4, 0x67, 0xe7, 0x0c, 0xf1, 0x37, 0x7e, 0x6c, 0x7f, 0xbb, 0x28, 0xfe, 0x4c, 0x9a, + 0x94, 0xa0, 0x14, 0x24, 0xb0, 0x3a, 0x29, 0x23, 0x71, 0xa3, 0xf8, 0x66, 0x16, 0xfa, 0x0a, 0xd9, + 0x70, 0x7a, 0x30, 0x37, 0xb9, 0x5f, 0x00, 0x08, 0xd7, 0x9c, 0xda, 0xd5, 0xc9, 0x82, 0x6c, 0x24, + 0x48, 0x12, 0xa9, 0x0e, 0x83, 0xb5, 0x6b, 0xe3, 0x56, 0x10, 0x70, 0x02, 0xaa, 0xf4, 0xd3, 0x2d, + 0xe7, 0xb9, 0x2a, 0x60, 0x4b, 0x01, 0x71, 0xcd, 0x90, 0xac, 0x59, 0x91, 0x32, 0x78, 0x15, 0x8a, + 0x52, 0x84, 0x75, 0x6d, 0xf8, 0x88, 0xe8, 0xa0, 0xdd, 0x27, 0xf9, 0x66, 0xf6, 0x9b, 0x9e, 0x14, + 0xcf, 0xcf, 0x0f, 0xb9, 0xad, 0x54, 0x9b, 0xa8, 0x4c, 0x90, 0x92, 0x6b, 0xf3, 0x5e, 0x7b, 0xa8, + 0xa5, 0x23, 0x4c, 0xdd, 0x57, 0x87, 0xe2, 0xa2, 0x07, 0xd9, 0x30, 0x38, 0xad, 0xbd, 0x72, 0xb0, + 0x15, 0x25, 0xa9, 0x94, 0x5f, 0x8e, 0x94, 0xf1, 0x6a, 0x5c, 0x87, 0x3d, 0x90, 0x70, 0x65, 0x42, + 0x1d, 0x3a, 0x2e, 0xf7, 0xe3, 0xb3, 0x8c, 0xbf, 0x1c, 0x38, 0xdc, 0xd6, 0x40, 0xa6, 0x18, 0x30, + 0x87, 0xab, 0x40, 0xb5, 0x7d, 0x3a, 0x8d, 0x75, 0x39, 0x8a, 0x92, 0xb2, 0x1c, 0xbc, 0x83, 0xe8, + 0x96, 0x91, 0x14, 0xd9, 0x68, 0xad, 0x12, 0xcc, 0x70, 0x22, 0xdd, 0x80, 0x8c, 0x81, 0xb6, 0xd6, + 0xc1, 0xf2, 0x1d, 0xa0, 0xfd, 0xf5, 0xb8, 0x83, 0x1a, 0x75, 0xd4, 0xaf, 0x64, 0x8b, 0x2b, 0xf7, + 0xf5, 0x31, 0x90, 0x79, 0xc6, 0x17, 0x23, 0x5f, 0xc6, 0x9e, 0x0e, 0x67, 0x3c, 0x0c, 0x5f, 0x0a, + 0x03, 0xb3, 0x98, 0xf4, 0x36, 0x75, 0x4c, 0x1e, 0xb5, 0x22, 0x6d, 0xe8, 0xe3, 0x16, 0x9f, 0xfd, + 0xdf, 0x33, 0x90, 0x1d, 0xea, 0xba, 0xde, 0x5a, 0x2b, 0x5d, 0xbe, 0xd7, 0x57, 0xcd, 0xc3, 0xbc, + 0xae, 0x02, 0xd3, 0x41, 0x1f, 0x3d, 0x5f, 0x83, 0xbc, 0x86, 0xf2, 0x5b, 0xb8, 0x7d, 0x0b, 0xd1, + 0x9a, 0x5a, 0x19, 0x5b, 0x8c, 0x53, 0xcd, 0x9a, 0x1c, 0x08, 0xec, 0xe9, 0xac, 0x3e, 0x41, 0x5a, + 0x31, 0xb1, 0x72, 0x05, 0xd6, 0xfd, 0x94, 0x70, 0x1d, 0xca, 0x05, 0x7c, 0x1c, 0x12, 0xcc, 0x42, + 0x2f, 0x26, 0x8d, 0xee, 0x4a, 0xdf, 0xaf, 0xab, 0x61, 0xd6, 0x24, 0x96, 0xe0, 0x40, 0x89, 0xff, + 0xb0, 0xc2, 0xce, 0x44, 0xf2, 0x71, 0x03, 0x06, 0x57, 0xfe, 0x26, 0x7c, 0x80, 0x7b, 0xdf, 0x08, + 0xcc, 0xd6, 0x09, 0x13, 0x2e, 0x9e, 0xd1, 0xa5, 0xad, 0x99, 0x64, 0xd7, 0x79, 0xf7, 0x28, 0xb1, + 0xd8, 0x72, 0x64, 0x3a, 0xdf, 0xf5, 0x9c, 0x84, 0x13, 0x5c, 0x54, 0x87, 0x37, 0x4f, 0xe4, 0x21, + 0x96, 0x9f, 0x0b, 0x36, 0x2b, 0xd1, 0x5c, 0xba, 0x77, 0x54, 0x93, 0x77, 0x63, 0xef, 0x5a, 0x50, + 0x01, 0x55, 0x94, 0x7b, 0x55, 0x3a, 0x05, 0x3f, 0x75, 0xe0, 0xfc, 0x9b, 0x0b, 0xa1, 0x25, 0xba, + 0xab, 0x24, 0x45, 0x62, 0x45, 0x10, 0x80, 0xfd, 0x43, 0x5b, 0x91, 0x92, 0x87, 0x95, 0xf4, 0x23, + 0xfd, 0xb2, 0x08, 0xea, 0x8f, 0xe7, 0xc5, 0x18, 0xdf, 0x33, 0xc6, 0x6d, 0xa2, 0x92, 0xa2, 0x19, + 0x5c, 0xca, 0x48, 0xcb, 0xcb, 0x3c, 0xdf, 0xcb, 0xf0, 0x24, 0xae, 0x12, 0x4d, 0xf6, 0xc3, 0x57, + 0xbd, 0x5c, 0x82, 0xda, 0xa2, 0x3e, 0x59, 0xdf, 0x8c, 0xb7, 0x67, 0x55, 0x0f, 0xb4, 0x56, 0xab, + 0x52, 0xe2, 0xfd, 0xc8, 0x7b, 0x80, 0x5e, 0xe4, 0x3e, 0xcf, 0x3c, 0xff, 0x59, 0x26, 0x22, 0x34, + 0x01, 0xe3, 0xde, 0xab, 0x74, 0x67, 0x72, 0x65, 0x91, 0xc5, 0x4d, 0xed, 0x2b, 0x96, 0x10, 0x24, + 0x4d, 0xb8, 0x4e, 0x40, 0xba, 0x92, 0x8d, 0xa8, 0xef, 0xf7, 0x57, 0x12, 0xeb, 0x30, 0x95, 0xec, + 0x14, 0x95, 0x2d, 0x4d, 0x94, 0x5a, 0xfc, 0x77, 0x5b, 0xf8, 0xc6, 0xb0, 0x6d, 0xb8, 0xde, 0xec, + 0x11, 0xd6, 0x7c, 0x51, 0xe6, 0x2c, 0x46, 0xe5, 0x41, 0x8b, 0x05, 0xc2, 0x2a, 0xa0, 0x44, 0x3c, + 0xb4, 0x05, 0x37, 0x0c, 0x66, 0x72, 0x33, 0xe4, 0x9a, 0x48, 0xdd, 0x80, 0xa5, 0x19, 0x32, 0x3d, + 0xbb, 0x0e, 0xf6, 0x21, 0x99, 0x0c, 0x14, 0x12, 0xcf, 0xd0, 0xe0, 0x93, 0x57, 0xb8, 0x22, 0x01, + 0x30, 0x45, 0x89, 0xa4, 0xe0, 0x03, 0xa3, 0x52, 0xec, 0x07, 0x36, 0x52, 0x53, 0xde, 0xbf, 0x06, + 0xa6, 0x7c, 0x67, 0x9c, 0xad, 0xcc, 0x56, 0x2c, 0x0e, 0xdd, 0x6a, 0xcb, 0x0b, 0x16, 0xa0, 0x9c, + 0x55, 0xc6, 0x7e, 0xfc, 0x99, 0x66, 0x41, 0xf0, 0x76, 0xdf, 0x03, 0x06, 0xec, 0x51, 0x90, 0xa7, + 0xfc, 0x50, 0x0e, 0x6a, 0x9d, 0xb5, 0xb9, 0xd5, 0x54, 0x28, 0x97, 0x04, 0x27, 0x35, 0x24, 0x87, + 0xc4, 0xd7, 0x17, 0x5b, 0xd0, 0x5c, 0x6c, 0x58, 0x89, 0xae, 0x96, 0xdd, 0x8e, 0x27, 0xa8, 0xfb, + 0x9a, 0x93, 0x54, 0x3a, 0xbd, 0x9e, 0x42, 0xd0, 0xb6, 0x7a, 0xc3, 0x08, 0xc6, 0xa5, 0x4f, 0xa6, + 0xc5, 0x8c, 0xfa, 0xb4, 0x74, 0x8f, 0x47, 0x5c, 0x85, 0x87, 0xf0, 0x46, 0x21, 0x40, 0x02, 0x8e, + 0x79, 0x19, 0xa7, 0xcf, 0xc6, 0xfa, 0x5c, 0x26, 0xfd, 0xa0, 0x3a, 0x66, 0xc1, 0xfa, 0x17, 0xef, + 0x07, 0x9f, 0x22, 0x1f, 0x0f, 0x8b, 0x80, 0x34, 0x8e, 0xc7, 0x2e, 0x42, 0xf0, 0x9b, 0x5d, 0xbc, + 0x26, 0xe7, 0x2d, 0xde, 0xbc, 0xdb, 0xeb, 0xc7, 0x29, 0x87, 0x07, 0xd9, 0xc7, 0xb5, 0x3e, 0x71, + 0xfb, 0xdc, 0x7f, 0x36, 0xa2, 0xe9, 0x58, 0xe6, 0xcc, 0x63, 0x75, 0x36, 0x52, 0xca, 0xe7, 0x06, + 0x1b, 0xa8, 0xbb, 0x03, 0x10, 0xce, 0xa5, 0xe9, 0x66, 0xac, 0xdd, 0x59, 0x0f, 0x3a, 0x90, 0x60, + 0x68, 0xe8, 0xeb, 0x60, 0xf1, 0xa8, 0xa0, 0xdc, 0x39, 0x07, 0x40, 0x05, 0x43, 0xb5, 0x6f, 0x3d, + 0x3b, 0x5a, 0x34, 0x53, 0xc2, 0x6c, 0xa4, 0x47, 0x4c, 0xe1, 0xfe, 0x7f, 0x37, 0xfb, 0x91, 0xca, + 0x28, 0x7a, 0xdc, 0xef, 0xde, 0xc4, 0x44, 0xf4, 0xc0, 0x22, 0xd2, 0x4c, 0x48, 0x16, 0x54, 0x01, + 0x7c, 0xdf, 0xe4, 0x3f, 0x29, 0x51, 0xae, 0x9c, 0x98, 0xf4, 0x73, 0x36, 0x94, 0x0d, 0xe2, 0xc8, + 0x35, 0xd9, 0xe2, 0xbc, 0x5c, 0x0b, 0xc7, 0xc6, 0xdd, 0x70, 0x2e, 0x6f, 0xdd, 0x23, 0xfe, 0xef, + 0x4c, 0xaf, 0x06, 0xce, 0x1c, 0x26, 0xf9, 0xe9, 0x02, 0x22, 0xe9, 0x4d, 0x26, 0x80, 0xbc, 0x5a, + 0x18, 0xc0, 0x2b, 0x76, 0xae, 0x65, 0x17, 0x6a, 0x56, 0xa4, 0xeb, 0xaa, 0xb7, 0x65, 0xe1, 0x55, + 0xfa, 0xe5, 0x08, 0x95, 0x3c, 0x33, 0xca, 0xa0, 0xb0, 0x03, 0x09, 0x22, 0x81, 0x98, 0x3b, 0x93, + 0x6e, 0xb2, 0x1a, 0xba, 0x05, 0x0c, 0xfd, 0xe4, 0x51, 0x10, 0xe0, 0x1c, 0x1e, 0xf5, 0x7c, 0xf8, + 0x22, 0x86, 0x6d, 0x00, 0x2d, 0x39, 0xaf, 0x8a, 0x25, 0xa2, 0xbc, 0x8b, 0x80, 0xfe, 0x1c, 0x87, + 0x5a, 0xd6, 0x7f, 0xf5, 0xeb, 0x13, 0x59, 0xf8, 0x37, 0xda, 0xf7, 0xf8, 0xe2, 0x39, 0xbb, 0x12, + 0x45, 0xb4, 0x2d, 0x03, 0x43, 0x44, 0x11, 0xf7, 0x0b, 0x32, 0x82, 0x0c, 0x68, 0xca, 0x8e, 0xf3, + 0x5c, 0x44, 0x02, 0x53, 0xb0, 0x0a, 0xa7, 0x74, 0x8b, 0x48, 0x8c, 0x54, 0xb0, 0x69, 0xfb, 0xfe, + 0xdf, 0xbe, 0xb7, 0x44, 0x66, 0x6c, 0x51, 0x8a, 0x6b, 0x62, 0xf9, 0x26, 0x63, 0xc2, 0x62, 0xe1, + 0x68, 0xcd, 0x24, 0xe5, 0xff, 0xa2, 0x01, 0x3d, 0x9b, 0x80, 0xed, 0xfd, 0x41, 0xb1, 0x9c, 0xba, + 0x60, 0xfd, 0x3d, 0xd3, 0x32, 0xa9, 0x1d, 0x16, 0xd7, 0x9e, 0xc8, 0x08, 0xe8, 0xb7, 0x8c, 0x67, + 0xb1, 0x8e, 0x53, 0xaf, 0xa5, 0x71, 0x8c, 0xab, 0x50, 0x74, 0xf8, 0x93, 0x00, 0x79, 0xbf, 0xa5, + 0xda, 0x78, 0x82, 0x57, 0x97, 0x8b, 0xfe, 0x61, 0x3c, 0xd3, 0xa1, 0xca, 0xbe, 0xde, 0x60, 0x5a, + 0xb6, 0x10, 0x64, 0xf9, 0x86, 0x44, 0x9c, 0xa8, 0xad, 0xd3, 0x52, 0x12, 0xa0, 0xcc, 0x8b, 0xaa, + 0x39, 0xec, 0x9c, 0xc3, 0x43, 0x43, 0xe8, 0xd7, 0x79, 0xdb, 0xb8, 0x59, 0x85, 0x96, 0x7a, 0x92, + 0x38, 0xff, 0x24, 0x10, 0xed, 0xc1, 0x87, 0x5d, 0x86, 0x34, 0x87, 0x2b, 0xd0, 0x5d, 0x3d, 0xac, + 0x2c, 0x27, 0xd2, 0xa9, 0x75, 0x2d, 0xa3, 0xf2, 0xd3, 0xdb, 0xe4, 0xa6, 0xde, 0xe9, 0x0b, 0xd2, + 0x61, 0x5c, 0xd5, 0xdd, 0xd1, 0x6d, 0x1f, 0x68, 0x27, 0xb3, 0x40, 0x60, 0x1a, 0x5d, 0x5b, 0xa9, + 0xcd, 0x85, 0x85, 0x4d, 0x73, 0xa9, 0x16, 0x46, 0x65, 0x4a, 0xff, 0x72, 0xb1, 0x1c, 0x73, 0xa2 +}; + +#endif diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoads.cpp new file mode 100644 index 000000000..6e3677e26 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoads.cpp @@ -0,0 +1,182 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc200000, 0x0a7d0024, 0xdc2407ff, + 0x0b7d0024, 0xdc280001, 0x0c7d0024, 0xdc2c0003, 0x0d7d0024, 0xdc300002, 0x0e7d0024, 0xdc300400, + 0x187d0024, 0xdc2807ff, 0x197d0024, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, + 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x000000d2, 0x0000007a, 0x000026f2, 0xffffa665, 0x0ca66526, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xfe1ec477, 0x0000357a}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x000000f2, 0x00000035, 0x00006526, 0x00000ca6, 0x120ca665, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xe4fe1ec4, 0x00009735}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x000000a4, 0x0000005a, 0x0000449e, 0xffffadea, 0x9fadea44, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x37307a70, 0x0000b65a}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x000000dd, 0x00000060, 0x00009ea4, 0xffffea44, 0xadea449e, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x307a70d9, 0x00005a60}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x00000026, 0xffffff97, 0x0000a665, 0x0000120c, 0xd2120ca6, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x8ce4fe1e, 0x00004397}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x0000007a, 0x0000005a, 0x00009735, 0xffff9d43, 0x819d4397, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5131541e, 0x0000295a}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x00000097, 0xffffffdc, 0x00009d43, 0x00003c81, 0x513c819d, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x4cb65131, 0x0000f3dc}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x00000000, 0x00000058, 0x00003386, 0xffffc349, 0xc0c34933, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xb4de5e8e, 0x00003c58}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x00000021, 0xffffff8b, 0x0000d19a, 0xffff82a1, 0xc782a1d1, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x401ae5e1, 0x00009c8b}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x000000fa, 0x0000003d, 0x000017c8, 0xffffe6ab, 0xe4e6ab17, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3f5aefae, 0x00000e3d}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x0000008d, 0x00000074, 0x0000538b, 0xfffffa29, 0x6dfa2953, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xda660308, 0x0000e774}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x0000005d, 0x00000066, 0x00007957, 0xffff86c7, 0x8d86c779, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xb19c74d6, 0x0000f666}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x00000010, 0x0000006e, 0x0000fc1c, 0xffffc9eb, 0x3ac9ebfc, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3c3e2f3f, 0x0000506e}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x00000015, 0xffffffbe, 0x000078a4, 0xffffe536, 0x26e53678, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x936f25cd, 0x0000d7be}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x000000a0, 0x0000006b, 0x00004ec7, 0xffffeba6, 0x4eeba64e, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x6b431e8e, 0x0000f86b}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x000000c8, 0xffffffd3, 0x00003527, 0x000067e7, 0xca67e735, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xd052e9fc, 0x00005bd3}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x000000cd, 0xfffffffd, 0x0000e9fc, 0xffffd052, 0x66d052e9, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3a515bd3, 0x000041fd}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x000000fc, 0xffffffb0, 0x0000267e, 0xffffb948, 0x9eb94826, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xf83f261f, 0x00009bb0}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x00000079, 0xffffffba, 0x000069c5, 0x00001a8c, 0x151a8c69, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x892f4c71, 0x0000deba}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x00000013, 0x00000033, 0x0000835f, 0x00002ddd, 0x722ddd83, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x19598433, 0x00000233}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x000000ca, 0x0000001b, 0x00006436, 0xffff959f, 0x13959f64, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1588587f, 0x0000071b}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x00000088, 0x0000000d, 0x0000aae4, 0xffffe110, 0xdee110aa, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xe242e585, 0x0000380d}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x00000013, 0xffffffef, 0x0000ea6b, 0xffffa06b, 0xb2a06bea, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x72ee855b, 0x0000a4ef}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x000000af, 0x00000015, 0x0000fb40, 0xffffad6d, 0x2fad6dfb, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xde591a71, 0x0000bf15}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x00000073, 0x0000002f, 0x0000c858, 0x00003527, 0xe73527c8, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xe9fccd90, 0x0000a52f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x0000009d, 0xffffffde, 0x000023c0, 0xffffdea4, 0x49dea423, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xa50e02f7, 0x00001fde}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x000000fe, 0xffffff9a, 0x0000602e, 0x00000715, 0x0d071560, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x30633e8d, 0x0000489a}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "flat loads: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "flat loads: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("flat loads tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX2X3.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX2X3.cpp new file mode 100644 index 000000000..2642ebc78 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX2X3.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc340001, 0x0f7d0024, 0xdc3c0007, + 0x117d0024, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, + 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa66526f2, 0x89d2120c, 0x5d1889d2, 0x81e80e95, 0x6f160936, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x0ca66526, 0x1889d212, 0x955d1889, 0x3681e80e, 0x6b6f1609, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xadea449e, 0x8aa0459f, 0xecce8aa0, 0x40199f09, 0x6f03851f, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xea449ea4, 0xa0459fad, 0xce8aa045, 0x199f09ec, 0x03851f40, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x120ca665, 0x5d1889d2, 0x0e955d18, 0x093681e8, 0x116b6f16, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x9d439735, 0x5f513c81, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x3c819d43, 0x32095f51, 0x672e3209, 0x47efa229, 0x60e553ad, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xc3493386, 0x010dfac0, 0x7d59010d, 0xcbb17d18, 0x7ef72fd3, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x82a1d19a, 0xb41ce3c7, 0x3b5db41c, 0x7ce0e57f, 0x00280664, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xe6ab17c8, 0xa2c98ce4, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xfa29538b, 0xfc1ae26d, 0x4312fc1a, 0x1835159f, 0xb5fd7f6b, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x86c77957, 0x35e9c58d, 0x6f4835e9, 0x8d406c57, 0x4a4ad30d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xc9ebfc1c, 0x1ae0f83a, 0x43151ae0, 0xc7e70a45, 0x21c1452e, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xe53678a4, 0x6d03a026, 0x02016d03, 0xfc1fabaf, 0x3716dbf7, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xeba64ec7, 0x6c3f954e, 0xa8636c3f, 0x7080725e, 0x9e00052d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x67e73527, 0x9c2a88ca, 0xb0e49c2a, 0x17c8fa9b, 0x8ce4e6ab, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xd052e9fc, 0x0d8fd866, 0x84530d8f, 0xefaef525, 0x6cde3f5a, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xb948267e, 0x0b25a49e, 0xd5d30b25, 0xa083e4b7, 0xcfb3bb6d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x1a8c69c5, 0x3678a415, 0x26e53678, 0x016d03a0, 0x1fabaf02, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x2ddd835f, 0xc6429a72, 0xaac7c642, 0xa31b01f2, 0x599eb598, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x959f6436, 0x2be99013, 0x08252be9, 0x9f1b1c06, 0xfa5829ed, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xe110aae4, 0xb6eac1de, 0x62f1b6ea, 0x34343f1b, 0xf308081c, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa06bea6b, 0x945aacb2, 0x1b43945a, 0x66bd4d39, 0xf886f4f0, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xad6dfb40, 0xce007b2f, 0xccb8ce00, 0xa73e5b47, 0x7c7a524d, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x3527c858, 0x88ca67e7, 0x9c2a88ca, 0xfa9bb0e4, 0xe6ab17c8, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xdea423c0, 0xe90c7c49, 0x8cede90c, 0x6a782b20, 0x414c4857, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x0715602e, 0x5e8e080d, 0xb4de5e8e, 0xd8f27c75, 0xdc10e5e8, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "flat loads x2 x3: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "flat loads x2 x3: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("flat loads x2 x3 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX4.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX4.cpp new file mode 100644 index 000000000..008004a55 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatLoadsX4.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc3807f1, 0x147d0024, 0xbf8c0070, + 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, + 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xe5522840, 0xd0e7469d, 0x80412474, 0x357aebf6, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x9de55228, 0x74d0e746, 0xf6804124, 0x97357aeb, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x97578278, 0x3c61fe8b, 0xbecaa1d3, 0xb65a60de, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x578278da, 0x61fe8b97, 0xcaa1d33c, 0x5a60debe, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x469de552, 0x2474d0e7, 0xebf68041, 0x4397357a, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x553e074f, 0x8b833038, 0x6595f862, 0x295aec03, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x3038553e, 0xf8628b83, 0xec036595, 0xf3dc295a, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x1ff610a6, 0x59e9d36c, 0x30633e8d, 0x3c584877, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x300b011b, 0x9b79ebd2, 0xc90fa8c4, 0x9c8be880, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5bd3a52f, 0x225e3a51, 0xd65eeb8d, 0x0e3d40d4, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x4506273f, 0x554bd692, 0x7d422acd, 0xe774511b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x528a1578, 0xf86d7584, 0xdda0e888, 0xf666f927, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xc6e0e1a7, 0xda95b3a4, 0xa42ead3a, 0x506e741f, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xfd9f16e3, 0x1d9033df, 0x5adebaea, 0xd7be5d2b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x871b5d02, 0x6e53112f, 0x05ab81b3, 0xf86b2339, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x1a2baeed, 0x4fd4b8c8, 0x61539dbe, 0x5bd3a52f, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x68e162c2, 0xffe524cd, 0x9b3d01a2, 0x41fded80, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5873c62f, 0xe73527c8, 0x2a88ca67, 0x9bb0e49c, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6d22b51e, 0x9f16e3e8, 0x9033dffd, 0xdebaea1d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x2c46c27e, 0xab9c2334, 0x31cfa0b5, 0x02334e95, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x24ff8388, 0x9a693293, 0x8428251f, 0x071b82c2, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x380d0562, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x7cb4af22, 0xb43c7bab, 0xb183013d, 0xa4ef2271, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x4a5c9177, 0x0b128e2b, 0xfafd7702, 0xbf157cc0, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xaeede5d8, 0xb8c81a2b, 0x9dbe4fd4, 0xa52f6153, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6d03a026, 0xabaf0201, 0xdbf7fc1f, 0x1fde3716, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa02ac205, 0x05b43c44, 0x72660c37, 0x489ae433, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "flat loads x4: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "flat loads x4: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("flat loads x4 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FlatStore.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FlatStore.cpp new file mode 100644 index 000000000..25bcf9396 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FlatStore.cpp @@ -0,0 +1,143 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t MaskedThreads = 16; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint32_t CheckedBytes = 0x2000; +constexpr std::uint8_t Fill = 0xcd; + +alignas(256) constexpr std::array FlatStoreCode{ + 0x34020082, 0x34040084, 0x340c0081, 0x340e0083, 0x4a060200, 0x7e080201, 0x4a1806ff, 0x00000204, + 0x7e1a0201, 0x4a1c04ff, 0x00001000, 0x4a1000ff, 0xa1b2c300, 0x4a1202ff, 0x51000000, 0x4a1404ff, + 0x00c0ffee, 0x4a160eff, 0x7e570000, 0xdc708000, 0x00000801, 0xdc700100, 0x007d0903, 0xdc708ffc, + 0x007d0a0c, 0xdc608400, 0x00000800, 0xdc688480, 0x00000906, 0xdc708601, 0x00000b07, 0xdc788000, + 0x0000080e, 0xdc748400, 0x0000080e, 0xdc7c8800, 0x0000090e, 0xdc308000, 0x0f000001, 0xdc708500, + 0x00000f01, 0x7da80090, 0xdc708300, 0x00000a01, 0xbf810000, +}; + +class GuestBlock { +public: + explicit GuestBlock(bool writable) { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "flat store: cannot allocate the guest block"); + Clear(); + GuestAllocations::Mutation().Add(block, BlockBytes, true, writable); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + void Clear() { std::memset(block, Fill, BlockBytes); } + const std::uint8_t* Data() const { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::vector Expected() { + std::vector image(CheckedBytes, Fill); + const auto put = [&](std::uint32_t offset, std::uint32_t value, std::uint32_t bytes) { + for (std::uint32_t byte = 0; byte < bytes; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); + }; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::array data{0xa1b2c300u + tid, 0x51000000u + tid * 4u, 0x00c0ffeeu + tid * 16u, 0x7e570000u + tid * 8u}; + put(0x000u + tid * 4u, data[0], 4); + put(0x100u + tid * 4u, data[1], 4); + put(0x200u + tid * 4u, data[2], 4); + if (tid < MaskedThreads) put(0x300u + tid * 4u, data[2], 4); + put(0x400u + tid, data[0], 1); + put(0x480u + tid * 2u, data[1], 2); + put(0x500u + tid * 4u, data[0], 4); + put(0x601u + tid * 8u, data[3], 4); + for (std::uint32_t dword = 0; dword < 3; ++dword) put(0x800u + tid * 16u + dword * 4u, data[dword + 1u], 4); + for (std::uint32_t dword = 0; dword < 4; ++dword) put(0x1000u + tid * 16u + dword * 4u, data[dword], 4); + for (std::uint32_t dword = 0; dword < 2; ++dword) put(0x1400u + tid * 16u + dword * 4u, data[dword], 4); + } + return image; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(8, 0u); + userData[0] = static_cast(address); + userData[1] = static_cast(address >> 32u); + const std::span code(FlatStoreCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void RunStores(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + guest.Clear(); + Dispatch(device, waveSize, guest.Data()); + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < CheckedBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], "flat store: wave" + std::to_string(waveSize) + " byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + +void RunReadOnly(AgcDriver::VulkanDevice& device, const GuestBlock& guest) { + Dispatch(device, 32, guest.Data()); + for (std::uint32_t offset = 0; offset < CheckedBytes; ++offset) { + Require(guest.Data()[offset] == Fill, "flat store: a store into a read-only range changed byte " + std::to_string(offset)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock writable(true); + const GuestBlock readOnly(false); + RunStores(*device, writable, 32); + RunStores(*device, writable, 64); + RunReadOnly(*device, readOnly); + std::puts("flat store tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp new file mode 100644 index 000000000..7242e910c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Float16Dot.cpp @@ -0,0 +1,162 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 8; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array DotCode{ + 0x34020084, 0x34060085, 0xe0301000, 0x80000401, 0xe0301004, 0x80000501, 0xe0301008, 0x80000601, + 0xbf8c3f70, 0xcc13400a, 0x1c1a0b04, 0xcc13480b, 0x141a0b04, 0xcc13420c, 0xbc1a0b04, 0xcc13c00d, + 0x1c1a0b04, 0xe0701000, 0x80010a03, 0xe0701004, 0x80010b03, 0xe0701008, 0x80010c03, 0xe070100c, + 0x80010d03, 0xbf810000, +}; + +constexpr std::array, 8> Edges{{ + {0x3c003c00u, 0x3c003c00u, 0x00000000u}, {0x7c000000u, 0x3c000000u, 0x3f800000u}, {0x7c003c00u, 0x00003c00u, 0x3f800000u}, + {0xfc007c00u, 0x3c003c00u, 0x40000000u}, {0x80000000u, 0x00008000u, 0x80000000u}, {0x7bff7bffu, 0x7bff7bffu, 0xcf800000u}, + {0x38003800u, 0xb800b800u, 0x3e800000u}, {0x7e003c00u, 0x3c003c00u, 0x00000000u}, +}}; + +float Half(std::uint32_t bits) { + const std::uint32_t sign = (bits & 0x8000u) << 16u; + const std::uint32_t exponent = (bits >> 10u) & 0x1fu; + const std::uint32_t mantissa = bits & 0x3ffu; + if (exponent == 0x1fu) return std::bit_cast(sign | 0x7f800000u | (mantissa << 13u)); + if (exponent == 0u) return std::bit_cast(sign) + (sign != 0u ? -1.0f : 1.0f) * std::ldexp(static_cast(mantissa), -24); + return std::bit_cast(sign | ((exponent + 112u) << 23u) | (mantissa << 13u)); +} + +std::uint32_t RandomHalf(std::uint64_t& state) { + state = state * 6364136223846793005ull + 1442695040888963407ull; + const auto bits = static_cast(state >> 33u); + return (bits & 0x8000u) | ((8u + (bits >> 16u) % 15u) << 10u) | (bits & 0x3ffu); +} + +void FillInput() { + std::uint64_t state = 0x9e3779b97f4a7c15ull; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + auto* words = &Input[tid * Inputs]; + if (tid < Edges.size()) { + std::copy(Edges[tid].begin(), Edges[tid].end(), words); + continue; + } + words[0] = RandomHalf(state) | (RandomHalf(state) << 16u); + words[1] = RandomHalf(state) | (RandomHalf(state) << 16u); + words[2] = std::bit_cast(Half(RandomHalf(state)) * Half(RandomHalf(state))); + } +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +auto Compile(AgcDriver::VulkanDevice& device, std::span code) { + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device) { + Output.fill(0xdeadbeefu); + const std::span code(DotCode); + const auto result = Compile(device, code); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckRefused(AgcDriver::VulkanDevice& device, std::uint32_t word0, const std::string& what) { + alignas(256) const std::array code{word0, 0x1c1a0b04u, 0xbf810000u}; + std::string refusal; + try { + static_cast(Compile(device, code)); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("v_dot2_f32_f16 accumulator op_sel, op_sel_hi and neg_hi are not implemented") != std::string::npos, what + " was not refused"); +} + +float Dot(float aLow, float aHigh, float bLow, float bHigh, float c) { + return std::fma(aHigh, bHigh, std::fma(aLow, bLow, c)); +} + +void Expect(std::uint32_t tid, std::uint32_t actual, float expected, const char* name) { + const std::uint32_t bits = std::bit_cast(expected); + const bool nan = std::isnan(expected) && std::isnan(std::bit_cast(actual)); + Require(nan || actual == bits, std::string("v_dot2_f32_f16: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(bits)); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto* in = &Input[tid * Inputs]; + const auto* out = &Output[tid * Results]; + const float aLow = Half(in[0] & 0xffffu); + const float aHigh = Half(in[0] >> 16u); + const float bLow = Half(in[1] & 0xffffu); + const float bHigh = Half(in[1] >> 16u); + const float c = std::bit_cast(in[2]); + const float plain = Dot(aLow, aHigh, bLow, bHigh, c); + Expect(tid, out[0], plain, "plain"); + Expect(tid, out[1], Dot(aHigh, aLow, bLow, bHigh, c), "op_sel swapping the halves of src0"); + Expect(tid, out[2], Dot(-aLow, aHigh, bLow, -bHigh, -c), "neg_lo src0, neg_hi src1 and neg src2"); + if (!std::isnan(plain)) Expect(tid, out[3], plain > 0.0f ? std::min(plain, 1.0f) : 0.0f, "clamp"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillInput(); + Run(*device); + Check(); + CheckRefused(*device, 0xcc13600au, "op_sel on the accumulator"); + CheckRefused(*device, 0xcc13440au, "neg_hi on the accumulator"); + CheckRefused(*device, 0xcc13000au, "op_sel_hi cleared on the accumulator"); + std::puts("v_dot2_f32_f16 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp new file mode 100644 index 000000000..45b992a20 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Float16ResultModifiers.cpp @@ -0,0 +1,202 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0301000, 0x80000401, 0xe0301004, 0x80000501, 0xe0301008, 0x80000601, + 0xe030100c, 0x80000701, 0x7e140280, 0x7e160280, 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, + 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280, 0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, + 0x7e300280, 0x7e320280, 0xbf8c3f70, 0xd5d4800a, 0x00000104, 0xd5d4000b, 0x08000104, 0xd5d5800c, + 0x08000104, 0xd5e0000d, 0x08000104, 0xd5e0800e, 0x00000105, 0xd58a800f, 0x00000107, 0xd58a0010, + 0x18000107, 0xd5d08011, 0x00000106, 0xd74b8012, 0x041a0b04, 0xd74b0013, 0x0c1a0b04, 0x64280af9, + 0x04042404, 0x6a2a0af9, 0x04044404, 0xd52f8016, 0x00020d07, 0xd52f0017, 0x18020d07, 0xd5db8018, + 0x00000105, 0x64320af9, 0x04043504, 0xe0701000, 0x80010a03, 0xe0701004, 0x80010b03, 0xe0701008, + 0x80010c03, 0xe070100c, 0x80010d03, 0xe0701010, 0x80010e03, 0xe0701014, 0x80010f03, 0xe0701018, + 0x80011003, 0xe070101c, 0x80011103, 0xe0701020, 0x80011203, 0xe0701024, 0x80011303, 0xe0701028, + 0x80011403, 0xe070102c, 0x80011503, 0xe0701030, 0x80011603, 0xe0701034, 0x80011703, 0xe0701038, + 0x80011803, 0xe070103c, 0x80011903, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00003400u, 0x00003800u, 0x00003000u, 0x3e99999au}, + {0x00003e00u, 0x0000b400u, 0x00004000u, 0xbfd9999au}, + {0x00007b53u, 0x000070e2u, 0x00007bffu, 0x47ffdc00u}, + {0x00000400u, 0x00003800u, 0x00000000u, 0x33000000u}, + {0x00007e00u, 0x00003c00u, 0x00003c00u, 0x7fc00000u}, + {0x00007c00u, 0x00003c00u, 0x0000bc00u, 0x7f800000u}, + {0x0000c000u, 0x0000c200u, 0x00004400u, 0x80000000u}, + {0x00000001u, 0x00003a00u, 0x00008001u, 0x477ff000u}, + {0x00002265u, 0x000091b7u, 0x0000d8f1u, 0xcd613e30u}, + {0x0000c386u, 0x00001027u, 0x0000414cu, 0xc2ce6f44u}, + {0x00007311u, 0x000078e5u, 0x0000a6ceu, 0xc9e9c616u}, + {0x000035bfu, 0x00001807u, 0x00000741u, 0xc324c985u}, + {0x0000c464u, 0x0000b222u, 0x00007204u, 0x442e3d43u}, + {0x0000b8b6u, 0x0000cd44u, 0x00003a90u, 0x37730edfu}, + {0x0000f813u, 0x00006c0fu, 0x0000b9d1u, 0x38c0c8fdu}, + {0x0000c381u, 0x00007019u, 0x0000f06du, 0x3bab6c39u}, + {0x0000587fu, 0x00003b1au, 0x0000ad45u, 0x380208a9u}, + {0x0000c2cdu, 0x000075a8u, 0x0000f3c6u, 0x4a2f20aau}, + {0x0000ed2fu, 0x00000580u, 0x00006a8au, 0xb94067edu}, + {0x0000dc25u, 0x00004be0u, 0x00001ef2u, 0xbe3edc0au}, + {0x0000552bu, 0x0000e544u, 0x0000b8b3u, 0xb610a9f7u}, + {0x0000efbau, 0x0000f79bu, 0x00006c0fu, 0x4da98f1du}, + {0x000048beu, 0x0000966bu, 0x0000f934u, 0x3e2434e3u}, + {0x0000be65u, 0x0000cc22u, 0x0000677fu, 0xb3fa7aa7u}, + {0x0000c69du, 0x0000acabu, 0x0000bcfbu, 0xc74803e3u}, + {0x000029e8u, 0x0000855cu, 0x0000d707u, 0xbb968a43u}, + {0x00000792u, 0x00007825u, 0x00000b21u, 0x4efbc8d6u}, + {0x0000b410u, 0x0000d92au, 0x0000fbb2u, 0x3a1890c7u}, + {0x0000fb69u, 0x0000c541u, 0x00003313u, 0x3b6fe507u}, + {0x0000678au, 0x00005804u, 0x0000f3d4u, 0x44ef7febu}, + {0x0000a8c2u, 0x00008c49u, 0x00009be3u, 0xbab9f87fu}, + {0x00006239u, 0x0000c89du, 0x0000db61u, 0xbd91a1b7u}, +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00003c00u, 0x00004400u, 0x00003800u, 0x00003c00u, 0x00000000u, 0x000034cdu, 0x000034cdu, 0x00003c00u, 0x00003400u, 0x00003400u, 0x00003a00u, 0x00003000u, 0x000034ccu, 0x000034ccu, 0x00000000u, 0x3a000000u}, + {0x00003955u, 0x00003955u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000becdu, 0x00003c00u, 0x00003c00u, 0x00003e80u, 0x00003c00u, 0x0000b600u, 0x0000beccu, 0x0000beccu, 0x00000000u, 0x3c000000u}, + {0x00000118u, 0x00000118u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00007c00u, 0x00007bffu, 0x00007bffu, 0x00003c00u, 0x3c000000u}, + {0x00003c00u, 0x00007400u, 0x00002000u, 0x00000e48u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000200u, 0x00000200u, 0x00003800u, 0x00000200u, 0x00000000u, 0x00000000u, 0x00000000u, 0x38000000u}, + {0x00000000u, 0x00007e00u, 0x00000000u, 0x00007e00u, 0x00000000u, 0x00000000u, 0x00007e00u, 0x00003c00u, 0x00000000u, 0x00007e00u, 0x00000000u, 0x00007e00u, 0x00007e00u, 0x00007e00u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00003c00u, 0x0000fe00u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00007c00u, 0x00007c00u, 0x00007c00u, 0x00003c00u, 0x3c000000u}, + {0x00000000u, 0x0000b800u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00003c00u, 0x00003c00u, 0x00004900u, 0x00000000u, 0x00004600u, 0x00008000u, 0x00008000u, 0x00000000u, 0x00000000u}, + {0x00003c00u, 0x00007c00u, 0x00000c00u, 0x00000006u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00000000u, 0x00008000u, 0x00003a00u, 0x00000001u, 0x00007bffu, 0x00007bffu, 0x00000000u, 0x3a000000u}, + {0x00003c00u, 0x00005501u, 0x00002f27u, 0x00002d04u, 0x00000000u, 0x00000000u, 0x0000fc00u, 0x00003c00u, 0x00000000u, 0x0000d8f1u, 0x0000220au, 0x00008092u, 0x0000fbffu, 0x0000fbffu, 0x00000000u, 0x220a0000u}, + {0x00000000u, 0x0000b441u, 0x00000000u, 0x00003bfau, 0x00001a86u, 0x00000000u, 0x0000d673u, 0x00003c00u, 0x00003c00u, 0x0000414bu, 0x00000000u, 0x000097cfu, 0x0000d673u, 0x0000d673u, 0x00000000u, 0x00000000u}, + {0x00000487u, 0x00000487u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000fc00u, 0x00003c00u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00007c00u, 0x0000fbffu, 0x0000fbffu, 0x00003c00u, 0x3c000000u}, + {0x00003c00u, 0x00004192u, 0x000038cbu, 0x00003a31u, 0x00002253u, 0x00000000u, 0x0000d926u, 0x00003c00u, 0x000012b1u, 0x000012b1u, 0x000035c7u, 0x000011c9u, 0x0000d926u, 0x0000d926u, 0x00000000u, 0x35c70000u}, + {0x00000000u, 0x0000b34au, 0x00000000u, 0x0000b913u, 0x00000000u, 0x00003c00u, 0x00006172u, 0x00003c00u, 0x00003c00u, 0x00007204u, 0x00000000u, 0x00003abbu, 0x00006171u, 0x00006171u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x0000becbu, 0x00000000u, 0x0000383du, 0x00000000u, 0x000000f3u, 0x000000f3u, 0x00003c00u, 0x00003c00u, 0x00004a9du, 0x00000000u, 0x00004a34u, 0x000000f3u, 0x000000f3u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x000081f7u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000606u, 0x00000606u, 0x00003c00u, 0x00000000u, 0x0000fc00u, 0x00000000u, 0x0000fc00u, 0x00000606u, 0x00000606u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x0000b444u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00001d5bu, 0x00001d5bu, 0x00003c00u, 0x00000000u, 0x0000f8f3u, 0x00003c00u, 0x0000f7b0u, 0x00001d5bu, 0x00001d5bu, 0x00003c00u, 0x3c000000u}, + {0x00001f1eu, 0x00001f1eu, 0x00003c00u, 0x0000b9a8u, 0x00000000u, 0x00000208u, 0x00000208u, 0x00003c00u, 0x00003c00u, 0x000057fau, 0x00003c00u, 0x000057fbu, 0x00000208u, 0x00000208u, 0x00000000u, 0x3c000000u}, + {0x00000000u, 0x0000b4b5u, 0x00000000u, 0x0000b8b0u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00000000u, 0x0000fc00u, 0x00003c00u, 0x0000fc00u, 0x00007bffu, 0x00007bffu, 0x00003c00u, 0x3c000000u}, + {0x00000000u, 0x00008a2cu, 0x00000000u, 0x00000000u, 0x00001052u, 0x00000000u, 0x00008a03u, 0x00003c00u, 0x00003c00u, 0x00006a8au, 0x00000000u, 0x0000b721u, 0x00008a03u, 0x00008a03u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00009bb9u, 0x00000000u, 0x0000bc00u, 0x00000000u, 0x00000000u, 0x0000b1f7u, 0x00003c00u, 0x00000000u, 0x0000ec14u, 0x00000000u, 0x0000ec14u, 0x0000b1f6u, 0x0000b1f6u, 0x00003c00u, 0x00000000u}, + {0x00002231u, 0x00002231u, 0x00003c00u, 0x0000bb64u, 0x00000000u, 0x00000000u, 0x00008024u, 0x00003c00u, 0x00000000u, 0x0000fc00u, 0x00000000u, 0x0000fc00u, 0x00008024u, 0x00008024u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00008824u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00003c00u, 0x00007c00u, 0x00000000u, 0x00007c00u, 0x00007bffu, 0x00007bffu, 0x00000000u, 0x00000000u}, + {0x00002ebfu, 0x00002ebfu, 0x00003c00u, 0x00002e46u, 0x00000000u, 0x00003122u, 0x00003122u, 0x00003c00u, 0x00000000u, 0x0000f934u, 0x00003c00u, 0x0000a39cu, 0x00003121u, 0x00003121u, 0x00000000u, 0x3c000000u}, + {0x00000000u, 0x0000b901u, 0x00000000u, 0x000038a6u, 0x0000323eu, 0x00000000u, 0x00008002u, 0x00003c00u, 0x00003c00u, 0x00006799u, 0x00000000u, 0x00004e9bu, 0x00008001u, 0x00008001u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x0000b0d7u, 0x00000000u, 0x0000393au, 0x00000000u, 0x00000000u, 0x0000fa40u, 0x00003c00u, 0x00000000u, 0x0000ba1au, 0x00000000u, 0x000037b8u, 0x0000fa40u, 0x0000fa40u, 0x00000000u, 0x00000000u}, + {0x00003c00u, 0x00004d6bu, 0x000032e0u, 0x00003493u, 0x00000000u, 0x00000000u, 0x00009cb4u, 0x00003c00u, 0x00000000u, 0x0000d707u, 0x000029e5u, 0x0000803fu, 0x00009cb4u, 0x00009cb4u, 0x00000000u, 0x29e50000u}, + {0x00003c00u, 0x0000703au, 0x00002181u, 0x000011f2u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00003c00u, 0x000043d8u, 0x00003c00u, 0x000043d8u, 0x00007bffu, 0x00007bffu, 0x00003c00u, 0x3c000000u}, + {0x00000000u, 0x0000c3e0u, 0x00000000u, 0x0000bbffu, 0x00000000u, 0x000010c5u, 0x000010c5u, 0x00003c00u, 0x00000000u, 0x0000fbb1u, 0x00000000u, 0x0000513fu, 0x000010c4u, 0x000010c4u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00008114u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00001b7fu, 0x00001b7fu, 0x00003c00u, 0x00003c00u, 0x00007c00u, 0x00000000u, 0x00007c00u, 0x00001b7fu, 0x00001b7fu, 0x00000000u, 0x00000000u}, + {0x0000103fu, 0x0000103fu, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x0000677cu, 0x00003c00u, 0x00003c00u, 0x00007c00u, 0x00003c00u, 0x00007c00u, 0x0000677bu, 0x0000677bu, 0x00003c00u, 0x3c000000u}, + {0x00000000u, 0x0000cebau, 0x00000000u, 0x0000b368u, 0x00000000u, 0x00000000u, 0x000095d0u, 0x00003c00u, 0x00000000u, 0x00009bdeu, 0x00000000u, 0x000000a3u, 0x000095cfu, 0x000095cfu, 0x00000000u, 0x00000000u}, + {0x00001524u, 0x00001524u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000ac8du, 0x00003c00u, 0x00000000u, 0x0000ef68u, 0x00003c00u, 0x0000ef2du, 0x0000ac8du, 0x0000ac8du, 0x00000000u, 0x3c000000u}, +}; +constexpr const char* Names[16] = {"rcp_clamp", "rcp_mul2", "sqrt_clamp_mul2", "sin_mul2", "sin_clamp", "cvt_f16_f32_clamp", "cvt_f16_f32_div2", "cvt_f16_u16_clamp", "fma_clamp", "fma_mul2", "add_sdwa_clamp", "mul_sdwa_mul2", "pkrtz_clamp", "pkrtz_div2", "floor_clamp", "add_sdwa_hi_clamp"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +bool IsNan16(std::uint32_t bits) { + return (bits & 0x7fffu) > 0x7c00u; +} + +bool SameHalf(std::uint32_t actual, std::uint32_t expected) { + return actual == expected || (IsNan16(actual) && IsNan16(expected)); +} + +double HalfValue(std::uint32_t bits) { + const double magnitude = (bits & 0x7c00u) == 0u ? std::ldexp(static_cast(bits & 0x3ffu), -24) : std::ldexp(static_cast((bits & 0x3ffu) | 0x400u), static_cast((bits >> 10u) & 0x1fu) - 25); + return (bits & 0x8000u) != 0u ? -magnitude : magnitude; +} + +bool NearHalf(std::uint32_t actual, std::uint32_t expected, bool clamped) { + if (clamped && (actual & 0x8000u) != 0u) return false; + if (!clamped && (expected & 0x7fffu) == 0u) return actual == expected; + if (IsNan16(actual) || IsNan16(expected) || (actual & 0x7fffu) == 0x7c00u || (expected & 0x7fffu) == 0x7c00u) return SameHalf(actual, expected); + return std::abs(HalfValue(actual) - HalfValue(expected)) <= 0x1p-10; +} + +void ExpectNear(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name, bool clamped) { + Require((actual >> 16u) == (expected >> 16u) && NearHalf(actual & 0xffffu, expected & 0xffffu, clamped), std::string("float16 result modifiers: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected near " + Hex(expected)); +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(SameHalf(actual & 0xffffu, expected & 0xffffu) && SameHalf(actual >> 16u, expected >> 16u), std::string("float16 result modifiers: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t index = 0; index < 16; ++index) { + if (index == 3u || index == 4u) ExpectNear(tid, out[index], Expected[tid][index], Names[index], index == 4u); + else Expect(tid, out[index], Expected[tid][index], Names[index]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("float16 result modifiers tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FmaLegacy.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FmaLegacy.cpp new file mode 100644 index 000000000..e9c867787 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FmaLegacy.cpp @@ -0,0 +1,144 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 8; +constexpr std::uint32_t Results = 8; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array FmaLegacyCode{ + 0x34020083, 0x34060083, 0xe0302000, 0x80000401, 0xe0302004, 0x80000501, 0xe0302008, 0x80000601, + 0xbf8c3f70, 0xd540000a, 0x041a0b04, 0xd540000b, 0x241a0b04, 0xd540010c, 0x841a0b04, 0xd540000d, + 0x041a0a80, 0xd540020e, 0x641a0b04, 0xe0702000, 0x80010a03, 0xe0702004, 0x80010b03, 0xe0702008, + 0x80010c03, 0xe070200c, 0x80010d03, 0xe0702010, 0x80010e03, 0xbf810000, +}; + +struct Row { + std::uint32_t a, b, c; +}; + +constexpr std::array Rows{{ + {0x00000000u, 0x7f800000u, 0x3f800000u}, + {0x80000000u, 0x7f800000u, 0xc0200000u}, + {0x7f800000u, 0x00000000u, 0x3f400000u}, + {0xff800000u, 0x80000000u, 0x40400000u}, + {0x00000000u, 0x7fc00000u, 0x3fc00000u}, + {0x7fc00000u, 0x80000000u, 0xc0800000u}, + {0x00000000u, 0x40000000u, 0x7f800000u}, + {0x80000000u, 0xc0400000u, 0xff800000u}, + {0x00000000u, 0x3f800000u, 0x7fc00000u}, + {0x3fc00000u, 0x40100000u, 0xbf400000u}, + {0xc0c00000u, 0x3f000000u, 0x41200000u}, + {0x7f800000u, 0x40000000u, 0x3f800000u}, + {0x7f800000u, 0xbf800000u, 0x7f800000u}, + {0x7fc00000u, 0x3f800000u, 0x3f800000u}, + {0x40400000u, 0x40800000u, 0xc1400000u}, + {0x7f7fffffu, 0x40000000u, 0x3f800000u}, + {0x3ec00000u, 0xc1480000u, 0x42c88000u}, + {0xc4800000u, 0xbd800000u, 0xc2800000u}, + {0x40e00000u, 0x41100000u, 0x3f000000u}, + {0x80000000u, 0x80000000u, 0x40a00000u}, +}}; + +std::array BufferDescriptor(const void* data, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (4u << 16u), count, 0x01016facu}; +} + +void Run(AgcDriver::VulkanDevice& device) { + Input.fill(0u); + for (std::uint32_t tid = 0; tid < Rows.size(); ++tid) { + const auto& row = Rows[tid]; + const std::array words{row.a, row.b, row.c}; + std::copy(words.begin(), words.end(), Input.begin() + tid * Inputs); + } + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size())); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size())); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(FmaLegacyCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +bool IsNan(std::uint32_t bits) { + return (bits & 0x7fffffffu) > 0x7f800000u; +} + +std::uint32_t FmaLegacy(float a, float b, float c) { + if (a == 0.0f || b == 0.0f) { + return std::bit_cast(0.0f + c); + } + return std::bit_cast(std::fma(a, b, c)); +} + +void Check() { + constexpr std::array names{ + "v_fma_legacy_f32", "v_fma_legacy_f32 -src0", "v_fma_legacy_f32 |src0| -src2", "v_fma_legacy_f32 src0=0", "v_fma_legacy_f32 -src0 -|src1|", + }; + for (std::uint32_t tid = 0; tid < Rows.size(); ++tid) { + const auto& row = Rows[tid]; + const float a = std::bit_cast(row.a); + const float b = std::bit_cast(row.b); + const float c = std::bit_cast(row.c); + const std::array expected{ + FmaLegacy(a, b, c), FmaLegacy(-a, b, c), FmaLegacy(std::fabs(a), b, -c), FmaLegacy(0.0f, b, c), FmaLegacy(-a, -std::fabs(b), c), + }; + for (std::uint32_t j = 0; j < expected.size(); ++j) { + const auto actual = Output[tid * Results + j]; + const bool matches = actual == expected[j] || (IsNan(actual) && IsNan(expected[j])); + Require(matches, std::string("fma legacy: lane ") + std::to_string(tid) + " (" + Hex(row.a) + ", " + Hex(row.b) + ", " + Hex(row.c) + ") " + names[j] + " is " + Hex(actual) + ", expected " + Hex(expected[j])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("fma legacy tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicCsub.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicCsub.cpp new file mode 100644 index 000000000..7c84e3e8c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicCsub.cpp @@ -0,0 +1,249 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 4; +constexpr std::uint32_t Written = 3; +constexpr std::uint32_t SharedOffset = Threads * 4u; +constexpr std::uint32_t Untouched = 0xdeadbeefu; +constexpr std::uint32_t SharedPartial = 0x800186a0u; +constexpr std::uint32_t SharedExhausted = 1000u; +constexpr std::size_t BlockBytes = 4096; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060082, 0xe0301000, 0x80000a01, 0xe0301004, 0x80000b01, 0xe0301008, 0x80000c01, + 0x7e0e02ff, 0x00000100, 0xbf8c3f70, 0xdcd18000, 0x14080a03, 0xdcd18000, 0x15080b07, 0xdcd18004, + 0x16080c07, 0xbf8c3f70, 0xe0701000, 0x80011401, 0xe0701004, 0x80011501, 0xe0701008, 0x80011601, + 0xbf810000, +}; + +alignas(256) constexpr std::array NoReturnCode{ + 0xdcd08000, 0x00080a03, 0xbf810000, +}; + +alignas(256) constexpr std::array FlatSegmentCode{ + 0xdcd10000, 0x147f0a03, 0xbf810000, +}; + +struct Row { + std::uint32_t memory; + std::uint32_t subtrahend; +}; + +constexpr std::array Edges{{ + {0u, 0u}, {0u, 1u}, {1u, 0u}, {1u, 1u}, + {5u, 7u}, {7u, 5u}, {0xffffffffu, 1u}, {1u, 0xffffffffu}, + {0x80000000u, 0x7fffffffu}, {0x7fffffffu, 0x80000000u}, {0xffffffffu, 0xffffffffu}, {0x80000000u, 0x80000000u}, + {0x80000001u, 0x80000000u}, {0u, 0xffffffffu}, {0xffffffffu, 0u}, {0x12345678u, 0x12345679u}, +}}; + +Row RowOf(std::uint32_t tid) { + if (tid < Edges.size()) return Edges[tid]; + const std::uint32_t memory = (tid + 1u) * 0x9e3779b9u; + const std::uint32_t subtrahend = (tid + 1u) * 0x85ebca6bu; + return {memory, (tid & 3u) == 0u ? subtrahend >> 8u : subtrahend}; +} + +std::uint32_t SharedPartialStep(std::uint32_t tid) { return tid + 1u; } +std::uint32_t SharedExhaustedStep(std::uint32_t tid) { return tid + 100u; } + +std::uint32_t SubtractClamped(std::uint32_t memory, std::uint32_t subtrahend) { + return memory >= subtrahend ? memory - subtrahend : 0u; +} + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global atomic csub: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint32_t value) { + for (std::uint32_t byte = 0; byte < 4u; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, tid * 4u, RowOf(tid).memory); + } + Put(image, SharedOffset, SharedPartial); + Put(image, SharedOffset + 4u, SharedExhausted); + return image; +} + +std::vector Expected() { + auto image = Initial(); + std::uint32_t partial = SharedPartial; + std::uint32_t exhausted = SharedExhausted; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const Row row = RowOf(tid); + Put(image, tid * 4u, SubtractClamped(row.memory, row.subtrahend)); + partial = SubtractClamped(partial, SharedPartialStep(tid)); + exhausted = SubtractClamped(exhausted, SharedExhaustedStep(tid)); + } + Require(partial != 0u && exhausted == 0u, "global atomic csub: the shared dwords do not cover both outcomes"); + Put(image, SharedOffset, partial); + Put(image, SharedOffset + 4u, exhausted); + return image; +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::vector UserData(const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + return userData; +} + +ShaderRecompiler::RecompileResult Recompile(const AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, std::span userData) { + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void CheckChain(const std::string& where, std::uint32_t column, std::uint32_t initial, std::uint32_t (*step)(std::uint32_t)) { + std::vector> order; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + order.emplace_back(Output[tid * Results + column], tid); + } + std::sort(order.begin(), order.end(), [](const auto& a, const auto& b) { return a.first > b.first; }); + std::uint32_t running = initial; + for (const auto& [returned, tid] : order) { + Require(returned == running, where + " returned " + Hex(returned) + " to thread " + std::to_string(tid) + ", expected " + Hex(running)); + running = SubtractClamped(running, step(tid)); + } +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + Input.fill(0u); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Input[tid * Inputs] = RowOf(tid).subtrahend; + Input[tid * Inputs + 1u] = SharedPartialStep(tid); + Input[tid * Inputs + 2u] = SharedExhaustedStep(tid); + } + Output.fill(Untouched); + const auto userData = UserData(guest.Data()); + const std::span code(Code); + const auto result = Recompile(device, code, waveSize, userData); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + + const auto wave = "global atomic csub: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const Row row = RowOf(tid); + const auto returned = Output[tid * Results]; + Require(returned == row.memory, wave + "thread " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.memory)); + for (std::uint32_t j = Written; j < Results; ++j) { + Require(Output[tid * Results + j] == Untouched, wave + "thread " + std::to_string(tid) + " result " + std::to_string(j) + " was written"); + } + } + CheckChain(wave + "dword shared by all lanes", 1u, SharedPartial, SharedPartialStep); + CheckChain(wave + "dword exhausted by all lanes", 2u, SharedExhausted, SharedExhaustedStep); + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + +void CheckRejected(const AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code, const std::string& reason) { + const auto userData = UserData(guest.Data()); + std::string failure; + try { + static_cast(Recompile(device, code, 32, userData)); + } catch (const std::exception& error) { + failure = error.what(); + } + Require(failure.find(reason) != std::string::npos, "global atomic csub: expected '" + reason + "', got '" + failure + "'"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + Run(*device, guest, 32); + Run(*device, guest, 64); + CheckRejected(*device, guest, NoReturnCode, "global_atomic_csub without glc is not supported"); + CheckRejected(*device, guest, FlatSegmentCode, "global_atomic_csub is available only in the global segment"); + std::puts("global atomic csub tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp new file mode 100644 index 000000000..3409e7768 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics.cpp @@ -0,0 +1,436 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t RowsPerOp = 16; +constexpr std::uint32_t OpCount = 13; +constexpr std::uint32_t NarrowOps = 13; +constexpr std::uint32_t OpStride = 0x200; +constexpr std::uint32_t Special = OpCount * OpStride; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array GlobalAtomicsCode{ + 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, + 0x4a080480, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0a80, 0xdcc18000, 0x14080a03, 0xbf8c3f70, + 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000200, 0x4a0804ff, 0x00000400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000200, 0xdcc58000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, + 0x4a0602ff, 0x00000400, 0x4a0804ff, 0x00000800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000400, 0xdcc98000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000600, + 0x4a0804ff, 0x00000c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000600, 0xdccd8000, + 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000800, 0x4a0804ff, 0x00001000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000800, 0xdcd58000, 0x14080a03, 0xbf8c3f70, + 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000a00, 0x4a0804ff, 0x00001400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000a00, 0xdcd98000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, + 0x4a0602ff, 0x00000c00, 0x4a0804ff, 0x00001800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000c00, 0xdcdd8000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000e00, + 0x4a0804ff, 0x00001c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000e00, 0xdce18000, + 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001000, 0x4a0804ff, 0x00002000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001000, 0xdce58000, 0x14080a03, 0xbf8c3f70, + 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001200, 0x4a0804ff, 0x00002400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00001200, 0xdce98000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, + 0x4a0602ff, 0x00001400, 0x4a0804ff, 0x00002800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00001400, 0xdced8000, 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001600, + 0x4a0804ff, 0x00002c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001600, 0xdcf18000, + 0x14080a03, 0xbf8c3f70, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00001800, 0x4a0804ff, 0x00003000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001800, 0xdcf58000, 0x14080a03, 0xbf8c3f70, + 0xe0701000, 0x80011406, 0xbf810000 +}; + +struct Row { + std::uint32_t op; + std::uint64_t memory; + std::uint64_t data; + std::uint64_t comparator; + std::uint64_t returned; + std::uint64_t final; +}; + +constexpr std::array Rows{{ + {0, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {0, 0x1ull, 0x80000000ull, 0xf9677975ull, 0x1ull, 0x80000000ull}, + {0, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xffffffffull}, + {0, 0x7fffffffull, 0x0ull, 0xfc5039f7ull, 0x7fffffffull, 0x0ull}, + {0, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x7fffffffull}, + {0, 0x80000001ull, 0xfffffffeull, 0x1098ad65ull, 0x80000001ull, 0xfffffffeull}, + {0, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xdeadbeefull}, + {0, 0xffffffffull, 0x2ull, 0x19211eull, 0xffffffffull, 0x2ull}, + {0, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x80000001ull}, + {0, 0xdeadbeefull, 0x12345678ull, 0xcdbc2be9ull, 0xdeadbeefull, 0x12345678ull}, + {0, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {0, 0x1ull, 0x80000000ull, 0xa3639922ull, 0x1ull, 0x80000000ull}, + {0, 0x46e9a894ull, 0xd107eea9ull, 0x46e9a894ull, 0x46e9a894ull, 0xd107eea9ull}, + {0, 0xbc50bcc8ull, 0x58ffc3ecull, 0x4cd661ull, 0xbc50bcc8ull, 0x58ffc3ecull}, + {0, 0xb9093c7eull, 0x665932a4ull, 0xb9093c7eull, 0xb9093c7eull, 0x665932a4ull}, + {0, 0xd88449d9ull, 0xfe358db5ull, 0xba80659full, 0xd88449d9ull, 0xfe358db5ull}, + {1, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {1, 0x1ull, 0x80000000ull, 0x109949f9ull, 0x1ull, 0x1ull}, + {1, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xffffffffull}, + {1, 0x7fffffffull, 0x0ull, 0x22d348c2ull, 0x7fffffffull, 0x7fffffffull}, + {1, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x7fffffffull}, + {1, 0x80000001ull, 0xfffffffeull, 0x42872e08ull, 0x80000001ull, 0x80000001ull}, + {1, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xdeadbeefull}, + {1, 0xffffffffull, 0x2ull, 0x675c2152ull, 0xffffffffull, 0xffffffffull}, + {1, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x80000001ull}, + {1, 0xdeadbeefull, 0x12345678ull, 0x2f1ed38ull, 0xdeadbeefull, 0xdeadbeefull}, + {1, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {1, 0x1ull, 0x80000000ull, 0xd6be8bc1ull, 0x1ull, 0x1ull}, + {1, 0x283d5502ull, 0xfc565b67ull, 0x283d5502ull, 0x283d5502ull, 0xfc565b67ull}, + {1, 0xe503313aull, 0x1722ca72ull, 0xdd44c959ull, 0xe503313aull, 0xe503313aull}, + {1, 0x2a841e2ull, 0x52a450e2ull, 0x2a841e2ull, 0x2a841e2ull, 0x52a450e2ull}, + {1, 0xcc440ac9ull, 0x46313f70ull, 0xee4a57bbull, 0xcc440ac9ull, 0xcc440ac9ull}, + {2, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {2, 0x1ull, 0x80000000ull, 0x452f2ffcull, 0x1ull, 0x80000001ull}, + {2, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0x1ull}, + {2, 0x7fffffffull, 0x0ull, 0xf5091963ull, 0x7fffffffull, 0x7fffffffull}, + {2, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0xffffffffull}, + {2, 0x80000001ull, 0xfffffffeull, 0xeb165c4ull, 0x80000001ull, 0x7fffffffull}, + {2, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xdeadbeedull}, + {2, 0xffffffffull, 0x2ull, 0x1a438e52ull, 0xffffffffull, 0x1ull}, + {2, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x92345679ull}, + {2, 0xdeadbeefull, 0x12345678ull, 0x64638c03ull, 0xdeadbeefull, 0xf0e21567ull}, + {2, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {2, 0x1ull, 0x80000000ull, 0xe6d2996dull, 0x1ull, 0x80000001ull}, + {2, 0xa9bdd75aull, 0x70734273ull, 0xa9bdd75aull, 0xa9bdd75aull, 0x1a3119cdull}, + {2, 0x8aa692d0ull, 0x74d3a033ull, 0x5a7fd942ull, 0x8aa692d0ull, 0xff7a3303ull}, + {2, 0x201fe536ull, 0xf8ddb196ull, 0x201fe536ull, 0x201fe536ull, 0x18fd96ccull}, + {2, 0x15f79627ull, 0xb2346d27ull, 0x8a6b642dull, 0x15f79627ull, 0xc82c034eull}, + {3, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0xffffffffull}, + {3, 0x1ull, 0x80000000ull, 0x69639719ull, 0x1ull, 0x80000001ull}, + {3, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0x3ull}, + {3, 0x7fffffffull, 0x0ull, 0x89e34c92ull, 0x7fffffffull, 0x7fffffffull}, + {3, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x1ull}, + {3, 0x80000001ull, 0xfffffffeull, 0x73229920ull, 0x80000001ull, 0x80000003ull}, + {3, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0x2152410full}, + {3, 0xffffffffull, 0x2ull, 0x66310986ull, 0xffffffffull, 0xfffffffdull}, + {3, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x92345677ull}, + {3, 0xdeadbeefull, 0x12345678ull, 0x8fa4a357ull, 0xdeadbeefull, 0xcc796877ull}, + {3, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0xffffffffull}, + {3, 0x1ull, 0x80000000ull, 0x64bdad88ull, 0x1ull, 0x80000001ull}, + {3, 0xd5b148e7ull, 0x91d4c68ull, 0xd5b148e7ull, 0xd5b148e7ull, 0xcc93fc7full}, + {3, 0xb6cd96e4ull, 0xa145e36full, 0x6efd9be0ull, 0xb6cd96e4ull, 0x1587b375ull}, + {3, 0xf123997cull, 0x12d3d18aull, 0xf123997cull, 0xf123997cull, 0xde4fc7f2ull}, + {3, 0x62bfff89ull, 0x2106fea7ull, 0x22db0452ull, 0x62bfff89ull, 0x41b900e2ull}, + {4, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x0ull}, + {4, 0x1ull, 0x80000000ull, 0xdbbb69abull, 0x1ull, 0x80000000ull}, + {4, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xffffffffull}, + {4, 0x7fffffffull, 0x0ull, 0xdffce4e3ull, 0x7fffffffull, 0x0ull}, + {4, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x80000000ull}, + {4, 0x80000001ull, 0xfffffffeull, 0x77ac152aull, 0x80000001ull, 0x80000001ull}, + {4, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xdeadbeefull}, + {4, 0xffffffffull, 0x2ull, 0x24880f60ull, 0xffffffffull, 0xffffffffull}, + {4, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x80000001ull}, + {4, 0xdeadbeefull, 0x12345678ull, 0x6adc5e6full, 0xdeadbeefull, 0xdeadbeefull}, + {4, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x0ull}, + {4, 0x1ull, 0x80000000ull, 0x5c5e447eull, 0x1ull, 0x80000000ull}, + {4, 0xbc193f27ull, 0x5b2c838cull, 0xbc193f27ull, 0xbc193f27ull, 0xbc193f27ull}, + {4, 0xa69c5ce5ull, 0xbf61575eull, 0x341a640bull, 0xa69c5ce5ull, 0xa69c5ce5ull}, + {4, 0x1836851cull, 0x12fef585ull, 0x1836851cull, 0x1836851cull, 0x12fef585ull}, + {4, 0xaa5c538dull, 0x618e48b9ull, 0x56002b6cull, 0xaa5c538dull, 0xaa5c538dull}, + {5, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x0ull}, + {5, 0x1ull, 0x80000000ull, 0x21d3075eull, 0x1ull, 0x1ull}, + {5, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0x2ull}, + {5, 0x7fffffffull, 0x0ull, 0x16b60422ull, 0x7fffffffull, 0x0ull}, + {5, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x7fffffffull}, + {5, 0x80000001ull, 0xfffffffeull, 0xfd31cfd7ull, 0x80000001ull, 0x80000001ull}, + {5, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xdeadbeefull}, + {5, 0xffffffffull, 0x2ull, 0x6c5db104ull, 0xffffffffull, 0x2ull}, + {5, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x12345678ull}, + {5, 0xdeadbeefull, 0x12345678ull, 0xca2b6cbeull, 0xdeadbeefull, 0x12345678ull}, + {5, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x0ull}, + {5, 0x1ull, 0x80000000ull, 0xdc32d1aaull, 0x1ull, 0x1ull}, + {5, 0xfd11be0eull, 0x30eaba72ull, 0xfd11be0eull, 0xfd11be0eull, 0x30eaba72ull}, + {5, 0x4f696046ull, 0xed71a68aull, 0x59f1d18dull, 0x4f696046ull, 0x4f696046ull}, + {5, 0x2e192541ull, 0x1f6c7f26ull, 0x2e192541ull, 0x2e192541ull, 0x1f6c7f26ull}, + {5, 0x7a1ff8aeull, 0xdae2a240ull, 0xd604dd68ull, 0x7a1ff8aeull, 0x7a1ff8aeull}, + {6, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {6, 0x1ull, 0x80000000ull, 0xadf8324cull, 0x1ull, 0x1ull}, + {6, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0x2ull}, + {6, 0x7fffffffull, 0x0ull, 0x9f75da08ull, 0x7fffffffull, 0x7fffffffull}, + {6, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x7fffffffull}, + {6, 0x80000001ull, 0xfffffffeull, 0xcd48b9bdull, 0x80000001ull, 0xfffffffeull}, + {6, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xfffffffeull}, + {6, 0xffffffffull, 0x2ull, 0x40591809ull, 0xffffffffull, 0x2ull}, + {6, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x12345678ull}, + {6, 0xdeadbeefull, 0x12345678ull, 0x951a2694ull, 0xdeadbeefull, 0x12345678ull}, + {6, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {6, 0x1ull, 0x80000000ull, 0x55ff16b5ull, 0x1ull, 0x1ull}, + {6, 0xadb9fc15ull, 0x9263cee2ull, 0xadb9fc15ull, 0xadb9fc15ull, 0xadb9fc15ull}, + {6, 0xa055a0b2ull, 0xe06373c3ull, 0x406ffc43ull, 0xa055a0b2ull, 0xe06373c3ull}, + {6, 0xe442a859ull, 0xbcdeb639ull, 0xe442a859ull, 0xe442a859ull, 0xe442a859ull}, + {6, 0x238c6bf4ull, 0xfe8fedc6ull, 0x771aaf91ull, 0x238c6bf4ull, 0x238c6bf4ull}, + {7, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {7, 0x1ull, 0x80000000ull, 0x237244c9ull, 0x1ull, 0x80000000ull}, + {7, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xffffffffull}, + {7, 0x7fffffffull, 0x0ull, 0xf62e5e4full, 0x7fffffffull, 0x7fffffffull}, + {7, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x80000000ull}, + {7, 0x80000001ull, 0xfffffffeull, 0x90bfe553ull, 0x80000001ull, 0xfffffffeull}, + {7, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xfffffffeull}, + {7, 0xffffffffull, 0x2ull, 0x44b4cc99ull, 0xffffffffull, 0xffffffffull}, + {7, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x80000001ull}, + {7, 0xdeadbeefull, 0x12345678ull, 0x99faa521ull, 0xdeadbeefull, 0xdeadbeefull}, + {7, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {7, 0x1ull, 0x80000000ull, 0x5136e276ull, 0x1ull, 0x80000000ull}, + {7, 0x64a7abb4ull, 0x146d75bcull, 0x64a7abb4ull, 0x64a7abb4ull, 0x64a7abb4ull}, + {7, 0x2ae5498eull, 0x63a37ae1ull, 0xde40e42bull, 0x2ae5498eull, 0x63a37ae1ull}, + {7, 0x99206560ull, 0x2a0494fdull, 0x99206560ull, 0x99206560ull, 0x99206560ull}, + {7, 0x634e77acull, 0xbfe9ef2bull, 0x70824c54ull, 0x634e77acull, 0xbfe9ef2bull}, + {8, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x0ull}, + {8, 0x1ull, 0x80000000ull, 0x5b03f923ull, 0x1ull, 0x0ull}, + {8, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0x2ull}, + {8, 0x7fffffffull, 0x0ull, 0x32c25127ull, 0x7fffffffull, 0x0ull}, + {8, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x0ull}, + {8, 0x80000001ull, 0xfffffffeull, 0x1197fd88ull, 0x80000001ull, 0x80000000ull}, + {8, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xdeadbeeeull}, + {8, 0xffffffffull, 0x2ull, 0x7aa29c55ull, 0xffffffffull, 0x2ull}, + {8, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x0ull}, + {8, 0xdeadbeefull, 0x12345678ull, 0x4dc23c90ull, 0xdeadbeefull, 0x12241668ull}, + {8, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x0ull}, + {8, 0x1ull, 0x80000000ull, 0xfa62c0f8ull, 0x1ull, 0x0ull}, + {8, 0xfd7537d1ull, 0x72643b5aull, 0xfd7537d1ull, 0xfd7537d1ull, 0x70643350ull}, + {8, 0xd8dd1ff5ull, 0x8007f94cull, 0x1c7c7b29ull, 0xd8dd1ff5ull, 0x80051944ull}, + {8, 0xadaa9f69ull, 0x542db28dull, 0xadaa9f69ull, 0xadaa9f69ull, 0x4289209ull}, + {8, 0xeba8ad6dull, 0xaf3f49e4ull, 0xbf7eef4eull, 0xeba8ad6dull, 0xab280964ull}, + {9, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {9, 0x1ull, 0x80000000ull, 0xd2dc3da8ull, 0x1ull, 0x80000001ull}, + {9, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xffffffffull}, + {9, 0x7fffffffull, 0x0ull, 0xcbcf2e9cull, 0x7fffffffull, 0x7fffffffull}, + {9, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0xffffffffull}, + {9, 0x80000001ull, 0xfffffffeull, 0xf1616b9dull, 0x80000001ull, 0xffffffffull}, + {9, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xffffffffull}, + {9, 0xffffffffull, 0x2ull, 0xaf3ff23eull, 0xffffffffull, 0xffffffffull}, + {9, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x92345679ull}, + {9, 0xdeadbeefull, 0x12345678ull, 0xd2b04560ull, 0xdeadbeefull, 0xdebdfeffull}, + {9, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {9, 0x1ull, 0x80000000ull, 0xef07f743ull, 0x1ull, 0x80000001ull}, + {9, 0x24c74e5ull, 0x5084dec1ull, 0x24c74e5ull, 0x24c74e5ull, 0x52ccfee5ull}, + {9, 0x8c6705a2ull, 0x1e525d48ull, 0xab05ab28ull, 0x8c6705a2ull, 0x9e775deaull}, + {9, 0xa36a05e5ull, 0x3a793efcull, 0xa36a05e5ull, 0xa36a05e5ull, 0xbb7b3ffdull}, + {9, 0xd5f31646ull, 0xa296c3e7ull, 0x213632ddull, 0xd5f31646ull, 0xf7f7d7e7ull}, + {10, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {10, 0x1ull, 0x80000000ull, 0x11e1dbaaull, 0x1ull, 0x80000001ull}, + {10, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0xfffffffdull}, + {10, 0x7fffffffull, 0x0ull, 0x2cd60e1aull, 0x7fffffffull, 0x7fffffffull}, + {10, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0xffffffffull}, + {10, 0x80000001ull, 0xfffffffeull, 0x1f025ad0ull, 0x80000001ull, 0x7fffffffull}, + {10, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0x21524111ull}, + {10, 0xffffffffull, 0x2ull, 0x5d9eb5b7ull, 0xffffffffull, 0xfffffffdull}, + {10, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x92345679ull}, + {10, 0xdeadbeefull, 0x12345678ull, 0xf167a808ull, 0xdeadbeefull, 0xcc99e897ull}, + {10, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {10, 0x1ull, 0x80000000ull, 0x9f4e6575ull, 0x1ull, 0x80000001ull}, + {10, 0x8bd3aa1cull, 0x44b01dcdull, 0x8bd3aa1cull, 0x8bd3aa1cull, 0xcf63b7d1ull}, + {10, 0xc2c143f7ull, 0x51bc9443ull, 0xd65e2124ull, 0xc2c143f7ull, 0x937dd7b4ull}, + {10, 0xb0cb286eull, 0xec5194a4ull, 0xb0cb286eull, 0xb0cb286eull, 0x5c9abccaull}, + {10, 0xe17a51ecull, 0xe0bfc82aull, 0x44dde368ull, 0xe17a51ecull, 0x1c599c6ull}, + {11, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {11, 0x1ull, 0x80000000ull, 0x20d071afull, 0x1ull, 0x2ull}, + {11, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0x3ull}, + {11, 0x7fffffffull, 0x0ull, 0x7a347eebull, 0x7fffffffull, 0x0ull}, + {11, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x0ull}, + {11, 0x80000001ull, 0xfffffffeull, 0x56e21801ull, 0x80000001ull, 0x80000002ull}, + {11, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0x0ull}, + {11, 0xffffffffull, 0x2ull, 0x7a390ac1ull, 0xffffffffull, 0x0ull}, + {11, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x12345679ull}, + {11, 0xdeadbeefull, 0x12345678ull, 0xe457cd1full, 0xdeadbeefull, 0x0ull}, + {11, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {11, 0x1ull, 0x80000000ull, 0x460a51e2ull, 0x1ull, 0x2ull}, + {11, 0xe3202a2aull, 0xe3202a2bull, 0xe3202a2aull, 0xe3202a2aull, 0xe3202a2bull}, + {11, 0x9ec2949ull, 0x9ec2949ull, 0x947613e8ull, 0x9ec2949ull, 0x0ull}, + {11, 0x8f8118afull, 0x8f8118b0ull, 0x8f8118afull, 0x8f8118afull, 0x8f8118b0ull}, + {11, 0x761757b6ull, 0x761757b6ull, 0x8be15c75ull, 0x761757b6ull, 0x0ull}, + {12, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {12, 0x1ull, 0x80000000ull, 0x7c5a8c15ull, 0x1ull, 0x0ull}, + {12, 0x2ull, 0xffffffffull, 0x2ull, 0x2ull, 0x1ull}, + {12, 0x7fffffffull, 0x0ull, 0x29769614ull, 0x7fffffffull, 0x0ull}, + {12, 0x80000000ull, 0x7fffffffull, 0x80000000ull, 0x80000000ull, 0x7fffffffull}, + {12, 0x80000001ull, 0xfffffffeull, 0x537a54d3ull, 0x80000001ull, 0x80000000ull}, + {12, 0xfffffffeull, 0xdeadbeefull, 0xfffffffeull, 0xfffffffeull, 0xdeadbeefull}, + {12, 0xffffffffull, 0x2ull, 0x6add45d9ull, 0xffffffffull, 0x2ull}, + {12, 0x12345678ull, 0x80000001ull, 0x12345678ull, 0x12345678ull, 0x12345677ull}, + {12, 0xdeadbeefull, 0x12345678ull, 0x978c0812ull, 0xdeadbeefull, 0x12345678ull}, + {12, 0x0ull, 0x1ull, 0x0ull, 0x0ull, 0x1ull}, + {12, 0x1ull, 0x80000000ull, 0xf67e5e26ull, 0x1ull, 0x0ull}, + {12, 0xebea6cf1ull, 0xebea6cf2ull, 0xebea6cf1ull, 0xebea6cf1ull, 0xebea6cf0ull}, + {12, 0xf9a9360ull, 0xf9a9360ull, 0xf85b81cdull, 0xf9a9360ull, 0xf9a935full}, + {12, 0x6e723d5cull, 0x6e723d5dull, 0x6e723d5cull, 0x6e723d5cull, 0x6e723d5bull}, + {12, 0x1f609b4full, 0x1f609b4full, 0x69d177f8ull, 0x1f609b4full, 0x1f609b4eull}, +}}; + +class GuestBlock { +public: + explicit GuestBlock(bool writable) { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global atomics: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, writable); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + const std::uint8_t* Data() const { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint64_t value, std::uint32_t bytes) { + for (std::uint32_t byte = 0; byte < bytes; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%llx", static_cast(value)); + return text; +} + +const Row& RowOf(std::uint32_t op, std::uint32_t tid) { + return Rows.at(op * RowsPerOp + tid % RowsPerOp); +} + +std::uint32_t OpBytes(std::uint32_t op) { + return op < NarrowOps ? 4u : 8u; +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); + } + } + return image; +} + +std::vector Expected() { + auto image = Initial(); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); + } + } + return image; +} + +void FillInput() { + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + auto* words = &Input[(op * Threads + tid) * 4u]; + if (OpBytes(op) == 4u) { + words[0] = static_cast(row.data); + words[1] = static_cast(row.comparator); + words[2] = 0u; + words[3] = 0u; + } else { + words[0] = static_cast(row.data); + words[1] = static_cast(row.data >> 32u); + words[2] = static_cast(row.comparator); + words[3] = static_cast(row.comparator >> 32u); + } + } + } +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(GlobalAtomicsCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Output.fill(Sentinel); + Dispatch(device, waveSize, guest.Data()); + const auto wave = "global atomics: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + const auto* words = &Output[(op * Threads + tid) * 2u]; + const auto returned = OpBytes(op) == 4u ? static_cast(words[0]) : (static_cast(words[1]) << 32u) | words[0]; + Require(returned == row.returned, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.returned)); + } + } + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock writable(true); + RunAtomics(*device, writable, 32); + std::puts("global atomics tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics64.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics64.cpp new file mode 100644 index 000000000..541e26eb0 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomics64.cpp @@ -0,0 +1,400 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t RowsPerOp = 16; +constexpr std::uint32_t OpCount = 11; +constexpr std::uint32_t NarrowOps = 0; +constexpr std::uint32_t OpStride = 0x200; +constexpr std::uint32_t Special = OpCount * OpStride; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array GlobalAtomicsCode{ + 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, + 0x4a080480, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0a80, 0xdd418000, 0x14080a03, 0xbf8c3f70, + 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000200, 0x4a0804ff, 0x00000400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000200, 0xdd458000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00000400, 0x4a0804ff, 0x00000800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000400, 0xdd498000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000600, + 0x4a0804ff, 0x00000c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000600, 0xdd4d8000, + 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000800, 0x4a0804ff, 0x00001000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000800, 0xdd558000, 0x14080a03, 0xbf8c3f70, + 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000a00, 0x4a0804ff, 0x00001400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000a00, 0xdd598000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00000c00, 0x4a0804ff, 0x00001800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000c00, 0xdd5d8000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000e00, + 0x4a0804ff, 0x00001c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000e00, 0xdd618000, + 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00001000, 0x4a0804ff, 0x00002000, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00001000, 0xdd658000, 0x14080a03, 0xbf8c3f70, + 0xe0741000, 0x80011406, 0x4a0602ff, 0x00001200, 0x4a0804ff, 0x00002400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00001200, 0xdd698000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00001400, 0x4a0804ff, 0x00002800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00001400, 0xdd6d8000, 0x14080a03, 0xbf8c3f70, 0xe0741000, 0x80011406, 0xbf810000 +}; + +struct Row { + std::uint32_t op; + std::uint64_t memory; + std::uint64_t data; + std::uint64_t comparator; + std::uint64_t returned; + std::uint64_t final; +}; + +constexpr std::array Rows{{ + {0, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {0, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {0, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {0, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xb210ee8d643f54e6ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {0, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {0, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {0, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {0, 0xfffffffeffffffffull, 0x1ull, 0xed5fb21be38948d7ull, 0xfffffffeffffffffull, 0x1ull}, + {0, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {0, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {0, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {0, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xb346daefa27ab170ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {0, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull, 0x53bb9629497dfb3full, 0x53bb9629497dfb3full, 0xa4abfed10ad2a242ull}, + {0, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull, 0x6c12816967391baaull, 0x6c12816965391baaull, 0xce7cd7b44b45eaf3ull}, + {0, 0xdba996ccffffffffull, 0x10bbe64100000001ull, 0xdba996ccffffffffull, 0xdba996ccffffffffull, 0x10bbe64100000001ull}, + {0, 0x5a7c138cffffffffull, 0x9c15fed00000001ull, 0x5a7c138cdfffffffull, 0x5a7c138cffffffffull, 0x9c15fed00000001ull}, + {1, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {1, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {1, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {1, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x166c48c6e9c1b01cull, 0x8000000000000000ull, 0x8000000000000000ull}, + {1, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {1, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {1, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {1, 0xfffffffeffffffffull, 0x1ull, 0x866b08e67ea9d02cull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {1, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {1, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {1, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {1, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa54604e5b9b1311full, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {1, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull, 0xb9a56d03ead2890full, 0xb9a56d03ead2890full, 0x242f2f3b525af861ull}, + {1, 0x65c783e7e26c0130ull, 0x19a0106761c2f186ull, 0x65c783e7e06c0130ull, 0x65c783e7e26c0130ull, 0x65c783e7e26c0130ull}, + {1, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull, 0x7bebc240ffffffffull, 0x7bebc240ffffffffull, 0x4da1ba4b00000001ull}, + {1, 0x57e3536bffffffffull, 0xd8d033bd00000001ull, 0x57e3536bdfffffffull, 0x57e3536bffffffffull, 0x57e3536bffffffffull}, + {2, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {2, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xffffffff00000000ull}, + {2, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {2, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd4db9193123f6ebaull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {2, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xfffffffffffffffeull}, + {2, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {2, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {2, 0xfffffffeffffffffull, 0x1ull, 0x25fd502c82facbc0ull, 0xfffffffeffffffffull, 0xffffffff00000000ull}, + {2, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {2, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {2, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xffffffff00000000ull}, + {2, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf9124f0e4aae24c5ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {2, 0xc3e86469baffc1dull, 0xc22f0e9d6d69338dull, 0xc3e86469baffc1dull, 0xc3e86469baffc1dull, 0xce6d94e409192faaull}, + {2, 0xe499e6544c91e53ull, 0x4647904bf55b8053ull, 0xe499e6546c91e53ull, 0xe499e6544c91e53ull, 0x54912eb13a249ea6ull}, + {2, 0x7a4a48e2ffffffffull, 0x7ccc4b5300000001ull, 0x7a4a48e2ffffffffull, 0x7a4a48e2ffffffffull, 0xf716943600000000ull}, + {2, 0xa925179dffffffffull, 0xb2b1ac1800000001ull, 0xa925179ddfffffffull, 0xa925179dffffffffull, 0x5bd6c3b600000000ull}, + {3, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x8000000000000001ull}, + {3, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x100000002ull}, + {3, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {3, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x6835bf86a066a97eull, 0x8000000000000000ull, 0x6dcba98765432110ull}, + {3, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, + {3, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {3, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ull}, + {3, 0xfffffffeffffffffull, 0x1ull, 0xbcbca7d9810b83c1ull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, + {3, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456779abcdef0ull}, + {3, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x8000000000000001ull}, + {3, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x100000002ull}, + {3, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x663c553a9a16e35ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {3, 0x63fbf4e8e453a4c5ull, 0xb7a15652045c43f6ull, 0x63fbf4e8e453a4c5ull, 0x63fbf4e8e453a4c5ull, 0xac5a9e96dff760cfull}, + {3, 0x1603b25645bdd8aaull, 0x1f87b96dd545e1f1ull, 0x1603b25647bdd8aaull, 0x1603b25645bdd8aaull, 0xf67bf8e87077f6b9ull}, + {3, 0x757fd168ffffffffull, 0x2356488500000001ull, 0x757fd168ffffffffull, 0x757fd168ffffffffull, 0x522988e3fffffffeull}, + {3, 0x752b2a35ffffffffull, 0x869e617c00000001ull, 0x752b2a35dfffffffull, 0x752b2a35ffffffffull, 0xee8cc8b9fffffffeull}, + {4, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {4, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {4, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd286c038892fd9d4ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {4, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {4, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {4, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {4, 0xfffffffeffffffffull, 0x1ull, 0x80b34c5cb5907e40ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {4, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {4, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {4, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {4, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xe7cd1f2260293767ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {4, 0x8372abbda96ba8eeull, 0x5e71be928ba29363ull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull, 0x8372abbda96ba8eeull}, + {4, 0xce77c3cdcc677910ull, 0xdf44f6f934647f0eull, 0xce77c3cdce677910ull, 0xce77c3cdcc677910ull, 0xce77c3cdcc677910ull}, + {4, 0x9b3edbb1ffffffffull, 0xf4f16cf100000001ull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull, 0x9b3edbb1ffffffffull}, + {4, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull, 0xadf3fbf0dfffffffull, 0xadf3fbf0ffffffffull, 0x8b29592900000001ull}, + {5, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {5, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {5, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {5, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xff63cf82d01938e9ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {5, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {5, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {5, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0xffffffffull}, + {5, 0xfffffffeffffffffull, 0x1ull, 0x2a88358605f1254ull, 0xfffffffeffffffffull, 0x1ull}, + {5, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x100000000ull}, + {5, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {5, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {5, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf078418740b4f6d3ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {5, 0x8d4a240ceafb92f2ull, 0xd789c424b5282b1dull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull, 0x8d4a240ceafb92f2ull}, + {5, 0x64b3620ed7a9434full, 0x930b9894c3741be0ull, 0x64b3620ed5a9434full, 0x64b3620ed7a9434full, 0x64b3620ed7a9434full}, + {5, 0x62a12f63ffffffffull, 0x4a1df58400000001ull, 0x62a12f63ffffffffull, 0x62a12f63ffffffffull, 0x4a1df58400000001ull}, + {5, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull, 0xd0826a0cdfffffffull, 0xd0826a0cffffffffull, 0x14fe08aa00000001ull}, + {6, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {6, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {6, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {6, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x35f6b71611838017ull, 0x8000000000000000ull, 0x123456789abcdef0ull}, + {6, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {6, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {6, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, + {6, 0xfffffffeffffffffull, 0x1ull, 0x884dcfec36f43da6ull, 0xfffffffeffffffffull, 0x1ull}, + {6, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, + {6, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {6, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {6, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x97e21514d87b299dull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {6, 0x7ae9f54bc19e56ddull, 0x4a6ac7aeb830ed76ull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull, 0x7ae9f54bc19e56ddull}, + {6, 0x5b1c8aa1eb9a258cull, 0xf4a0ea8b3badac72ull, 0x5b1c8aa1e99a258cull, 0x5b1c8aa1eb9a258cull, 0x5b1c8aa1eb9a258cull}, + {6, 0x869f35deffffffffull, 0xdeffa68100000001ull, 0x869f35deffffffffull, 0x869f35deffffffffull, 0xdeffa68100000001ull}, + {6, 0x5752b4dfffffffffull, 0x2a51ee7e00000001ull, 0x5752b4dfdfffffffull, 0x5752b4dfffffffffull, 0x5752b4dfffffffffull}, + {7, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {7, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {7, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {7, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xd316f470e2d8993bull, 0x8000000000000000ull, 0x8000000000000000ull}, + {7, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {7, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {7, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x100000000ull}, + {7, 0xfffffffeffffffffull, 0x1ull, 0xfc2b7ace1072e7b9ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {7, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull}, + {7, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {7, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {7, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x142c1e683d47a123ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {7, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull, 0x5ea0e74f0d30cb76ull, 0x5ea0e74f0d30cb76ull, 0xecd62560f1407a13ull}, + {7, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull, 0x6f8664b99fb31c8bull, 0x6f8664b99db31c8bull, 0x77417f9d1c387d00ull}, + {7, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull, 0x1e0ce078ffffffffull, 0x1e0ce078ffffffffull, 0xc130cf3c00000001ull}, + {7, 0xd160d889ffffffffull, 0x2b456f6800000001ull, 0xd160d889dfffffffull, 0xd160d889ffffffffull, 0xd160d889ffffffffull}, + {8, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x0ull}, + {8, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0x1ull}, + {8, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x0ull}, + {8, 0x8000000000000000ull, 0x123456789abcdef0ull, 0x691beca72ac31615ull, 0x8000000000000000ull, 0x0ull}, + {8, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {8, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0x0ull}, + {8, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x0ull}, + {8, 0xfffffffeffffffffull, 0x1ull, 0xc89594109911783aull, 0xfffffffeffffffffull, 0x1ull}, + {8, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x0ull}, + {8, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x0ull}, + {8, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0x1ull}, + {8, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xa69bfe93d6efb4ddull, 0x7fffffffffffffffull, 0x0ull}, + {8, 0xd5e7cc579d21963aull, 0x93826a1ee8ab2ed1ull, 0xd5e7cc579d21963aull, 0xd5e7cc579d21963aull, 0x9182481688210610ull}, + {8, 0x657c382c4e1cfcddull, 0x837a0dc63ac35f32ull, 0x657c382c4c1cfcddull, 0x657c382c4e1cfcddull, 0x17808040a005c10ull}, + {8, 0x7552c284ffffffffull, 0x995c2cf200000001ull, 0x7552c284ffffffffull, 0x7552c284ffffffffull, 0x1150008000000001ull}, + {8, 0xe20d4f8dffffffffull, 0x4864cdbc00000001ull, 0xe20d4f8ddfffffffull, 0xe20d4f8dffffffffull, 0x40044d8c00000001ull}, + {9, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {9, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffeffffffffull}, + {9, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {9, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xbad3dfdbb007d4ecull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {9, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull}, + {9, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {9, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {9, 0xfffffffeffffffffull, 0x1ull, 0xedecea3c6fa0d23full, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {9, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {9, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {9, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffeffffffffull}, + {9, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xf7926675f45d1971ull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {9, 0x360443a82569e20cull, 0x3800d9ff899231faull, 0x360443a82569e20cull, 0x360443a82569e20cull, 0x3e04dbffadfbf3feull}, + {9, 0xc265df77a6c71a0dull, 0x5e94d6ccc50557f8ull, 0xc265df77a4c71a0dull, 0xc265df77a6c71a0dull, 0xdef5dfffe7c75ffdull}, + {9, 0x41a90d6bffffffffull, 0x4bb3092400000001ull, 0x41a90d6bffffffffull, 0x41a90d6bffffffffull, 0x4bbb0d6fffffffffull}, + {9, 0xc54e9269ffffffffull, 0xf536f34000000001ull, 0xc54e9269dfffffffull, 0xc54e9269ffffffffull, 0xf57ef369ffffffffull}, + {10, 0x0ull, 0x7fffffffffffffffull, 0x0ull, 0x0ull, 0x7fffffffffffffffull}, + {10, 0x1ull, 0xfffffffeffffffffull, 0x3ull, 0x1ull, 0xfffffffefffffffeull}, + {10, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {10, 0x8000000000000000ull, 0x123456789abcdef0ull, 0xe3b411de678ee5c0ull, 0x8000000000000000ull, 0x923456789abcdef0ull}, + {10, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x0ull}, + {10, 0xffffffffull, 0x0ull, 0xffffffdfull, 0xffffffffull, 0xffffffffull}, + {10, 0x100000000ull, 0xffffffffull, 0x100000000ull, 0x100000000ull, 0x1ffffffffull}, + {10, 0xfffffffeffffffffull, 0x1ull, 0xe4e171d83c299defull, 0xfffffffeffffffffull, 0xfffffffefffffffeull}, + {10, 0x123456789abcdef0ull, 0x100000000ull, 0x123456789abcdef0ull, 0x123456789abcdef0ull, 0x123456799abcdef0ull}, + {10, 0x0ull, 0x7fffffffffffffffull, 0x200ull, 0x0ull, 0x7fffffffffffffffull}, + {10, 0x1ull, 0xfffffffeffffffffull, 0x1ull, 0x1ull, 0xfffffffefffffffeull}, + {10, 0x7fffffffffffffffull, 0x8000000000000000ull, 0xaa23249b400d9ebdull, 0x7fffffffffffffffull, 0xffffffffffffffffull}, + {10, 0xc1f06114a97ad060ull, 0x679f7fe64c950a15ull, 0xc1f06114a97ad060ull, 0xc1f06114a97ad060ull, 0xa66f1ef2e5efda75ull}, + {10, 0x59bdb13042bcfe48ull, 0x1669010b3ea903bbull, 0x59bdb13040bcfe48ull, 0x59bdb13042bcfe48ull, 0x4fd4b03b7c15fdf3ull}, + {10, 0x701d03a9ffffffffull, 0xf96a081600000001ull, 0x701d03a9ffffffffull, 0x701d03a9ffffffffull, 0x89770bbffffffffeull}, + {10, 0x9b817862ffffffffull, 0x3c3e50fe00000001ull, 0x9b817862dfffffffull, 0x9b817862ffffffffull, 0xa7bf289cfffffffeull}, +}}; + +class GuestBlock { +public: + explicit GuestBlock(bool writable) { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global atomics: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, writable); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + const std::uint8_t* Data() const { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint64_t value, std::uint32_t bytes) { + for (std::uint32_t byte = 0; byte < bytes; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%llx", static_cast(value)); + return text; +} + +const Row& RowOf(std::uint32_t op, std::uint32_t tid) { + return Rows.at(op * RowsPerOp + tid % RowsPerOp); +} + +std::uint32_t OpBytes(std::uint32_t op) { + return op < NarrowOps ? 4u : 8u; +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); + } + } + return image; +} + +std::vector Expected() { + auto image = Initial(); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); + } + } + return image; +} + +void FillInput() { + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + auto* words = &Input[(op * Threads + tid) * 4u]; + if (OpBytes(op) == 4u) { + words[0] = static_cast(row.data); + words[1] = static_cast(row.comparator); + words[2] = 0u; + words[3] = 0u; + } else { + words[0] = static_cast(row.data); + words[1] = static_cast(row.data >> 32u); + words[2] = static_cast(row.comparator); + words[3] = static_cast(row.comparator >> 32u); + } + } + } +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(GlobalAtomicsCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Output.fill(Sentinel); + Dispatch(device, waveSize, guest.Data()); + const auto wave = "global atomics: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + const auto* words = &Output[(op * Threads + tid) * 2u]; + const auto returned = OpBytes(op) == 4u ? static_cast(words[0]) : (static_cast(words[1]) << 32u) | words[0]; + Require(returned == row.returned, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.returned)); + } + } + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock writable(true); + RunAtomics(*device, writable, 32); + std::puts("global atomics 64 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsFloat.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsFloat.cpp new file mode 100644 index 000000000..cf7b06501 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsFloat.cpp @@ -0,0 +1,750 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t OpCount = 8; +constexpr std::uint32_t NarrowOps = 3; +constexpr std::uint32_t OpStride = 0x200; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array GlobalAtomicsFloatCode{ + 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, + 0x4a080480, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0a80, 0xdcf98000, 0x14080a03, 0xbf8c0070, + 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000200, 0x4a0804ff, 0x00000400, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000200, 0xd70f6a1e, 0x02020608, 0x7e3e0209, 0x503e3e80, 0xdcfd0000, + 0x147d0a1e, 0xbf8c0070, 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000400, 0x4a0804ff, 0x00000800, + 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000400, 0xdd018000, 0x14080a03, 0xbf8c0070, + 0xe0701000, 0x80011406, 0x4a0602ff, 0x00000600, 0x4a0804ff, 0x00000c00, 0xe0381000, 0x80000a04, + 0xbf8c3f70, 0x4a0c0aff, 0x00000600, 0xdd798000, 0x14080a03, 0xbf8c0070, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00000800, 0x4a0804ff, 0x00001000, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000800, 0xdd7d8000, 0x14080a03, 0xbf8c0070, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000a00, + 0x4a0804ff, 0x00001400, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000a00, 0xd70f6a1e, + 0x02020608, 0x7e3e0209, 0x503e3e80, 0xdd810000, 0x147d0a1e, 0xbf8c0070, 0xe0741000, 0x80011406, + 0x4a0602ff, 0x00000c00, 0x4a0804ff, 0x00001800, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, + 0x00000c00, 0xdd718000, 0x14080a03, 0xbf8c0070, 0xe0741000, 0x80011406, 0x4a0602ff, 0x00000e00, + 0x4a0804ff, 0x00001c00, 0xe0381000, 0x80000a04, 0xbf8c3f70, 0x4a0c0aff, 0x00000e00, 0xd70f6a1e, + 0x02020608, 0x7e3e0209, 0x503e3e80, 0xdd750000, 0x147d0a1e, 0xbf8c0070, 0xe0741000, 0x80011406, + 0xbf810000 +}; + +struct Row { + std::uint32_t op; + std::uint64_t memory; + std::uint64_t data; + std::uint64_t comparator; + std::uint64_t final; +}; + +constexpr std::array Rows{{ + {0, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {0, 0x0ull, 0x80000000ull, 0x80000000ull, 0x80000000ull}, + {0, 0x0ull, 0x3f800000ull, 0xc0200000ull, 0x0ull}, + {0, 0x0ull, 0xbf800000ull, 0xbf800000ull, 0x0ull}, + {0, 0x0ull, 0x3fc00000ull, 0x0ull, 0x3fc00000ull}, + {0, 0x0ull, 0x7f800000ull, 0xffc12345ull, 0x0ull}, + {0, 0x0ull, 0xff800000ull, 0xff800000ull, 0x0ull}, + {0, 0x0ull, 0x7fa00000ull, 0x807fffffull, 0x0ull}, + {0, 0x0ull, 0xff800001ull, 0xff800001ull, 0x0ull}, + {0, 0x0ull, 0x800000ull, 0x800000ull, 0x0ull}, + {0, 0x80000000ull, 0x0ull, 0x80000000ull, 0x0ull}, + {0, 0x80000000ull, 0x80000000ull, 0x0ull, 0x80000000ull}, + {0, 0x80000000ull, 0x3f800000ull, 0x7f800000ull, 0x80000000ull}, + {0, 0x80000000ull, 0xbf800000ull, 0xbf800000ull, 0x80000000ull}, + {0, 0x80000000ull, 0xc0200000ull, 0x0ull, 0xc0200000ull}, + {0, 0x80000000ull, 0x7f800000ull, 0x7fa00000ull, 0x80000000ull}, + {0, 0x80000000ull, 0xff800000ull, 0xff800000ull, 0x80000000ull}, + {0, 0x80000000ull, 0x7fa00000ull, 0x7f7fffffull, 0x80000000ull}, + {0, 0x80000000ull, 0xff800001ull, 0xff800001ull, 0x80000000ull}, + {0, 0x80000000ull, 0x7f7fffffull, 0x3f800000ull, 0x80000000ull}, + {0, 0x80000000ull, 0x800000ull, 0x800000ull, 0x80000000ull}, + {0, 0x3f800000ull, 0x0ull, 0x3f800000ull, 0x0ull}, + {0, 0x3f800000ull, 0x80000000ull, 0xbf800000ull, 0x3f800000ull}, + {0, 0x3f800000ull, 0x3f800000ull, 0xff800000ull, 0x3f800000ull}, + {0, 0x3f800000ull, 0x7f800000ull, 0xff800001ull, 0x3f800000ull}, + {0, 0x3f800000ull, 0x7fa00000ull, 0x800000ull, 0x3f800000ull}, + {0, 0xbf800000ull, 0x0ull, 0xbf800000ull, 0x0ull}, + {0, 0xbf800000ull, 0x80000000ull, 0x3f800000ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0x3f800000ull, 0x7fc00000ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0x7f800000ull, 0x1ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0xff800000ull, 0xff800000ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0x7fa00000ull, 0x0ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0xff800001ull, 0xff800001ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0x7f7fffffull, 0x3fc00000ull, 0xbf800000ull}, + {0, 0xbf800000ull, 0x800000ull, 0x800000ull, 0xbf800000ull}, + {0, 0x3fc00000ull, 0x0ull, 0x3fc00000ull, 0x0ull}, + {0, 0x3fc00000ull, 0x80000000ull, 0xbfc00000ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0x3f800000ull, 0xffc12345ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0xbf800000ull, 0xbf800000ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0x3fc00000ull, 0x3fc00000ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0x7f800000ull, 0x807fffffull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0xff800000ull, 0xff800000ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0x7fa00000ull, 0x80000000ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0xff800001ull, 0xff800001ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0x7f7fffffull, 0xc0200000ull, 0x3fc00000ull}, + {0, 0x3fc00000ull, 0x800000ull, 0x800000ull, 0x3fc00000ull}, + {0, 0xc0200000ull, 0x0ull, 0xc0200000ull, 0x0ull}, + {0, 0xc0200000ull, 0x80000000ull, 0x40200000ull, 0xc0200000ull}, + {0, 0xc0200000ull, 0x3f800000ull, 0x7fa00000ull, 0xc0200000ull}, + {0, 0xc0200000ull, 0xbf800000ull, 0xbf800000ull, 0xc0200000ull}, + {0, 0xc0200000ull, 0x7f800000ull, 0x7f7fffffull, 0xc0200000ull}, + {0, 0xc0200000ull, 0xff800000ull, 0xff800000ull, 0xc0200000ull}, + {0, 0xc0200000ull, 0x7fa00000ull, 0x3f800000ull, 0xc0200000ull}, + {0, 0xc0200000ull, 0xff800001ull, 0xff800001ull, 0xc0200000ull}, + {0, 0xc0200000ull, 0x7f7fffffull, 0x7f800000ull, 0xc0200000ull}, + {0, 0xc0200000ull, 0x800000ull, 0x800000ull, 0xc0200000ull}, + {0, 0x7f800000ull, 0x0ull, 0x7f800000ull, 0x0ull}, + {0, 0x7f800000ull, 0x80000000ull, 0xff800000ull, 0x7f800000ull}, + {0, 0x7f800000ull, 0x3f800000ull, 0xff800001ull, 0x7f800000ull}, + {0, 0x7f800000ull, 0xbf800000ull, 0xbf800000ull, 0x7f800000ull}, + {0, 0x7f800000ull, 0x7f800000ull, 0x800000ull, 0x7f800000ull}, + {0, 0xff800000ull, 0x0ull, 0xff800000ull, 0x0ull}, + {0, 0xff800000ull, 0x80000000ull, 0x7f800000ull, 0xff800000ull}, + {1, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {1, 0x0ull, 0x80000000ull, 0x80000000ull, 0x80000000ull}, + {1, 0x0ull, 0x3f800000ull, 0xc0200000ull, 0x0ull}, + {1, 0x0ull, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0x0ull, 0x3fc00000ull, 0x0ull, 0x0ull}, + {1, 0x0ull, 0xc0200000ull, 0x80000000ull, 0xc0200000ull}, + {1, 0x0ull, 0x7f800000ull, 0xffc12345ull, 0x0ull}, + {1, 0x0ull, 0xff800000ull, 0xff800000ull, 0xff800000ull}, + {1, 0x0ull, 0x7fc00000ull, 0x0ull, 0x0ull}, + {1, 0x0ull, 0xffc12345ull, 0x80000000ull, 0x0ull}, + {1, 0x0ull, 0x7fa00000ull, 0x807fffffull, 0x7fe00000ull}, + {1, 0x0ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {1, 0x0ull, 0x1ull, 0x0ull, 0x0ull}, + {1, 0x0ull, 0x807fffffull, 0x80000000ull, 0x807fffffull}, + {1, 0x0ull, 0x7f7fffffull, 0x80000000ull, 0x0ull}, + {1, 0x0ull, 0x800000ull, 0x800000ull, 0x0ull}, + {1, 0x80000000ull, 0x0ull, 0x80000000ull, 0x80000000ull}, + {1, 0x80000000ull, 0x80000000ull, 0x0ull, 0x80000000ull}, + {1, 0x80000000ull, 0x3f800000ull, 0x7f800000ull, 0x80000000ull}, + {1, 0x80000000ull, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0x80000000ull, 0x3fc00000ull, 0x80000000ull, 0x80000000ull}, + {1, 0x80000000ull, 0xc0200000ull, 0x0ull, 0xc0200000ull}, + {1, 0x80000000ull, 0x7f800000ull, 0x7fa00000ull, 0x80000000ull}, + {1, 0x80000000ull, 0xff800000ull, 0xff800000ull, 0xff800000ull}, + {1, 0x80000000ull, 0x7fc00000ull, 0x80000000ull, 0x80000000ull}, + {1, 0x80000000ull, 0xffc12345ull, 0x0ull, 0x80000000ull}, + {1, 0x80000000ull, 0x7fa00000ull, 0x7f7fffffull, 0x7fe00000ull}, + {1, 0x80000000ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {1, 0x80000000ull, 0x1ull, 0x80000000ull, 0x80000000ull}, + {1, 0x80000000ull, 0x807fffffull, 0x0ull, 0x807fffffull}, + {1, 0x80000000ull, 0x7f7fffffull, 0x3f800000ull, 0x80000000ull}, + {1, 0x80000000ull, 0x800000ull, 0x800000ull, 0x80000000ull}, + {1, 0x3f800000ull, 0x0ull, 0x3f800000ull, 0x0ull}, + {1, 0x3f800000ull, 0x80000000ull, 0xbf800000ull, 0x80000000ull}, + {1, 0x3f800000ull, 0x3f800000ull, 0xff800000ull, 0x3f800000ull}, + {1, 0x3f800000ull, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0x3f800000ull, 0x3fc00000ull, 0x3f800000ull, 0x3f800000ull}, + {1, 0x3f800000ull, 0xc0200000ull, 0xbf800000ull, 0xc0200000ull}, + {1, 0x3f800000ull, 0x7f800000ull, 0xff800001ull, 0x3f800000ull}, + {1, 0x3f800000ull, 0xff800000ull, 0xff800000ull, 0xff800000ull}, + {1, 0x3f800000ull, 0x7fc00000ull, 0x3f800000ull, 0x3f800000ull}, + {1, 0x3f800000ull, 0xffc12345ull, 0xbf800000ull, 0x3f800000ull}, + {1, 0x3f800000ull, 0x7fa00000ull, 0x800000ull, 0x7fe00000ull}, + {1, 0x3f800000ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {1, 0x3f800000ull, 0x1ull, 0x3f800000ull, 0x1ull}, + {1, 0x3f800000ull, 0x807fffffull, 0xbf800000ull, 0x807fffffull}, + {1, 0x3f800000ull, 0x7f7fffffull, 0xbf800000ull, 0x3f800000ull}, + {1, 0x3f800000ull, 0x800000ull, 0x800000ull, 0x800000ull}, + {1, 0xbf800000ull, 0x0ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0x80000000ull, 0x3f800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0x3f800000ull, 0x7fc00000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0x3fc00000ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0xc0200000ull, 0x3f800000ull, 0xc0200000ull}, + {1, 0xbf800000ull, 0x7f800000ull, 0x1ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0xff800000ull, 0xff800000ull, 0xff800000ull}, + {1, 0xbf800000ull, 0x7fc00000ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0xffc12345ull, 0x3f800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0x7fa00000ull, 0x0ull, 0x7fe00000ull}, + {1, 0xbf800000ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {1, 0xbf800000ull, 0x1ull, 0xbf800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0x807fffffull, 0x3f800000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0x7f7fffffull, 0x3fc00000ull, 0xbf800000ull}, + {1, 0xbf800000ull, 0x800000ull, 0x800000ull, 0xbf800000ull}, + {2, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {2, 0x0ull, 0x80000000ull, 0x80000000ull, 0x0ull}, + {2, 0x0ull, 0x3f800000ull, 0xc0200000ull, 0x3f800000ull}, + {2, 0x0ull, 0xbf800000ull, 0xbf800000ull, 0x0ull}, + {2, 0x0ull, 0x3fc00000ull, 0x0ull, 0x3fc00000ull}, + {2, 0x0ull, 0xc0200000ull, 0x80000000ull, 0x0ull}, + {2, 0x0ull, 0x7f800000ull, 0xffc12345ull, 0x7f800000ull}, + {2, 0x0ull, 0xff800000ull, 0xff800000ull, 0x0ull}, + {2, 0x0ull, 0x7fc00000ull, 0x0ull, 0x0ull}, + {2, 0x0ull, 0xffc12345ull, 0x80000000ull, 0x0ull}, + {2, 0x0ull, 0x7fa00000ull, 0x807fffffull, 0x7fe00000ull}, + {2, 0x0ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {2, 0x0ull, 0x1ull, 0x0ull, 0x1ull}, + {2, 0x0ull, 0x807fffffull, 0x80000000ull, 0x0ull}, + {2, 0x0ull, 0x7f7fffffull, 0x80000000ull, 0x7f7fffffull}, + {2, 0x0ull, 0x800000ull, 0x800000ull, 0x800000ull}, + {2, 0x80000000ull, 0x0ull, 0x80000000ull, 0x0ull}, + {2, 0x80000000ull, 0x80000000ull, 0x0ull, 0x80000000ull}, + {2, 0x80000000ull, 0x3f800000ull, 0x7f800000ull, 0x3f800000ull}, + {2, 0x80000000ull, 0xbf800000ull, 0xbf800000ull, 0x80000000ull}, + {2, 0x80000000ull, 0x3fc00000ull, 0x80000000ull, 0x3fc00000ull}, + {2, 0x80000000ull, 0xc0200000ull, 0x0ull, 0x80000000ull}, + {2, 0x80000000ull, 0x7f800000ull, 0x7fa00000ull, 0x7f800000ull}, + {2, 0x80000000ull, 0xff800000ull, 0xff800000ull, 0x80000000ull}, + {2, 0x80000000ull, 0x7fc00000ull, 0x80000000ull, 0x80000000ull}, + {2, 0x80000000ull, 0xffc12345ull, 0x0ull, 0x80000000ull}, + {2, 0x80000000ull, 0x7fa00000ull, 0x7f7fffffull, 0x7fe00000ull}, + {2, 0x80000000ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {2, 0x80000000ull, 0x1ull, 0x80000000ull, 0x1ull}, + {2, 0x80000000ull, 0x807fffffull, 0x0ull, 0x80000000ull}, + {2, 0x80000000ull, 0x7f7fffffull, 0x3f800000ull, 0x7f7fffffull}, + {2, 0x80000000ull, 0x800000ull, 0x800000ull, 0x800000ull}, + {2, 0x3f800000ull, 0x0ull, 0x3f800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x80000000ull, 0xbf800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x3f800000ull, 0xff800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0xbf800000ull, 0xbf800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x3fc00000ull, 0x3f800000ull, 0x3fc00000ull}, + {2, 0x3f800000ull, 0xc0200000ull, 0xbf800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x7f800000ull, 0xff800001ull, 0x7f800000ull}, + {2, 0x3f800000ull, 0xff800000ull, 0xff800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x7fc00000ull, 0x3f800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0xffc12345ull, 0xbf800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x7fa00000ull, 0x800000ull, 0x7fe00000ull}, + {2, 0x3f800000ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {2, 0x3f800000ull, 0x1ull, 0x3f800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x807fffffull, 0xbf800000ull, 0x3f800000ull}, + {2, 0x3f800000ull, 0x7f7fffffull, 0xbf800000ull, 0x7f7fffffull}, + {2, 0x3f800000ull, 0x800000ull, 0x800000ull, 0x3f800000ull}, + {2, 0xbf800000ull, 0x0ull, 0xbf800000ull, 0x0ull}, + {2, 0xbf800000ull, 0x80000000ull, 0x3f800000ull, 0x80000000ull}, + {2, 0xbf800000ull, 0x3f800000ull, 0x7fc00000ull, 0x3f800000ull}, + {2, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull, 0xbf800000ull}, + {2, 0xbf800000ull, 0x3fc00000ull, 0xbf800000ull, 0x3fc00000ull}, + {2, 0xbf800000ull, 0xc0200000ull, 0x3f800000ull, 0xbf800000ull}, + {2, 0xbf800000ull, 0x7f800000ull, 0x1ull, 0x7f800000ull}, + {2, 0xbf800000ull, 0xff800000ull, 0xff800000ull, 0xbf800000ull}, + {2, 0xbf800000ull, 0x7fc00000ull, 0xbf800000ull, 0xbf800000ull}, + {2, 0xbf800000ull, 0xffc12345ull, 0x3f800000ull, 0xbf800000ull}, + {2, 0xbf800000ull, 0x7fa00000ull, 0x0ull, 0x7fe00000ull}, + {2, 0xbf800000ull, 0xff800001ull, 0xff800001ull, 0xffc00001ull}, + {2, 0xbf800000ull, 0x1ull, 0xbf800000ull, 0x1ull}, + {2, 0xbf800000ull, 0x807fffffull, 0x3f800000ull, 0x807fffffull}, + {2, 0xbf800000ull, 0x7f7fffffull, 0x3fc00000ull, 0x7f7fffffull}, + {2, 0xbf800000ull, 0x800000ull, 0x800000ull, 0x800000ull}, + {3, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {3, 0x0ull, 0x8000000000000000ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {3, 0x0ull, 0x3ff0000000000000ull, 0xc004000000000000ull, 0x0ull}, + {3, 0x0ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0x0ull}, + {3, 0x0ull, 0x3ff8000000000000ull, 0x0ull, 0x3ff8000000000000ull}, + {3, 0x0ull, 0x7ff0000000000000ull, 0xfff8000012345678ull, 0x0ull}, + {3, 0x0ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0x0ull}, + {3, 0x0ull, 0x7ff4000000000000ull, 0x800fffffffffffffull, 0x0ull}, + {3, 0x0ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0x0ull}, + {3, 0x0ull, 0x10000000000000ull, 0x10000000000000ull, 0x0ull}, + {3, 0x8000000000000000ull, 0x0ull, 0x8000000000000000ull, 0x0ull}, + {3, 0x8000000000000000ull, 0x8000000000000000ull, 0x0ull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0x3ff0000000000000ull, 0x7ff0000000000000ull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0xc004000000000000ull, 0x0ull, 0xc004000000000000ull}, + {3, 0x8000000000000000ull, 0x7ff0000000000000ull, 0x7ff4000000000000ull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0x7ff4000000000000ull, 0x7fefffffffffffffull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0x7fefffffffffffffull, 0x3ff0000000000000ull, 0x8000000000000000ull}, + {3, 0x8000000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0x8000000000000000ull}, + {3, 0x3ff0000000000000ull, 0x0ull, 0x3ff0000000000000ull, 0x0ull}, + {3, 0x3ff0000000000000ull, 0x8000000000000000ull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {3, 0x3ff0000000000000ull, 0x3ff0000000000000ull, 0xfff0000000000000ull, 0x3ff0000000000000ull}, + {3, 0x3ff0000000000000ull, 0x7ff0000000000000ull, 0xfff0000000000001ull, 0x3ff0000000000000ull}, + {3, 0x3ff0000000000000ull, 0x7ff4000000000000ull, 0x10000000000000ull, 0x3ff0000000000000ull}, + {3, 0xbff0000000000000ull, 0x0ull, 0xbff0000000000000ull, 0x0ull}, + {3, 0xbff0000000000000ull, 0x8000000000000000ull, 0x3ff0000000000000ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0x3ff0000000000000ull, 0x7ff8000000000000ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0x7ff0000000000000ull, 0x1ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0x7ff4000000000000ull, 0x0ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0x7fefffffffffffffull, 0x3ff8000000000000ull, 0xbff0000000000000ull}, + {3, 0xbff0000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0xbff0000000000000ull}, + {3, 0x3ff8000000000000ull, 0x0ull, 0x3ff8000000000000ull, 0x0ull}, + {3, 0x3ff8000000000000ull, 0x8000000000000000ull, 0xbff8000000000000ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0x3ff0000000000000ull, 0xfff8000012345678ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0x3ff8000000000000ull, 0x3ff8000000000000ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0x7ff0000000000000ull, 0x800fffffffffffffull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0x7ff4000000000000ull, 0x8000000000000000ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0x7fefffffffffffffull, 0xc004000000000000ull, 0x3ff8000000000000ull}, + {3, 0x3ff8000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0x3ff8000000000000ull}, + {3, 0xc004000000000000ull, 0x0ull, 0xc004000000000000ull, 0x0ull}, + {3, 0xc004000000000000ull, 0x8000000000000000ull, 0x4004000000000000ull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0x3ff0000000000000ull, 0x7ff4000000000000ull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0x7ff0000000000000ull, 0x7fefffffffffffffull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0x7ff4000000000000ull, 0x3ff0000000000000ull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0x7fefffffffffffffull, 0x7ff0000000000000ull, 0xc004000000000000ull}, + {3, 0xc004000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0xc004000000000000ull}, + {3, 0x7ff0000000000000ull, 0x0ull, 0x7ff0000000000000ull, 0x0ull}, + {3, 0x7ff0000000000000ull, 0x8000000000000000ull, 0xfff0000000000000ull, 0x7ff0000000000000ull}, + {3, 0x7ff0000000000000ull, 0x3ff0000000000000ull, 0xfff0000000000001ull, 0x7ff0000000000000ull}, + {3, 0x7ff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0x7ff0000000000000ull}, + {3, 0x7ff0000000000000ull, 0x7ff0000000000000ull, 0x10000000000000ull, 0x7ff0000000000000ull}, + {3, 0xfff0000000000000ull, 0x0ull, 0xfff0000000000000ull, 0x0ull}, + {3, 0xfff0000000000000ull, 0x8000000000000000ull, 0x7ff0000000000000ull, 0xfff0000000000000ull}, + {4, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {4, 0x0ull, 0x8000000000000000ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {4, 0x0ull, 0x3ff0000000000000ull, 0xc004000000000000ull, 0x0ull}, + {4, 0x0ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0x0ull, 0x3ff8000000000000ull, 0x0ull, 0x0ull}, + {4, 0x0ull, 0xc004000000000000ull, 0x8000000000000000ull, 0xc004000000000000ull}, + {4, 0x0ull, 0x7ff0000000000000ull, 0xfff8000012345678ull, 0x0ull}, + {4, 0x0ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull}, + {4, 0x0ull, 0x7ff8000000000000ull, 0x0ull, 0x0ull}, + {4, 0x0ull, 0xfff8000012345678ull, 0x8000000000000000ull, 0x0ull}, + {4, 0x0ull, 0x7ff4000000000000ull, 0x800fffffffffffffull, 0x7ffc000000000000ull}, + {4, 0x0ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {4, 0x0ull, 0x1ull, 0x0ull, 0x0ull}, + {4, 0x0ull, 0x800fffffffffffffull, 0x8000000000000000ull, 0x800fffffffffffffull}, + {4, 0x0ull, 0x7fefffffffffffffull, 0x8000000000000000ull, 0x0ull}, + {4, 0x0ull, 0x10000000000000ull, 0x10000000000000ull, 0x0ull}, + {4, 0x8000000000000000ull, 0x0ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0x8000000000000000ull, 0x0ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0x3ff0000000000000ull, 0x7ff0000000000000ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0x8000000000000000ull, 0x3ff8000000000000ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0xc004000000000000ull, 0x0ull, 0xc004000000000000ull}, + {4, 0x8000000000000000ull, 0x7ff0000000000000ull, 0x7ff4000000000000ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull}, + {4, 0x8000000000000000ull, 0x7ff8000000000000ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0xfff8000012345678ull, 0x0ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0x7ff4000000000000ull, 0x7fefffffffffffffull, 0x7ffc000000000000ull}, + {4, 0x8000000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {4, 0x8000000000000000ull, 0x1ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0x800fffffffffffffull, 0x0ull, 0x800fffffffffffffull}, + {4, 0x8000000000000000ull, 0x7fefffffffffffffull, 0x3ff0000000000000ull, 0x8000000000000000ull}, + {4, 0x8000000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0x8000000000000000ull}, + {4, 0x3ff0000000000000ull, 0x0ull, 0x3ff0000000000000ull, 0x0ull}, + {4, 0x3ff0000000000000ull, 0x8000000000000000ull, 0xbff0000000000000ull, 0x8000000000000000ull}, + {4, 0x3ff0000000000000ull, 0x3ff0000000000000ull, 0xfff0000000000000ull, 0x3ff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0x3ff8000000000000ull, 0x3ff0000000000000ull, 0x3ff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0xc004000000000000ull, 0xbff0000000000000ull, 0xc004000000000000ull}, + {4, 0x3ff0000000000000ull, 0x7ff0000000000000ull, 0xfff0000000000001ull, 0x3ff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0x7ff8000000000000ull, 0x3ff0000000000000ull, 0x3ff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0xfff8000012345678ull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0x7ff4000000000000ull, 0x10000000000000ull, 0x7ffc000000000000ull}, + {4, 0x3ff0000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {4, 0x3ff0000000000000ull, 0x1ull, 0x3ff0000000000000ull, 0x1ull}, + {4, 0x3ff0000000000000ull, 0x800fffffffffffffull, 0xbff0000000000000ull, 0x800fffffffffffffull}, + {4, 0x3ff0000000000000ull, 0x7fefffffffffffffull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {4, 0x3ff0000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0x10000000000000ull}, + {4, 0xbff0000000000000ull, 0x0ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x8000000000000000ull, 0x3ff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x3ff0000000000000ull, 0x7ff8000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x3ff8000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0xc004000000000000ull, 0x3ff0000000000000ull, 0xc004000000000000ull}, + {4, 0xbff0000000000000ull, 0x7ff0000000000000ull, 0x1ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x7ff8000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0xfff8000012345678ull, 0x3ff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x7ff4000000000000ull, 0x0ull, 0x7ffc000000000000ull}, + {4, 0xbff0000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {4, 0xbff0000000000000ull, 0x1ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x800fffffffffffffull, 0x3ff0000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x7fefffffffffffffull, 0x3ff8000000000000ull, 0xbff0000000000000ull}, + {4, 0xbff0000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0xbff0000000000000ull}, + {5, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {5, 0x0ull, 0x8000000000000000ull, 0x8000000000000000ull, 0x0ull}, + {5, 0x0ull, 0x3ff0000000000000ull, 0xc004000000000000ull, 0x3ff0000000000000ull}, + {5, 0x0ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0x0ull}, + {5, 0x0ull, 0x3ff8000000000000ull, 0x0ull, 0x3ff8000000000000ull}, + {5, 0x0ull, 0xc004000000000000ull, 0x8000000000000000ull, 0x0ull}, + {5, 0x0ull, 0x7ff0000000000000ull, 0xfff8000012345678ull, 0x7ff0000000000000ull}, + {5, 0x0ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0x0ull}, + {5, 0x0ull, 0x7ff8000000000000ull, 0x0ull, 0x0ull}, + {5, 0x0ull, 0xfff8000012345678ull, 0x8000000000000000ull, 0x0ull}, + {5, 0x0ull, 0x7ff4000000000000ull, 0x800fffffffffffffull, 0x7ffc000000000000ull}, + {5, 0x0ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {5, 0x0ull, 0x1ull, 0x0ull, 0x1ull}, + {5, 0x0ull, 0x800fffffffffffffull, 0x8000000000000000ull, 0x0ull}, + {5, 0x0ull, 0x7fefffffffffffffull, 0x8000000000000000ull, 0x7fefffffffffffffull}, + {5, 0x0ull, 0x10000000000000ull, 0x10000000000000ull, 0x10000000000000ull}, + {5, 0x8000000000000000ull, 0x0ull, 0x8000000000000000ull, 0x0ull}, + {5, 0x8000000000000000ull, 0x8000000000000000ull, 0x0ull, 0x8000000000000000ull}, + {5, 0x8000000000000000ull, 0x3ff0000000000000ull, 0x7ff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x8000000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0x8000000000000000ull}, + {5, 0x8000000000000000ull, 0x3ff8000000000000ull, 0x8000000000000000ull, 0x3ff8000000000000ull}, + {5, 0x8000000000000000ull, 0xc004000000000000ull, 0x0ull, 0x8000000000000000ull}, + {5, 0x8000000000000000ull, 0x7ff0000000000000ull, 0x7ff4000000000000ull, 0x7ff0000000000000ull}, + {5, 0x8000000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0x8000000000000000ull}, + {5, 0x8000000000000000ull, 0x7ff8000000000000ull, 0x8000000000000000ull, 0x8000000000000000ull}, + {5, 0x8000000000000000ull, 0xfff8000012345678ull, 0x0ull, 0x8000000000000000ull}, + {5, 0x8000000000000000ull, 0x7ff4000000000000ull, 0x7fefffffffffffffull, 0x7ffc000000000000ull}, + {5, 0x8000000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {5, 0x8000000000000000ull, 0x1ull, 0x8000000000000000ull, 0x1ull}, + {5, 0x8000000000000000ull, 0x800fffffffffffffull, 0x0ull, 0x8000000000000000ull}, + {5, 0x8000000000000000ull, 0x7fefffffffffffffull, 0x3ff0000000000000ull, 0x7fefffffffffffffull}, + {5, 0x8000000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0x10000000000000ull}, + {5, 0x3ff0000000000000ull, 0x0ull, 0x3ff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x8000000000000000ull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x3ff0000000000000ull, 0xfff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x3ff8000000000000ull, 0x3ff0000000000000ull, 0x3ff8000000000000ull}, + {5, 0x3ff0000000000000ull, 0xc004000000000000ull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x7ff0000000000000ull, 0xfff0000000000001ull, 0x7ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x7ff8000000000000ull, 0x3ff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0xfff8000012345678ull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x7ff4000000000000ull, 0x10000000000000ull, 0x7ffc000000000000ull}, + {5, 0x3ff0000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {5, 0x3ff0000000000000ull, 0x1ull, 0x3ff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x800fffffffffffffull, 0xbff0000000000000ull, 0x3ff0000000000000ull}, + {5, 0x3ff0000000000000ull, 0x7fefffffffffffffull, 0xbff0000000000000ull, 0x7fefffffffffffffull}, + {5, 0x3ff0000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0x3ff0000000000000ull}, + {5, 0xbff0000000000000ull, 0x0ull, 0xbff0000000000000ull, 0x0ull}, + {5, 0xbff0000000000000ull, 0x8000000000000000ull, 0x3ff0000000000000ull, 0x8000000000000000ull}, + {5, 0xbff0000000000000ull, 0x3ff0000000000000ull, 0x7ff8000000000000ull, 0x3ff0000000000000ull}, + {5, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {5, 0xbff0000000000000ull, 0x3ff8000000000000ull, 0xbff0000000000000ull, 0x3ff8000000000000ull}, + {5, 0xbff0000000000000ull, 0xc004000000000000ull, 0x3ff0000000000000ull, 0xbff0000000000000ull}, + {5, 0xbff0000000000000ull, 0x7ff0000000000000ull, 0x1ull, 0x7ff0000000000000ull}, + {5, 0xbff0000000000000ull, 0xfff0000000000000ull, 0xfff0000000000000ull, 0xbff0000000000000ull}, + {5, 0xbff0000000000000ull, 0x7ff8000000000000ull, 0xbff0000000000000ull, 0xbff0000000000000ull}, + {5, 0xbff0000000000000ull, 0xfff8000012345678ull, 0x3ff0000000000000ull, 0xbff0000000000000ull}, + {5, 0xbff0000000000000ull, 0x7ff4000000000000ull, 0x0ull, 0x7ffc000000000000ull}, + {5, 0xbff0000000000000ull, 0xfff0000000000001ull, 0xfff0000000000001ull, 0xfff8000000000001ull}, + {5, 0xbff0000000000000ull, 0x1ull, 0xbff0000000000000ull, 0x1ull}, + {5, 0xbff0000000000000ull, 0x800fffffffffffffull, 0x3ff0000000000000ull, 0x800fffffffffffffull}, + {5, 0xbff0000000000000ull, 0x7fefffffffffffffull, 0x3ff8000000000000ull, 0x7fefffffffffffffull}, + {5, 0xbff0000000000000ull, 0x10000000000000ull, 0x10000000000000ull, 0x10000000000000ull}, + {6, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {6, 0x0ull, 0x1ull, 0x8000000000000000ull, 0x1ull}, + {6, 0x0ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x1ull}, + {6, 0x1ull, 0x0ull, 0x1ull, 0x0ull}, + {6, 0x1ull, 0x1ull, 0x8000000000000001ull, 0x0ull}, + {6, 0x1ull, 0x2ull, 0xfffffffffffffffeull, 0x2ull}, + {6, 0x1ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x2ull}, + {6, 0x7fffffffffffffffull, 0x0ull, 0x7fffffffffffffffull, 0x0ull}, + {6, 0x0ull, 0x2ull, 0xffffffffffffffffull, 0x1ull}, + {6, 0x0ull, 0x8000000000000000ull, 0x0ull, 0x1ull}, + {6, 0x0ull, 0xffffffffffffffffull, 0x8000000000000000ull, 0x1ull}, + {6, 0x0ull, 0xfffffffffffffffeull, 0x100000001ull, 0x1ull}, + {6, 0x0ull, 0xffffffffull, 0xffffffffull, 0x1ull}, + {6, 0x0ull, 0x100000000ull, 0x0ull, 0x1ull}, + {6, 0x0ull, 0x100000001ull, 0x8000000000000000ull, 0x1ull}, + {6, 0x0ull, 0x123456789abcdef0ull, 0x3ull, 0x1ull}, + {6, 0x0ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0x1ull}, + {6, 0x0ull, 0xfffffffeull, 0x0ull, 0x1ull}, + {6, 0x0ull, 0x3ull, 0x8000000000000000ull, 0x1ull}, + {6, 0x0ull, 0x80000000ull, 0x1ull, 0x1ull}, + {6, 0x0ull, 0x7fffffffull, 0x7fffffffull, 0x1ull}, + {6, 0x1ull, 0x8000000000000000ull, 0x1ull, 0x2ull}, + {6, 0x1ull, 0xffffffffffffffffull, 0x8000000000000001ull, 0x2ull}, + {6, 0x1ull, 0xfffffffffffffffeull, 0x123456789abcdef0ull, 0x2ull}, + {6, 0x1ull, 0xffffffffull, 0xffffffffull, 0x2ull}, + {6, 0x1ull, 0x100000000ull, 0x1ull, 0x2ull}, + {6, 0x1ull, 0x100000001ull, 0x8000000000000001ull, 0x2ull}, + {6, 0x1ull, 0x123456789abcdef0ull, 0x80000000ull, 0x2ull}, + {6, 0x1ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0x2ull}, + {6, 0x1ull, 0xfffffffeull, 0x1ull, 0x2ull}, + {6, 0x1ull, 0x3ull, 0x8000000000000001ull, 0x2ull}, + {6, 0x1ull, 0x80000000ull, 0x2ull, 0x2ull}, + {6, 0x1ull, 0x7fffffffull, 0x7fffffffull, 0x2ull}, + {6, 0x2ull, 0x0ull, 0x2ull, 0x0ull}, + {6, 0x2ull, 0x1ull, 0x8000000000000002ull, 0x0ull}, + {6, 0x2ull, 0x2ull, 0xffffffffull, 0x0ull}, + {6, 0x2ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x3ull}, + {6, 0x2ull, 0x8000000000000000ull, 0x2ull, 0x3ull}, + {6, 0x2ull, 0xffffffffffffffffull, 0x8000000000000002ull, 0x3ull}, + {6, 0x2ull, 0xfffffffffffffffeull, 0xfffffffeffffffffull, 0x3ull}, + {6, 0x2ull, 0xffffffffull, 0xffffffffull, 0x3ull}, + {6, 0x2ull, 0x100000000ull, 0x2ull, 0x3ull}, + {6, 0x2ull, 0x100000001ull, 0x8000000000000002ull, 0x3ull}, + {6, 0x2ull, 0x123456789abcdef0ull, 0x7fffffffull, 0x3ull}, + {6, 0x2ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0x3ull}, + {6, 0x2ull, 0xfffffffeull, 0x2ull, 0x3ull}, + {6, 0x2ull, 0x3ull, 0x8000000000000002ull, 0x3ull}, + {6, 0x2ull, 0x80000000ull, 0x7fffffffffffffffull, 0x3ull}, + {6, 0x2ull, 0x7fffffffull, 0x7fffffffull, 0x3ull}, + {6, 0x7fffffffffffffffull, 0x1ull, 0xffffffffffffffffull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x2ull, 0x100000000ull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x8000000000000000ull}, + {6, 0x7fffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x8000000000000000ull}, + {6, 0x7fffffffffffffffull, 0xfffffffffffffffeull, 0xfffffffeull, 0x8000000000000000ull}, + {6, 0x7fffffffffffffffull, 0xffffffffull, 0xffffffffull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x100000000ull, 0x7fffffffffffffffull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x100000001ull, 0xffffffffffffffffull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x123456789abcdef0ull, 0x0ull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0x8000000000000000ull}, + {6, 0x7fffffffffffffffull, 0xfffffffeull, 0x7fffffffffffffffull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x3ull, 0xffffffffffffffffull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x80000000ull, 0x8000000000000000ull, 0x0ull}, + {6, 0x7fffffffffffffffull, 0x7fffffffull, 0x7fffffffull, 0x0ull}, + {7, 0x0ull, 0x0ull, 0x0ull, 0x0ull}, + {7, 0x0ull, 0x1ull, 0x8000000000000000ull, 0x1ull}, + {7, 0x0ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull}, + {7, 0x1ull, 0x0ull, 0x1ull, 0x0ull}, + {7, 0x1ull, 0x1ull, 0x8000000000000001ull, 0x0ull}, + {7, 0x1ull, 0x2ull, 0xfffffffffffffffeull, 0x0ull}, + {7, 0x1ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x0ull}, + {7, 0x7fffffffffffffffull, 0x0ull, 0x7fffffffffffffffull, 0x0ull}, + {7, 0x0ull, 0x2ull, 0xffffffffffffffffull, 0x2ull}, + {7, 0x0ull, 0x8000000000000000ull, 0x0ull, 0x8000000000000000ull}, + {7, 0x0ull, 0xffffffffffffffffull, 0x8000000000000000ull, 0xffffffffffffffffull}, + {7, 0x0ull, 0xfffffffffffffffeull, 0x100000001ull, 0xfffffffffffffffeull}, + {7, 0x0ull, 0xffffffffull, 0xffffffffull, 0xffffffffull}, + {7, 0x0ull, 0x100000000ull, 0x0ull, 0x100000000ull}, + {7, 0x0ull, 0x100000001ull, 0x8000000000000000ull, 0x100000001ull}, + {7, 0x0ull, 0x123456789abcdef0ull, 0x3ull, 0x123456789abcdef0ull}, + {7, 0x0ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0xfffffffeffffffffull}, + {7, 0x0ull, 0xfffffffeull, 0x0ull, 0xfffffffeull}, + {7, 0x0ull, 0x3ull, 0x8000000000000000ull, 0x3ull}, + {7, 0x0ull, 0x80000000ull, 0x1ull, 0x80000000ull}, + {7, 0x0ull, 0x7fffffffull, 0x7fffffffull, 0x7fffffffull}, + {7, 0x1ull, 0x8000000000000000ull, 0x1ull, 0x0ull}, + {7, 0x1ull, 0xffffffffffffffffull, 0x8000000000000001ull, 0x0ull}, + {7, 0x1ull, 0xfffffffffffffffeull, 0x123456789abcdef0ull, 0x0ull}, + {7, 0x1ull, 0xffffffffull, 0xffffffffull, 0x0ull}, + {7, 0x1ull, 0x100000000ull, 0x1ull, 0x0ull}, + {7, 0x1ull, 0x100000001ull, 0x8000000000000001ull, 0x0ull}, + {7, 0x1ull, 0x123456789abcdef0ull, 0x80000000ull, 0x0ull}, + {7, 0x1ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0x0ull}, + {7, 0x1ull, 0xfffffffeull, 0x1ull, 0x0ull}, + {7, 0x1ull, 0x3ull, 0x8000000000000001ull, 0x0ull}, + {7, 0x1ull, 0x80000000ull, 0x2ull, 0x0ull}, + {7, 0x1ull, 0x7fffffffull, 0x7fffffffull, 0x0ull}, + {7, 0x2ull, 0x0ull, 0x2ull, 0x0ull}, + {7, 0x2ull, 0x1ull, 0x8000000000000002ull, 0x1ull}, + {7, 0x2ull, 0x2ull, 0xffffffffull, 0x1ull}, + {7, 0x2ull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x1ull}, + {7, 0x2ull, 0x8000000000000000ull, 0x2ull, 0x1ull}, + {7, 0x2ull, 0xffffffffffffffffull, 0x8000000000000002ull, 0x1ull}, + {7, 0x2ull, 0xfffffffffffffffeull, 0xfffffffeffffffffull, 0x1ull}, + {7, 0x2ull, 0xffffffffull, 0xffffffffull, 0x1ull}, + {7, 0x2ull, 0x100000000ull, 0x2ull, 0x1ull}, + {7, 0x2ull, 0x100000001ull, 0x8000000000000002ull, 0x1ull}, + {7, 0x2ull, 0x123456789abcdef0ull, 0x7fffffffull, 0x1ull}, + {7, 0x2ull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0x1ull}, + {7, 0x2ull, 0xfffffffeull, 0x2ull, 0x1ull}, + {7, 0x2ull, 0x3ull, 0x8000000000000002ull, 0x1ull}, + {7, 0x2ull, 0x80000000ull, 0x7fffffffffffffffull, 0x1ull}, + {7, 0x2ull, 0x7fffffffull, 0x7fffffffull, 0x1ull}, + {7, 0x7fffffffffffffffull, 0x1ull, 0xffffffffffffffffull, 0x1ull}, + {7, 0x7fffffffffffffffull, 0x2ull, 0x100000000ull, 0x2ull}, + {7, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7fffffffffffffffull, 0x7ffffffffffffffeull}, + {7, 0x7fffffffffffffffull, 0x8000000000000000ull, 0x7fffffffffffffffull, 0x7ffffffffffffffeull}, + {7, 0x7fffffffffffffffull, 0xffffffffffffffffull, 0xffffffffffffffffull, 0x7ffffffffffffffeull}, + {7, 0x7fffffffffffffffull, 0xfffffffffffffffeull, 0xfffffffeull, 0x7ffffffffffffffeull}, + {7, 0x7fffffffffffffffull, 0xffffffffull, 0xffffffffull, 0xffffffffull}, + {7, 0x7fffffffffffffffull, 0x100000000ull, 0x7fffffffffffffffull, 0x100000000ull}, + {7, 0x7fffffffffffffffull, 0x100000001ull, 0xffffffffffffffffull, 0x100000001ull}, + {7, 0x7fffffffffffffffull, 0x123456789abcdef0ull, 0x0ull, 0x123456789abcdef0ull}, + {7, 0x7fffffffffffffffull, 0xfffffffeffffffffull, 0xfffffffeffffffffull, 0x7ffffffffffffffeull}, + {7, 0x7fffffffffffffffull, 0xfffffffeull, 0x7fffffffffffffffull, 0xfffffffeull}, + {7, 0x7fffffffffffffffull, 0x3ull, 0xffffffffffffffffull, 0x3ull}, + {7, 0x7fffffffffffffffull, 0x80000000ull, 0x8000000000000000ull, 0x80000000ull}, + {7, 0x7fffffffffffffffull, 0x7fffffffull, 0x7fffffffull, 0x7fffffffull} +}}; + +class GuestBlock { +public: + explicit GuestBlock(bool writable) { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global float atomics: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, writable); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + const std::uint8_t* Data() const { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint64_t value, std::uint32_t bytes) { + for (std::uint32_t byte = 0; byte < bytes; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%llx", static_cast(value)); + return text; +} + +const Row& RowOf(std::uint32_t op, std::uint32_t tid) { + return Rows.at(op * Threads + tid); +} + +std::uint32_t OpBytes(std::uint32_t op) { + return op < NarrowOps ? 4u : 8u; +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); + } + } + return image; +} + +std::vector Expected() { + auto image = Initial(); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); + } + } + return image; +} + +void FillInput() { + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + auto* words = &Input[(op * Threads + tid) * 4u]; + if (OpBytes(op) == 4u) { + words[0] = static_cast(row.data); + words[1] = static_cast(row.comparator); + words[2] = 0u; + words[3] = 0u; + } else { + words[0] = static_cast(row.data); + words[1] = static_cast(row.data >> 32u); + words[2] = static_cast(row.comparator); + words[3] = static_cast(row.comparator >> 32u); + } + } + } +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(GlobalAtomicsFloatCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Output.fill(Sentinel); + Dispatch(device, waveSize, guest.Data()); + const auto wave = "global float atomics: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + const auto* words = &Output[(op * Threads + tid) * 2u]; + const auto returned = OpBytes(op) == 4u ? static_cast(words[0]) : (static_cast(words[1]) << 32u) | words[0]; + Require(returned == row.memory, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.memory)); + } + } + const auto expected = Expected(); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto offset = op * OpStride + tid * 8u; + std::uint64_t actual = 0; + std::memcpy(&actual, guest.Data() + offset, OpBytes(op)); + const auto& row = RowOf(op, tid); + Require(actual == row.final, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " memory " + Hex(row.memory) + " data " + Hex(row.data) + " comparator " + Hex(row.comparator) + " is " + Hex(actual) + ", expected " + Hex(row.final)); + } + } + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + +void RunReadOnly(AgcDriver::VulkanDevice& device, GuestBlock& guest) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Dispatch(device, 32, guest.Data()); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + Require(guest.Data()[offset] == initial[offset], "global float atomics: an atomic into a read-only range changed byte " + std::to_string(offset)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock writable(true); + GuestBlock readOnly(false); + RunAtomics(*device, writable, 32); + RunAtomics(*device, writable, 64); + RunReadOnly(*device, readOnly); + std::puts("global float atomics tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsLanes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsLanes.cpp new file mode 100644 index 000000000..7629fb5af --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalAtomicsLanes.cpp @@ -0,0 +1,251 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t RowsPerOp = 16; +constexpr std::uint32_t OpCount = 0; +constexpr std::uint32_t NarrowOps = 0; +constexpr std::uint32_t OpStride = 0x200; +constexpr std::uint32_t Special = OpCount * OpStride; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint8_t Fill = 0xcd; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array GlobalAtomicsCode{ + 0x34020083, 0x34040084, 0x340a0083, 0x7e2802ff, 0x05e471e1, 0x7e2a02ff, 0x05e471e1, 0x4a060280, + 0x7e2802ff, 0x05e471e1, 0x4a140081, 0xdcc88000, 0x00080a03, 0xbf8c3f70, 0xd70f6a1e, 0x02020608, + 0x7e3e0209, 0x503e3e80, 0xd70f6a1e, 0x02023cff, 0x00000200, 0x503e3e80, 0x34140088, 0xdced0000, + 0x147d0a1e, 0xbf8c0070, 0x4a0c0a80, 0xe0701000, 0x80011406, 0x7e0602ff, 0x00000400, 0x4a1414ff, + 0x00000100, 0xdcc98004, 0x14080a03, 0xbf8c3f70, 0x4a0c0aff, 0x00000200, 0xe0701000, 0x80011406, + 0x4a0602ff, 0x00000800, 0x4a1400ff, 0x00000100, 0x7e160280, 0xdd488000, 0x00080a03, 0xbf8c3f70, + 0x7da80090, 0x4a0602ff, 0x00000600, 0x7e1402ff, 0x000000f0, 0xdce88000, 0x00080a03, 0xbf8c3f70, + 0xbf810000 +}; + +struct Row { + std::uint32_t op; + std::uint64_t memory; + std::uint64_t data; + std::uint64_t comparator; + std::uint64_t returned; + std::uint64_t final; +}; + +constexpr std::array Rows{}; + +class GuestBlock { +public: + explicit GuestBlock(bool writable) { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global atomics: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, writable); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + const std::uint8_t* Data() const { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +void Put(std::vector& image, std::uint32_t offset, std::uint64_t value, std::uint32_t bytes) { + for (std::uint32_t byte = 0; byte < bytes; ++byte) image.at(offset + byte) = static_cast(value >> (byte * 8u)); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%llx", static_cast(value)); + return text; +} + +const Row& RowOf(std::uint32_t op, std::uint32_t tid) { + return Rows.at(op * RowsPerOp + tid % RowsPerOp); +} + +std::uint32_t OpBytes(std::uint32_t op) { + return op < NarrowOps ? 4u : 8u; +} + +std::vector Initial() { + std::vector image(BlockBytes, Fill); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).memory, OpBytes(op)); + } + } + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, Special + tid * 8u, 0x1000u * tid, 4); + Put(image, Special + Threads * 8u + tid * 8u, 0xa5a5a5a5u ^ tid, 4); + Put(image, Special + Threads * 24u + tid * 8u, 0x0f0f0f00u + tid, 4); + Put(image, Special + Threads * 32u + tid * 8u, 0x7fffffffffffff00ull + tid, 8); + } + Put(image, Special + Threads * 16u + 4u, 0x11u, 4); + return image; +} + +std::vector Expected() { + auto image = Initial(); + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, op * OpStride + tid * 8u, RowOf(op, tid).final, OpBytes(op)); + } + } + std::uint32_t total = 0x11u; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Put(image, Special + tid * 8u, 0x1000u * tid + tid + 1u, 4); + Put(image, Special + Threads * 8u + tid * 8u, (0xa5a5a5a5u ^ tid) ^ (tid << 8u), 4); + if (tid < 16u) Put(image, Special + Threads * 24u + tid * 8u, (0x0f0f0f00u + tid) | 0xf0u, 4); + Put(image, Special + Threads * 32u + tid * 8u, 0x7fffffffffffff00ull + tid + 0x100u + tid, 8); + total += (tid + 1u) << 8u; + } + Put(image, Special + Threads * 16u + 4u, total, 4); + return image; +} + +void FillInput() { + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + auto* words = &Input[(op * Threads + tid) * 4u]; + if (OpBytes(op) == 4u) { + words[0] = static_cast(row.data); + words[1] = static_cast(row.comparator); + words[2] = 0u; + words[3] = 0u; + } else { + words[0] = static_cast(row.data); + words[1] = static_cast(row.data >> 32u); + words[2] = static_cast(row.comparator); + words[3] = static_cast(row.comparator >> 32u); + } + } + } +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(GlobalAtomicsCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void RunAtomics(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Output.fill(Sentinel); + Dispatch(device, waveSize, guest.Data()); + const auto wave = "global atomics: wave" + std::to_string(waveSize) + " "; + for (std::uint32_t op = 0; op < OpCount; ++op) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& row = RowOf(op, tid); + const auto* words = &Output[(op * Threads + tid) * 2u]; + const auto returned = OpBytes(op) == 4u ? static_cast(words[0]) : (static_cast(words[1]) << 32u) | words[0]; + Require(returned == row.returned, wave + "op " + std::to_string(op) + " lane " + std::to_string(tid) + " returned " + Hex(returned) + ", expected " + Hex(row.returned)); + } + } + std::vector> order; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto flat = Output[(OpCount * Threads + tid) * 2u]; + Require(flat == (0xa5a5a5a5u ^ tid), wave + "flat xor returned " + Hex(flat) + " to lane " + std::to_string(tid)); + order.emplace_back(Output[((OpCount + 1u) * Threads + tid) * 2u], tid); + } + std::sort(order.begin(), order.end()); + std::uint32_t running = 0x11u; + for (const auto& [returned, tid] : order) { + Require(returned == running, wave + "contended add returned " + Hex(returned) + " to lane " + std::to_string(tid) + ", expected " + Hex(running)); + running += (tid + 1u) << 8u; + } + const auto expected = Expected(); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + const auto actual = guest.Data()[offset]; + Require(actual == expected[offset], wave + "byte " + std::to_string(offset) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[offset])); + } +} + +void RunReadOnly(AgcDriver::VulkanDevice& device, GuestBlock& guest) { + const auto initial = Initial(); + std::memcpy(guest.Data(), initial.data(), BlockBytes); + FillInput(); + Dispatch(device, 32, guest.Data()); + for (std::uint32_t offset = 0; offset < BlockBytes; ++offset) { + Require(guest.Data()[offset] == initial[offset], "global atomics: an atomic into a read-only range changed byte " + std::to_string(offset)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock writable(true); + GuestBlock readOnly(false); + RunAtomics(*device, writable, 32); + RunAtomics(*device, writable, 64); + RunReadOnly(*device, readOnly); + std::puts("global atomics lanes tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoads.cpp new file mode 100644 index 000000000..ad91fd604 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoads.cpp @@ -0,0 +1,182 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc208800, 0x0a080020, 0xdc2487ff, + 0x0b080020, 0xdc288ffd, 0x0c080020, 0xdc2c8001, 0x0d7d0022, 0xdc308fff, 0x0e080020, 0xdc308000, + 0x18080021, 0xdc208800, 0x197d0022, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, + 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x000000d2, 0x00000029, 0x0000ebf6, 0x00004397, 0x4397357a, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x6526f2d2, 0x000000d2}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x000000f2, 0xffffffdc, 0x00007aeb, 0xffff9d43, 0x9d439735, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xa66526f2, 0x000000f2}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x00000026, 0xfffffff3, 0x0000357a, 0xffff819d, 0x819d4397, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x0ca66526, 0x00000026}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x00000065, 0x0000003d, 0x00009735, 0x00003c81, 0x3c819d43, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x120ca665, 0x00000065}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x00000077, 0x00000072, 0x00007ea7, 0x00003154, 0x31541eca, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xa2731cb1, 0x00000077}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x00000049, 0xffffffad, 0x000034f9, 0x00001eca, 0x1eca7ea7, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1cb172ff, 0x00000049}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x000000c6, 0xffffff91, 0x00002075, 0xffffb0c1, 0xb0c19888, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xdc295aec, 0x000000c6}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x000000f1, 0x00000054, 0x00001873, 0xffffdf25, 0xdf25a310, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x7dee2657, 0x000000f1}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x000000a8, 0x0000002d, 0x0000a25c, 0xffff9c05, 0x9c0507a5, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xf610a667, 0x000000a8}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x000000d7, 0xffffffb6, 0x00004159, 0xffffb70c, 0xb70c48ee, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x3e052289, 0x000000d7}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x0000003b, 0x00000015, 0x00007b82, 0x000024e5, 0x24e5c17c, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xdebaea1d, 0x0000003b}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x00000021, 0x00000071, 0x00007769, 0xffff8cc2, 0x8cc24bff, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x7f9859b1, 0x00000021}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x00000011, 0x00000016, 0x0000b53e, 0x00001018, 0x10187326, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x224cb259, 0x00000011}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x99571c29, 0x00000065}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x00000053, 0x00000067, 0x00004431, 0xffffd3b4, 0xd3b4c14d, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x034ab01d, 0x00000053}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x000000fd, 0x00000072, 0x0000d4a3, 0x000069d0, 0x69d0cbd1, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x15602efe, 0x000000fd}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x00000086, 0xfffffffe, 0x000070d6, 0xffff8e1d, 0x8e1de1ab, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x65d656c0, 0x00000086}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x00000019, 0x00000057, 0x00005567, 0xffffdf45, 0xdf45fb80, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xd14b0cf5, 0x00000019}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x0000009e, 0xffffff91, 0x000017c8, 0xffff8ce4, 0x8ce4e6ab, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1d426570, 0x0000009e}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x00000065, 0xffffff90, 0x0000764b, 0xffff9a41, 0x9a412405, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x79fec2ec, 0x00000065}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x000000a2, 0x0000001c, 0x00005a25, 0xffffd1d4, 0xd1d4a3aa, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x524da73e, 0x000000a2}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x0000008c, 0x00000017, 0x00006d7c, 0xffffa815, 0xa815a39f, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x660a3790, 0x0000008c}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x00000047, 0x0000000d, 0x0000d463, 0x00001bf9, 0x1bf9e87d, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0xaedff3c7, 0x00000047}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x000000c7, 0xffffffdb, 0x0000b20a, 0x0000100b, 0x100bbc11, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x92b36eda, 0x000000c7}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x00000025, 0x0000002b, 0x00006fd8, 0x00001477, 0x1477e9be, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x63779354, 0x00000025}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x000000c2, 0xffffffd1, 0x0000e2a4, 0x00003181, 0x31813cb2, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x764984b5, 0x000000c2}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x00000049, 0xfffffff9, 0x00001856, 0x0000575d, 0x575da8dc, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x1d9033df, 0x00000049}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global loads: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "global loads: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("global loads tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX2X3.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX2X3.cpp new file mode 100644 index 000000000..49ee77d74 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX2X3.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc348ffa, 0x0f7d0022, 0xdc3c8003, + 0x11080020, 0xbf8c0070, 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, + 0x80011202, 0xe0781030, 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xf6804124, 0x97357aeb, 0x513c819d, 0x2e32095f, 0xefa22967, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xebf68041, 0x4397357a, 0x5f513c81, 0x672e3209, 0x47efa229, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x7aebf680, 0x9d439735, 0x095f513c, 0x29672e32, 0xad47efa2, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x357aebf6, 0x819d4397, 0x32095f51, 0xa229672e, 0x53ad47ef, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa734f958, 0x541eca7e, 0x204cb651, 0x16a2f996, 0x660affc8, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xf95881df, 0xca7ea734, 0xb6513154, 0xf996204c, 0xffc816a2, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x75f13ba1, 0xc1988820, 0x08a59ac0, 0x85539459, 0x73d7de27, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x7326b53e, 0x25a31018, 0x6645c8aa, 0x02f743cf, 0x8fd2a50e, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5ca32e13, 0x0507a5a2, 0xeebcae0b, 0xfbcf54ff, 0x7c7b8218, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5905426c, 0x0c48ee41, 0x52e95ec2, 0x809af6c4, 0x9e49d979, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x8218fbcf, 0xe5c17c7b, 0x6ab73608, 0x185620a0, 0x575da8dc, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x69d0cbd1, 0xc24bff77, 0xd5c720a6, 0xd9c85a78, 0x60b4443a, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x3ecdef03, 0x187326b5, 0xaadf25a3, 0xcf6645c8, 0x0e02f743, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x31813cb2, 0xb4c14d44, 0xb9279ed7, 0xb93ff927, 0x59859a53, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa3aa5a25, 0xd0cbd1d4, 0xc24bff77, 0xc720a68c, 0xc85a78d5, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xd6bd1487, 0x1de1ab70, 0x3b6b431e, 0x7e7923d3, 0x777b0e8e, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x677ed35a, 0x45fb8055, 0x34f95881, 0x1eca7ea7, 0xb6513154, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xc8fa9bb0, 0xe4e6ab17, 0x4cd6a2c9, 0x36b17e32, 0xd6bd1487, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x4ba1fb80, 0x41240576, 0xbd0167bc, 0x6edae83e, 0xbf9692b3, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x25f6d039, 0xd4a3aa5a, 0x7769d0cb, 0x8cc24bff, 0xd5c720a6, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x7c7a524d, 0x15a39f6d, 0xed275ce5, 0x0e62da4d, 0xe790d315, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x6302c753, 0xf9e87dd4, 0x378b4009, 0xf3c8b729, 0x598433f0, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x0aa2a4b1, 0x0bbc11b2, 0x5dc397db, 0xd1f4d133, 0x10aae488, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xd8600189, 0x77e9be6f, 0x2c73a7bd, 0x421aff39, 0x1f09a43b, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0xa4765520, 0x813cb2e2, 0xb4c14d44, 0x279ed7d3, 0x3ff927b9, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5620a06a, 0x5da8dc18, 0x8d86c779, 0x4835e9c5, 0x406c576f, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global loads x2 x3: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "global loads x2 x3: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("global loads x2 x3 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX4.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX4.cpp new file mode 100644 index 000000000..d0bb6392d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalLoadsX4.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "FlatGlobalLoadsRegion.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Sentinel = 0x05e471e1; +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t RegionOffset = Threads * 16u; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array LoadsCode{ + 0x34020084, 0x34040086, 0xdc388000, 0x04080001, 0xbf8c3f70, 0x7e1402ff, 0x5a5a0000, 0x7e1602ff, + 0x5a5a0001, 0x7e1802ff, 0x5a5a0002, 0x7e1a02ff, 0x5a5a0003, 0x7e1c02ff, 0x5a5a0004, 0x7e1e02ff, + 0x5a5a0005, 0x7e2002ff, 0x5a5a0006, 0x7e2202ff, 0x5a5a0007, 0x7e2402ff, 0x5a5a0008, 0x7e2602ff, + 0x5a5a0009, 0x7e2802ff, 0x5a5a000a, 0x7e2a02ff, 0x5a5a000b, 0x7e2c02ff, 0x5a5a000c, 0x7e2e02ff, + 0x5a5a000d, 0x7e3002ff, 0x5a5a000e, 0x7e3202ff, 0x5a5a000f, 0x4a4008ff, 0x00000a00, 0x4a420eff, + 0x00000200, 0xd70f6a22, 0x02024008, 0x7e460209, 0x50464680, 0x4a4c0aff, 0x00000200, 0xd70f6a24, + 0x02024c08, 0x7e4a0209, 0x504a4a80, 0x36500c81, 0x7daa5080, 0xdc3887f8, 0x147d0022, 0xbf8c0070, + 0xbefe04c1, 0xe0781000, 0x80010a02, 0xe0781010, 0x80010e02, 0xe0781020, 0x80011202, 0xe0781030, + 0x80011602, 0xbf810000 +}; + +struct Row { + std::array input; + std::array results; +}; + +constexpr std::array Rows{{ + {{0x00000000, 0x00000000, 0x3ceb3ffd, 0x00000000}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6595f862, 0x295aec03, 0x523df3dc, 0x457d538e, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000001, 0x00000001, 0x97b75093, 0x00000001}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x036595f8, 0xdc295aec, 0x8e523df3, 0x48457d53, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000002, 0x00000fa0, 0x8b529b4b, 0x00000002}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xec036595, 0xf3dc295a, 0x538e523d, 0xe048457d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000003, 0x00000f9f, 0x21636369, 0x00000003}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5aec0365, 0x3df3dc29, 0x7d538e52, 0xfce04845, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003ff, 0x00000002, 0x5eb561a5, 0x000017fc}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x30d907a2, 0x72bdad38, 0xa92515b0, 0x948e5f94, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fe, 0x00000003, 0xea7b5bf4, 0x000017fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003fd, 0x000007ff, 0x9a9a80fd, 0x000017fa}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x07a2e287, 0xad3830d9, 0x15b072bd, 0x5f94a925, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000201, 0x00000801, 0x795b929f, 0x00000ffd}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x62c3c5bc, 0x91b7a807, 0x63034f25, 0x2db116b5, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000086, 0x000009b0, 0xe8a8529f, 0x0000006b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x3b946093, 0x54ebd216, 0x9bd7f852, 0x53f53ed6, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003c0, 0x00000426, 0x3bfd1d33, 0x000011a0}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x02701056, 0x2dd3f4aa, 0x602ab9e7, 0xcd71014b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000188, 0x00000b79, 0x8a7d43b5, 0x00000f0c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x693895b7, 0xb6e0664f, 0xdeca057c, 0x2b2c163e, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003cf, 0x0000065a, 0xdc6bf1e1, 0x00001473}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x59ac90cd, 0x15783291, 0x7584528a, 0xe888f86d, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000134, 0x000003b5, 0x26d0b944, 0x00001450}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000031e, 0x00000bdc, 0xabe19f59, 0x0000007c}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xf6724fb3, 0x7153291c, 0x4a5c9177, 0x0b128e2b, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000083, 0x0000028c, 0x0af438d3, 0x000012ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x936f25cd, 0x163b9460, 0x5254ebd2, 0xd69bd7f8, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x00000c7b, 0xd2d58443, 0x000000fe}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000227, 0x00000790, 0xb804d821, 0x00001308}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xb7d12049, 0x675b4866, 0xc676e8d8, 0xdca0e1a0, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000319, 0x00000b6d, 0x651c5253, 0x00000da9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x324d47e5, 0x724fb31c, 0x53291cf6, 0x5c917771, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000038e, 0x00000f6c, 0xe0fd67dd, 0x0000044a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x6c7e37f1, 0xfe28bb7f, 0xa0949a4c, 0x3ab02414, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002ec, 0x0000018f, 0x22cedafa, 0x00000125}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003f5, 0x00000378, 0xf729b4c9, 0x00000841}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa5a87b5e, 0x57dd4c23, 0x07a2e287, 0xad3830d9, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000037d, 0x00000c76, 0xdaf0105b, 0x0000140d}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x3d40d4d6, 0x911b0a0e, 0xd1eba0cd, 0xe767b4ff, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000268, 0x000006bd, 0xd55ec1a5, 0x0000103b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa14f8d0a, 0x902df1a3, 0x7f913ad6, 0xec4185b7, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000316, 0x0000092f, 0x88bafad9, 0x00000b3a}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xe50f0b34, 0x1c324d47, 0xf6724fb3, 0x7153291c, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000342, 0x00000959, 0xe779c471, 0x0000076f}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x528bc830, 0x17eb5b02, 0x9da049a4, 0xb3a7bbef, 0x5a5a000e, 0x5a5a000f}}, + {{0x000002b1, 0x00000ae9, 0xdb3d1151, 0x000000ea}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xa0d6d4b0, 0x0d8273a0, 0x6d34b2ad, 0xedd883ac, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000023c, 0x000009b1, 0xb2109306, 0x0000157b}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}}, + {{0x0000014e, 0x00000b2d, 0xf6f22f41, 0x00000a71}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x082b465a, 0xdb19a044, 0x1095f2e7, 0x9f733159, 0x5a5a000e, 0x5a5a000f}}, + {{0x000000d5, 0x00000b6b, 0x360c4979, 0x000014f9}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x87b15796, 0x2b17d0bf, 0xfa5d515c, 0x834fd313, 0x5a5a000e, 0x5a5a000f}}, + {{0x00000222, 0x0000048f, 0x103ef3c3, 0x000003fb}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x164fb710, 0xd1204939, 0x5b4866b7, 0x76e8d867, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003db, 0x00000da9, 0xfd63ed5b, 0x00001470}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0xe888f86d, 0xf927dda0, 0x9e9bf666, 0x0fcfcf14, 0x5a5a000e, 0x5a5a000f}}, + {{0x000003de, 0x0000016a, 0xccf3d0b2, 0x00000b02}, {0x5a5a0000, 0x5a5a0001, 0x5a5a0002, 0x5a5a0003, 0x5a5a0004, 0x5a5a0005, 0x5a5a0006, 0x5a5a0007, 0x5a5a0008, 0x5a5a0009, 0x5a5a000a, 0x5a5a000b, 0x5a5a000c, 0x5a5a000d, 0x5a5a000e, 0x5a5a000f}} +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global loads x4: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", value); + return text; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::uint8_t* base) { + const auto address = static_cast(reinterpret_cast(base)); + std::vector userData(10, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span code) { + Output.fill(Sentinel); + Dispatch(device, code, guest.Data()); + const std::string wave = "global loads x4: lane "; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto& expected = Rows[tid].results; + for (std::uint32_t result = 0; result < Results; ++result) { + const auto actual = Output[tid * Results + result]; + Require(actual == expected[result], wave + std::to_string(tid) + " v" + std::to_string(10u + result) + " is " + Hex(actual) + ", expected " + Hex(expected[result])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::memcpy(guest.Data() + tid * 16u, Rows[tid].input.data(), 16u); + } + std::memcpy(guest.Data() + RegionOffset, FlatGlobalLoadsRegion.data(), FlatGlobalLoadsRegion.size()); + Run(*device, guest, LoadsCode); + std::puts("global loads x4 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GlobalStoreAddtid.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalStoreAddtid.cpp new file mode 100644 index 000000000..fcf2f94d3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GlobalStoreAddtid.cpp @@ -0,0 +1,179 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint32_t Base = 2048; +constexpr std::uint32_t Fill = 0x5a5a5a5au; + +alignas(256) constexpr std::array AddtidCode{ + 0x800aff08, 0x00000800, 0x820b8009, 0x160800ff, 0x00000101, 0x4a0808ff, 0x00005100, 0x36500083, + 0x3a520880, 0xbefc0380, 0xdc5c8000, 0x000a2900, 0x3a5208ff, 0x11110000, 0xbefc03c0, 0xdc5c8f00, + 0x000a2900, 0x3a5208ff, 0x22220000, 0xbefc03ff, 0x0000ffff, 0xdc5c8c00, 0x000a2900, 0x3a5208ff, + 0x33330000, 0xbefc0380, 0x7daa5082, 0xdc5c8200, 0x000a2900, 0xbefe04c1, 0x3a5208ff, 0x44440000, + 0xbefc0380, 0xdc5c8800, 0x000a2900, 0x3a5208ff, 0x55550000, 0xbefc0380, 0xdc5c87fc, 0x000a2900, + 0xbf810000, +}; + +struct Store { + std::int32_t offset; + std::uint32_t pattern; + bool masked; +}; + +constexpr std::array Stores{{ + {0, 0x00000000u, false}, + {-256, 0x11110000u, false}, + {-1024, 0x22220000u, false}, + {512, 0x33330000u, true}, + {-2048, 0x44440000u, false}, + {2044, 0x55550000u, false}, +}}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "global store addtid: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::vector Expected(std::uint32_t lanes) { + std::vector memory(BlockBytes / 4u, Fill); + for (const auto& store : Stores) { + for (std::uint32_t lane = 0; lane < lanes; ++lane) { + if (store.masked && (lane & 3u) == 2u) continue; + memory[(Base + store.offset + lane * 4u) / 4u] = (0x5100u + lane * 0x101u) ^ store.pattern; + } + } + return memory; +} + +void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, const std::string& name) { + std::vector memory(BlockBytes / 4u, Fill); + std::memcpy(guest.Data(), memory.data(), BlockBytes); + const auto address = static_cast(reinterpret_cast(guest.Data())); + std::vector userData(10, 0u); + userData[8] = static_cast(address); + userData[9] = static_cast(address >> 32u); + const std::span code(AddtidCode); + const std::array regions{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, regions}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + std::memcpy(memory.data(), guest.Data(), BlockBytes); + const auto expected = Expected(waveSize); + for (std::size_t dword = 0; dword < memory.size(); ++dword) { + Require(memory[dword] == expected[dword], "global store addtid: " + name + " byte " + std::to_string(dword * 4u) + " is " + Hex(memory[dword]) + ", expected " + Hex(expected[dword])); + } +} + +void CheckRejected(const AgcDriver::VulkanDevice& device, std::span code, const std::string& reason) { + const std::array regions{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{32, 1, 1}, 0, {false, false, false}, false, 1}; + const std::vector userData(10, 0u); + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, regions}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + std::string failure; + try { + static_cast(ShaderRecompiler::Recompile(request)); + } catch (const std::exception& error) { + failure = error.what(); + } + Require(failure.find(reason) != std::string::npos, "global store addtid: expected '" + reason + "', got '" + failure + "'"); +} + +void CheckRejections(const AgcDriver::VulkanDevice& device) { + alignas(256) static constexpr std::array flatSegment{0xdc5c0000, 0x000a2900, 0xbf810000}; + alignas(256) static constexpr std::array scratchSegment{0xdc5c4000, 0x000a2900, 0xbf810000}; + alignas(256) static constexpr std::array noScalarBase{0xdc5c8000, 0x007d2900, 0xbf810000}; + CheckRejected(device, flatSegment, "global_store_dword_addtid is available only in the global segment"); + CheckRejected(device, scratchSegment, "global_store_dword_addtid is available only in the global segment"); + CheckRejected(device, noScalarBase, "global_store_dword_addtid supports only an SGPR pair as base address"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + CheckRejections(*device); + if (device->Target().subgroupSize < 32u) { + std::printf("skipped, the device's subgroups are narrower than a wave (%u lanes)\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + GuestBlock guest; + Run(*device, guest, 32, device->Target(), "wave32"); + Run(*device, guest, 64, device->Target(), "wave64"); + Run(*device, guest, 64, device->ComputeTarget(32), "wave64 split"); + std::puts("global store addtid tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAddressDimension.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAddressDimension.cpp new file mode 100644 index 000000000..0910f962a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAddressDimension.cpp @@ -0,0 +1,188 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Words = 32; +constexpr std::uint32_t Size = 16; +constexpr std::uint32_t Levels = 5; +constexpr std::uint32_t Format8888UNorm = 56; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t ArrayInput = 0; +constexpr std::uint32_t ArrayResult = 8; +constexpr std::uint32_t PlainInput = 16; +constexpr std::uint32_t PlainResult = 24; +alignas(256) std::array Buffer{}; +alignas(256) std::array Texels{}; + +alignas(256) constexpr std::array Code{ + 0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301008, 0x80000401, 0xe030100c, + 0x80000501, 0xe0301010, 0x80000601, 0xe0301014, 0x80000701, 0xe0301018, 0x80000801, 0xe030101c, + 0x80000901, 0xe0301040, 0x80001001, 0xe0301044, 0x80001101, 0xe0301048, 0x80001201, 0xe030104c, + 0x80001301, 0xe0301050, 0x80001401, 0xe0301054, 0x80001501, 0xbf8c3f70, 0xf0900f28, 0x00610c02, + 0xe0701020, 0x80000c01, 0xe0701024, 0x80000d01, 0xe0701028, 0x80000e01, 0xe070102c, 0x80000f01, + 0xf0041f28, 0x00010c06, 0xe0701030, 0x80000c01, 0xe0701034, 0x80000d01, 0xe0701038, 0x80000e01, + 0xe070103c, 0x80000f01, 0xf0900f08, 0x00610c10, 0xe0701060, 0x80000c01, 0xe0701064, 0x80000d01, + 0xe0701068, 0x80000e01, 0xe070106c, 0x80000f01, 0xf0041f08, 0x00010c13, 0xe0701070, 0x80000c01, + 0xe0701074, 0x80000d01, 0xe0701078, 0x80000e01, 0xe070107c, 0x80000f01, 0xbf810000, +}; + +alignas(256) constexpr std::array NarrowCode{ + 0x34020087, 0xe0301040, 0x80001001, 0xe0301044, 0x80001101, 0xbf8c3f70, 0xf0900f00, 0x00610c10, + 0xe0701070, 0x80000c01, 0xbf810000, +}; + +struct Sample { + std::uint32_t level; + std::uint32_t x; + std::uint32_t y; +}; + +Sample SampleOf(std::uint32_t tid) { + return {tid % Levels, (tid * 7u + 3u) % Size, (tid * 5u + 1u) % Size}; +} + +std::uint32_t Bits(float value) { + return std::bit_cast(value); +} + +void FillInput() { + Buffer.fill(0xdeadbeefu); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto sample = SampleOf(tid); + const auto u = Bits((static_cast(sample.x) + 0.5f) / static_cast(Size)); + const auto v = Bits((static_cast(sample.y) + 0.5f) / static_cast(Size)); + const auto lod = Bits(static_cast(sample.level)); + const auto x = sample.x >> sample.level; + const auto y = sample.y >> sample.level; + auto* words = &Buffer[tid * Words]; + const std::array array{u, v, Bits(0.0f), lod, x, y, 0u, sample.level}; + const std::array plain{u, v, lod, x, y, sample.level}; + std::copy(array.begin(), array.end(), words + ArrayInput); + std::copy(plain.begin(), plain.end(), words + PlainInput); + } +} + +void FillTexture() { + const auto mips = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, Format8888UNorm, Size, Size, Levels); + Require(AgcDriver::Graphics::ComputeSurfaceSize(mips, 1) <= Texels.size(), "image address dimension: the mip chain does not fit the texel storage"); + Texels.fill(0xeeu); + for (std::uint32_t level = 0; level < Levels; ++level) { + const auto& mip = mips[level]; + for (std::uint32_t y = 0; y < mip.height; ++y) { + for (std::uint32_t x = 0; x < mip.width; ++x) { + auto* texel = &Texels[mip.tiledOffset + static_cast(y) * mip.pitchBytes + x * 4u]; + texel[0] = static_cast(level); + texel[1] = static_cast(x); + texel[2] = static_cast(y); + texel[3] = 255u; + } + } + } +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::array TextureDescriptor(const void* data) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (((Size - 1u) & 3u) << 30u), + ((Size - 1u) >> 2u) | ((Size - 1u) << 14u), + 0xfacu | ((Levels - 1u) << 16u) | (Type2D << 28u), + 0u, + (Levels - 1u) << 4u, + 0u, + 0u, + }; +} + +std::array SamplerDescriptor() { + return {0u, 0xfffu << 12u, 1u << 26u, 0u}; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code) { + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); + const auto texture = TextureDescriptor(Texels.data()); + const auto sampler = SamplerDescriptor(); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 12); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + constexpr std::array names{"image_sample_l 2d_array", "image_load_mip 2d_array", "image_sample_l 2d", "image_load_mip 2d"}; + constexpr std::array results{ArrayResult, ArrayResult + 4u, PlainResult, PlainResult + 4u}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto sample = SampleOf(tid); + const std::array expected{sample.level, sample.x >> sample.level, sample.y >> sample.level, 255u}; + for (std::uint32_t index = 0; index < names.size(); ++index) { + for (std::uint32_t component = 0; component < 4u; ++component) { + const float value = std::bit_cast(Buffer[tid * Words + results[index] + component]) * 255.0f; + Require(std::lround(value) == static_cast(expected[component]), std::string(names[index]) + " on a 2D texture: thread " + std::to_string(tid) + " component " + std::to_string(component) + " is " + std::to_string(value) + ", expected " + std::to_string(expected[component])); + } + } + } +} + +void RequireRefused(AgcDriver::VulkanDevice& device) { + std::string refusal; + try { + Run(device, NarrowCode); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("too few coordinate components") != std::string::npos, "image_sample_l 1d on a 2D texture was not refused: " + refusal); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillInput(); + FillTexture(); + Run(*device, Code); + Check(); + RequireRefused(*device); + std::puts("image address dimension tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp index 7eaf3d20f..f27314c9f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomics.cpp @@ -21,6 +21,8 @@ constexpr std::uint32_t Results = 16; constexpr std::uint32_t Operations = 6; constexpr std::uint32_t TextureHeight = 8; constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Format32SInt = 21; +constexpr std::uint32_t Format32Float = 22; constexpr std::uint32_t Type2D = 9; alignas(256) std::array Input{}; alignas(256) std::array Output{}; @@ -71,24 +73,24 @@ std::array BufferDescriptor(const void* data, std::uint32_t by return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; } -std::array TextureDescriptor(const void* data, std::uint32_t width, std::uint32_t height) { +std::array TextureDescriptor(const void* data, std::uint32_t width, std::uint32_t height, std::uint32_t format) { const auto address = static_cast(reinterpret_cast(data)); return { static_cast(address >> 8u), - static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((width - 1u) & 3u) << 30u), + static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((width - 1u) & 3u) << 30u), ((width - 1u) >> 2u) | ((height - 1u) << 14u), 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u, }; } -void Run(AgcDriver::VulkanDevice& device) { +void Run(AgcDriver::VulkanDevice& device, std::uint32_t format) { Output.fill(0xdeadbeefu); Texels.fill(0xdeadbeefu); std::vector userData(16, 0u); const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); - const auto texture = TextureDescriptor(Texels.data(), Threads, TextureHeight); + const auto texture = TextureDescriptor(Texels.data(), Threads, TextureHeight, format); std::copy(input.begin(), input.end(), userData.begin()); std::copy(output.begin(), output.end(), userData.begin() + 4); std::copy(texture.begin(), texture.end(), userData.begin() + 8); @@ -107,7 +109,7 @@ void Run(AgcDriver::VulkanDevice& device) { device.WaitIdle(); } -void Check() { +void Check(std::uint32_t format) { constexpr std::array names{"image_atomic_sub", "image_atomic_smin", "image_atomic_smax", "image_atomic_inc", "image_atomic_dec", "image_atomic_cmpswap"}; for (std::uint32_t tid = 0; tid < Threads; ++tid) { const auto* in = &Input[tid * Inputs]; @@ -127,8 +129,8 @@ void Check() { for (std::uint32_t j = 0; j < Operations; ++j) { const std::uint32_t returned = Output[tid * Results + 2u * j]; const std::uint32_t stored = Output[tid * Results + 2u * j + 1u]; - Require(returned == a, std::string(names[j]) + ": thread " + std::to_string(tid) + " returned " + std::to_string(returned) + ", expected " + std::to_string(a)); - Require(stored == expected[j], std::string(names[j]) + ": thread " + std::to_string(tid) + " stored " + std::to_string(stored) + ", expected " + std::to_string(expected[j])); + Require(returned == a, std::string(names[j]) + ", format " + std::to_string(format) + ": thread " + std::to_string(tid) + " returned " + std::to_string(returned) + ", expected " + std::to_string(a)); + Require(stored == expected[j], std::string(names[j]) + ", format " + std::to_string(format) + ": thread " + std::to_string(tid) + " stored " + std::to_string(stored) + ", expected " + std::to_string(expected[j])); } } } @@ -140,8 +142,10 @@ int main() { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; FillInput(); - Run(*device); - Check(); + for (const auto format : {Format32UInt, Format32SInt, Format32Float}) { + Run(*device, format); + Check(format); + } std::puts("image atomics tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsBitwise.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsBitwise.cpp new file mode 100644 index 000000000..59fbc3c05 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsBitwise.cpp @@ -0,0 +1,142 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Operations = 5; +constexpr std::uint32_t TextureHeight = 8; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Type2D = 9; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; +alignas(4096) std::array Texels{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e100300, 0x7e120280, 0x7e140304, + 0xf03c0108, 0x00020a08, 0x7e160305, 0xf0602108, 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, + 0xbf8c3f70, 0x7e28030b, 0x7e2a030c, 0x7e120281, 0x7e140304, 0xf03c0108, 0x00020a08, 0x7e160305, + 0xf0642108, 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, 0xbf8c3f70, 0x7e2c030b, 0x7e2e030c, + 0x7e120282, 0x7e140304, 0xf03c0108, 0x00020a08, 0x7e160305, 0xf0682108, 0x00020b08, 0x7e180280, + 0xf0442108, 0x00020c08, 0xbf8c3f70, 0x7e30030b, 0x7e32030c, 0x7e120283, 0x7e140304, 0xf03c0108, + 0x00020a08, 0x7e160305, 0xf0542108, 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, 0xbf8c3f70, + 0x7e34030b, 0x7e36030c, 0x7e120284, 0x7e140304, 0xf03c0108, 0x00020a08, 0x7e160305, 0xf05c2108, + 0x00020b08, 0x7e180280, 0xf0442108, 0x00020c08, 0xbf8c3f70, 0x7e38030b, 0x7e3a030c, 0xe0701000, + 0x80011403, 0xe0701004, 0x80011503, 0xe0701008, 0x80011603, 0xe070100c, 0x80011703, 0xe0701010, + 0x80011803, 0xe0701014, 0x80011903, 0xe0701018, 0x80011a03, 0xe070101c, 0x80011b03, 0xe0701020, + 0x80011c03, 0xe0701024, 0x80011d03, 0xbf810000, +}; + +constexpr std::array, 14> Edges{{ + {0x0u, 0x0u}, {0x0u, 0xffffffffu}, {0xffffffffu, 0x0u}, {0xffffffffu, 0xffffffffu}, {0x7fffffffu, 0x80000000u}, {0x80000000u, 0x7fffffffu}, + {0xffffffffu, 0x1u}, {0x1u, 0xffffffffu}, {0xaaaaaaaau, 0x55555555u}, {0xf0f0f0f0u, 0xff00ff00u}, {0x5u, 0x5u}, {0x4u, 0x5u}, + {0x6u, 0x5u}, {0x80000000u, 0x80000000u}, +}}; + +void FillInput() { + std::uint64_t state = 0x9e3779b97f4a7c15ull; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + auto* words = &Input[tid * Inputs]; + for (std::uint32_t j = 0; j < 2u; ++j) { + state = state * 6364136223846793005ull + 1442695040888963407ull; + words[j] = static_cast(state >> 32u); + } + if (tid < Edges.size()) { + words[0] = Edges[tid][0]; + words[1] = Edges[tid][1]; + } + } +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::array TextureDescriptor(const void* data, std::uint32_t width, std::uint32_t height) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((width - 1u) & 3u) << 30u), + ((width - 1u) >> 2u) | ((height - 1u) << 14u), + 0xfacu | (Type2D << 28u), + 0u, 0u, 0u, 0u, + }; +} + +void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + Output.fill(0xdeadbeefu); + Texels.fill(0xdeadbeefu); + std::vector userData(16, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + const auto texture = TextureDescriptor(Texels.data(), Threads, TextureHeight); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + std::copy(texture.begin(), texture.end(), userData.begin() + 8); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(const char* run) { + constexpr std::array names{"image_atomic_and", "image_atomic_or", "image_atomic_xor", "image_atomic_umin", "image_atomic_umax"}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto* in = &Input[tid * Inputs]; + const std::uint32_t a = in[0]; + const std::uint32_t b = in[1]; + const std::array expected{a & b, a | b, a ^ b, std::min(a, b), std::max(a, b)}; + for (std::uint32_t j = 0; j < Operations; ++j) { + const std::uint32_t returned = Output[tid * Results + 2u * j]; + const std::uint32_t stored = Output[tid * Results + 2u * j + 1u]; + const auto where = std::string(names[j]) + " " + run + ": thread " + std::to_string(tid); + Require(returned == a, where + " returned " + std::to_string(returned) + ", expected " + std::to_string(a)); + Require(stored == expected[j], where + " stored " + std::to_string(stored) + ", expected " + std::to_string(expected[j])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillInput(); + Run(*device, 32, device->Target()); + Check("wave32"); + Run(*device, 64, device->Target()); + Check("wave64"); + Run(*device, 64, device->ComputeTarget(32)); + Check("wave64 split"); + std::puts("image atomics bitwise tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp index ff553a2b3..1799bebc2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageAtomicsFloat.cpp @@ -24,6 +24,9 @@ constexpr std::uint32_t Rows = 800; constexpr std::uint32_t TextureWidth = 64; constexpr std::uint32_t TextureHeight = 4; constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Format32SInt = 21; +constexpr std::uint32_t Format32Float = 22; +constexpr std::uint32_t Format16_16Float = 29; constexpr std::uint32_t Type2D = 9; alignas(256) std::array Buffer{}; alignas(4096) std::array Texels{}; @@ -474,22 +477,22 @@ std::array BufferDescriptor(const void* data, std::uint32_t by return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; } -std::array TextureDescriptor(const void* data) { +std::array TextureDescriptor(const void* data, std::uint32_t format) { const auto address = static_cast(reinterpret_cast(data)); return { static_cast(address >> 8u), - static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((TextureWidth - 1u) & 3u) << 30u), + static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((TextureWidth - 1u) & 3u) << 30u), ((TextureWidth - 1u) >> 2u) | ((TextureHeight - 1u) << 14u), 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u, }; } -void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, const char* name) { +ShaderRecompiler::RecompileResult Compile(std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, std::uint32_t format) { const std::span code(Code); std::vector userData(16, 0u); const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); - const auto texture = TextureDescriptor(Texels.data()); + const auto texture = TextureDescriptor(Texels.data(), format); std::copy(buffer.begin(), buffer.end(), userData.begin()); std::copy(texture.begin(), texture.end(), userData.begin() + 4); const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; @@ -501,7 +504,12 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRe {0, 0, 0, 128} }; request.useCache = false; - const auto result = ShaderRecompiler::Recompile(request); + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, const char* name, std::uint32_t format) { + const std::span code(Code); + const auto result = Compile(waveSize, target, format); Texels.fill(0xdeadbeefu); for (std::uint32_t first = 0; first < Rows; first += waveSize) { Buffer.fill(0xdeadbeefu); @@ -521,7 +529,7 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRe for (std::uint32_t j = 0; j < Checked; ++j) { const auto actual = Buffer[MaxLanes * Inputs + lane * Results + j]; char message[192]; - std::snprintf(message, sizeof(message), "image float atomics %s: row %u %s is 0x%08x, expected 0x%08x", name, row, names[j], actual, expected[j]); + std::snprintf(message, sizeof(message), "image float atomics %s, format %u: row %u %s is 0x%08x, expected 0x%08x", name, format, row, names[j], actual, expected[j]); Require(actual == expected[j], message); } } @@ -534,9 +542,18 @@ int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - Run(*device, 32, device->Target(), "wave32"); - Run(*device, 64, device->Target(), "wave64"); - Run(*device, 64, device->ComputeTarget(32), "wave64 split"); + for (const auto format : {Format32UInt, Format32SInt, Format32Float}) { + Run(*device, 32, device->Target(), "wave32", format); + Run(*device, 64, device->Target(), "wave64", format); + Run(*device, 64, device->ComputeTarget(32), "wave64 split", format); + } + bool refused = false; + try { + static_cast(Compile(32, device->Target(), Format16_16Float)); + } catch (const std::exception& error) { + refused = std::string(error.what()).find("atomic image descriptor uses an unsupported format 29") != std::string::npos; + } + Require(refused, "image float atomics on a 16_16 float surface were not refused"); std::puts("image float atomics tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleBiasA16.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleBiasA16.cpp new file mode 100644 index 000000000..ec41e7e3f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleBiasA16.cpp @@ -0,0 +1,189 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Words = 32; +constexpr std::uint32_t ResultWord = 8; +constexpr std::uint32_t Size = 16; +constexpr std::uint32_t Levels = 5; +constexpr std::uint32_t Clamps = 4; +constexpr std::uint32_t Format8888UNorm = 56; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t MinLodCapability = 42; +alignas(256) std::array Buffer{}; +alignas(256) std::array Texels{}; + +alignas(256) constexpr std::array BiasCode{ + 0x34020087, 0xe0301000, 0x80000501, 0xe0301004, 0x80000601, 0xbf8c3f70, 0xf0940f08, 0x40610c05, + 0xe0701020, 0x80000c01, 0xe0701024, 0x80000d01, 0xe0701028, 0x80000e01, 0xe070102c, 0x80000f01, + 0x7e0802ff, 0x00000201, 0xf0d40f08, 0x40610c04, 0xe0701030, 0x80000c01, 0xe0701034, 0x80000d01, + 0xe0701038, 0x80000e01, 0xe070103c, 0x80000f01, 0x7e0802ff, 0x00003e3f, 0xf0d40f08, 0x40610c04, + 0xe0701040, 0x80000c01, 0xe0701044, 0x80000d01, 0xe0701048, 0x80000e01, 0xe070104c, 0x80000f01, + 0xbf810000, +}; + +alignas(256) constexpr std::array BiasClampCode{ + 0x34020087, 0xe0301000, 0x80000501, 0xe0301004, 0x80000601, 0xe0301008, 0x80000701, 0xbf8c3f70, + 0xf0980f08, 0x40610c05, 0xe0701050, 0x80000c01, 0xe0701054, 0x80000d01, 0xe0701058, 0x80000e01, + 0xe070105c, 0x80000f01, 0xbf810000, +}; + +struct Sample { + std::uint32_t x; + std::uint32_t y; + std::uint32_t clamp; +}; + +Sample SampleOf(std::uint32_t tid) { + return {(tid * 7u + 3u) % Size, (tid * 5u + 1u) % Size, (tid / 3u) % Clamps}; +} + +std::uint32_t Half(float value) { + if (value == 0.0f) { + return 0u; + } + const auto bits = std::bit_cast(value); + return (((bits >> 23u) & 0xffu) - 112u) << 10u | ((bits >> 13u) & 0x3ffu); +} + +std::uint32_t Coordinate(std::uint32_t texel) { + return Half((static_cast(texel) + 0.5f) / static_cast(Size)); +} + +void FillInput() { + Buffer.fill(0xdeadbeefu); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto sample = SampleOf(tid); + auto* words = &Buffer[tid * Words]; + words[0] = Half(0.0f) | (Coordinate((sample.x + 5u) % Size) << 16u); + words[1] = Coordinate(sample.x) | (Coordinate(sample.y) << 16u); + words[2] = Half(static_cast(sample.clamp)) | (Half(4.0f) << 16u); + } +} + +void FillTexture() { + const auto mips = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, Format8888UNorm, Size, Size, Levels); + Require(AgcDriver::Graphics::ComputeSurfaceSize(mips, 1) <= Texels.size(), "image sample a16 bias: the mip chain does not fit the texel storage"); + Texels.fill(0xeeu); + for (std::uint32_t level = 0; level < Levels; ++level) { + const auto& mip = mips[level]; + for (std::uint32_t y = 0; y < mip.height; ++y) { + for (std::uint32_t x = 0; x < mip.width; ++x) { + auto* texel = &Texels[mip.tiledOffset + static_cast(y) * mip.pitchBytes + x * 4u]; + texel[0] = static_cast(level); + texel[1] = static_cast(x); + texel[2] = static_cast(y); + texel[3] = 255u; + } + } + } +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::array TextureDescriptor(const void* data) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (((Size - 1u) & 3u) << 30u), + ((Size - 1u) >> 2u) | ((Size - 1u) << 14u), + 0xfacu | ((Levels - 1u) << 16u) | (Type2D << 28u), + 0u, + (Levels - 1u) << 4u, + 0u, + 0u, + }; +} + +std::array SamplerDescriptor() { + return {0u, 0xfffu << 12u, 1u << 26u, 0u}; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code) { + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); + const auto texture = TextureDescriptor(Texels.data()); + const auto sampler = SamplerDescriptor(); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 12); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +std::uint32_t Wrap(std::uint32_t coordinate, std::int32_t offset, std::uint32_t size) { + const auto extent = static_cast(size); + return static_cast(((static_cast(coordinate) + offset) % extent + extent) % extent); +} + +void Check(bool clamped) { + constexpr std::array names{"image_sample_b a16", "image_sample_b_o a16 (1, 2)", "image_sample_b_o a16 (-1, -2)", "image_sample_b_cl a16"}; + constexpr std::array, 4> offsets{{{0, 0}, {1, 2}, {-1, -2}, {0, 0}}}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto sample = SampleOf(tid); + const std::array levels{0u, 0u, 0u, sample.clamp}; + for (std::uint32_t index = 0; index < (clamped ? 4u : 3u); ++index) { + const std::uint32_t level = levels[index]; + const std::uint32_t size = Size >> level; + const std::array expected{level, Wrap(sample.x >> level, offsets[index][0], size), Wrap(sample.y >> level, offsets[index][1], size), 255u}; + for (std::uint32_t component = 0; component < 4u; ++component) { + const float value = std::bit_cast(Buffer[tid * Words + ResultWord + index * 4u + component]) * 255.0f; + Require(std::lround(value) == static_cast(expected[component]), std::string(names[index]) + ": thread " + std::to_string(tid) + " component " + std::to_string(component) + " is " + std::to_string(value) + ", expected " + std::to_string(expected[component])); + } + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const auto capabilities = device->Target().supportedCapabilities; + const bool clamped = std::find(capabilities.begin(), capabilities.end(), MinLodCapability) != capabilities.end(); + FillInput(); + FillTexture(); + Run(*device, BiasCode); + if (clamped) { + Run(*device, BiasClampCode); + } + Check(clamped); + std::puts(clamped ? "image sample a16 bias tests passed" : "image sample a16 bias tests passed, image_sample_b_cl skipped without shaderResourceMinLod"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp new file mode 100644 index 000000000..5c29b7e6c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp @@ -0,0 +1,345 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Wave64Threads = 64; +constexpr std::uint32_t Inputs = 12; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 4; +constexpr std::uint32_t Depth = 2; +constexpr std::uint32_t Format32Float = 22; + +alignas(256) constexpr std::array Input{ + 0x3ce00000, 0x3e000000, 0x3e800000, 0x00000000, 0x3f800000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3d300000, 0x3ec00000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, + 0x3d700000, 0x3f200000, 0x3e800000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x3d980000, 0x3f600000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, + 0x3db80000, 0x3e000000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, 0x3dd80000, 0x3ec00000, 0x3f400000, 0x00000000, + 0x3f800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3df80000, 0x3f200000, 0x3f400000, 0x00000000, 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3e0c0000, 0x3f600000, 0x3f400000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x3e1c0000, 0x3e000000, 0x3e800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3e2c0000, 0x3ec00000, 0x3e800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x3a800000, 0x00000000, + 0x3e3c0000, 0x3f200000, 0x3e800000, 0x00000000, 0x3f800000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3e4c0000, 0x3f600000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3e5c0000, 0x3e000000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3a800000, 0x3a800000, 0x00000000, 0x00000000, 0x3e6c0000, 0x3ec00000, 0x3f400000, 0x00000000, + 0x3a800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x3e7c0000, 0x3f200000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x3e860000, 0x3f600000, 0x3f400000, 0x00000000, + 0x3f800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3e8e0000, 0x3e000000, 0x3e800000, 0x00000000, 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3e960000, 0x3ec00000, 0x3e800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3f800000, 0x00000000, 0x00000000, 0x00000000, + 0x3e9e0000, 0x3f200000, 0x3e800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, 0x3ea60000, 0x3f600000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, + 0x3eae0000, 0x3e000000, 0x3f400000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3eb60000, 0x3ec00000, 0x3f400000, 0x00000000, + 0x3a800000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3ebe0000, 0x3f200000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3f800000, 0x00000000, 0x00000000, 0x00000000, 0x3ec60000, 0x3f600000, 0x3f400000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, + 0x3ece0000, 0x3e000000, 0x3e800000, 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3ed60000, 0x3ec00000, 0x3e800000, 0x00000000, + 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x00000000, + 0x3ede0000, 0x3f200000, 0x3e800000, 0x00000000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x3ee60000, 0x3f600000, 0x3e800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3f800000, 0x00000000, 0x00000000, 0x00000000, + 0x3eee0000, 0x3e000000, 0x3f400000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, 0x3ef60000, 0x3ec00000, 0x3f400000, 0x00000000, + 0x3f800000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3efe0000, 0x3f200000, 0x3f400000, 0x00000000, 0x00000000, 0x3a800000, 0x3a800000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x3f030000, 0x3f600000, 0x3f400000, 0x00000000, + 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, 0x3a800000, 0x00000000, 0x00000000, +}; +alignas(256) std::array Buffer{}; +alignas(256) std::array Texels{}; + +alignas(256) constexpr std::array Code2D{ + 0x160200b0, 0x34060086, 0xe0381000, 0x80000401, 0xe0381010, 0x80002801, 0xe0381020, 0x80003201, + 0xbe900404, 0xbe920406, 0xbe940408, 0xbe96040a, 0xbe98040c, 0xbe9a040e, 0x7e140280, 0x7e160280, + 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280, + 0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, 0x7e300280, 0x7e320280, 0xbf8c3f70, 0x5e585328, + 0xd52f002d, 0x0201012a, 0x5e5c6732, 0xd52f002f, 0x02010134, 0x5e600b04, 0xd52f0031, 0x02010106, + 0xd52f0036, 0x02010128, 0xd52f0037, 0x02010132, 0xd52f003b, 0x02010104, 0x7e780281, 0x7e7a02f6, + 0x7e7c02ff, 0x00000101, 0x7e800328, 0x7e820329, 0x7e840332, 0x7e860333, 0x7e880304, 0x7e8a0305, + 0xf0880108, 0x00c40a40, 0x7e800328, 0x7e820329, 0x7e840332, 0x7e860333, 0x7e880304, 0x7e8a0305, + 0xf1a00108, 0x00c40b40, 0x7e800328, 0x7e820329, 0x7e840332, 0x7e860333, 0x7e880304, 0x7e8a0305, + 0xbe9e037e, 0xbefe03ff, 0xaeeeffff, 0xf1a00108, 0x00c40c40, 0xbefe031e, 0x7e80032c, 0x7e82032e, + 0x7e840304, 0x7e860305, 0xf0880109, 0x00c40d40, 0x7e80032c, 0x7e82032e, 0x7e840304, 0x7e860305, + 0xf1a00109, 0x00c40e40, 0x7e800328, 0x7e820329, 0x7e840332, 0x7e860333, 0x7e880330, 0xf0880108, + 0x40c40f40, 0x7e80032c, 0x7e82032e, 0x7e840330, 0xf0880109, 0x40c41040, 0x7e80033c, 0x7e820328, + 0x7e840329, 0x7e860332, 0x7e880333, 0x7e8a0304, 0x7e8c0305, 0xf1b00108, 0x00c41140, 0x7e80033e, + 0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305, 0xf0c80109, 0x00c41240, 0x7e80033c, 0x7e82032c, + 0x7e84032e, 0x7e860304, 0x7e880305, 0xf1b00109, 0x00c41340, 0x7e80033c, 0x7e82032c, 0x7e84032e, + 0x7e860330, 0xf0c80109, 0x40c41440, 0x7e80033c, 0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305, + 0xbe9e037e, 0xbefe03ff, 0xaeeeffff, 0xf1b00109, 0x00c41540, 0xbefe031e, 0xbf8c3f70, 0xe0701600, + 0x80000a03, 0xe0701604, 0x80000b03, 0xe0701608, 0x80000c03, 0xe070160c, 0x80000d03, 0xe0701610, + 0x80000e03, 0xe0701614, 0x80000f03, 0xe0701618, 0x80001003, 0xe070161c, 0x80001103, 0xe0701620, + 0x80001203, 0xe0701624, 0x80001303, 0xe0701628, 0x80001403, 0xe070162c, 0x80001503, 0xbf810000, +}; + +alignas(256) constexpr std::array Code3D{ + 0x160200b0, 0x34060086, 0xe0381000, 0x80000401, 0xe0381010, 0x80002801, 0xe0381020, 0x80003201, + 0xbe900404, 0xbe920406, 0xbe940408, 0xbe96040a, 0xbe98040c, 0xbe9a040e, 0x7e140280, 0x7e160280, + 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280, + 0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, 0x7e300280, 0x7e320280, 0xbf8c3f70, 0x5e585328, + 0xd52f002d, 0x0201012a, 0x5e5c6732, 0xd52f002f, 0x02010134, 0x5e600b04, 0xd52f0031, 0x02010106, + 0xd52f0036, 0x02010128, 0xd52f0037, 0x02010132, 0xd52f003b, 0x02010104, 0x7e780281, 0x7e7a02f6, + 0x7e7c02ff, 0x00000101, 0x7e800328, 0x7e820329, 0x7e84032a, 0x7e860332, 0x7e880333, 0x7e8a0334, + 0x7e8c0304, 0x7e8e0305, 0x7e900306, 0xf0880110, 0x00c40a40, 0x7e800328, 0x7e820329, 0x7e84032a, + 0x7e860332, 0x7e880333, 0x7e8a0334, 0x7e8c0304, 0x7e8e0305, 0x7e900306, 0xf1a00110, 0x00c40b40, + 0x7e80032c, 0x7e82032d, 0x7e84032e, 0x7e86032f, 0x7e880304, 0x7e8a0305, 0x7e8c0306, 0xf0880111, + 0x00c40c40, 0x7e80032c, 0x7e82032d, 0x7e84032e, 0x7e86032f, 0x7e880304, 0x7e8a0305, 0x7e8c0306, + 0xf1a00111, 0x00c40d40, 0x7e800328, 0x7e820329, 0x7e84032a, 0x7e860332, 0x7e880333, 0x7e8a0334, + 0x7e8c0330, 0x7e8e0331, 0xf0880110, 0x40c40e40, 0x7e80032c, 0x7e82032d, 0x7e84032e, 0x7e86032f, + 0x7e880330, 0x7e8a0331, 0xf0880111, 0x40c40f40, 0xbf8c3f70, 0xe0701600, 0x80000a03, 0xe0701604, + 0x80000b03, 0xe0701608, 0x80000c03, 0xe070160c, 0x80000d03, 0xe0701610, 0x80000e03, 0xe0701614, + 0x80000f03, 0xbf810000, +}; + +alignas(256) constexpr std::array Code1D{ + 0x160200b0, 0x34060086, 0xe0381000, 0x80000401, 0xe0381010, 0x80002801, 0xe0381020, 0x80003201, + 0xbe900404, 0xbe920406, 0xbe940408, 0xbe96040a, 0xbe98040c, 0xbe9a040e, 0x7e140280, 0x7e160280, + 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280, + 0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, 0x7e300280, 0x7e320280, 0xbf8c3f70, 0x5e585328, + 0xd52f002d, 0x0201012a, 0x5e5c6732, 0xd52f002f, 0x02010134, 0x5e600b04, 0xd52f0031, 0x02010106, + 0xd52f0036, 0x02010128, 0xd52f0037, 0x02010132, 0xd52f003b, 0x02010104, 0x7e780281, 0x7e7a02f6, + 0x7e7c02ff, 0x00000101, 0x7e800328, 0x7e820332, 0x7e840304, 0xf0880100, 0x00c40a40, 0x7e800336, + 0x7e820337, 0x7e840304, 0xf0880101, 0x00c40b40, 0x7e800336, 0x7e820337, 0x7e840304, 0xf1a00101, + 0x00c40c40, 0x7e800328, 0x7e820332, 0x7e84033b, 0xf0880100, 0x40c40d40, 0x7e800336, 0x7e820337, + 0x7e84033b, 0xf1a00101, 0x40c40e40, 0xbf8c3f70, 0xe0701600, 0x80000a03, 0xe0701604, 0x80000b03, + 0xe0701608, 0x80000c03, 0xe070160c, 0x80000d03, 0xe0701610, 0x80000e03, 0xbf810000, +}; + +constexpr std::array Names2D{"image_sample_d", "image_sample_cd", "image_sample_cd (exec)", "image_sample_d_g16", "image_sample_cd_g16", "image_sample_d a16", "image_sample_d_g16 a16", "image_sample_cd_o", "image_sample_d_o_g16", "image_sample_cd_o_g16", "image_sample_d_o_g16 a16", "image_sample_cd_o_g16 (exec)"}; +constexpr std::array Masked2D{false, false, true, false, false, false, false, false, false, false, false, true}; +constexpr std::array Expected2D{ + 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x40100000, + 0x42cc8000, 0x40100000, 0x40100000, 0x40100000, 0x42cc0000, 0x42cc8000, 0x42cc8000, 0x42cc0000, + 0x42cc8000, 0x42cc0000, 0x42cc0000, 0x42ce8000, 0x434b0000, 0x42ce8000, 0x42ce0000, 0x42ce8000, + 0x434b0000, 0x434b4000, 0x434b4000, 0x434b0000, 0x434b4000, 0x434b0000, 0x434b0000, 0x434c4000, + 0x43980000, 0x434c4000, 0x434c0000, 0x434c4000, 0x43980000, 0x43982000, 0x43982000, 0x43980000, + 0x43982000, 0x43980000, 0x43980000, 0x4398a000, 0x43988000, 0x4398a000, 0x43988000, 0x4398a000, + 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40c00000, + 0x42d40000, 0x40c00000, 0x40c00000, 0x40c00000, 0x42d48000, 0x42d40000, 0x42d40000, 0x42d48000, + 0x42d40000, 0x42d48000, 0x42d48000, 0x42d60000, 0x434f4000, 0x42d60000, 0x42d68000, 0x42d60000, + 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x43500000, + 0x439a0000, 0x43500000, 0x43500000, 0x43500000, 0x439a0000, 0x439a0000, 0x439a0000, 0x439a0000, + 0x439a0000, 0x439a0000, 0x439a0000, 0x439a8000, 0x439a8000, 0x439a8000, 0x439a8000, 0x439a8000, + 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41200000, + 0x42dc0000, 0x41200000, 0x41200000, 0x41200000, 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000, + 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42de0000, 0x43530000, 0x42de0000, 0x42de0000, 0x42de0000, + 0x43534000, 0x43530000, 0x43530000, 0x43534000, 0x43530000, 0x43534000, 0x43534000, 0x43540000, + 0x439c2000, 0x43540000, 0x43544000, 0x43540000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, + 0x439c0000, 0x439c0000, 0x439c0000, 0x439c8000, 0x439c8000, 0x439c8000, 0x439c8000, 0x439c8000, + 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41600000, + 0x42e40000, 0x41600000, 0x41600000, 0x41600000, 0x42e40000, 0x42e40000, 0x42e40000, 0x42e40000, + 0x42e40000, 0x42e40000, 0x42e40000, 0x42e60000, 0x43570000, 0x42e60000, 0x42e60000, 0x42e60000, + 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43580000, + 0x439e0000, 0x43580000, 0x43580000, 0x43580000, 0x439e2000, 0x439e0000, 0x439e0000, 0x439e2000, + 0x439e0000, 0x439e2000, 0x439e2000, 0x439e8000, 0x439ea000, 0x439e8000, 0x439ea000, 0x439e8000, + 0x41880000, 0x41880000, 0x00000000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41900000, + 0x42ec0000, 0x41900000, 0x41900000, 0x00000000, 0x42ec8000, 0x42ec0000, 0x42ec8000, 0x42ec8000, + 0x42ec0000, 0x42ec8000, 0x42ec8000, 0x42ee0000, 0x435b4000, 0x42ee0000, 0x42ee8000, 0x42ee8000, + 0x435b0000, 0x435b0000, 0x435b4000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435c0000, + 0x43a00000, 0x435c0000, 0x435c0000, 0x435c4000, 0x43a00000, 0x43a00000, 0x43a02000, 0x43a00000, + 0x43a00000, 0x43a00000, 0x43a00000, 0x43a08000, 0x43a08000, 0x43a08000, 0x43a08000, 0x43a0a000, + 0x41a80000, 0x41a80000, 0x00000000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41b00000, + 0x42f40000, 0x41b00000, 0x41b00000, 0x00000000, 0x42f40000, 0x42f40000, 0x42f40000, 0x42f40000, + 0x42f40000, 0x42f40000, 0x42f40000, 0x42f60000, 0x435f0000, 0x42f60000, 0x42f60000, 0x42f60000, + 0x435f4000, 0x435f0000, 0x435f0000, 0x435f4000, 0x435f0000, 0x435f4000, 0x435f4000, 0x43600000, + 0x43a22000, 0x43600000, 0x43604000, 0x43600000, 0x43a20000, 0x43a20000, 0x43a20000, 0x43a20000, + 0x43a20000, 0x43a20000, 0x43a20000, 0x43a28000, 0x43a28000, 0x43a28000, 0x43a28000, 0x43a28000, + 0x41c80000, 0x41c80000, 0x00000000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41d00000, + 0x42fc0000, 0x41d00000, 0x41d00000, 0x00000000, 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000, + 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fe0000, 0x43630000, 0x42fe0000, 0x42fe0000, 0x42fe0000, + 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43640000, + 0x43a40000, 0x43640000, 0x43640000, 0x43640000, 0x43a42000, 0x43a40000, 0x43a40000, 0x43a42000, + 0x43a40000, 0x43a42000, 0x43a42000, 0x43a48000, 0x43a4a000, 0x43a48000, 0x43a4a000, 0x43a48000, + 0x41e80000, 0x41e80000, 0x00000000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41f00000, + 0x43020000, 0x41f00000, 0x41f00000, 0x00000000, 0x43024000, 0x43020000, 0x43024000, 0x43024000, + 0x43020000, 0x43024000, 0x43024000, 0x43030000, 0x43674000, 0x43030000, 0x43034000, 0x43034000, + 0x43670000, 0x43670000, 0x00000000, 0x43670000, 0x43670000, 0x43670000, 0x43670000, 0x43680000, + 0x43a60000, 0x43680000, 0x43680000, 0x00000000, 0x43a60000, 0x43a60000, 0x43a62000, 0x43a60000, + 0x43a60000, 0x43a60000, 0x43a60000, 0x43a68000, 0x43a68000, 0x43a68000, 0x43a68000, 0x43a6a000, +}; +constexpr std::array Names3D{"image_sample_d 3D", "image_sample_cd 3D", "image_sample_d_g16 3D", "image_sample_cd_g16 3D", "image_sample_d a16 3D", "image_sample_d_g16 a16 3D"}; +constexpr std::array Masked3D{false, false, false, false, false, false}; +constexpr std::array Expected3D{ + 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x42cc0000, 0x42cc8000, + 0x42cc0000, 0x42cc8000, 0x42cc0000, 0x42cc0000, 0x434b0000, 0x434b4000, 0x434b0000, 0x434b4000, + 0x434b0000, 0x434b0000, 0x43980000, 0x43982000, 0x43980000, 0x43982000, 0x43980000, 0x43980000, + 0x447b4000, 0x447b4000, 0x447b4000, 0x447b4000, 0x447b4000, 0x447b4000, 0x448a4800, 0x448a4000, + 0x448a4800, 0x448a4000, 0x448a4800, 0x448a4800, 0x4496e000, 0x4496e000, 0x4496e000, 0x4496e000, + 0x4496e000, 0x4496e000, 0x44a38000, 0x44a38000, 0x44a38000, 0x44a38000, 0x44a38000, 0x44a38000, + 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x42dc0000, 0x42dc0000, + 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x43534000, 0x43530000, 0x43534000, 0x43530000, + 0x43534000, 0x43534000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, + 0x447d4000, 0x447d4000, 0x447d4000, 0x447d4000, 0x447d4000, 0x447d4000, 0x448b4000, 0x448b4000, + 0x448b4000, 0x448b4000, 0x448b4000, 0x448b4000, 0x4497e000, 0x4497e000, 0x4497e000, 0x4497e000, + 0x4497e000, 0x4497e000, 0x44a48800, 0x44a48000, 0x44a48800, 0x44a48000, 0x44a48800, 0x44a48800, + 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x42ec8000, 0x42ec0000, + 0x42ec8000, 0x42ec0000, 0x42ec8000, 0x42ec8000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435b0000, + 0x435b0000, 0x435b0000, 0x43a00000, 0x43a00000, 0x43a00000, 0x43a00000, 0x43a00000, 0x43a00000, + 0x447f4000, 0x447f4000, 0x447f4000, 0x447f4000, 0x447f4000, 0x447f4000, 0x448c4000, 0x448c4000, + 0x448c4000, 0x448c4000, 0x448c4000, 0x448c4000, 0x4498e800, 0x4498e000, 0x4498e800, 0x4498e000, + 0x4498e800, 0x4498e800, 0x44a58000, 0x44a58000, 0x44a58000, 0x44a58000, 0x44a58000, 0x44a58000, + 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x42fc0000, 0x42fc0000, + 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, + 0x43630000, 0x43630000, 0x43a42000, 0x43a40000, 0x43a42000, 0x43a40000, 0x43a42000, 0x43a42000, + 0x4480a000, 0x4480a000, 0x4480a000, 0x4480a000, 0x4480a000, 0x4480a000, 0x448d4800, 0x448d4000, + 0x448d4800, 0x448d4000, 0x448d4800, 0x448d4800, 0x4499e000, 0x4499e000, 0x4499e000, 0x4499e000, + 0x4499e000, 0x4499e000, 0x44a68000, 0x44a68000, 0x44a68000, 0x44a68000, 0x44a68000, 0x44a68000, +}; +constexpr std::array Names1D{"image_sample_d 1D", "image_sample_d_g16 1D", "image_sample_cd_g16 1D", "image_sample_d a16 1D", "image_sample_cd_g16 a16 1D"}; +constexpr std::array Masked1D{false, false, false, false, false}; +constexpr std::array Expected1D{ + 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x40000000, 0x40000000, 0x40100000, + 0x40000000, 0x40100000, 0x40400000, 0x40400000, 0x40500000, 0x40400000, 0x40500000, 0x40800000, + 0x40800000, 0x40880000, 0x40800000, 0x40880000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, + 0x40a00000, 0x40c80000, 0x40c80000, 0x40c00000, 0x40c80000, 0x40c00000, 0x40e00000, 0x40e00000, + 0x40e00000, 0x40e00000, 0x40e00000, 0x41000000, 0x41000000, 0x41000000, 0x41000000, 0x41000000, + 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41200000, 0x41200000, 0x41200000, + 0x41200000, 0x41200000, 0x41340000, 0x41340000, 0x41300000, 0x41340000, 0x41300000, 0x41400000, + 0x41400000, 0x41400000, 0x41400000, 0x41400000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, + 0x41500000, 0x41600000, 0x41600000, 0x41600000, 0x41600000, 0x41600000, 0x41700000, 0x41700000, + 0x41700000, 0x41700000, 0x41700000, 0x41820000, 0x41820000, 0x41800000, 0x41820000, 0x41800000, + 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41920000, 0x41920000, 0x41900000, + 0x41920000, 0x41900000, 0x41980000, 0x41980000, 0x41980000, 0x41980000, 0x41980000, 0x41a00000, + 0x41a00000, 0x41a00000, 0x41a00000, 0x41a00000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41a80000, + 0x41a80000, 0x41b00000, 0x41b00000, 0x41b00000, 0x41b00000, 0x41b00000, 0x41ba0000, 0x41ba0000, + 0x41b80000, 0x41ba0000, 0x41b80000, 0x41c00000, 0x41c00000, 0x41c00000, 0x41c00000, 0x41c00000, + 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41d00000, 0x41d00000, 0x41d00000, + 0x41d00000, 0x41d00000, 0x41d80000, 0x41d80000, 0x41d80000, 0x41d80000, 0x41d80000, 0x41e20000, + 0x41e20000, 0x41e00000, 0x41e20000, 0x41e00000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41e80000, + 0x41e80000, 0x41f20000, 0x41f20000, 0x41f00000, 0x41f20000, 0x41f00000, 0x41f80000, 0x41f80000, + 0x41f80000, 0x41f80000, 0x41f80000, 0x42000000, 0x42000000, 0x42000000, 0x42000000, 0x42000000, +}; + +std::array TextureDescriptor(std::uint32_t type, std::uint32_t height, std::uint32_t depth) { + const auto address = static_cast(reinterpret_cast(Texels.data())); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32Float << 20u) | (((Width - 1u) & 3u) << 30u), + ((Width - 1u) >> 2u) | ((height - 1u) << 14u) | (1u << 31u), + 0xfacu | (type << 28u), + depth - 1u, 0u, 0u, 0u, + }; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::uint32_t SourceRow(std::uint32_t tid) { + return tid < Threads ? tid : (tid - Threads + Threads / 2u) % Threads; +} + +template +std::array MovedStores(const std::array& code, std::uint32_t threads) { + auto moved = code; + for (auto& word : moved) { + if ((word & 0xfffff000u) == 0xe0701000u && (word & 0xfffu) >= Threads * Inputs * 4u) word += (threads - Threads) * Inputs * 4u; + } + return moved; +} + +template +void Run(AgcDriver::VulkanDevice& device, const std::array& source, std::uint32_t threads, std::uint32_t type, std::uint32_t height, std::uint32_t depth, + const std::array& names, const std::array& masked, const std::array& expected) { + alignas(256) static std::array code; + code = MovedStores(source, threads); + for (std::uint32_t tid = 0; tid < threads; ++tid) { + std::copy_n(Input.begin() + SourceRow(tid) * Inputs, Inputs, Buffer.begin() + tid * Inputs); + } + std::fill(Buffer.begin() + threads * Inputs, Buffer.end(), 0xdeadbeefu); + for (std::uint32_t z = 0; z < depth; ++z) { + for (std::uint32_t y = 0; y < height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + Texels[(z * height + y) * Width + x] = static_cast(x + 100u * y + 1000u * z); + } + } + } + const auto address = reinterpret_cast(Buffer.data()); + const std::array buffer{static_cast(address), static_cast((address >> 32u) & 0xffffu), static_cast(Buffer.size() * 4u), 0x01016facu}; + const auto texture = TextureDescriptor(type, height, depth); + const std::array sampler{0x92u, (4u * 256u) << 12u, (1u << 22u) | (1u << 26u), 0u}; + std::vector userData(16, 0u); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 12); + const std::span span(code); + const std::array memory{{{reinterpret_cast(span.data()), std::as_bytes(span)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(span.data()), span, 0, {}}, + {threads, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(span.data())); + device.WaitIdle(); + for (std::uint32_t tid = 0; tid < threads; ++tid) { + for (std::uint32_t k = 0; k < Cases; ++k) { + if (masked[k] && (device.Target().subgroupSize < Threads || tid >= Threads)) continue; + const auto actual = Buffer[threads * Inputs + tid * Results + k]; + const auto wanted = expected[SourceRow(tid) * Cases + k]; + const bool close = std::fabs(std::bit_cast(actual) - std::bit_cast(wanted)) <= 1.0f / 64.0f; + Require(close, std::string(names[k]) + ": wave" + std::to_string(threads) + " lane " + std::to_string(tid) + " is " + Hex(actual) + ", expected " + Hex(wanted)); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < Threads) { + std::printf("EXEC-masked cases skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + } + std::vector waves{Threads}; + if (device->Target().subgroupSize >= Threads) waves.push_back(Wave64Threads); + for (const auto threads : waves) { + Run(*device, Code2D, threads, 9u, Height, 1u, Names2D, Masked2D, Expected2D); + Run(*device, Code3D, threads, 10u, Height, Depth, Names3D, Masked3D, Expected3D); + Run(*device, Code1D, threads, 8u, 1u, 1u, Names1D, Masked1D, Expected1D); + } + std::puts("image sample derivatives tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageTexelStatus.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTexelStatus.cpp new file mode 100644 index 000000000..d2ff375fe --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTexelStatus.cpp @@ -0,0 +1,349 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t StorageResults = 4; +constexpr std::uint32_t SampleResults = 16; +constexpr std::uint32_t StorageWidth = 32; +constexpr std::uint32_t StorageHeight = 8; +constexpr std::uint32_t SampleWidth = 64; +constexpr std::uint32_t SampleHeight = 2; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Format32Float = 22; +constexpr std::uint32_t Type2D = 9; +alignas(256) std::array Buffer{}; +alignas(4096) std::array StorageTexels{}; +alignas(4096) std::array SampleTexels{}; + +alignas(256) constexpr std::array StorageCode{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe93030f, 0xbe92030e, 0xbe91030d, + 0xbe90030c, 0xbe8f030b, 0xbe8e030a, 0xbe8d0309, 0xbe8c0308, 0xbe8b0307, 0xbe8a0306, 0xbe890305, + 0xbe880304, 0x7e0a02ff, 0x5a7a0005, 0x7e0e02ff, 0x5a7a0007, 0x7e100300, 0x7e120280, 0xf0210108, + 0x00020408, 0xbbfd0000, 0x7e140280, 0xf0442108, 0x00020a08, 0xbf8c3f70, 0x7e120281, 0xf0230108, + 0x00020608, 0xbbfd0000, 0x7e160280, 0xf0442108, 0x00020b08, 0xbf8c3f70, 0xe0701200, 0x80000a03, + 0xe0701204, 0x80000b03, 0xe0701208, 0x80000503, 0xe070120c, 0x80000703, 0xbf810000, +}; + +alignas(256) constexpr std::array SampleCode{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe93030f, 0xbe92030e, 0xbe91030d, + 0xbe90030c, 0xbe8f030b, 0xbe8e030a, 0xbe8d0309, 0xbe8c0308, 0xbe8b0307, 0xbe8a0306, 0xbe890305, + 0xbe880304, 0x7e1402ff, 0x5a7a000a, 0x7e1602ff, 0x5a7a000b, 0x7e1802ff, 0x5a7a000c, 0x7e1a02ff, + 0x5a7a000d, 0x7e1c02ff, 0x5a7a000e, 0x7e1e02ff, 0x5a7a000f, 0x7e2002ff, 0x5a7a0010, 0x7e2202ff, + 0x5a7a0011, 0x7e2402ff, 0x5a7a0012, 0x7e2602ff, 0x5a7a0013, 0x7e2802ff, 0x5a7a0014, 0x7e2a02ff, + 0x5a7a0015, 0x7e2c02ff, 0x5a7a0016, 0x7e2e02ff, 0x5a7a0017, 0x7e3002ff, 0x5a7a0018, 0x7e3202ff, + 0x5a7a0019, 0xf0810108, 0x00820a04, 0xf1010108, 0x00820c04, 0x7e540280, 0xf03b0308, 0x0002112a, + 0xf0910108, 0x00821404, 0x34500081, 0x7e520281, 0xf0030108, 0x00021628, 0x7e300280, 0xf0010108, + 0x80021828, 0xbf8c3f70, 0xe0701200, 0x80000a03, 0xe0701204, 0x80000b03, 0xe0701208, 0x80000c03, + 0xe070120c, 0x80000d03, 0xe0701210, 0x80000e03, 0xe0701214, 0x80000f03, 0xe0701218, 0x80001003, + 0xe070121c, 0x80001103, 0xe0701220, 0x80001203, 0xe0701224, 0x80001303, 0xe0701228, 0x80001403, + 0xe070122c, 0x80001503, 0xe0701230, 0x80001603, 0xe0701234, 0x80001703, 0xe0701238, 0x80001803, + 0xe070123c, 0x80001903, 0xbf810000, +}; + +alignas(256) constexpr std::array, 5> RefusedCode{{ + {0xf0820108u, 0x00820a04u, 0xbf810000u}, + {0xf1020108u, 0x00820a04u, 0xbf810000u}, + {0xf1820308u, 0x00820a04u, 0xbf810000u}, + {0xf0452108u, 0x00020a08u, 0xbf810000u}, + {0xf0010108u, 0x0002ff08u, 0xbf810000u}, +}}; + +constexpr std::array, Threads> StorageInputs{{ + {0x00000000u, 0x00000000u, 0xb3c4904au}, + {0x00000000u, 0x00000005u, 0x00000000u}, + {0x00000005u, 0x00000000u, 0x7be90291u}, + {0xffffffffu, 0x00000001u, 0xffffffffu}, + {0x00000001u, 0xffffffffu, 0x9e5c77e7u}, + {0x7fffffffu, 0x00000001u, 0x7fffffffu}, + {0x80000000u, 0x80000000u, 0x4285a9afu}, + {0xffffffffu, 0xffffffffu, 0xffffffffu}, + {0x7df64fcfu, 0xf6453aa7u, 0x92191efbu}, + {0xd9895985u, 0x68518d38u, 0xd9895985u}, + {0xcf273511u, 0x138c146fu, 0x57100d0au}, + {0x2bb1b013u, 0xd6e649d7u, 0x2bb1b013u}, + {0xa95d2364u, 0x3976bad3u, 0x5bc5a782u}, + {0x4bcdebb2u, 0xa6480378u, 0x4bcdebb2u}, + {0xcbad05bau, 0xa433bde1u, 0xd1c28862u}, + {0x8f217978u, 0x860b0f2au, 0x8f217978u}, + {0x871aba07u, 0xb551a6ccu, 0x661dc63eu}, + {0x9a7f8d63u, 0x6e7133fbu, 0x9a7f8d63u}, + {0xce07ea31u, 0xe31b5068u, 0x9da9bf21u}, + {0x92b8b3e9u, 0xe399ac38u, 0x92b8b3e9u}, + {0x1b9abec0u, 0x1cbb3115u, 0x8d8433aeu}, + {0x613db786u, 0x7f5a7218u, 0x613db786u}, + {0x2084fc98u, 0xa69ea4ecu, 0x74859b95u}, + {0xb5b4acfcu, 0x1191b75bu, 0xb5b4acfcu}, + {0x8bec71e7u, 0xe0f3debeu, 0x77f93ffbu}, + {0x9f1254f3u, 0x48de1c56u, 0x9f1254f3u}, + {0xef1e9ff4u, 0xc7a1d8dbu, 0xb8ab9e91u}, + {0x9bcffc0fu, 0xc7bb053fu, 0x9bcffc0fu}, + {0x3c796f72u, 0xb5943014u, 0xf08eac42u}, + {0x436b6cdeu, 0x8333e5a8u, 0x436b6cdeu}, + {0x4457feb1u, 0xeaf89aefu, 0xfdcec3b5u}, + {0xdb26bcebu, 0x5d63880eu, 0xdb26bcebu}, +}}; + +constexpr std::array, Threads> StorageExpected{{ + {0x00000000u, 0xb3c4904au, 0x5a7a0005u, 0x5a7a0007u}, + {0x00000000u, 0x00000000u, 0x5a7a0005u, 0x5a7a0007u}, + {0x00000005u, 0x7be90291u, 0x5a7a0005u, 0x5a7a0007u}, + {0xffffffffu, 0xffffffffu, 0x5a7a0005u, 0x5a7a0007u}, + {0x00000001u, 0x9e5c77e7u, 0x5a7a0005u, 0x5a7a0007u}, + {0x7fffffffu, 0x7fffffffu, 0x5a7a0005u, 0x5a7a0007u}, + {0x80000000u, 0x4285a9afu, 0x5a7a0005u, 0x5a7a0007u}, + {0xffffffffu, 0xffffffffu, 0x5a7a0005u, 0x5a7a0007u}, + {0x7df64fcfu, 0x92191efbu, 0x5a7a0005u, 0x5a7a0007u}, + {0xd9895985u, 0xd9895985u, 0x5a7a0005u, 0x5a7a0007u}, + {0xcf273511u, 0x57100d0au, 0x5a7a0005u, 0x5a7a0007u}, + {0x2bb1b013u, 0x2bb1b013u, 0x5a7a0005u, 0x5a7a0007u}, + {0xa95d2364u, 0x5bc5a782u, 0x5a7a0005u, 0x5a7a0007u}, + {0x4bcdebb2u, 0x4bcdebb2u, 0x5a7a0005u, 0x5a7a0007u}, + {0xcbad05bau, 0xd1c28862u, 0x5a7a0005u, 0x5a7a0007u}, + {0x8f217978u, 0x8f217978u, 0x5a7a0005u, 0x5a7a0007u}, + {0x871aba07u, 0x661dc63eu, 0x5a7a0005u, 0x5a7a0007u}, + {0x9a7f8d63u, 0x9a7f8d63u, 0x5a7a0005u, 0x5a7a0007u}, + {0xce07ea31u, 0x9da9bf21u, 0x5a7a0005u, 0x5a7a0007u}, + {0x92b8b3e9u, 0x92b8b3e9u, 0x5a7a0005u, 0x5a7a0007u}, + {0x1b9abec0u, 0x8d8433aeu, 0x5a7a0005u, 0x5a7a0007u}, + {0x613db786u, 0x613db786u, 0x5a7a0005u, 0x5a7a0007u}, + {0x2084fc98u, 0x74859b95u, 0x5a7a0005u, 0x5a7a0007u}, + {0xb5b4acfcu, 0xb5b4acfcu, 0x5a7a0005u, 0x5a7a0007u}, + {0x8bec71e7u, 0x77f93ffbu, 0x5a7a0005u, 0x5a7a0007u}, + {0x9f1254f3u, 0x9f1254f3u, 0x5a7a0005u, 0x5a7a0007u}, + {0xef1e9ff4u, 0xb8ab9e91u, 0x5a7a0005u, 0x5a7a0007u}, + {0x9bcffc0fu, 0x9bcffc0fu, 0x5a7a0005u, 0x5a7a0007u}, + {0x3c796f72u, 0xf08eac42u, 0x5a7a0005u, 0x5a7a0007u}, + {0x436b6cdeu, 0x436b6cdeu, 0x5a7a0005u, 0x5a7a0007u}, + {0x4457feb1u, 0xfdcec3b5u, 0x5a7a0005u, 0x5a7a0007u}, + {0xdb26bcebu, 0xdb26bcebu, 0x5a7a0005u, 0x5a7a0007u}, +}}; + +constexpr std::array, Threads> SampleInputs{{ + {0x3c000000u, 0x3e800000u, 0x00000000u}, + {0x3d200000u, 0x3e800000u, 0x00000000u}, + {0x3d900000u, 0x3e800000u, 0x00000000u}, + {0x3dd00000u, 0x3e800000u, 0x00000000u}, + {0x3e080000u, 0x3e800000u, 0x00000000u}, + {0x3e280000u, 0x3e800000u, 0x00000000u}, + {0x3e480000u, 0x3e800000u, 0x00000000u}, + {0x3e680000u, 0x3e800000u, 0x00000000u}, + {0x3e840000u, 0x3e800000u, 0x00000000u}, + {0x3e940000u, 0x3e800000u, 0x00000000u}, + {0x3ea40000u, 0x3e800000u, 0x00000000u}, + {0x3eb40000u, 0x3e800000u, 0x00000000u}, + {0x3ec40000u, 0x3e800000u, 0x00000000u}, + {0x3ed40000u, 0x3e800000u, 0x00000000u}, + {0x3ee40000u, 0x3e800000u, 0x00000000u}, + {0x3ef40000u, 0x3e800000u, 0x00000000u}, + {0x3f020000u, 0x3e800000u, 0x00000000u}, + {0x3f0a0000u, 0x3e800000u, 0x00000000u}, + {0x3f120000u, 0x3e800000u, 0x00000000u}, + {0x3f1a0000u, 0x3e800000u, 0x00000000u}, + {0x3f220000u, 0x3e800000u, 0x00000000u}, + {0x3f2a0000u, 0x3e800000u, 0x00000000u}, + {0x3f320000u, 0x3e800000u, 0x00000000u}, + {0x3f3a0000u, 0x3e800000u, 0x00000000u}, + {0x3f420000u, 0x3e800000u, 0x00000000u}, + {0x3f4a0000u, 0x3e800000u, 0x00000000u}, + {0x3f520000u, 0x3e800000u, 0x00000000u}, + {0x3f5a0000u, 0x3e800000u, 0x00000000u}, + {0x3f620000u, 0x3e800000u, 0x00000000u}, + {0x3f6a0000u, 0x3e800000u, 0x00000000u}, + {0x3f720000u, 0x3e800000u, 0x00000000u}, + {0x3f7a0000u, 0x3e800000u, 0x00000000u}, +}}; + +constexpr std::array, Threads> SampleExpected{{ + {0x00000000u, 0x5a7a000bu, 0x42c80000u, 0x42ca0000u, 0x3f800000u, 0x00000000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x00000000u, 0x5a7a0015u, 0x42c80000u, 0x5a7a0017u, 0x00005640u, 0x5a7a0019u}, + {0x40000000u, 0x5a7a000bu, 0x42cc0000u, 0x42ce0000u, 0x40400000u, 0x40000000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x40000000u, 0x5a7a0015u, 0x42cc0000u, 0x5a7a0017u, 0x00005660u, 0x5a7a0019u}, + {0x40800000u, 0x5a7a000bu, 0x42d00000u, 0x42d20000u, 0x40a00000u, 0x40800000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x40800000u, 0x5a7a0015u, 0x42d00000u, 0x5a7a0017u, 0x00005680u, 0x5a7a0019u}, + {0x40c00000u, 0x5a7a000bu, 0x42d40000u, 0x42d60000u, 0x40e00000u, 0x40c00000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x40c00000u, 0x5a7a0015u, 0x42d40000u, 0x5a7a0017u, 0x000056a0u, 0x5a7a0019u}, + {0x41000000u, 0x5a7a000bu, 0x42d80000u, 0x42da0000u, 0x41100000u, 0x41000000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41000000u, 0x5a7a0015u, 0x42d80000u, 0x5a7a0017u, 0x000056c0u, 0x5a7a0019u}, + {0x41200000u, 0x5a7a000bu, 0x42dc0000u, 0x42de0000u, 0x41300000u, 0x41200000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41200000u, 0x5a7a0015u, 0x42dc0000u, 0x5a7a0017u, 0x000056e0u, 0x5a7a0019u}, + {0x41400000u, 0x5a7a000bu, 0x42e00000u, 0x42e20000u, 0x41500000u, 0x41400000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41400000u, 0x5a7a0015u, 0x42e00000u, 0x5a7a0017u, 0x00005700u, 0x5a7a0019u}, + {0x41600000u, 0x5a7a000bu, 0x42e40000u, 0x42e60000u, 0x41700000u, 0x41600000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41600000u, 0x5a7a0015u, 0x42e40000u, 0x5a7a0017u, 0x00005720u, 0x5a7a0019u}, + {0x41800000u, 0x5a7a000bu, 0x42e80000u, 0x42ea0000u, 0x41880000u, 0x41800000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41800000u, 0x5a7a0015u, 0x42e80000u, 0x5a7a0017u, 0x00005740u, 0x5a7a0019u}, + {0x41900000u, 0x5a7a000bu, 0x42ec0000u, 0x42ee0000u, 0x41980000u, 0x41900000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41900000u, 0x5a7a0015u, 0x42ec0000u, 0x5a7a0017u, 0x00005760u, 0x5a7a0019u}, + {0x41a00000u, 0x5a7a000bu, 0x42f00000u, 0x42f20000u, 0x41a80000u, 0x41a00000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41a00000u, 0x5a7a0015u, 0x42f00000u, 0x5a7a0017u, 0x00005780u, 0x5a7a0019u}, + {0x41b00000u, 0x5a7a000bu, 0x42f40000u, 0x42f60000u, 0x41b80000u, 0x41b00000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41b00000u, 0x5a7a0015u, 0x42f40000u, 0x5a7a0017u, 0x000057a0u, 0x5a7a0019u}, + {0x41c00000u, 0x5a7a000bu, 0x42f80000u, 0x42fa0000u, 0x41c80000u, 0x41c00000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41c00000u, 0x5a7a0015u, 0x42f80000u, 0x5a7a0017u, 0x000057c0u, 0x5a7a0019u}, + {0x41d00000u, 0x5a7a000bu, 0x42fc0000u, 0x42fe0000u, 0x41d80000u, 0x41d00000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41d00000u, 0x5a7a0015u, 0x42fc0000u, 0x5a7a0017u, 0x000057e0u, 0x5a7a0019u}, + {0x41e00000u, 0x5a7a000bu, 0x43000000u, 0x43010000u, 0x41e80000u, 0x41e00000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41e00000u, 0x5a7a0015u, 0x43000000u, 0x5a7a0017u, 0x00005800u, 0x5a7a0019u}, + {0x41f00000u, 0x5a7a000bu, 0x43020000u, 0x43030000u, 0x41f80000u, 0x41f00000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x41f00000u, 0x5a7a0015u, 0x43020000u, 0x5a7a0017u, 0x00005810u, 0x5a7a0019u}, + {0x42000000u, 0x5a7a000bu, 0x43040000u, 0x43050000u, 0x42040000u, 0x42000000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42000000u, 0x5a7a0015u, 0x43040000u, 0x5a7a0017u, 0x00005820u, 0x5a7a0019u}, + {0x42080000u, 0x5a7a000bu, 0x43060000u, 0x43070000u, 0x420c0000u, 0x42080000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42080000u, 0x5a7a0015u, 0x43060000u, 0x5a7a0017u, 0x00005830u, 0x5a7a0019u}, + {0x42100000u, 0x5a7a000bu, 0x43080000u, 0x43090000u, 0x42140000u, 0x42100000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42100000u, 0x5a7a0015u, 0x43080000u, 0x5a7a0017u, 0x00005840u, 0x5a7a0019u}, + {0x42180000u, 0x5a7a000bu, 0x430a0000u, 0x430b0000u, 0x421c0000u, 0x42180000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42180000u, 0x5a7a0015u, 0x430a0000u, 0x5a7a0017u, 0x00005850u, 0x5a7a0019u}, + {0x42200000u, 0x5a7a000bu, 0x430c0000u, 0x430d0000u, 0x42240000u, 0x42200000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42200000u, 0x5a7a0015u, 0x430c0000u, 0x5a7a0017u, 0x00005860u, 0x5a7a0019u}, + {0x42280000u, 0x5a7a000bu, 0x430e0000u, 0x430f0000u, 0x422c0000u, 0x42280000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42280000u, 0x5a7a0015u, 0x430e0000u, 0x5a7a0017u, 0x00005870u, 0x5a7a0019u}, + {0x42300000u, 0x5a7a000bu, 0x43100000u, 0x43110000u, 0x42340000u, 0x42300000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42300000u, 0x5a7a0015u, 0x43100000u, 0x5a7a0017u, 0x00005880u, 0x5a7a0019u}, + {0x42380000u, 0x5a7a000bu, 0x43120000u, 0x43130000u, 0x423c0000u, 0x42380000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42380000u, 0x5a7a0015u, 0x43120000u, 0x5a7a0017u, 0x00005890u, 0x5a7a0019u}, + {0x42400000u, 0x5a7a000bu, 0x43140000u, 0x43150000u, 0x42440000u, 0x42400000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42400000u, 0x5a7a0015u, 0x43140000u, 0x5a7a0017u, 0x000058a0u, 0x5a7a0019u}, + {0x42480000u, 0x5a7a000bu, 0x43160000u, 0x43170000u, 0x424c0000u, 0x42480000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42480000u, 0x5a7a0015u, 0x43160000u, 0x5a7a0017u, 0x000058b0u, 0x5a7a0019u}, + {0x42500000u, 0x5a7a000bu, 0x43180000u, 0x43190000u, 0x42540000u, 0x42500000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42500000u, 0x5a7a0015u, 0x43180000u, 0x5a7a0017u, 0x000058c0u, 0x5a7a0019u}, + {0x42580000u, 0x5a7a000bu, 0x431a0000u, 0x431b0000u, 0x425c0000u, 0x42580000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42580000u, 0x5a7a0015u, 0x431a0000u, 0x5a7a0017u, 0x000058d0u, 0x5a7a0019u}, + {0x42600000u, 0x5a7a000bu, 0x431c0000u, 0x431d0000u, 0x42640000u, 0x42600000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42600000u, 0x5a7a0015u, 0x431c0000u, 0x5a7a0017u, 0x000058e0u, 0x5a7a0019u}, + {0x42680000u, 0x5a7a000bu, 0x431e0000u, 0x431f0000u, 0x426c0000u, 0x42680000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42680000u, 0x5a7a0015u, 0x431e0000u, 0x5a7a0017u, 0x000058f0u, 0x5a7a0019u}, + {0x42700000u, 0x5a7a000bu, 0x43200000u, 0x43210000u, 0x42740000u, 0x42700000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42700000u, 0x5a7a0015u, 0x43200000u, 0x5a7a0017u, 0x00005900u, 0x5a7a0019u}, + {0x42780000u, 0x5a7a000bu, 0x43220000u, 0x43230000u, 0x427c0000u, 0x42780000u, 0x5a7a0010u, 0x00000040u, 0x00000002u, 0x5a7a0013u, 0x42780000u, 0x5a7a0015u, 0x43220000u, 0x5a7a0017u, 0x00005910u, 0x5a7a0019u}, +}}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::array TextureDescriptor(const void* data, std::uint32_t format, std::uint32_t width, std::uint32_t height) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((width - 1u) & 3u) << 30u), + ((width - 1u) >> 2u) | ((height - 1u) << 14u), + 0xfacu | (Type2D << 28u), + 0u, 0u, 0u, 0u, + }; +} + +std::array SamplerDescriptor() { + return {0u, 0xfffu << 12u, 1u << 26u, 0u}; +} + +void Dispatch(AgcDriver::VulkanDevice& device, std::span code, const std::vector& userData) { + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void FillBuffer(const std::array, Threads>& inputs) { + Buffer.fill(0xdeadbeefu); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::copy(inputs[tid].begin(), inputs[tid].end(), Buffer.begin() + tid * Inputs); + Buffer[tid * Inputs + 3u] = 0u; + } +} + +void RunStorage(AgcDriver::VulkanDevice& device) { + FillBuffer(StorageInputs); + StorageTexels.fill(0xdeadbeefu); + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); + const auto texture = TextureDescriptor(StorageTexels.data(), Format32UInt, StorageWidth, StorageHeight); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + Dispatch(device, StorageCode, userData); +} + +void RunSample(AgcDriver::VulkanDevice& device) { + FillBuffer(SampleInputs); + const auto mips = AgcDriver::Graphics::ComputeMipLayout(AgcDriver::Graphics::TextureTileMode::kLinear, Format32Float, SampleWidth, SampleHeight, 1); + Require(AgcDriver::Graphics::ComputeSurfaceSize(mips, 1) <= SampleTexels.size() * 4u, "image texel status: the texture does not fit the texel storage"); + SampleTexels.fill(0u); + for (std::uint32_t y = 0; y < SampleHeight; ++y) { + for (std::uint32_t x = 0; x < SampleWidth; ++x) { + SampleTexels[(mips[0].tiledOffset + static_cast(y) * mips[0].pitchBytes) / 4u + x] = std::bit_cast(static_cast(x + 100u * y)); + } + } + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); + const auto texture = TextureDescriptor(SampleTexels.data(), Format32Float, SampleWidth, SampleHeight); + const auto sampler = SamplerDescriptor(); + std::copy(buffer.begin(), buffer.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 12); + Dispatch(device, SampleCode, userData); +} + +void CheckRefused(AgcDriver::VulkanDevice& device) { + constexpr std::array names{"image_sample lwe", "image_gather4 lwe", "image_get_lod lwe", "image_atomic_add tfe", "image_load tfe status past v255"}; + std::vector userData(16, 0u); + for (std::uint32_t i = 0; i < RefusedCode.size(); ++i) { + const std::span code(RefusedCode[i]); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + bool refused = false; + try { + (void)ShaderRecompiler::Recompile(request); + } catch (const std::exception& error) { + refused = std::string(error.what()).find("MIMG") != std::string::npos; + } + Require(refused, std::string(names[i]) + " was not refused"); + } +} + +void Expect(const char* name, std::uint32_t tid, std::uint32_t actual, std::uint32_t expected) { + char message[160]; + std::snprintf(message, sizeof(message), "%s: thread %u is 0x%08x, expected 0x%08x", name, tid, actual, expected); + Require(actual == expected, message); +} + +void CheckStorage() { + constexpr std::array names{"image_store tfe", "image_store tfe lwe", "image_store tfe data status", "image_store tfe lwe data status"}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t j = 0; j < StorageResults; ++j) { + Expect(names[j], tid, Buffer[Threads * Inputs + tid * Results + j], StorageExpected[tid][j]); + } + } +} + +void CheckSample() { + constexpr std::array names{"image_sample tfe", "image_sample tfe status", "image_gather4 tfe x", "image_gather4 tfe y", "image_gather4 tfe z", "image_gather4 tfe w", "image_gather4 tfe status", "image_get_resinfo tfe lwe width", "image_get_resinfo tfe lwe height", "image_get_resinfo tfe lwe status", "image_sample_l tfe", "image_sample_l tfe status", "image_load tfe lwe", "image_load tfe lwe status", "image_load tfe d16", "image_load tfe d16 status"}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t j = 0; j < SampleResults; ++j) { + Expect(names[j], tid, Buffer[Threads * Inputs + tid * Results + j], SampleExpected[tid][j]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + RunStorage(*device); + CheckStorage(); + RunSample(*device); + CheckSample(); + CheckRefused(*device); + std::puts("image texel status tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LdsAtomics64.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LdsAtomics64.cpp new file mode 100644 index 000000000..5d9e2bc7e --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LdsAtomics64.cpp @@ -0,0 +1,240 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 8; +constexpr std::uint32_t Results = 128; +constexpr std::uint32_t Operations = 35; +constexpr std::uint32_t ReturningOperations = 18; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020085, 0x34060089, 0x34040083, 0x7ef80280, 0x7efa0280, 0x7efc0280, 0xe0301000, 0x80000401, + 0xe0301004, 0x80000501, 0xe0301008, 0x80000601, 0xe030100c, 0x80000701, 0xe0301010, 0x80000801, + 0xe0301014, 0x80000901, 0xbf8c3f70, 0xd9340000, 0x00000402, 0xd9340100, 0x00000402, 0xd9340200, + 0x00000402, 0xd9340300, 0x00000402, 0xd9340400, 0x00000402, 0xd9340500, 0x00000402, 0xd9340600, + 0x00000402, 0xd9340700, 0x00000402, 0xd9340800, 0x00000402, 0xd9340900, 0x00000402, 0xd9340a00, + 0x00000402, 0xd9340b00, 0x00000402, 0xd9340c00, 0x00000402, 0xd9340d00, 0x00000402, 0xd9340e00, + 0x00000402, 0xd9340f00, 0x00000402, 0xd9341000, 0x00000402, 0xd9341100, 0x00000402, 0xd9341200, + 0x00000402, 0xd9341300, 0x00000402, 0xd9341400, 0x00000402, 0xd9341500, 0x00000402, 0xd9341600, + 0x00000402, 0xd9341700, 0x00000402, 0xd9341800, 0x00000402, 0xd9341900, 0x00000402, 0xd9341a00, + 0x00000402, 0xd9341b00, 0x00000402, 0xd9341c00, 0x00000402, 0xd9341d00, 0x00000402, 0xd9341e00, + 0x00000402, 0xd9341f00, 0x00000402, 0xd9342000, 0x00000402, 0xd9342100, 0x00000402, 0xd9342200, + 0x00000402, 0xd9342300, 0x00007c7e, 0xd9342308, 0x00007c7e, 0xbf8cc07f, 0xbf8a0000, 0xd9000000, + 0x00000602, 0xd9040100, 0x00000602, 0xd9080200, 0x00000602, 0xd90c0300, 0x00000602, 0xd9100400, + 0x00000602, 0xd9140500, 0x00000602, 0xd9180600, 0x00000602, 0xd91c0700, 0x00000602, 0xd9200800, + 0x00000602, 0xd9240900, 0x00000602, 0xd9280a00, 0x00000602, 0xd92c0b00, 0x00000602, 0xd9300c00, + 0x00080602, 0xd9400d00, 0x00080602, 0xd9440e00, 0x00080602, 0xd9480f00, 0x00000602, 0xd94c1000, + 0x00000602, 0xd9801100, 0x0a000602, 0xd9841200, 0x0c000602, 0xd9881300, 0x0e000602, 0xd98c1400, + 0x10000602, 0xd9901500, 0x12000602, 0xd9941600, 0x14000602, 0xd9981700, 0x16000602, 0xd99c1800, + 0x18000602, 0xd9a01900, 0x1a000602, 0xd9a41a00, 0x1c000602, 0xd9a81b00, 0x1e000602, 0xd9ac1c00, + 0x20000602, 0xd9b01d00, 0x22080602, 0xd9b41e00, 0x24000602, 0xd9c01f00, 0x26080602, 0xd9c42000, + 0x28080602, 0xd9c82100, 0x2a000602, 0xd9cc2200, 0x2c000602, 0xd9802300, 0x2e00067e, 0xd9a02308, + 0x3000087e, 0xbf8cc07f, 0xbf8a0000, 0xd9d80000, 0x32000002, 0xd9d80100, 0x34000002, 0xd9d80200, + 0x36000002, 0xd9d80300, 0x38000002, 0xd9d80400, 0x3a000002, 0xd9d80500, 0x3c000002, 0xd9d80600, + 0x3e000002, 0xd9d80700, 0x40000002, 0xd9d80800, 0x42000002, 0xd9d80900, 0x44000002, 0xd9d80a00, + 0x46000002, 0xd9d80b00, 0x48000002, 0xd9d80c00, 0x4a000002, 0xd9d80d00, 0x4c000002, 0xd9d80e00, + 0x4e000002, 0xd9d80f00, 0x50000002, 0xd9d81000, 0x52000002, 0xd9d81100, 0x54000002, 0xd9d81200, + 0x56000002, 0xd9d81300, 0x58000002, 0xd9d81400, 0x5a000002, 0xd9d81500, 0x5c000002, 0xd9d81600, + 0x5e000002, 0xd9d81700, 0x60000002, 0xd9d81800, 0x62000002, 0xd9d81900, 0x64000002, 0xd9d81a00, + 0x66000002, 0xd9d81b00, 0x68000002, 0xd9d81c00, 0x6a000002, 0xd9d81d00, 0x6c000002, 0xd9d81e00, + 0x6e000002, 0xd9d81f00, 0x70000002, 0xd9d82000, 0x72000002, 0xd9d82100, 0x74000002, 0xd9d82200, + 0x76000002, 0xd9d82300, 0x7800007e, 0xd9d82308, 0x7a00007e, 0xbf8cc07f, 0xe0701000, 0x80013203, + 0xe0701004, 0x80013303, 0xe0701008, 0x80013403, 0xe070100c, 0x80013503, 0xe0701010, 0x80013603, + 0xe0701014, 0x80013703, 0xe0701018, 0x80013803, 0xe070101c, 0x80013903, 0xe0701020, 0x80013a03, + 0xe0701024, 0x80013b03, 0xe0701028, 0x80013c03, 0xe070102c, 0x80013d03, 0xe0701030, 0x80013e03, + 0xe0701034, 0x80013f03, 0xe0701038, 0x80014003, 0xe070103c, 0x80014103, 0xe0701040, 0x80014203, + 0xe0701044, 0x80014303, 0xe0701048, 0x80014403, 0xe070104c, 0x80014503, 0xe0701050, 0x80014603, + 0xe0701054, 0x80014703, 0xe0701058, 0x80014803, 0xe070105c, 0x80014903, 0xe0701060, 0x80014a03, + 0xe0701064, 0x80014b03, 0xe0701068, 0x80014c03, 0xe070106c, 0x80014d03, 0xe0701070, 0x80014e03, + 0xe0701074, 0x80014f03, 0xe0701078, 0x80015003, 0xe070107c, 0x80015103, 0xe0701080, 0x80015203, + 0xe0701084, 0x80015303, 0xe0701088, 0x80015403, 0xe070108c, 0x80015503, 0xe0701090, 0x80015603, + 0xe0701094, 0x80015703, 0xe0701098, 0x80015803, 0xe070109c, 0x80015903, 0xe07010a0, 0x80015a03, + 0xe07010a4, 0x80015b03, 0xe07010a8, 0x80015c03, 0xe07010ac, 0x80015d03, 0xe07010b0, 0x80015e03, + 0xe07010b4, 0x80015f03, 0xe07010b8, 0x80016003, 0xe07010bc, 0x80016103, 0xe07010c0, 0x80016203, + 0xe07010c4, 0x80016303, 0xe07010c8, 0x80016403, 0xe07010cc, 0x80016503, 0xe07010d0, 0x80016603, + 0xe07010d4, 0x80016703, 0xe07010d8, 0x80016803, 0xe07010dc, 0x80016903, 0xe07010e0, 0x80016a03, + 0xe07010e4, 0x80016b03, 0xe07010e8, 0x80016c03, 0xe07010ec, 0x80016d03, 0xe07010f0, 0x80016e03, + 0xe07010f4, 0x80016f03, 0xe07010f8, 0x80017003, 0xe07010fc, 0x80017103, 0xe0701100, 0x80017203, + 0xe0701104, 0x80017303, 0xe0701108, 0x80017403, 0xe070110c, 0x80017503, 0xe0701110, 0x80017603, + 0xe0701114, 0x80017703, 0xe0701118, 0x80017803, 0xe070111c, 0x80017903, 0xe0701120, 0x80017a03, + 0xe0701124, 0x80017b03, 0xe0701128, 0x80010a03, 0xe070112c, 0x80010b03, 0xe0701130, 0x80010c03, + 0xe0701134, 0x80010d03, 0xe0701138, 0x80010e03, 0xe070113c, 0x80010f03, 0xe0701140, 0x80011003, + 0xe0701144, 0x80011103, 0xe0701148, 0x80011203, 0xe070114c, 0x80011303, 0xe0701150, 0x80011403, + 0xe0701154, 0x80011503, 0xe0701158, 0x80011603, 0xe070115c, 0x80011703, 0xe0701160, 0x80011803, + 0xe0701164, 0x80011903, 0xe0701168, 0x80011a03, 0xe070116c, 0x80011b03, 0xe0701170, 0x80011c03, + 0xe0701174, 0x80011d03, 0xe0701178, 0x80011e03, 0xe070117c, 0x80011f03, 0xe0701180, 0x80012003, + 0xe0701184, 0x80012103, 0xe0701188, 0x80012203, 0xe070118c, 0x80012303, 0xe0701190, 0x80012403, + 0xe0701194, 0x80012503, 0xe0701198, 0x80012603, 0xe070119c, 0x80012703, 0xe07011a0, 0x80012803, + 0xe07011a4, 0x80012903, 0xe07011a8, 0x80012a03, 0xe07011ac, 0x80012b03, 0xe07011b0, 0x80012c03, + 0xe07011b4, 0x80012d03, 0xe07011b8, 0x80012e03, 0xe07011bc, 0x80012f03, 0xe07011c0, 0x80013003, + 0xe07011c4, 0x80013103, 0xbf810000, +}; + +constexpr std::uint64_t Sign = 0x8000000000000000ull; + +bool Nan(std::uint64_t bits) { return (bits & ~Sign) > 0x7ff0000000000000ull; } +std::uint64_t OrderKey(std::uint64_t bits) { return (bits & Sign) != 0u ? ~bits : bits | Sign; } +std::uint64_t MinMax(std::uint64_t old, std::uint64_t data, bool max) { + if (Nan(old)) return data; + if (Nan(data)) return old; + return (max ? OrderKey(data) > OrderKey(old) : OrderKey(data) < OrderKey(old)) ? data : old; +} +bool FloatEqual(std::uint64_t a, std::uint64_t b) { + return (a == b && !Nan(a)) || ((a | b) & ~Sign) == 0u; +} + +std::uint64_t Word(const std::uint32_t* words, std::uint32_t index) { + return words[index * 2u] | (static_cast(words[index * 2u + 1u]) << 32u); +} + +void Fill(std::uint32_t tid, std::uint32_t* words) { + constexpr std::array floats{ + 0x0000000000000000ull, 0x8000000000000000ull, 0x3ff0000000000000ull, 0xbff0000000000000ull, + 0x7ff8000000000000ull, 0x7ff0000000000000ull, 0x4004000000000000ull, 0xc008000000000000ull, + 0x0000000000000001ull, 0x7ff0000000000001ull, 0xfff0000000000000ull, 0x800fffffffffffffull, + }; + const std::uint32_t kind = tid % 4u; + const std::uint64_t seed = (tid + 1u) * 0x9e3779b97f4a7c15ull; + std::array values{}; + if (kind == 0u) values = {seed, seed * 0xbf58476d1ce4e5b9ull, seed ^ 0x94d049bb133111ebull}; + if (kind == 1u) values = {tid % 7u, (tid * 3u) % 7u, tid % 5u}; + if (kind == 2u) values = {floats[tid % 12u], floats[(tid / 2u + 3u) % 12u], floats[(tid + 5u) % 12u]}; + if (kind == 3u) values = {seed, (tid & 4u) != 0u ? seed : seed + 0x100000000ull, seed * 3u}; + for (std::uint32_t i = 0; i < values.size(); ++i) { + words[i * 2u] = static_cast(values[i]); + words[i * 2u + 1u] = static_cast(values[i] >> 32u); + } +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint64_t value) { + char text[24]; + std::snprintf(text, sizeof(text), "0x%016llx", static_cast(value)); + return text; +} + +void Expect(std::uint32_t tid, std::uint64_t actual, std::uint64_t expected, const std::string& name) { + Require(actual == expected, "lds 64-bit atomics: lane " + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 2244u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckLanes() { + constexpr std::array names{ + "ds_add_u64", "ds_sub_u64", "ds_rsub_u64", "ds_inc_u64", "ds_dec_u64", "ds_min_i64", "ds_max_i64", "ds_min_u64", "ds_max_u64", + "ds_and_b64", "ds_or_b64", "ds_xor_b64", "ds_mskor_b64", "ds_cmpst_b64", "ds_cmpst_f64", "ds_min_f64", "ds_max_f64", + "ds_add_rtn_u64", "ds_sub_rtn_u64", "ds_rsub_rtn_u64", "ds_inc_rtn_u64", "ds_dec_rtn_u64", "ds_min_rtn_i64", "ds_max_rtn_i64", + "ds_min_rtn_u64", "ds_max_rtn_u64", "ds_and_rtn_b64", "ds_or_rtn_b64", "ds_xor_rtn_b64", "ds_mskor_rtn_b64", "ds_wrxchg_rtn_b64", + "ds_cmpst_rtn_b64", "ds_cmpst_rtn_f64", "ds_min_rtn_f64", "ds_max_rtn_f64", + }; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + const std::uint64_t a = Word(in, 0); + const std::uint64_t b = Word(in, 1); + const std::uint64_t c = Word(in, 2); + const auto sa = static_cast(a); + const auto sb = static_cast(b); + const std::array common{ + a + b, a - b, b - a, a >= b ? 0u : a + 1u, (a == 0u || a > b) ? b : a - 1u, + sb < sa ? b : a, sb > sa ? b : a, b < a ? b : a, b > a ? b : a, + a & b, a | b, a ^ b, (a & ~b) | c, a == b ? c : a, FloatEqual(a, b) ? c : a, + MinMax(a, b, false), MinMax(a, b, true), + }; + std::array memory{}; + std::copy(common.begin(), common.end(), memory.begin()); + std::copy(common.begin(), common.begin() + 13, memory.begin() + 17); + memory[30] = b; + std::copy(common.begin() + 13, common.end(), memory.begin() + 31); + for (std::uint32_t j = 0; j < Operations; ++j) Expect(tid, Word(out, j), memory[j], names[j]); + for (std::uint32_t j = 0; j < ReturningOperations; ++j) Expect(tid, Word(out, Operations + 2u + j), a, std::string(names[17u + j]) + " returned value"); + } +} + +void CheckContention() { + std::uint64_t total = 0; + std::uint64_t maximum = 0; + std::vector pending; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + total += Word(&Input[tid * Inputs], 1); + maximum = std::max(maximum, Word(&Input[tid * Inputs], 2)); + pending.push_back(tid); + } + Expect(0, Word(Output.data(), Operations), total, "contended ds_add_rtn_u64 total"); + Expect(0, Word(Output.data(), Operations + 1u), maximum, "contended ds_max_rtn_u64 result"); + std::uint64_t current = 0; + while (!pending.empty()) { + auto next = pending.end(); + for (auto it = pending.begin(); it != pending.end(); ++it) { + if (Word(&Output[*it * Results], Operations + 2u + ReturningOperations) != current) continue; + if (next == pending.end() || Word(&Input[*it * Inputs], 1) == 0u) next = it; + } + Require(next != pending.end(), "lds 64-bit atomics: contended ds_add_rtn_u64 returned values do not form one serial order at " + Hex(current)); + current += Word(&Input[*next * Inputs], 1); + pending.erase(next); + } + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint64_t old = Word(&Output[tid * Results], Operations + 3u + ReturningOperations); + bool seen = old == 0u; + for (std::uint32_t lane = 0; lane < Threads; ++lane) seen |= Word(&Input[lane * Inputs], 2) == old; + Require(seen && old <= maximum, "lds 64-bit atomics: contended ds_max_rtn_u64 returned " + Hex(old)); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + CheckLanes(); + CheckContention(); + std::puts("lds 64-bit atomics tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Permlane16.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Permlane16.cpp new file mode 100644 index 000000000..d66899aff --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Permlane16.cpp @@ -0,0 +1,293 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe9403ff, 0x76543210, 0xbe9503ff, + 0xfedcba98, 0xbe9603ff, 0x89abcdef, 0xbe9703ff, 0x01234567, 0xbe9803ff, 0x1f2e3d4c, 0xbe9903ff, + 0x8a9b7c60, 0xbe9a03ff, 0x55555555, 0xbe9b03ff, 0x55555555, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xd777000a, 0x00542904, 0xd777000b, + 0x005c2d04, 0xd777000c, 0x00643104, 0xd777000d, 0x02010104, 0xd777000e, 0x03058304, 0xd778000f, + 0x00542904, 0xd7780010, 0x005c2d04, 0xd7780011, 0x00643104, 0xd7780012, 0x006c3504, 0xbefe03ff, + 0x33cc0ff3, 0xd7770013, 0x00643104, 0xd7770814, 0x00643104, 0xd7771015, 0x00643104, 0xd7771816, + 0x00643104, 0xd7780017, 0x005c2d04, 0xd7780818, 0x005c2d04, 0xd7781019, 0x005c2d04, 0xbefe03c1, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe9403ff, 0x76543210, 0xbe9503ff, + 0xfedcba98, 0xbe9603ff, 0x89abcdef, 0xbe9703ff, 0x01234567, 0xbe9803ff, 0x1f2e3d4c, 0xbe9903ff, + 0x8a9b7c60, 0xbe9a03ff, 0x55555555, 0xbe9b03ff, 0x55555555, 0x7e1402ff, 0xdeaddead, 0x7e1602ff, + 0xdeaddead, 0x7e1802ff, 0xdeaddead, 0x7e1a02ff, 0xdeaddead, 0x7e1c02ff, 0xdeaddead, 0x7e1e02ff, + 0xdeaddead, 0x7e2002ff, 0xdeaddead, 0x7e2202ff, 0xdeaddead, 0x7e2402ff, 0xdeaddead, 0x7e2602ff, + 0xdeaddead, 0x7e2802ff, 0xdeaddead, 0x7e2a02ff, 0xdeaddead, 0x7e2c02ff, 0xdeaddead, 0x7e2e02ff, + 0xdeaddead, 0x7e3002ff, 0xdeaddead, 0x7e3202ff, 0xdeaddead, 0xd777000a, 0x00542904, 0xd777000b, + 0x005c2d04, 0xd777000c, 0x00643104, 0xd777000d, 0x02010104, 0xd777000e, 0x03058304, 0xd778000f, + 0x00542904, 0xd7780010, 0x005c2d04, 0xd7780011, 0x00643104, 0xd7780012, 0x006c3504, 0xbefe03ff, + 0x33cc0ff3, 0xbeff03ff, 0xc3a5960f, 0xd7770013, 0x00643104, 0xd7770814, 0x00643104, 0xd7771015, + 0x00643104, 0xd7771816, 0x00643104, 0xd7780017, 0x005c2d04, 0xd7780818, 0x005c2d04, 0xd7781019, + 0x005c2d04, 0xbefe04c1, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, + 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x1e7ea419u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x51c9bc70u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80a4df5au, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xf38b2ffcu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8306d03bu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xa5aec797u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xdc28ff90u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xf3f49249u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1a466884u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xe255accbu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x39292d22u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xe5121482u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x99dd251du, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x9f199504u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8e7aa6e9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x6bad6be2u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc88b2875u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x9293de8fu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8c3d5f16u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd7a7a3ccu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xbb049a79u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc6cd75e9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc4a334bfu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x7dabe929u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc0433cbdu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc5e818fau, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x96263ae6u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x70eb9a0au, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3d6c51e3u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00a61f93u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x9d3c7decu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x14aa4e71u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1c593af5u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x49889310u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd11745adu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1919e93au, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x731c9452u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x02f0ee99u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xd0944996u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xe4163207u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xaf67d461u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x7d836e77u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xadec6f11u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x5071950eu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x35d14880u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x65b00a2du, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x406329bcu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x59001ac9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xfadb8908u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffb88309u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xede7bd0cu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x5b4c8012u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xce1d62e0u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x60581ccau, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffe976abu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xbef7e268u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x837a7935u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xa341738cu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1389b372u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xb91dddd9u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x57241955u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x16f44881u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x8eb5140fu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x89be9c1cu, 0x00000000u, 0x00000000u, 0x00000000u} +}; + +constexpr std::uint32_t Expected32[32][16] = { + {0x1e7ea419u, 0x6bad6be2u, 0x99dd251du, 0x1e7ea419u, 0x6bad6be2u, 0xc88b2875u, 0x14aa4e71u, 0x3d6c51e3u, 0xc6cd75e9u, 0xdeaddeadu, 0x99dd251du, 0x00000000u, 0x99dd251du, 0xdeaddeadu, 0x14aa4e71u, 0x00000000u}, + {0x51c9bc70u, 0x8e7aa6e9u, 0x8306d03bu, 0x1e7ea419u, 0x6bad6be2u, 0x9293de8fu, 0x9d3c7decu, 0xbb049a79u, 0xc6cd75e9u, 0x8306d03bu, 0x8306d03bu, 0x8306d03bu, 0x8306d03bu, 0xdeaddeadu, 0x9d3c7decu, 0x00000000u}, + {0x80a4df5au, 0x9f199504u, 0x9f199504u, 0x1e7ea419u, 0x6bad6be2u, 0x8c3d5f16u, 0x00a61f93u, 0x00a61f93u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xf38b2ffcu, 0x99dd251du, 0xf38b2ffcu, 0x1e7ea419u, 0x6bad6be2u, 0xd7a7a3ccu, 0x3d6c51e3u, 0xd7a7a3ccu, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8306d03bu, 0xe5121482u, 0x8e7aa6e9u, 0x1e7ea419u, 0x6bad6be2u, 0xbb049a79u, 0x70eb9a0au, 0x9d3c7decu, 0xc6cd75e9u, 0xdeaddeadu, 0x8e7aa6e9u, 0x00000000u, 0x8e7aa6e9u, 0xdeaddeadu, 0x70eb9a0au, 0x00000000u}, + {0xa5aec797u, 0x39292d22u, 0x80a4df5au, 0x1e7ea419u, 0x6bad6be2u, 0xc6cd75e9u, 0x96263ae6u, 0x8c3d5f16u, 0xc6cd75e9u, 0xdeaddeadu, 0x80a4df5au, 0x00000000u, 0x80a4df5au, 0xdeaddeadu, 0x96263ae6u, 0x00000000u}, + {0xdc28ff90u, 0xe255accbu, 0x6bad6be2u, 0x1e7ea419u, 0x6bad6be2u, 0xc4a334bfu, 0xc5e818fau, 0x14aa4e71u, 0xc6cd75e9u, 0xdeaddeadu, 0x6bad6be2u, 0x00000000u, 0x6bad6be2u, 0xc5e818fau, 0xc5e818fau, 0xc5e818fau}, + {0xf3f49249u, 0x1a466884u, 0x51c9bc70u, 0x1e7ea419u, 0x6bad6be2u, 0x7dabe929u, 0xc0433cbdu, 0x9293de8fu, 0xc6cd75e9u, 0x51c9bc70u, 0x51c9bc70u, 0x51c9bc70u, 0x51c9bc70u, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu}, + {0x1a466884u, 0xf3f49249u, 0x1e7ea419u, 0x1e7ea419u, 0x6bad6be2u, 0xc0433cbdu, 0x7dabe929u, 0xc88b2875u, 0xc6cd75e9u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u}, + {0xe255accbu, 0xdc28ff90u, 0xdc28ff90u, 0x1e7ea419u, 0x6bad6be2u, 0xc5e818fau, 0xc4a334bfu, 0xc4a334bfu, 0xc6cd75e9u, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu}, + {0x39292d22u, 0xa5aec797u, 0x99dd251du, 0x1e7ea419u, 0x6bad6be2u, 0x96263ae6u, 0xc6cd75e9u, 0x3d6c51e3u, 0xc6cd75e9u, 0xdeaddeadu, 0x99dd251du, 0x00000000u, 0x99dd251du, 0xdeaddeadu, 0xc6cd75e9u, 0x00000000u}, + {0xe5121482u, 0x8306d03bu, 0xf3f49249u, 0x1e7ea419u, 0x6bad6be2u, 0x70eb9a0au, 0xbb049a79u, 0x7dabe929u, 0xc6cd75e9u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0xdeaddeadu, 0xbb049a79u, 0x00000000u}, + {0x99dd251du, 0xf38b2ffcu, 0xe5121482u, 0x1e7ea419u, 0x6bad6be2u, 0x3d6c51e3u, 0xd7a7a3ccu, 0x70eb9a0au, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9f199504u, 0x80a4df5au, 0xe255accbu, 0x1e7ea419u, 0x6bad6be2u, 0x00a61f93u, 0x8c3d5f16u, 0xc5e818fau, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0x51c9bc70u, 0x39292d22u, 0x1e7ea419u, 0x6bad6be2u, 0x9d3c7decu, 0x9293de8fu, 0x96263ae6u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6bad6be2u, 0x1e7ea419u, 0x1a466884u, 0x1e7ea419u, 0x6bad6be2u, 0x14aa4e71u, 0xc88b2875u, 0xc0433cbdu, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc88b2875u, 0x14aa4e71u, 0x3d6c51e3u, 0xc88b2875u, 0x14aa4e71u, 0x1e7ea419u, 0x6bad6be2u, 0x99dd251du, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9293de8fu, 0x9d3c7decu, 0xbb049a79u, 0xc88b2875u, 0x14aa4e71u, 0x51c9bc70u, 0x8e7aa6e9u, 0x8306d03bu, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8c3d5f16u, 0x00a61f93u, 0x00a61f93u, 0xc88b2875u, 0x14aa4e71u, 0x80a4df5au, 0x9f199504u, 0x9f199504u, 0xa5aec797u, 0x00a61f93u, 0x00a61f93u, 0x00a61f93u, 0x00a61f93u, 0xdeaddeadu, 0x9f199504u, 0x00000000u}, + {0xd7a7a3ccu, 0x3d6c51e3u, 0xd7a7a3ccu, 0xc88b2875u, 0x14aa4e71u, 0xf38b2ffcu, 0x99dd251du, 0xf38b2ffcu, 0xa5aec797u, 0xd7a7a3ccu, 0xd7a7a3ccu, 0xd7a7a3ccu, 0xd7a7a3ccu, 0xdeaddeadu, 0x99dd251du, 0x00000000u}, + {0xbb049a79u, 0x70eb9a0au, 0x9d3c7decu, 0xc88b2875u, 0x14aa4e71u, 0x8306d03bu, 0xe5121482u, 0x8e7aa6e9u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc6cd75e9u, 0x96263ae6u, 0x8c3d5f16u, 0xc88b2875u, 0x14aa4e71u, 0xa5aec797u, 0x39292d22u, 0x80a4df5au, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc4a334bfu, 0xc5e818fau, 0x14aa4e71u, 0xc88b2875u, 0x14aa4e71u, 0xdc28ff90u, 0xe255accbu, 0x6bad6be2u, 0xa5aec797u, 0xdeaddeadu, 0x14aa4e71u, 0x00000000u, 0x14aa4e71u, 0xe255accbu, 0xe255accbu, 0xe255accbu}, + {0x7dabe929u, 0xc0433cbdu, 0x9293de8fu, 0xc88b2875u, 0x14aa4e71u, 0xf3f49249u, 0x1a466884u, 0x51c9bc70u, 0xa5aec797u, 0xdeaddeadu, 0x9293de8fu, 0x00000000u, 0x9293de8fu, 0x1a466884u, 0x1a466884u, 0x1a466884u}, + {0xc0433cbdu, 0x7dabe929u, 0xc88b2875u, 0xc88b2875u, 0x14aa4e71u, 0x1a466884u, 0xf3f49249u, 0x1e7ea419u, 0xa5aec797u, 0xdeaddeadu, 0xc88b2875u, 0x00000000u, 0xc88b2875u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u}, + {0xc5e818fau, 0xc4a334bfu, 0xc4a334bfu, 0xc88b2875u, 0x14aa4e71u, 0xe255accbu, 0xdc28ff90u, 0xdc28ff90u, 0xa5aec797u, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u}, + {0x96263ae6u, 0xc6cd75e9u, 0x3d6c51e3u, 0xc88b2875u, 0x14aa4e71u, 0x39292d22u, 0xa5aec797u, 0x99dd251du, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x70eb9a0au, 0xbb049a79u, 0x7dabe929u, 0xc88b2875u, 0x14aa4e71u, 0xe5121482u, 0x8306d03bu, 0xf3f49249u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3d6c51e3u, 0xd7a7a3ccu, 0x70eb9a0au, 0xc88b2875u, 0x14aa4e71u, 0x99dd251du, 0xf38b2ffcu, 0xe5121482u, 0xa5aec797u, 0xdeaddeadu, 0x70eb9a0au, 0x00000000u, 0x70eb9a0au, 0xdeaddeadu, 0xf38b2ffcu, 0x00000000u}, + {0x00a61f93u, 0x8c3d5f16u, 0xc5e818fau, 0xc88b2875u, 0x14aa4e71u, 0x9f199504u, 0x80a4df5au, 0xe255accbu, 0xa5aec797u, 0xc5e818fau, 0xc5e818fau, 0xc5e818fau, 0xc5e818fau, 0xdeaddeadu, 0x80a4df5au, 0x00000000u}, + {0x9d3c7decu, 0x9293de8fu, 0x96263ae6u, 0xc88b2875u, 0x14aa4e71u, 0x8e7aa6e9u, 0x51c9bc70u, 0x39292d22u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x14aa4e71u, 0xc88b2875u, 0xc0433cbdu, 0xc88b2875u, 0x14aa4e71u, 0x6bad6be2u, 0x1e7ea419u, 0x1a466884u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu} +}; +constexpr std::uint32_t Expected64[64][16] = { + {0x1e7ea419u, 0x6bad6be2u, 0x99dd251du, 0x1e7ea419u, 0x6bad6be2u, 0xc88b2875u, 0x14aa4e71u, 0x3d6c51e3u, 0xc6cd75e9u, 0xdeaddeadu, 0x99dd251du, 0x00000000u, 0x99dd251du, 0xdeaddeadu, 0x14aa4e71u, 0x00000000u}, + {0x51c9bc70u, 0x8e7aa6e9u, 0x8306d03bu, 0x1e7ea419u, 0x6bad6be2u, 0x9293de8fu, 0x9d3c7decu, 0xbb049a79u, 0xc6cd75e9u, 0x8306d03bu, 0x8306d03bu, 0x8306d03bu, 0x8306d03bu, 0xdeaddeadu, 0x9d3c7decu, 0x00000000u}, + {0x80a4df5au, 0x9f199504u, 0x9f199504u, 0x1e7ea419u, 0x6bad6be2u, 0x8c3d5f16u, 0x00a61f93u, 0x00a61f93u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xf38b2ffcu, 0x99dd251du, 0xf38b2ffcu, 0x1e7ea419u, 0x6bad6be2u, 0xd7a7a3ccu, 0x3d6c51e3u, 0xd7a7a3ccu, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8306d03bu, 0xe5121482u, 0x8e7aa6e9u, 0x1e7ea419u, 0x6bad6be2u, 0xbb049a79u, 0x70eb9a0au, 0x9d3c7decu, 0xc6cd75e9u, 0xdeaddeadu, 0x8e7aa6e9u, 0x00000000u, 0x8e7aa6e9u, 0xdeaddeadu, 0x70eb9a0au, 0x00000000u}, + {0xa5aec797u, 0x39292d22u, 0x80a4df5au, 0x1e7ea419u, 0x6bad6be2u, 0xc6cd75e9u, 0x96263ae6u, 0x8c3d5f16u, 0xc6cd75e9u, 0xdeaddeadu, 0x80a4df5au, 0x00000000u, 0x80a4df5au, 0xdeaddeadu, 0x96263ae6u, 0x00000000u}, + {0xdc28ff90u, 0xe255accbu, 0x6bad6be2u, 0x1e7ea419u, 0x6bad6be2u, 0xc4a334bfu, 0xc5e818fau, 0x14aa4e71u, 0xc6cd75e9u, 0xdeaddeadu, 0x6bad6be2u, 0x00000000u, 0x6bad6be2u, 0xc5e818fau, 0xc5e818fau, 0xc5e818fau}, + {0xf3f49249u, 0x1a466884u, 0x51c9bc70u, 0x1e7ea419u, 0x6bad6be2u, 0x7dabe929u, 0xc0433cbdu, 0x9293de8fu, 0xc6cd75e9u, 0x51c9bc70u, 0x51c9bc70u, 0x51c9bc70u, 0x51c9bc70u, 0xc0433cbdu, 0xc0433cbdu, 0xc0433cbdu}, + {0x1a466884u, 0xf3f49249u, 0x1e7ea419u, 0x1e7ea419u, 0x6bad6be2u, 0xc0433cbdu, 0x7dabe929u, 0xc88b2875u, 0xc6cd75e9u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x1e7ea419u, 0x7dabe929u, 0x7dabe929u, 0x7dabe929u}, + {0xe255accbu, 0xdc28ff90u, 0xdc28ff90u, 0x1e7ea419u, 0x6bad6be2u, 0xc5e818fau, 0xc4a334bfu, 0xc4a334bfu, 0xc6cd75e9u, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu}, + {0x39292d22u, 0xa5aec797u, 0x99dd251du, 0x1e7ea419u, 0x6bad6be2u, 0x96263ae6u, 0xc6cd75e9u, 0x3d6c51e3u, 0xc6cd75e9u, 0xdeaddeadu, 0x99dd251du, 0x00000000u, 0x99dd251du, 0xdeaddeadu, 0xc6cd75e9u, 0x00000000u}, + {0xe5121482u, 0x8306d03bu, 0xf3f49249u, 0x1e7ea419u, 0x6bad6be2u, 0x70eb9a0au, 0xbb049a79u, 0x7dabe929u, 0xc6cd75e9u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u, 0xdeaddeadu, 0xbb049a79u, 0x00000000u}, + {0x99dd251du, 0xf38b2ffcu, 0xe5121482u, 0x1e7ea419u, 0x6bad6be2u, 0x3d6c51e3u, 0xd7a7a3ccu, 0x70eb9a0au, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9f199504u, 0x80a4df5au, 0xe255accbu, 0x1e7ea419u, 0x6bad6be2u, 0x00a61f93u, 0x8c3d5f16u, 0xc5e818fau, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8e7aa6e9u, 0x51c9bc70u, 0x39292d22u, 0x1e7ea419u, 0x6bad6be2u, 0x9d3c7decu, 0x9293de8fu, 0x96263ae6u, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x6bad6be2u, 0x1e7ea419u, 0x1a466884u, 0x1e7ea419u, 0x6bad6be2u, 0x14aa4e71u, 0xc88b2875u, 0xc0433cbdu, 0xc6cd75e9u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc88b2875u, 0x14aa4e71u, 0x3d6c51e3u, 0xc88b2875u, 0x14aa4e71u, 0x1e7ea419u, 0x6bad6be2u, 0x99dd251du, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x9293de8fu, 0x9d3c7decu, 0xbb049a79u, 0xc88b2875u, 0x14aa4e71u, 0x51c9bc70u, 0x8e7aa6e9u, 0x8306d03bu, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8c3d5f16u, 0x00a61f93u, 0x00a61f93u, 0xc88b2875u, 0x14aa4e71u, 0x80a4df5au, 0x9f199504u, 0x9f199504u, 0xa5aec797u, 0x00a61f93u, 0x00a61f93u, 0x00a61f93u, 0x00a61f93u, 0xdeaddeadu, 0x9f199504u, 0x00000000u}, + {0xd7a7a3ccu, 0x3d6c51e3u, 0xd7a7a3ccu, 0xc88b2875u, 0x14aa4e71u, 0xf38b2ffcu, 0x99dd251du, 0xf38b2ffcu, 0xa5aec797u, 0xd7a7a3ccu, 0xd7a7a3ccu, 0xd7a7a3ccu, 0xd7a7a3ccu, 0xdeaddeadu, 0x99dd251du, 0x00000000u}, + {0xbb049a79u, 0x70eb9a0au, 0x9d3c7decu, 0xc88b2875u, 0x14aa4e71u, 0x8306d03bu, 0xe5121482u, 0x8e7aa6e9u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc6cd75e9u, 0x96263ae6u, 0x8c3d5f16u, 0xc88b2875u, 0x14aa4e71u, 0xa5aec797u, 0x39292d22u, 0x80a4df5au, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xc4a334bfu, 0xc5e818fau, 0x14aa4e71u, 0xc88b2875u, 0x14aa4e71u, 0xdc28ff90u, 0xe255accbu, 0x6bad6be2u, 0xa5aec797u, 0xdeaddeadu, 0x14aa4e71u, 0x00000000u, 0x14aa4e71u, 0xe255accbu, 0xe255accbu, 0xe255accbu}, + {0x7dabe929u, 0xc0433cbdu, 0x9293de8fu, 0xc88b2875u, 0x14aa4e71u, 0xf3f49249u, 0x1a466884u, 0x51c9bc70u, 0xa5aec797u, 0xdeaddeadu, 0x9293de8fu, 0x00000000u, 0x9293de8fu, 0x1a466884u, 0x1a466884u, 0x1a466884u}, + {0xc0433cbdu, 0x7dabe929u, 0xc88b2875u, 0xc88b2875u, 0x14aa4e71u, 0x1a466884u, 0xf3f49249u, 0x1e7ea419u, 0xa5aec797u, 0xdeaddeadu, 0xc88b2875u, 0x00000000u, 0xc88b2875u, 0xf3f49249u, 0xf3f49249u, 0xf3f49249u}, + {0xc5e818fau, 0xc4a334bfu, 0xc4a334bfu, 0xc88b2875u, 0x14aa4e71u, 0xe255accbu, 0xdc28ff90u, 0xdc28ff90u, 0xa5aec797u, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0xc4a334bfu, 0xdc28ff90u, 0xdc28ff90u, 0xdc28ff90u}, + {0x96263ae6u, 0xc6cd75e9u, 0x3d6c51e3u, 0xc88b2875u, 0x14aa4e71u, 0x39292d22u, 0xa5aec797u, 0x99dd251du, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x70eb9a0au, 0xbb049a79u, 0x7dabe929u, 0xc88b2875u, 0x14aa4e71u, 0xe5121482u, 0x8306d03bu, 0xf3f49249u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x3d6c51e3u, 0xd7a7a3ccu, 0x70eb9a0au, 0xc88b2875u, 0x14aa4e71u, 0x99dd251du, 0xf38b2ffcu, 0xe5121482u, 0xa5aec797u, 0xdeaddeadu, 0x70eb9a0au, 0x00000000u, 0x70eb9a0au, 0xdeaddeadu, 0xf38b2ffcu, 0x00000000u}, + {0x00a61f93u, 0x8c3d5f16u, 0xc5e818fau, 0xc88b2875u, 0x14aa4e71u, 0x9f199504u, 0x80a4df5au, 0xe255accbu, 0xa5aec797u, 0xc5e818fau, 0xc5e818fau, 0xc5e818fau, 0xc5e818fau, 0xdeaddeadu, 0x80a4df5au, 0x00000000u}, + {0x9d3c7decu, 0x9293de8fu, 0x96263ae6u, 0xc88b2875u, 0x14aa4e71u, 0x8e7aa6e9u, 0x51c9bc70u, 0x39292d22u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x14aa4e71u, 0xc88b2875u, 0xc0433cbdu, 0xc88b2875u, 0x14aa4e71u, 0x6bad6be2u, 0x1e7ea419u, 0x1a466884u, 0xa5aec797u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x1c593af5u, 0x59001ac9u, 0x35d14880u, 0x1c593af5u, 0x59001ac9u, 0xfadb8908u, 0x89be9c1cu, 0x57241955u, 0x60581ccau, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0x89be9c1cu, 0x89be9c1cu, 0x89be9c1cu}, + {0x49889310u, 0x406329bcu, 0x731c9452u, 0x1c593af5u, 0x59001ac9u, 0xffb88309u, 0x8eb5140fu, 0xce1d62e0u, 0x60581ccau, 0xdeaddeadu, 0x731c9452u, 0x00000000u, 0x731c9452u, 0x8eb5140fu, 0x8eb5140fu, 0x8eb5140fu}, + {0xd11745adu, 0x65b00a2du, 0x65b00a2du, 0x1c593af5u, 0x59001ac9u, 0xede7bd0cu, 0x16f44881u, 0x16f44881u, 0x60581ccau, 0xdeaddeadu, 0x65b00a2du, 0x00000000u, 0x65b00a2du, 0xdeaddeadu, 0x16f44881u, 0x00000000u}, + {0x1919e93au, 0x35d14880u, 0x1919e93au, 0x1c593af5u, 0x59001ac9u, 0x5b4c8012u, 0x57241955u, 0x5b4c8012u, 0x60581ccau, 0x1919e93au, 0x1919e93au, 0x1919e93au, 0x1919e93au, 0xdeaddeadu, 0x57241955u, 0x00000000u}, + {0x731c9452u, 0x5071950eu, 0x406329bcu, 0x1c593af5u, 0x59001ac9u, 0xce1d62e0u, 0xb91dddd9u, 0x8eb5140fu, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x02f0ee99u, 0xadec6f11u, 0xd11745adu, 0x1c593af5u, 0x59001ac9u, 0x60581ccau, 0x1389b372u, 0xede7bd0cu, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xd0944996u, 0x7d836e77u, 0x59001ac9u, 0x1c593af5u, 0x59001ac9u, 0xffe976abu, 0xa341738cu, 0x89be9c1cu, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xe4163207u, 0xaf67d461u, 0x49889310u, 0x1c593af5u, 0x59001ac9u, 0xbef7e268u, 0x837a7935u, 0xffb88309u, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xaf67d461u, 0xe4163207u, 0x1c593af5u, 0x1c593af5u, 0x59001ac9u, 0x837a7935u, 0xbef7e268u, 0xfadb8908u, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x7d836e77u, 0xd0944996u, 0xd0944996u, 0x1c593af5u, 0x59001ac9u, 0xa341738cu, 0xffe976abu, 0xffe976abu, 0x60581ccau, 0xdeaddeadu, 0xd0944996u, 0x00000000u, 0xd0944996u, 0xdeaddeadu, 0xffe976abu, 0x00000000u}, + {0xadec6f11u, 0x02f0ee99u, 0x35d14880u, 0x1c593af5u, 0x59001ac9u, 0x1389b372u, 0x60581ccau, 0x57241955u, 0x60581ccau, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0x35d14880u, 0x60581ccau, 0x60581ccau, 0x60581ccau}, + {0x5071950eu, 0x731c9452u, 0xe4163207u, 0x1c593af5u, 0x59001ac9u, 0xb91dddd9u, 0xce1d62e0u, 0xbef7e268u, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x35d14880u, 0x1919e93au, 0x5071950eu, 0x1c593af5u, 0x59001ac9u, 0x57241955u, 0x5b4c8012u, 0xb91dddd9u, 0x60581ccau, 0xdeaddeadu, 0x5071950eu, 0x00000000u, 0x5071950eu, 0xdeaddeadu, 0x5b4c8012u, 0x00000000u}, + {0x65b00a2du, 0xd11745adu, 0x7d836e77u, 0x1c593af5u, 0x59001ac9u, 0x16f44881u, 0xede7bd0cu, 0xa341738cu, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x406329bcu, 0x49889310u, 0xadec6f11u, 0x1c593af5u, 0x59001ac9u, 0x8eb5140fu, 0xffb88309u, 0x1389b372u, 0x60581ccau, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x59001ac9u, 0x1c593af5u, 0xaf67d461u, 0x1c593af5u, 0x59001ac9u, 0x89be9c1cu, 0xfadb8908u, 0x837a7935u, 0x60581ccau, 0xdeaddeadu, 0xaf67d461u, 0x00000000u, 0xaf67d461u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u}, + {0xfadb8908u, 0x89be9c1cu, 0x57241955u, 0xfadb8908u, 0x89be9c1cu, 0x1c593af5u, 0x59001ac9u, 0x35d14880u, 0x02f0ee99u, 0xdeaddeadu, 0x57241955u, 0x00000000u, 0x57241955u, 0x59001ac9u, 0x59001ac9u, 0x59001ac9u}, + {0xffb88309u, 0x8eb5140fu, 0xce1d62e0u, 0xfadb8908u, 0x89be9c1cu, 0x49889310u, 0x406329bcu, 0x731c9452u, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xede7bd0cu, 0x16f44881u, 0x16f44881u, 0xfadb8908u, 0x89be9c1cu, 0xd11745adu, 0x65b00a2du, 0x65b00a2du, 0x02f0ee99u, 0xdeaddeadu, 0x16f44881u, 0x00000000u, 0x16f44881u, 0xdeaddeadu, 0x65b00a2du, 0x00000000u}, + {0x5b4c8012u, 0x57241955u, 0x5b4c8012u, 0xfadb8908u, 0x89be9c1cu, 0x1919e93au, 0x35d14880u, 0x1919e93au, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xce1d62e0u, 0xb91dddd9u, 0x8eb5140fu, 0xfadb8908u, 0x89be9c1cu, 0x731c9452u, 0x5071950eu, 0x406329bcu, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x60581ccau, 0x1389b372u, 0xede7bd0cu, 0xfadb8908u, 0x89be9c1cu, 0x02f0ee99u, 0xadec6f11u, 0xd11745adu, 0x02f0ee99u, 0xede7bd0cu, 0xede7bd0cu, 0xede7bd0cu, 0xede7bd0cu, 0xadec6f11u, 0xadec6f11u, 0xadec6f11u}, + {0xffe976abu, 0xa341738cu, 0x89be9c1cu, 0xfadb8908u, 0x89be9c1cu, 0xd0944996u, 0x7d836e77u, 0x59001ac9u, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xbef7e268u, 0x837a7935u, 0xffb88309u, 0xfadb8908u, 0x89be9c1cu, 0xe4163207u, 0xaf67d461u, 0x49889310u, 0x02f0ee99u, 0xdeaddeadu, 0xffb88309u, 0x00000000u, 0xffb88309u, 0xdeaddeadu, 0xaf67d461u, 0x00000000u}, + {0x837a7935u, 0xbef7e268u, 0xfadb8908u, 0xfadb8908u, 0x89be9c1cu, 0xaf67d461u, 0xe4163207u, 0x1c593af5u, 0x02f0ee99u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xfadb8908u, 0xdeaddeadu, 0xe4163207u, 0x00000000u}, + {0xa341738cu, 0xffe976abu, 0xffe976abu, 0xfadb8908u, 0x89be9c1cu, 0x7d836e77u, 0xd0944996u, 0xd0944996u, 0x02f0ee99u, 0xdeaddeadu, 0xffe976abu, 0x00000000u, 0xffe976abu, 0xdeaddeadu, 0xd0944996u, 0x00000000u}, + {0x1389b372u, 0x60581ccau, 0x57241955u, 0xfadb8908u, 0x89be9c1cu, 0xadec6f11u, 0x02f0ee99u, 0x35d14880u, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0xb91dddd9u, 0xce1d62e0u, 0xbef7e268u, 0xfadb8908u, 0x89be9c1cu, 0x5071950eu, 0x731c9452u, 0xe4163207u, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x57241955u, 0x5b4c8012u, 0xb91dddd9u, 0xfadb8908u, 0x89be9c1cu, 0x35d14880u, 0x1919e93au, 0x5071950eu, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x16f44881u, 0xede7bd0cu, 0xa341738cu, 0xfadb8908u, 0x89be9c1cu, 0x65b00a2du, 0xd11745adu, 0x7d836e77u, 0x02f0ee99u, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu, 0xdeaddeadu}, + {0x8eb5140fu, 0xffb88309u, 0x1389b372u, 0xfadb8908u, 0x89be9c1cu, 0x406329bcu, 0x49889310u, 0xadec6f11u, 0x02f0ee99u, 0xdeaddeadu, 0x1389b372u, 0x00000000u, 0x1389b372u, 0x49889310u, 0x49889310u, 0x49889310u}, + {0x89be9c1cu, 0xfadb8908u, 0x837a7935u, 0xfadb8908u, 0x89be9c1cu, 0x59001ac9u, 0x1c593af5u, 0xaf67d461u, 0x02f0ee99u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0x837a7935u, 0x1c593af5u, 0x1c593af5u, 0x1c593af5u} +}; + +constexpr const char* Names[Checked] = {"permlane16_identity", "permlane16_reverse", "permlane16_mixed", "permlane16_zero", "permlane16_all_ones", "permlanex16_identity", "permlanex16_reverse", "permlanex16_mixed", "permlanex16_broadcast", "inactive_permlane16", "inactive_permlane16_fi", "inactive_permlane16_bc", "inactive_permlane16_fi_bc", "inactive_permlanex16", "inactive_permlanex16_fi", "inactive_permlanex16_bc"}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked]) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "permlane16 wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64); + std::puts("permlane16 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelHelperLanes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelHelperLanes.cpp index 482a0766e..0660b8036 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelHelperLanes.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelHelperLanes.cpp @@ -53,11 +53,10 @@ std::vector> Triangles() { return vertices; } -void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize) { +void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { static const auto triangles = Triangles(); Pixels.fill(std::byte{0}); Lanes.fill(Sentinel); - const auto target = device.Target(); std::vector vertexUserData(4, 0u); const auto vertexBuffer = BufferDescriptor(triangles.data(), 16u, static_cast(triangles.size())); @@ -156,10 +155,18 @@ int main() { } for (const auto waveSize : {64u, 32u}) { for (std::uint32_t pass = 0; pass < 4; ++pass) { - Draw(*device, waveSize); + Draw(*device, waveSize, device->Target()); Check(waveSize); } } + if (device->Target().subgroupSize == 32u) { + auto wide = device->Target(); + wide.subgroupSize = 64u; + for (std::uint32_t pass = 0; pass < 4; ++pass) { + Draw(*device, 32u, wide); + Check(32u); + } + } std::puts("pixel helper lane tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelLds.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelLds.cpp new file mode 100644 index 000000000..ba7b210d8 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelLds.cpp @@ -0,0 +1,177 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Sentinel = 0xdeadbeefu; +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 32; +alignas(256) std::array Pixels{}; +alignas(256) std::array Results{}; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +std::vector PixelCode(bool bounded) { + std::vector code{0x7e040f00, 0x7e060f01, 0xd5430006, 0x0409ff03, Width}; + const auto vop2 = [&](std::uint32_t opcode, std::uint32_t destination, std::uint32_t source, std::uint32_t vector) { + code.push_back(opcode | (destination << 17u) | (vector << 9u) | source); + }; + vop2(0x34000000, 8, 0x88, 6); + for (std::uint32_t index = 0; index < 32; ++index) { + vop2(0x4a000000, 4, 0x80 + index, 2); + vop2(0x36000000, 9, 0x9f, 4); + vop2(0x34000000, 13, 0x82, 9); + vop2(0x4a000000, 5, 0x80 + index, 8); + for (std::uint32_t bank = 0; bank < 3; ++bank) { + if (bank != 0) vop2(0x4a000000, 5, 0xa0, 5); + code.push_back(0xd8340000 | (bank * 256u)); + code.push_back(0x0000050d); + } + } + code.push_back(0xbf8cc07f); + if (bounded) vop2(0x36000000, 14, 0x9f, 3); + vop2(0x34000000, 15, 0x82, bounded ? 14 : 3); + for (std::uint32_t bank = 0; bank < 3; ++bank) { + code.push_back(0xd8d80000 | (bank * 256u)); + code.push_back(((10u + bank) << 24u) | 15u); + } + code.push_back(0xbf8cc07f); + for (std::uint32_t bank = 0; bank < 3; ++bank) { + code.push_back(0xe0702000 | (bank * 4u)); + code.push_back(0x80010006 | ((10u + bank) << 8u)); + } + code.insert(code.end(), {0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000}); + return code; +} + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +constexpr std::array, 3> Triangles{{ + {-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f} +}}; + +void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, bool bounded) { + const auto& triangles = Triangles; + const auto pixelCode = PixelCode(bounded); + Pixels.fill(std::byte{0}); + Results.fill(Sentinel); + const auto target = device.Target(); + + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(triangles.data(), 16u, static_cast(triangles.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.wave32 = waveSize == 32u; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY); + pixel.posX = true; + pixel.posY = true; + pixel.targetOutputMode[0] = 9; + pixel.targetExportMapping.fill(0xe4u); + std::vector pixelUserData(8, 0u); + const auto results = BufferDescriptor(Results.data(), 16u, Width * Height); + std::copy(results.begin(), results.end(), pixelUserData.begin() + 4); + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(std::span(pixelCode))}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {waveSize, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + {0, 0, vertexPush, 128 - vertexPush} + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, vertexPush} + }}; + + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u}; + state.colors = {state.color}; + state.hasColorTarget = true; + state.renderExtent = {Width, Height}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(Height), static_cast(Width), -static_cast(Height), 0, 1}; + state.negativeOneToOne = false; + state.scissor = {{0, 0}, {Width, Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15; + state.blends = {state.blend}; + state.blendConstants = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(triangles.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); + device.WaitIdle(); +} + +void Check(std::uint32_t waveSize, bool bounded) { + const auto what = "pixel LDS wave" + std::to_string(waveSize) + (bounded ? " bounded" : " unbounded"); + for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) { + for (std::uint32_t channel = 0; channel < 4; ++channel) { + Require(Pixels[pixel * 4u + channel] == std::byte{255}, what + ": missing color at pixel " + std::to_string(pixel)); + } + const auto x = pixel % Width; + const auto y = pixel / Width; + const auto index = (y + 32u - x) & 31u; + for (std::uint32_t bank = 0; bank < 3; ++bank) { + const auto expected = pixel * 256u + index + bank * 32u; + const auto actual = Results[pixel * 4u + bank]; + Require(actual == expected, what + ": pixel " + std::to_string(pixel) + " bank " + std::to_string(bank) + " expected " + std::to_string(expected) + " got " + std::to_string(actual)); + } + Require(Results[pixel * 4u + 3u] == Sentinel, what + ": output stride guard overwritten"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32u) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + for (const auto waveSize : {64u, 32u}) { + for (const auto bounded : {true, false}) { + std::fprintf(stderr, "pixel LDS wave%u %s\n", waveSize, bounded ? "bounded" : "unbounded"); + for (std::uint32_t pass = 0; pass < 3; ++pass) { + Draw(*device, waveSize, bounded); + Check(waveSize, bounded); + } + } + } + std::puts("pixel LDS tests passed: 12 draws, 24576 pixels, 73728 LDS results"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelLdsLaneSlots.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelLdsLaneSlots.cpp new file mode 100644 index 000000000..a1aa594a1 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelLdsLaneSlots.cpp @@ -0,0 +1,165 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Sentinel = 0xdeadbeefu; +constexpr std::uint32_t Width = 64; +constexpr std::uint32_t Height = 32; +alignas(256) std::array Pixels{}; +alignas(256) std::array Results{}; + +alignas(256) constexpr std::array VertexCode{ + 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, +}; + +std::vector PixelCode() { + std::vector code{0x7e040f00, 0x7e060f01, 0xd5430006, 0x0409ff03, Width}; + const auto vop2 = [&](std::uint32_t opcode, std::uint32_t destination, std::uint32_t source, std::uint32_t vector) { + code.push_back(opcode | (destination << 17u) | (vector << 9u) | source); + }; + vop2(0x34000000, 8, 0x82, 6); + code.push_back(0xbefc0380); + for (std::uint32_t bank = 0; bank < 3; ++bank) { + vop2(0x4a000000, 5, 0x80 + bank, 8); + code.push_back(0xdac00000 | (bank * 256u)); + code.push_back(0x00000500); + } + code.push_back(0xbf8cc07f); + for (std::uint32_t bank = 0; bank < 3; ++bank) { + code.push_back(0xdac40000 | (((bank + 1u) % 3u) * 256u)); + code.push_back((10u + bank) << 24u); + } + code.push_back(0xbf8cc07f); + for (std::uint32_t bank = 0; bank < 3; ++bank) { + code.push_back(0xe0702000 | (bank * 4u)); + code.push_back(0x80010006 | ((10u + bank) << 8u)); + } + code.insert(code.end(), {0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000}); + return code; +} + +std::array BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu}; +} + +constexpr std::array, 3> Triangles{{ + {-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f} +}}; + +void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize) { + const auto& triangles = Triangles; + const auto pixelCode = PixelCode(); + Pixels.fill(std::byte{0}); + Results.fill(Sentinel); + const auto target = device.Target(); + + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(triangles.data(), 16u, static_cast(triangles.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + const auto vertexResult = ShaderRecompiler::Recompile(vertex); + const auto vertexPush = static_cast(vertexResult.pushConstants.size()); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.wave32 = waveSize == 32u; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY); + pixel.posX = true; + pixel.posY = true; + pixel.targetOutputMode[0] = 9; + pixel.targetExportMapping.fill(0xe4u); + std::vector pixelUserData(8, 0u); + const auto results = BufferDescriptor(Results.data(), 16u, Width * Height); + std::copy(results.begin(), results.end(), pixelUserData.begin() + 4); + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(std::span(pixelCode))}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {waveSize, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + {0, 0, vertexPush, 128 - vertexPush} + }; + fragment.useCache = false; + const auto pixelResult = ShaderRecompiler::Recompile(fragment); + const std::array shaders{{ + {ShaderStage::Vertex, &vertexResult, 0}, + {ShaderStage::Fragment, &pixelResult, vertexPush} + }}; + + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u}; + state.colors = {state.color}; + state.hasColorTarget = true; + state.renderExtent = {Width, Height}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(Height), static_cast(Width), -static_cast(Height), 0, 1}; + state.negativeOneToOne = false; + state.scissor = {{0, 0}, {Width, Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15; + state.blends = {state.blend}; + state.blendConstants = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(triangles.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); + device.WaitIdle(); +} + +void Check(std::uint32_t waveSize) { + const auto what = "pixel LDS lane slots wave" + std::to_string(waveSize); + for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) { + for (std::uint32_t channel = 0; channel < 4; ++channel) { + Require(Pixels[pixel * 4u + channel] == std::byte{255}, what + ": missing color at pixel " + std::to_string(pixel)); + } + for (std::uint32_t bank = 0; bank < 3; ++bank) { + const auto expected = pixel * 4u + (bank + 1u) % 3u; + const auto actual = Results[pixel * 4u + bank]; + Require(actual == expected, what + ": pixel " + std::to_string(pixel) + " bank " + std::to_string(bank) + " expected " + std::to_string(expected) + " got " + std::to_string(actual)); + } + Require(Results[pixel * 4u + 3u] == Sentinel, what + ": output stride guard overwritten"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32u) { + std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + for (const auto waveSize : {64u, 32u}) { + std::fprintf(stderr, "pixel LDS lane slots wave%u\n", waveSize); + for (std::uint32_t pass = 0; pass < 3; ++pass) { + Draw(*device, waveSize); + Check(waveSize); + } + } + std::puts("pixel LDS lane slot tests passed: 6 draws, 12288 pixels, 36864 LDS results"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarGetreg.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarGetreg.cpp new file mode 100644 index 000000000..ae8c61581 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarGetreg.cpp @@ -0,0 +1,102 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34060086, 0xbe9403ff, 0x55555555, 0xbe9503ff, 0x55555555, 0xb9141801, 0xb9150881, 0x7e140214, + 0x7e160215, 0xe0701000, 0x80010a03, 0xe0701004, 0x80010b03, 0xbf810000, +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +ShaderRecompiler::RecompileRequest Request(AgcDriver::VulkanDevice& device, std::span code, std::span userData, std::span memory) { + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + return request; +} + +void ExpectRefused(AgcDriver::VulkanDevice& device, std::uint32_t word, const char* what) { + alignas(256) const std::array code{word, 0xbf810000u}; + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(std::span(code))}}}; + const std::vector userData(8, 0u); + std::string refusal; + try { + static_cast(ShaderRecompiler::Recompile(Request(device, code, userData, memory))); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("only the MODE round mode fields are modeled") != std::string::npos, std::string("s_getreg_b32 reading ") + what + " was not refused"); +} + +void Run(AgcDriver::VulkanDevice& device) { + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const auto result = ShaderRecompiler::Recompile(Request(device, code, userData, memory)); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + constexpr std::array names{"hwreg(MODE, 0, 4)", "hwreg(MODE, 2, 2)"}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + for (std::uint32_t index = 0; index < names.size(); ++index) { + const std::uint32_t actual = Output[tid * Results + index]; + Require(actual == 0u, std::string("s_getreg_b32: lane ") + std::to_string(tid) + " " + names[index] + " is " + Hex(actual) + ", expected 0x00000000"); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + ExpectRefused(*device, 0xb9141901u, "the MODE denormal fields"); + ExpectRefused(*device, 0xb914f804u, "hardware register 4"); + std::puts("s_getreg_b32 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp new file mode 100644 index 000000000..a4362f4c3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEnd.cpp @@ -0,0 +1,116 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0301000, 0x80000401, 0xbf8c3f70, 0xbf800000, 0xf4980000, 0xfa000000, + 0xf49c0000, 0xfa000000, 0xf4a00000, 0xfa000000, 0xf4a40000, 0xfa000000, 0xbfa40000, 0x4a0a0881, + 0x7e0c02ff, 0x00000bad, 0xbf060000, 0xbf840007, 0xbf840003, 0xe0701000, 0x80010503, 0xbf9e0000, + 0xe0701000, 0x80010603, 0xbf9b0000, 0xe0701004, 0x80010603, 0xbf810000, +}; + +alignas(256) constexpr std::array RoundTowardZeroCode{0xbfa40003, 0xbf810000}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { words[0] = tid * 0x01010101u + 7u; } + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("scalar program end: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +ShaderRecompiler::RecompileRequest Request(AgcDriver::VulkanDevice& device, std::span code, std::span userData, std::span memory) { + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + return request; +} + +void ExpectRefused(AgcDriver::VulkanDevice& device) { + const std::span code(RoundTowardZeroCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const std::vector userData(8, 0u); + bool refused = false; + try { + static_cast(ShaderRecompiler::Recompile(Request(device, code, userData, memory))); + } catch (const std::exception&) { + refused = true; + } + Require(refused, "scalar program end: s_round_mode 3 was translated"); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const auto result = ShaderRecompiler::Recompile(Request(device, code, userData, memory)); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + Expect(tid, out[0], in[0] + 1u, "store before the end"); + Expect(tid, out[1], 0xdeadbeefu, "nothing past the end"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + ExpectRefused(*device); + std::puts("scalar program end tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEndSaved.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEndSaved.cpp new file mode 100644 index 000000000..100b4552e --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramEndSaved.cpp @@ -0,0 +1,96 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0301000, 0x80000401, 0xbf8c3f70, 0xbf800000, 0xf4980000, 0xfa000000, + 0xf49c0000, 0xfa000000, 0xf4a00000, 0xfa000000, 0xf4a40000, 0xfa000000, 0xbfa40000, 0x4a0a0881, + 0x7e0c02ff, 0x00000bad, 0xbf060000, 0xbf840007, 0xbf840003, 0xe0701000, 0x80010503, 0xbf9b0000, + 0xe0701000, 0x80010603, 0xbf9e0000, 0xe0701004, 0x80010603, 0xbf810000, +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { words[0] = tid * 0x01010101u + 7u; } + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("scalar program end saved: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + Expect(tid, out[0], in[0] + 1u, "store before the end"); + Expect(tid, out[1], 0xdeadbeefu, "nothing past the end"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("scalar program end saved tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramFlow.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramFlow.cpp new file mode 100644 index 000000000..2ffaf0609 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarProgramFlow.cpp @@ -0,0 +1,321 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 14; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeadbeef, 0x7e1602ff, + 0xdeadbeef, 0x7e1802ff, 0xdeadbeef, 0x7e1a02ff, 0xdeadbeef, 0x7e1c02ff, 0xdeadbeef, 0x7e1e02ff, + 0xdeadbeef, 0x7e2002ff, 0xdeadbeef, 0x7e2202ff, 0xdeadbeef, 0x7e2402ff, 0xdeadbeef, 0x7e2602ff, + 0xdeadbeef, 0x7e2802ff, 0xdeadbeef, 0x7e2a02ff, 0xdeadbeef, 0x7e2c02ff, 0xdeadbeef, 0x7e2e02ff, + 0xdeadbeef, 0x7e180505, 0xd5480008, 0x02050104, 0x7d8a1080, 0xbe8e3c6a, 0xbf890003, 0x890cff0c, + 0x00005a5a, 0x7e120280, 0x800cff0c, 0x00000100, 0x887e0e7e, 0x7e14020c, 0x7e180505, 0xd5480008, + 0x02050304, 0x7d8a1080, 0xbe8e3c6a, 0xbf890003, 0x890cff0c, 0x00005a5a, 0x7e120280, 0x800cff0c, + 0x00000200, 0x887e0e7e, 0x7e16020c, 0x7e180505, 0xd5480008, 0x02050504, 0x7d8a1080, 0xbe8e3c6a, + 0xbf890003, 0x890cff0c, 0x00005a5a, 0x7e120280, 0x800cff0c, 0x00000300, 0x887e0e7e, 0x7e18020c, + 0x7e180506, 0xd5480008, 0x02050104, 0x7d8a1080, 0xbe8e3c6a, 0xbf880003, 0x8f0c810c, 0x800cff0c, + 0x00001000, 0x890cb30c, 0x887e0e7e, 0x7e1a020c, 0x7e180506, 0xd5480008, 0x02050304, 0x7d8a1080, + 0xbe8e3c6a, 0xbf880003, 0x8f0c810c, 0x800cff0c, 0x00002000, 0x890cb30c, 0x887e0e7e, 0x7e1c020c, + 0x7e180506, 0xd5480008, 0x02050504, 0x7d8a1080, 0xbe8e3c6a, 0xbf880003, 0x8f0c810c, 0x800cff0c, + 0x00003000, 0x890cb30c, 0x887e0e7e, 0x7e1e020c, 0x7e180506, 0xd5480008, 0x02050104, 0x7d8a1080, + 0xbf870002, 0x800cff0c, 0x00010000, 0x890cff0c, 0x00000055, 0x7e20020c, 0x7e180506, 0xd5480008, + 0x02050304, 0x7d8a1080, 0xbf870002, 0x800cff0c, 0x00020000, 0x890cff0c, 0x00000055, 0x7e22020c, + 0x7e180506, 0xd5480008, 0x02050504, 0x7d8a1080, 0xbf870002, 0x800cff0c, 0x00030000, 0x890cff0c, + 0x00000055, 0x7e24020c, 0x7e180506, 0xd5480008, 0x02050104, 0x7d8a1080, 0xbf860002, 0x800cff0c, + 0x00010000, 0x890cff0c, 0x00000077, 0x7e26020c, 0x7e180506, 0xd5480008, 0x02050304, 0x7d8a1080, + 0xbf860002, 0x800cff0c, 0x00020000, 0x890cff0c, 0x00000077, 0x7e28020c, 0x7e180506, 0xd5480008, + 0x02050504, 0x7d8a1080, 0xbf860002, 0x800cff0c, 0x00030000, 0x890cff0c, 0x00000077, 0x7e2a020c, + 0xbe8c03ff, 0x00001000, 0xbf970001, 0x800c810c, 0xbf980001, 0x800c820c, 0xbf990001, 0x800c840c, + 0xbf9a0001, 0x800c880c, 0x4a2c0a0c, 0xbf8f0002, 0xbfa00001, 0xbfa10001, 0x4a100d05, 0x3a120d05, + 0xbfa3ffe3, 0xbefc03ff, 0x00001234, 0xbf960000, 0xbf8f0000, 0x4c2e1308, 0xe0781000, 0x80010a03, + 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeadbeef, 0x7e1602ff, + 0xdeadbeef, 0x7e1802ff, 0xdeadbeef, 0x7e1a02ff, 0xdeadbeef, 0x7e1c02ff, 0xdeadbeef, 0x7e1e02ff, + 0xdeadbeef, 0x7e2002ff, 0xdeadbeef, 0x7e2202ff, 0xdeadbeef, 0x7e2402ff, 0xdeadbeef, 0x7e2602ff, + 0xdeadbeef, 0x7e2802ff, 0xdeadbeef, 0x7e2a02ff, 0xdeadbeef, 0x7e2c02ff, 0xdeadbeef, 0x7e2e02ff, + 0xdeadbeef, 0x7e180505, 0xd5480008, 0x02050104, 0x7d8a1080, 0xbe8e246a, 0xbf890003, 0x890cff0c, + 0x00005a5a, 0x7e120280, 0x800cff0c, 0x00000100, 0x88fe0e7e, 0x7e14020c, 0x7e180505, 0xd5480008, + 0x02050304, 0x7d8a1080, 0xbe8e246a, 0xbf890003, 0x890cff0c, 0x00005a5a, 0x7e120280, 0x800cff0c, + 0x00000200, 0x88fe0e7e, 0x7e16020c, 0x7e180505, 0xd5480008, 0x02050504, 0x7d8a1080, 0xbe8e246a, + 0xbf890003, 0x890cff0c, 0x00005a5a, 0x7e120280, 0x800cff0c, 0x00000300, 0x88fe0e7e, 0x7e18020c, + 0x7e180506, 0xd5480008, 0x02050104, 0x7d8a1080, 0xbe8e246a, 0xbf880003, 0x8f0c810c, 0x800cff0c, + 0x00001000, 0x890cb30c, 0x88fe0e7e, 0x7e1a020c, 0x7e180506, 0xd5480008, 0x02050304, 0x7d8a1080, + 0xbe8e246a, 0xbf880003, 0x8f0c810c, 0x800cff0c, 0x00002000, 0x890cb30c, 0x88fe0e7e, 0x7e1c020c, + 0x7e180506, 0xd5480008, 0x02050504, 0x7d8a1080, 0xbe8e246a, 0xbf880003, 0x8f0c810c, 0x800cff0c, + 0x00003000, 0x890cb30c, 0x88fe0e7e, 0x7e1e020c, 0x7e180506, 0xd5480008, 0x02050104, 0x7d8a1080, + 0xbf870002, 0x800cff0c, 0x00010000, 0x890cff0c, 0x00000055, 0x7e20020c, 0x7e180506, 0xd5480008, + 0x02050304, 0x7d8a1080, 0xbf870002, 0x800cff0c, 0x00020000, 0x890cff0c, 0x00000055, 0x7e22020c, + 0x7e180506, 0xd5480008, 0x02050504, 0x7d8a1080, 0xbf870002, 0x800cff0c, 0x00030000, 0x890cff0c, + 0x00000055, 0x7e24020c, 0x7e180506, 0xd5480008, 0x02050104, 0x7d8a1080, 0xbf860002, 0x800cff0c, + 0x00010000, 0x890cff0c, 0x00000077, 0x7e26020c, 0x7e180506, 0xd5480008, 0x02050304, 0x7d8a1080, + 0xbf860002, 0x800cff0c, 0x00020000, 0x890cff0c, 0x00000077, 0x7e28020c, 0x7e180506, 0xd5480008, + 0x02050504, 0x7d8a1080, 0xbf860002, 0x800cff0c, 0x00030000, 0x890cff0c, 0x00000077, 0x7e2a020c, + 0xbe8c03ff, 0x00001000, 0xbf970001, 0x800c810c, 0xbf980001, 0x800c820c, 0xbf990001, 0x800c840c, + 0xbf9a0001, 0x800c880c, 0x4a2c0a0c, 0xbf8f0002, 0xbfa00001, 0xbfa10001, 0x4a100d05, 0x3a120d05, + 0xbfa3ffe3, 0xbefc03ff, 0x00001234, 0xbf960000, 0xbf8f0000, 0x4c2e1308, 0xe0781000, 0x80010a03, + 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x00000000u, 0x51c9bc70u, 0x80a4df5au, 0x00000000u}, + {0x00000001u, 0x8306d03bu, 0xa5aec797u, 0x00000000u}, + {0x00000001u, 0xf3f49249u, 0x1a466884u, 0x00000000u}, + {0x00000001u, 0x39292d22u, 0xe5121482u, 0x00000000u}, + {0x00000001u, 0x9f199504u, 0x8e7aa6e9u, 0x00000000u}, + {0x00000004u, 0xc88b2875u, 0x9293de8fu, 0x00000000u}, + {0x00000001u, 0xd7a7a3ccu, 0xbb049a79u, 0x00000000u}, + {0x00000001u, 0xc4a334bfu, 0x7dabe929u, 0x00000000u}, + {0x00000001u, 0xc5e818fau, 0x96263ae6u, 0x00000000u}, + {0x00000000u, 0x3d6c51e3u, 0x00a61f93u, 0x00000000u}, + {0x00000001u, 0x14aa4e71u, 0x1c593af5u, 0x00000000u}, + {0x00000000u, 0xd11745adu, 0x1919e93au, 0x00000000u}, + {0x00000000u, 0x02f0ee99u, 0xd0944996u, 0x00000000u}, + {0x00000001u, 0xaf67d461u, 0x7d836e77u, 0x00000000u}, + {0x00000001u, 0x5071950eu, 0x35d14880u, 0x00000000u}, + {0x00000000u, 0x406329bcu, 0x59001ac9u, 0x00000000u}, + {0x00000001u, 0xffb88309u, 0xede7bd0cu, 0x00000000u}, + {0x00000000u, 0xce1d62e0u, 0x60581ccau, 0x00000000u}, + {0x00000001u, 0xbef7e268u, 0x837a7935u, 0x00000000u}, + {0x00000001u, 0x1389b372u, 0xb91dddd9u, 0x00000000u}, + {0x00000000u, 0x16f44881u, 0x8eb5140fu, 0x00000000u}, + {0x00000001u, 0x4ac87fc0u, 0x49a047dcu, 0x00000000u}, + {0x00000000u, 0x24114258u, 0xe060a724u, 0x00000000u}, + {0x00000001u, 0xb437bdb5u, 0xb766ff10u, 0x00000000u}, + {0x00000001u, 0x4f1a8a1du, 0x06555096u, 0x00000000u}, + {0x00000001u, 0xb5fb12e0u, 0x5e6c9992u, 0x00000000u}, + {0x00000000u, 0x7608d942u, 0x6c167229u, 0x00000000u}, + {0x00000000u, 0x66160227u, 0xe41686cdu, 0x00000000u}, + {0x00000001u, 0x8df05f25u, 0x7f9ac098u, 0x00000000u}, + {0x00000001u, 0x1dcf884cu, 0x6ce9da66u, 0x00000000u}, + {0x00000001u, 0xcd73de00u, 0xc0099ebau, 0x00000000u}, + {0x00000001u, 0x99a46df0u, 0xe01c683eu, 0x00000000u}, + {0x00000000u, 0x65ed0de4u, 0xeab06e9bu, 0x00000000u}, + {0x00000001u, 0x423674a6u, 0xd82fb0f1u, 0x00000000u}, + {0x00000000u, 0x90624fe3u, 0xed1d9d16u, 0x00000000u}, + {0x00000001u, 0x7aac319fu, 0x8350a73fu, 0x00000000u}, + {0x00000001u, 0xce1fd3d9u, 0x067f0cfcu, 0x00000000u}, + {0x00000001u, 0x397a7623u, 0xc40d31b5u, 0x00000000u}, + {0x00000000u, 0xd1711cbdu, 0x0c83e786u, 0x00000000u}, + {0x00000001u, 0xb464c554u, 0x85f184e0u, 0x00000000u}, + {0x00000003u, 0xaba4c70eu, 0x1a42721eu, 0x00000000u}, + {0x00000001u, 0x9f4fb02bu, 0x6dedc86au, 0x00000000u}, + {0x00000000u, 0x2475263cu, 0x7b6bc3c6u, 0x00000000u}, + {0x00000001u, 0x3a9afa39u, 0xd9814559u, 0x00000000u}, + {0x00000001u, 0x1f3c42b2u, 0xa06d7a69u, 0x00000000u}, + {0x00000001u, 0x74f09af5u, 0xac9f21dfu, 0x00000000u}, + {0x00000000u, 0xb6eafff5u, 0xedf165dau, 0x00000000u}, + {0x00000001u, 0xfcbad167u, 0xfa97002cu, 0x00000000u}, + {0x00000001u, 0x4041f5eeu, 0x8f4ecb4fu, 0x00000000u}, + {0x00000000u, 0xab74fe57u, 0xff8b2a6au, 0x00000000u}, + {0x00000000u, 0x8c53765fu, 0xccbfd2ecu, 0x00000000u}, + {0x00000001u, 0x807c7e30u, 0xb2c55523u, 0x00000000u}, + {0x00000000u, 0xce0d8d87u, 0xe26b524au, 0x00000000u}, + {0x00000000u, 0x6edd77d8u, 0x33d91808u, 0x00000000u}, + {0x00000000u, 0xd4f398eeu, 0x44df8a13u, 0x00000000u}, + {0x00000000u, 0x0879d955u, 0x265c28eau, 0x00000000u}, + {0x00000001u, 0xe1cd7be8u, 0x98e6e5b7u, 0x00000000u}, + {0x00000001u, 0x754e64f4u, 0xcdb54088u, 0x00000000u}, + {0x00000001u, 0xed4d19b8u, 0x7d510557u, 0x00000000u}, + {0x00000000u, 0xebe80fa9u, 0x33288e16u, 0x00000000u}, + {0x00000000u, 0xb1515fffu, 0x7c4e4248u, 0x00000000u}, + {0x00000000u, 0x82e26123u, 0xc446ab8cu, 0x00000000u}, + {0x00000001u, 0x7f45b096u, 0xced66b44u, 0x00000000u}, + {0x00000000u, 0x8ece78b0u, 0xa9f5307cu, 0x00000000u} +}; + +constexpr std::uint32_t Expected32[32][14] = { + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x51c9cc7fu, 0x010138a0u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x8306e04au, 0x020d8026u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xf3f4a258u, 0x24880000u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x39293d31u, 0x42000804u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x9f19a513u, 0x1c310800u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xc88b3884u, 0x0106100au}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xd7a7b3dbu, 0x26090490u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xc4a344ceu, 0x89464052u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xc5e82909u, 0x084031c4u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x3d6c61f2u, 0x00482306u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x14aa5e80u, 0x281014e2u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xd11755bcu, 0x22228250u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x02f0fea8u, 0x01209120u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xaf67e470u, 0x5a0688c2u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x5071a51du, 0x20a20000u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x406339cbu, 0x80001110u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xffb89318u, 0xdb410210u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xce1d72efu, 0x80300180u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xbef7f277u, 0x04e4c040u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x1389c381u, 0x221322a0u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x16f45890u, 0x0d680002u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x4ac88fcfu, 0x91008f80u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x24115267u, 0x40000400u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xb437cdc4u, 0x684d7a20u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x4f1a9a2cu, 0x0c200028u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xb5fb22efu, 0x28d02100u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x7608e951u, 0xc800a000u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x66161236u, 0xc82c040au}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x8df06f34u, 0x1b208000u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x1dcf985bu, 0x19931088u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0xcd73ee0fu, 0x80033c00u}, + {0x51c9bd70u, 0x51c9e82au, 0x51c9bf70u, 0x0149ce87u, 0x80a4df69u, 0x0149ee87u, 0x80a4df0fu, 0x80a6df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a4df2du, 0x80a7df2du, 0x99a47dffu, 0x0008d060u} +}; +constexpr std::uint32_t Expected64[64][14] = { + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x51c9cc7fu, 0x010138a0u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x8306e04au, 0x020d8026u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xf3f4a258u, 0x24880000u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x39293d31u, 0x42000804u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x9f19a513u, 0x1c310800u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xc88b3884u, 0x0106100au}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xd7a7b3dbu, 0x26090490u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xc4a344ceu, 0x89464052u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xc5e82909u, 0x084031c4u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x3d6c61f2u, 0x00482306u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x14aa5e80u, 0x281014e2u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xd11755bcu, 0x22228250u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x02f0fea8u, 0x01209120u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xaf67e470u, 0x5a0688c2u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x5071a51du, 0x20a20000u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x406339cbu, 0x80001110u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xffb89318u, 0xdb410210u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xce1d72efu, 0x80300180u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xbef7f277u, 0x04e4c040u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x1389c381u, 0x221322a0u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x16f45890u, 0x0d680002u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x4ac88fcfu, 0x91008f80u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x24115267u, 0x40000400u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xb437cdc4u, 0x684d7a20u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x4f1a9a2cu, 0x0c200028u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xb5fb22efu, 0x28d02100u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x7608e951u, 0xc800a000u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x66161236u, 0xc82c040au}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x8df06f34u, 0x1b208000u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x1dcf985bu, 0x19931088u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xcd73ee0fu, 0x80033c00u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x99a47dffu, 0x0008d060u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x65ed1df3u, 0xc1401900u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x423684b5u, 0x804c6140u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x90625ff2u, 0x00001a04u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x7aac41aeu, 0x0400423eu}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xce1fe3e8u, 0x0c3e01b0u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x397a8632u, 0x00106042u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xd1712cccu, 0x00020908u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xb464d563u, 0x08c10880u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xaba4d71du, 0x1400841cu}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x9f4fc03au, 0x1a9b0054u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x2475364bu, 0x40c20408u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x3a9b0a48u, 0x31008032u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x1f3c52c1u, 0x00588440u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x74f0ab04u, 0x492001aau}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xb6eb1004u, 0x49c0cba0u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xfcbae176u, 0xf1240048u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x404205fdu, 0x0081829cu}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xab750e66u, 0x56005484u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x8c53866eu, 0x1826a498u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x807c8e3fu, 0x0088a840u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xce0d9d96u, 0x84120004u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x6edd87e7u, 0x45b22010u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xd4f3a8fdu, 0x89a71004u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x0879e964u, 0x00b01080u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xe1cd8bf7u, 0x0188c340u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x754e7503u, 0x8a088100u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xed4d29c7u, 0xda820220u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xebe81fb8u, 0x46501c00u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0xb151700eu, 0x60808490u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x82e27132u, 0x00844200u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x7f45c0a5u, 0x9c884008u}, + {0x51c9bd70u, 0x51c9be70u, 0x51c9bf70u, 0x0149ce87u, 0x0149de87u, 0x0149ee87u, 0x80a4df0fu, 0x80a4df0fu, 0x80a4df0fu, 0x80a5df2du, 0x80a6df2du, 0x80a7df2du, 0x8ece88bfu, 0x11886060u} +}; + +constexpr const char* Names[Checked] = {"execnz_partial", "execnz_upper_half", "execnz_lower_lane", "execz_partial", "execz_upper_half", "execz_lower_lane", "vccnz_partial", "vccnz_upper_half", "vccnz_lower_lane", "vccz_partial", "vccz_upper_half", "vccz_lower_lane", "cdbg_not_taken", "hints"}; +constexpr bool CrossLane[Checked] = {true, true, true, true, true, true, true, true, true, true, true, true, false, false}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +ShaderRecompiler::RecompileResult Compile(std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + const auto result = Compile(code, waveSize, target); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked], bool wholeWave) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + if (CrossLane[index] && !wholeWave) continue; + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "program flow wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const bool wholeWave = device->Target().subgroupSize >= 32u; + if (!wholeWave) std::printf("subgroup size %u cannot hold a wave32, checking only the columns without a wave-wide branch\n", device->Target().subgroupSize); + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32, wholeWave); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64, wholeWave); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64, wholeWave); + std::puts("scalar program flow tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSetpc.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSetpc.cpp new file mode 100644 index 000000000..bc390fee3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSetpc.cpp @@ -0,0 +1,316 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t MaxThreads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Checked = 5; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Wave32Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeadbeef, 0x7e1602ff, + 0xdeadbeef, 0x7e1802ff, 0xdeadbeef, 0x7e1a02ff, 0xdeadbeef, 0x7e1c02ff, 0xdeadbeef, 0xbe940380, + 0xd7600008, 0x02002904, 0xd760000b, 0x02002905, 0xd760000a, 0x02002906, 0xbe930308, 0x83888308, + 0x8f088308, 0xbe8c1f00, 0x800cff0c, 0x00000178, 0x820d800d, 0xf4040386, 0x10000000, 0xbf8cc07f, + 0xbe901f00, 0x80100e10, 0x82110f11, 0xbe802010, 0x8009ff0a, 0x11110000, 0xbf820008, 0x8009ff0a, + 0x22220000, 0xbf820005, 0x8009ff0a, 0x33330000, 0xbf820002, 0x8009ff0a, 0x44440000, 0xd761000a, + 0x02002809, 0xbe880313, 0x83888208, 0x8f088308, 0xbe981f00, 0x8018ff18, 0x00000124, 0x82198019, + 0xf404068c, 0x10000008, 0xbf8cc07f, 0xbe9c1f00, 0x801c1a1c, 0x821d1b1d, 0xbe80201c, 0x8909ff0a, + 0x0000a000, 0xbf820006, 0x8909ff0a, 0x0000b000, 0xbf820003, 0x8909ff0a, 0x0000c000, 0xbf820000, + 0xd761000b, 0x02002809, 0xbe89030a, 0xbe901f00, 0x8010ff10, 0x00000018, 0xbe802010, 0xbe8903ff, + 0x00000bad, 0x8909c109, 0x80098109, 0xd761000c, 0x02002809, 0x8712870b, 0xbe890380, 0x80090a09, + 0x80928112, 0xbf06c112, 0xbf850004, 0xbe901f00, 0x8110ff10, 0xffffffec, 0xbe802010, 0xd761000d, + 0x02002809, 0x9012840b, 0x87128312, 0xbe89030a, 0x8f098109, 0x8909ff09, 0x9e3779b9, 0xbf068012, + 0xbf850005, 0x80928112, 0xbe901f00, 0x8090ff10, 0x0000001c, 0xbe802010, 0xd761000e, 0x02002809, + 0x80148114, 0xbf0aa014, 0xbf85ff9d, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xbf810000, + 0x0000000c, 0x00000000, 0x00000018, 0x00000000, 0x00000024, 0x00000000, 0x00000030, 0x00000000, + 0x0000000c, 0x00000000, 0x00000018, 0x00000000, 0x00000024, 0x00000000, +}; + +alignas(256) constexpr std::array Wave64Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeadbeef, 0x7e1602ff, + 0xdeadbeef, 0x7e1802ff, 0xdeadbeef, 0x7e1a02ff, 0xdeadbeef, 0x7e1c02ff, 0xdeadbeef, 0xbe940380, + 0xd7600008, 0x02002904, 0xd760000b, 0x02002905, 0xd760000a, 0x02002906, 0xbe930308, 0x83888308, + 0x8f088308, 0xbe8c1f00, 0x800cff0c, 0x00000178, 0x820d800d, 0xf4040386, 0x10000000, 0xbf8cc07f, + 0xbe901f00, 0x80100e10, 0x82110f11, 0xbe802010, 0x8009ff0a, 0x11110000, 0xbf820008, 0x8009ff0a, + 0x22220000, 0xbf820005, 0x8009ff0a, 0x33330000, 0xbf820002, 0x8009ff0a, 0x44440000, 0xd761000a, + 0x02002809, 0xbe880313, 0x83888208, 0x8f088308, 0xbe981f00, 0x8018ff18, 0x00000124, 0x82198019, + 0xf404068c, 0x10000008, 0xbf8cc07f, 0xbe9c1f00, 0x801c1a1c, 0x821d1b1d, 0xbe80201c, 0x8909ff0a, + 0x0000a000, 0xbf820006, 0x8909ff0a, 0x0000b000, 0xbf820003, 0x8909ff0a, 0x0000c000, 0xbf820000, + 0xd761000b, 0x02002809, 0xbe89030a, 0xbe901f00, 0x8010ff10, 0x00000018, 0xbe802010, 0xbe8903ff, + 0x00000bad, 0x8909c109, 0x80098109, 0xd761000c, 0x02002809, 0x8712870b, 0xbe890380, 0x80090a09, + 0x80928112, 0xbf06c112, 0xbf850004, 0xbe901f00, 0x8110ff10, 0xffffffec, 0xbe802010, 0xd761000d, + 0x02002809, 0x9012840b, 0x87128312, 0xbe89030a, 0x8f098109, 0x8909ff09, 0x9e3779b9, 0xbf068012, + 0xbf850005, 0x80928112, 0xbe901f00, 0x8090ff10, 0x0000001c, 0xbe802010, 0xd761000e, 0x02002809, + 0x80148114, 0xbf0ac014, 0xbf85ff9d, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xbf810000, + 0x0000000c, 0x00000000, 0x00000018, 0x00000000, 0x00000024, 0x00000000, 0x00000030, 0x00000000, + 0x0000000c, 0x00000000, 0x00000018, 0x00000000, 0x00000024, 0x00000000, +}; + +alignas(256) constexpr std::array ReversedSubtractCode{ + 0xbe901f00, 0x809010c0, 0xbe802010, 0xbf810000, +}; + +constexpr std::uint32_t Rows[64][4] = { + {0x00000000u, 0x1e7ea419u, 0x51c9bc70u, 0x00000000u}, + {0x00000001u, 0x80a4df5au, 0xf38b2ffcu, 0x00000000u}, + {0x00000002u, 0x8306d03bu, 0xa5aec797u, 0x00000000u}, + {0x00000003u, 0xdc28ff90u, 0xf3f49249u, 0x00000000u}, + {0x00000004u, 0x1a466884u, 0xe255accbu, 0x00000000u}, + {0x00000005u, 0x39292d22u, 0xe5121482u, 0x00000000u}, + {0x00000007u, 0x99dd251du, 0x9f199504u, 0x00000000u}, + {0x00000008u, 0x8e7aa6e9u, 0x6bad6be2u, 0x00000000u}, + {0x00000010u, 0xc88b2875u, 0x9293de8fu, 0x00000000u}, + {0x7fffffffu, 0x8c3d5f16u, 0xd7a7a3ccu, 0x00000000u}, + {0x80000000u, 0xbb049a79u, 0xc6cd75e9u, 0x00000000u}, + {0xfffffff8u, 0xc4a334bfu, 0x7dabe929u, 0x00000000u}, + {0xffffffffu, 0xc0433cbdu, 0xc5e818fau, 0x00000000u}, + {0x00000000u, 0x96263ae6u, 0x70eb9a0au, 0x00000000u}, + {0x00000001u, 0x3d6c51e3u, 0x00a61f93u, 0x00000000u}, + {0x00000002u, 0x9d3c7decu, 0x14aa4e71u, 0x00000000u}, + {0x00000003u, 0x1c593af5u, 0x49889310u, 0x00000000u}, + {0x00000004u, 0xd11745adu, 0x1919e93au, 0x00000000u}, + {0x00000005u, 0x731c9452u, 0x02f0ee99u, 0x00000000u}, + {0x00000007u, 0xd0944996u, 0xe4163207u, 0x00000000u}, + {0x00000008u, 0xaf67d461u, 0x7d836e77u, 0x00000000u}, + {0x00000010u, 0xadec6f11u, 0x5071950eu, 0x00000000u}, + {0x7fffffffu, 0x35d14880u, 0x65b00a2du, 0x00000000u}, + {0x80000000u, 0x406329bcu, 0x59001ac9u, 0x00000000u}, + {0xfffffff8u, 0xfadb8908u, 0xffb88309u, 0x00000000u}, + {0xffffffffu, 0xede7bd0cu, 0x5b4c8012u, 0x00000000u}, + {0xffffffffu, 0x60581ccau, 0xffe976abu, 0x00000000u}, + {0xfffffff8u, 0x837a7935u, 0xa341738cu, 0x00000000u}, + {0x00000001u, 0xb91dddd9u, 0x57241955u, 0x00000000u}, + {0x00000001u, 0x8eb5140fu, 0x89be9c1cu, 0x00000000u}, + {0x00000004u, 0x49a047dcu, 0x751b4c83u, 0x00000000u}, + {0x00000002u, 0xe060a724u, 0xa51149bbu, 0x00000000u}, + {0xfffffff8u, 0xb766ff10u, 0x93d20470u, 0x00000000u}, + {0x00000004u, 0x06555096u, 0xd9090b89u, 0x00000000u}, + {0xfffffff8u, 0x5e6c9992u, 0x5d111a9du, 0x00000000u}, + {0x00000008u, 0x6c167229u, 0x17371472u, 0x00000000u}, + {0x00000007u, 0xe41686cdu, 0x95f19a51u, 0x00000000u}, + {0x00000010u, 0x7f9ac098u, 0xde0279e1u, 0x00000000u}, + {0x00000001u, 0x6ce9da66u, 0x81a0ba05u, 0x00000000u}, + {0xffffffffu, 0xc0099ebau, 0xdde3a361u, 0x00000000u}, + {0x7fffffffu, 0xe01c683eu, 0x7db4304du, 0x00000000u}, + {0x00000007u, 0xeab06e9bu, 0x864fa3f3u, 0x00000000u}, + {0x00000004u, 0xd82fb0f1u, 0x6b82e6c9u, 0x00000000u}, + {0x7fffffffu, 0xed1d9d16u, 0xfb759e0fu, 0x00000000u}, + {0x00000008u, 0x8350a73fu, 0x849acfb5u, 0x00000000u}, + {0xffffffffu, 0x067f0cfcu, 0x93550840u, 0x00000000u}, + {0x00000003u, 0xc40d31b5u, 0x2106119eu, 0x00000000u}, + {0x00000000u, 0xf675299bu, 0xb464c554u, 0x00000000u}, + {0x00000010u, 0xe306f0c4u, 0xaba4c70eu, 0x00000000u}, + {0x00000001u, 0xb7a1774fu, 0x9f4fb02bu, 0x00000000u}, + {0x00000007u, 0x78549014u, 0x2475263cu, 0x00000000u}, + {0x00000008u, 0xe261e34au, 0x3a9afa39u, 0x00000000u}, + {0x00000001u, 0xa06d7a69u, 0xb3618e1cu, 0x00000000u}, + {0x00000008u, 0xac9f21dfu, 0x782afe6bu, 0x00000000u}, + {0xfffffff8u, 0xedf165dau, 0xf5a9ca5fu, 0x00000000u}, + {0x00000010u, 0x4041f5eeu, 0x8f4ecb4fu, 0x00000000u}, + {0x00000007u, 0xab74fe57u, 0xff8b2a6au, 0x00000000u}, + {0x00000004u, 0x8c53765fu, 0xccbfd2ecu, 0x00000000u}, + {0x80000000u, 0x807c7e30u, 0xb2c55523u, 0x00000000u}, + {0x00000008u, 0xce0d8d87u, 0xe26b524au, 0x00000000u}, + {0x00000007u, 0x6edd77d8u, 0x33d91808u, 0x00000000u}, + {0x00000004u, 0xd4f398eeu, 0x44df8a13u, 0x00000000u}, + {0x00000000u, 0x0879d955u, 0x265c28eau, 0x00000000u}, + {0x80000000u, 0xe1cd7be8u, 0x98e6e5b7u, 0x00000000u} +}; + +constexpr std::uint32_t Expected32[32][5] = { + {0x62dabc70u, 0x51c91c70u, 0x51c9bc71u, 0xa39378e0u, 0xe57f7b0bu}, + {0x15ad2ffcu, 0xf38b9ffcu, 0xf38b2ffdu, 0xdaa18ff4u, 0x6c75353bu}, + {0xd8e1c797u, 0xa5ae0797u, 0xa5aec798u, 0x96bb1e5cu, 0x71d3d897u}, + {0x38389249u, 0xf3f45249u, 0xf3f4924au, 0xf3f49249u, 0x6d8bc3efu}, + {0x2699accbu, 0xe2556ccbu, 0xe255acccu, 0x6bac5ff7u, 0x5a9c202fu}, + {0x29561482u, 0xe512d482u, 0xe5121483u, 0xaf363d86u, 0xf214c83fu}, + {0xe35d9504u, 0x9f195504u, 0x9f199505u, 0xba997e18u, 0xde3fdedbu}, + {0xaff16be2u, 0x6badabe2u, 0x6bad6be3u, 0xd75ad7c4u, 0x87ef333fu}, + {0xd6d7de8fu, 0x92931e8fu, 0x9293de90u, 0x6f77375au, 0x02024917u}, + {0x1beba3ccu, 0xd7a763ccu, 0xd7a7a3cdu, 0xe5957a94u, 0xfcc705fbu}, + {0x0b1175e9u, 0xc6cdb5e9u, 0xc6cd75eau, 0x8d9aebd2u, 0x47e8ff77u}, + {0xc1efe929u, 0x7dab2929u, 0x7dabe92au, 0xed5f4948u, 0xf1813377u}, + {0x0a2c18fau, 0xc5e8d8fau, 0xc5e818fbu, 0xa37095dcu, 0x75be2e47u}, + {0x81fc9a0au, 0x70eb3a0au, 0x70eb9a0bu, 0x16713646u, 0x5dd8bc7fu}, + {0x22c81f93u, 0x00a6af93u, 0x00a61f94u, 0x02987e4cu, 0xdfb490b7u}, + {0x47dd4e71u, 0x14aa8e71u, 0x14aa4e72u, 0x67538835u, 0x7fd61fa7u}, + {0x8dcc9310u, 0x49885310u, 0x49889311u, 0xb9337260u, 0xb3b690e7u}, + {0x5d5de93au, 0x1919293au, 0x1919e93bu, 0x969b775cu, 0x124b25ffu}, + {0x4734ee99u, 0x02f02e99u, 0x02f0ee9au, 0x08d2cbcbu, 0xa99a30afu}, + {0x285a3207u, 0xe416f207u, 0xe4163208u, 0x3c9b5e31u, 0x320142d7u}, + {0xc1c76e77u, 0x7d83ae77u, 0x7d836e78u, 0xfb06dceeu, 0x369f1f97u}, + {0x94b5950eu, 0x5071550eu, 0x5071950fu, 0xa0e32a1cu, 0xe39fdef3u}, + {0xa9f40a2du, 0x65b0ca2du, 0x65b00a2eu, 0x65b00a2du, 0x55576de3u}, + {0x9d441ac9u, 0x5900dac9u, 0x59001acau, 0xbd0085edu, 0xbb3e0d77u}, + {0x43fc8309u, 0xffb84309u, 0xffb8830au, 0xffb88309u, 0x61467fabu}, + {0x9f908012u, 0x5b4c4012u, 0x5b4c8013u, 0xc87e805au, 0x28ae799du}, + {0x442d76abu, 0xffe9b6abu, 0xffe976acu, 0xffbc6401u, 0x61e594efu}, + {0xe785738cu, 0xa341b38cu, 0xa341738du, 0xd388b548u, 0x1f289927u}, + {0x79461955u, 0x5724a955u, 0x57241956u, 0xae4832aau, 0xfec9ef9fu}, + {0xabe09c1cu, 0x89be2c1cu, 0x89be9c1du, 0x4df4e0e0u, 0x8d4a4181u}, + {0xb95f4c83u, 0x751b8c83u, 0x751b4c84u, 0x49887e8fu, 0x7634b8c7u}, + {0xd84449bbu, 0xa51189bbu, 0xa51149bcu, 0x395670a7u, 0xf20e21f7u} +}; +constexpr std::uint32_t Expected64[64][5] = { + {0x62dabc70u, 0x51c91c70u, 0x51c9bc71u, 0xa39378e0u, 0xe57f7b0bu}, + {0x15ad2ffcu, 0xf38b9ffcu, 0xf38b2ffdu, 0xdaa18ff4u, 0x6c75353bu}, + {0xd8e1c797u, 0xa5ae0797u, 0xa5aec798u, 0x96bb1e5cu, 0x71d3d897u}, + {0x38389249u, 0xf3f45249u, 0xf3f4924au, 0xf3f49249u, 0x6d8bc3efu}, + {0x2699accbu, 0xe2556ccbu, 0xe255acccu, 0x6bac5ff7u, 0x5a9c202fu}, + {0x29561482u, 0xe512d482u, 0xe5121483u, 0xaf363d86u, 0xf214c83fu}, + {0xe35d9504u, 0x9f195504u, 0x9f199505u, 0xba997e18u, 0xde3fdedbu}, + {0xaff16be2u, 0x6badabe2u, 0x6bad6be3u, 0xd75ad7c4u, 0x87ef333fu}, + {0xd6d7de8fu, 0x92931e8fu, 0x9293de90u, 0x6f77375au, 0x02024917u}, + {0x1beba3ccu, 0xd7a763ccu, 0xd7a7a3cdu, 0xe5957a94u, 0xfcc705fbu}, + {0x0b1175e9u, 0xc6cdb5e9u, 0xc6cd75eau, 0x8d9aebd2u, 0x47e8ff77u}, + {0xc1efe929u, 0x7dab2929u, 0x7dabe92au, 0xed5f4948u, 0xf1813377u}, + {0x0a2c18fau, 0xc5e8d8fau, 0xc5e818fbu, 0xa37095dcu, 0x75be2e47u}, + {0x81fc9a0au, 0x70eb3a0au, 0x70eb9a0bu, 0x16713646u, 0x5dd8bc7fu}, + {0x22c81f93u, 0x00a6af93u, 0x00a61f94u, 0x02987e4cu, 0xdfb490b7u}, + {0x47dd4e71u, 0x14aa8e71u, 0x14aa4e72u, 0x67538835u, 0x7fd61fa7u}, + {0x8dcc9310u, 0x49885310u, 0x49889311u, 0xb9337260u, 0xb3b690e7u}, + {0x5d5de93au, 0x1919293au, 0x1919e93bu, 0x969b775cu, 0x124b25ffu}, + {0x4734ee99u, 0x02f02e99u, 0x02f0ee9au, 0x08d2cbcbu, 0xa99a30afu}, + {0x285a3207u, 0xe416f207u, 0xe4163208u, 0x3c9b5e31u, 0x320142d7u}, + {0xc1c76e77u, 0x7d83ae77u, 0x7d836e78u, 0xfb06dceeu, 0x369f1f97u}, + {0x94b5950eu, 0x5071550eu, 0x5071950fu, 0xa0e32a1cu, 0xe39fdef3u}, + {0xa9f40a2du, 0x65b0ca2du, 0x65b00a2eu, 0x65b00a2du, 0x55576de3u}, + {0x9d441ac9u, 0x5900dac9u, 0x59001acau, 0xbd0085edu, 0xbb3e0d77u}, + {0x43fc8309u, 0xffb84309u, 0xffb8830au, 0xffb88309u, 0x61467fabu}, + {0x9f908012u, 0x5b4c4012u, 0x5b4c8013u, 0xc87e805au, 0x28ae799du}, + {0x442d76abu, 0xffe9b6abu, 0xffe976acu, 0xffbc6401u, 0x61e594efu}, + {0xe785738cu, 0xa341b38cu, 0xa341738du, 0xd388b548u, 0x1f289927u}, + {0x79461955u, 0x5724a955u, 0x57241956u, 0xae4832aau, 0xfec9ef9fu}, + {0xabe09c1cu, 0x89be2c1cu, 0x89be9c1du, 0x4df4e0e0u, 0x8d4a4181u}, + {0xb95f4c83u, 0x751b8c83u, 0x751b4c84u, 0x49887e8fu, 0x7634b8c7u}, + {0xd84449bbu, 0xa51189bbu, 0xa51149bcu, 0x395670a7u, 0xf20e21f7u}, + {0xd8160470u, 0x93d2c470u, 0x93d20471u, 0x93d20470u, 0xed119b0bu}, + {0x1d4d0b89u, 0xd909cb89u, 0xd9090b8au, 0xef3f50bfu, 0xc67da4efu}, + {0xa1551a9du, 0x5d11da9du, 0x5d111a9eu, 0x17334fd7u, 0xd61de0bfu}, + {0x5b7b1472u, 0x1737d472u, 0x17371473u, 0x2e6e28e4u, 0x633ccfbfu}, + {0xda359a51u, 0x95f15a51u, 0x95f19a52u, 0x83a99de6u, 0xb5d44d1bu}, + {0x224679e1u, 0xde02b9e1u, 0xde0279e2u, 0xde0279e1u, 0xda506d4fu}, + {0xa3c2ba05u, 0x81a00a05u, 0x81a0ba06u, 0x8b651623u, 0xd781bc07u}, + {0x2227a361u, 0xdde36361u, 0xdde3a362u, 0x99aaea23u, 0xf50597f7u}, + {0xc1f8304du, 0x7db4f04du, 0x7db4304eu, 0x6fed521bu, 0xf07ca537u}, + {0xca93a3f3u, 0x864f63f3u, 0x864fa3f4u, 0x193e8fccu, 0xbb670507u}, + {0xafc6e6c9u, 0x6b8226c9u, 0x6b82e6cau, 0xd705cd92u, 0x9311cd77u}, + {0x3fb99e0fu, 0xfb755e0fu, 0xfb759e10u, 0xe0375269u, 0x4f8ff2f7u}, + {0xc8decfb5u, 0x849a0fb5u, 0x849acfb6u, 0x24d67da8u, 0x62935ab7u}, + {0xd7990840u, 0x9355c840u, 0x93550841u, 0xe0a92940u, 0x1e6f25e7u}, + {0x654a119eu, 0x2106d19eu, 0x2106119fu, 0xc62469b4u, 0x3b5eb807u}, + {0xc575c554u, 0xb4646554u, 0xb464c555u, 0xd1931550u, 0x73ca9f9bu}, + {0xefe8c70eu, 0xaba4070eu, 0xaba4c70fu, 0x5a37e346u, 0xc97ef7a5u}, + {0xc171b02bu, 0x9f4f002bu, 0x9f4fb02cu, 0xfa7d8158u, 0xa0a819efu}, + {0x68b9263cu, 0x2475e63cu, 0x2475263du, 0xb649bf2cu, 0x338d123bu}, + {0x7edefa39u, 0x3a9a3a39u, 0x3a9afa3au, 0xafd0eeabu, 0xeb028dcbu}, + {0xd5838e1cu, 0xb3613e1cu, 0xb3618e1du, 0x66c31c38u, 0x41881ccfu}, + {0xbc6efe6bu, 0x782a3e6bu, 0x782afe6cu, 0xc157f358u, 0x42f27367u}, + {0x39edca5fu, 0xf5a90a5fu, 0xf5a9ca60u, 0xe0fd5f1du, 0x74fea3b7u}, + {0xd392cb4fu, 0x8f4e0b4fu, 0x8f4ecb50u, 0xeb278f29u, 0xa0f23657u}, + {0x43cf2a6au, 0xff8bea6au, 0xff8b2a6bu, 0xfc595350u, 0x5c752363u}, + {0x1103d2ecu, 0xccbf12ecu, 0xccbfd2edu, 0x65fe9760u, 0x90a6c17bu}, + {0xf7095523u, 0xb2c59523u, 0xb2c55524u, 0xb2c55523u, 0x076af3d7u}, + {0x26af524au, 0xe26b924au, 0xe26b524bu, 0x135a9250u, 0x5ae1dd2du}, + {0x781d1808u, 0x33d9d808u, 0x33d91809u, 0x33d91808u, 0x6d3deaebu}, + {0x89238a13u, 0x44df4a13u, 0x44df8a14u, 0xe21cc685u, 0xfc783cb7u}, + {0x376d28eau, 0x265c88eau, 0x265c28ebu, 0xe628f57cu, 0x3b292963u}, + {0xdd2ae5b7u, 0x98e625b7u, 0x98e6e5b8u, 0x98e6e5b7u, 0x1db34197u} +}; + +constexpr const char* Names[Checked] = {"jump_table", "jump_table_offset", "skip_forward", "loop_add_negative", "loop_sub"}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +ShaderRecompiler::RecompileResult Compile(std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) { + for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + const auto result = Compile(code, waveSize, target); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckReversedSubtractRefused(AgcDriver::VulkanDevice& device) { + std::string refusal; + try { + static_cast(Compile(ReversedSubtractCode, 32, device.ComputeTarget(32))); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("unsupported dynamic s_setpc_b64") != std::string::npos, "s_setpc_b64 after s_sub_u32 with the immediate first was not refused"); +} + +template +void Check(const std::uint32_t (&expected)[Lanes][Checked]) { + for (std::uint32_t tid = 0; tid < Lanes; ++tid) { + for (std::uint32_t index = 0; index < Checked; ++index) { + const auto actual = Output[tid * Results + index]; + Require(actual == expected[tid][index], "setpc wave" + std::to_string(Lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected[tid][index])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device, Wave32Code, 32, device->ComputeTarget(32)); + Check(Expected32); + Run(*device, Wave64Code, 64, device->Target()); + Check(Expected64); + Run(*device, Wave64Code, 64, device->ComputeTarget(32)); + Check(Expected64); + CheckReversedSubtractRefused(*device); + std::puts("setpc tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSop1.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSop1.cpp new file mode 100644 index 000000000..c26c22609 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSop1.cpp @@ -0,0 +1,274 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 176; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x160600ff, 0x000002c0, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe940380, 0xd7600008, + 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, 0xd760000b, 0x02002907, 0xbe9e0380, + 0xbf060b0b, 0xbe8c0408, 0x821e1e1e, 0xd761000a, 0x0200280c, 0xd761000b, 0x0200280d, 0xbe8c040a, + 0xbe8e0408, 0x871f810b, 0xbe8c0508, 0x821e1e1e, 0xd761000c, 0x0200280c, 0xbe8c040a, 0xbe8e0408, + 0x871f810b, 0xbe8c0608, 0x821e1e1e, 0xd761000d, 0x0200280c, 0xd761000e, 0x0200280d, 0xbe8c0708, + 0x821e1e1e, 0xd761000f, 0x0200280c, 0xbe8c0808, 0x821e1e1e, 0xd7610010, 0x0200280c, 0xd7610011, + 0x0200280d, 0xbe8c0908, 0x821e1e1e, 0xd7610012, 0x0200280c, 0xbe8c0a08, 0x821e1e1e, 0xd7610013, + 0x0200280c, 0xd7610014, 0x0200280d, 0xbf060b0b, 0xbe8c0b08, 0x821e1e1e, 0xd7610015, 0x0200280c, + 0xbf060b0b, 0xbe8c0c08, 0x821e1e1e, 0xd7610016, 0x0200280c, 0xd7610017, 0x0200280d, 0xbe8c0d08, + 0x821e1e1e, 0xd7610018, 0x0200280c, 0xd7610019, 0x0200281e, 0x80148114, 0xbf0aa014, 0xbf85ffb7, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, 0xd760000b, + 0x02002907, 0xbe9e0380, 0xbe8c0e08, 0x821e1e1e, 0xd761000a, 0x0200280c, 0xbe8c0f08, 0x821e1e1e, + 0xd761000b, 0x0200280c, 0xbe8c1008, 0x821e1e1e, 0xd761000c, 0x0200280c, 0xbf060b0b, 0xbe8c1108, + 0x821e1e1e, 0xd761000d, 0x0200280c, 0xbf060b0b, 0xbe8c1208, 0x821e1e1e, 0xd761000e, 0x0200280c, + 0xbf060b0b, 0xbe8c1308, 0x821e1e1e, 0xd761000f, 0x0200280c, 0xbf060b0b, 0xbe8c1408, 0x821e1e1e, + 0xd7610010, 0x0200280c, 0xbf060b0b, 0xbe8c1508, 0x821e1e1e, 0xd7610011, 0x0200280c, 0xbf060b0b, + 0xbe8c1608, 0x821e1e1e, 0xd7610012, 0x0200280c, 0xbf060b0b, 0xbe8c1708, 0x821e1e1e, 0xd7610013, + 0x0200280c, 0xbf060b0b, 0xbe8c1808, 0x821e1e1e, 0xd7610014, 0x0200280c, 0xbf060b0b, 0xbe8c1908, + 0x821e1e1e, 0xd7610015, 0x0200280c, 0xbf060b0b, 0xbe8c1a08, 0x821e1e1e, 0xd7610016, 0x0200280c, + 0xbe8c040a, 0xbe8e0408, 0xbf060b0b, 0xbe8c1b0a, 0x821e1e1e, 0xd7610017, 0x0200280c, 0xbe8c040a, + 0xbe8e0408, 0xbf060b0b, 0xbe8c1d0a, 0x821e1e1e, 0xd7610018, 0x0200280c, 0xd7610019, 0x0200281e, + 0x80148114, 0xbf0aa014, 0xbf85ffa6, 0xe0781040, 0x80010a03, 0xe0781050, 0x80010e03, 0xe0781060, + 0x80011203, 0xe0781070, 0x80011603, 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, + 0xd760000a, 0x02002906, 0xd760000b, 0x02002907, 0xbe9e0380, 0xbe8c040a, 0xbe8e0408, 0xbf060b0b, + 0xbe8c1c0a, 0x821e1e1e, 0xd761000a, 0x0200280c, 0xd761000b, 0x0200280d, 0xbe8c040a, 0xbe8e0408, + 0xbf060b0b, 0xbe8c1e0a, 0x821e1e1e, 0xd761000c, 0x0200280c, 0xd761000d, 0x0200280d, 0xbe8c2c08, + 0x821e1e1e, 0xd761000e, 0x0200280c, 0xbe8c2d08, 0x821e1e1e, 0xd761000f, 0x0200280c, 0xd7610010, + 0x0200280d, 0xbe8c3408, 0x821e1e1e, 0xd7610011, 0x0200280c, 0xbf060b0b, 0xbe8c3b08, 0x821e1e1e, + 0xd7610012, 0x0200280c, 0xd7610013, 0x0200280d, 0x877c830a, 0xbf060b0b, 0xbe8c2e08, 0x821e1e1e, + 0xd7610014, 0x0200280c, 0x877c820a, 0xbf060b0b, 0xbe8c2f08, 0x821e1e1e, 0xd7610015, 0x0200280c, + 0xd7610016, 0x0200280d, 0xd7610019, 0x0200281e, 0x80148114, 0xbf0aa014, 0xbf85ffbd, 0xe0781080, + 0x80010a03, 0xe0781090, 0x80010e03, 0xe07810a0, 0x80011203, 0xe07810b0, 0x80011603, 0xbe940380, + 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, 0xd760000b, 0x02002907, + 0xbe9e0380, 0xbe8c040a, 0xbe8e0408, 0x877c830a, 0xbf060b0b, 0xbe8c3008, 0x821e1e1e, 0xd761000a, + 0x0200280c, 0xd761000b, 0x0200280d, 0xd761000c, 0x0200280e, 0xd761000d, 0x0200280f, 0xbe8c040a, + 0xbe8e0408, 0x877c820a, 0xbf060b0b, 0xbe8c3108, 0x821e1e1e, 0xd761000e, 0x0200280c, 0xd761000f, + 0x0200280d, 0xd7610010, 0x0200280e, 0xd7610011, 0x0200280f, 0xbe8c040a, 0xbe8e0408, 0x877cff0a, + 0x00030003, 0xbf060b0b, 0xbe8c4908, 0x821e1e1e, 0xd7610012, 0x0200280c, 0xd7610013, 0x0200280d, + 0xd7610014, 0x0200280e, 0xd7610015, 0x0200280f, 0xbefe0309, 0xbe8c3c08, 0xbe8d037e, 0xbefe03c1, + 0x821e1e1e, 0xd7610016, 0x0200280c, 0xd7610017, 0x0200280d, 0xd7610019, 0x0200281e, 0x80148114, + 0xbf0aa014, 0xbf85ffbe, 0xe07810c0, 0x80010a03, 0xe07810d0, 0x80010e03, 0xe07810e0, 0x80011203, + 0xe07810f0, 0x80011603, 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, + 0x02002906, 0xd760000b, 0x02002907, 0xbe9e0380, 0xbefe0309, 0xbe8c3d08, 0xbe8d037e, 0xbefe03c1, + 0x821e1e1e, 0xd761000a, 0x0200280c, 0xd761000b, 0x0200280d, 0xbefe0309, 0xbe8c3e08, 0xbe8d037e, + 0xbefe03c1, 0x821e1e1e, 0xd761000c, 0x0200280c, 0xd761000d, 0x0200280d, 0xbefe0309, 0xbe8c3f08, + 0xbe8d037e, 0xbefe03c1, 0x821e1e1e, 0xd761000e, 0x0200280c, 0xd761000f, 0x0200280d, 0xbefe0309, + 0xbe8c4008, 0xbe8d037e, 0xbefe03c1, 0x821e1e1e, 0xd7610010, 0x0200280c, 0xd7610011, 0x0200280d, + 0xbefe0309, 0xbe8c4108, 0xbe8d037e, 0xbefe03c1, 0x821e1e1e, 0xd7610012, 0x0200280c, 0xd7610013, + 0x0200280d, 0xbefe0309, 0xbe8c4208, 0xbe8d037e, 0xbefe03c1, 0x821e1e1e, 0xd7610014, 0x0200280c, + 0xd7610015, 0x0200280d, 0xbefe0309, 0xbe8c4308, 0xbe8d037e, 0xbefe03c1, 0x821e1e1e, 0xd7610016, + 0x0200280c, 0xd7610017, 0x0200280d, 0xd7610019, 0x0200281e, 0x80148114, 0xbf0aa014, 0xbf85ffb3, + 0xe0781100, 0x80010a03, 0xe0781110, 0x80010e03, 0xe0781120, 0x80011203, 0xe0781130, 0x80011603, + 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, 0xd760000b, + 0x02002907, 0xbe9e0380, 0xbefe0309, 0xbe8c4408, 0xbe8d037e, 0xbefe03c1, 0x821e1e1e, 0xd761000a, + 0x0200280c, 0xd761000b, 0x0200280d, 0xbefe0309, 0xbe8c4508, 0xbe8d037e, 0xbefe03c1, 0x821e1e1e, + 0xd761000c, 0x0200280c, 0xd761000d, 0x0200280d, 0xbefe0309, 0xbe8c4608, 0xbe8d037e, 0xbefe03c1, + 0x821e1e1e, 0xd761000e, 0x0200280c, 0xd761000f, 0x0200280d, 0xbefe0309, 0xbe8c4708, 0xbe8d037e, + 0xbefe03c1, 0x821e1e1e, 0xd7610010, 0x0200280c, 0xd7610011, 0x0200280d, 0xbefe030a, 0xbeff030b, + 0xbe8c2408, 0xbe8e037e, 0xbe8f037f, 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd7610012, 0x0200280c, + 0xd7610013, 0x0200280d, 0xd7610014, 0x0200280e, 0xd7610015, 0x0200280f, 0xd7610019, 0x0200281e, + 0x80148114, 0xbf0aa014, 0xbf85ffbe, 0xe0781140, 0x80010a03, 0xe0781150, 0x80010e03, 0xe0781160, + 0x80011203, 0xe0781170, 0x80011603, 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, + 0xd760000a, 0x02002906, 0xd760000b, 0x02002907, 0xbe9e0380, 0xbefe030a, 0xbeff030b, 0xbe8c2508, + 0xbe8e037e, 0xbe8f037f, 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd761000a, 0x0200280c, 0xd761000b, + 0x0200280d, 0xd761000c, 0x0200280e, 0xd761000d, 0x0200280f, 0xbefe030a, 0xbeff030b, 0xbe8c2608, + 0xbe8e037e, 0xbe8f037f, 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd761000e, 0x0200280c, 0xd761000f, + 0x0200280d, 0xd7610010, 0x0200280e, 0xd7610011, 0x0200280f, 0xbefe030a, 0xbeff030b, 0xbe8c2708, + 0xbe8e037e, 0xbe8f037f, 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd7610012, 0x0200280c, 0xd7610013, + 0x0200280d, 0xd7610014, 0x0200280e, 0xd7610015, 0x0200280f, 0xd7610019, 0x0200281e, 0x80148114, + 0xbf0aa014, 0xbf85ffc2, 0xe0781180, 0x80010a03, 0xe0781190, 0x80010e03, 0xe07811a0, 0x80011203, + 0xe07811b0, 0x80011603, 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, + 0x02002906, 0xd760000b, 0x02002907, 0xbe9e0380, 0xbefe030a, 0xbeff030b, 0xbe8c2808, 0xbe8e037e, + 0xbe8f037f, 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd761000a, 0x0200280c, 0xd761000b, 0x0200280d, + 0xd761000c, 0x0200280e, 0xd761000d, 0x0200280f, 0xbefe030a, 0xbeff030b, 0xbe8c2908, 0xbe8e037e, + 0xbe8f037f, 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd761000e, 0x0200280c, 0xd761000f, 0x0200280d, + 0xd7610010, 0x0200280e, 0xd7610011, 0x0200280f, 0xbefe030a, 0xbeff030b, 0xbe8c2a08, 0xbe8e037e, + 0xbe8f037f, 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd7610012, 0x0200280c, 0xd7610013, 0x0200280d, + 0xd7610014, 0x0200280e, 0xd7610015, 0x0200280f, 0xd7610019, 0x0200281e, 0x80148114, 0xbf0aa014, + 0xbf85ffc2, 0xe07811c0, 0x80010a03, 0xe07811d0, 0x80010e03, 0xe07811e0, 0x80011203, 0xe07811f0, + 0x80011603, 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, + 0xd760000b, 0x02002907, 0xbe9e0380, 0xbefe030a, 0xbeff030b, 0xbe8c2b08, 0xbe8e037e, 0xbe8f037f, + 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd761000a, 0x0200280c, 0xd761000b, 0x0200280d, 0xd761000c, + 0x0200280e, 0xd761000d, 0x0200280f, 0xbefe030a, 0xbeff030b, 0xbe8c3708, 0xbe8e037e, 0xbe8f037f, + 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd761000e, 0x0200280c, 0xd761000f, 0x0200280d, 0xd7610010, + 0x0200280e, 0xd7610011, 0x0200280f, 0xbefe030a, 0xbeff030b, 0xbe8c3808, 0xbe8e037e, 0xbe8f037f, + 0xbefe03c1, 0xbeff0380, 0x821e1e1e, 0xd7610012, 0x0200280c, 0xd7610013, 0x0200280d, 0xd7610014, + 0x0200280e, 0xd7610015, 0x0200280f, 0xd7610019, 0x0200281e, 0x80148114, 0xbf0aa014, 0xbf85ffc2, + 0xe0781200, 0x80010a03, 0xe0781210, 0x80010e03, 0xe0781220, 0x80011203, 0xe0781230, 0x80011603, + 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, 0xd760000b, + 0x02002907, 0xbe9e0380, 0xbefe030a, 0xbeff030b, 0xbe8c3908, 0xbe8e037e, 0xbe8f037f, 0xbefe03c1, + 0xbeff0380, 0x821e1e1e, 0xd761000a, 0x0200280c, 0xd761000b, 0x0200280d, 0xd761000c, 0x0200280e, + 0xd761000d, 0x0200280f, 0xbefe030a, 0xbeff030b, 0xbe8c3a08, 0xbe8e037e, 0xbe8f037f, 0xbefe03c1, + 0xbeff0380, 0x821e1e1e, 0xd761000e, 0x0200280c, 0xd761000f, 0x0200280d, 0xd7610010, 0x0200280e, + 0xd7610011, 0x0200280f, 0xbf000a08, 0x821e1e1e, 0xbf010a08, 0x821e1e1e, 0xbf020a08, 0x821e1e1e, + 0xbf030a08, 0x821e1e1e, 0xbf040a08, 0x821e1e1e, 0xbf050a08, 0x821e1e1e, 0xbf060a08, 0x821e1e1e, + 0xbf070a08, 0x821e1e1e, 0xbf080a08, 0x821e1e1e, 0xbf090a08, 0x821e1e1e, 0xbf0a0a08, 0x821e1e1e, + 0xbf0b0a08, 0x821e1e1e, 0xbf120a08, 0x821e1e1e, 0xbf130a08, 0x821e1e1e, 0xbf0c0a08, 0x821e1e1e, + 0xbf0d0a08, 0x821e1e1e, 0xbf0e0a08, 0x821e1e1e, 0xbf0f0a08, 0x821e1e1e, 0xb1888000, 0x821e1e1e, + 0xb2087fff, 0x821e1e1e, 0xb288ffff, 0x821e1e1e, 0xb3080000, 0x821e1e1e, 0xb3888000, 0x821e1e1e, + 0xb4080001, 0x821e1e1e, 0xb4888000, 0x821e1e1e, 0xb508ffff, 0x821e1e1e, 0xb5888000, 0x821e1e1e, + 0xb608ffff, 0x821e1e1e, 0xb6887fff, 0x821e1e1e, 0xd7610019, 0x0200281e, 0x80148114, 0xbf0aa014, + 0xbf85ff98, 0xe0781240, 0x80010a03, 0xe0781250, 0x80010e03, 0xe0781260, 0x80011203, 0xe0781270, + 0x80011603, 0xbe940380, 0xd7600008, 0x02002904, 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, + 0xd760000b, 0x02002907, 0xbe9e0380, 0xb708ffff, 0x821e1e1e, 0xbf060b0b, 0xb00c8001, 0x821e1e1e, + 0xd761000a, 0x0200280c, 0xbe8c0308, 0xb78c8000, 0x821e1e1e, 0xd761000b, 0x0200280c, 0xbe8c0308, + 0xb78c7fff, 0x821e1e1e, 0xd761000c, 0x0200280c, 0xbe8c0308, 0xbf060b0b, 0xb80cfffd, 0x821e1e1e, + 0xd761000d, 0x0200280c, 0xd7610019, 0x0200281e, 0x80148114, 0xbf0aa014, 0xbf85ffdb, 0xe0781280, + 0x80010a03, 0xe0781290, 0x80010e03, 0xe07812a0, 0x80011203, 0xe07812b0, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0xffff8001u, 0x0000003fu, 0x00000040u, 0xffffffffu}, + {0x00000000u, 0x00000000u, 0x00010000u, 0x00000001u}, + {0xffffffffu, 0xffffffffu, 0xffff8001u, 0x12345678u}, + {0x00007fffu, 0xd4cbd76du, 0x00007fffu, 0xd4cbd76du}, + {0x80000000u, 0x2bf2354eu, 0xcb2e075eu, 0x9abcdef0u}, + {0xffff8000u, 0x00007fffu, 0x74d6d11fu, 0x55555555u}, + {0x00008000u, 0x9628ee8du, 0x00000000u, 0xffffffffu}, + {0x0000ffffu, 0x831ef5c3u, 0x0f0f0f0fu, 0x00f000f0u}, + {0x00000020u, 0x00000003u, 0x7fffffffu, 0x00000003u}, + {0x00f000f0u, 0x80000000u, 0x00000003u, 0x1fb17c23u}, + {0x00000003u, 0x11111111u, 0x00010000u, 0x0000001fu}, + {0x12345678u, 0x7731af10u, 0x40000000u, 0x00008000u}, + {0xffff8001u, 0x00010003u, 0xffff8001u, 0x12bd4aceu}, + {0x26e87555u, 0x6bf46c69u, 0xffffffffu, 0x92b1d3f2u}, + {0x00020001u, 0x40000000u, 0x80000000u, 0x40000000u}, + {0x4f426dcbu, 0xfe3b890bu, 0x72158370u, 0xf0f0f0f0u}, + {0x5affb229u, 0xfffffffeu, 0x0000ffffu, 0x211c70cfu}, + {0x7f1b103cu, 0x9abcdef0u, 0x00007fffu, 0xd1bc52d9u}, + {0xfc891b4au, 0xf0f0f0f0u, 0x26a2c0bdu, 0x00000020u}, + {0x0000003fu, 0x00000000u, 0x0f0f0f0fu, 0x519088f5u}, + {0x00000003u, 0x11111111u, 0x00000003u, 0x11111111u}, + {0x00000021u, 0x00000003u, 0x00000020u, 0x00000003u}, + {0xf0f0f0f0u, 0xffff8000u, 0xf0f0f0f0u, 0xfffffffeu}, + {0x7afb2c68u, 0x80000000u, 0x00010003u, 0x7a86f7a2u}, + {0xaaaaaaaau, 0x00000001u, 0xaaaaaaaau, 0xdd02de92u}, + {0x00000021u, 0x00010000u, 0x55555555u, 0x00010000u}, + {0x31f51707u, 0xd17e4497u, 0x00010000u, 0xd17e4497u}, + {0x00007fffu, 0x00000040u, 0xf4de2c08u, 0x00020001u}, + {0x00010000u, 0x00010003u, 0x00000000u, 0x00010003u}, + {0xe8e72789u, 0x00000040u, 0xe8e72789u, 0x00010003u}, + {0x7691b06fu, 0x80000000u, 0x2b855c1fu, 0x070d7109u}, + {0x256badf9u, 0x988af3fbu, 0xa842bc19u, 0x27e9e06fu} +}; +constexpr std::uint32_t Expected[32][176] = { + {0xffff8001u, 0x0000003fu, 0xffff8001u, 0xffff8001u, 0x0000003fu, 0x00007ffeu, 0x00007ffeu, 0xffffffc0u, 0xfffff00fu, 0xfffff00fu, 0x000000ffu, 0x8001ffffu, 0xfc000000u, 0x8001ffffu, 0x0000000eu, 0x000003ffu, 0x00000028u, 0x00000012u, 0x00000018u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001au, 0x00000011u, 0x0000001au, 0x00000001u, 0xffff8001u, 0x00000040u, 0x00000041u, 0x00007fffu, 0x00000040u, 0xffffffffu, 0x00000041u, 0xffffffffu, 0x000000f9u, 0x000003f9u, 0x00000000u, 0x00007fffu, 0xc0000003u, 0xffffffffu, 0xffff8001u, 0xffff8001u, 0x0000003fu, 0x00000000u, 0x00000000u, 0x000000ffu, 0xffff8001u, 0xffffffffu, 0xffff8001u, 0x0000003fu, 0xffff8001u, 0x0000003fu, 0xffff8001u, 0x0000003fu, 0xffff8001u, 0xffffffffu, 0xffff8001u, 0x0000003fu, 0x0000003fu, 0x00000001u, 0x00000000u, 0x0000000fu, 0x0000003fu, 0xffff803fu, 0x0000003fu, 0xffff803eu, 0x0000003fu, 0xffff8000u, 0x0000003fu, 0xffffffc1u, 0x0000003fu, 0xfffffffeu, 0x0000003fu, 0x00007fc0u, 0x0000003fu, 0x00007fc1u, 0x00000000u, 0x0000007fu, 0x0000003fu, 0x0000003eu, 0x0000003fu, 0x00007fffu, 0x0000003eu, 0x0000003eu, 0xffff8000u, 0xffff8000u, 0x00000040u, 0xffffffffu, 0x00000000u, 0x0000003fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00000040u, 0xffffffffu, 0xffff8041u, 0xffffffffu, 0x00000040u, 0xffffffffu, 0xffff8041u, 0xffffffc0u, 0x00000040u, 0xffffffffu, 0xffff8001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000040u, 0xffffffffu, 0xffffffbfu, 0x0000003fu, 0x00000040u, 0xffffffffu, 0xffffffffu, 0xffffffc0u, 0x00000040u, 0xffffffffu, 0x00007fbeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000040u, 0xffffffffu, 0x00007fbeu, 0x0000003fu, 0x00000040u, 0xffffffffu, 0x00000040u, 0xffffffc0u, 0x00000040u, 0xffffffffu, 0x00007ffeu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000040u, 0xffffffc0u, 0x00000040u, 0xffffffc0u, 0xffff8001u, 0x00000000u, 0xffff8001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69b8aa2eu, 0xffff8001u, 0xffff0001u, 0x00000000u, 0x00017ffdu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000020u, 0x000003e7u, 0x00000040u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00010000u, 0x00010001u, 0x00004fffu, 0x00010000u, 0x00000001u, 0x00010001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000000c7u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0x00000000u, 0x0000000fu, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000008u, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000006u, 0x00010000u, 0x00000001u, 0xfffeffffu, 0xfffffffeu, 0x00010000u, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x00010000u, 0x00000001u, 0xfffeffffu, 0xfffffffeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010000u, 0x00000001u, 0xfffeffffu, 0xfffffffeu, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x49a6d3a9u, 0xffff8001u, 0xffff8000u, 0x00007fffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0xffffffffu, 0xffffffffu, 0xffff8001u, 0xffff8001u, 0x12345678u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x0000021eu, 0x00000000u, 0x00000020u, 0x00000040u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffff8001u, 0xffff8003u, 0x00003fffu, 0xffff8001u, 0x12345678u, 0xffff8003u, 0x12345678u, 0x000000ffu, 0x0000ffffu, 0x00000000u, 0x00000001u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0xffff8001u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffff8001u, 0x12345678u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x0000000fu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000049u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffff8001u, 0x12345678u, 0xffff8001u, 0x12345678u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u, 0xffff8001u, 0x12345678u, 0xffffffffu, 0xffffffffu, 0xffff8001u, 0x12345678u, 0x00007ffeu, 0xedcba987u, 0xffff8001u, 0x12345678u, 0x00007ffeu, 0xedcba987u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xffff8001u, 0x12345678u, 0xffffffffu, 0xffffffffu, 0xffff8001u, 0x12345678u, 0x00007ffeu, 0xedcba987u, 0xffff8001u, 0x12345678u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000006u, 0xffff8001u, 0x12345678u, 0xffff8001u, 0x12345678u, 0xffff8001u, 0x12345678u, 0x00000000u, 0x00000000u, 0xffff8001u, 0x12345678u, 0xffff8001u, 0x12345678u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000005u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00007ffeu, 0xedcba987u, 0x00007ffeu, 0xedcba987u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x2e38aa2eu, 0xffff8001u, 0xffff7fffu, 0x00007ffeu, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x00007fffu, 0xd4cbd76du, 0x00007fffu, 0x00007fffu, 0xd4cbd76du, 0xffff8000u, 0xffff8000u, 0x2b342892u, 0x0000ffffu, 0x0000ffffu, 0xffffffffu, 0xfffe0000u, 0xb6ebd32bu, 0xfffe0000u, 0x00000011u, 0x000003ffu, 0x0000001du, 0x0000000fu, 0x00000023u, 0x0000000fu, 0x0000000fu, 0x00000000u, 0x00000000u, 0x00000011u, 0x00000000u, 0x00000011u, 0x00000002u, 0xffffffffu, 0x00007fffu, 0x00007fffu, 0x80007fffu, 0x00007fffu, 0x00007fffu, 0x54cbd76du, 0x00007fffu, 0xd4cbd76du, 0x0000000fu, 0x0000ff0fu, 0x00000000u, 0x00007fffu, 0x3fffffffu, 0x00000000u, 0xd4cbd76du, 0x00007fffu, 0xd4cbd76du, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00007fffu, 0xd4cbd76du, 0x00007fffu, 0x00007fffu, 0x00007fffu, 0xd4cbd76du, 0x00007fffu, 0xd4cbd76du, 0xd4cbd76du, 0xd4cbd76du, 0x00007fffu, 0xd4cbd76du, 0xd4cbd76du, 0x0000576du, 0x00000000u, 0x0000000fu, 0xd4cbd76du, 0xd4cbffffu, 0xd4cbd76du, 0xd4cba892u, 0xd4cbd76du, 0x00002892u, 0xd4cbd76du, 0x2b347fffu, 0xd4cbd76du, 0xffffa892u, 0xd4cbd76du, 0x2b340000u, 0xd4cbd76du, 0x2b34576du, 0x00000000u, 0x0000007fu, 0xd4cbd76du, 0xd4cb8000u, 0xd4cbd76du, 0xffffd76du, 0xd4cb8000u, 0xd4cb8000u, 0x00002892u, 0x00002892u, 0x00007fffu, 0xd4cbd76du, 0x00007fffu, 0xd4cbd76du, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00007fffu, 0xd4cbd76du, 0x00007fffu, 0xd4cbd76du, 0x00007fffu, 0xd4cbd76du, 0x00000000u, 0x00000000u, 0x00007fffu, 0xd4cbd76du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00007fffu, 0xd4cbd76du, 0xffffffffu, 0xffffffffu, 0x00007fffu, 0xd4cbd76du, 0xffff8000u, 0x2b342892u, 0x00007fffu, 0xd4cbd76du, 0xffff8000u, 0x2b342892u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00007fffu, 0xd4cbd76du, 0xffffffffu, 0xffffffffu, 0x00007fffu, 0xd4cbd76du, 0x00000000u, 0x00000000u, 0x00007fffu, 0xd4cbd76du, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000005u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x12cb4988u, 0xffff8001u, 0xffffffffu, 0x0000fffeu, 0xfffe8003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x80000000u, 0x2bf2354eu, 0xcb2e075eu, 0xcb2e075eu, 0x9abcdef0u, 0x7fffffffu, 0x7fffffffu, 0xd40dcab1u, 0xf0000000u, 0xf0000000u, 0xffffffffu, 0x00000001u, 0x72ac4fd4u, 0x00000001u, 0x0000001fu, 0x0000027fu, 0x0000002eu, 0x00000001u, 0x00000012u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x0000001fu, 0x00000000u, 0x00000002u, 0x00000001u, 0x00000002u, 0x00000000u, 0x00000000u, 0x8b2e075eu, 0xcb2e075eu, 0x00007fffu, 0x8b2e075eu, 0x9abcdef0u, 0xcb2e075eu, 0x9abcdef0u, 0x00000080u, 0x0000ff80u, 0x00000000u, 0x80000000u, 0x00000000u, 0xc0000000u, 0xcb2e075eu, 0xcb2e075eu, 0x9abcdef0u, 0x00000000u, 0x00000000u, 0x000000ffu, 0xcb2e075eu, 0x9abcdef0u, 0x80000000u, 0x2bf2354eu, 0xcb2e075eu, 0x9abcdef0u, 0x80000000u, 0x2bf2354eu, 0xcb2e075eu, 0x9abcdef0u, 0xcb2e075eu, 0x2bf2354eu, 0x2bf2354eu, 0x00000000u, 0x00000000u, 0x0000000eu, 0x2bf2354eu, 0xabf2354eu, 0x2bf2354eu, 0xabf2354eu, 0x2bf2354eu, 0x80000000u, 0x2bf2354eu, 0xd40dcab1u, 0x2bf2354eu, 0xffffffffu, 0x2bf2354eu, 0x540dcab1u, 0x2bf2354eu, 0x540dcab1u, 0x00000000u, 0x0000007fu, 0x2bf2354eu, 0x2bf2354eu, 0x2bf2354eu, 0x7fffffffu, 0x2bf2354eu, 0x2bf2354eu, 0x80000000u, 0x80000000u, 0xcb2e075eu, 0x9abcdef0u, 0x80000000u, 0x0ab01440u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0xcb2e075eu, 0x9abcdef0u, 0xcb2e075eu, 0xbbfefffeu, 0xcb2e075eu, 0x9abcdef0u, 0x4b2e075eu, 0xb14eebbeu, 0xcb2e075eu, 0x9abcdef0u, 0x00000000u, 0x2142210eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xcb2e075eu, 0x9abcdef0u, 0xb4d1f8a1u, 0x6ff3354fu, 0xcb2e075eu, 0x9abcdef0u, 0x7fffffffu, 0xf54febbfu, 0xcb2e075eu, 0x9abcdef0u, 0x34d1f8a1u, 0x44010001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xcb2e075eu, 0x9abcdef0u, 0xb4d1f8a1u, 0x4eb11441u, 0xcb2e075eu, 0x9abcdef0u, 0x4b2e075eu, 0x900ccab0u, 0xcb2e075eu, 0x9abcdef0u, 0xffffffffu, 0xdebddef1u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x4b2e075eu, 0x900ccab0u, 0x4b2e075eu, 0x900ccab0u, 0x00000000u, 0x2142210eu, 0x00000000u, 0x2142210eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69a6d26eu, 0xffff8001u, 0x7fff8000u, 0x80007fffu, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000000du}, + {0xffff8000u, 0x00007fffu, 0xffff8000u, 0xffff8000u, 0x00007fffu, 0x00007fffu, 0x00007fffu, 0xffff8000u, 0xfffff000u, 0xfffff000u, 0x0000ffffu, 0x0001ffffu, 0xfffe0000u, 0x0001ffffu, 0x0000000fu, 0x000003ffu, 0x00000020u, 0x00000011u, 0x00000020u, 0x00000000u, 0x00000000u, 0x0000000fu, 0x0000000fu, 0x00000000u, 0x00000011u, 0x00000011u, 0x00000011u, 0x00000000u, 0xffff8000u, 0x74d6d11fu, 0xf4d6d11fu, 0x00007fffu, 0x74d6d11fu, 0x55555555u, 0xf4d6d11fu, 0x55555555u, 0x000000f8u, 0x00000ff8u, 0x00000000u, 0x00008000u, 0xc0000000u, 0xffffffffu, 0x55555555u, 0x74d6d11fu, 0x55555555u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x74d6d11fu, 0x55555555u, 0xffff8000u, 0xffff8000u, 0x74d6d11fu, 0x55555555u, 0xffff8000u, 0x00007fffu, 0x74d6d11fu, 0x55555555u, 0x55555555u, 0x00007fffu, 0x00007fffu, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00007fffu, 0xffffffffu, 0x00007fffu, 0xffffffffu, 0x00007fffu, 0xffff8000u, 0x00007fffu, 0xffff8000u, 0x00007fffu, 0xffffffffu, 0x00007fffu, 0x00000000u, 0x00007fffu, 0x00000000u, 0x00000000u, 0x0000007cu, 0x00007fffu, 0x00007fffu, 0x00007fffu, 0x00007fffu, 0x00007fffu, 0x00007fffu, 0xffff8000u, 0xffff8000u, 0x74d6d11fu, 0x55555555u, 0x74d68000u, 0x00005555u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x74d6d11fu, 0x55555555u, 0xffffd11fu, 0x55557fffu, 0x74d6d11fu, 0x55555555u, 0x8b29511fu, 0x55552aaau, 0x74d6d11fu, 0x55555555u, 0x8b290000u, 0x00002aaau, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x74d6d11fu, 0x55555555u, 0xffffaee0u, 0xaaaaffffu, 0x74d6d11fu, 0x55555555u, 0x8b297fffu, 0xffffaaaau, 0x74d6d11fu, 0x55555555u, 0x00002ee0u, 0xaaaa8000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x74d6d11fu, 0x55555555u, 0x74d6aee0u, 0xaaaad555u, 0x74d6d11fu, 0x55555555u, 0x0000511fu, 0x55550000u, 0x74d6d11fu, 0x55555555u, 0x74d6ffffu, 0xffffd555u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0000511fu, 0x55550000u, 0x0000511fu, 0x55550000u, 0x8b290000u, 0x00002aaau, 0x8b290000u, 0x00002aaau, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69b8ae2eu, 0xffff8001u, 0xffff0000u, 0xffffffffu, 0x00018000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x00008000u, 0x9628ee8du, 0x00008000u, 0x00008000u, 0x9628ee8du, 0xffff7fffu, 0xffff7fffu, 0x69d71172u, 0x0000f000u, 0x0000f000u, 0xffffffffu, 0x00010000u, 0xb1771469u, 0x00010000u, 0x0000001fu, 0x000003ffu, 0x0000002fu, 0x00000001u, 0x00000011u, 0x00000000u, 0x00000000u, 0x0000000fu, 0x0000000fu, 0x00000010u, 0x00000000u, 0x00000010u, 0x00000001u, 0x00000000u, 0xffff8000u, 0x00000000u, 0x00000001u, 0x00007fffu, 0x00000000u, 0xffffffffu, 0x00000001u, 0xffffffffu, 0x00000008u, 0x0000ff08u, 0x00000000u, 0x00008000u, 0xc0000000u, 0x00000000u, 0x00008000u, 0x00008000u, 0x9628ee8du, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00008000u, 0xffffffffu, 0x00008000u, 0x9628ee8du, 0x00008000u, 0x9628ee8du, 0x00008000u, 0x9628ee8du, 0x00008000u, 0xffffffffu, 0x00008000u, 0x9628ee8du, 0x9628ee8du, 0x00008000u, 0x00000000u, 0x0000000fu, 0x9628ee8du, 0x9628ee8du, 0x9628ee8du, 0x96286e8du, 0x9628ee8du, 0x00000000u, 0x9628ee8du, 0x69d79172u, 0x9628ee8du, 0xffff7fffu, 0x9628ee8du, 0x69d71172u, 0x9628ee8du, 0x69d79172u, 0x00000000u, 0x0000006fu, 0x9628ee8du, 0x96286e8du, 0x9628ee8du, 0xffffffffu, 0x96286e8du, 0x96286e8du, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x9628ee8du, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001du, 0x00000000u, 0xffffffffu, 0x00008000u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0x00008000u, 0x69d71172u, 0x00000000u, 0xffffffffu, 0x00008000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x9628ee8du, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x69d71172u, 0x00000000u, 0xffffffffu, 0xffff7fffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0xffffffffu, 0xffff7fffu, 0x9628ee8du, 0x00000000u, 0xffffffffu, 0x00000000u, 0x69d71172u, 0x00000000u, 0xffffffffu, 0xffff7fffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0x69d71172u, 0x00000000u, 0x69d71172u, 0x00008000u, 0x00000000u, 0x00008000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e38d398u, 0xffff8001u, 0x00000000u, 0x0000ffffu, 0xfffe8000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x0000ffffu, 0x831ef5c3u, 0x0f0f0f0fu, 0x0f0f0f0fu, 0x00f000f0u, 0xffff0000u, 0xffff0000u, 0x7ce10a3cu, 0x0000ffffu, 0x0000ffffu, 0xffffffffu, 0xffff0000u, 0xc3af78c1u, 0xffff0000u, 0x00000010u, 0x0000027fu, 0x0000001fu, 0x00000010u, 0x00000021u, 0x00000010u, 0x00000010u, 0x00000000u, 0x00000000u, 0x00000010u, 0x00000000u, 0x00000010u, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x0f0f0f0fu, 0x0f0f8f0fu, 0x00007fffu, 0x0f0f0f0fu, 0x00f000f0u, 0x0f0f8f0fu, 0x00f000f0u, 0x0000000fu, 0x0000ff0fu, 0x00000000u, 0x0000ffffu, 0xffffffffu, 0x00000000u, 0x00f000f0u, 0x0f0f0f0fu, 0x00f000f0u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x0f0f0f0fu, 0x00f000f0u, 0x0000ffffu, 0x0000ffffu, 0x0f0f0f0fu, 0x00f000f0u, 0x0000ffffu, 0x831ef5c3u, 0x0f0f0f0fu, 0x00f000f0u, 0x0000ffffu, 0x00f000f0u, 0x831ef5c3u, 0x0000f5c3u, 0x00000000u, 0x0000000fu, 0x831ef5c3u, 0x831effffu, 0x831ef5c3u, 0x831e0a3cu, 0x831ef5c3u, 0x00000a3cu, 0x831ef5c3u, 0x7ce1ffffu, 0x831ef5c3u, 0xffff0a3cu, 0x831ef5c3u, 0x7ce10000u, 0x831ef5c3u, 0x7ce1f5c3u, 0x00000000u, 0x0000007fu, 0x831ef5c3u, 0x831e0000u, 0x831ef5c3u, 0xfffff5c3u, 0x831e0000u, 0x831e0000u, 0x00000a3cu, 0x00000a3cu, 0x0f0f0f0fu, 0x00f000f0u, 0x00000f0fu, 0x001000c0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x0f0f0f0fu, 0x00f000f0u, 0x0f0fffffu, 0x83fef5f3u, 0x0f0f0f0fu, 0x00f000f0u, 0x0f0ff0f0u, 0x83eef533u, 0x0f0f0f0fu, 0x00f000f0u, 0x0000f0f0u, 0x830ef503u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0f0f0f0fu, 0x00f000f0u, 0xf0f0ffffu, 0xff1fffcfu, 0x0f0f0f0fu, 0x00f000f0u, 0xfffff0f0u, 0xffefff3fu, 0x0f0f0f0fu, 0x00f000f0u, 0xf0f00000u, 0x7c010a0cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0f0f0f0fu, 0x00f000f0u, 0xf0f00f0fu, 0x7c110accu, 0x0f0f0f0fu, 0x00f000f0u, 0x0f0f0000u, 0x00e00030u, 0x0f0f0f0fu, 0x00f000f0u, 0xffff0f0fu, 0x7cf10afcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0f0f0000u, 0x00e00030u, 0x0f0f0000u, 0x00e00030u, 0x0000f0f0u, 0x830ef503u, 0x0000f0f0u, 0x830ef503u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69a6ab86u, 0xffff8001u, 0x00007fffu, 0x00017ffeu, 0xfffd0003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x00000020u, 0x00000003u, 0x00000020u, 0x00000020u, 0x00000003u, 0xffffffdfu, 0xffffffdfu, 0xfffffffcu, 0x000000f0u, 0x000000f0u, 0x0000000fu, 0x04000000u, 0xc0000000u, 0x04000000u, 0x0000001fu, 0x000003ffu, 0x0000003du, 0x00000001u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000005u, 0x00000005u, 0x0000001au, 0x0000001eu, 0x0000001au, 0x0000001eu, 0x00000020u, 0x00000020u, 0x7fffffffu, 0xffffffffu, 0x00007fffu, 0x7fffffffu, 0x00000003u, 0x7fffffffu, 0x80000003u, 0x00000002u, 0x00000102u, 0x00000000u, 0x00000020u, 0x00000c00u, 0x00000000u, 0x00000003u, 0x7fffffffu, 0x00000003u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x7fffffffu, 0x00000003u, 0x00000020u, 0x00000020u, 0x7fffffffu, 0x00000003u, 0x00000020u, 0x00000003u, 0x7fffffffu, 0x00000003u, 0x00000020u, 0x00000003u, 0x00000003u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00000003u, 0x00000023u, 0x00000003u, 0x00000023u, 0x00000003u, 0x00000020u, 0x00000003u, 0xfffffffcu, 0x00000003u, 0xffffffffu, 0x00000003u, 0xffffffdcu, 0x00000003u, 0xffffffdcu, 0x00000000u, 0x0000007fu, 0x00000003u, 0x00000003u, 0x00000003u, 0xffffffdfu, 0x00000003u, 0x00000003u, 0x00000020u, 0x00000020u, 0x7fffffffu, 0x00000003u, 0x00000020u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x7fffffffu, 0x00000003u, 0x7fffffffu, 0x00000003u, 0x7fffffffu, 0x00000003u, 0x7fffffdfu, 0x00000000u, 0x7fffffffu, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000006u, 0x7fffffffu, 0x00000003u, 0x80000020u, 0xffffffffu, 0x7fffffffu, 0x00000003u, 0xffffffdfu, 0xfffffffcu, 0x7fffffffu, 0x00000003u, 0x80000000u, 0xfffffffcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x7fffffffu, 0x00000003u, 0x80000020u, 0xffffffffu, 0x7fffffffu, 0x00000003u, 0x7fffffdfu, 0x00000000u, 0x7fffffffu, 0x00000003u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x7fffffdfu, 0x00000000u, 0x7fffffdfu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x49a6d389u, 0xffff8001u, 0xffff8020u, 0x0000801fu, 0xffffffa0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x00f000f0u, 0x80000000u, 0x00f000f0u, 0x00f000f0u, 0x80000000u, 0xff0fff0fu, 0xff0fff0fu, 0x7fffffffu, 0x00f000f0u, 0x00f000f0u, 0xf0000000u, 0x0f000f00u, 0x00000001u, 0x0f000f00u, 0x00000018u, 0x000003ffu, 0x00000037u, 0x00000008u, 0x00000009u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000004u, 0x00000008u, 0x00000000u, 0x00000008u, 0x00000001u, 0xfffffff0u, 0x000000f0u, 0x00000003u, 0x0000000bu, 0x00007fffu, 0x00000003u, 0x1fb17c23u, 0x0000000bu, 0x1fb17c23u, 0x00000022u, 0x00008022u, 0x00000000u, 0x00f000f0u, 0x0000ff00u, 0x0000ff00u, 0x1fb17c23u, 0x00000003u, 0x1fb17c23u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000003u, 0x1fb17c23u, 0x00f000f0u, 0x00f000f0u, 0x00000003u, 0x1fb17c23u, 0x00f000f0u, 0x80000000u, 0x1fb17c23u, 0x1fb17c23u, 0x00f000f0u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x80000000u, 0x80f000f0u, 0x80000000u, 0x80f000f0u, 0x80000000u, 0x00f000f0u, 0x80000000u, 0x7fffffffu, 0x80000000u, 0xffffffffu, 0x80000000u, 0x7f0fff0fu, 0x80000000u, 0x7f0fff0fu, 0x00000000u, 0x0000007fu, 0x80000000u, 0x80000000u, 0x80000000u, 0xff0fff0fu, 0x80000000u, 0x80000000u, 0x00f000f0u, 0x00f000f0u, 0x00000003u, 0x1fb17c23u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001eu, 0x00000003u, 0x1fb17c23u, 0x00f000f3u, 0x9fb17c23u, 0x00000003u, 0x1fb17c23u, 0x00f000f3u, 0x9fb17c23u, 0x00000003u, 0x1fb17c23u, 0x00f000f0u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000003u, 0x1fb17c23u, 0xfffffffcu, 0xe04e83dcu, 0x00000003u, 0x1fb17c23u, 0xffffffffu, 0xffffffffu, 0x00000003u, 0x1fb17c23u, 0xff0fff0cu, 0x604e83dcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000003u, 0x1fb17c23u, 0xff0fff0cu, 0x604e83dcu, 0x00000003u, 0x1fb17c23u, 0x00000003u, 0x1fb17c23u, 0x00000003u, 0x1fb17c23u, 0xff0fff0fu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000003u, 0x1fb17c23u, 0x00000003u, 0x1fb17c23u, 0x00f000f0u, 0x80000000u, 0x00f000f0u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e38d38eu, 0xffff8001u, 0x00ef80f0u, 0x00f080efu, 0xfd2ffd30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x00000003u, 0x11111111u, 0x00000003u, 0x00000003u, 0x11111111u, 0xfffffffcu, 0xfffffffcu, 0xeeeeeeeeu, 0x0000000fu, 0x0000000fu, 0xffffffffu, 0xc0000000u, 0x88888888u, 0xc0000000u, 0x0000001eu, 0x000003ffu, 0x00000036u, 0x00000002u, 0x0000000au, 0x00000002u, 0x00000002u, 0x00000000u, 0x00000000u, 0x0000001eu, 0x00000003u, 0x0000001eu, 0x00000003u, 0x00000003u, 0x00000003u, 0x00010000u, 0x00010001u, 0x00007fffu, 0x00010000u, 0x0000001fu, 0x00010001u, 0x0000001fu, 0x00000001u, 0x0000ff01u, 0x00000000u, 0x00000003u, 0x0000000fu, 0x00000000u, 0x00000003u, 0x00000003u, 0x11111111u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000003u, 0x0000001fu, 0x00000003u, 0x11111111u, 0x00000003u, 0x11111111u, 0x00000003u, 0x11111111u, 0x00010000u, 0x00000003u, 0x00000003u, 0x11111111u, 0x11111111u, 0x00000001u, 0x00000000u, 0x0000000fu, 0x11111111u, 0x11111113u, 0x11111111u, 0x11111112u, 0x11111111u, 0x00000002u, 0x11111111u, 0xeeeeeeefu, 0x11111111u, 0xfffffffeu, 0x11111111u, 0xeeeeeeecu, 0x11111111u, 0xeeeeeeedu, 0x00000000u, 0x0000007fu, 0x11111111u, 0x11111110u, 0x11111111u, 0xfffffffdu, 0x11111110u, 0x11111110u, 0x00000002u, 0x00000002u, 0x00010000u, 0x0000001fu, 0x00000000u, 0x00000011u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00010000u, 0x0000001fu, 0x00010003u, 0x1111111fu, 0x00010000u, 0x0000001fu, 0x00010003u, 0x1111110eu, 0x00010000u, 0x0000001fu, 0x00000003u, 0x11111100u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010000u, 0x0000001fu, 0xfffeffffu, 0xfffffff1u, 0x00010000u, 0x0000001fu, 0xffffffffu, 0xffffffeeu, 0x00010000u, 0x0000001fu, 0xfffefffcu, 0xeeeeeee0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010000u, 0x0000001fu, 0xfffefffcu, 0xeeeeeef1u, 0x00010000u, 0x0000001fu, 0x00010000u, 0x0000000eu, 0x00010000u, 0x0000001fu, 0xfffffffcu, 0xeeeeeeffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010000u, 0x0000000eu, 0x00010000u, 0x0000000eu, 0x00000003u, 0x11111100u, 0x00000003u, 0x11111100u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69a6ab89u, 0xffff8001u, 0xffff8003u, 0x00008002u, 0xfffffff7u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x12345678u, 0x7731af10u, 0x40000000u, 0x40000000u, 0x00008000u, 0xedcba987u, 0xedcba987u, 0x88ce50efu, 0xffffffffu, 0xffffffffu, 0xfffffff0u, 0x1e6a2c48u, 0x08f58ceeu, 0x1e6a2c48u, 0x00000013u, 0x0000027fu, 0x00000023u, 0x0000000du, 0x0000001du, 0x00000000u, 0x00000000u, 0x00000003u, 0x00000003u, 0x00000003u, 0x00000001u, 0x00000003u, 0x00000001u, 0x00000078u, 0x00005678u, 0x40000000u, 0x40000001u, 0x00007fffu, 0x40000000u, 0x00008000u, 0x40000001u, 0x00008000u, 0x000000ffu, 0x0000feffu, 0x00000000u, 0x12345678u, 0x333c3fc0u, 0x030c0f30u, 0x12345678u, 0x12345678u, 0x7731af10u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x12345678u, 0x00008000u, 0x12345678u, 0x7731af10u, 0x12345678u, 0x7731af10u, 0x12345678u, 0x7731af10u, 0x12345678u, 0x00008000u, 0x12345678u, 0x7731af10u, 0x7731af10u, 0x12300610u, 0x00000000u, 0x0000000fu, 0x7731af10u, 0x7735ff78u, 0x7731af10u, 0x6505f968u, 0x7731af10u, 0x00045068u, 0x7731af10u, 0x9afe56ffu, 0x7731af10u, 0xedcff9efu, 0x7731af10u, 0x88ca0087u, 0x7731af10u, 0x9afa0697u, 0x00000000u, 0x0000007fu, 0x7731af10u, 0x6501a900u, 0x7731af10u, 0xfffbaf97u, 0x6501a900u, 0x6501a900u, 0x00045068u, 0x00045068u, 0x40000000u, 0x00008000u, 0x00000000u, 0x00008000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x40000000u, 0x00008000u, 0x52345678u, 0x7731af10u, 0x40000000u, 0x00008000u, 0x52345678u, 0x77312f10u, 0x40000000u, 0x00008000u, 0x12345678u, 0x77312f10u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x40000000u, 0x00008000u, 0xbfffffffu, 0xffffffffu, 0x40000000u, 0x00008000u, 0xffffffffu, 0xffff7fffu, 0x40000000u, 0x00008000u, 0xadcba987u, 0x88ce50efu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x40000000u, 0x00008000u, 0xadcba987u, 0x88ced0efu, 0x40000000u, 0x00008000u, 0x40000000u, 0x00000000u, 0x40000000u, 0x00008000u, 0xedcba987u, 0x88ced0efu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x40000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x12345678u, 0x77312f10u, 0x12345678u, 0x77312f10u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69a6d38eu, 0xffff8001u, 0x1233d678u, 0x1234d677u, 0xc962fc98u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0xffff8001u, 0x00010003u, 0xffff8001u, 0xffff8001u, 0x12bd4aceu, 0x00007ffeu, 0x00007ffeu, 0xfffefffcu, 0xfffff00fu, 0xfffff00fu, 0x000f000fu, 0x8001ffffu, 0xc0008000u, 0x8001ffffu, 0x0000000eu, 0x0000027fu, 0x0000002bu, 0x00000012u, 0x00000015u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000000fu, 0x00000011u, 0x0000000fu, 0x00000001u, 0xffff8001u, 0xffff8001u, 0xffff8003u, 0x00007fffu, 0xffff8001u, 0x12bd4aceu, 0xffff8003u, 0x12bd4aceu, 0x000000f9u, 0x000011f9u, 0x00000000u, 0x00007fffu, 0xc0000003u, 0xffffffffu, 0x00010003u, 0xffff8001u, 0x00010003u, 0x00000000u, 0x00000000u, 0x000000ffu, 0xffff8001u, 0xffff8001u, 0xffff8001u, 0x00010003u, 0xffff8001u, 0x00010003u, 0xffff8001u, 0x00010003u, 0xffff8001u, 0x12bd4aceu, 0xffff8001u, 0x00010003u, 0x00010003u, 0x00010001u, 0x00000000u, 0x0000000fu, 0x00010003u, 0xffff8003u, 0x00010003u, 0xfffe8002u, 0x00010003u, 0xfffe8000u, 0x00010003u, 0xfffffffdu, 0x00010003u, 0xfffefffeu, 0x00010003u, 0x00007ffcu, 0x00010003u, 0x00017ffdu, 0x00000000u, 0x0000007fu, 0x00010003u, 0x00000002u, 0x00010003u, 0x00017fffu, 0x00000002u, 0x00000002u, 0xfffe8000u, 0xfffe8000u, 0xffff8001u, 0x12bd4aceu, 0xffff8001u, 0x00010002u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0xffff8001u, 0x12bd4aceu, 0xffff8001u, 0x12bd4acfu, 0xffff8001u, 0x12bd4aceu, 0x00000000u, 0x12bc4acdu, 0xffff8001u, 0x12bd4aceu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xffff8001u, 0x12bd4aceu, 0xffffffffu, 0xed43b533u, 0xffff8001u, 0x12bd4aceu, 0x00007ffeu, 0xfffefffdu, 0xffff8001u, 0x12bd4aceu, 0x00007ffeu, 0xed42b530u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xffff8001u, 0x12bd4aceu, 0xffffffffu, 0xed43b532u, 0xffff8001u, 0x12bd4aceu, 0x00000000u, 0x12bc4accu, 0xffff8001u, 0x12bd4aceu, 0xffffffffu, 0xfffffffeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0x12bc4accu, 0x00000000u, 0x12bc4accu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x72cad22eu, 0xffff8001u, 0xffff0001u, 0x00000000u, 0x00017ffdu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x26e87555u, 0x6bf46c69u, 0xffffffffu, 0xffffffffu, 0x92b1d3f2u, 0xd9178aaau, 0xd9178aaau, 0x940b9396u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xaaae1764u, 0x96362fd6u, 0xaaae1764u, 0x00000010u, 0x0000027fu, 0x0000001eu, 0x00000010u, 0x00000022u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000001u, 0x00000002u, 0x00000001u, 0x00000055u, 0x00007555u, 0x7fffffffu, 0xffffffffu, 0x00007fffu, 0xffffffffu, 0x12b1d3f2u, 0xffffffffu, 0x92b1d3f2u, 0x000000ffu, 0x0000ffffu, 0x00000000u, 0x26e87555u, 0x3f333333u, 0x0c3cfcc0u, 0x92b1d3f2u, 0xffffffffu, 0x92b1d3f2u, 0x00000000u, 0x00000000u, 0x000000ffu, 0xffffffffu, 0x92b1d3f2u, 0x26e87555u, 0x26e87555u, 0xffffffffu, 0x92b1d3f2u, 0x26e87555u, 0x6bf46c69u, 0xffffffffu, 0x92b1d3f2u, 0x26e87555u, 0x92b1d3f2u, 0x6bf46c69u, 0x22e06441u, 0x00000000u, 0x0000000fu, 0x6bf46c69u, 0x6ffc7d7du, 0x6bf46c69u, 0x4d1c193cu, 0x6bf46c69u, 0x04081114u, 0x6bf46c69u, 0xb6ebf7d7u, 0x6bf46c69u, 0xdd1f9bbeu, 0x6bf46c69u, 0x90038282u, 0x6bf46c69u, 0xb2e3e6c3u, 0x00000000u, 0x0000007fu, 0x6bf46c69u, 0x49140828u, 0x6bf46c69u, 0xfbf7eeebu, 0x49140828u, 0x49140828u, 0x04081114u, 0x04081114u, 0xffffffffu, 0x92b1d3f2u, 0x26e87555u, 0x02b04060u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0xffffffffu, 0x92b1d3f2u, 0xffffffffu, 0xfbf5fffbu, 0xffffffffu, 0x92b1d3f2u, 0xd9178aaau, 0xf945bf9bu, 0xffffffffu, 0x92b1d3f2u, 0x00000000u, 0x69442c09u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xffffffffu, 0x92b1d3f2u, 0x26e87555u, 0x6ffe6c6du, 0xffffffffu, 0x92b1d3f2u, 0xd9178aaau, 0xfd4fbf9fu, 0xffffffffu, 0x92b1d3f2u, 0x00000000u, 0x040a0004u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xffffffffu, 0x92b1d3f2u, 0x26e87555u, 0x06ba4064u, 0xffffffffu, 0x92b1d3f2u, 0xd9178aaau, 0x90019392u, 0xffffffffu, 0x92b1d3f2u, 0xffffffffu, 0x96bbd3f6u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xd9178aaau, 0x90019392u, 0xd9178aaau, 0x90019392u, 0x00000000u, 0x69442c09u, 0x00000000u, 0x69442c09u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e26d38eu, 0xffff8001u, 0x26e7f555u, 0x26e8f554u, 0x8b46a001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x00020001u, 0x40000000u, 0x80000000u, 0x80000000u, 0x40000000u, 0xfffdfffeu, 0xfffdfffeu, 0xbfffffffu, 0x000f000fu, 0x000f000fu, 0xf0000000u, 0x80004000u, 0x00000002u, 0x80004000u, 0x0000001eu, 0x0000027fu, 0x0000003du, 0x00000002u, 0x00000003u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00000001u, 0x0000000eu, 0x00000001u, 0x00000001u, 0x00000001u, 0x80000000u, 0x80000001u, 0x00007fffu, 0x80000000u, 0x40000000u, 0x80000001u, 0x40000000u, 0x00000011u, 0x00008011u, 0x00000000u, 0x00020001u, 0x00000003u, 0x0000000cu, 0x00020001u, 0x00020001u, 0x40000000u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00020001u, 0x40000000u, 0x00020001u, 0x40000000u, 0x00020001u, 0x40000000u, 0x00020001u, 0x40000000u, 0x00020001u, 0x40000000u, 0x00020001u, 0x40000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x40000000u, 0x40020001u, 0x40000000u, 0x40020001u, 0x40000000u, 0x00020001u, 0x40000000u, 0xbfffffffu, 0x40000000u, 0xffffffffu, 0x40000000u, 0xbffdfffeu, 0x40000000u, 0xbffdfffeu, 0x00000000u, 0x0000007fu, 0x40000000u, 0x40000000u, 0x40000000u, 0xfffdfffeu, 0x40000000u, 0x40000000u, 0x00020001u, 0x00020001u, 0x80000000u, 0x40000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x80000000u, 0x40000000u, 0x80020001u, 0x40000000u, 0x80000000u, 0x40000000u, 0x80020001u, 0x00000000u, 0x80000000u, 0x40000000u, 0x00020001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x80000000u, 0x40000000u, 0x7fffffffu, 0xffffffffu, 0x80000000u, 0x40000000u, 0xffffffffu, 0xbfffffffu, 0x80000000u, 0x40000000u, 0x7ffdfffeu, 0xbfffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x80000000u, 0x40000000u, 0x7ffdfffeu, 0xffffffffu, 0x80000000u, 0x40000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x40000000u, 0xfffdfffeu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00020001u, 0x00000000u, 0x00020001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e26ab8eu, 0xffff8001u, 0x00018001u, 0x00028000u, 0xfff9fffdu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x4f426dcbu, 0xfe3b890bu, 0x72158370u, 0x72158370u, 0xf0f0f0f0u, 0xb0bd9234u, 0xb0bd9234u, 0x01c476f4u, 0xffffffffu, 0xffffffffu, 0xffffff0fu, 0xd3b642f2u, 0xd091dc7fu, 0xd3b642f2u, 0x0000000fu, 0x0000027fu, 0x0000001du, 0x00000011u, 0x00000023u, 0x00000002u, 0x00000002u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000007u, 0xffffffcbu, 0x00006dcbu, 0x72148370u, 0x72158370u, 0x00007fffu, 0x72158370u, 0xf0f0f0f0u, 0x72158370u, 0xf0f1f0f0u, 0x000000ffu, 0x0000fdffu, 0x00000000u, 0x4f426dcbu, 0x3cf3f0cfu, 0x30ff300cu, 0x4f426dcbu, 0x4f426dcbu, 0xfe3b890bu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x4f426dcbu, 0xf0f0f0f0u, 0x4f426dcbu, 0xfe3b890bu, 0x4f426dcbu, 0xfe3b890bu, 0x4f426dcbu, 0xfe3b890bu, 0x72158370u, 0x4f426dcbu, 0x4f426dcbu, 0xfe3b890bu, 0xfe3b890bu, 0x4e02090bu, 0x00000000u, 0x0000000fu, 0xfe3b890bu, 0xff7bedcbu, 0xfe3b890bu, 0xb179e4c0u, 0xfe3b890bu, 0x014064c0u, 0xfe3b890bu, 0x4fc67fffu, 0xfe3b890bu, 0xb1fdf6f4u, 0xfe3b890bu, 0x00841234u, 0xfe3b890bu, 0x4e861b3fu, 0x00000000u, 0x0000007fu, 0xfe3b890bu, 0xb0398000u, 0xfe3b890bu, 0xfebf9b3fu, 0xb0398000u, 0xb0398000u, 0x014064c0u, 0x014064c0u, 0x72158370u, 0xf0f0f0f0u, 0x42000140u, 0xf0308000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x72158370u, 0xf0f0f0f0u, 0x7f57effbu, 0xfefbf9fbu, 0x72158370u, 0xf0f0f0f0u, 0x3d57eebbu, 0x0ecb79fbu, 0x72158370u, 0xf0f0f0f0u, 0x0d426c8bu, 0x0e0b090bu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x72158370u, 0xf0f0f0f0u, 0xcfea7dcfu, 0xff3f8f0fu, 0x72158370u, 0xf0f0f0f0u, 0xbdfffebfu, 0x0fcf7fffu, 0x72158370u, 0xf0f0f0f0u, 0x80a81004u, 0x01040604u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x72158370u, 0xf0f0f0f0u, 0xc2a81144u, 0xf1348604u, 0x72158370u, 0xf0f0f0f0u, 0x30158230u, 0x00c070f0u, 0x72158370u, 0xf0f0f0f0u, 0xf2bd9374u, 0xf1f4f6f4u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x30158230u, 0x00c070f0u, 0x30158230u, 0x00c070f0u, 0x0d426c8bu, 0x0e0b090bu, 0x0d426c8bu, 0x0e0b090bu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69a6cb8eu, 0xffff8001u, 0x4f41edcbu, 0x4f42edcau, 0x1238b69fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x5affb229u, 0xfffffffeu, 0x5affb229u, 0x5affb229u, 0xfffffffeu, 0xa5004dd6u, 0xa5004dd6u, 0x00000001u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x944dff5au, 0x7fffffffu, 0x944dff5au, 0x0000000du, 0x000003ffu, 0x0000000eu, 0x00000013u, 0x00000032u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x0000001fu, 0x00000029u, 0xffffb229u, 0x0000ffffu, 0x8000ffffu, 0x00007fffu, 0x0000ffffu, 0x211c70cfu, 0x0000ffffu, 0xa11c70cfu, 0x000000ffu, 0x0000ffffu, 0x00000000u, 0x5affb229u, 0xcf0c0cc3u, 0x33ccffffu, 0x211c70cfu, 0x0000ffffu, 0x211c70cfu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x0000ffffu, 0x211c70cfu, 0x5affb229u, 0x5affb229u, 0x0000ffffu, 0x211c70cfu, 0x5affb229u, 0xfffffffeu, 0x211c70cfu, 0x211c70cfu, 0x5affb229u, 0xfffffffeu, 0xfffffffeu, 0x5affb228u, 0x00000000u, 0x0000000fu, 0xfffffffeu, 0xffffffffu, 0xfffffffeu, 0xa5004dd7u, 0xfffffffeu, 0x00000001u, 0xfffffffeu, 0x5affb229u, 0xfffffffeu, 0xa5004dd7u, 0xfffffffeu, 0x00000000u, 0xfffffffeu, 0x5affb228u, 0x00000000u, 0x0000007du, 0xfffffffeu, 0xa5004dd6u, 0xfffffffeu, 0xfffffffeu, 0xa5004dd6u, 0xa5004dd6u, 0x00000001u, 0x00000001u, 0x0000ffffu, 0x211c70cfu, 0x0000b229u, 0x211c70ceu, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x0000ffffu, 0x211c70cfu, 0x5affffffu, 0xffffffffu, 0x0000ffffu, 0x211c70cfu, 0x5aff4dd6u, 0xdee38f31u, 0x0000ffffu, 0x211c70cfu, 0x5aff0000u, 0xdee38f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0000ffffu, 0x211c70cfu, 0xffffb229u, 0xfffffffeu, 0x0000ffffu, 0x211c70cfu, 0xffff4dd6u, 0xdee38f31u, 0x0000ffffu, 0x211c70cfu, 0xa5000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0000ffffu, 0x211c70cfu, 0xa500b229u, 0x211c70ceu, 0x0000ffffu, 0x211c70cfu, 0x00004dd6u, 0x00000001u, 0x0000ffffu, 0x211c70cfu, 0xa500ffffu, 0x211c70cfu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00004dd6u, 0x00000001u, 0x00004dd6u, 0x00000001u, 0x5aff0000u, 0xdee38f30u, 0x5aff0000u, 0xdee38f30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e38cb8eu, 0xffff8001u, 0x5aff3229u, 0x5b003228u, 0xef00e985u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x7f1b103cu, 0x9abcdef0u, 0x7f1b103cu, 0x7f1b103cu, 0x9abcdef0u, 0x80e4efc3u, 0x80e4efc3u, 0x6543210fu, 0xfffff0ffu, 0xfffff0ffu, 0xfffffff0u, 0x3c08d8feu, 0x0f7b3d59u, 0x3c08d8feu, 0x00000010u, 0x000003ffu, 0x0000001du, 0x00000010u, 0x00000023u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000002u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000001u, 0x0000003cu, 0x0000103cu, 0x00007fffu, 0x80007fffu, 0x00007fffu, 0x00007fffu, 0x51bc52d9u, 0x00007fffu, 0xd1bc52d9u, 0x000000fbu, 0x0000fefbu, 0x00000000u, 0x7f1b103cu, 0x03000ff0u, 0x3fff03cfu, 0xd1bc52d9u, 0x00007fffu, 0xd1bc52d9u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00007fffu, 0xd1bc52d9u, 0x7f1b103cu, 0x7f1b103cu, 0x00007fffu, 0xd1bc52d9u, 0x7f1b103cu, 0x9abcdef0u, 0xd1bc52d9u, 0xd1bc52d9u, 0x7f1b103cu, 0x9abcdef0u, 0x9abcdef0u, 0x1a181030u, 0x00000000u, 0x0000000fu, 0x9abcdef0u, 0xffbfdefcu, 0x9abcdef0u, 0xe5a7ceccu, 0x9abcdef0u, 0x6503000cu, 0x9abcdef0u, 0x7f5b313fu, 0x9abcdef0u, 0xe5e7efcfu, 0x9abcdef0u, 0x00402103u, 0x9abcdef0u, 0x1a583133u, 0x00000000u, 0x0000007fu, 0x9abcdef0u, 0x80a4cec0u, 0x9abcdef0u, 0x9afcfff3u, 0x80a4cec0u, 0x80a4cec0u, 0x6503000cu, 0x6503000cu, 0x00007fffu, 0xd1bc52d9u, 0x0000103cu, 0x90bc52d0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00007fffu, 0xd1bc52d9u, 0x7f1b7fffu, 0xdbbcdef9u, 0x00007fffu, 0xd1bc52d9u, 0x7f1b6fc3u, 0x4b008c29u, 0x00007fffu, 0xd1bc52d9u, 0x7f1b0000u, 0x0a008c20u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00007fffu, 0xd1bc52d9u, 0xffff903cu, 0xbefffff6u, 0x00007fffu, 0xd1bc52d9u, 0xffffefc3u, 0x6f43ad2fu, 0x00007fffu, 0xd1bc52d9u, 0x80e48000u, 0x24432106u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00007fffu, 0xd1bc52d9u, 0x80e4903cu, 0xb4ff73d6u, 0x00007fffu, 0xd1bc52d9u, 0x00006fc3u, 0x41000009u, 0x00007fffu, 0xd1bc52d9u, 0x80e4ffffu, 0xf5ff73dfu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00006fc3u, 0x41000009u, 0x00006fc3u, 0x41000009u, 0x7f1b0000u, 0x0a008c20u, 0x7f1b0000u, 0x0a008c20u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e38cb8eu, 0xffff8001u, 0x7f1a903cu, 0x7f1b903bu, 0x82aecf4cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0xfc891b4au, 0xf0f0f0f0u, 0x26a2c0bdu, 0x26a2c0bdu, 0x00000020u, 0x0376e4b5u, 0x0376e4b5u, 0x0f0f0f0fu, 0xffffffffu, 0xffffffffu, 0xf0f0f0f0u, 0x52d8913fu, 0x0f0f0f0fu, 0x52d8913fu, 0x00000010u, 0x0000027fu, 0x00000020u, 0x00000010u, 0x00000020u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000006u, 0x00000004u, 0x0000004au, 0x00001b4au, 0x06a2c0bdu, 0x26a2c0bdu, 0x00007fffu, 0x26a2c0bdu, 0x00000020u, 0x26a2c0bdu, 0x20000020u, 0x000000ffu, 0x0000aaffu, 0x00000000u, 0x0376e4b6u, 0x03cf30ccu, 0xfff0c0c3u, 0xf0f0f0f0u, 0xfc891b4au, 0xf0f0f0f0u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x26a2c0bdu, 0xfc891b4au, 0xfc891b4au, 0xf0f0f0f0u, 0xfc891b4au, 0xf0f0f0f0u, 0xfc891b4au, 0xf0f0f0f0u, 0x26a2c0bdu, 0x00000020u, 0xf0f0f0f0u, 0xf0f0f0f0u, 0xf0f0f0f0u, 0xf0801040u, 0x00000000u, 0x0000000fu, 0xf0f0f0f0u, 0xfcf9fbfau, 0xf0f0f0f0u, 0x0c79ebbau, 0xf0f0f0f0u, 0x0c090b0au, 0xf0f0f0f0u, 0xff8f1f4fu, 0xf0f0f0f0u, 0x0f7fefbfu, 0xf0f0f0f0u, 0x03060405u, 0xf0f0f0f0u, 0xf3861445u, 0x00000000u, 0x0000007fu, 0xf0f0f0f0u, 0x0070e0b0u, 0xf0f0f0f0u, 0xf3f6f4f5u, 0x0070e0b0u, 0x0070e0b0u, 0x0c090b0au, 0x0c090b0au, 0x26a2c0bdu, 0x00000020u, 0x24800008u, 0x00000020u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x26a2c0bdu, 0x00000020u, 0xfeabdbffu, 0xf0f0f0f0u, 0x26a2c0bdu, 0x00000020u, 0xda2bdbf7u, 0xf0f0f0d0u, 0x26a2c0bdu, 0x00000020u, 0xd8091b42u, 0xf0f0f0d0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x26a2c0bdu, 0x00000020u, 0xfddd3f4au, 0xffffffffu, 0x26a2c0bdu, 0x00000020u, 0xdb7ffff7u, 0xffffffdfu, 0x26a2c0bdu, 0x00000020u, 0x01542400u, 0x0f0f0f0fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x26a2c0bdu, 0x00000020u, 0x25d42408u, 0x0f0f0f2fu, 0x26a2c0bdu, 0x00000020u, 0x0222c0b5u, 0x00000000u, 0x26a2c0bdu, 0x00000020u, 0x27f6e4bdu, 0x0f0f0f2fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0222c0b5u, 0x00000000u, 0x0222c0b5u, 0x00000000u, 0xd8091b42u, 0xf0f0f0d0u, 0xd8091b42u, 0xf0f0f0d0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69b8aa6eu, 0xffff8001u, 0xfc889b4au, 0xfc899b49u, 0x0a64ae22u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x0000003fu, 0x00000000u, 0x0000003fu, 0x0000003fu, 0x00000000u, 0xffffffc0u, 0xffffffc0u, 0xffffffffu, 0x000000ffu, 0x000000ffu, 0x00000000u, 0xfc000000u, 0x00000000u, 0xfc000000u, 0x0000001au, 0x000003ffu, 0x0000003au, 0x00000006u, 0x00000006u, 0x00000006u, 0x00000006u, 0x00000000u, 0x00000000u, 0x0000001au, 0x0000003au, 0x0000001au, 0x0000003au, 0x0000003fu, 0x0000003fu, 0x0f0f0f0fu, 0x0f0f8f0fu, 0x00007fffu, 0x0f0f0f0fu, 0x519088f5u, 0x0f0f8f0fu, 0x519088f5u, 0x00000003u, 0x00000003u, 0x00000000u, 0x0000003fu, 0x00000fffu, 0x00000000u, 0x519088f5u, 0x0f0f0f0fu, 0x519088f5u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x0f0f0f0fu, 0x519088f5u, 0x0000003fu, 0x0000003fu, 0x0f0f0f0fu, 0x519088f5u, 0x0000003fu, 0x00000000u, 0x0f0f0f0fu, 0x519088f5u, 0x0000003fu, 0x519088f5u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00000000u, 0x0000003fu, 0x00000000u, 0x0000003fu, 0x00000000u, 0x0000003fu, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffffu, 0x00000000u, 0xffffffc0u, 0x00000000u, 0xffffffc0u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffc0u, 0x00000000u, 0x00000000u, 0x0000003fu, 0x0000003fu, 0x0f0f0f0fu, 0x519088f5u, 0x0000000fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000000bu, 0x0f0f0f0fu, 0x519088f5u, 0x0f0f0f3fu, 0x519088f5u, 0x0f0f0f0fu, 0x519088f5u, 0x0f0f0f30u, 0x519088f5u, 0x0f0f0f0fu, 0x519088f5u, 0x00000030u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0f0f0f0fu, 0x519088f5u, 0xf0f0f0ffu, 0xae6f770au, 0x0f0f0f0fu, 0x519088f5u, 0xfffffff0u, 0xffffffffu, 0x0f0f0f0fu, 0x519088f5u, 0xf0f0f0c0u, 0xae6f770au, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0f0f0f0fu, 0x519088f5u, 0xf0f0f0cfu, 0xae6f770au, 0x0f0f0f0fu, 0x519088f5u, 0x0f0f0f00u, 0x519088f5u, 0x0f0f0f0fu, 0x519088f5u, 0xffffffcfu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x0f0f0f00u, 0x519088f5u, 0x0f0f0f00u, 0x519088f5u, 0x00000030u, 0x00000000u, 0x00000030u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69a6d389u, 0xffff8001u, 0xffff803fu, 0x0000803eu, 0xffffff43u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x00000003u, 0x11111111u, 0x00000003u, 0x00000003u, 0x11111111u, 0xfffffffcu, 0xfffffffcu, 0xeeeeeeeeu, 0x0000000fu, 0x0000000fu, 0xffffffffu, 0xc0000000u, 0x88888888u, 0xc0000000u, 0x0000001eu, 0x000003ffu, 0x00000036u, 0x00000002u, 0x0000000au, 0x00000002u, 0x00000002u, 0x00000000u, 0x00000000u, 0x0000001eu, 0x00000003u, 0x0000001eu, 0x00000003u, 0x00000003u, 0x00000003u, 0x00000003u, 0x0000000bu, 0x00007fffu, 0x00000003u, 0x11111111u, 0x0000000bu, 0x11111111u, 0x00000001u, 0x0000ff01u, 0x00000000u, 0x00000003u, 0x0000000fu, 0x00000000u, 0x11111111u, 0x00000003u, 0x11111111u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000003u, 0x11111111u, 0x00000003u, 0x00000003u, 0x00000003u, 0x11111111u, 0x00000003u, 0x11111111u, 0x11111111u, 0x11111111u, 0x00000003u, 0x11111111u, 0x11111111u, 0x00000001u, 0x00000000u, 0x0000000fu, 0x11111111u, 0x11111113u, 0x11111111u, 0x11111112u, 0x11111111u, 0x00000002u, 0x11111111u, 0xeeeeeeefu, 0x11111111u, 0xfffffffeu, 0x11111111u, 0xeeeeeeecu, 0x11111111u, 0xeeeeeeedu, 0x00000000u, 0x0000007fu, 0x11111111u, 0x11111110u, 0x11111111u, 0xfffffffdu, 0x11111110u, 0x11111110u, 0x00000002u, 0x00000002u, 0x00000003u, 0x11111111u, 0x00000003u, 0x11111111u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00000003u, 0x11111111u, 0x00000003u, 0x11111111u, 0x00000003u, 0x11111111u, 0x00000000u, 0x00000000u, 0x00000003u, 0x11111111u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000003u, 0x11111111u, 0xffffffffu, 0xffffffffu, 0x00000003u, 0x11111111u, 0xfffffffcu, 0xeeeeeeeeu, 0x00000003u, 0x11111111u, 0xfffffffcu, 0xeeeeeeeeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000003u, 0x11111111u, 0xffffffffu, 0xffffffffu, 0x00000003u, 0x11111111u, 0x00000000u, 0x00000000u, 0x00000003u, 0x11111111u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000005u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x12cb5389u, 0xffff8001u, 0xffff8003u, 0x00008002u, 0xfffffff7u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x00000021u, 0x00000003u, 0x00000021u, 0x00000021u, 0x00000003u, 0xffffffdeu, 0xffffffdeu, 0xfffffffcu, 0x000000ffu, 0x000000ffu, 0x0000000fu, 0x84000000u, 0xc0000000u, 0x84000000u, 0x0000001eu, 0x000003ffu, 0x0000003cu, 0x00000002u, 0x00000004u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x0000001au, 0x0000001eu, 0x0000001au, 0x0000001eu, 0x00000021u, 0x00000021u, 0x00000020u, 0x00000021u, 0x00007fffu, 0x00000020u, 0x00000002u, 0x00000020u, 0x00000003u, 0x00000003u, 0x00000103u, 0x00000000u, 0x00000021u, 0x00000c03u, 0x00000000u, 0x00000021u, 0x00000021u, 0x00000003u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000021u, 0x00000003u, 0x00000021u, 0x00000003u, 0x00000021u, 0x00000003u, 0x00000021u, 0x00000003u, 0x00000021u, 0x00000003u, 0x00000021u, 0x00000003u, 0x00000003u, 0x00000001u, 0x00000000u, 0x0000000fu, 0x00000003u, 0x00000023u, 0x00000003u, 0x00000022u, 0x00000003u, 0x00000020u, 0x00000003u, 0xfffffffdu, 0x00000003u, 0xfffffffeu, 0x00000003u, 0xffffffdcu, 0x00000003u, 0xffffffddu, 0x00000000u, 0x0000007fu, 0x00000003u, 0x00000002u, 0x00000003u, 0xffffffdfu, 0x00000002u, 0x00000002u, 0x00000020u, 0x00000020u, 0x00000020u, 0x00000003u, 0x00000020u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00000020u, 0x00000003u, 0x00000021u, 0x00000003u, 0x00000020u, 0x00000003u, 0x00000001u, 0x00000000u, 0x00000020u, 0x00000003u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000020u, 0x00000003u, 0xffffffffu, 0xffffffffu, 0x00000020u, 0x00000003u, 0xffffffdfu, 0xfffffffcu, 0x00000020u, 0x00000003u, 0xffffffdeu, 0xfffffffcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000020u, 0x00000003u, 0xfffffffeu, 0xffffffffu, 0x00000020u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000020u, 0x00000003u, 0xfffffffeu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000005u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x2e38ab89u, 0xffff8001u, 0xffff8021u, 0x00008020u, 0xffffff9du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0xf0f0f0f0u, 0xffff8000u, 0xf0f0f0f0u, 0xf0f0f0f0u, 0xfffffffeu, 0x0f0f0f0fu, 0x0f0f0f0fu, 0x00007fffu, 0xf0f0f0f0u, 0xf0f0f0f0u, 0xfffff000u, 0x0f0f0f0fu, 0x0001ffffu, 0x0f0f0f0fu, 0x00000010u, 0x0000027fu, 0x0000001fu, 0x00000010u, 0x00000021u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000011u, 0xfffffff0u, 0xfffff0f0u, 0xf0f0f0f0u, 0xf0f1f0f0u, 0x00007fffu, 0xf0f0f0f0u, 0xfffefffeu, 0xf0f0f0f0u, 0xfffffffeu, 0x000000aau, 0x0000f8aau, 0x00000000u, 0x0f0f0f10u, 0xff00ff00u, 0xff00ff00u, 0xf0f0f0f0u, 0xf0f0f0f0u, 0xffff8000u, 0x00000000u, 0x00000000u, 0x000000ffu, 0xf0f0f0f0u, 0xfffffffeu, 0xf0f0f0f0u, 0xffff8000u, 0xf0f0f0f0u, 0xffff8000u, 0xf0f0f0f0u, 0xffff8000u, 0xf0f0f0f0u, 0xfffffffeu, 0xf0f0f0f0u, 0xffff8000u, 0xffff8000u, 0xf0f08000u, 0x00000000u, 0x0000000fu, 0xffff8000u, 0xfffff0f0u, 0xffff8000u, 0x0f0f70f0u, 0xffff8000u, 0x000070f0u, 0xffff8000u, 0xf0f0ffffu, 0xffff8000u, 0x0f0f7fffu, 0xffff8000u, 0x00000f0fu, 0xffff8000u, 0xf0f08f0fu, 0x00000000u, 0x0000007fu, 0xffff8000u, 0x0f0f0000u, 0xffff8000u, 0xffff8f0fu, 0x0f0f0000u, 0x0f0f0000u, 0x000070f0u, 0x000070f0u, 0xf0f0f0f0u, 0xfffffffeu, 0xf0f0f0f0u, 0xffff8000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0xf0f0f0f0u, 0xfffffffeu, 0xf0f0f0f0u, 0xfffffffeu, 0xf0f0f0f0u, 0xfffffffeu, 0x00000000u, 0x00007ffeu, 0xf0f0f0f0u, 0xfffffffeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000006u, 0xf0f0f0f0u, 0xfffffffeu, 0xffffffffu, 0xffff8001u, 0xf0f0f0f0u, 0xfffffffeu, 0x0f0f0f0fu, 0x00007fffu, 0xf0f0f0f0u, 0xfffffffeu, 0x0f0f0f0fu, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xf0f0f0f0u, 0xfffffffeu, 0xffffffffu, 0xffff8001u, 0xf0f0f0f0u, 0xfffffffeu, 0x00000000u, 0x00007ffeu, 0xf0f0f0f0u, 0xfffffffeu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0x00007ffeu, 0x00000000u, 0x00007ffeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x52caca6eu, 0xffff8001u, 0xf0f070f0u, 0xf0f170efu, 0x2d2d2d30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x7afb2c68u, 0x80000000u, 0x00010003u, 0x00010003u, 0x7a86f7a2u, 0x8504d397u, 0x8504d397u, 0x7fffffffu, 0xffffffffu, 0xffffffffu, 0xf0000000u, 0x1634df5eu, 0x00000001u, 0x1634df5eu, 0x0000000eu, 0x0000027fu, 0x0000002du, 0x00000012u, 0x00000013u, 0x00000000u, 0x00000000u, 0x00000003u, 0x00000003u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000068u, 0x00002c68u, 0x00010003u, 0x0001000bu, 0x00007fffu, 0x00010003u, 0x7a86f7a2u, 0x0001000bu, 0x7a86f7a2u, 0x000000ffu, 0x000080ffu, 0x00000000u, 0x7afb2c68u, 0x0cf03cc0u, 0x3fccffcfu, 0x7a86f7a2u, 0x00010003u, 0x7a86f7a2u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00010003u, 0x7a86f7a2u, 0x7afb2c68u, 0x7afb2c68u, 0x00010003u, 0x7a86f7a2u, 0x7afb2c68u, 0x80000000u, 0x00010003u, 0x7a86f7a2u, 0x7afb2c68u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x80000000u, 0xfafb2c68u, 0x80000000u, 0xfafb2c68u, 0x80000000u, 0x7afb2c68u, 0x80000000u, 0x7fffffffu, 0x80000000u, 0xffffffffu, 0x80000000u, 0x0504d397u, 0x80000000u, 0x0504d397u, 0x00000000u, 0x0000007fu, 0x80000000u, 0x80000000u, 0x80000000u, 0x8504d397u, 0x80000000u, 0x80000000u, 0x7afb2c68u, 0x7afb2c68u, 0x00010003u, 0x7a86f7a2u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00010003u, 0x7a86f7a2u, 0x7afb2c6bu, 0xfa86f7a2u, 0x00010003u, 0x7a86f7a2u, 0x7afa2c6bu, 0xfa86f7a2u, 0x00010003u, 0x7a86f7a2u, 0x7afa2c68u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010003u, 0x7a86f7a2u, 0xfffffffcu, 0x8579085du, 0x00010003u, 0x7a86f7a2u, 0xfffeffffu, 0xffffffffu, 0x00010003u, 0x7a86f7a2u, 0x8504d394u, 0x0579085du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010003u, 0x7a86f7a2u, 0x8505d394u, 0x0579085du, 0x00010003u, 0x7a86f7a2u, 0x00000003u, 0x7a86f7a2u, 0x00010003u, 0x7a86f7a2u, 0x8505d397u, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000003u, 0x7a86f7a2u, 0x00000003u, 0x7a86f7a2u, 0x7afa2c68u, 0x80000000u, 0x7afa2c68u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e38ab8eu, 0xffff8001u, 0x7afaac68u, 0x7afbac67u, 0x8f0e7ac8u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0xaaaaaaaau, 0x00000001u, 0xaaaaaaaau, 0xaaaaaaaau, 0xdd02de92u, 0x55555555u, 0x55555555u, 0xfffffffeu, 0xffffffffu, 0xffffffffu, 0x0000000fu, 0x55555555u, 0x80000000u, 0x55555555u, 0x00000010u, 0x0000027fu, 0x0000002fu, 0x00000010u, 0x00000011u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x0000001fu, 0x00000001u, 0x0000001fu, 0xffffffaau, 0xffffaaaau, 0xaaaaaaaau, 0xaaaaaeaau, 0x00007fffu, 0xaaaaaaaau, 0xdd02da92u, 0xaaaaaaaau, 0xdd02de92u, 0x000000ffu, 0x000001ffu, 0x00000000u, 0x55555556u, 0xccccccccu, 0xccccccccu, 0xaaaaaaaau, 0xaaaaaaaau, 0xdd02de92u, 0x00000000u, 0x00000000u, 0x000000ffu, 0xaaaaaaaau, 0xdd02de92u, 0xaaaaaaaau, 0x00000001u, 0xaaaaaaaau, 0xdd02de92u, 0xaaaaaaaau, 0x00000001u, 0xaaaaaaaau, 0xdd02de92u, 0xaaaaaaaau, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00000001u, 0xaaaaaaabu, 0x00000001u, 0xaaaaaaabu, 0x00000001u, 0xaaaaaaaau, 0x00000001u, 0xfffffffeu, 0x00000001u, 0xffffffffu, 0x00000001u, 0x55555554u, 0x00000001u, 0x55555554u, 0x00000000u, 0x0000007fu, 0x00000001u, 0x00000001u, 0x00000001u, 0x55555555u, 0x00000001u, 0x00000001u, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xdd02de92u, 0xaaaaaaaau, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0xaaaaaaaau, 0xdd02de92u, 0xaaaaaaaau, 0xdd02de93u, 0xaaaaaaaau, 0xdd02de92u, 0x00000000u, 0xdd02de93u, 0xaaaaaaaau, 0xdd02de92u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xaaaaaaaau, 0xdd02de92u, 0xffffffffu, 0x22fd216du, 0xaaaaaaaau, 0xdd02de92u, 0x55555555u, 0xffffffffu, 0xaaaaaaaau, 0xdd02de92u, 0x55555555u, 0x22fd216cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xaaaaaaaau, 0xdd02de92u, 0xffffffffu, 0x22fd216cu, 0xaaaaaaaau, 0xdd02de92u, 0x00000000u, 0xdd02de92u, 0xaaaaaaaau, 0xdd02de92u, 0xffffffffu, 0xfffffffeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0xdd02de92u, 0x00000000u, 0xdd02de92u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x72cad26eu, 0xffff8001u, 0xaaaa2aaau, 0xaaab2aa9u, 0x00000002u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x00000021u, 0x00010000u, 0x55555555u, 0x55555555u, 0x00010000u, 0xffffffdeu, 0xffffffdeu, 0xfffeffffu, 0x000000ffu, 0x000000ffu, 0x000f0000u, 0x84000000u, 0x00008000u, 0x84000000u, 0x0000001eu, 0x0000027fu, 0x0000003du, 0x00000002u, 0x00000003u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x0000001au, 0x0000000fu, 0x0000001au, 0x0000000fu, 0x00000021u, 0x00000021u, 0x55555555u, 0x55755555u, 0x00007fffu, 0x55555555u, 0x00010000u, 0x55755555u, 0x00010000u, 0x00000003u, 0x00001003u, 0x00000000u, 0x00000021u, 0x00000c03u, 0x00000000u, 0x00010000u, 0x00000021u, 0x00010000u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x55555555u, 0x00000021u, 0x00000021u, 0x00010000u, 0x00000021u, 0x00010000u, 0x00000021u, 0x00010000u, 0x55555555u, 0x00010000u, 0x00000021u, 0x00010000u, 0x00010000u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00010000u, 0x00010021u, 0x00010000u, 0x00010021u, 0x00010000u, 0x00000021u, 0x00010000u, 0xfffeffffu, 0x00010000u, 0xffffffffu, 0x00010000u, 0xfffeffdeu, 0x00010000u, 0xfffeffdeu, 0x00000000u, 0x0000007fu, 0x00010000u, 0x00010000u, 0x00010000u, 0xffffffdeu, 0x00010000u, 0x00010000u, 0x00000021u, 0x00000021u, 0x55555555u, 0x00010000u, 0x00000001u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x55555555u, 0x00010000u, 0x55555575u, 0x00010000u, 0x55555555u, 0x00010000u, 0x55555574u, 0x00000000u, 0x55555555u, 0x00010000u, 0x00000020u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x55555555u, 0x00010000u, 0xaaaaaaabu, 0xffffffffu, 0x55555555u, 0x00010000u, 0xfffffffeu, 0xfffeffffu, 0x55555555u, 0x00010000u, 0xaaaaaa8au, 0xfffeffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x55555555u, 0x00010000u, 0xaaaaaa8bu, 0xffffffffu, 0x55555555u, 0x00010000u, 0x55555554u, 0x00000000u, 0x55555555u, 0x00010000u, 0xffffffdfu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x55555554u, 0x00000000u, 0x55555554u, 0x00000000u, 0x00000020u, 0x00000000u, 0x00000020u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x69a6d389u, 0xffff8001u, 0xffff8021u, 0x00008020u, 0xffffff9du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x31f51707u, 0xd17e4497u, 0x31f51707u, 0x31f51707u, 0xd17e4497u, 0xce0ae8f8u, 0xce0ae8f8u, 0x2e81bb68u, 0xffffff0fu, 0xffffff0fu, 0xffffffffu, 0xe0e8af8cu, 0xe9227e8bu, 0xe0e8af8cu, 0x00000010u, 0x000003ffu, 0x0000001fu, 0x00000010u, 0x00000021u, 0x00000003u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000002u, 0x00000002u, 0x00000007u, 0x00001707u, 0x00010000u, 0x00010001u, 0x00007fffu, 0x00010000u, 0xd17e4497u, 0x00010001u, 0xd17e4497u, 0x000000fdu, 0x0000fffdu, 0x00000000u, 0x31f51707u, 0x033f003fu, 0x0f03ff33u, 0x31f51707u, 0x31f51707u, 0xd17e4497u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x31f51707u, 0xd17e4497u, 0x31f51707u, 0xd17e4497u, 0x31f51707u, 0xd17e4497u, 0x31f51707u, 0xd17e4497u, 0x00010000u, 0x31f51707u, 0x31f51707u, 0xd17e4497u, 0xd17e4497u, 0x11740407u, 0x00000000u, 0x0000000fu, 0xd17e4497u, 0xf1ff5797u, 0xd17e4497u, 0xe08b5390u, 0xd17e4497u, 0x20811300u, 0xd17e4497u, 0x3ff5bf6fu, 0xd17e4497u, 0xee8bfbf8u, 0xd17e4497u, 0x0e00a868u, 0xd17e4497u, 0x1f74ac6fu, 0x00000000u, 0x0000007fu, 0xd17e4497u, 0xc00a4090u, 0xd17e4497u, 0xdf7eecffu, 0xc00a4090u, 0xc00a4090u, 0x20811300u, 0x20811300u, 0x00010000u, 0xd17e4497u, 0x00010000u, 0xd17e4497u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x00010000u, 0xd17e4497u, 0x31f51707u, 0xd17e4497u, 0x00010000u, 0xd17e4497u, 0x31f41707u, 0x00000000u, 0x00010000u, 0xd17e4497u, 0x31f41707u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010000u, 0xd17e4497u, 0xffffffffu, 0xffffffffu, 0x00010000u, 0xd17e4497u, 0xfffeffffu, 0x2e81bb68u, 0x00010000u, 0xd17e4497u, 0xce0ae8f8u, 0x2e81bb68u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00010000u, 0xd17e4497u, 0xce0be8f8u, 0xffffffffu, 0x00010000u, 0xd17e4497u, 0x00000000u, 0x00000000u, 0x00010000u, 0xd17e4497u, 0xce0be8f8u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000005u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x31f41707u, 0x00000000u, 0x31f41707u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x2e38ab8eu, 0xffff8001u, 0x31f49707u, 0x31f59706u, 0x6a20baebu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x00007fffu, 0x00000040u, 0x00007fffu, 0x00007fffu, 0x00000040u, 0xffff8000u, 0xffff8000u, 0xffffffbfu, 0x0000ffffu, 0x0000ffffu, 0x000000f0u, 0xfffe0000u, 0x02000000u, 0xfffe0000u, 0x00000011u, 0x000003ffu, 0x00000030u, 0x0000000fu, 0x00000010u, 0x0000000fu, 0x0000000fu, 0x00000000u, 0x00000000u, 0x00000011u, 0x00000019u, 0x00000011u, 0x00000019u, 0xffffffffu, 0x00007fffu, 0xf4de2c08u, 0xf4de2d08u, 0x00007fffu, 0xf4de2c08u, 0x00020001u, 0xf4de2d08u, 0x00020001u, 0x0000000fu, 0x0000020fu, 0x00000000u, 0x00007fffu, 0x3fffffffu, 0x00000000u, 0x00007fffu, 0x00007fffu, 0x00000040u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00007fffu, 0x00020001u, 0x00007fffu, 0x00000040u, 0x00007fffu, 0x00000040u, 0x00007fffu, 0x00000040u, 0xf4de2c08u, 0x00020001u, 0x00007fffu, 0x00000040u, 0x00000040u, 0x00000040u, 0x00000000u, 0x0000000fu, 0x00000040u, 0x00007fffu, 0x00000040u, 0x00007fbfu, 0x00000040u, 0x00007fbfu, 0x00000040u, 0xffffffffu, 0x00000040u, 0xffffffbfu, 0x00000040u, 0xffff8000u, 0x00000040u, 0xffff8040u, 0x00000000u, 0x0000007fu, 0x00000040u, 0x00000000u, 0x00000040u, 0xffff8040u, 0x00000000u, 0x00000000u, 0x00007fbfu, 0x00007fbfu, 0xf4de2c08u, 0x00020001u, 0x00002c08u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000000bu, 0xf4de2c08u, 0x00020001u, 0xf4de7fffu, 0x00020041u, 0xf4de2c08u, 0x00020001u, 0xf4de53f7u, 0x00020041u, 0xf4de2c08u, 0x00020001u, 0x000053f7u, 0x00000040u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xf4de2c08u, 0x00020001u, 0x0b21ffffu, 0xfffdfffeu, 0xf4de2c08u, 0x00020001u, 0xffffd3f7u, 0xffffffffu, 0xf4de2c08u, 0x00020001u, 0x0b218000u, 0xfffdffbeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xf4de2c08u, 0x00020001u, 0x0b21ac08u, 0xfffdffbeu, 0xf4de2c08u, 0x00020001u, 0xf4de0000u, 0x00020001u, 0xf4de2c08u, 0x00020001u, 0xffffac08u, 0xffffffbfu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xf4de0000u, 0x00020001u, 0xf4de0000u, 0x00020001u, 0x000053f7u, 0x00000040u, 0x000053f7u, 0x00000040u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e26a988u, 0xffff8001u, 0xffffffffu, 0x0000fffeu, 0xfffe8003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u}, + {0x00010000u, 0x00010003u, 0x00010000u, 0x00010000u, 0x00010003u, 0xfffeffffu, 0xfffeffffu, 0xfffefffcu, 0x000f0000u, 0x000f0000u, 0x000f000fu, 0x00008000u, 0xc0008000u, 0x00008000u, 0x0000001fu, 0x000003ffu, 0x0000003cu, 0x00000001u, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000010u, 0x00000010u, 0x0000000fu, 0x0000000fu, 0x0000000fu, 0x0000000fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00007fffu, 0x00000000u, 0x00010003u, 0x00000001u, 0x00010003u, 0x00000010u, 0x00001110u, 0x00000000u, 0x00010000u, 0x00000000u, 0x00000003u, 0x00010000u, 0x00010000u, 0x00010003u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00010000u, 0x00010003u, 0x00010000u, 0x00010003u, 0x00010000u, 0x00010003u, 0x00010000u, 0x00010003u, 0x00010000u, 0x00010003u, 0x00010000u, 0x00010003u, 0x00010003u, 0x00010000u, 0x00000000u, 0x0000000fu, 0x00010003u, 0x00010003u, 0x00010003u, 0x00000003u, 0x00010003u, 0x00000000u, 0x00010003u, 0xfffffffcu, 0x00010003u, 0xfffeffffu, 0x00010003u, 0xfffefffcu, 0x00010003u, 0xfffffffcu, 0x00000000u, 0x0000006fu, 0x00010003u, 0x00000003u, 0x00010003u, 0xffffffffu, 0x00000003u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010003u, 0x00000000u, 0x00010003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001du, 0x00000000u, 0x00010003u, 0x00010000u, 0x00010003u, 0x00000000u, 0x00010003u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00010003u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0x00010003u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00010003u, 0xffffffffu, 0xfffefffcu, 0x00000000u, 0x00010003u, 0xfffeffffu, 0xfffefffcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0x00010003u, 0xfffeffffu, 0xffffffffu, 0x00000000u, 0x00010003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010003u, 0xfffeffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000005u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010000u, 0x00000000u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x2e38d38eu, 0xffff8001u, 0x00008000u, 0x00017fffu, 0xfffd0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0xe8e72789u, 0x00000040u, 0xe8e72789u, 0xe8e72789u, 0x00000040u, 0x1718d876u, 0x1718d876u, 0xffffffbfu, 0xffffffffu, 0xffffffffu, 0x000000f0u, 0x91e4e717u, 0x02000000u, 0x91e4e717u, 0x0000000fu, 0x000003ffu, 0x0000002eu, 0x00000011u, 0x00000012u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000019u, 0x00000003u, 0x00000019u, 0xffffff89u, 0x00002789u, 0xe8e72589u, 0xe8e72789u, 0x00007fffu, 0xe8e72589u, 0x00010003u, 0xe8e72789u, 0x00010003u, 0x000000ffu, 0x000002ffu, 0x00000000u, 0x1718d877u, 0x0c3fc0c3u, 0xfcc0fc3fu, 0x00000040u, 0xe8e72789u, 0x00000040u, 0x00000000u, 0x00000000u, 0x000000ffu, 0xe8e72789u, 0xe8e72789u, 0xe8e72789u, 0x00000040u, 0xe8e72789u, 0x00000040u, 0xe8e72789u, 0x00000040u, 0xe8e72789u, 0x00010003u, 0xe8e72789u, 0x00000040u, 0x00000040u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x00000040u, 0xe8e727c9u, 0x00000040u, 0xe8e727c9u, 0x00000040u, 0xe8e72789u, 0x00000040u, 0xffffffbfu, 0x00000040u, 0xffffffffu, 0x00000040u, 0x1718d836u, 0x00000040u, 0x1718d836u, 0x00000000u, 0x0000007fu, 0x00000040u, 0x00000040u, 0x00000040u, 0x1718d876u, 0x00000040u, 0x00000040u, 0xe8e72789u, 0xe8e72789u, 0xe8e72789u, 0x00010003u, 0xe8e72789u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0xe8e72789u, 0x00010003u, 0xe8e72789u, 0x00010043u, 0xe8e72789u, 0x00010003u, 0x00000000u, 0x00010043u, 0xe8e72789u, 0x00010003u, 0x00000000u, 0x00000040u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xe8e72789u, 0x00010003u, 0xffffffffu, 0xfffefffcu, 0xe8e72789u, 0x00010003u, 0x1718d876u, 0xffffffffu, 0xe8e72789u, 0x00010003u, 0x1718d876u, 0xfffeffbcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xe8e72789u, 0x00010003u, 0xffffffffu, 0xfffeffbcu, 0xe8e72789u, 0x00010003u, 0x00000000u, 0x00010003u, 0xe8e72789u, 0x00010003u, 0xffffffffu, 0xffffffbfu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x00000000u, 0x00010003u, 0x00000000u, 0x00010003u, 0x00000000u, 0x00000040u, 0x00000000u, 0x00000040u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x72caaa6eu, 0xffff8001u, 0xe8e6a789u, 0xe8e7a788u, 0x454a8965u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x7691b06fu, 0x80000000u, 0x7691b06fu, 0x7691b06fu, 0x80000000u, 0x896e4f90u, 0x896e4f90u, 0x7fffffffu, 0xfffff0ffu, 0xfffff0ffu, 0xf0000000u, 0xf60d896eu, 0x00000001u, 0xf60d896eu, 0x0000000fu, 0x000003ffu, 0x0000002eu, 0x00000011u, 0x00000012u, 0x00000004u, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000001u, 0x0000006fu, 0xffffb06fu, 0x2b855c1fu, 0xab855c1fu, 0x00007fffu, 0x2b855c1fu, 0x070d7109u, 0xab855c1fu, 0x070d7109u, 0x000000fbu, 0x000080fbu, 0x00000000u, 0x7691b06fu, 0xcf003cffu, 0x3f3cc303u, 0x070d7109u, 0x2b855c1fu, 0x070d7109u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x2b855c1fu, 0x070d7109u, 0x7691b06fu, 0x7691b06fu, 0x2b855c1fu, 0x070d7109u, 0x7691b06fu, 0x80000000u, 0x2b855c1fu, 0x070d7109u, 0x7691b06fu, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x0000000eu, 0x80000000u, 0xf691b06fu, 0x80000000u, 0xf691b06fu, 0x80000000u, 0x7691b06fu, 0x80000000u, 0x7fffffffu, 0x80000000u, 0xffffffffu, 0x80000000u, 0x096e4f90u, 0x80000000u, 0x096e4f90u, 0x00000000u, 0x0000007fu, 0x80000000u, 0x80000000u, 0x80000000u, 0x896e4f90u, 0x80000000u, 0x80000000u, 0x7691b06fu, 0x7691b06fu, 0x2b855c1fu, 0x070d7109u, 0x2281100fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0x2b855c1fu, 0x070d7109u, 0x7f95fc7fu, 0x870d7109u, 0x2b855c1fu, 0x070d7109u, 0x5d14ec70u, 0x870d7109u, 0x2b855c1fu, 0x070d7109u, 0x5410a060u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x2b855c1fu, 0x070d7109u, 0xf6fbb3efu, 0xf8f28ef6u, 0x2b855c1fu, 0x070d7109u, 0xdd7eeff0u, 0xffffffffu, 0x2b855c1fu, 0x070d7109u, 0x806a0380u, 0x78f28ef6u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x2b855c1fu, 0x070d7109u, 0xa2eb138fu, 0x78f28ef6u, 0x2b855c1fu, 0x070d7109u, 0x09044c10u, 0x070d7109u, 0x2b855c1fu, 0x070d7109u, 0xabef5f9fu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x09044c10u, 0x070d7109u, 0x09044c10u, 0x070d7109u, 0x5410a060u, 0x80000000u, 0x5410a060u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e38d38eu, 0xffff8001u, 0x7691306fu, 0x7692306eu, 0x9c4aeeb3u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u}, + {0x256badf9u, 0x988af3fbu, 0x256badf9u, 0x256badf9u, 0x988af3fbu, 0xda945206u, 0xda945206u, 0x67750c04u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x9fb5d6a4u, 0xdfcf5119u, 0x9fb5d6a4u, 0x0000000du, 0x000003ffu, 0x0000001au, 0x00000013u, 0x00000026u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000002u, 0x00000001u, 0xfffffff9u, 0xffffadf9u, 0xa842bc19u, 0xaa42bc19u, 0x00007fffu, 0xa842bc19u, 0x27e9e06fu, 0xaa42bc19u, 0x27e9e06fu, 0x000000ffu, 0x0000ffffu, 0x00000000u, 0x256badf9u, 0xccf3ffc3u, 0x0c333ccfu, 0x988af3fbu, 0x256badf9u, 0x988af3fbu, 0x00000000u, 0x00000000u, 0x000000ffu, 0xa842bc19u, 0x256badf9u, 0x256badf9u, 0x988af3fbu, 0x256badf9u, 0x988af3fbu, 0x256badf9u, 0x988af3fbu, 0xa842bc19u, 0x27e9e06fu, 0x988af3fbu, 0x988af3fbu, 0x988af3fbu, 0x000aa1f9u, 0x00000000u, 0x0000000fu, 0x988af3fbu, 0xbdebfffbu, 0x988af3fbu, 0xbde15e02u, 0x988af3fbu, 0x25610c00u, 0x988af3fbu, 0x677fadfdu, 0x988af3fbu, 0xfff55e06u, 0x988af3fbu, 0x42140004u, 0x988af3fbu, 0x421ea1fdu, 0x00000000u, 0x0000007fu, 0x988af3fbu, 0x98805202u, 0x988af3fbu, 0xda9ef3ffu, 0x98805202u, 0x98805202u, 0x25610c00u, 0x25610c00u, 0xa842bc19u, 0x27e9e06fu, 0x2042ac19u, 0x0088e06bu, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu, 0xa842bc19u, 0x27e9e06fu, 0xad6bbdf9u, 0xbfebf3ffu, 0xa842bc19u, 0x27e9e06fu, 0x8d2911e0u, 0xbf631394u, 0xa842bc19u, 0x27e9e06fu, 0x052901e0u, 0x98021390u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xa842bc19u, 0x27e9e06fu, 0x77ffefffu, 0xd89efffbu, 0xa842bc19u, 0x27e9e06fu, 0xdfbd53e6u, 0xff771f94u, 0xa842bc19u, 0x27e9e06fu, 0x52944206u, 0x40140c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0xa842bc19u, 0x27e9e06fu, 0x72d6ee1fu, 0x409cec6bu, 0xa842bc19u, 0x27e9e06fu, 0x88001000u, 0x27610004u, 0xa842bc19u, 0x27e9e06fu, 0xfad6fe1fu, 0x67fdec6fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000007u, 0x88001000u, 0x27610004u, 0x88001000u, 0x27610004u, 0x052901e0u, 0x98021390u, 0x052901e0u, 0x98021390u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x6e26d38eu, 0xffff8001u, 0x256b2df9u, 0x256c2df8u, 0x8fbcf615u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000009u} +}; +constexpr const char* Names[176] = {"mov_b64 s12", "mov_b64 s13", "cmov_b32 s12", "cmov_b64 s12", "cmov_b64 s13", "not_b32 s12", "not_b64 s12", "not_b64 s13", "wqm_b32 s12", "wqm_b64 s12", "wqm_b64 s13", "brev_b32 s12", "brev_b64 s12", "brev_b64 s13", "bcnt0_i32_b32 s12", "scc mov_b64,cmov_b32,cmov_b64,not_b32,not_b64,wqm_b32,wqm_b64,brev_b32,brev_b64,bcnt0_i32_b32", "bcnt0_i32_b64 s12", "bcnt1_i32_b32 s12", "bcnt1_i32_b64 s12", "ff0_i32_b32 s12", "ff0_i32_b64 s12", "ff1_i32_b32 s12", "ff1_i32_b64 s12", "flbit_i32_b32 s12", "flbit_i32_b64 s12", "flbit_i32 s12", "flbit_i32_i64 s12", "sext_i32_i8 s12", "sext_i32_i16 s12", "bitset0_b32 s12", "bitset1_b32 s12", "scc bcnt0_i32_b64,bcnt1_i32_b32,bcnt1_i32_b64,ff0_i32_b32,ff0_i32_b64,ff1_i32_b32,ff1_i32_b64,flbit_i32_b32,flbit_i32_b64,flbit_i32,flbit_i32_i64,sext_i32_i8,sext_i32_i16,bitset0_b32,bitset1_b32", "bitset0_b64 s12", "bitset0_b64 s13", "bitset1_b64 s12", "bitset1_b64 s13", "quadmask_b32 s12", "quadmask_b64 s12", "quadmask_b64 s13", "abs_i32 s12", "bitreplicate_b64_b32 s12", "bitreplicate_b64_b32 s13", "movrels_b32 s12", "movrels_b64 s12", "movrels_b64 s13", nullptr, nullptr, "scc bitset0_b64,bitset1_b64,quadmask_b32,quadmask_b64,abs_i32,bitreplicate_b64_b32,movrels_b32,movrels_b64", "movreld_b32 s12", "movreld_b32 s13", "movreld_b32 s14", "movreld_b32 s15", "movreld_b64 s12", "movreld_b64 s13", "movreld_b64 s14", "movreld_b64 s15", "movrelsd_2_b32 s12", "movrelsd_2_b32 s13", "movrelsd_2_b32 s14", "movrelsd_2_b32 s15", "and_saveexec_b32 s12", "and_saveexec_b32 s13", nullptr, "scc movreld_b32,movreld_b64,movrelsd_2_b32,and_saveexec_b32", "or_saveexec_b32 s12", "or_saveexec_b32 s13", "xor_saveexec_b32 s12", "xor_saveexec_b32 s13", "andn2_saveexec_b32 s12", "andn2_saveexec_b32 s13", "orn2_saveexec_b32 s12", "orn2_saveexec_b32 s13", "nand_saveexec_b32 s12", "nand_saveexec_b32 s13", "nor_saveexec_b32 s12", "nor_saveexec_b32 s13", "xnor_saveexec_b32 s12", "xnor_saveexec_b32 s13", nullptr, "scc or_saveexec_b32,xor_saveexec_b32,andn2_saveexec_b32,orn2_saveexec_b32,nand_saveexec_b32,nor_saveexec_b32,xnor_saveexec_b32", "andn1_saveexec_b32 s12", "andn1_saveexec_b32 s13", "orn1_saveexec_b32 s12", "orn1_saveexec_b32 s13", "andn1_wrexec_b32 s12", "andn1_wrexec_b32 s13", "andn2_wrexec_b32 s12", "andn2_wrexec_b32 s13", "and_saveexec_b64 s12", "and_saveexec_b64 s13", "and_saveexec_b64 s14", "and_saveexec_b64 s15", nullptr, nullptr, nullptr, "scc andn1_saveexec_b32,orn1_saveexec_b32,andn1_wrexec_b32,andn2_wrexec_b32,and_saveexec_b64", "or_saveexec_b64 s12", "or_saveexec_b64 s13", "or_saveexec_b64 s14", "or_saveexec_b64 s15", "xor_saveexec_b64 s12", "xor_saveexec_b64 s13", "xor_saveexec_b64 s14", "xor_saveexec_b64 s15", "andn2_saveexec_b64 s12", "andn2_saveexec_b64 s13", "andn2_saveexec_b64 s14", "andn2_saveexec_b64 s15", nullptr, nullptr, nullptr, "scc or_saveexec_b64,xor_saveexec_b64,andn2_saveexec_b64", "orn2_saveexec_b64 s12", "orn2_saveexec_b64 s13", "orn2_saveexec_b64 s14", "orn2_saveexec_b64 s15", "nand_saveexec_b64 s12", "nand_saveexec_b64 s13", "nand_saveexec_b64 s14", "nand_saveexec_b64 s15", "nor_saveexec_b64 s12", "nor_saveexec_b64 s13", "nor_saveexec_b64 s14", "nor_saveexec_b64 s15", nullptr, nullptr, nullptr, "scc orn2_saveexec_b64,nand_saveexec_b64,nor_saveexec_b64", "xnor_saveexec_b64 s12", "xnor_saveexec_b64 s13", "xnor_saveexec_b64 s14", "xnor_saveexec_b64 s15", "andn1_saveexec_b64 s12", "andn1_saveexec_b64 s13", "andn1_saveexec_b64 s14", "andn1_saveexec_b64 s15", "orn1_saveexec_b64 s12", "orn1_saveexec_b64 s13", "orn1_saveexec_b64 s14", "orn1_saveexec_b64 s15", nullptr, nullptr, nullptr, "scc xnor_saveexec_b64,andn1_saveexec_b64,orn1_saveexec_b64", "andn1_wrexec_b64 s12", "andn1_wrexec_b64 s13", "andn1_wrexec_b64 s14", "andn1_wrexec_b64 s15", "andn2_wrexec_b64 s12", "andn2_wrexec_b64 s13", "andn2_wrexec_b64 s14", "andn2_wrexec_b64 s15", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "scc andn1_wrexec_b64,andn2_wrexec_b64,cmp_eq_i32,cmp_lg_i32,cmp_gt_i32,cmp_ge_i32,cmp_lt_i32,cmp_le_i32,cmp_eq_u32,cmp_lg_u32,cmp_gt_u32,cmp_ge_u32,cmp_lt_u32,cmp_le_u32,cmp_eq_u64,cmp_lg_u64,bitcmp0_b32,bitcmp1_b32,bitcmp0_b64,bitcmp1_b64,cmpk_eq_i32,cmpk_lg_i32,cmpk_gt_i32,cmpk_ge_i32,cmpk_lt_i32,cmpk_le_i32,cmpk_eq_u32,cmpk_lg_u32,cmpk_gt_u32,cmpk_ge_u32,cmpk_lt_u32", "movk_i32 s12", "addk_i32 0x8000 s12", "addk_i32 0x7fff s12", "mulk_i32 s12", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "scc cmpk_le_u32,movk_i32,addk_i32,addk_i32,mulk_i32"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("scalar sop1: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < Results; ++i) { + if (Names[i] != nullptr) Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("scalar sop1 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSop2.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSop2.cpp new file mode 100644 index 000000000..cec18794d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSop2.cpp @@ -0,0 +1,198 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 64; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060088, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbe940380, 0xd7600008, 0x02002904, + 0xd7600009, 0x02002905, 0xd760000a, 0x02002906, 0xd760000b, 0x02002907, 0xbe9e0380, 0x871f810b, + 0x828c0a08, 0x821e1e1e, 0xd761000a, 0x0200280c, 0x871f810b, 0x858c0a08, 0x821e1e1e, 0xd761000b, + 0x0200280c, 0xd761000c, 0x0200280d, 0x810c0a08, 0x821e1e1e, 0xd761000d, 0x0200280c, 0x818c0a08, + 0x821e1e1e, 0xd761000e, 0x0200280c, 0x830c0a08, 0x821e1e1e, 0xd761000f, 0x0200280c, 0x838c0a08, + 0x821e1e1e, 0xd7610010, 0x0200280c, 0x840c0a08, 0x821e1e1e, 0xd7610011, 0x0200280c, 0x848c0a08, + 0x821e1e1e, 0xd7610012, 0x0200280c, 0x890c0a08, 0x821e1e1e, 0xd7610013, 0x0200280c, 0x8a0c0a08, + 0x821e1e1e, 0xd7610014, 0x0200280c, 0x8b0c0a08, 0x821e1e1e, 0xd7610015, 0x0200280c, 0x8d0c0a08, + 0x821e1e1e, 0xd7610016, 0x0200280c, 0x8e0c0a08, 0x821e1e1e, 0xd7610017, 0x0200280c, 0xd7610019, + 0x0200281e, 0xbe9e0380, 0x888c0a08, 0x821e1e1e, 0xd761001a, 0x0200280c, 0xd761001b, 0x0200280d, + 0x898c0a08, 0x821e1e1e, 0xd761001c, 0x0200280c, 0xd761001d, 0x0200280d, 0x8a8c0a08, 0x821e1e1e, + 0xd761001e, 0x0200280c, 0xd761001f, 0x0200280d, 0x8b8c0a08, 0x821e1e1e, 0xd7610020, 0x0200280c, + 0xd7610021, 0x0200280d, 0x8c8c0a08, 0x821e1e1e, 0xd7610022, 0x0200280c, 0xd7610023, 0x0200280d, + 0x8d8c0a08, 0x821e1e1e, 0xd7610024, 0x0200280c, 0xd7610025, 0x0200280d, 0x8e8c0a08, 0x821e1e1e, + 0xd7610026, 0x0200280c, 0xd7610027, 0x0200280d, 0xd7610029, 0x0200281e, 0xbe9e0380, 0x8f8c0a08, + 0x821e1e1e, 0xd761002a, 0x0200280c, 0xd761002b, 0x0200280d, 0x900c0a08, 0x821e1e1e, 0xd761002c, + 0x0200280c, 0x908c0a08, 0x821e1e1e, 0xd761002d, 0x0200280c, 0xd761002e, 0x0200280d, 0x910c0a08, + 0x821e1e1e, 0xd761002f, 0x0200280c, 0x918c0a08, 0x821e1e1e, 0xd7610030, 0x0200280c, 0xd7610031, + 0x0200280d, 0xbf060b0b, 0x920c0a08, 0x821e1e1e, 0xd7610032, 0x0200280c, 0xbf060b0b, 0x928c0a08, + 0x821e1e1e, 0xd7610033, 0x0200280c, 0xd7610034, 0x0200280d, 0x940c0a08, 0x821e1e1e, 0xd7610035, + 0x0200280c, 0x938c0a08, 0x821e1e1e, 0xd7610036, 0x0200280c, 0x948c0a08, 0x821e1e1e, 0xd7610037, + 0x0200280c, 0xd7610038, 0x0200280d, 0xd7610039, 0x0200281e, 0xbe9e0380, 0x960c0a08, 0x821e1e1e, + 0xd761003a, 0x0200280c, 0x970c0a08, 0x821e1e1e, 0xd761003b, 0x0200280c, 0x978c0a08, 0x821e1e1e, + 0xd761003c, 0x0200280c, 0x980c0a08, 0x821e1e1e, 0xd761003d, 0x0200280c, 0x988c0a08, 0x821e1e1e, + 0xd761003e, 0x0200280c, 0xbf060b0b, 0x990c0a08, 0x821e1e1e, 0xd761003f, 0x0200280c, 0xbf060b0b, + 0x998c0a08, 0x821e1e1e, 0xd7610040, 0x0200280c, 0xbf060b0b, 0x9a0c0a08, 0x821e1e1e, 0xd7610041, + 0x0200280c, 0xbf060b0b, 0x9a8c0a08, 0x821e1e1e, 0xd7610042, 0x0200280c, 0xbf060b0b, 0x9b0c0a08, + 0x821e1e1e, 0xd7610043, 0x0200280c, 0xd7610049, 0x0200281e, 0x80148114, 0xbf0aa014, 0xbf85ff26, + 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, + 0xe0781040, 0x80011a03, 0xe0781050, 0x80011e03, 0xe0781060, 0x80012203, 0xe0781070, 0x80012603, + 0xe0781080, 0x80012a03, 0xe0781090, 0x80012e03, 0xe07810a0, 0x80013203, 0xe07810b0, 0x80013603, + 0xe07810c0, 0x80013a03, 0xe07810d0, 0x80013e03, 0xe07810e0, 0x80014203, 0xe07810f0, 0x80014603, + 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0xbde5c099u, 0x00000001u, 0xc6a53877u, 0xa6233255u}, + {0x00000000u, 0x4e5a4cafu, 0x00000000u, 0xd86f17ecu}, + {0x00410003u, 0x80000000u, 0xffffffffu, 0xffff8000u}, + {0x80000000u, 0x00000040u, 0x7fffffffu, 0x0010001cu}, + {0xe05e9899u, 0xb77701e2u, 0x007f0000u, 0x0000001fu}, + {0x7fffffffu, 0xb89c4e56u, 0xff297d0eu, 0xffffffffu}, + {0x4b78dc3du, 0x0000003fu, 0x741b324du, 0xb057c162u}, + {0x00008000u, 0x40000000u, 0x61394c10u, 0x0000003fu}, + {0xffffffffu, 0x40000000u, 0x7fffffffu, 0x66868695u}, + {0x0000003fu, 0x40000000u, 0x617959ceu, 0xffff8000u}, + {0x00010000u, 0x00000040u, 0xc4069545u, 0x80000000u}, + {0x12345678u, 0x00000000u, 0x015c33b2u, 0x00010000u}, + {0x2a759159u, 0x4a0fe75du, 0x0000ffffu, 0x00010000u}, + {0x0000ffffu, 0xe4cc4132u, 0x00000001u, 0x0000003fu}, + {0x43892dfcu, 0x00000005u, 0x960d5a8fu, 0x0021001fu}, + {0x0020003fu, 0x7b121dc5u, 0x2f4d4c86u, 0x00000040u}, + {0x00000001u, 0x5b8e8fb2u, 0x04a012e8u, 0x00410003u}, + {0x00000000u, 0x00000040u, 0xf453324eu, 0xffff8000u}, + {0xf129c8c6u, 0x10000000u, 0x00080004u, 0x1ba95a54u}, + {0x0001001fu, 0x0960afe9u, 0x7fffffffu, 0x10000000u}, + {0xeb07c30du, 0x0010001cu, 0x8b8e8f4eu, 0x00400000u}, + {0x007f0000u, 0x0020003fu, 0x74a677c6u, 0x2547f19cu}, + {0x00000002u, 0x558d2adbu, 0x216d27a2u, 0x90656c8fu}, + {0xa161d909u, 0x69f0441eu, 0x0040003fu, 0xffffffffu}, + {0x7aa286acu, 0x40000000u, 0x00000021u, 0x00000040u}, + {0x7fd39898u, 0x2e1c5d3au, 0x0010001cu, 0x10000000u}, + {0x0020003fu, 0xa9fd1ce9u, 0x00000005u, 0x0010001cu}, + {0x43cdc356u, 0xb0ddb48bu, 0xf4f6717cu, 0xa6523995u}, + {0x00000000u, 0x00080004u, 0x0010001cu, 0x10000000u}, + {0xcc3136d8u, 0x0010001cu, 0xbce240c6u, 0x68916d08u}, + {0x00000040u, 0xb036d9ddu, 0x0001001fu, 0x84a4c93fu}, + {0x0000003fu, 0x0001001fu, 0x7a2c54a0u, 0x7fffffffu} +}; +constexpr std::uint32_t Expected[32][64] = { + {0xf7408821u, 0xbde5c099u, 0x00000001u, 0x848af910u, 0xf7408822u, 0xbde5c099u, 0xbde5c099u, 0xc6a53877u, 0xc6a53877u, 0x7b40f8eeu, 0x3940c088u, 0xbdffc799u, 0x001a0700u, 0x84bf0711u, 0x00000000u, 0x0000199fu, 0xffe5f8ffu, 0xa6233255u, 0x7b40f8eeu, 0xa6233254u, 0x3940c088u, 0x00000000u, 0xbdffc799u, 0x59dccdabu, 0x7b5affeeu, 0xfffffffeu, 0x001a0700u, 0x59dccdaau, 0x84bf0711u, 0x59dccdabu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x4c800000u, 0x0000017bu, 0x00000000u, 0x00000000u, 0xffffff7bu, 0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0xff800000u, 0xffffff7bu, 0x0000017bu, 0x00000000u, 0x00000000u, 0x0000035eu, 0x08bf77deu, 0x4270b9a9u, 0xbe3c3adbu, 0xb5d33d3fu, 0xa5014207u, 0x3877c099u, 0xc6a5c099u, 0xc6a5bde5u, 0x935a41eeu, 0x0ecf48deu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x00000000u, 0x00000000u, 0xd86f17ecu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000007u, 0x00000000u, 0xde7f5fefu, 0x00000000u, 0x96355b43u, 0x00000000u, 0x06104803u, 0xffffffffu, 0x6fdaecbfu, 0xffffffffu, 0xb7b5fb53u, 0xffffffffu, 0x2180a010u, 0xffffffffu, 0x69caa4bcu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x4e5a4cafu, 0x00000000u, 0x00000000u, 0x4e5a4cafu, 0x00000000u, 0x00000000u, 0x4e5a4cafu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000002b8u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000001fu}, + {0x00410004u, 0xffffffffu, 0xffff8000u, 0x00410002u, 0x00410004u, 0xffffffffu, 0x00410003u, 0x00410003u, 0xffffffffu, 0xffbefffcu, 0x00000000u, 0x00410003u, 0x00000000u, 0x00410003u, 0x00000000u, 0x000010d5u, 0xffffffffu, 0xffff8000u, 0xffbefffcu, 0x7fff8000u, 0x00000000u, 0x00000000u, 0x00410003u, 0x80007fffu, 0xffbefffcu, 0x7fffffffu, 0x00000000u, 0x00007fffu, 0x00410003u, 0x80007fffu, 0x00000000u, 0x0000006fu, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x000002b9u, 0x00410004u, 0x00820005u, 0x0104000bu, 0x02080017u, 0x0410002fu, 0xffff0003u, 0xffff0003u, 0xffff0041u, 0x00410002u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x00000001u, 0x7fffffffu, 0x0010001cu, 0xffffffffu, 0x00000001u, 0x80000000u, 0x7fffffffu, 0x7fffffffu, 0x80000000u, 0xffffffffu, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000033cu, 0xffffffffu, 0x0010005cu, 0xffffffffu, 0x0010005cu, 0x80000000u, 0x00000040u, 0x80000000u, 0xffefffe3u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0xffefffa3u, 0x00000000u, 0xffefffa3u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000001u, 0x00000000u, 0x00000000u, 0x0000015eu, 0x00000001u, 0x7fffffffu, 0x7fffffffu, 0x7fffffffu, 0x7fffffffu, 0xffff0000u, 0x7fff0000u, 0x7fff8000u, 0x3fffffffu, 0xc0000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0xdfdf9898u, 0xe05e9899u, 0xb77701e2u, 0xe0dd9899u, 0xdfdf9899u, 0xe05e9899u, 0x007f0000u, 0x007f0000u, 0xe05e9899u, 0xe0219899u, 0xe0009899u, 0xffdeffffu, 0x1f806766u, 0x1fde6766u, 0x00000000u, 0x0000093fu, 0xe07f9899u, 0xb77701ffu, 0xe0219899u, 0xb77701fdu, 0xe0009899u, 0xb77701e0u, 0xffdeffffu, 0xffffffe2u, 0xffa1ffffu, 0xfffffffdu, 0x1f806766u, 0x4888fe00u, 0x1fde6766u, 0x4888fe02u, 0x00000000u, 0x0000007fu, 0xe05e9899u, 0xb77701e2u, 0xe05e9899u, 0xe05e9899u, 0xb77701e2u, 0xe05e9899u, 0xe05e9899u, 0xb77701e2u, 0x01ffffffu, 0x01ffffffu, 0x00000000u, 0xe05e9899u, 0xe05e9899u, 0xe05e9899u, 0xb77701e2u, 0x000003ffu, 0x20206767u, 0xc13c3132u, 0x81f96264u, 0x0373c4c8u, 0x06688990u, 0x00009899u, 0x007f9899u, 0x007fe05eu, 0x006f4eedu, 0xfff04eedu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x80d682f0u, 0x7fffffffu, 0xb89c4e56u, 0x7f297d0du, 0x80d682f1u, 0xff297d0eu, 0x7fffffffu, 0x7fffffffu, 0xff297d0eu, 0x80d682f1u, 0x00d682f1u, 0x7fffffffu, 0x00000000u, 0x7f297d0eu, 0x00000000u, 0x00001addu, 0xffffffffu, 0xffffffffu, 0x80d682f1u, 0x4763b1a9u, 0x00d682f1u, 0x00000000u, 0x7fffffffu, 0xb89c4e56u, 0x80d682f1u, 0x4763b1a9u, 0x00000000u, 0x00000000u, 0x7f297d0eu, 0xb89c4e56u, 0x00000000u, 0x0000007du, 0xffffc000u, 0x13959fffu, 0x0001ffffu, 0x3959ffffu, 0x0002e271u, 0x0001ffffu, 0x3959ffffu, 0xfffee271u, 0xffffc000u, 0xffffc000u, 0xffffffffu, 0x0001ffffu, 0x0001ffffu, 0x3959ffffu, 0x00000071u, 0x000003ffu, 0x7f297d0fu, 0xff297d0cu, 0xff297d0au, 0xff297d06u, 0xff297cfeu, 0x7d0effffu, 0xff29ffffu, 0xff297fffu, 0x7f94be86u, 0xff94be87u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0xd75da9f0u, 0x741b324du, 0xb057c162u, 0xbf940e8au, 0xd75da9f0u, 0x4b78dc3du, 0x4b78dc3du, 0x741b324du, 0x741b324du, 0x3f63ee70u, 0x0b60cc30u, 0xcbfcddbfu, 0x80840182u, 0xc09c118fu, 0x00000000u, 0x0000159fu, 0x7f7bfe7du, 0xb057c17fu, 0x3f63ee70u, 0xb057c15du, 0x0b60cc30u, 0x0000001du, 0xcbfcddbfu, 0x4fa83ebfu, 0xbfe7eff2u, 0xffffffddu, 0x80840182u, 0x4fa83e80u, 0xc09c118fu, 0x4fa83ea2u, 0x00000000u, 0x0000007fu, 0x1b87a000u, 0x0007e96fu, 0x00025bc6u, 0x01fa5bc6u, 0x00000000u, 0x00025bc6u, 0x01fa5bc6u, 0x00000000u, 0xffffe000u, 0xffffe000u, 0xffffffffu, 0x00025bc6u, 0x00025bc6u, 0x01fa5bc6u, 0x00000000u, 0x000003ffu, 0x28a25610u, 0x0b0ceac7u, 0xa1fea341u, 0xcfe21435u, 0x2ba8f61du, 0x324ddc3du, 0x741bdc3du, 0x741b4b78u, 0x223ac85fu, 0x223ac85fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x9ec733efu, 0x00008000u, 0x40000000u, 0x6139cc10u, 0x9ec733f0u, 0x00008000u, 0x00008000u, 0x61394c10u, 0x61394c10u, 0x6139cc10u, 0x00008000u, 0x9ec6b3efu, 0x9ec633efu, 0x9ec633efu, 0x00000000u, 0x0000199fu, 0x6139cc10u, 0x4000003fu, 0x6139cc10u, 0x4000003fu, 0x00008000u, 0x40000000u, 0x9ec6b3efu, 0xffffffc0u, 0xffffffffu, 0xffffffffu, 0x9ec633efu, 0xbfffffc0u, 0x9ec633efu, 0xbfffffc0u, 0x00000000u, 0x0000007fu, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00004000u, 0x00000000u, 0x00000000u, 0x00004000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00004000u, 0x000002b9u, 0x6138cc10u, 0x613a4c10u, 0x613b4c10u, 0x613d4c10u, 0x61414c10u, 0x4c108000u, 0x61398000u, 0x61390000u, 0x0000309cu, 0x0000309cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x7fffffffu, 0xffffffffu, 0x40000000u, 0x7ffffffeu, 0x80000000u, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0xffffffffu, 0x80000000u, 0x80000000u, 0xffffffffu, 0x00000000u, 0x7fffffffu, 0x00000000u, 0x0000093du, 0xffffffffu, 0x66868695u, 0x80000000u, 0x26868695u, 0x80000000u, 0x00000000u, 0xffffffffu, 0xd979796au, 0x80000000u, 0xbfffffffu, 0x00000000u, 0x9979796au, 0x7fffffffu, 0xd979796au, 0x00000000u, 0x0000007fu, 0x00000000u, 0x80000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0xffffffffu, 0x00000001u, 0x00000000u, 0x00000000u, 0x0000035eu, 0x80000000u, 0x7ffffffdu, 0x7ffffffbu, 0x7ffffff7u, 0x7fffffefu, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x7ffffffeu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x9e86a671u, 0x617959ceu, 0xffff8000u, 0x61795a0du, 0x9e86a671u, 0x0000003fu, 0x0000003fu, 0x617959ceu, 0x617959ceu, 0x617959f1u, 0x00000031u, 0x9e86a63fu, 0x9e86a600u, 0x9e86a60eu, 0x00000000u, 0x0000119fu, 0x617959ffu, 0xffff8000u, 0x617959f1u, 0xbfff8000u, 0x00000031u, 0x00000000u, 0x9e86a63fu, 0x40007fffu, 0xfffffff1u, 0xbfffffffu, 0x9e86a600u, 0x00007fffu, 0x9e86a60eu, 0x40007fffu, 0x00000000u, 0x0000007fu, 0x000fc000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00010000u, 0xffffc000u, 0xffffc000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010000u, 0x000002b9u, 0x6179598fu, 0x61795a4cu, 0x61795acau, 0x61795bc6u, 0x61795dbeu, 0x59ce003fu, 0x6179003fu, 0x61790000u, 0x00000017u, 0x00000017u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x3bfa6abbu, 0xc4069545u, 0x80000000u, 0xc4079545u, 0x3bfa6abbu, 0xc4069545u, 0x00010000u, 0x00010000u, 0xc4069545u, 0xc4079545u, 0x00010000u, 0x3bf96abau, 0x3bf86abau, 0x3bf86abau, 0x00000000u, 0x000010dfu, 0xc4079545u, 0x80000040u, 0xc4079545u, 0x80000040u, 0x00010000u, 0x00000040u, 0x3bf96abau, 0x7fffffffu, 0xffffffffu, 0xffffffffu, 0x3bf86abau, 0x7fffffbfu, 0x3bf86abau, 0x7fffffbfu, 0x00000000u, 0x0000007fu, 0x00200000u, 0x00000800u, 0x00000800u, 0x00000800u, 0x00000002u, 0x00000800u, 0x00000800u, 0x00000002u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003f8u, 0x3bfa6abbu, 0xc4089545u, 0xc40a9545u, 0xc40e9545u, 0xc4169545u, 0x95450000u, 0xc4060000u, 0xc4060001u, 0x0000c406u, 0xffffc406u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x10d822c6u, 0x015c33b2u, 0x00010000u, 0x13908a2au, 0x10d822c6u, 0x015c33b2u, 0x015c33b2u, 0x12345678u, 0x12345678u, 0x136865cau, 0x12204448u, 0xfeb7de7du, 0xec838805u, 0xec979a35u, 0x00000000u, 0x0000007fu, 0x137c77fau, 0x00010000u, 0x136865cau, 0x00010000u, 0x12204448u, 0x00000000u, 0xfeb7de7du, 0xfffeffffu, 0xffebedcfu, 0xffffffffu, 0xec838805u, 0xfffeffffu, 0xec979a35u, 0xfffeffffu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x59e00000u, 0x0000048du, 0x00000000u, 0x00000000u, 0x0000048du, 0x00000000u, 0x00000000u, 0xfffc0000u, 0x00000000u, 0xfffc0000u, 0x0000048du, 0x0000048du, 0x00000000u, 0x00000000u, 0x0000035eu, 0x10d822c6u, 0x25c4e0a2u, 0x4a2d8d92u, 0x92fee772u, 0x24a19b32u, 0x33b25678u, 0x015c5678u, 0x015c1234u, 0x0018c2d2u, 0x0018c2d2u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000023fu}, + {0x2a74915au, 0x0000ffffu, 0x00010000u, 0x2a769158u, 0x2a74915au, 0x0000ffffu, 0x0000ffffu, 0x2a759159u, 0x2a759159u, 0x2a756ea6u, 0x2a750000u, 0xffff9159u, 0xd58a0000u, 0xd58a9159u, 0x00000000u, 0x0000007fu, 0x2a75ffffu, 0x4a0fe75du, 0x2a756ea6u, 0x4a0ee75du, 0x2a750000u, 0x4a0ee75du, 0xffff9159u, 0xffffffffu, 0xffff6ea6u, 0xfffeffffu, 0xd58a0000u, 0xb5f018a2u, 0xd58a9159u, 0xb5f118a2u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000218u, 0x2a74915au, 0x54ec22b1u, 0xa9d74563u, 0x53ad8ac7u, 0xa75a158fu, 0xffff9159u, 0x00009159u, 0x00002a75u, 0x00002a75u, 0x00002a75u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000027fu}, + {0x0000fffdu, 0x0000ffffu, 0xe4cc4132u, 0x00010000u, 0x0000fffeu, 0x00000001u, 0x00000001u, 0x0000ffffu, 0x0000ffffu, 0x0000fffeu, 0x0000fffeu, 0xffffffffu, 0xffff0000u, 0xffff0001u, 0x00000000u, 0x0000087fu, 0x0000ffffu, 0xe4cc413fu, 0x0000fffeu, 0xe4cc410du, 0x0000fffeu, 0xe4cc4100u, 0xffffffffu, 0xfffffff2u, 0xfffffffeu, 0xffffffcdu, 0xffff0000u, 0x1b33bec0u, 0xffff0001u, 0x1b33bef2u, 0x00000000u, 0x0000007fu, 0x0001fffeu, 0xc9988264u, 0x00007fffu, 0x00007fffu, 0x72662099u, 0x00007fffu, 0x00007fffu, 0xf2662099u, 0xfffffffeu, 0xfffffffeu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003f8u, 0x0000fffeu, 0x0001ffffu, 0x0003fffdu, 0x0007fff9u, 0x000ffff1u, 0x0001ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0xad7bd36cu, 0x43892dfcu, 0x00000005u, 0xd996888bu, 0xad7bd36du, 0x960d5a8fu, 0x43892dfcu, 0x43892dfcu, 0x960d5a8fu, 0xd5847773u, 0x41802570u, 0x6bfbadfcu, 0x28728000u, 0x2a7b888cu, 0x00000000u, 0x00001adfu, 0xd78d7fffu, 0x0021001fu, 0xd5847773u, 0x0021001au, 0x41802570u, 0x00000000u, 0x6bfbadfcu, 0xffdeffe5u, 0xfdf6f773u, 0xfffffffau, 0x28728000u, 0xffdeffe0u, 0x2a7b888cu, 0xffdeffe5u, 0x00000000u, 0x0000007fu, 0x96fe0000u, 0x0002a1c4u, 0x00008712u, 0x000a8712u, 0x00000000u, 0x00008712u, 0x000a8712u, 0x00000000u, 0xffff8000u, 0xffff8000u, 0xffffffffu, 0x00000712u, 0x00000712u, 0x00000712u, 0x00000000u, 0x000003ffu, 0x52842c93u, 0x1d1fb687u, 0xa432127fu, 0xb256ca6fu, 0xcea03a4fu, 0x5a8f2dfcu, 0x960d2dfcu, 0x960d4389u, 0x2795e6ccu, 0xe40cb8d0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0xd0d2b3b9u, 0x2f4d4c86u, 0x00000040u, 0x2f6d4cc5u, 0xd0d2b3b9u, 0x0020003fu, 0x0020003fu, 0x2f4d4c86u, 0x2f4d4c86u, 0x2f6d4cb9u, 0x00200039u, 0xd0b2b37fu, 0xd092b340u, 0xd092b346u, 0x00000000u, 0x0000119fu, 0x2f6d4cbfu, 0x7b121dc5u, 0x2f6d4cb9u, 0x7b121d85u, 0x00200039u, 0x7b121d85u, 0xd0b2b37fu, 0xffffffffu, 0xfffffff9u, 0xffffffbfu, 0xd092b340u, 0x84ede23au, 0xd092b346u, 0x84ede27au, 0x00000000u, 0x0000007fu, 0x08000fc0u, 0xc4877140u, 0x00008000u, 0x14008000u, 0x01ec4877u, 0x00008000u, 0x14008000u, 0x01ec4877u, 0xffffffc0u, 0xffffffc0u, 0xffffffffu, 0x00008000u, 0x00008000u, 0x14008000u, 0x01ec4877u, 0x000003ffu, 0x2f2d4c47u, 0x2f8d4d04u, 0x2fcd4d82u, 0x304d4e7eu, 0x314d5076u, 0x4c86003fu, 0x2f4d003fu, 0x2f4d0020u, 0x0005e9b5u, 0x0005e9b5u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0xfb5fed18u, 0x00000001u, 0x5b8e8fb2u, 0x04a012e9u, 0xfb5fed19u, 0x00000001u, 0x00000001u, 0x04a012e8u, 0x04a012e8u, 0x04a012e9u, 0x00000001u, 0xfb5fed17u, 0xfb5fed16u, 0xfb5fed16u, 0x00000000u, 0x0000199fu, 0x04a012e9u, 0x5bcf8fb3u, 0x04a012e9u, 0x5bcf8fb1u, 0x00000001u, 0x5b8e8fb0u, 0xfb5fed17u, 0xffbefffeu, 0xffffffffu, 0xfffffffdu, 0xfb5fed16u, 0xa430704cu, 0xfb5fed16u, 0xa430704eu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000100u, 0x00000000u, 0x005b8e8fu, 0x00000000u, 0x00000000u, 0x005b8e8fu, 0x00000000u, 0x00000100u, 0x00000000u, 0x00000100u, 0x00000000u, 0x00000000u, 0x005b8e8fu, 0x00000000u, 0x000002b9u, 0x04a012e7u, 0x04a012eau, 0x04a012ecu, 0x04a012f0u, 0x04a012f8u, 0x12e80001u, 0x04a00001u, 0x04a00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x0baccdb2u, 0xf453324eu, 0xffff8000u, 0xf453324eu, 0x0baccdb2u, 0xf453324eu, 0x00000000u, 0x00000000u, 0xf453324eu, 0xf453324eu, 0x00000000u, 0x0baccdb1u, 0x0baccdb1u, 0x0baccdb1u, 0x00000000u, 0x000010d7u, 0xf453324eu, 0xffff8040u, 0xf453324eu, 0xffff8040u, 0x00000000u, 0x00000040u, 0x0baccdb1u, 0x00007fffu, 0xffffffffu, 0xffffffffu, 0x0baccdb1u, 0x00007fbfu, 0x0baccdb1u, 0x00007fbfu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00100000u, 0x00000000u, 0x01000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00000000u, 0x000002b9u, 0x0baccdb2u, 0xf453324eu, 0xf453324eu, 0xf453324eu, 0xf453324eu, 0x324e0000u, 0xf4530000u, 0xf4530000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0xf121c8c2u, 0x00080004u, 0x1ba95a54u, 0xf131c8cau, 0xf121c8c2u, 0xf129c8c6u, 0x00080004u, 0x00080004u, 0xf129c8c6u, 0xf121c8c2u, 0xf121c8c2u, 0xffffffffu, 0x0ed63739u, 0x0ede373du, 0x00000000u, 0x0000013fu, 0xf129c8c6u, 0x1ba95a54u, 0xf121c8c2u, 0x0ba95a54u, 0xf121c8c2u, 0x00000000u, 0xffffffffu, 0xf456a5abu, 0xfff7fffbu, 0xefffffffu, 0x0ed63739u, 0xe456a5abu, 0x0ede373du, 0xf456a5abu, 0x00000000u, 0x0000007fu, 0x129c8c60u, 0x0000000fu, 0x0f129c8cu, 0x0f129c8cu, 0x01000000u, 0xff129c8cu, 0x0f129c8cu, 0x01000000u, 0x000003f0u, 0x000003f0u, 0x00000000u, 0xffffff8cu, 0x0000008cu, 0x0000008cu, 0x00000000u, 0x000003ffu, 0x0ede373eu, 0xe25b9190u, 0xc4af231cu, 0x89564634u, 0x12a48c64u, 0x0004c8c6u, 0x0008c8c6u, 0x0008f129u, 0x00078952u, 0xffff894eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x80010020u, 0x7fffffffu, 0x10000000u, 0x8001001eu, 0x80010020u, 0x0001001fu, 0x0001001fu, 0x7fffffffu, 0x7fffffffu, 0x7ffeffe0u, 0x00000000u, 0x8001001fu, 0x80000000u, 0x8001001fu, 0x00000000u, 0x00001597u, 0x7fffffffu, 0x1960afe9u, 0x7ffeffe0u, 0x1960afe9u, 0x00000000u, 0x0960afe9u, 0x8001001fu, 0xefffffffu, 0xfffeffe0u, 0xffffffffu, 0x80000000u, 0xe69f5016u, 0x8001001fu, 0xe69f5016u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000218u, 0x7ffeffe0u, 0x8002003du, 0x8004007bu, 0x800800f7u, 0x801001efu, 0xffff001fu, 0x7fff001fu, 0x7fff0001u, 0x0000800fu, 0x0000800fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x5f7933bfu, 0x8b8e8f4eu, 0x00400000u, 0x7696525bu, 0x5f7933bfu, 0x8b8e8f4eu, 0x8b8e8f4eu, 0xeb07c30du, 0xeb07c30du, 0x60894c43u, 0x60014001u, 0xff77f3bdu, 0x147030b0u, 0x9f76b3bcu, 0x00000000u, 0x0000047fu, 0xeb8fcf4fu, 0x0050001cu, 0x60894c43u, 0x0050001cu, 0x60014001u, 0x0010001cu, 0xff77f3bdu, 0xffbfffffu, 0x74f97cf3u, 0xffffffffu, 0x147030b0u, 0xffafffe3u, 0x9f76b3bcu, 0xffafffe3u, 0x00000000u, 0x0000007fu, 0xf0c34000u, 0x00073ac1u, 0x0003ac1fu, 0x0073ac1fu, 0x00000040u, 0xffffac1fu, 0x0073ac1fu, 0x00000040u, 0x07ffc000u, 0x07ffc000u, 0x00000000u, 0xffffec1fu, 0x00002c1fu, 0x00002c1fu, 0x00000000u, 0x000003ffu, 0x5f7933bfu, 0x619e1568u, 0x37ad9b82u, 0xe3cca7b6u, 0x3c0ac01eu, 0x8f4ec30du, 0x8b8ec30du, 0x8b8eeb07u, 0x802018c7u, 0x0989c66cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x8bd8883au, 0x74a677c6u, 0x2547f19cu, 0x752577c6u, 0x8bd8883au, 0x007f0000u, 0x007f0000u, 0x74a677c6u, 0x74a677c6u, 0x74d977c6u, 0x00590000u, 0x8b7f8839u, 0x8b008839u, 0x8b268839u, 0x00000000u, 0x0000119fu, 0x74ff77c6u, 0x2567f1bfu, 0x74d977c6u, 0x2567f1a3u, 0x00590000u, 0x00200023u, 0x8b7f8839u, 0xdab80e7fu, 0xffd9ffffu, 0xffffffe3u, 0x8b008839u, 0xda980e40u, 0x8b268839u, 0xda980e5cu, 0x00000000u, 0x0000007fu, 0x1fc00000u, 0x08000fc0u, 0x0001fc00u, 0xfc01fc00u, 0x00008000u, 0x0001fc00u, 0xfc01fc00u, 0x00008000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0001fc00u, 0x0001fc00u, 0xfc01fc00u, 0x00000000u, 0x000003ffu, 0x742777c6u, 0x75a477c6u, 0x76a277c6u, 0x789e77c6u, 0x7c9677c6u, 0x77c60000u, 0x74a60000u, 0x74a6007fu, 0x0039de95u, 0x0039de95u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0xde92d85fu, 0x00000002u, 0x558d2adbu, 0x216d27a4u, 0xde92d860u, 0x00000002u, 0x00000002u, 0x216d27a2u, 0x216d27a2u, 0x216d27a0u, 0x00000000u, 0xde92d85fu, 0xde92d85du, 0xde92d85fu, 0x00000000u, 0x00001997u, 0x216d27a2u, 0xd5ed6edfu, 0x216d27a0u, 0xc5e84654u, 0x00000000u, 0x45880250u, 0xde92d85fu, 0x7f9fbbfbu, 0xfffffffdu, 0xeffad774u, 0xde92d85du, 0x2a129120u, 0xde92d85fu, 0x3a17b9abu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000008u, 0x00000000u, 0x15634ab6u, 0x00000000u, 0x00000000u, 0x15634ab6u, 0x00000000u, 0x0000000cu, 0x00000000u, 0x0000000cu, 0x00000000u, 0x00000000u, 0x15634ab6u, 0x00000000u, 0x000002b9u, 0x216d27a0u, 0x216d27a6u, 0x216d27aau, 0x216d27b2u, 0x216d27c2u, 0x27a20002u, 0x216d0002u, 0x216d0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0xa121d8c9u, 0xa161d909u, 0x69f0441eu, 0xa1a1d948u, 0xa121d8cau, 0xa161d909u, 0x0040003fu, 0x0040003fu, 0xa161d909u, 0xa121d936u, 0xa121d900u, 0xffffffc9u, 0x5e9e26c0u, 0x5ede26c9u, 0x00000000u, 0x0000093fu, 0xa161d93fu, 0xffffffffu, 0xa121d936u, 0x960fbbe1u, 0xa121d900u, 0x00000000u, 0xffffffc9u, 0x69f0441eu, 0xffbffff6u, 0x960fbbe1u, 0x5e9e26c0u, 0x00000000u, 0x5ede26c9u, 0x69f0441eu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x80000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0xffffffffu, 0x00000001u, 0x00000000u, 0x00000000u, 0x0000035eu, 0x5ede2736u, 0x4303b251u, 0x85c76463u, 0x0b4ec887u, 0x165d90cfu, 0x003fd909u, 0x0040d909u, 0x0040a161u, 0x0028589du, 0xffe8585eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0x7aa2868bu, 0x00000021u, 0x00000040u, 0x7aa286cdu, 0x7aa2868bu, 0x00000021u, 0x00000021u, 0x7aa286acu, 0x7aa286acu, 0x7aa2868du, 0x7aa2868cu, 0xfffffffeu, 0x855d7952u, 0x855d7972u, 0x00000000u, 0x0000007fu, 0x7aa286adu, 0x40000040u, 0x7aa2868du, 0x40000040u, 0x7aa2868cu, 0x40000000u, 0xfffffffeu, 0xffffffbfu, 0xffffffdfu, 0xffffffffu, 0x855d7952u, 0xbfffffbfu, 0x855d7972u, 0xbfffffbfu, 0x00000000u, 0x0000007fu, 0x00000000u, 0xf5450d58u, 0x3d514356u, 0x20000000u, 0x00000000u, 0x3d514356u, 0x20000000u, 0x00000000u, 0x00001ffeu, 0x00000000u, 0xfffffffeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003f8u, 0x7aa2868bu, 0xf5450d79u, 0xea8a1ad1u, 0xd5143581u, 0xaa286ae1u, 0x002186acu, 0x000086acu, 0x00007aa2u, 0x0000000fu, 0x0000000fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000002ffu}, + {0x7fc3987cu, 0x0010001cu, 0x10000000u, 0x7fe398b4u, 0x7fc3987cu, 0x0010001cu, 0x0010001cu, 0x7fd39898u, 0x7fd39898u, 0x7fc39884u, 0x7fc39880u, 0xfffffffbu, 0x802c6763u, 0x803c677bu, 0x00000000u, 0x0000007fu, 0x7fd3989cu, 0x3e1c5d3au, 0x7fc39884u, 0x3e1c5d3au, 0x7fc39880u, 0x2e1c5d3au, 0xfffffffbu, 0xefffffffu, 0xffefffe7u, 0xffffffffu, 0x802c6763u, 0xc1e3a2c5u, 0x803c677bu, 0xc1e3a2c5u, 0x00000000u, 0x0000007fu, 0x80000000u, 0xa7fd3989u, 0x00000007u, 0xe1c5d3a7u, 0x00000002u, 0x00000007u, 0xe1c5d3a7u, 0x00000002u, 0xf0000000u, 0xf0000000u, 0x000fffffu, 0x00000007u, 0x00000007u, 0x0000d3a7u, 0x00000000u, 0x000003ffu, 0x7fc3987cu, 0xffb7314cu, 0xff5e627cu, 0xfeacc4dcu, 0xfd49899cu, 0x001c9898u, 0x00109898u, 0x00107fd3u, 0x0007fd47u, 0x0007fd47u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000002ffu}, + {0x0020003au, 0x00000005u, 0x0010001cu, 0x00200044u, 0x0020003au, 0x00000005u, 0x00000005u, 0x0020003fu, 0x0020003fu, 0x0020003au, 0x0020003au, 0xffffffffu, 0xffdfffc0u, 0xffdfffc5u, 0x00000000u, 0x0000007fu, 0x0020003fu, 0xa9fd1cfdu, 0x0020003au, 0xa9ed1cf5u, 0x0020003au, 0xa9ed1ce1u, 0xffffffffu, 0xffffffebu, 0xfffffffau, 0xffeffff7u, 0xffdfffc0u, 0x5602e302u, 0xffdfffc5u, 0x5612e30au, 0x00000000u, 0x0000007fu, 0x040007e0u, 0x3fa39d20u, 0x00010001u, 0x48010001u, 0x054fe8e7u, 0x00010001u, 0x48010001u, 0xfd4fe8e7u, 0xffffffe0u, 0xffffffe0u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003f8u, 0x0020003au, 0x00400083u, 0x00800101u, 0x010001fdu, 0x020003f5u, 0x0005003fu, 0x0000003fu, 0x00000020u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x4ed751d9u, 0x43cdc356u, 0xb0ddb48bu, 0x38c434d2u, 0x4ed751dau, 0xf4f6717cu, 0x43cdc356u, 0x43cdc356u, 0xf4f6717cu, 0xb73bb22au, 0x03098202u, 0x4bcdcfd7u, 0x08000c81u, 0x48c44dd5u, 0x00000000u, 0x000018dfu, 0xf7fff37eu, 0xb6dfbd9fu, 0xb73bb22au, 0x168f8d1eu, 0x03098202u, 0x108d840au, 0x4bcdcfd7u, 0xf9fdf6ebu, 0xbf3bbeabu, 0x5fafcf7eu, 0x08000c81u, 0x49204260u, 0x48c44dd5u, 0xe97072e1u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x60000000u, 0x00000004u, 0x0000000bu, 0x00000000u, 0x00000004u, 0xfffffffbu, 0xffffffffu, 0xf0000000u, 0x00000000u, 0xf0000000u, 0x00000004u, 0x00000004u, 0x0000000bu, 0x00000000u, 0x000003ffu, 0x4ed751dau, 0x7c91f828u, 0x042d7ed4u, 0x13648c2cu, 0x31d2a6dcu, 0x717cc356u, 0xf4f6c356u, 0xf4f643cdu, 0x40e163f6u, 0xfd13a0a0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0xffefffe4u, 0x0010001cu, 0x10000000u, 0x0010001cu, 0xffefffe4u, 0x00000000u, 0x00000000u, 0x0010001cu, 0x0010001cu, 0x0010001cu, 0x00000000u, 0xffefffe3u, 0xffefffe3u, 0xffefffe3u, 0x00000000u, 0x00001197u, 0x0010001cu, 0x10080004u, 0x0010001cu, 0x10080004u, 0x00000000u, 0x00080004u, 0xffefffe3u, 0xefffffffu, 0xffffffffu, 0xffffffffu, 0xffefffe3u, 0xeff7fffbu, 0xffefffe3u, 0xeff7fffbu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x40000000u, 0x00000000u, 0x00800040u, 0x00000000u, 0x00000000u, 0x00800040u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000040u, 0x00000000u, 0x000002b9u, 0x0010001cu, 0x0010001cu, 0x0010001cu, 0x0010001cu, 0x0010001cu, 0x001c0000u, 0x00100000u, 0x00100000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x0f4ef612u, 0xbce240c6u, 0x68916d08u, 0x8913779eu, 0x0f4ef612u, 0xbce240c6u, 0xbce240c6u, 0xcc3136d8u, 0xcc3136d8u, 0x70d3761eu, 0x40113618u, 0xcf3dbff9u, 0x030c8921u, 0x8f2c89e1u, 0x00000000u, 0x0000007fu, 0xfcf376deu, 0x68916d1cu, 0x70d3761eu, 0x68816d14u, 0x40113618u, 0x00000014u, 0xcf3dbff9u, 0x977e92ffu, 0x73dfff3fu, 0xffeffff7u, 0x030c8921u, 0x976e92e3u, 0x8f2c89e1u, 0x977e92ebu, 0x00000000u, 0x0000007fu, 0x0c4db600u, 0x04000733u, 0x0330c4dbu, 0x7330c4dbu, 0x00004000u, 0xff30c4dbu, 0x7330c4dbu, 0x00004000u, 0x3fffffc0u, 0x3fffffc0u, 0x00000000u, 0xff30c4dbu, 0x0330c4dbu, 0x7330c4dbu, 0x00004000u, 0x000003ffu, 0x0f4ef612u, 0x5544ae76u, 0xeda71c26u, 0x1e6bf786u, 0x7ff5ae46u, 0x40c636d8u, 0xbce236d8u, 0xbce2cc31u, 0x96a89b63u, 0x0d9523c5u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000003ffu}, + {0xffff0020u, 0x00000040u, 0xb036d9ddu, 0x0001005fu, 0xffff0021u, 0x00000040u, 0x00000040u, 0x0001001fu, 0x0001001fu, 0x0001005fu, 0x00000040u, 0xfffeffe0u, 0xfffeffa0u, 0xfffeffa0u, 0x00000000u, 0x0000199fu, 0x0001005fu, 0xb4b6d9ffu, 0x0001005fu, 0x349210e2u, 0x00000040u, 0x301210c0u, 0xfffeffe0u, 0xfb7fffddu, 0xffffffffu, 0x7fdb36e2u, 0xfffeffa0u, 0x4b492600u, 0xfffeffa0u, 0xcb6def1du, 0x00000000u, 0x0000007fu, 0x00000000u, 0x80000020u, 0x00000000u, 0x606db3bau, 0x00000001u, 0x00000000u, 0x606db3bau, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000002b8u, 0x0000ffdfu, 0x0001009fu, 0x0001011fu, 0x0001021fu, 0x0001041fu, 0x001f0040u, 0x00010040u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu}, + {0x85d3ab9eu, 0x0000003fu, 0x0001001fu, 0x7a2c54dfu, 0x85d3ab9fu, 0x0000003fu, 0x0000003fu, 0x7a2c54a0u, 0x7a2c54a0u, 0x7a2c549fu, 0x0000001fu, 0x85d3ab7fu, 0x85d3ab40u, 0x85d3ab60u, 0x00000000u, 0x0000199fu, 0x7a2c54bfu, 0x7fffffffu, 0x7a2c549fu, 0x7ffeffe0u, 0x0000001fu, 0x00000000u, 0x85d3ab7fu, 0x8001001fu, 0xffffffdfu, 0xfffeffe0u, 0x85d3ab40u, 0x80000000u, 0x85d3ab60u, 0x8001001fu, 0x00000000u, 0x0000007fu, 0x00000000u, 0x0000003fu, 0x0000003fu, 0x0001001fu, 0x00000000u, 0x0000003fu, 0x0001001fu, 0x00000000u, 0x7fffffffu, 0x00000000u, 0xffffffffu, 0x0000003fu, 0x0000003fu, 0x0001001fu, 0x00000000u, 0x000003ffu, 0x7a2c5461u, 0x7a2c551eu, 0x7a2c559cu, 0x7a2c5698u, 0x7a2c5890u, 0x54a0003fu, 0x7a2c003fu, 0x7a2c0000u, 0x0000001eu, 0x0000001eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000021fu} +}; +constexpr const char* Names[64] = {"subb_u32", "cselect_b64 lo", "cselect_b64 hi", "add_i32", "sub_i32", "min_i32", "min_u32", "max_i32", "max_u32", "xor_b32", "andn2_b32", "orn2_b32", "nor_b32", "xnor_b32", nullptr, "scc subb_u32,cselect_b64,add_i32,sub_i32,min_i32,min_u32,max_i32,max_u32,xor_b32,andn2_b32,orn2_b32,nor_b32,xnor_b32", "or_b64 lo", "or_b64 hi", "xor_b64 lo", "xor_b64 hi", "andn2_b64 lo", "andn2_b64 hi", "orn2_b64 lo", "orn2_b64 hi", "nand_b64 lo", "nand_b64 hi", "nor_b64 lo", "nor_b64 hi", "xnor_b64 lo", "xnor_b64 hi", nullptr, "scc or_b64,xor_b64,andn2_b64,orn2_b64,nand_b64,nor_b64,xnor_b64", "lshl_b64 lo", "lshl_b64 hi", "lshr_b32", "lshr_b64 lo", "lshr_b64 hi", "ashr_i32", "ashr_i64 lo", "ashr_i64 hi", "bfm_b32", "bfm_b64 lo", "bfm_b64 hi", "bfe_i32", "bfe_u32", "bfe_u64 lo", "bfe_u64 hi", "scc lshl_b64,lshr_b32,lshr_b64,ashr_i32,ashr_i64,bfm_b32,bfm_b64,bfe_i32,bfe_u32,bfe_u64", "absdiff_i32", "lshl1_add_u32", "lshl2_add_u32", "lshl3_add_u32", "lshl4_add_u32", "pack_ll_b32_b16", "pack_lh_b32_b16", "pack_hh_b32_b16", "mul_hi_u32", "mul_hi_i32", nullptr, nullptr, nullptr, nullptr, nullptr, "scc absdiff_i32,lshl1_add_u32,lshl2_add_u32,lshl3_add_u32,lshl4_add_u32,pack_ll_b32_b16,pack_lh_b32_b16,pack_hh_b32_b16,mul_hi_u32,mul_hi_i32"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("scalar sop2: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 64; ++i) { + if (Names[i] != nullptr) Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("scalar sop2 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSopkMisc.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSopkMisc.cpp index dd2b48530..4760ee0ea 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSopkMisc.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSopkMisc.cpp @@ -70,7 +70,7 @@ void ExpectRefused(AgcDriver::VulkanDevice& device) { const std::vector userData(8, 0u); bool refused = false; try { - ShaderRecompiler::Recompile(Request(device, code, userData, memory)); + static_cast(ShaderRecompiler::Recompile(Request(device, code, userData, memory))); } catch (const std::exception&) { refused = true; } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarWideLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarWideLoads.cpp new file mode 100644 index 000000000..c38e112d1 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarWideLoads.cpp @@ -0,0 +1,249 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint32_t Results = 96; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x160600ff, 0x00000180, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xbea40400, 0xbea60402, + 0xbea20380, 0xd7600008, 0x02004504, 0xd7600009, 0x02004505, 0xd760000a, 0x02004506, 0xf4280312, + 0x10000000, 0xf4280412, 0x12000014, 0xf4280512, 0x1201fff0, 0xf4280612, 0xfa0001f8, 0xbf8cc07f, + 0xd761000a, 0x0200440c, 0xd761000b, 0x0200440d, 0xd761000c, 0x0200440e, 0xd761000d, 0x0200440f, + 0xd761000e, 0x02004410, 0xd761000f, 0x02004411, 0xd7610010, 0x02004412, 0xd7610011, 0x02004413, + 0xd7610012, 0x02004414, 0xd7610013, 0x02004415, 0xd7610014, 0x02004416, 0xd7610015, 0x02004417, + 0xd7610016, 0x02004418, 0xd7610017, 0x02004419, 0xd7610018, 0x0200441a, 0xd7610019, 0x0200441b, + 0x80228122, 0xbf0aa022, 0xbf85ffce, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbea20380, 0xd7600008, 0x02004504, 0xd7600009, 0x02004505, + 0xd760000a, 0x02004506, 0xf4300312, 0x10000000, 0xbf8cc07f, 0xd761000a, 0x0200440c, 0xd761000b, + 0x0200440d, 0xd761000c, 0x0200440e, 0xd761000d, 0x0200440f, 0xd761000e, 0x02004410, 0xd761000f, + 0x02004411, 0xd7610010, 0x02004412, 0xd7610011, 0x02004413, 0xd7610012, 0x02004414, 0xd7610013, + 0x02004415, 0xd7610014, 0x02004416, 0xd7610015, 0x02004417, 0xd7610016, 0x02004418, 0xd7610017, + 0x02004419, 0xd7610018, 0x0200441a, 0xd7610019, 0x0200441b, 0x80228122, 0xbf0aa022, 0xbf85ffd4, + 0xe0781040, 0x80010a03, 0xe0781050, 0x80010e03, 0xe0781060, 0x80011203, 0xe0781070, 0x80011603, + 0xbea20380, 0xd7600008, 0x02004504, 0xd7600009, 0x02004505, 0xd760000a, 0x02004506, 0xf4300312, + 0x12000014, 0xbf8cc07f, 0xd761000a, 0x0200440c, 0xd761000b, 0x0200440d, 0xd761000c, 0x0200440e, + 0xd761000d, 0x0200440f, 0xd761000e, 0x02004410, 0xd761000f, 0x02004411, 0xd7610010, 0x02004412, + 0xd7610011, 0x02004413, 0xd7610012, 0x02004414, 0xd7610013, 0x02004415, 0xd7610014, 0x02004416, + 0xd7610015, 0x02004417, 0xd7610016, 0x02004418, 0xd7610017, 0x02004419, 0xd7610018, 0x0200441a, + 0xd7610019, 0x0200441b, 0x80228122, 0xbf0aa022, 0xbf85ffd4, 0xe0781080, 0x80010a03, 0xe0781090, + 0x80010e03, 0xe07810a0, 0x80011203, 0xe07810b0, 0x80011603, 0xbea20380, 0xd7600008, 0x02004504, + 0xd7600009, 0x02004505, 0xd760000a, 0x02004506, 0xf4280312, 0x100001f0, 0xf4280412, 0xfa0001fc, + 0xf4280512, 0x100fffff, 0xf4280612, 0x12000100, 0xbf8cc07f, 0xd761000a, 0x0200440c, 0xd761000b, + 0x0200440d, 0xd761000c, 0x0200440e, 0xd761000d, 0x0200440f, 0xd761000e, 0x02004410, 0xd761000f, + 0x02004411, 0xd7610010, 0x02004412, 0xd7610011, 0x02004413, 0xd7610012, 0x02004414, 0xd7610013, + 0x02004415, 0xd7610014, 0x02004416, 0xd7610015, 0x02004417, 0xd7610016, 0x02004418, 0xd7610017, + 0x02004419, 0xd7610018, 0x0200441a, 0xd7610019, 0x0200441b, 0x80228122, 0xbf0aa022, 0xbf85ffce, + 0xe07810c0, 0x80010a03, 0xe07810d0, 0x80010e03, 0xe07810e0, 0x80011203, 0xe07810f0, 0x80011603, + 0x7e580507, 0x882a2c26, 0xbea80424, 0xbeab0327, 0xbea20380, 0xd7600008, 0x02004504, 0xd7600009, + 0x02004505, 0xd760000a, 0x02004506, 0xf4200314, 0x12000014, 0xbf8cc07f, 0xd761000a, 0x0200440c, + 0x80228122, 0xbf0aa022, 0xbf85fff2, 0xe0781100, 0x80010a03, 0xe0781110, 0x80010e03, 0xe0781120, + 0x80011203, 0xe0781130, 0x80011603, 0xbea20380, 0xd7600008, 0x02004504, 0xd7600009, 0x02004505, + 0xd760000a, 0x02004506, 0xf4100312, 0xfa000026, 0xbf8cc07f, 0xd761000a, 0x0200440c, 0xd761000b, + 0x0200440d, 0xd761000c, 0x0200440e, 0xd761000d, 0x0200440f, 0xd761000e, 0x02004410, 0xd761000f, + 0x02004411, 0xd7610010, 0x02004412, 0xd7610011, 0x02004413, 0xd7610012, 0x02004414, 0xd7610013, + 0x02004415, 0xd7610014, 0x02004416, 0xd7610015, 0x02004417, 0xd7610016, 0x02004418, 0xd7610017, + 0x02004419, 0xd7610018, 0x0200441a, 0xd7610019, 0x0200441b, 0x80228122, 0xbf0aa022, 0xbf85ffd4, + 0xe0781140, 0x80010a03, 0xe0781150, 0x80010e03, 0xe0781160, 0x80011203, 0xe0781170, 0x80011603, + 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u}, + {0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u}, + {0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu}, + {0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du}, + {0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u}, + {0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u}, + {0x00000010u, 0x000001fdu, 0x00000040u, 0x830b46acu}, + {0x000001c0u, 0x00000204u, 0x000001a0u, 0xc01ff328u}, + {0x000001c4u, 0x000001ffu, 0x000001a1u, 0x38487fe1u}, + {0x000001c1u, 0x00000010u, 0x000001a2u, 0xd1999ef1u}, + {0x000001d0u, 0x000001c4u, 0x000001a3u, 0x692734d6u}, + {0x000001ecu, 0xffffffe1u, 0x0000019cu, 0xf568863bu}, + {0x000001f0u, 0x00000006u, 0x00000010u, 0xc93eb5e4u}, + {0x000001f3u, 0xfffffff0u, 0x00000014u, 0x78f588b9u}, + {0x000001f4u, 0x7ffffffcu, 0x00000020u, 0x84052a95u}, + {0x000001f8u, 0xffffffecu, 0x00000022u, 0x24c83161u}, + {0x000001fcu, 0x000001c0u, 0x00000088u, 0xc0983b6du}, + {0x000001fdu, 0x000001f4u, 0x0000008cu, 0x4e9d2c57u}, + {0x000001ffu, 0x00000002u, 0x0000008du, 0xc1b9aecfu}, + {0x00000200u, 0x00000004u, 0x00000100u, 0x4c9f5e6du}, + {0x00000201u, 0x00000000u, 0x00000104u, 0x964bc1d0u}, + {0x00000204u, 0x000001f3u, 0x00000108u, 0xb53d4566u}, + {0x00000240u, 0x000001f0u, 0x0000010bu, 0xa5293a5bu}, + {0xffffffecu, 0x000001e0u, 0x00000150u, 0x78adc6ecu}, + {0xfffffff0u, 0x000001fcu, 0x00000180u, 0x65b49589u}, + {0xfffffffcu, 0x00000001u, 0x00000184u, 0xd22a8a62u}, + {0xffffffe1u, 0x000001c1u, 0x00000030u, 0x9e321a50u}, + {0x80000000u, 0x00000100u, 0x00000034u, 0xd933a5c9u}, + {0x7ffffffcu, 0x80000000u, 0x00000035u, 0xc6f918bcu}, + {0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u}, + {0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u}, + {0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u} +}; +constexpr std::uint32_t Expected[32][96] = { + {0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000035u, 0xc6f918bcu, 0x00000100u, 0x00000200u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000035u, 0xc6f918bcu, 0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000035u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001ecu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000240u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000004u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x7ffffffcu, 0x80000000u, 0x00000035u, 0xc6f918bcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x7ffffffcu, 0x80000000u, 0x00000035u, 0xc6f918bcu, 0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x80000000u, 0x00000035u, 0xc6f918bcu, 0x00000100u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000035u, 0xc6f918bcu, 0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x7ffffffcu, 0x80000000u, 0x00000035u, 0xc6f918bcu, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x7ffffffcu, 0x80000000u, 0x00000035u, 0xc6f918bcu, 0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001fdu, 0x000001f4u, 0x0000008cu, 0x4e9d2c57u, 0xfffffffcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000005cu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x000001fdu, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000200u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001fcu, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x00000201u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x000001fdu, 0x00000004u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001fcu, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x00000201u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000003u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001fcu, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x00000201u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000200u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001f4u, 0x0000008cu, 0x4e9d2c57u, 0x000001ffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001f4u, 0x0000008cu, 0x4e9d2c57u, 0x000001ffu, 0x00000002u, 0x0000008du, 0xc1b9aecfu, 0x00000200u, 0x00000004u, 0x00000100u, 0x4c9f5e6du, 0x00000201u, 0x00000000u, 0x00000104u, 0x964bc1d0u, 0x00000204u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001f4u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x000001fcu, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x000001fcu, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x000001fdu, 0x000001f4u, 0x0000008cu, 0x4e9d2c57u, 0x000001ffu, 0x00000002u, 0x0000008du, 0xc1b9aecfu, 0x00000200u, 0x00000004u, 0x00000100u, 0x4c9f5e6du, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000003u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u}, + {0x00000100u, 0x00000034u, 0xd933a5c9u, 0x7ffffffcu, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001a4u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000100u, 0x00000034u, 0xd933a5c9u, 0x7ffffffcu, 0x80000000u, 0x00000035u, 0xc6f918bcu, 0x00000100u, 0x00000200u, 0x0000005cu, 0xe5308181u, 0x000001e0u, 0x000001ecu, 0x00000060u, 0x196419e4u, 0x000001b4u, 0x00000201u, 0x00000004u, 0x8b2b5909u, 0x00000001u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbb26d5d9u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000001fcu, 0x000001c0u, 0x00000088u, 0xc0983b6du, 0x00000201u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfffffffcu, 0x00000001u, 0xac811eafu, 0x00000002u, 0x000001b4u, 0x00000002u, 0x6c24691du, 0x00000003u, 0x000001d0u, 0x00000003u, 0x4c6254c0u, 0x00000006u, 0x000001f8u, 0x00000007u, 0x473d0ab8u, 0x00000010u} +}; +constexpr const char* Names[96] = {"buffer_load_dwordx4 s8 dword 0", "buffer_load_dwordx4 s8 dword 1", "buffer_load_dwordx4 s8 dword 2", "buffer_load_dwordx4 s8 dword 3", "buffer_load_dwordx4 s9 offset 0x14 dword 0", "buffer_load_dwordx4 s9 offset 0x14 dword 1", "buffer_load_dwordx4 s9 offset 0x14 dword 2", "buffer_load_dwordx4 s9 offset 0x14 dword 3", "buffer_load_dwordx4 s9 offset 0x1fff0 dword 0", "buffer_load_dwordx4 s9 offset 0x1fff0 dword 1", "buffer_load_dwordx4 s9 offset 0x1fff0 dword 2", "buffer_load_dwordx4 s9 offset 0x1fff0 dword 3", "buffer_load_dwordx4 imm 0x1f8 dword 0", "buffer_load_dwordx4 imm 0x1f8 dword 1", "buffer_load_dwordx4 imm 0x1f8 dword 2", "buffer_load_dwordx4 imm 0x1f8 dword 3", "buffer_load_dwordx16 s8 dword 0", "buffer_load_dwordx16 s8 dword 1", "buffer_load_dwordx16 s8 dword 2", "buffer_load_dwordx16 s8 dword 3", "buffer_load_dwordx16 s8 dword 4", "buffer_load_dwordx16 s8 dword 5", "buffer_load_dwordx16 s8 dword 6", "buffer_load_dwordx16 s8 dword 7", "buffer_load_dwordx16 s8 dword 8", "buffer_load_dwordx16 s8 dword 9", "buffer_load_dwordx16 s8 dword 10", "buffer_load_dwordx16 s8 dword 11", "buffer_load_dwordx16 s8 dword 12", "buffer_load_dwordx16 s8 dword 13", "buffer_load_dwordx16 s8 dword 14", "buffer_load_dwordx16 s8 dword 15", "buffer_load_dwordx16 s9 offset 0x14 dword 0", "buffer_load_dwordx16 s9 offset 0x14 dword 1", "buffer_load_dwordx16 s9 offset 0x14 dword 2", "buffer_load_dwordx16 s9 offset 0x14 dword 3", "buffer_load_dwordx16 s9 offset 0x14 dword 4", "buffer_load_dwordx16 s9 offset 0x14 dword 5", "buffer_load_dwordx16 s9 offset 0x14 dword 6", "buffer_load_dwordx16 s9 offset 0x14 dword 7", "buffer_load_dwordx16 s9 offset 0x14 dword 8", "buffer_load_dwordx16 s9 offset 0x14 dword 9", "buffer_load_dwordx16 s9 offset 0x14 dword 10", "buffer_load_dwordx16 s9 offset 0x14 dword 11", "buffer_load_dwordx16 s9 offset 0x14 dword 12", "buffer_load_dwordx16 s9 offset 0x14 dword 13", "buffer_load_dwordx16 s9 offset 0x14 dword 14", "buffer_load_dwordx16 s9 offset 0x14 dword 15", "buffer_load_dwordx4 s8 offset 0x1f0 dword 0", "buffer_load_dwordx4 s8 offset 0x1f0 dword 1", "buffer_load_dwordx4 s8 offset 0x1f0 dword 2", "buffer_load_dwordx4 s8 offset 0x1f0 dword 3", "buffer_load_dwordx4 imm 0x1fc dword 0", "buffer_load_dwordx4 imm 0x1fc dword 1", "buffer_load_dwordx4 imm 0x1fc dword 2", "buffer_load_dwordx4 imm 0x1fc dword 3", "buffer_load_dwordx4 s8 offset 0xfffff dword 0", "buffer_load_dwordx4 s8 offset 0xfffff dword 1", "buffer_load_dwordx4 s8 offset 0xfffff dword 2", "buffer_load_dwordx4 s8 offset 0xfffff dword 3", "buffer_load_dwordx4 s9 offset 0x100 dword 0", "buffer_load_dwordx4 s9 offset 0x100 dword 1", "buffer_load_dwordx4 s9 offset 0x100 dword 2", "buffer_load_dwordx4 s9 offset 0x100 dword 3", "buffer_load_dword s9 offset 0x14 gpu descriptor", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "load_dwordx16 imm 0x26 dword 0", "load_dwordx16 imm 0x26 dword 1", "load_dwordx16 imm 0x26 dword 2", "load_dwordx16 imm 0x26 dword 3", "load_dwordx16 imm 0x26 dword 4", "load_dwordx16 imm 0x26 dword 5", "load_dwordx16 imm 0x26 dword 6", "load_dwordx16 imm 0x26 dword 7", "load_dwordx16 imm 0x26 dword 8", "load_dwordx16 imm 0x26 dword 9", "load_dwordx16 imm 0x26 dword 10", "load_dwordx16 imm 0x26 dword 11", "load_dwordx16 imm 0x26 dword 12", "load_dwordx16 imm 0x26 dword 13", "load_dwordx16 imm 0x26 dword 14", "load_dwordx16 imm 0x26 dword 15"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + Require(block != nullptr, "scalar wide loads: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, false); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* Data() { return block; } + +private: + std::uint8_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("scalar wide loads: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + GuestBlock guest; + std::memset(guest.Data(), 0, BlockBytes); + std::memcpy(guest.Data(), Input.data(), Input.size() * 4u); + const auto input = BufferDescriptor(guest.Data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::span guestBytes(guest.Data(), Input.size() * 4u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}, {reinterpret_cast(guest.Data()), std::as_bytes(guestBytes)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 96; ++i) { + if (Names[i] != nullptr) Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("scalar wide loads tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp new file mode 100644 index 000000000..10863bc09 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp @@ -0,0 +1,218 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x06140af9, 0x06000604, 0x7e160307, + 0x08160af9, 0x05061504, 0x0a180af9, 0x06142a04, 0x101a0af9, 0x03260404, 0x0e1c0af9, 0x06010604, + 0x1e1e0af9, 0x160a0604, 0x7e200307, 0x20200af9, 0x08061104, 0x20220af9, 0x060d2604, 0x64240af9, + 0x05010004, 0x66260af9, 0x02150d04, 0x7e280307, 0x68280af9, 0x24031304, 0x6a2a0af9, 0x00040604, + 0x7e2c0307, 0x742c0af9, 0x05081404, 0x722e0af9, 0x19050204, 0x06300af9, 0x06052b04, 0x6a320af9, + 0x06020904, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x40404040u, 0x8b2af49fu}, + {0x00000000u, 0x10663f9du, 0x8b35e743u, 0x7d7d9db4u}, + {0x0008dbf2u, 0x47004700u, 0x3c000000u, 0x80000000u}, + {0x05c21fabu, 0x00470047u, 0x80000000u, 0x47004700u}, + {0x1a0e7cdeu, 0x00000000u, 0xefc2742bu, 0xcd8d3db8u}, + {0xc4de998au, 0xaf1fba34u, 0x0000bc00u, 0xbc00bc00u}, + {0x00000000u, 0x00470047u, 0xc2c2c2c2u, 0xfda0095du}, + {0x3c000000u, 0x80808080u, 0x5960b098u, 0xadd5eff7u}, + {0xbc00bc00u, 0x00000000u, 0x42a502c0u, 0x3c000000u}, + {0x80808080u, 0x1a64a58eu, 0xfe242714u, 0x3c003c00u}, + {0x00000000u, 0x7c007c00u, 0x7f7f7f7fu, 0x3c003c00u}, + {0x00470047u, 0x00000000u, 0x3468787du, 0x7e7103e1u}, + {0x00470047u, 0x80000000u, 0x702bd672u, 0x47004700u}, + {0x00000000u, 0x00800080u, 0x4659ff82u, 0xc2c2c2c2u}, + {0x7f800000u, 0x00000000u, 0x01000100u, 0x1b1c3f27u}, + {0x80000000u, 0x00800080u, 0xbf800000u, 0x829ea49cu}, + {0x00000000u, 0x0000bc00u, 0x3343e632u, 0x13e60ef5u}, + {0x00000000u, 0x00470047u, 0xb5a54b0bu, 0x6769ec45u}, + {0x00000000u, 0x00800080u, 0xa2e7068bu, 0x871c3c9cu}, + {0x00000000u, 0x00800080u, 0xafe5c4b5u, 0x3c003c00u}, + {0x00000000u, 0x00ff00ffu, 0x0000bc00u, 0x40404040u}, + {0x00000000u, 0x00ff00ffu, 0x7f800000u, 0x00ff00ffu}, + {0x00000000u, 0x00ff00ffu, 0xb4ad5866u, 0xe4ea7accu}, + {0x00000000u, 0x00ff00ffu, 0xd03c7dbeu, 0x5de25876u}, + {0x00000000u, 0x01000100u, 0x52eff50fu, 0x00000000u}, + {0x00000000u, 0x182f8e4au, 0xe117e1fau, 0x0000bc00u}, + {0x00000000u, 0x20d8e2bcu, 0xe73074b1u, 0xbf800000u}, + {0x00000000u, 0x2333ad9cu, 0x00800080u, 0x3f800000u}, + {0x00000000u, 0x24f70eabu, 0xfdb88e95u, 0x17e6487au}, + {0x00000000u, 0x2a778095u, 0x00470047u, 0x7c007c00u}, + {0x00000000u, 0x2b119b05u, 0x0000bc00u, 0x4ce5144fu}, + {0x00000000u, 0x2b5d55fdu, 0x03f55439u, 0x7f7f7f7fu} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000000u, 0x0000f49fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x8b2a009fu, 0x00000000u, 0x00000000u, 0x80000000u, 0x002af49fu, 0x00000000u, 0x8b2a0000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x10663f9du, 0x10669db4u, 0xff9d0000u, 0x00000000u, 0x00000000u, 0x90663f9du, 0x7d7d00b4u, 0x10663f9du, 0x00000066u, 0x80660000u, 0x9d7d9db4u, 0x00000000u, 0x7d7d0000u, 0x00000000u, 0x9d000000u, 0x00000000u}, + {0x47004700u, 0x94f20000u, 0x00000000u, 0x00000000u, 0x006dbcbdu, 0xc7004700u, 0x8000f200u, 0x3f800000u, 0x00000000u, 0x80080000u, 0x00000000u, 0x00008000u, 0x80004700u, 0x00080000u, 0x00000000u, 0x00003800u}, + {0x004700f2u, 0x1fab4700u, 0xfff20000u, 0x00000000u, 0x00000000u, 0x80470047u, 0x4700ab00u, 0x00470047u, 0x00000066u, 0x86090000u, 0x42004700u, 0x00000001u, 0x47000047u, 0x00c20000u, 0x09000000u, 0x00000000u}, + {0x000000deu, 0x7cde3db8u, 0xffde0000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xcd8ddeb8u, 0x00001a0eu, 0x0000007cu, 0x9a0e0000u, 0x1a8d3db8u, 0x00007edeu, 0xcd8d0000u, 0x000e0000u, 0x0e000000u, 0x00000000u}, + {0xaf1fba34u, 0x998abc00u, 0x00000000u, 0x0000c158u, 0x80000000u, 0x2f1fba34u, 0xbc003400u, 0x00000000u, 0x0000001fu, 0x44de0000u, 0x3400bc00u, 0x00008000u, 0xbc00af1fu, 0x00de0000u, 0x00000000u, 0xffffac00u}, + {0x00470047u, 0x0047095du, 0x00470000u, 0x00000000u, 0x00000000u, 0x80470047u, 0xfda0475du, 0x00470047u, 0x00000047u, 0x80470000u, 0x47a0095du, 0x00000000u, 0xfda00000u, 0x00000000u, 0x47000000u, 0x00000000u}, + {0x80808080u, 0x0000eff7u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0xadd500f7u, 0x00003c00u, 0x00000080u, 0xbc000000u, 0x44d5eff7u, 0x00000000u, 0xadd58080u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xbc000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x3c000000u, 0x00000000u, 0x000000bcu, 0x3c000000u, 0xbc000000u, 0x00008000u, 0x3c000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x1a64a58eu, 0x9ae43c00u, 0xff8e0000u, 0x00000000u, 0x00000000u, 0x9a64a58eu, 0x3c008000u, 0x1a64a58eu, 0x00000068u, 0x001c0000u, 0x8e003c00u, 0x00008000u, 0x3c001a64u, 0x00800000u, 0x8e000000u, 0x00000300u}, + {0x7c007c00u, 0x7c003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfc007c00u, 0x3c000000u, 0x3f800000u, 0x00000000u, 0x80000000u, 0x00003c00u, 0x00000000u, 0x3c000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000047u, 0x004703e1u, 0x00470000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x7e7147e1u, 0x00000047u, 0x00000000u, 0x80470000u, 0x007103e1u, 0x00000000u, 0x7e710000u, 0x00470000u, 0x47000000u, 0x00000000u}, + {0x00000047u, 0x80474700u, 0x00470000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x47004700u, 0x00000047u, 0x00000000u, 0x80470000u, 0x00004700u, 0x00000000u, 0x47008000u, 0x00470000u, 0x47000000u, 0x00000000u}, + {0x00800080u, 0x0080c2c2u, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0xc2c200c2u, 0x00800080u, 0x00000080u, 0x80800000u, 0x80c2c2c2u, 0x00000000u, 0xc2c20000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x00003f27u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x1b1c0027u, 0x00007f80u, 0x00000000u, 0xff800000u, 0x7f1c3f27u, 0x00000000u, 0x1b1c0000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00800080u, 0x0080a49cu, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0x829e009cu, 0x00800080u, 0x00000080u, 0x80800000u, 0x009ea49cu, 0x00000000u, 0x829e0000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x0000bc00u, 0x00000ef5u, 0x00000000u, 0x00000000u, 0x00000000u, 0x8000bc00u, 0x13e600f5u, 0x0000bc00u, 0x00000000u, 0x80000000u, 0x00e60ef5u, 0x00000000u, 0x13e60000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00470047u, 0x0047ec45u, 0x00470000u, 0x00000000u, 0x00000000u, 0x80470047u, 0x67694745u, 0x00470047u, 0x00000047u, 0x80470000u, 0x4769ec45u, 0x00000000u, 0x67690000u, 0x00000000u, 0x47000000u, 0x00000000u}, + {0x00800080u, 0x00803c9cu, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0x871c009cu, 0x00800080u, 0x00000080u, 0x80800000u, 0x801c3c9cu, 0x00000000u, 0x871c0000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00800080u, 0x00803c00u, 0xff800000u, 0x00000000u, 0x00000000u, 0x80800080u, 0x3c000000u, 0x00800080u, 0x00000080u, 0x80800000u, 0x80003c00u, 0x00000000u, 0x3c000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff4040u, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0x40400040u, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xff404040u, 0x00000000u, 0x40400000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff00ffu, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xffff00ffu, 0x00000000u, 0x00ff0000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff7accu, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0xe4ea00ccu, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xffea7accu, 0x00000000u, 0xe4ea0000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00ff00ffu, 0x00ff5876u, 0xffff0000u, 0x00000000u, 0x00000000u, 0x80ff00ffu, 0x5de20076u, 0x00ff00ffu, 0x000000ffu, 0x80ff0000u, 0xffe25876u, 0x00000000u, 0x5de20000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x01000100u, 0x01000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x81000100u, 0x00000000u, 0x01000100u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x182f8e4au, 0x182fbc00u, 0x004a0000u, 0x00000000u, 0x00000000u, 0x982f8e4au, 0x00004a00u, 0x182f8e4au, 0x0000002fu, 0x802f0000u, 0x4a00bc00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x4a000000u, 0x00000000u}, + {0x20d8e2bcu, 0x20d80000u, 0xffbc0000u, 0x00000000u, 0x00000000u, 0xa0d8e2bcu, 0xbf800000u, 0x20d8e2bcu, 0x000000d8u, 0x80d80000u, 0xbc800000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0xbc000000u, 0x00000000u}, + {0x2333ad9cu, 0x23330000u, 0xff9c0000u, 0x00000000u, 0x00000000u, 0xa333ad9cu, 0x3f800000u, 0x2333ad9cu, 0x00000033u, 0x80330000u, 0x9c800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x9c000000u, 0x00000000u}, + {0x24f70eabu, 0x24f7487au, 0xffab0000u, 0x00000000u, 0x00000000u, 0xa4f70eabu, 0x17e6007au, 0x24f70eabu, 0x000000f7u, 0x80f70000u, 0xabe6487au, 0x00000000u, 0x17e60000u, 0x00000000u, 0xab000000u, 0x00000000u}, + {0x2a778095u, 0x2a777c00u, 0xff950000u, 0x00000000u, 0x00000000u, 0xaa778095u, 0x7c000000u, 0x2a778095u, 0x00000077u, 0x80770000u, 0x95007c00u, 0x00000000u, 0x7c000000u, 0x00000000u, 0x95000000u, 0x00000000u}, + {0x2b119b05u, 0x2b11144fu, 0x00050000u, 0x00000000u, 0x00000000u, 0xab119b05u, 0x4ce5054fu, 0x2b119b05u, 0x00000011u, 0x80110000u, 0x05e5144fu, 0x00000000u, 0x4ce50000u, 0x00000000u, 0x05000000u, 0x00000000u}, + {0x2b5d55fdu, 0x2b5d7f7fu, 0xfffd0000u, 0x00000000u, 0x00000000u, 0xab5d55fdu, 0x7f7f007fu, 0x2b5d55fdu, 0x0000005du, 0x805d0000u, 0xfd7f7f7fu, 0x00000000u, 0x7f7f0000u, 0x00000000u, 0xfd000000u, 0x00000000u} +}; +constexpr std::uint32_t Checked[32] = {0xffffu, 0xfdfdu, 0x5dadu, 0xa05au, 0xb27au, 0x7fa7u, 0xb818u, 0xea77u, 0xfaffu, 0x5cfdu, 0x7ffdu, 0x9d38u, 0x9d38u, 0xf8fdu, 0xb977u, 0xfcfdu, 0xff5eu, 0xb818u, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xf8fdu, 0xfffdu, 0xfdbdu, 0xfdfdu, 0xfdfdu, 0xfdfdu, 0xf9fdu, 0xfdbdu, 0xfdfdu}; +constexpr const char* Names[16] = { + "add_f32 BYTE_0/DWORD -> DWORD", + "sub_f32 DWORD/WORD_1 -> WORD_1", + "subrev_f32 WORD_0/DWORD -> BYTE_2", + "mul_f32 DWORD/BYTE_3 -> WORD_0", + "mul_legacy_f32 BYTE_1/DWORD -> DWORD", + "min_f32 BYTE_2/DWORD -> DWORD", + "max_f32 DWORD/BYTE_0 -> BYTE_1", + "max_f32 WORD_1/DWORD -> DWORD", + "add_f16 BYTE_1/WORD_1 -> BYTE_0", + "sub_f16 WORD_1/BYTE_2 -> WORD_1", + "subrev_f16 BYTE_3/WORD_0 -> BYTE_3", + "mul_f16 WORD_0/BYTE_0 -> DWORD", + "min_f16 BYTE_0/WORD_1 -> WORD_0", + "max_f16 WORD_1/BYTE_1 -> BYTE_2", + "add_f32 WORD_1/DWORD -> BYTE_3", + "mul_f16 BYTE_2/DWORD -> BYTE_1", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("sdwa float selectors: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span code) { + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + const std::span code(Code); + const auto result = Compile(device, code); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckByteSelectorClampRefused(AgcDriver::VulkanDevice& device) { + constexpr std::array, 6> forms{{ + {0x640a0cf9u, 0x04052004u}, + {0x660a0cf9u, 0x04022504u}, + {0x680a0cf9u, 0x03042604u}, + {0x6a0a0cf9u, 0x05043304u}, + {0x740a0cf9u, 0x06012404u}, + {0x720a0cf9u, 0x00062604u}, + }}; + for (const auto& form : forms) { + alignas(256) const std::array code{form[0], form[1], 0xbf810000u}; + std::string refusal; + try { + static_cast(Compile(device, code)); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("VOP2 SDWA clamp with byte selectors is not implemented") != std::string::npos, "f16 SDWA form " + Hex(form[0]) + " " + Hex(form[1]) + " with clamp was not refused"); + } +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + if ((Checked[tid] >> i) & 1u) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + CheckByteSelectorClampRefused(*device); + std::puts("sdwa float selectors tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaIntegerClamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaIntegerClamp.cpp new file mode 100644 index 000000000..3174ec093 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaIntegerClamp.cpp @@ -0,0 +1,188 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x4a140af9, 0x06012604, 0x7e160307, + 0x4c160af9, 0x030c3504, 0x4e180af9, 0x0a062804, 0x7d8a0c80, 0x501a0af9, 0x05062404, 0xd501000e, + 0x01a90280, 0x7e1e0307, 0x7d8a0c80, 0x521e0af9, 0x06023304, 0xd5010010, 0x01a90280, 0x7d8a0c80, + 0x54220af9, 0x04082604, 0xd5010012, 0x01a90280, 0x12260af9, 0x0d062d04, 0x16280af9, 0x06062604, + 0x142a0af9, 0x06062604, 0x262c0af9, 0x01062604, 0x7e2e0307, 0x302e0af9, 0x06003404, 0x3c300af9, + 0x06062604, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00010000u, 0x51bb9a4eu}, + {0x007fffffu, 0xfd503aeeu, 0x00000000u, 0x00008000u}, + {0x00000001u, 0x219db754u, 0x00000000u, 0xfdf13c3fu}, + {0x00008000u, 0xfffffffeu, 0x000000ffu, 0x7f7f7f7fu}, + {0x00010000u, 0x80000001u, 0x00007fffu, 0x00008000u}, + {0xfffffffeu, 0x00000000u, 0x00008000u, 0x7fffffffu}, + {0x00000000u, 0x00000002u, 0x00000000u, 0x00008000u}, + {0x00000000u, 0x00010000u, 0x00000000u, 0xe5224648u}, + {0x00010000u, 0x00000000u, 0x00007fffu, 0x80000000u}, + {0x0013fd86u, 0x08ce4bd3u, 0xa671bed2u, 0xffff0000u}, + {0x00ffffffu, 0x00000002u, 0x00000000u, 0xa8d118f0u}, + {0xffff0000u, 0xffff0000u, 0x007fffffu, 0x8bc63fd0u}, + {0xfffffffeu, 0x00ff00ffu, 0x00000000u, 0x00000002u}, + {0x00000000u, 0x00000000u, 0x7f7f7f7fu, 0x00ff00ffu}, + {0x00000000u, 0x00000000u, 0x93552cc2u, 0xffffffffu}, + {0x00000000u, 0x00000000u, 0xccaf1cddu, 0xf37566c1u}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0x0000ffffu}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0x12345678u}, + {0x00000000u, 0x00000001u, 0x00ffffffu, 0x33869405u}, + {0x00000000u, 0x00000001u, 0xff00ff00u, 0x202be01bu}, + {0x00000000u, 0x00000002u, 0x4c31a5c3u, 0x5534d12cu}, + {0x00000000u, 0x00000002u, 0x7ba28771u, 0x007fffffu}, + {0x00000000u, 0x00000002u, 0x7fffffffu, 0xb8cf0c88u}, + {0x00000000u, 0x00000002u, 0xf6340ef5u, 0xcf296973u}, + {0x00000000u, 0x00000002u, 0xffff0000u, 0xffffffffu}, + {0x00000000u, 0x0000007fu, 0x0000ffffu, 0x8d2cc93bu}, + {0x00000000u, 0x0000007fu, 0x121b4baau, 0x00000002u}, + {0x00000000u, 0x0000007fu, 0x12345678u, 0x00ff00ffu}, + {0x00000000u, 0x0000007fu, 0x7f7f7f7fu, 0x000000ffu}, + {0x00000000u, 0x00000080u, 0x00000001u, 0x00010000u}, + {0x00000000u, 0x00000080u, 0x0000ffffu, 0x00ff00ffu}, + {0x00000000u, 0x00000080u, 0x0000ffffu, 0x80000000u} +}; +constexpr std::uint32_t Expected[32][15] = { + {0x00000000u, 0x00009a4eu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00bb9a4eu, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x51bb0000u, 0xffffffffu}, + {0xfd503bedu, 0xff028000u, 0x00000000u, 0x0000fd4fu, 0x00000000u, 0x00008000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0xffffffffu, 0x0000281du, 0x0000003au, 0x0000ffffu, 0x02d03aeeu}, + {0x219db754u, 0x00003c3fu, 0xffffff9cu, 0x0000219eu, 0x00000000u, 0x00f13c3fu, 0x00000001u, 0x0000b753u, 0x00000000u, 0x219d0000u, 0x009db754u, 0xffffffffu, 0x00000001u, 0xfdf1dbaau, 0xde6248aau}, + {0xffffffffu, 0x7f017f7fu, 0xffffffffu, 0x00008000u, 0x00000000u, 0x007f7f7fu, 0x00000001u, 0x0000fffdu, 0x00000000u, 0x80000000u, 0xffffffffu, 0xffffffffu, 0x000000ffu, 0x7f7ffffeu, 0x00008001u}, + {0x80000001u, 0x00008000u, 0x00000000u, 0x00008001u, 0x00000000u, 0x00008000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x7ffefffeu}, + {0x000000ffu, 0xfffeffffu, 0x00000000u, 0x0000ffffu, 0x00000000u, 0xfeffffffu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fff0000u, 0x00000001u}, + {0x00000002u, 0x00008000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00008000u, 0x00000001u, 0x00000002u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000002u, 0xfffffffdu}, + {0x00010000u, 0x00004648u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00224648u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xe5220000u, 0xfffeffffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xfffeffffu}, + {0x08ce4cd0u, 0xfd7e0000u, 0x00000048u, 0x00000655u, 0x00000000u, 0x00ff0000u, 0x00000001u, 0x00000000u, 0x00000001u, 0xffff0000u, 0xffffffffu, 0xfffffc1eu, 0x0000004bu, 0xffff392fu, 0xf72249aau}, + {0x00000101u, 0xffff18f0u, 0x00000000u, 0x0000ffffu, 0x00000000u, 0xfdd118f0u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x01fffffeu, 0xffffffffu, 0x00000000u, 0xa8d10000u, 0xff000002u}, + {0xffff0000u, 0x00003fd0u, 0xffffffffu, 0x0000ffffu, 0x00000001u, 0x00c63fd0u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0xffffffffu, 0x00000001u, 0x00000000u, 0x8bc60000u, 0xffffffffu}, + {0x00ff01feu, 0xfffe0002u, 0x00000001u, 0x0000ffffu, 0x00000001u, 0x00000002u, 0x00000001u, 0x00000000u, 0x00000001u, 0xfe020000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00ff00feu}, + {0x00000000u, 0x000000ffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00ff00ffu, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00ff0000u, 0xffffffffu}, + {0x00000000u, 0x0000ffffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00ffffffu, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffff0000u, 0xffffffffu}, + {0x00000000u, 0x000066c1u, 0x00000000u, 0x00000001u, 0x00000000u, 0x007566c1u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xf3750000u, 0xffffffffu}, + {0x00000000u, 0x0000ffffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x0000ffffu, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu}, + {0x00000000u, 0x00005678u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00345678u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x12340000u, 0xffffffffu}, + {0x00000001u, 0x00009405u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00869405u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x33860001u, 0xfffffffeu}, + {0x00000001u, 0x0000e01bu, 0x00000000u, 0x00000001u, 0x00000000u, 0x002be01bu, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x202b0001u, 0xfffffffeu}, + {0x00000002u, 0x0000d12cu, 0x00000000u, 0x00000001u, 0x00000000u, 0x0034d12cu, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x55340002u, 0xfffffffdu}, + {0x00000002u, 0x0000ffffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x007fffffu, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x007f0002u, 0xfffffffdu}, + {0x00000002u, 0x00000c88u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00cf0c88u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xb8cf0002u, 0xfffffffdu}, + {0x00000002u, 0x00006973u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00296973u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xcf290002u, 0xfffffffdu}, + {0x00000002u, 0x0000ffffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00ffffffu, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffff0002u, 0xfffffffdu}, + {0x0000007fu, 0x0000c93bu, 0x00000000u, 0x00000001u, 0x00000000u, 0x002cc93bu, 0x00000001u, 0x0000007eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x8d2c007fu, 0xffffff80u}, + {0x0000007fu, 0x00000002u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000002u, 0x00000001u, 0x0000007eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000007fu, 0xffffff80u}, + {0x0000007fu, 0x000000ffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00ff00ffu, 0x00000001u, 0x0000007eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00ff007fu, 0xffffff80u}, + {0x0000007fu, 0x000000ffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x000000ffu, 0x00000001u, 0x0000007eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000007fu, 0xffffff80u}, + {0x00000080u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00010000u, 0x00000001u, 0x0000007fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00010080u, 0xffffff7fu}, + {0x00000080u, 0x000000ffu, 0x00000000u, 0x00000001u, 0x00000000u, 0x00ff00ffu, 0x00000001u, 0x0000007fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00ff0080u, 0xffffff7fu}, + {0x00000080u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000001u, 0x0000007fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000080u, 0xffffff7fu} +}; +constexpr const char* Names[15] = { + "add_nc_u32", + "sub_nc_u32", + "subrev_nc_u32", + "add_co_ci_u32", + "add_co_ci_u32 carry", + "sub_co_ci_u32", + "sub_co_ci_u32 carry", + "subrev_co_ci_u32", + "subrev_co_ci_u32 carry", + "mul_i32_i24", + "mul_u32_u24", + "mul_hi_i32_i24", + "min_u32", + "ashrrev_i32", + "xnor_b32", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("sdwa integer clamp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 15; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("sdwa integer clamp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaIntegerSelectors.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaIntegerSelectors.cpp new file mode 100644 index 000000000..2d9b96b79 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaIntegerSelectors.cpp @@ -0,0 +1,189 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x22140af9, 0x05090604, 0x7e160307, + 0x24160af9, 0x0b041504, 0x7d8a0c80, 0x52180af9, 0x05000204, 0xd501000d, 0x01a90280, 0x7d8a0c80, + 0x541c0af9, 0x020c0c04, 0xd501000f, 0x01a90280, 0x7e200307, 0x7d8a0c80, 0x50200af9, 0x0d0b1504, + 0xd5010011, 0x01a90280, 0x12240af9, 0x060d0904, 0x7e260307, 0x28260af9, 0x06061304, 0x2c280af9, + 0x06000d04, 0x302a0af9, 0x0d060204, 0x7e2c0307, 0x342c0af9, 0x01061004, 0x362e0af9, 0x06060104, + 0x3c300af9, 0x06020b04, 0x4e320af9, 0x04080d04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, + 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00800000u, 0x000000ffu}, + {0x08f2e4e2u, 0x857c2007u, 0x01010101u, 0xebf2481cu}, + {0x8020aa77u, 0x00000001u, 0x00ff00ffu, 0x9c1571f4u}, + {0x80000000u, 0x80808080u, 0x00008000u, 0x5e028715u}, + {0x80808080u, 0x00ffffffu, 0x00ffffffu, 0x5914081cu}, + {0x007fffffu, 0xfffffffeu, 0x091bcae1u, 0xdb1f699au}, + {0x007fffffu, 0x00008000u, 0x0000007fu, 0x80000000u}, + {0x80659126u, 0x80808080u, 0x24c5a003u, 0xbab7d0d3u}, + {0x00000000u, 0x01010101u, 0x6b8644adu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x254c883fu, 0xffff0000u}, + {0x00000000u, 0x00000000u, 0x31fb13a6u, 0x7f7f7f7fu}, + {0x00000000u, 0x00000001u, 0x00000002u, 0x80000000u}, + {0x00000000u, 0x00000001u, 0x00800000u, 0x00000080u}, + {0x00000000u, 0x00000001u, 0x00ffffffu, 0x007fffffu}, + {0x00000000u, 0x00000001u, 0xad138795u, 0x43a562a0u}, + {0x00000000u, 0x00000001u, 0xff00ff00u, 0x20046976u}, + {0x00000000u, 0x00000001u, 0xffff0000u, 0x80808080u}, + {0x00000000u, 0x00000010u, 0x00008000u, 0x00000080u}, + {0x00000000u, 0x00000010u, 0x00008000u, 0x00007fffu}, + {0x00000000u, 0x00000010u, 0x0000fff0u, 0x00000001u}, + {0x00000000u, 0x0000007fu, 0x00007fffu, 0x00007fffu}, + {0x00000000u, 0x0000007fu, 0x00008000u, 0x80000000u}, + {0x00000000u, 0x0000007fu, 0x80808080u, 0xbde9de02u}, + {0x00000000u, 0x00000080u, 0x0000fff0u, 0xf91a74d9u}, + {0x00000000u, 0x00000080u, 0xfffffffeu, 0xdd7491acu}, + {0x00000000u, 0x000000ffu, 0x00007fffu, 0xfefefefeu}, + {0x00000000u, 0x000000ffu, 0x0000fff0u, 0xf940015bu}, + {0x00000000u, 0x000000ffu, 0x00ffffffu, 0xfacb11deu}, + {0x00000000u, 0x000000ffu, 0x5901b440u, 0x0000fff0u}, + {0x00000000u, 0x000000ffu, 0xff00ff00u, 0x000000ffu}, + {0x00000000u, 0x00007fffu, 0x0964340cu, 0x931d22eau}, + {0x00000000u, 0x00007fffu, 0x14a4a43du, 0xbad19730u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000000u, 0x000000ffu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x000100ffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0xffffffe4u, 0xe4e2481cu, 0x00650000u, 0x00000001u, 0x00001b99u, 0x00000001u, 0x8585481cu, 0x00000000u, 0xffff9e00u, 0x07f2481cu, 0x08010000u, 0x005f0000u, 0xebf24880u, 0x00000200u, 0x0a000000u, 0x20250000u}, + {0xffffffaau, 0xaa7771f4u, 0x00760000u, 0x00000000u, 0x00005588u, 0x00000001u, 0xff8171f4u, 0x00000000u, 0x00002000u, 0x771571f4u, 0x00000000u, 0x00000000u, 0x9c157100u, 0x00000100u, 0xde000000u, 0xff8a0000u}, + {0x00000000u, 0x00008715u, 0x007f0000u, 0x00000001u, 0x0000007fu, 0x00000000u, 0x80018715u, 0x00000001u, 0x00000000u, 0x80028715u, 0x80800000u, 0x00800000u, 0x5e028780u, 0x00000000u, 0x7f000000u, 0x80800000u}, + {0xffffff80u, 0x8080081cu, 0x00800000u, 0x00000001u, 0xffff807eu, 0x00000001u, 0x0080081cu, 0x00000001u, 0xffff8000u, 0x8014081cu, 0xffff0000u, 0x00ff0000u, 0x591408ffu, 0x00008000u, 0x80000000u, 0x007f0000u}, + {0xffffffffu, 0xffff699au, 0x00ff0000u, 0x00000001u, 0x000000ffu, 0x00000001u, 0x0000699au, 0x00000001u, 0x00000200u, 0xfe1f699au, 0x00010000u, 0x00ff0000u, 0xdb1f6900u, 0x0000fe00u, 0x7e000000u, 0xffff0000u}, + {0xffffffffu, 0xffff0000u, 0x00fe0000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00010000u, 0x00000000u, 0x00000000u, 0xff000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80010000u}, + {0xffffff91u, 0x9126d0d3u, 0x00a50000u, 0x00000001u, 0x00006f59u, 0x00000001u, 0x8001d0d3u, 0x00000001u, 0xffff8000u, 0x80b7d0d3u, 0x02020000u, 0x00020000u, 0xbab7d000u, 0x00000000u, 0x1a000000u, 0x805a0000u}, + {0x00000000u, 0x00010000u, 0x00fe0000u, 0x00000001u, 0x00000000u, 0x00000000u, 0x01020000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x01010000u, 0x00010000u, 0x00000001u, 0x00000000u, 0xfe000000u, 0x01010000u}, + {0x00000000u, 0x00000000u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00ff0000u, 0x00000000u, 0x00000000u, 0xffff0000u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00000000u, 0x00007f7fu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00017f7fu, 0x00000000u, 0x00000000u, 0x007f7f7fu, 0x00000000u, 0x00000000u, 0x7f7f7f00u, 0x00000000u, 0xff000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00010000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00010000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xfe000000u, 0x00010000u}, + {0x00000000u, 0x00000080u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00010080u, 0x00000000u, 0x00000000u, 0x01000080u, 0x00010000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xfe000000u, 0x00010000u}, + {0x00000000u, 0x0000ffffu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x0001ffffu, 0x00000000u, 0x00000000u, 0x017fffffu, 0x00010000u, 0x00000000u, 0x007fff00u, 0x00000000u, 0xfe000000u, 0x00010000u}, + {0x00000000u, 0x000062a0u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x000162a0u, 0x00000000u, 0x00000000u, 0x01a562a0u, 0x00010000u, 0x00000000u, 0x43a56200u, 0x00000000u, 0xfe000000u, 0x00010000u}, + {0x00000000u, 0x00006976u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00016976u, 0x00000000u, 0x00000000u, 0x01046976u, 0x00010000u, 0x00000000u, 0x20046900u, 0x00000000u, 0xfe000000u, 0x00010000u}, + {0x00000000u, 0x00008080u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00018080u, 0x00000000u, 0x00000000u, 0x01808080u, 0x00010000u, 0x00000000u, 0x80808000u, 0x00000000u, 0xfe000000u, 0x00010000u}, + {0x00000000u, 0x00000080u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00010080u, 0x00000000u, 0x00000000u, 0x10000080u, 0x00100000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xef000000u, 0x00100000u}, + {0x00000000u, 0x00007fffu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00017fffu, 0x00000000u, 0x00000000u, 0x10007fffu, 0x00100000u, 0x00000000u, 0x00007f00u, 0x00000000u, 0xef000000u, 0x00100000u}, + {0x00000000u, 0x00000001u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00010001u, 0x00000000u, 0x00000000u, 0x10000001u, 0x00100000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xef000000u, 0x00100000u}, + {0x00000000u, 0x00007fffu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00017fffu, 0x00000000u, 0x00000000u, 0x7f007fffu, 0x007f0000u, 0x00000000u, 0x00007f00u, 0x00000000u, 0x80000000u, 0x007f0000u}, + {0x00000000u, 0x00000000u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00010000u, 0x00000000u, 0x00000000u, 0x7f000000u, 0x007f0000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x007f0000u}, + {0x00000000u, 0x0000de02u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x0001de02u, 0x00000000u, 0x00000000u, 0x7fe9de02u, 0x007f0000u, 0x00000000u, 0xbde9de00u, 0x00000000u, 0x80000000u, 0x007f0000u}, + {0x00000000u, 0x000074d9u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x000174d9u, 0x00000000u, 0x00000000u, 0x801a74d9u, 0x00800000u, 0x00000000u, 0xf91a7400u, 0x00000000u, 0x7f000000u, 0x00800000u}, + {0x00000000u, 0x000091acu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x000191acu, 0x00000000u, 0x00000000u, 0x807491acu, 0x00800000u, 0x00000000u, 0xdd749100u, 0x00000000u, 0x7f000000u, 0x00800000u}, + {0x00000000u, 0x0000fefeu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x0001fefeu, 0x00000000u, 0x00000000u, 0xfffefefeu, 0x00ff0000u, 0x00000000u, 0xfefefe00u, 0x00000000u, 0x00000000u, 0x00ff0000u}, + {0x00000000u, 0x0000015bu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x0001015bu, 0x00000000u, 0x00000000u, 0xff40015bu, 0x00ff0000u, 0x00000000u, 0xf9400100u, 0x00000000u, 0x00000000u, 0x00ff0000u}, + {0x00000000u, 0x000011deu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x000111deu, 0x00000000u, 0x00000000u, 0xffcb11deu, 0x00ff0000u, 0x00000000u, 0xfacb1100u, 0x00000000u, 0x00000000u, 0x00ff0000u}, + {0x00000000u, 0x0000fff0u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x0001fff0u, 0x00000000u, 0x00000000u, 0xff00fff0u, 0x00ff0000u, 0x00000000u, 0x0000ff00u, 0x00000000u, 0x00000000u, 0x00ff0000u}, + {0x00000000u, 0x000000ffu, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x000100ffu, 0x00000000u, 0x00000000u, 0xff0000ffu, 0x00ff0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00ff0000u}, + {0x00000000u, 0x000022eau, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x000122eau, 0x00000000u, 0x00000000u, 0xff1d22eau, 0x7fff0000u, 0x00000000u, 0x931d227fu, 0x00000000u, 0x00000000u, 0x7fff0000u}, + {0x00000000u, 0x00009730u, 0x00ff0000u, 0x00000001u, 0xffffffffu, 0x00000001u, 0x00019730u, 0x00000000u, 0x00000000u, 0xffd19730u, 0x7fff0000u, 0x00000000u, 0xbad1977fu, 0x00000000u, 0x00000000u, 0x7fff0000u} +}; +constexpr const char* Names[16] = { + "min_i32", + "max_i32", + "sub_co_ci_u32", + "sub_co_ci_u32 carry", + "subrev_co_ci_u32", + "subrev_co_ci_u32 carry", + "add_co_ci_u32", + "add_co_ci_u32 carry", + "mul_i32_i24", + "max_u32", + "lshrrev_b32", + "ashrrev_i32", + "lshlrev_b32", + "and_b32", + "xnor_b32", + "subrev_nc_u32", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("sdwa integer selectors: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("sdwa integer selectors tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/TbufferFormatD16.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/TbufferFormatD16.cpp new file mode 100644 index 000000000..8367d9e9f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/TbufferFormatD16.cpp @@ -0,0 +1,183 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xbe880300, 0xbe890301, 0xbe8a0302, 0xbe8b03ff, 0x0104bfac, 0xbe8c0300, + 0xbe8d0301, 0xbe8e0302, 0xbe8f03ff, 0x0104b80f, 0x7e1402ff, 0xdeadbeef, 0x7e1602ff, 0xdeadbeef, + 0x7e1802ff, 0xdeadbeef, 0x7e1a02ff, 0xdeadbeef, 0x7e1c02ff, 0xdeadbeef, 0x7e1e02ff, 0xdeadbeef, + 0x7e2002ff, 0xdeadbeef, 0x7e2202ff, 0xdeadbeef, 0x7e2402ff, 0xdeadbeef, 0x7e2602ff, 0xdeadbeef, + 0x7e2802ff, 0xdeadbeef, 0x7e2a02ff, 0xdeadbeef, 0x7e2c02ff, 0xdeadbeef, 0x7e2e02ff, 0xdeadbeef, + 0x7e3002ff, 0xdeadbeef, 0x7e3202ff, 0xdeadbeef, 0xea0b1000, 0x80220a01, 0xea131008, 0x80220c01, + 0xea1b1000, 0x80220e01, 0xea231008, 0x80221001, 0xe9c31000, 0x80231201, 0xe9eb1004, 0x80231401, + 0xea6b1000, 0x80221601, 0xea3b1008, 0x80231801, 0xbf8c3f70, 0xe0781000, 0x80010a03, 0xe0781010, + 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x0000ffffu, 0x80007fffu, 0x477ff000u, 0x7fc12345u}, + {0x80018000u, 0xffff0001u, 0x47800000u, 0x00000001u}, + {0x7c007bffu, 0xfc017e00u, 0x387fc000u, 0xc77fefffu}, + {0x3c00bc00u, 0x00ff7f80u, 0x33000001u, 0x7f800001u}, + {0x3f800000u, 0xbf800000u, 0x80000000u, 0xff800000u}, + {0x00000000u, 0xffffffffu, 0x7f7fffffu, 0x0001fffeu}, + {0xff812345u, 0x44288af2u, 0xd7027544u, 0xfd52c4d7u}, + {0x15c069bbu, 0xfce9537du, 0xf31476ddu, 0x16dc7861u}, + {0x199a4550u, 0xff812345u, 0x84aea175u, 0x51f08872u}, + {0x337fffffu, 0xd1213a4cu, 0x387fe000u, 0x337fffffu}, + {0x0158da62u, 0x7519a222u, 0x206d5e90u, 0x8ffa3554u}, + {0x3eaaaaabu, 0xd688103cu, 0xdaa5a003u, 0x8ca521f2u}, + {0xcf1d870du, 0x3eaaaaabu, 0x75e96b20u, 0x2b1c7d43u}, + {0x9d04abb9u, 0x477fe000u, 0x57a51839u, 0x821fd1c5u}, + {0x451afa03u, 0x3a9f5beeu, 0x61d759eeu, 0xdbd5edc9u}, + {0xecf74a37u, 0x82d0649bu, 0xee311fcdu, 0x6a5f6961u}, + {0x55353630u, 0x59016afbu, 0xd40f13f7u, 0x2df598f1u}, + {0xf86ec9a8u, 0x92559822u, 0x55b1a984u, 0xbe063793u}, + {0x509850eau, 0x38800000u, 0xbf800000u, 0x38800000u}, + {0x3b5736d1u, 0x48c9c423u, 0x8ee50022u, 0xe836ab09u}, + {0x1c06662cu, 0x14595e94u, 0xb19e16edu, 0xff0e1cb5u}, + {0x337fffffu, 0x1a490212u, 0xbf3b7a63u, 0x8ad4e305u}, + {0x63998cbau, 0x337fffffu, 0x8444c95au, 0xf34b3f43u}, + {0xff812345u, 0x477fe000u, 0xe01833bau, 0xb1764071u}, + {0x477fe000u, 0x44bf4210u, 0x07697348u, 0xf2ef3abbu}, + {0xad69e13cu, 0xd1645a10u, 0x5f2fe57fu, 0x091ebf4au}, + {0x3eaaaaabu, 0x198219beu, 0x20dbf8a0u, 0x5c43ebf6u}, + {0xdc0cdaf2u, 0x74bc740cu, 0xc9c00644u, 0xf2c8ae61u}, + {0xff812345u, 0xe8441553u, 0x38800000u, 0x7f800000u}, + {0x24d9a4e2u, 0xb286c097u, 0x38800000u, 0x5b60c5d2u}, + {0xc2c082a3u, 0x796e2dd3u, 0x4fea231au, 0xed6ec742u}, + {0x3f800000u, 0xd352bf48u, 0x5e6a7337u, 0xbf159734u} +}; + +constexpr std::uint32_t Expected[32][16] = { + {0x00003c00u, 0x38003800u, 0x3878b000u, 0x3bfc3469u, 0x00007bffu, 0x78007800u, 0x7478ec00u, 0x77fc7069u, 0x3c003c00u, 0x00000000u, 0x007fffffu, 0xff800000u, 0x80000000u, 0x7e097bffu, 0x477ff000u, 0x7fc12345u}, + {0x38003800u, 0x3c000100u, 0x38780000u, 0x00000200u, 0x78007800u, 0x7bff3c00u, 0x74780000u, 0x00003c00u, 0x38040000u, 0x38041c04u, 0x00000001u, 0xffffffffu, 0xfff88000u, 0x00007bffu, 0x47800000u, 0x00000001u}, + {0x37c037c0u, 0x3be037e0u, 0x3710b800u, 0xb710b000u, 0x77c077c0u, 0x7be077e0u, 0x7310f400u, 0xf310ec00u, 0x37b83c00u, 0x37c80000u, 0x007e0000u, 0xfffc0001u, 0xfbff7bffu, 0xfbff03ffu, 0x387fc000u, 0xc77fefffu}, + {0x338039e0u, 0x1bf837f8u, 0x36600200u, 0x3bf80200u, 0x738079e0u, 0x5bf877f8u, 0x72603c00u, 0x77f83c00u, 0x39e60000u, 0x33880000u, 0x007fff80u, 0x0000ffffu, 0x00002005u, 0x7c010000u, 0x33000001u, 0x7f800001u}, + {0x33f00000u, 0x39fc0000u, 0xbc000000u, 0x9c000000u, 0x73f00000u, 0x79fc0000u, 0xf8000000u, 0xd8000000u, 0x00000000u, 0x33e83804u, 0x00000000u, 0xffbfff80u, 0xbc003c00u, 0xfc008000u, 0x80000000u, 0xff800000u}, + {0x00000000u, 0x3c003c00u, 0x3bf88200u, 0x02008400u, 0x00000000u, 0x7bff7bffu, 0x77f8bc00u, 0x3c00c000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffff0000u, 0x00007bffu, 0x7f7fffffu, 0x0001fffeu}, + {0x3bfc3069u, 0x34433858u, 0xb5203b54u, 0xa55cb765u, 0x7bfc7069u, 0x74427858u, 0xf1207754u, 0xe15cf365u, 0x30643454u, 0x3c00380cu, 0xff8afff2u, 0x00440028u, 0x6144fc09u, 0xfbfffbffu, 0xd7027544u, 0xfd52c4d7u}, + {0x2d70369cu, 0x3be73538u, 0xae763b6eu, 0x31b73b86u, 0x6d70769cu, 0x7be77538u, 0xea76776eu, 0x6db77786u, 0x369739deu, 0x2d453a06u, 0x0053007du, 0xfffcffe9u, 0xfbff0000u, 0x0000fbffu, 0xf31476ddu, 0x16dc7861u}, + {0x2e673455u, 0x3bfc3069u, 0xbbb5b9e9u, 0x391fbb79u, 0x6e667455u, 0x7bfc7069u, 0xf7b5f5e9u, 0x751ff779u, 0x34543505u, 0x2e4638d5u, 0x00230045u, 0xffffff81u, 0xfc090000u, 0x7bff8000u, 0x84aea175u, 0x51f08872u}, + {0x32703c00u, 0x3a89334au, 0x3710b400u, 0x36708200u, 0x72707bffu, 0x7a89734au, 0x7310f000u, 0x7270bc00u, 0x3c003c00u, 0x326637f8u, 0x003a004cu, 0xffd10021u, 0xfbff0000u, 0x000003ffu, 0x387fe000u, 0x337fffffu}, + {0x1d603ad3u, 0x37523911u, 0x340e39e9u, 0xbb0036abu, 0x5d607ad3u, 0x77527911u, 0x700e75e9u, 0xf70072aau, 0x3ad73626u, 0x1c043586u, 0xffa20022u, 0x00750019u, 0x7bff0000u, 0x80000000u, 0x206d5e90u, 0x8ffa3554u}, + {0x33d53955u, 0x3ab42c0fu, 0xb4abba00u, 0xbb36343eu, 0x73d57955u, 0x7ab46c0fu, 0xf0abf600u, 0xf736703eu, 0x3955395du, 0x33c83955u, 0x0010003cu, 0xffd6ff88u, 0xfbff3555u, 0x8000fbffu, 0xdaa5a003u, 0x8ca521f2u}, + {0x3a793838u, 0x33d53955u, 0x3b5f3ab2u, 0x35643bd4u, 0x7a797838u, 0x73d57955u, 0x775f76b2u, 0x716477d4u, 0x383c2a87u, 0x3a7e2f47u, 0xffaaffabu, 0x003effaau, 0x3555fbffu, 0x00007bffu, 0x75e96b20u, 0x2b1c7d43u}, + {0x38e8395eu, 0x34783b00u, 0x397a320eu, 0xbbdeb5c7u, 0x78e8795eu, 0x74787b00u, 0x757a6e0eu, 0xf7def1c7u, 0x395d39ceu, 0x38ed2404u, 0xffe00000u, 0x0047007fu, 0x7bff8000u, 0x80007bffu, 0x57a51839u, 0x821fd1c5u}, + {0x34523bd0u, 0x335435bfu, 0x3a1d399fu, 0xb485b08eu, 0x74527bd0u, 0x735475bfu, 0x761d759fu, 0xf085ec8eu, 0x3bd82206u, 0x34542e87u, 0x005bffeeu, 0x003aff9fu, 0x14fa68d7u, 0xfbff7bffu, 0x61d759eeu, 0xdbd5edc9u}, + {0x3b6834a3u, 0x3817364au, 0xb07433f3u, 0x3aa63a96u, 0x7b6874a3u, 0x7816764au, 0xec746ff3u, 0x76a67696u, 0x34a532e7u, 0x3b673bc0u, 0x0064ff9bu, 0xff82ffd0u, 0x8000fbffu, 0x7bfffbffu, 0xee311fcdu, 0x6a5f6961u}, + {0x355332c6u, 0x359036b0u, 0xb57e30feu, 0x35bfba71u, 0x755372c6u, 0x759076b0u, 0xf17e6cfeu, 0x71bff671u, 0x32c73206u, 0x355532a7u, 0x006afffbu, 0x00590001u, 0x7bff7bffu, 0x0000fbffu, 0xd40f13f7u, 0x2df598f1u}, + {0x3bc33a4du, 0x389338c1u, 0x395bb968u, 0xb82036f2u, 0x7bc37a4du, 0x789378c1u, 0x755bf568u, 0xf42072f2u, 0x3a4e3945u, 0x3bc836e7u, 0xff980022u, 0xff920055u, 0x8000fbffu, 0xb0317bffu, 0x55b1a984u, 0xbe063793u}, + {0x350a350fu, 0x33100000u, 0xb8080000u, 0x37100000u, 0x750a750fu, 0x73100000u, 0xf4080000u, 0x73100000u, 0x35053b57u, 0x350538c5u, 0x00000000u, 0x0038ff80u, 0x04007bffu, 0x0400bc00u, 0xbf800000u, 0x38800000u}, + {0x336b32dau, 0x348d3a21u, 0xbb121440u, 0xb1f3b94fu, 0x736b72dau, 0x748d7a21u, 0xf7125040u, 0xedf2f54fu, 0x32c73a8fu, 0x33673575u, 0xffc40023u, 0x0048ffc9u, 0x7bff1ab9u, 0xfbff8000u, 0x8ee50022u, 0xe836ab09u}, + {0x2f023663u, 0x2d1635e9u, 0xb8e631bbu, 0x9f90332du, 0x6f027663u, 0x6d1675e9u, 0xf4e66dbbu, 0xdb906f2du, 0x36663186u, 0x2f072606u, 0x005eff94u, 0x00140059u, 0x00000000u, 0xfbff8000u, 0xb19e16edu, 0xff0e1cb5u}, + {0x32703c00u, 0x2e922024u, 0xb80c3ba6u, 0xbb53b33fu, 0x72707bffu, 0x6e926024u, 0xf40c77a6u, 0xf753ef3fu, 0x3c003c00u, 0x326637f8u, 0x00020012u, 0x001a0049u, 0x00000000u, 0x8000b9dbu, 0xbf3b7a63u, 0x8ad4e305u}, + {0x363a3866u, 0x32703c00u, 0xbbbcb6d5u, 0xae5b37e8u, 0x763a7866u, 0x72707bffu, 0xf7bcf2d5u, 0xea5a73e8u, 0x386439d6u, 0x363638cdu, 0xffffffffu, 0x0033007fu, 0x00007bffu, 0xfbff8000u, 0x8444c95au, 0xf34b3f43u}, + {0x3bfc3069u, 0x34783b00u, 0xb3fa3677u, 0xb8e93807u, 0x7bfc7069u, 0x74787b00u, 0xeffa7277u, 0xf4e97407u, 0x30643454u, 0x3c00380cu, 0xffe00000u, 0x0047007fu, 0x7bfffc09u, 0x8000fbffu, 0xe01833bau, 0xb1764071u}, + {0x34783b00u, 0x344c3421u, 0x2b693b35u, 0xae893757u, 0x74787b00u, 0x744c7421u, 0x67697734u, 0xea887357u, 0x3b070000u, 0x347437f8u, 0x00420010u, 0x0044ffbfu, 0x65fa7bffu, 0xfbff0000u, 0x07697348u, 0xf2ef3abbu}, + {0x396b3b0au, 0x3a8b35a1u, 0x39f3b2a0u, 0x2c8fb80bu, 0x796b7b0au, 0x7a8b75a1u, 0x75f3eea0u, 0x688ff40bu, 0x3b0f3388u, 0x396d3697u, 0x005a0010u, 0xffd10064u, 0xfbff8000u, 0x00007bffu, 0x5f2fe57fu, 0x091ebf4au}, + {0x33d53955u, 0x2e612e70u, 0x341bab60u, 0x39c4b103u, 0x73d57955u, 0x6e606e70u, 0x701be760u, 0x75c4ed02u, 0x3955395du, 0x33c83955u, 0x0019ffbeu, 0x0019ff82u, 0x00003555u, 0x7bff0000u, 0x20dbf8a0u, 0x5c43ebf6u}, + {0x3ae03ad8u, 0x374c3741u, 0xb6c82a44u, 0xae9cb91au, 0x7ae07ad8u, 0x774c7741u, 0xf2c86644u, 0xea9cf51au, 0x3ad73b98u, 0x3ae72a06u, 0x0074000cu, 0x0074ffbcu, 0x7bfffbffu, 0xfbfffbffu, 0xc9c00644u, 0xf2c8ae61u}, + {0x3bfc3069u, 0x3b422d55u, 0x37100000u, 0x3bf80000u, 0x7bfc7069u, 0x7b426d55u, 0x73100000u, 0x77f80000u, 0x30643454u, 0x3c00380cu, 0x00150053u, 0xffe80044u, 0xfbfffc09u, 0x7c000400u, 0x38800000u, 0x7f800000u}, + {0x309b3927u, 0x39943a05u, 0x37100000u, 0x39b6b746u, 0x709b7927u, 0x79947a05u, 0x73100000u, 0x75b6f346u, 0x39253b17u, 0x30853acfu, 0xffc0ff97u, 0xffb2ff86u, 0x80000000u, 0x7bff0400u, 0x38800000u, 0x5b60c5d2u}, + {0x3a163815u, 0x379731bau, 0x38ff3463u, 0xb0a5b718u, 0x7a167815u, 0x779771bau, 0x74ff7063u, 0xeca4f318u, 0x3814391du, 0x3a163a06u, 0x002dffd3u, 0x0079006eu, 0x7bffd604u, 0xfbff7bffu, 0x4fea231au, 0xed6ec742u}, + {0x33f00000u, 0x3a9b39fau, 0x39e73b33u, 0xb80fba8du, 0x73f00000u, 0x7a9b79fau, 0x75e77733u, 0xf40ff68du, 0x00000000u, 0x33e83804u, 0xffbf0048u, 0xffd30052u, 0xfbff3c00u, 0xb8ac7bffu, 0x5e6a7337u, 0xbf159734u} +}; + +constexpr const char* Names[Results] = { + "16_16_16_16_UNORM offset 0 xy", "16_16_16_16_UNORM offset 0 zw", + "16_16_16_16_SNORM offset 8 xy", "16_16_16_16_SNORM offset 8 zw", + "16_16_16_16_USCALED offset 0 xy", "16_16_16_16_USCALED offset 0 zw", + "16_16_16_16_SSCALED offset 8 xy", "16_16_16_16_SSCALED offset 8 zw", + "8_8_8_8_UNORM swizzled offset 0 xy", "8_8_8_8_UNORM swizzled offset 0 zw", + "8_8_8_8_SINT swizzled offset 4 xy", "8_8_8_8_SINT swizzled offset 4 zw", + "32_32_32_32_FLOAT offset 0 xy", "32_32_32_32_FLOAT offset 0 zw", + "16_16_16_16_FLOAT swizzled offset 8 xy", "16_16_16_16_FLOAT swizzled offset 8 zw", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("tbuffer format d16: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < Results; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("tbuffer format d16 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ViewPastLastMip.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ViewPastLastMip.cpp new file mode 100644 index 000000000..c2b535c5d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ViewPastLastMip.cpp @@ -0,0 +1,114 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 8; +constexpr std::uint32_t Side = 256; +constexpr std::uint32_t MaxMip = 2; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t TileR64KBX = 0x1b; +constexpr std::uint32_t Type2D = 9; +constexpr std::size_t SurfaceBytes = 0x60000; +constexpr std::size_t TailBlockBytes = 0x10000; +constexpr std::uint32_t StoredPast = 0x12345678u; +constexpr std::uint32_t StoredLast = 0x9abcdef0u; +constexpr std::uint32_t Untouched = 0xdeadbeefu; +constexpr std::array PastOffsets{0x4800, 0x4804, 0x4808, 0x480c, 0x4880, 0x4884, 0x4888, 0x488c}; +constexpr std::array LastOffsets{0x8400, 0x8404, 0x8408, 0x840c, 0x8480, 0x8484, 0x8488, 0x848c}; + +alignas(256) constexpr std::array StorePastCode{ + 0x7e040300, 0x7e060280, 0x7e0202ff, StoredPast, 0xf0200108, 0x00020102, 0xbf810000, +}; + +alignas(256) constexpr std::array StoreLastCode{ + 0x7e040300, 0x7e060280, 0x7e0202ff, StoredLast, 0xf0200108, 0x00020102, 0xbf810000, +}; + +std::array TextureDescriptor(const std::uint32_t* texels, std::uint32_t level) { + const auto address = static_cast(reinterpret_cast(texels)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Side - 1u) & 3u) << 30u), + ((Side - 1u) >> 2u) | ((Side - 1u) << 14u), + 0xfacu | (level << 12u) | (level << 16u) | (TileR64KBX << 20u) | (Type2D << 28u), + 0u, + MaxMip << 4u, + 0u, + 0u, + }; +} + +void Store(AgcDriver::VulkanDevice& device, const std::uint32_t* texels, std::uint32_t level, std::span code) { + std::vector userData(16, 0u); + const auto texture = TextureDescriptor(texels, level); + std::copy(texture.begin(), texture.end(), userData.begin() + 8); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + AgcDriver::Graphics::FlushCachedTextures(device.Device()); + } + device.WaitIdle(); +} + +void Check(const std::uint32_t* texels, bool last, const std::string& step) { + for (const auto offset : PastOffsets) Require(texels[offset / 4u] == StoredPast, step + ": byte offset " + std::to_string(offset) + " does not hold the store through level 3, past MAX_MIP 2, at its addrlib tail slot"); + for (const auto offset : LastOffsets) Require(texels[offset / 4u] == (last ? StoredLast : Untouched), step + ": byte offset " + std::to_string(offset) + " of level 2 holds an unexpected value"); + for (auto index = TailBlockBytes / 4u; index < SurfaceBytes / 4u; ++index) Require(texels[index] == Untouched, step + ": byte offset " + std::to_string(index * 4u) + " of levels 0 and 1 changed"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + std::vector storage((SurfaceBytes + 0x10000u) / 4u); + auto* texels = reinterpret_cast((reinterpret_cast(storage.data()) + 0xffffu) & ~std::uintptr_t{0xffffu}); + std::fill(texels, texels + SurfaceBytes / 4u, Untouched); + { + GuestAllocations::Mutation mutation; + mutation.Add(texels, SurfaceBytes, true, true); + } + Store(*device, texels, 3u, StorePastCode); + Check(texels, false, "store through level 3"); + Store(*device, texels, 2u, StoreLastCode); + Check(texels, true, "store through level 2 after level 3"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(texels); + } + std::puts("view past the last mip tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop1FloatUnary.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop1FloatUnary.cpp new file mode 100644 index 000000000..4832b5c96 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop1FloatUnary.cpp @@ -0,0 +1,220 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 64; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060088, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x2c100c90, 0x7e144504, 0x7e164505, + 0x7e184904, 0x7e1a4905, 0x7e1c4304, 0x7e1e4305, 0x7e204704, 0x7e224705, 0x7e244104, 0x7e264105, + 0x7e287f04, 0x7e2a7f05, 0x7e2c8104, 0x7e2e8105, 0x7e306704, 0x7e326705, 0x7e345d04, 0x7e365d05, + 0x7e385504, 0x7e3a5505, 0x7e3c5704, 0x7e3e5705, 0x7e404f04, 0x7e424f05, 0x7e444b04, 0x7e464b05, + 0x7e486b04, 0x7e4a6b05, 0x7e4c6d04, 0x7e4e6d05, 0x7e500307, 0x7e50b906, 0x7e520307, 0x7e52b908, + 0x7e540307, 0x7e54b706, 0x7e560307, 0x7e56b708, 0x7e580307, 0x7e58bb06, 0x7e5a0307, 0x7e5abb08, + 0x7e5c0307, 0x7e5cbd06, 0x7e5e0307, 0x7e5ebd08, 0x7e600307, 0x7e60bf06, 0x7e620307, 0x7e62bf08, + 0x7e640307, 0x7e64ab06, 0x7e660307, 0x7e66ab08, 0x7e680307, 0x7e68ad06, 0x7e6a0307, 0x7e6aad08, + 0x7e6c0307, 0x7e6ca906, 0x7e6e0307, 0x7e6ea908, 0x7e700307, 0x7e70af06, 0x7e720307, 0x7e72af08, + 0x7e740307, 0x7e74b106, 0x7e760307, 0x7e76b108, 0x7e780307, 0x7e78c106, 0x7e7a0307, 0x7e7ac108, + 0x7e7c0307, 0x7e7cc306, 0x7e7e0307, 0x7e7ec308, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, + 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xe0781040, 0x80011a03, 0xe0781050, 0x80011e03, + 0xe0781060, 0x80012203, 0xe0781070, 0x80012603, 0xe0781080, 0x80012a03, 0xe0781090, 0x80012e03, + 0xe07810a0, 0x80013203, 0xe07810b0, 0x80013603, 0xe07810c0, 0x80013a03, 0xe07810d0, 0x80013e03, + 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x807fffffu, 0x007fffffu, 0x3c003800u, 0x13579bdfu}, + {0x7f800001u, 0xffc00000u, 0x7c01fe00u, 0x12345678u}, + {0xff800000u, 0x80000000u, 0xfc008000u, 0x9abcdef0u}, + {0xb0800000u, 0x3f800000u, 0x84003400u, 0x5555aaaau}, + {0x7f800000u, 0x3e2b6c1au, 0xbe007c00u, 0x2de0a56au}, + {0xbfc00000u, 0x43000000u, 0x00006401u, 0x7274aa88u}, + {0xc2fc0000u, 0x7fc00000u, 0xc0c1fc01u, 0x94d3d2ffu}, + {0x7fa00000u, 0xff800001u, 0xebdb4800u, 0xfb20bf6cu}, + {0xbf800000u, 0x3ab77039u, 0x7e0063ffu, 0xeb609511u}, + {0x00000000u, 0x3f400000u, 0x9ba1236cu, 0x8a4ee80du}, + {0x3dcccccdu, 0x3f400000u, 0x0000ba00u, 0xe8a8529fu}, + {0x7fa00000u, 0x40b5e831u, 0x54ed5567u, 0xe779c470u}, + {0xbfa4797cu, 0x43000000u, 0x3800c85au, 0x691406beu}, + {0x4036ac94u, 0xba85a805u, 0x53869182u, 0xec007b1bu}, + {0xbeffcf24u, 0x00000000u, 0xa7e21400u, 0x686e80a9u}, + {0x7fa00000u, 0x3f800000u, 0xbd813570u, 0x75dd67deu}, + {0xbb27aea0u, 0xbf9e8d90u, 0x1a67bbffu, 0x526c5cc5u}, + {0xbe381e7fu, 0xbc0a3bbfu, 0x2ce6fe00u, 0xc9794969u}, + {0xc2fc0000u, 0x3cee8ea0u, 0x5484b800u, 0x6bd881fdu}, + {0x421c4347u, 0x3dcccccdu, 0xf08320f5u, 0xd2d0d097u}, + {0x40200000u, 0xbf800000u, 0x4e023c00u, 0x7d7015fcu}, + {0x384fbc0eu, 0x42d05018u, 0x3bff8400u, 0x75f99ac4u}, + {0xffc00000u, 0xb966cd34u, 0x8000bbffu, 0xefbd6b85u}, + {0xb3800000u, 0x3abb690bu, 0xf181eaa6u, 0xaae550d5u}, + {0xc2473fefu, 0xbf000000u, 0x6bdefc01u, 0x291e6ca0u}, + {0x3e720beau, 0x3f000000u, 0xac8cacf7u, 0x2feb67afu}, + {0xb8732b55u, 0x38281e0au, 0x185eb800u, 0x43a10696u}, + {0xbb96ece4u, 0xbe800000u, 0x64004248u, 0xa51d4257u}, + {0xbe800000u, 0xc2fc0000u, 0xc4725640u, 0x90e5ac8du}, + {0xc0200000u, 0xbf000000u, 0x95fe0d70u, 0x89f40abfu}, + {0x3df74341u, 0x7fc00000u, 0x3e007c01u, 0xc9717e02u}, + {0x3f000000u, 0xbc321c7bu, 0x26983800u, 0x7d3055d3u} +}; +constexpr std::uint32_t Expected[32][54] = { + {0x80000000u, 0x3f800000u, 0xbf800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3f7fffffu, 0x007fffffu, 0xffffff82u, 0xffffff82u, 0xbf7ffffeu, 0x3f7ffffeu, 0x80000000u, 0x00000000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0xff800000u, 0x3f800000u, 0x3f800000u, 0x81c90fd3u, 0x01c90fd3u, 0x3f800000u, 0x3f800000u, 0x13573c00u, 0x13573c00u, 0x13570000u, 0x13573c00u, 0x13570000u, 0x13573c00u, 0x13570000u, 0x13573c00u, 0x13573800u, 0x13570000u, 0x135739a8u, 0x13573c00u, 0x13573da8u, 0x13573c00u, 0x13574000u, 0x13573c00u, 0x1357bc00u, 0x13570000u, 0x13573da8u, 0x13574000u, 0x13570000u, 0x13570000u, 0x1357bc00u, 0x13573c00u}, + {0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x7fc00001u, 0xffc00000u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u, 0x1234fe00u, 0x12347e01u}, + {0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800000u, 0x80000000u, 0xffc00000u, 0x80000000u, 0xffc00000u, 0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xff800000u, 0xffc00000u, 0xff800000u, 0x00000000u, 0x3f800000u, 0xffc00000u, 0x80000000u, 0xffc00000u, 0x3f800000u, 0x9abc8000u, 0x9abcfc00u, 0x9abc8000u, 0x9abcfc00u, 0x9abc8000u, 0x9abcfc00u, 0x9abc8000u, 0x9abcfc00u, 0x9abc0000u, 0x9abcfe00u, 0x9abc8000u, 0x9abcfe00u, 0x9abcfc00u, 0x9abcfe00u, 0x9abcfc00u, 0x9abc8000u, 0x9abcfc00u, 0x9abcfe00u, 0x9abc3c00u, 0x9abc0000u, 0x9abc8000u, 0x9abcfe00u, 0x9abc3c00u, 0x9abcfe00u}, + {0x80000000u, 0x3f800000u, 0xbf800000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x3f7fffffu, 0x00000000u, 0xffffffe3u, 0x00000001u, 0xbf000000u, 0x3f000000u, 0xffc00000u, 0x3f800000u, 0xffc00000u, 0x3f800000u, 0xce800000u, 0x3f800000u, 0xce800000u, 0x3f800000u, 0xffc00000u, 0x00000000u, 0x3f800000u, 0x40000000u, 0xb1c90fd5u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x55553c00u, 0x55558000u, 0x55550000u, 0x5555bc00u, 0x55550000u, 0x55558000u, 0x55550000u, 0x55558000u, 0x55553400u, 0x55553bffu, 0x55553800u, 0x5555fe00u, 0x55554000u, 0x5555fe00u, 0x55554400u, 0x5555f400u, 0x5555c000u, 0x5555fe00u, 0x55553cc2u, 0x55553c00u, 0x55553c00u, 0x55558e48u, 0x55550000u, 0x55553c00u}, + {0x7f800000u, 0x3f800000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0xffc00000u, 0x3e2b6c1au, 0x00000000u, 0xfffffffeu, 0x7f800000u, 0x3f2b6c1au, 0x7f800000u, 0x3ed17c3eu, 0x00000000u, 0x401c6be5u, 0x00000000u, 0x40bf2759u, 0x00000000u, 0x40bf2759u, 0x7f800000u, 0xc025079cu, 0x7f800000u, 0x3f8fbfa7u, 0xffc00000u, 0x3f5e4b27u, 0xffc00000u, 0x3efdf103u, 0x2de07c00u, 0x2de0bc00u, 0x2de07c00u, 0x2de0c000u, 0x2de07c00u, 0x2de0bc00u, 0x2de07c00u, 0x2de0c000u, 0x2de0fe00u, 0x2de03800u, 0x2de07c00u, 0x2de0fe00u, 0x2de00000u, 0x2de0fe00u, 0x2de00000u, 0x2de0b955u, 0x2de07c00u, 0x2de0fe00u, 0x2de07c00u, 0x2de035a8u, 0x2de0fe00u, 0x2de00000u, 0x2de0fe00u, 0x2de0bc00u}, + {0xbf800000u, 0x43000000u, 0xc0000000u, 0x43000000u, 0xbf800000u, 0x43000000u, 0xc0000000u, 0x43000000u, 0x3f000000u, 0x00000000u, 0x00000001u, 0x00000008u, 0xbf400000u, 0x3f000000u, 0xffc00000u, 0x413504f3u, 0xffc00000u, 0x3db504f3u, 0xbf2aaaaau, 0x3c000000u, 0xbf2aaaaau, 0x3c000000u, 0xffc00000u, 0x40e00000u, 0x3eb504f3u, 0x7f800000u, 0x00000000u, 0x00000000u, 0xbf800000u, 0x3f800000u, 0x72746401u, 0x72740000u, 0x72746401u, 0x72740000u, 0x72746401u, 0x72740000u, 0x72746401u, 0x72740000u, 0x72740000u, 0x72740000u, 0x72745000u, 0x72740000u, 0x727427ffu, 0x72747c00u, 0x727413feu, 0x72747c00u, 0x72744900u, 0x7274fc00u, 0x72747c00u, 0x72743c00u, 0x72740000u, 0x72740000u, 0x72743c00u, 0x72743c00u}, + {0xc2fc0000u, 0x7fc00000u, 0xc2fc0000u, 0x7fc00000u, 0xc2fc0000u, 0x7fc00000u, 0xc2fc0000u, 0x7fc00000u, 0x00000000u, 0x7fc00000u, 0x00000007u, 0x00000000u, 0xbf7c0000u, 0x7fc00000u, 0xffc00000u, 0x7fc00000u, 0xffc00000u, 0x7fc00000u, 0xbc020820u, 0x7fc00000u, 0xbc020820u, 0x7fc00000u, 0xffc00000u, 0x7fc00000u, 0x00800000u, 0x7fc00000u, 0x00000000u, 0x7fc00000u, 0x3f800000u, 0x7fc00000u, 0x94d3fe01u, 0x94d3c000u, 0x94d3fe01u, 0x94d3c200u, 0x94d3fe01u, 0x94d3c000u, 0x94d3fe01u, 0x94d3c000u, 0x94d3fe01u, 0x94d338fcu, 0x94d3fe01u, 0x94d3fe00u, 0x94d3fe01u, 0x94d3fe00u, 0x94d3fe01u, 0x94d3b6bbu, 0x94d3fe01u, 0x94d3fe00u, 0x94d3fe01u, 0x94d33229u, 0x94d3fe01u, 0x94d3b996u, 0x94d3fe01u, 0x94d3b9bau}, + {0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x00000000u, 0x00000000u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0x7fe00000u, 0xffc00001u, 0xfb204800u, 0xfb20ebdbu, 0xfb204800u, 0xfb20ebdbu, 0xfb204800u, 0xfb20ebdbu, 0xfb204800u, 0xfb20ebdbu, 0xfb200000u, 0xfb200000u, 0xfb2041a8u, 0xfb20fe00u, 0xfb2035a8u, 0xfb20fe00u, 0xfb203000u, 0xfb208c13u, 0xfb204200u, 0xfb20fe00u, 0xfb205c00u, 0xfb200000u, 0xfb200000u, 0xfb200000u, 0xfb203c00u, 0xfb203c00u}, + {0xbf800000u, 0x3f800000u, 0xbf800000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x00000000u, 0x3ab77039u, 0x00000001u, 0xfffffff7u, 0xbf000000u, 0x3f377039u, 0xffc00000u, 0x3d193b72u, 0xffc00000u, 0x41d5d86bu, 0xbf800000u, 0x4432a1d8u, 0xbf800000u, 0x4432a1d8u, 0xffc00000u, 0xc117b18fu, 0x3f000000u, 0x3f801fceu, 0x00000000u, 0x3c101202u, 0x3f800000u, 0x3f7ffd77u, 0xeb606400u, 0xeb607e00u, 0xeb6063feu, 0xeb607e00u, 0xeb6063feu, 0xeb607e00u, 0xeb606400u, 0xeb607e00u, 0xeb603800u, 0xeb607e00u, 0xeb604fffu, 0xeb607e00u, 0xeb602800u, 0xeb607e00u, 0xeb601401u, 0xeb607e00u, 0xeb604900u, 0xeb607e00u, 0xeb607c00u, 0xeb607e00u, 0xeb600000u, 0xeb607e00u, 0xeb60bc00u, 0xeb607e00u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x3f400000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f400000u, 0x00000000u, 0x3f5db3d7u, 0x7f800000u, 0x3f93cd3au, 0x7f800000u, 0x3faaaaaau, 0x7f800000u, 0x3faaaaaau, 0xff800000u, 0xbed47fccu, 0x3f800000u, 0x3fd744fdu, 0x00000000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x8a4e3c00u, 0x8a4e8000u, 0x8a4e0000u, 0x8a4ebc00u, 0x8a4e0000u, 0x8a4e8000u, 0x8a4e0000u, 0x8a4e8000u, 0x8a4e236cu, 0x8a4e3bf8u, 0x8a4e2fb5u, 0x8a4efe00u, 0x8a4e4827u, 0x8a4efe00u, 0x8a4e5450u, 0x8a4edc32u, 0x8a4ec61cu, 0x8a4efe00u, 0x8a4e3c0au, 0x8a4e3bfbu, 0x8a4e2dd2u, 0x8a4ea5feu, 0x8a4e3bf8u, 0x8a4e3bffu}, + {0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3dcccccdu, 0x3f400000u, 0xfffffffdu, 0x00000000u, 0x3f4ccccdu, 0x3f400000u, 0x3ea1e89bu, 0x3f5db3d7u, 0x404a62c2u, 0x3f93cd3au, 0x41200000u, 0x3faaaaaau, 0x41200000u, 0x3faaaaaau, 0xc0549a79u, 0xbed47fccu, 0x3f892fdfu, 0x3fd744fdu, 0x3f167919u, 0xbf800000u, 0x3f4f1bbdu, 0x00000000u, 0xe8a88000u, 0xe8a80000u, 0xe8a8bc00u, 0xe8a80000u, 0xe8a88000u, 0xe8a80000u, 0xe8a8bc00u, 0xe8a80000u, 0xe8a83400u, 0xe8a80000u, 0xe8a8fe00u, 0xe8a80000u, 0xe8a8fe00u, 0xe8a87c00u, 0xe8a8bd55u, 0xe8a87c00u, 0xe8a8fe00u, 0xe8a8fc00u, 0xe8a838c2u, 0xe8a83c00u, 0xe8a83c00u, 0xe8a80000u, 0xe8a80000u, 0xe8a83c00u}, + {0x7fe00000u, 0x40c00000u, 0x7fe00000u, 0x40a00000u, 0x7fe00000u, 0x40a00000u, 0x7fe00000u, 0x40c00000u, 0x7fe00000u, 0x3f2f4188u, 0x00000000u, 0x00000003u, 0x7fe00000u, 0x3f35e831u, 0x7fe00000u, 0x4018975du, 0x7fe00000u, 0x3ed6be5du, 0x7fe00000u, 0x3e3422d1u, 0x7fe00000u, 0x3e3422d1u, 0x7fe00000u, 0x402073a0u, 0x7fe00000u, 0x424dba58u, 0x7fe00000u, 0xbf6aaf52u, 0x7fe00000u, 0xbecc89e7u, 0xe7795570u, 0xe77954f0u, 0xe7795560u, 0xe77954e0u, 0xe7795560u, 0xe77954e0u, 0xe7795560u, 0xe77954f0u, 0xe7793700u, 0xe7793a80u, 0xe77948a6u, 0xe7794870u, 0xe7792ee2u, 0xe7792f36u, 0xe77921ecu, 0xe779227fu, 0xe779466fu, 0xe779464du, 0xe7797c00u, 0xe7797c00u, 0xe779361fu, 0xe779bb64u, 0xe779bb64u, 0xe779361fu}, + {0xbf800000u, 0x43000000u, 0xc0000000u, 0x43000000u, 0xbf800000u, 0x43000000u, 0xbf800000u, 0x43000000u, 0x3f370d08u, 0x00000000u, 0x00000001u, 0x00000008u, 0xbf24797cu, 0x3f000000u, 0xffc00000u, 0x413504f3u, 0xffc00000u, 0x3db504f3u, 0xbf473a78u, 0x3c000000u, 0xbf473a78u, 0x3c000000u, 0xffc00000u, 0x40e00000u, 0x3ed21db6u, 0x7f800000u, 0xbf79d980u, 0x00000000u, 0xbe5f1c7bu, 0x3f800000u, 0x6914c800u, 0x69143c00u, 0x6914c880u, 0x69140000u, 0x6914c800u, 0x69140000u, 0x6914c880u, 0x69140000u, 0x691434c0u, 0x69143800u, 0x6914fe00u, 0x691439a8u, 0x6914fe00u, 0x69143da8u, 0x6914af5bu, 0x69144000u, 0x6914fe00u, 0x6914bc00u, 0x691418eau, 0x69143da8u, 0x69143ba8u, 0x69140000u, 0x6914b4a5u, 0x6914bc00u}, + {0x40400000u, 0x80000000u, 0x40000000u, 0xbf800000u, 0x40000000u, 0x80000000u, 0x40400000u, 0x80000000u, 0x3f5ab250u, 0x3f7fbd2cu, 0x00000002u, 0xfffffff7u, 0x3f36ac94u, 0xbf05a805u, 0x3fd8404eu, 0xffc00000u, 0x3f17870au, 0xffc00000u, 0x3eb36129u, 0xc4752a85u, 0x3eb36129u, 0xc4752a85u, 0x3fc1ae32u, 0xffc00000u, 0x40e767f9u, 0x3f7fd1b2u, 0xbf4afbf1u, 0xbbd1f20bu, 0x3f1bfde3u, 0x3f7ffea7u, 0xec008000u, 0xec0053a0u, 0xec00bc00u, 0xec005380u, 0xec008000u, 0xec005380u, 0xec008000u, 0xec005380u, 0xec003bffu, 0xec003200u, 0xec00fe00u, 0xec0047c2u, 0xec00fe00u, 0xec003020u, 0xec00e5cfu, 0xec002441u, 0xec00fe00u, 0xec0045e9u, 0xec003bffu, 0xec007c00u, 0xec009c53u, 0xec003b64u, 0xec003c00u, 0xec00361fu}, + {0x80000000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3f00186eu, 0x00000000u, 0xffffffffu, 0x00000000u, 0xbf7fcf24u, 0x00000000u, 0xffc00000u, 0x00000000u, 0xffc00000u, 0x7f800000u, 0xc0001873u, 0x7f800000u, 0xc0001873u, 0x7f800000u, 0xffc00000u, 0xff800000u, 0x3f3510edu, 0x3f800000u, 0xbb197f05u, 0x00000000u, 0xbf7fffd1u, 0x3f800000u, 0x686e3c00u, 0x686e8000u, 0x686e0000u, 0x686ebc00u, 0x686e0000u, 0x686e8000u, 0x686e0000u, 0x686e8000u, 0x686e1400u, 0x686e3bc1u, 0x686e2800u, 0x686efe00u, 0x686e5000u, 0x686efe00u, 0x686e6400u, 0x686ed00fu, 0x686ec900u, 0x686efe00u, 0x686e3c01u, 0x686e3bd5u, 0x686e1e48u, 0x686eb227u, 0x686e3c00u, 0x686e3bdau}, + {0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x7fe00000u, 0x3f000000u, 0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x3f800000u, 0x7fe00000u, 0x00000000u, 0x7fe00000u, 0x40000000u, 0x7fe00000u, 0x00000000u, 0x7fe00000u, 0x3f800000u, 0x75dd3c00u, 0x75ddbc00u, 0x75dd0000u, 0x75ddc000u, 0x75dd0000u, 0x75ddbc00u, 0x75dd0000u, 0x75ddbc00u, 0x75dd3570u, 0x75dd38feu, 0x75dd38aau, 0x75ddfe00u, 0x75dd3eddu, 0x75ddfe00u, 0x75dd41e3u, 0x75ddb9d0u, 0x75ddbe3au, 0x75ddfe00u, 0x75dd3d10u, 0x75dd362au, 0x75dd3ac2u, 0x75ddb99fu, 0x75ddb848u, 0x75ddb9b1u}, + {0x80000000u, 0xbf800000u, 0xbf800000u, 0xc0000000u, 0x80000000u, 0xbf800000u, 0x80000000u, 0xbf800000u, 0x3f7f5851u, 0x3f42e4e0u, 0xfffffff8u, 0x00000001u, 0xbf27aea0u, 0xbf1e8d90u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xc3c36ad8u, 0xbf4eab4au, 0xc3c36ad8u, 0xbf4eab4au, 0xffc00000u, 0xffc00000u, 0x3f7f8be0u, 0x3ed8f684u, 0xbc83b10eu, 0xbf7f5abeu, 0x3f7ff787u, 0x3d91591fu, 0x526c8000u, 0x526c3c00u, 0x526cbc00u, 0x526c0000u, 0x526c8000u, 0x526c0000u, 0x526cbc00u, 0x526c0000u, 0x526c1000u, 0x526c1a67u, 0x526cfe00u, 0x526c2b28u, 0x526cfe00u, 0x526c4c79u, 0x526cbc01u, 0x526c5d00u, 0x526cfe00u, 0x526cc829u, 0x526c3800u, 0x526c3c02u, 0x526c1a48u, 0x526c2507u, 0x526c3c00u, 0x526c3c00u}, + {0x80000000u, 0x80000000u, 0xbf800000u, 0xbf800000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x3f51f860u, 0x3f7dd711u, 0xfffffffeu, 0xfffffffau, 0xbf381e7fu, 0xbf0a3bbfu, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xc0b1f8c4u, 0xc2ed0c63u, 0xc0b1f8c4u, 0xc2ed0c63u, 0xffc00000u, 0xffc00000u, 0x3f6200b1u, 0x3f7e81dbu, 0xbf67804fu, 0xbd5908e3u, 0x3eda91e0u, 0x3f7fa3efu, 0xc979fe00u, 0xc9793c00u, 0xc979fe00u, 0xc9790000u, 0xc979fe00u, 0xc9790000u, 0xc979fe00u, 0xc9790000u, 0xc979fe00u, 0xc9792ce6u, 0xc979fe00u, 0xc979346du, 0xc979fe00u, 0xc979433bu, 0xc979fe00u, 0xc9794a88u, 0xc979fe00u, 0xc979c36au, 0xc979fe00u, 0xc9793c38u, 0xc979fe00u, 0xc9793767u, 0xc979fe00u, 0xc9793b18u}, + {0xc2fc0000u, 0x3f800000u, 0xc2fc0000u, 0x00000000u, 0xc2fc0000u, 0x00000000u, 0xc2fc0000u, 0x00000000u, 0x00000000u, 0x3cee8ea0u, 0x00000007u, 0xfffffffbu, 0xbf7c0000u, 0x3f6e8ea0u, 0xffc00000u, 0x3e2ebe5au, 0xffc00000u, 0x40bb853fu, 0xbc020820u, 0x42095bf0u, 0xbc020820u, 0x42095bf0u, 0xffc00000u, 0xc0a34205u, 0x00800000u, 0x3f829c24u, 0x00000000u, 0x3e3a5195u, 0x3f800000u, 0x3f7bba09u, 0x6bd88000u, 0x6bd85490u, 0x6bd8bc00u, 0x6bd85480u, 0x6bd88000u, 0x6bd85480u, 0x6bd88000u, 0x6bd85480u, 0x6bd83800u, 0x6bd83400u, 0x6bd8fe00u, 0x6bd84840u, 0x6bd8fe00u, 0x6bd82f88u, 0x6bd8c000u, 0x6bd82316u, 0x6bd8fe00u, 0x6bd8462du, 0x6bd839a8u, 0x6bd87c00u, 0x6bd80000u, 0x6bd83c00u, 0x6bd8bc00u, 0x6bd80000u}, + {0x42200000u, 0x3f800000u, 0x421c0000u, 0x00000000u, 0x421c0000u, 0x00000000u, 0x421c0000u, 0x00000000u, 0x3d868e00u, 0x3dcccccdu, 0x00000006u, 0xfffffffdu, 0x3f1c4347u, 0x3f4ccccdu, 0x40c80219u, 0x3ea1e89bu, 0x3e23d552u, 0x404a62c2u, 0x3cd1b2b1u, 0x41200000u, 0x3cd1b2b1u, 0x41200000u, 0x40a935e8u, 0xc0549a79u, 0x5305f6c3u, 0x3f892fdfu, 0x3ecd67efu, 0x3f167919u, 0x3f6a7ed2u, 0x3f4f1bbdu, 0xd2d03c00u, 0xd2d0f083u, 0xd2d00000u, 0xd2d0f083u, 0xd2d00000u, 0xd2d0f083u, 0xd2d00000u, 0xd2d0f083u, 0xd2d020f5u, 0xd2d00000u, 0xd2d02e4cu, 0xd2d0fe00u, 0xd2d04915u, 0xd2d0fe00u, 0xd2d05675u, 0xd2d08718u, 0xd2d0c6b1u, 0xd2d0fe00u, 0xd2d03c07u, 0xd2d00000u, 0xd2d02bc8u, 0xd2d00000u, 0xd2d03bfcu, 0xd2d03c00u}, + {0x40400000u, 0xbf800000u, 0x40000000u, 0xbf800000u, 0x40000000u, 0xbf800000u, 0x40000000u, 0xbf800000u, 0x3f000000u, 0x00000000u, 0x00000002u, 0x00000001u, 0x3f200000u, 0xbf000000u, 0x3fca62c2u, 0xffc00000u, 0x3f21e89bu, 0xffc00000u, 0x3ecccccdu, 0xbf800000u, 0x3ecccccdu, 0xbf800000u, 0x3fa934f0u, 0xffc00000u, 0x40b504f3u, 0x3f000000u, 0x00000000u, 0x00000000u, 0xbf800000u, 0x3f800000u, 0x7d703c00u, 0x7d704e40u, 0x7d703c00u, 0x7d704e00u, 0x7d703c00u, 0x7d704e00u, 0x7d703c00u, 0x7d704e00u, 0x7d700000u, 0x7d702800u, 0x7d703c00u, 0x7d7044e7u, 0x7d703c00u, 0x7d703287u, 0x7d703c00u, 0x7d702954u, 0x7d700000u, 0x7d704496u, 0x7d704000u, 0x7d707c00u, 0x7d700000u, 0x7d70323eu, 0x7d703c00u, 0x7d703bd9u}, + {0x3f800000u, 0x42d20000u, 0x00000000u, 0x42d00000u, 0x00000000u, 0x42d00000u, 0x00000000u, 0x42d00000u, 0x384fbc0eu, 0x3e203000u, 0xfffffff2u, 0x00000007u, 0x3f4fbc0eu, 0x3f505018u, 0x3be69ba6u, 0x41234a92u, 0x430e180cu, 0x3dc8ac0cu, 0x469dbd5fu, 0x3c1d4d46u, 0x469dbd5fu, 0x3c1d4d46u, 0xc164d28cu, 0x40d67bc4u, 0x3f800120u, 0x738ea8dcu, 0x39a32780u, 0x3f55050cu, 0x3f7fffffu, 0x3f0dfb22u, 0x75f98000u, 0x75f93c00u, 0x75f9bc00u, 0x75f90000u, 0x75f98000u, 0x75f90000u, 0x75f98000u, 0x75f93c00u, 0x75f93bffu, 0x75f93bffu, 0x75f9fe00u, 0x75f93bffu, 0x75f9fe00u, 0x75f93c00u, 0x75f9f400u, 0x75f93c01u, 0x75f9fe00u, 0x75f991c6u, 0x75f93c00u, 0x75f93fffu, 0x75f98e48u, 0x75f99a48u, 0x75f93c00u, 0x75f93c00u}, + {0xffc00000u, 0x80000000u, 0xffc00000u, 0xbf800000u, 0xffc00000u, 0x80000000u, 0xffc00000u, 0x80000000u, 0xffc00000u, 0x3f7ff193u, 0x00000000u, 0xfffffff4u, 0xffc00000u, 0xbf66cd34u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xc58df98au, 0xffc00000u, 0xc58df98au, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0x3f7ff600u, 0xffc00000u, 0xbab54564u, 0xffc00000u, 0x3f7fffefu, 0xefbd8000u, 0xefbd8000u, 0xefbdbc00u, 0xefbd8000u, 0xefbd8000u, 0xefbd8000u, 0xefbdbc00u, 0xefbd8000u, 0xefbd1000u, 0xefbd0000u, 0xefbdfe00u, 0xefbd8000u, 0xefbdfe00u, 0xefbdfc00u, 0xefbdbc01u, 0xefbdfc00u, 0xefbdfe00u, 0xefbdfc00u, 0xefbd3800u, 0xefbd3c00u, 0xefbd1a48u, 0xefbd8000u, 0xefbd3c00u, 0xefbd3c00u}, + {0x80000000u, 0x3f800000u, 0xbf800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3f7fffffu, 0x3abb690bu, 0xffffffe9u, 0xfffffff7u, 0xbf000000u, 0x3f3b690bu, 0xffc00000u, 0x3d1ae1ddu, 0xffc00000u, 0x41d3912fu, 0xcb800000u, 0x442ed8a7u, 0xcb800000u, 0x442ed8a7u, 0xffc00000u, 0xc11732f8u, 0x3f7fffffu, 0x3f80207eu, 0xb4c90fd5u, 0x3c133094u, 0x3f800000u, 0x3f7ffd5au, 0xaae5eaa6u, 0xaae5f181u, 0xaae5eaa6u, 0xaae5f181u, 0xaae5eaa6u, 0xaae5f181u, 0xaae5eaa6u, 0xaae5f181u, 0xaae50000u, 0xaae50000u, 0xaae5fe00u, 0xaae5fe00u, 0xaae5fe00u, 0xaae5fe00u, 0xaae58cd0u, 0xaae585d0u, 0xaae5fe00u, 0xaae5fe00u, 0xaae50000u, 0xaae50000u, 0xaae50000u, 0xaae50000u, 0xaae53c00u, 0xaae53c00u}, + {0xc2440000u, 0x80000000u, 0xc2480000u, 0xbf800000u, 0xc2440000u, 0x80000000u, 0xc2480000u, 0x80000000u, 0x3e401100u, 0x3f000000u, 0x00000006u, 0x00000000u, 0xbf473fefu, 0xbf000000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xbca474f9u, 0xc0000000u, 0xbca474f9u, 0xc0000000u, 0xffc00000u, 0xffc00000u, 0x2691c581u, 0x3f3504f3u, 0x3f6c8d95u, 0x00000000u, 0x3ec3bdbcu, 0xbf800000u, 0x291efe01u, 0x291e6bdeu, 0x291efe01u, 0x291e6bdeu, 0x291efe01u, 0x291e6bdeu, 0x291efe01u, 0x291e6bdeu, 0x291efe01u, 0x291e0000u, 0x291efe01u, 0x291e53efu, 0x291efe01u, 0x291e2409u, 0x291efe01u, 0x291e0c11u, 0x291efe01u, 0x291e49fdu, 0x291efe01u, 0x291e7c00u, 0x291efe01u, 0x291e0000u, 0x291efe01u, 0x291e3c00u}, + {0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3e720beau, 0x3f000000u, 0xfffffffeu, 0x00000000u, 0x3f720beau, 0x3f000000u, 0x3ef8ecefu, 0x3f3504f3u, 0x4003a345u, 0x3fb504f3u, 0x40876101u, 0x40000000u, 0x40876101u, 0x40000000u, 0xc0052ccdu, 0xbf800000u, 0x3f96c99du, 0x3fb504f3u, 0x3f7f0ff2u, 0x00000000u, 0x3daf2153u, 0xbf800000u, 0x2feb8000u, 0x2feb8000u, 0x2febbc00u, 0x2febbc00u, 0x2feb8000u, 0x2feb8000u, 0x2feb8000u, 0x2feb8000u, 0x2feb3b61u, 0x2feb3b6eu, 0x2febfe00u, 0x2febfe00u, 0x2febfe00u, 0x2febfe00u, 0x2febca72u, 0x2febcb0au, 0x2febfe00u, 0x2febfe00u, 0x2feb3b95u, 0x2feb3b9eu, 0x2febb77eu, 0x2febb6e8u, 0x2feb3b11u, 0x2feb3b37u}, + {0x80000000u, 0x3f800000u, 0xbf800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3f7ffc33u, 0x38281e0au, 0xfffffff2u, 0xfffffff2u, 0xbf732b55u, 0x3f281e0au, 0xffc00000u, 0x3bcf74c4u, 0xffc00000u, 0x431df394u, 0xc686c0feu, 0x46c2e957u, 0xc686c0feu, 0x46c2e957u, 0xffc00000u, 0xc169b4f2u, 0x3f7ffd5eu, 0x3f8000e9u, 0xb9befc10u, 0x398409fbu, 0x3f7ffffeu, 0x3f7fffffu, 0x43a18000u, 0x43a13c00u, 0x43a1bc00u, 0x43a10000u, 0x43a18000u, 0x43a10000u, 0x43a18000u, 0x43a10000u, 0x43a13800u, 0x43a1185eu, 0x43a1fe00u, 0x43a129e9u, 0x43a1fe00u, 0x43a14d6au, 0x43a1c000u, 0x43a15f54u, 0x43a1fe00u, 0x43a1c870u, 0x43a139a8u, 0x43a13c02u, 0x43a10000u, 0x43a122dcu, 0x43a1bc00u, 0x43a13c00u}, + {0x80000000u, 0x80000000u, 0xbf800000u, 0xbf800000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x3f7ed226u, 0x3f400000u, 0xfffffff9u, 0xffffffffu, 0xbf16ece4u, 0xbf000000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xc3591d2cu, 0xc0800000u, 0xc3591d2cu, 0xc0800000u, 0xffc00000u, 0xffc00000u, 0x3f7f2f1bu, 0x3f5744fdu, 0xbced0a3eu, 0xbf800000u, 0x3f7fe48eu, 0x00000000u, 0xa51d4400u, 0xa51d6400u, 0xa51d4200u, 0xa51d6400u, 0xa51d4200u, 0xa51d6400u, 0xa51d4200u, 0xa51d6400u, 0xa51d3080u, 0xa51d0000u, 0xa51d3f17u, 0xa51d5000u, 0xa51d3884u, 0xa51d2800u, 0xa51d3518u, 0xa51d1400u, 0xa51d3e9bu, 0xa51d4900u, 0xa51d4869u, 0xa51d7c00u, 0xa51d3a2fu, 0xa51d0000u, 0xa51d3913u, 0xa51d3c00u}, + {0x80000000u, 0xc2fc0000u, 0xbf800000u, 0xc2fc0000u, 0x80000000u, 0xc2fc0000u, 0x80000000u, 0xc2fc0000u, 0x3f400000u, 0x00000000u, 0xffffffffu, 0x00000007u, 0xbf000000u, 0xbf7c0000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xc0800000u, 0xbc020820u, 0xc0800000u, 0xbc020820u, 0xffc00000u, 0xffc00000u, 0x3f5744fdu, 0x00800000u, 0xbf800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x90e55640u, 0x90e5c400u, 0x90e55640u, 0x90e5c500u, 0x90e55640u, 0x90e5c400u, 0x90e55640u, 0x90e5c400u, 0x90e50000u, 0x90e53870u, 0x90e54900u, 0x90e5fe00u, 0x90e52e66u, 0x90e5fe00u, 0x90e5211fu, 0x90e5b333u, 0x90e546a5u, 0x90e5fe00u, 0x90e57c00u, 0x90e529e0u, 0x90e50000u, 0x90e5b564u, 0x90e53c00u, 0x90e5bb88u}, + {0xc0000000u, 0x80000000u, 0xc0400000u, 0xbf800000u, 0xc0000000u, 0x80000000u, 0xc0000000u, 0x80000000u, 0x3f000000u, 0x3f000000u, 0x00000002u, 0x00000000u, 0xbf200000u, 0xbf000000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xbecccccdu, 0xc0000000u, 0xbecccccdu, 0xc0000000u, 0xffc00000u, 0xffc00000u, 0x3e3504f3u, 0x3f3504f3u, 0x00000000u, 0x00000000u, 0xbf800000u, 0xbf800000u, 0x89f43c00u, 0x89f48000u, 0x89f40000u, 0x89f4bc00u, 0x89f40000u, 0x89f48000u, 0x89f40000u, 0x89f48000u, 0x89f40d70u, 0x89f43bfdu, 0x89f424aau, 0x89f4fe00u, 0x89f452ddu, 0x89f4fe00u, 0x89f469e3u, 0x89f4e157u, 0x89f4c9c7u, 0x89f4fe00u, 0x89f43c00u, 0x89f43bfeu, 0x89f41845u, 0x89f4a0b5u, 0x89f43c00u, 0x89f43c00u}, + {0x3f800000u, 0x7fc00000u, 0x00000000u, 0x7fc00000u, 0x00000000u, 0x7fc00000u, 0x00000000u, 0x7fc00000u, 0x3df74341u, 0x7fc00000u, 0xfffffffdu, 0x00000000u, 0x3f774341u, 0x7fc00000u, 0x3eb1e746u, 0x7fc00000u, 0x40383098u, 0x7fc00000u, 0x410485e4u, 0x7fc00000u, 0x410485e4u, 0x7fc00000u, 0xc04334d3u, 0x7fc00000u, 0x3f8b2c3eu, 0x7fc00000u, 0x3f301a40u, 0x7fc00000u, 0x3f39ce5au, 0x7fc00000u, 0xc9717e01u, 0xc9714000u, 0xc9717e01u, 0xc9713c00u, 0xc9717e01u, 0xc9713c00u, 0xc9717e01u, 0xc9714000u, 0xc9717e01u, 0xc9713800u, 0xc9717e01u, 0xc9713ce6u, 0xc9717e01u, 0xc9713a88u, 0xc9717e01u, 0xc9713955u, 0xc9717e01u, 0xc97138aeu, 0xc9717e01u, 0xc97141a8u, 0xc9717e01u, 0xc9710000u, 0xc9717e01u, 0xc971bc00u}, + {0x3f800000u, 0x80000000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x3f000000u, 0x3f7d378eu, 0x00000000u, 0xfffffffau, 0x3f000000u, 0xbf321c7bu, 0x3f3504f3u, 0xffc00000u, 0x3fb504f3u, 0xffc00000u, 0x40000000u, 0xc2b7f993u, 0x40000000u, 0xc2b7f993u, 0xbf800000u, 0xffc00000u, 0x3fb504f3u, 0x3f7e1407u, 0x00000000u, 0xbd8bc78bu, 0xbf800000u, 0x3f7f672du, 0x7d303c00u, 0x7d303c00u, 0x7d300000u, 0x7d300000u, 0x7d300000u, 0x7d300000u, 0x7d300000u, 0x7d300000u, 0x7d303800u, 0x7d302698u, 0x7d3039a8u, 0x7d303123u, 0x7d303da8u, 0x7d30463bu, 0x7d304000u, 0x7d3050dau, 0x7d30bc00u, 0x7d30c547u, 0x7d303da8u, 0x7d303c12u, 0x7d300000u, 0x7d303128u, 0x7d30bc00u, 0x7d303be5u} +}; +constexpr const char* Names[54] = {"ceil_f32 v4", "ceil_f32 v5", "floor_f32 v4", "floor_f32 v5", "trunc_f32 v4", "trunc_f32 v5", "rndne_f32 v4", "rndne_f32 v5", "fract_f32 v4", "fract_f32 v5", "frexp_exp_i32_f32 v4", "frexp_exp_i32_f32 v5", "frexp_mant_f32 v4", "frexp_mant_f32 v5", "sqrt_f32 v4", "sqrt_f32 v5", "rsq_f32 v4", "rsq_f32 v5", "rcp_f32 v4", "rcp_f32 v5", "rcp_iflag_f32 v4", "rcp_iflag_f32 v5", "log_f32 v4", "log_f32 v5", "exp_f32 v4", "exp_f32 v5", "sin_f32 v4", "sin_f32 v5", "cos_f32 v4", "cos_f32 v5", "ceil_f16 v6", "ceil_f16 v8", "floor_f16 v6", "floor_f16 v8", "trunc_f16 v6", "trunc_f16 v8", "rndne_f16 v6", "rndne_f16 v8", "fract_f16 v6", "fract_f16 v8", "sqrt_f16 v6", "sqrt_f16 v8", "rsq_f16 v6", "rsq_f16 v8", "rcp_f16 v6", "rcp_f16 v8", "log_f16 v6", "log_f16 v8", "exp_f16 v6", "exp_f16 v8", "sin_f16 v6", "sin_f16 v8", "cos_f16 v6", "cos_f16 v8"}; +constexpr std::uint64_t Checked[32] = {0x003fffffc3ffc000ull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull, 0x003fffffffffffffull}; +constexpr std::uint32_t Tolerance[54] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::string Hex(std::uint32_t value); +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name); + +double Half(std::uint32_t bits) { + const auto exponent = static_cast((bits >> 10u) & 0x1fu); + const double magnitude = exponent == 0x1f ? ((bits & 0x3ffu) ? NAN : INFINITY) : std::ldexp(static_cast((bits & 0x3ffu) | (exponent ? 0x400u : 0u)), (exponent ? exponent : 1) - 25); + return (bits & 0x8000u) ? -magnitude : magnitude; +} + +bool Finite(double value) { + return std::isfinite(value) && value != 0.0; +} + +void ExpectNear(std::uint32_t tid, std::uint32_t index, std::uint32_t actual, std::uint32_t expected) { + const bool half = Tolerance[index] >= 5u; + const double a = half ? Half(actual & 0xffffu) : std::bit_cast(actual); + const double e = half ? Half(expected & 0xffffu) : std::bit_cast(expected); + if (!Finite(e) || (half && (actual >> 16u) != (expected >> 16u))) { + Expect(tid, actual, expected, Names[index]); + return; + } + const double x = half ? Half((Rows[tid][2] >> (index % 2u ? 16u : 0u)) & 0xffffu) : std::bit_cast(Rows[tid][index % 2u]); + const double error = std::abs(a - e); + const double relative = error / std::abs(e); + bool near = false; + switch (Tolerance[index]) { + case 1u: near = relative <= 0x1p-20; break; + case 2u: near = error <= 0x1p-21 || relative <= 0x1p-20; break; + case 3u: near = relative <= (4.0 + 2.0 * std::abs(x)) * 0x1p-23; break; + case 4u: near = error <= 0x1p-11; break; + case 5u: near = relative <= 0x1p-9; break; + default: near = error <= 0x1p-10; break; + } + Require(near, std::string("vop1 float unary: lane ") + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected near " + Hex(expected)); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop1 float unary: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 54; ++i) { + if (((Checked[tid] >> i) & 1u) == 0u) continue; + if (Tolerance[i] == 0u) Expect(tid, out[i], Expected[tid][i], Names[i]); + else ExpectNear(tid, i, out[i], Expected[tid][i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop1 float unary tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop1Vop2Integer.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop1Vop2Integer.cpp new file mode 100644 index 000000000..773fd3e38 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop1Vop2Integer.cpp @@ -0,0 +1,173 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 64; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060088, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e146f04, 0x7e167104, 0x7e187304, + 0x7e1a7504, 0x7e1c7704, 0x7e1e0d04, 0x7e200b04, 0x7e220f04, 0x7e241104, 0x7e261904, 0x7e281b04, + 0x7e2a1d04, 0x7e2ca304, 0x7e2ea104, 0x7e30a504, 0x7e32a704, 0x12340b04, 0x14360b04, 0x16380b04, + 0x183a0b04, 0x223c0b04, 0x243e0b04, 0x26400b04, 0x28420b04, 0x2c440b04, 0x30460b04, 0x34480b04, + 0x364a0b04, 0x384c0b04, 0x3a4e0b04, 0x3c500b04, 0x4a520b04, 0x4c540b04, 0x4e560b04, 0x36100c81, + 0x7d8a1080, 0x02580b04, 0x36100c81, 0x7d8a1080, 0x505a0b04, 0xd501002e, 0x01a90280, 0x36100c81, + 0x7d8a1080, 0x525e0b04, 0xd5010030, 0x01a90280, 0x36100c81, 0x7d8a1080, 0x54620b04, 0xd5010032, + 0x01a90280, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xe0781040, 0x80011a03, 0xe0781050, 0x80011e03, 0xe0781060, 0x80012203, 0xe0781070, + 0x80012603, 0xe0781080, 0x80012a03, 0xe0781090, 0x80012e03, 0xe07810a0, 0x80013203, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x3effffffu, 0x00000005u, 0x00000001u, 0x00000000u}, + {0xbeffffffu, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x4b000001u, 0x7fffffffu, 0x00000001u, 0x00000000u}, + {0xcb000001u, 0x00000021u, 0x00000000u, 0x00000000u}, + {0x4affffffu, 0x00ffffffu, 0x00000001u, 0x00000000u}, + {0xbf000000u, 0xff800000u, 0x00000000u, 0x00000000u}, + {0x7fc00000u, 0x00800000u, 0x00000001u, 0x00000000u}, + {0xffc00000u, 0x00000020u, 0x00000000u, 0x00000000u}, + {0x7f800001u, 0xffffffffu, 0x00000001u, 0x00000000u}, + {0xff800001u, 0x00000000u, 0x00000001u, 0x00000000u}, + {0x4effffffu, 0x00000001u, 0x00000000u, 0x00000000u}, + {0xcf000000u, 0x80000001u, 0x00000001u, 0x00000000u}, + {0xbf000001u, 0x00800000u, 0x00000000u, 0x00000000u}, + {0xaf000000u, 0x00007fffu, 0x00000001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xbf000000u, 0x00000003u}, + {0x58457b3au, 0xa7913051u, 0xfffffffeu, 0xcf000000u}, + {0x80000000u, 0x00000000u, 0xad489bceu, 0x96fc31a0u}, + {0xfffffffeu, 0x00000001u, 0x80000000u, 0xffffffffu}, + {0x39a77c00u, 0xc0200000u, 0x0000fc00u, 0x4f800000u}, + {0x5c90a958u, 0x00ffffffu, 0x3e7d1bfbu, 0x4f000000u}, + {0xffff890bu, 0x72158370u, 0x00003c00u, 0x58d5563du}, + {0x14a0f9e7u, 0x00003800u, 0x00008000u, 0xdd2e1609u}, + {0x83f73f16u, 0xa7abe1c2u, 0x0dd27a65u, 0x0000bc00u}, + {0x2607679du, 0xf4998d7cu, 0x7fc00000u, 0x80000001u}, + {0xdd02de92u, 0xbfc00000u, 0x007fffffu, 0x3f000000u}, + {0x3d932289u, 0x31dec4f4u, 0x3606defcu, 0x9abcdef0u}, + {0x5dbe3023u, 0x00008000u, 0x3836e865u, 0x18189af4u}, + {0xffff5884u, 0x12345678u, 0x4effffffu, 0x6eb4fff8u}, + {0x33061fbcu, 0xd1cee715u, 0x0000001fu, 0xc17a4f81u}, + {0x75b00b15u, 0x00008001u, 0xc5acb068u, 0x0000bc00u}, + {0xffffffffu, 0x0000001fu, 0x0000fbffu, 0xfffffffeu}, + {0x953f48f1u, 0x93bd04cfu, 0x0000000fu, 0x0becd7b0u} +}; +constexpr std::uint32_t Expected[32][41] = { + {0xc1000000u, 0xffffff7cu, 0x00000002u, 0x00000000u, 0x00000002u, 0x4e7c0000u, 0x4e7c0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbd800000u, 0x0000bc00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0xfffffffbu, 0xffffffffu, 0x04fffffbu, 0x00000000u, 0x00000005u, 0x3effffffu, 0x00000005u, 0x3effffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000005u, 0x3effffffu, 0x3efffffau, 0xc1000005u, 0x3f000004u, 0x3efffffau, 0xc1000006u, 0x00000005u, 0x3f000005u, 0x00000000u, 0x3efffff9u, 0x00000000u, 0xc1000005u, 0x00000001u}, + {0x41000000u, 0xffffff7du, 0x00000000u, 0x00000000u, 0x00000001u, 0x4f3f0000u, 0xce820000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xbd800000u, 0x0000bc00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xbeffffffu, 0x80000000u, 0xbeffffffu, 0x00000001u, 0xffffffffu, 0x00000000u, 0x80000000u, 0xbeffffffu, 0x3effffffu, 0xc1000000u, 0x3effffffu, 0x3effffffu, 0xc1000001u, 0xbeffffffu, 0x3effffffu, 0x00000001u, 0x3effffffu, 0x00000000u, 0xc1000001u, 0x00000001u}, + {0xb4fffffeu, 0x800000d2u, 0x00000001u, 0x00000000u, 0x00000001u, 0x4e960000u, 0x4e960000u, 0x00800001u, 0x00800001u, 0x00800001u, 0x00800001u, 0x3d800000u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00ffffffu, 0x00000000u, 0x4b000001u, 0x7fffffffu, 0x4b000001u, 0x7fffffffu, 0x3fffffffu, 0x3fffffffu, 0xfffffffeu, 0x4b000001u, 0x7fffffffu, 0x34fffffeu, 0xcb000001u, 0xcb000000u, 0xcb000002u, 0x34fffffeu, 0x7fffffffu, 0xcb000001u, 0x00000000u, 0xcb000001u, 0x00000001u, 0x34fffffdu, 0x00000000u}, + {0x34fffffeu, 0x800000d3u, 0x00000000u, 0x00000000u, 0x00000002u, 0x4f4b0000u, 0xce540000u, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x3d800000u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000021u, 0x00000000u, 0x00000021u, 0x00000000u, 0xcb000001u, 0x00000021u, 0x00000021u, 0xcb000001u, 0x00000010u, 0x00000010u, 0x00000042u, 0x00000001u, 0xcb000021u, 0xcb000020u, 0x34ffffdfu, 0xcb000022u, 0xcaffffe0u, 0x35000020u, 0xcb000001u, 0xcb000022u, 0x00000000u, 0xcaffffe0u, 0x00000000u, 0x35000020u, 0x00000001u}, + {0xb5000000u, 0xffffff52u, 0x00000001u, 0x00000000u, 0x00000001u, 0x4e960000u, 0x4e960000u, 0x007fffffu, 0x007fffffu, 0x00800000u, 0x007fffffu, 0xbd800000u, 0x0000bc00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0xfe000001u, 0x0000ffffu, 0x00ffffffu, 0x4affffffu, 0x00ffffffu, 0x4affffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x00ffffffu, 0x4affffffu, 0x4a000000u, 0xb5ffffffu, 0x4bfffffeu, 0x4a000000u, 0xb6000000u, 0x00ffffffu, 0x4bffffffu, 0x00000000u, 0x49ffffffu, 0x00000000u, 0xb5ffffffu, 0x00000001u}, + {0x40ffffffu, 0x000000fdu, 0x00000000u, 0x00000018u, 0x00000001u, 0x4f3f0000u, 0xce820000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbf000000u, 0xff800000u, 0xbf000000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xbf000000u, 0xff800000u, 0x40800000u, 0xbf7fffffu, 0xbe800000u, 0xbf800000u, 0x40800000u, 0xbf000000u, 0xbe800000u, 0x00000001u, 0xbf800000u, 0x00000001u, 0x40800000u, 0x00000000u}, + {0x803fffffu, 0x000003feu, 0x00000001u, 0x00000016u, 0x00000001u, 0x4eff8000u, 0x4eff8000u, 0x00000000u, 0x00000000u, 0x7fffffffu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00002000u, 0x00000000u, 0x00006000u, 0x00800000u, 0x7fc00000u, 0x00800000u, 0x7fc00000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x7fc00000u, 0x7f400000u, 0x80bfffffu, 0x80400000u, 0x7f400000u, 0x80c00000u, 0x00800000u, 0x80400001u, 0x00000000u, 0x7f3fffffu, 0x00000000u, 0x80bfffffu, 0x00000001u}, + {0x003fffffu, 0x000003ffu, 0x00000000u, 0x00000016u, 0x0000000au, 0x4f7fc000u, 0xca800000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xf8000000u, 0xffffffffu, 0x18000000u, 0x00000000u, 0xffc00000u, 0x00000020u, 0x00000020u, 0xffc00000u, 0x00000020u, 0x00000020u, 0x00000020u, 0x00000000u, 0xffc00020u, 0xffc00020u, 0x003fffdfu, 0xffc00020u, 0xffbfffe0u, 0x00400020u, 0xffc00000u, 0xffc00020u, 0x00000000u, 0xffbfffe0u, 0x00000000u, 0x00400020u, 0x00000001u}, + {0x807ffffeu, 0x800001feu, 0x00000001u, 0x00000000u, 0x00000001u, 0x4eff0000u, 0x4eff0000u, 0x00000000u, 0x00000000u, 0x7fffffffu, 0x7fffffffu, 0x3d800000u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x007fffffu, 0x00000000u, 0x007fffffu, 0x00008000u, 0xffffffffu, 0x7f800001u, 0x7f800001u, 0xffffffffu, 0x7fffffffu, 0xffffffffu, 0xfffffffeu, 0x7f800001u, 0xffffffffu, 0x807ffffeu, 0x7f800001u, 0x7f800000u, 0x7f800002u, 0x807ffffeu, 0xffffffffu, 0x7f800001u, 0x00000001u, 0x7f800001u, 0x00000001u, 0x807ffffdu, 0x00000000u}, + {0x007ffffeu, 0x800001ffu, 0x00000000u, 0x00000000u, 0x00000009u, 0x4f7f8000u, 0xcafffffeu, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x3d800000u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800001u, 0x00000000u, 0x00000000u, 0xff800001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800001u, 0xff800001u, 0x007ffffeu, 0xff800001u, 0xff800001u, 0x007fffffu, 0x00000000u, 0xff800002u, 0x00000000u, 0xff800000u, 0x00000000u, 0x007ffffeu, 0x00000001u}, + {0xb1000000u, 0xffffff72u, 0x00000001u, 0x00000000u, 0x00000001u, 0x4e9e0000u, 0x4e9e0000u, 0x7fffff80u, 0x7fffff80u, 0x7fffff80u, 0x7fffff80u, 0xbd800000u, 0x0000bc00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00ffffffu, 0x00000000u, 0x00000001u, 0x4effffffu, 0x00000001u, 0x4effffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000001u, 0x4effffffu, 0x4efffffeu, 0xb1000001u, 0x4f000000u, 0x4efffffeu, 0xb1000002u, 0x4effffffu, 0x4f000000u, 0x00000000u, 0x4efffffeu, 0x00000000u, 0xb1000002u, 0x00000001u}, + {0x30ffffffu, 0x000000f3u, 0x00000000u, 0x00000018u, 0x00000002u, 0x4f4f0000u, 0xce440000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000001u, 0xcf000000u, 0x80000001u, 0xcf000000u, 0x80000001u, 0x80000001u, 0x80000001u, 0x80000000u, 0xcf000001u, 0x4f000001u, 0xb0fffffeu, 0x4f000001u, 0x4effffffu, 0xb1000001u, 0x80000001u, 0x4f000002u, 0x00000001u, 0x4efffffeu, 0x00000000u, 0xb1000000u, 0x00000001u}, + {0x40fffffeu, 0x800000fdu, 0x00000000u, 0x00000000u, 0x00000001u, 0x4f3f0000u, 0xce820000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x3d800000u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u, 0xff800000u, 0xffffffffu, 0x00800000u, 0x00000000u, 0xbf000001u, 0x00800000u, 0x00800000u, 0xbf000001u, 0x00400000u, 0x00400000u, 0x01000000u, 0x00000000u, 0xbf800001u, 0xbf800001u, 0x407ffffeu, 0xbf800001u, 0xbe800001u, 0x417fffffu, 0xbf000001u, 0xbf800001u, 0x00000000u, 0xbe800001u, 0x00000000u, 0x417fffffu, 0x00000001u}, + {0x50ffffffu, 0x000000f5u, 0x00000000u, 0x00000018u, 0x00000001u, 0x4f2f0000u, 0xcea20000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xaf000000u, 0x00007fffu, 0x00007fffu, 0xaf000000u, 0x00007fffu, 0x00007fffu, 0x00007fffu, 0x00000000u, 0xaf007fffu, 0xaf007fffu, 0x50ff8000u, 0xaf007fffu, 0xaeff8001u, 0x51007fffu, 0x00007fffu, 0xaf008000u, 0x00000000u, 0xaeff8000u, 0x00000000u, 0x51007ffeu, 0x00000001u}, + {0xffffffffu, 0x00000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xa7ba84c5u, 0x5cdea21au, 0x00000001u, 0x00000001u, 0x00000001u, 0x4eb08af6u, 0x4eb08af6u, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x7fffffffu, 0xbec00000u, 0x000077b4u, 0x000077b4u, 0x0000e740u, 0x00007fffu, 0xaef0dd5au, 0xffffe1ecu, 0xe8f0dd5au, 0x00002767u, 0xa7913051u, 0x58457b3au, 0x58457b3au, 0xa7913051u, 0x00000029u, 0xffffffe9u, 0x44000000u, 0x00013010u, 0xffd57b7bu, 0xffd44b6bu, 0x002bb494u, 0xffd6ab8bu, 0xb0b44ae9u, 0x4f4bb517u, 0x58457b3au, 0xffd6ab8bu, 0x00000000u, 0xb0b44ae9u, 0x00000001u, 0x4f4bb517u, 0x00000000u}, + {0x7fffffffu, 0x00000001u, 0x00000000u, 0x0000001fu, 0x00000001u, 0x4f000000u, 0xcf000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x7fffffffu, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000001u}, + {0x00000001u, 0x7fffffffu, 0x00000000u, 0x00000001u, 0x0000001fu, 0x4f800000u, 0xc0000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0xbe000000u, 0x0000c000u, 0x00007c00u, 0x00000000u, 0x00000000u, 0xfffffffeu, 0xffffffffu, 0x00fffffeu, 0x00000000u, 0xfffffffeu, 0x00000001u, 0x00000001u, 0xfffffffeu, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000000u, 0xffffffffu, 0xfffffffdu, 0x00000003u, 0xfffffffeu, 0xffffffffu, 0x00000000u, 0xfffffffdu, 0x00000000u, 0x00000003u, 0x00000001u}, + {0xc65883ffu, 0x003ee59cu, 0x00000002u, 0x0000000au, 0x00000002u, 0x4e669df0u, 0x4e669df0u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000077c0u, 0x000077c0u, 0x0000ffffu, 0x00007fffu, 0x80000000u, 0xfffff4efu, 0x80000000u, 0x000014efu, 0xc0200000u, 0x39a77c00u, 0x39a77c00u, 0xc0200000u, 0xc0200000u, 0xc0200000u, 0xc0200000u, 0x00200000u, 0xf9a77c00u, 0xf9877c00u, 0x067883ffu, 0xf9c77c00u, 0x79877c00u, 0x86788400u, 0x39a77c00u, 0xf9c77c00u, 0x00000000u, 0x79877c00u, 0x00000001u, 0x86788400u, 0x00000000u}, + {0xa36f56a7u, 0x1a95093au, 0x00000001u, 0x00000003u, 0x00000001u, 0x4eb92153u, 0x4eb92153u, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x7fffffffu, 0xbf000000u, 0x0000f56au, 0x0000794bu, 0x00000000u, 0x00000000u, 0x006f56a8u, 0x00000000u, 0x576f56a8u, 0x000090a9u, 0x00ffffffu, 0x5c90a958u, 0x00ffffffu, 0x5c90a958u, 0x00000000u, 0x00000000u, 0xff000000u, 0x0090a958u, 0x5cffffffu, 0x5c6f56a7u, 0xa390a958u, 0x5d90a957u, 0x5b90a959u, 0xa46f56a7u, 0x00ffffffu, 0x5d90a958u, 0x00000000u, 0x5b90a958u, 0x00000000u, 0xa46f56a6u, 0x00000001u}, + {0x000076f4u, 0xd091ffffu, 0x00000000u, 0x00000000u, 0x00000011u, 0x4f7fff89u, 0xc6edea00u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0xbea00000u, 0x0000f76fu, 0x00007848u, 0x00000000u, 0x00000000u, 0x00d395d0u, 0xfffffff6u, 0x70d395d0u, 0x00001579u, 0xffff890bu, 0x72158370u, 0x72158370u, 0xffff890bu, 0x000e42b0u, 0x000e42b0u, 0xac1b8000u, 0x72158100u, 0xffff8b7bu, 0x8dea0a7bu, 0x7215f584u, 0x72150c7bu, 0x8dea059bu, 0x7215fa65u, 0xffff890bu, 0x72150c7bu, 0x00000001u, 0x8dea059bu, 0x00000000u, 0x7215fa65u, 0x00000001u}, + {0xeb5f0618u, 0xe79f0528u, 0x00000003u, 0x00000000u, 0x00000003u, 0x4da507cfu, 0x4da507cfu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3ee00000u, 0x0000e619u, 0x00007bcfu, 0x00000000u, 0x00008000u, 0x36aa8800u, 0xffffffebu, 0x36aa8800u, 0x00000023u, 0x00003800u, 0x14a0f9e7u, 0x00003800u, 0x14a0f9e7u, 0x00000070u, 0x00000070u, 0x001c0000u, 0x00003800u, 0x14a0f9e7u, 0x14a0c1e7u, 0xeb5f3e18u, 0x14a131e7u, 0x14a0c1e7u, 0xeb5f3e19u, 0x14a0f9e7u, 0x14a131e7u, 0x00000000u, 0x14a0c1e7u, 0x00000000u, 0xeb5f3e19u, 0x00000001u}, + {0x7c08c0e9u, 0x68fcefc1u, 0x00000000u, 0x00000001u, 0x00000001u, 0x4f03f73fu, 0xcef81182u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x3ec00000u, 0x000073e3u, 0x000073e3u, 0x00000001u, 0x00000001u, 0x558224acu, 0x000002e0u, 0x2d8224acu, 0x0000a601u, 0x83f73f16u, 0xa7abe1c2u, 0x83f73f16u, 0xa7abe1c2u, 0x0000029eu, 0xfffffe9eu, 0x70800000u, 0x83a32102u, 0xa7ffffd6u, 0x245cded4u, 0xdba3212bu, 0x2ba320d8u, 0xdc4b5d54u, 0x23b4a2acu, 0xa7abe1c2u, 0x2ba320d9u, 0x00000001u, 0xdc4b5d53u, 0x00000001u, 0x23b4a2abu, 0x00000000u}, + {0xd9f89862u, 0xb9e6e064u, 0x00000002u, 0x00000000u, 0x00000002u, 0x4e181d9eu, 0x4e181d9eu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbe400000u, 0x0000767au, 0x0000767au, 0x0000079du, 0x0000079du, 0x677ca90cu, 0xfffffd09u, 0x047ca90cu, 0x00000471u, 0xf4998d7cu, 0x2607679du, 0x2607679du, 0xf4998d7cu, 0x00000007u, 0xffffffffu, 0x80000000u, 0x2401051cu, 0xf69feffdu, 0xd29eeae1u, 0x2d61151eu, 0x1aa0f519u, 0x316dda21u, 0xce9225dfu, 0x2607679du, 0x1aa0f519u, 0x00000001u, 0x316dda21u, 0x00000001u, 0xce9225dfu, 0x00000000u}, + {0x22fd216du, 0x497b40bbu, 0x00000000u, 0x00000001u, 0x00000002u, 0x4f5d02dfu, 0xce0bf486u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x3e000000u, 0x0000f02eu, 0x00007af5u, 0x00000000u, 0x0000fe5cu, 0x5b800000u, 0xffffff48u, 0xed800000u, 0x00000226u, 0xbfc00000u, 0xdd02de92u, 0xbfc00000u, 0xdd02de92u, 0x00002ff0u, 0xffffeff0u, 0x00000000u, 0x9d000000u, 0xffc2de92u, 0x62c2de92u, 0x9d3d216du, 0x9cc2de92u, 0x1d42de92u, 0xe2bd216eu, 0xbfc00000u, 0x9cc2de93u, 0x00000001u, 0x1d42de91u, 0x00000000u, 0xe2bd216du, 0x00000001u}, + {0xc26cdd76u, 0x9144c9bcu, 0x00000002u, 0x00000000u, 0x00000002u, 0x4e764c8au, 0x4e764c8au, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbee00000u, 0x00007051u, 0x00007051u, 0x00000000u, 0x00000000u, 0xa87bce94u, 0x00000e21u, 0x257bce94u, 0x00008009u, 0x31dec4f4u, 0x3d932289u, 0x31dec4f4u, 0x3d932289u, 0x0018ef62u, 0x0018ef62u, 0xbd89e800u, 0x31920080u, 0x3ddfe6fdu, 0x0c4de67du, 0xf3b21982u, 0x6f71e77du, 0x0bb45d95u, 0xf44ba26bu, 0x3d932289u, 0x6f71e77du, 0x00000000u, 0x0bb45d95u, 0x00000000u, 0xf44ba26bu, 0x00000001u}, + {0xa241cfdcu, 0xc40c7dbau, 0x00000001u, 0x00000000u, 0x00000001u, 0x4ebb7c60u, 0x4ebb7c60u, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x7fffffffu, 0x3e400000u, 0x00007204u, 0x00007204u, 0x00000000u, 0x00000000u, 0x18118000u, 0xffffffdfu, 0x18118000u, 0x0000005fu, 0x00008000u, 0x5dbe3023u, 0x00008000u, 0x5dbe3023u, 0x00001000u, 0x00001000u, 0x00040000u, 0x00000000u, 0x5dbeb023u, 0x5dbeb023u, 0xa2414fdcu, 0x5dbeb023u, 0x5dbdb023u, 0xa2424fddu, 0x00008000u, 0x5dbeb024u, 0x00000000u, 0x5dbdb022u, 0x00000000u, 0xa2424fdcu, 0x00000001u}, + {0x0000a77bu, 0x211affffu, 0x00000000u, 0x00000002u, 0x00000010u, 0x4f7fff59u, 0xc7277c00u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x3e800000u, 0x00007588u, 0x00007588u, 0x00000090u, 0x00000090u, 0xc23dd5e0u, 0xffffffddu, 0x3a3dd5e0u, 0x00003434u, 0xffff5884u, 0x12345678u, 0x12345678u, 0xffff5884u, 0x01234567u, 0x01234567u, 0x23456780u, 0x12345000u, 0xffff5efcu, 0xedcb0efcu, 0x1234f103u, 0x1233aefcu, 0xedcb020cu, 0x1234fdf4u, 0x12345678u, 0x1233aefdu, 0x00000001u, 0xedcb020bu, 0x00000000u, 0x1234fdf3u, 0x00000001u}, + {0xccf9e043u, 0x3df860ccu, 0x00000002u, 0x00000002u, 0x00000002u, 0x4e4c187fu, 0x4e4c187fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xbe800000u, 0x00006fefu, 0x00006fefu, 0x00000000u, 0x00000000u, 0x546b3e6cu, 0xfffffed3u, 0x106b3e6cu, 0x000004f3u, 0xd1cee715u, 0x33061fbcu, 0x33061fbcu, 0xd1cee715u, 0x0000000du, 0xfffffffdu, 0x50000000u, 0x11060714u, 0xf3ceffbdu, 0xe2c8f8a9u, 0x1d370756u, 0x04d506d1u, 0x613738a7u, 0x9ec8c759u, 0xd1cee715u, 0x04d506d2u, 0x00000001u, 0x613738a6u, 0x00000001u, 0x9ec8c758u, 0x00000000u}, + {0x8a4ff4eau, 0xa8d00daeu, 0x00000001u, 0x00000000u, 0x00000001u, 0x4eeb6016u, 0x4eeb6016u, 0xffffffffu, 0x7fffffffu, 0x7fffffffu, 0x7fffffffu, 0x3ea00000u, 0x0000698au, 0x0000698au, 0x00000000u, 0x00000000u, 0x053a8b15u, 0xffffffd8u, 0x063a8b15u, 0x00000058u, 0x00008001u, 0x75b00b15u, 0x00008001u, 0x75b00b15u, 0x00000000u, 0x00000000u, 0x00200000u, 0x00000001u, 0x75b08b15u, 0x75b08b14u, 0x8a4f74ebu, 0x75b08b16u, 0x75af8b14u, 0x8a5074ecu, 0x75b00b15u, 0x75b08b16u, 0x00000000u, 0x75af8b14u, 0x00000000u, 0x8a5074ecu, 0x00000001u}, + {0x00000000u, 0xffffffffu, 0x00000000u, 0x00000000u, 0xffffffffu, 0x4f800000u, 0xbf800000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0xbd800000u, 0x0000bc00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0xffffffe1u, 0xffffffffu, 0x1effffe1u, 0x00000000u, 0xffffffffu, 0x0000001fu, 0x0000001fu, 0xffffffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x0000001fu, 0xffffffffu, 0xffffffe0u, 0x0000001fu, 0x0000001eu, 0xffffffe0u, 0x00000020u, 0x0000001fu, 0x0000001fu, 0x00000001u, 0xffffffdfu, 0x00000000u, 0x0000001fu, 0x00000001u}, + {0x6ac0b70eu, 0x8f12fca9u, 0x00000000u, 0x00000000u, 0x00000001u, 0x4f153f49u, 0xced5816eu, 0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0x3d800000u, 0x0000748fu, 0x0000748fu, 0x00000009u, 0x00000009u, 0x193cbedfu, 0xffffef71u, 0x0a3cbedfu, 0x00002ebau, 0x93bd04cfu, 0x953f48f1u, 0x93bd04cfu, 0x953f48f1u, 0x000049deu, 0xffffc9deu, 0x099e0000u, 0x913d00c1u, 0x97bf4cffu, 0x06824c3eu, 0xf97db3c1u, 0x28fc4dc0u, 0x01824422u, 0xfe7dbbdeu, 0x93bd04cfu, 0x28fc4dc1u, 0x00000001u, 0x01824421u, 0x00000000u, 0xfe7dbbddu, 0x00000001u} +}; +constexpr const char* Names[41] = {"not_b32", "bfrev_b32", "ffbh_u32", "ffbl_b32", "ffbh_i32", "cvt_f32_u32", "cvt_f32_i32", "cvt_u32_f32", "cvt_i32_f32", "cvt_rpi_i32_f32", "cvt_flr_i32_f32", "cvt_off_f32_i4", "cvt_f16_i16", "cvt_f16_u16", "cvt_u16_f16", "cvt_i16_f16", "mul_i32_i24", "mul_hi_i32_i24", "mul_u32_u24", "mul_hi_u32_u24", "min_i32", "max_i32", "min_u32", "max_u32", "lshrrev_b32", "ashrrev_i32", "lshlrev_b32", "and_b32", "or_b32", "xor_b32", "xnor_b32", "add_nc_u32", "sub_nc_u32", "subrev_nc_u32", "cndmask_b32", "add_co_ci_u32", "add_co_ci_u32 carry", "sub_co_ci_u32", "sub_co_ci_u32 carry", "subrev_co_ci_u32", "subrev_co_ci_u32 carry"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop1 vop2 integer: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 41; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop1 vop2 integer tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop2ModifierForms.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop2ModifierForms.cpp new file mode 100644 index 000000000..9b07c7f68 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop2ModifierForms.cpp @@ -0,0 +1,192 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7d8a0c80, 0x7e1402ff, 0xdeadbeef, + 0x7e1602ff, 0xdeadbeef, 0x7e1802ff, 0xdeadbeef, 0x7e1a02ff, 0xdeadbeef, 0x7e1c02ff, 0xdeadbeef, + 0x7e1e02ff, 0xdeadbeef, 0x7e2002ff, 0xdeadbeef, 0x7e2202ff, 0xdeadbeef, 0x7e2402ff, 0xdeadbeef, + 0x7e2602ff, 0xdeadbeef, 0x7e2802ff, 0xdeadbeef, 0x7e2a02ff, 0xdeadbeef, 0x7e2c02ff, 0xdeadbeef, + 0x7e2e02ff, 0xdeadbeef, 0x7e3002ff, 0xdeadbeef, 0x7e3202ff, 0xdeadbeef, 0x02140afa, 0xff10e404, + 0x02160afa, 0xffe0e404, 0xd501020c, 0x21aa0b04, 0x021a0af9, 0x16360604, 0x0e1c0afa, 0xff90e404, + 0x0e1e0af9, 0x16360604, 0x0e200af9, 0x06062604, 0x0e220af9, 0x16064604, 0xd5078012, 0x1a020b04, + 0xd5070013, 0x32020b04, 0xd5078314, 0x42020b04, 0xd53b0015, 0x22020b04, 0x762c0afa, 0xff20e404, + 0x762e0af9, 0x06360604, 0xd53b0118, 0x02020b04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, + 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u}, + {0x00000000u, 0x7f800000u, 0x00000001u, 0x00000000u}, + {0x3fc00000u, 0x3fc00000u, 0x00000001u, 0x00000000u}, + {0x3fc00000u, 0x7f800000u, 0x00000000u, 0x00000000u}, + {0xbf800000u, 0x3f800000u, 0x00000001u, 0x00000000u}, + {0x3fc00000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xc0000000u, 0xabcdbc00u, 0x00000000u, 0x00000000u}, + {0x3fc00000u, 0xff800000u, 0x00000000u, 0x00000000u}, + {0xc0000000u, 0xff61b1e6u, 0x00000000u, 0x00000000u}, + {0x3e800000u, 0xc0000000u, 0x00000001u, 0x00000000u}, + {0x3e800000u, 0xbf800000u, 0x00000001u, 0x00000000u}, + {0x3f800000u, 0xbf800000u, 0x00000000u, 0x00000000u}, + {0xc0000000u, 0x7f800000u, 0x00000001u, 0x00000000u}, + {0x3e800000u, 0x3f800000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3fc00000u, 0x00000001u, 0x00000000u}, + {0x807fffffu, 0x00000000u, 0x00000001u, 0x00000000u}, + {0x3f19999au, 0x00000001u, 0x00000001u, 0x00000000u}, + {0x3f19999au, 0x00003555u, 0x00000000u, 0x00000000u}, + {0x3f19999au, 0xffffffe2u, 0x00000001u, 0x00000000u}, + {0x00003c00u, 0x00000000u, 0x00000001u, 0x00000000u}, + {0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u}, + {0x00007e01u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x807fffffu, 0x00000001u, 0x00000000u}, + {0x00000000u, 0x7fc00001u, 0x00000001u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0xc0000000u, 0x00000001u, 0x00000000u}, + {0x80000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x807fffffu, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x7fc00001u, 0x00000001u, 0x00000000u}, + {0x80000000u, 0xffc00000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00007e01u, 0x00000001u, 0x00000000u} +}; +constexpr std::uint32_t Expected[32][15] = { + {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x3fc00000u, 0xbfc00000u, 0x3fc00000u, 0xbfc00000u, 0xc0100000u, 0x40100000u, 0x3f800000u, 0xc0900000u, 0x3f800000u, 0xc1100000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xbfc00000u, 0x3fc00000u, 0xbfc00000u, 0xbfc00000u, 0xff800000u, 0x7f800000u, 0x3f800000u, 0xff800000u, 0x3f800000u, 0xff800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x3f800000u, 0xbf800000u, 0x3f800000u, 0xbf800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x40800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xbfc00000u, 0x3fc00000u, 0xbfc00000u, 0xbfc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x40000000u, 0x40000000u, 0x40000000u, 0xc0000000u, 0x2c4dbc00u, 0xac4dbc00u, 0x2c4dbc00u, 0xaccdbc00u, 0x2bcdbc00u, 0xad4dbc00u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xbfc00000u, 0x3fc00000u, 0xbfc00000u, 0xbfc00000u, 0xff800000u, 0xff800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x40000000u, 0x40000000u, 0x40000000u, 0xc0000000u, 0x7f800000u, 0xff800000u, 0x3f800000u, 0xff800000u, 0x3f800000u, 0xff800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xc0000000u, 0xc0000000u, 0x40000000u, 0x40000000u, 0xbf000000u, 0xbf000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x40000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xbf800000u, 0xbf800000u, 0x3f800000u, 0x3f800000u, 0xbe800000u, 0xbe800000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xbf800000u, 0x3f800000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x40800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xbe800000u, 0x3e800000u, 0xbe800000u, 0xbe800000u, 0xbe800000u, 0x3e800000u, 0x3e800000u, 0xbf000000u, 0x3e000000u, 0xbf800000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x3fc00000u, 0xbfc00000u, 0x3fc00000u, 0xbfc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead7fffu, 0xdead7fffu, 0x0000ffffu, 0xdead7fffu}, + {0x00000001u, 0x80000001u, 0x00000001u, 0x80000001u, 0x80000001u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead1d9au, 0xdead1d9au, 0x00009d9au, 0xdead1d9au}, + {0xbf19999au, 0x3f19999au, 0xbf19999au, 0xbf19999au, 0x80002000u, 0x00002000u, 0x00002000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead7c00u, 0xdead7c00u, 0x0000fc00u, 0xdead7c00u}, + {0xffffffe2u, 0xffffffe2u, 0x7fffffe2u, 0x7fffffe2u, 0x7fffffe2u, 0x7fffffe2u, 0x00000000u, 0x7fffffe2u, 0x00000000u, 0xffffffe2u, 0x00000000u, 0xdead0000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdeadbc00u, 0xdead3c00u, 0x0000bc00u, 0xdead3c00u}, + {0x80003c00u, 0x00003c00u, 0x80003c00u, 0x80003c00u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdeadfc00u, 0xdead7c00u, 0x0000fc00u, 0xdead7c00u}, + {0x80007e01u, 0x00007e01u, 0x80007e01u, 0x80007e01u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdeadfe01u, 0xdead7e01u, 0x0000fe01u, 0xdead7e01u}, + {0x807fffffu, 0x807fffffu, 0x007fffffu, 0x007fffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x7fc00001u, 0xffc00001u, 0x7fc00001u, 0xffc00001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0xc0000000u, 0xc0000000u, 0x40000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x7fc00001u, 0xffc00001u, 0x7fc00001u, 0xffc00001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u}, + {0x00007e01u, 0x80007e01u, 0x00007e01u, 0x80007e01u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xdead8000u, 0xdead0000u, 0x00008000u, 0xdead0000u} +}; +constexpr std::uint32_t Checked[32] = {0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x7fffu, 0x780fu, 0x780fu, 0x780fu, 0x780fu, 0x780fu, 0x780fu, 0x780fu, 0x780fu, 0x780fu, 0x7fffu, 0x7fffu, 0x780fu, 0x780fu, 0x780fu, 0x780fu, 0x780fu}; +constexpr const char* Names[15] = { + "v_cndmask_b32_dpp v10, -v4, v5, vcc_lo", + "v_cndmask_b32_dpp v11, |v4|, -|v5|, vcc_lo", + "v_cndmask_b32_e64 v12, -v4, |v5|, vcc_lo", + "v_cndmask_b32_sdwa v13, -|v4|, -v5, vcc_lo", + "v_mul_legacy_f32_dpp v14, -v4, |v5|", + "v_mul_legacy_f32_sdwa v15, -|v4|, -v5", + "v_mul_legacy_f32_sdwa v16, v4, v5 clamp", + "v_mul_legacy_f32_sdwa v17, v4, -v5 mul:2", + "v_mul_legacy_f32_e64 v18, v4, v5 clamp div:2", + "v_mul_legacy_f32_e64 v19, -v4, v5 mul:4", + "v_mul_legacy_f32_e64 v20, |v4|, -|v5| clamp", + "v_ldexp_f16_e64 v21, -v4, v5", + "v_ldexp_f16_dpp v22, |v4|, v5", + "v_ldexp_f16_sdwa v23, -|v4|, v5", + "v_ldexp_f16_e64 v24, |v4|, v5", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop2 modifier forms: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 15; ++i) { + if ((Checked[tid] >> i) & 1u) Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop2 modifier forms tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3AlignOpSel.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3AlignOpSel.cpp new file mode 100644 index 000000000..8a07028cb --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3AlignOpSel.cpp @@ -0,0 +1,190 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e140307, 0xd54e000a, 0x041a0b04, + 0x7e160307, 0xd54e080b, 0x041a0b04, 0x7e180307, 0xd54e100c, 0x041a0b04, 0x7e1a0307, 0xd54e180d, + 0x041a0b04, 0x7e1c0307, 0xd54e200e, 0x041a0b04, 0x7e1e0307, 0xd54e400f, 0x041a0b04, 0x7e200307, + 0xd54e4810, 0x041a0b04, 0x7e220307, 0xd54e7811, 0x041a0b04, 0x7e240307, 0xd54f0012, 0x041a0b04, + 0x7e260307, 0xd54f0813, 0x041a0b04, 0x7e280307, 0xd54f1014, 0x041a0b04, 0x7e2a0307, 0xd54f1815, + 0x041a0b04, 0x7e2c0307, 0xd54f2016, 0x041a0b04, 0x7e2e0307, 0xd54f4017, 0x041a0b04, 0x7e300307, + 0xd54f5018, 0x041a0b04, 0x7e320307, 0xd54f7819, 0x041a0b04, 0xe0781000, 0x80010a03, 0xe0781010, + 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0xc0001234u, 0x73ab4876u, 0x04082408u, 0x7253edc6u}, + {0x05b4d756u, 0xedccc000u, 0x1f210011u, 0x24aa1734u}, + {0x8000fffeu, 0xad0ee1e7u, 0x00ffffffu, 0x80018000u}, + {0xdc8cf93au, 0xe3653b50u, 0x21030300u, 0xffff0002u}, + {0x400000ffu, 0xfffe4000u, 0xff001101u, 0x80018000u}, + {0xf8800b51u, 0xfffe4000u, 0x20018024u, 0x7ffffffeu}, + {0x80017fffu, 0x8000edccu, 0x031f0180u, 0xb63f2c62u}, + {0xc0001234u, 0x80010002u, 0x01203f03u, 0x0001fffeu}, + {0x89e7d15fu, 0x986e86cbu, 0x01010300u, 0x30b17d0bu}, + {0x8001fffeu, 0x97491e23u, 0x0300241fu, 0x800000ffu}, + {0x00011234u, 0x7fffc000u, 0x3f040300u, 0x1b98fbe4u}, + {0xf542441du, 0x00007fffu, 0x03803f3fu, 0x7fff00ffu}, + {0x80000001u, 0x03e0d681u, 0x00041007u, 0xc000fffeu}, + {0x2d7c5048u, 0x728a6fcfu, 0x3f043f80u, 0x00007fffu}, + {0xedccc000u, 0xff769e37u, 0x0701043fu, 0x36971e1bu}, + {0x8001edccu, 0x62dc08d6u, 0x2100100fu, 0x5f27ff08u}, + {0xf589d99au, 0xddd4a054u, 0x0f10101fu, 0x2891dd3cu}, + {0xafdd8733u, 0x6be49ee7u, 0x3f101101u, 0xedccffffu}, + {0x4b1e943eu, 0x205bc308u, 0x000f0f11u, 0xffff8000u}, + {0x3fc2a908u, 0x482ea760u, 0xff072103u, 0xfb019df4u}, + {0x00000000u, 0x40008001u, 0x24030001u, 0x80004000u}, + {0x00010001u, 0x8bcce7cdu, 0x03212007u, 0x0001fffeu}, + {0x0000c000u, 0xedcc0000u, 0x0303003fu, 0x000100ffu}, + {0x4000ffffu, 0x40004000u, 0x0003ffffu, 0x0001edccu}, + {0xc0000002u, 0xa8f51ac5u, 0x0f113f20u, 0xa2592b9du}, + {0xe1594dc4u, 0x95468325u, 0x01212407u, 0xb6125e0cu}, + {0xfffefffeu, 0xffff7fffu, 0x20ff2024u, 0x131e2d48u}, + {0x80004000u, 0x4f1c9ce2u, 0x0fff3f07u, 0x065479e4u}, + {0xc77d357fu, 0x000100ffu, 0x0f032104u, 0x33080a1du}, + {0xc0000000u, 0x88ebd524u, 0x08211110u, 0x64bd7a63u}, + {0x4000ffffu, 0x2141c6d1u, 0xff203f00u, 0x53935c55u}, + {0xb8d0c65du, 0x369a9ad7u, 0x0011201fu, 0x5937c1f0u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x3473ab48u, 0x473ab487u, 0x3473ab48u, 0x473ab487u, 0x3473ab48u, 0xab480000u, 0xb4870000u, 0xb4870000u, 0x73ab4876u, 0x73ab4876u, 0x73ab4876u, 0x73ab4876u, 0x73ab4876u, 0x48760000u, 0x48760000u, 0x48760000u}, + {0x6bab76e6u, 0xedccc000u, 0x76e66000u, 0x0b69aeadu, 0x6bab76e6u, 0x76e60000u, 0xc0000000u, 0xaead0000u, 0x56edccc0u, 0xedccc000u, 0x56edccc0u, 0xb4d756edu, 0x56edccc0u, 0xccc00000u, 0xccc00000u, 0x56ed0000u}, + {0x0001fffdu, 0x0001fffdu, 0x0001fffdu, 0xad0ee1e7u, 0x0001fffdu, 0xfffd0000u, 0xfffd0000u, 0xe1e70000u, 0x00fffeadu, 0x00fffeadu, 0x00fffeadu, 0xad0ee1e7u, 0x00fffeadu, 0xfead0000u, 0xfead0000u, 0xe1e70000u}, + {0xe3653b50u, 0x5c6ca76au, 0x5c6ca76au, 0x71b29da8u, 0xe3653b50u, 0x3b500000u, 0xa76a0000u, 0x9da80000u, 0xe3653b50u, 0x8cf93ae3u, 0x8cf93ae3u, 0x3ae3653bu, 0xe3653b50u, 0x3b500000u, 0x3ae30000u, 0x653b0000u}, + {0xffff2000u, 0x007fffffu, 0xfffe4000u, 0x800001ffu, 0xffff2000u, 0x20000000u, 0xffff0000u, 0x01ff0000u, 0xfffffe40u, 0xfffffe40u, 0xfffe4000u, 0x0000ffffu, 0xfffffe40u, 0xfe400000u, 0x40000000u, 0xffff0000u}, + {0x1fffe400u, 0xfffe4000u, 0xffff2000u, 0xfffe4000u, 0x1fffe400u, 0xe4000000u, 0x40000000u, 0x40000000u, 0xfffe4000u, 0xfffe4000u, 0x51fffe40u, 0xfffe4000u, 0xfffe4000u, 0x40000000u, 0xfe400000u, 0x40000000u}, + {0x8000edccu, 0xc00076e6u, 0x0002ffffu, 0xf0001db9u, 0x8000edccu, 0xedcc0000u, 0x76e60000u, 0x1db90000u, 0x8000edccu, 0xff8000edu, 0x017fff80u, 0x017fff80u, 0x8000edccu, 0xedcc0000u, 0xff800000u, 0xff800000u}, + {0x90002000u, 0x80002469u, 0x80010002u, 0x40008001u, 0x90002000u, 0x20000000u, 0x24690000u, 0x80010000u, 0x00123480u, 0x00123480u, 0x80010002u, 0x34800100u, 0x00123480u, 0x34800000u, 0x00020000u, 0x01000000u}, + {0x986e86cbu, 0xf30dd0d9u, 0xcc374365u, 0xcc374365u, 0x986e86cbu, 0x86cb0000u, 0xd0d90000u, 0x43650000u, 0x986e86cbu, 0xe7d15f98u, 0x5f986e86u, 0x5f986e86u, 0x986e86cbu, 0x86cb0000u, 0x6e860000u, 0x6e860000u}, + {0x0003fffdu, 0xe97491e2u, 0x97491e23u, 0xd2e923c4u, 0x0003fffdu, 0xfffd0000u, 0x91e20000u, 0x23c40000u, 0x01fffe97u, 0x97491e23u, 0x97491e23u, 0x01fffe97u, 0x01fffe97u, 0xfe970000u, 0x1e230000u, 0xfe970000u}, + {0x7fffc000u, 0x8ffff800u, 0x47fffc00u, 0x00022468u, 0x7fffc000u, 0xc0000000u, 0xf8000000u, 0x24680000u, 0x7fffc000u, 0x0112347fu, 0x7fffc000u, 0x0112347fu, 0x7fffc000u, 0xc0000000u, 0xc0000000u, 0x347f0000u}, + {0xea84883au, 0xea84883au, 0x00007fffu, 0xa0000fffu, 0xea84883au, 0x883a0000u, 0x883a0000u, 0x0fff0000u, 0x42441d00u, 0x42441d00u, 0x00007fffu, 0x42441d00u, 0x42441d00u, 0x1d000000u, 0x7fff0000u, 0x1d000000u}, + {0x0207c1adu, 0x000103e0u, 0x103e0d68u, 0x03e0d681u, 0x0207c1adu, 0xc1ad0000u, 0x03e00000u, 0xd6810000u, 0x00000103u, 0x03e0d681u, 0x03e0d681u, 0x03e0d681u, 0x00000103u, 0x01030000u, 0xd6810000u, 0xd6810000u}, + {0x728a6fcfu, 0x5af8a090u, 0x8728a6fcu, 0x5af8a090u, 0x728a6fcfu, 0x6fcf0000u, 0xa0900000u, 0xa0900000u, 0x728a6fcfu, 0x7c504872u, 0x728a6fcfu, 0x7c504872u, 0x728a6fcfu, 0x6fcf0000u, 0x6fcf0000u, 0x48720000u}, + {0xdb998001u, 0x0ff769e3u, 0x7fbb4f1bu, 0x01feed3cu, 0xdb998001u, 0x80010000u, 0x69e30000u, 0xed3c0000u, 0xccc000ffu, 0xff769e37u, 0x00ff769eu, 0xccc000ffu, 0xccc000ffu, 0x00ff0000u, 0x769e0000u, 0x00ff0000u}, + {0xdb98c5b8u, 0xedcc62dcu, 0x62dc08d6u, 0x316e046bu, 0xdb98c5b8u, 0xc5b80000u, 0x62dc0000u, 0x046b0000u, 0x01edcc62u, 0x62dc08d6u, 0x62dc08d6u, 0xcc62dc08u, 0x01edcc62u, 0xcc620000u, 0x08d60000u, 0xdc080000u}, + {0xeb13b335u, 0xd99addd4u, 0xd99addd4u, 0xb335bba9u, 0xeb13b335u, 0xb3350000u, 0xddd40000u, 0xbba90000u, 0x89d99addu, 0xddd4a054u, 0xddd4a054u, 0x89d99addu, 0x89d99addu, 0x9add0000u, 0xa0540000u, 0x9add0000u}, + {0xb5f24f73u, 0xc399b5f2u, 0x87336be4u, 0x5fbb0e66u, 0xb5f24f73u, 0x4f730000u, 0xb5f20000u, 0x0e660000u, 0x336be49eu, 0x336be49eu, 0x6be49ee7u, 0xdd87336bu, 0x336be49eu, 0xe49e0000u, 0x9ee70000u, 0x336b0000u}, + {0x4a1f102du, 0x287c40b7u, 0x287c40b7u, 0x205bc308u, 0x4a1f102du, 0x102d0000u, 0x40b70000u, 0xc3080000u, 0x3e205bc3u, 0x1e943e20u, 0x1e943e20u, 0x205bc308u, 0x3e205bc3u, 0x5bc30000u, 0x3e200000u, 0xc3080000u}, + {0x0905d4ecu, 0x241753b0u, 0x10905d4eu, 0x7f855210u, 0x0905d4ecu, 0xd4ec0000u, 0x53b00000u, 0x52100000u, 0xc2a90848u, 0x08482ea7u, 0xc2a90848u, 0xc2a90848u, 0xc2a90848u, 0x08480000u, 0x08480000u, 0x08480000u}, + {0x20004000u, 0x40008001u, 0x08001000u, 0x04000800u, 0x20004000u, 0x40000000u, 0x80010000u, 0x08000000u, 0x00400080u, 0x40008001u, 0x00000040u, 0x40008001u, 0x00400080u, 0x00800000u, 0x00400000u, 0x80010000u}, + {0x031799cfu, 0x8bcce7cdu, 0xc5e673e6u, 0x31799cf9u, 0x031799cfu, 0x99cf0000u, 0xe7cd0000u, 0x9cf90000u, 0x0100018bu, 0x8bcce7cdu, 0x018bcce7u, 0x0100018bu, 0x0100018bu, 0x018b0000u, 0xcce70000u, 0x018b0000u}, + {0x00018001u, 0xedcc0000u, 0x1db98000u, 0x1db98000u, 0x00018001u, 0x80010000u, 0x00000000u, 0x80000000u, 0x00c000edu, 0xedcc0000u, 0x00c000edu, 0x00c000edu, 0x00c000edu, 0x00ed0000u, 0x00ed0000u, 0x00ed0000u}, + {0x8001fffeu, 0x8001fffeu, 0xe8000800u, 0x40004000u, 0x8001fffeu, 0xfffe0000u, 0xfffe0000u, 0x40000000u, 0x00ffff40u, 0x00ffff40u, 0x00ffff40u, 0x40004000u, 0x00ffff40u, 0xff400000u, 0xff400000u, 0x40000000u}, + {0xa8f51ac5u, 0x80000005u, 0x0001547au, 0x000551eau, 0xa8f51ac5u, 0x1ac50000u, 0x00050000u, 0x51ea0000u, 0xa8f51ac5u, 0x000002a8u, 0x02a8f51au, 0x000002a8u, 0xa8f51ac5u, 0x1ac50000u, 0xf51a0000u, 0x02a80000u}, + {0x892a8d06u, 0x49546832u, 0x4aa34192u, 0x4aa34192u, 0x892a8d06u, 0x8d060000u, 0x68320000u, 0x41920000u, 0x594dc495u, 0x95468325u, 0xc4954683u, 0xc4954683u, 0x594dc495u, 0xc4950000u, 0x46830000u, 0x46830000u}, + {0xeffff7ffu, 0xffff7fffu, 0xfffdfffdu, 0xffff7fffu, 0xeffff7ffu, 0xf7ff0000u, 0x7fff0000u, 0x7fff0000u, 0xffff7fffu, 0xffff7fffu, 0xfefffeffu, 0xffff7fffu, 0xffff7fffu, 0x7fff0000u, 0xfeff0000u, 0x7fff0000u}, + {0x009e3939u, 0x00008000u, 0x00008000u, 0x80009e39u, 0x009e3939u, 0x39390000u, 0x80000000u, 0x9e390000u, 0x0040004fu, 0x0040004fu, 0x0040004fu, 0x0040004fu, 0x0040004fu, 0x004f0000u, 0x004f0000u, 0x004f0000u}, + {0xf000100fu, 0x8000807fu, 0xe000201fu, 0x6afe0002u, 0xf000100fu, 0x100f0000u, 0x807f0000u, 0x00020000u, 0x000100ffu, 0x7f000100u, 0x7d357f00u, 0x7d357f00u, 0x000100ffu, 0x00ff0000u, 0x7f000000u, 0x7f000000u}, + {0x000088ebu, 0x00004475u, 0x4475ea92u, 0x0088ebd5u, 0x000088ebu, 0x88eb0000u, 0x44750000u, 0xebd50000u, 0x88ebd524u, 0x0088ebd5u, 0x0088ebd5u, 0x88ebd524u, 0x88ebd524u, 0xd5240000u, 0xebd50000u, 0xd5240000u}, + {0x2141c6d1u, 0x8001fffeu, 0x2141c6d1u, 0x8001fffeu, 0x2141c6d1u, 0xc6d10000u, 0xfffe0000u, 0xfffe0000u, 0x2141c6d1u, 0x00ffff21u, 0x2141c6d1u, 0x00ffff21u, 0x2141c6d1u, 0xc6d10000u, 0xc6d10000u, 0xff210000u}, + {0x71a18cbau, 0x369a9ad7u, 0x632e9b4du, 0x369a9ad7u, 0x71a18cbau, 0x8cba0000u, 0x9ad70000u, 0x9ad70000u, 0xd0c65d36u, 0x369a9ad7u, 0x5d369a9au, 0x369a9ad7u, 0xd0c65d36u, 0x5d360000u, 0x9a9a0000u, 0x9ad70000u} +}; +constexpr const char* Names[16] = { + "v_alignbit_b32 v10, v4, v5, v6 op_sel:[0,0,0,0]", + "v_alignbit_b32 v11, v4, v5, v6 op_sel:[1,0,0,0]", + "v_alignbit_b32 v12, v4, v5, v6 op_sel:[0,1,0,0]", + "v_alignbit_b32 v13, v4, v5, v6 op_sel:[1,1,0,0]", + "v_alignbit_b32 v14, v4, v5, v6 op_sel:[0,0,1,0]", + "v_alignbit_b32 v15, v4, v5, v6 op_sel:[0,0,0,1]", + "v_alignbit_b32 v16, v4, v5, v6 op_sel:[1,0,0,1]", + "v_alignbit_b32 v17, v4, v5, v6 op_sel:[1,1,1,1]", + "v_alignbyte_b32 v18, v4, v5, v6 op_sel:[0,0,0,0]", + "v_alignbyte_b32 v19, v4, v5, v6 op_sel:[1,0,0,0]", + "v_alignbyte_b32 v20, v4, v5, v6 op_sel:[0,1,0,0]", + "v_alignbyte_b32 v21, v4, v5, v6 op_sel:[1,1,0,0]", + "v_alignbyte_b32 v22, v4, v5, v6 op_sel:[0,0,1,0]", + "v_alignbyte_b32 v23, v4, v5, v6 op_sel:[0,0,0,1]", + "v_alignbyte_b32 v24, v4, v5, v6 op_sel:[0,1,0,1]", + "v_alignbyte_b32 v25, v4, v5, v6 op_sel:[1,1,1,1]", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 align op sel: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 align op sel tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3CubeClamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3CubeClamp.cpp new file mode 100644 index 000000000..07e25ea2b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3CubeClamp.cpp @@ -0,0 +1,207 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd544000a, 0x041a0b04, 0xd545000b, + 0x041a0b04, 0xd546000c, 0x041a0b04, 0xd547000d, 0x041a0b04, 0xd544800e, 0x041a0b04, 0xd545800f, + 0x041a0b04, 0xd5468010, 0x041a0b04, 0xd5478011, 0x041a0b04, 0xd5440212, 0x241a0b04, 0xd5450113, + 0xc41a0b04, 0xd5460414, 0x641a0b04, 0xd5470115, 0x841a0b04, 0xd5448016, 0xe41a0b04, 0xd5458417, + 0x441a0b04, 0xd5468118, 0x841a0b04, 0xd5478219, 0x241a0b04, 0xe0781000, 0x80010a03, 0xe0781010, + 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x3f800000u, 0xbf800000u, 0x3f800000u, 0x00000000u}, + {0x7f800001u, 0x3f000000u, 0x3f000000u, 0x00000000u}, + {0x40000000u, 0x3f000000u, 0xff812345u, 0x00000000u}, + {0x80000007u, 0x7fc00000u, 0x7fc00000u, 0x00000000u}, + {0x00000007u, 0xffc00000u, 0x7f800001u, 0x00000000u}, + {0xff800000u, 0x80000007u, 0x00000007u, 0x00000000u}, + {0x7fc00000u, 0x00000007u, 0xff800000u, 0x00000000u}, + {0xbf000000u, 0x00000000u, 0x80000007u, 0x00000000u}, + {0x00000007u, 0x00000007u, 0x7f800001u, 0x00000000u}, + {0x00000007u, 0x7f800000u, 0x80000007u, 0x00000000u}, + {0x00000007u, 0x00000007u, 0x80000007u, 0x00000000u}, + {0xff800000u, 0xff800000u, 0x40000000u, 0x00000000u}, + {0x40000000u, 0xc0200000u, 0xc0200000u, 0x00000000u}, + {0x7fc00000u, 0xff800000u, 0x80000000u, 0x00000000u}, + {0xc0200000u, 0x7f800000u, 0x7f800000u, 0x00000000u}, + {0xbf000000u, 0xbf000000u, 0x3f000000u, 0x00000000u}, + {0x7fc00000u, 0xff800000u, 0xc0200000u, 0x00000000u}, + {0x00000000u, 0xff800000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xff800000u, 0xbf000000u, 0x00000000u}, + {0xff800000u, 0x00000000u, 0xff800000u, 0x00000000u}, + {0xff800000u, 0x7f800000u, 0x00000000u, 0x00000000u}, + {0xff800000u, 0x7f800000u, 0xbf000000u, 0x00000000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x00000000u}, + {0xff800000u, 0xff800000u, 0xbf000000u, 0x00000000u}, + {0xff800000u, 0xbf000000u, 0xbf000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xffc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xff812345u, 0x00000000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x40800000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x40800000u, 0x3f800000u, 0x3f800000u, 0x40000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x40800000u, 0xbf800000u, 0xbf800000u, 0xc0000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0x00000000u, 0xbf000000u, 0xbf000000u, 0x7f800001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x3f000000u, 0x7f800001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x7f812345u, 0xbf000000u, 0x40800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0xff812345u, 0x3f000000u, 0x40800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xffc00000u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0xff800001u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f800001u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x00000007u, 0x00000007u, 0xff800000u, 0x3f800000u, 0x00000007u, 0x00000007u, 0x00000000u, 0x00000000u, 0x00000007u, 0x80000007u, 0x7f800000u, 0x00000000u, 0x00000007u, 0x00000007u, 0x3f800000u}, + {0x00000000u, 0x7f800000u, 0x80000007u, 0x7fc00000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800000u, 0x00000007u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x80000007u, 0x80000000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000007u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000007u, 0x00000000u, 0x3f800000u}, + {0x40000000u, 0x00000007u, 0x7f800001u, 0x00000000u, 0x3f800000u, 0x00000007u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000007u, 0x7f800001u, 0x00000000u, 0x3f800000u, 0x00000007u, 0x00000000u, 0x00000000u}, + {0x40000000u, 0x00000007u, 0x80000007u, 0x7f800000u, 0x3f800000u, 0x00000007u, 0x00000000u, 0x3f800000u, 0x40000000u, 0x00000007u, 0x80000007u, 0x7f800000u, 0x3f800000u, 0x00000007u, 0x00000007u, 0x3f800000u}, + {0x40800000u, 0x00000007u, 0x80000007u, 0x00000000u, 0x3f800000u, 0x00000007u, 0x00000000u, 0x00000000u, 0x40800000u, 0x00000007u, 0x00000007u, 0x00000000u, 0x3f800000u, 0x00000007u, 0x00000000u, 0x00000000u}, + {0x40400000u, 0xff800000u, 0xc0000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x7f800000u, 0x40000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u}, + {0x40a00000u, 0xc0000000u, 0x40200000u, 0xc0a00000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x40a00000u, 0x40000000u, 0xc0200000u, 0x40a00000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xff800000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x40800000u, 0xc0200000u, 0xff800000u, 0x7f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x40800000u, 0xc0200000u, 0x7f800000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x40800000u, 0xbf000000u, 0x3f000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x3f800000u, 0x40800000u, 0xbf000000u, 0xbf000000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x3f800000u}, + {0x00000000u, 0x40200000u, 0x7f800000u, 0x7fc00000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0xc0200000u, 0xff800000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x40400000u, 0x00000000u, 0x80000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x00000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x40400000u, 0x00000000u, 0x3f000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x3f000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x40a00000u, 0x7f800000u, 0x80000000u, 0xff800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x40a00000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x40000000u, 0xff800000u, 0x00000000u, 0x7f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x40000000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x40000000u, 0xff800000u, 0xbf000000u, 0x7f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x40000000u, 0x7f800000u, 0xbf000000u, 0x7f800000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x3f800000u}, + {0x40a00000u, 0x7f800000u, 0x7f800000u, 0xff800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x40a00000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x40400000u, 0xff800000u, 0x3f000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x40000000u, 0x7f800000u, 0x3f000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x3f800000u, 0xbf000000u, 0x3f000000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0xbf000000u, 0xbf000000u, 0x7f800000u, 0x00000000u, 0x3f000000u, 0x3f000000u, 0x3f800000u}, + {0x40800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x40800000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x40000000u, 0x00000000u, 0x7fc00000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x7fc00000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x40000000u, 0x00000000u, 0xffc00000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x7fc00000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x40000000u, 0x00000000u, 0x7f800001u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x7f800001u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x40000000u, 0x00000000u, 0xff812345u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x00000000u, 0x7f812345u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u} +}; +constexpr const char* Names[16] = { + "v_cubeid_f32 v10, v4, v5, v6", + "v_cubesc_f32 v11, v4, v5, v6", + "v_cubetc_f32 v12, v4, v5, v6", + "v_cubema_f32 v13, v4, v5, v6", + "v_cubeid_f32 v14, v4, v5, v6 clamp", + "v_cubesc_f32 v15, v4, v5, v6 clamp", + "v_cubetc_f32 v16, v4, v5, v6 clamp", + "v_cubema_f32 v17, v4, v5, v6 clamp", + "v_cubeid_f32 v18, -v4, |v5|, v6", + "v_cubesc_f32 v19, |v4|, -v5, -v6", + "v_cubetc_f32 v20, -v4, -v5, |v6|", + "v_cubema_f32 v21, |v4|, v5, -v6", + "v_cubeid_f32 v22, -v4, -v5, -v6 clamp", + "v_cubesc_f32 v23, v4, -v5, |v6| clamp", + "v_cubetc_f32 v24, |v4|, v5, -v6 clamp", + "v_cubema_f32 v25, -v4, |v5|, v6 clamp", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 cube clamp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span code) { + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + const std::span code(Code); + const auto result = Compile(device, code); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void CheckOmodRefused(AgcDriver::VulkanDevice& device) { + for (const std::uint32_t opcode : {0x144u, 0x145u, 0x146u, 0x147u}) { + alignas(256) const std::array code{0xd400000au | (opcode << 16u), 0x0c1a0b04u, 0xbf810000u}; + std::string refusal; + try { + static_cast(Compile(device, code)); + } catch (const std::exception& error) { + refusal = error.what(); + } + Require(refusal.find("VOP3 source modifiers are not implemented") != std::string::npos, "VOP3 opcode " + Hex(opcode) + " with omod was not refused"); + } +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + CheckOmodRefused(*device); + std::puts("vop3 cube clamp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Integer16TernaryOpSel.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Integer16TernaryOpSel.cpp new file mode 100644 index 000000000..ba6169955 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Integer16TernaryOpSel.cpp @@ -0,0 +1,190 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e140307, 0xd755280a, 0x041a0b04, + 0x7e160307, 0xd756500b, 0x041a0b04, 0x7e180307, 0xd752180c, 0x041a0b04, 0x7e1a0307, 0xd753600d, + 0x041a0b04, 0x7e1c0307, 0xd759380e, 0x041a0b04, 0x7e1e0307, 0xd759000f, 0x041a0b04, 0x7e200307, + 0xd7564810, 0x041a0b04, 0x7e220307, 0xd7527011, 0x041a0b04, 0x7e240307, 0xd7583012, 0x041a0b04, + 0x7e260307, 0xd7554013, 0x041a0b04, 0x7e280307, 0xd7530814, 0x041a0b04, 0x7e2a0307, 0xd7595815, + 0x041a0b04, 0x7e2c0307, 0xd7560016, 0x041a0b04, 0x7e2e0307, 0xd7520017, 0x041a0b04, 0x7e300307, + 0xd7551018, 0x041a0b04, 0x7e320307, 0xd7596019, 0x041a0b04, 0xe0781000, 0x80010a03, 0xe0781010, + 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x4000fffeu, 0x1eede1dcu, 0xc1dc7ed2u, 0xffff7fffu}, + {0xedccc000u, 0x00ff00ffu, 0x123400ffu, 0x00020002u}, + {0x9fe407c8u, 0x40004000u, 0x40000001u, 0xedccffffu}, + {0x00ff00ffu, 0x00ff00ffu, 0x80018001u, 0x12340001u}, + {0xffff00ffu, 0x24f75609u, 0x0000edccu, 0xa1ae691du}, + {0x40000000u, 0x75916032u, 0x00f77715u, 0x9e9c8053u}, + {0x78bd5cc4u, 0x80010000u, 0x000000ffu, 0xc000edccu}, + {0x123400ffu, 0x00011234u, 0x400000ffu, 0x4000c000u}, + {0x00011234u, 0x7fff1234u, 0x12341234u, 0x00fffffeu}, + {0x1234c000u, 0xc0000001u, 0x00021234u, 0x5eda0014u}, + {0x12347fffu, 0x40e8d5edu, 0x00ff0002u, 0x67604bebu}, + {0x00028001u, 0x00ff8001u, 0x00ff00ffu, 0x9b20386du}, + {0x2027553bu, 0x12341234u, 0x7fff1234u, 0xffffffffu}, + {0x00001234u, 0x12340000u, 0x024e6fa2u, 0x80252ce1u}, + {0x49ff7159u, 0xffff8000u, 0xffff8000u, 0x80010002u}, + {0xc0007fffu, 0x0000c000u, 0x00004000u, 0x80010001u}, + {0x00ff0000u, 0x7fff0000u, 0x7cfc2ed5u, 0x8001edccu}, + {0xc0007fffu, 0xedcc7fffu, 0x00021234u, 0xffff00ffu}, + {0x00021234u, 0x0002edccu, 0x00000001u, 0x9138a73cu}, + {0x3e2f6da7u, 0x00d72a89u, 0x0f50aef1u, 0x13022bdcu}, + {0x00ff8000u, 0xfcbf3c4bu, 0x8001c000u, 0x5df76ae5u}, + {0x00008001u, 0x8001edccu, 0x0001edccu, 0x5dd86332u}, + {0xdf891833u, 0xbe5937f8u, 0xfffe1234u, 0x542edc5fu}, + {0x0d9f3670u, 0x55502ea4u, 0x0002fffeu, 0xbcda0e3bu}, + {0x00020001u, 0x7e9868d8u, 0xc67c9920u, 0xf336da84u}, + {0xc000edccu, 0xfffe00ffu, 0x62aa0f64u, 0x80011234u}, + {0x80001234u, 0x401b25b8u, 0x3b404f9du, 0x9ec28382u}, + {0x0001ffffu, 0xdd91f71cu, 0xf13510b5u, 0x00020000u}, + {0x40004000u, 0x7fff8001u, 0x22c35d87u, 0xc0000000u}, + {0x2a18f40fu, 0x6bbd2914u, 0xc000fffeu, 0xedcc0002u}, + {0xa7f488d4u, 0x00ff4000u, 0x000100ffu, 0xedccfffeu}, + {0xfffefffeu, 0x7fff00ffu, 0x0002fffeu, 0xd816b1beu} +}; +constexpr std::uint32_t Expected[32][16] = { + {0xffff4000u, 0xfffe7fffu, 0xffff1eedu, 0xc1dc7fffu, 0xffff4000u, 0xffffe1dcu, 0xe1dc7fffu, 0xc1dc7fffu, 0xfffffffeu, 0x7ed27fffu, 0xffff4000u, 0x40007fffu, 0xfffffffeu, 0xffffe1dcu, 0xffff7ed2u, 0xe1dc7fffu}, + {0x00021234u, 0xc0000002u, 0x0002edccu, 0x00ff0002u, 0x00021234u, 0x000200ffu, 0xedcc0002u, 0xc0000002u, 0x000200ffu, 0x00ff0002u, 0x000200ffu, 0x00ff0002u, 0x0002c000u, 0x0002c000u, 0x000200ffu, 0x12340002u}, + {0xedcc4000u, 0x4000ffffu, 0xedcc9fe4u, 0x07c8ffffu, 0xedcc4000u, 0xedcc07c8u, 0x9fe4ffffu, 0x07c8ffffu, 0xedcc4000u, 0x4000ffffu, 0xedcc0001u, 0x4000ffffu, 0xedcc4000u, 0xedcc0001u, 0xedcc4000u, 0x4000ffffu}, + {0x123400ffu, 0x80010001u, 0x12348001u, 0x00ff0001u, 0x123400ffu, 0x123400ffu, 0x80010001u, 0x80010001u, 0x123400ffu, 0x00ff0001u, 0x123400ffu, 0x00ff0001u, 0x12348001u, 0x12348001u, 0x123400ffu, 0x00ff0001u}, + {0xa1ae5609u, 0xedcc691du, 0xa1aeedccu, 0x0000691du, 0xa1ae24f7u, 0xa1ae5609u, 0xffff691du, 0x0000691du, 0xa1ae00ffu, 0x5609691du, 0xa1ae5609u, 0xedcc691du, 0xa1aeedccu, 0xa1aeedccu, 0xa1ae24f7u, 0x00ff691du}, + {0x9e9c6032u, 0x77158053u, 0x9e9c4000u, 0x00008053u, 0x9e9c4000u, 0x9e9c6032u, 0x77158053u, 0x00008053u, 0x9e9c00f7u, 0x77158053u, 0x9e9c4000u, 0x75918053u, 0x9e9c7715u, 0x9e9c0000u, 0x9e9c7715u, 0x00f78053u}, + {0xc00078bdu, 0x8001edccu, 0xc0008001u, 0x0000edccu, 0xc00078bdu, 0xc00000ffu, 0x78bdedccu, 0x8001edccu, 0xc0000000u, 0x5cc4edccu, 0xc0000000u, 0x78bdedccu, 0xc0005cc4u, 0xc0000000u, 0xc0005cc4u, 0x0000edccu}, + {0x40004000u, 0x00ffc000u, 0x40000001u, 0x00ffc000u, 0x40001234u, 0x400000ffu, 0x1234c000u, 0x0001c000u, 0x400000ffu, 0x1234c000u, 0x400000ffu, 0x00ffc000u, 0x40001234u, 0x400000ffu, 0x400000ffu, 0x1234c000u}, + {0x00ff1234u, 0x7ffffffeu, 0x00ff0001u, 0x1234fffeu, 0x00ff1234u, 0x00ff1234u, 0x1234fffeu, 0x1234fffeu, 0x00ff1234u, 0x1234fffeu, 0x00ff0001u, 0x1234fffeu, 0x00ff1234u, 0x00ff1234u, 0x00ff7fffu, 0x1234fffeu}, + {0x5eda1234u, 0xc0000014u, 0x5edac000u, 0x00010014u, 0x5eda1234u, 0x5eda1234u, 0x12340014u, 0xc0000014u, 0x5edac000u, 0x12340014u, 0x5eda0001u, 0x12340014u, 0x5edac000u, 0x5edac000u, 0x5eda1234u, 0x00020014u}, + {0x67601234u, 0x7fff4bebu, 0x67600002u, 0x00ff4bebu, 0x67601234u, 0x67607fffu, 0xd5ed4bebu, 0x00ff4bebu, 0x676040e8u, 0x7fff4bebu, 0x67600002u, 0x12344bebu, 0x6760d5edu, 0x6760d5edu, 0x67607fffu, 0x7fff4bebu}, + {0x9b2000ffu, 0x8001386du, 0x9b200002u, 0x00ff386du, 0x9b2000ffu, 0x9b208001u, 0x8001386du, 0x8001386du, 0x9b2000ffu, 0x00ff386du, 0x9b200002u, 0x00ff386du, 0x9b208001u, 0x9b208001u, 0x9b2000ffu, 0x8001386du}, + {0xffff7fffu, 0x553bffffu, 0xffff1234u, 0x1234ffffu, 0xffff2027u, 0xffff1234u, 0x2027ffffu, 0x1234ffffu, 0xffff553bu, 0x553bffffu, 0xffff1234u, 0x1234ffffu, 0xffff553bu, 0xffff1234u, 0xffff553bu, 0x553bffffu}, + {0x8025024eu, 0x6fa22ce1u, 0x80250000u, 0x00002ce1u, 0x8025024eu, 0x80251234u, 0x6fa22ce1u, 0x024e2ce1u, 0x80251234u, 0x6fa22ce1u, 0x80250000u, 0x12342ce1u, 0x80256fa2u, 0x80250000u, 0x80256fa2u, 0x024e2ce1u}, + {0x800149ffu, 0xffff0002u, 0x80018000u, 0x71590002u, 0x8001ffffu, 0x80018000u, 0x80000002u, 0xffff0002u, 0x8001ffffu, 0x71590002u, 0x800149ffu, 0x80000002u, 0x80018000u, 0x80018000u, 0x80017159u, 0x80000002u}, + {0x80010000u, 0x7fff0001u, 0x8001c000u, 0x00000001u, 0x80010000u, 0x80017fffu, 0xc0000001u, 0x00000001u, 0x80010000u, 0x7fff0001u, 0x80014000u, 0x40000001u, 0x8001c000u, 0x8001c000u, 0x80017fffu, 0x7fff0001u}, + {0x80017cfcu, 0x7fffedccu, 0x800100ffu, 0x0000edccu, 0x80017cfcu, 0x80010000u, 0x2ed5edccu, 0x0000edccu, 0x80017cfcu, 0x2ed5edccu, 0x80010000u, 0x2ed5edccu, 0x80012ed5u, 0x80010000u, 0x80017fffu, 0x0000edccu}, + {0xffff7fffu, 0xedcc00ffu, 0xffffc000u, 0x000200ffu, 0xffffc000u, 0xffff7fffu, 0xc00000ffu, 0xedcc00ffu, 0xffff0002u, 0x7fff00ffu, 0xffff1234u, 0xc00000ffu, 0xffff7fffu, 0xffff1234u, 0xffff7fffu, 0x7fff00ffu}, + {0x91380002u, 0x1234a73cu, 0x91380001u, 0x0000a73cu, 0x91380002u, 0x91381234u, 0xedcca73cu, 0x0000a73cu, 0x91380002u, 0x1234a73cu, 0x91380001u, 0x0002a73cu, 0x9138edccu, 0x9138edccu, 0x91381234u, 0x1234a73cu}, + {0x13023e2fu, 0xaef12bdcu, 0x1302aef1u, 0x0f502bdcu, 0x13020f50u, 0x13026da7u, 0xaef12bdcu, 0x00d72bdcu, 0x13020f50u, 0x6da72bdcu, 0x13022a89u, 0x3e2f2bdcu, 0x1302aef1u, 0x1302aef1u, 0x13026da7u, 0x2a892bdcu}, + {0x5df73c4bu, 0xfcbf6ae5u, 0x5df7c000u, 0x3c4b6ae5u, 0x5df78001u, 0x5df78000u, 0xc0006ae5u, 0x80006ae5u, 0x5df78001u, 0x3c4b6ae5u, 0x5df700ffu, 0xc0006ae5u, 0x5df7c000u, 0x5df78000u, 0x5df7fcbfu, 0x80006ae5u}, + {0x5dd80001u, 0xedcc6332u, 0x5dd88001u, 0x00016332u, 0x5dd80001u, 0x5dd8edccu, 0xedcc6332u, 0x80016332u, 0x5dd88001u, 0xedcc6332u, 0x5dd80000u, 0x80016332u, 0x5dd8edccu, 0x5dd88001u, 0x5dd8edccu, 0x80016332u}, + {0x542e37f8u, 0xbe59dc5fu, 0x542ebe59u, 0x1833dc5fu, 0x542edf89u, 0x542e1833u, 0xdf89dc5fu, 0xbe59dc5fu, 0x542efffeu, 0x37f8dc5fu, 0x542e1234u, 0xbe59dc5fu, 0x542e37f8u, 0x542e1234u, 0x542e1833u, 0x37f8dc5fu}, + {0xbcda2ea4u, 0xfffe0e3bu, 0xbcdafffeu, 0x00020e3bu, 0xbcda0d9fu, 0xbcda3670u, 0xfffe0e3bu, 0x00020e3bu, 0xbcda3670u, 0x36700e3bu, 0xbcda0d9fu, 0x55500e3bu, 0xbcdafffeu, 0xbcdafffeu, 0xbcda5550u, 0x2ea40e3bu}, + {0xf33668d8u, 0x9920da84u, 0xf3369920u, 0x0001da84u, 0xf3367e98u, 0xf33668d8u, 0x9920da84u, 0xc67cda84u, 0xf3360001u, 0x68d8da84u, 0xf3360002u, 0x7e98da84u, 0xf3369920u, 0xf3369920u, 0xf3367e98u, 0x68d8da84u}, + {0x800162aau, 0xfffe1234u, 0x8001c000u, 0x00ff1234u, 0x8001c000u, 0x80010f64u, 0xc0001234u, 0xedcc1234u, 0x8001fffeu, 0x0f641234u, 0x800100ffu, 0xc0001234u, 0x8001edccu, 0x8001edccu, 0x80010f64u, 0x62aa1234u}, + {0x9ec23b40u, 0x4f9d8382u, 0x9ec28000u, 0x12348382u, 0x9ec2401bu, 0x9ec225b8u, 0x80008382u, 0x12348382u, 0x9ec23b40u, 0x4f9d8382u, 0x9ec225b8u, 0x4f9d8382u, 0x9ec24f9du, 0x9ec21234u, 0x9ec24f9du, 0x25b88382u}, + {0x00020001u, 0xffff0000u, 0x0002dd91u, 0xf1350000u, 0x0002dd91u, 0x0002f71cu, 0xf71c0000u, 0xdd910000u, 0x0002f135u, 0x10b50000u, 0x00020001u, 0x10b50000u, 0x0002ffffu, 0x0002f71cu, 0x000210b5u, 0xf71c0000u}, + {0xc0004000u, 0x7fff0000u, 0xc0004000u, 0x22c30000u, 0xc0004000u, 0xc0005d87u, 0x80010000u, 0x22c30000u, 0xc0004000u, 0x5d870000u, 0xc0004000u, 0x5d870000u, 0xc0008001u, 0xc0008001u, 0xc0007fffu, 0x40000000u}, + {0xedcc2a18u, 0xfffe0002u, 0xedccfffeu, 0x29140002u, 0xedcc6bbdu, 0xedccf40fu, 0xfffe0002u, 0xc0000002u, 0xedccf40fu, 0x29140002u, 0xedcc2914u, 0x6bbd0002u, 0xedccfffeu, 0xedccf40fu, 0xedcc6bbdu, 0xc0000002u}, + {0xedcc4000u, 0x88d4fffeu, 0xedcca7f4u, 0x0001fffeu, 0xedcc00ffu, 0xedcc4000u, 0xa7f4fffeu, 0x88d4fffeu, 0xedcc0001u, 0x4000fffeu, 0xedcc00ffu, 0x00fffffeu, 0xedcc88d4u, 0xedcc88d4u, 0xedcc00ffu, 0x4000fffeu}, + {0xd81600ffu, 0xfffeb1beu, 0xd816fffeu, 0x0002b1beu, 0xd8167fffu, 0xd816fffeu, 0xfffeb1beu, 0xfffeb1beu, 0xd8160002u, 0x00ffb1beu, 0xd81600ffu, 0xfffeb1beu, 0xd816fffeu, 0xd816fffeu, 0xd8167fffu, 0x00ffb1beu} +}; +constexpr const char* Names[16] = { + "v_max3_i16 v10, v4, v5, v6 op_sel:[1,0,1,0]", + "v_max3_u16 v11, v4, v5, v6 op_sel:[0,1,0,1]", + "v_min3_i16 v12, v4, v5, v6 op_sel:[1,1,0,0]", + "v_min3_u16 v13, v4, v5, v6 op_sel:[0,0,1,1]", + "v_med3_u16 v14, v4, v5, v6 op_sel:[1,1,1,0]", + "v_med3_u16 v15, v4, v5, v6 op_sel:[0,0,0,0]", + "v_max3_u16 v16, v4, v5, v6 op_sel:[1,0,0,1]", + "v_min3_i16 v17, v4, v5, v6 op_sel:[0,1,1,1]", + "v_med3_i16 v18, v4, v5, v6 op_sel:[0,1,1,0]", + "v_max3_i16 v19, v4, v5, v6 op_sel:[0,0,0,1]", + "v_min3_u16 v20, v4, v5, v6 op_sel:[1,0,0,0]", + "v_med3_u16 v21, v4, v5, v6 op_sel:[1,1,0,1]", + "v_max3_u16 v22, v4, v5, v6 op_sel:[0,0,0,0]", + "v_min3_i16 v23, v4, v5, v6 op_sel:[0,0,0,0]", + "v_max3_i16 v24, v4, v5, v6 op_sel:[0,1,0,0]", + "v_med3_u16 v25, v4, v5, v6 op_sel:[0,0,1,1]", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 integer16 ternary op sel: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 integer16 ternary op sel tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClamp.cpp new file mode 100644 index 000000000..4bc1207d3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClamp.cpp @@ -0,0 +1,188 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd543800a, 0x041a0b04, 0xd542800b, + 0x041a0b04, 0xd740800c, 0x041a0b04, 0xd75e800d, 0x041a0b04, 0xd773800e, 0x041a0b04, 0xd775800f, + 0x041a0b04, 0xd55a8010, 0x041a0b04, 0xd55b8011, 0x041a0b04, 0xd55c8012, 0x041a0b04, 0xd55d8013, + 0x041a0b04, 0xd5718014, 0x041a0b04, 0x7e2a0307, 0xd740d815, 0x041a0b04, 0xd75eb016, 0x041a0b04, + 0xd7758817, 0x041a0b04, 0xd7739018, 0x041a0b04, 0x7e320307, 0xd75ef819, 0x041a0b04, 0xe0781000, + 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00000001u, 0x0000ffffu}, + {0x00000000u, 0x00007fffu, 0xffffffffu, 0x0000fff0u}, + {0x01010101u, 0x01010101u, 0xffffffffu, 0xff00ff00u}, + {0x00000001u, 0xff00ff00u, 0x80000000u, 0x7fffffffu}, + {0x00000000u, 0x00000000u, 0x506377feu, 0x09418c53u}, + {0x00000000u, 0x00000000u, 0xad078efau, 0x00000002u}, + {0x00000000u, 0x00000001u, 0x00000002u, 0x293b6032u}, + {0x00000000u, 0x00000001u, 0x0000fff0u, 0x00ff00ffu}, + {0x00000000u, 0x00000001u, 0x00ff00ffu, 0xdbd16e44u}, + {0x00000000u, 0x00000001u, 0x671b91d7u, 0xb1836b5au}, + {0x00000000u, 0x00000001u, 0xb51b0523u, 0x54b074b8u}, + {0x00000000u, 0x00000001u, 0xff0ec345u, 0x00007fffu}, + {0x00000000u, 0x00000002u, 0x00000000u, 0xfefefefeu}, + {0x00000000u, 0x0000007fu, 0x00000002u, 0xcee69ed0u}, + {0x00000000u, 0x0000007fu, 0x12345678u, 0x40778188u}, + {0x00000000u, 0x0000007fu, 0x1bf35776u, 0x12345678u}, + {0x00000000u, 0x0000007fu, 0x29c0ba3bu, 0x48e98a0au}, + {0x00000000u, 0x0000007fu, 0x9d299c7bu, 0x39cdd549u}, + {0x00000000u, 0x0000007fu, 0x9ff9949eu, 0x00ffffffu}, + {0x00000000u, 0x00000080u, 0x000000ffu, 0x12345678u}, + {0x00000000u, 0x00000080u, 0x00008000u, 0x80000001u}, + {0x00000000u, 0x00000080u, 0x0000fff0u, 0xae15989du}, + {0x00000000u, 0x00000080u, 0x0000ffffu, 0x00000080u}, + {0x00000000u, 0x00000080u, 0x80000001u, 0x0000007fu}, + {0x00000000u, 0x00000080u, 0xaf4a9e38u, 0xd42344f4u}, + {0x00000000u, 0x00000080u, 0xea4d8c45u, 0x00625618u}, + {0x00000000u, 0x00000080u, 0xfd4fb563u, 0xff00ff00u}, + {0x00000000u, 0x000000ffu, 0x00000001u, 0x310733d9u}, + {0x00000000u, 0x000000ffu, 0x2f3bacc0u, 0xffff0000u}, + {0x00000000u, 0x000000ffu, 0x85f55b11u, 0xffff0000u}, + {0x00000000u, 0x000000ffu, 0xff00ff00u, 0x9fc408e0u}, + {0x00000000u, 0x000000ffu, 0xffff0000u, 0xffff0000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x0001ffffu, 0x00000000u, 0x00000001u, 0x00000001u, 0x0000ffffu}, + {0xffffffffu, 0xffffffffu, 0x0000ffffu, 0x0000ffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xfffffff0u, 0x0000ffffu, 0xffffffffu, 0xffffffffu, 0xfffffff0u}, + {0xffffffffu, 0x7fffffffu, 0x0000ffffu, 0x00007fffu, 0xffffffffu, 0x00010200u, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffff00u, 0x00007fffu, 0x00010200u, 0xffffffffu, 0x7fffff00u}, + {0x8000ff00u, 0x8000ff00u, 0x0000ff00u, 0x0000ff00u, 0x8000ff00u, 0x80000000u, 0x800001ffu, 0x81ff0000u, 0x8001fdffu, 0xffffffffu, 0x800001feu, 0x0000ffffu, 0x00008000u, 0x80000000u, 0x8000ff00u, 0x8000ffffu}, + {0x506377feu, 0x506377feu, 0x000077feu, 0x000077feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x506377feu, 0x77fe8c53u, 0x00005063u, 0x506377feu, 0x506377feu, 0x50638c53u}, + {0xad078efau, 0xad078efau, 0x00008efau, 0x00008efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0xad078efau, 0x8efa0002u, 0x0000ad07u, 0xad078efau, 0xad078efau, 0xad070002u}, + {0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000003u, 0x00010002u, 0x00000003u, 0x00000003u, 0x00000003u, 0x00026032u, 0x00000000u, 0x00000002u, 0x00000002u, 0x00006032u}, + {0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff1u, 0x0001fff0u, 0x0000fff1u, 0x0000fff1u, 0x0000fff1u, 0xfff000ffu, 0x00000000u, 0x0000fff0u, 0x0000fff0u, 0x000000ffu}, + {0x00ff00ffu, 0x00ff00ffu, 0x000000ffu, 0x000000ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff0100u, 0x010000ffu, 0x00ff0100u, 0x00ff0100u, 0x00ff0100u, 0x00ff6e44u, 0x000000ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff6e44u}, + {0x671b91d7u, 0x671b91d7u, 0x000091d7u, 0x000091d7u, 0x671b91d7u, 0x671b91d7u, 0x671b91d8u, 0x671c91d7u, 0x671b91d8u, 0x671b91d8u, 0x671b91d8u, 0x91d76b5au, 0x0000671bu, 0x671b91d7u, 0x671b91d7u, 0x671b6b5au}, + {0xb51b0523u, 0xb51b0523u, 0x00000523u, 0x00000523u, 0xb51b0523u, 0xb51b0523u, 0xb51b0524u, 0xb51c0523u, 0xb51b0524u, 0xb51b0524u, 0xb51b0524u, 0x052374b8u, 0x0000b51bu, 0xb51b0523u, 0xb51b0523u, 0xb51b74b8u}, + {0xff0ec345u, 0xff0ec345u, 0x0000c345u, 0x0000c345u, 0xff0ec345u, 0xff0ec345u, 0xff0ec346u, 0xff0fc345u, 0xff0ec346u, 0xff0ec346u, 0xff0ec346u, 0xc3457fffu, 0x0000ff0eu, 0xff0ec345u, 0xff0ec345u, 0xff0e7fffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00020000u, 0x00000002u, 0x00000002u, 0x00000002u, 0x0000fefeu, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000fefeu}, + {0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000081u, 0x007f0002u, 0x00000081u, 0x00000081u, 0x00000081u, 0x00029ed0u, 0x00000000u, 0x00000002u, 0x00000002u, 0x00009ed0u}, + {0x12345678u, 0x12345678u, 0x00005678u, 0x00005678u, 0x12345678u, 0x12345678u, 0x123456f7u, 0x12b35678u, 0x123456f7u, 0x123456f7u, 0x123456f7u, 0x56788188u, 0x00001234u, 0x12345678u, 0x12345678u, 0x12348188u}, + {0x1bf35776u, 0x1bf35776u, 0x00005776u, 0x00005776u, 0x1bf35776u, 0x1bf35776u, 0x1bf357f5u, 0x1c725776u, 0x1bf357f5u, 0x1bf357f5u, 0x1bf357f5u, 0x57765678u, 0x00001bf3u, 0x1bf35776u, 0x1bf35776u, 0x1bf35678u}, + {0x29c0ba3bu, 0x29c0ba3bu, 0x0000ba3bu, 0x0000ba3bu, 0x29c0ba3bu, 0x29c0ba3bu, 0x29c0babau, 0x2a3fba3bu, 0x29c0babau, 0x29c0babau, 0x29c0babau, 0xba3b8a0au, 0x000029c0u, 0x29c0ba3bu, 0x29c0ba3bu, 0x29c08a0au}, + {0x9d299c7bu, 0x9d299c7bu, 0x00009c7bu, 0x00009c7bu, 0x9d299c7bu, 0x9d299c7bu, 0x9d299cfau, 0x9da89c7bu, 0x9d299cfau, 0x9d299cfau, 0x9d299cfau, 0x9c7bd549u, 0x00009d29u, 0x9d299c7bu, 0x9d299c7bu, 0x9d29d549u}, + {0x9ff9949eu, 0x9ff9949eu, 0x0000949eu, 0x0000949eu, 0x9ff9949eu, 0x9ff9949eu, 0x9ff9951du, 0xa078949eu, 0x9ff9951du, 0x9ff9951du, 0x9ff9951du, 0x949effffu, 0x00009ff9u, 0x9ff9949eu, 0x9ff9949eu, 0x9ff9ffffu}, + {0x000000ffu, 0x000000ffu, 0x000000ffu, 0x000000ffu, 0x000000ffu, 0x000000ffu, 0x0000017fu, 0x008000ffu, 0x0000017fu, 0x0000017fu, 0x0000017fu, 0x00ff5678u, 0x00000000u, 0x000000ffu, 0x000000ffu, 0x00005678u}, + {0x00008000u, 0x00008000u, 0x00008000u, 0x00008000u, 0x00008000u, 0x00008000u, 0x00008080u, 0x00808000u, 0x00008080u, 0x00008080u, 0x00008080u, 0x80000001u, 0x00000000u, 0x00008000u, 0x00008000u, 0x00000001u}, + {0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x0000fff0u, 0x00010070u, 0x0080fff0u, 0x00010070u, 0x00010070u, 0x00010070u, 0xfff0989du, 0x00000000u, 0x0000fff0u, 0x0000fff0u, 0x0000989du}, + {0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0000ffffu, 0x0001007fu, 0x0080ffffu, 0x0001007fu, 0x0001007fu, 0x0001007fu, 0xffff0080u, 0x00000000u, 0x0000ffffu, 0x0000ffffu, 0x00000080u}, + {0x80000001u, 0x80000001u, 0x00000001u, 0x00000001u, 0x80000001u, 0x80000001u, 0x80000081u, 0x80800001u, 0x80000081u, 0x80000081u, 0x80000081u, 0x0001007fu, 0x00008000u, 0x80000001u, 0x80000001u, 0x8000007fu}, + {0xaf4a9e38u, 0xaf4a9e38u, 0x00009e38u, 0x00009e38u, 0xaf4a9e38u, 0xaf4a9e38u, 0xaf4a9eb8u, 0xafca9e38u, 0xaf4a9eb8u, 0xaf4a9eb8u, 0xaf4a9eb8u, 0x9e3844f4u, 0x0000af4au, 0xaf4a9e38u, 0xaf4a9e38u, 0xaf4a44f4u}, + {0xea4d8c45u, 0xea4d8c45u, 0x00008c45u, 0x00008c45u, 0xea4d8c45u, 0xea4d8c45u, 0xea4d8cc5u, 0xeacd8c45u, 0xea4d8cc5u, 0xea4d8cc5u, 0xea4d8cc5u, 0x8c455618u, 0x0000ea4du, 0xea4d8c45u, 0xea4d8c45u, 0xea4d5618u}, + {0xfd4fb563u, 0xfd4fb563u, 0x0000b563u, 0x0000b563u, 0xfd4fb563u, 0xfd4fb563u, 0xfd4fb5e3u, 0xfdcfb563u, 0xfd4fb5e3u, 0xfd4fb5e3u, 0xfd4fb5e3u, 0xb563ff00u, 0x0000fd4fu, 0xfd4fb563u, 0xfd4fb563u, 0xfd4fff00u}, + {0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000001u, 0x00000100u, 0x00ff0001u, 0x00000100u, 0x00000100u, 0x00000100u, 0x000133d9u, 0x00000000u, 0x00000001u, 0x00000001u, 0x000033d9u}, + {0x2f3bacc0u, 0x2f3bacc0u, 0x0000acc0u, 0x0000acc0u, 0x2f3bacc0u, 0x2f3bacc0u, 0x2f3badbfu, 0x303aacc0u, 0x2f3badbfu, 0x2f3badbfu, 0x2f3badbfu, 0xacc00000u, 0x00002f3bu, 0x2f3bacc0u, 0x2f3bacc0u, 0x2f3b0000u}, + {0x85f55b11u, 0x85f55b11u, 0x00005b11u, 0x00005b11u, 0x85f55b11u, 0x85f55b11u, 0x85f55c10u, 0x86f45b11u, 0x85f55c10u, 0x85f55c10u, 0x85f55c10u, 0x5b110000u, 0x000085f5u, 0x85f55b11u, 0x85f55b11u, 0x85f50000u}, + {0xff00ff00u, 0xff00ff00u, 0x0000ff00u, 0x0000ff00u, 0xff00ff00u, 0xff00ff00u, 0xff00ffffu, 0xffffff00u, 0xff00ffffu, 0xff00ffffu, 0xff00ffffu, 0xff0008e0u, 0x0000ff00u, 0xff00ff00u, 0xff00ff00u, 0xff0008e0u}, + {0xffff0000u, 0xffff0000u, 0x00000000u, 0x00000000u, 0xffff0000u, 0xffff0000u, 0xffff00ffu, 0xffffffffu, 0xffff00ffu, 0xffff00ffu, 0xffff00ffu, 0x00000000u, 0x0000ffffu, 0xffff0000u, 0xffff0000u, 0xffff0000u} +}; +constexpr const char* Names[16] = { + "mad_u32_u24", + "mad_i32_i24", + "mad_u16", + "mad_i16", + "mad_u32_u16", + "mad_i32_i16", + "sad_u8", + "sad_hi_u8", + "sad_u16", + "sad_u32", + "msad_u8", + "mad_u16 op_sel", + "mad_i16 op_sel", + "mad_i32_i16 op_sel", + "mad_u32_u16 op_sel", + "mad_i16 op_sel", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 integer clamp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 integer clamp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClampWide.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClampWide.cpp new file mode 100644 index 000000000..adc82c757 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3IntegerClampWide.cpp @@ -0,0 +1,187 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd572800a, 0x041a0d04, 0xd573800c, + 0x041a0d04, 0xd575800e, 0x04120d04, 0xd7558012, 0x041a0b04, 0xd7568013, 0x041a0b04, 0xd7528014, + 0x041a0b04, 0xd7538015, 0x041a0b04, 0xd7588016, 0x041a0b04, 0xd7598017, 0x041a0b04, 0xd54e8018, + 0x041a0b04, 0xd54f8019, 0x041a0b04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, + 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000000u, 0x00000000u, 0x00800000u, 0x0000ffffu}, + {0x0000007fu, 0xe3718038u, 0xfffffffeu, 0xfd7c84feu}, + {0x0000007fu, 0xfffffffeu, 0x0000007fu, 0xfffffffeu}, + {0xfffffffeu, 0x00000002u, 0xb93201bfu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3ff51543u, 0x8ca197b1u}, + {0x00000000u, 0x00000001u, 0x007fffffu, 0x29813b74u}, + {0x00000000u, 0x00000001u, 0x00ff00ffu, 0x00007fffu}, + {0x00000000u, 0x00000001u, 0x01010101u, 0xa48d7f36u}, + {0x00000000u, 0x00000001u, 0x1ef5acc3u, 0x5d780492u}, + {0x00000000u, 0x00000001u, 0x41f8bfb7u, 0x3f57a9d3u}, + {0x00000000u, 0x00000001u, 0xfefefefeu, 0x0000007fu}, + {0x00000000u, 0x00000002u, 0x00ffffffu, 0x80000000u}, + {0x00000000u, 0x00000002u, 0x7351a059u, 0x00007fffu}, + {0x00000000u, 0x00000002u, 0xcb50b50fu, 0x7fffffffu}, + {0x00000000u, 0x0000007fu, 0x0000ffffu, 0x01010101u}, + {0x00000000u, 0x0000007fu, 0x0ac13011u, 0xffff0000u}, + {0x00000000u, 0x0000007fu, 0x4b88f735u, 0x000000ffu}, + {0x00000000u, 0x0000007fu, 0xffffffffu, 0x0000fff0u}, + {0x00000000u, 0x00000080u, 0x00000000u, 0x00ffffffu}, + {0x00000000u, 0x00000080u, 0xffffffffu, 0xde945808u}, + {0x00000000u, 0x000000ffu, 0x000000ffu, 0x00000080u}, + {0x00000000u, 0x000000ffu, 0x7fffffffu, 0xca9c52c9u}, + {0x00000000u, 0x000000ffu, 0x80000001u, 0x0000007fu}, + {0x00000000u, 0x000000ffu, 0xffff0000u, 0x00007fffu}, + {0x00000000u, 0x000000ffu, 0xffffffffu, 0xffffffffu}, + {0x00000000u, 0x00007fffu, 0x01010101u, 0xcff6ab04u}, + {0x00000000u, 0x00007fffu, 0x44e9da39u, 0x63548e6fu}, + {0x00000000u, 0x00007fffu, 0x4944fa2au, 0xf5524d50u}, + {0x00000000u, 0x00007fffu, 0xa1dfdb1eu, 0xa9e05a10u}, + {0x00000000u, 0x00007fffu, 0xffff0000u, 0x01010101u}, + {0x00000000u, 0x00007fffu, 0xffffffffu, 0x0000007fu}, + {0x00000000u, 0x0000fff0u, 0x00000080u, 0x01010101u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x01000080u, 0x0080ffffu, 0x01000080u, 0x0080ffffu, 0x00000080u, 0x00000080u, 0x00800080u, 0x0001007fu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0xffff8841u, 0xffffffffu, 0xffff8841u, 0x000003fbu, 0xe37183fbu, 0xffffffffu, 0xfd7c87d0u, 0x0000007fu, 0x0000fffeu, 0x00008038u, 0x0000007fu, 0x0000fffeu, 0x00008038u, 0x000001ffu, 0x007fe371u}, + {0x017d007fu, 0xffffffffu, 0x007f007fu, 0xffffffffu, 0x0000007fu, 0xffffffffu, 0x000000feu, 0xffffffffu, 0x0000007fu, 0x0000fffeu, 0x0000fffeu, 0x0000007fu, 0x0000007fu, 0x0000007fu, 0x000000ffu, 0x00007fffu}, + {0xbbf4040fu, 0x012c0227u, 0xbbf4040fu, 0x012c0227u, 0xffffffffu, 0x000002c4u, 0xb93203e6u, 0x0000012cu, 0x000001bfu, 0x0000fffeu, 0x0000fffeu, 0x00000002u, 0x00000002u, 0x000001bfu, 0xfffffffcu, 0xfffffe00u}, + {0x418116cfu, 0x8e2d993du, 0x418116cfu, 0x8e2d993du, 0x0000018cu, 0x0000018cu, 0x3ff516cfu, 0x8ca1993du, 0x00001543u, 0x00001543u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x02fdffffu, 0x2bfd3df0u, 0x02fcffffu, 0x2bfd3df0u, 0x0000027du, 0x0000027eu, 0x0080027bu, 0x29813df0u, 0x00000001u, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x02fe02fdu, 0x01ff81fcu, 0x02fd02fdu, 0x01fe81fcu, 0x000001feu, 0x000001ffu, 0x00ff02fcu, 0x000081fdu, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x01040105u, 0xa4907f39u, 0x01040105u, 0xa4907f39u, 0x00000004u, 0x00000004u, 0x01010104u, 0xa48d7f39u, 0x00000101u, 0x00000101u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x2176af45u, 0x5ff90713u, 0x2176af45u, 0x5ff90713u, 0x00000282u, 0x00000282u, 0x1ef5af44u, 0x5d780713u, 0x00000001u, 0x0000acc3u, 0x0000acc3u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x44a6c266u, 0x4205ac81u, 0x44a6c266u, 0x4205ac81u, 0x000002afu, 0x000002afu, 0x41f8c265u, 0x3f57ac81u, 0x00000001u, 0x0000bfb7u, 0x0000bfb7u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0x03f70476u, 0xffffffffu, 0x03f70476u, 0x000003f8u, 0x000003f8u, 0xfeff02f5u, 0x00000476u, 0x00000001u, 0x0000fefeu, 0x0000fefeu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x03feffffu, 0x82fb02fbu, 0x03fcffffu, 0x82fb02fbu, 0x000002fdu, 0x000002ffu, 0x010002fau, 0x800002fbu, 0x00000002u, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000000u}, + {0x750ca216u, 0x01bb81bau, 0x750ca216u, 0x01bb81bau, 0x000001bdu, 0x000001bdu, 0x7351a214u, 0x000081bau, 0x00000002u, 0x0000a059u, 0x0000a059u, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000000u}, + {0xcd2db6eeu, 0x81dcffffu, 0xcd2db6eeu, 0x81dcffffu, 0x000001dfu, 0x000001dfu, 0xcb50b6ecu, 0x800001dcu, 0x00000002u, 0x0000b50fu, 0x0000b50fu, 0x00000000u, 0x00000000u, 0x00000002u, 0x00000000u, 0x00000000u}, + {0x027dffffu, 0x0280037eu, 0x01feffffu, 0x028002ffu, 0x000001feu, 0x0000027du, 0x000101fdu, 0x01010280u, 0x0000007fu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0x0c38311du, 0xffff008du, 0x0c38311du, 0xffff008du, 0x0000010cu, 0x000001f6u, 0x0ac1309eu, 0xffff012bu, 0x00003011u, 0x00003011u, 0x00000000u, 0x00000000u, 0x0000007fu, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0x4d70f934u, 0x0180027fu, 0x4d70f934u, 0x0180027fu, 0x000001ffu, 0x00000267u, 0x4b88f8b5u, 0x0000027fu, 0x0000007fu, 0x0000f735u, 0x0000f735u, 0x00000000u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0x037dffffu, 0xffffffffu, 0x037dffffu, 0x000003fcu, 0x000003fcu, 0xffffffffu, 0x0001036du, 0x0000007fu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x0000007fu, 0x00000000u, 0x00000000u}, + {0x00800000u, 0x017fffffu, 0x00000000u, 0x00ffffffu, 0x00000000u, 0x00000080u, 0x00000000u, 0x00ffffffu, 0x00000080u, 0x00000080u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000080u, 0x00000080u}, + {0xffffffffu, 0xe2105b84u, 0xffffffffu, 0xe2105b84u, 0x000003fcu, 0x000003fcu, 0xffffffffu, 0xde945b84u, 0x00000080u, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000080u, 0x00000000u, 0x00000000u}, + {0x01fe01feu, 0x01fe027eu, 0x00ff01feu, 0x00ff017fu, 0x000000ffu, 0x000001feu, 0x000001feu, 0x0000017fu, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u}, + {0x837cffffu, 0xcd195546u, 0x837cffffu, 0xcd195546u, 0x0000037cu, 0x0000047cu, 0x8000027cu, 0xca9c5546u, 0x000000ffu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000000u, 0x00000000u}, + {0x80800082u, 0x018001ffu, 0x80800082u, 0x00810100u, 0x00000081u, 0x0000017fu, 0x80000082u, 0x00000100u, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x0000007fu, 0x00000000u}, + {0xffff01feu, 0x02fd80feu, 0xffff01feu, 0x01fe80feu, 0x000001feu, 0x000001feu, 0xffff00ffu, 0x000081fdu, 0x000000ffu, 0x000000ffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x000000ffu, 0x000000ffu}, + {0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu, 0x000003fcu, 0x000003fcu, 0xffffffffu, 0xffffffffu, 0x000000ffu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x000000ffu, 0x00000000u, 0x00000000u}, + {0x02020105u, 0xd174ac82u, 0x02020105u, 0xd174ac82u, 0x00000004u, 0x00008100u, 0x0101027fu, 0xcff6ac82u, 0x00007fffu, 0x00007fffu, 0x00000000u, 0x00000000u, 0x00000101u, 0x00000101u, 0x00003fffu, 0x0000007fu}, + {0x47a0dc79u, 0x64608fd3u, 0x47a0dc79u, 0x64608fd3u, 0x00000240u, 0x000082b6u, 0x44e9db9du, 0x63548f7bu, 0x00007fffu, 0x0000da39u, 0x0000da39u, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00000000u, 0x0000007fu}, + {0x4b62fbdbu, 0xf6054f65u, 0x4b62fbdbu, 0xf6054f65u, 0x000001b1u, 0x0000821du, 0x4944fc3fu, 0xf5524e03u, 0x00007fffu, 0x0000fa2au, 0x0000fa2au, 0x00000000u, 0x00000000u, 0x00007fffu, 0x0000001fu, 0x00000000u}, + {0xa415dd97u, 0xab235b4bu, 0xa415dd97u, 0xab235b4bu, 0x00000279u, 0x00008235u, 0xa1dfdc59u, 0xa9e05b53u, 0x00007fffu, 0x0000db1eu, 0x0000db1eu, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00000000u, 0x00000000u}, + {0xffff01feu, 0x037f0181u, 0xffff01feu, 0x02800181u, 0x000001feu, 0x000080feu, 0xffff0080u, 0x01010280u, 0x00007fffu, 0x00007fffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00007fffu}, + {0xffffffffu, 0x027e02fdu, 0xffffffffu, 0x027e02fdu, 0x000003fcu, 0x000082fcu, 0xffffffffu, 0x000002fdu, 0x00007fffu, 0x0000ffffu, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00007fffu, 0x00000000u, 0x00000000u}, + {0x01700100u, 0x03700370u, 0x00800100u, 0x01810181u, 0x00000080u, 0x00010070u, 0x00000100u, 0x01010181u, 0x00000080u, 0x0000fff0u, 0x0000fff0u, 0x00000000u, 0x00000000u, 0x00000080u, 0x0000fff0u, 0x0000fff0u} +}; +constexpr const char* Names[16] = { + "qsad_pk_u16_u8 0", + "qsad_pk_u16_u8 1", + "mqsad_pk_u16_u8 0", + "mqsad_pk_u16_u8 1", + "mqsad_u32_u8 0", + "mqsad_u32_u8 1", + "mqsad_u32_u8 2", + "mqsad_u32_u8 3", + "max3_i16", + "max3_u16", + "min3_i16", + "min3_u16", + "med3_i16", + "med3_u16", + "alignbit_b32", + "alignbyte_b32", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 integer clamp wide: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 integer clamp wide tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3LegacySourceModifiers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3LegacySourceModifiers.cpp new file mode 100644 index 000000000..5562b5295 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3LegacySourceModifiers.cpp @@ -0,0 +1,186 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e1402ff, 0xdeadbeef, 0x7e1602ff, + 0xdeadbeef, 0x7e1802ff, 0xdeadbeef, 0x7e1a02ff, 0xdeadbeef, 0x7e1c02ff, 0xdeadbeef, 0x7e1e02ff, + 0xdeadbeef, 0x7e2002ff, 0xdeadbeef, 0x7e2202ff, 0xdeadbeef, 0x7e2402ff, 0xdeadbeef, 0x7e2602ff, + 0xdeadbeef, 0x7e2802ff, 0xdeadbeef, 0x7e2a02ff, 0xdeadbeef, 0x7e2c02ff, 0xdeadbeef, 0x7e2e02ff, + 0xdeadbeef, 0x7e3002ff, 0xdeadbeef, 0x7e3202ff, 0xdeadbeef, 0xd550000a, 0x041a0b04, 0xd550000b, + 0x241a0b04, 0xd550000c, 0x441a0b04, 0xd550020d, 0x841a0b04, 0xd550070e, 0x641a0b04, 0xd550050f, + 0x841a0b04, 0xd55e0010, 0x041a0f04, 0xd55e0011, 0x241a0f04, 0xd55e0112, 0x041a0f04, 0xd55e0113, + 0x241a0f04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x3fc00000u, 0x00000000u, 0xbf800000u, 0x7e318ad6u}, + {0xc0000000u, 0x3fc00000u, 0x3fc00000u, 0x1b3a953cu}, + {0xff7ffffeu, 0x437e8000u, 0x7f800000u, 0x8511fd5bu}, + {0x3f19999au, 0x00000000u, 0x7f800000u, 0x4607d625u}, + {0x7fc00001u, 0x3fc00000u, 0x3f000000u, 0xe4fead80u}, + {0x40200000u, 0x7f800000u, 0x3f000002u, 0xb68c914bu}, + {0x437f8000u, 0x437e8000u, 0x7f7fffffu, 0x400224c0u}, + {0xffc00000u, 0xc0000000u, 0x7f7fffffu, 0xd9c578ddu}, + {0x7f7fffffu, 0xbf800000u, 0x7f61b1e6u, 0xc7c63fe1u}, + {0x7f800000u, 0x3fc00000u, 0xbf400000u, 0x00b09f63u}, + {0x7f800000u, 0x3f19999au, 0x7f800000u, 0x054367bau}, + {0xff800000u, 0x7f800000u, 0xff7fffffu, 0xdee7b644u}, + {0x7fc00001u, 0x7f800000u, 0xc9a27dd4u, 0xeac29dbfu}, + {0xffc00000u, 0x7f7fffffu, 0xb587728cu, 0x929cedc6u}, + {0x7f7fffffu, 0x3f800000u, 0x7f7fffffu, 0x034bd1bau}, + {0x7f7fffffu, 0xbf800000u, 0xff800000u, 0x88c035d3u}, + {0x0da24260u, 0x7f7fffffu, 0xd4ab4b77u, 0xc51bdf32u}, + {0x00000000u, 0x00000000u, 0xf149f2cau, 0x9531985du}, + {0x00000000u, 0x00000000u, 0x7f800000u, 0x36f675ccu}, + {0x00000000u, 0x3fc00000u, 0xbf000002u, 0xdbc496cbu}, + {0x00000000u, 0x7f800000u, 0x95e761d1u, 0x2e05319au}, + {0x7f800000u, 0x00000000u, 0x7f61b1e6u, 0x3173b8d9u}, + {0x7f7fffffu, 0x00000000u, 0xbf000000u, 0x933de2fcu}, + {0xbf400000u, 0xff7fffffu, 0xff800000u, 0x79f35099u}, + {0xbf800000u, 0x00000000u, 0xff7fffffu, 0x03213676u}, + {0x7f61b1e6u, 0x7fc00001u, 0xff7ffffeu, 0x7ace7351u}, + {0x3f800001u, 0x7fc00001u, 0xff800000u, 0xe3153518u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x110e2cb6u}, + {0x80000000u, 0x80000000u, 0x7f800001u, 0xf81e54ddu}, + {0x80000000u, 0xc0000000u, 0x80000000u, 0x8d118e37u}, + {0x80000000u, 0x7fc00001u, 0x437e8000u, 0x28541424u}, + {0x7f800000u, 0x00000000u, 0x7f800001u, 0xa4672c0cu} +}; +constexpr std::uint32_t Expected[32][10] = { + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xbf020000u, 0xbf000000u, 0xbf020000u, 0xbf000000u}, + {0xc0400000u, 0x40400000u, 0x40400000u, 0xff7fffffu, 0x40400000u, 0xff7fffffu, 0x3fc00000u, 0x3fc00002u, 0x3fc00002u, 0x3fc00000u}, + {0xff800000u, 0x7f800000u, 0x7f800000u, 0xff7fffffu, 0x7f800000u, 0xff7fffffu, 0x00800000u, 0xff800000u, 0xff800000u, 0x00800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xff7fffffu, 0x00000000u, 0xff7fffffu, 0x7f800100u, 0x7f800000u, 0x7f800100u, 0x7f800000u}, + {0x7fc00001u, 0xffc00001u, 0x7fc00001u, 0xff7fffffu, 0xffc00001u, 0xff7fffffu, 0x3f000000u, 0x3f000000u, 0x3f000000u, 0x3f000000u}, + {0x7f800000u, 0xff800000u, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x02000002u, 0x00000002u, 0x02000002u, 0x00000002u}, + {0x477e00c0u, 0xc77e00c0u, 0xc77e00c0u, 0xff7fffffu, 0x477e00c0u, 0xff7fffffu, 0x7f7fffffu, 0x7f7fff00u, 0x7f7fffffu, 0x7f7fff00u}, + {0xffc00000u, 0x7fc00000u, 0xffc00000u, 0xff7fffffu, 0xffc00000u, 0xff7fffffu, 0x7f7f00ffu, 0x7f7f00ffu, 0x7f7f00ffu, 0x7f7f00ffu}, + {0xff7fffffu, 0x7f7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x7f61ffe6u, 0x7f6100e6u, 0x7f61ffe6u, 0x7f6100e6u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x7f800000u, 0x7f800000u, 0xff7fffffu, 0xff400000u, 0x00400000u, 0xff400000u, 0x00400000u}, + {0x7f800000u, 0xff800000u, 0xff800000u, 0xff7fffffu, 0x7f800000u, 0xff7fffffu, 0x7fff0000u, 0x7f000000u, 0x7fff0000u, 0x7f000000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff800000u, 0xff7fffffu, 0xff7fffffu, 0xff7fff00u, 0xff7fffffu, 0xff7fffffu, 0xff7fff00u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x7fc00001u, 0xff7fffffu, 0xff7fffffu, 0x00a27dd4u, 0x00a27dd4u, 0x00a27dd4u, 0x00a27dd4u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xffc00000u, 0xff7fffffu, 0xff7fffffu, 0xb500728cu, 0xb500728cu, 0xb500728cu, 0xb500728cu}, + {0x7f7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x7fffffffu, 0x7f00ffffu, 0x7fffffffu, 0x7f00ffffu}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x7f7fffffu, 0x7f7fffffu, 0xff7fffffu, 0xff800000u, 0x00800000u, 0xff800000u, 0x00800000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x4da2425fu, 0xff7fffffu, 0xff7fffffu, 0xd4004b77u, 0xd4004b77u, 0xd4004b77u, 0xd4004b77u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xf14900cau, 0xf14900cau, 0xf14900cau, 0xf14900cau}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xff7fffffu, 0x00000000u, 0xff7fffffu, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0x00000002u, 0x00000002u, 0x00000002u, 0x00000002u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0xff7fffffu, 0xff7fffffu, 0x950061d1u, 0x950061d1u, 0x950061d1u, 0x950061d1u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xff7fffffu, 0x00000000u, 0xff7fffffu, 0x7f61ffe6u, 0x7f6100e6u, 0x7f61ffe6u, 0x7f6100e6u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xbf0000ffu, 0xbf000000u, 0xbf0000ffu, 0xbf000000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff3fffffu, 0xff7fffffu, 0xff7fffffu, 0xff800000u, 0xff800100u, 0xff800100u, 0xff800000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0xff7fffffu, 0xff00ffffu, 0xff01ffffu, 0xff01ffffu, 0xff00ffffu}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7ffffeu, 0xff7f00feu, 0xff7ffffeu, 0xff7f00feu}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff800001u, 0xff800000u, 0xff800001u, 0xff800000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x437e8000u, 0x437e8000u, 0x437e8000u, 0x437e8000u}, + {0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0xff7fffffu, 0x7f8000ffu, 0x7f800000u, 0x7f8000ffu, 0x7f800000u} +}; +constexpr std::uint32_t Checked[32] = {0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu, 0x03ffu}; +constexpr const char* Names[10] = { + "v_mullit_f32 v10, v4, v5, v6", + "v_mullit_f32 v11, -v4, v5, v6", + "v_mullit_f32 v12, v4, -v5, v6", + "v_mullit_f32 v13, v4, |v5|, -v6", + "v_mullit_f32 v14, -|v4|, -|v5|, |v6|", + "v_mullit_f32 v15, |v4|, v5, -|v6|", + "v_cvt_pk_u8_f32 v16, v4, v7, v6", + "v_cvt_pk_u8_f32 v17, -v4, v7, v6", + "v_cvt_pk_u8_f32 v18, |v4|, v7, v6", + "v_cvt_pk_u8_f32 v19, -|v4|, v7, v6", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 legacy source modifiers: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 10; ++i) { + if ((Checked[tid] >> i) & 1u) Expect(tid, out[i], Expected[tid][i], Names[i]); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 legacy source modifiers tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3MadClamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3MadClamp.cpp new file mode 100644 index 000000000..53aef39dc --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3MadClamp.cpp @@ -0,0 +1,167 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd577880a, 0x041a0b04, 0xd501000c, + 0x00210280, 0xd576880d, 0x041a0b04, 0xd501000f, 0x00210280, 0xe0701000, 0x80010a03, 0xe0701004, + 0x80010b03, 0xe0701008, 0x80010c03, 0xe070100c, 0x80010d03, 0xe0701010, 0x80010e03, 0xe0701014, + 0x80010f03, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x00000001u, 0x00000001u, 0xfffffffeu, 0xffffffffu}, + {0x00000001u, 0x00000001u, 0xffffffffu, 0xffffffffu}, + {0x00000002u, 0x00000001u, 0xfffffffeu, 0xffffffffu}, + {0x00000001u, 0x00000001u, 0xfffffffeu, 0x7fffffffu}, + {0x00000001u, 0x00000001u, 0xffffffffu, 0x7fffffffu}, + {0x00000001u, 0xffffffffu, 0x00000001u, 0x80000000u}, + {0x00000001u, 0xffffffffu, 0x00000000u, 0x80000000u}, + {0x00000002u, 0xffffffffu, 0x00000001u, 0x80000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0xffffffffu, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x00000000u, 0x40000000u}, + {0x80000000u, 0x80000000u, 0xffffffffu, 0x3fffffffu}, + {0x7fffffffu, 0x80000000u, 0x00000000u, 0xc0000000u}, + {0x7fffffffu, 0x80000000u, 0xffffffffu, 0xbfffffffu}, + {0x7fffffffu, 0x7fffffffu, 0xffffffffu, 0x7fffffffu}, + {0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu}, + {0x00000000u, 0x00000000u, 0xffffffffu, 0x7fffffffu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u}, + {0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000001u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x00000001u, 0x80000000u, 0xffffffffu, 0xffffffffu}, + {0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0xffffffffu, 0xffffffffu}, + {0x80000000u, 0x00000001u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00000001u, 0xffffffffu, 0xffffffffu}, + {0x80000000u, 0x00000001u, 0x00000000u, 0x80000000u}, + {0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}, + {0x80000000u, 0x80000001u, 0x00000000u, 0xc0000000u}, + {0x80000001u, 0xffffffffu, 0xffffffffu, 0x7fffffffu}, + {0xfffffffeu, 0xfffffffeu, 0x00000000u, 0x40000000u}, + {0x3ceb3ffdu, 0x97b75092u, 0x8b529b4au, 0x21636369u}, + {0x5eb561a4u, 0xea7b5bf5u, 0x9a9a80fdu, 0x795b929eu} +}; +constexpr std::uint32_t Expected[32][6] = { + {0xffffffffu, 0xffffffffu, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0xffffffffu, 0x7fffffffu, 0x00000000u, 0xffffffffu, 0x7fffffffu, 0x00000000u}, + {0xffffffffu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x00000001u, 0x00000000u, 0x80000001u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x00000001u, 0xffffffffu, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x00000001u, 0xffffffffu, 0x80000001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000001u, 0x00000000u, 0x00000000u, 0x00000001u, 0xfffffffeu, 0x00000000u}, + {0xffffffffu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0xffffffffu, 0x7fffffffu, 0x00000000u, 0xffffffffu, 0x7fffffffu, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x00000001u, 0x80000000u, 0xffffffffu, 0x00000000u}, + {0x7fffffffu, 0x80000000u, 0x00000001u, 0x7fffffffu, 0xffffffffu, 0x00000000u}, + {0xffffffffu, 0x7fffffffu, 0x00000000u, 0x00000000u, 0xbfffffffu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0xffffffffu, 0x7fffffffu, 0x00000000u, 0xffffffffu, 0x7fffffffu, 0x00000000u}, + {0x00000000u, 0x80000000u, 0x00000001u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0xffffffffu, 0x00000001u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x7fffffffu, 0xffffffffu, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xffffffffu, 0xffffffffu, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x00000000u}, + {0x80000000u, 0xffffffffu, 0x00000001u, 0x80000000u, 0x00000000u, 0x00000000u}, + {0x7fffffffu, 0xffffffffu, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0x00000000u, 0x80000000u, 0x00000001u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0xc0000000u, 0x00000001u, 0x00000000u, 0xc0000000u, 0x00000000u}, + {0x80000000u, 0xffffffffu, 0x00000001u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0xffffffffu, 0x7fffffffu, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0x00000004u, 0x40000000u, 0x00000000u, 0xffffffffu, 0xffffffffu, 0x00000001u}, + {0xc6572994u, 0x08928581u, 0x00000000u, 0xc6572994u, 0x457dc57eu, 0x00000000u}, + {0x89b23ef1u, 0x71659f65u, 0x00000000u, 0x89b23ef1u, 0xd01b0109u, 0x00000000u} +}; +constexpr const char* Names[6] = {"i64 clamp lo", "i64 clamp hi", "i64 clamp carry", "u64 clamp lo", "u64 clamp hi", "u64 clamp carry"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 mad clamp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 6; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 mad clamp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3PackLdexp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3PackLdexp.cpp new file mode 100644 index 000000000..baa0da0a1 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3PackLdexp.cpp @@ -0,0 +1,169 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd768000a, 0x02020b04, 0xd768020b, + 0x22020b04, 0xd769000c, 0x02020b04, 0xd769010d, 0x62020b04, 0xd762000e, 0x02020d04, 0xd762010f, + 0x22020d04, 0xd7620010, 0x02020f05, 0xd54d0011, 0x041a0b04, 0xd7110012, 0x02020b04, 0xd7110213, + 0x22020b04, 0xd7111814, 0x02020b04, 0xd7111115, 0x22020b04, 0xd56b0016, 0x02020b04, 0xd56b0017, + 0x02020f06, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, + 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x3fc00000u, 0x00800000u, 0x00000080u, 0xffffffffu}, + {0xbf800001u, 0x80ffffffu, 0xffffff81u, 0x00000000u}, + {0x3f800000u, 0x7f000000u, 0x0000007fu, 0x00000000u}, + {0x40000000u, 0x00800001u, 0xffffff82u, 0xfffffffeu}, + {0x3f7fff80u, 0x3effff00u, 0x00000000u, 0x00000001u}, + {0xbf000000u, 0x3f000000u, 0x00000115u, 0x348abf59u}, + {0x382000a0u, 0xffc00000u, 0xffffff00u, 0x0000007fu}, + {0x7fa12345u, 0xb8400180u, 0x367c8d22u, 0xffff0000u}, + {0x37800101u, 0x3840017fu, 0x80000001u, 0x000000fdu}, + {0x00000000u, 0x00000001u, 0xffffff69u, 0xffffff00u}, + {0xd62c214cu, 0x7f800001u, 0x000000feu, 0xffffff6bu}, + {0xffc00000u, 0x3ac900c9u, 0xffffffffu, 0x00000116u}, + {0x38a00141u, 0xff800000u, 0x00000000u, 0x00000098u}, + {0x628aa774u, 0x807fffffu, 0xffffff69u, 0xffffff81u}, + {0x7f800000u, 0x3b490191u, 0x0fdc7365u, 0x00000080u}, + {0xff800000u, 0x7f800000u, 0x00000000u, 0x00000116u}, + {0x807fffffu, 0x80000000u, 0x00000096u, 0x00000080u}, + {0x7f800001u, 0x3a800000u, 0x00001000u, 0x00000082u}, + {0x37c000c0u, 0x7fa12345u, 0xffffffffu, 0x7f703ee0u}, + {0x00000001u, 0xab16df9cu, 0xa053bb90u, 0x55e635d7u}, + {0x8dc822deu, 0x00000000u, 0x7fffffffu, 0x002baa81u}, + {0xffc00000u, 0xffbfffffu, 0x00000097u, 0x00000082u}, + {0x80000000u, 0x2d6e2b74u, 0xffffff6bu, 0x9fb7084au}, + {0xf2d7d40fu, 0x7f800001u, 0x00000100u, 0x7ffffffeu}, + {0x3c000000u, 0x80000000u, 0xfffffe00u, 0x1b1c3f27u}, + {0x3efffe01u, 0x3f000001u, 0xffffff6bu, 0xffffffffu}, + {0x3f000000u, 0xb8a0013fu, 0x00000096u, 0x5cdff86du}, + {0x3b490192u, 0x4801920eu, 0x00000115u, 0x7fffffffu}, + {0x3ac900cau, 0x37c000c1u, 0x00000081u, 0x00000082u}, + {0xbf7ffcffu, 0x32135b34u, 0x00000017u, 0x00000002u}, + {0x38a00141u, 0x3b490193u, 0xffffff82u, 0x8acac024u}, + {0x3820009fu, 0x3f7fffc0u, 0x0000007eu, 0x80000000u} +}; +constexpr std::uint32_t Expected[32][14] = { + {0x00007fffu, 0x00008001u, 0x0000ffffu, 0x00000000u, 0x7f800000u, 0xff800000u, 0x00000000u, 0x1fa00000u, 0x00000000u, 0x00008000u, 0x00803fc0u, 0x00808000u, 0x00000000u, 0xffffff80u}, + {0x00008001u, 0x00007fffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80ffffffu, 0xa0c08080u, 0xffff0001u, 0x7fff8001u, 0x80ffbf80u, 0x80ff8001u, 0xc17fffffu, 0x00000000u}, + {0x7fff7fffu, 0x7fff8001u, 0xffffffffu, 0x00000000u, 0x7f000000u, 0xff000000u, 0x7f000000u, 0x5f400000u, 0x00000000u, 0x00008000u, 0x7f003f80u, 0x7f008000u, 0x00000000u, 0x00000000u}, + {0x00007fffu, 0x00008001u, 0x0000ffffu, 0x00000000u, 0x01000000u, 0x81000000u, 0x00000000u, 0x20400000u, 0x00010000u, 0x00018000u, 0x00804000u, 0x00808000u, 0x40000000u, 0x000000fcu}, + {0x3fff7fffu, 0x3fff8001u, 0x7fffffffu, 0x00000000u, 0x3f7fff80u, 0xbf7fff80u, 0x3f7fff00u, 0x3ebfff40u, 0xff00ff80u, 0x7f007f80u, 0x3eff3f7fu, 0x3effff80u, 0x00008000u, 0x00000000u}, + {0x4000c000u, 0x40004000u, 0x80000000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x7f800000u, 0x7f000000u, 0x00000000u, 0x00008000u, 0x3f00bf00u, 0x3f008000u, 0x00000000u, 0xda210b4du}, + {0x00000001u, 0x0000ffffu, 0x00000002u, 0x00000000u, 0x00000000u, 0x80000000u, 0xffc00000u, 0x9c700050u, 0x000000a0u, 0x000080a0u, 0xffc03820u, 0xffc080a0u, 0xd8000000u, 0xffff8100u}, + {0xffff0000u, 0x00010000u, 0x00000000u, 0x00030000u, 0x7fa12345u, 0xffa12345u, 0x80000000u, 0x9b701262u, 0x01802345u, 0x0180a345u, 0xb8407fa1u, 0xb840a345u, 0xdaf4e780u, 0x72de0000u}, + {0x00010001u, 0x0001ffffu, 0x00030001u, 0x00000000u, 0x00000000u, 0x80000000u, 0x7f800000u, 0x37600140u, 0x017f0101u, 0x017f8101u, 0x38403780u, 0x38408101u, 0x80c1807fu, 0x800000fdu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000001u, 0x00010000u, 0x00018000u, 0x00000000u, 0x00008000u, 0x00000000u, 0x00009700u}, + {0x00008001u, 0x00007fffu, 0x00000000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x7f800001u, 0xaa561026u, 0x0001214cu, 0x0001a14cu, 0x7f80d62cu, 0x7f80a14cu, 0x302c214cu, 0xffff6c2au}, + {0x00320000u, 0x00320000u, 0x00640000u, 0x00000000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0x9dc50065u, 0x00c90000u, 0x00c98000u, 0x3ac9ffc0u, 0x3ac98000u, 0xcdc00000u, 0xfffffeeau}, + {0x80010003u, 0x7ffffffdu, 0x00000005u, 0xffff0000u, 0x38a00141u, 0xb8a00141u, 0xff800000u, 0x9b900020u, 0x00000141u, 0x00008141u, 0xff8038a0u, 0xff808141u, 0x5f800000u, 0x00000000u}, + {0x00007fffu, 0x00008001u, 0x0000ffffu, 0x00000000u, 0x170aa774u, 0x970aa774u, 0x80000000u, 0x7185d3bau, 0xffffa774u, 0x7fff2774u, 0x807f628au, 0x807fa774u, 0x5775588cu, 0x00004ae9u}, + {0x00647fffu, 0x00648001u, 0x00c9ffffu, 0x00000000u, 0x7f800000u, 0xff800000u, 0x7b490191u, 0x5d640149u, 0x01910000u, 0x01918000u, 0x3b497f80u, 0x3b498000u, 0xb7800000u, 0xee39b280u}, + {0x7fff8001u, 0x7fff7fffu, 0xffff0000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x7f800000u, 0xbf800000u, 0x00000000u, 0x00008000u, 0x7f80ff80u, 0x7f808000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x803f7f7fu, 0x0000ffffu, 0x00007fffu, 0x8000807fu, 0x8000ffffu, 0x80000000u, 0x00004b00u}, + {0x00200000u, 0x00200000u, 0x00400000u, 0x00000000u, 0x7f800001u, 0xff800001u, 0x7b800000u, 0x5c800000u, 0x00000001u, 0x00008001u, 0x3a807f80u, 0x3a808001u, 0x3a800000u, 0x00082000u}, + {0x00000001u, 0x0000ffffu, 0x00000001u, 0x00000000u, 0x374000c0u, 0xb74000c0u, 0x7fa12345u, 0x5bb11283u, 0x234500c0u, 0x234580c0u, 0x7fa137c0u, 0x7fa180c0u, 0xff9a73c0u, 0x808fc120u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0xff800000u, 0x550b704eu, 0xdf9c0001u, 0x5f9c8001u, 0xab160000u, 0xab168001u, 0xab16df9cu, 0x4f8755f0u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x00000000u, 0x4764116fu, 0x000022deu, 0x0000a2deu, 0x00008dc8u, 0x0000a2deu, 0x00000000u, 0x7fd4557fu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc00000u, 0xffc00000u, 0xffbfffffu, 0xffbf7f80u, 0xffff0000u, 0x7fff8000u, 0xffbfffc0u, 0xffbf8000u, 0x00400000u, 0x00004caeu}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x5737163au, 0x2b740000u, 0x2b748000u, 0x2d6e8000u, 0x2d6e8000u, 0x00000000u, 0x0a782ceeu}, + {0x00008001u, 0x00007fffu, 0x00000000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x7f800001u, 0xb8ab6a08u, 0x0001d40fu, 0x0001540fu, 0x7f80f2d7u, 0x7f80d40fu, 0x6b57d40fu, 0xfffffe00u}, + {0x00000100u, 0x0000ff00u, 0x00000200u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x5e000000u, 0x00000000u, 0x00008000u, 0x80003c00u, 0x80008000u, 0x00000000u, 0xc781b200u}, + {0x40003fffu, 0x4000c001u, 0x80007fffu, 0x00000000u, 0x00000000u, 0x80000000u, 0x3e800001u, 0x3f807f01u, 0x0001fe01u, 0x00017e01u, 0x3f003effu, 0x3f00fe01u, 0x7dfffe01u, 0x00000095u}, + {0xfffe4000u, 0x0002c000u, 0x00008000u, 0x00050000u, 0x7f800000u, 0xff800000u, 0xff800000u, 0x7b50001fu, 0x013f0000u, 0x013f8000u, 0xb8a03f00u, 0xb8a08000u, 0x81000000u, 0x6b3b8fdeu}, + {0x7fff0064u, 0x7fffff9cu, 0xffff00c9u, 0x00000000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x41254a50u, 0x920e0192u, 0x120e8192u, 0x48013b49u, 0x48018192u, 0xf27559fcu, 0x7ffffeebu}, + {0x00010032u, 0x0001ffceu, 0x00020065u, 0x00000000u, 0x7b4900cau, 0xfb4900cau, 0x78c000c1u, 0x38c400c6u, 0x00c100cau, 0x00c180cau, 0x37c03ac9u, 0x37c080cau, 0x4f09984au, 0x00004182u}, + {0x00008003u, 0x00007ffdu, 0x00000000u, 0x00000000u, 0xcafffcffu, 0xcafffcffu, 0x33135b34u, 0x7849ab9au, 0x5b34fcffu, 0x5b347cffu, 0x3213bf7fu, 0x3213fcffu, 0xf9db08ccu, 0x0000002eu}, + {0x00650003u, 0x0065fffdu, 0x00c90005u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x3a75016au, 0x01930141u, 0x01938141u, 0x3b4938a0u, 0x3b498141u, 0x7a6af953u, 0xb0356e48u}, + {0x7fff0001u, 0x7fffffffu, 0xffff0002u, 0x00000000u, 0x7720009fu, 0xf720009fu, 0x00000000u, 0x3b4f7fafu, 0xffc0009fu, 0x7fc0809fu, 0x3f7f3820u, 0x3f7f809fu, 0x687fd840u, 0x00000000u} +}; +constexpr const char* Names[14] = {"cvt_pknorm_i16_f320", "cvt_pknorm_i16_f321", "cvt_pknorm_u16_f322", "cvt_pknorm_u16_f323", "ldexp_f324", "ldexp_f325", "ldexp_f326", "lerp_u87", "pack_b32_f168", "pack_b32_f169", "pack_b32_f1610", "pack_b32_f1611", "mul_lo_i32_0", "mul_lo_i32_1"}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("vop3 pack ldexp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 14; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("vop3 pack ldexp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp index 00055340c..8d4c14553 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3Sad.cpp @@ -90,19 +90,6 @@ void Run(AgcDriver::VulkanDevice& device) { device.WaitIdle(); } -void CheckClampRefused(AgcDriver::VulkanDevice& device) { - for (const std::uint32_t opcode : {0x15au, 0x15bu, 0x15cu, 0x171u}) { - alignas(256) const std::array code{0xd400800au | (opcode << 16u), 0x041a0b04u, 0xbf810000u}; - std::string refusal; - try { - static_cast(Compile(device, code)); - } catch (const std::exception& error) { - refusal = error.what(); - } - Require(refusal.find("VOP3 source modifiers are not implemented") != std::string::npos, "VOP3 opcode " + Hex(opcode) + " with clamp was not refused"); - } -} - std::uint32_t Sad(std::uint32_t lhs, std::uint32_t rhs, std::uint32_t fieldBits, bool masked) { const std::uint32_t fieldMask = (1u << fieldBits) - 1u; std::uint32_t sum = 0u; @@ -154,7 +141,6 @@ int main() { FillInput(); Run(*device); Check(); - CheckClampRefused(*device); std::puts("vop3 sad tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Wave32Subgroup.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Wave32Subgroup.cpp index b85dda5aa..afd510b19 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Wave32Subgroup.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Wave32Subgroup.cpp @@ -70,12 +70,22 @@ int main() { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; const auto target = device->ComputeTarget(WaveSize); - if (target.subgroupSize != WaveSize) { + if (target.subgroupSize < WaveSize) { std::printf("skipped, the device runs wave32 programs on %u-wide subgroups\n", target.subgroupSize); return VulkanTestSkipped; } Run(*device, target); Check(); + if (device->Target().subgroupSize != target.subgroupSize && device->Target().subgroupSize >= WaveSize) { + Run(*device, device->Target()); + Check(); + } + if (device->Target().subgroupSize == WaveSize) { + auto wide = device->Target(); + wide.subgroupSize = 64; + Run(*device, wide); + Check(); + } std::puts("wave32 subgroup tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAjm.native/Export.cpp b/core/libs/prx/libSceAjm.native/Export.cpp index ff03fa3b4..7fe1d9502 100644 --- a/core/libs/prx/libSceAjm.native/Export.cpp +++ b/core/libs/prx/libSceAjm.native/Export.cpp @@ -33,18 +33,6 @@ int APS5_VABI sceAjmBatchJobControl(AjmBatchInfo* info, uint32_t instance, uint6 return 0; } -int APS5_VABI sceAjmBatchJobDecodeSingle(AjmBatchInfo* info, uint32_t instance, const void* bitstream_input, size_t bitstream_input_size, void* pcm_output, size_t pcm_output_size, void* result) { - (void)info; - (void)instance; - (void)bitstream_input; - (void)bitstream_input_size; - (void)pcm_output; - (void)pcm_output_size; - (void)result; - AjmStub(__func__); - return 0; -} - int APS5_VABI sceAjmBatchJobDecodeSplit(AjmBatchInfo* info, uint32_t instance, const AjmBuffer* input_buffers, size_t input_buffers_num, const AjmBuffer* output_buffers, size_t output_buffers_num, void* result) { (void)info; (void)instance; @@ -69,23 +57,6 @@ int APS5_VABI sceAjmBatchJobEncode(AjmBatchInfo* info, uint32_t instance, const return 0; } -int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo* info, uint32_t instance, void* result, size_t result_size) { - (void)info; - (void)instance; - (void)result; - (void)result_size; - AjmStub(__func__); - return 0; -} - -int APS5_VABI sceAjmBatchJobGetGaplessDecode(AjmBatchInfo* info, uint32_t instance, void* result) { - (void)info; - (void)instance; - (void)result; - AjmStub(__func__); - return 0; -} - int APS5_VABI sceAjmBatchJobGetInfo(AjmBatchInfo* info, uint32_t instance, void* result) { (void)info; (void)instance; @@ -129,9 +100,4 @@ const char* APS5_VABI sceAjmStrError(int error) { return nullptr; } -int APS5_VABI sceAjmDecMp3ParseFrame() { - AjmStub(__func__); - return 0; -} - } diff --git a/core/libs/prx/libSceAjm.native/src/Ajm.cpp b/core/libs/prx/libSceAjm.native/src/Ajm.cpp index 22749392c..3b30d8d53 100644 --- a/core/libs/prx/libSceAjm.native/src/Ajm.cpp +++ b/core/libs/prx/libSceAjm.native/src/Ajm.cpp @@ -16,6 +16,7 @@ extern "C" { #include #include #include +#include #include #include #include @@ -26,6 +27,7 @@ extern "C" { namespace { constexpr int SCE_AJM_ERROR_INVALID_INSTANCE = static_cast(0x80930003); +constexpr int SCE_AJM_ERROR_INVALID_BATCH = static_cast(0x80930004); constexpr int SCE_AJM_ERROR_INVALID_PARAMETER = static_cast(0x80930005); constexpr int SCE_AJM_ERROR_OUT_OF_RESOURCES = static_cast(0x80930007); @@ -51,6 +53,7 @@ bool TraceEnabled() { if (TraceEnabled()) std::fprintf(stderr, __VA_ARGS__); \ } while (0) +constexpr std::uint64_t RUN_GET_CODEC_INFO = 1ull << 11; constexpr std::uint64_t RUN_MULTIPLE_FRAMES = 1ull << 12; constexpr std::uint64_t SIDEBAND_GAPLESS_DECODE = 1ull << 45; constexpr std::uint64_t SIDEBAND_FORMAT = 1ull << 46; @@ -87,6 +90,13 @@ struct SidebandMultipleFrames { std::uint32_t reserved; }; +struct SidebandAt9CodecInfo { + std::uint32_t superframeSize; + std::uint32_t framesInSuperframe; + std::uint32_t nextFrameSize; + std::uint32_t frameSamples; +}; + struct Instance { std::uint32_t codec = 0; std::uint64_t flags = 0; @@ -119,6 +129,8 @@ std::map> g_instances; std::atomic g_nextContext{1}; std::atomic g_nextInstance{1}; std::atomic g_nextBatch{1}; +std::mutex g_batchLock; +std::set g_batches; enum class JobKind : std::uint32_t { Initialize = 1, @@ -337,6 +349,20 @@ void EmitFrame(Instance& instance, PcmOutputs& outputs, const std::uint8_t* pcm, instance.totalDecodedSamples += count; } +SidebandFormat CurrentFormat(const Instance& instance) { + switch (instance.codec) { + case CODEC_AT9: return {static_cast(instance.info.channels), ChannelMask(static_cast(instance.info.channels)), static_cast(instance.info.samplingRate), PcmEncoding(instance), 0, 0}; + case CODEC_MP3: return {instance.mp3Channels, ChannelMask(instance.mp3Channels), instance.mp3SampleRate, PcmEncoding(instance), instance.mp3Bitrate, 0}; + case CODEC_OPUS: return {instance.opusChannels, ChannelMask(instance.opusChannels), instance.opusSampleRate, PcmEncoding(instance), 0, 0}; + default: throw std::runtime_error("AJM: the format of codec " + std::to_string(instance.codec) + " is not implemented"); + } +} + +SidebandAt9CodecInfo At9CodecInfo(const Instance& instance) { + const auto superframeSize = static_cast(instance.info.superframeSize); + return {superframeSize, static_cast(instance.info.framesInSuperframe), instance.superframeRemaining == 0 ? superframeSize : instance.superframeRemaining, static_cast(instance.info.frameSamples)}; +} + void WriteRunSideband(const JobHeader& job, const Instance& instance, std::int32_t result, std::size_t consumed, std::size_t produced, std::uint32_t frames, const SidebandFormat& format) { auto* sideband = static_cast(job.sideband); std::size_t offset = 0; @@ -353,6 +379,10 @@ void WriteRunSideband(const JobHeader& job, const Instance& instance, std::int32 } if (job.flags & SIDEBAND_FORMAT) write(&format, sizeof(format)); if (job.flags & SIDEBAND_GAPLESS_DECODE) write(&instance.gapless, sizeof(instance.gapless)); + if (job.flags & RUN_GET_CODEC_INFO) { + const SidebandAt9CodecInfo codecInfo = At9CodecInfo(instance); + write(&codecInfo, sizeof(codecInfo)); + } if (job.flags & RUN_MULTIPLE_FRAMES) { const SidebandMultipleFrames multiple{frames, 0}; write(&multiple, sizeof(multiple)); @@ -431,7 +461,7 @@ void RunAt9(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, c for (std::size_t i = 0; i < input.size() && i < 16; ++i) std::fprintf(stderr, " %02x", input[i]); std::fprintf(stderr, "\n"); } - WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, SidebandFormat{static_cast(channels), ChannelMask(channels), static_cast(instance.info.samplingRate), encoding, 0, 0}); + WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, CurrentFormat(instance)); } struct Mp3Frame { @@ -546,7 +576,7 @@ void RunMp3(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, c AJM_TRACE("[ajm] instance %u mp3 run flags 0x%llx: %zu input bytes, %zu output bytes -> result 0x%x, %u frames, consumed %zu, produced %zu, %u channels, %u Hz, %u bps, total samples %llu\n", job.instance, static_cast(job.flags), input.size(), pcmOutputs.capacity, static_cast(result), frames, consumed, pcmOutputs.produced, instance.mp3Channels, instance.mp3SampleRate, instance.mp3Bitrate, static_cast(instance.totalDecodedSamples)); - WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, SidebandFormat{instance.mp3Channels, ChannelMask(instance.mp3Channels), instance.mp3SampleRate, encoding, instance.mp3Bitrate, 0}); + WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, CurrentFormat(instance)); } void OpenOpus(Instance& instance) { static const bool quiet = (av_log_set_level(TraceEnabled() ? AV_LOG_WARNING : AV_LOG_QUIET), true); @@ -682,7 +712,7 @@ void RunOpus(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, if (consumed == input.size() && frames == 0 && pcmOutputs.produced == 0 && result == 0) result |= AJM_RESULT_PARTIAL_INPUT; AJM_TRACE("[ajm] instance %u opus run flags 0x%llx: %zu input bytes, %zu output bytes -> result 0x%x, %u frames, consumed %zu, produced %zu, total samples %llu\n", job.instance, static_cast(job.flags), input.size(), pcmOutputs.capacity, static_cast(result), frames, consumed, pcmOutputs.produced, static_cast(instance.totalDecodedSamples)); - WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, SidebandFormat{instance.opusChannels, ChannelMask(instance.opusChannels), instance.opusSampleRate, encoding, 0, 0}); + WriteRunSideband(job, instance, result, consumed, pcmOutputs.produced, frames, CurrentFormat(instance)); } void Execute(const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* outputs) { @@ -729,14 +759,21 @@ void Execute(const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* out if (!instance->initialized) { AJM_TRACE("[ajm] instance %u run before initialize\n", job.instance); WriteResult(job.sideband, job.sidebandSize, AJM_RESULT_NOT_INITIALIZED); + } else if (instance->codec != CODEC_AT9 && instance->codec != CODEC_MP3 && instance->codec != CODEC_OPUS) { + throw std::runtime_error("AJM: decoding codec " + std::to_string(instance->codec) + " is not implemented"); + } else if ((job.flags & RUN_GET_CODEC_INFO) && (job.flags & RUN_MULTIPLE_FRAMES)) { + NotImplemented_nid_no_patch("AJM run job with both RUN_GET_CODEC_INFO and RUN_MULTIPLE_FRAMES (sideband order)"); + } else if ((job.flags & RUN_GET_CODEC_INFO) && instance->codec != CODEC_AT9) { + NotImplemented_nid_no_patch("AJM RUN_GET_CODEC_INFO for a codec other than ATRAC9"); + } else if (job.inputCount == 0 && job.outputCount == 0) { + AJM_TRACE("[ajm] instance %u run flags 0x%llx without buffers, sideband %llu bytes\n", job.instance, static_cast(job.flags), static_cast(job.sidebandSize)); + WriteRunSideband(job, *instance, 0, 0, 0, 0, CurrentFormat(*instance)); } else if (instance->codec == CODEC_AT9) { RunAt9(*instance, job, inputs, outputs); } else if (instance->codec == CODEC_MP3) { RunMp3(*instance, job, inputs, outputs); - } else if (instance->codec == CODEC_OPUS) { - RunOpus(*instance, job, inputs, outputs); } else { - throw std::runtime_error("AJM: decoding codec " + std::to_string(instance->codec) + " is not implemented"); + RunOpus(*instance, job, inputs, outputs); } break; default: @@ -832,6 +869,36 @@ int APS5_VABI sceAjmDecAt9ParseConfigData(const void* config_data, AjmDecAt9Conf return 0; } +int APS5_VABI sceAjmDecMp3ParseFrame(const uint8_t* stream, uint32_t streamSize, int parseOfl, AjmDecMp3ParseFrame* frame) { + if (!stream || streamSize < 4 || !frame) return SCE_AJM_ERROR_INVALID_PARAMETER; + static constexpr std::uint32_t sampleRates[4][4] = {{11025, 12000, 8000, 0}, {0, 0, 0, 0}, {22050, 24000, 16000, 0}, {44100, 48000, 32000, 0}}; + static constexpr std::uint32_t kbps[4][16] = { + {0, 8, 16, 24, 32, 40, 48, 56, 64, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0}, + }; + const std::uint32_t header = (std::uint32_t{stream[0]} << 24u) | (std::uint32_t{stream[1]} << 16u) | (std::uint32_t{stream[2]} << 8u) | stream[3]; + const std::uint32_t version = (header >> 19u) & 3u; + const std::uint32_t sampleRate = sampleRates[version][(header >> 10u) & 3u]; + const std::uint32_t bitrate = kbps[version][(header >> 12u) & 15u] * 1000u; + const bool valid = (header >> 21u) == 0x7FFu && sampleRate != 0 && bitrate != 0; + AJM_TRACE("[ajm] parse mp3 frame %02x %02x %02x %02x (%u bytes, parse ofl %d) -> %s, %u Hz, %u bps\n", stream[0], stream[1], stream[2], stream[3], streamSize, parseOfl, valid ? "ok" : "invalid", sampleRate, bitrate); + if (!valid) return SCE_AJM_ERROR_INVALID_PARAMETER; + if (parseOfl) NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (original file length lookup)"); + const bool mpeg1 = version == 3; + frame->frame_size = (mpeg1 ? 144u : 72u) * bitrate / sampleRate + ((header >> 9u) & 1u); + frame->num_channels = ((header >> 6u) & 3u) == 3u ? 1u : 2u; + frame->samples_per_channel = mpeg1 ? 1152u : 576u; + frame->bitrate = bitrate; + frame->sample_rate = sampleRate; + frame->encoder_delay = 0; + frame->num_frames = 0; + frame->total_samples = 0; + frame->ofl_type = 0; + return 0; +} + int APS5_VABI sceAjmBatchInitialize(void* buffer, size_t size, AjmBatchInfo* info) { if (!buffer || !info) return SCE_AJM_ERROR_INVALID_PARAMETER; info->p_buffer = buffer; @@ -877,6 +944,18 @@ int APS5_VABI sceAjmBatchJobDecode(AjmBatchInfo* info, uint32_t instance, const return sceAjmBatchJobRun(info, instance, SIDEBAND_STREAM, bitstream_input, bitstream_input_size, pcm_output, pcm_output_size, result, sizeof(SidebandResult) + sizeof(SidebandStream)); } +int APS5_VABI sceAjmBatchJobDecodeSingle(AjmBatchInfo* info, uint32_t instance, const void* bitstream_input, size_t bitstream_input_size, void* pcm_output, size_t pcm_output_size, void* result) { + return sceAjmBatchJobRun(info, instance, SIDEBAND_STREAM, bitstream_input, bitstream_input_size, pcm_output, pcm_output_size, result, sizeof(SidebandResult) + sizeof(SidebandStream)); +} + +int APS5_VABI sceAjmBatchJobGetGaplessDecode(AjmBatchInfo* info, uint32_t instance, void* result) { + return sceAjmBatchJobRunSplit(info, instance, SIDEBAND_GAPLESS_DECODE, nullptr, 0, nullptr, 0, result, sizeof(SidebandResult) + sizeof(SidebandGaplessDecode)); +} + +int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo* info, uint32_t instance, void* result, size_t result_size) { + return sceAjmBatchJobRunSplit(info, instance, RUN_GET_CODEC_INFO, nullptr, 0, nullptr, 0, result, result_size); +} + int APS5_VABI sceAjmBatchJobGetStatistics(AjmBatchInfo* info, float interval, void* result) { (void)interval; return Append(info, MakeHeader(JobKind::GetStatistics, 0, result, 24), nullptr, nullptr); @@ -910,14 +989,22 @@ int APS5_VABI sceAjmBatchStart(uint32_t context, const AjmBatchInfo* info, int p cursor += record.bytes; } if (error) std::memset(error, 0, sizeof(*error)); - *batch = g_nextBatch.fetch_add(1, std::memory_order_relaxed); + const auto id = g_nextBatch.fetch_add(1, std::memory_order_relaxed); + { + std::lock_guard lock(g_batchLock); + g_batches.insert(id); + } + *batch = id; return 0; } int APS5_VABI sceAjmBatchWait(uint32_t context, uint32_t batch, uint32_t timeout, AjmBatchError* error) { (void)context; - (void)batch; (void)timeout; + { + std::lock_guard lock(g_batchLock); + if (g_batches.erase(batch) == 0) return SCE_AJM_ERROR_INVALID_BATCH; + } if (error) std::memset(error, 0, sizeof(*error)); return 0; } diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index e7ae4fef6..6251abea9 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -42,4 +42,24 @@ if(BUILD_TESTING) set_tests_properties(audio_out_mix_level_pad_spk PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(audio_out_last_output_time_tests tests/AudioOutLastOutputTime.cpp) + target_include_directories(audio_out_last_output_time_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(audio_out_last_output_time_tests PRIVATE libSceAudioOut libkernel libc) + configure_windows_unwind(audio_out_last_output_time_tests) + add_test(NAME audio_out_last_output_time COMMAND audio_out_last_output_time_tests) + if(WIN32) + set_tests_properties(audio_out_last_output_time PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(audio_out2_latency_tests tests/AudioOut2Latency.cpp) + target_include_directories(audio_out2_latency_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(audio_out2_latency_tests PRIVATE libSceAudioOut libc) + configure_windows_unwind(audio_out2_latency_tests) + add_test(NAME audio_out2_latency COMMAND audio_out2_latency_tests) + if(WIN32) + set_tests_properties(audio_out2_latency PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp index 8f865806a..de2184619 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut.cpp @@ -89,6 +89,7 @@ struct Port { int volume[8] = {}; int mixLevel = DEFAULT_VOLUME; std::uint64_t lastOutputTime = 0; + std::uint64_t lastDataOutputTime = 0; SDL_AudioDeviceID device = 0; SDL_AudioSpec spec = {}; }; @@ -328,6 +329,7 @@ int APS5_VABI sceAudioOutOpen(int userId, int type, int index, std::uint32_t len port.format = format; port.channels = channelsForFormat(format); port.lastOutputTime = 0; + port.lastDataOutputTime = 0; port.mixLevel = type == PORT_TYPE_PADSPK ? DEFAULT_PADSPK_MIX_LEVEL : DEFAULT_VOLUME; for (int c = 0; c < port.channels; c++) { port.volume[c] = DEFAULT_VOLUME; @@ -374,6 +376,7 @@ int APS5_VABI sceAudioOutOutput(int handle, const void* ptr) { queueAudio(*port, ptr); port->lastOutputTime = sceKernelGetProcessTime(); + if (ptr != nullptr) port->lastDataOutputTime = port->lastOutputTime; return static_cast(port->samplesNum); } @@ -425,7 +428,10 @@ int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam* param, std::uint32_t num) const std::uint64_t done = sceKernelGetProcessTime(); for (std::uint32_t i = 0; i < num; i++) { - if (auto* port = getPort(param[i].handle)) port->lastOutputTime = done; + if (auto* port = getPort(param[i].handle)) { + port->lastOutputTime = done; + if (param[i].ptr != nullptr) port->lastDataOutputTime = done; + } } return static_cast(first.samplesNum); @@ -457,6 +463,19 @@ int APS5_VABI sceAudioOutSetVolume(int handle, std::uint32_t flag, int* vol) { return 0; } +int APS5_VABI sceAudioOutGetLastOutputTime(int handle, std::uint64_t* outputTime) { + if (outputTime == nullptr) { + return -2144993276; + } + std::lock_guard lock(g_mutex); + Port* port = getPort(handle); + if (port == nullptr) { + return -2144993277; + } + *outputTime = port->lastDataOutputTime; + return 0; +} + int APS5_VABI sceAudioOutGetPortState(int handle, AudioOutPortState* state) { if (state == nullptr) { return -2144993276; diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp index d8b0455c2..ac3f33976 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp @@ -5,8 +5,7 @@ extern "C" { int APS5_VABI sceAudioOut2MasteringInit(uint32_t flags) { - (void)flags; - NotImplemented_nid_no_patch(__func__); + if (flags != 0) NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp b/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp index b690814de..f658ddf10 100644 --- a/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp +++ b/core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp @@ -1,12 +1,21 @@ +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +static constexpr int USER_ID_SYSTEM = 0xFF; +static constexpr std::uint32_t SUPPORTED_3D_LATENCY = 2; + extern "C" { int APS5_VABI sceAudioOut2Initialize(void) { return 0; } +int APS5_VABI sceAudioOut2Set3DLatency(int userId, std::uint32_t latency) { + if (userId != USER_ID_SYSTEM || latency != SUPPORTED_3D_LATENCY) NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceAudioOut2GetSystemState(AudioOut2SystemState* state) { if (!state) return static_cast(0x80260502); state->loudness = 0.0f; diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp new file mode 100644 index 000000000..7f3404f2a --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2Latency.cpp @@ -0,0 +1,55 @@ +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceAudioOut2Initialize(); +int APS5_VABI sceAudioOut2Set3DLatency(int, std::uint32_t); +int APS5_VABI sceAudioOut2MasteringInit(std::uint32_t); +} + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +template +static bool ThrowsRuntimeError(TFunction function) { + try { + function(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +namespace { + +constexpr int systemUser = 0xFF; +constexpr int user = 0x10000000; + +void TestSet3DLatency() { + Require(sceAudioOut2Set3DLatency(systemUser, 2) == 0, "latency 2 for the system user must be accepted"); + Require(sceAudioOut2Set3DLatency(systemUser, 2) == 0, "latency 2 must be accepted again"); + Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(systemUser, 1); }), "latency 1 must throw"); + Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(systemUser, 3); }), "latency 3 must throw"); + Require(ThrowsRuntimeError([] { sceAudioOut2Set3DLatency(user, 2); }), "a user other than the system user must throw"); +} + +void TestMasteringInit() { + Require(sceAudioOut2MasteringInit(0) == 0, "flags 0 must be accepted"); + Require(ThrowsRuntimeError([] { sceAudioOut2MasteringInit(1); }), "flags 1 must throw"); +} + +} + +int main() { + Require(sceAudioOut2Initialize() == 0, "initialization must succeed"); + TestSet3DLatency(); + TestMasteringInit(); + return 0; +} diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp new file mode 100644 index 000000000..bc6a54315 --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOutLastOutputTime.cpp @@ -0,0 +1,158 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t); +int APS5_VABI sceAudioOutClose(int); +int APS5_VABI sceAudioOutOutput(int, const void*); +int APS5_VABI sceAudioOutOutputs(AudioOutOutputParam*, std::uint32_t); +int APS5_VABI sceAudioOutGetLastOutputTime(int, std::uint64_t*); +std::uint64_t APS5_VABI sceKernelGetProcessTime(); +} + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +namespace { + +constexpr int user = 0x10000000; +constexpr int portTypeMain = 0; +constexpr int portTypeVibration = 10; +constexpr std::uint32_t formatS16Mono = 0; +constexpr std::uint32_t frames = 256; +constexpr std::uint32_t frequency = 48000; +constexpr std::uint64_t untouched = 0xA5A5A5A5A5A5A5A5ull; +constexpr int invalidPort = static_cast(0x80260003); +constexpr int invalidPointer = static_cast(0x80260004); + +int Open(int type) { + const int handle = sceAudioOutOpen(user, type, 0, frames, frequency, formatS16Mono); + Require(handle > 0, "port must open"); + return handle; +} + +std::uint64_t LastOutputTime(int handle) { + std::uint64_t time = untouched; + Require(sceAudioOutGetLastOutputTime(handle, &time) == 0, "an open port must report its last output time"); + return time; +} + +void Pause() { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); +} + +std::uint64_t OutputAndCheck(int handle, const std::vector& block) { + Pause(); + const std::uint64_t before = sceKernelGetProcessTime(); + Require(sceAudioOutOutput(handle, block.data()) == static_cast(frames), "output must accept the block"); + const std::uint64_t after = sceKernelGetProcessTime(); + const std::uint64_t time = LastOutputTime(handle); + Require(time >= before && time <= after, "last output time must be the process time of the output"); + return time; +} + +void TestOutput(int type) { + const std::vector block(frames, 256); + const int handle = Open(type); + Require(LastOutputTime(handle) == 0, "a port that never output must report 0"); + + const std::uint64_t first = OutputAndCheck(handle, block); + Pause(); + Require(LastOutputTime(handle) == first, "the time must not move without an output"); + + const std::uint64_t second = OutputAndCheck(handle, block); + Require(second > first, "a later output must report a later time"); + + Pause(); + Require(sceAudioOutOutput(handle, nullptr) == static_cast(frames), "waiting for the output must succeed"); + Require(LastOutputTime(handle) == second, "waiting without data must not move the time"); + + Require(sceAudioOutClose(handle) == 0, "port must close"); +} + +void TestPortsAreIndependent() { + const std::vector block(frames, 256); + const int played = Open(portTypeMain); + const int silent = Open(portTypeMain); + Require(played != silent, "two open ports must have different handles"); + OutputAndCheck(played, block); + Require(LastOutputTime(silent) == 0, "an output must not move the time of another port"); + Require(sceAudioOutClose(played) == 0, "port must close"); + Require(sceAudioOutClose(silent) == 0, "port must close"); +} + +void TestOutputs() { + const std::vector block(frames, 256); + const int first = Open(portTypeMain); + const int second = Open(portTypeMain); + const int waiting = Open(portTypeMain); + AudioOutOutputParam params[3] = {{first, block.data()}, {second, block.data()}, {waiting, nullptr}}; + + Pause(); + const std::uint64_t before = sceKernelGetProcessTime(); + Require(sceAudioOutOutputs(params, 3) == static_cast(frames), "outputs must accept the blocks"); + const std::uint64_t after = sceKernelGetProcessTime(); + + for (const int handle : {first, second}) { + const std::uint64_t time = LastOutputTime(handle); + Require(time >= before && time <= after, "outputs must set the time of every port that received data"); + } + Require(LastOutputTime(waiting) == 0, "outputs must not move the time of a port that received no data"); + + for (const int handle : {first, second, waiting}) Require(sceAudioOutClose(handle) == 0, "port must close"); +} + +void TestReopenedPort() { + const std::vector block(frames, 256); + const int handle = Open(portTypeMain); + OutputAndCheck(handle, block); + Require(sceAudioOutClose(handle) == 0, "port must close"); + const int reopened = Open(portTypeMain); + Require(LastOutputTime(reopened) == 0, "a port opened after a close must not keep the time of the closed one"); + Require(sceAudioOutClose(reopened) == 0, "port must close"); +} + +void TestErrors() { + const std::vector block(frames, 256); + const int handle = Open(portTypeMain); + const std::uint64_t time = OutputAndCheck(handle, block); + Require(sceAudioOutGetLastOutputTime(handle, nullptr) == invalidPointer, "a null destination must be rejected"); + Require(LastOutputTime(handle) == time, "a rejected call must not move the time"); + + std::uint64_t destination = untouched; + Require(sceAudioOutGetLastOutputTime(0, &destination) == invalidPort, "handle 0 must be rejected"); + Require(sceAudioOutGetLastOutputTime(-1, &destination) == invalidPort, "a negative handle must be rejected"); + Require(sceAudioOutGetLastOutputTime(handle + 1, &destination) == invalidPort, "a port that is not open must be rejected"); + Require(sceAudioOutGetLastOutputTime(1000, &destination) == invalidPort, "an out of range handle must be rejected"); + Require(sceAudioOutClose(handle) == 0, "port must close"); + Require(sceAudioOutGetLastOutputTime(handle, &destination) == invalidPort, "a closed port must be rejected"); + Require(destination == untouched, "a rejected call must not write the destination"); +} + +} + +int main() { +#ifdef _WIN32 + _putenv_s("SDL_AUDIODRIVER", "dummy"); +#else + setenv("SDL_AUDIODRIVER", "dummy", 1); +#endif + sceKernelGetProcessTime(); + TestOutput(portTypeMain); + TestOutput(portTypeVibration); + TestPortsAreIndependent(); + TestOutputs(); + TestReopenedPort(); + TestErrors(); + return 0; +} diff --git a/core/libs/prx/libSceAvPlayer/src/Source.cpp b/core/libs/prx/libSceAvPlayer/src/Source.cpp index c453be1bf..a59c07a47 100644 --- a/core/libs/prx/libSceAvPlayer/src/Source.cpp +++ b/core/libs/prx/libSceAvPlayer/src/Source.cpp @@ -32,6 +32,7 @@ constexpr std::uint32_t VideoBufferAlignment = 0x100; constexpr std::uint32_t AudioBufferAlignment = 0x10; constexpr std::uint32_t AudioChunkSamples = 1024; constexpr std::uint32_t AudioMaxChannels = 8; +constexpr std::uint32_t VideoDecodeAheadFrames = 4; constexpr std::size_t VideoPacketLimit = 30; constexpr std::size_t AudioPacketLimit = 8; constexpr std::size_t AudioOnlyPacketLimit = 30; @@ -326,6 +327,7 @@ public: return result; } } + addVideoDecodeAheadBuffers(); const auto start = startOffset; startOffset = 0; seek(start); @@ -605,7 +607,7 @@ private: bufferHeight = AlignUp(static_cast(decoder.context->height), VideoHeightAlignment); decoder.bufferSize = pitch * bufferHeight * 3 / 2; count = settings.videoBuffers; - decoder.retained = count > 3 ? count - 3 : 0; + decoder.retained = settings.videoBuffers > 3 ? settings.videoBuffers - 3 : 0; texture = true; alignment = VideoBufferAlignment; } else { @@ -624,6 +626,27 @@ private: return SCE_OK; } + void addVideoDecodeAheadBuffers() { + if (video.stream < 0) return; + + const auto& memory = settings.memory; + std::vector extra; + extra.reserve(VideoDecodeAheadFrames); + for (std::uint32_t index = 0; index < VideoDecodeAheadFrames; ++index) { + auto* buffer = static_cast(memory.allocate_texture( + memory.object_ptr, VideoBufferAlignment, video.bufferSize)); + if (!buffer) { + for (auto* allocated : extra) memory.deallocate_texture(memory.object_ptr, allocated); + return; + } + extra.push_back(buffer); + } + for (auto* buffer : extra) { + video.allocated.push_back(buffer); + video.free.push_back(buffer); + } + } + void releaseDecoders() { const auto& memory = settings.memory; for (auto* decoder : {&video, &audio}) { diff --git a/core/libs/prx/libSceFont/src/Library.cpp b/core/libs/prx/libSceFont/src/Library.cpp index 46b421dcb..90f5cc8b1 100644 --- a/core/libs/prx/libSceFont/src/Library.cpp +++ b/core/libs/prx/libSceFont/src/Library.cpp @@ -297,6 +297,15 @@ int APS5_VABI sceFontRendererSetOutlineBufferPolicy(FontRenderer fontRenderer, s return SCE_FONT_OK; } +int APS5_VABI sceFontGetPixelResolution(FontLibrary library, std::uint32_t* subPixelCount) { + if (!subPixelCount) return SCE_FONT_ERROR_INVALID_PARAMETER; + *subPixelCount = 0; + const auto* lib = static_cast(library); + if (!lib || lib->magic != LIBRARY_MAGIC || !lib->sys_driver || !lib->sys_driver->pixel_resolution) return SCE_FONT_ERROR_INVALID_LIBRARY; + *subPixelCount = lib->sys_driver->pixel_resolution(); + return SCE_FONT_OK; +} + int APS5_VABI sceFontAttachDeviceCacheBuffer(FontLibrary library, void* buffer, std::uint32_t size) { auto* lib = static_cast(library); if (!lib || lib->magic != LIBRARY_MAGIC) return SCE_FONT_ERROR_INVALID_LIBRARY; diff --git a/core/libs/prx/libSceFont/src/Render.cpp b/core/libs/prx/libSceFont/src/Render.cpp index 74cdde106..0da94912c 100644 --- a/core/libs/prx/libSceFont/src/Render.cpp +++ b/core/libs/prx/libSceFont/src/Render.cpp @@ -81,6 +81,18 @@ int CachedColumnOffset(FontHandle handle, FontHandleNative* font, float& offset) return rc; } +void LoadKerning(FT_Face face, float scaleW, float scaleH, std::uint32_t preCode, std::uint32_t code, FontKerning* kerning) { + const FT_UInt previousGlyph = face ? FT_Get_Char_Index(face, preCode) : 0; + const FT_UInt glyph = face ? FT_Get_Char_Index(face, code) : 0; + if (!face || previousGlyph == 0 || glyph == 0) return; + const auto charW = static_cast(static_cast(scaleW * 64.0f)); + const auto charH = static_cast(static_cast(scaleH * 64.0f)); + FT_Set_Char_Size(face, charW, charH, 72, 72); + FT_Vector delta{}; + FT_Get_Kerning(face, previousGlyph, glyph, FT_KERNING_DEFAULT, &delta); + kerning->offsetX = static_cast(delta.x) / 64.0f; +} + int RenderDirectional(FontHandle fontHandle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result, std::uint16_t direction) { auto* font = GetNativeFont(fontHandle); if (!font || font->magic != HANDLE_MAGIC) { @@ -175,21 +187,35 @@ int APS5_VABI sceFontGetKerning(FontHandle fontHandle, std::uint32_t preCode, st if (!font || font->magic != HANDLE_MAGIC || !AcquireFontLock(font, fontLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; *kerning = {}; const FontState* state = TryGetState(fontHandle); - const FT_Face face = state ? state->face : nullptr; - const FT_UInt previousGlyph = face ? FT_Get_Char_Index(face, preCode) : 0; - const FT_UInt glyph = face ? FT_Get_Char_Index(face, code) : 0; - if (face && previousGlyph != 0 && glyph != 0) { - const auto charW = static_cast(static_cast(state->scaleW * 64.0f)); - const auto charH = static_cast(static_cast(state->scaleH * 64.0f)); - FT_Set_Char_Size(face, charW, charH, 72, 72); - FT_Vector delta{}; - FT_Get_Kerning(face, previousGlyph, glyph, FT_KERNING_DEFAULT, &delta); - kerning->offsetX = static_cast(delta.x) / 64.0f; - } + if (state) LoadKerning(state->face, state->scaleW, state->scaleH, preCode, code, kerning); ReleaseFontLock(font, fontLock); return SCE_FONT_OK; } +int APS5_VABI sceFontGetRenderScaledKerning(FontHandle fontHandle, std::uint32_t preCode, std::uint32_t code, FontKerning* kerning) { + if (!kerning) return SCE_FONT_ERROR_INVALID_PARAMETER; + *kerning = {}; + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + if (!font || font->magic != HANDLE_MAGIC || !AcquireFontLock(font, fontLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + std::uint32_t cachedLock = 0; + if (!AcquireCachedStyleLock(font, cachedLock)) { + ReleaseFontLock(font, fontLock); + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + int rc = SCE_FONT_ERROR_NOT_BOUND_RENDERER; + if (font->renderer) { + float scaleW = 0.0f; + float scaleH = 0.0f; + rc = StyleStateGetScalePixel(&font->cached_style.state, &scaleW, &scaleH); + const FontState* state = TryGetState(fontHandle); + if (rc == SCE_FONT_OK && state) LoadKerning(state->face, scaleW, scaleH, preCode, code, kerning); + } + ReleaseCachedStyleLock(font, cachedLock); + ReleaseFontLock(font, fontLock); + return rc; +} + int APS5_VABI sceFontRenderCharGlyphImage(FontHandle fontHandle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result) { auto* font = GetNativeFont(fontHandle); std::uint32_t fontLock = 0; diff --git a/core/libs/prx/libSceFont/src/Unimplemented.cpp b/core/libs/prx/libSceFont/src/Unimplemented.cpp index 16ac20268..c1b833af5 100644 --- a/core/libs/prx/libSceFont/src/Unimplemented.cpp +++ b/core/libs/prx/libSceFont/src/Unimplemented.cpp @@ -117,16 +117,6 @@ int APS5_VABI sceFontGetGlyphExpandBufferState() { return 0; } -int APS5_VABI sceFontGetPixelResolution() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGetRenderScaledKerning() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFontGetScriptLanguage() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceHttp/CMakeLists.txt b/core/libs/prx/libSceHttp/CMakeLists.txt index 0093dd1c0..473651532 100644 --- a/core/libs/prx/libSceHttp/CMakeLists.txt +++ b/core/libs/prx/libSceHttp/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceHttp SHARED EXCLUDE_FROM_ALL Export.cpp src/Uri.cpp) +add_library(libSceHttp SHARED EXCLUDE_FROM_ALL Export.cpp src/Parse.cpp src/Uri.cpp) target_include_directories(libSceHttp PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceHttp) diff --git a/core/libs/prx/libSceHttp/Export.cpp b/core/libs/prx/libSceHttp/Export.cpp index ef0874b74..0bbfa9e09 100644 --- a/core/libs/prx/libSceHttp/Export.cpp +++ b/core/libs/prx/libSceHttp/Export.cpp @@ -246,15 +246,19 @@ int APS5_VABI sceHttpSetChunkedTransferEnabled(int id, int enable) { } -APS5_EXPORT("i9mhafzkEi8", sceHttpUnknown00); -int APS5_VABI sceHttpUnknown00(void) { - NotImplemented_nid_no_patch("i9mhafzkEi8"); +int APS5_VABI sceHttpSetInflateGZIPEnabled(int id, int enable) { + (void)id; + if (static_cast(enable) > 1) return ERROR_INVALID_VALUE; return 0; } -APS5_EXPORT("vO4B-42ef-k", sceHttpUnknown01); -int APS5_VABI sceHttpUnknown01(void) { - NotImplemented_nid_no_patch("vO4B-42ef-k"); +int APS5_VABI sceHttpSetRequestStatusCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceHttpParseResponseHeader(void) { + NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceHttp/src/HttpErrors.hpp b/core/libs/prx/libSceHttp/src/HttpErrors.hpp index 66d70c7e4..b95f8d25b 100644 --- a/core/libs/prx/libSceHttp/src/HttpErrors.hpp +++ b/core/libs/prx/libSceHttp/src/HttpErrors.hpp @@ -5,5 +5,7 @@ inline constexpr int ERROR_OUT_OF_MEMORY = static_cast(0x80431022); inline constexpr int ERROR_NETWORK = static_cast(0x80431063); inline constexpr int ERROR_INVALID_VALUE = static_cast(0x804311FE); inline constexpr int ERROR_INVALID_URL = static_cast(0x80433060); +inline constexpr int ERROR_PARSE_HTTP_INVALID_RESPONSE = static_cast(0x80432060); +inline constexpr int ERROR_PARSE_HTTP_INVALID_VALUE = static_cast(0x804321FE); #endif diff --git a/core/libs/prx/libSceHttp/src/Parse.cpp b/core/libs/prx/libSceHttp/src/Parse.cpp new file mode 100644 index 000000000..93b9d109b --- /dev/null +++ b/core/libs/prx/libSceHttp/src/Parse.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include "prx/libc/include/General.hpp" +#include "prx/libSceHttp/src/HttpErrors.hpp" + +namespace { + +bool isAsciiDigit(char c) { + return c >= '0' && c <= '9'; +} + +bool parseVersionNumber(const char* line, size_t lineLen, size_t& index, int32_t* value) { + if (index >= lineLen || !isAsciiDigit(line[index])) return false; + uint32_t number = 0; + while (index < lineLen && isAsciiDigit(line[index])) { + number = number * 10 + static_cast(line[index] - '0'); + ++index; + } + *value = static_cast(number); + return true; +} + +} + +extern "C" { + +int APS5_VABI sceHttpParseStatusLine(const char* statusLine, size_t lineLen, int32_t* httpMajorVer, int32_t* httpMinorVer, + int32_t* responseCode, const char** reasonPhrase, size_t* phraseLen) { + if (!statusLine) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (!httpMajorVer || !httpMinorVer || !responseCode || !reasonPhrase || !phraseLen) return ERROR_PARSE_HTTP_INVALID_VALUE; + + *httpMajorVer = 0; + *httpMinorVer = 0; + if (lineLen < 8 || std::memcmp(statusLine, "HTTP/", 5) != 0) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + + size_t index = 5; + if (!parseVersionNumber(statusLine, lineLen, index, httpMajorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (index >= lineLen || statusLine[index] != '.') return ERROR_PARSE_HTTP_INVALID_RESPONSE; + ++index; + if (!parseVersionNumber(statusLine, lineLen, index, httpMinorVer)) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (index >= lineLen || statusLine[index] != ' ') return ERROR_PARSE_HTTP_INVALID_RESPONSE; + ++index; + + if (lineLen - index < 3) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + if (!isAsciiDigit(statusLine[index]) || !isAsciiDigit(statusLine[index + 1]) || !isAsciiDigit(statusLine[index + 2])) { + return ERROR_PARSE_HTTP_INVALID_RESPONSE; + } + *responseCode = (statusLine[index] - '0') * 100 + (statusLine[index + 1] - '0') * 10 + (statusLine[index + 2] - '0'); + index += 3; + + const char* phrase = statusLine + index; + const void* lineFeed = std::memchr(phrase, '\n', lineLen - index); + if (!lineFeed) return ERROR_PARSE_HTTP_INVALID_RESPONSE; + + size_t length = static_cast(static_cast(lineFeed) - phrase); + const size_t consumed = index + length + 1; + if (consumed > static_cast(INT32_MAX)) { + NotImplemented_nid_no_patch("sceHttpParseStatusLine: status line longer than INT32_MAX"); + } + if (length > 0 && phrase[length - 1] == '\r') --length; + + *reasonPhrase = phrase; + *phraseLen = length; + return static_cast(consumed); +} + +} diff --git a/core/libs/prx/libSceHttp2/Export.cpp b/core/libs/prx/libSceHttp2/Export.cpp index cadaea011..911b65730 100644 --- a/core/libs/prx/libSceHttp2/Export.cpp +++ b/core/libs/prx/libSceHttp2/Export.cpp @@ -209,4 +209,9 @@ int APS5_VABI sceHttp2AbortRequest(int req_id) { return 0; } +int APS5_VABI sceHttp2GetMemoryPoolStats() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt new file mode 100644 index 000000000..3aae2c755 --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -0,0 +1,23 @@ +add_library(libSceLibcInternal SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceLibcInternal PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceLibcInternal PRIVATE ${CMAKE_DL_LIBS}) +configure_windows_unwind(libSceLibcInternal) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_thread_atexit_tests ${LIBS_INCLUDE_DIR}/tests/GuestThreadAtexit.cpp) + target_include_directories(guest_thread_atexit_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_thread_atexit_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_thread_atexit_tests) + add_test(NAME guest_thread_atexit COMMAND guest_thread_atexit_tests) + set_tests_properties(guest_thread_atexit PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_thread_atexit PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibcInternal/Export.cpp b/core/libs/prx/libSceLibcInternal/Export.cpp index 49699b6ca..c84ee09a1 100644 --- a/core/libs/prx/libSceLibcInternal/Export.cpp +++ b/core/libs/prx/libSceLibcInternal/Export.cpp @@ -1,5 +1,69 @@ +#include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/HeapDiagnostics.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +extern "C" void APS5_VABI sceKernelSetThreadDtors(thread_dtors_func_t dtors); + +namespace { + +using ThreadDestructorFunction = void (APS5_VABI*)(void*); + +struct ThreadDestructor { + ThreadDestructorFunction function; + void* object; + void* dsoSymbol; +}; + +struct ThreadDestructorsTag {}; + +std::vector& ThreadDestructors() { + return HostThreadLocal, ThreadDestructorsTag>(); +} + +bool IsInLoadedImage(const void* address) { + if (address == nullptr) + return false; +#ifdef _WIN32 + HMODULE module = nullptr; + return GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, static_cast(address), &module) != 0; +#else + Dl_info info{}; + return dladdr(address, &info) != 0; +#endif +} + +void APS5_VABI RunThreadDestructors_nid_no_patch() { + auto& destructors = ThreadDestructors(); + while (!destructors.empty()) { + const ThreadDestructor destructor = destructors.back(); + destructors.pop_back(); + const auto* function = reinterpret_cast(destructor.function); + if (!IsInLoadedImage(function)) { + std::ostringstream message; + message << "thread_local destructor " << function << " of dso " << destructor.dsoSymbol << " is not in a loaded image"; + throw std::runtime_error(message.str()); + } + destructor.function(destructor.object); + } +} + +void RegisterThreadExitHook() { + [[maybe_unused]] static const bool registered = [] { + sceKernelSetThreadDtors(RunThreadDestructors_nid_no_patch); + return true; + }(); +} + +} extern "C" { @@ -13,4 +77,20 @@ void APS5_VABI sceLibcHeapGetTraceInfo_nid_postfix(Info* info) { LibcHeapTraceInfo_nid_no_patch(info); } +int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(ThreadDestructorFunction destructor, void* object, void* dsoSymbol) { + RegisterThreadExitHook(); + ThreadDestructors().push_back({destructor, object, dsoSymbol}); + return 0; +} + +void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix() { + RunThreadDestructors_nid_no_patch(); +} + +int APS5_VABI _sceLibcInternalForceTlsDestructor_nid_postfix(KernelModule handle) { + (void)handle; + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libSceMsgDialog.native/CMakeLists.txt b/core/libs/prx/libSceMsgDialog.native/CMakeLists.txt new file mode 100644 index 000000000..111746d44 --- /dev/null +++ b/core/libs/prx/libSceMsgDialog.native/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libSceMsgDialog.native SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceMsgDialog.native PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceMsgDialog.native) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_msg_dialog_native_tests ${LIBS_INCLUDE_DIR}/tests/GuestMsgDialogNative.cpp) + target_include_directories(guest_msg_dialog_native_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_msg_dialog_native_tests PRIVATE libSceMsgDialog.native) + add_test(NAME guest_msg_dialog_native COMMAND guest_msg_dialog_native_tests) + if(WIN32) + set_tests_properties(guest_msg_dialog_native PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceMsgDialog.native/Export.cpp b/core/libs/prx/libSceMsgDialog.native/Export.cpp index 1423e8fed..027eff38d 100644 --- a/core/libs/prx/libSceMsgDialog.native/Export.cpp +++ b/core/libs/prx/libSceMsgDialog.native/Export.cpp @@ -8,10 +8,12 @@ namespace { std::atomic g_status{0}; +constexpr int COMMON_DIALOG_STATUS_RUNNING = 2; constexpr int COMMON_DIALOG_STATUS_FINISHED = 3; constexpr int COMMON_DIALOG_RESULT_OK = 0; constexpr int COMMON_DIALOG_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMON_DIALOG_ERROR_NOT_RUNNING = static_cast(0x80B8000Bu); constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); constexpr int BUTTON_ID_OK = 1; } @@ -20,6 +22,12 @@ extern "C" { int SceMsgDialogNativeModuleLoaded_nid_no_patch = 1; +int APS5_VABI sceMsgDialogClose(void) { + int expected = COMMON_DIALOG_STATUS_RUNNING; + if (!g_status.compare_exchange_strong(expected, COMMON_DIALOG_STATUS_FINISHED)) return COMMON_DIALOG_ERROR_NOT_RUNNING; + return 0; +} + int APS5_VABI sceMsgDialogGetResult(MsgDialogResult* result) { const int status = g_status.load(); if (status == 0) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index eb0b51b35..19a4b22bd 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -650,6 +650,11 @@ int APS5_VABI sceNetEpollCreate(const char* name, int flags) { return id; } +int APS5_VABI sceNetGetMemoryPoolStats() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceNetEpollDestroy(int eid) { std::lock_guard lk(g_mutex); if (g_epolls.erase(eid) == 0) { diff --git a/core/libs/prx/libSceNpCommerce/Export.cpp b/core/libs/prx/libSceNpCommerce/Export.cpp index 6d8f3f581..6b2bd01d9 100644 --- a/core/libs/prx/libSceNpCommerce/Export.cpp +++ b/core/libs/prx/libSceNpCommerce/Export.cpp @@ -74,4 +74,9 @@ int APS5_VABI sceNpCommerceShowPsStoreIcon(int pos) { return 0; } +int APS5_VABI sceNpCommerceSetPsStoreIconLayout(int layout) { + (void)layout; + return 0; +} + } diff --git a/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt new file mode 100644 index 000000000..2519e8cc8 --- /dev/null +++ b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt @@ -0,0 +1,21 @@ +add_library(libSceNpCppWebApi SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpCppWebApi PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpCppWebApi) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_np_cpp_web_api_string_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpCppWebApiString.cpp) + target_include_directories(guest_np_cpp_web_api_string_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_cpp_web_api_string_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_string_tests) + add_test(NAME guest_np_cpp_web_api_string COMMAND guest_np_cpp_web_api_string_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_string PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp index 50c5a6ad9..75caf2847 100644 --- a/core/libs/prx/libSceNpCppWebApi/Export.cpp +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -1,3 +1,4 @@ +#include #include #include #include "SceTypes.hpp" @@ -267,89 +268,100 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_12 return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_(void* self) { - (void)self; - return 0; +struct IntrusivePtr { + void* object; + void (APS5_VABI* deleter)(void*); + void* libContext; +}; +static_assert(sizeof(IntrusivePtr) == 0x18); + +static constexpr std::size_t ReferenceCountOffset = 0; +static constexpr std::size_t VectorReferenceCountOffset = 0x30; + +static std::atomic_ref ReferenceCount(void* object, const std::size_t offset) { + return std::atomic_ref(*reinterpret_cast(static_cast(object) + offset)); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev(void* self) { - (void)self; - return 0; +static void CopyIntrusivePtr(IntrusivePtr* self, const IntrusivePtr* source, const std::size_t referenceCountOffset) { + *self = *source; + if (self->object) ReferenceCount(self->object, referenceCountOffset).fetch_add(1); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev(void* self) { - (void)self; - return 0; +static void ReleaseIntrusivePtr(IntrusivePtr* self, const std::size_t referenceCountOffset, const char* function) { + if (!self->object) return; + if (ReferenceCount(self->object, referenceCountOffset).fetch_sub(1) != 1) return; + if (!self->deleter) NotImplemented_nid_no_patch(function); + self->deleter(self->object); + self->object = nullptr; + self->deleter = nullptr; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, ReferenceCountOffset); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { + CopyIntrusivePtr(self, source, ReferenceCountOffset); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev(IntrusivePtr* self) { + *self = {}; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev(IntrusivePtr* self) { + *self = {}; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, ReferenceCountOffset, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, VectorReferenceCountOffset, __func__); } int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEED2Ev(void* self) { @@ -447,14 +459,24 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common21DownStreamTransactionINS2_12Intrusive return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(void* self) { - (void)self; - return 0; +struct CommonString { + std::int32_t referenceCount; + char* buffer; + std::uint64_t bufferSize; + void* libContext; +}; +static_assert(sizeof(CommonString) == 0x20 && offsetof(CommonString, buffer) == 0x08); +static_assert(offsetof(CommonString, bufferSize) == 0x10 && offsetof(CommonString, libContext) == 0x18); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(CommonString* self, void* libContext) { + self->referenceCount = 0; + self->buffer = nullptr; + self->bufferSize = 0; + self->libContext = libContext; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD1Ev(CommonString* self) { + if (self->buffer != nullptr) NotImplemented_nid_no_patch("sce::Np::CppWebApi::Common::String buffer release"); } int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_12Leaderboards2V14UserEEEE8pushBackERKS8_() { @@ -663,54 +685,44 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_1 return SCE_NP_WEBAPI_ERROR_UNAVAILABLE; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv(const IntrusivePtr* self) { + return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEENS4_INS6_9RecordApi26RecordScoreResponseHeadersEEEE18getResponseHeadersERSB_() { @@ -727,9 +739,8 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6Binary9getBinaryEv() { return 0; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5c_strEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +const char* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(const CommonString* self) { + return self->buffer != nullptr ? self->buffer : ""; } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_12Leaderboards2V15EntryEEEEdeEv() { diff --git a/core/libs/prx/libSceNpSessionSignaling/CMakeLists.txt b/core/libs/prx/libSceNpSessionSignaling/CMakeLists.txt new file mode 100644 index 000000000..6446469df --- /dev/null +++ b/core/libs/prx/libSceNpSessionSignaling/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libSceNpSessionSignaling SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceNpSessionSignaling PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceNpSessionSignaling) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_np_session_signaling_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpSessionSignaling.cpp) + target_include_directories(guest_np_session_signaling_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_session_signaling_tests PRIVATE libSceNpSessionSignaling) + add_test(NAME guest_np_session_signaling COMMAND guest_np_session_signaling_tests) + if(WIN32) + set_tests_properties(guest_np_session_signaling PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpSessionSignaling/Export.cpp b/core/libs/prx/libSceNpSessionSignaling/Export.cpp index ecdaae31b..eb3ee27e0 100644 --- a/core/libs/prx/libSceNpSessionSignaling/Export.cpp +++ b/core/libs/prx/libSceNpSessionSignaling/Export.cpp @@ -69,9 +69,23 @@ int32_t APS5_VABI sceNpSessionSignalingGetConnectionStatus(int32_t context_id, i int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(int32_t context_id, void* info) { (void)context_id; - (void)info; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!info) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + return SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE; } +int APS5_VABI sceNpSessionSignalingRequestPrepare(uint32_t contextId, uint32_t* requestId) { + (void)contextId; + if (!requestId) return SCE_NP_SESSION_SIGNALING_ERROR_INVALID_ARGUMENT; + return SCE_NP_SESSION_SIGNALING_ERROR_UNAVAILABLE; +} + +int APS5_VABI sceNpSessionSignalingGetMemoryInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNpSessionSignalingGetConnectionStatistics(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceNpTrophy2/CMakeLists.txt b/core/libs/prx/libSceNpTrophy2/CMakeLists.txt index c86b204c3..06ccd898a 100644 --- a/core/libs/prx/libSceNpTrophy2/CMakeLists.txt +++ b/core/libs/prx/libSceNpTrophy2/CMakeLists.txt @@ -11,3 +11,21 @@ target_include_directories(libSceNpTrophy2 PRIVATE ) target_link_libraries(libSceNpTrophy2 PRIVATE libc) configure_windows_unwind(libSceNpTrophy2) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_nptrophy2_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpTrophy2.cpp) + target_include_directories(guest_nptrophy2_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_nptrophy2_tests PRIVATE libSceNpTrophy2 libc) + configure_windows_unwind(guest_nptrophy2_tests) + add_test(NAME guest_nptrophy2 COMMAND guest_nptrophy2_tests) + if(WIN32) + set_tests_properties(guest_nptrophy2 PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpTrophy2/src/Context.cpp b/core/libs/prx/libSceNpTrophy2/src/Context.cpp index 65a97045b..e259afb73 100644 --- a/core/libs/prx/libSceNpTrophy2/src/Context.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/Context.cpp @@ -30,4 +30,8 @@ int APS5_VABI sceNpTrophy2RegisterContext(int context, int handle, uint64_t opti return SCE_NP_TROPHY2_OK; } +int APS5_VABI sceNpTrophy2UnregisterUnlockCallback() { + return SCE_NP_TROPHY2_OK; +} + } diff --git a/core/libs/prx/libScePlayGoDialog/Export.cpp b/core/libs/prx/libScePlayGoDialog/Export.cpp index 5b85d52c3..16615b02e 100644 --- a/core/libs/prx/libScePlayGoDialog/Export.cpp +++ b/core/libs/prx/libScePlayGoDialog/Export.cpp @@ -74,10 +74,8 @@ int APS5_VABI scePlayGoDialogGetResult(void* result) { return 0; } -APS5_EXPORT("NOAMxY2EGS0", scePlayGoDialogUnknown00); -int APS5_VABI scePlayGoDialogUnknown00(void) { - NotImplemented_nid_no_patch("NOAMxY2EGS0"); - return 0; +int APS5_VABI scePlayGoDialogGetStatus(void) { + return g_status.load(); } } diff --git a/core/libs/prx/libScePngEnc/Export.cpp b/core/libs/prx/libScePngEnc/Export.cpp index 11aa6731b..4ff0af04e 100644 --- a/core/libs/prx/libScePngEnc/Export.cpp +++ b/core/libs/prx/libScePngEnc/Export.cpp @@ -52,13 +52,15 @@ PngEncContext* context(void* handle) { return ctx && ctx->magic == CONTEXT_MAGIC ? ctx : nullptr; } -int filterMode(std::uint16_t filterType) { - if (filterType == FILTER_ALL) return -1; - if (filterType & FILTER_SUB) return 1; - if (filterType & FILTER_UP) return 2; - if (filterType & FILTER_AVERAGE) return 3; - if (filterType & FILTER_PAETH) return 4; - return 0; +std::uint8_t filterSet(std::uint16_t filterType) { + if (filterType == FILTER_ALL) return Decoder::Png::FILTER_ALL; + if (filterType == 0) return Decoder::Png::FILTER_NONE; + std::uint8_t filters = 0; + if (filterType & FILTER_SUB) filters |= Decoder::Png::FILTER_SUB; + if (filterType & FILTER_UP) filters |= Decoder::Png::FILTER_UP; + if (filterType & FILTER_AVERAGE) filters |= Decoder::Png::FILTER_AVERAGE; + if (filterType & FILTER_PAETH) filters |= Decoder::Png::FILTER_PAETH; + return filters; } } @@ -120,7 +122,7 @@ int APS5_VABI scePngEncEncode(void* handle, const PngEncEncodeParam* param, PngE if (channels == 4) *destination++ = source[3]; } } - png = Decoder::Png::Encode(pixels, width, height, channels, {param->compression_level, filterMode(param->filter_type)}); + png = Decoder::Png::Encode(pixels, width, height, channels, {param->compression_level, filterSet(param->filter_type)}); } catch (const std::exception&) { return PNG_ENC_ERROR_FATAL; } diff --git a/core/libs/prx/libScePsml_debug/CMakeLists.txt b/core/libs/prx/libScePsml_debug/CMakeLists.txt new file mode 100644 index 000000000..ee07e525e --- /dev/null +++ b/core/libs/prx/libScePsml_debug/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libScePsml_debug SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libScePsml_debug PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libScePsml_debug) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_psml_tests ${LIBS_INCLUDE_DIR}/tests/GuestPsml.cpp) + target_include_directories(guest_psml_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_psml_tests PRIVATE libScePsml_debug) + add_test(NAME guest_psml COMMAND guest_psml_tests) + if(WIN32) + set_tests_properties(guest_psml PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libScePsml_debug/Export.cpp b/core/libs/prx/libScePsml_debug/Export.cpp index 67d7cf76b..2e8ee6e64 100644 --- a/core/libs/prx/libScePsml_debug/Export.cpp +++ b/core/libs/prx/libScePsml_debug/Export.cpp @@ -3,22 +3,45 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr std::int32_t SCE_PSML_ERROR_NOT_INITIALIZED = static_cast(0x8A810001); + +} + extern "C" { +std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement1100(void* requirement, const void* param) { + (void)requirement; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + +std::int32_t APS5_VABI scePsmlMfsrCreateContext1100(void** context, const void* param) { + (void)context; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1100(void* context, void* commandBuffer, const void* param) { + (void)context; + (void)commandBuffer; + (void)param; + return SCE_PSML_ERROR_NOT_INITIALIZED; +} + APS5_EXPORT("+2KpvixvL6E", scePsmlUnknown__P2KpvixvL6E); int APS5_VABI scePsmlUnknown__P2KpvixvL6E() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("3WVD91e12ZQ", scePsmlUnknown_3WVD91e12ZQ); -int APS5_VABI scePsmlUnknown_3WVD91e12ZQ() { +int APS5_VABI scePsmlMfsrInit() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("AHalTX9wFZY", scePsmlUnknown_AHalTX9wFZY); -int APS5_VABI scePsmlUnknown_AHalTX9wFZY() { +int APS5_VABI scePsmlMfsrGetDispatchMfsrPacketSizeInDwords() { NotImplemented_nid_no_patch(__func__); return 0; } @@ -47,8 +70,7 @@ int APS5_VABI scePsmlUnknown_GJY0MvuTcs8() { return 0; } -APS5_EXPORT("JaLBe0P3jSU", scePsmlUnknown_JaLBe0P3jSU); -int APS5_VABI scePsmlUnknown_JaLBe0P3jSU() { +int APS5_VABI scePsmlMfsrReleaseContext() { NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceRazorCpu/Export.cpp b/core/libs/prx/libSceRazorCpu/Export.cpp index 83a295d21..0a5ec3953 100644 --- a/core/libs/prx/libSceRazorCpu/Export.cpp +++ b/core/libs/prx/libSceRazorCpu/Export.cpp @@ -24,4 +24,15 @@ int APS5_VABI sceRazorCpuJobManagerSequence(const void* args) { return 0; } +int APS5_VABI sceRazorCpuPushMarkerStatic(const char* name, uint32_t color, uint32_t flags) { + (void)name; + (void)color; + (void)flags; + return 0; +} + +int APS5_VABI sceRazorCpuPopMarker(void) { + return 0; +} + } diff --git a/core/libs/prx/libSceRtc/Export.cpp b/core/libs/prx/libSceRtc/Export.cpp index 272b78cb3..08bf4bb06 100644 --- a/core/libs/prx/libSceRtc/Export.cpp +++ b/core/libs/prx/libSceRtc/Export.cpp @@ -109,6 +109,7 @@ std::int64_t localOffsetSeconds(std::uint64_t utcTick) { int addTicks(RtcTick* dst, const RtcTick* src, std::int64_t count, std::int64_t unit) { if (!dst || !src) return SCE_RTC_ERROR_INVALID_POINTER; + if (src->tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_VALUE; if (count != 0 && (count > std::numeric_limits::max() / unit || count < std::numeric_limits::min() / unit)) return SCE_RTC_ERROR_INVALID_VALUE; const std::int64_t delta = count * unit; if (delta < 0 ? src->tick < static_cast(-(delta + 1)) + 1 : MAX_TICK - src->tick < static_cast(delta)) return SCE_RTC_ERROR_INVALID_VALUE; @@ -118,6 +119,7 @@ int addTicks(RtcTick* dst, const RtcTick* src, std::int64_t count, std::int64_t int addMonths(RtcTick* dst, const RtcTick* src, std::int64_t months) { if (!dst || !src) return SCE_RTC_ERROR_INVALID_POINTER; + if (src->tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_VALUE; RtcDateTime time = fromTick(src->tick); const std::int64_t monthIndex = time.year * 12 + (time.month - 1) + months; const std::int64_t year = monthIndex / 12; @@ -270,6 +272,7 @@ int APS5_VABI sceRtcSetWin32FileTime(RtcDateTime* time, uint64_t win32_time) { int APS5_VABI sceRtcFormatRFC3339(char* date_time, const RtcTick* utc, int time_zone_minutes) { if (!date_time || !utc) return SCE_RTC_ERROR_INVALID_POINTER; + if (time_zone_minutes < -1439 || time_zone_minutes > 1439) return SCE_RTC_ERROR_INVALID_VALUE; RtcTick local{}; if (const int result = addTicks(&local, utc, time_zone_minutes, TICKS_PER_MINUTE); result != 0) return result; const RtcDateTime time = fromTick(local.tick); diff --git a/core/libs/prx/libSceSaveData.native/CMakeLists.txt b/core/libs/prx/libSceSaveData.native/CMakeLists.txt index 42ebeecd4..163948431 100644 --- a/core/libs/prx/libSceSaveData.native/CMakeLists.txt +++ b/core/libs/prx/libSceSaveData.native/CMakeLists.txt @@ -17,4 +17,14 @@ if(BUILD_TESTING) set_tests_properties(guest_savedata_delete PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_savedata_ps4_tests ${LIBS_INCLUDE_DIR}/tests/GuestSaveDataPs4.cpp) + target_include_directories(guest_savedata_ps4_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_savedata_ps4_tests PRIVATE libSceSaveData.native libc) + configure_windows_unwind(guest_savedata_ps4_tests) + add_test(NAME guest_savedata_ps4 COMMAND guest_savedata_ps4_tests) + if(WIN32) + set_tests_properties(guest_savedata_ps4 PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceSaveData.native/Export.cpp b/core/libs/prx/libSceSaveData.native/Export.cpp index 5c262309c..f0b798f5d 100644 --- a/core/libs/prx/libSceSaveData.native/Export.cpp +++ b/core/libs/prx/libSceSaveData.native/Export.cpp @@ -699,16 +699,24 @@ int APS5_VABI sceSaveDataUmount2(uint32_t mode, const SaveDataMountPoint* mount_ return rc; } - -APS5_EXPORT("RjMlsR8EXrw", sceSaveDataUnknown00); -int APS5_VABI sceSaveDataUnknown00(void) { - NotImplemented_nid_no_patch("RjMlsR8EXrw"); - return 0; +int APS5_VABI sceSaveDataTransferringMountPs4(const SaveDataTransferringMount* mount, SaveDataMountResult* mount_result) { + const int rc = transferringMount(mount, mount_result); + SAVEDATA_TRACE("transferringMountPs4 -> 0x%08x", static_cast(rc)); + return rc; } -APS5_EXPORT("X4MYzukPc3g", sceSaveDataUnknown01); -int APS5_VABI sceSaveDataUnknown01(void) { - NotImplemented_nid_no_patch("X4MYzukPc3g"); - return 0; +static int dirNameSearchPs4(const SaveDataDirNameSearchCond* cond, SaveDataDirNameSearchResult* result) { + if (cond == nullptr || result == nullptr) { + throw std::runtime_error("sceSaveDataDirNameSearchPs4: null argument"); + } + result->hit_num = 0; + result->set_num = 0; + return SAVE_DATA_OK; +} + +int APS5_VABI sceSaveDataDirNameSearchPs4(const SaveDataDirNameSearchCond* cond, SaveDataDirNameSearchResult* result) { + const int rc = dirNameSearchPs4(cond, result); + SAVEDATA_TRACE("dirNameSearchPs4 user=%d -> 0x%08x", cond->user_id, static_cast(rc)); + return rc; } } diff --git a/core/libs/prx/libSceShare/CMakeLists.txt b/core/libs/prx/libSceShare/CMakeLists.txt new file mode 100644 index 000000000..74ac02fb2 --- /dev/null +++ b/core/libs/prx/libSceShare/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(libSceShare SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceShare PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceShare) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_share_tests ${LIBS_INCLUDE_DIR}/tests/GuestShare.cpp) + target_include_directories(guest_share_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_share_tests PRIVATE libSceShare) + add_test(NAME guest_share COMMAND guest_share_tests) + if(WIN32) + set_tests_properties(guest_share PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceShare/Export.cpp b/core/libs/prx/libSceShare/Export.cpp index b75a31a22..caabed9b9 100644 --- a/core/libs/prx/libSceShare/Export.cpp +++ b/core/libs/prx/libSceShare/Export.cpp @@ -5,6 +5,13 @@ // Capture and sharing are not emulated; policy and parameter calls are accepted. +namespace { + +constexpr std::int32_t ERROR_NOT_SUPPORTED = static_cast(0x81960007); +constexpr std::int32_t REQUEST_ID_INVALID = -1; + +} + extern "C" { int APS5_VABI sceShareCaptureScreenshot(const void* param, int32_t* req_id) { @@ -85,10 +92,12 @@ int APS5_VABI sceShareUnregisterContentEventCallback(void* callback) { return 0; } -APS5_EXPORT("GQTObcITIXI", sceShareUnknown_GQTObcITIXI); -int32_t APS5_VABI sceShareUnknown_GQTObcITIXI(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceShareCaptureScreenshotExtended(const void* extended_param, int32_t* req_id) { + (void)extended_param; + if (req_id != nullptr) { + *req_id = REQUEST_ID_INVALID; + } + return ERROR_NOT_SUPPORTED; } } diff --git a/core/libs/prx/libSceSystemService/Export.cpp b/core/libs/prx/libSceSystemService/Export.cpp index 261f91556..205897fe2 100644 --- a/core/libs/prx/libSceSystemService/Export.cpp +++ b/core/libs/prx/libSceSystemService/Export.cpp @@ -80,12 +80,19 @@ int APS5_VABI sceSystemServiceParamGetInt(int paramId, int* value) { return SYSTEM_SERVICE_OK; } -int APS5_VABI sceSystemServiceParamGetString(int param_id, char* buf, size_t buf_size) { - (void)param_id; - (void)buf; - (void)buf_size; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSystemServiceParamGetString(int paramId, char* buf, size_t bufSize) { + if (buf == nullptr || bufSize == 0) { + return SYSTEM_SERVICE_ERROR_PARAMETER; + } + if (paramId != SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME) { + NotImplemented_nid_no_patch("sceSystemServiceParamGetString: parameter other than the system name"); + } + if (bufSize < SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH) { + NotImplemented_nid_no_patch("sceSystemServiceParamGetString: buffer shorter than 65 bytes"); + } + constexpr char SystemName[] = "PS5"; + std::memcpy(buf, SystemName, sizeof(SystemName)); + return SYSTEM_SERVICE_OK; } int APS5_VABI sceSystemServicePowerTick(void) { @@ -115,6 +122,16 @@ int APS5_VABI sceSystemServiceInitializePlayerDialogParam(void* param) { return SYSTEM_SERVICE_OK; } +int APS5_VABI sceSystemServiceDisableMediaPlay() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSystemServiceReenableMediaPlay() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceSystemServiceLaunchPlayerDialog(const void* param) { if (param == nullptr) return SYSTEM_SERVICE_ERROR_PARAMETER; return SYSTEM_SERVICE_OK; diff --git a/core/libs/prx/libSceSystemService/SystemService.hpp b/core/libs/prx/libSceSystemService/SystemService.hpp index 7a2c84a12..e86820da4 100644 --- a/core/libs/prx/libSceSystemService/SystemService.hpp +++ b/core/libs/prx/libSceSystemService/SystemService.hpp @@ -1,6 +1,7 @@ #ifndef CORE_LIBS_PRX_LIBSCESYSTEMSERVICE_SYSTEMSERVICE_HPP #define CORE_LIBS_PRX_LIBSCESYSTEMSERVICE_SYSTEMSERVICE_HPP +#include #include constexpr int SYSTEM_SERVICE_OK = 0; @@ -12,9 +13,12 @@ constexpr int SYSTEM_SERVICE_PARAM_ID_DATE_FORMAT = 2; constexpr int SYSTEM_SERVICE_PARAM_ID_TIME_FORMAT = 3; constexpr int SYSTEM_SERVICE_PARAM_ID_TIME_ZONE = 4; constexpr int SYSTEM_SERVICE_PARAM_ID_SUMMERTIME = 5; +constexpr int SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME = 6; constexpr int SYSTEM_SERVICE_PARAM_ID_GAME_PARENTAL_LEVEL = 7; constexpr int SYSTEM_SERVICE_PARAM_ID_ENTER_BUTTON_ASSIGN = 1000; +constexpr std::size_t SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH = 65; + constexpr int SYSTEM_SERVICE_PARAM_LANG_ENGLISH_US = 1; constexpr int SYSTEM_SERVICE_PARAM_DATE_FORMAT_DDMMYYYY = 1; constexpr int SYSTEM_SERVICE_PARAM_TIME_FORMAT_24HOUR = 1; diff --git a/core/libs/prx/libSceUlt/CMakeLists.txt b/core/libs/prx/libSceUlt/CMakeLists.txt new file mode 100644 index 000000000..2d57d4f69 --- /dev/null +++ b/core/libs/prx/libSceUlt/CMakeLists.txt @@ -0,0 +1,24 @@ +add_library(libSceUlt SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceUlt PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceUlt) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_ult_queue_tests ${LIBS_INCLUDE_DIR}/tests/GuestUltQueue.cpp) + target_include_directories(guest_ult_queue_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ult_queue_tests PRIVATE libSceUlt libc) + configure_windows_unwind(guest_ult_queue_tests) + foreach(testCase IN ITEMS validation fifo blocking shared-pool finalize stress) + add_test(NAME guest_ult_queue_${testCase} COMMAND guest_ult_queue_tests ${testCase}) + set_tests_properties(guest_ult_queue_${testCase} PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_ult_queue_${testCase} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() +endif() diff --git a/core/libs/prx/libSceUlt/Export.cpp b/core/libs/prx/libSceUlt/Export.cpp index ce49fcc2c..a224d3c35 100644 --- a/core/libs/prx/libSceUlt/Export.cpp +++ b/core/libs/prx/libSceUlt/Export.cpp @@ -3,9 +3,10 @@ #include #include #include -#include +#include #include #include +#include #include #include @@ -23,7 +24,7 @@ std::mutex gMutex; std::unordered_map> gMutexes; std::unordered_map> gSemaphores; std::unordered_map gResourcePools; -std::unordered_map gQueueDataPools; +std::unordered_map> gQueueDataPools; std::unordered_map> gQueues; std::unordered_map gRuntimes; std::unordered_map> gUlthreads; @@ -52,6 +53,59 @@ int queueGetState(void* queue, std::shared_ptr* out) { return ULT_OK; } +int QueueTransfer(void* queue, void* data, const void* source, bool push, bool blocking) { + std::shared_ptr state; + if (int result = queueGetState(queue, &state); result != ULT_OK) return result; + if ((push ? source : data) == nullptr) return ULT_ERROR_NULL; + auto& pool = *state->_pool; + std::unique_lock lock(pool._mutex); + if (!state->_alive) return ULT_ERROR_STATE; + const auto ready = [&] { return !state->_alive || (push ? pool._free : state->_head) != UINT32_MAX; }; + if (!ready()) { + if (!blocking) return ULT_ERROR_AGAIN; + ++state->_waiters; + try { + (push ? pool._spaceAvailable : state->_dataAvailable).wait(lock, ready); + } catch (...) { + --state->_waiters; + throw; + } + --state->_waiters; + if (!state->_alive) return ULT_ERROR_STATE; + } + if (push) { + const auto slot = pool._free; + std::memcpy(pool._data.data() + slot * pool._dataSize, source, state->_dataSize); + pool._free = pool._next[slot]; + pool._next[slot] = UINT32_MAX; + if (state->_tail == UINT32_MAX) state->_head = slot; + else pool._next[state->_tail] = slot; + state->_tail = slot; + } else { + const auto slot = state->_head; + std::memcpy(data, pool._data.data() + slot * pool._dataSize, state->_dataSize); + state->_head = pool._next[slot]; + if (state->_head == UINT32_MAX) state->_tail = UINT32_MAX; + pool._next[slot] = pool._free; + pool._free = slot; + } + lock.unlock(); + if (push) state->_dataAvailable.notify_one(); + else pool._spaceAvailable.notify_one(); + return ULT_OK; +} + +void ReleaseQueue(UltQueueState& state) { + auto& pool = *state._pool; + if (state._tail != UINT32_MAX) { + pool._next[state._tail] = pool._free; + pool._free = state._head; + } + state._head = state._tail = UINT32_MAX; + state._alive = false; + --pool._queues; +} + void* APS5_VABI ulthreadRunner(void* arg) { auto* state = static_cast(arg); return reinterpret_cast(static_cast(state->_entry(state->_arg))); @@ -79,8 +133,15 @@ int APS5_VABI sceUltFinalize() { gSemaphores.clear(); gMutexes.clear(); gResourcePools.clear(); - gQueueDataPools.clear(); + for (auto& entry : gQueues) { + auto& state = *entry.second; + std::lock_guard stateLock(state._pool->_mutex); + ReleaseQueue(state); + state._dataAvailable.notify_all(); + state._pool->_spaceAvailable.notify_all(); + } gQueues.clear(); + gQueueDataPools.clear(); gRuntimes.clear(); gUlthreads.clear(); } @@ -162,38 +223,71 @@ int APS5_VABI sceUltWaitingQueueResourcePoolCreate(void* pool, const char* name, } std::uint64_t APS5_VABI sceUltQueueDataResourcePoolGetWorkAreaSize(std::uint32_t numData, std::uint64_t dataSize, std::uint32_t numQueueObject) { - const std::uint64_t dataArea = static_cast(numData) * alignUp(dataSize, 8u); - const std::uint64_t queueArea = static_cast(numQueueObject) * 512u; + if (dataSize > UINT64_MAX - 7u) throw std::out_of_range("ULT queue data size overflow"); + const auto stride = alignUp(dataSize, 8u); + const auto queueArea = static_cast(numQueueObject) * 512u; + if (stride != 0 && numData > (UINT64_MAX - queueArea) / stride) { + throw std::out_of_range("ULT queue work area size overflow"); + } + const std::uint64_t dataArea = static_cast(numData) * stride; return alignUp(dataArea + queueArea, 8u); } int APS5_VABI sceUltQueueDataResourcePoolCreate(void* pool, const char* name, std::uint32_t numData, std::uint64_t dataSize, std::uint32_t numQueueObject, void* waitingQueueResourcePool, void* workArea, const void* optParam, std::uint32_t buildVersion) { (void)name; - (void)optParam; (void)buildVersion; if (pool == nullptr) { return ULT_ERROR_NULL; } + if ((reinterpret_cast(pool) & 7u) != 0) return ULT_ERROR_ALIGNMENT; + if (numData == 0 || numQueueObject == 0 || dataSize == 0 || + dataSize > std::numeric_limits::max() / numData) return ULT_ERROR_RANGE; + if (optParam != nullptr) NotImplemented_nid_no_patch("sceUltQueueDataResourcePoolCreate: optParam"); std::lock_guard lock(gMutex); if (waitingQueueResourcePool != nullptr && gResourcePools.find(waitingQueueResourcePool) == gResourcePools.end()) { return ULT_ERROR_INVALID; } + if (gQueueDataPools.contains(pool)) return ULT_ERROR_STATE; + auto state = std::make_shared(); + state->_numData = numData; + state->_dataSize = dataSize; + state->_numQueueObject = numQueueObject; + state->_waitingPool = waitingQueueResourcePool; + state->_workArea = workArea; + state->_data.resize(numData * dataSize); + state->_next.resize(numData); + for (std::uint32_t slot = 0; slot < numData; ++slot) { + state->_next[slot] = slot + 1u < numData ? slot + 1u : UINT32_MAX; + } + state->_free = 0; + gQueueDataPools.emplace(pool, std::move(state)); std::memset(pool, 0, 512); - gQueueDataPools[pool] = {numData, dataSize, numQueueObject, waitingQueueResourcePool, workArea}; + return ULT_OK; +} + +int APS5_VABI sceUltQueueDataResourcePoolDestroy(void* pool) { + if (pool == nullptr) return ULT_ERROR_NULL; + std::lock_guard lock(gMutex); + const auto it = gQueueDataPools.find(pool); + if (it == gQueueDataPools.end()) return ULT_ERROR_STATE; + auto state = it->second; + std::lock_guard stateLock(state->_mutex); + if (state->_queues != 0) return ULT_ERROR_BUSY; + gQueueDataPools.erase(it); return ULT_OK; } int APS5_VABI sceUltQueueCreate(void* queue, const char* name, std::uint64_t dataSize, void* waitingQueueResourcePool, void* queueDataResourcePool, const void* optParam, std::uint32_t buildVersion) { (void)name; - (void)optParam; (void)buildVersion; if (queue == nullptr) { return ULT_ERROR_NULL; } + if ((reinterpret_cast(queue) & 7u) != 0) return ULT_ERROR_ALIGNMENT; + if (dataSize == 0) return ULT_ERROR_RANGE; + if (optParam != nullptr) NotImplemented_nid_no_patch("sceUltQueueCreate: optParam"); auto state = std::make_shared(); state->_dataSize = dataSize; - state->_waitingPool = waitingQueueResourcePool; - state->_dataPool = queueDataResourcePool; std::lock_guard lock(gMutex); auto dataPoolIt = gQueueDataPools.find(queueDataResourcePool); if (dataPoolIt == gQueueDataPools.end()) { @@ -202,48 +296,48 @@ int APS5_VABI sceUltQueueCreate(void* queue, const char* name, std::uint64_t dat if (waitingQueueResourcePool != nullptr && gResourcePools.find(waitingQueueResourcePool) == gResourcePools.end()) { return ULT_ERROR_INVALID; } - state->_capacity = dataPoolIt->second._numData; + if (gQueues.contains(queue)) return ULT_ERROR_STATE; + state->_pool = dataPoolIt->second; + auto& pool = *state->_pool; + std::lock_guard stateLock(pool._mutex); + if (dataSize > pool._dataSize || waitingQueueResourcePool != pool._waitingPool) return ULT_ERROR_INVALID; + if (pool._queues == pool._numQueueObject) return ULT_ERROR_AGAIN; + gQueues.emplace(queue, std::move(state)); + ++pool._queues; std::memset(queue, 0, 512); - gQueues[queue] = std::move(state); return ULT_OK; } int APS5_VABI sceUltQueuePush(void* queue, const void* data) { - std::shared_ptr state; - if (int ret = queueGetState(queue, &state); ret != ULT_OK) { - return ret; - } - if (data == nullptr && state->_dataSize != 0) { - return ULT_ERROR_NULL; - } - std::lock_guard lock(state->_mutex); - if (state->_capacity != 0 && state->_items.size() >= state->_capacity) { - return ULT_OK; - } - auto& item = state->_items.emplace_back(static_cast(state->_dataSize)); - if (!item.empty()) { - std::memcpy(item.data(), data, item.size()); - } - return ULT_OK; + return QueueTransfer(queue, nullptr, data, true, true); +} + +int APS5_VABI sceUltQueueTryPush(void* queue, const void* data) { + return QueueTransfer(queue, nullptr, data, true, false); +} + +int APS5_VABI sceUltQueuePop(void* queue, void* data) { + return QueueTransfer(queue, data, nullptr, false, true); } int APS5_VABI sceUltQueueTryPop(void* queue, void* data) { - std::shared_ptr state; - if (int ret = queueGetState(queue, &state); ret != ULT_OK) { - return ret; - } - if (data == nullptr && state->_dataSize != 0) { - return ULT_ERROR_NULL; - } - std::lock_guard lock(state->_mutex); - if (state->_items.empty()) { - return ULT_ERROR_AGAIN; - } - auto item = std::move(state->_items.front()); - state->_items.pop_front(); - if (!item.empty()) { - std::memcpy(data, item.data(), item.size()); + return QueueTransfer(queue, data, nullptr, false, false); +} + +int APS5_VABI sceUltQueueDestroy(void* queue) { + if (queue == nullptr) return ULT_ERROR_NULL; + std::lock_guard lock(gMutex); + const auto it = gQueues.find(queue); + if (it == gQueues.end()) return ULT_ERROR_STATE; + auto state = it->second; + auto& pool = *state->_pool; + { + std::lock_guard stateLock(pool._mutex); + if (state->_waiters != 0) return ULT_ERROR_BUSY; + ReleaseQueue(*state); + gQueues.erase(it); } + pool._spaceAvailable.notify_all(); return ULT_OK; } diff --git a/core/libs/prx/libSceUlt/SceUltTypes.hpp b/core/libs/prx/libSceUlt/SceUltTypes.hpp index d37181ef8..1a8a0c8d5 100644 --- a/core/libs/prx/libSceUlt/SceUltTypes.hpp +++ b/core/libs/prx/libSceUlt/SceUltTypes.hpp @@ -1,6 +1,13 @@ #ifndef CORE_LIBS_PRX_LIBSCEULT_SCEULTTYPES_HPP #define CORE_LIBS_PRX_LIBSCEULT_SCEULTTYPES_HPP +#include +#include +#include +#include +#include +#include "SceTypes.hpp" + constexpr int ULT_OK = 0; constexpr int ULT_ERROR_NULL = -2139029503; constexpr int ULT_ERROR_ALIGNMENT = -2139029502; @@ -30,6 +37,12 @@ struct UltResourcePoolState { }; struct UltQueueDataPoolState { + std::mutex _mutex; + std::condition_variable _spaceAvailable; + std::vector _data; + std::vector _next; + std::uint32_t _free = UINT32_MAX; + std::uint32_t _queues = 0; std::uint32_t _numData = 0; std::uint64_t _dataSize = 0; std::uint32_t _numQueueObject = 0; @@ -38,12 +51,13 @@ struct UltQueueDataPoolState { }; struct UltQueueState { - std::mutex _mutex; - std::deque> _items; + std::shared_ptr _pool; + std::condition_variable _dataAvailable; + std::uint32_t _head = UINT32_MAX; + std::uint32_t _tail = UINT32_MAX; + std::uint32_t _waiters = 0; + bool _alive = true; std::uint64_t _dataSize = 0; - std::uint32_t _capacity = 0; - void* _waitingPool = nullptr; - void* _dataPool = nullptr; }; struct UltRuntimeState { diff --git a/core/libs/prx/libSceUsbd/Export.cpp b/core/libs/prx/libSceUsbd/Export.cpp new file mode 100644 index 000000000..fb34206c2 --- /dev/null +++ b/core/libs/prx/libSceUsbd/Export.cpp @@ -0,0 +1,10 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp index ea75f8d80..adf59924a 100644 --- a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp +++ b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp @@ -62,6 +62,8 @@ static constexpr std::size_t VIDEO_OUT_FLIP_QUEUE_CAPACITY = 16; static constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED = 0; static constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_COMPRESSED = 1; +static constexpr std::uint64_t VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE = 0; +static constexpr std::uint64_t VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY = 8; static constexpr int VIDEO_OUT_EVENT_FLIP = 0; static constexpr int VIDEO_OUT_EVENT_VBLANK = 1; diff --git a/core/libs/prx/libSceVideoOut/src/Buffer.cpp b/core/libs/prx/libSceVideoOut/src/Buffer.cpp index aa40461e1..25fb6a241 100644 --- a/core/libs/prx/libSceVideoOut/src/Buffer.cpp +++ b/core/libs/prx/libSceVideoOut/src/Buffer.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -10,7 +11,7 @@ AgcDriver::DisplayBuffer DescribeVideoOutBuffer(const VideoOutBuffer& buffer, const BufferAttributeGroup& group) { const auto& attribute = group.attribute; if (attribute.reserved0 != 0 || attribute.pad0 != 0 || attribute.reserved1[0] != 0 || attribute.reserved1[1] != 0 || attribute.reserved1[2] != 0) throw std::runtime_error("VideoOut: reserved buffer attribute bits are set"); - if (attribute.tiling_mode > 1 || (attribute.tiling_mode == 0 && attribute.pitch_in_pixel != 0) || attribute.aspect_ratio != 0 || attribute.option != 0) throw std::runtime_error("VideoOut: unsupported tiling, pitch, aspect ratio or buffer option"); + if (attribute.tiling_mode > 1 || (attribute.tiling_mode == 0 && attribute.pitch_in_pixel != 0) || attribute.aspect_ratio != 0 || (attribute.option != VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE && attribute.option != VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY)) throw std::runtime_error("VideoOut: unsupported tiling " + std::to_string(attribute.tiling_mode) + ", pitch " + std::to_string(attribute.pitch_in_pixel) + ", aspect ratio " + std::to_string(attribute.aspect_ratio) + " or buffer option " + std::to_string(attribute.option)); if (group.category != VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED || buffer.metadataAddress != 0 || attribute.dcc_control != 0 || attribute.dcc_cb_register_clear_color != 0) throw std::runtime_error("VideoOut: DCC presentation is not implemented"); const AgcDriver::DisplayBuffer result{buffer.dataAddress, attribute.pixel_format, attribute.width, attribute.height, attribute.tiling_mode, attribute.pitch_in_pixel}; static_cast(AgcDriverDisplayBufferSize_nid_postfix(result)); diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index b6d5471d2..b61d35d41 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -199,6 +199,7 @@ VideoOutDriver& VideoOutDriver::Get() { } VideoOutDriver::VideoOutDriver() { + SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2"); if (SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) { throw std::runtime_error(std::string("SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: ") + SDL_GetError()); } diff --git a/core/libs/prx/libSceVideoOut/tests/Flip.cpp b/core/libs/prx/libSceVideoOut/tests/Flip.cpp index ad78a9798..9dd1463b0 100644 --- a/core/libs/prx/libSceVideoOut/tests/Flip.cpp +++ b/core/libs/prx/libSceVideoOut/tests/Flip.cpp @@ -162,6 +162,18 @@ void testDecode() { expectFailure([&] { AgcDriver::ReadDisplayBuffer(buffer); }); buffer.address = std::numeric_limits::max() & ~uint64_t{65535}; expectFailure([&] { AgcDriver::DisplayBufferSize(buffer); }); + const VideoOutBuffer registered{0, reinterpret_cast(tiled.data()), 0}; + BufferAttributeGroup group{}; + sceVideoOutSetBufferAttribute2(&group.attribute, 0x8000000000000000ull, 0, 259, 137, VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE, 0, 0); + group.occupied = true; + const auto plain = DescribeVideoOutBuffer(registered, group); + group.attribute.option = VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY; + const auto strict = DescribeVideoOutBuffer(registered, group); + check(strict.address == plain.address && strict.pixelFormat == plain.pixelFormat && strict.width == plain.width && strict.height == plain.height && strict.tilingMode == plain.tilingMode && strict.pitchInPixel == plain.pitchInPixel, "the STRICT_COLORIMETRY option changed the described buffer"); + for (const uint64_t option : {1ull, 32ull, 40ull}) { + group.attribute.option = option; + check(expectFailure([&] { DescribeVideoOutBuffer(registered, group); }).find("buffer option " + std::to_string(option)) != std::string::npos, "an unsupported buffer option was accepted"); + } } void testOpenParam() { diff --git a/core/libs/prx/libSceVoice/Export.cpp b/core/libs/prx/libSceVoice/Export.cpp index 735f0697e..0f824d139 100644 --- a/core/libs/prx/libSceVoice/Export.cpp +++ b/core/libs/prx/libSceVoice/Export.cpp @@ -204,4 +204,9 @@ int APS5_VABI sceVoiceWriteToIPort(uint32_t input_port_id, const void* data, uin return 0; } +int APS5_VABI sceVoiceSetMuteFlag() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libSceVoiceQoS/Export.cpp b/core/libs/prx/libSceVoiceQoS/Export.cpp index a0af71ff3..4cb9ab16f 100644 --- a/core/libs/prx/libSceVoiceQoS/Export.cpp +++ b/core/libs/prx/libSceVoiceQoS/Export.cpp @@ -13,4 +13,59 @@ int APS5_VABI sceVoiceQoSInit(void* mem_block, uint32_t mem_size, int32_t app_ty return 0; } +int APS5_VABI sceVoiceQoSEnd() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSCreateLocalEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSDeleteLocalEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSCreateRemoteEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSDeleteRemoteEndpoint() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSConnect() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSDisconnect() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSSetLocalEndpointAttribute() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSSetRemoteEndpointAttribute() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSReadPacket() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceVoiceQoSWritePacket() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libc/Export.cpp b/core/libs/prx/libc/Export.cpp index 17128de2e..3fb873fd7 100644 --- a/core/libs/prx/libc/Export.cpp +++ b/core/libs/prx/libc/Export.cpp @@ -30,12 +30,6 @@ extern "C" { return 0; } -APS5_EXPORT("Pu0Ecyk-7FU", libcUnknown_Pu0Ecyk_M7FU); -int APS5_VABI libcUnknown_Pu0Ecyk_M7FU() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - // Dead import of Cyberpunk 2077 (PPSA04029): no call sites, but the // Windows loader resolves imports strictly, so it must be present. APS5_EXPORT("u2tMGOLaqnE", libcUnknown_u2tMGOLaqnE); @@ -70,12 +64,6 @@ int APS5_VABI libcCyberUnknown19(void) { } -APS5_EXPORT("2gbcltk3swE", libcCyberUnknown00); -std::uint64_t APS5_VABI libcCyberUnknown00(void) { - NotImplemented_nid_no_patch("2gbcltk3swE"); - return 0; -} - APS5_EXPORT("DiGVep5yB5w", libcCyberUnknown01); std::uint64_t APS5_VABI libcCyberUnknown01(void) { NotImplemented_nid_no_patch("DiGVep5yB5w"); diff --git a/core/libs/prx/libc/src/AllocatingStrings.cpp b/core/libs/prx/libc/src/AllocatingStrings.cpp index d3539587b..74779c475 100644 --- a/core/libs/prx/libc/src/AllocatingStrings.cpp +++ b/core/libs/prx/libc/src/AllocatingStrings.cpp @@ -30,6 +30,10 @@ char* APS5_VABI strndup_nid_postfix(const char* source, std::size_t limit) { } } +char* APS5_VABI strdup_nid_postfix(const char* source) { + return strndup_nid_postfix(source, static_cast(-1)); +} + int APS5_VABI asprintf_nid_postfix(char** destination, const char* format, ...) { if (!destination) { errno = 22; return -1; } *destination = nullptr; diff --git a/core/libs/prx/libc/src/FormattingWide.cpp b/core/libs/prx/libc/src/FormattingWide.cpp index 6684ffc68..ad53b9862 100644 --- a/core/libs/prx/libc/src/FormattingWide.cpp +++ b/core/libs/prx/libc/src/FormattingWide.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -88,7 +89,7 @@ void AppendPadded(std::u16string& out, const std::u16string& text, bool left, in if (left) out.append(pad, u' '); } -std::u16string FormatWide(const char16_t* format, VaList* source) { +std::u16string FormatWide(const char16_t* format, VaList* source, bool rejectNullStrings = false) { if (format == nullptr || source == nullptr) throw std::invalid_argument("Null formatting argument"); LibcDetail::FormatArguments args(source); std::u16string out; @@ -171,10 +172,12 @@ std::u16string FormatWide(const char16_t* format, VaList* source) { const std::size_t limit = precision < 0 ? SIZE_MAX : static_cast(precision); if (conversion == u'S' || length == "l") { const char16_t* value = args.Next(); + if (value == nullptr && rejectNullStrings) throw std::invalid_argument("Null string argument"); if (value == nullptr) value = u"(null)"; for (std::size_t index = 0; index < limit && value[index] != 0; ++index) text.push_back(value[index]); } else { const char* value = args.Next(); + if (value == nullptr && rejectNullStrings) throw std::invalid_argument("Null string argument"); AppendUtf16(text, value != nullptr ? value : "(null)", limit); } AppendPadded(out, text, left, width); @@ -204,6 +207,27 @@ int APS5_VABI vswprintf_nid_postfix(char16_t* buffer, std::size_t size, const ch } } +int APS5_VABI snwprintf_s_nid_postfix(char16_t* buffer, std::size_t size, const char16_t* format, ...) { + constexpr std::size_t RsizeMax = SIZE_MAX >> 1; + int result = -1; + if (buffer != nullptr && format != nullptr && size != 0 && size <= RsizeMax) { + APS5_VA_BEGIN(format); + try { + const std::u16string text = FormatWide(format, reinterpret_cast(args), true); + if (text.size() <= static_cast(INT_MAX)) { + const std::size_t copied = text.size() < size - 1 ? text.size() : size - 1; + std::memcpy(buffer, text.data(), copied * sizeof(char16_t)); + buffer[copied] = 0; + result = static_cast(text.size()); + } + } catch (const std::invalid_argument&) { + } + APS5_VA_END(); + } + if (result < 0 && buffer != nullptr && size != 0 && size < RsizeMax) buffer[0] = 0; + return result; +} + int APS5_VABI wprintf_nid_postfix(const char16_t* format, ...) { APS5_VA_BEGIN(format); int result = -1; diff --git a/core/libs/prx/libc/src/GuestArena.cpp b/core/libs/prx/libc/src/GuestArena.cpp index b53450599..92f4877dc 100644 --- a/core/libs/prx/libc/src/GuestArena.cpp +++ b/core/libs/prx/libc/src/GuestArena.cpp @@ -21,7 +21,7 @@ namespace { constexpr std::uintptr_t ArenaStart = 0x0000000200000000ull; constexpr std::uintptr_t SystemReservedStart = 0x00000007FFFFC000ull; constexpr std::uintptr_t SystemReservedEnd = 0x0000001000000000ull; -constexpr std::size_t ArenaSize = 0x0000007000000000ull; +constexpr std::uintptr_t ApplicationAreaEnd = 0x000000FC00000000ull; std::uintptr_t alignUp(std::uintptr_t value, std::size_t alignment) { return (value + alignment - 1) & ~(static_cast(alignment) - 1); @@ -69,8 +69,11 @@ public: std::lock_guard lock(_lock); const auto start = reinterpret_cast(pointer); const auto next = _used.lower_bound(start); - if ((next != _used.end() && next->first < start + bytes) || (next != _used.begin() && std::prev(next)->second > start)) - throw std::runtime_error("fixed guest mapping overlaps a guest arena range"); + if ((next != _used.end() && next->first < start + bytes) || (next != _used.begin() && std::prev(next)->second > start)) { + char message[160]; + std::snprintf(message, sizeof(message), "fixed guest mapping 0x%llx+0x%zx overlaps %s", static_cast(start), bytes, OverlapsHostRegion(start, bytes) ? "a host region in the guest arena" : "a guest arena range"); + throw std::runtime_error(message); + } _used.emplace(start, start + bytes); } @@ -103,7 +106,7 @@ private: SYSTEM_INFO system{}; GetSystemInfo(&system); const std::uintptr_t granularity = system.dwAllocationGranularity; - const std::uintptr_t end = ArenaStart + ArenaSize; + const std::uintptr_t end = ApplicationAreaEnd; for (std::uintptr_t cursor = ArenaStart; cursor < end;) { MEMORY_BASIC_INFORMATION info{}; if (VirtualQuery(reinterpret_cast(cursor), &info, sizeof(info)) == 0) throw std::system_error(static_cast(GetLastError()), std::system_category(), "query the guest arena range"); @@ -197,18 +200,28 @@ bool GuestArenaHostRegionOverlaps_nid_postfix(std::uintptr_t address, std::size_ return Arena::Get().OverlapsHostRegion(address, bytes); } +namespace { + +std::invalid_argument OutsideArena(const char* operation, const void* pointer, std::size_t bytes) { + char message[128]; + std::snprintf(message, sizeof(message), "%s 0x%llx+0x%zx outside the guest arena", operation, static_cast(reinterpret_cast(pointer)), bytes); + return std::invalid_argument(message); +} + +} + void GuestArenaCommit_nid_postfix(void* pointer, std::size_t bytes, std::uint32_t protection, std::size_t granule) { - if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("commit outside the guest arena"); + if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("commit", pointer, bytes); WindowsMappings::Get().Commit(pointer, bytes, protection, granule, Arena::Get().WriteWatched()); } void GuestArenaReset_nid_postfix(void* pointer, std::size_t bytes) { - if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("reset outside the guest arena"); + if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("reset", pointer, bytes); WindowsMappings::Get().Reset(pointer, bytes); } void GuestArenaMap_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection) { - if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("shared mapping outside the guest arena"); + if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("shared mapping", pointer, bytes); WindowsMappings::Get().Map(pointer, bytes, section, offset, protection); } diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 40eda10b4..f2fb249c4 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include "prx/libc/include/General.hpp" @@ -13,6 +15,7 @@ float APS5_VABI acosf_nid_postfix(float x) { return std::acos(x); } float APS5_VABI atan2f_nid_postfix(float y, float x) { return std::atan2(y, x); } float APS5_VABI tanf_nid_postfix(float x) { return std::tan(x); } float APS5_VABI log10f_nid_postfix(float x) { return std::log10(x); } +float APS5_VABI logbf_nid_postfix(float x) { return std::logb(x); } double APS5_VABI exp2_nid_postfix(double x) { return std::exp2(x); } double APS5_VABI ldexp_nid_postfix(double x, int exponent) { return std::ldexp(x, exponent); } double APS5_VABI scalbn_nid_postfix(double x, int exponent) { return std::scalbn(x, exponent); } @@ -78,6 +81,22 @@ double APS5_VABI tanh_nid_postfix(double x) { return std::tanh(x); } float APS5_VABI tanhf_nid_postfix(float x) { return std::tanh(x); } float APS5_VABI _FSinh_nid_postfix(float x, float y) { return y * std::sinh(x); } float APS5_VABI _FCosh_nid_postfix(float x, float y) { return y * std::cosh(x); } + +struct alignas(16) LibcFloatConstant { std::uint32_t bits[4]; }; +LibcFloatConstant _FInf_nid_postfix {{0x7f800000u, 0, 0, 0}}; +LibcFloatConstant _FNan_nid_postfix {{0x7fc00000u, 0, 0, 0}}; + +short APS5_VABI _FDtest_nid_postfix(const float* value) { + constexpr short Denormal = -2, Finite = -1, Zero = 0, Infinite = 1, NotANumber = 2; + if (value == nullptr) throw std::invalid_argument("_FDtest: null value"); + std::uint32_t bits; + std::memcpy(&bits, value, sizeof(bits)); + const auto exponent = bits & 0x7f800000u; + const auto fraction = bits & 0x007fffffu; + if (exponent == 0x7f800000u) return fraction != 0 ? NotANumber : Infinite; + if (exponent == 0) return fraction != 0 ? Denormal : Zero; + return Finite; +} int APS5_VABI __isnanf_nid_postfix(float x) { return std::isnan(x) ? 1 : 0; } int APS5_VABI __signbitf_nid_postfix(float x) { return std::signbit(x) ? 1 : 0; } diff --git a/core/libs/prx/libc/src/RuntimeSupport.cpp b/core/libs/prx/libc/src/RuntimeSupport.cpp index ef00dd455..e6bf99f61 100644 --- a/core/libs/prx/libc/src/RuntimeSupport.cpp +++ b/core/libs/prx/libc/src/RuntimeSupport.cpp @@ -93,6 +93,10 @@ unsigned int APS5_VABI _Atomic_load_4_nid_postfix(volatile unsigned int* target, return GccAtomicLoad(target); } +std::intmax_t APS5_VABI strtoimax_nid_postfix(const char* str, char** endptr, int base) { + return std::strtoimax(str, endptr, base); +} + std::uintmax_t APS5_VABI strtoumax_nid_postfix(const char* str, char** endptr, int base) { return std::strtoumax(str, endptr, base); } diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index 263d2050a..7c9be2a2e 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -1,5 +1,5 @@ -#include "prx/libc/include/ApplicationHeap.hpp" #include +#include #include #include #include @@ -82,12 +82,12 @@ size_t APS5_VABI strlcpy_nid_postfix(char* dest, const char* src, size_t size) { return srcLen; } -long APS5_VABI strtol_nid_postfix(const char* str, char** endptr, int base) { - return std::strtol(str, endptr, base); +std::int64_t APS5_VABI strtol_nid_postfix(const char* str, char** endptr, int base) { + return std::strtoll(str, endptr, base); } -unsigned long APS5_VABI strtoul_nid_postfix(const char* str, char** endptr, int base) { - return std::strtoul(str, endptr, base); +std::uint64_t APS5_VABI strtoul_nid_postfix(const char* str, char** endptr, int base) { + return std::strtoull(str, endptr, base); } long long APS5_VABI strtoll_nid_postfix(const char* str, char** endptr, int base) { @@ -113,6 +113,10 @@ int APS5_VABI atoi_nid_postfix(const char* str) { return std::atoi(str); } +std::div_t APS5_VABI div_nid_postfix(int numerator, int denominator) { + return std::div(numerator, denominator); +} + const wchar_t* APS5_VABI wmemchr_nid_postfix(const wchar_t* s, wchar_t c, size_t n) { return std::wmemchr(s, c, n); } @@ -155,13 +159,6 @@ int APS5_VABI strncasecmp_nid_postfix(const char* s1, const char* s2, size_t n) return static_cast(*s1) - static_cast(*s2); } -char* APS5_VABI strdup_nid_postfix(const char* s) { - std::size_t len = std::strlen(s) + 1; - char* copy = static_cast(ApplicationHeapAllocate_nid_no_patch(len)); - std::memcpy(copy, s, len); - return copy; -} - int APS5_VABI bcmp_nid_postfix(const void* s1, const void* s2, size_t n) { return std::memcmp(s1, s2, n); } diff --git a/core/libs/prx/libc/src/exception/StandardExceptions.cpp b/core/libs/prx/libc/src/exception/StandardExceptions.cpp index 353538a5f..16da81ad5 100644 --- a/core/libs/prx/libc/src/exception/StandardExceptions.cpp +++ b/core/libs/prx/libc/src/exception/StandardExceptions.cpp @@ -267,6 +267,12 @@ void APS5_VABI _ZNSt11regex_errorC1ERKS__nid_postfix(LibcException::ExceptionObj void APS5_VABI _ZNSt11regex_errorC2ERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source) { LibcException::Copy(self, source, _ZTVSt11regex_error_nid_postfix); } LibcException::ExceptionObject* APS5_VABI _ZNSt11regex_erroraSERKS__nid_postfix(LibcException::ExceptionObject* self, const LibcException::ExceptionObject* source) { return LibcException::Assign(self, source); } +LibcException::TypeRecord _ZTISt11range_error_nid_postfix {LibcException::SingleTypeVtable + 2, "St11range_error", &_ZTISt13runtime_error_nid_postfix}; +LibcException::ExceptionVtable _ZTVSt11range_error_nid_postfix {0, &_ZTISt11range_error_nid_postfix, LibcException::DestroyMessage, LibcException::DeleteMessage, LibcException::MessageWhat}; +void APS5_VABI _ZNSt11range_errorD1Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } +void APS5_VABI _ZNSt11range_errorD2Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } +void APS5_VABI _ZNSt11range_errorD0Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DeleteMessage(self); } + LibcException::TypeRecord _ZTINSt8ios_base7failureE_nid_postfix {LibcException::SingleTypeVtable + 2, "NSt8ios_base7failureE", &_ZTISt12system_error_nid_postfix}; LibcException::ExceptionVtable _ZTVNSt8ios_base7failureE_nid_postfix {0, &_ZTINSt8ios_base7failureE_nid_postfix, LibcException::DestroyMessage, LibcException::DeleteMessage, LibcException::MessageWhat}; void APS5_VABI _ZNSt8ios_base7failureD1Ev_nid_postfix(LibcException::ExceptionObject* self) { LibcException::DestroyMessage(self); } @@ -281,6 +287,7 @@ const char* APS5_VABI _ZNKSt8ios_base7failure4whatEv_nid_postfix(const LibcExcep [[noreturn]] void APS5_VABI _ZSt14_Xout_of_rangePKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt12out_of_range_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt14_Xlength_errorPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt12length_error_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt18_Xinvalid_argumentPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt16invalid_argument_nid_postfix, message); } +[[noreturn]] void APS5_VABI _ZSt13_Xrange_errorPKc_nid_postfix(const char* message) { LibcException::ThrowMessage(_ZTVSt11range_error_nid_postfix, message); } [[noreturn]] void APS5_VABI _ZSt19_Xbad_function_callv_nid_postfix() { LibcException::ThrowPlain(_ZTVSt17bad_function_call_nid_postfix); } [[noreturn]] void APS5_VABI _ZSt13_Xregex_errorNSt15regex_constants10error_typeE_nid_postfix(std::regex_constants::error_type code) { struct RegexObject { LibcException::ExceptionObject base; std::regex_constants::error_type code; }; diff --git a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp index 2e06061f3..099d6392a 100644 --- a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp +++ b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp @@ -424,6 +424,15 @@ void ReportAllThreads() { if (skippedCount != 0) Report(" %llu further thread(s) left out\n", static_cast(skippedCount)); } +DWORD WINAPI HangWatchdog(LPVOID param) { + const auto seconds = static_cast(reinterpret_cast(param)); + Sleep(static_cast(seconds * 1000ull)); + Report("\nFATAL: timed dump after %llu s (APS5_HANG_DUMP_SECS); aborting\n", seconds); + std::fflush(stderr); + std::abort(); + return 0; +} + // abort() and the UCRT's invalid-parameter path end the process with a silent fast fail // (0xc0000409); both are reported with the calling thread's stack first. void ReportBacktrace(const char* what, bool reportThreads) { @@ -462,6 +471,15 @@ const bool g_crashReportInstalled = [] { std::signal(SIGABRT, AbortSignalHandler); _set_invalid_parameter_handler(InvalidParameterHandler); InstallWatch(); + if (const char* hang = std::getenv("APS5_HANG_DUMP_SECS")) { + unsigned long long seconds = 0; + if (hang[0] >= '0' && hang[0] <= '9') { + char* end = nullptr; + const auto value = std::strtoull(hang, &end, 10); + if (end && *end == 0) seconds = value; + } + if (seconds > 0) CreateThread(nullptr, 0, HangWatchdog, reinterpret_cast(static_cast(seconds)), 0, nullptr); + } return true; }(); diff --git a/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp b/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp index 89b53c3f2..aa36abe72 100644 --- a/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp +++ b/core/libs/prx/libkernel/AppMetadata/src/ParamJsonParser.cpp @@ -1,6 +1,7 @@ #include "prx/libkernel/AppMetadata/include/ParamJsonParser.hpp" #include +#include #include #include #include @@ -21,6 +22,7 @@ struct JsonValue { JsonType type = JsonType::Null; bool boolValue = false; double numberValue = 0.0; + std::string numberText; std::string stringValue; JsonArray arrayValue; JsonObject objectValue; @@ -193,7 +195,8 @@ JsonValue parseJsonNumber(const std::string& text, std::size_t& position) { } JsonValue value; value.type = JsonType::Number; - value.numberValue = std::stod(text.substr(start, position - start)); + value.numberText = text.substr(start, position - start); + value.numberValue = std::stod(value.numberText); return value; } @@ -261,7 +264,14 @@ ParsedParamJson parseParamJson(const std::filesystem::path& paramJsonPath) { result.titleId = titleId; if (const JsonValue* downloadData = findObjectMember(root, "downloadDataSize")) { if (downloadData->type != JsonType::Number || downloadData->numberValue < 0) throw std::runtime_error("param.json downloadDataSize is not a size"); - result.downloadDataSizeMiB = static_cast(downloadData->numberValue); + if (downloadData->numberText.find_first_of("-.eE") == std::string::npos) { + const auto& number = downloadData->numberText; + const auto parsed = std::from_chars(number.data(), number.data() + number.size(), result.downloadDataSizeMiB); + if (parsed.ec != std::errc{} || parsed.ptr != number.data() + number.size()) throw std::runtime_error("param.json downloadDataSize exceeds uint64 range"); + } else { + if (downloadData->numberValue >= 0x1p64) throw std::runtime_error("param.json downloadDataSize exceeds uint64 range"); + result.downloadDataSizeMiB = static_cast(downloadData->numberValue); + } } return result; } diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 81792a6ad..43949deb4 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -70,6 +70,15 @@ if(MINGW) endif() if(BUILD_TESTING) + add_test_executable(param_json_parser_tests ${LIBS_INCLUDE_DIR}/tests/ParamJsonParser.cpp) + target_include_directories(param_json_parser_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(param_json_parser_tests PRIVATE libkernel libc) + configure_windows_unwind(param_json_parser_tests) + add_test(NAME param_json_parser COMMAND param_json_parser_tests) + if(WIN32) + set_tests_properties(param_json_parser PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_posix_sem_tests ${LIBS_INCLUDE_DIR}/tests/GuestPosixSem.cpp) target_include_directories(guest_posix_sem_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_posix_sem_tests PRIVATE libkernel libc) @@ -176,6 +185,16 @@ if(BUILD_TESTING) set_tests_properties(guest_cpu_time_clocks PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_freebsd_clocks_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreebsdClocks.cpp) + target_include_directories(guest_freebsd_clocks_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freebsd_clocks_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_freebsd_clocks_tests) + add_test(NAME guest_freebsd_clocks COMMAND guest_freebsd_clocks_tests) + set_tests_properties(guest_freebsd_clocks PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_freebsd_clocks PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_nanosleep_tests ${LIBS_INCLUDE_DIR}/tests/GuestNanosleep.cpp) target_include_directories(guest_nanosleep_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_nanosleep_tests PRIVATE libkernel libc) @@ -216,6 +235,16 @@ if(BUILD_TESTING) set_tests_properties(guest_pthread_rwlock PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_pthread_key_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadKey.cpp) + target_include_directories(guest_pthread_key_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pthread_key_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_pthread_key_tests) + add_test(NAME guest_pthread_key COMMAND guest_pthread_key_tests) + set_tests_properties(guest_pthread_key PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_pthread_key PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_signal_tests ${LIBS_INCLUDE_DIR}/tests/GuestSignals.cpp) target_include_directories(guest_signal_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_signal_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index c98408281..ef114beba 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -226,7 +226,12 @@ public: #ifdef _WIN32 const auto size = static_cast(bytes); section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, static_cast(size >> 32), static_cast(size), nullptr); - if (!section) throw std::system_error(static_cast(GetLastError()), std::system_category(), "create direct memory backing"); + if (!section) { + const auto error = static_cast(GetLastError()); + char message[96]; + std::snprintf(message, sizeof(message), "create direct memory backing of 0x%llx bytes (%llu MiB)", static_cast(size), static_cast((size + 0xFFFFF) >> 20)); + throw std::system_error(error, std::system_category(), message); + } #else file = memfd_create("direct memory", MFD_CLOEXEC); if (file < 0) throw std::system_error(errno, std::generic_category(), "create direct memory backing"); @@ -619,6 +624,11 @@ int DoReserveVirtual(void** addr, size_t len, int flags, size_t alignment) { if (len == 0 || (len & (PS5_PAGE_SIZE - 1)) != 0) return SCE_KERNEL_ERROR_EINVAL; GuestAllocations::Mutation mutation; const bool fixed = *addr != nullptr && (flags & GuestMapFixedFlag) != 0; + if (fixed && mutation.Covers(*addr, len)) { + constexpr int GuestMapNoOverwrite = 0x80; + if ((flags & GuestMapNoOverwrite) == 0 && RemapFixedIntoRegistered(mutation, *addr, len, 0, GuestMapFixedFlag)) return 0; + mutation.RequireAvailable(*addr, len); + } if (fixed) mutation.RequireAvailable(*addr, len); constexpr int GuestMapNoCoalesce = 0x400000; void* mapped = MapAligned(fixed ? *addr : nullptr, len, PROT_NONE, fixed ? GuestMapFixedFlag | (flags & GuestMapNoCoalesce) : 0, alignment); diff --git a/core/libs/prx/libkernel/DirectMemory/Export.cpp b/core/libs/prx/libkernel/DirectMemory/Export.cpp index 5f26acc79..373de6c2a 100644 --- a/core/libs/prx/libkernel/DirectMemory/Export.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Export.cpp @@ -10,12 +10,17 @@ #define NOMINMAX #include #else +#include #include #include +#include +#include #endif #include +#include #include #include +#include #include #include #include @@ -405,13 +410,73 @@ int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry* entries, int num_entries, } +namespace { + +#ifdef _WIN32 +std::mutex g_workingSetLock; + +bool HostRangeAccessible(std::uintptr_t start, std::uintptr_t end) { + for (auto cursor = start; cursor < end;) { + MEMORY_BASIC_INFORMATION region{}; + if (VirtualQuery(reinterpret_cast(cursor), ®ion, sizeof(region)) != sizeof(region)) return false; + if (region.State != MEM_COMMIT || (region.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) return false; + cursor = reinterpret_cast(region.BaseAddress) + region.RegionSize; + } + return true; +} + +int LockHostPages(std::uintptr_t start, std::uintptr_t end) { + if (!HostRangeAccessible(start, end)) return SCE_KERNEL_ERROR_ENOMEM; + auto* const address = reinterpret_cast(start); + const SIZE_T bytes = end - start; + std::lock_guard lock(g_workingSetLock); + if (VirtualLock(address, bytes)) return 0; + if (GetLastError() != ERROR_WORKING_SET_QUOTA) throw std::system_error(static_cast(GetLastError()), std::system_category(), "VirtualLock failed"); + SIZE_T minimum = 0; + SIZE_T maximum = 0; + DWORD flags = 0; + if (!GetProcessWorkingSetSizeEx(GetCurrentProcess(), &minimum, &maximum, &flags)) throw std::system_error(static_cast(GetLastError()), std::system_category(), "GetProcessWorkingSetSizeEx failed"); + if (bytes > std::numeric_limits::max() - maximum) return SCE_KERNEL_ERROR_EAGAIN; + if (!SetProcessWorkingSetSizeEx(GetCurrentProcess(), minimum + bytes, maximum + bytes, flags)) return SCE_KERNEL_ERROR_EAGAIN; + if (VirtualLock(address, bytes)) return 0; + if (GetLastError() != ERROR_WORKING_SET_QUOTA) throw std::system_error(static_cast(GetLastError()), std::system_category(), "VirtualLock failed"); + return SCE_KERNEL_ERROR_EAGAIN; +} +#else +int LockHostPages(std::uintptr_t start, std::uintptr_t end) { + auto* const address = reinterpret_cast(start); + const std::size_t bytes = end - start; + if (::mlock(address, bytes) == 0) return 0; + int error = errno; + rlimit limit{}; + if ((error == ENOMEM || error == EPERM || error == EAGAIN) && getrlimit(RLIMIT_MEMLOCK, &limit) == 0 && limit.rlim_cur < limit.rlim_max) { + limit.rlim_cur = limit.rlim_max; + if (setrlimit(RLIMIT_MEMLOCK, &limit) == 0) { + if (::mlock(address, bytes) == 0) return 0; + error = errno; + } + } + switch (error) { + case ENOMEM: return SCE_KERNEL_ERROR_ENOMEM; + case EPERM: return SCE_KERNEL_ERROR_EPERM; + case EAGAIN: return SCE_KERNEL_ERROR_EAGAIN; + default: throw std::system_error(error, std::generic_category(), "mlock failed"); + } +} +#endif + +} + extern "C" { int APS5_VABI sceKernelMlock_nid_postfix(void* address, std::uint64_t length) { - (void)address; - (void)length; - NotImplemented_nid_no_patch(__func__); - return 0; + constexpr std::uintptr_t pageMask = PS5_PAGE_SIZE - 1; + const auto first = reinterpret_cast(address); + if (length > UINTPTR_MAX - first || first + length > UINTPTR_MAX - pageMask) return SCE_KERNEL_ERROR_EINVAL; + const auto start = first & ~pageMask; + const auto end = (first + length + pageMask) & ~pageMask; + if (start == end) return 0; + return LockHostPages(start, end); } } diff --git a/core/libs/prx/libkernel/Pthread/src/Tsd.cpp b/core/libs/prx/libkernel/Pthread/src/Tsd.cpp index 8f6442f8a..b737b2f16 100644 --- a/core/libs/prx/libkernel/Pthread/src/Tsd.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Tsd.cpp @@ -1,6 +1,8 @@ #include "../include/Pthread.hpp" #include "prx/libc/include/General.hpp" #include +#include +#include #include #include @@ -9,34 +11,41 @@ static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016; static constexpr int SCE_KERNEL_ERROR_EAGAIN = 0x80020023; static constexpr int MAX_KEYS = 512; static constexpr int DESTRUCTOR_ITERATIONS = 4; +static constexpr std::uint64_t FREE_KEY = 0; using GuestKeyDestructor = void (APS5_VABI*)(void*); struct KeySlot { - bool used = false; + std::atomic sequence{FREE_KEY}; GuestKeyDestructor destructor = nullptr; }; +struct ThreadValue { + void* value = nullptr; + std::uint64_t sequence = FREE_KEY; +}; + static std::mutex g_keyLock; static std::array g_keys; +static std::uint64_t g_lastSequence = FREE_KEY; struct ThreadValues { - std::array values{}; + std::array values{}; ~ThreadValues() { for (int round = 0; round < DESTRUCTOR_ITERATIONS; ++round) { bool called = false; for (int key = 0; key < MAX_KEYS; ++key) { - void* value = values[key]; - if (!value) continue; + const ThreadValue entry = values[key]; + if (!entry.value) continue; GuestKeyDestructor destructor; { std::lock_guard lock(g_keyLock); - destructor = g_keys[key].used ? g_keys[key].destructor : nullptr; + destructor = g_keys[key].sequence.load(std::memory_order_relaxed) == entry.sequence ? g_keys[key].destructor : nullptr; } - values[key] = nullptr; + values[key] = {}; if (destructor) { - destructor(value); + destructor(entry.value); called = true; } } @@ -47,20 +56,15 @@ struct ThreadValues { static thread_local ThreadValues g_values; -static bool IsValidKey(PthreadKey key) { - if (key < 0 || key >= MAX_KEYS) return false; - std::lock_guard lock(g_keyLock); - return g_keys[key].used; -} - extern "C" { int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor) { if (!key) throw std::runtime_error("scePthreadKeyCreate: null key"); std::lock_guard lock(g_keyLock); for (int index = 0; index < MAX_KEYS; ++index) { - if (!g_keys[index].used) { - g_keys[index] = {true, reinterpret_cast(destructor)}; + if (g_keys[index].sequence.load(std::memory_order_relaxed) == FREE_KEY) { + g_keys[index].destructor = reinterpret_cast(destructor); + g_keys[index].sequence.store(++g_lastSequence, std::memory_order_release); *key = index; return SCE_OK; } @@ -71,19 +75,23 @@ int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t int APS5_VABI scePthreadKeyDelete(PthreadKey key) { if (key < 0 || key >= MAX_KEYS) return SCE_KERNEL_ERROR_EINVAL; std::lock_guard lock(g_keyLock); - if (!g_keys[key].used) return SCE_KERNEL_ERROR_EINVAL; - g_keys[key] = {}; + if (g_keys[key].sequence.load(std::memory_order_relaxed) == FREE_KEY) return SCE_KERNEL_ERROR_EINVAL; + g_keys[key].sequence.store(FREE_KEY, std::memory_order_release); + g_keys[key].destructor = nullptr; return SCE_OK; } void* APS5_VABI scePthreadGetspecific(PthreadKey key) { if (key < 0 || key >= MAX_KEYS) return nullptr; - return g_values.values[key]; + const ThreadValue& entry = g_values.values[key]; + return entry.sequence == g_keys[key].sequence.load(std::memory_order_acquire) ? entry.value : nullptr; } int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value) { - if (!IsValidKey(key)) return SCE_KERNEL_ERROR_EINVAL; - g_values.values[key] = value; + if (key < 0 || key >= MAX_KEYS) return SCE_KERNEL_ERROR_EINVAL; + const std::uint64_t sequence = g_keys[key].sequence.load(std::memory_order_acquire); + if (sequence == FREE_KEY) return SCE_KERNEL_ERROR_EINVAL; + g_values.values[key] = {value, sequence}; return SCE_OK; } diff --git a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp index 87944ae04..733ae7c41 100644 --- a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp +++ b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp @@ -25,23 +25,16 @@ struct AddressWaiter { std::mutex g_waitersLock; std::unordered_map> g_waiters; -bool IsWordAddress(std::uintptr_t address) { - return address != 0 && address % alignof(std::uint32_t) == 0; +template +bool IsAlignedAddress(std::uintptr_t address) { + return address != 0 && address % alignof(TValue) == 0; } -} // namespace - -extern "C" { - -int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { - (void)name; +template +int WaitOnAddress(TValue* address, TValue expected, const KernelUseconds* timeout, const void* caller) { const auto key = reinterpret_cast(address); - if (!IsWordAddress(key)) { - APS5_INVALID_ARG_EX; - } - std::unique_lock lock(g_waitersLock); - if (std::atomic_ref(*address).load() != expected) { + if (std::atomic_ref(*address).load() != expected) { return SYNC_ON_ADDRESS_OK; } @@ -65,13 +58,41 @@ int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t e } } lock.unlock(); - KernelTraceWait_nid_postfix("addr", __builtin_return_address(0), static_cast(waited.count()), !woken); + KernelTraceWait_nid_postfix("addr", caller, static_cast(waited.count()), !woken); return woken ? SYNC_ON_ADDRESS_OK : SCE_KERNEL_ERROR_ETIMEDOUT; } +} // namespace + +extern "C" { + +int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + +int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + +int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, const KernelUseconds* timeout, const char* name) { + (void)name; + if (!IsAlignedAddress(reinterpret_cast(address))) { + APS5_INVALID_ARG_EX; + } + return WaitOnAddress(address, expected, timeout, __builtin_return_address(0)); +} + int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count) { const auto key = reinterpret_cast(address); - if (!IsWordAddress(key) || count < 0) { + if (!IsAlignedAddress(key) || count < 0) { APS5_INVALID_ARG_EX; } diff --git a/core/libs/prx/libkernel/System/src/Coredump.cpp b/core/libs/prx/libkernel/System/src/Coredump.cpp index 710d956cd..27efa3e65 100644 --- a/core/libs/prx/libkernel/System/src/Coredump.cpp +++ b/core/libs/prx/libkernel/System/src/Coredump.cpp @@ -31,8 +31,7 @@ int APS5_VABI sceKernelDebugWriteCppExceptionInfo(const void* exception, uint64_ } -APS5_EXPORT("5nc2gdLNsok", sceCoredumpUnknown00); -int APS5_VABI sceCoredumpUnknown00(void) { +int APS5_VABI sceCoredumpAttachUserFile(void) { NotImplemented_nid_no_patch("5nc2gdLNsok"); return 0; } @@ -43,26 +42,22 @@ int APS5_VABI sceCoredumpUnknown01(void) { return 0; } -APS5_EXPORT("MEJ7tc7ThwM", sceCoredumpUnknown02); -int APS5_VABI sceCoredumpUnknown02(void) { +int APS5_VABI sceCoredumpAttachMemoryRegionAsUserFile(void) { NotImplemented_nid_no_patch("MEJ7tc7ThwM"); return 0; } -APS5_EXPORT("Uxqkdta7wEg", sceCoredumpUnknown03); -int APS5_VABI sceCoredumpUnknown03(void) { +int APS5_VABI sceCoredumpSetUserDataType(void) { NotImplemented_nid_no_patch("Uxqkdta7wEg"); return 0; } -APS5_EXPORT("dei8oUx6DbU", sceCoredumpUnknown04); -int APS5_VABI sceCoredumpUnknown04(void) { +int APS5_VABI sceCoredumpDebugTextOut(void) { NotImplemented_nid_no_patch("dei8oUx6DbU"); return 0; } -APS5_EXPORT("kK0DUW1Ukgc", sceCoredumpUnknown05); -int APS5_VABI sceCoredumpUnknown05(void) { +int APS5_VABI sceCoredumpGetStopInfoCpu(void) { NotImplemented_nid_no_patch("kK0DUW1Ukgc"); return 0; } diff --git a/core/libs/prx/libkernel/Time/Time.cpp b/core/libs/prx/libkernel/Time/Time.cpp index 8cde1fc97..395f85829 100644 --- a/core/libs/prx/libkernel/Time/Time.cpp +++ b/core/libs/prx/libkernel/Time/Time.cpp @@ -23,6 +23,8 @@ #else #include #include +#include +#include #endif extern "C" int* APS5_VABI __error_nid_postfix(); @@ -263,7 +265,7 @@ int APS5_VABI usleep_nid_postfix(KernelUseconds microseconds) { } #ifdef _WIN32 -static std::uint64_t CpuTimeNanos(bool callingThread) { +static std::uint64_t CpuTimeNanos(bool callingThread, bool countKernel) { FILETIME creation{}; FILETIME exitTime{}; FILETIME kernel{}; @@ -278,7 +280,7 @@ static std::uint64_t CpuTimeNanos(bool callingThread) { const auto toTicks = [](const FILETIME& time) { return (static_cast(time.dwHighDateTime) << 32) | time.dwLowDateTime; }; - return (toTicks(kernel) + toTicks(user)) * 100ULL; + return (toTicks(user) + (countKernel ? toTicks(kernel) : 0ULL)) * 100ULL; } static std::uint64_t CpuTimeResolutionNanos() { @@ -291,12 +293,48 @@ static std::uint64_t CpuTimeResolutionNanos() { throw std::runtime_error("clock_getres: GetSystemTimeAdjustment reported no clock interrupt period"); return static_cast(increment) * 100ULL; } + +static std::uint64_t ProcessCpuNanos(bool countSystem) { + return CpuTimeNanos(false, countSystem); +} + +static std::uint64_t ProcessCpuResolutionNanos() { + return CpuTimeResolutionNanos(); +} +#else +static std::uint64_t ClockTicksPerSecond() { + const long rate = sysconf(_SC_CLK_TCK); + if (rate <= 0) + throw std::runtime_error("clock_gettime: sysconf(_SC_CLK_TCK) reported no clock tick rate"); + return static_cast(rate); +} + +static std::uint64_t ProcessCpuNanos(bool countSystem) { + struct tms usage{}; + errno = 0; + if (times(&usage) == static_cast(-1) && errno != 0) + throw std::system_error(errno, std::generic_category(), "clock_gettime: times failed"); + const std::uint64_t rate = ClockTicksPerSecond(); + const std::uint64_t ticks = static_cast(usage.tms_utime) + (countSystem ? static_cast(usage.tms_stime) : 0ULL); + return ticks / rate * 1000000000ULL + ticks % rate * 1000000000ULL / rate; +} + +static std::uint64_t ProcessCpuResolutionNanos() { + const std::uint64_t rate = ClockTicksPerSecond(); + return (1000000000ULL + rate - 1ULL) / rate; +} #endif int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { if (tp == nullptr) { APS5_INVALID_ARG_EX; } + if (clockId == 1 || clockId == 2) { + const std::uint64_t nanos = ProcessCpuNanos(clockId == 2); + tp->tv_sec = static_cast(nanos / 1000000000ULL); + tp->tv_nsec = static_cast(nanos % 1000000000ULL); + return 0; + } #ifdef _WIN32 if (clockId == 0 || clockId == 9) { FILETIME ft{}; @@ -315,17 +353,17 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { t -= 116444736000000000ULL; t *= 100ULL; tp->tv_sec = static_cast(t / 1000000000ULL); - tp->tv_nsec = static_cast(t % 1000000000ULL); + tp->tv_nsec = clockId == 13 ? 0 : static_cast(t % 1000000000ULL); return 0; } - if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 11 || clockId == 12) { + if (clockId == 4 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 11 || clockId == 12) { std::uint64_t nanos = GetMonotonicNanos(); tp->tv_sec = static_cast(nanos / 1000000000ULL); tp->tv_nsec = static_cast(nanos % 1000000000ULL); return 0; } if (clockId == 14 || clockId == 15) { - const std::uint64_t nanos = CpuTimeNanos(clockId == 14); + const std::uint64_t nanos = CpuTimeNanos(clockId == 14, true); tp->tv_sec = static_cast(nanos / 1000000000ULL); tp->tv_nsec = static_cast(nanos % 1000000000ULL); return 0; @@ -370,7 +408,7 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { return -1; } tp->tv_sec = static_cast(ts.tv_sec); - tp->tv_nsec = static_cast(ts.tv_nsec); + tp->tv_nsec = clockId == 13 ? 0 : static_cast(ts.tv_nsec); return 0; #endif } @@ -405,13 +443,24 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) { if (res == nullptr) { APS5_INVALID_ARG_EX; } + if (clockId == 1 || clockId == 2) { + const std::uint64_t nanos = ProcessCpuResolutionNanos(); + res->tv_sec = static_cast(nanos / 1000000000ULL); + res->tv_nsec = static_cast(nanos % 1000000000ULL); + return 0; + } + if (clockId == 13) { + res->tv_sec = 1; + res->tv_nsec = 0; + return 0; + } #ifdef _WIN32 if (clockId == 0 || clockId == 9) { res->tv_sec = 0; res->tv_nsec = 100LL; return 0; } - if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12 || clockId == 13) { + if (clockId == 4 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12) { static const std::uint64_t freq = [] { LARGE_INTEGER f{}; QueryPerformanceFrequency(&f); @@ -437,7 +486,6 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) { nativeId = CLOCK_REALTIME; break; case 10: - case 13: #ifdef CLOCK_REALTIME_COARSE nativeId = CLOCK_REALTIME_COARSE; #else diff --git a/core/libs/prx/libkernel/Unimplemented.cpp b/core/libs/prx/libkernel/Unimplemented.cpp index c969fc55e..96466573a 100644 --- a/core/libs/prx/libkernel/Unimplemented.cpp +++ b/core/libs/prx/libkernel/Unimplemented.cpp @@ -10,14 +10,12 @@ int APS5_VABI sceCoredumpWriteUserData() { return 0; } -APS5_EXPORT("vNe1w4diLCs", sceKernelUnknown00); -int APS5_VABI sceKernelUnknown00(void) { +int APS5_VABI __tls_get_addr_nid_postfix(void) { NotImplemented_nid_no_patch("vNe1w4diLCs"); return 0; } -APS5_EXPORT("teiItL2boFw", sceKernelUnknown01); -int APS5_VABI sceKernelUnknown01(void) { +int APS5_VABI sceKernelReleaseFlexibleMemory(void) { NotImplemented_nid_no_patch("teiItL2boFw"); return 0; } @@ -25,8 +23,7 @@ int APS5_VABI sceKernelUnknown01(void) { // Canonical lib is libScePosix (dead import of Cyberpunk 2077): 35 of its // 36 sibling imports resolve to libkernel, and libScePosix is not in the // game's NEEDED list so only a NEEDED module can satisfy the loader here. -APS5_EXPORT("0D4-FVvEikw", sceKernelUnknown02); -int APS5_VABI sceKernelUnknown02(void) { +int APS5_VABI pthread_cancel_nid_postfix(void) { NotImplemented_nid_no_patch("0D4-FVvEikw"); return 0; } diff --git a/core/libs/prx/ulobjmgr/CMakeLists.txt b/core/libs/prx/ulobjmgr/CMakeLists.txt new file mode 100644 index 000000000..84d347d1a --- /dev/null +++ b/core/libs/prx/ulobjmgr/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(ulobjmgr SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(ulobjmgr PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(ulobjmgr) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_ulobjmgr_tests ${LIBS_INCLUDE_DIR}/tests/GuestUlobjmgr.cpp) + target_include_directories(guest_ulobjmgr_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ulobjmgr_tests PRIVATE ulobjmgr) + add_test(NAME guest_ulobjmgr COMMAND guest_ulobjmgr_tests) + if(WIN32) + set_tests_properties(guest_ulobjmgr PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/ulobjmgr/Export.cpp b/core/libs/prx/ulobjmgr/Export.cpp index 528a0dfc2..25b7f547b 100644 --- a/core/libs/prx/ulobjmgr/Export.cpp +++ b/core/libs/prx/ulobjmgr/Export.cpp @@ -3,17 +3,23 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int ULOBJMGR_EINVAL = 22; +constexpr std::uint32_t ULOBJMGR_ID_LIMIT = 0x4000; + +} + extern "C" { -int APS5_VABI _sceUlobjmgrRegisterObject(void* object) { - (void)object; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _sceUlobjmgrRegisterObject(std::uint64_t object, std::int32_t kind, std::uint32_t* id) { + if (object == 0 || kind == 0 || id == nullptr) return ULOBJMGR_EINVAL; + *id = 0; return 0; } -int APS5_VABI _sceUlobjmgrUnregisterObject(void* object) { - (void)object; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI _sceUlobjmgrUnregisterObject(std::uint32_t id) { + if (id >= ULOBJMGR_ID_LIMIT) return ULOBJMGR_EINVAL; return 0; } diff --git a/core/libs/tests/ApplicationHeap.cpp b/core/libs/tests/ApplicationHeap.cpp index 3fb557ed1..0f08924bb 100644 --- a/core/libs/tests/ApplicationHeap.cpp +++ b/core/libs/tests/ApplicationHeap.cpp @@ -14,6 +14,9 @@ void APS5_VABI _ZdlPv_nid_postfix(void*); void APS5_VABI _ZdaPv_nid_postfix(void*); void APS5_VABI _ZdlPvSt11align_val_t_nid_postfix(void*, std::size_t); void* ApplicationHeapRealign_nid_no_patch(void*, std::size_t, std::size_t); +char* APS5_VABI strdup_nid_postfix(const char*); +char* APS5_VABI strndup_nid_postfix(const char*, std::size_t); +int* APS5_VABI __error_nid_postfix(); } namespace { @@ -192,4 +195,23 @@ int main(int argc, char** argv) { reject([] { ApplicationHeapAllocate_nid_no_patch(64); }); recurse = false; require(ApplicationHeapAllocate_nid_no_patch(64) == storage.data()); + const char text[] = "guest string"; + char* copy = strdup_nid_postfix(text); + require(copy == reinterpret_cast(storage.data()) && lastSize == sizeof(text)); + require(std::strcmp(copy, text) == 0 && copy != text); + ApplicationHeapFree_nid_no_patch(copy); + copy = strdup_nid_postfix(""); + require(lastSize == 1 && copy[0] == '\0'); + ApplicationHeapFree_nid_no_patch(copy); + copy = strndup_nid_postfix(text, 5); + require(lastSize == 6 && std::strcmp(copy, "guest") == 0); + ApplicationHeapFree_nid_no_patch(copy); + fail = true; + *__error_nid_postfix() = 0; + require(strdup_nid_postfix(text) == nullptr && *__error_nid_postfix() == 12); + *__error_nid_postfix() = 0; + require(strdup_nid_postfix("") == nullptr && *__error_nid_postfix() == 12); + *__error_nid_postfix() = 0; + require(strndup_nid_postfix(text, 5) == nullptr && *__error_nid_postfix() == 12); + fail = false; } diff --git a/core/libs/tests/ExceptionRuntime.cpp b/core/libs/tests/ExceptionRuntime.cpp index 2e50cd0ba..bb8df2c16 100644 --- a/core/libs/tests/ExceptionRuntime.cpp +++ b/core/libs/tests/ExceptionRuntime.cpp @@ -17,6 +17,7 @@ extern "C" void __cxa_decrement_exception_refcount_nid_postfix(void*); extern "C" void __cxa_rethrow_primary_exception_nid_postfix(void*); extern "C" [[noreturn]] void _ZSt14_Xout_of_rangePKc_nid_postfix(const char*); +extern "C" [[noreturn]] void _ZSt13_Xrange_errorPKc_nid_postfix(const char*); extern "C" [[noreturn]] void __cxa_bad_cast_nid_postfix(); extern "C" [[noreturn]] void _ZNKSt9exception6_RaiseEv_nid_postfix(const void*); extern "C" void* __cxa_vec_new3_nid_postfix(std::size_t, std::size_t, std::size_t, void(*)(void*), void(*)(void*), void*(*)(std::size_t), void(*)(void*, std::size_t)); @@ -190,6 +191,13 @@ int main() { try { throw Virtual(); } catch (Base& value) { assert(value.value == 7); } try { _ZSt14_Xout_of_rangePKc_nid_postfix("test message"); } catch (const std::logic_error& value) { assert(std::strcmp(value.what(), "test message") == 0); } + try { _ZSt13_Xrange_errorPKc_nid_postfix("range message"); } + catch (const std::range_error& value) { if (std::strcmp(value.what(), "range message") != 0) std::abort(); } + try { _ZSt13_Xrange_errorPKc_nid_postfix(nullptr); } + catch (const std::runtime_error& value) { if (std::strcmp(value.what(), "") != 0) std::abort(); } + try { _ZSt13_Xrange_errorPKc_nid_postfix("not a logic error"); } + catch (const std::logic_error&) { std::abort(); } + catch (const std::range_error&) {} try { __cxa_bad_cast_nid_postfix(); } catch (const std::exception& value) { assert(value.what() != nullptr); } try { _ZNKSt9exception6_RaiseEv_nid_postfix(nullptr); assert(false); } diff --git a/core/libs/tests/GuestAjm.cpp b/core/libs/tests/GuestAjm.cpp index f262079b3..71524e885 100644 --- a/core/libs/tests/GuestAjm.cpp +++ b/core/libs/tests/GuestAjm.cpp @@ -5,17 +5,24 @@ #include #include #include +#include #include extern "C" { int APS5_VABI sceAjmInitialize(std::int64_t, std::uint32_t*); int APS5_VABI sceAjmFinalize(std::uint32_t); int APS5_VABI sceAjmDecAt9ParseConfigData(const void*, AjmDecAt9ConfigDataInfo*); +int APS5_VABI sceAjmDecMp3ParseFrame(const std::uint8_t*, std::uint32_t, int, AjmDecMp3ParseFrame*); int APS5_VABI sceAjmInstanceCreate(std::uint32_t, std::uint32_t, std::uint64_t, std::uint32_t*); int APS5_VABI sceAjmInstanceDestroy(std::uint32_t, std::uint32_t); int APS5_VABI sceAjmBatchInitialize(void*, std::size_t, AjmBatchInfo*); int APS5_VABI sceAjmBatchJobInitialize(AjmBatchInfo*, std::uint32_t, const void*, std::size_t, void*); int APS5_VABI sceAjmBatchJobDecode(AjmBatchInfo*, std::uint32_t, const void*, std::size_t, void*, std::size_t, void*); +int APS5_VABI sceAjmBatchJobDecodeSingle(AjmBatchInfo*, std::uint32_t, const void*, std::size_t, void*, std::size_t, void*); +int APS5_VABI sceAjmBatchJobRun(AjmBatchInfo*, std::uint32_t, std::uint64_t, const void*, std::size_t, void*, std::size_t, void*, std::size_t); +int APS5_VABI sceAjmBatchJobSetGaplessDecode(AjmBatchInfo*, std::uint32_t, const void*, int, void*); +int APS5_VABI sceAjmBatchJobGetGaplessDecode(AjmBatchInfo*, std::uint32_t, void*); +int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo*, std::uint32_t, void*, std::size_t); int APS5_VABI sceAjmBatchStart(std::uint32_t, const AjmBatchInfo*, int, AjmBatchError*, std::uint32_t*); int APS5_VABI sceAjmBatchWait(std::uint32_t, std::uint32_t, std::uint32_t, AjmBatchError*); } @@ -148,6 +155,190 @@ void TestOpus(std::uint32_t context) { Require(sceAjmInstanceDestroy(context, instance) == 0); } +bool ParsesTo(const std::uint8_t* stream, std::uint32_t streamSize, std::uint64_t frameSize, std::uint32_t channels, std::uint32_t samples, std::uint32_t bitrate, std::uint32_t sampleRate) { + AjmDecMp3ParseFrame frame; + std::memset(&frame, 0xFF, sizeof(frame)); + if (sceAjmDecMp3ParseFrame(stream, streamSize, 0, &frame) != 0) return false; + return frame.frame_size == frameSize && frame.num_channels == channels && frame.samples_per_channel == samples && frame.bitrate == bitrate && frame.sample_rate == sampleRate && + frame.encoder_delay == 0 && frame.num_frames == 0 && frame.total_samples == 0 && frame.ofl_type == 0; +} + +void TestMp3ParseFrame() { + constexpr int invalidParameter = static_cast(0x80930005); + static_assert(sizeof(AjmDecMp3ParseFrame) == 40); + for (std::size_t offset = 0; offset < sizeof(MP3_MONO); offset += 96) { + Require(ParsesTo(MP3_MONO + offset, static_cast(sizeof(MP3_MONO) - offset), 96, 1, 1152, 32000, 48000)); + } + + const std::uint8_t mpeg1StereoPadded[4] = {0xFF, 0xFB, 0x92, 0x00}; + Require(ParsesTo(mpeg1StereoPadded, 4, 418, 2, 1152, 128000, 44100)); + const std::uint8_t mpeg1Protected[4] = {0xFF, 0xFA, 0xE8, 0x40}; + Require(ParsesTo(mpeg1Protected, 4, 1440, 2, 1152, 320000, 32000)); + const std::uint8_t mpeg2Mono[4] = {0xFF, 0xF3, 0x80, 0xC0}; + Require(ParsesTo(mpeg2Mono, 4, 208, 1, 576, 64000, 22050)); + const std::uint8_t mpeg2DualChannel[4] = {0xFF, 0xF3, 0xE8, 0x80}; + Require(ParsesTo(mpeg2DualChannel, 4, 720, 2, 576, 160000, 16000)); + const std::uint8_t mpeg25Mono[4] = {0xFF, 0xE3, 0x88, 0xC0}; + Require(ParsesTo(mpeg25Mono, 4, 576, 1, 576, 64000, 8000)); + const std::uint8_t mpeg25StereoPadded[4] = {0xFF, 0xE3, 0x12, 0x00}; + Require(ParsesTo(mpeg25StereoPadded, 4, 53, 2, 576, 8000, 11025)); + + AjmDecMp3ParseFrame frame{}; + Require(sceAjmDecMp3ParseFrame(nullptr, 4, 0, &frame) == invalidParameter); + Require(sceAjmDecMp3ParseFrame(MP3_MONO, 4, 0, nullptr) == invalidParameter); + Require(sceAjmDecMp3ParseFrame(MP3_MONO, 3, 0, &frame) == invalidParameter); + const std::uint8_t brokenSync[4] = {0xFF, 0xDB, 0x14, 0xC4}; + Require(sceAjmDecMp3ParseFrame(brokenSync, 4, 0, &frame) == invalidParameter); + const std::uint8_t reservedVersion[4] = {0xFF, 0xEB, 0x14, 0xC4}; + Require(sceAjmDecMp3ParseFrame(reservedVersion, 4, 0, &frame) == invalidParameter); + const std::uint8_t freeBitrate[4] = {0xFF, 0xFB, 0x04, 0xC4}; + Require(sceAjmDecMp3ParseFrame(freeBitrate, 4, 0, &frame) == invalidParameter); + const std::uint8_t forbiddenBitrate[4] = {0xFF, 0xFB, 0xF4, 0xC4}; + Require(sceAjmDecMp3ParseFrame(forbiddenBitrate, 4, 0, &frame) == invalidParameter); + const std::uint8_t reservedSampleRate[4] = {0xFF, 0xFB, 0x1C, 0xC4}; + Require(sceAjmDecMp3ParseFrame(reservedSampleRate, 4, 0, &frame) == invalidParameter); + const std::uint8_t mpeg25Above64Kbps[4] = {0xFF, 0xE3, 0x98, 0xC0}; + Require(sceAjmDecMp3ParseFrame(mpeg25Above64Kbps, 4, 0, &frame) == invalidParameter); +} + +struct GaplessDecode { + std::uint32_t totalSamples; + std::uint16_t skipSamples; + std::uint16_t skippedSamples; +}; + +struct GaplessSideband { + std::int32_t result; + std::int32_t internalResult; + std::uint32_t totalSamples; + std::uint16_t skipSamples; + std::uint16_t skippedSamples; +}; + +struct At9CodecInfoSideband { + std::int32_t result; + std::int32_t internalResult; + std::uint32_t superframeSize; + std::uint32_t framesInSuperframe; + std::uint32_t nextFrameSize; + std::uint32_t frameSamples; +}; + +void Submit(std::uint32_t context, const AjmBatchInfo& info) { + std::uint32_t id = 0; + AjmBatchError error{}; + Require(sceAjmBatchStart(context, &info, 0, &error, &id) == 0 && sceAjmBatchWait(context, id, 0, &error) == 0); +} + +bool Refused(std::uint32_t context, const AjmBatchInfo& info) { + std::uint32_t id = 0; + AjmBatchError error{}; + try { + static_cast(sceAjmBatchStart(context, &info, 0, &error, &id)); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +void TestDecodeSingle(std::uint32_t context) { + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 0, 0, &instance) == 0); + std::vector batch(0x40); + std::vector pcm(1152 * 6); + AjmBatchInfo info{}; + DecodeSideband sideband{}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobDecodeSingle(&info, instance, MP3_MONO, sizeof(MP3_MONO), pcm.data(), pcm.size() * sizeof(std::int16_t), &sideband) == 0); + Submit(context, info); + Require(sideband.result == 0 && sideband.inputConsumed == 96 && sideband.outputWritten == 1152 * 2 && sideband.totalDecodedSamples == 1152); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + +void TestGaplessDecode(std::uint32_t context) { + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 0, 0, &instance) == 0); + std::vector batch(4096); + const GaplessDecode gapless{2000, 100, 0}; + AjmBatchInfo info{}; + std::int32_t setResult[2] = {-1, -1}; + GaplessSideband before{-1, -1, 0, 0, 0xffff}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobSetGaplessDecode(&info, instance, &gapless, 1, setResult) == 0); + Require(sceAjmBatchJobGetGaplessDecode(&info, instance, &before) == 0); + Submit(context, info); + Require(setResult[0] == 0); + Require(before.result == 0 && before.internalResult == 0 && before.totalSamples == 2000 && before.skipSamples == 100 && before.skippedSamples == 0); + + std::vector pcm(1152); + DecodeSideband decoded{}; + GaplessSideband after{-1, -1, 0, 0, 0}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobDecodeSingle(&info, instance, MP3_MONO, sizeof(MP3_MONO), pcm.data(), pcm.size() * sizeof(std::int16_t), &decoded) == 0); + Require(sceAjmBatchJobGetGaplessDecode(&info, instance, &after) == 0); + Submit(context, info); + Require(decoded.result == 0 && decoded.inputConsumed == 96 && decoded.outputWritten == (1152 - 100) * 2); + Require(after.result == 0 && after.skippedSamples == 100); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + +void TestCodecInfo(std::uint32_t context) { + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 1, 0, &instance) == 0); + std::vector batch(4096); + AjmBatchInfo info{}; + At9CodecInfoSideband early{-1, -1, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobGetCodecInfo(&info, instance, &early, sizeof(early)) == 0); + Submit(context, info); + Require(early.result == 1 && early.superframeSize == 0xaaaaaaaau); + + const std::uint8_t config[8] = {0xFE, 0x72, 0x1F, 0xF0}; + std::int32_t initResult[2] = {-1, -1}; + At9CodecInfoSideband codec{-1, -1, 0, 0, 0, 0}; + At9CodecInfoSideband bounded{-1, -1, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobInitialize(&info, instance, config, sizeof(config), initResult) == 0); + Require(sceAjmBatchJobGetCodecInfo(&info, instance, &codec, sizeof(codec)) == 0); + Require(sceAjmBatchJobGetCodecInfo(&info, instance, &bounded, 2 * sizeof(std::int32_t)) == 0); + Submit(context, info); + Require(initResult[0] == 0); + Require(codec.result == 0 && codec.internalResult == 0 && codec.superframeSize == 1024 && codec.framesInSuperframe == 4 && codec.nextFrameSize == 1024 && codec.frameSamples == 256); + Require(bounded.result == 0 && bounded.superframeSize == 0xaaaaaaaau && bounded.frameSamples == 0xaaaaaaaau); + + constexpr std::uint64_t runGetCodecInfo = 1ull << 11; + constexpr std::uint64_t runMultipleFrames = 1ull << 12; + std::uint8_t sideband[64] = {}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobRun(&info, instance, runGetCodecInfo | runMultipleFrames, nullptr, 0, nullptr, 0, sideband, sizeof(sideband)) == 0); + Require(Refused(context, info)); + Require(sceAjmInstanceDestroy(context, instance) == 0); + + Require(sceAjmInstanceCreate(context, 0, 0, &instance) == 0); + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobGetCodecInfo(&info, instance, sideband, sizeof(sideband)) == 0); + Require(Refused(context, info)); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + +void TestBatchWaitRelease(std::uint32_t context) { + constexpr int invalidBatch = static_cast(0x80930004); + std::vector batch(64); + AjmBatchInfo info{}; + AjmBatchError error{}; + std::uint32_t first = 0; + std::uint32_t second = 0; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchStart(context, &info, 0, &error, &first) == 0); + Require(sceAjmBatchStart(context, &info, 0, &error, &second) == 0 && second != first); + Require(sceAjmBatchWait(context, second, 0, &error) == 0); + Require(sceAjmBatchWait(context, first, 0, &error) == 0); + Require(sceAjmBatchWait(context, first, 0, &error) == invalidBatch); + Require(sceAjmBatchWait(context, second, 0, &error) == invalidBatch); + Require(sceAjmBatchWait(context, 0, 0, &error) == invalidBatch); + Require(sceAjmBatchWait(context, second + 1, 0, &error) == invalidBatch); +} + } int main() { @@ -164,7 +355,12 @@ int main() { const std::uint8_t badHeader[4] = {0xFD, 0x72, 0x1F, 0xF0}; Require(sceAjmDecAt9ParseConfigData(badHeader, &info) == invalidParameter); Require(sceAjmDecAt9ParseConfigData(nullptr, &info) == invalidParameter); + TestMp3ParseFrame(); TestMp3(context); TestOpus(context); + TestDecodeSingle(context); + TestGaplessDecode(context); + TestCodecInfo(context); + TestBatchWaitRelease(context); Require(sceAjmFinalize(context) == 0); } diff --git a/core/libs/tests/GuestAvPlayer.cpp b/core/libs/tests/GuestAvPlayer.cpp index 4092f512f..e9981ef53 100644 --- a/core/libs/tests/GuestAvPlayer.cpp +++ b/core/libs/tests/GuestAvPlayer.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -473,12 +474,23 @@ struct Allocations { std::mutex mutex; std::map blocks; int textures = 0; + std::optional textureLimit; }; Allocations allocations; +int ActiveTextureCountLocked() { + return static_cast(std::count_if(allocations.blocks.begin(), allocations.blocks.end(), [](const auto& allocation) { + return allocation.second.texture; + })); +} + void* Allocate(std::uint32_t alignment, std::uint32_t size, bool texture) { const auto aligned = static_cast(std::max(alignment, 16)); + { + std::lock_guard lock(allocations.mutex); + if (texture && allocations.textureLimit && ActiveTextureCountLocked() >= *allocations.textureLimit) return nullptr; + } void* memory = ::operator new(size, aligned); std::lock_guard lock(allocations.mutex); allocations.blocks[memory] = {texture, aligned}; @@ -509,6 +521,16 @@ bool IsAllocation(const void* memory, bool texture) { return found != allocations.blocks.end() && found->second.texture == texture; } +void SetTextureLimit(std::optional limit) { + std::lock_guard lock(allocations.mutex); + allocations.textureLimit = limit; +} + +int TextureCount() { + std::lock_guard lock(allocations.mutex); + return ActiveTextureCountLocked(); +} + struct Events { std::mutex mutex; std::vector> received; @@ -898,6 +920,28 @@ void TestWithoutAllocators() { Check(sceAvPlayerClose(handle) == 0, "close failed"); } +void TestOptionalVideoBuffersRespectMemoryLimit() { + const auto run = [](int textureLimit, int expectedTextures) { + SetTextureLimit(textureLimit); + AvPlayerInitData init = InitData(nullptr); + init.num_output_video_framebuffers = 2; + auto* player = sceAvPlayerInit(&init); + Check(player != nullptr, "init with limited texture memory failed"); + Check(sceAvPlayerAddSource(player, "/app0/avplayer.mp4") == 0, "add source with limited texture memory failed"); + Check(WaitFor([&] { return TextureCount() == expectedTextures && sceAvPlayerIsActive(player); }), "unexpected optional video buffer count"); + AvPlayerFrameInfoEx frame{}; + Check(WaitFor([&] { return sceAvPlayerGetVideoDataEx(player, &frame) != 0; }), "no frame with limited texture memory"); + CheckVideoPicture(frame); + Check(sceAvPlayerClose(player) == 0, "close with limited texture memory failed"); + Check(TextureCount() == 0, "limited texture allocations were not released"); + }; + + run(2, 2); + run(4, 2); + run(6, 6); + SetTextureLimit(std::nullopt); +} + void TestFileReplacementAutoStart() { HostFile file; AvPlayerInitData init = InitData(nullptr); @@ -951,6 +995,7 @@ int main() { TestChangeStream(); TestExtendedExports(); TestWithoutAllocators(); + TestOptionalVideoBuffersRespectMemoryLimit(); TestFileReplacementAutoStart(); TestHandedOutFramesStayIntact(); std::puts("AvPlayer tests passed"); diff --git a/core/libs/tests/GuestFont.cpp b/core/libs/tests/GuestFont.cpp index 569b3aef4..74ebc5694 100644 --- a/core/libs/tests/GuestFont.cpp +++ b/core/libs/tests/GuestFont.cpp @@ -1,10 +1,11 @@ -#include "prx/libSceFont/include/FontTypes.hpp" +#include "prx/libSceFont/include/FontDriver.hpp" #include #include #include #include #include -#include +#include +#include #include extern "C" { @@ -14,6 +15,7 @@ int APS5_VABI sceFontCreateLibrary(const FontMemory*, const void*, FontLibrary*) int APS5_VABI sceFontDestroyLibrary(FontLibrary*); int APS5_VABI sceFontCreateRenderer(const FontMemory*, const void*, FontRenderer*); int APS5_VABI sceFontDestroyRenderer(FontRenderer*); +int APS5_VABI sceFontGetPixelResolution(FontLibrary, std::uint32_t*); int APS5_VABI sceFontSupportSystemFonts(FontLibrary); int APS5_VABI sceFontSupportExternalFonts(FontLibrary, std::uint32_t, std::uint32_t); int APS5_VABI sceFontOpenFontSet(FontLibrary, std::uint32_t, std::uint32_t, const FontOpenDetail*, FontHandle*); @@ -21,6 +23,13 @@ int APS5_VABI sceFontOpenFontMemory(FontLibrary, const void*, std::uint32_t, con int APS5_VABI sceFontCloseFont(FontHandle); int APS5_VABI sceFontSetResolutionDpi(FontHandle, std::uint32_t, std::uint32_t); int APS5_VABI sceFontGetResolutionDpi(FontHandle, std::uint32_t*, std::uint32_t*); +int APS5_VABI sceFontSetScalePixel(FontHandle, float, float); +int APS5_VABI sceFontBindRenderer(FontHandle, FontRenderer); +int APS5_VABI sceFontUnbindRenderer(FontHandle); +int APS5_VABI sceFontSetupRenderScalePixel(FontHandle, float, float); +int APS5_VABI sceFontSetupRenderScalePoint(FontHandle, float, float); +int APS5_VABI sceFontGetKerning(FontHandle, std::uint32_t, std::uint32_t, FontKerning*); +int APS5_VABI sceFontGetRenderScaledKerning(FontHandle, std::uint32_t, std::uint32_t, FontKerning*); const void* APS5_VABI sceFontSelectLibraryFt(int); const void* APS5_VABI sceFontSelectRendererFt(int); } @@ -43,6 +52,10 @@ static void APS5_VABI Release(void*, void* pointer) { std::free(pointer); } +static std::uint32_t APS5_VABI CoarsePixelResolution() { + return 16; +} + static void Put16(std::vector& out, int value) { out.push_back(static_cast((value >> 8) & 0xFF)); out.push_back(static_cast(value & 0xFF)); @@ -54,19 +67,21 @@ static std::vector Words(std::initializer_list values) { return out; } -static std::vector EmptyGlyphFont() { - const std::vector>> tables = { - {"glyf", Words({0, 0})}, - {"head", Words({1, 0, 1, 0, 0, 0, 0x5F0F, 0x3CF5, 0, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 2, 0, 0})}, - {"hhea", Words({1, 0, 800, -200, 0, 500, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1})}, - {"hmtx", Words({500, 0})}, - {"loca", Words({0, 0})}, - {"maxp", Words({1, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0})}, - }; - std::vector font = Words({1, 0, static_cast(tables.size()), 64, 2, 32}); - int offset = 12 + 16 * static_cast(tables.size()); +static std::vector BuildFont(int glyphCount, std::map> tables) { + tables["glyf"] = Words({0, 0}); + tables["head"] = Words({1, 0, 1, 0, 0, 0, 0x5F0F, 0x3CF5, 0, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 2, 0, 0}); + tables["hhea"] = Words({1, 0, 800, -200, 0, 500, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1}); + tables["hmtx"] = Words({500}); + tables["hmtx"].resize(2 + 2 * static_cast(glyphCount)); + tables["loca"].assign(2 * (static_cast(glyphCount) + 1), 0); + tables["maxp"] = Words({1, 0, glyphCount, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0}); + const int tableCount = static_cast(tables.size()); + int entrySelector = 0; + while ((2 << entrySelector) <= tableCount) ++entrySelector; + std::vector font = Words({1, 0, tableCount, 16 << entrySelector, entrySelector, 16 * tableCount - (16 << entrySelector)}); + int offset = 12 + 16 * tableCount; for (const auto& table : tables) { - font.insert(font.end(), table.first, table.first + 4); + font.insert(font.end(), table.first.begin(), table.first.end()); for (const int value : {0, 0, offset >> 16, offset & 0xFFFF, 0, static_cast(table.second.size())}) Put16(font, value); offset += static_cast((table.second.size() + 3) & ~std::size_t{3}); } @@ -77,6 +92,21 @@ static std::vector EmptyGlyphFont() { return font; } +static std::vector EmptyGlyphFont() { + return BuildFont(1, {}); +} + +static std::vector KerningFont() { + return BuildFont(3, { + {"cmap", Words({0, 1, 3, 1, 0, 12, 4, 40, 0, 6, 4, 1, 2, 'A', 'V', 0xFFFF, 0, 'A', 'V', 0xFFFF, 1 - 'A', 2 - 'V', 1, 0, 0, 0})}, + {"kern", Words({0, 1, 0, 20, 1, 1, 6, 0, 0, 1, 2, -200})}, + }); +} + +static bool KerningIs(const FontKerning& kerning, float offsetX) { + return kerning.offsetX == offsetX && kerning.offsetY == 0.0f && kerning.positionX == 0.0f && kerning.positionY == 0.0f; +} + int main() { constexpr std::uint32_t SystemFontSet = 0x18070043u; const FontMemoryInterface iface{Allocate, Release, nullptr, nullptr, nullptr, nullptr}; @@ -89,6 +119,25 @@ int main() { Require(sceFontCreateLibrary(&memory, nullptr, &library) == SCE_FONT_ERROR_INVALID_PARAMETER && library == nullptr); Require(sceFontCreateLibrary(&memory, sceFontSelectLibraryFt(0), &library) == SCE_FONT_OK && library != nullptr); + std::uint32_t subPixelCount = 1; + Require(sceFontGetPixelResolution(library, &subPixelCount) == SCE_FONT_OK && subPixelCount == 64); + Require(sceFontGetPixelResolution(library, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontGetPixelResolution(nullptr, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + subPixelCount = 1; + Require(sceFontGetPixelResolution(nullptr, &subPixelCount) == SCE_FONT_ERROR_INVALID_LIBRARY && subPixelCount == 0); + FontHandleOpaque notALibrary{}; + subPixelCount = 1; + Require(sceFontGetPixelResolution(¬ALibrary, &subPixelCount) == SCE_FONT_ERROR_INVALID_LIBRARY && subPixelCount == 0); + Font::SysDriver coarseDriver = *static_cast(sceFontSelectLibraryFt(0)); + coarseDriver.pixel_resolution = CoarsePixelResolution; + FontLibrary coarseLibrary = nullptr; + Require(sceFontCreateLibrary(&memory, &coarseDriver, &coarseLibrary) == SCE_FONT_OK && coarseLibrary != nullptr); + Require(sceFontGetPixelResolution(coarseLibrary, &subPixelCount) == SCE_FONT_OK && subPixelCount == 16); + Require(sceFontGetPixelResolution(library, &subPixelCount) == SCE_FONT_OK && subPixelCount == 64); + coarseDriver.pixel_resolution = nullptr; + Require(sceFontGetPixelResolution(coarseLibrary, &subPixelCount) == SCE_FONT_ERROR_INVALID_LIBRARY && subPixelCount == 0); + Require(sceFontDestroyLibrary(&coarseLibrary) == SCE_FONT_OK && coarseLibrary == nullptr); + FontHandle font = reinterpret_cast(&memory); Require(sceFontOpenFontSet(library, SystemFontSet, 1, nullptr, &font) == SCE_FONT_ERROR_NO_SUPPORT_FUNCTION && font == nullptr); Require(sceFontSupportSystemFonts(library) == SCE_FONT_OK); @@ -126,6 +175,36 @@ int main() { FontRenderer renderer = nullptr; Require(sceFontCreateRenderer(&memory, sceFontSelectRendererFt(0), &renderer) == SCE_FONT_OK && renderer != nullptr); + + const std::vector kerningData = KerningFont(); + font = nullptr; + Require(sceFontOpenFontMemory(library, kerningData.data(), static_cast(kerningData.size()), nullptr, &font) == SCE_FONT_OK && font != nullptr); + Require(sceFontSetResolutionDpi(font, 144, 144) == SCE_FONT_OK); + Require(sceFontSetScalePixel(font, 100.0f, 100.0f) == SCE_FONT_OK); + FontKerning kerning{1.0f, 1.0f, 1.0f, 1.0f}; + Require(sceFontGetKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -20.0f)); + kerning = {1.0f, 1.0f, 1.0f, 1.0f}; + Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_ERROR_NOT_BOUND_RENDERER && KerningIs(kerning, 0.0f)); + Require(sceFontBindRenderer(font, renderer) == SCE_FONT_OK); + Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -20.0f)); + Require(sceFontSetupRenderScalePixel(font, 50.0f, 50.0f) == SCE_FONT_OK); + Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -10.0f)); + Require(sceFontGetKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -20.0f)); + Require(sceFontSetupRenderScalePoint(font, 20.0f, 20.0f) == SCE_FONT_OK); + Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_OK && KerningIs(kerning, -8.0f)); + kerning = {1.0f, 1.0f, 1.0f, 1.0f}; + Require(sceFontGetRenderScaledKerning(font, 'V', 'A', &kerning) == SCE_FONT_OK && KerningIs(kerning, 0.0f)); + kerning = {1.0f, 1.0f, 1.0f, 1.0f}; + Require(sceFontGetRenderScaledKerning(font, 'A', 'B', &kerning) == SCE_FONT_OK && KerningIs(kerning, 0.0f)); + Require(sceFontGetRenderScaledKerning(font, 'A', 'V', nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + kerning = {1.0f, 1.0f, 1.0f, 1.0f}; + Require(sceFontGetRenderScaledKerning(nullptr, 'A', 'V', &kerning) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && KerningIs(kerning, 0.0f)); + kerning = {1.0f, 1.0f, 1.0f, 1.0f}; + Require(sceFontGetRenderScaledKerning(&unopened, 'A', 'V', &kerning) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && KerningIs(kerning, 0.0f)); + Require(sceFontUnbindRenderer(font) == SCE_FONT_OK); + Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_ERROR_NOT_BOUND_RENDERER); + Require(sceFontCloseFont(font) == SCE_FONT_OK); + Require(sceFontDestroyRenderer(&renderer) == SCE_FONT_OK && renderer == nullptr); Require(sceFontDestroyRenderer(&renderer) == SCE_FONT_ERROR_INVALID_RENDERER); diff --git a/core/libs/tests/GuestFreebsdClocks.cpp b/core/libs/tests/GuestFreebsdClocks.cpp new file mode 100644 index 000000000..6c7f959f0 --- /dev/null +++ b/core/libs/tests/GuestFreebsdClocks.cpp @@ -0,0 +1,138 @@ +#include "SceTypes.hpp" +#include +#include + +extern "C" { +int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); +int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res); +int APS5_VABI sceKernelClockGettime(KernelClockid clockId, KernelTimespec* tp); +int APS5_VABI sceKernelClockGetres(KernelClockid clockId, KernelTimespec* tp); +int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds); +} + +static constexpr int SCE_OK = 0; + +static constexpr int GUEST_CLOCK_REALTIME = 0; +static constexpr int GUEST_CLOCK_VIRTUAL = 1; +static constexpr int GUEST_CLOCK_PROF = 2; +static constexpr int GUEST_CLOCK_MONOTONIC = 4; +static constexpr int GUEST_CLOCK_SECOND = 13; +static constexpr int GUEST_CLOCK_PROCESS_CPUTIME_ID = 15; + +static constexpr std::int64_t NANOS_PER_SECOND = 1000000000LL; +static constexpr std::int64_t NANOS_PER_MILLISECOND = 1000000LL; +static constexpr std::int64_t BURN_NANOS = 200 * NANOS_PER_MILLISECOND; +static constexpr std::int64_t IDLE_LIMIT_NANOS = 100 * NANOS_PER_MILLISECOND; +static constexpr std::int64_t GIVE_UP_NANOS = 10 * NANOS_PER_SECOND; + +static void Require(bool value) { if (!value) std::abort(); } + +static KernelTimespec Read(int clockId) { + KernelTimespec time{-1, -1}; + Require(clock_gettime_nid_postfix(clockId, &time) == SCE_OK); + Require(time.tv_sec >= 0); + Require(time.tv_nsec >= 0 && time.tv_nsec < NANOS_PER_SECOND); + return time; +} + +static std::int64_t Nanos(int clockId) { + const KernelTimespec time = Read(clockId); + return time.tv_sec * NANOS_PER_SECOND + time.tv_nsec; +} + +static std::int64_t ResolutionNanos(int clockId) { + KernelTimespec resolution{-1, -1}; + Require(clock_getres_nid_postfix(clockId, &resolution) == SCE_OK); + Require(resolution.tv_sec == 0); + Require(resolution.tv_nsec > 0 && resolution.tv_nsec < NANOS_PER_SECOND); + KernelTimespec sceResolution{-1, -1}; + Require(sceKernelClockGetres(clockId, &sceResolution) == SCE_OK); + Require(sceResolution.tv_sec == resolution.tv_sec && sceResolution.tv_nsec == resolution.tv_nsec); + return resolution.tv_nsec; +} + +static void RequireNeverDecreases(int clockId) { + std::int64_t previous = Nanos(clockId); + for (int i = 0; i < 10000; ++i) { + const std::int64_t current = Nanos(clockId); + Require(current >= previous); + previous = current; + } +} + +static void SecondClockReportsWholeSeconds() { + KernelTimespec resolution{-1, -1}; + Require(clock_getres_nid_postfix(GUEST_CLOCK_SECOND, &resolution) == SCE_OK); + Require(resolution.tv_sec == 1 && resolution.tv_nsec == 0); + resolution = {-1, -1}; + Require(sceKernelClockGetres(GUEST_CLOCK_SECOND, &resolution) == SCE_OK); + Require(resolution.tv_sec == 1 && resolution.tv_nsec == 0); + + KernelTimespec sceSecond{-1, -1}; + Require(sceKernelClockGettime(GUEST_CLOCK_SECOND, &sceSecond) == SCE_OK); + Require(sceSecond.tv_sec > 0 && sceSecond.tv_nsec == 0); + + std::int64_t previous = 0; + for (int i = 0; i < 1000; ++i) { + const KernelTimespec before = Read(GUEST_CLOCK_REALTIME); + const KernelTimespec second = Read(GUEST_CLOCK_SECOND); + const KernelTimespec after = Read(GUEST_CLOCK_REALTIME); + Require(second.tv_nsec == 0); + Require(second.tv_sec >= before.tv_sec - 1 && second.tv_sec <= after.tv_sec); + Require(second.tv_sec >= previous); + previous = second.tv_sec; + } +} + +static void ProcessClocksAreOrdered() { + const std::int64_t profResolution = ResolutionNanos(GUEST_CLOCK_PROF); + Require(ResolutionNanos(GUEST_CLOCK_VIRTUAL) == profResolution); + for (int i = 0; i < 1000; ++i) { + const std::int64_t user = Nanos(GUEST_CLOCK_VIRTUAL); + const std::int64_t userAndSystem = Nanos(GUEST_CLOCK_PROF); + const std::int64_t execution = Nanos(GUEST_CLOCK_PROCESS_CPUTIME_ID); + Require(user <= userAndSystem); + Require(userAndSystem <= execution + profResolution); + } +} + +static void BusyProcessAccumulatesUserTime() { + const std::int64_t wallStart = Nanos(GUEST_CLOCK_MONOTONIC); + const std::int64_t profStart = Nanos(GUEST_CLOCK_PROF); + const std::int64_t userStart = Nanos(GUEST_CLOCK_VIRTUAL); + std::int64_t user = userStart; + volatile std::uint64_t work = 0; + while (user - userStart < BURN_NANOS) { + Require(Nanos(GUEST_CLOCK_MONOTONIC) - wallStart < GIVE_UP_NANOS); + for (int i = 0; i < 1000000; ++i) work = work + 1; + const std::int64_t current = Nanos(GUEST_CLOCK_VIRTUAL); + Require(current >= user); + user = current; + } + Require(Nanos(GUEST_CLOCK_PROF) - profStart >= user - userStart); +} + +static void IdleProcessAccumulatesNone() { + const std::int64_t userStart = Nanos(GUEST_CLOCK_VIRTUAL); + const std::int64_t profStart = Nanos(GUEST_CLOCK_PROF); + Require(sceKernelUsleep_nid_postfix(200000) == SCE_OK); + Require(Nanos(GUEST_CLOCK_VIRTUAL) - userStart <= IDLE_LIMIT_NANOS); + Require(Nanos(GUEST_CLOCK_PROF) - profStart <= IDLE_LIMIT_NANOS); +} + +int main() { + SecondClockReportsWholeSeconds(); + + KernelTimespec time{-1, -1}; + Require(sceKernelClockGettime(GUEST_CLOCK_VIRTUAL, &time) == SCE_OK); + Require(time.tv_sec >= 0 && time.tv_nsec >= 0 && time.tv_nsec < NANOS_PER_SECOND); + time = {-1, -1}; + Require(sceKernelClockGettime(GUEST_CLOCK_PROF, &time) == SCE_OK); + Require(time.tv_sec >= 0 && time.tv_nsec >= 0 && time.tv_nsec < NANOS_PER_SECOND); + + RequireNeverDecreases(GUEST_CLOCK_VIRTUAL); + RequireNeverDecreases(GUEST_CLOCK_PROF); + ProcessClocksAreOrdered(); + BusyProcessAccumulatesUserTime(); + IdleProcessAccumulatesNone(); +} diff --git a/core/libs/tests/GuestHttp.cpp b/core/libs/tests/GuestHttp.cpp index fa1ae4a63..6800c55c8 100644 --- a/core/libs/tests/GuestHttp.cpp +++ b/core/libs/tests/GuestHttp.cpp @@ -7,6 +7,7 @@ extern "C" { int APS5_VABI sceHttpUriParse(SceHttpUriElement*, const char*, void*, std::size_t*, std::size_t); +int APS5_VABI sceHttpSetInflateGZIPEnabled(int, int); int APS5_VABI sceHttpUriBuild(char*, std::size_t*, std::size_t, const SceHttpUriElement*, std::uint32_t); int APS5_VABI sceHttpUriEscape(char*, std::size_t*, std::size_t, const char*); int APS5_VABI sceHttpCreateEpoll(int, HttpEpollHandle*); @@ -16,6 +17,7 @@ int APS5_VABI sceHttpCreateRequest2(int, const char*, const char*, std::uint64_t int APS5_VABI sceHttpsEnableOption(int, std::uint32_t); int APS5_VABI sceHttpsLoadCert(int, int, void*, void*, void*); int APS5_VABI sceHttpGetLastErrno(int, int*); +int APS5_VABI sceHttpParseStatusLine(const char*, std::size_t, std::int32_t*, std::int32_t*, std::int32_t*, const char**, std::size_t*); } static void Require(bool value) { if (!value) std::abort(); } @@ -97,10 +99,61 @@ int main() { Require(sceHttpReadData(1, data, sizeof(data)) == network); Require(sceHttpCreateRequest2(1, "GET", "/", 0) > 0); + Require(sceHttpSetInflateGZIPEnabled(1, 0) == 0); + Require(sceHttpSetInflateGZIPEnabled(1, 1) == 0); + Require(sceHttpSetInflateGZIPEnabled(1, 2) == invalidValue); + Require(sceHttpSetInflateGZIPEnabled(1, -1) == invalidValue); Require(sceHttpsEnableOption(1, 0) == 0); Require(sceHttpsLoadCert(1, 0, nullptr, nullptr, nullptr) == 0); int httpErrno = -1; Require(sceHttpGetLastErrno(1, &httpErrno) == 0); Require(httpErrno == 0); Require(sceHttpGetLastErrno(1, nullptr) == invalidValue); + + constexpr int parseInvalidResponse = static_cast(0x80432060); + constexpr int parseInvalidValue = static_cast(0x804321FE); + std::int32_t major = -1; + std::int32_t minor = -1; + std::int32_t code = -1; + const char* phrase = nullptr; + std::size_t phraseLength = 0; + auto parse = [&](const char* line, std::size_t length) { + return sceHttpParseStatusLine(line, length, &major, &minor, &code, &phrase, &phraseLength); + }; + + const char* response = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"; + Require(parse(response, std::strlen(response)) == 17); + Require(major == 1 && minor == 1 && code == 200); + Require(phrase == response + 12 && phraseLength == 3 && std::strncmp(phrase, " OK", 3) == 0); + + const char* lineFeedOnly = "HTTP/10.25 404 Not Found\n"; + Require(parse(lineFeedOnly, std::strlen(lineFeedOnly)) == 25); + Require(major == 10 && minor == 25 && code == 404); + Require(phrase == lineFeedOnly + 14 && phraseLength == 10); + + const char* noPhrase = "HTTP/2.0 204\r\n"; + Require(parse(noPhrase, std::strlen(noPhrase)) == 14); + Require(major == 2 && minor == 0 && code == 204); + Require(phrase == noPhrase + 12 && phraseLength == 0); + + phrase = nullptr; + phraseLength = 0; + Require(parse(nullptr, 17) == parseInvalidResponse); + Require(sceHttpParseStatusLine(response, 17, nullptr, &minor, &code, &phrase, &phraseLength) == parseInvalidValue); + Require(sceHttpParseStatusLine(response, 17, &major, &minor, &code, &phrase, nullptr) == parseInvalidValue); + major = -1; + minor = -1; + Require(parse("HTTX/1.1 200 OK\n", 16) == parseInvalidResponse); + Require(major == 0 && minor == 0); + Require(parse("HTTP/1.1", 7) == parseInvalidResponse); + Require(parse("HTTP/123", 8) == parseInvalidResponse); + Require(parse("HTTP/x.1 200 OK\n", 16) == parseInvalidResponse); + Require(parse("HTTP/1 200 OK\n", 14) == parseInvalidResponse); + Require(parse("HTTP/1. 200 OK\n", 15) == parseInvalidResponse); + Require(parse("HTTP/1.1/200 OK\n", 16) == parseInvalidResponse); + Require(parse("HTTP/1.1 2x0 OK\n", 16) == parseInvalidResponse); + Require(parse("HTTP/1.1 20", 11) == parseInvalidResponse); + Require(parse(response, 15) == parseInvalidResponse); + Require(parse(response, 16) == parseInvalidResponse); + Require(phrase == nullptr && phraseLength == 0); } diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 96ccf6af8..eaf3cc121 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -1,19 +1,29 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include +#include #include extern "C" { double APS5_VABI atof_nid_postfix(const char*); float APS5_VABI strtof_nid_postfix(const char*, char**); long double APS5_VABI strtold_nid_postfix(const char*, char**); +std::int64_t APS5_VABI strtol_nid_postfix(const char*, char**, int); +std::uint64_t APS5_VABI strtoul_nid_postfix(const char*, char**, int); +std::intmax_t APS5_VABI strtoimax_nid_postfix(const char*, char**, int); int* APS5_VABI __error_nid_postfix(); +struct LibcFloatConstant { std::uint32_t bits[4]; }; +extern LibcFloatConstant _FInf_nid_postfix; +extern LibcFloatConstant _FNan_nid_postfix; +short APS5_VABI _FDtest_nid_postfix(const float*); float APS5_VABI fmodf_nid_postfix(float, float); float APS5_VABI asinf_nid_postfix(float); float APS5_VABI acosf_nid_postfix(float); float APS5_VABI atan2f_nid_postfix(float, float); float APS5_VABI tanf_nid_postfix(float); float APS5_VABI log10f_nid_postfix(float); +float APS5_VABI logbf_nid_postfix(float); double APS5_VABI exp2_nid_postfix(double); double APS5_VABI ldexp_nid_postfix(double, int); double APS5_VABI scalbn_nid_postfix(double, int); @@ -21,6 +31,7 @@ float APS5_VABI scalbnf_nid_postfix(float, int); double APS5_VABI frexp_nid_postfix(double, int*); float APS5_VABI frexpf_nid_postfix(float, int*); std::int64_t APS5_VABI lround_nid_postfix(double); +std::div_t APS5_VABI div_nid_postfix(int, int); std::int64_t APS5_VABI lroundf_nid_postfix(float); std::int64_t APS5_VABI llround_nid_postfix(double); int APS5_VABI __isfinitef_nid_postfix(float); @@ -29,7 +40,116 @@ int APS5_VABI __isnormalf_nid_postfix(float); int APS5_VABI __isinff_nid_postfix(float); } static void Require(bool value) { if (!value) std::abort(); } + +static void CheckIntegerConversions() { + struct SignedCase { + const char* text; + int base; + std::int64_t value; + std::size_t consumed; + int error; + }; + const SignedCase signedCases[] = { + {"-42tail", 10, -42, 3, 0}, + {"2147483648!", 10, INT64_C(2147483648), 10, 0}, + {"-2147483649!", 10, -INT64_C(2147483649), 11, 0}, + {"4294967296!", 10, INT64_C(4294967296), 10, 0}, + {"9223372036854775807!", 10, INT64_MAX, 19, 0}, + {"-9223372036854775808!", 10, INT64_MIN, 20, 0}, + {"9223372036854775808!", 10, INT64_MAX, 19, 34}, + {"-9223372036854775809!", 10, INT64_MIN, 20, 34}, + {"18446744073709551616000!", 10, INT64_MAX, 23, 34}, + {" \t+0x100000000z", 0, INT64_C(4294967296), 14, 0}, + {"-0x8000000000000000!", 0, INT64_MIN, 19, 0}, + {"0x8000000000000000!", 16, INT64_MAX, 18, 34}, + {"0100000000000!", 0, INT64_C(8589934592), 13, 0}, + {"100000000000000000000000000000000!", 2, INT64_C(4294967296), 33, 0}, + {"z!", 36, 35, 1, 0}, + {"", 10, 0, 0, 0}, + {" \t+!", 10, 0, 0, 0}, + {"123!", 10, 123, 3, 0} + }; + for (const auto& test : signedCases) { + char* end = nullptr; + *__error_nid_postfix() = 0; + const auto value = strtol_nid_postfix(test.text, &end, test.base); + if (value != test.value || end != test.text + test.consumed || *__error_nid_postfix() != test.error) { + std::fprintf(stderr, "Guest strtol failed for '%s' in base %d\n", test.text, test.base); + std::abort(); + } + *__error_nid_postfix() = 0; + const auto maxValue = strtoimax_nid_postfix(test.text, &end, test.base); + if (maxValue != test.value || end != test.text + test.consumed || *__error_nid_postfix() != test.error) { + std::fprintf(stderr, "Guest strtoimax failed for '%s' in base %d\n", test.text, test.base); + std::abort(); + } + } + struct UnsignedCase { + const char* text; + int base; + std::uint64_t value; + std::size_t consumed; + int error; + }; + const UnsignedCase unsignedCases[] = { + {"4294967296!", 10, UINT64_C(4294967296), 10, 0}, + {"9223372036854775808!", 10, UINT64_C(9223372036854775808), 19, 0}, + {"18446744073709551615!", 10, UINT64_MAX, 20, 0}, + {"18446744073709551616!", 10, UINT64_MAX, 20, 34}, + {"18446744073709551616000!", 10, UINT64_MAX, 23, 34}, + {"-1!", 10, UINT64_MAX, 2, 0}, + {"-4294967296!", 10, UINT64_MAX - UINT64_C(4294967295), 11, 0}, + {"-18446744073709551615!", 10, 1, 21, 0}, + {"-18446744073709551616!", 10, UINT64_MAX, 21, 34}, + {" \t+0xffffffffffffffffz", 0, UINT64_MAX, 21, 0}, + {"0x10000000000000000!", 16, UINT64_MAX, 19, 34}, + {"0100000000000!", 0, UINT64_C(8589934592), 13, 0}, + {"100000000000000000000000000000000!", 2, UINT64_C(4294967296), 33, 0}, + {"z!", 36, 35, 1, 0}, + {"", 10, 0, 0, 0}, + {" \t-!", 10, 0, 0, 0}, + {"123!", 10, 123, 3, 0} + }; + for (const auto& test : unsignedCases) { + char* end = nullptr; + *__error_nid_postfix() = 0; + const auto value = strtoul_nid_postfix(test.text, &end, test.base); + if (value != test.value || end != test.text + test.consumed || *__error_nid_postfix() != test.error) { + std::fprintf(stderr, "Guest strtoul failed for '%s' in base %d\n", test.text, test.base); + std::abort(); + } + } + *__error_nid_postfix() = 13; + Require(strtol_nid_postfix("-4294967296", nullptr, 10) == -INT64_C(4294967296)); + Require(*__error_nid_postfix() == 13); + Require(strtoul_nid_postfix("4294967296", nullptr, 10) == UINT64_C(4294967296)); + Require(*__error_nid_postfix() == 13); + *__error_nid_postfix() = 0; +} + +static void CheckFloatClassification() { + Require(_FInf_nid_postfix.bits[0] == 0x7f800000u && _FNan_nid_postfix.bits[0] == 0x7fc00000u); + for (int word = 1; word < 4; ++word) Require(_FInf_nid_postfix.bits[word] == 0 && _FNan_nid_postfix.bits[word] == 0); + const struct { std::uint32_t bits; short code; } cases[] = { + {0x00000000u, 0}, {0x80000000u, 0}, {0x00000001u, -2}, {0x807fffffu, -2}, {0x00800000u, -1}, + {0xbf800000u, -1}, {0x7f7fffffu, -1}, {0x7f800000u, 1}, {0xff800000u, 1}, {0x7f800001u, 2}, + {0x7fc00000u, 2}, {0xff810000u, 2}, {0x7f810000u, 2}, + }; + for (const auto& test : cases) { + float value; + std::memcpy(&value, &test.bits, sizeof(value)); + if (_FDtest_nid_postfix(&value) != test.code) { + std::fprintf(stderr, "Guest _FDtest failed for %08x\n", test.bits); + std::abort(); + } + } + Require(_FDtest_nid_postfix(reinterpret_cast(&_FInf_nid_postfix)) == 1); + Require(_FDtest_nid_postfix(reinterpret_cast(&_FNan_nid_postfix)) == 2); +} + int main() { + CheckFloatClassification(); + CheckIntegerConversions(); Require(atof_nid_postfix(" -12.5tail") == -12.5); char* end = nullptr; const char input[] = "0x1.8p+2 remainder"; @@ -49,6 +169,11 @@ int main() { Require(std::abs(atan2f_nid_postfix(1.f, -1.f) - 2.3561945f) < 0.000001f); Require(tanf_nid_postfix(0.f) == 0.f); Require(log10f_nid_postfix(100.f) == 2.f); + Require(logbf_nid_postfix(8.f) == 3.f && logbf_nid_postfix(-0.75f) == -1.f); + Require(logbf_nid_postfix(std::numeric_limits::denorm_min()) == -149.f); + Require(logbf_nid_postfix(0.f) == -std::numeric_limits::infinity()); + Require(logbf_nid_postfix(-std::numeric_limits::infinity()) == std::numeric_limits::infinity()); + Require(std::isnan(logbf_nid_postfix(std::numeric_limits::quiet_NaN()))); Require(exp2_nid_postfix(-3.) == 0.125); Require(ldexp_nid_postfix(0.75, 4) == 12.); Require(scalbn_nid_postfix(0.75, -2) == 0.1875); @@ -71,4 +196,12 @@ int main() { Require(__isnormalf_nid_postfix(std::numeric_limits::denorm_min()) == 0); Require(__isnormal_nid_postfix(1.) == 1 && __isnormal_nid_postfix(0.) == 0); Require(__isnormal_nid_postfix(std::numeric_limits::denorm_min()) == 0); + const auto quotient = div_nid_postfix(7, 2); + Require(quotient.quot == 3 && quotient.rem == 1); + const auto negativeNumerator = div_nid_postfix(-7, 2); + Require(negativeNumerator.quot == -3 && negativeNumerator.rem == -1); + const auto negativeDenominator = div_nid_postfix(7, -2); + Require(negativeDenominator.quot == -3 && negativeDenominator.rem == 1); + const auto minimum = div_nid_postfix(std::numeric_limits::min(), 10); + Require(minimum.quot == -214748364 && minimum.rem == -8); } diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index d7dd2ff78..f7ba8fa38 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -28,6 +28,7 @@ #include #endif #ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN #include #endif @@ -57,6 +58,7 @@ int APS5_VABI sceKernelAioInitializeImpl(void*, std::int32_t); int APS5_VABI sceKernelAioSubmitReadCommands(KernelAioRwRequest*, std::int32_t, std::int32_t, std::int32_t*); int APS5_VABI sceKernelAioWaitRequest(std::int32_t, std::int32_t*, std::uint32_t*); int APS5_VABI sceKernelAioDeleteRequest(std::int32_t, std::int32_t*); +int APS5_VABI sceKernelMlock_nid_postfix(void*, std::uint64_t); } static void Require(bool condition, std::source_location location = std::source_location::current()) { @@ -175,7 +177,38 @@ static void CheckFixedVirtualReservation() { void* fixed = requested; Require(sceKernelReserveVirtualRange(&fixed, page * 2, 0x400010, 0) == 0); Require(fixed == requested); - Require(sceKernelMunmap(fixed, page * 2) == 0); + void* again = requested; + Require(sceKernelReserveVirtualRange(&again, page * 2, 0x400010, 0) == 0); + Require(again == requested); + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); + void* mapped = requested; + Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0x10, phys, 0) == 0); + Require(mapped == requested); + static_cast(mapped)[0] = 11; + VirtualQueryInfo before{}; + Require(sceKernelVirtualQuery(mapped, 0, &before, sizeof(before)) == 0); + Require(before.is_direct); + void* reserved = requested; + Require(sceKernelReserveVirtualRange(&reserved, page * 2, 0x10, 0) == 0); + Require(reserved == requested); + VirtualQueryInfo after{}; + Require(sceKernelVirtualQuery(reserved, 0, &after, sizeof(after)) != 0); + void* remapped = requested; + Require(sceKernelMapDirectMemory(&remapped, page * 2, 3, 0x10, phys, 0) == 0); + Require(remapped == requested); + VirtualQueryInfo revived{}; + Require(sceKernelVirtualQuery(remapped, 0, &revived, sizeof(revived)) == 0); + Require(revived.is_direct); + bool refused = false; + try { + sceKernelReserveVirtualRange(&reserved, page * 2, 0x90, 0); + } catch (const std::exception&) { + refused = true; + } + Require(refused); + Require(sceKernelMunmap(reserved, page * 2) == 0); + Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); void* pooled = nullptr; Require(sceKernelMemoryPoolReserve(requested, page * 2, 0, 0x10, &pooled) == 0); Require(pooled == requested); @@ -203,8 +236,90 @@ static void CheckNoOverwriteRejectsHostOccupiedMapping() { GuestArena::GuestArenaReset_nid_postfix(target, page); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } + +static void CheckFixedMappingsReachTheApplicationAreaEnd() { + constexpr std::size_t page = 0x4000; + constexpr std::uintptr_t applicationAreaEnd = 0xFC00000000ull; + std::uintptr_t base = 0; + std::size_t size = 0; + GuestArena::GuestArenaRange_nid_postfix(&base, &size); + Require(base == 0x200000000ull && base + size == applicationAreaEnd); + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); + void* const requested = reinterpret_cast(applicationAreaEnd - page * 2); + void* mapped = requested; + Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0x90, phys, 0) == 0); + Require(mapped == requested); + static_cast(mapped)[page * 2 - 1] = 7; + void* alias = nullptr; + Require(sceKernelMapDirectMemory(&alias, page, 3, 0, phys + page, 0) == 0); + Require(static_cast(alias)[page - 1] == 7); + VirtualQueryInfo info{}; + Require(sceKernelVirtualQuery(mapped, 0, &info, sizeof(info)) == 0 && info.is_direct && info.end == applicationAreaEnd); + void* beyond = reinterpret_cast(applicationAreaEnd); + bool refused = false; + try { + refused = sceKernelMapDirectMemory(&beyond, page, 3, 0x10, phys, 0) != 0; + } catch (const std::exception&) { + refused = true; + } + Require(refused && beyond == reinterpret_cast(applicationAreaEnd)); + Require(sceKernelMunmap(alias, page) == 0); + Require(sceKernelMunmap(mapped, page * 2) == 0); + Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); +} #endif +#if defined(__linux__) +static std::size_t LockedKilobytes() { + std::ifstream status("/proc/self/status"); + std::string line; + while (std::getline(status, line)) { + if (line.rfind("VmLck:", 0) == 0) return std::strtoull(line.c_str() + 6, nullptr, 10); + } + return 0; +} +#endif + +static void CheckMlock() { + constexpr std::size_t page = 0x4000; +#ifdef _WIN32 + constexpr std::size_t length = 0x400000; +#else + constexpr std::size_t length = 0x10000; +#endif + constexpr int outOfMemory = static_cast(0x8002000cu); + constexpr int invalid = static_cast(0x80020016u); + void* mapped = nullptr; + Require(sceKernelMapFlexibleMemory(&mapped, length, 3, 0) == 0); + auto* bytes = static_cast(mapped); + Require(sceKernelMlock_nid_postfix(mapped, 0) == 0); +#if defined(__linux__) + const auto lockedBefore = LockedKilobytes(); +#endif + Require(sceKernelMlock_nid_postfix(bytes + 1, length - page) == 0); +#ifdef _WIN32 + SIZE_T minimum = 0; + SIZE_T maximum = 0; + DWORD limits = 0; + Require(GetProcessWorkingSetSizeEx(GetCurrentProcess(), &minimum, &maximum, &limits) && minimum >= length && maximum > minimum); + Require(VirtualUnlock(mapped, length)); + Require(!VirtualUnlock(mapped, length) && GetLastError() == ERROR_NOT_LOCKED); +#elif defined(__linux__) + Require(LockedKilobytes() - lockedBefore == length / 1024); +#endif + Require(sceKernelMlock_nid_postfix(mapped, length) == 0); + Require(sceKernelMlock_nid_postfix(mapped, length) == 0); + bytes[length - 1] = 7; + Require(sceKernelMlock_nid_postfix(reinterpret_cast(std::numeric_limits::max() - page + 1), page * 2) == invalid); + void* reserved = nullptr; + Require(sceKernelReserveVirtualRange(&reserved, page, 0, 0) == 0); + Require(sceKernelMlock_nid_postfix(reserved, page) == outOfMemory); + Require(sceKernelMunmap(reserved, page) == 0); + Require(sceKernelMunmap(mapped, length) == 0); + Require(sceKernelMlock_nid_postfix(mapped, page) == outOfMemory); +} + static void CheckSharedDirectMemoryLifecycle() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; @@ -586,10 +701,12 @@ int main() { CheckInternalNamedFlexibleMapping(); CheckDirectMemoryFollowsPhysicalPages(); CheckFixedVirtualReservation(); + CheckMlock(); CheckSharedDirectMemoryLifecycle(); CheckHeapAfterMappingReuse(); #ifdef _WIN32 CheckNoOverwriteRejectsHostOccupiedMapping(); + CheckFixedMappingsReachTheApplicationAreaEnd(); #endif CheckSharedWriteTracking(); CheckReadsIntoSharedWriteTracking(); diff --git a/core/libs/tests/GuestMsgDialogNative.cpp b/core/libs/tests/GuestMsgDialogNative.cpp new file mode 100644 index 000000000..6e3b757e2 --- /dev/null +++ b/core/libs/tests/GuestMsgDialogNative.cpp @@ -0,0 +1,49 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI sceMsgDialogInitialize(void); +int APS5_VABI sceMsgDialogOpen(const void* param); +int APS5_VABI sceMsgDialogUpdateStatus(void); +int APS5_VABI sceMsgDialogGetResult(MsgDialogResult* result); +int APS5_VABI sceMsgDialogClose(void); +int APS5_VABI sceMsgDialogTerminate(void); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr int kStatusInitialized = 1; +constexpr int kStatusFinished = 3; +constexpr int kErrNotRunning = static_cast(0x80B8000Bu); +constexpr int kButtonIdOk = 1; + +} + +int main() { + std::uint8_t param[0x60]{}; + + Require(sceMsgDialogClose() == kErrNotRunning); + Require(sceMsgDialogUpdateStatus() == 0); + + Require(sceMsgDialogInitialize() == 0); + Require(sceMsgDialogClose() == kErrNotRunning); + Require(sceMsgDialogUpdateStatus() == kStatusInitialized); + + Require(sceMsgDialogOpen(param) == 0); + Require(sceMsgDialogUpdateStatus() == kStatusFinished); + Require(sceMsgDialogClose() == kErrNotRunning); + Require(sceMsgDialogUpdateStatus() == kStatusFinished); + + MsgDialogResult result{}; + result.button_id = -1; + Require(sceMsgDialogGetResult(&result) == 0); + Require(result.result == 0); + Require(result.button_id == kButtonIdOk); + + Require(sceMsgDialogTerminate() == 0); + Require(sceMsgDialogClose() == kErrNotRunning); +} diff --git a/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp new file mode 100644 index 000000000..8b5c7b585 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp @@ -0,0 +1,256 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +using Deleter = void (APS5_VABI*)(void*); + +struct IntrusivePtr { + void* object; + Deleter deleter; + void* libContext; +}; + +using Construct = void (APS5_VABI*)(IntrusivePtr*); +using Copy = void (APS5_VABI*)(IntrusivePtr*, const IntrusivePtr*); +using Destroy = void (APS5_VABI*)(IntrusivePtr*); +using Access = void* (APS5_VABI*)(const IntrusivePtr*); + +extern "C" { +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev(IntrusivePtr*); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_(IntrusivePtr*, const IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(IntrusivePtr*, const IntrusivePtr*); + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev(IntrusivePtr*); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev(IntrusivePtr*); + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv(const IntrusivePtr*); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv(const IntrusivePtr*); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static constexpr std::size_t ObjectSize = 0x40; +static constexpr std::size_t VectorReferenceCountOffset = 0x30; + +struct DestroyCase { + Destroy destroy; + std::size_t referenceCountOffset; +}; + +static void* deletedObject = nullptr; +static int deleterCalls = 0; + +static void APS5_VABI RecordDelete(void* object) { + deletedObject = object; + ++deleterCalls; +} + +static std::int32_t ReadCount(const unsigned char* object, const std::size_t offset) { + std::int32_t value; + std::memcpy(&value, object + offset, sizeof(value)); + return value; +} + +static void WriteCount(unsigned char* object, const std::size_t offset, const std::int32_t value) { + std::memcpy(object + offset, &value, sizeof(value)); +} + +static bool OnlyCountChanged(const unsigned char* object, const std::size_t offset) { + for (std::size_t index = 0; index < ObjectSize; ++index) { + if (index >= offset && index < offset + sizeof(std::int32_t)) continue; + if (object[index] != 0x5a) return false; + } + return true; +} + +static void TestConstruct(const Construct construct) { + IntrusivePtr pointer; + std::memset(&pointer, 0xa5, sizeof(pointer)); + construct(&pointer); + Require(pointer.object == nullptr); + Require(pointer.deleter == nullptr); + Require(pointer.libContext == nullptr); +} + +static void TestAccess(const Access access) { + int object = 0; + int context = 0; + const IntrusivePtr empty{nullptr, RecordDelete, &context}; + Require(access(&empty) == nullptr); + const IntrusivePtr filled{&object, nullptr, nullptr}; + Require(access(&filled) == &object); +} + +static void TestCopy(const Copy copy) { + alignas(8) unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + WriteCount(object, 0, 1); + int context = 0; + + const IntrusivePtr source{object, RecordDelete, &context}; + IntrusivePtr target; + std::memset(&target, 0xa5, sizeof(target)); + copy(&target, &source); + Require(target.object == object); + Require(target.deleter == RecordDelete); + Require(target.libContext == &context); + Require(source.object == object); + Require(ReadCount(object, 0) == 2); + Require(OnlyCountChanged(object, 0)); + + const IntrusivePtr empty{nullptr, RecordDelete, &context}; + std::memset(&target, 0xa5, sizeof(target)); + copy(&target, &empty); + Require(target.object == nullptr); + Require(target.deleter == RecordDelete); + Require(target.libContext == &context); +} + +static void TestDestroy(const DestroyCase& testCase) { + alignas(8) unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + int context = 0; + const std::size_t offset = testCase.referenceCountOffset; + + deleterCalls = 0; + deletedObject = nullptr; + + IntrusivePtr empty{nullptr, RecordDelete, &context}; + testCase.destroy(&empty); + Require(empty.object == nullptr); + Require(empty.deleter == RecordDelete); + Require(empty.libContext == &context); + Require(deleterCalls == 0); + + WriteCount(object, offset, 2); + IntrusivePtr shared{object, RecordDelete, &context}; + testCase.destroy(&shared); + Require(ReadCount(object, offset) == 1); + Require(shared.object == object); + Require(shared.deleter == RecordDelete); + Require(shared.libContext == &context); + Require(deleterCalls == 0); + + IntrusivePtr last{object, RecordDelete, &context}; + testCase.destroy(&last); + Require(ReadCount(object, offset) == 0); + Require(OnlyCountChanged(object, offset)); + Require(deleterCalls == 1); + Require(deletedObject == object); + Require(last.object == nullptr); + Require(last.deleter == nullptr); + Require(last.libContext == &context); + + WriteCount(object, offset, 1); + IntrusivePtr pooled{object, nullptr, &context}; + bool thrown = false; + try { + testCase.destroy(&pooled); + } catch (const std::runtime_error&) { + thrown = true; + } + Require(thrown); + Require(deleterCalls == 1); +} + +static void TestSharedOwnership() { + alignas(8) unsigned char object[ObjectSize]; + std::memset(object, 0x5a, sizeof(object)); + WriteCount(object, 0, 1); + deleterCalls = 0; + deletedObject = nullptr; + + IntrusivePtr owner; + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev(&owner); + owner.object = object; + owner.deleter = RecordDelete; + + IntrusivePtr argument; + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_(&argument, &owner); + Require(_ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(&argument) == object); + Require(ReadCount(object, 0) == 2); + + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(&argument); + Require(ReadCount(object, 0) == 1); + Require(deleterCalls == 0); + + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev(&owner); + Require(ReadCount(object, 0) == 0); + Require(deleterCalls == 1); + Require(deletedObject == object); + Require(_ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv(&owner) == nullptr); +} + +int main() { + Require(sizeof(IntrusivePtr) == 0x18); + + const Construct constructors[] = { + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEC1Ev, + }; + for (const auto construct : constructors) TestConstruct(construct); + + const Access accessors[] = { + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V15EntryEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6BinaryEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEEdeEv, + }; + for (const auto access : accessors) TestAccess(access); + + const Copy copies[] = { + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEEC1ERS7_, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEEC1ERS7_, + }; + for (const auto copy : copies) TestCopy(copy); + + const DestroyCase destructors[] = { + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V121GetRankingRequestBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122GetRankingResponseBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V122RecordScoreRequestBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V123RecordScoreResponseBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V130GetBoardDefinitionResponseBodyEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V14UserEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_12Leaderboards2V19RecordApi26RecordScoreResponseHeadersEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_12Leaderboards2V15EntryEEEEEED1Ev, VectorReferenceCountOffset}, + }; + for (const auto& testCase : destructors) TestDestroy(testCase); + + TestSharedOwnership(); + return 0; +} diff --git a/core/libs/tests/GuestNpCppWebApiString.cpp b/core/libs/tests/GuestNpCppWebApiString.cpp new file mode 100644 index 000000000..c54ee17c3 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiString.cpp @@ -0,0 +1,112 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +extern "C" { +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(void* self, void* libContext); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD1Ev(void* self); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(const void* self); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr std::size_t kStringSize = 0x20; +constexpr std::size_t kGuardSize = 0x10; +constexpr std::size_t kReferenceCountOffset = 0x00; +constexpr std::size_t kBufferOffset = 0x08; +constexpr std::size_t kBufferSizeOffset = 0x10; +constexpr std::size_t kLibContextOffset = 0x18; +constexpr unsigned char kFill = 0xA5; + +struct Storage { + alignas(8) unsigned char bytes[kStringSize + kGuardSize]; +}; + +template +TValue Load(const Storage& storage, std::size_t offset) { + TValue value; + std::memcpy(&value, storage.bytes + offset, sizeof(value)); + return value; +} + +template +void Store(Storage& storage, std::size_t offset, TValue value) { + std::memcpy(storage.bytes + offset, &value, sizeof(value)); +} + +bool GuardIntact(const Storage& storage) { + for (std::size_t i = kStringSize; i < sizeof(storage.bytes); i++) { + if (storage.bytes[i] != kFill) return false; + } + return true; +} + +bool DestructorThrows(Storage& storage) { + try { + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +void TestConstructedStringIsEmpty() { + std::uint64_t libContext = 1; + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, &libContext); + + Require(Load(storage, kReferenceCountOffset) == 0); + Require(Load(storage, kBufferOffset) == nullptr); + Require(Load(storage, kBufferSizeOffset) == 0); + Require(Load(storage, kLibContextOffset) == &libContext); + Require(GuardIntact(storage)); + + const char* text = _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes); + Require(text != nullptr && text[0] == '\0'); + + Storage before = storage; + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); + Require(std::memcmp(before.bytes, storage.bytes, sizeof(storage.bytes)) == 0); +} + +void TestNullLibContextIsStored() { + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, nullptr); + + Require(Load(storage, kLibContextOffset) == nullptr); + Require(Load(storage, kBufferOffset) == nullptr); + Require(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes)[0] == '\0'); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(storage.bytes); +} + +void TestBufferIsReturnedAndNotDroppedSilently() { + std::uint64_t libContext = 1; + char text[] = "ranking"; + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(storage.bytes, &libContext); + Store(storage, kBufferOffset, text); + Store(storage, kBufferSizeOffset, sizeof(text)); + + Require(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(storage.bytes) == text); + Require(DestructorThrows(storage)); + Require(std::strcmp(text, "ranking") == 0); +} + +} + +int main() { + TestConstructedStringIsEmpty(); + TestNullLibContextIsStored(); + TestBufferIsReturnedAndNotDroppedSilently(); +} diff --git a/core/libs/tests/GuestNpSessionSignaling.cpp b/core/libs/tests/GuestNpSessionSignaling.cpp new file mode 100644 index 000000000..10ec8b222 --- /dev/null +++ b/core/libs/tests/GuestNpSessionSignaling.cpp @@ -0,0 +1,51 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceNpSessionSignalingInitialize(void* param); +int APS5_VABI sceNpSessionSignalingCreateContext2(const void* param, std::uint32_t* contextId); +std::int32_t APS5_VABI sceNpSessionSignalingGetLocalNetInfo(std::int32_t contextId, void* info); +int APS5_VABI sceNpSessionSignalingRequestPrepare(std::uint32_t contextId, std::uint32_t* requestId); +int APS5_VABI sceNpSessionSignalingTerminate(void); +} + +namespace { + +constexpr int InvalidArgument = static_cast(0x80552D02u); +constexpr int Unavailable = static_cast(0x80552D06u); + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "NpSessionSignaling: %s\n", message); + std::abort(); + } +} + +} + +int main() { + unsigned char initializeParam[32] = {}; + Require(sceNpSessionSignalingInitialize(initializeParam) == 0, "initialize failed"); + unsigned char contextParam[64] = {}; + std::uint32_t contextId = 0; + Require(sceNpSessionSignalingCreateContext2(contextParam, &contextId) == 0, "context creation failed"); + + std::uint32_t requestId = 0xa5a5a5a5u; + Require(sceNpSessionSignalingRequestPrepare(contextId, nullptr) == InvalidArgument, "null request id"); + Require(sceNpSessionSignalingRequestPrepare(contextId, &requestId) == Unavailable, "prepare without network"); + Require(requestId == 0xa5a5a5a5u, "failed prepare wrote a request id"); + + const auto netContextId = static_cast(contextId); + unsigned char info[16]; + std::memset(info, 0xa5, sizeof(info)); + unsigned char untouched[sizeof(info)]; + std::memcpy(untouched, info, sizeof(info)); + Require(sceNpSessionSignalingGetLocalNetInfo(netContextId, nullptr) == InvalidArgument, "null info"); + Require(sceNpSessionSignalingGetLocalNetInfo(netContextId, info) == Unavailable, "local net info without network"); + Require(std::memcmp(info, untouched, sizeof(info)) == 0, "failed query modified the info"); + + Require(sceNpSessionSignalingTerminate() == 0, "terminate failed"); +} diff --git a/core/libs/tests/GuestNpTrophy2.cpp b/core/libs/tests/GuestNpTrophy2.cpp new file mode 100644 index 000000000..5a286ab35 --- /dev/null +++ b/core/libs/tests/GuestNpTrophy2.cpp @@ -0,0 +1,20 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { +int APS5_VABI sceNpTrophy2RegisterUnlockCallback(void*, void*); +int APS5_VABI sceNpTrophy2UnregisterUnlockCallback(); +} + +static void Require(bool value) { if (!value) std::abort(); } + +static void APS5_VABI OnUnlock(int, int, void*) {} + +int main() { + int userdata = 0; + Require(sceNpTrophy2UnregisterUnlockCallback() == 0); + Require(sceNpTrophy2RegisterUnlockCallback(reinterpret_cast(&OnUnlock), &userdata) == 0); + Require(sceNpTrophy2UnregisterUnlockCallback() == 0); + Require(sceNpTrophy2RegisterUnlockCallback(reinterpret_cast(&OnUnlock), &userdata) == 0); + Require(sceNpTrophy2UnregisterUnlockCallback() == 0); +} diff --git a/core/libs/tests/GuestPlayGoDialog.cpp b/core/libs/tests/GuestPlayGoDialog.cpp index 9c313bda1..40a6a6710 100644 --- a/core/libs/tests/GuestPlayGoDialog.cpp +++ b/core/libs/tests/GuestPlayGoDialog.cpp @@ -10,6 +10,7 @@ int APS5_VABI scePlayGoDialogTerminate(void); int APS5_VABI scePlayGoDialogOpen(const void* param); int APS5_VABI scePlayGoDialogClose(void); int APS5_VABI scePlayGoDialogUpdateStatus(void); +int APS5_VABI scePlayGoDialogGetStatus(void); int APS5_VABI scePlayGoDialogGetResult(void* result); } @@ -21,6 +22,7 @@ int main() { constexpr std::int32_t paramInvalid = static_cast(0x80ED0003); constexpr std::int32_t invalidState = static_cast(0x80ED0005); + Require(scePlayGoDialogGetStatus() == 0); Require(scePlayGoDialogTerminate() == notInitialized); Require(scePlayGoDialogOpen(nullptr) == paramInvalid); Require(scePlayGoDialogGetResult(nullptr) == notInitialized); @@ -29,11 +31,13 @@ int main() { Require(scePlayGoDialogOpen(&openParam) == invalidState); Require(scePlayGoDialogInitialize() == 0); + Require(scePlayGoDialogGetStatus() == 1); Require(scePlayGoDialogInitialize() == alreadyInitialized); Require(scePlayGoDialogOpen(nullptr) == paramInvalid); Require(scePlayGoDialogUpdateStatus() == 1); Require(scePlayGoDialogOpen(&openParam) == 0); + Require(scePlayGoDialogGetStatus() == 3); Require(scePlayGoDialogUpdateStatus() == 3); PlayGoDialogResult result{}; @@ -41,6 +45,7 @@ int main() { Require(result.result == 0); Require(scePlayGoDialogClose() == 0); Require(scePlayGoDialogTerminate() == 0); + Require(scePlayGoDialogGetStatus() == 0); Require(scePlayGoDialogTerminate() == notInitialized); return 0; } diff --git a/core/libs/tests/GuestPngEnc.cpp b/core/libs/tests/GuestPngEnc.cpp index 9208ae7f0..7535dae85 100644 --- a/core/libs/tests/GuestPngEnc.cpp +++ b/core/libs/tests/GuestPngEnc.cpp @@ -1,8 +1,10 @@ #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "Decoder/Png.hpp" +#include #include #include +#include #include extern "C" { @@ -84,6 +86,32 @@ int main() { } } + constexpr std::uint32_t rowSize = 16 * 4; + std::vector rows(rowSize * 6); + std::uint32_t noise = 12345; + for (std::uint32_t y = 0; y < 6; ++y) { + for (std::uint32_t i = 0; i < rowSize; ++i) { + noise = noise * 1103515245u + 12345u; + rows[y * rowSize + i] = y == 2 ? static_cast(noise >> 16) : static_cast(i / 4 * 8); + } + } + std::copy_n(rows.begin() + 2 * rowSize, rowSize, rows.begin() + 3 * rowSize); + PngEncEncodeParam filtered{rows.data(), png.data(), static_cast(rows.size()), static_cast(png.size()), + 16, 6, rowSize, 0, 19, 8, 0, 0, 6}; + const auto encodeWith = [&](std::uint16_t filterType) { + filtered.filter_type = filterType; + const int size = scePngEncEncode(handle, &filtered, nullptr); + Require(size > 0); + return std::vector(png.begin(), png.begin() + size); + }; + using namespace Decoder::Png; + const std::pair filterSets[] = { + {0, FILTER_NONE}, {1, FILTER_SUB}, {2, FILTER_UP}, {4, FILTER_AVERAGE}, {8, FILTER_PAETH}, + {3, FILTER_SUB | FILTER_UP}, {6, FILTER_UP | FILTER_AVERAGE}, {9, FILTER_SUB | FILTER_PAETH}, + {14, FILTER_UP | FILTER_AVERAGE | FILTER_PAETH}, {15, FILTER_ALL}}; + for (const auto& [filterType, filters] : filterSets) Require(encodeWith(filterType) == Encode(rows, 16, 6, 4, {6, filters})); + Require(encodeWith(3) != encodeWith(1) && encodeWith(3) != encodeWith(2) && encodeWith(15) != encodeWith(14)); + encode.png_mem_size = 20; PngEncOutputInfo info{1, 1}; Require(scePngEncEncode(handle, &encode, &info) == dataOverflow && info.data_size == 0 && info.processed_height == 0); diff --git a/core/libs/tests/GuestPsml.cpp b/core/libs/tests/GuestPsml.cpp new file mode 100644 index 000000000..d0aac1d17 --- /dev/null +++ b/core/libs/tests/GuestPsml.cpp @@ -0,0 +1,53 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" { +std::int32_t APS5_VABI scePsmlMfsrGetContextBufferRequirement1100(void* requirement, const void* param); +std::int32_t APS5_VABI scePsmlMfsrCreateContext1100(void** context, const void* param); +std::int32_t APS5_VABI scePsmlMfsrGetDispatchMfsrPacket1100(void* context, void* commandBuffer, const void* param); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +constexpr std::int32_t kErrNotInitialized = static_cast(0x8A810001); + +struct CommandBuffer { + std::uint32_t* cursor; + std::uint32_t sizeInDwords; +}; + +} + +int main() { + std::array param{}; + + std::array requirement; + requirement.fill(0xa5); + const auto untouchedRequirement = requirement; + Require(scePsmlMfsrGetContextBufferRequirement1100(requirement.data(), param.data()) == kErrNotInitialized); + Require(requirement == untouchedRequirement); + Require(scePsmlMfsrGetContextBufferRequirement1100(nullptr, nullptr) == kErrNotInitialized); + + std::uint64_t contextStorage = 0; + void* const sentinel = &contextStorage; + void* context = sentinel; + Require(scePsmlMfsrCreateContext1100(&context, param.data()) == kErrNotInitialized); + Require(context == sentinel); + Require(scePsmlMfsrCreateContext1100(nullptr, nullptr) == kErrNotInitialized); + + std::array dwords; + dwords.fill(0xa5a5a5a5u); + const auto untouchedDwords = dwords; + CommandBuffer commandBuffer{dwords.data(), static_cast(dwords.size())}; + Require(scePsmlMfsrGetDispatchMfsrPacket1100(&contextStorage, &commandBuffer, param.data()) == kErrNotInitialized); + Require(commandBuffer.cursor == dwords.data()); + Require(commandBuffer.sizeInDwords == dwords.size()); + Require(dwords == untouchedDwords); + Require(scePsmlMfsrGetDispatchMfsrPacket1100(nullptr, &commandBuffer, param.data()) == kErrNotInitialized); + Require(commandBuffer.cursor == dwords.data()); + Require(dwords == untouchedDwords); +} diff --git a/core/libs/tests/GuestPthreadKey.cpp b/core/libs/tests/GuestPthreadKey.cpp new file mode 100644 index 000000000..e9bdd1653 --- /dev/null +++ b/core/libs/tests/GuestPthreadKey.cpp @@ -0,0 +1,100 @@ +#include "SceTypes.hpp" +#include +#include +#include + +extern "C" { +int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); +int APS5_VABI scePthreadJoin(Pthread thread, void** retval); +int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor); +int APS5_VABI scePthreadKeyDelete(PthreadKey key); +void* APS5_VABI scePthreadGetspecific(PthreadKey key); +int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value); +} + +static constexpr int SCE_OK = 0; +static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); + +static void Require(bool value) { if (!value) std::abort(); } + +static std::atomic deletedCalls{0}, reusedCalls{0}, liveCalls{0}; +static std::atomic liveValue{nullptr}; +static std::atomic stage{0}; +static PthreadKey sharedKey = -1; +static int first = 1, second = 2, third = 3; + +static void APS5_VABI Deleted(void*) { ++deletedCalls; } +static void APS5_VABI Reused(void*) { ++reusedCalls; } +static void APS5_VABI Live(void* value) { ++liveCalls; liveValue = value; } + +static pthread_key_destructor_func_t Destructor(void (APS5_VABI* function)(void*)) { + return reinterpret_cast(function); +} + +static void WaitFor(int value) { + while (stage.load() != value) std::this_thread::yield(); +} + +static void* APS5_VABI ReuseBeforeExit(void*) { + PthreadKey live = -1; + Require(scePthreadKeyCreate(&live, Destructor(Live)) == SCE_OK); + Require(scePthreadSetspecific(live, &third) == SCE_OK); + PthreadKey deleted = -1; + Require(scePthreadKeyCreate(&deleted, Destructor(Deleted)) == SCE_OK); + Require(scePthreadSetspecific(deleted, &first) == SCE_OK); + Require(scePthreadKeyDelete(deleted) == SCE_OK); + PthreadKey reused = -1; + Require(scePthreadKeyCreate(&reused, Destructor(Reused)) == SCE_OK); + Require(reused == deleted); + sharedKey = live; + return nullptr; +} + +static void* APS5_VABI HoldAcrossReuse(void*) { + Require(scePthreadSetspecific(sharedKey, &first) == SCE_OK); + Require(scePthreadGetspecific(sharedKey) == &first); + stage = 1; + WaitFor(2); + Require(scePthreadGetspecific(sharedKey) == nullptr); + Require(scePthreadSetspecific(sharedKey, &second) == SCE_OK); + Require(scePthreadGetspecific(sharedKey) == &second); + return nullptr; +} + +int main() { + PthreadKey key = -1; + Require(scePthreadKeyCreate(&key, nullptr) == SCE_OK); + Require(scePthreadGetspecific(key) == nullptr); + Require(scePthreadSetspecific(key, &first) == SCE_OK); + Require(scePthreadGetspecific(key) == &first); + Require(scePthreadKeyDelete(key) == SCE_OK); + Require(scePthreadGetspecific(key) == nullptr); + Require(scePthreadSetspecific(key, &second) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadKeyDelete(key) == SCE_KERNEL_ERROR_EINVAL); + + PthreadKey reused = -1; + Require(scePthreadKeyCreate(&reused, nullptr) == SCE_OK); + Require(reused == key); + Require(scePthreadGetspecific(reused) == nullptr); + Require(scePthreadSetspecific(reused, &second) == SCE_OK); + Require(scePthreadGetspecific(reused) == &second); + Require(scePthreadKeyDelete(reused) == SCE_OK); + + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, ReuseBeforeExit, nullptr, nullptr) == SCE_OK); + Require(scePthreadJoin(thread, nullptr) == SCE_OK); + Require(liveCalls == 1 && liveValue == &third); + Require(deletedCalls == 0 && reusedCalls == 0); + Require(scePthreadKeyDelete(sharedKey) == SCE_OK); + + Require(scePthreadKeyCreate(&sharedKey, nullptr) == SCE_OK); + const PthreadKey held = sharedKey; + Require(scePthreadCreate(&thread, nullptr, HoldAcrossReuse, nullptr, nullptr) == SCE_OK); + WaitFor(1); + Require(scePthreadKeyDelete(sharedKey) == SCE_OK); + Require(scePthreadKeyCreate(&sharedKey, nullptr) == SCE_OK); + Require(sharedKey == held); + stage = 2; + Require(scePthreadJoin(thread, nullptr) == SCE_OK); + Require(scePthreadGetspecific(sharedKey) == nullptr); +} diff --git a/core/libs/tests/GuestRazorCpu.cpp b/core/libs/tests/GuestRazorCpu.cpp index 2b966d8a6..0f1469a82 100644 --- a/core/libs/tests/GuestRazorCpu.cpp +++ b/core/libs/tests/GuestRazorCpu.cpp @@ -7,6 +7,8 @@ std::uint32_t APS5_VABI sceRazorCpuIsCapturing(void); int APS5_VABI sceRazorCpuJobManagerDispatch(const void* args); int APS5_VABI sceRazorCpuJobManagerJob(const void* args); int APS5_VABI sceRazorCpuJobManagerSequence(const void* args); +int APS5_VABI sceRazorCpuPushMarkerStatic(const char* name, std::uint32_t color, std::uint32_t flags); +int APS5_VABI sceRazorCpuPopMarker(void); } namespace { @@ -20,4 +22,10 @@ int main() { Require(sceRazorCpuJobManagerDispatch(nullptr) == 0); Require(sceRazorCpuJobManagerJob(nullptr) == 0); Require(sceRazorCpuJobManagerSequence(nullptr) == 0); + Require(sceRazorCpuPushMarkerStatic("outer", 0x80ffffffu, 2) == 0); + Require(sceRazorCpuPushMarkerStatic("inner", 0x80ffffffu, 2) == 0); + Require(sceRazorCpuPopMarker() == 0); + Require(sceRazorCpuPopMarker() == 0); + Require(sceRazorCpuPushMarkerStatic("unbalanced", 0x80ffffffu, 2) == 0); + Require(sceRazorCpuIsCapturing() == 0); } diff --git a/core/libs/tests/GuestRtc.cpp b/core/libs/tests/GuestRtc.cpp index 23d203f06..2d843ebfe 100644 --- a/core/libs/tests/GuestRtc.cpp +++ b/core/libs/tests/GuestRtc.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include extern "C" { int APS5_VABI sceRtcCheckValid(const RtcDateTime*); @@ -96,6 +98,22 @@ int main() { Require(sceRtcFormatRFC3339(text, &tick, 0) == 0 && std::strcmp(text, "2024-02-29T12:34:56.78Z") == 0); Require(sceRtcFormatRFC3339(text, &tick, 90) == 0 && std::strcmp(text, "2024-02-29T14:04:56.78+01:30") == 0); Require(sceRtcFormatRFC3339(text, &tick, -300) == 0 && std::strcmp(text, "2024-02-29T07:34:56.78-05:00") == 0); + Require(sceRtcFormatRFC3339(text, &tick, 1439) == 0 && std::strcmp(text, "2024-03-01T12:33:56.78+23:59") == 0); + Require(sceRtcFormatRFC3339(text, &tick, -1439) == 0 && std::strcmp(text, "2024-02-28T12:35:56.78-23:59") == 0); + for (int offset : {0, 1, -1, 59, -59, 60, -60, 90, -300, 1439, -1439}) { + RtcTick parsed{}; + Require(sceRtcFormatRFC3339(text, &tick, offset) == 0); + Require(sceRtcParseRFC3339(&parsed, text) == 0 && parsed.tick == leapDayTick - 9000); + } + for (int offset : {1440, -1440, 6000, -6000, std::numeric_limits::min(), std::numeric_limits::max()}) { + std::memset(text, 'x', sizeof(text)); + char original[sizeof(text)]; + std::memcpy(original, text, sizeof(text)); + Require(sceRtcFormatRFC3339(text, &tick, offset) == invalidValue); + Require(std::memcmp(text, original, sizeof(text)) == 0); + } + Require(sceRtcFormatRFC3339(nullptr, &tick, 1440) == invalidPointer); + Require(sceRtcFormatRFC3339(text, nullptr, 1440) == invalidPointer); Require(sceRtcParseRFC3339(&tick, "2024-02-29T14:04:56.789+01:30") == 0 && tick.tick == leapDayTick); Require(sceRtcParseRFC3339(&tick, "2024-02-29t12:34:56.789z") == 0 && tick.tick == leapDayTick); Require(sceRtcParseRFC3339(&tick, "1970-01-01T00:00:00Z") == 0 && tick.tick == unixEpochTick); @@ -168,6 +186,38 @@ int main() { Require(sceRtcTickAddTicks(&result, &source, 1) == invalidValue); Require(sceRtcTickAddTicks(nullptr, &source, 1) == invalidPointer); + const std::uint64_t invalidTicks[] = {maxTick + 1, std::numeric_limits::max()}; + for (std::uint64_t invalidTick : invalidTicks) { + source.tick = invalidTick; + result.tick = 123; + Require(sceRtcTickAddTicks(&result, &source, 0) == invalidValue && result.tick == 123); + Require(sceRtcTickAddTicks(&result, &source, 1) == invalidValue && result.tick == 123); + Require(sceRtcTickAddTicks(&result, &source, -1) == invalidValue && result.tick == 123); + Require(sceRtcTickAddSeconds(&result, &source, 0) == invalidValue && result.tick == 123); + Require(sceRtcTickAddDays(&result, &source, -1) == invalidValue && result.tick == 123); + Require(sceRtcTickAddMonths(&result, &source, 0) == invalidValue && result.tick == 123); + Require(sceRtcTickAddYears(&result, &source, -1) == invalidValue && result.tick == 123); + Require(sceRtcTickAddTicks(&source, &source, 0) == invalidValue && source.tick == invalidTick); + Require(sceRtcTickAddMonths(&source, &source, -1) == invalidValue && source.tick == invalidTick); + Require(sceRtcTickAddTicks(nullptr, &source, 0) == invalidPointer); + Require(sceRtcTickAddMonths(nullptr, &source, 0) == invalidPointer); + } + + source.tick = maxTick; + Require(sceRtcTickAddTicks(&source, &source, 0) == 0 && source.tick == maxTick); + Require(sceRtcTickAddTicks(&source, &source, -1) == 0 && source.tick == maxTick - 1); + source.tick = maxTick; + Require(sceRtcTickAddMonths(&source, &source, 0) == 0 && source.tick == maxTick); + Require(sceRtcTickAddYears(&result, &source, -1) == 0); + Require(sceRtcSetTick(&converted, &result) == 0 && Equal(converted, RtcDateTime{9998, 12, 31, 23, 59, 59, 999999})); + result.tick = 123; + Require(sceRtcTickAddMonths(&result, &source, 1) == invalidValue && result.tick == 123); + Require(sceRtcTickAddTicks(&result, &source, std::numeric_limits::min()) == invalidValue && result.tick == 123); + Require(sceRtcTickAddSeconds(&result, &source, std::numeric_limits::max()) == invalidValue && result.tick == 123); + source.tick = 0; + Require(sceRtcTickAddMonths(&source, &source, 0) == 0 && source.tick == 0); + Require(sceRtcTickAddMonths(&result, &source, -1) == invalidValue && result.tick == 123); + RtcTick now{}; Require(sceRtcGetCurrentTick(&now) == 0 && now.tick > leapDayTick); RtcTick local{}; diff --git a/core/libs/tests/GuestSaveDataPs4.cpp b/core/libs/tests/GuestSaveDataPs4.cpp new file mode 100644 index 000000000..39e7b14d1 --- /dev/null +++ b/core/libs/tests/GuestSaveDataPs4.cpp @@ -0,0 +1,93 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include + +extern "C" int APS5_VABI sceSaveDataTransferringMountPs4(const SaveDataTransferringMount*, SaveDataMountResult*); +extern "C" int APS5_VABI sceSaveDataDirNameSearchPs4(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*); +extern "C" int APS5_VABI sceSaveDataDirNameSearch(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*); + +namespace { + +constexpr int SaveDataErrorNotFound = -2137063416; + +int failures = 0; + +void Check(bool condition, const std::string& what) { + if (condition) return; + std::fprintf(stderr, "savedata ps4 check failed: %s\n", what.c_str()); + ++failures; +} + +template +bool ThrowsRuntimeError(TFunction function) { + try { + function(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +void TestTransferringMountPs4() { + SceSaveDataTitleId title{}; + std::strcpy(title.data, "CUSA00001"); + SceSaveDataDirName dir{}; + std::strcpy(dir.data, "kept"); + SaveDataTransferringMount mount{}; + mount.user_id = 1; + mount.title_id = &title; + mount.dir_name = &dir; + SaveDataMountResult result{}; + std::memset(&result, 0xAA, sizeof(result)); + Check(sceSaveDataTransferringMountPs4(&mount, &result) == SaveDataErrorNotFound, "TransferringMountPs4 returns NOT_FOUND"); + Check(result.mount_point.data[0] == '\0', "TransferringMountPs4 reports no mount point"); +} + +void TestDirNameSearchPs4() { + SceSaveDataDirName names[4]{}; + std::memset(names, 0x5A, sizeof(names)); + SaveDataDirNameSearchCond cond{}; + cond.user_id = 1; + SaveDataDirNameSearchResult result{}; + result.hit_num = 7; + result.set_num = 7; + result.dir_names = names; + result.dir_names_num = 4; + + Check(sceSaveDataDirNameSearch(&cond, &result) == 0 && result.hit_num == 1 && result.set_num == 1, "the PS5 search finds the PS5 save"); + + std::memset(names, 0x5A, sizeof(names)); + result.hit_num = 7; + result.set_num = 7; + Check(sceSaveDataDirNameSearchPs4(&cond, &result) == 0, "DirNameSearchPs4 succeeds"); + Check(result.hit_num == 0, "DirNameSearchPs4 writes zero hits"); + Check(result.set_num == 0, "DirNameSearchPs4 writes zero set entries"); + Check(static_cast(names[0].data[0]) == 0x5A, "DirNameSearchPs4 leaves the name buffer untouched"); + + Check(ThrowsRuntimeError([&] { sceSaveDataDirNameSearchPs4(nullptr, &result); }), "null cond throws"); + Check(ThrowsRuntimeError([&] { sceSaveDataDirNameSearchPs4(&cond, nullptr); }), "null result throws"); +} + +} + +int main() { + const auto root = std::filesystem::temp_directory_path() / ("anyps5-savedata-ps4-" + std::to_string(std::random_device{}())); + std::filesystem::create_directories(root / "_sd" / "kept"); + const auto previous = std::filesystem::current_path(); + std::filesystem::current_path(root); + + TestTransferringMountPs4(); + TestDirNameSearchPs4(); + + std::filesystem::current_path(previous); + std::error_code error; + std::filesystem::remove_all(root, error); + if (failures != 0) return 1; + std::printf("savedata ps4 tests passed\n"); + return 0; +} diff --git a/core/libs/tests/GuestShare.cpp b/core/libs/tests/GuestShare.cpp new file mode 100644 index 000000000..353b80bb4 --- /dev/null +++ b/core/libs/tests/GuestShare.cpp @@ -0,0 +1,30 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI sceShareCaptureScreenshotExtended(const void* extended_param, std::int32_t* req_id); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +} + +int main() { + constexpr std::int32_t notSupported = static_cast(0x81960007); + std::uint8_t param[64]{}; + + std::int32_t reqId = 7; + Require(sceShareCaptureScreenshotExtended(param, &reqId) == notSupported); + Require(reqId == -1); + + reqId = 7; + Require(sceShareCaptureScreenshotExtended(nullptr, &reqId) == notSupported); + Require(reqId == -1); + + Require(sceShareCaptureScreenshotExtended(param, nullptr) == notSupported); + Require(sceShareCaptureScreenshotExtended(nullptr, nullptr) == notSupported); + return 0; +} diff --git a/core/libs/tests/GuestSyncOnAddress.cpp b/core/libs/tests/GuestSyncOnAddress.cpp index 363c35d9a..e5954051f 100644 --- a/core/libs/tests/GuestSyncOnAddress.cpp +++ b/core/libs/tests/GuestSyncOnAddress.cpp @@ -10,6 +10,8 @@ extern "C" { int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name); +int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, const KernelUseconds* timeout, const char* name); int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count); } @@ -130,14 +132,87 @@ static void WaitsOnTheLowHalfOfAnOnceState() { waiter.join(); } +static void SizedWaitsTimeOutWhileTheValueMatches() { + std::uint32_t word = 5; + std::uint64_t value = 0x500000005ULL; + const KernelUseconds none = 0; + Require(sceKernelSyncOnAddressWait32(&word, 4, &none, "differs") == SCE_OK); + Require(sceKernelSyncOnAddressWait32(&word, 5, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + + const KernelUseconds timeout = 20000; + auto start = std::chrono::steady_clock::now(); + Require(sceKernelSyncOnAddressWait32(&word, 5, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15)); + start = std::chrono::steady_clock::now(); + Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15)); +} + +static void Wait64ComparesTheHighHalf() { + std::uint64_t value = 0x100000001ULL; + const KernelUseconds none = 0; + Require(sceKernelSyncOnAddressWait64(&value, 0x1ULL, &none, "high") == SCE_OK); + Require(sceKernelSyncOnAddressWait64(&value, 0x200000001ULL, &none, "high") == SCE_OK); + Require(sceKernelSyncOnAddressWait64(&value, 0x100000001ULL, &none, "high") == SCE_KERNEL_ERROR_ETIMEDOUT); +} + +static void WakeReleasesWaitersOfEverySize() { + std::uint64_t value = 1; + auto* word = reinterpret_cast(&value); + std::atomic started{0}; + std::atomic finished{0}; + std::vector results(3, -1); + + std::vector waiters; + waiters.emplace_back([&] { + ++started; + results[0] = sceKernelSyncOnAddressWait(word, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + waiters.emplace_back([&] { + ++started; + results[1] = sceKernelSyncOnAddressWait32(word, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + waiters.emplace_back([&] { + ++started; + results[2] = sceKernelSyncOnAddressWait64(&value, 1, &FAILSAFE_TIMEOUT, "sized"); + ++finished; + }); + AwaitCount(started, 3); + std::this_thread::sleep_for(SETTLE); + + Require(sceKernelSyncOnAddressWake(&value, 1) == SCE_OK); + AwaitCount(finished, 1); + std::this_thread::sleep_for(SETTLE); + Require(finished.load() == 1); + + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (finished.load() != 3) { + Require(std::chrono::steady_clock::now() < deadline); + Require(sceKernelSyncOnAddressWake(&value, INT_MAX) == SCE_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + for (auto& waiter : waiters) waiter.join(); + for (const int result : results) Require(result == SCE_OK); +} + static void RejectsInvalidArguments() { std::uint32_t words[2] = {1, 1}; auto* misaligned = reinterpret_cast(reinterpret_cast(words) + 1); Require(Rejects([&] { sceKernelSyncOnAddressWait(nullptr, 1, nullptr, nullptr); })); Require(Rejects([&] { sceKernelSyncOnAddressWait(misaligned, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait32(nullptr, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait32(misaligned, 1, nullptr, nullptr); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(nullptr, 1); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(misaligned, 1); })); Require(Rejects([&] { sceKernelSyncOnAddressWake(words, -1); })); + + std::uint64_t values[2] = {1, 1}; + auto* wordAligned = reinterpret_cast(reinterpret_cast(values) + sizeof(std::uint32_t)); + Require(Rejects([&] { sceKernelSyncOnAddressWait64(nullptr, 1, nullptr, nullptr); })); + Require(Rejects([&] { sceKernelSyncOnAddressWait64(wordAligned, 1, nullptr, nullptr); })); } int main() { @@ -146,5 +221,8 @@ int main() { WakeReleasesAWaiterWithoutAValueChange(); WakeHonoursTheCountAndTheAddress(); WaitsOnTheLowHalfOfAnOnceState(); + SizedWaitsTimeOutWhileTheValueMatches(); + Wait64ComparesTheHighHalf(); + WakeReleasesWaitersOfEverySize(); RejectsInvalidArguments(); } diff --git a/core/libs/tests/GuestSystemService.cpp b/core/libs/tests/GuestSystemService.cpp index 88193689e..fe937fc57 100644 --- a/core/libs/tests/GuestSystemService.cpp +++ b/core/libs/tests/GuestSystemService.cpp @@ -1,13 +1,26 @@ #include "SceTypes.hpp" #include "prx/libSceSystemService/SystemService.hpp" +#include #include +#include +#include extern "C" int APS5_VABI sceSystemServiceGetHdrToneMapLuminance(SystemServiceHdrToneMapLuminance* luminance); +extern "C" int APS5_VABI sceSystemServiceParamGetString(int paramId, char* buf, std::size_t bufSize); namespace { void Require(bool value) { if (!value) std::abort(); } +bool ParamGetStringThrows(int paramId, char* buf, std::size_t bufSize) { + try { + static_cast(sceSystemServiceParamGetString(paramId, buf, bufSize)); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + } int main() { @@ -17,4 +30,14 @@ int main() { Require(luminance.max_full_frame_tone_map_luminance == 100.0f); Require(luminance.max_tone_map_luminance == 100.0f); Require(luminance.min_tone_map_luminance == 0.0f); + + char name[SYSTEM_SERVICE_MAX_SYSTEM_NAME_LENGTH]; + std::memset(name, 'x', sizeof(name)); + Require(sceSystemServiceParamGetString(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, nullptr, sizeof(name)) == SYSTEM_SERVICE_ERROR_PARAMETER); + Require(sceSystemServiceParamGetString(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, name, 0) == SYSTEM_SERVICE_ERROR_PARAMETER); + Require(ParamGetStringThrows(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, name, sizeof(name) - 1)); + Require(ParamGetStringThrows(SYSTEM_SERVICE_PARAM_ID_LANG, name, sizeof(name))); + Require(name[0] == 'x'); + Require(sceSystemServiceParamGetString(SYSTEM_SERVICE_PARAM_ID_SYSTEM_NAME, name, sizeof(name)) == SYSTEM_SERVICE_OK); + Require(std::strcmp(name, "PS5") == 0); } diff --git a/core/libs/tests/GuestThreadAtexit.cpp b/core/libs/tests/GuestThreadAtexit.cpp new file mode 100644 index 000000000..f37fd5d52 --- /dev/null +++ b/core/libs/tests/GuestThreadAtexit.cpp @@ -0,0 +1,87 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include + +extern "C" { +int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); +int APS5_VABI scePthreadJoin(Pthread thread, void** retval); +Pthread APS5_VABI scePthreadSelf(); +int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(void (APS5_VABI* destructor)(void*), void* object, void* dsoSymbol); +void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix(); +} + +namespace { + +using Destructor = void (APS5_VABI*)(void*); + +void Require(bool value) { if (!value) std::abort(); } + +int dsoHandle = 0; +std::vector calls; +std::vector callers; + +void* Object(std::intptr_t value) { return reinterpret_cast(value); } + +int Register(Destructor destructor, std::intptr_t object) { + return _sceLibcInternalThreadAtexit_nid_postfix(destructor, Object(object), &dsoHandle); +} + +void APS5_VABI Record(void* object) { + calls.push_back(reinterpret_cast(object)); + callers.push_back(scePthreadSelf()); +} + +void APS5_VABI RecordAndRegister(void* object) { + Record(object); + Require(Register(Record, 9) == 0); +} + +void* APS5_VABI Worker(void*) { + Require(Register(Record, 4) == 0); + Require(Register(Record, 5) == 0); + return nullptr; +} + +bool DestructorsThrow() { + try { + _sceLibcInternalThreadDtors_nid_postfix(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +} + +int main() { + const Pthread mainThread = scePthreadSelf(); + Require(Register(Record, 1) == 0); + Require(Register(RecordAndRegister, 2) == 0); + Require(Register(Record, 3) == 0); + _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls == std::vector{3, 2, 9, 1}); + Require(callers == std::vector(4, mainThread)); + _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls.size() == 4); + + calls.clear(); + callers.clear(); + Pthread worker = nullptr; + Require(scePthreadCreate(&worker, nullptr, Worker, nullptr, "ThreadAtexit") == 0); + Require(scePthreadJoin(worker, nullptr) == 0); + Require(calls == std::vector{5, 4}); + Require(callers == std::vector(2, worker)); + + calls.clear(); + std::vector heap(64); + Require(Register(Record, 6) == 0); + Require(Register(nullptr, 7) == 0); + Require(Register(reinterpret_cast(heap.data()), 8) == 0); + Require(DestructorsThrow()); + Require(DestructorsThrow()); + Require(calls.empty()); + _sceLibcInternalThreadDtors_nid_postfix(); + Require(calls == std::vector{6}); +} diff --git a/core/libs/tests/GuestUlobjmgr.cpp b/core/libs/tests/GuestUlobjmgr.cpp new file mode 100644 index 000000000..0fd79500f --- /dev/null +++ b/core/libs/tests/GuestUlobjmgr.cpp @@ -0,0 +1,32 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" { +int APS5_VABI _sceUlobjmgrRegisterObject(std::uint64_t object, std::int32_t kind, std::uint32_t* id); +int APS5_VABI _sceUlobjmgrUnregisterObject(std::uint32_t id); +} + +namespace { + +constexpr int einval = 22; + +void Require(bool value) { if (!value) std::abort(); } + +} + +int main() { + std::uint32_t id = 0xffffffffu; + Require(_sceUlobjmgrRegisterObject(0, 1, &id) == einval); + Require(id == 0xffffffffu); + Require(_sceUlobjmgrRegisterObject(0x1000, 0, &id) == einval); + Require(id == 0xffffffffu); + Require(_sceUlobjmgrRegisterObject(0x1000, 1, nullptr) == einval); + Require(_sceUlobjmgrRegisterObject(0x1000, -1, &id) == 0); + Require(id == 0); + + Require(_sceUlobjmgrUnregisterObject(id) == 0); + Require(_sceUlobjmgrUnregisterObject(0x3fff) == 0); + Require(_sceUlobjmgrUnregisterObject(0x4000) == einval); + Require(_sceUlobjmgrUnregisterObject(0xffffffffu) == einval); +} diff --git a/core/libs/tests/GuestUltQueue.cpp b/core/libs/tests/GuestUltQueue.cpp new file mode 100644 index 000000000..1ab7d5e97 --- /dev/null +++ b/core/libs/tests/GuestUltQueue.cpp @@ -0,0 +1,255 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceUltInitialize(); +int APS5_VABI sceUltFinalize(); +std::uint64_t APS5_VABI sceUltQueueDataResourcePoolGetWorkAreaSize(std::uint32_t, std::uint64_t, std::uint32_t); +int APS5_VABI sceUltQueueDataResourcePoolCreate(void*, const char*, std::uint32_t, std::uint64_t, std::uint32_t, void*, void*, const void*, std::uint32_t); +int APS5_VABI sceUltQueueDataResourcePoolDestroy(void*); +int APS5_VABI sceUltQueueCreate(void*, const char*, std::uint64_t, void*, void*, const void*, std::uint32_t); +int APS5_VABI sceUltQueueDestroy(void*); +int APS5_VABI sceUltQueuePush(void*, const void*); +int APS5_VABI sceUltQueueTryPush(void*, const void*); +int APS5_VABI sceUltQueuePop(void*, void*); +int APS5_VABI sceUltQueueTryPop(void*, void*); +} + +static constexpr int Null = -2139029503; +static constexpr int Alignment = -2139029502; +static constexpr int Range = -2139029501; +static constexpr int Invalid = -2139029500; +static constexpr int State = -2139029498; +static constexpr int Busy = -2139029497; +static constexpr int Again = -2139029496; + +static void Require(bool value, int line) { + if (!value) { + std::fprintf(stderr, "ULT queue check failed at line %d\n", line); + std::abort(); + } +} + +#define CHECK(value) Require((value), __LINE__) + +struct alignas(8) Object { std::array bytes{}; }; + +struct Fixture { + Object pool; + Object first; + Object second; + + void Create(std::uint32_t slots, std::uint32_t queues = 1, std::uint64_t size = sizeof(std::uint64_t)) { + CHECK(sceUltQueueDataResourcePoolCreate(&pool, "pool", slots, size, queues, nullptr, nullptr, nullptr, 0) == 0); + CHECK(sceUltQueueCreate(&first, "first", size, nullptr, &pool, nullptr, 0) == 0); + if (queues > 1) CHECK(sceUltQueueCreate(&second, "second", size, nullptr, &pool, nullptr, 0) == 0); + } + + void Destroy(bool secondQueue = false) { + CHECK(sceUltQueueDestroy(&first) == 0); + if (secondQueue) CHECK(sceUltQueueDestroy(&second) == 0); + CHECK(sceUltQueueDataResourcePoolDestroy(&pool) == 0); + } +}; + +static void Validation() { + Fixture fixture; + Object extra; + std::uint64_t value = 99; + CHECK(sceUltQueueDataResourcePoolCreate(nullptr, nullptr, 1, 8, 1, nullptr, nullptr, nullptr, 0) == Null); + CHECK(sceUltQueueDataResourcePoolCreate(fixture.pool.bytes.data() + 1, nullptr, 1, 8, 1, nullptr, nullptr, nullptr, 0) == Alignment); + CHECK(sceUltQueueDataResourcePoolCreate(&fixture.pool, nullptr, 0, 8, 1, nullptr, nullptr, nullptr, 0) == Range); + CHECK(sceUltQueueDataResourcePoolCreate(&fixture.pool, nullptr, 1, 0, 1, nullptr, nullptr, nullptr, 0) == Range); + CHECK(sceUltQueueDataResourcePoolCreate(&fixture.pool, nullptr, 1, 8, 0, nullptr, nullptr, nullptr, 0) == Range); + CHECK(sceUltQueueDataResourcePoolCreate(&fixture.pool, nullptr, 2, UINT64_MAX, 1, nullptr, nullptr, nullptr, 0) == Range); + CHECK(sceUltQueueDataResourcePoolCreate(&fixture.pool, nullptr, 1, 8, 1, &extra, nullptr, nullptr, 0) == Invalid); + CHECK(sceUltQueueCreate(&fixture.first, nullptr, 8, nullptr, &fixture.pool, nullptr, 0) == Invalid); + fixture.Create(2); + CHECK(sceUltQueuePush(&fixture.first, &value) == 0); + CHECK(sceUltQueueDataResourcePoolCreate(&fixture.pool, nullptr, 1, 8, 1, nullptr, nullptr, nullptr, 0) == State); + CHECK(sceUltQueueCreate(&fixture.first, nullptr, 8, nullptr, &fixture.pool, nullptr, 0) == State); + std::uint64_t saved = 0; + CHECK(sceUltQueueTryPop(&fixture.first, &saved) == 0 && saved == value); + CHECK(sceUltQueueCreate(&extra, nullptr, 9, nullptr, &fixture.pool, nullptr, 0) == Invalid); + CHECK(sceUltQueueCreate(&extra, nullptr, 8, &extra, &fixture.pool, nullptr, 0) == Invalid); + CHECK(sceUltQueueCreate(&extra, nullptr, 8, nullptr, &fixture.pool, nullptr, 0) == Again); + CHECK(sceUltQueueCreate(nullptr, nullptr, 8, nullptr, &fixture.pool, nullptr, 0) == Null); + CHECK(sceUltQueueCreate(extra.bytes.data() + 1, nullptr, 8, nullptr, &fixture.pool, nullptr, 0) == Alignment); + CHECK(sceUltQueueCreate(&extra, nullptr, 0, nullptr, &fixture.pool, nullptr, 0) == Range); + CHECK(sceUltQueueTryPop(&fixture.first, &value) == Again && value == 99); + CHECK(sceUltQueueTryPush(&fixture.first, nullptr) == Null); + CHECK(sceUltQueuePop(&fixture.first, nullptr) == Null); + CHECK(sceUltQueuePush(nullptr, &value) == Null); + CHECK(sceUltQueuePop(&extra, &value) == State); + CHECK(sceUltQueueDataResourcePoolDestroy(&fixture.pool) == Busy); + CHECK(sceUltQueueDataResourcePoolDestroy(nullptr) == Null); + CHECK(sceUltQueueDestroy(nullptr) == Null); + CHECK(sceUltQueueDataResourcePoolGetWorkAreaSize(3, 9, 2) == 1072); + bool overflow = false; + try { sceUltQueueDataResourcePoolGetWorkAreaSize(1, UINT64_MAX, 1); } + catch (const std::out_of_range&) { overflow = true; } + CHECK(overflow); + bool rejected = false; + try { sceUltQueueCreate(&extra, nullptr, 8, nullptr, &fixture.pool, &extra, 0); } + catch (const std::runtime_error&) { rejected = true; } + CHECK(rejected); + rejected = false; + try { sceUltQueueDataResourcePoolCreate(&extra, nullptr, 1, 8, 1, nullptr, nullptr, &extra, 0); } + catch (const std::runtime_error&) { rejected = true; } + CHECK(rejected); + overflow = false; + try { sceUltQueueDataResourcePoolGetWorkAreaSize(UINT32_MAX, UINT64_MAX / 2, 1); } + catch (const std::out_of_range&) { overflow = true; } + CHECK(overflow); + fixture.Destroy(); + CHECK(sceUltQueueTryPush(&fixture.first, &value) == State); + CHECK(sceUltQueueDestroy(&fixture.first) == State); + CHECK(sceUltQueueDataResourcePoolDestroy(&fixture.pool) == State); + fixture.Create(1); + fixture.Destroy(); +} + +static void Fifo() { + Fixture fixture; + fixture.Create(3, 1, 13); + std::array, 4> items{}; + for (std::size_t index = 0; index < items.size(); ++index) { + for (std::size_t byte = 0; byte < items[index].size(); ++byte) items[index][byte] = index * 31 + byte; + } + for (int round = 0; round < 100; ++round) { + for (int index = 0; index < 3; ++index) CHECK(sceUltQueueTryPush(&fixture.first, items[index].data()) == 0); + CHECK(sceUltQueueTryPush(&fixture.first, items[3].data()) == Again); + std::array output; + for (int index = 0; index < 3; ++index) { + output.fill(0xAA); + CHECK(sceUltQueuePop(&fixture.first, output.data() + 1) == 0); + CHECK(output.front() == 0xAA && output.back() == 0xAA); + CHECK(std::memcmp(output.data() + 1, items[index].data(), 13) == 0); + } + CHECK(sceUltQueueTryPop(&fixture.first, output.data()) == Again); + } + fixture.Destroy(); +} + +static void WaitUntilBlocked(std::future& result) { + CHECK(result.wait_for(std::chrono::milliseconds(100)) == std::future_status::timeout); +} + +static void Blocking() { + Fixture fixture; + fixture.Create(1); + std::uint64_t first = 11, second = 22, output = 0; + CHECK(sceUltQueuePush(&fixture.first, &first) == 0); + auto producer = std::async(std::launch::async, [&] { return sceUltQueuePush(&fixture.first, &second); }); + WaitUntilBlocked(producer); + CHECK(sceUltQueueDestroy(&fixture.first) == Busy); + CHECK(sceUltQueuePop(&fixture.first, &output) == 0 && output == first); + CHECK(producer.get() == 0); + CHECK(sceUltQueuePop(&fixture.first, &output) == 0 && output == second); + auto consumer = std::async(std::launch::async, [&] { return sceUltQueuePop(&fixture.first, &output); }); + WaitUntilBlocked(consumer); + CHECK(sceUltQueueDestroy(&fixture.first) == Busy); + CHECK(sceUltQueuePush(&fixture.first, &first) == 0); + CHECK(consumer.get() == 0 && output == first); + fixture.Destroy(); +} + +static void SharedPool() { + Fixture fixture; + fixture.Create(1, 2); + std::uint64_t first = 111, second = 222, output = 0; + auto consumer = std::async(std::launch::async, [&] { return sceUltQueuePop(&fixture.second, &output); }); + WaitUntilBlocked(consumer); + CHECK(sceUltQueuePush(&fixture.first, &first) == 0); + WaitUntilBlocked(consumer); + CHECK(sceUltQueueTryPush(&fixture.second, &second) == Again); + CHECK(sceUltQueueTryPop(&fixture.second, &output) == Again); + auto producer = std::async(std::launch::async, [&] { return sceUltQueuePush(&fixture.second, &second); }); + WaitUntilBlocked(producer); + CHECK(sceUltQueueDestroy(&fixture.second) == Busy); + CHECK(sceUltQueueDestroy(&fixture.first) == 0); + CHECK(producer.get() == 0); + CHECK(consumer.get() == 0 && output == second); + CHECK(sceUltQueueCreate(&fixture.first, "replacement", 4, nullptr, &fixture.pool, nullptr, 0) == 0); + const std::uint32_t small = 42; + CHECK(sceUltQueuePush(&fixture.first, &small) == 0); + std::uint32_t smallOutput = 0; + CHECK(sceUltQueuePop(&fixture.first, &smallOutput) == 0 && smallOutput == small); + fixture.Destroy(true); +} + +static void Finalize() { + Fixture fixture; + fixture.Create(1, 2); + std::uint64_t value = 7, output = 123; + CHECK(sceUltQueuePush(&fixture.first, &value) == 0); + auto producer = std::async(std::launch::async, [&] { return sceUltQueuePush(&fixture.first, &value); }); + auto consumer = std::async(std::launch::async, [&] { return sceUltQueuePop(&fixture.second, &output); }); + WaitUntilBlocked(producer); + WaitUntilBlocked(consumer); + CHECK(sceUltQueueDestroy(&fixture.first) == Busy); + CHECK(sceUltQueueDestroy(&fixture.second) == Busy); + CHECK(sceUltFinalize() == 0); + CHECK(producer.get() == State); + CHECK(consumer.get() == State && output == 123); + CHECK(sceUltQueueTryPop(&fixture.first, &output) == State); + CHECK(sceUltInitialize() == 0); + fixture.Create(1, 2); + CHECK(sceUltQueueTryPop(&fixture.first, &output) == Again); + CHECK(sceUltQueuePush(&fixture.second, &value) == 0); + CHECK(sceUltQueuePop(&fixture.second, &output) == 0 && output == value); + fixture.Destroy(true); +} + +static void Stress() { + Fixture fixture; + fixture.Create(7); + constexpr std::size_t workers = 4; + constexpr std::size_t perWorker = 2000; + std::array, workers * perWorker> seen{}; + std::vector producers, consumers; + for (std::size_t worker = 0; worker < workers; ++worker) { + consumers.emplace_back([&] { + for (std::size_t index = 0; index < perWorker; ++index) { + std::uint64_t value = UINT64_MAX; + CHECK(sceUltQueuePop(&fixture.first, &value) == 0); + CHECK(value < seen.size()); + ++seen[value]; + } + }); + producers.emplace_back([&, worker] { + for (std::size_t index = 0; index < perWorker; ++index) { + const std::uint64_t value = worker * perWorker + index; + CHECK(sceUltQueuePush(&fixture.first, &value) == 0); + } + }); + } + for (auto& producer : producers) producer.join(); + for (auto& consumer : consumers) consumer.join(); + for (const auto& count : seen) CHECK(count == 1); + std::uint64_t output = 0; + CHECK(sceUltQueueTryPop(&fixture.first, &output) == Again); + fixture.Destroy(); +} + +int main(int argc, char** argv) { + CHECK(argc == 2); + CHECK(sceUltInitialize() == 0); + if (std::strcmp(argv[1], "validation") == 0) Validation(); + else if (std::strcmp(argv[1], "fifo") == 0) Fifo(); + else if (std::strcmp(argv[1], "blocking") == 0) Blocking(); + else if (std::strcmp(argv[1], "shared-pool") == 0) SharedPool(); + else if (std::strcmp(argv[1], "finalize") == 0) Finalize(); + else if (std::strcmp(argv[1], "stress") == 0) Stress(); + else CHECK(false); + CHECK(sceUltFinalize() == 0); +} diff --git a/core/libs/tests/GuestWideFormatting.cpp b/core/libs/tests/GuestWideFormatting.cpp index 2aea154dd..343ef0e51 100644 --- a/core/libs/tests/GuestWideFormatting.cpp +++ b/core/libs/tests/GuestWideFormatting.cpp @@ -1,11 +1,13 @@ #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include #include #include #include extern "C" int APS5_VABI vswprintf_nid_postfix(char16_t*, std::size_t, const char16_t*, VaList*); +extern "C" int APS5_VABI snwprintf_s_nid_postfix(char16_t*, std::size_t, const char16_t*, ...); static int APS5_VABI Format(char16_t* buffer, std::size_t size, const char16_t* format, ...) { #ifdef _WIN32 @@ -34,7 +36,60 @@ static void Check(const char16_t* format, const char* input, const char16_t* exp } } +static void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "snwprintf_s: %s\n", message); + std::abort(); + } +} + +static void CheckBounded() { + constexpr std::size_t rsizeMax = SIZE_MAX >> 1; + const char16_t* const nullWide = nullptr; + const char* const nullNarrow = nullptr; + char16_t buffer[16]; + + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 16, u"%d-%ls-%s", 42, u"ab", "\xc3\xa9") == 7, "complete output length"); + Require(buffer == std::u16string(u"42-ab-\u00e9"), "complete output"); + + buffer[5] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 5, u"%d-%ls", 42, u"abcd") == 7, "truncated output length"); + Require(buffer == std::u16string(u"42-a") && buffer[5] == u'x', "truncated output"); + + buffer[0] = u'x'; + buffer[1] = u'y'; + Require(snwprintf_s_nid_postfix(buffer, 1, u"%x", 0xabcu) == 3, "one unit buffer length"); + Require(buffer[0] == 0 && buffer[1] == u'y', "one unit buffer"); + + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 16, u"") == 0 && buffer[0] == 0, "empty format"); + + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 16, nullptr) < 0 && buffer[0] == 0, "null format"); + Require(snwprintf_s_nid_postfix(nullptr, 16, u"a") < 0, "null buffer"); + + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 0, u"a") < 0 && buffer[0] == u'x', "zero size"); + + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, rsizeMax + 1, u"a") < 0 && buffer[0] == u'x', "size above RSIZE_MAX"); + + int written = -7; + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 16, u"ab%n", &written) < 0 && buffer[0] == 0 && written == -7, "%n"); + + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 16, u"a%ls", nullWide) < 0 && buffer[0] == 0, "null %ls argument"); + + buffer[0] = u'x'; + Require(snwprintf_s_nid_postfix(buffer, 16, u"a%s", nullNarrow) < 0 && buffer[0] == 0, "null %s argument"); + + Require(Format(buffer, 16, u"%s", nullNarrow) == 6 && buffer == std::u16string(u"(null)"), "vswprintf null %s argument"); +} + int main() { + CheckBounded(); Check(u"%.2s", "\xc3\xa9\xc3\xa8", u"\u00e9\u00e8"); Check(u"%.1s", "\xc3\xa9\xc3\xa8", u"\u00e9"); Check(u"%.0s", "\xc3\xa9", u""); diff --git a/core/libs/tests/ParamJsonParser.cpp b/core/libs/tests/ParamJsonParser.cpp new file mode 100644 index 000000000..fc3c06cb0 --- /dev/null +++ b/core/libs/tests/ParamJsonParser.cpp @@ -0,0 +1,56 @@ +#include "prx/libkernel/AppMetadata/include/ParamJsonParser.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +static ParsedParamJson ParseSize(const std::filesystem::path& path, const std::string& size) { + { + std::ofstream file(path); + file << R"({"titleId":"PPSA00000","localizedParameters":{"en-US":{"titleName":"Example"}})"; + if (!size.empty()) file << ",\"downloadDataSize\":" << size; + file << '}'; + Require(static_cast(file), "Cannot write param.json fixture"); + } + return parseParamJson(path); +} + +int main() { + const auto path = std::filesystem::temp_directory_path() / ("anyps5-param-json-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".json"); + int result = 0; + try { + const std::pair sizes[] = { + {"", 0}, {"0", 0}, {"-0", 0}, {"4096", 4096}, + {"9007199254740993", 9007199254740993ull}, + {"18446744073709551614", std::numeric_limits::max() - 1}, + {"18446744073709551615", std::numeric_limits::max()}, + {"1e3", 1000}, {"1.25", 1} + }; + for (const auto& [text, expected] : sizes) { + const auto parsed = ParseSize(path, text); + Require(parsed.downloadDataSizeMiB == expected, std::string("Incorrect download size: ") + text); + Require(parsed.titleId == "PPSA00000" && parsed.title == "Example", "Title metadata changed"); + } + for (const auto* text : {"18446744073709551616", "18446744073709551617", "1.8446744073709552e19", + "1e40", "-1", "-0.5", "true", "null", "\"4096\""}) { + bool rejected = false; + try { ParseSize(path, text); } catch (const std::exception&) { rejected = true; } + Require(rejected, std::string("Accepted invalid download size: ") + text); + } + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + result = 1; + } + std::filesystem::remove(path); + return result; +} diff --git a/core/libs/tests/RayTracingMiss.cpp b/core/libs/tests/RayTracingMiss.cpp index 803f62a95..5ca726847 100644 --- a/core/libs/tests/RayTracingMiss.cpp +++ b/core/libs/tests/RayTracingMiss.cpp @@ -6,12 +6,20 @@ using namespace ShaderRecompiler; static void Require(bool value) { if (!value) throw std::runtime_error("ray tracing miss regression"); } -int main() { - const std::array code{0xF1981F01u, 0x00000000u}; +struct Form { + std::uint32_t word0; + std::uint32_t word1; + RdnaOpcode opcode; + std::uint32_t addressDwords; +}; +static void Check(const Form& form) { + const std::array code{form.word0, form.word1}; const RdnaInstruction instruction = DecodeRdnaMimg(0u, code, 0u); - Require(instruction.op == RdnaOpcode::ImageBvhIntersectRay); + Require(instruction.op == form.opcode); Require(instruction.family == RdnaInstructionFamily::MIMG); Require(IsImageOpcode(instruction.op)); + Require(instruction.imageAddressComponents == form.addressDwords); + Require(GetRdnaImageAddressDwordCount(instruction.imageSampleFlags, instruction.imageAddressComponents) == form.addressDwords); IrProgram program; auto& block = program.CreateBlock(); program.SetEntryBlock(block); @@ -26,8 +34,14 @@ int main() { Require(thrown); } else { context.TranslateInstruction(instruction); - if (RayTracingMiss()) { - for (auto* value : block.Instructions()) Require(value->Opcode() != IrOpcode::ImageBvhIntersectRay); - } + bool emitted = false; + for (auto* value : block.Instructions()) emitted = emitted || value->Opcode() == IrOpcode::ImageBvhIntersectRay; + Require(emitted != RayTracingMiss()); } } +int main() { + Check({0xF1981F01u, 0x00000000u, RdnaOpcode::ImageBvhIntersectRay, 11u}); + Check({0xF1981F01u, 0x40000000u, RdnaOpcode::ImageBvhIntersectRay, 8u}); + Check({0xF19C1F01u, 0x00000000u, RdnaOpcode::ImageBvh64IntersectRay, 12u}); + Check({0xF19C1F01u, 0x40000000u, RdnaOpcode::ImageBvh64IntersectRay, 9u}); +} diff --git a/core/libs/tests/Wave32WideSubgroup.cpp b/core/libs/tests/Wave32WideSubgroup.cpp new file mode 100644 index 000000000..af28fb59d --- /dev/null +++ b/core/libs/tests/Wave32WideSubgroup.cpp @@ -0,0 +1,201 @@ +#include "SpirvBackend/SpirvEmitter.hpp" +#include "SpirvBackend/SpirvEmitterInstructions.hpp" +#include "SpirvBackend/SpirvMemory/SpirvSubgroup.hpp" +#include "SpirvBackend/SpirvMemory/SpirvConstants.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ShaderRecompiler; + +namespace { + +using Value = std::array; +using Lanes = std::vector; + +void Require(bool condition, const char* reason) { + if (!condition) throw std::runtime_error(reason); +} + +class Subgroup { +public: + explicit Subgroup(std::uint32_t size) : size(size) {} + + void Set(std::uint32_t id, Lanes lanes) { + values[id] = std::move(lanes); + } + + const Lanes& Get(std::uint32_t id) const { + return values.at(id); + } + + void Execute(std::span code) { + for (std::size_t offset = 5; offset < code.size();) { + const auto count = code[offset] >> 16u; + Require(count != 0 && count <= code.size() - offset, "malformed SPIR-V instruction"); + const auto words = code.subspan(offset, count); + const auto op = static_cast(words[0] & 0xffffu); + offset += count; + switch (op) { + case spv::OpTypeInt: + case spv::OpTypeBool: + case spv::OpTypeVector: + continue; + case spv::OpStore: + values[words[1]] = values.at(words[2]); + continue; + default: + break; + } + auto& result = values[words[2]]; + result.resize(size); + for (std::uint32_t lane = 0; lane < size; ++lane) { + const auto arg = [&](std::size_t index) -> const Value& { return values.at(words[index]).at(lane); }; + auto& value = result[lane]; + switch (op) { + case spv::OpConstant: value[0] = words[3]; break; + case spv::OpConstantTrue: value[0] = 1; break; + case spv::OpConstantFalse: value[0] = 0; break; + case spv::OpLoad: value = arg(3); break; + case spv::OpBitwiseAnd: value[0] = arg(3)[0] & arg(4)[0]; break; + case spv::OpBitwiseOr: value[0] = arg(3)[0] | arg(4)[0]; break; + case spv::OpBitwiseXor: value[0] = arg(3)[0] ^ arg(4)[0]; break; + case spv::OpIAdd: value[0] = arg(3)[0] + arg(4)[0]; break; + case spv::OpIMul: value[0] = arg(3)[0] * arg(4)[0]; break; + case spv::OpShiftRightLogical: value[0] = arg(3)[0] >> arg(4)[0]; break; + case spv::OpShiftLeftLogical: value[0] = arg(3)[0] << arg(4)[0]; break; + case spv::OpINotEqual: value[0] = arg(3)[0] != arg(4)[0]; break; + case spv::OpIEqual: value[0] = arg(3)[0] == arg(4)[0]; break; + case spv::OpULessThan: value[0] = arg(3)[0] < arg(4)[0]; break; + case spv::OpLogicalAnd: value[0] = arg(3)[0] && arg(4)[0]; break; + case spv::OpSelect: value = arg(3)[0] ? arg(4) : arg(5); break; + case spv::OpCompositeExtract: value[0] = arg(3).at(words[4]); break; + case spv::OpVectorExtractDynamic: value[0] = arg(3).at(arg(4)[0]); break; + case spv::OpCompositeConstruct: + for (std::size_t component = 0; component < count - 3u; ++component) value.at(component) = arg(3u + component)[0]; + break; + case spv::OpGroupNonUniformBallot: + value.fill(0); + for (std::uint32_t source = 0; source < size; ++source) { + if (values.at(words[4])[source][0]) value[source / 32u] |= 1u << (source % 32u); + } + break; + case spv::OpGroupNonUniformBallotFindLSB: + value[0] = 0xffffffffu; + for (std::uint32_t component = 0; component < 4; ++component) { + if (arg(4)[component] != 0) { + value[0] = component * 32u + std::countr_zero(arg(4)[component]); + break; + } + } + break; + case spv::OpGroupNonUniformShuffle: + Require(arg(5)[0] < size, "shuffle escaped the host subgroup"); + value = values.at(words[4]).at(arg(5)[0]); + break; + default: + throw std::runtime_error("unexpected opcode in subgroup test: " + std::to_string(op)); + } + } + } + } + +private: + std::uint32_t size; + std::unordered_map values; +}; + +void Check(std::uint32_t size, const Value& masks) { + IrProgram program; + program.SetWaveSize(32); + SpirvEmitterState state(program, {}); + state.splitSubgroup = size > 32; + state.subgroupLocalInvocationIdVariable = state.module.AllocateId(); + SpirvValueEmitContext context(state); + context.scratchU32Variable = state.module.AllocateId(); + IrValue source(IrOpcode::GetAttribute, IrType::U32, 1); + IrValue predicate(IrOpcode::GetExec, IrType::Bool, 2); + IrValue requestedLane(IrOpcode::GetAttribute, IrType::U32, 3); + IrValue first(IrOpcode::ReadFirstLane, IrType::U32, 4); + first.AddArgument(&source); + first.AddArgument(&predicate); + IrValue read(IrOpcode::ReadLane, IrType::U32, 5); + read.AddArgument(&source); + read.AddArgument(&requestedLane); + IrValue control(IrOpcode::Void, IrType::U32, 6); + control.SetImmediateU32(0x7c1fu); + IrValue swizzle(IrOpcode::SwizzleU32, IrType::U32, 7); + swizzle.AddArgument(&source); + swizzle.AddArgument(&control); + swizzle.AddArgument(&predicate); + IrValue replacement(IrOpcode::Void, IrType::U32, 8); + replacement.SetImmediateU32(0x12345678u); + IrValue writeTarget(IrOpcode::Void, IrType::U32, 9); + writeTarget.SetImmediateU32(7u); + IrValue write(IrOpcode::WriteLane, IrType::U32, 10); + write.AddArgument(&replacement); + write.AddArgument(&writeTarget); + write.AddArgument(&source); + const auto sourceId = context.Def(&source); + const auto predicateId = context.Def(&predicate); + const auto requestedId = context.Def(&requestedLane); + const auto logicalId = EmitSubgroupLocalInvocationId(state); + const auto ballot = context.Ballot(&predicate); + const auto readFirst = EmitReadFirstLane(context, first); + const auto readLane = EmitReadLane(context, read); + const auto active = EmitBallotLaneActiveBool(state, ballot, requestedId); + const auto quad = EmitDppGroupPermTargetLane(state, logicalId, 0x1bu, 2u); + const auto quadValue = context.Shuffle(read, 0, quad.lane); + const auto hardwareActive = EmitSubgroupLaneActiveBool(state, requestedId); + const auto swizzled = EmitSwizzleU32(context, swizzle); + const auto written = EmitWriteLane(context, write); + Subgroup subgroup(size); + Lanes host(size), sources(size), predicates(size), requested(size); + for (std::uint32_t lane = 0; lane < size; ++lane) { + host[lane][0] = lane; + sources[lane][0] = 0x1000u + lane; + predicates[lane][0] = (masks[lane / 32u] >> (lane % 32u)) & 1u; + requested[lane][0] = 31u - lane % 32u; + } + subgroup.Set(state.subgroupLocalInvocationIdVariable, host); + subgroup.Set(sourceId, sources); + subgroup.Set(predicateId, predicates); + subgroup.Set(requestedId, requested); + subgroup.Execute(state.module.Finalize()); + for (std::uint32_t lane = 0; lane < size; ++lane) { + const auto mask = masks[lane / 32u]; + const auto base = lane / 32u * 32u; + Require(subgroup.Get(logicalId)[lane][0] == lane % 32u, "lane id escaped its wave32"); + Require(subgroup.Get(ballot)[lane] == Value{mask, 0, 0, 0}, "ballot mixed independent wave32s"); + Require(subgroup.Get(readFirst)[lane][0] == 0x1000u + base + (mask == 0 ? 0u : std::countr_zero(mask)), "readfirstlane read a different wave32"); + Require(subgroup.Get(readLane)[lane][0] == 0x1000u + base + requested[lane][0], "readlane read a different wave32"); + Require(subgroup.Get(active)[lane][0] == ((mask >> requested[lane][0]) & 1u), "source lane activity came from another wave32"); + Require(subgroup.Get(quadValue)[lane][0] == 0x1000u + (lane & ~3u) + 3u - lane % 4u, "DPP quad permutation crossed waves"); + Require(subgroup.Get(hardwareActive)[lane][0] == 1u, "upper wave32 lanes were treated as inactive"); + Require(subgroup.Get(swizzled)[lane][0] == (((mask >> requested[lane][0]) & 1u) != 0 ? 0x1000u + base + requested[lane][0] : 0u), "DS swizzle read another wave32 or ignored source EXEC"); + Require(subgroup.Get(written)[lane][0] == (lane % 32u == 7u ? 0x12345678u : 0x1000u + lane), "writelane missed the upper wave32"); + } +} + +} + +int main() { + try { + for (std::uint32_t size : {32u, 64u, 128u}) { + for (const Value& masks : {Value{0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu}, Value{1, 0x80000000u, 0x100, 0x4000}, Value{0x55555555u, 0xaaaaaaaau, 0x12345678u, 0xfedcba98u}, Value{0, 1, 0, 0x80000000u}, Value{1, 0, 0x80000000u, 0}, Value{0, 0, 0, 0}}) { + Check(size, masks); + } + } + std::puts("wave32 wide subgroup tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index c04c4883a..0dca9d1f2 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -125,9 +125,16 @@ endif() if(BUILD_TESTING) + add_test_executable(autorun_exit_tests + ${CMAKE_CURRENT_SOURCE_DIR}/cli/tests/AutorunExitTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cli/src/Autorun.cpp) + target_include_directories(autorun_exit_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli/include) + add_test(NAME autorun_exit_status COMMAND autorun_exit_tests) + set_tests_properties(autorun_exit_status PROPERTIES TIMEOUT 20) add_test_executable(buffer_bounds_tests ${CMAKE_CURRENT_SOURCE_DIR}/io/tests/BufferBoundsTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteReader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteWriter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/io/src/BufferUtils.cpp ) target_include_directories(buffer_bounds_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/io/include) @@ -201,12 +208,18 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_entry_argv.py $) add_test(NAME windows_gui COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_gui.py $) + add_test(NAME windows_address_space COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_address_space.py $) add_test(NAME windows_icon COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_icon.py $) add_test(NAME guest_intel_trampolines COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_intel_trampolines.py $) add_test(NAME guest_tls COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_tls.py $) + add_test(NAME guest_module_directories COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_module_directories.py $) + add_test(NAME guest_host_libc COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_host_libc.py $) add_test(NAME elf_header COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_elf_header.py $) add_test(NAME input_magic COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/cli/src/Autorun.cpp b/core/relinker/cli/src/Autorun.cpp index 30979a985..3bb7016be 100644 --- a/core/relinker/cli/src/Autorun.cpp +++ b/core/relinker/cli/src/Autorun.cpp @@ -20,12 +20,15 @@ int Autorun(const std::string& absPath, bool toWindows) { const int rawCode = std::system(cmd.c_str()); int exitCode = rawCode; - if (toWindows) { - std::cout << "\nExit code: " << rawCode << '\n'; - } else { - if (rawCode != -1) exitCode = (rawCode >> 8) & 0xFF; - std::cout << "\nRaw exit code: " << rawCode << "; Unpacked: " << exitCode << '\n'; +#ifdef _WIN32 + std::cout << "\nExit code: " << rawCode << '\n'; +#else + if (rawCode != -1) { + const int signal = rawCode & 0x7F; + exitCode = signal ? 128 + signal : (rawCode >> 8) & 0xFF; } + std::cout << "\nRaw exit code: " << rawCode << "; Unpacked: " << exitCode << '\n'; +#endif std::cout << "\nPress Enter to exit...\n"; std::cin.ignore(std::numeric_limits::max(), '\n'); diff --git a/core/relinker/cli/tests/AutorunExitTests.cpp b/core/relinker/cli/tests/AutorunExitTests.cpp new file mode 100644 index 000000000..f6fc70829 --- /dev/null +++ b/core/relinker/cli/tests/AutorunExitTests.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int, char* argv[]) { + const auto executable = std::filesystem::absolute(argv[0]); + const auto name = executable.stem().string(); + if (name.starts_with("anyps5-autorun-child-")) { + if (name.find("signal") != std::string::npos) std::raise(SIGILL); + return 42; + } + const auto child = std::filesystem::temp_directory_path() / ("anyps5-autorun-child-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + " with spaces" + executable.extension().string()); +#ifndef _WIN32 + const auto signaled = child.parent_path() / ("anyps5-autorun-child-signal-" + child.filename().string()); +#endif + std::istringstream input("\n\n\n"); + auto* savedInput = std::cin.rdbuf(input.rdbuf()); + int result = 0; + try { + std::filesystem::copy_file(executable, child); + for (const bool windows : {false, true}) { + const int code = Cli::Autorun(child.string(), windows); + if (code != 42) throw std::runtime_error("Autorun changed exit code 42 to " + std::to_string(code)); + } +#ifndef _WIN32 + std::filesystem::rename(child, signaled); + const int code = Cli::Autorun(signaled.string(), false); + if (code != 128 + SIGILL) throw std::runtime_error("Autorun lost the child signal"); +#endif + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + result = 1; + } + std::cin.rdbuf(savedInput); + std::filesystem::remove(child); +#ifndef _WIN32 + std::filesystem::remove(signaled); +#endif + return result; +} diff --git a/core/relinker/codegen/include/codegen/x86/Sha1Operands.hpp b/core/relinker/codegen/include/codegen/x86/Sha1Operands.hpp index 6f00e1d92..01faa731e 100644 --- a/core/relinker/codegen/include/codegen/x86/Sha1Operands.hpp +++ b/core/relinker/codegen/include/codegen/x86/Sha1Operands.hpp @@ -1,8 +1,10 @@ #ifndef CODEGEN_X86_SHA1OPERANDS_HPP #define CODEGEN_X86_SHA1OPERANDS_HPP +#include #include #include +#include namespace Codegen { @@ -18,6 +20,7 @@ struct Sha1Operands { std::uint8_t Destination; std::uint8_t Source; std::uint8_t Function; + std::optional Memory; }; [[nodiscard]] Sha1Operands DecodeSha1(const std::uint8_t* data, std::size_t length); diff --git a/core/relinker/codegen/include/codegen/x86/StubBodyBuilder.hpp b/core/relinker/codegen/include/codegen/x86/StubBodyBuilder.hpp index 942f7db56..692cbc6a4 100644 --- a/core/relinker/codegen/include/codegen/x86/StubBodyBuilder.hpp +++ b/core/relinker/codegen/include/codegen/x86/StubBodyBuilder.hpp @@ -25,6 +25,7 @@ struct MemoryOperand { std::uint8_t Sib; std::int32_t Displacement; bool StackBase; + std::size_t EncodedSize; }; [[nodiscard]] MemoryOperand DecodeMemoryOperand(const std::uint8_t* data, std::size_t length, std::size_t modRmOffset, std::uint8_t rex, std::vector prefixes); diff --git a/core/relinker/codegen/src/x86/Sha1Lowering.cpp b/core/relinker/codegen/src/x86/Sha1Lowering.cpp index 0c5253747..c8a4d2b5c 100644 --- a/core/relinker/codegen/src/x86/Sha1Lowering.cpp +++ b/core/relinker/codegen/src/x86/Sha1Lowering.cpp @@ -156,6 +156,17 @@ void _emitMessage2(StubBodyBuilder& body, const Sha1Operands& operands) { } void Sha1Lowering::EmitOutOfLine(StubBodyBuilder& body, const Sha1Operands& operands) const { + if (operands.Memory) { + auto loaded = operands; + loaded.Memory.reset(); + loaded.Source = loaded.Destination; + loaded.Source = _scratch<1>(loaded)[0]; + body.Spill(loaded.Source); + body.Load(loaded.Source, *operands.Memory); + EmitOutOfLine(body, loaded); + body.Restore(loaded.Source); + return; + } switch (operands.Operation) { case Sha1Operation::Rnds4: _emitRounds(body, operands); diff --git a/core/relinker/codegen/src/x86/Sha1Operands.cpp b/core/relinker/codegen/src/x86/Sha1Operands.cpp index ffcb25b82..950267e12 100644 --- a/core/relinker/codegen/src/x86/Sha1Operands.cpp +++ b/core/relinker/codegen/src/x86/Sha1Operands.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include namespace Codegen { @@ -9,6 +11,7 @@ using namespace X64OpcodeConstants; Sha1Operands DecodeSha1(const std::uint8_t* data, const std::size_t length) { std::size_t pos = 0; std::uint8_t rex = 0; + std::vector prefixes; while (pos < length) { const std::uint8_t b = data[pos]; @@ -25,6 +28,8 @@ Sha1Operands DecodeSha1(const std::uint8_t* data, const std::size_t length) { b != PrefixSegEs && b != PrefixSegFs && b != PrefixSegGs) { break; } + if (b != PrefixLock) + prefixes.push_back(b); rex = 0; pos += 1; } @@ -46,19 +51,22 @@ Sha1Operands DecodeSha1(const std::uint8_t* data, const std::size_t length) { throw CodegenException("Not a SHA-1 instruction"); } - const std::uint8_t modrm = data[pos + 3]; + const std::size_t modRmOffset = pos + 3; + const std::uint8_t modrm = data[modRmOffset]; + std::size_t immediateOffset = modRmOffset + 1; + operands.Destination = static_cast(((modrm >> ModRmRegShift) & ModRmRegMask) | (((rex & 0x4) != 0) ? 8 : 0)); if (((modrm >> ModRmModShift) & ModRmModMask) != ModRmModRegister) { - throw CodegenException("SHA-1 instruction with a memory operand"); + operands.Memory = DecodeMemoryOperand(data, length, modRmOffset, rex, std::move(prefixes)); + immediateOffset = modRmOffset + operands.Memory->EncodedSize; + } else { + operands.Source = static_cast((modrm & ModRmRmMask) | (((rex & 0x1) != 0) ? 8 : 0)); } if (operands.Operation == Sha1Operation::Rnds4) { - if (pos + 5 > length) { + if (immediateOffset >= length) { throw CodegenException("SHA1RNDS4 truncated before its immediate"); } - operands.Function = static_cast(data[pos + 4] & 3); + operands.Function = static_cast(data[immediateOffset] & 3); } - - operands.Destination = static_cast(((modrm >> ModRmRegShift) & ModRmRegMask) | (((rex & 0x4) != 0) ? 8 : 0)); - operands.Source = static_cast((modrm & ModRmRmMask) | (((rex & 0x1) != 0) ? 8 : 0)); return operands; } diff --git a/core/relinker/codegen/src/x86/StubBodyBuilder.cpp b/core/relinker/codegen/src/x86/StubBodyBuilder.cpp index 0fef6e627..7828b9691 100644 --- a/core/relinker/codegen/src/x86/StubBodyBuilder.cpp +++ b/core/relinker/codegen/src/x86/StubBodyBuilder.cpp @@ -58,7 +58,7 @@ MemoryOperand DecodeMemoryOperand(const std::uint8_t* data, const std::size_t le if (modRmOffset >= length) throw CodegenException("Memory operand truncated before its ModRM byte"); const auto modrm = data[modRmOffset]; - MemoryOperand operand{std::move(prefixes), static_cast(rex & (kRexX | kRexB)), static_cast((modrm >> ModRmModShift) & ModRmModMask), static_cast(modrm & ModRmRmMask), 0, 0, false}; + MemoryOperand operand{std::move(prefixes), static_cast(rex & (kRexX | kRexB)), static_cast((modrm >> ModRmModShift) & ModRmModMask), static_cast(modrm & ModRmRmMask), 0, 0, false, 0}; if (operand.Mod == ModRmModRegister) throw CodegenException("Register operand where a memory operand was expected"); if (operand.Mod == ModRmModIndirect && operand.Rm == ModRmRmRipRelative) @@ -73,6 +73,7 @@ MemoryOperand DecodeMemoryOperand(const std::uint8_t* data, const std::size_t le const auto size = _displacementSize(operand.Mod, operand.Rm, operand.Sib); if (pos + size > length) throw CodegenException("Memory operand truncated in its displacement"); + operand.EncodedSize = pos + size - modRmOffset; if (size == Disp8Size) operand.Displacement = static_cast(data[pos]); for (std::size_t index = 0; size == Disp32Size && index < size; ++index) diff --git a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp index 4bd58800d..c4fab89f4 100644 --- a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp +++ b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp @@ -221,9 +221,26 @@ void sha1Operands() { check({0x44, 0x0F, 0x38, 0xC8, 0xC0}, Codegen::Sha1Operation::Nexte, 8, 0, 0); check({0x0F, 0x38, 0xC9, 0xD5}, Codegen::Sha1Operation::Msg1, 2, 5, 0); check({0x2E, 0x41, 0x0F, 0x38, 0xCA, 0xCA}, Codegen::Sha1Operation::Msg2, 1, 10, 0); - requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xC9, 0x08}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA-1 memory operand was accepted"); - requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0x08, 0x00}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 memory operand was accepted"); + const Bytes memory = {0x65, 0x67, 0x44, 0x0F, 0x3A, 0xCC, 0x54, 0x8C, 0xF0, 0xFE}; + const auto decoded = Codegen::DecodeSha1(memory.data(), memory.size()); + require(decoded.Operation == Codegen::Sha1Operation::Rnds4 && decoded.Destination == 10 && decoded.Function == 2 && decoded.Memory && decoded.Memory->Prefixes == Bytes{0x65, 0x67} && decoded.Memory->Mod == 1 && decoded.Memory->Rm == 4 && decoded.Memory->Sib == 0x8C && decoded.Memory->Displacement == -16 && decoded.Memory->StackBase, "SHA-1 memory operand was decoded incorrectly"); + const auto memoryFunction = [](const Bytes& site, const int dst, const std::int32_t displacement, const int function) { + const auto operands = Codegen::DecodeSha1(site.data(), site.size()); + require(operands.Operation == Codegen::Sha1Operation::Rnds4 && operands.Destination == dst && operands.Memory && !operands.Memory->StackBase && operands.Memory->Displacement == displacement && operands.Function == function, "SHA1RNDS4 immediate was not read after its memory operand"); + }; + memoryFunction({0x0F, 0x3A, 0xCC, 0x08, 0x03}, 1, 0, 3); + memoryFunction({0x0F, 0x3A, 0xCC, 0x50, 0x02, 0x01}, 2, 2, 1); + memoryFunction({0x0F, 0x3A, 0xCC, 0x91, 0x78, 0x56, 0x34, 0x12, 0x01}, 2, 0x12345678, 1); + memoryFunction({0x41, 0x0F, 0x3A, 0xCC, 0x14, 0x24, 0x02}, 2, 0, 2); + memoryFunction({0x0F, 0x3A, 0xCC, 0x14, 0x25, 0x78, 0x56, 0x34, 0x12, 0x03}, 2, 0x12345678, 3); + const Bytes message = {0x0F, 0x38, 0xC9, 0x08}; + const auto decodedMessage = Codegen::DecodeSha1(message.data(), message.size()); + require(decodedMessage.Operation == Codegen::Sha1Operation::Msg1 && decodedMessage.Destination == 1 && decodedMessage.Memory && decodedMessage.Memory->Mod == 0 && decodedMessage.Memory->Rm == 0, "SHA1MSG1 memory operand was decoded incorrectly"); + requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xC9, 0x15, 0, 0, 0, 0}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA-1 RIP-relative operand was accepted"); + requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0x15, 0, 0, 0, 0, 0x00}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 RIP-relative operand was accepted"); + requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xC9, 0x54, 0x24}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "Truncated SHA-1 memory operand was accepted"); requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0xCA}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 without its immediate was accepted"); + requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0x50, 0x02}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 memory form without its immediate was accepted"); requireFailure([] { const Bytes bytes = {0x66, 0x0F, 0x38, 0xC8, 0xCA}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "Prefixed 0F 38 C8 was decoded as SHA-1"); requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xCC, 0xCA}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA-256 was decoded as SHA-1"); const Bytes prefixedRounds = {0x66, 0x0F, 0x3A, 0xCC, 0xCA, 0x00}; @@ -293,6 +310,12 @@ void matcherSubstitutions() { const auto sha1 = match(bytes); require(sha1 && sha1->Lowering == Codegen::Amd64OnlyLowering::Trampoline && sha1->InstructionName == name, "SHA-1 instruction was not lowered through a stub"); } + for (const auto& [bytes, name] : {std::pair{Bytes{0x0F, 0x3A, 0xCC, 0x08, 0x01}, "SHA1RNDS4"}, {Bytes{0x0F, 0x38, 0xC8, 0x48, 0x10}, "SHA1NEXTE"}, {Bytes{0x0F, 0x38, 0xC9, 0x4C, 0x24, 0x18}, "SHA1MSG1"}, {Bytes{0x45, 0x0F, 0x38, 0xCA, 0x21}, "SHA1MSG2"}}) { + const auto sha1 = match(bytes); + require(sha1 && sha1->Lowering == Codegen::Amd64OnlyLowering::Trampoline && sha1->InstructionName == name, "SHA-1 memory form was not lowered through a stub"); + } + const auto stackMessage = match({0x0F, 0x38, 0xC9, 0x4C, 0x24, 0x18}); + require(stackMessage && stackMessage->StubBody.size() > 22 && Bytes(stackMessage->StubBody.begin() + 13, stackMessage->StubBody.begin() + 22) == Bytes{0xF3, 0x0F, 0x6F, 0x84, 0x24, 0xA8, 0x00, 0x00, 0x00}, "SHA-1 stack operand was not loaded past the spilled scratch register"); const auto stub = match(kInsertqHighSite); require(stub && stub->Lowering == Codegen::Amd64OnlyLowering::Trampoline && stub->StubBody == kInsertqHighBody && stub->ReturnBranchOffset == 15 && stub->InstructionName == "INSERTQ", "INSERTQ was not lowered through a stub"); const auto topAligned = match({0x66, 0x0F, 0x78, 0xC3, 0x18, 0x28}); @@ -466,7 +489,12 @@ void converterSha1() { requireFailure([&] { (void)converter->Convert(beforeReturn, {segmentHeader(text.size())}); }, "Short SHA-1 instruction followed by a return was relocated"); auto memoryForm = file; memoryForm[0x214] = 0x28; - require(failureOffset([&] { (void)converter->Convert(memoryForm, {segmentHeader(text.size())}); }, "SHA-1 memory form was accepted") == 0x211, "SHA-1 operand failure does not carry the file offset"); + const auto memoryResult = converter->Convert(memoryForm, {segmentHeader(text.size())}); + require(memoryResult.Trampolines.size() == 3 && memoryResult.Reports[2].InstructionName == "SHA1MSG2", "SHA-1 memory form was not lowered through a stub"); + auto ripRelative = file; + const Bytes ripMessage = {0x0F, 0x38, 0xCA, 0x2D, 0x00, 0x00, 0x00, 0x00, 0xC3}; + std::copy(ripMessage.begin(), ripMessage.end(), ripRelative.begin() + 0x211); + require(failureOffset([&] { (void)converter->Convert(ripRelative, {segmentHeader(text.size() + 3)}); }, "SHA-1 RIP-relative form was accepted") == 0x211, "SHA-1 operand failure does not carry the file offset"); } void converterMonitorWait() { @@ -1030,7 +1058,9 @@ void sha1Execution() { {0x0F, 0x3A, 0xCC, 0xD2, 0x02}, {0x45, 0x0F, 0x3A, 0xCC, 0xCE, 0x00}, {0x41, 0x0F, 0x3A, 0xCC, 0xC0, 0x01}, {0x0F, 0x38, 0xC8, 0xD5}, {0x0F, 0x38, 0xC8, 0xD2}, {0x44, 0x0F, 0x38, 0xC8, 0xC7}, {0x0F, 0x38, 0xC9, 0xD5}, {0x0F, 0x38, 0xC9, 0xD2}, {0x41, 0x0F, 0x38, 0xC9, 0xC7}, - {0x0F, 0x38, 0xCA, 0xD5}, {0x0F, 0x38, 0xCA, 0xD2}, {0x45, 0x0F, 0x38, 0xCA, 0xFF}}; + {0x0F, 0x38, 0xCA, 0xD5}, {0x0F, 0x38, 0xCA, 0xD2}, {0x45, 0x0F, 0x38, 0xCA, 0xFF}, + {0x0F, 0x3A, 0xCC, 0x10, 0x02}, {0x44, 0x0F, 0x3A, 0xCC, 0x40, 0x10, 0x01}, {0x0F, 0x3A, 0xCC, 0x80, 0x10, 0x00, 0x00, 0x00, 0x00}, {0x0F, 0x3A, 0xCC, 0x44, 0x20, 0x10, 0xFF}, + {0x0F, 0x38, 0xC8, 0x00}, {0x0F, 0x38, 0xC9, 0x48, 0x10}, {0x44, 0x0F, 0x38, 0xCA, 0xB8, 0x10, 0x00, 0x00, 0x00}}; for (const auto& site : sites) { const auto operands = Codegen::DecodeSha1(site.data(), site.size()); const auto match = matcher->Match(site.data(), site.size()); @@ -1041,19 +1071,24 @@ void sha1Execution() { reg[0] = random(); reg[1] = random(); } + alignas(16) const std::uint64_t memory[4] = {random(), random(), random(), random()}; + const auto rax = operands.Memory ? reinterpret_cast(memory) : 0x5A5A5A5A5A5A5A5Aull; std::uint32_t x[4]; std::uint32_t y[4]; std::memcpy(x, xmmIn[operands.Destination], sizeof(x)); - std::memcpy(y, xmmIn[operands.Source], sizeof(y)); + if (operands.Memory) + std::memcpy(y, reinterpret_cast(memory) + operands.Memory->Displacement, sizeof(y)); + else + std::memcpy(y, xmmIn[operands.Source], sizeof(y)); const auto expected = sha1Reference(operands, x, y); - const auto run = runStubBody(*match, 0x5A5A5A5A5A5A5A5Aull, xmmIn); + const auto run = runStubBody(*match, rax, xmmIn); for (unsigned reg = 0; reg < 16; ++reg) { if (reg == operands.Destination) require(std::memcmp(run.Xmm[reg], expected.data(), 16) == 0, "SHA-1 stub computed the wrong result"); else require(run.Xmm[reg][0] == xmmIn[reg][0] && run.Xmm[reg][1] == xmmIn[reg][1], "SHA-1 stub clobbered an xmm register"); } - require(run.Rax == 0x5A5A5A5A5A5A5A5Aull && run.Rcx == 0x1122334455667788ull && (run.Flags & 0x8D5) == (0x8D7 & 0x8D5), "SHA-1 stub changed a general register or RFLAGS"); + require(run.Rax == rax && run.Rcx == 0x1122334455667788ull && (run.Flags & 0x8D5) == (0x8D7 & 0x8D5), "SHA-1 stub changed a general register or RFLAGS"); } } } diff --git a/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp b/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp index 61c0a567d..3c4e9ac34 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp @@ -30,7 +30,7 @@ std::vector WindowsPeWriter::Write(const std::vector& s Io::WriteU16(result, optionalOffset + 50, 2); Io::WriteU32(result, optionalOffset + 60, LoadRva); Io::WriteU16(result, optionalOffset + 68, windowsGui ? 2 : 3); - Io::WriteU16(result, optionalOffset + 70, directories[5].Size == 0 ? 0x100 : 0x160); + Io::WriteU16(result, optionalOffset + 70, directories[5].Size == 0 ? 0x100 : 0x140); Io::WriteU64(result, optionalOffset + 72, 0x210000); Io::WriteU64(result, optionalOffset + 80, 0x200000); Io::WriteU64(result, optionalOffset + 88, 0x100000); diff --git a/core/relinker/elfpatcher/tests/Amd64OnlyWindowsTests.cpp b/core/relinker/elfpatcher/tests/Amd64OnlyWindowsTests.cpp index 2a48ce66d..749aa1923 100644 --- a/core/relinker/elfpatcher/tests/Amd64OnlyWindowsTests.cpp +++ b/core/relinker/elfpatcher/tests/Amd64OnlyWindowsTests.cpp @@ -344,6 +344,150 @@ void executeSha256(const Harness& harness, const Codegen::IAmd64OnlyInstructionM } } +struct Sha1Memory { + std::uint8_t ModRm; + Bytes Tail; + std::optional Slot; + const char* Name; +}; + +const std::array kSha1Memory = {{ + {0x03, {}, std::uint8_t{0}, "[rbx]"}, + {0x43, {0x70}, std::uint8_t{7}, "[rbx+0x70]"}, + {0x83, {0xF0, 0x00, 0x00, 0x00}, std::uint8_t{15}, "[rbx+0xf0]"}, + {0x44, {0x24, 0xF0}, std::nullopt, "[rsp-0x10]"}}}; + +struct Sha1Step { + std::uint8_t Opcode; + std::uint8_t Destination; + std::uint8_t Source; + std::uint8_t Immediate = 0; + bool Memory = false; +}; + +Bytes encodeSha1(const Sha1Step& step) { + Bytes bytes; + const auto rex = static_cast(0x40 | (step.Destination >= 8 ? 4 : 0) | (!step.Memory && step.Source >= 8 ? 1 : 0)); + if (rex != 0x40) bytes.push_back(rex); + bytes.insert(bytes.end(), {0x0F, static_cast(step.Opcode == 0xCC ? 0x3A : 0x38), step.Opcode}); + if (step.Memory) { + const auto& memory = kSha1Memory[step.Source]; + bytes.push_back(static_cast(memory.ModRm | ((step.Destination & 7) << 3))); + bytes.insert(bytes.end(), memory.Tail.begin(), memory.Tail.end()); + } else { + bytes.push_back(static_cast(0xC0 | ((step.Destination & 7) << 3) | (step.Source & 7))); + } + if (step.Opcode == 0xCC) bytes.push_back(step.Immediate); + return bytes; +} + +std::uint32_t rotl(const std::uint32_t value, const unsigned count) { + return (value << count) | (value >> (32 - count)); +} + +void sha1Reference(const Sha1Step& step, const State& input, std::uint8_t (&xmm)[16][16]) { + std::uint32_t x[4]; + std::uint32_t y[4]; + std::uint32_t r[4]; + std::memcpy(x, xmm[step.Destination], sizeof(x)); + if (!step.Memory) { + std::memcpy(y, xmm[step.Source], sizeof(y)); + } else if (const auto slot = kSha1Memory[step.Source].Slot) { + std::memcpy(y, input.Xmm[*slot], sizeof(y)); + } else { + const std::uint64_t canary[2] = {kCanary, kCanary}; + std::memcpy(y, canary, sizeof(y)); + } + if (step.Opcode == 0xC8) { + r[0] = y[0]; + r[1] = y[1]; + r[2] = y[2]; + r[3] = y[3] + rotl(x[3], 30); + } else if (step.Opcode == 0xC9) { + r[0] = x[0] ^ y[2]; + r[1] = x[1] ^ y[3]; + r[2] = x[2] ^ x[0]; + r[3] = x[3] ^ x[1]; + } else if (step.Opcode == 0xCA) { + r[3] = rotl(x[3] ^ y[2], 1); + r[2] = rotl(x[2] ^ y[1], 1); + r[1] = rotl(x[1] ^ y[0], 1); + r[0] = rotl(x[0] ^ r[3], 1); + } else { + constexpr std::uint32_t keys[4] = {0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6}; + const auto function = step.Immediate & 3; + std::uint32_t a = x[3], b = x[2], c = x[1], d = x[0], e = 0; + for (int round = 0; round < 4; ++round) { + std::uint32_t f = b ^ c ^ d; + if (function == 0) f = (b & c) ^ (~b & d); + if (function == 2) f = (b & c) ^ (b & d) ^ (c & d); + const auto next = f + rotl(a, 5) + y[3 - round] + e + keys[function]; + e = d; d = c; c = rotl(b, 30); b = a; a = next; + } + r[0] = d; + r[1] = c; + r[2] = b; + r[3] = a; + } + std::memcpy(xmm[step.Destination], r, sizeof(r)); +} + +std::string describeSha1(const std::vector& steps) { + std::string text; + for (const auto& step : steps) { + text += std::string(step.Opcode == 0xCC ? "sha1rnds4" : step.Opcode == 0xC8 ? "sha1nexte" : step.Opcode == 0xC9 ? "sha1msg1" : "sha1msg2") + " xmm" + std::to_string(step.Destination) + ", "; + text += step.Memory ? std::string(kSha1Memory[step.Source].Name) : "xmm" + std::to_string(step.Source); + if (step.Opcode == 0xCC) text += ", " + std::to_string(step.Immediate); + text += "; "; + } + return text; +} + +void executeSha1(const Harness& harness, const Codegen::IAmd64OnlyInstructionMatcher& matcher, const std::vector& steps, std::mt19937_64& random, const int images) { + std::vector sites; + for (const auto& step : steps) sites.push_back(encodeSha1(step)); + const std::vector> spans(sites.begin(), sites.end()); + const auto match = steps.size() == 1 ? matcher.Match(sites[0].data(), sites[0].size()) : matcher.MatchSequence(spans, {}); + require(match.has_value() && match->Lowering == Codegen::Amd64OnlyLowering::Trampoline, "SHA-1 was not lowered through a stub: " + describeSha1(steps)); + std::uint16_t written = 0; + for (const auto& step : steps) written = static_cast(written | (1u << step.Destination)); + const std::array flags = {0x202, 0x203, 0x246, 0xAC7}; + for (int image = 0; image < images; ++image) { + State input{}; + for (auto& lane : input.Xmm) for (auto& byte : lane) byte = static_cast(random()); + input.FlagsIn = flags[static_cast(image) % flags.size()]; + State state = input; + harness.Run(match->StubBody, match->ReturnBranchOffset, state); + ++g_executions; + State expected = input; + for (const auto& step : steps) sha1Reference(step, input, expected.Xmm); + for (unsigned reg = 0; reg < 16; ++reg) + if (((written >> reg) & 1) != 0) require(std::memcmp(state.Xmm[reg], expected.Xmm[reg], 16) == 0, "Lowered SHA-1 computed the wrong xmm" + std::to_string(reg) + ": " + describeSha1(steps)); + requireEnvironment(input, state, written, describeSha1(steps)); + } +} + +void sha1Execution(const Harness& harness, const Codegen::IAmd64OnlyInstructionMatcher& matcher, std::mt19937_64& random) { + const std::array opcodes = {0xCC, 0xC8, 0xC9, 0xCA}; + for (const auto opcode : opcodes) { + for (std::uint8_t dst = 0; dst < 16; ++dst) { + for (std::uint8_t src = 0; src < 16; ++src) + executeSha1(harness, matcher, {{opcode, dst, src, static_cast(random())}}, random, 2); + for (std::uint8_t memory = 0; memory < kSha1Memory.size(); ++memory) + for (std::uint8_t function = 0; function < 4; ++function) + executeSha1(harness, matcher, {{opcode, dst, memory, static_cast((random() & 0xFC) | function), true}}, random, 2); + } + } + for (int sequence = 0; sequence < 256; ++sequence) { + std::vector steps(2 + random() % 2); + for (auto& step : steps) { + const bool memory = random() % 2 == 0; + step = {opcodes[random() % opcodes.size()], static_cast(random() % 16), static_cast(random() % (memory ? kSha1Memory.size() : 16)), static_cast(random()), memory}; + } + executeSha1(harness, matcher, steps, random, 2); + } +} + void clzeroExecution(const Harness& harness, const Codegen::IAmd64OnlyInstructionMatcher& matcher, std::mt19937_64& random) { const Bytes plain = {0x0F, 0x01, 0xFC}; const Bytes addressSize32 = {0x67, 0x0F, 0x01, 0xFC}; @@ -418,6 +562,7 @@ void cpuExecution() { executeSha256(harness, *matcher, steps, random, 2); } clzeroExecution(harness, *matcher, random); + sha1Execution(harness, *matcher, random); } Bytes elfFixture(const Bytes& text) { diff --git a/core/relinker/io/include/io/BufferUtils.hpp b/core/relinker/io/include/io/BufferUtils.hpp index 365a50847..82cc8d0ec 100644 --- a/core/relinker/io/include/io/BufferUtils.hpp +++ b/core/relinker/io/include/io/BufferUtils.hpp @@ -2,6 +2,8 @@ #define IO_BUFFERUTILS_HPP #include +#include +#include #include #include @@ -27,7 +29,14 @@ std::uint64_t ReadU64(const std::vector& buf, std::size_t offset); template constexpr T AlignUp(T value, T alignment) { - return (value + alignment - 1) / alignment * alignment; + if (value < 0 || alignment <= 0) + throw std::invalid_argument("AlignUp requires a nonnegative value and a positive alignment"); + const T remainder = value % alignment; + if (remainder == 0) return value; + const T padding = alignment - remainder; + if (value > std::numeric_limits::max() - padding) + throw std::overflow_error("Aligned value exceeds integer range"); + return value + padding; } std::uint64_t AlignUp64(std::uint64_t value, std::uint64_t alignment); diff --git a/core/relinker/io/src/BufferUtils.cpp b/core/relinker/io/src/BufferUtils.cpp index c0d389588..d4ce96336 100644 --- a/core/relinker/io/src/BufferUtils.cpp +++ b/core/relinker/io/src/BufferUtils.cpp @@ -60,11 +60,7 @@ void AppendString(std::vector& buf, const std::string& str) { } void AlignBuffer(std::vector& buf, std::size_t alignment) { - const std::size_t remainder = buf.size() % alignment; - if (remainder != 0) { - const std::size_t padding = alignment - remainder; - buf.resize(buf.size() + padding, 0); - } + buf.resize(AlignUp(buf.size(), alignment), 0); } std::uint64_t AlignUp64(std::uint64_t value, std::uint64_t alignment) { diff --git a/core/relinker/io/src/ByteWriter.cpp b/core/relinker/io/src/ByteWriter.cpp index 080fd5f0b..5d7aed2d3 100644 --- a/core/relinker/io/src/ByteWriter.cpp +++ b/core/relinker/io/src/ByteWriter.cpp @@ -1,21 +1,30 @@ #include #include +#include namespace Io { void ByteWriter::WriteU8(std::vector& buf, std::size_t offset, std::uint8_t v) const { + if (offset >= buf.size()) + throw std::out_of_range("ByteWriter::WriteU8 out of bounds"); buf[offset] = v; } void ByteWriter::WriteU16(std::vector& buf, std::size_t offset, std::uint16_t v) const { + if (offset > buf.size() || buf.size() - offset < 2) + throw std::out_of_range("ByteWriter::WriteU16 out of bounds"); std::memcpy(buf.data() + offset, &v, 2); } void ByteWriter::WriteU32(std::vector& buf, std::size_t offset, std::uint32_t v) const { + if (offset > buf.size() || buf.size() - offset < 4) + throw std::out_of_range("ByteWriter::WriteU32 out of bounds"); std::memcpy(buf.data() + offset, &v, 4); } void ByteWriter::WriteU64(std::vector& buf, std::size_t offset, std::uint64_t v) const { + if (offset > buf.size() || buf.size() - offset < 8) + throw std::out_of_range("ByteWriter::WriteU64 out of bounds"); std::memcpy(buf.data() + offset, &v, 8); } diff --git a/core/relinker/io/tests/BufferBoundsTests.cpp b/core/relinker/io/tests/BufferBoundsTests.cpp index 8cdacaf8a..6767291ff 100644 --- a/core/relinker/io/tests/BufferBoundsTests.cpp +++ b/core/relinker/io/tests/BufferBoundsTests.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -10,6 +11,8 @@ namespace { using Bytes = std::vector; +static_assert(Io::AlignUp(std::uint64_t{17}, std::uint64_t{16}) == 32); + void require(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } @@ -70,14 +73,58 @@ void writeBounds(const TWrite& write, const std::string& name) { invalidRanges(bytes, [&](Bytes& buffer, std::size_t offset) { write(buffer, offset, value); }, name); } +template +void requireThrows(const TOperation& operation, const std::string& message) { + try { + operation(); + } catch (const TError&) { + return; + } + throw std::runtime_error(message); +} + +template +void alignmentBounds() { + constexpr auto maximum = std::numeric_limits::max(); + require(Io::AlignUp(TValue{0}, TValue{16}) == 0, "AlignUp changed zero"); + require(Io::AlignUp(TValue{17}, TValue{16}) == 32, "AlignUp failed to round up"); + require(Io::AlignUp(TValue{7}, TValue{3}) == 9, "AlignUp requires a power-of-two alignment"); + require(Io::AlignUp(maximum, TValue{1}) == maximum, "AlignUp changed an aligned maximum"); + require(Io::AlignUp(static_cast(maximum - 15), TValue{16}) == maximum - 15, "AlignUp changed an aligned value near the maximum"); + require(Io::AlignUp(static_cast(maximum - 1), maximum) == maximum, "AlignUp overflowed for a representable result"); + requireThrows([&] { Io::AlignUp(maximum, TValue{16}); }, "AlignUp accepted an overflowing result"); + requireThrows([] { Io::AlignUp(TValue{1}, TValue{0}); }, "AlignUp accepted zero alignment"); +} + } int main() { try { const Io::ByteReader reader; + const Io::ByteWriter writer; + alignmentBounds(); + alignmentBounds(); + alignmentBounds(); + alignmentBounds(); + requireThrows([] { Io::AlignUp(-1, 16); }, "AlignUp accepted a negative value"); + requireThrows([] { Io::AlignUp(1, -1); }, "AlignUp accepted negative alignment"); + require(Io::AlignUp64(std::numeric_limits::max() - 1, std::numeric_limits::max()) == std::numeric_limits::max(), "AlignUp64 overflowed for a representable result"); + requireThrows([] { Io::AlignUp64(std::numeric_limits::max(), 16); }, "AlignUp64 accepted an overflowing result"); + Bytes aligned{1, 2, 3}; + const auto original = aligned; + requireThrows([&] { Io::AlignBuffer(aligned, 0); }, "AlignBuffer accepted zero alignment"); + require(aligned == original, "AlignBuffer modified the buffer on failure"); + Io::AlignBuffer(aligned, 4); + require(aligned == Bytes({1, 2, 3, 0}), "AlignBuffer failed to preserve bytes and zero-fill padding"); + Io::AlignBuffer(aligned, 4); + require(aligned == Bytes({1, 2, 3, 0}), "AlignBuffer changed an aligned buffer"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU16(bytes, offset); }, "ByteReader::ReadU16"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU32(bytes, offset); }, "ByteReader::ReadU32"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU64(bytes, offset); }, "ByteReader::ReadU64"); + writeBounds([&](Bytes& bytes, std::size_t offset, std::uint8_t value) { writer.WriteU8(bytes, offset, value); }, "ByteWriter::WriteU8"); + writeBounds([&](Bytes& bytes, std::size_t offset, std::uint16_t value) { writer.WriteU16(bytes, offset, value); }, "ByteWriter::WriteU16"); + writeBounds([&](Bytes& bytes, std::size_t offset, std::uint32_t value) { writer.WriteU32(bytes, offset, value); }, "ByteWriter::WriteU32"); + writeBounds([&](Bytes& bytes, std::size_t offset, std::uint64_t value) { writer.WriteU64(bytes, offset, value); }, "ByteWriter::WriteU64"); readBounds(Io::ReadU16, "ReadU16"); readBounds(Io::ReadU32, "ReadU32"); readBounds(Io::ReadU64, "ReadU64"); diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index 05b13295a..2e3b6553e 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -80,8 +80,8 @@ int main(const int argc, char* argv[]) { ); std::cout << "System: " << (args.toWindows ? "Windows" : "Linux") << "; unused-filter=" << args.unusedFilterLevel << "\n"; - std::cout << "sce_module/sce_modules processing: " << (args.skipSceModule ? "disabled (--skip-sce-module)" : "enabled") << '\n'; - for (const auto& name : args.excludedSceModules) std::cout << "sce_module excluded: " << name << '\n'; + std::cout << "sce_module/sce_modules/prx processing: " << (args.skipSceModule ? "disabled (--skip-sce-module)" : "enabled") << '\n'; + for (const auto& name : args.excludedSceModules) std::cout << "Guest module excluded: " << name << '\n'; auto result = pipeline->Relink(sourceBytes); for (const auto& patch : result.Patches) { if (patch.Offset > sourceBytes.size() || patch.Bytes.size() > sourceBytes.size() - patch.Offset) @@ -134,10 +134,8 @@ int main(const int argc, char* argv[]) { std::cout << "Expected runtime layout (relative to the output executable):\n" << std::filesystem::path(absPath).filename().string() << "\n" << "libs/\n *.prx\napp0/\n \n"; - if (!guestArtifacts.empty()) { - std::cout << " " << guestArtifacts.front().Path.parent_path().filename().string() << "/\n"; - for (const auto& artifact : guestArtifacts) std::cout << " " << artifact.Path.filename().string() << '\n'; - } + for (const auto& artifact : guestArtifacts) + std::cout << " " << artifact.Path.parent_path().filename().string() << "/" << artifact.Path.filename().string() << '\n'; std::cout << "Game resources and system libraries must be placed in this layout separately.\n"; if (args.runPath != "$ORIGIN/libs") std::cout << "Custom library search path (--rpath): " << args.runPath << '\n'; diff --git a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp index a2ba2e2f2..e065dc76e 100644 --- a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp +++ b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp @@ -80,7 +80,7 @@ std::set CodeInstructionCollector::Collect(const std::ve std::set roots; std::map functions; const auto addRoot = [&](std::uint64_t address) { if (isCode(address)) roots.insert(address); }; - const auto addFunction = [&](std::uint64_t begin, std::uint64_t size) { + const auto addFunction = [&](std::uint64_t begin, std::uint64_t size, bool symbolAlias) { if (size == 0) return; if (!isCode(begin) || size > std::numeric_limits::max() - begin) throw Domain::RelinkerException("Code analysis: invalid function range", begin); @@ -95,8 +95,9 @@ std::set CodeInstructionCollector::Collect(const std::ve } if (!mapped) throw Domain::RelinkerException("Code analysis: function is not file-backed", begin); const auto [position, inserted] = functions.emplace(begin, begin + size); - if (!inserted && position->second != begin + size) + if (!inserted && !symbolAlias && position->second != begin + size) throw Domain::RelinkerException("Code analysis: conflicting function ranges", begin); + position->second = std::max(position->second, begin + size); roots.insert(begin); }; range(0, 64); @@ -117,7 +118,7 @@ std::set CodeInstructionCollector::Collect(const std::ve if ((bytes[offset + 4] & 15) == 2 && Io::ReadU16(bytes, offset + 6) != 0) { const auto address = Io::ReadU64(bytes, offset + 8); addRoot(address); - addFunction(address, Io::ReadU64(bytes, offset + 16)); + addFunction(address, Io::ReadU64(bytes, offset + 16), true); } } } @@ -169,7 +170,7 @@ std::set CodeInstructionCollector::Collect(const std::ve } for (const auto& function : UnusedNidFilter::ReadExceptionFunctions(bytes, headers, pointers.Values, importSlots)) { if (function.End <= function.Begin) throw Domain::RelinkerException("Code analysis: invalid unwind function range", function.Begin); - addFunction(function.Begin, function.End - function.Begin); + addFunction(function.Begin, function.End - function.Begin, false); for (const auto target : function.ExtraTargets) addRoot(target); } if (roots.empty()) throw Domain::RelinkerException("Code analysis: no code entry points"); diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp index 27bd97b78..eebb504ca 100644 --- a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -15,26 +15,35 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path const auto root = std::filesystem::absolute(inputPath).parent_path(); const auto singular = root / "sce_module"; const auto plural = root / "sce_modules"; + const auto prx = root / "prx"; const bool hasSingular = std::filesystem::exists(singular); const bool hasPlural = std::filesystem::exists(plural); + const bool hasPrx = std::filesystem::exists(prx); if (hasSingular && hasPlural) throw Domain::RelinkerException("Both sce_module and sce_modules exist beside the input executable"); - if (!hasSingular && !hasPlural) throw Domain::RelinkerException("sce_module/sce_modules was not found beside the input executable: " + root.string() + ". Use --skip-sce-module only if this game can run without these modules."); - const auto directory = hasSingular ? singular : plural; - if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + directory.string()); + if (!hasSingular && !hasPlural && !hasPrx) throw Domain::RelinkerException("sce_module/sce_modules/prx was not found beside the input executable: " + root.string() + ". Use --skip-sce-module only if this game can run without these modules."); + std::vector directories; + if (hasSingular || hasPlural) directories.push_back(hasSingular ? singular : plural); + if (hasPrx) directories.push_back(prx); std::vector paths; std::set unmatchedExclusions = excludedModules; - for (const auto& entry : std::filesystem::directory_iterator(directory)) { - if (entry.path().filename().string().ends_with(GuestModuleSuffix)) continue; - if (unmatchedExclusions.erase(entry.path().filename().string()) != 0) continue; - if (!entry.is_regular_file()) continue; - std::ifstream stream(entry.path(), std::ios::binary); - if (!stream) throw Domain::RelinkerException("Cannot read guest candidate: " + entry.path().string()); - char magic[4]{}; - stream.read(magic, 4); - if (stream.bad()) throw Domain::RelinkerException("Cannot read guest candidate magic: " + entry.path().string()); - if (stream.gcount() == 4 && static_cast(magic[0]) == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F') paths.push_back(entry.path()); + for (const auto& directory : directories) { + if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + directory.string()); + for (const auto& entry : std::filesystem::directory_iterator(directory)) { + if (entry.path().filename().string().ends_with(GuestModuleSuffix)) continue; + if (excludedModules.contains(entry.path().filename().string())) { + unmatchedExclusions.erase(entry.path().filename().string()); + continue; + } + if (!entry.is_regular_file()) continue; + std::ifstream stream(entry.path(), std::ios::binary); + if (!stream) throw Domain::RelinkerException("Cannot read guest candidate: " + entry.path().string()); + char magic[4]{}; + stream.read(magic, 4); + if (stream.bad()) throw Domain::RelinkerException("Cannot read guest candidate magic: " + entry.path().string()); + if (stream.gcount() == 4 && static_cast(magic[0]) == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F') paths.push_back(entry.path()); + } } - if (!unmatchedExclusions.empty()) throw Domain::RelinkerException("Excluded sce_module file not found: " + *unmatchedExclusions.begin()); + if (!unmatchedExclusions.empty()) throw Domain::RelinkerException("Excluded guest module file not found: " + *unmatchedExclusions.begin()); std::sort(paths.begin(), paths.end()); if (paths.empty()) return {}; if (lazyBinding) throw Domain::RelinkerException("Guest modules require eager binding; --lazy-binding is incompatible"); @@ -126,14 +135,14 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path addHost(std::string(start, end)); } for (const auto& image : images) for (const auto& dependency : image.Dependencies) addHost(dependency); + if (uniqueHosts.contains("libSceLibcInternal.prx") && uniqueHosts.insert("libc.prx").second) hostLibraries.push_back("libc.prx"); dynamic.DynamicSegmentData.clear(); const auto addNeeded = [&](const std::string& name) { Io::AppendU64(dynamic.DynamicSegmentData, 1); Io::AppendU64(dynamic.DynamicSegmentData, dynamic.DynStrData.size()); Io::AppendString(dynamic.DynStrData, name); }; - const auto relativeDirectory = "app0/" + directory.filename().generic_string(); - for (const auto index : order) if (!windows) addNeeded("$ORIGIN/" + relativeDirectory + "/" + images[index].OutputName); + for (const auto index : order) if (!windows) addNeeded("$ORIGIN/app0/" + images[index].SourcePath.parent_path().filename().generic_string() + "/" + images[index].OutputName); for (const auto& name : hostLibraries) addNeeded(name); std::string guestRunPath = runPath; if (!windows) { @@ -142,9 +151,10 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path else if (!std::filesystem::path(guestRunPath).is_absolute()) throw Domain::RelinkerException("Guest Linux run path must be absolute or begin with $ORIGIN"); } std::vector artifacts; - const auto destination = std::filesystem::absolute(outputPath).parent_path() / relativeDirectory; for (const auto index : order) { const auto& image = images[index]; + const auto relativeDirectory = "app0/" + image.SourcePath.parent_path().filename().generic_string(); + const auto destination = std::filesystem::absolute(outputPath).parent_path() / relativeDirectory; const auto target = destination / image.OutputName; if (target.lexically_normal() == std::filesystem::absolute(outputPath).lexically_normal()) throw Domain::RelinkerException("Guest output collides with the executable output"); for (const auto& source : paths) if (std::filesystem::exists(target) && std::filesystem::equivalent(source, target)) throw Domain::RelinkerException("Guest output would overwrite an input module: " + target.string()); @@ -156,7 +166,10 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path if (windows) output = Elfpatcher::GuestModuleWriter().WriteWindows(image, runtime); else { std::vector needed; - for (const auto dependency : dependencies[index]) needed.push_back("$ORIGIN/" + images[dependency].OutputName); + for (const auto dependency : dependencies[index]) { + const auto dependencyPath = images[dependency].SourcePath.parent_path() / images[dependency].OutputName; + needed.push_back("$ORIGIN/" + dependencyPath.lexically_relative(image.SourcePath.parent_path()).generic_string()); + } needed.insert(needed.end(), hostLibraries.begin(), hostLibraries.end()); output = Elfpatcher::GuestModuleWriter().WriteLinux(image, needed, guestRunPath); } diff --git a/core/relinker/relinker/tests/test_guest_host_libc.py b/core/relinker/relinker/tests/test_guest_host_libc.py new file mode 100644 index 000000000..bb1c776b0 --- /dev/null +++ b/core/relinker/relinker/tests/test_guest_host_libc.py @@ -0,0 +1,67 @@ +from pathlib import Path +import struct +import subprocess +import sys +import tempfile + +from test_guest_intel_trampolines import PLAIN_SITE, guest_fixture, main_fixture +from test_guest_module_directories import needed_libraries + +DYNAMIC = 0x4800 +STRINGS = 0x4A00 + + +def importing(*libraries): + image = main_fixture() + strings = b"\0" + b"".join(name.encode() + b"\0" for name in libraries) + image[STRINGS:STRINGS + len(strings)] = strings + tags = [] + offset = 1 + for name in libraries: + tags.append((1, offset)) + offset += len(name) + 1 + tags += [(5, STRINGS), (10, len(strings)), (6, 0x4620), (11, 24), (7, 0x4700), (8, 0), (9, 24), (0, 0)] + for index, tag in enumerate(tags): + struct.pack_into("> 32, \ + f"stack not below {GUEST_ARENA_START:#x}: exit code {executed.returncode:#x}" + print("Windows address space integration tests passed") + + +if __name__ == "__main__": + main() diff --git a/core/shader/recompiler/CMakeLists.txt b/core/shader/recompiler/CMakeLists.txt index 0082a7142..50ff3356d 100644 --- a/core/shader/recompiler/CMakeLists.txt +++ b/core/shader/recompiler/CMakeLists.txt @@ -96,6 +96,7 @@ function(add_shader_recompiler target runtimeTarget) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/ConstantFolder.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/DeadCodeEliminator.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/ReadLaneEliminator.cpp + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/MaskedSelectEliminator.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/ResourceTracker.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/ResourceMaterializer.cpp ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/BindingAllocator.cpp @@ -218,6 +219,7 @@ function(add_shader_recompiler target runtimeTarget) ResourceMaterializer::SpecializationNanoseconds SetDebugProbeActive ShaderCacheDirectory + ThreadOwned WriteFileAtomically ) set(shaderCacheManifest) diff --git a/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp b/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp index 9d9ef50cb..2c5ae693d 100644 --- a/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp +++ b/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp @@ -56,6 +56,7 @@ std::uint32_t estimatedSpirvWords(const RdnaInstruction& instruction) { case RdnaOpcode::DsPermuteB32: return 1200u; case RdnaOpcode::ImageBvhIntersectRay: + case RdnaOpcode::ImageBvh64IntersectRay: return 2000u; default: break; @@ -126,7 +127,7 @@ bool resolveSetpcTarget(const RdnaProgram& program, std::uint32_t setpcIndex, st if (getProgramCounter.op == RdnaOpcode::SGetpcB64 && getProgramCounter.destination.kind == RdnaOperandKind::ScalarRegister && getProgramCounter.destination.reg == pcRegister && arithmetic.destination.kind == RdnaOperandKind::ScalarRegister && arithmetic.destination.reg == pcRegister) { std::uint32_t immediate = 0; const bool adds = arithmetic.op == RdnaOpcode::SAddU32 || arithmetic.op == RdnaOpcode::SAddI32; - const bool subtracts = arithmetic.op == RdnaOpcode::SSubU32 || arithmetic.op == RdnaOpcode::SSubI32; + const bool subtracts = (arithmetic.op == RdnaOpcode::SSubU32 || arithmetic.op == RdnaOpcode::SSubI32) && isRegister(arithmetic.source0, RdnaOperandKind::ScalarRegister, pcRegister); if ((adds || subtracts) && (isRegister(arithmetic.source0, RdnaOperandKind::ScalarRegister, pcRegister) || isRegister(arithmetic.source1, RdnaOperandKind::ScalarRegister, pcRegister)) && (isImmediate(arithmetic.source0, immediate) || isImmediate(arithmetic.source1, immediate))) { const auto base = instructionEndProgramCounter(getProgramCounter); target = (adds ? base + immediate : base - immediate) & ~3u; diff --git a/core/shader/recompiler/Deserialize.cpp b/core/shader/recompiler/Deserialize.cpp index 4e2574a14..59f3c075d 100644 --- a/core/shader/recompiler/Deserialize.cpp +++ b/core/shader/recompiler/Deserialize.cpp @@ -7,6 +7,7 @@ #include "Optimization/BindingAllocator.hpp" #include "Optimization/ConstantFolder.hpp" #include "Optimization/DeadCodeEliminator.hpp" +#include "Optimization/MaskedSelectEliminator.hpp" #include "Optimization/ReadLaneEliminator.hpp" #include "Optimization/ResourceTracker.hpp" #include "Optimization/ShaderInfoCollector.hpp" @@ -208,6 +209,13 @@ int main(int argc, char** argv) { deadCodeEliminator.Eliminate(program); } + MaskedSelectEliminator maskedSelectEliminator; + const auto maskedSelectStats = maskedSelectEliminator.Eliminate(program); + std::cout << "MaskedSelectEliminator removed " << maskedSelectStats.removedSelects << "\n"; + if (maskedSelectStats.removedSelects != 0u) { + deadCodeEliminator.Eliminate(program); + } + SrtWalker srtWalker; srtWalker.BuildPlan(program); std::cout << "SrtWalker::BuildPlan OK\n"; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index fefa0c3cb..805a366f9 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -139,8 +139,6 @@ enum class IrOpcode : std::uint16_t { CompositeExtractU32x3, CompositeExtractU32x4, PackHalf2x16, - PackSnorm2x16, - PackUnorm2x16, PackFloat2x16Rtz, FPAbs32, FPNeg32, @@ -239,7 +237,6 @@ enum class IrOpcode : std::uint16_t { FPCos, FPExp2, FPLog2, - FPLdexp, FPRoundEven32, FPFloor32, FPCeil32, @@ -248,6 +245,7 @@ enum class IrOpcode : std::uint16_t { FPAdd64, FPMul64, FPFma64, + FPFmaScale64, FPMin64, FPMax64, FPSaturate64, @@ -259,6 +257,10 @@ enum class IrOpcode : std::uint16_t { FPFract64, FPFrexpMant64, FPFrexpExp64, + FPRcp64, + FPRsq64, + FPSqrt64, + FPTrigPreop64, ConvertF32F64, ConvertF64F32, ConvertF64S32, @@ -288,6 +290,39 @@ enum class IrOpcode : std::uint16_t { StoreAddressU8, StoreAddressU16, StoreAddressU32, + AddressAtomicSwap32, + AddressAtomicCmpSwap32, + AddressAtomicIAdd32, + AddressAtomicISub32, + AddressAtomicSMin32, + AddressAtomicUMin32, + AddressAtomicSMax32, + AddressAtomicUMax32, + AddressAtomicAnd32, + AddressAtomicOr32, + AddressAtomicXor32, + AddressAtomicInc32, + AddressAtomicDec32, + AddressAtomicUSubSat32, + AddressAtomicSwap64, + AddressAtomicCmpSwap64, + AddressAtomicIAdd64, + AddressAtomicISub64, + AddressAtomicSMin64, + AddressAtomicUMin64, + AddressAtomicSMax64, + AddressAtomicUMax64, + AddressAtomicAnd64, + AddressAtomicOr64, + AddressAtomicXor64, + AddressAtomicFCmpSwap32, + AddressAtomicFMin32, + AddressAtomicFMax32, + AddressAtomicFCmpSwap64, + AddressAtomicFMin64, + AddressAtomicFMax64, + AddressAtomicInc64, + AddressAtomicDec64, LoadBufferU8, LoadBufferU16, LoadBufferU32, @@ -317,6 +352,7 @@ enum class IrOpcode : std::uint16_t { BufferAtomicFMax32, BufferAtomicInc32, BufferAtomicDec32, + BufferAtomicUSubSat32, BufferAtomicIAdd64, BufferAtomicISub64, BufferAtomicSMin64, @@ -364,6 +400,24 @@ enum class IrOpcode : std::uint16_t { SharedAtomicCmpstF32, SharedAtomicMskor32, SharedAtomicWrap32, + SharedAtomicSwap64, + SharedAtomicIAdd64, + SharedAtomicISub64, + SharedAtomicRsub64, + SharedAtomicInc64, + SharedAtomicDec64, + SharedAtomicSMin64, + SharedAtomicUMin64, + SharedAtomicSMax64, + SharedAtomicUMax64, + SharedAtomicAnd64, + SharedAtomicOr64, + SharedAtomicXor64, + SharedAtomicFMin64, + SharedAtomicFMax64, + SharedAtomicCmpst64, + SharedAtomicCmpstF64, + SharedAtomicMskor64, DataAppend, DataConsume, SwizzleU32, @@ -404,7 +458,7 @@ enum class IrOpcode : std::uint16_t { enum class BufferAccess { None, Read, Write, Atomic }; enum class SharedAccess { None, Read, Write, Atomic, Append, Consume }; -enum class AddressAccess { None, Read, Write }; +enum class AddressAccess { None, Read, Write, Atomic }; enum class ImageAccess { None, Read, Write, Atomic }; enum class ImageResourceClass { None, Sampled, Storage }; @@ -421,7 +475,8 @@ struct ImageOpcodeInfo { }; struct DppMoveFlags { - std::uint16_t control = 0; + static constexpr std::uint32_t Lanes8 = 0x1000000u; + std::uint32_t control = 0; std::uint8_t rowMask = 0xf; std::uint8_t bankMask = 0xf; bool fetchInactive = false; diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index c9b3e81ab..7057ea930 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -180,8 +180,6 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("CompositeExtractU32x3", U32, U32x3, U32), makeMeta("CompositeExtractU32x4", U32, U32x4, U32), makeMeta("PackHalf2x16", U32, F32x2), - makeMeta("PackSnorm2x16", U32, F32x2), - makeMeta("PackUnorm2x16", U32, F32x2), makeMeta("PackFloat2x16Rtz", U32, F32, F32), makeMeta("FPAbs32", F32, F32), makeMeta("FPNeg32", F32, F32), @@ -280,7 +278,6 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("FPCos", F32, F32), makeMeta("FPExp2", F32, F32), makeMeta("FPLog2", F32, F32), - makeMeta("FPLdexp", F32, F32, U32), makeMeta("FPRoundEven32", F32, F32), makeMeta("FPFloor32", F32, F32), makeMeta("FPCeil32", F32, F32), @@ -289,6 +286,7 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("FPAdd64", U64, U64, U64), makeMeta("FPMul64", U64, U64, U64), makeMeta("FPFma64", U64, U64, U64, U64), + makeMeta("FPFmaScale64", U64, U64, U64, U64, U32), makeMeta("FPMin64", U64, U64, U64), makeMeta("FPMax64", U64, U64, U64), makeMeta("FPSaturate64", U64, U64), @@ -300,6 +298,10 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("FPFract64", U64, U64), makeMeta("FPFrexpMant64", U64, U64), makeMeta("FPFrexpExp64", U32, U64), + makeMeta("FPRcp64", U64, U64), + makeMeta("FPRsq64", U64, U64), + makeMeta("FPSqrt64", U64, U64), + makeMeta("FPTrigPreop64", U64, U64, U32), makeMeta("ConvertF32F64", F32, U64), makeMeta("ConvertF64F32", U64, F32), makeMeta("ConvertF64S32", U64, U32), @@ -308,7 +310,7 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("ConvertU32F64", U32, U64), makeMeta("LaneId", U32), makeMeta("WriteLane", U32, U32, U32, U32), - makeMeta("Permlane16U32", U32, U32, U32, U32, U1), + makeMeta("Permlane16U32", U32, U32, U32, U32, U1, U32), makeMeta("BpermuteU32", U32, U32, U32, U1), makeMeta("PermuteU32", U32, U32, U32, U1), makeMeta("GetSrtResource", SrtResource), @@ -329,6 +331,39 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("StoreAddressU8", Void, AddressResource, U32, U32, U8, U1), makeMeta("StoreAddressU16", Void, AddressResource, U32, U32, U16, U1), makeMeta("StoreAddressU32", Void, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicSwap32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicCmpSwap32", U32, AddressResource, U32, U32, U32, U32, U1), + makeMeta("AddressAtomicIAdd32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicISub32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicSMin32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicUMin32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicSMax32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicUMax32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicAnd32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicOr32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicXor32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicInc32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicDec32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicUSubSat32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicSwap64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicCmpSwap64", U64, AddressResource, U32, U32, U64, U64, U1), + makeMeta("AddressAtomicIAdd64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicISub64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicSMin64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicUMin64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicSMax64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicUMax64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicAnd64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicOr64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicXor64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicFCmpSwap32", U32, AddressResource, U32, U32, U32, U32, U1), + makeMeta("AddressAtomicFMin32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicFMax32", U32, AddressResource, U32, U32, U32, U1), + makeMeta("AddressAtomicFCmpSwap64", U64, AddressResource, U32, U32, U64, U64, U1), + makeMeta("AddressAtomicFMin64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicFMax64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicInc64", U64, AddressResource, U32, U32, U64, U1), + makeMeta("AddressAtomicDec64", U64, AddressResource, U32, U32, U64, U1), makeMeta("LoadBufferU8", U8, BufferResource, U32, U32, U32, U1), makeMeta("LoadBufferU16", U16, BufferResource, U32, U32, U32, U1), makeMeta("LoadBufferU32", U32, BufferResource, U32, U32, U32, U1), @@ -358,6 +393,7 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("BufferAtomicFMax32", U32, BufferResource, U32, U32, U32, U32, U1), makeMeta("BufferAtomicInc32", U32, BufferResource, U32, U32, U32, U32, U1), makeMeta("BufferAtomicDec32", U32, BufferResource, U32, U32, U32, U32, U1), + makeMeta("BufferAtomicUSubSat32", U32, BufferResource, U32, U32, U32, U32, U1), makeMeta("BufferAtomicIAdd64", U64, BufferResource, U32, U32, U32, U64, U1), makeMeta("BufferAtomicISub64", U64, BufferResource, U32, U32, U32, U64, U1), makeMeta("BufferAtomicSMin64", U64, BufferResource, U32, U32, U32, U64, U1), @@ -405,6 +441,24 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("SharedAtomicCmpstF32", U32, U32, U32, U32, U1), makeMeta("SharedAtomicMskor32", U32, U32, U32, U32, U1), makeMeta("SharedAtomicWrap32", U32, U32, U32, U32, U1), + makeMeta("SharedAtomicSwap64", U64, U32, U64, U1), + makeMeta("SharedAtomicIAdd64", U64, U32, U64, U1), + makeMeta("SharedAtomicISub64", U64, U32, U64, U1), + makeMeta("SharedAtomicRsub64", U64, U32, U64, U1), + makeMeta("SharedAtomicInc64", U64, U32, U64, U1), + makeMeta("SharedAtomicDec64", U64, U32, U64, U1), + makeMeta("SharedAtomicSMin64", U64, U32, U64, U1), + makeMeta("SharedAtomicUMin64", U64, U32, U64, U1), + makeMeta("SharedAtomicSMax64", U64, U32, U64, U1), + makeMeta("SharedAtomicUMax64", U64, U32, U64, U1), + makeMeta("SharedAtomicAnd64", U64, U32, U64, U1), + makeMeta("SharedAtomicOr64", U64, U32, U64, U1), + makeMeta("SharedAtomicXor64", U64, U32, U64, U1), + makeMeta("SharedAtomicFMin64", U64, U32, U64, U1), + makeMeta("SharedAtomicFMax64", U64, U32, U64, U1), + makeMeta("SharedAtomicCmpst64", U64, U32, U64, U64, U1), + makeMeta("SharedAtomicCmpstF64", U64, U32, U64, U64, U1), + makeMeta("SharedAtomicMskor64", U64, U32, U64, U64, U1), makeMeta("DataAppend", U32, U32, U1, U32, U32), makeMeta("DataConsume", U32, U32, U1, U32, U32), makeMeta("SwizzleU32", U32, U32, U32, U1), @@ -504,6 +558,7 @@ BufferAccess BufferAccessOf(IrOpcode opcode) { case IrOpcode::BufferAtomicFMax32: case IrOpcode::BufferAtomicInc32: case IrOpcode::BufferAtomicDec32: + case IrOpcode::BufferAtomicUSubSat32: case IrOpcode::BufferAtomicIAdd64: case IrOpcode::BufferAtomicISub64: case IrOpcode::BufferAtomicSMin64: @@ -595,6 +650,24 @@ SharedAccess SharedAccessOf(IrOpcode opcode) { case IrOpcode::SharedAtomicCmpstF32: case IrOpcode::SharedAtomicMskor32: case IrOpcode::SharedAtomicWrap32: + case IrOpcode::SharedAtomicSwap64: + case IrOpcode::SharedAtomicIAdd64: + case IrOpcode::SharedAtomicISub64: + case IrOpcode::SharedAtomicRsub64: + case IrOpcode::SharedAtomicInc64: + case IrOpcode::SharedAtomicDec64: + case IrOpcode::SharedAtomicSMin64: + case IrOpcode::SharedAtomicUMin64: + case IrOpcode::SharedAtomicSMax64: + case IrOpcode::SharedAtomicUMax64: + case IrOpcode::SharedAtomicAnd64: + case IrOpcode::SharedAtomicOr64: + case IrOpcode::SharedAtomicXor64: + case IrOpcode::SharedAtomicFMin64: + case IrOpcode::SharedAtomicFMax64: + case IrOpcode::SharedAtomicCmpst64: + case IrOpcode::SharedAtomicCmpstF64: + case IrOpcode::SharedAtomicMskor64: return SharedAccess::Atomic; case IrOpcode::DataAppend: return SharedAccess::Append; @@ -609,6 +682,24 @@ std::uint32_t SharedComponentCount(IrOpcode opcode) { switch (opcode) { case IrOpcode::LoadSharedU32x2: case IrOpcode::WriteSharedU32x2: + case IrOpcode::SharedAtomicSwap64: + case IrOpcode::SharedAtomicIAdd64: + case IrOpcode::SharedAtomicISub64: + case IrOpcode::SharedAtomicRsub64: + case IrOpcode::SharedAtomicInc64: + case IrOpcode::SharedAtomicDec64: + case IrOpcode::SharedAtomicSMin64: + case IrOpcode::SharedAtomicUMin64: + case IrOpcode::SharedAtomicSMax64: + case IrOpcode::SharedAtomicUMax64: + case IrOpcode::SharedAtomicAnd64: + case IrOpcode::SharedAtomicOr64: + case IrOpcode::SharedAtomicXor64: + case IrOpcode::SharedAtomicFMin64: + case IrOpcode::SharedAtomicFMax64: + case IrOpcode::SharedAtomicCmpst64: + case IrOpcode::SharedAtomicCmpstF64: + case IrOpcode::SharedAtomicMskor64: return 2u; case IrOpcode::LoadSharedU32x3: case IrOpcode::WriteSharedU32x3: @@ -641,6 +732,41 @@ AddressOpcodeInfo AddressOpcodeInfoOf(IrOpcode opcode) { return {AddressAccess::Write, 16u}; case IrOpcode::StoreAddressU32: return {AddressAccess::Write, 32u}; + case IrOpcode::AddressAtomicSwap32: + case IrOpcode::AddressAtomicCmpSwap32: + case IrOpcode::AddressAtomicIAdd32: + case IrOpcode::AddressAtomicISub32: + case IrOpcode::AddressAtomicSMin32: + case IrOpcode::AddressAtomicUMin32: + case IrOpcode::AddressAtomicSMax32: + case IrOpcode::AddressAtomicUMax32: + case IrOpcode::AddressAtomicAnd32: + case IrOpcode::AddressAtomicOr32: + case IrOpcode::AddressAtomicXor32: + case IrOpcode::AddressAtomicInc32: + case IrOpcode::AddressAtomicDec32: + case IrOpcode::AddressAtomicUSubSat32: + case IrOpcode::AddressAtomicFCmpSwap32: + case IrOpcode::AddressAtomicFMin32: + case IrOpcode::AddressAtomicFMax32: + return {AddressAccess::Atomic, 32u}; + case IrOpcode::AddressAtomicSwap64: + case IrOpcode::AddressAtomicCmpSwap64: + case IrOpcode::AddressAtomicIAdd64: + case IrOpcode::AddressAtomicISub64: + case IrOpcode::AddressAtomicSMin64: + case IrOpcode::AddressAtomicUMin64: + case IrOpcode::AddressAtomicSMax64: + case IrOpcode::AddressAtomicUMax64: + case IrOpcode::AddressAtomicAnd64: + case IrOpcode::AddressAtomicOr64: + case IrOpcode::AddressAtomicXor64: + case IrOpcode::AddressAtomicFCmpSwap64: + case IrOpcode::AddressAtomicFMin64: + case IrOpcode::AddressAtomicFMax64: + case IrOpcode::AddressAtomicInc64: + case IrOpcode::AddressAtomicDec64: + return {AddressAccess::Atomic, 32u, 2u}; default: return {}; } @@ -696,7 +822,8 @@ bool IrOpcodeHasSideEffects(IrOpcode opcode) { if (sharedAccess == SharedAccess::Write || sharedAccess == SharedAccess::Atomic || sharedAccess == SharedAccess::Append || sharedAccess == SharedAccess::Consume) { return true; } - if (AddressOpcodeInfoOf(opcode).access == AddressAccess::Write) { + const AddressAccess addressAccess = AddressOpcodeInfoOf(opcode).access; + if (addressAccess == AddressAccess::Write || addressAccess == AddressAccess::Atomic) { return true; } const ImageAccess imageAccess = ImageOpcodeInfoOf(opcode).access; diff --git a/core/shader/recompiler/Optimization/include/Optimization/MaskedSelectEliminator.hpp b/core/shader/recompiler/Optimization/include/Optimization/MaskedSelectEliminator.hpp new file mode 100644 index 000000000..fad7eed9c --- /dev/null +++ b/core/shader/recompiler/Optimization/include/Optimization/MaskedSelectEliminator.hpp @@ -0,0 +1,20 @@ +#ifndef CORE_SHADER_RECOMPILIER_OPTIMIZATION_INCLUDE_OPTIMIZATION_MASKEDSELECTELIMINATOR_HPP +#define CORE_SHADER_RECOMPILIER_OPTIMIZATION_INCLUDE_OPTIMIZATION_MASKEDSELECTELIMINATOR_HPP + +#include "IntermediateRepresentation/IrProgram.hpp" +#include + +namespace ShaderRecompiler { + +struct MaskedSelectEliminationStats { + std::uint32_t removedSelects = 0; +}; + +class MaskedSelectEliminator { +public: + [[nodiscard]] MaskedSelectEliminationStats Eliminate(IrProgram& program) const; +}; + +} + +#endif diff --git a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp index 7fef993b7..2b06d89d7 100644 --- a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp +++ b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp @@ -241,6 +241,7 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con const auto& image = info.images.at(resource); physical.imageWritten.push_back(image.written || image.atomic); physical.imageDepthCompare.push_back(image.depthCompare); + physical.imageAtomic.push_back(image.atomic); } break; case DescriptorRole::GuestSamplers: diff --git a/core/shader/recompiler/Optimization/src/MaskedSelectEliminator.cpp b/core/shader/recompiler/Optimization/src/MaskedSelectEliminator.cpp new file mode 100644 index 000000000..74f4cda6f --- /dev/null +++ b/core/shader/recompiler/Optimization/src/MaskedSelectEliminator.cpp @@ -0,0 +1,394 @@ +#include "Optimization/MaskedSelectEliminator.hpp" +#include "Optimization/DeadCodeEliminator.hpp" +#include "RdnaDecoder/RdnaInstruction.hpp" +#include +#include +#include +#include +#include + +namespace ShaderRecompiler { +namespace { + +constexpr std::size_t VisitLimit = 4096; +constexpr std::uint32_t DepthLimit = 8; + +bool isSelect(IrOpcode opcode) { + return opcode == IrOpcode::SelectU1 || opcode == IrOpcode::SelectU32 || opcode == IrOpcode::SelectF32; +} + +bool isLaneLocal(IrOpcode opcode) { + if (IrOpcodeHasSideEffects(opcode)) return false; + switch (opcode) { + case IrOpcode::Identity: + case IrOpcode::IAdd32: + case IrOpcode::ISub32: + case IrOpcode::IMul32: + case IrOpcode::SelectU1: + case IrOpcode::SelectU32: + case IrOpcode::SelectF32: + case IrOpcode::BitCastU16F16: + case IrOpcode::BitCastF16U16: + case IrOpcode::BitCastU32F32: + case IrOpcode::BitCastF32U32: + case IrOpcode::ConvertU16U32: + case IrOpcode::ConvertU32U16: + case IrOpcode::ConvertU8U32: + case IrOpcode::ConvertU32U8: + case IrOpcode::ConvertF32F16: + case IrOpcode::ConvertF16F32: + case IrOpcode::ConvertS32F32: + case IrOpcode::ConvertU32F32: + case IrOpcode::ConvertF32S32: + case IrOpcode::ConvertF32U32: + case IrOpcode::ConvertF32F64: + case IrOpcode::ConvertF64F32: + case IrOpcode::ConvertF64S32: + case IrOpcode::ConvertF64U32: + case IrOpcode::ConvertS32F64: + case IrOpcode::ConvertU32F64: + case IrOpcode::CompositeConstructU64: + case IrOpcode::CompositeConstructU32x2: + case IrOpcode::CompositeConstructU32x3: + case IrOpcode::CompositeConstructF32x2: + case IrOpcode::CompositeConstructU32x4: + case IrOpcode::CompositeExtractU64: + case IrOpcode::CompositeExtractU32x2: + case IrOpcode::CompositeExtractU32x3: + case IrOpcode::CompositeExtractU32x4: + case IrOpcode::PackHalf2x16: + case IrOpcode::PackFloat2x16Rtz: + case IrOpcode::BitFieldInsert: + case IrOpcode::BitFieldUExtract: + case IrOpcode::BitFieldSExtract: + case IrOpcode::IAdd64: + case IrOpcode::IAddCarry32: + case IrOpcode::ISub64: + case IrOpcode::IMul64: + case IrOpcode::SMulHi: + case IrOpcode::UMulHi: + case IrOpcode::IAbs32: + case IrOpcode::ShiftLeftLogical32: + case IrOpcode::ShiftLeftLogical64: + case IrOpcode::ShiftRightLogical32: + case IrOpcode::ShiftRightLogical64: + case IrOpcode::ShiftRightArithmetic32: + case IrOpcode::ShiftRightArithmetic64: + case IrOpcode::BitwiseAnd32: + case IrOpcode::BitwiseAnd64: + case IrOpcode::BitwiseOr32: + case IrOpcode::BitwiseXor32: + case IrOpcode::BitwiseNot32: + case IrOpcode::BitReverse32: + case IrOpcode::BitCount32: + case IrOpcode::BitCount64: + case IrOpcode::FindUMsb32: + case IrOpcode::FindUMsb64: + case IrOpcode::FindILsb32: + case IrOpcode::SMin32: + case IrOpcode::UMin32: + case IrOpcode::SMax32: + case IrOpcode::UMax32: + case IrOpcode::SMinTri32: + case IrOpcode::UMinTri32: + case IrOpcode::SMaxTri32: + case IrOpcode::UMaxTri32: + case IrOpcode::SMedTri32: + case IrOpcode::UMedTri32: + case IrOpcode::SLessThan32: + case IrOpcode::SLessThan64: + case IrOpcode::ULessThan32: + case IrOpcode::ULessThan64: + case IrOpcode::IEqual32: + case IrOpcode::IEqual64: + case IrOpcode::SLessThanEqual32: + case IrOpcode::ULessThanEqual32: + case IrOpcode::SGreaterThan32: + case IrOpcode::UGreaterThan32: + case IrOpcode::UGreaterThan64: + case IrOpcode::INotEqual32: + case IrOpcode::INotEqual64: + case IrOpcode::SGreaterThanEqual32: + case IrOpcode::UGreaterThanEqual32: + case IrOpcode::LogicalOr: + case IrOpcode::LogicalAnd: + case IrOpcode::LogicalXor: + case IrOpcode::LogicalNot: + case IrOpcode::FPOrdEqual32: + case IrOpcode::FPUnordEqual32: + case IrOpcode::FPOrdNotEqual32: + case IrOpcode::FPUnordNotEqual32: + case IrOpcode::FPOrdLessThan32: + case IrOpcode::FPUnordLessThan32: + case IrOpcode::FPOrdGreaterThan32: + case IrOpcode::FPUnordGreaterThan32: + case IrOpcode::FPOrdLessThanEqual32: + case IrOpcode::FPUnordLessThanEqual32: + case IrOpcode::FPOrdGreaterThanEqual32: + case IrOpcode::FPUnordGreaterThanEqual32: + case IrOpcode::FPIsNan32: + case IrOpcode::FPCmpClass32: + case IrOpcode::FPAbs32: + case IrOpcode::FPNeg32: + case IrOpcode::FPSaturate32: + case IrOpcode::FPAdd32: + case IrOpcode::FPSub32: + case IrOpcode::FPFma32: + case IrOpcode::FPMad32: + case IrOpcode::FPMul32: + case IrOpcode::FPMin32: + case IrOpcode::FPMax32: + case IrOpcode::FPMinTri32: + case IrOpcode::FPMaxTri32: + case IrOpcode::FPMedTri32: + case IrOpcode::FPRecip32: + case IrOpcode::FPRecipIFlag32: + case IrOpcode::FPRecipSqrt32: + case IrOpcode::FPSqrt: + case IrOpcode::FPSin: + case IrOpcode::FPCos: + case IrOpcode::FPExp2: + case IrOpcode::FPLog2: + case IrOpcode::FPRoundEven32: + case IrOpcode::FPFloor32: + case IrOpcode::FPCeil32: + case IrOpcode::FPTrunc32: + case IrOpcode::FPFract32: + case IrOpcode::FPAdd64: + case IrOpcode::FPMul64: + case IrOpcode::FPFma64: + case IrOpcode::FPMin64: + case IrOpcode::FPMax64: + case IrOpcode::FPSaturate64: + case IrOpcode::FPLdexp64: + case IrOpcode::FPRoundEven64: + case IrOpcode::FPFloor64: + case IrOpcode::FPCeil64: + case IrOpcode::FPTrunc64: + case IrOpcode::FPFract64: + case IrOpcode::FPFrexpMant64: + case IrOpcode::FPFrexpExp64: + case IrOpcode::MakeImageAddress: + return true; + default: + return false; + } +} + +bool readsOnlyWhereActive(IrOpcode opcode) { + switch (opcode) { + case IrOpcode::LoadBufferU8: + case IrOpcode::LoadBufferU16: + case IrOpcode::LoadBufferU32: + case IrOpcode::LoadBufferU32x2: + case IrOpcode::LoadBufferU32x3: + case IrOpcode::LoadBufferU32x4: + case IrOpcode::StoreBufferU8: + case IrOpcode::StoreBufferU16: + case IrOpcode::StoreBufferU32: + case IrOpcode::StoreBufferU32x2: + case IrOpcode::StoreBufferU32x3: + case IrOpcode::StoreBufferU32x4: + case IrOpcode::LoadSharedU8: + case IrOpcode::LoadSharedU16: + case IrOpcode::LoadSharedU32: + case IrOpcode::LoadSharedU32x2: + case IrOpcode::LoadSharedU32x3: + case IrOpcode::LoadSharedU32x4: + case IrOpcode::WriteSharedU8: + case IrOpcode::WriteSharedU16: + case IrOpcode::WriteSharedU32: + case IrOpcode::WriteSharedU32x2: + case IrOpcode::WriteSharedU32x3: + case IrOpcode::WriteSharedU32x4: + case IrOpcode::SetAttribute: + return true; + default: + return false; + } +} + +bool isExplicitLodSample(const IrProgram& program, const IrValue& value) { + if (value.Opcode() != IrOpcode::ImageSampleRaw) return false; + if (program.Resources().stage != IrShaderStage::Pixel) return true; + const auto index = value.Flags().index; + if (index >= program.Resources().memoryInfo.size()) return false; + return (program.Resources().memoryInfo[index].imageSampleFlags & (RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLod | RdnaImageSampleFlagLevelZero)) != 0u; +} + +bool isImmediate(const IrValue* value, std::uint32_t immediate) { + return value->HasImmediate() && value->Type() == IrType::U32 && value->ImmediateU32() == immediate; +} + +const IrValue* otherArgument(const IrValue* value, std::uint32_t immediate) { + if (value->ArgumentCount() != 2u) return nullptr; + const IrValue* left = value->Argument(0)->Resolve(); + const IrValue* right = value->Argument(1)->Resolve(); + if (isImmediate(right, immediate)) return left; + if (isImmediate(left, immediate)) return right; + return nullptr; +} + +struct ThreadBit { + const IrValue* low = nullptr; + const IrValue* high = nullptr; +}; + +bool isLane(const IrValue* value) { + return value->Resolve()->Opcode() == IrOpcode::LaneId; +} + +std::optional threadBit(const IrValue* value, std::uint32_t waveSize) { + value = value->Resolve(); + if (value->Opcode() != IrOpcode::INotEqual32) return std::nullopt; + const IrValue* bit = otherArgument(value, 0u); + if (bit == nullptr || bit->Opcode() != IrOpcode::BitwiseAnd32) return std::nullopt; + const IrValue* shifted = otherArgument(bit, 1u); + if (shifted == nullptr || shifted->Opcode() != IrOpcode::ShiftRightLogical32) return std::nullopt; + const IrValue* amount = shifted->Argument(1)->Resolve(); + if (amount->Opcode() != IrOpcode::BitwiseAnd32) return std::nullopt; + const IrValue* lane = otherArgument(amount, 31u); + if (lane == nullptr || !isLane(lane)) return std::nullopt; + const IrValue* word = shifted->Argument(0)->Resolve(); + if (waveSize == 32u || word->HasImmediate()) return ThreadBit{word, word}; + if (word->Opcode() != IrOpcode::SelectU32) return std::nullopt; + const IrValue* half = word->Argument(0)->Resolve(); + if (half->Opcode() != IrOpcode::ULessThan32 || !isLane(half->Argument(0)) || !isImmediate(half->Argument(1)->Resolve(), 32u)) return std::nullopt; + return ThreadBit{word->Argument(1)->Resolve(), word->Argument(2)->Resolve()}; +} + +bool alwaysTrue(const IrValue* value, std::uint32_t waveSize, std::uint32_t depth = 0) { + value = value->Resolve(); + if (value->HasImmediate()) return value->Type() == IrType::Bool && value->ImmediateBool(); + if (depth > DepthLimit) return false; + if (value->Opcode() == IrOpcode::LogicalAnd) return alwaysTrue(value->Argument(0), waveSize, depth + 1u) && alwaysTrue(value->Argument(1), waveSize, depth + 1u); + const auto bit = threadBit(value, waveSize); + return bit && isImmediate(bit->low, 0xffffffffu) && isImmediate(bit->high, 0xffffffffu); +} + +bool implies(const IrValue* condition, const IrValue* mask, std::uint32_t waveSize, std::uint32_t depth = 0); + +bool wordsImply(const IrValue* low, const IrValue* high, const IrValue* mask, std::uint32_t waveSize, std::uint32_t depth) { + if (depth > DepthLimit) return false; + if (isImmediate(low, 0u) && (waveSize == 32u || isImmediate(high, 0u))) return true; + if (low->Opcode() == IrOpcode::CompositeExtractU32x4 && isImmediate(low->Argument(1)->Resolve(), 0u)) { + const IrValue* ballot = low->Argument(0)->Resolve(); + const bool sameBallot = waveSize == 32u || (high->Opcode() == IrOpcode::CompositeExtractU32x4 && high->Argument(0)->Resolve() == ballot && isImmediate(high->Argument(1)->Resolve(), 1u)); + if (ballot->Opcode() == IrOpcode::Ballot && sameBallot && implies(ballot->Argument(0), mask, waveSize, depth + 1u)) return true; + } + if (low->Opcode() != IrOpcode::BitwiseAnd32) return false; + if (waveSize == 32u) return wordsImply(low->Argument(0)->Resolve(), high, mask, waveSize, depth + 1u) || wordsImply(low->Argument(1)->Resolve(), high, mask, waveSize, depth + 1u); + if (high->Opcode() != IrOpcode::BitwiseAnd32) return false; + for (std::size_t lowIndex = 0; lowIndex < 2u; ++lowIndex) { + for (std::size_t highIndex = 0; highIndex < 2u; ++highIndex) { + if (wordsImply(low->Argument(lowIndex)->Resolve(), high->Argument(highIndex)->Resolve(), mask, waveSize, depth + 1u)) return true; + } + } + return false; +} + +bool implies(const IrValue* condition, const IrValue* mask, std::uint32_t waveSize, std::uint32_t depth) { + condition = condition->Resolve(); + if (condition == mask || alwaysTrue(mask, waveSize)) return true; + if (condition->HasImmediate()) return condition->Type() == IrType::Bool && !condition->ImmediateBool(); + if (depth > DepthLimit) return false; + if (condition->Opcode() == IrOpcode::LogicalAnd) return implies(condition->Argument(0), mask, waveSize, depth + 1u) || implies(condition->Argument(1), mask, waveSize, depth + 1u); + const auto bit = threadBit(condition, waveSize); + return bit && wordsImply(bit->low, bit->high, mask, waveSize, depth + 1u); +} + +std::unordered_set blocksOutsideLoops(const IrProgram& program) { + const auto& order = program.BlockOrder(); + if (order.empty()) return {}; + std::unordered_map state; + std::unordered_set looping; + std::vector> stack; + state[order.front()] = 1; + stack.emplace_back(order.front(), 0u); + while (!stack.empty()) { + auto& [block, next] = stack.back(); + if (next == block->Successors().size()) { + state[block] = 2; + stack.pop_back(); + continue; + } + const IrBlock* source = block; + const IrBlock* successor = block->Successors()[next++]; + int& successorState = state[successor]; + if (successorState == 0) { + successorState = 1; + stack.emplace_back(successor, 0u); + } else if (successorState == 1) { + std::unordered_set body{successor}; + std::vector pending{source}; + while (!pending.empty()) { + const IrBlock* member = pending.back(); + pending.pop_back(); + if (!body.insert(member).second) continue; + for (const IrBlock* predecessor : member->Predecessors()) pending.push_back(predecessor); + } + looping.insert(body.begin(), body.end()); + } + } + std::unordered_set result; + for (const auto& [block, visited] : state) { + if (visited == 2 && !looping.contains(block)) result.insert(block); + } + return result; +} + +bool unobservedWhereMasked(const IrProgram& program, const std::unordered_set& once, const IrValue& select, const IrValue* mask) { + const auto waveSize = program.WaveSize(); + std::vector pending{&select}; + std::unordered_set visited{&select}; + while (!pending.empty()) { + const IrValue* value = pending.back(); + pending.pop_back(); + for (const IrUse& use : value->OperandUses()) { + const IrValue* user = use.user; + if (user->Parent() == nullptr || !once.contains(user->Parent())) return false; + if (isSelect(user->Opcode()) && use.operand == 1u && implies(user->Argument(0), mask, waveSize)) continue; + if (user->Opcode() == IrOpcode::LogicalAnd && use.operand < 2u && implies(user->Argument(1u - use.operand), mask, waveSize)) continue; + if (readsOnlyWhereActive(user->Opcode()) && use.operand + 1u < user->ArgumentCount() && implies(user->Argument(user->ArgumentCount() - 1u), mask, waveSize)) continue; + if (!user->IsPhi() && !isLaneLocal(user->Opcode()) && !isExplicitLodSample(program, *user)) return false; + if (visited.insert(user).second) { + if (visited.size() > VisitLimit) return false; + pending.push_back(user); + } + } + } + return true; +} + +} + +MaskedSelectEliminationStats MaskedSelectEliminator::Eliminate(IrProgram& program) const { + MaskedSelectEliminationStats stats; + const auto once = blocksOutsideLoops(program); + bool changed = true; + while (changed) { + changed = false; + for (auto blockIt = program.BlockOrder().rbegin(); blockIt != program.BlockOrder().rend(); ++blockIt) { + const bool single = once.contains(*blockIt); + auto& instructions = (*blockIt)->Instructions(); + auto it = instructions.end(); + while (it != instructions.begin()) { + --it; + IrValue* inst = *it; + if (!isSelect(inst->Opcode()) || inst->ArgumentCount() != 3u || !inst->HasUses()) continue; + const IrValue* mask = inst->Argument(0)->Resolve(); + if (!alwaysTrue(mask, program.WaveSize()) && (!single || !unobservedWhereMasked(program, once, *inst, mask))) continue; + inst->ReplaceAllUsesWith(inst->Argument(1)); + inst->Invalidate(); + inst->SetParent(nullptr); + it = instructions.erase(it); + ++stats.removedSelects; + changed = true; + } + } + if (changed) DeadCodeEliminator{}.Eliminate(program); + } + return stats; +} + +} diff --git a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp index 4308347e1..943b3e14e 100644 --- a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp @@ -151,8 +151,8 @@ DecodedImage decodeImageDescriptor(const DescriptorValue& descriptor, const Imag decoded.dimension = descriptorDimension(descriptor, base.dimension); decoded.cube = descriptorIsCube(descriptor); const auto format = rawImageFormat(descriptor); - if (base.atomic && format != IrBufferFormat::Format32UInt) { - throw std::runtime_error("atomic image descriptor uses an unsupported format"); + if (base.atomic && format != IrBufferFormat::Format32UInt && format != IrBufferFormat::Format32SInt && format != IrBufferFormat::Format32Float) { + throw std::runtime_error("atomic image descriptor uses an unsupported format " + std::to_string(static_cast(format))); } const bool storage = base.resourceClass == ImageResourceClass::Storage; decoded.fmask = IsFmaskTextureFormat(format); @@ -170,7 +170,7 @@ DecodedImage decodeImageDescriptor(const DescriptorValue& descriptor, const Imag decoded.shaderSwizzle = descriptorImageSwizzle(descriptor); } const bool rawSintStorage = storage && format == IrBufferFormat::Format32SInt && base.written && !base.read && !base.atomic; - decoded.numericClass = SampledTextureNumericClass(format); + decoded.numericClass = base.atomic ? IrTextureNumericClass::Uint : SampledTextureNumericClass(format); if (!storage && !base.depthCompare && IsDepthBitsTexture(descriptor.dwords[1], descriptor.dwords[3])) { decoded.depthBits = true; decoded.depthUnorm16 = DepthBitsTextureWidth(descriptor.dwords[1], descriptor.dwords[3]) == 16u; diff --git a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp index 450a60b45..bcd33a91c 100644 --- a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp @@ -1016,6 +1016,7 @@ private: std::fprintf(stderr, "[bda] %s at pc 0x%08x: %s access, offset %s%s\n", std::string(IrOpcodeName(op)).c_str(), flags.pc, kind, immediateOffset ? "immediate" : "dynamic", memory.kind == ResourceKind::ScalarAddress && !immediateOffset ? " (a register offset is not planned by the SRT walker)" : ""); } m_info.usesDma = true; + m_info.bdaWrites = m_info.bdaWrites || addressInfo.access == AddressAccess::Write || addressInfo.access == AddressAccess::Atomic; return; } diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp index e27d5fb18..1c7598815 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaInstruction.hpp @@ -73,8 +73,12 @@ enum RdnaImageSampleFlag : std::uint32_t { RdnaImageSampleFlagCd = 1u << 8u, RdnaImageSampleFlagGatherHorizontal = 1u << 9u, RdnaImageSampleFlagAdjust = 1u << 10u, + RdnaImageSampleFlagG16 = 1u << 11u, }; +inline constexpr std::uint32_t RdnaImageSampleGradientCountShift = 12u; +inline constexpr std::uint32_t RdnaImageSampleGradientCountMask = 3u << RdnaImageSampleGradientCountShift; + inline constexpr std::uint32_t MaxRdnaInstructionRawWords = 5; inline constexpr std::uint32_t MaxRdnaImageNsaAddressComponents = 12; @@ -100,6 +104,7 @@ struct RdnaOperand { bool absolute = false; bool clamp = false; bool dpp = false; + bool dpp8 = false; }; struct RdnaInstruction { diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 5ed43f202..23d5277fa 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -140,6 +140,30 @@ enum class RdnaOpcode : std::uint16_t { ImageSampleDO, ImageSampleLzO, ImageSampleO, + ImageSampleCd, + ImageSampleCdCl, + ImageSampleCCd, + ImageSampleCCdCl, + ImageSampleCdO, + ImageSampleCdClO, + ImageSampleCCdO, + ImageSampleCCdClO, + ImageSampleDG16, + ImageSampleDClG16, + ImageSampleCDG16, + ImageSampleCDClG16, + ImageSampleDOG16, + ImageSampleDClOG16, + ImageSampleCDOG16, + ImageSampleCDClOG16, + ImageSampleCdG16, + ImageSampleCdClG16, + ImageSampleCCdG16, + ImageSampleCCdClG16, + ImageSampleCdOG16, + ImageSampleCdClOG16, + ImageSampleCCdOG16, + ImageSampleCCdClOG16, ImageLoad, ImageStore, ImageGetResinfo, @@ -279,6 +303,7 @@ enum class RdnaOpcode : std::uint16_t { VCubetcF32, VCubemaF32, VDot2cF32F16, + VDot2F32F16, VDot4cI32I8, VDot2I32I16, VDot2U32U16, @@ -411,6 +436,13 @@ enum class RdnaOpcode : std::uint16_t { VDivScaleF32, VDivFmasF32, VDivFixupF32, + VRcpF64, + VRsqF64, + VSqrtF64, + VTrigPreopF64, + VDivScaleF64, + VDivFmasF64, + VDivFixupF64, VCvtPknormI16F32, VCvtPknormU16F32, VCvtPkU16U32, @@ -744,6 +776,7 @@ enum class RdnaOpcode : std::uint16_t { BufferAtomicXor, BufferAtomicFmin, BufferAtomicFmax, + BufferAtomicCsub, FlatLoadUbyte, FlatLoadSbyte, FlatLoadUshort, @@ -767,6 +800,40 @@ enum class RdnaOpcode : std::uint16_t { FlatStoreDwordx2, FlatStoreDwordx3, FlatStoreDwordx4, + FlatAtomicSwap, + FlatAtomicCmpswap, + FlatAtomicAdd, + FlatAtomicSub, + FlatAtomicSmin, + FlatAtomicUmin, + FlatAtomicSmax, + FlatAtomicUmax, + FlatAtomicAnd, + FlatAtomicOr, + FlatAtomicXor, + FlatAtomicInc, + FlatAtomicDec, + FlatAtomicSwapX2, + FlatAtomicCmpswapX2, + FlatAtomicAddX2, + FlatAtomicSubX2, + FlatAtomicSminX2, + FlatAtomicUminX2, + FlatAtomicSmaxX2, + FlatAtomicUmaxX2, + FlatAtomicAndX2, + FlatAtomicOrX2, + FlatAtomicXorX2, + FlatAtomicFcmpswap, + FlatAtomicFmin, + FlatAtomicFmax, + FlatAtomicFcmpswapX2, + FlatAtomicFminX2, + FlatAtomicFmaxX2, + FlatAtomicIncX2, + FlatAtomicDecX2, + GlobalAtomicCsub, + GlobalStoreDwordAddtid, DsAddU32, DsAddRtnU32, DsSubU32, @@ -826,6 +893,41 @@ enum class RdnaOpcode : std::uint16_t { DsCmpstRtnB32, DsCmpstRtnF32, DsWrapRtnB32, + DsAddU64, + DsSubU64, + DsRsubU64, + DsIncU64, + DsDecU64, + DsMinI64, + DsMaxI64, + DsMinU64, + DsMaxU64, + DsAndB64, + DsOrB64, + DsXorB64, + DsMskorB64, + DsCmpstB64, + DsCmpstF64, + DsMinF64, + DsMaxF64, + DsAddRtnU64, + DsSubRtnU64, + DsRsubRtnU64, + DsIncRtnU64, + DsDecRtnU64, + DsMinRtnI64, + DsMaxRtnI64, + DsMinRtnU64, + DsMaxRtnU64, + DsAndRtnB64, + DsOrRtnB64, + DsXorRtnB64, + DsMskorRtnB64, + DsWrxchgRtnB64, + DsCmpstRtnB64, + DsCmpstRtnF64, + DsMinRtnF64, + DsMaxRtnF64, DsNop, DsReadU8D16, DsReadU8D16Hi, @@ -892,11 +994,13 @@ enum class RdnaOpcode : std::uint16_t { ImageGather4LO, ImageGather4O, ImageBvhIntersectRay, + ImageBvh64IntersectRay, VInterpP1F32, VInterpP2F32, VInterpMovF32, SWaitcntDepctr, SSendmsg, + SGetregB32, SSetregB32, SSetregImm32B32, SCmovkI32, @@ -912,11 +1016,16 @@ enum class RdnaOpcode : std::uint16_t { SIncperflevel, SDecperflevel, SWaitIdle, + SRoundMode, SGl1Inv, SDcacheInv, SDcacheWb, SMemtime, SMemrealtime, + SAtcProbe, + SAtcProbeBuffer, + SDcacheDiscard, + SDcacheDiscardX2, BufferGl0Inv, BufferGl1Inv, SDenormMode, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp index 6961f7f1e..53e957a5e 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp @@ -40,7 +40,30 @@ constexpr ImageOpcodeInfo imageOpcodes[] = { {0x35u, RdnaOpcode::ImageSampleBO, "image_sample_b_o", RdnaImageSampleFlagBias | RdnaImageSampleFlagOffset, true, false, false}, {0x37u, RdnaOpcode::ImageSampleLzO, "image_sample_lz_o", RdnaImageSampleFlagLevelZero | RdnaImageSampleFlagOffset, true, false, false}, {0x38u, RdnaOpcode::ImageSampleCO, "image_sample_c_o", RdnaImageSampleFlagCompare | RdnaImageSampleFlagOffset, true, false, false}, - {0x68u, RdnaOpcode::ImageSample, "image_sample_cd", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd, true, false, false}, + {0x68u, RdnaOpcode::ImageSampleCd, "image_sample_cd", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd, true, false, false}, + {0x69u, RdnaOpcode::ImageSampleCdCl, "image_sample_cd_cl", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagCd, true, false, false}, + {0x6au, RdnaOpcode::ImageSampleCCd, "image_sample_c_cd", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd, true, false, false}, + {0x6bu, RdnaOpcode::ImageSampleCCdCl, "image_sample_c_cd_cl", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagCd, true, false, false}, + {0x6cu, RdnaOpcode::ImageSampleCdO, "image_sample_cd_o", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd, true, false, false}, + {0x6du, RdnaOpcode::ImageSampleCdClO, "image_sample_cd_cl_o", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd, true, false, false}, + {0x6eu, RdnaOpcode::ImageSampleCCdO, "image_sample_c_cd_o", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd, true, false, false}, + {0x6fu, RdnaOpcode::ImageSampleCCdClO, "image_sample_c_cd_cl_o", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd, true, false, false}, + {0xa2u, RdnaOpcode::ImageSampleDG16, "image_sample_d_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagG16, true, false, false}, + {0xa3u, RdnaOpcode::ImageSampleDClG16, "image_sample_d_cl_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagG16, true, false, false}, + {0xaau, RdnaOpcode::ImageSampleCDG16, "image_sample_c_d_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagG16, true, false, false}, + {0xabu, RdnaOpcode::ImageSampleCDClG16, "image_sample_c_d_cl_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagG16, true, false, false}, + {0xb2u, RdnaOpcode::ImageSampleDOG16, "image_sample_d_o_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagG16, true, false, false}, + {0xb3u, RdnaOpcode::ImageSampleDClOG16, "image_sample_d_cl_o_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagG16, true, false, false}, + {0xbau, RdnaOpcode::ImageSampleCDOG16, "image_sample_c_d_o_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagG16, true, false, false}, + {0xbbu, RdnaOpcode::ImageSampleCDClOG16, "image_sample_c_d_cl_o_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagG16, true, false, false}, + {0xe8u, RdnaOpcode::ImageSampleCdG16, "image_sample_cd_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xe9u, RdnaOpcode::ImageSampleCdClG16, "image_sample_cd_cl_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xeau, RdnaOpcode::ImageSampleCCdG16, "image_sample_c_cd_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xebu, RdnaOpcode::ImageSampleCCdClG16, "image_sample_c_cd_cl_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xecu, RdnaOpcode::ImageSampleCdOG16, "image_sample_cd_o_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xedu, RdnaOpcode::ImageSampleCdClOG16, "image_sample_cd_cl_o_g16", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xeeu, RdnaOpcode::ImageSampleCCdOG16, "image_sample_c_cd_o_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, + {0xefu, RdnaOpcode::ImageSampleCCdClOG16, "image_sample_c_cd_cl_o_g16", RdnaImageSampleFlagCompare | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset | RdnaImageSampleFlagCd | RdnaImageSampleFlagG16, true, false, false}, {0x33u, RdnaOpcode::ImageSample, "image_sample_d_cl_o", RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagOffset, true, false, false}, {0xa0u, RdnaOpcode::ImageSample, "image_sample_a", RdnaImageSampleFlagAdjust, true, false, false}, {0xa1u, RdnaOpcode::ImageSample, "image_sample_cl_a", RdnaImageSampleFlagLodClamp | RdnaImageSampleFlagAdjust, true, false, false}, @@ -111,6 +134,7 @@ constexpr ImageOpcodeInfo imageOpcodes[] = { {0x0eu, RdnaOpcode::ImageGetResinfo, nullptr, 0, false, false, false}, {0x60u, RdnaOpcode::ImageGetLod, nullptr, 0, false, false, false}, {0xe6u, RdnaOpcode::ImageBvhIntersectRay, "image_bvh_intersect_ray", 0, false, false, false}, + {0xe7u, RdnaOpcode::ImageBvh64IntersectRay, "image_bvh64_intersect_ray", 0, false, false, false}, }; const ImageOpcodeInfo& lookupOpcode(std::uint32_t opcode) { @@ -123,12 +147,12 @@ const ImageOpcodeInfo& lookupOpcode(std::uint32_t opcode) { } void validateFlags(std::uint32_t flags) { - constexpr std::uint32_t known = (1u << 11u) - 1u; + constexpr std::uint32_t known = ((1u << 12u) - 1u) | RdnaImageSampleGradientCountMask; if ((flags & ~known) != 0u) { throw std::runtime_error("unknown image address flags"); } - if ((flags & RdnaImageSampleFlagCd) != 0u) { - throw std::runtime_error("unsupported image coarse derivative (_cd) address layout"); + if (((flags & RdnaImageSampleGradientCountMask) != 0u) != ((flags & RdnaImageSampleFlagDerivative) != 0u)) { + throw std::runtime_error("image derivative flags without a gradient count"); } const auto lodModes = flags & (RdnaImageSampleFlagLod | RdnaImageSampleFlagBias | RdnaImageSampleFlagDerivative | RdnaImageSampleFlagLevelZero); if (std::popcount(lodModes) > 1) { @@ -136,27 +160,45 @@ void validateFlags(std::uint32_t flags) { } } -std::uint32_t componentWidth(std::uint32_t flags, std::uint32_t component) { - if ((flags & RdnaImageSampleFlagA16) == 0u) { - return 32; - } +struct ComponentShape { + std::uint32_t width; + bool startsDword; +}; + +ComponentShape componentShape(std::uint32_t flags, std::uint32_t component) { + const bool a16 = (flags & RdnaImageSampleFlagA16) != 0u; std::uint32_t cursor = 0; if ((flags & RdnaImageSampleFlagOffset) != 0u) { if (component == cursor) { - return 32; + return {32, true}; } ++cursor; } if ((flags & RdnaImageSampleFlagBias) != 0u) { if (component == cursor) { - return 16; + return {a16 ? 16u : 32u, !a16}; } ++cursor; } - if ((flags & RdnaImageSampleFlagCompare) != 0u && component == cursor) { - return 32; + if ((flags & RdnaImageSampleFlagCompare) != 0u) { + if (component == cursor) { + return {32, true}; + } + ++cursor; } - return 16; + const auto gradients = (flags & RdnaImageSampleGradientCountMask) >> RdnaImageSampleGradientCountShift; + if (component < cursor + gradients * 2u) { + if ((flags & RdnaImageSampleFlagG16) == 0u) { + return {32, true}; + } + return {16, (component - cursor) % gradients == 0u}; + } + cursor += gradients * 2u; + return {a16 ? 16u : 32u, !a16 || (gradients != 0u && component == cursor)}; +} + +bool isBiasComponent(std::uint32_t flags, std::uint32_t component) { + return (flags & RdnaImageSampleFlagBias) != 0u && component == ((flags & RdnaImageSampleFlagOffset) != 0u ? 1u : 0u); } RdnaImageDimension decodeDimension(std::uint32_t dimension) { @@ -231,14 +273,14 @@ RdnaImageAddressComponent GetRdnaImageAddressComponentLayout(std::uint32_t flags } std::uint32_t offset = 0; for (std::uint32_t index = 0; index <= component; ++index) { - const auto width = componentWidth(flags, index); - if (width == 32u) { + const auto shape = componentShape(flags, index); + if (shape.startsDword) { offset = (offset + 31u) & ~31u; } if (index == component) { - return {offset, width}; + return {offset, shape.width}; } - offset += width; + offset += isBiasComponent(flags, index) ? 32u : shape.width; } throw std::runtime_error("invalid image address component"); } @@ -271,7 +313,9 @@ RdnaInstruction DecodeRdnaMimg(std::uint32_t programCounter, std::span> 18u) & 0x7Fu) | ((word0 & 1u) << 7u); const auto& info = lookupOpcode(opcode); - const auto reservedWord0 = info.sample || info.gather ? 0x00035040u : info.atomic ? 0x000340C0u : 0x00034040u; + const bool samplerOp = info.sample || info.gather || opcode == 0x60u; + const auto texelStatusBits = samplerOp ? 0x00010000u : opcode <= 0x0eu ? 0x00030000u : 0u; + const auto reservedWord0 = (info.sample || info.gather ? 0x00035040u : info.atomic ? 0x000340C0u : 0x00034040u) & ~texelStatusBits; if ((word0 & reservedWord0) != 0u || (word1 & 0x3C000000u) != 0u) { char message[96]; std::snprintf(message, sizeof(message), "unsupported or reserved MIMG control bits (words %08x %08x)", word0, word1); @@ -287,13 +331,16 @@ RdnaInstruction DecodeRdnaMimg(std::uint32_t programCounter, std::span> 3u) & 7u); const bool multisampled = dimension == RdnaImageDimension::Dim2DMsaa || dimension == RdnaImageDimension::Dim2DMsaaArray; if (multisampled && (info.sample || info.gather || opcode == 0x60u || (opcode >= 1u && opcode <= 5u) || opcode == 9u || opcode == 0x0bu)) { throw std::runtime_error("unsupported multisampled MIMG operation"); } + auto flags = info.flags | (a16 ? RdnaImageSampleFlagA16 : 0u); + if ((info.flags & RdnaImageSampleFlagDerivative) != 0u) { + flags |= gradientCount(dimension) << RdnaImageSampleGradientCountShift; + } + validateFlags(flags); const auto dmask = (word0 >> 8u) & 15u; const bool compareSwap = info.opcode == RdnaOpcode::ImageAtomicCmpswap || info.opcode == RdnaOpcode::ImageAtomicFcmpswap; if (dmask == 0u || (compareSwap ? dmask != 3u : (info.gather || info.atomic) && !std::has_single_bit(dmask))) { @@ -302,8 +349,9 @@ RdnaInstruction DecodeRdnaMimg(std::uint32_t programCounter, std::span(std::popcount(dmask)); const auto dataDwords = d16 ? (dataComponents + 1u) / 2u : dataComponents; - if (dataDwords > 256u - vdata) { + const auto statusDwords = (word0 & 0x00010000u) != 0u ? 1u : 0u; + if (dataDwords + statusDwords > 256u - vdata) { throw std::runtime_error("MIMG data register range overflow"); } const auto resource = ((word1 >> 16u) & 31u) * 4u; diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index 3d40b4cf9..2544e10eb 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace ShaderRecompiler { @@ -102,6 +103,7 @@ constexpr MemoryOpcodeInfo mubufOpcodes[] = { {0x31u, RdnaOpcode::BufferAtomicCmpswap, 1, 32, false, false, false}, {0x32u, RdnaOpcode::BufferAtomicAdd, 1, 32, false, false, false}, {0x33u, RdnaOpcode::BufferAtomicSub, 1, 32, false, false, false}, + {0x34u, RdnaOpcode::BufferAtomicCsub, 1, 32, false, false, false}, {0x35u, RdnaOpcode::BufferAtomicSmin, 1, 32, false, false, false}, {0x36u, RdnaOpcode::BufferAtomicUmin, 1, 32, false, false, false}, {0x37u, RdnaOpcode::BufferAtomicSmax, 1, 32, false, false, false}, @@ -169,6 +171,7 @@ constexpr MemoryOpcodeInfo flatOpcodes[] = { {0x0eu, RdnaOpcode::FlatLoadDwordx4, 4, 32, false, false, false}, {0x0fu, RdnaOpcode::FlatLoadDwordx3, 3, 32, false, false, false}, {0x16u, RdnaOpcode::GlobalLoadDwordAddtid, 1, 32, false, false, false}, + {0x17u, RdnaOpcode::GlobalStoreDwordAddtid, 1, 32, false, false, false}, {0x18u, RdnaOpcode::FlatStoreByte, 1, 8, false, false, false}, {0x1au, RdnaOpcode::FlatStoreShort, 1, 16, false, false, false}, {0x1cu, RdnaOpcode::FlatStoreDword, 1, 32, false, false, false}, @@ -183,6 +186,39 @@ constexpr MemoryOpcodeInfo flatOpcodes[] = { {0x25u, RdnaOpcode::FlatLoadShortD16Hi, 1, 16, false, false, false}, {0x19u, RdnaOpcode::FlatStoreByteD16Hi, 1, 8, false, false, false}, {0x1bu, RdnaOpcode::FlatStoreShortD16Hi, 1, 16, false, false, false}, + {0x30u, RdnaOpcode::FlatAtomicSwap, 1, 32, false, false, false}, + {0x31u, RdnaOpcode::FlatAtomicCmpswap, 1, 32, false, false, false}, + {0x32u, RdnaOpcode::FlatAtomicAdd, 1, 32, false, false, false}, + {0x33u, RdnaOpcode::FlatAtomicSub, 1, 32, false, false, false}, + {0x34u, RdnaOpcode::GlobalAtomicCsub, 1, 32, false, false, false}, + {0x35u, RdnaOpcode::FlatAtomicSmin, 1, 32, false, false, false}, + {0x36u, RdnaOpcode::FlatAtomicUmin, 1, 32, false, false, false}, + {0x37u, RdnaOpcode::FlatAtomicSmax, 1, 32, false, false, false}, + {0x38u, RdnaOpcode::FlatAtomicUmax, 1, 32, false, false, false}, + {0x39u, RdnaOpcode::FlatAtomicAnd, 1, 32, false, false, false}, + {0x3au, RdnaOpcode::FlatAtomicOr, 1, 32, false, false, false}, + {0x3bu, RdnaOpcode::FlatAtomicXor, 1, 32, false, false, false}, + {0x3cu, RdnaOpcode::FlatAtomicInc, 1, 32, false, false, false}, + {0x3du, RdnaOpcode::FlatAtomicDec, 1, 32, false, false, false}, + {0x50u, RdnaOpcode::FlatAtomicSwapX2, 2, 32, false, false, false}, + {0x51u, RdnaOpcode::FlatAtomicCmpswapX2, 2, 32, false, false, false}, + {0x52u, RdnaOpcode::FlatAtomicAddX2, 2, 32, false, false, false}, + {0x53u, RdnaOpcode::FlatAtomicSubX2, 2, 32, false, false, false}, + {0x55u, RdnaOpcode::FlatAtomicSminX2, 2, 32, false, false, false}, + {0x56u, RdnaOpcode::FlatAtomicUminX2, 2, 32, false, false, false}, + {0x57u, RdnaOpcode::FlatAtomicSmaxX2, 2, 32, false, false, false}, + {0x58u, RdnaOpcode::FlatAtomicUmaxX2, 2, 32, false, false, false}, + {0x59u, RdnaOpcode::FlatAtomicAndX2, 2, 32, false, false, false}, + {0x5au, RdnaOpcode::FlatAtomicOrX2, 2, 32, false, false, false}, + {0x5bu, RdnaOpcode::FlatAtomicXorX2, 2, 32, false, false, false}, + {0x3eu, RdnaOpcode::FlatAtomicFcmpswap, 1, 32, false, false, false}, + {0x3fu, RdnaOpcode::FlatAtomicFmin, 1, 32, false, false, false}, + {0x40u, RdnaOpcode::FlatAtomicFmax, 1, 32, false, false, false}, + {0x5eu, RdnaOpcode::FlatAtomicFcmpswapX2, 2, 32, false, false, false}, + {0x5fu, RdnaOpcode::FlatAtomicFminX2, 2, 32, false, false, false}, + {0x60u, RdnaOpcode::FlatAtomicFmaxX2, 2, 32, false, false, false}, + {0x5cu, RdnaOpcode::FlatAtomicIncX2, 2, 32, false, false, false}, + {0x5du, RdnaOpcode::FlatAtomicDecX2, 2, 32, false, false, false}, }; constexpr MemoryOpcodeInfo dsOpcodes[] = { @@ -203,6 +239,41 @@ constexpr MemoryOpcodeInfo dsOpcodes[] = { {0x33u, RdnaOpcode::DsMaxRtnF32, 1, 32, false, false, false}, {0x34u, RdnaOpcode::DsWrapRtnB32, 1, 32, false, false, false}, {0x55u, RdnaOpcode::DsAddRtnF32, 1, 32, false, false, false}, + {0x40u, RdnaOpcode::DsAddU64, 2, 32, false, false, false}, + {0x41u, RdnaOpcode::DsSubU64, 2, 32, false, false, false}, + {0x42u, RdnaOpcode::DsRsubU64, 2, 32, false, false, false}, + {0x43u, RdnaOpcode::DsIncU64, 2, 32, false, false, false}, + {0x44u, RdnaOpcode::DsDecU64, 2, 32, false, false, false}, + {0x45u, RdnaOpcode::DsMinI64, 2, 32, false, false, false}, + {0x46u, RdnaOpcode::DsMaxI64, 2, 32, false, false, false}, + {0x47u, RdnaOpcode::DsMinU64, 2, 32, false, false, false}, + {0x48u, RdnaOpcode::DsMaxU64, 2, 32, false, false, false}, + {0x49u, RdnaOpcode::DsAndB64, 2, 32, false, false, false}, + {0x4au, RdnaOpcode::DsOrB64, 2, 32, false, false, false}, + {0x4bu, RdnaOpcode::DsXorB64, 2, 32, false, false, false}, + {0x4cu, RdnaOpcode::DsMskorB64, 2, 32, false, false, false}, + {0x50u, RdnaOpcode::DsCmpstB64, 2, 32, false, false, false}, + {0x51u, RdnaOpcode::DsCmpstF64, 2, 32, false, false, false}, + {0x52u, RdnaOpcode::DsMinF64, 2, 32, false, false, false}, + {0x53u, RdnaOpcode::DsMaxF64, 2, 32, false, false, false}, + {0x60u, RdnaOpcode::DsAddRtnU64, 2, 32, false, false, false}, + {0x61u, RdnaOpcode::DsSubRtnU64, 2, 32, false, false, false}, + {0x62u, RdnaOpcode::DsRsubRtnU64, 2, 32, false, false, false}, + {0x63u, RdnaOpcode::DsIncRtnU64, 2, 32, false, false, false}, + {0x64u, RdnaOpcode::DsDecRtnU64, 2, 32, false, false, false}, + {0x65u, RdnaOpcode::DsMinRtnI64, 2, 32, false, false, false}, + {0x66u, RdnaOpcode::DsMaxRtnI64, 2, 32, false, false, false}, + {0x67u, RdnaOpcode::DsMinRtnU64, 2, 32, false, false, false}, + {0x68u, RdnaOpcode::DsMaxRtnU64, 2, 32, false, false, false}, + {0x69u, RdnaOpcode::DsAndRtnB64, 2, 32, false, false, false}, + {0x6au, RdnaOpcode::DsOrRtnB64, 2, 32, false, false, false}, + {0x6bu, RdnaOpcode::DsXorRtnB64, 2, 32, false, false, false}, + {0x6cu, RdnaOpcode::DsMskorRtnB64, 2, 32, false, false, false}, + {0x6du, RdnaOpcode::DsWrxchgRtnB64, 2, 32, false, false, false}, + {0x70u, RdnaOpcode::DsCmpstRtnB64, 2, 32, false, false, false}, + {0x71u, RdnaOpcode::DsCmpstRtnF64, 2, 32, false, false, false}, + {0x72u, RdnaOpcode::DsMinRtnF64, 2, 32, false, false, false}, + {0x73u, RdnaOpcode::DsMaxRtnF64, 2, 32, false, false, false}, {0x01u, RdnaOpcode::DsSubU32, 1, 32, false, false, false}, {0x05u, RdnaOpcode::DsMinI32, 1, 32, false, false, false}, {0x06u, RdnaOpcode::DsMaxI32, 1, 32, false, false, false}, @@ -418,6 +489,35 @@ bool isDsAtomicOpcode(RdnaOpcode opcode) { case RdnaOpcode::DsMinRtnF32: case RdnaOpcode::DsMaxRtnF32: case RdnaOpcode::DsAddRtnF32: + case RdnaOpcode::DsAddU64: + case RdnaOpcode::DsSubU64: + case RdnaOpcode::DsRsubU64: + case RdnaOpcode::DsIncU64: + case RdnaOpcode::DsDecU64: + case RdnaOpcode::DsMinI64: + case RdnaOpcode::DsMaxI64: + case RdnaOpcode::DsMinU64: + case RdnaOpcode::DsMaxU64: + case RdnaOpcode::DsAndB64: + case RdnaOpcode::DsOrB64: + case RdnaOpcode::DsXorB64: + case RdnaOpcode::DsMinF64: + case RdnaOpcode::DsMaxF64: + case RdnaOpcode::DsAddRtnU64: + case RdnaOpcode::DsSubRtnU64: + case RdnaOpcode::DsRsubRtnU64: + case RdnaOpcode::DsIncRtnU64: + case RdnaOpcode::DsDecRtnU64: + case RdnaOpcode::DsMinRtnI64: + case RdnaOpcode::DsMaxRtnI64: + case RdnaOpcode::DsMinRtnU64: + case RdnaOpcode::DsMaxRtnU64: + case RdnaOpcode::DsAndRtnB64: + case RdnaOpcode::DsOrRtnB64: + case RdnaOpcode::DsXorRtnB64: + case RdnaOpcode::DsWrxchgRtnB64: + case RdnaOpcode::DsMinRtnF64: + case RdnaOpcode::DsMaxRtnF64: return true; default: return false; } @@ -435,7 +535,13 @@ std::uint32_t dsSourceCount(RdnaOpcode opcode) { case RdnaOpcode::DsMskorRtnB32: case RdnaOpcode::DsCmpstRtnB32: case RdnaOpcode::DsCmpstRtnF32: - case RdnaOpcode::DsWrapRtnB32: return 3u; + case RdnaOpcode::DsWrapRtnB32: + case RdnaOpcode::DsMskorB64: + case RdnaOpcode::DsCmpstB64: + case RdnaOpcode::DsCmpstF64: + case RdnaOpcode::DsMskorRtnB64: + case RdnaOpcode::DsCmpstRtnB64: + case RdnaOpcode::DsCmpstRtnF64: return 3u; case RdnaOpcode::DsMinF32: case RdnaOpcode::DsMaxF32: return 2u; case RdnaOpcode::DsNop: return 0u; @@ -457,7 +563,48 @@ bool isFlatStoreOpcode(RdnaOpcode opcode) { case RdnaOpcode::FlatStoreDword: case RdnaOpcode::FlatStoreDwordx2: case RdnaOpcode::FlatStoreDwordx3: - case RdnaOpcode::FlatStoreDwordx4: return true; + case RdnaOpcode::FlatStoreDwordx4: + case RdnaOpcode::GlobalStoreDwordAddtid: return true; + default: return false; + } +} + +bool isFlatAtomicOpcode(RdnaOpcode opcode) { + switch (opcode) { + case RdnaOpcode::FlatAtomicSwap: + case RdnaOpcode::FlatAtomicCmpswap: + case RdnaOpcode::FlatAtomicAdd: + case RdnaOpcode::FlatAtomicSub: + case RdnaOpcode::FlatAtomicSmin: + case RdnaOpcode::FlatAtomicUmin: + case RdnaOpcode::FlatAtomicSmax: + case RdnaOpcode::FlatAtomicUmax: + case RdnaOpcode::FlatAtomicAnd: + case RdnaOpcode::FlatAtomicOr: + case RdnaOpcode::FlatAtomicXor: + case RdnaOpcode::FlatAtomicInc: + case RdnaOpcode::FlatAtomicDec: + case RdnaOpcode::FlatAtomicSwapX2: + case RdnaOpcode::FlatAtomicCmpswapX2: + case RdnaOpcode::FlatAtomicAddX2: + case RdnaOpcode::FlatAtomicSubX2: + case RdnaOpcode::FlatAtomicSminX2: + case RdnaOpcode::FlatAtomicUminX2: + case RdnaOpcode::FlatAtomicSmaxX2: + case RdnaOpcode::FlatAtomicUmaxX2: + case RdnaOpcode::FlatAtomicAndX2: + case RdnaOpcode::FlatAtomicOrX2: + case RdnaOpcode::FlatAtomicXorX2: + case RdnaOpcode::FlatAtomicFcmpswap: + case RdnaOpcode::FlatAtomicFmin: + case RdnaOpcode::FlatAtomicFmax: + case RdnaOpcode::FlatAtomicFcmpswapX2: + case RdnaOpcode::FlatAtomicFminX2: + case RdnaOpcode::FlatAtomicFmaxX2: + case RdnaOpcode::FlatAtomicIncX2: + case RdnaOpcode::FlatAtomicDecX2: + case RdnaOpcode::GlobalAtomicCsub: + return true; default: return false; } } @@ -492,6 +639,10 @@ RdnaOpcode cacheControlOpcode(RdnaInstructionFamily family, std::uint32_t opcode case 0x1fu: return RdnaOpcode::SGl1Inv; case 0x20u: return RdnaOpcode::SDcacheInv; case 0x21u: return RdnaOpcode::SDcacheWb; + case 0x26u: return RdnaOpcode::SAtcProbe; + case 0x27u: return RdnaOpcode::SAtcProbeBuffer; + case 0x28u: return RdnaOpcode::SDcacheDiscard; + case 0x29u: return RdnaOpcode::SDcacheDiscardX2; default: return RdnaOpcode::Invalid; } } @@ -656,7 +807,7 @@ RdnaInstruction DecodeRdnaFlat(std::uint32_t programCounter, std::span> 12u) & 1u; + const auto dlc = ((word0 >> 12u) & 1u) != 0u; const auto lds = (word0 >> 13u) & 1u; const auto seg = (word0 >> 14u) & 0x3u; const auto glc = ((word0 >> 16u) & 1u) != 0u; @@ -666,10 +817,14 @@ RdnaInstruction DecodeRdnaFlat(std::uint32_t programCounter, std::span> 16u) & 0x7Fu; const auto data = (word1 >> 8u) & 0xFFu; const auto addr = word1 & 0xFFu; - if (dlc != 0u || lds != 0u || glc || slc || seg == 3u) { + const auto& info = lookupOpcode(flatOpcodes, opcode, "FLAT opcode is not supported"); + const bool atomic = isFlatAtomicOpcode(info.opcode); + if (lds != 0u || (atomic && seg == 1u) || seg == 3u) { throw std::runtime_error("unsupported FLAT modifiers or segment"); } - const auto& info = lookupOpcode(flatOpcodes, opcode, "FLAT opcode is not supported"); + if (info.opcode == RdnaOpcode::GlobalAtomicCsub && seg != 2u) { + throw std::runtime_error("global_atomic_csub is available only in the global segment"); + } RdnaInstruction instruction{}; instruction.programCounter = programCounter; @@ -678,16 +833,18 @@ RdnaInstruction DecodeRdnaFlat(std::uint32_t programCounter, std::span> 28u) & 0xfu; } -void decodeVop1Dpp(std::uint32_t programCounter, std::span code, std::uint32_t wordIndex, std::uint32_t vdst, RdnaInstruction& instruction) { +void applyDpp8Modifier(RdnaOperand& operand, std::uint32_t modifier, bool fetchInactive) { + operand.dpp = true; + operand.dpp8 = true; + operand.dppCtrl = modifier >> 8u; + operand.dppFetchInactive = fetchInactive; +} + +void decodeVop1Dpp(std::uint32_t programCounter, std::span code, std::uint32_t wordIndex, std::uint32_t form, std::uint32_t vdst, RdnaInstruction& instruction) { if (static_cast(wordIndex) + 1u >= code.size()) { throw std::out_of_range("truncated VOP1 DPP instruction"); } @@ -1163,7 +1261,13 @@ void decodeVop1Dpp(std::uint32_t programCounter, std::span ? DecodeRdnaScalarDestination(vdst, programCounter) : DecodeRdnaVectorGpr(vdst); instruction.source0 = DecodeRdnaScalarSource(src0 + 256u, programCounter); - applyDppModifier(instruction.source0, modifier); + if (form == 250u && hasDppForm(instruction.op)) { + applyDppModifier(instruction.source0, modifier); + } else if (form != 250u && acceptsDpp8(instruction.op)) { + applyDpp8Modifier(instruction.source0, modifier, form == 234u); + } else { + throw std::invalid_argument(form == 250u ? "DPP modifier is not supported for opcode" : "DPP8 modifier is not supported for opcode"); + } instruction.sourceCount = 1; if (!isVop1FloatSourceOpcode(instruction.op) && (instruction.source0.negate || instruction.source0.absolute)) { @@ -1216,18 +1320,14 @@ struct Vop2SdwaRule { std::uint32_t src1Selectors = sdwaSelFull(); bool partialDst = false; bool sourceModifiers = false; + bool byteSelectorClamp = true; }; constexpr Vop2SdwaRule vop2SdwaRules[] = { {}, - {sdwaSelFull(), sdwaSelFull(), sdwaSelFull(), false, true}, - {sdwaSelWords() | sdwaSelFull(), sdwaSelWords() | sdwaSelFull(), sdwaSelWords() | sdwaSelFull(), true, true}, {sdwaSelAll(), sdwaSelAll(), sdwaSelAll(), true, true}, - {sdwaSelFull(), sdwaSelAll(), sdwaSelAll(), false, false}, {sdwaSelAll(), sdwaSelAll(), sdwaSelAll(), true, false}, - {sdwaSelFull(), sdwaSelAll(), sdwaSelAll(), false, false}, - {sdwaSelFull(), sdwaSelAll(), sdwaSelAll(), false, false}, - {sdwaSelWords() | sdwaSelFull(), sdwaSelAll(), sdwaSelAll(), true, false}, + {sdwaSelAll(), sdwaSelAll(), sdwaSelAll(), true, true, false}, }; const Vop2SdwaRule* findVop2SdwaRule(std::uint32_t encoding) { @@ -1238,6 +1338,11 @@ const Vop2SdwaRule* findVop2SdwaRule(std::uint32_t encoding) { return &vop2SdwaRules[static_cast(info->sdwaProfile)]; } +bool supportsVop2SdwaIntegerClamp(RdnaOpcode op, std::uint32_t encoding) { + const auto* info = findVectorOpcodeEntry(vop2Opcodes, encoding); + return info != nullptr && info->sdwaProfile == Vop2SdwaProfile::Integer && op != RdnaOpcode::VCndmaskB32; +} + bool isVop2SdwaDestinationSupported(const Vop2SdwaRule& rule, const Vop2SdwaFields& fields) { if (!hasSdwaSelector(rule.dstSelectors, fields.dstSel)) { return false; @@ -1256,12 +1361,12 @@ void validateVop2Sdwa(const RdnaInstruction& instruction, std::uint32_t opcode, if (fields.src0Sel > 6u || fields.src1Sel > 6u || fields.dstSel > 6u) { throw std::invalid_argument("VOP2 SDWA selector is invalid"); } - if ((fields.clamp != 0u || fields.omod != 0u) && !isVop2FloatOpcode(instruction.op)) { + const bool clampSupported = isVop2FloatOpcode(instruction.op) || supportsVop2SdwaIntegerClamp(instruction.op, opcode); + if ((fields.clamp != 0u && !clampSupported) || (fields.omod != 0u && !isVop2FloatOpcode(instruction.op))) { throw std::invalid_argument("VOP2 SDWA output modifiers are not supported"); } if (isFullWidthVop2Sdwa(fields)) { - if (!isVop2FloatOpcode(instruction.op) && instruction.op != RdnaOpcode::VCndmaskB32 && - (fields.src0Neg != 0u || fields.src0Abs != 0u || fields.src1Neg != 0u || fields.src1Abs != 0u)) { + if (hasUnsupportedVop2SourceModifiers(instruction.op, fields.src0Neg != 0u || fields.src0Abs != 0u, fields.src1Neg != 0u || fields.src1Abs != 0u)) { throw std::invalid_argument("VOP2 SDWA source modifiers are not supported"); } return; @@ -1282,6 +1387,10 @@ void validateVop2Sdwa(const RdnaInstruction& instruction, std::uint32_t opcode, if (!rule->sourceModifiers && hasSourceModifiers && !cndmaskFloatModifiers) { throw std::invalid_argument("VOP2 SDWA source modifiers are not supported"); } + const bool byteSelector = fields.src0Sel < 4u || fields.src1Sel < 4u || fields.dstSel < 4u; + if (!rule->byteSelectorClamp && fields.clamp != 0u && byteSelector) { + throw std::invalid_argument("VOP2 SDWA clamp with byte selectors is not implemented"); + } } void finalizeVop2Instruction(std::span code, std::uint32_t wordIndex, RdnaInstruction& instruction) { @@ -1345,7 +1454,7 @@ void decodeVop2Sdwa(std::uint32_t programCounter, std::span finalizeVop2Instruction(code, wordIndex, instruction); } -void decodeVop2Dpp(std::uint32_t programCounter, std::span code, std::uint32_t wordIndex, std::uint32_t opcode, std::uint32_t vdst, std::uint32_t vsrc1, RdnaInstruction& instruction) { +void decodeVop2Dpp(std::uint32_t programCounter, std::span code, std::uint32_t wordIndex, std::uint32_t form, std::uint32_t vdst, std::uint32_t vsrc1, RdnaInstruction& instruction) { if (static_cast(wordIndex) + 1u >= code.size()) { throw std::out_of_range("truncated VOP2 DPP instruction"); } @@ -1356,17 +1465,22 @@ void decodeVop2Dpp(std::uint32_t programCounter, std::span instruction.destination = DecodeRdnaVectorGpr(vdst); instruction.source1 = DecodeRdnaVectorGpr(vsrc1); instruction.source0 = DecodeRdnaScalarSource(src0 + 256u, programCounter); - applyDppModifier(instruction.source0, modifier); - instruction.source1.negate = ((modifier >> 22u) & 0x1u) != 0u; - instruction.source1.absolute = ((modifier >> 23u) & 0x1u) != 0u; + if (form == 250u && hasDppForm(instruction.op)) { + applyDppModifier(instruction.source0, modifier); + instruction.source1.negate = ((modifier >> 22u) & 0x1u) != 0u; + instruction.source1.absolute = ((modifier >> 23u) & 0x1u) != 0u; + } else if (form != 250u && acceptsDpp8(instruction.op)) { + applyDpp8Modifier(instruction.source0, modifier, form == 234u); + } else { + throw std::invalid_argument(form == 250u ? "DPP modifier is not supported for opcode" : "DPP8 modifier is not supported for opcode"); + } const bool packedFmac = instruction.op == RdnaOpcode::VPkFmacF16; if (packedFmac) { instruction.source0.negateHi = instruction.source0.negate; instruction.source1.negateHi = instruction.source1.negate; } - if (!isVop2FloatOpcode(instruction.op) && !packedFmac && - (instruction.source0.negate || instruction.source0.absolute || instruction.source1.negate || instruction.source1.absolute)) { + if (!packedFmac && hasUnsupportedVop2SourceModifiers(instruction.op, instruction.source0.negate || instruction.source0.absolute, instruction.source1.negate || instruction.source1.absolute)) { throw std::invalid_argument("VOP2 DPP integer source modifiers are not supported"); } finalizeVop2Instruction(code, wordIndex, instruction); @@ -1597,6 +1711,7 @@ std::uint32_t nativeVop3SourceCount(RdnaOpcode opcode) { case RdnaOpcode::VMinF64: case RdnaOpcode::VMaxF64: case RdnaOpcode::VLdexpF64: + case RdnaOpcode::VTrigPreopF64: case RdnaOpcode::VBfmB32: case RdnaOpcode::VBcntU32B32: case RdnaOpcode::VCvtPknormI16F32: @@ -1726,6 +1841,16 @@ void applyNativeVop3I16TernarySelectors(RdnaInstruction& instruction, std::uint3 } } +void applyNativeVop3AlignSelectors(RdnaInstruction& instruction, std::uint32_t opSel) { + if ((opSel & 0x3u) != 0u) { + instruction.source2.sdwaSel = opSel & 0x3u; + } + if ((opSel & 0x8u) != 0u) { + instruction.destination.sdwaSel = 5u; + instruction.destination.sdwaDstUnused = 0u; + } +} + void applyNativeVop3B16BinaryModifiers(RdnaInstruction& instruction, std::uint32_t opSel) { instruction.source0.opSel = (opSel & 0x1u) != 0u; instruction.source1.opSel = (opSel & 0x2u) != 0u; @@ -1748,6 +1873,8 @@ bool supportsNativeVop3SourceModifiers(RdnaOpcode opcode) { switch (opcode) { case RdnaOpcode::VMulLegacyF32: case RdnaOpcode::VMacLegacyF32: + case RdnaOpcode::VMullitF32: + case RdnaOpcode::VCvtPkU8F32: case RdnaOpcode::VCndmaskB32: case RdnaOpcode::VAddF32: case RdnaOpcode::VSubF32: @@ -1763,6 +1890,7 @@ bool supportsNativeVop3SourceModifiers(RdnaOpcode opcode) { case RdnaOpcode::VMaxF16: case RdnaOpcode::VMacF32: case RdnaOpcode::VMadF32: + case RdnaOpcode::VMadLegacyF32: case RdnaOpcode::VFmaF32: case RdnaOpcode::VPackB32F16: case RdnaOpcode::VCubeidF32: @@ -1779,11 +1907,15 @@ bool supportsNativeVop3SourceModifiers(RdnaOpcode opcode) { case RdnaOpcode::VDivFixupF32: case RdnaOpcode::VLdexpF32: case RdnaOpcode::VFmaF64: + case RdnaOpcode::VDivFmasF64: + case RdnaOpcode::VDivFixupF64: case RdnaOpcode::VAddF64: case RdnaOpcode::VMulF64: case RdnaOpcode::VMinF64: case RdnaOpcode::VMaxF64: - case RdnaOpcode::VLdexpF64: return true; + case RdnaOpcode::VLdexpF64: + case RdnaOpcode::VTrigPreopF64: + case RdnaOpcode::VLdexpF16: return true; default: return false; } } @@ -1797,6 +1929,7 @@ bool supportsNativeVop3ResultModifiers(RdnaOpcode opcode) { case RdnaOpcode::VSubF32: case RdnaOpcode::VSubrevF32: case RdnaOpcode::VMulF32: + case RdnaOpcode::VMulLegacyF32: case RdnaOpcode::VMinF32: case RdnaOpcode::VMaxF32: case RdnaOpcode::VMacF32: @@ -1806,11 +1939,14 @@ bool supportsNativeVop3ResultModifiers(RdnaOpcode opcode) { case RdnaOpcode::VCvtPkrtzF16F32: case RdnaOpcode::VLdexpF32: case RdnaOpcode::VFmaF64: + case RdnaOpcode::VDivFmasF64: + case RdnaOpcode::VDivFixupF64: case RdnaOpcode::VAddF64: case RdnaOpcode::VMulF64: case RdnaOpcode::VMinF64: case RdnaOpcode::VMaxF64: case RdnaOpcode::VLdexpF64: + case RdnaOpcode::VTrigPreopF64: case RdnaOpcode::VMin3F32: case RdnaOpcode::VMax3F32: case RdnaOpcode::VMed3F32: return true; @@ -1818,8 +1954,13 @@ bool supportsNativeVop3ResultModifiers(RdnaOpcode opcode) { } } +bool isF32DivisionStepOpcode(RdnaOpcode opcode) { + return opcode == RdnaOpcode::VDivFmasF32 || opcode == RdnaOpcode::VDivFixupF32; +} + bool supportsNativeVop3Clamp(RdnaOpcode opcode) { - return supportsNativeVop3ResultModifiers(opcode) || usesInexactClampControl(opcode) || usesSignedSaturateClamp(opcode); + return supportsNativeVop3ResultModifiers(opcode) || usesInexactClampControl(opcode) || usesIntegerSaturateClamp(opcode) || + isF32DivisionStepOpcode(opcode) || isCubeOpcode(opcode); } void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, bool scalarDst, std::uint32_t abs, std::uint32_t opSel, std::uint32_t clamp, std::uint32_t omod, std::uint32_t neg) { @@ -1836,7 +1977,13 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, return; } if (isNativeVop3I16TernaryOpcode(opcode)) { - if (abs != 0u || clamp != 0u || omod != 0u || neg != 0u) { + if (abs != 0u || omod != 0u || neg != 0u) { + throw std::invalid_argument("VOP3 source modifiers are not implemented"); + } + return; + } + if (isNativeVop3AlignOpcode(opcode)) { + if (abs != 0u || omod != 0u || neg != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } return; @@ -1849,23 +1996,29 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, } if (carryInOut || scalarDst) { const bool floatCompare = scalarDst && isVopcFloatCompareOpcode(opcode); - if (clamp != 0u || omod != 0u || (neg != 0u && !floatCompare && opcode != RdnaOpcode::VDivScaleF32)) { + if ((clamp != 0u && !isVop3BMadU64Opcode(opcode)) || omod != 0u || (neg != 0u && !floatCompare && opcode != RdnaOpcode::VDivScaleF32 && opcode != RdnaOpcode::VDivScaleF64)) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } return; } - if (usesSignedSaturateClamp(opcode)) { + if (usesIntegerSaturateClamp(opcode)) { if (abs != 0u || opSel != 0u || omod != 0u || neg != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } return; } - if (opcode == RdnaOpcode::VLdexpF32 || opcode == RdnaOpcode::VLdexpF64) { + if (opcode == RdnaOpcode::VLdexpF32 || opcode == RdnaOpcode::VLdexpF64 || opcode == RdnaOpcode::VTrigPreopF64) { if ((abs & ~1u) != 0u || opSel != 0u || (neg & ~1u) != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } return; } + if (opcode == RdnaOpcode::VLdexpF16 || opcode == RdnaOpcode::VCvtPkU8F32) { + if ((abs & ~1u) != 0u || opSel != 0u || clamp != 0u || omod != 0u || (neg & ~1u) != 0u) { + throw std::invalid_argument("VOP3 source modifiers are not implemented"); + } + return; + } if (opcode == RdnaOpcode::VCndmaskB32) { if ((abs & ~0x3u) != 0u || opSel != 0u || clamp != 0u || omod != 0u || (neg & ~0x3u) != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); @@ -1954,8 +2107,8 @@ RdnaInstruction DecodeRdnaVop1(std::uint32_t programCounter, std::span 2u) { instruction.source2 = DecodeRdnaScalarSource(src2, programCounter); } - if (instruction.op == RdnaOpcode::VFmaF32 || isMadMixF16(instruction.op) || isPackedVop3p(instruction.op) || isVop3pIntegerSaturate(instruction.op)) { + if (instruction.op == RdnaOpcode::VDot2F32F16 && ((((opSel | negHi) >> 2u) & 1u) != 0u || ((opSelHi >> 2u) & 1u) == 0u)) { + throw std::invalid_argument("VOP3P v_dot2_f32_f16 accumulator op_sel, op_sel_hi and neg_hi are not implemented"); + } + if (instruction.op == RdnaOpcode::VFmaF32 || instruction.op == RdnaOpcode::VDot2F32F16 || isMadMixF16(instruction.op) || isPackedVop3p(instruction.op) || isVop3pIntegerSaturate(instruction.op)) { instruction.destination.clamp = clamp != 0u; } else if (clamp != 0u && instruction.op != RdnaOpcode::VPkMulLoU16) { throw std::invalid_argument("VOP3P integer clamp is not implemented"); diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index 89dd3909a..817f4fc7f 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -19,6 +19,7 @@ #include "Optimization/include/Optimization/ConstantFolder.hpp" #include "Optimization/include/Optimization/DeadCodeEliminator.hpp" #include "Optimization/include/Optimization/DescriptorBindingBuilder.hpp" +#include "Optimization/include/Optimization/MaskedSelectEliminator.hpp" #include "Optimization/include/Optimization/ReadLaneEliminator.hpp" #include "Optimization/include/Optimization/RequestMemoryView.hpp" #include "Optimization/include/Optimization/ResourceMaterializer.hpp" @@ -155,6 +156,11 @@ IrProgram PrepareResourceProgram(const RecompileRequest& request) { deadCodeEliminator.Eliminate(program); } + constexpr MaskedSelectEliminator maskedSelectEliminator; + if (maskedSelectEliminator.Eliminate(program).removedSelects != 0u) { + deadCodeEliminator.Eliminate(program); + } + constexpr SrtWalker srtWalker; srtWalker.BuildPlan(program); deadCodeEliminator.Eliminate(program); diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index 9cf9234e1..054374fb5 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -310,6 +310,7 @@ struct DescriptorBinding { // Guest image elements the shader stores to (or updates atomically); the others are only read. std::vector imageWritten; std::vector imageDepthCompare; + std::vector imageAtomic; // Guest buffer elements the shader updates atomically (one entry per element of a GuestBuffers // binding, empty otherwise). An atomic on a host-imported range is a serialized PCIe round trip // (~0.4-0.5 us each on NVIDIA), so a driver may keep these elements in device-local memory. diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index e7ed428b8..c93a751b1 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -1,6 +1,7 @@ #include "ShaderDiskCache.hpp" #include "CacheKey.hpp" #include "ShaderCacheDirectory.hpp" +#include "ThreadOwned.hpp" #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX @@ -63,7 +64,7 @@ namespace ShaderRecompiler::ShaderDiskCache { #if defined(__linux__) && defined(__x86_64__) && defined(__GLIBCXX__) static_assert(sizeof(RecompileResult) == 176, "RecompileResult changed: update EncodeResult and DecodeResult"); -static_assert(sizeof(DescriptorBinding) == 264, "DescriptorBinding changed: update the binding encoder"); +static_assert(sizeof(DescriptorBinding) == 304, "DescriptorBinding changed: update the binding encoder"); static_assert(sizeof(VertexAttribute) == 28, "VertexAttribute changed: update the attribute encoder"); static_assert(sizeof(FragmentParameter) == 12, "FragmentParameter changed: update the parameter encoder"); static_assert(sizeof(CompiledShaderInfo) == 304, "CompiledShaderInfo changed: update the info encoder"); @@ -247,6 +248,7 @@ void encodeBinding(Writer& writer, const DescriptorBinding& binding) { writer.Flags(binding.samplerDepthCompare); writer.Flags(binding.imageWritten); writer.Flags(binding.imageDepthCompare); + writer.Flags(binding.imageAtomic); writer.Flags(binding.bufferAtomic); writer.Flags(binding.bufferWritten); } @@ -265,6 +267,7 @@ void decodeBinding(Reader& reader, DescriptorBinding& binding) { reader.Flags(binding.samplerDepthCompare); reader.Flags(binding.imageWritten); reader.Flags(binding.imageDepthCompare); + reader.Flags(binding.imageAtomic); reader.Flags(binding.bufferAtomic); reader.Flags(binding.bufferWritten); } @@ -603,7 +606,8 @@ public: bool Load(std::span key, CompiledVariant& variant) { const auto started = std::chrono::steady_clock::now(); const auto name = EntryName(key); - thread_local std::vector file; + thread_local std::vector* fileSlot = nullptr; + auto& file = ThreadOwned(fileSlot); bool loaded = false; if (!ReadWholeFile(directory / name, file)) { misses.fetch_add(1, std::memory_order_relaxed); @@ -778,7 +782,8 @@ void BuildKey(const RecompileRequest& request, std::uint32_t hostSubgroupSize, c writer.Value(FileMagic); writer.Value(FormatVersion); writer.Value(SourceVersion()); - thread_local std::vector memoryKey; + thread_local std::vector* memoryKeySlot = nullptr; + auto& memoryKey = ThreadOwned(memoryKeySlot); RecompileCacheKey::Build(request, memoryKey); writer.Values(std::span(memoryKey)); writer.Values(request.shader.code); diff --git a/core/shader/recompiler/ShaderDiskCache.hpp b/core/shader/recompiler/ShaderDiskCache.hpp index 216931eee..84c7a081b 100644 --- a/core/shader/recompiler/ShaderDiskCache.hpp +++ b/core/shader/recompiler/ShaderDiskCache.hpp @@ -12,7 +12,7 @@ namespace ShaderRecompiler::ShaderDiskCache { -inline constexpr std::uint32_t FormatVersion = 3; +inline constexpr std::uint32_t FormatVersion = 4; enum class LoadStatus { Loaded, diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp index 40ba4fccb..0b433bfeb 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp @@ -4,6 +4,7 @@ #include "IntermediateRepresentation/IrProgram.hpp" #include #include +#include #include #include @@ -16,9 +17,13 @@ struct SpirvRequirements { bool computeDerivatives = false; bool imageGatherExtended = false; bool functionLds = false; + bool ldsLock = false; + std::uint32_t functionLdsDwords = 0; + std::unordered_map functionLdsAddresses; bool functionScratch = false; bool pixelValidMask = false; bool bufferInt64Atomics = false; + bool sharedInt64Atomics = false; bool float64 = false; bool coherentBuffers = false; std::vector capabilities; @@ -26,7 +31,11 @@ struct SpirvRequirements { }; [[nodiscard]] SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program); +inline constexpr std::uint32_t FunctionLdsDwordLimit = 8192u; +[[nodiscard]] std::uint32_t FunctionLdsDwords(const IrProgram& program); +[[nodiscard]] std::unordered_map FunctionLdsLaneAddresses(const IrProgram& program); [[nodiscard]] std::unordered_set WaveUniformValues(const IrProgram& program); +[[nodiscard]] bool IsWaveMaskBranch(BranchCondition condition); } diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvBda.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvBda.hpp index f7c278915..b06ea3e98 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvBda.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvBda.hpp @@ -5,6 +5,7 @@ #include "SpirvBackend/SpirvEmitter.hpp" #include "BdaAbi.hpp" #include +#include namespace ShaderRecompiler { @@ -21,6 +22,8 @@ bool BdaInvocationsMayStop(const IrProgram& program); void StopBdaInvocationIf(SpirvEmitterState& state, std::uint32_t condition); std::uint32_t EmitBdaRead(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t bits); void EmitBdaWrite(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t value); +void EmitBdaStore(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t value, std::uint32_t bits); +std::uint32_t EmitBdaAtomic(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t bytes, const std::function& operation); // Reads the dwords of a 1-4 dword load at address + offset that the program extracts, with one // table lookup for the whole span; the per-byte lookups of EmitBdaRead remain the fallback (and // the only path under APS5_BDA_BYTE_READS=1). Dwords the program never extracts are neither read diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp index efb17a21a..201f4f9ae 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp @@ -78,7 +78,7 @@ void EmitMeshEntryPoint(SpirvEmitterState& state); void EmitMeshAllocate(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t MeshOutputPointer(SpirvEmitterState& state, StageOutputKind kind, std::uint32_t index = 0); std::uint32_t MeshPrimitivePointer(SpirvEmitterState& state); -DppTargetLane EmitDppQuadPermTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t control); +DppTargetLane EmitDppGroupPermTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t control, std::uint32_t laneBits); DppTargetLane EmitDppRowShiftTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t amount, bool left); DppTargetLane EmitDppRowRotateRightTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t amount); DppTargetLane EmitDppMirrorTargetLane(SpirvEmitterState& state, std::uint32_t subid, bool halfRow); @@ -96,6 +96,7 @@ std::uint32_t StorageBufferPackedStride(const SpirvEmitterState& state, const Me IrBufferFormat StorageBufferFormat(const SpirvEmitterState& state, const MemoryInfo& mem); void EmitMemoryOffsets(SpirvEmitterState& state); std::uint32_t LdsDwordCount(const SpirvEmitterState& state); +std::uint32_t EmitLdsLockPointer(SpirvEmitterState& state); MemoryResourceAccess PrepareMemoryResourceAccess(SpirvEmitterState& state, const MemoryInfo& mem); MemoryResourceAccess PrepareStorageBufferResourceAccess(SpirvEmitterState& state, const MemoryInfo& mem, std::uint32_t variable, std::uint32_t pointerType); std::uint32_t EmitMemoryElementIndex(SpirvEmitterState& state, const MemoryResourceAccess& access, std::uint32_t rawIndex); @@ -134,6 +135,7 @@ std::uint32_t Binary(SpirvEmitterState& state, std::uint32_t opcode, std::uint32 std::uint32_t Select(SpirvEmitterState& state, std::uint32_t type, std::uint32_t condition, std::uint32_t trueValue, std::uint32_t falseValue); std::uint32_t AtomicIncrement(SpirvEmitterState& state, std::uint32_t old, std::uint32_t limit); std::uint32_t AtomicDecrement(SpirvEmitterState& state, std::uint32_t old, std::uint32_t limit); +std::uint32_t AtomicUSubSat(SpirvEmitterState& state, std::uint32_t old, std::uint32_t value); std::uint32_t AtomicFloatMinMax(SpirvEmitterState& state, std::uint32_t old, std::uint32_t source, bool maxValue); std::uint32_t AtomicFloatCompareSwap(SpirvEmitterState& state, std::uint32_t old, std::uint32_t desired, std::uint32_t comparator); diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp index d89f236b3..b0e61840a 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp @@ -55,7 +55,6 @@ std::uint32_t EmitFPRecipSqrt32(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPSqrt(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPExp2(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPLog2(SpirvEmitterState& state, std::uint32_t arg0); -std::uint32_t EmitFPLdexp(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1); std::uint32_t EmitFPSin(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPCos(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitIdentity(SpirvValueEmitContext& ctx, std::uint32_t value); @@ -170,8 +169,6 @@ std::uint32_t EmitFNegateValue(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFAbsValue(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitPackHalf2x16(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitF32ToF16BitsRte(SpirvEmitterState& state, std::uint32_t value); -std::uint32_t EmitPackSnorm2x16(SpirvEmitterState& state, std::uint32_t arg0); -std::uint32_t EmitPackUnorm2x16(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPFma32(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2); std::uint32_t EmitFPMad32(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2); std::uint32_t EmitFPRoundEven32(SpirvEmitterState& state, std::uint32_t arg0); @@ -182,6 +179,7 @@ std::uint32_t EmitFPFract32(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPAdd64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1); std::uint32_t EmitFPMul64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1); std::uint32_t EmitFPFma64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2); +std::uint32_t EmitFPFmaScale64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2, std::uint32_t arg3); std::uint32_t EmitFPMin64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1); std::uint32_t EmitFPMax64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1); std::uint32_t EmitFPSaturate64(SpirvEmitterState& state, std::uint32_t arg0); @@ -193,6 +191,10 @@ std::uint32_t EmitFPTrunc64(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPFract64(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPFrexpMant64(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFPFrexpExp64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPRcp64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPRsq64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPSqrt64(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitFPTrigPreop64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1); std::uint32_t EmitConvertF32F64(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitConvertF64F32(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitConvertF64S32(SpirvEmitterState& state, std::uint32_t arg0); @@ -237,6 +239,7 @@ void EmitLoadAddressU32x4(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitStoreAddressU8(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitStoreAddressU16(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitStoreAddressU32(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitAddressAtomic(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitLoadBufferU8(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitLoadBufferU16(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitLoadBufferU32(SpirvValueEmitContext& ctx, const IrValue& inst); @@ -263,6 +266,7 @@ std::uint32_t EmitBufferAtomicOr32(SpirvValueEmitContext& ctx, const IrValue& in std::uint32_t EmitBufferAtomicOr64(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitBufferAtomicInc32(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitBufferAtomicDec32(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitBufferAtomicUSubSat32(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitBufferAtomicIAdd64(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitBufferAtomicISub64(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitBufferAtomicSMin64(SpirvValueEmitContext& ctx, const IrValue& inst); @@ -313,6 +317,24 @@ std::uint32_t EmitSharedAtomicCmpst32(SpirvValueEmitContext& ctx, const IrValue& std::uint32_t EmitSharedAtomicCmpstF32(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitSharedAtomicMskor32(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitSharedAtomicWrap32(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicSwap64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicIAdd64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicISub64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicRsub64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicInc64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicDec64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicSMin64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicUMin64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicSMax64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicUMax64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicAnd64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicOr64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicXor64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicFMin64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicFMax64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicCmpst64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicCmpstF64(SpirvValueEmitContext& ctx, const IrValue& inst); +std::uint32_t EmitSharedAtomicMskor64(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitDataAppend(SpirvValueEmitContext& ctx, const IrValue& inst); std::uint32_t EmitDataConsume(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitImageQueryDimensions(SpirvValueEmitContext& ctx, const IrValue& inst); diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp index 863dd4559..1e761cf73 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp @@ -82,6 +82,7 @@ struct SpirvEmitterState { SpirvRequirements requirements; std::uint32_t laneCount = 1; + bool splitSubgroup = false; std::unordered_set sharedLaneValues; std::uint32_t laneHalf = 0; // The target's SPIR-V version and what the device accepts, for capabilities an emitter adds diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp index 360dce9ed..2701d0530 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp @@ -13,6 +13,7 @@ std::uint32_t StorageBufferPackedStride(const SpirvEmitterState& state, const Me IrBufferFormat StorageBufferFormat(const SpirvEmitterState& state, const MemoryInfo& mem); void EmitMemoryOffsets(SpirvEmitterState& state); std::uint32_t LdsDwordCount(const SpirvEmitterState& state); +std::uint32_t EmitLdsLockPointer(SpirvEmitterState& state); MemoryResourceAccess PrepareStorageBufferResourceAccess(SpirvEmitterState& state, const MemoryInfo& mem, std::uint32_t variable, std::uint32_t pointerType); MemoryResourceAccess PrepareMemoryResourceAccess(SpirvEmitterState& state, const MemoryInfo& mem); std::uint32_t EmitMemoryElementIndex(SpirvEmitterState& state, const MemoryResourceAccess& access, std::uint32_t rawIndex); diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvSubgroup.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvSubgroup.hpp index 4910b7f24..c7e4746b6 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvSubgroup.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvSubgroup.hpp @@ -7,7 +7,9 @@ namespace ShaderRecompiler { std::uint32_t EmitSubgroupLocalInvocationId(SpirvEmitterState& state); -DppTargetLane EmitDppQuadPermTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t control); +std::uint32_t EmitHostSubgroupLane(SpirvEmitterState& state, std::uint32_t lane); +std::uint32_t EmitWaveBallot(SpirvEmitterState& state, std::uint32_t ballot); +DppTargetLane EmitDppGroupPermTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t control, std::uint32_t laneBits); DppTargetLane EmitDppRowShiftTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t amount, bool left); DppTargetLane EmitDppRowRotateRightTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t amount); DppTargetLane EmitDppMirrorTargetLane(SpirvEmitterState& state, std::uint32_t subid, bool halfRow); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp index d7c7dfbf3..c2090afca 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp @@ -1,8 +1,10 @@ #include "SpirvBackend/SpirvEmitterInstructions.hpp" #include #include +#include #include #include +#include namespace ShaderRecompiler { namespace { @@ -195,17 +197,7 @@ std::uint32_t AddRoundToOdd(SpirvEmitterState& state, std::uint32_t lhs, std::ui return ToF64(state, Join(state, SelectBits(state, inexact, odd, rounded))); } -} - -std::uint32_t EmitFPAdd64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { - return NanResult(state, Exact(state, spv::OpFAdd, ToF64(state, arg0), ToF64(state, arg1)), {arg0, arg1}); -} - -std::uint32_t EmitFPMul64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { - return NanResult(state, Exact(state, spv::OpFMul, ToF64(state, arg0), ToF64(state, arg1)), {arg0, arg1}); -} - -std::uint32_t EmitFPFma64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2) { +std::uint32_t FusedMultiplyAdd(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2, std::uint32_t scale) { const auto a = Classify(state, arg0); const auto b = Classify(state, arg1); const auto c = Classify(state, arg2); @@ -235,11 +227,14 @@ std::uint32_t EmitFPFma64(SpirvEmitterState& state, std::uint32_t arg0, std::uin const auto exact = TwoProduct(state, ToF64(state, Join(state, WithExponent(state, a.bits, ConstantU32(state, 1023u)))), ToF64(state, Join(state, WithExponent(state, b.bits, ConstantU32(state, 1023u))))); const auto sum = TwoSum(state, ToF64(state, Join(state, scaledC)), exact.high); const auto scaled = Exact(state, spv::OpFAdd, sum.high, AddRoundToOdd(state, sum.low, exact.low)); - const auto half = Binary(state, spv::OpSDiv, TypeI32(state), product, ConstantI32(state, 2)); - const auto rescaled = Exact(state, spv::OpFMul, Exact(state, spv::OpFMul, scaled, PowerOfTwo(state, half)), PowerOfTwo(state, Binary(state, spv::OpISub, TypeI32(state), product, half))); - const auto finite = SelectBits(state, far, c.bits, Split(state, FromF64(state, rescaled))); + const auto power = PowerOfTwo(state, scale); + const auto total = Glsl(state, TypeI32(state), GLSLstd450SClamp, Binary(state, spv::OpIAdd, TypeI32(state), product, scale), ConstantI32(state, -2044), ConstantI32(state, 2046)); + const auto half = Binary(state, spv::OpSDiv, TypeI32(state), total, ConstantI32(state, 2)); + const auto rescaled = Exact(state, spv::OpFMul, Exact(state, spv::OpFMul, scaled, PowerOfTwo(state, half)), PowerOfTwo(state, Binary(state, spv::OpISub, TypeI32(state), total, half))); + const auto farValue = Split(state, FromF64(state, Exact(state, spv::OpFMul, ToF64(state, arg2), power))); + const auto finite = SelectBits(state, far, farValue, Split(state, FromF64(state, rescaled))); - auto fallback = Split(state, FromF64(state, Exact(state, spv::OpFAdd, Exact(state, spv::OpFMul, ToF64(state, arg0), ToF64(state, arg1)), ToF64(state, arg2)))); + auto fallback = Split(state, FromF64(state, Exact(state, spv::OpFMul, Exact(state, spv::OpFAdd, Exact(state, spv::OpFMul, ToF64(state, arg0), ToF64(state, arg1)), ToF64(state, arg2)), power))); const auto finiteProduct = Binary(state, spv::OpLogicalAnd, TypeBool(state), Unary(state, spv::OpLogicalNot, TypeBool(state), isField(exponentA, 0x7ffu)), Unary(state, spv::OpLogicalNot, TypeBool(state), isField(exponentB, 0x7ffu))); fallback = SelectBits(state, Binary(state, spv::OpLogicalAnd, TypeBool(state), c.infinite, finiteProduct), c.bits, fallback); fallback = SelectBits(state, Classify(state, Join(state, fallback)).nan, {ConstantU32(state, 0u), ConstantU32(state, 0xfff80000u)}, fallback); @@ -253,6 +248,24 @@ std::uint32_t EmitFPFma64(SpirvEmitterState& state, std::uint32_t arg0, std::uin return Join(state, result); } +} + +std::uint32_t EmitFPAdd64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { + return NanResult(state, Exact(state, spv::OpFAdd, ToF64(state, arg0), ToF64(state, arg1)), {arg0, arg1}); +} + +std::uint32_t EmitFPMul64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { + return NanResult(state, Exact(state, spv::OpFMul, ToF64(state, arg0), ToF64(state, arg1)), {arg0, arg1}); +} + +std::uint32_t EmitFPFma64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2) { + return FusedMultiplyAdd(state, arg0, arg1, arg2, ConstantI32(state, 0)); +} + +std::uint32_t EmitFPFmaScale64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2, std::uint32_t arg3) { + return FusedMultiplyAdd(state, arg0, arg1, arg2, Unary(state, spv::OpBitcast, TypeI32(state), arg3)); +} + std::uint32_t EmitFPMin64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { return MinMax(state, arg0, arg1, false); } @@ -352,6 +365,83 @@ std::uint32_t EmitFPFrexpExp64(SpirvEmitterState& state, std::uint32_t arg0) { return Select(state, TypeU32(state), special, ConstantU32(state, 0u), Unary(state, spv::OpBitcast, TypeU32(state), exponent)); } +std::uint32_t EmitFPRcp64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto cls = Classify(state, arg0); + const auto sign = Binary(state, spv::OpBitwiseAnd, TypeU32(state), cls.bits.high, ConstantU32(state, 0x80000000u)); + const auto quotient = Split(state, FromF64(state, Exact(state, spv::OpFDiv, ConstantF64(state, 0x3ff0000000000000ull), ToF64(state, arg0)))); + auto result = SelectBits(state, cls.zero, {ConstantU32(state, 0u), Binary(state, spv::OpBitwiseOr, TypeU32(state), sign, ConstantU32(state, 0x7ff00000u))}, quotient); + result = SelectBits(state, cls.infinite, {ConstantU32(state, 0u), sign}, result); + return Join(state, SelectBits(state, cls.nan, Quiet(state, cls.bits), result)); +} + +std::uint32_t EmitFPRsq64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto cls = Classify(state, arg0); + const auto sign = Binary(state, spv::OpBitwiseAnd, TypeU32(state), cls.bits.high, ConstantU32(state, 0x80000000u)); + const auto root = Glsl(state, TypeF64(state), GLSLstd450Sqrt, ToF64(state, arg0)); + auto result = Split(state, FromF64(state, Exact(state, spv::OpFDiv, ConstantF64(state, 0x3ff0000000000000ull), root))); + result = SelectBits(state, cls.negative, {ConstantU32(state, 0u), ConstantU32(state, 0xfff80000u)}, result); + result = SelectBits(state, cls.zero, {ConstantU32(state, 0u), Binary(state, spv::OpBitwiseOr, TypeU32(state), sign, ConstantU32(state, 0x7ff00000u))}, result); + result = SelectBits(state, cls.infinite, SelectBits(state, cls.negative, result, {ConstantU32(state, 0u), ConstantU32(state, 0u)}), result); + return Join(state, SelectBits(state, cls.nan, Quiet(state, cls.bits), result)); +} + +std::uint32_t EmitFPSqrt64(SpirvEmitterState& state, std::uint32_t arg0) { + const auto cls = Classify(state, arg0); + auto result = Split(state, FromF64(state, Glsl(state, TypeF64(state), GLSLstd450Sqrt, ToF64(state, arg0)))); + result = SelectBits(state, cls.negative, {ConstantU32(state, 0u), ConstantU32(state, 0xfff80000u)}, result); + const auto passthrough = Binary(state, spv::OpLogicalOr, TypeBool(state), cls.zero, Binary(state, spv::OpLogicalAnd, TypeBool(state), cls.infinite, Unary(state, spv::OpLogicalNot, TypeBool(state), cls.negative))); + result = SelectBits(state, passthrough, cls.bits, result); + return Join(state, SelectBits(state, cls.nan, Quiet(state, cls.bits), result)); +} + +std::uint32_t EmitFPTrigPreop64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { + static constexpr std::array TwoOverPi{ + 0xa2f9836eu, 0x4e441529u, 0xfc2757d1u, 0xf534ddc0u, 0xdb629599u, 0x3c439041u, 0xfe5163abu, 0xdebbc561u, + 0xb7246e3au, 0x424dd2e0u, 0x06492eeau, 0x09d1921cu, 0xfe1deb1cu, 0xb129a73eu, 0xe88235f5u, 0x2ebb4484u, + 0xe99c7026u, 0xb45f7e41u, 0x3991d639u, 0x835339f4u, 0x9c845f8bu, 0xbdf9283bu, 0x1ff897ffu, 0xde05980fu, + 0xef2f118bu, 0x5a0a6d1fu, 0x6d367ecfu, 0x27cb09b7u, 0x4f463f66u, 0x9e5fea2du, 0x7527bac7u, 0xebe5f17bu, + 0x3d0739f7u, 0x8a5292eau, 0x6bfb5fb1u, 0x1f8d5d08u, 0x56033046u, 0x00000000u, 0x00000000u, 0x00000000u, + }; + std::array words{}; + for (std::size_t index = 0; index < words.size(); ++index) words[index] = ConstantU32(state, TwoOverPi[index]); + const auto arrayType = state.module.Type(spv::OpTypeArray, TypeU32(state), ConstantU32(state, static_cast(words.size()))); + const auto table = state.module.DefineGlobalVariable(TypePointer(state, spv::StorageClassPrivate, arrayType), spv::StorageClassPrivate); + state.module.AddFunction(spv::OpStore, table, state.module.Constant(spv::OpConstantComposite, arrayType, std::span(words))); + const auto load = [&](std::uint32_t index) { + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpAccessChain, TypePointer(state, spv::StorageClassPrivate, TypeU32(state)), pointer, table, index); + const auto value = state.module.AllocateId(); + state.module.AddFunction(spv::OpLoad, TypeU32(state), value, pointer); + return value; + }; + + const auto bits = Split(state, arg0); + const auto exponent = Binary(state, spv::OpBitwiseAnd, TypeU32(state), Binary(state, spv::OpShiftRightLogical, TypeU32(state), bits.high, ConstantU32(state, 20u)), ConstantU32(state, 0x7ffu)); + const auto extra = Binary(state, spv::OpISub, TypeU32(state), Glsl(state, TypeU32(state), GLSLstd450UMax, exponent, ConstantU32(state, 1077u)), ConstantU32(state, 1077u)); + const auto segment = Binary(state, spv::OpIMul, TypeU32(state), Binary(state, spv::OpBitwiseAnd, TypeU32(state), arg1, ConstantU32(state, 31u)), ConstantU32(state, 53u)); + const auto shift = Glsl(state, TypeU32(state), GLSLstd450UMin, Binary(state, spv::OpIAdd, TypeU32(state), segment, extra), ConstantU32(state, 1201u)); + const auto word = Binary(state, spv::OpShiftRightLogical, TypeU32(state), shift, ConstantU32(state, 5u)); + const auto offset = Binary(state, spv::OpBitwiseAnd, TypeU32(state), shift, ConstantU32(state, 31u)); + const auto aligned = Binary(state, spv::OpIEqual, TypeBool(state), offset, ConstantU32(state, 0u)); + const auto rest = Binary(state, spv::OpISub, TypeU32(state), ConstantU32(state, 32u), offset); + const auto funnel = [&](std::uint32_t upper, std::uint32_t lower) { + const auto merged = Binary(state, spv::OpBitwiseOr, TypeU32(state), Binary(state, spv::OpShiftLeftLogical, TypeU32(state), upper, offset), Binary(state, spv::OpShiftRightLogical, TypeU32(state), lower, rest)); + return Select(state, TypeU32(state), aligned, upper, merged); + }; + const auto first = load(word); + const auto second = load(Binary(state, spv::OpIAdd, TypeU32(state), word, ConstantU32(state, 1u))); + const auto third = load(Binary(state, spv::OpIAdd, TypeU32(state), word, ConstantU32(state, 2u))); + const auto high = Unary(state, spv::OpConvertUToF, TypeF64(state), funnel(first, second)); + const auto low = Unary(state, spv::OpConvertUToF, TypeF64(state), Binary(state, spv::OpShiftRightLogical, TypeU32(state), funnel(second, third), ConstantU32(state, 11u))); + const auto mantissa = Exact(state, spv::OpFAdd, Exact(state, spv::OpFMul, high, ConstantF64(state, 0x4140000000000000ull)), low); + + const auto large = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), exponent, ConstantU32(state, 1968u)); + const auto bias = Select(state, TypeU32(state), large, ConstantU32(state, 1023u + 600u - 53u + 128u), ConstantU32(state, 1023u + 600u - 53u)); + const auto scaleExponent = Binary(state, spv::OpISub, TypeU32(state), bias, shift); + const auto scale = ToF64(state, Join(state, {ConstantU32(state, 0u), Binary(state, spv::OpShiftLeftLogical, TypeU32(state), scaleExponent, ConstantU32(state, 20u))})); + return FromF64(state, Exact(state, spv::OpFMul, Exact(state, spv::OpFMul, mantissa, scale), ConstantF64(state, 0x1a70000000000000ull))); +} + std::uint32_t EmitConvertF32F64(SpirvEmitterState& state, std::uint32_t arg0) { const auto converted = Unary(state, spv::OpBitcast, TypeU32(state), Unary(state, spv::OpFConvert, TypeF32(state), ToF64(state, arg0))); const auto cls = Classify(state, arg0); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp index 8ed46f17e..ec5d995ea 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp @@ -1,4 +1,5 @@ #include "SpirvBackend/SpirvEmitterInstructions.hpp" +#include "SpirvBackend/SpirvMemory/SpirvSubgroup.hpp" #include "IntermediateRepresentation/IrValue.hpp" #include #include @@ -159,8 +160,9 @@ std::uint32_t EmitF32ToU32(SpirvEmitterState& state, std::uint32_t src, bool sig return Select(state, TypeU32(state), zero, ConstantU32(state, 0u), high); } -std::uint32_t EmitDppWriteCondition(SpirvValueEmitContext& ctx, const DppMoveFlags& flags, std::uint32_t exec) { +std::uint32_t EmitDppWriteCondition(SpirvValueEmitContext& ctx, const IrValue& inst) { auto& state = ctx.state; + const auto flags = inst.Flags(); const auto lane = EmitSubgroupLocalInvocationId(state); const auto bankShift = state.module.AllocateId(); const auto rowShift = state.module.AllocateId(); @@ -190,9 +192,13 @@ std::uint32_t EmitDppWriteCondition(SpirvValueEmitContext& ctx, const DppMoveFla const auto bounded = state.module.AllocateId(); state.module.AddFunction(spv::OpLogicalAnd, TypeBool(state), bounded, writable, target.valid); writable = bounded; + if (!flags.fetchInactive && (flags.control & DppMoveFlags::Lanes8) == 0u) { + const auto sourceActive = EmitBallotLaneActiveBool(state, ctx.Ballot(inst.Argument(2)), target.lane); + writable = Binary(state, spv::OpLogicalAnd, TypeBool(state), writable, sourceActive); + } } const auto result = state.module.AllocateId(); - state.module.AddFunction(spv::OpLogicalAnd, TypeBool(state), result, exec, writable); + state.module.AddFunction(spv::OpLogicalAnd, TypeBool(state), result, ctx.Arg(inst, 2), writable); return result; } @@ -201,10 +207,11 @@ std::uint32_t EmitDsMaskedLaneRead(SpirvEmitterState& state, std::uint32_t sourc if (state.laneCount == 2) { lane = Binary(state, spv::OpBitwiseAnd, TypeU32(state), lane, ConstantU32(state, 31u)); } + const auto physicalLane = EmitHostSubgroupLane(state, lane); const auto shuffled = state.module.AllocateId(); - state.module.AddFunction(spv::OpGroupNonUniformShuffle, TypeU32(state), shuffled, ConstantU32(state, spv::ScopeSubgroup), source, lane); + state.module.AddFunction(spv::OpGroupNonUniformShuffle, TypeU32(state), shuffled, ConstantU32(state, spv::ScopeSubgroup), source, physicalLane); const auto sourceExec = state.module.AllocateId(); - state.module.AddFunction(spv::OpGroupNonUniformShuffle, TypeBool(state), sourceExec, ConstantU32(state, spv::ScopeSubgroup), exec, lane); + state.module.AddFunction(spv::OpGroupNonUniformShuffle, TypeBool(state), sourceExec, ConstantU32(state, spv::ScopeSubgroup), exec, physicalLane); const auto sourceActive = Binary(state, spv::OpLogicalAnd, TypeBool(state), sourceExec, EmitSubgroupLaneActiveBool(state, lane)); return Select(state, TypeU32(state), sourceActive, shuffled, ConstantU32(state, 0u)); } @@ -249,7 +256,10 @@ std::uint32_t EmitPackFloat2x16Rtz(SpirvEmitterState& state, std::uint32_t arg0, } std::uint32_t EmitFPSaturate32(SpirvEmitterState& state, std::uint32_t arg0) { - return EmitExt(state, TypeF32(state), GLSLstd450FClamp, {arg0, ConstantF32(state, 0u), ConstantF32(state, 0x3f800000u)}); + const auto bits = Unary(state, spv::OpBitcast, TypeU32(state), arg0); + const auto positive = Binary(state, spv::OpULessThan, TypeBool(state), Binary(state, spv::OpISub, TypeU32(state), bits, ConstantU32(state, 1u)), ConstantU32(state, 0x7f800000u)); + const auto upper = EmitExt(state, TypeU32(state), GLSLstd450UMin, {bits, ConstantU32(state, 0x3f800000u)}); + return Unary(state, spv::OpBitcast, TypeF32(state), Select(state, TypeU32(state), positive, upper, ConstantU32(state, 0u))); } std::uint32_t EmitIAdd64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { @@ -458,11 +468,6 @@ std::uint32_t EmitFPLog2(SpirvEmitterState& state, std::uint32_t arg0) { return EmitExt(state, TypeF32(state), GLSLstd450Log2, {EmitFlushF32DenormToSignedZero(state, arg0)}); } -std::uint32_t EmitFPLdexp(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1) { - const auto exponent = Unary(state, spv::OpBitcast, TypeI32(state), arg1); - return EmitExt(state, TypeF32(state), GLSLstd450Ldexp, {arg0, exponent}); -} - std::uint32_t EmitFPSin(SpirvEmitterState& state, std::uint32_t arg0) { const auto cycle = EmitTrigCycleF32(state, arg0, true); const auto source = Binary(state, spv::OpFMul, TypeF32(state), cycle, ConstantF32(state, 0x40c90fdbu)); @@ -491,7 +496,7 @@ std::uint32_t EmitDppMoveU32(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto target = EmitDppTargetLane(state, flags.control); const auto shuffled = ctx.Shuffle(inst, 0, target.lane); if (flags.fetchInactive) { - return shuffled; + return EmitNative(state, target.valid, shuffled, ConstantU32(state, 0u)); } const auto ballot = ctx.Ballot(inst.Argument(1)); const auto sourceActive = EmitBallotLaneActiveBool(state, ballot, target.lane); @@ -501,8 +506,7 @@ std::uint32_t EmitDppMoveU32(SpirvValueEmitContext& ctx, const IrValue& inst) { } std::uint32_t EmitDppUpdateU32(SpirvValueEmitContext& ctx, const IrValue& inst) { - const auto flags = inst.Flags(); - const auto write = EmitDppWriteCondition(ctx, flags, ctx.Arg(inst, 2)); + const auto write = EmitDppWriteCondition(ctx, inst); return EmitNative(ctx.state, write, ctx.Arg(inst, 0), ctx.Arg(inst, 1)); } @@ -589,7 +593,7 @@ std::uint32_t EmitPermlane16U32(SpirvValueEmitContext& ctx, const IrValue& inst) } const auto sourceExec = ctx.Shuffle(inst, 3, target); const auto result = state.module.AllocateId(); - state.module.AddFunction(spv::OpSelect, TypeU32(state), result, sourceExec, shuffled, ConstantU32(state, 0u)); + state.module.AddFunction(spv::OpSelect, TypeU32(state), result, sourceExec, shuffled, flags.boundControl ? ConstantU32(state, 0u) : ctx.Arg(inst, 4)); return result; } @@ -950,14 +954,6 @@ std::uint32_t EmitPackHalf2x16(SpirvEmitterState& state, std::uint32_t arg0) { return Binary(state, spv::OpBitwiseOr, TypeU32(state), component(0u), high); } -std::uint32_t EmitPackSnorm2x16(SpirvEmitterState& state, std::uint32_t arg0) { - return EmitGlsl(state, arg0); -} - -std::uint32_t EmitPackUnorm2x16(SpirvEmitterState& state, std::uint32_t arg0) { - return EmitGlsl(state, arg0); -} - std::uint32_t EmitFPFma32(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2) { return Exact(state, EmitGlsl(state, arg0, arg1, arg2)); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp index edcc2d49b..82fdd645b 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp @@ -1,6 +1,9 @@ #include "SpirvBackend/SpirvAnalysis.hpp" +#include +#include #include #include +#include namespace ShaderRecompiler { @@ -63,17 +66,250 @@ bool LaneSource(const IrProgram& program, const IrValue& value) { } +namespace { + +class AddressBound { +public: + std::optional Of(const IrValue* value) { + if (value == nullptr) return std::nullopt; + value = value->Resolve(); + if (value == nullptr) return std::nullopt; + if (value->HasImmediate()) return static_cast(value->ImmediateU32()); + if (const auto known = bounds.find(value); known != bounds.end()) return known->second; + if (!visiting.insert(value).second || visiting.size() > MaxDepth) return std::nullopt; + const auto result = compute(*value); + visiting.erase(value); + bounds.emplace(value, result); + return result; + } + +private: + static constexpr std::size_t MaxDepth = 256; + static constexpr std::uint64_t Max32 = 0xffffffffull; + + static std::optional fits(std::uint64_t value) { + return value <= Max32 ? std::optional(value) : std::nullopt; + } + static std::uint64_t ones(std::uint64_t value) { + std::uint64_t mask = 0; + while (mask < value) mask = (mask << 1u) | 1u; + return mask; + } + + std::optional compute(const IrValue& value) { + const auto argument = [&](std::size_t index) { return index < value.ArgumentCount() ? Of(value.Argument(index)) : std::nullopt; }; + switch (value.Opcode()) { + case IrOpcode::LaneId: + return 63u; + case IrOpcode::IAdd32: { + const auto a = argument(0), b = argument(1); + if (!a || !b) return std::nullopt; + return fits(*a + *b); + } + case IrOpcode::IMul32: { + const auto a = argument(0), b = argument(1); + if (!a || !b || (*b != 0 && *a > Max32 / *b)) return std::nullopt; + return *a * *b; + } + case IrOpcode::ShiftLeftLogical32: + case IrOpcode::IShiftLeft32: { + const auto a = argument(0), b = argument(1); + if (!a || !b || *b >= 32u) return std::nullopt; + return fits(*a << *b); + } + case IrOpcode::ShiftRightLogical32: + case IrOpcode::IShiftRightLogical32: + return argument(0); + case IrOpcode::BitwiseAnd32: + case IrOpcode::IAnd32: { + const auto a = argument(0), b = argument(1); + if (a && b) return std::min(*a, *b); + return a ? a : b; + } + case IrOpcode::BitwiseOr32: + case IrOpcode::IOr32: + case IrOpcode::BitwiseXor32: + case IrOpcode::IXor32: { + const auto a = argument(0), b = argument(1); + if (!a || !b) return std::nullopt; + return ones(std::max(*a, *b)); + } + case IrOpcode::UMin32: { + const auto a = argument(0), b = argument(1); + if (a && b) return std::min(*a, *b); + return a ? a : b; + } + case IrOpcode::UMax32: { + const auto a = argument(0), b = argument(1); + if (!a || !b) return std::nullopt; + return std::max(*a, *b); + } + case IrOpcode::SelectU32: { + const auto a = argument(1), b = argument(2); + if (!a || !b) return std::nullopt; + return std::max(*a, *b); + } + case IrOpcode::BitFieldUExtract: { + const auto width = argument(2); + if (width && *width < 32u) return (1ull << *width) - 1u; + return argument(0); + } + case IrOpcode::Phi: { + std::uint64_t result = 0; + for (std::size_t index = 0; index < value.ArgumentCount(); ++index) { + const auto incoming = Of(value.Argument(index)); + if (!incoming) return std::nullopt; + result = std::max(result, *incoming); + } + return result; + } + default: + return std::nullopt; + } + } + + std::unordered_map> bounds; + std::unordered_set visiting; +}; + +std::uint32_t SharedAccessDwords(IrOpcode opcode) { + switch (opcode) { + case IrOpcode::LoadSharedU8: + case IrOpcode::LoadSharedU16: + case IrOpcode::LoadSharedU32: + case IrOpcode::WriteSharedU8: + case IrOpcode::WriteSharedU16: + case IrOpcode::WriteSharedU32: + case IrOpcode::SharedAtomicFMin32: + case IrOpcode::SharedAtomicFMax32: + case IrOpcode::SharedAtomicSwap32: + case IrOpcode::SharedAtomicIAdd32: + case IrOpcode::SharedAtomicISub32: + case IrOpcode::SharedAtomicInc32: + case IrOpcode::SharedAtomicDec32: + case IrOpcode::SharedAtomicSMin32: + case IrOpcode::SharedAtomicUMin32: + case IrOpcode::SharedAtomicSMax32: + case IrOpcode::SharedAtomicUMax32: + case IrOpcode::SharedAtomicAnd32: + case IrOpcode::SharedAtomicOr32: + case IrOpcode::SharedAtomicXor32: + case IrOpcode::SharedAtomicRsub32: + case IrOpcode::SharedAtomicFAdd32: + case IrOpcode::SharedAtomicCmpst32: + case IrOpcode::SharedAtomicCmpstF32: + case IrOpcode::SharedAtomicMskor32: + case IrOpcode::SharedAtomicWrap32: + return 1u; + case IrOpcode::LoadSharedU32x2: + case IrOpcode::WriteSharedU32x2: + return 2u; + case IrOpcode::LoadSharedU32x3: + case IrOpcode::WriteSharedU32x3: + return 3u; + case IrOpcode::LoadSharedU32x4: + case IrOpcode::WriteSharedU32x4: + return 4u; + default: + return 0u; + } +} + +} + +std::uint32_t FunctionLdsDwords(const IrProgram& program) { + AddressBound bound; + std::uint64_t needed = 0; + for (const IrBlock* block : program.BlockOrder()) { + for (const IrValue* inst : block->Instructions()) { + if (SharedAccessOf(inst->Opcode()) == SharedAccess::None) continue; + const auto index = inst->Flags().index; + if (index >= program.Resources().memoryInfo.size()) return FunctionLdsDwordLimit; + const auto& memory = program.Resources().memoryInfo[index]; + if (memory.kind != ResourceKind::Lds) continue; + const auto dwords = SharedAccessDwords(inst->Opcode()); + if (dwords == 0u || inst->ArgumentCount() == 0) return FunctionLdsDwordLimit; + const auto address = bound.Of(inst->Argument(0)); + if (!address || *address + memory.offset > 0xffffffffull) return FunctionLdsDwordLimit; + needed = std::max(needed, ((*address + memory.offset) >> 2u) + dwords); + if (needed >= FunctionLdsDwordLimit) return FunctionLdsDwordLimit; + } + } + return static_cast(std::min(std::max((needed + 63u) & ~63ull, 64u), FunctionLdsDwordLimit)); +} + +namespace { + +struct LaneAffine { + std::uint64_t stride = 0; + std::uint64_t constant = 0; +}; + +std::optional LaneAffineOf(const IrValue* value, std::uint32_t depth = 0) { + if (value == nullptr || depth > 64u) return std::nullopt; + value = value->Resolve(); + if (value == nullptr) return std::nullopt; + if (value->HasImmediate()) return LaneAffine{0u, value->ImmediateU32()}; + const auto argument = [&](std::size_t index) { return index < value->ArgumentCount() ? LaneAffineOf(value->Argument(index), depth + 1u) : std::nullopt; }; + switch (value->Opcode()) { + case IrOpcode::LaneId: + return LaneAffine{1u, 0u}; + case IrOpcode::IAdd32: { + const auto a = argument(0), b = argument(1); + if (!a || !b) return std::nullopt; + return LaneAffine{a->stride + b->stride, a->constant + b->constant}; + } + case IrOpcode::IMul32: { + const auto a = argument(0), b = argument(1); + if (!a || !b) return std::nullopt; + if (a->stride == 0u) return LaneAffine{b->stride * a->constant, b->constant * a->constant}; + if (b->stride == 0u) return LaneAffine{a->stride * b->constant, a->constant * b->constant}; + return std::nullopt; + } + case IrOpcode::ShiftLeftLogical32: + case IrOpcode::IShiftLeft32: { + const auto a = argument(0), b = argument(1); + if (!a || !b || b->stride != 0u || b->constant >= 32u) return std::nullopt; + return LaneAffine{a->stride << b->constant, a->constant << b->constant}; + } + default: + return std::nullopt; + } +} + +} + +std::unordered_map FunctionLdsLaneAddresses(const IrProgram& program) { + std::unordered_map addresses; + if (FunctionLdsDwords(program) >= FunctionLdsDwordLimit) return {}; + std::optional stride; + for (const IrBlock* block : program.BlockOrder()) { + for (const IrValue* inst : block->Instructions()) { + if (SharedAccessOf(inst->Opcode()) == SharedAccess::None) continue; + const auto& memory = program.Resources().memoryInfo[inst->Flags().index]; + if (memory.kind != ResourceKind::Lds) continue; + const auto affine = LaneAffineOf(inst->Argument(0)); + if (!affine || affine->stride % 4u != 0u || (stride && *stride != affine->stride)) return {}; + stride = affine->stride; + if (affine->constant + memory.offset > 0xffffffffull) return {}; + addresses.emplace(inst, static_cast(affine->constant)); + } + } + if (!stride || *stride == 0u) return {}; + return addresses; +} + SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { SpirvRequirements requirements {}; for (const IrBlock* block : program.BlockOrder()) { for (const IrValue* inst : block->Instructions()) { - if (BufferAccessOf(inst->Opcode()) == BufferAccess::Atomic && inst->Type() == IrType::U64) { + const auto addressAccess = AddressOpcodeInfoOf(inst->Opcode()).access; + if ((BufferAccessOf(inst->Opcode()) == BufferAccess::Atomic || addressAccess == AddressAccess::Atomic) && inst->Type() == IrType::U64) { requirements.bufferInt64Atomics = true; } if (IsFloat64Opcode(inst->Opcode())) { requirements.float64 = true; } - const auto addressAccess = AddressOpcodeInfoOf(inst->Opcode()).access; if (addressAccess != AddressAccess::None) { const auto memoryIndex = inst->Flags().index; if (memoryIndex >= program.Resources().memoryInfo.size()) { @@ -84,8 +320,6 @@ SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { throw std::runtime_error("scratch operation has no per-thread storage"); } requirements.functionScratch = true; - } else if (addressAccess == AddressAccess::Write) { - throw std::runtime_error("writable FLAT/GLOBAL addresses require GPU ownership tracking"); } } if (BufferAccessOf(inst->Opcode()) != BufferAccess::None) { @@ -113,6 +347,9 @@ SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { } } const auto sharedAccess = SharedAccessOf(inst->Opcode()); + if (sharedAccess == SharedAccess::Atomic && inst->Type() == IrType::U64) { + requirements.sharedInt64Atomics = true; + } if (sharedAccess != SharedAccess::None) { const auto index = inst->Flags().index; if (index >= program.Resources().memoryInfo.size()) { @@ -124,6 +361,8 @@ SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { } if (program.Resources().stage != IrShaderStage::Compute && program.Resources().stage != IrShaderStage::Mesh && kind == ResourceKind::Lds) { requirements.functionLds = true; + } else if (sharedAccess == SharedAccess::Atomic && inst->Type() == IrType::U64 && kind == ResourceKind::Lds) { + requirements.ldsLock = true; } if (sharedAccess == SharedAccess::Append || sharedAccess == SharedAccess::Consume) { requirements.subgroupBallot = true; @@ -189,9 +428,30 @@ SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { } } } + for (const auto& info : program.Metadata().blockInfo) { + if (info.terminator.kind == TerminatorKind::ConditionalBranch && IsWaveMaskBranch(info.terminator.condition)) { + requirements.subgroupBallot = true; + } + } + if (requirements.functionLds) { + requirements.functionLdsDwords = FunctionLdsDwords(program); + requirements.functionLdsAddresses = FunctionLdsLaneAddresses(program); + if (!requirements.functionLdsAddresses.empty()) { + std::uint64_t needed = 0; + for (const auto& [inst, address] : requirements.functionLdsAddresses) { + const auto offset = program.Resources().memoryInfo[inst->Flags().index].offset; + needed = std::max(needed, ((std::uint64_t{address} + offset) >> 2u) + SharedAccessDwords(inst->Opcode())); + } + requirements.functionLdsDwords = static_cast(std::max((needed + 63u) & ~63ull, 64u)); + } + } return requirements; } +bool IsWaveMaskBranch(BranchCondition condition) { + return condition == BranchCondition::ExecZero || condition == BranchCondition::ExecNonZero || condition == BranchCondition::VccZero || condition == BranchCondition::VccNonZero; +} + std::unordered_set WaveUniformValues(const IrProgram& program) { std::unordered_map varying; const auto isVarying = [&](const IrValue* value) { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp index 3787acf5c..df12bfd78 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvBdaRead.cpp @@ -83,6 +83,21 @@ std::uint32_t UsedBdaDwords(const IrValue& inst, std::uint32_t dwords) { return used; } +void EmitBdaStoreAt(SpirvEmitterState& state, std::uint32_t address, std::uint32_t bytes, std::uint32_t pointerType, std::uint32_t value, std::uint32_t instruction, std::uint32_t accessMask) { + const auto physical = state.module.AllocateId(); + state.module.AddFunction(spv::OpFunctionCall, TypeScalarU64(state), physical, state.bdaWritePointerFunction, address, ConstantU32(state, bytes), instruction); + EmitIfCondition(state, Binary(state, spv::OpINotEqual, TypeBool(state), physical, BdaConstant(state, 0u)), [&] { + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpConvertUToPtr, pointerType, pointer, physical); + state.module.AddFunction(spv::OpStore, pointer, value, accessMask, bytes); + state.module.AddFunction(spv::OpFunctionCall, state.module.Type(spv::OpTypeVoid), state.module.AllocateId(), state.bdaNoteWriteFunction, address); + }); +} + +std::uint32_t BdaAddressUnaligned(SpirvEmitterState& state, std::uint32_t address) { + return Binary(state, spv::OpINotEqual, TypeBool(state), Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), address, BdaConstant(state, 3u)), BdaConstant(state, 0u)); +} + } // APS5_BDA_BYTE_READS=1 restores one lookup and one byte load per byte for every read. @@ -143,16 +158,57 @@ void EmitBdaWrite(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t auto& state = ctx.state; if (state.bdaWritePointerFunction == 0 || state.bdaNoteWriteFunction == 0) ctx.Fail(inst, "BDA write functions are missing"); const auto instruction = ConstantU32(state, inst.Flags().pc); - const auto unaligned = Binary(state, spv::OpINotEqual, TypeBool(state), Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), address, BdaConstant(state, 3u)), BdaConstant(state, 0u)); + const auto unaligned = BdaAddressUnaligned(state, address); EmitIfCondition(state, unaligned, [&] { RecordBdaFault(state, address, ConstantU32(state, 4u), instruction, BdaAbi::FaultReason::Unaligned); }); EmitIfCondition(state, Unary(state, spv::OpLogicalNot, TypeBool(state), unaligned), [&] { + EmitBdaStoreAt(state, address, 4u, TypePhysicalU32Pointer(state), value, instruction, BdaAccessMask(state, inst)); + }); +} + +void EmitBdaStore(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t value, std::uint32_t bits) { + auto& state = ctx.state; + if (bits != 8u && bits != 16u && bits != 32u) ctx.Fail(inst, "unsupported BDA store width"); + if (state.bdaWritePointerFunction == 0 || state.bdaNoteWriteFunction == 0) ctx.Fail(inst, "BDA write functions are missing"); + const auto instruction = ConstantU32(state, inst.Flags().pc); + const auto accessMask = BdaAccessMask(state, inst); + const auto storeBytes = [&] { + const auto byteType = state.module.Type(spv::OpTypeInt, 8u, 0u); + const auto bytePointer = TypePointer(state, spv::StorageClassPhysicalStorageBuffer, byteType); + for (std::uint32_t byte = 0; byte < bits / 8u; ++byte) { + const auto guest = byte == 0u ? address : Binary(state, spv::OpIAdd, TypeScalarU64(state), address, BdaConstant(state, byte)); + const auto shifted = byte == 0u ? value : Binary(state, spv::OpShiftRightLogical, TypeU32(state), value, ConstantU32(state, byte * 8u)); + EmitBdaStoreAt(state, guest, 1u, bytePointer, Unary(state, spv::OpUConvert, byteType, shifted), instruction, accessMask); + } + }; + if (bits != 32u) { + storeBytes(); + return; + } + const auto unaligned = BdaAddressUnaligned(state, address); + EmitIfCondition(state, unaligned, storeBytes); + EmitIfCondition(state, Unary(state, spv::OpLogicalNot, TypeBool(state), unaligned), [&] { + EmitBdaStoreAt(state, address, 4u, TypePhysicalU32Pointer(state), value, instruction, accessMask); + }); +} + +std::uint32_t EmitBdaAtomic(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t address, std::uint32_t bytes, const std::function& operation) { + auto& state = ctx.state; + if (bytes != 4u && bytes != 8u) ctx.Fail(inst, "unsupported BDA atomic width"); + if (state.bdaWritePointerFunction == 0 || state.bdaNoteWriteFunction == 0) ctx.Fail(inst, "BDA write functions are missing"); + const auto instruction = ConstantU32(state, inst.Flags().pc); + const auto type = bytes == 8u ? TypeScalarU64(state) : TypeU32(state); + const auto zero = bytes == 8u ? BdaConstant(state, 0u) : ConstantU32(state, 0u); + const auto unaligned = Binary(state, spv::OpINotEqual, TypeBool(state), Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), address, BdaConstant(state, bytes - 1u)), BdaConstant(state, 0u)); + EmitIfCondition(state, unaligned, [&] { RecordBdaFault(state, address, ConstantU32(state, bytes), instruction, BdaAbi::FaultReason::Unaligned); }); + return EmitValueOrDefaultIfCondition(state, Unary(state, spv::OpLogicalNot, TypeBool(state), unaligned), type, zero, [&] { const auto physical = state.module.AllocateId(); - state.module.AddFunction(spv::OpFunctionCall, TypeScalarU64(state), physical, state.bdaWritePointerFunction, address, ConstantU32(state, 4u), instruction); - EmitIfCondition(state, Binary(state, spv::OpINotEqual, TypeBool(state), physical, BdaConstant(state, 0u)), [&] { + state.module.AddFunction(spv::OpFunctionCall, TypeScalarU64(state), physical, state.bdaWritePointerFunction, address, ConstantU32(state, bytes), instruction); + return EmitValueOrDefaultIfCondition(state, Binary(state, spv::OpINotEqual, TypeBool(state), physical, BdaConstant(state, 0u)), type, zero, [&] { const auto pointer = state.module.AllocateId(); - state.module.AddFunction(spv::OpConvertUToPtr, TypePhysicalU32Pointer(state), pointer, physical); - state.module.AddFunction(spv::OpStore, pointer, value, BdaAccessMask(state, inst), 4u); + state.module.AddFunction(spv::OpConvertUToPtr, TypePointer(state, spv::StorageClassPhysicalStorageBuffer, type), pointer, physical); + const auto old = operation(pointer); state.module.AddFunction(spv::OpFunctionCall, state.module.Type(spv::OpTypeVoid), state.module.AllocateId(), state.bdaNoteWriteFunction, address); + return old; }); }); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp index 3139d4ace..8dcc53b53 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp @@ -4,6 +4,7 @@ #include "SpirvBackend/SpirvEmitterState.hpp" #include "SpirvBackend/SpirvFlowEmitter.hpp" #include "SpirvBackend/SpirvMemory/SpirvModuleSetup.hpp" +#include "SpirvBackend/SpirvMemory/SpirvSubgroup.hpp" #include #include #include @@ -119,7 +120,7 @@ std::uint32_t SpirvValueEmitContext::Ballot(const IrValue* predicate) { const auto low = state.module.AllocateId(); state.module.AddFunction(spv::OpGroupNonUniformBallot, ballotType, low, scope, otherHalf == nullptr || half == 0u ? Def(predicate) : otherHalf->Def(predicate)); if (otherHalf == nullptr) { - return low; + return EmitWaveBallot(state, low); } const auto high = state.module.AllocateId(); const auto lowWord = state.module.AllocateId(); @@ -158,7 +159,7 @@ std::uint32_t SpirvValueEmitContext::Shuffle(const IrValue& inst, std::size_t in const auto scope = ConstantU32(state, spv::ScopeSubgroup); const auto low = state.module.AllocateId(); if (otherHalf == nullptr) { - state.module.AddFunction(spv::OpGroupNonUniformShuffle, type, low, scope, Arg(inst, index), lane); + state.module.AddFunction(spv::OpGroupNonUniformShuffle, type, low, scope, Arg(inst, index), EmitHostSubgroupLane(state, lane)); return low; } const auto physicalLane = EmitBinaryU32(state, spv::OpBitwiseAnd, lane, ConstantU32(state, 31u)); @@ -247,6 +248,8 @@ std::vector SpirvEmitter::Emit(const IrProgram& program, const Sh state.supportedCapabilities = target.supportedCapabilities; state.supportedExtensions = target.supportedExtensions; state.nonConstantImageOffsets = target.nonConstantImageOffsets; + state.splitSubgroup = program.WaveSize() == 32u && target.subgroupSize > 32u; + if (state.splitSubgroup && (state.requirements.subgroupBallot || state.requirements.subgroupShuffle)) state.requirements.subgroupLocalInvocationId = true; const auto* workgroup = ShaderWorkgroupInputFor(state); state.laneCount = workgroup != nullptr && program.WaveSize() == 64u && workgroup->hostSubgroupSize == 32u ? 2u : 1u; if (state.laneCount == 2u) state.sharedLaneValues = WaveUniformValues(program); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index eac7c878b..c05dcf3df 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -1,6 +1,7 @@ #include "SpirvBackend/SpirvFlowEmitter.hpp" #include "SpirvBackend/SpirvBda.hpp" #include "SpirvBackend/SpirvEmitterInstructions.hpp" +#include "SpirvBackend/SpirvMemory/SpirvSubgroup.hpp" #include #include #include @@ -119,8 +120,38 @@ bool IsContinueTarget(const IrProgram& program, std::uint32_t block) { return false; } +std::uint32_t EmitWaveAny(SpirvEmitterState& state, std::uint32_t predicate) { + const auto ballot = state.module.AllocateId(); + state.module.AddFunction(spv::OpGroupNonUniformBallot, TypeU32Vector(state, 4u), ballot, ConstantU32(state, spv::ScopeSubgroup), predicate); + const auto wave = EmitWaveBallot(state, ballot); + const auto low = state.module.AllocateId(); + const auto high = state.module.AllocateId(); + const auto result = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), low, wave, 0u); + state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), high, wave, 1u); + state.module.AddFunction(spv::OpINotEqual, TypeBool(state), result, EmitBinaryU32(state, spv::OpBitwiseOr, low, high), ConstantU32(state, 0u)); + return result; +} + +std::uint32_t EmitWaveMaskBranchCondition(SpirvValueEmitContext& ctx, const BlockInfo& info) { + auto& state = ctx.state; + const auto kind = info.terminator.condition; + if (kind == BranchCondition::ExecNonZero || kind == BranchCondition::VccNonZero) { + return EmitWaveAny(state, ctx.Def(info.condition)); + } + const auto clear = state.module.AllocateId(); + const auto result = state.module.AllocateId(); + state.module.AddFunction(spv::OpLogicalNot, TypeBool(state), clear, ctx.Def(info.condition)); + state.module.AddFunction(spv::OpLogicalNot, TypeBool(state), result, EmitWaveAny(state, clear)); + return result; +} + std::uint32_t EmitBranchCondition(SpirvValueEmitContext& ctx, const BlockInfo& info) { - if (ctx.otherHalf == nullptr || info.terminator.condition == BranchCondition::ScalarInstruction || info.terminator.condition == BranchCondition::GotoVariable || info.terminator.condition == BranchCondition::IndirectTarget) { + const auto condition = info.terminator.condition; + if (ctx.otherHalf == nullptr && IsWaveMaskBranch(condition)) { + return EmitWaveMaskBranchCondition(ctx, info); + } + if (ctx.otherHalf == nullptr || condition == BranchCondition::ScalarInstruction || condition == BranchCondition::GotoVariable || condition == BranchCondition::IndirectTarget) { return ctx.Def(info.condition); } auto& state = ctx.state; @@ -313,8 +344,6 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::CompositeExtractU32x3: return Invoke(EmitCompositeExtractU32x3, ctx, inst); case IrOpcode::CompositeExtractU32x4: return Invoke(EmitCompositeExtractU32x4, ctx, inst); case IrOpcode::PackHalf2x16: return Invoke(EmitPackHalf2x16, ctx, inst); - case IrOpcode::PackSnorm2x16: return Invoke(EmitPackSnorm2x16, ctx, inst); - case IrOpcode::PackUnorm2x16: return Invoke(EmitPackUnorm2x16, ctx, inst); case IrOpcode::PackFloat2x16Rtz: return Invoke(EmitPackFloat2x16Rtz, ctx, inst); case IrOpcode::FPAbs32: return Invoke(EmitFPAbs32, ctx, inst); case IrOpcode::FPNeg32: return Invoke(EmitFPNeg32, ctx, inst); @@ -413,7 +442,6 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::FPCos: return Invoke(EmitFPCos, ctx, inst); case IrOpcode::FPExp2: return Invoke(EmitFPExp2, ctx, inst); case IrOpcode::FPLog2: return Invoke(EmitFPLog2, ctx, inst); - case IrOpcode::FPLdexp: return Invoke(EmitFPLdexp, ctx, inst); case IrOpcode::FPRoundEven32: return Invoke(EmitFPRoundEven32, ctx, inst); case IrOpcode::FPFloor32: return Invoke(EmitFPFloor32, ctx, inst); case IrOpcode::FPCeil32: return Invoke(EmitFPCeil32, ctx, inst); @@ -422,6 +450,7 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::FPAdd64: return Invoke(EmitFPAdd64, ctx, inst); case IrOpcode::FPMul64: return Invoke(EmitFPMul64, ctx, inst); case IrOpcode::FPFma64: return Invoke(EmitFPFma64, ctx, inst); + case IrOpcode::FPFmaScale64: return Invoke(EmitFPFmaScale64, ctx, inst); case IrOpcode::FPMin64: return Invoke(EmitFPMin64, ctx, inst); case IrOpcode::FPMax64: return Invoke(EmitFPMax64, ctx, inst); case IrOpcode::FPSaturate64: return Invoke(EmitFPSaturate64, ctx, inst); @@ -433,6 +462,10 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::FPFract64: return Invoke(EmitFPFract64, ctx, inst); case IrOpcode::FPFrexpMant64: return Invoke(EmitFPFrexpMant64, ctx, inst); case IrOpcode::FPFrexpExp64: return Invoke(EmitFPFrexpExp64, ctx, inst); + case IrOpcode::FPRcp64: return Invoke(EmitFPRcp64, ctx, inst); + case IrOpcode::FPRsq64: return Invoke(EmitFPRsq64, ctx, inst); + case IrOpcode::FPSqrt64: return Invoke(EmitFPSqrt64, ctx, inst); + case IrOpcode::FPTrigPreop64: return Invoke(EmitFPTrigPreop64, ctx, inst); case IrOpcode::ConvertF32F64: return Invoke(EmitConvertF32F64, ctx, inst); case IrOpcode::ConvertF64F32: return Invoke(EmitConvertF64F32, ctx, inst); case IrOpcode::ConvertF64S32: return Invoke(EmitConvertF64S32, ctx, inst); @@ -462,6 +495,39 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::StoreAddressU8: return Invoke(EmitStoreAddressU8, ctx, inst); case IrOpcode::StoreAddressU16: return Invoke(EmitStoreAddressU16, ctx, inst); case IrOpcode::StoreAddressU32: return Invoke(EmitStoreAddressU32, ctx, inst); + case IrOpcode::AddressAtomicSwap32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicCmpSwap32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicIAdd32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicISub32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicSMin32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicUMin32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicSMax32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicUMax32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicAnd32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicOr32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicXor32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicInc32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicDec32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicUSubSat32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicSwap64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicCmpSwap64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicIAdd64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicISub64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicSMin64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicUMin64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicSMax64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicUMax64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicAnd64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicOr64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicXor64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicFCmpSwap32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicFMin32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicFMax32: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicFCmpSwap64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicFMin64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicFMax64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicInc64: return Invoke(EmitAddressAtomic, ctx, inst); + case IrOpcode::AddressAtomicDec64: return Invoke(EmitAddressAtomic, ctx, inst); case IrOpcode::LoadBufferU8: return Invoke(EmitLoadBufferU8, ctx, inst); case IrOpcode::LoadBufferU16: return Invoke(EmitLoadBufferU16, ctx, inst); case IrOpcode::LoadBufferU32: return Invoke(EmitLoadBufferU32, ctx, inst); @@ -488,6 +554,7 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::BufferAtomicOr64: return Invoke(EmitBufferAtomicOr64, ctx, inst); case IrOpcode::BufferAtomicInc32: return Invoke(EmitBufferAtomicInc32, ctx, inst); case IrOpcode::BufferAtomicDec32: return Invoke(EmitBufferAtomicDec32, ctx, inst); + case IrOpcode::BufferAtomicUSubSat32: return Invoke(EmitBufferAtomicUSubSat32, ctx, inst); case IrOpcode::BufferAtomicIAdd64: return Invoke(EmitBufferAtomicIAdd64, ctx, inst); case IrOpcode::BufferAtomicISub64: return Invoke(EmitBufferAtomicISub64, ctx, inst); case IrOpcode::BufferAtomicSMin64: return Invoke(EmitBufferAtomicSMin64, ctx, inst); @@ -538,6 +605,24 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::SharedAtomicCmpstF32: return Invoke(EmitSharedAtomicCmpstF32, ctx, inst); case IrOpcode::SharedAtomicMskor32: return Invoke(EmitSharedAtomicMskor32, ctx, inst); case IrOpcode::SharedAtomicWrap32: return Invoke(EmitSharedAtomicWrap32, ctx, inst); + case IrOpcode::SharedAtomicSwap64: return Invoke(EmitSharedAtomicSwap64, ctx, inst); + case IrOpcode::SharedAtomicIAdd64: return Invoke(EmitSharedAtomicIAdd64, ctx, inst); + case IrOpcode::SharedAtomicISub64: return Invoke(EmitSharedAtomicISub64, ctx, inst); + case IrOpcode::SharedAtomicRsub64: return Invoke(EmitSharedAtomicRsub64, ctx, inst); + case IrOpcode::SharedAtomicInc64: return Invoke(EmitSharedAtomicInc64, ctx, inst); + case IrOpcode::SharedAtomicDec64: return Invoke(EmitSharedAtomicDec64, ctx, inst); + case IrOpcode::SharedAtomicSMin64: return Invoke(EmitSharedAtomicSMin64, ctx, inst); + case IrOpcode::SharedAtomicUMin64: return Invoke(EmitSharedAtomicUMin64, ctx, inst); + case IrOpcode::SharedAtomicSMax64: return Invoke(EmitSharedAtomicSMax64, ctx, inst); + case IrOpcode::SharedAtomicUMax64: return Invoke(EmitSharedAtomicUMax64, ctx, inst); + case IrOpcode::SharedAtomicAnd64: return Invoke(EmitSharedAtomicAnd64, ctx, inst); + case IrOpcode::SharedAtomicOr64: return Invoke(EmitSharedAtomicOr64, ctx, inst); + case IrOpcode::SharedAtomicXor64: return Invoke(EmitSharedAtomicXor64, ctx, inst); + case IrOpcode::SharedAtomicFMin64: return Invoke(EmitSharedAtomicFMin64, ctx, inst); + case IrOpcode::SharedAtomicFMax64: return Invoke(EmitSharedAtomicFMax64, ctx, inst); + case IrOpcode::SharedAtomicCmpst64: return Invoke(EmitSharedAtomicCmpst64, ctx, inst); + case IrOpcode::SharedAtomicCmpstF64: return Invoke(EmitSharedAtomicCmpstF64, ctx, inst); + case IrOpcode::SharedAtomicMskor64: return Invoke(EmitSharedAtomicMskor64, ctx, inst); case IrOpcode::DataAppend: return Invoke(EmitDataAppend, ctx, inst); case IrOpcode::DataConsume: return Invoke(EmitDataConsume, ctx, inst); case IrOpcode::SwizzleU32: return Invoke(EmitSwizzleU32, ctx, inst); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp index 251b1a045..9fe529a7c 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp @@ -82,6 +82,10 @@ bool HasFlag(const MemoryInfo& mem, std::uint32_t flag) { return (mem.imageSampleFlags & flag) != 0u; } +const RdnaImageDimensionInfo& AddressDimension(const ImageEmitAccess& access) { + return RdnaImageDimensionInfoFor(access.mem.imageDimension); +} + std::uint32_t ZeroF32(SpirvEmitterState& state) { return ConstantF32(state, 0); } @@ -111,10 +115,10 @@ std::uint32_t AddressF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& acce return GetRdnaImageAddressComponentLayout(access.mem.imageSampleFlags, component).bitWidth == 16u ? EmitF16BitsToF32(ctx.state, value) : Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value); } -ImageSampleLayout Layout(const MemoryInfo& mem, RdnaImageDimension dimension) { +ImageSampleLayout Layout(const MemoryInfo& mem) { ImageSampleLayout layout; std::uint32_t cursor = 0; - const auto& info = RdnaImageDimensionInfoFor(dimension); + const auto& info = RdnaImageDimensionInfoFor(mem.imageDimension); if (HasFlag(mem, RdnaImageSampleFlagOffset)) { layout.offset = cursor++; } @@ -155,8 +159,8 @@ std::uint32_t CubeLayer(SpirvEmitterState& state, std::uint32_t value) { return result; } -std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t first, std::uint32_t components) { - if (first == NoImageComponent || access.mem.imageAddressComponents < first + components) { +std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t first, std::uint32_t components, std::uint32_t encoded) { + if (first == NoImageComponent || encoded < components || access.mem.imageAddressComponents < first + encoded) { ctx.Fail(access.inst, "has an image address with too few coordinate components"); } const bool cube = access.image.cube; @@ -184,7 +188,7 @@ std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access std::uint32_t CoordU32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { const auto components = RdnaImageDimensionInfoFor(access.image.dimension).coordinateComponents; - if (access.mem.imageAddressComponents < components) { + if (AddressDimension(access).coordinateComponents < components || access.mem.imageAddressComponents < components) { ctx.Fail(access.inst, "has an image address with too few coordinate components"); } const auto x = AddressU32(ctx, access, 0); @@ -206,7 +210,7 @@ std::uint32_t LodU32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) if (!access.mem.imageHasMip) { return ConstantU32(ctx.state, 0); } - const auto component = RdnaImageDimensionInfoFor(access.image.dimension).coordinateComponents; + const auto component = AddressDimension(access).coordinateComponents; if (access.mem.imageAddressComponents <= component) { ctx.Fail(access.inst, "has an image address without the mip level component"); } @@ -245,7 +249,9 @@ std::uint32_t SampledComponentZero(SpirvEmitterState& state, IrTextureNumericCla std::uint32_t TableResult(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t result) { if (access.table.mapped == 0) return result; auto& state = ctx.state; - return Select(state, TypeU32Vector(state, 4), access.table.mapped, result, ConstantU32CompositeZero(state, 4)); + const auto mapped = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeConstruct, TypeBoolVector(state, 4), mapped, access.table.mapped, access.table.mapped, access.table.mapped, access.table.mapped); + return Select(state, TypeU32Vector(state, 4), mapped, result, ConstantU32CompositeZero(state, 4)); } std::uint32_t ResultVector(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t value, IrTextureNumericClass numericClass, bool dref, bool gather) { @@ -689,7 +695,7 @@ void EmitQueryLodOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { auto& state = ctx.state; state.module.EmitCapability(spv::CapabilityImageQuery); const auto sampled = MakeSampledImage(state, access.mem.resource, access.mem.sampler, access.slot); - const auto coord = CoordF32(ctx, access, 0, RdnaImageDimensionInfoFor(access.image.dimension).spatialComponents); + const auto coord = CoordF32(ctx, access, 0, RdnaImageDimensionInfoFor(access.image.dimension).spatialComponents, AddressDimension(access).spatialComponents); const auto lod = state.module.AllocateId(); state.module.AddFunction(spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled, coord); std::uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0)}; @@ -713,10 +719,11 @@ void EmitReadOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { const auto color = state.module.AllocateId(); const auto coord = CoordU32(ctx, access); if (dimensionInfo.multisampled != 0u) { - if (access.mem.imageAddressComponents <= dimensionInfo.coordinateComponents) { + const auto& addressInfo = AddressDimension(access); + if (addressInfo.multisampled == 0u || access.mem.imageAddressComponents <= addressInfo.coordinateComponents) { ctx.Fail(access.inst, "has no sample index in the image address"); } - state.module.AddFunction(spv::OpImageFetch, ImageVectorType(state, numericClass, 4), color, descriptor, coord, spv::ImageOperandsSampleMask, AddressU32(ctx, access, dimensionInfo.coordinateComponents)); + state.module.AddFunction(spv::OpImageFetch, ImageVectorType(state, numericClass, 4), color, descriptor, coord, spv::ImageOperandsSampleMask, AddressU32(ctx, access, addressInfo.coordinateComponents)); } else { state.module.AddFunction(spv::OpImageFetch, ImageVectorType(state, numericClass, 4), color, descriptor, coord, spv::ImageOperandsLodMask, LodU32(ctx, access)); } @@ -781,12 +788,12 @@ void EmitAtomicOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { SampleSetup MakeSampleSetup(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { const auto& dimensionInfo = RdnaImageDimensionInfoFor(access.image.dimension); - const auto layout = Layout(access.mem, access.image.dimension); + const auto layout = Layout(access.mem); const bool dref = HasFlag(access.mem, RdnaImageSampleFlagCompare); if (dref && access.image.conversionFormat != IrBufferFormat::Invalid) { ctx.Fail(access.inst, "uses depth comparison with a packed integer image"); } - const auto coord = CoordF32(ctx, access, layout.coord, dimensionInfo.coordinateComponents); + const auto coord = CoordF32(ctx, access, layout.coord, dimensionInfo.coordinateComponents, AddressDimension(access).coordinateComponents); return {dimensionInfo, layout, access.image.numericClass, dref, coord}; } @@ -906,8 +913,8 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con std::vector operands; if (HasFlag(mem, RdnaImageSampleFlagDerivative)) { operandMask |= spv::ImageOperandsGradMask; - operands.push_back(CoordF32(ctx, access, setup.layout.gradX, setup.dimensionInfo.spatialComponents)); - operands.push_back(CoordF32(ctx, access, setup.layout.gradY, setup.dimensionInfo.spatialComponents)); + operands.push_back(CoordF32(ctx, access, setup.layout.gradX, setup.dimensionInfo.spatialComponents, AddressDimension(access).spatialComponents)); + operands.push_back(CoordF32(ctx, access, setup.layout.gradY, setup.dimensionInfo.spatialComponents, AddressDimension(access).spatialComponents)); } else if (explicitLod) { operandMask |= spv::ImageOperandsLodMask; operands.push_back(HasFlag(mem, RdnaImageSampleFlagLod) ? AddressF32(ctx, access, setup.layout.lod) : ZeroF32(state)); @@ -1199,35 +1206,41 @@ void EmitImageBvhIntersectRay(SpirvValueEmitContext& ctx, const IrValue& inst) { const IrValue* address = ctx.ImageAddress(inst.Argument(1)); const auto component = [&](std::uint32_t index) { return ctx.Arg(*address, index); }; const bool a16 = (ctx.Memory(inst).imageSampleFlags & RdnaImageSampleFlagA16) != 0u; + const bool node64 = ctx.Memory(inst).addressIsFull; + const std::uint32_t ray = node64 ? 2u : 1u; - const auto node = component(0); - const auto type = op(spv::OpBitwiseAnd, u32, node, uint(7u)); - const auto nodeIndex = op(spv::OpShiftRightLogical, u64, widen(node), BdaConstant(state, 3u)); + const auto nodeLow = component(0); + const auto node = node64 ? pair(nodeLow, component(1)) : widen(nodeLow); + const auto type = op(spv::OpBitwiseAnd, u32, nodeLow, uint(7u)); + const auto nodeIndex = op(spv::OpShiftRightLogical, u64, node, BdaConstant(state, 3u)); const auto baseUnits = pair(word[0], op(spv::OpBitwiseAnd, u32, word[1], uint(0xffu))); const auto lastNode = pair(word[2], op(spv::OpBitwiseAnd, u32, word[3], uint(0x3ffu))); const auto boxGrow = op(spv::OpBitwiseAnd, u32, op(spv::OpShiftRightLogical, u32, word[1], uint(23u)), uint(0xffu)); const auto boxSort = op(spv::OpINotEqual, boolean, op(spv::OpBitwiseAnd, u32, word[1], uint(0x80000000u)), uint(0u)); const auto barycentrics = op(spv::OpINotEqual, boolean, op(spv::OpBitwiseAnd, u32, word[3], uint(1u << 24u)), uint(0u)); - const auto nodeAddress = op(spv::OpIAdd, u64, op(spv::OpShiftLeftLogical, u64, baseUnits, BdaConstant(state, 8u)), op(spv::OpShiftLeftLogical, u64, widen(op(spv::OpBitwiseAnd, u32, node, uint(~7u))), BdaConstant(state, 3u))); - const auto present = op(spv::OpINotEqual, boolean, baseUnits, BdaConstant(state, 0u)); - const auto valid = both(present, op(spv::OpULessThanEqual, boolean, nodeIndex, lastNode)); + const auto nodeAddress = op(spv::OpIAdd, u64, op(spv::OpShiftLeftLogical, u64, baseUnits, BdaConstant(state, 8u)), op(spv::OpShiftLeftLogical, u64, op(spv::OpBitwiseAnd, u64, node, BdaConstant(state, ~std::uint64_t{7u})), BdaConstant(state, 3u))); + const auto within = [&](spv::Op compare) { + const auto inRange = op(compare, boolean, nodeIndex, lastNode); + return node64 ? inRange : both(op(spv::OpINotEqual, boolean, baseUnits, BdaConstant(state, 0u)), inRange); + }; + const auto valid = within(spv::OpULessThanEqual); const auto isTriangle = both(valid, op(spv::OpULessThanEqual, boolean, type, uint(1u))); const auto isBox16 = both(valid, op(spv::OpIEqual, boolean, type, uint(4u))); - const auto isBox32 = both(both(present, op(spv::OpULessThan, boolean, nodeIndex, lastNode)), op(spv::OpIEqual, boolean, type, uint(5u))); + const auto isBox32 = both(within(spv::OpULessThan), op(spv::OpIEqual, boolean, type, uint(5u))); - const auto extent = asFloat(component(1)); + const auto extent = asFloat(component(ray)); std::array origin{}; std::array direction{}; std::array inverse{}; for (std::uint32_t axis = 0; axis < 3u; ++axis) { - origin[axis] = asFloat(component(2u + axis)); + origin[axis] = asFloat(component(ray + 1u + axis)); if (!a16) { - direction[axis] = asFloat(component(5u + axis)); - inverse[axis] = asFloat(component(8u + axis)); + direction[axis] = asFloat(component(ray + 4u + axis)); + inverse[axis] = asFloat(component(ray + 7u + axis)); continue; } const auto half = [&](std::uint32_t index) { - const auto packed = component(5u + index / 2u); + const auto packed = component(ray + 4u + index / 2u); return EmitF16BitsToF32(state, index % 2u == 0u ? op(spv::OpBitwiseAnd, u32, packed, uint(0xffffu)) : op(spv::OpShiftRightLogical, u32, packed, uint(16u))); }; direction[axis] = half(axis); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp index 1292d8e47..9c7ee7228 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp @@ -11,8 +11,6 @@ namespace ShaderRecompiler { namespace { - constexpr std::uint32_t FunctionLdsDwords = 8192u; - [[noreturn]] void FailEmit(const std::string& reason) { throw std::runtime_error("SPIR-V module emission failed: " + reason); } @@ -41,7 +39,7 @@ namespace { if (ShaderWorkgroupInput(state) == nullptr) { FailEmit("function LDS was not prepared before function emission"); } - state.ldsVariable = state.module.DefineGlobalVariable(TypeU32ArrayPointer(state, spv::StorageClassWorkgroup, LdsDwordCount(state)), spv::StorageClassWorkgroup); + state.ldsVariable = state.module.DefineGlobalVariable(TypeU32ArrayPointer(state, spv::StorageClassWorkgroup, LdsDwordCount(state) + (state.requirements.ldsLock ? 1u : 0u)), spv::StorageClassWorkgroup); state.module.AddName(state.ldsVariable, "lds_dwords"); } @@ -96,7 +94,18 @@ void EmitMemoryOffsets(SpirvEmitterState& state) { std::uint32_t LdsDwordCount(const SpirvEmitterState& state) { const auto* workgroup = ShaderWorkgroupInput(state); - return workgroup != nullptr ? workgroup->ldsSizeDwords : FunctionLdsDwords; + if (workgroup != nullptr) return workgroup->ldsSizeDwords; + return state.requirements.functionLdsDwords != 0u ? state.requirements.functionLdsDwords : FunctionLdsDwordLimit; +} + +std::uint32_t EmitLdsLockPointer(SpirvEmitterState& state) { + if (!state.requirements.ldsLock) { + FailEmit("LDS lock was not requested by the program analysis"); + } + EnsureLdsStorage(state); + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpAccessChain, TypeU32ElementPointer(state, spv::StorageClassWorkgroup), pointer, state.ldsVariable, ConstantU32(state, LdsDwordCount(state))); + return pointer; } MemoryResourceAccess PrepareStorageBufferResourceAccess(SpirvEmitterState& state, const MemoryInfo& mem, std::uint32_t variable, std::uint32_t pointerType) { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp index 23216880a..3657db5d6 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp @@ -1,5 +1,6 @@ #include "SpirvBackend/SpirvBda.hpp" #include "SpirvBackend/SpirvEmitterInstructions.hpp" +#include "SpirvBackend/SpirvMemory/SpirvSubgroup.hpp" #include "SpirvBackend/SpirvBufferFormat.hpp" #include #include @@ -87,6 +88,11 @@ std::uint32_t ByteAddress(SpirvValueEmitContext& ctx, const IrValue& inst, const return BufferByteAddress(ctx, inst, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2), ctx.Arg(inst, 3)); case ResourceKind::Lds: case ResourceKind::Gds: + if (mem.kind == ResourceKind::Lds) { + if (const auto found = state.requirements.functionLdsAddresses.find(&inst); found != state.requirements.functionLdsAddresses.end()) { + return ConstantU32(state, found->second + mem.offset); + } + } if (mem.offset == 0u) { return ctx.Arg(inst, 0); } @@ -732,13 +738,22 @@ void LoadAddressWide(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint3 void StoreAddress(SpirvValueEmitContext& ctx, const IrValue& inst, std::uint32_t bits) { const auto& mem = ctx.Memory(inst); - if (mem.kind != ResourceKind::Scratch) { - ctx.Fail(inst, "must write a scratch resource because physical address stores have no emitter"); - } - if (bits == 32u) { - StoreWord(ctx, inst, mem); - } else { - StoreSubword(ctx, inst, mem, bits); + switch (mem.kind) { + case ResourceKind::Scratch: + if (bits == 32u) { + StoreWord(ctx, inst, mem); + } else { + StoreSubword(ctx, inst, mem, bits); + } + return; + case ResourceKind::Flat: + case ResourceKind::Global: + EmitIfCondition(ctx.state, ActiveArgument(ctx, inst), [&]() { + EmitBdaStore(ctx, inst, GuestAddress(ctx, inst, mem), ctx.Arg(inst, inst.ArgumentCount() - 2u), bits); + }); + return; + default: + ctx.Fail(inst, "must write a scratch or physical address resource"); } } @@ -985,6 +1000,16 @@ struct AtomicFloatBits { result = select(signalingNan(old), op(spv::OpBitwiseOr, old, constant(quiet())), result); return select(signalingNan(source), op(spv::OpBitwiseOr, source, constant(quiet())), result); } + std::uint32_t compareSwap(std::uint32_t old, std::uint32_t comparator, std::uint32_t desired) const { + return select(equal(old, comparator), desired, old); + } + std::uint32_t increment(std::uint32_t old, std::uint32_t limit) const { + return select(test(spv::OpUGreaterThanEqual, old, limit), constant(0u), op(spv::OpIAdd, old, constant(1u))); + } + std::uint32_t decrement(std::uint32_t old, std::uint32_t limit) const { + const auto wrap = Binary(state, spv::OpLogicalOr, TypeBool(state), test(spv::OpIEqual, old, constant(0u)), test(spv::OpUGreaterThan, old, limit)); + return select(wrap, limit, op(spv::OpISub, old, constant(1u))); + } }; std::uint32_t BufferFloatAtomic(SpirvValueEmitContext& ctx, const IrValue& inst, bool maxValue) { @@ -1045,6 +1070,106 @@ std::uint32_t SharedFloatMinMax(SpirvValueEmitContext& ctx, const IrValue& inst, }); } +std::uint32_t AddressAtomicOpcode(IrOpcode opcode) { + switch (opcode) { + case IrOpcode::AddressAtomicSwap32: + case IrOpcode::AddressAtomicSwap64: + return spv::OpAtomicExchange; + case IrOpcode::AddressAtomicCmpSwap32: + case IrOpcode::AddressAtomicCmpSwap64: + return spv::OpAtomicCompareExchange; + case IrOpcode::AddressAtomicIAdd32: + case IrOpcode::AddressAtomicIAdd64: + return spv::OpAtomicIAdd; + case IrOpcode::AddressAtomicISub32: + case IrOpcode::AddressAtomicISub64: + return spv::OpAtomicISub; + case IrOpcode::AddressAtomicSMin32: + case IrOpcode::AddressAtomicSMin64: + return spv::OpAtomicSMin; + case IrOpcode::AddressAtomicUMin32: + case IrOpcode::AddressAtomicUMin64: + return spv::OpAtomicUMin; + case IrOpcode::AddressAtomicSMax32: + case IrOpcode::AddressAtomicSMax64: + return spv::OpAtomicSMax; + case IrOpcode::AddressAtomicUMax32: + case IrOpcode::AddressAtomicUMax64: + return spv::OpAtomicUMax; + case IrOpcode::AddressAtomicAnd32: + case IrOpcode::AddressAtomicAnd64: + return spv::OpAtomicAnd; + case IrOpcode::AddressAtomicOr32: + case IrOpcode::AddressAtomicOr64: + return spv::OpAtomicOr; + case IrOpcode::AddressAtomicXor32: + case IrOpcode::AddressAtomicXor64: + return spv::OpAtomicXor; + default: + throw std::runtime_error("AddressAtomicOpcode: opcode has no SPIR-V atomic instruction"); + } +} + +std::uint32_t AddressAtomic(SpirvValueEmitContext& ctx, const IrValue& inst) { + auto& state = ctx.state; + const auto& mem = ctx.Memory(inst); + if (mem.kind != ResourceKind::Flat && mem.kind != ResourceKind::Global) { + ctx.Fail(inst, "must access a physical address resource"); + } + const bool wide = inst.Type() == IrType::U64; + const auto type = wide ? TypeU64(state) : TypeU32(state); + const auto zero = wide ? ConstantU64(state, 0u) : ConstantU32(state, 0u); + return EmitValueOrDefaultIfCondition(state, ActiveArgument(ctx, inst), type, zero, [&]() { + const auto scalarType = wide ? TypeScalarU64(state) : TypeU32(state); + const auto scalar = [&](std::uint32_t value) { return wide ? Unary(state, spv::OpBitcast, scalarType, value) : value; }; + const auto value = scalar(ctx.Arg(inst, 3)); + const auto old = EmitBdaAtomic(ctx, inst, GuestAddress(ctx, inst, mem), wide ? 8u : 4u, [&](std::uint32_t pointer) { + if (inst.Opcode() == IrOpcode::AddressAtomicInc32 || inst.Opcode() == IrOpcode::AddressAtomicDec32) { + const bool increment = inst.Opcode() == IrOpcode::AddressAtomicInc32; + return AtomicUpdate(state, pointer, mem.kind, [&](std::uint32_t current) { + return increment ? AtomicIncrement(state, current, value) : AtomicDecrement(state, current, value); + }); + } + if (inst.Opcode() == IrOpcode::AddressAtomicUSubSat32) { + return AtomicUpdate(state, pointer, mem.kind, [&](std::uint32_t current) { return AtomicUSubSat(state, current, value); }); + } + const AtomicFloatBits bits{state, wide}; + const auto update = [&](auto&& replacement) { return AtomicUpdateTyped(state, pointer, mem.kind, scalarType, replacement); }; + switch (inst.Opcode()) { + case IrOpcode::AddressAtomicFCmpSwap32: + case IrOpcode::AddressAtomicFCmpSwap64: { + const auto comparator = scalar(ctx.Arg(inst, 4)); + return update([&](std::uint32_t current) { return bits.compareSwap(current, comparator, value); }); + } + case IrOpcode::AddressAtomicFMin32: + case IrOpcode::AddressAtomicFMin64: + return update([&](std::uint32_t current) { return bits.minMax(current, value, false); }); + case IrOpcode::AddressAtomicFMax32: + case IrOpcode::AddressAtomicFMax64: + return update([&](std::uint32_t current) { return bits.minMax(current, value, true); }); + case IrOpcode::AddressAtomicInc64: + return update([&](std::uint32_t current) { return bits.increment(current, value); }); + case IrOpcode::AddressAtomicDec64: + return update([&](std::uint32_t current) { return bits.decrement(current, value); }); + default: + break; + } + const auto scope = ConstantU32(state, spv::ScopeDevice); + const auto semantics = ConstantU32(state, spv::MemorySemanticsMaskNone); + const auto opcode = AddressAtomicOpcode(inst.Opcode()); + const auto result = state.module.AllocateId(); + if (opcode == spv::OpAtomicCompareExchange) { + state.module.AddFunction(opcode, scalarType, result, pointer, scope, semantics, semantics, value, scalar(ctx.Arg(inst, 4))); + } else { + state.module.AddFunction(opcode, scalarType, result, pointer, scope, semantics, value); + } + EmitDeviceAtomicMemoryBarrier(state); + return result; + }); + return wide ? Unary(state, spv::OpBitcast, type, old) : old; + }); +} + std::uint32_t AppendConsume(SpirvValueEmitContext& ctx, const IrValue& inst, bool append) { auto& state = ctx.state; if (ctx.half == 1u) { @@ -1082,10 +1207,110 @@ std::uint32_t AppendConsume(SpirvValueEmitContext& ctx, const IrValue& inst, boo return value; }); const auto result = state.module.AllocateId(); - state.module.AddFunction(spv::OpGroupNonUniformShuffle, TypeU32(state), result, ConstantU32(state, spv::ScopeSubgroup), atomic, sourceLane); + state.module.AddFunction(spv::OpGroupNonUniformShuffle, TypeU32(state), result, ConstantU32(state, spv::ScopeSubgroup), atomic, EmitHostSubgroupLane(state, sourceLane)); return result; } +template +std::uint32_t LockedLdsUpdate(SpirvEmitterState& state, TUpdate&& update) { + const auto lock = EmitLdsLockPointer(state); + const auto header = state.module.AllocateId(); + const auto body = state.module.AllocateId(); + const auto critical = state.module.AllocateId(); + const auto after = state.module.AllocateId(); + const auto cont = state.module.AllocateId(); + const auto merge = state.module.AllocateId(); + state.module.AddFunction(spv::OpBranch, header); + EmitLabel(state, header); + state.module.AddFunction(spv::OpLoopMerge, merge, cont, spv::LoopControlMaskNone); + state.module.AddFunction(spv::OpBranch, body); + EmitLabel(state, body); + const auto previous = state.module.AllocateId(); + state.module.AddFunction(spv::OpAtomicCompareExchange, TypeU32(state), previous, lock, ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, spv::MemorySemanticsAcquireMask | spv::MemorySemanticsWorkgroupMemoryMask), ConstantU32(state, spv::MemorySemanticsMaskNone), ConstantU32(state, 1u), ConstantU32(state, 0u)); + const auto acquired = Binary(state, spv::OpIEqual, TypeBool(state), previous, ConstantU32(state, 0u)); + state.module.AddFunction(spv::OpSelectionMerge, after, spv::SelectionControlMaskNone); + state.module.AddFunction(spv::OpBranchConditional, acquired, critical, after); + EmitLabel(state, critical); + const auto old = update(); + state.module.AddFunction(spv::OpAtomicStore, lock, ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, spv::MemorySemanticsReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask), ConstantU32(state, 0u)); + const auto criticalExit = state.currentLabel; + state.module.AddFunction(spv::OpBranch, after); + EmitLabel(state, after); + const auto result = state.module.AllocateId(); + state.module.AddFunction(spv::OpPhi, TypeU64(state), result, old, criticalExit, ConstantU64(state, 0u), body); + state.module.AddFunction(spv::OpBranchConditional, acquired, merge, cont); + EmitLabel(state, cont); + state.module.AddFunction(spv::OpBranch, header); + EmitLabel(state, merge); + return result; +} + +template +std::uint32_t SharedAtomic64(SpirvValueEmitContext& ctx, const IrValue& inst, TReplacement&& replacement) { + auto& state = ctx.state; + const auto& mem = SharedMemory(ctx, inst); + return EmitValueOrDefaultIfCondition(state, ActiveArgument(ctx, inst), TypeU64(state), ConstantU64(state, 0u), [&]() { + const auto resource = PrepareMemoryResourceAccess(state, mem); + const auto rawIndex = DwordIndex(ctx, inst, mem); + const auto low = EmitMemoryElementIndex(state, resource, rawIndex); + const auto high = EmitMemoryElementIndex(state, resource, Binary(state, spv::OpIAdd, TypeU32(state), rawIndex, ConstantU32(state, 1u))); + return EmitValueOrDefaultIfCondition(state, EmitMemoryElementInBounds(state, resource, high), TypeU64(state), ConstantU64(state, 0u), [&]() { + const auto update = [&]() { + const auto lowPointer = EmitMemoryElementPointer(state, resource, low); + const auto highPointer = EmitMemoryElementPointer(state, resource, high); + const auto lowValue = state.module.AllocateId(); + const auto highValue = state.module.AllocateId(); + state.module.AddFunction(spv::OpLoad, TypeU32(state), lowValue, lowPointer); + state.module.AddFunction(spv::OpLoad, TypeU32(state), highValue, highPointer); + const auto old = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeConstruct, TypeU64(state), old, lowValue, highValue); + const auto next = Unary(state, spv::OpBitcast, TypeU64(state), replacement(state, Unary(state, spv::OpBitcast, TypeScalarU64(state), old))); + const auto nextLow = state.module.AllocateId(); + const auto nextHigh = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), nextLow, next, 0u); + state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), nextHigh, next, 1u); + state.module.AddFunction(spv::OpStore, lowPointer, nextLow); + state.module.AddFunction(spv::OpStore, highPointer, nextHigh); + return old; + }; + return state.requirements.ldsLock ? LockedLdsUpdate(state, update) : update(); + }); + }); +} + +template +std::uint32_t SharedAtomic64Binary(SpirvValueEmitContext& ctx, const IrValue& inst, TOperation&& operation) { + const auto data = ScalarU64Argument(ctx, inst, 2u); + return SharedAtomic64(ctx, inst, [&](SpirvEmitterState& state, std::uint32_t old) { + return operation(state, old, data); + }); +} + +std::uint32_t SharedAtomic64Op(SpirvValueEmitContext& ctx, const IrValue& inst, spv::Op opcode, bool dataFirst) { + return SharedAtomic64Binary(ctx, inst, [&](SpirvEmitterState& state, std::uint32_t old, std::uint32_t data) { + return dataFirst ? Binary(state, opcode, TypeScalarU64(state), data, old) : Binary(state, opcode, TypeScalarU64(state), old, data); + }); +} + +std::uint32_t SharedAtomic64Select(SpirvValueEmitContext& ctx, const IrValue& inst, spv::Op compare) { + return SharedAtomic64Binary(ctx, inst, [&](SpirvEmitterState& state, std::uint32_t old, std::uint32_t data) { + return Select(state, TypeScalarU64(state), Binary(state, compare, TypeBool(state), data, old), data, old); + }); +} + +std::uint32_t SharedFloatMinMax64(SpirvValueEmitContext& ctx, const IrValue& inst, bool maxValue) { + return SharedAtomic64Binary(ctx, inst, [&](SpirvEmitterState& state, std::uint32_t old, std::uint32_t data) { + const AtomicFloatBits bits{state, true}; + auto result = bits.select(bits.test(maxValue ? spv::OpUGreaterThan : spv::OpULessThan, bits.orderKey(data), bits.orderKey(old)), data, old); + result = bits.select(bits.nan(data), old, result); + return bits.select(bits.nan(old), data, result); + }); +} + +std::uint32_t ScalarU64Constant(SpirvEmitterState& state, std::uint32_t value) { + return state.module.Constant(spv::OpConstant, TypeScalarU64(state), value, 0u); +} + } std::uint32_t AtomicIncrement(SpirvEmitterState& state, std::uint32_t old, std::uint32_t limit) { @@ -1102,6 +1327,12 @@ std::uint32_t AtomicDecrement(SpirvEmitterState& state, std::uint32_t old, std:: return Select(state, TypeU32(state), wrap, limit, next); } +std::uint32_t AtomicUSubSat(SpirvEmitterState& state, std::uint32_t old, std::uint32_t value) { + const auto fits = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), old, value); + const auto difference = Binary(state, spv::OpISub, TypeU32(state), old, value); + return Select(state, TypeU32(state), fits, difference, ConstantU32(state, 0u)); +} + std::uint32_t AtomicFloatMinMax(SpirvEmitterState& state, std::uint32_t old, std::uint32_t source, bool maxValue) { return AtomicFloatBits{state, false}.minMax(old, source, maxValue); } @@ -1133,6 +1364,7 @@ void EmitReadConstBuffer(SpirvValueEmitContext& ctx, const IrValue& inst) { } auto& state = ctx.state; const auto address = Binary(state, spv::OpIAdd, TypeU32(state), ctx.Arg(inst, 1), ConstantU32(state, mem.offset)); + const auto noCarry = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), address, ctx.Arg(inst, 1)); if (mem.gpuDescriptor) { const IrValue* handle = inst.Argument(0)->Resolve(); if (handle->Opcode() != IrOpcode::GetBufferResource || handle->ArgumentCount() != 4u) { @@ -1144,7 +1376,7 @@ void EmitReadConstBuffer(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto stride = EmitBitFieldUExtract(state, word1, ConstantU32(state, 16u), ConstantU32(state, 14u)); const auto size = Select(state, u32, Binary(state, spv::OpIEqual, TypeBool(state), stride, ConstantU32(state, 0u)), records, Binary(state, spv::OpIMul, u32, stride, records)); const auto byte = Binary(state, spv::OpBitwiseAnd, u32, address, ConstantU32(state, ~3u)); - const auto inBounds = Binary(state, spv::OpULessThan, TypeBool(state), byte, size); + const auto inBounds = AndCondition(state, noCarry, Binary(state, spv::OpULessThan, TypeBool(state), byte, size)); const auto base = DeviceAddressFromWords(state, ctx.Arg(*handle, 0), EmitBitFieldUExtract(state, word1, ConstantU32(state, 0u), ConstantU32(state, 16u))); const auto guest = Binary(state, spv::OpIAdd, TypeScalarU64(state), base, Unary(state, spv::OpUConvert, TypeScalarU64(state), byte)); ctx.Define(inst, EmitValueOrZeroIfCondition(state, inBounds, [&] { return EmitBdaRead(ctx, inst, guest, 32u); })); @@ -1153,7 +1385,7 @@ void EmitReadConstBuffer(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto rawIndex = Binary(state, spv::OpShiftRightLogical, TypeU32(state), address, ConstantU32(state, 2u)); const auto access = PrepareMemoryResourceAccess(state, mem); const auto element = EmitMemoryElementIndex(state, access, rawIndex); - const auto inBounds = EmitMemoryElementInBounds(state, access, element); + const auto inBounds = AndCondition(state, noCarry, EmitMemoryElementInBounds(state, access, element)); ctx.Define(inst, EmitValueOrZeroIfCondition(state, inBounds, [&]() { const auto value = state.module.AllocateId(); state.module.AddFunction(spv::OpLoad, TypeU32(state), value, EmitMemoryElementPointer(state, access, element)); @@ -1213,6 +1445,10 @@ void EmitStoreAddressU32(SpirvValueEmitContext& ctx, const IrValue& inst) { StoreAddress(ctx, inst, 32u); } +std::uint32_t EmitAddressAtomic(SpirvValueEmitContext& ctx, const IrValue& inst) { + return AddressAtomic(ctx, inst); +} + void EmitLoadBufferU8(SpirvValueEmitContext& ctx, const IrValue& inst) { ctx.Define(inst, LoadSubword(ctx, inst, BufferMemory(ctx, inst), 8u)); } @@ -1499,6 +1735,99 @@ std::uint32_t EmitSharedAtomicWrap32(SpirvValueEmitContext& ctx, const IrValue& }); } +std::uint32_t EmitSharedAtomicSwap64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Binary(ctx, inst, [](SpirvEmitterState&, std::uint32_t, std::uint32_t data) { return data; }); +} + +std::uint32_t EmitSharedAtomicIAdd64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Op(ctx, inst, spv::OpIAdd, false); +} + +std::uint32_t EmitSharedAtomicISub64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Op(ctx, inst, spv::OpISub, false); +} + +std::uint32_t EmitSharedAtomicRsub64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Op(ctx, inst, spv::OpISub, true); +} + +std::uint32_t EmitSharedAtomicInc64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Binary(ctx, inst, [](SpirvEmitterState& state, std::uint32_t old, std::uint32_t limit) { + const auto wrap = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), old, limit); + return Select(state, TypeScalarU64(state), wrap, ScalarU64Constant(state, 0u), Binary(state, spv::OpIAdd, TypeScalarU64(state), old, ScalarU64Constant(state, 1u))); + }); +} + +std::uint32_t EmitSharedAtomicDec64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Binary(ctx, inst, [](SpirvEmitterState& state, std::uint32_t old, std::uint32_t limit) { + const auto zero = Binary(state, spv::OpIEqual, TypeBool(state), old, ScalarU64Constant(state, 0u)); + const auto wrap = Binary(state, spv::OpLogicalOr, TypeBool(state), zero, Binary(state, spv::OpUGreaterThan, TypeBool(state), old, limit)); + return Select(state, TypeScalarU64(state), wrap, limit, Binary(state, spv::OpISub, TypeScalarU64(state), old, ScalarU64Constant(state, 1u))); + }); +} + +std::uint32_t EmitSharedAtomicSMin64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Select(ctx, inst, spv::OpSLessThan); +} + +std::uint32_t EmitSharedAtomicUMin64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Select(ctx, inst, spv::OpULessThan); +} + +std::uint32_t EmitSharedAtomicSMax64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Select(ctx, inst, spv::OpSGreaterThan); +} + +std::uint32_t EmitSharedAtomicUMax64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Select(ctx, inst, spv::OpUGreaterThan); +} + +std::uint32_t EmitSharedAtomicAnd64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Op(ctx, inst, spv::OpBitwiseAnd, false); +} + +std::uint32_t EmitSharedAtomicOr64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Op(ctx, inst, spv::OpBitwiseOr, false); +} + +std::uint32_t EmitSharedAtomicXor64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedAtomic64Op(ctx, inst, spv::OpBitwiseXor, false); +} + +std::uint32_t EmitSharedAtomicFMin64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedFloatMinMax64(ctx, inst, false); +} + +std::uint32_t EmitSharedAtomicFMax64(SpirvValueEmitContext& ctx, const IrValue& inst) { + return SharedFloatMinMax64(ctx, inst, true); +} + +std::uint32_t EmitSharedAtomicCmpst64(SpirvValueEmitContext& ctx, const IrValue& inst) { + const auto compare = ScalarU64Argument(ctx, inst, 3u); + const auto source = ScalarU64Argument(ctx, inst, 2u); + return SharedAtomic64(ctx, inst, [&](SpirvEmitterState& state, std::uint32_t old) { + return Select(state, TypeScalarU64(state), Binary(state, spv::OpIEqual, TypeBool(state), old, compare), source, old); + }); +} + +std::uint32_t EmitSharedAtomicCmpstF64(SpirvValueEmitContext& ctx, const IrValue& inst) { + const auto compare = ScalarU64Argument(ctx, inst, 3u); + const auto source = ScalarU64Argument(ctx, inst, 2u); + return SharedAtomic64(ctx, inst, [&](SpirvEmitterState& state, std::uint32_t old) { + const AtomicFloatBits bits{state, true}; + return bits.select(bits.equal(old, compare), source, old); + }); +} + +std::uint32_t EmitSharedAtomicMskor64(SpirvValueEmitContext& ctx, const IrValue& inst) { + const auto mask = ScalarU64Argument(ctx, inst, 3u); + const auto source = ScalarU64Argument(ctx, inst, 2u); + return SharedAtomic64(ctx, inst, [&](SpirvEmitterState& state, std::uint32_t old) { + const auto kept = Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), old, Unary(state, spv::OpNot, TypeScalarU64(state), mask)); + return Binary(state, spv::OpBitwiseOr, TypeScalarU64(state), kept, source); + }); +} + std::uint32_t EmitBufferAtomicInc32(SpirvValueEmitContext& ctx, const IrValue& inst) { return EmitAtomicUpdate(ctx, inst, BufferMemory(ctx, inst), AtomicIncrement); } @@ -1507,6 +1836,10 @@ std::uint32_t EmitBufferAtomicDec32(SpirvValueEmitContext& ctx, const IrValue& i return EmitAtomicUpdate(ctx, inst, BufferMemory(ctx, inst), AtomicDecrement); } +std::uint32_t EmitBufferAtomicUSubSat32(SpirvValueEmitContext& ctx, const IrValue& inst) { + return EmitAtomicUpdate(ctx, inst, BufferMemory(ctx, inst), AtomicUSubSat); +} + std::uint32_t EmitBufferAtomicIAdd64(SpirvValueEmitContext& ctx, const IrValue& inst) { return BufferAtomic64(ctx, inst); } @@ -1548,20 +1881,14 @@ std::uint32_t EmitBufferAtomicFCmpSwap32(SpirvValueEmitContext& ctx, const IrVal const auto comparator = ctx.Arg(inst, inst.ArgumentCount() - 2u); const auto& mem = BufferMemory(ctx, inst); return EmitAtomicAccess(ctx, inst, mem, [&](std::uint32_t pointer) { - return AtomicUpdate(ctx.state, pointer, mem.kind, [&](std::uint32_t old) { - const AtomicFloatBits bits{ctx.state, false}; - return bits.select(bits.equal(old, comparator), desired, old); - }); + return AtomicUpdate(ctx.state, pointer, mem.kind, [&](std::uint32_t old) { return AtomicFloatBits{ctx.state, false}.compareSwap(old, comparator, desired); }); }); } std::uint32_t EmitBufferAtomicFCmpSwap64(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto desired = ScalarU64Argument(ctx, inst, 3u); const auto comparator = ScalarU64Argument(ctx, inst, 2u); - return BufferAtomic64Update(ctx, inst, [&](std::uint32_t old) { - const AtomicFloatBits bits{ctx.state, true}; - return bits.select(bits.equal(old, comparator), desired, old); - }); + return BufferAtomic64Update(ctx, inst, [&](std::uint32_t old) { return AtomicFloatBits{ctx.state, true}.compareSwap(old, comparator, desired); }); } std::uint32_t EmitBufferAtomicFMin64(SpirvValueEmitContext& ctx, const IrValue& inst) { @@ -1576,19 +1903,12 @@ std::uint32_t EmitBufferAtomicFMax64(SpirvValueEmitContext& ctx, const IrValue& std::uint32_t EmitBufferAtomicInc64(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto limit = ScalarU64Argument(ctx, inst, 2u); - return BufferAtomic64Update(ctx, inst, [&](std::uint32_t old) { - const AtomicFloatBits bits{ctx.state, true}; - return bits.select(bits.test(spv::OpUGreaterThanEqual, old, limit), bits.constant(0u), bits.op(spv::OpIAdd, old, bits.constant(1u))); - }); + return BufferAtomic64Update(ctx, inst, [&](std::uint32_t old) { return AtomicFloatBits{ctx.state, true}.increment(old, limit); }); } std::uint32_t EmitBufferAtomicDec64(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto limit = ScalarU64Argument(ctx, inst, 2u); - return BufferAtomic64Update(ctx, inst, [&](std::uint32_t old) { - const AtomicFloatBits bits{ctx.state, true}; - const auto wrap = Binary(ctx.state, spv::OpLogicalOr, TypeBool(ctx.state), bits.test(spv::OpIEqual, old, bits.constant(0u)), bits.test(spv::OpUGreaterThan, old, limit)); - return bits.select(wrap, limit, bits.op(spv::OpISub, old, bits.constant(1u))); - }); + return BufferAtomic64Update(ctx, inst, [&](std::uint32_t old) { return AtomicFloatBits{ctx.state, true}.decrement(old, limit); }); } } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp index 3a3b9539d..9b56e98ae 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp @@ -123,6 +123,9 @@ void DefineModule(SpirvEmitterState& state) { state.module.EmitCapability(spv::CapabilityInt64); state.module.EmitCapability(spv::CapabilityInt64Atomics); } + if (state.requirements.sharedInt64Atomics) { + state.module.EmitCapability(spv::CapabilityInt64); + } if (state.clipDistanceVariable != 0) { state.module.EmitCapability(spv::CapabilityClipDistance); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp index 314e04cd0..6532d500d 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvSubgroup.cpp @@ -17,20 +17,41 @@ namespace ShaderRecompiler throw std::runtime_error("SPIR-V module emission failed: " + reason); } + std::uint32_t HostInvocationId(SpirvEmitterState& state) { + if (state.subgroupLocalInvocationIdVariable == 0) { + FailEmit("SubgroupLocalInvocationId was not declared before function emission"); + } + const auto value = state.module.AllocateId(); + state.module.AddFunction(spv::OpLoad, TypeU32(state), value, state.subgroupLocalInvocationIdVariable); + return value; + } + } std::uint32_t EmitSubgroupLocalInvocationId(SpirvEmitterState& state) { - if (state.subgroupLocalInvocationIdVariable == 0) { - FailEmit("SubgroupLocalInvocationId was not declared before function emission"); - } - const auto value = state.module.AllocateId(); - state.module.AddFunction(spv::OpLoad, TypeU32(state), value, state.subgroupLocalInvocationIdVariable); + const auto value = HostInvocationId(state); + if (state.splitSubgroup) return EmitBinaryU32(state, spv::OpBitwiseAnd, value, ConstantU32(state, 31u)); return state.laneHalf == 0 ? value : EmitAddU32(state, value, ConstantU32(state, 32)); } - DppTargetLane EmitDppQuadPermTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t control) { - constexpr std::uint32_t laneBits = 2u; - constexpr std::uint32_t laneMask = (1u << laneBits) - 1u; + std::uint32_t EmitHostSubgroupLane(SpirvEmitterState& state, std::uint32_t lane) { + if (!state.splitSubgroup) return lane; + const auto base = EmitBinaryU32(state, spv::OpBitwiseAnd, HostInvocationId(state), ConstantU32(state, ~31u)); + return EmitAddU32(state, base, lane); + } + + std::uint32_t EmitWaveBallot(SpirvEmitterState& state, std::uint32_t ballot) { + if (!state.splitSubgroup) return ballot; + const auto word = EmitBinaryU32(state, spv::OpShiftRightLogical, HostInvocationId(state), ConstantU32(state, 5u)); + const auto mask = state.module.AllocateId(); + const auto result = state.module.AllocateId(); + state.module.AddFunction(spv::OpVectorExtractDynamic, TypeU32(state), mask, ballot, word); + state.module.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4u), result, mask, ConstantU32(state, 0u), ConstantU32(state, 0u), ConstantU32(state, 0u)); + return result; + } + + DppTargetLane EmitDppGroupPermTargetLane(SpirvEmitterState& state, std::uint32_t subid, std::uint32_t control, std::uint32_t laneBits) { + const std::uint32_t laneMask = (1u << laneBits) - 1u; const auto groupBase = state.module.AllocateId(); const auto lane = state.module.AllocateId(); const auto shift = state.module.AllocateId(); @@ -99,8 +120,11 @@ namespace ShaderRecompiler DppTargetLane EmitDppTargetLane(SpirvEmitterState& state, std::uint32_t control) { const auto subid = EmitSubgroupLocalInvocationId(state); + if ((control & DppMoveFlags::Lanes8) != 0u) { + return EmitDppGroupPermTargetLane(state, subid, control & ~DppMoveFlags::Lanes8, 3u); + } if (control <= 0xffu) { - return EmitDppQuadPermTargetLane(state, subid, control); + return EmitDppGroupPermTargetLane(state, subid, control, 2u); } if (control >= 0x101u && control <= 0x10fu) { return EmitDppRowShiftTargetLane(state, subid, control & 0xfu, true); @@ -117,6 +141,13 @@ namespace ShaderRecompiler if (control == 0x141u) { return EmitDppMirrorTargetLane(state, subid, true); } + if (control >= 0x150u && control <= 0x15fu) { + const auto row = state.module.AllocateId(); + const auto target = state.module.AllocateId(); + state.module.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, subid, ConstantU32(state, 0xfffffff0u)); + state.module.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, row, ConstantU32(state, control & 0xfu)); + return {target, ConstantBool(state, true)}; + } if (control >= 0x160u && control <= 0x16fu) { const auto target = state.module.AllocateId(); state.module.AddFunction(spv::OpBitwiseXor, TypeU32(state), target, subid, ConstantU32(state, control & 0xfu)); @@ -157,6 +188,6 @@ namespace ShaderRecompiler std::uint32_t EmitSubgroupLaneActiveBool(SpirvEmitterState& state, std::uint32_t lane) { const auto activeBallot = state.module.AllocateId(); state.module.AddFunction(spv::OpGroupNonUniformBallot, TypeU32Vector(state, 4), activeBallot, ConstantU32(state, spv::ScopeSubgroup), ConstantBool(state, true)); - return EmitBallotLaneActiveBool(state, activeBallot, lane); + return EmitBallotLaneActiveBool(state, EmitWaveBallot(state, activeBallot), lane); } } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index b85d7ee98..1fb490ff1 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -809,8 +809,8 @@ std::uint32_t EmitMinMaxF32Value(SpirvEmitterState& state, std::uint32_t lhs, st const auto zeroBits = Binary(state, maxValue ? spv::OpBitwiseAnd : spv::OpBitwiseOr, TypeU32(state), lhsClass.bits, rhsClass.bits); const auto numericBits = Select(state, TypeU32(state), bothZero, zeroBits, orderedBits); - const auto rhsNanBits = Select(state, TypeU32(state), rhsClass.nan, lhsClass.bits, numericBits); - const auto resultBits = Select(state, TypeU32(state), lhsClass.nan, rhsClass.bits, rhsNanBits); + const auto lhsNanBits = Select(state, TypeU32(state), lhsClass.nan, rhsClass.bits, numericBits); + const auto resultBits = Select(state, TypeU32(state), rhsClass.nan, lhsClass.bits, lhsNanBits); return Unary(state, spv::OpBitcast, TypeF32(state), resultBits); } @@ -916,6 +916,10 @@ void EmitProgram(SpirvEmitterState& state) { state.module.AddFunction(spv::OpVariable, TypePointer(state, spv::StorageClassFunction, TypeU32(state)), lane.scratchU32Variable, spv::StorageClassFunction); } } + if (state.requirements.ldsLock) { + state.module.AddFunction(spv::OpStore, EmitLdsLockPointer(state), ConstantU32(state, 0u)); + state.module.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask)); + } if (state.gdsVariable != 0u) { state.gdsLength = state.module.AllocateId(); state.module.AddFunction(spv::OpArrayLength, TypeU32(state), state.gdsLength, state.gdsVariable, 0u); diff --git a/core/shader/recompiler/ThreadOwned.hpp b/core/shader/recompiler/ThreadOwned.hpp new file mode 100644 index 000000000..94a0d084b --- /dev/null +++ b/core/shader/recompiler/ThreadOwned.hpp @@ -0,0 +1,59 @@ +#ifndef CORE_SHADER_RECOMPILER_THREADOWNED_HPP +#define CORE_SHADER_RECOMPILER_THREADOWNED_HPP + +#include +#include +#include +#include + +namespace ShaderRecompiler { + +namespace ThreadOwnedDetail { + +struct Owned { + void* object; + void (*destroy)(void*); +}; + +inline void DestroyAll(void* value) { + auto* owned = static_cast*>(value); + for (auto it = owned->rbegin(); it != owned->rend(); ++it) it->destroy(it->object); + delete owned; +} + +inline pthread_key_t Key() { + static const pthread_key_t key = [] { + pthread_key_t created{}; + if (pthread_key_create(&created, DestroyAll) != 0) throw std::runtime_error("ThreadOwned: pthread_key_create failed"); + return created; + }(); + return key; +} + +inline void DestroyAtThreadExit(void* object, void (*destroy)(void*)) { + auto* owned = static_cast*>(pthread_getspecific(Key())); + if (owned == nullptr) { + owned = new std::vector(); + if (pthread_setspecific(Key(), owned) != 0) { + delete owned; + throw std::runtime_error("ThreadOwned: pthread_setspecific failed"); + } + } + owned->push_back({object, destroy}); +} + +} + +template +T& ThreadOwned(T*& slot) { + if (slot == nullptr) { + auto object = std::make_unique(); + ThreadOwnedDetail::DestroyAtThreadExit(object.get(), [](void* value) { delete static_cast(value); }); + slot = object.release(); + } + return *slot; +} + +} + +#endif diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index 8622e85b1..3bf343733 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -4,6 +4,7 @@ #include "Translation/InstructionTranslator.hpp" #include #include +#include namespace ShaderRecompiler { @@ -44,6 +45,7 @@ private: IrU1 threadBit(const std::array& mask); void writeRawU32(const RdnaOperand& operand, IrU32 value); IrF32 applyF32ResultModifiers(const RdnaOperand& operand, IrF32 value); + IrF32 applyF16ResultModifiers(const RdnaOperand& operand, IrF32 value); void writeOperand(const RdnaOperand& operand, IrValue* value); IrU32 packHalf2x16(IrF32 low, IrF32 high); void write16Bits(const RdnaOperand& operand, IrU32 value); @@ -93,6 +95,7 @@ private: bool dsAtomic(const RdnaInstruction& inst, IrOpcode opcode, bool returnsValue); bool flatLoad(const RdnaInstruction& inst); bool flatStore(const RdnaInstruction& inst); + bool flatAtomic(const RdnaInstruction& inst, IrOpcode opcode); bool imageGetResinfo(const RdnaInstruction& inst); bool imageGetLod(const RdnaInstruction& inst); bool imageLoad(const RdnaInstruction& inst); @@ -108,15 +111,17 @@ private: bool dsWrite(const RdnaInstruction& inst); bool dsWrite2(const RdnaInstruction& inst); bool dsAtomic2(const RdnaInstruction& inst, IrOpcode opcode, bool returnsValue); + bool dsAtomic64(const RdnaInstruction& inst, IrOpcode opcode, bool returnsValue); bool dsAppendConsume(const RdnaInstruction& inst, IrOpcode opcode); bool dsAddtid(const RdnaInstruction& inst, bool write); - bool globalLoadAddtid(const RdnaInstruction& inst); + bool globalAddtid(const RdnaInstruction& inst, bool write); bool dsSwizzleB32(const RdnaInstruction& inst); bool dsBpermuteB32(const RdnaInstruction& inst); bool dsPermuteB32(const RdnaInstruction& inst); IrF32 selectF32(IrU1 condition, IrF32 trueValue, IrF32 falseValue); IrU32 convertF32ToU32Saturated(IrF32 value, float upperBound, float safeUpper, std::uint32_t highResult); IrU32 convertF32ToI32Saturated(IrF32 value, float lowerBound, float upperBound, float safeUpper, std::uint32_t lowerResult, std::uint32_t upperResult); + IrU32 convertFlooredF32ToI32(IrF32 source, IrF32 floored); IrU32 packU16Lanes(IrU32 low, IrU32 high); void emitCompareResult(const RdnaInstruction& inst, IrU1 value, bool scalar, bool cmpx); void emitCompareConstant(const RdnaInstruction& inst, bool value, bool scalar, bool cmpx); @@ -144,12 +149,12 @@ private: void vFrexpExpI32F32(const RdnaInstruction& inst); void vCvtOffF32I4(const RdnaInstruction& inst); void vCvtPkrtzF16F32(const RdnaInstruction& inst); - void vCvtPknormF32(const RdnaInstruction& inst, IrOpcode opcode); + void vCvtPknormF32(const RdnaInstruction& inst, bool signedValue); void vCvtPkU8F32(const RdnaInstruction& inst); void vPackB32F16(const RdnaInstruction& inst); bool packedFloat16(const RdnaInstruction& inst, IrOpcode opcode, bool accumulator, bool quietSnan); - bool float16Unary(const RdnaInstruction& inst, IrOpcode opcode, bool invalidNegative); - bool float16Trig(const RdnaInstruction& inst, IrOpcode opcode); + bool float16Unary(const RdnaInstruction& inst, IrOpcode opcode); + std::pair unaryFloatSpecials(IrOpcode opcode, IrF32 argument, IrF32 result); bool vDivFixupF16(const RdnaInstruction& inst); bool float16Binary(const RdnaInstruction& inst, IrOpcode opcode, bool reverse); bool float16Ternary(const RdnaInstruction& inst, IrOpcode opcode, bool accumulator, bool mix); @@ -160,17 +165,26 @@ private: bool vDivFmasF32(const RdnaInstruction& inst); bool vDivFixupF32(const RdnaInstruction& inst); bool vFrexpMantF32(const RdnaInstruction& inst); + bool vFmaLegacyF32(const RdnaInstruction& inst); + bool vLdexpF32(const RdnaInstruction& inst); IrU32 readF16Bits(const RdnaOperand& operand); IrU32 normF16(IrU32 bits, bool signedValue); + IrU32 normF32(IrU32 bits, bool signedValue); bool vLdexpF16(const RdnaInstruction& inst); bool vFrexpF16(const RdnaInstruction& inst, bool exponent); bool vCvtNormF16(const RdnaInstruction& inst, bool signedValue); bool vCvtPknormF16(const RdnaInstruction& inst, bool signedValue); bool vSatPkU8I16(const RdnaInstruction& inst); bool vMulLegacyF32(const RdnaInstruction& inst, bool accumulate); + bool vMullitF32(const RdnaInstruction& inst); void emitFloat16ClassCompare(const RdnaInstruction& inst, bool cmpx); bool float64Operation(const RdnaInstruction& inst, IrOpcode opcode); + void writeF64Result(const RdnaOperand& operand, IrValue& value); + bool vDivScaleF64(const RdnaInstruction& inst); + bool vDivFmasF64(const RdnaInstruction& inst); + bool vDivFixupF64(const RdnaInstruction& inst); bool vDot2cF32F16(const RdnaInstruction& inst); + bool vDot2F32F16(const RdnaInstruction& inst); bool vCubeidF32(const RdnaInstruction& inst); bool vCubescF32(const RdnaInstruction& inst); bool vCubetcF32(const RdnaInstruction& inst); @@ -179,6 +193,8 @@ private: bool integer16Shift(const RdnaInstruction& inst, IrOpcode opcode, bool arithmetic); bool integer16Binary(const RdnaInstruction& inst, IrOpcode opcode, bool sign); bool integer16Mad(const RdnaInstruction& inst, bool sign, bool wide); + IrU32 saturateU16Result(const RdnaInstruction& inst, const IrU32& value, bool sign); + IrU32 wideAdd(const RdnaInstruction& inst, const IrU32& low, const IrU32& high, const IrU32& addend, bool sign); bool vAddSubNcI32(const RdnaInstruction& inst, bool subtract); bool integer16Ternary(const RdnaInstruction& inst, IrOpcode opcode, bool sign); bool vMed3I16(const RdnaInstruction& inst); @@ -216,6 +232,7 @@ private: bool bfmB32(const RdnaInstruction& inst); IrU32 rightMask32(IrU32 count); IrU64 rightMask64(IrU32 count); + IrU32 extractBits32(IrU32 source, IrU32 offset, IrU32 rawCount, bool sign); bool sBfmB64(const RdnaInstruction& inst); bool sBfeU32(const RdnaInstruction& inst, bool sign); bool sBfeU64(const RdnaInstruction& inst); @@ -235,10 +252,11 @@ private: bool vCndmaskB32(const RdnaInstruction& inst); bool packB16(const RdnaInstruction& inst, bool high0, bool high1); void sSubvectorLoop(const RdnaInstruction& inst, bool begin); - void sSaveexec(const RdnaInstruction& inst, IrOpcode operation, bool negateExec, bool negateSource, bool write64, bool negateResult = false, bool writeDestination = true); + void sSaveexec(const RdnaInstruction& inst, IrOpcode operation, bool negateExec, bool negateSource, bool write64, bool negateResult = false, bool writeResult = false); void addU32(const RdnaInstruction& inst, bool vector, bool useCarryIn); void subU32(const RdnaInstruction& inst, bool vector, bool reverse); void subbU32(const RdnaInstruction& inst, bool vector, bool reverse); + bool vAddSubNcU32(const RdnaInstruction& inst, bool subtract, bool reverse); void sAbsdiffI32(const RdnaInstruction& inst); void sAddSubI32(const RdnaInstruction& inst, bool subtract); void sLshlAddU32(const RdnaInstruction& inst, std::uint32_t shiftAmount); diff --git a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp index 48fedcf5c..2a47caf74 100644 --- a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp @@ -25,17 +25,17 @@ bool SaveexecWritesDestinationFirst() { return writeFirst; } -void TranslationContext::sSaveexec(const RdnaInstruction& inst, IrOpcode operation, bool negateExec, bool negateSource, bool write64, bool negateResult, bool writeDestination) { +void TranslationContext::sSaveexec(const RdnaInstruction& inst, IrOpcode operation, bool negateExec, bool negateSource, bool write64, bool negateResult, bool writeResult) { // Callers name the lane-wise operation; the exec mask words themselves combine bitwise. if (operation == IrOpcode::LogicalAnd) operation = IrOpcode::BitwiseAnd32; else if (operation == IrOpcode::LogicalOr) operation = IrOpcode::BitwiseOr32; else if (operation == IrOpcode::LogicalXor) operation = IrOpcode::BitwiseXor32; - const bool writeFirst = SaveexecWritesDestinationFirst() && writeDestination; + const bool writeFirst = SaveexecWritesDestinationFirst() && !writeResult; if (write64) { const std::array oldExec{IrU32(ir.GetExecLo()), IrU32(ir.GetExecHi())}; if (writeFirst) writeU32Pair(inst.destination, oldExec); const std::array source = readU32Pair(sourceAt(inst, 0u)); - if (!writeFirst && writeDestination) writeU32Pair(inst.destination, oldExec); + if (!writeFirst && !writeResult) writeU32Pair(inst.destination, oldExec); IrValue& lowExecOperand = negateExec ? ir.BitwiseNot(oldExec[0].Value()) : oldExec[0].Value(); IrValue& lowSourceOperand = negateSource ? ir.BitwiseNot(source[0].Value()) : source[0].Value(); IrValue& highExecOperand = negateExec ? ir.BitwiseNot(oldExec[1].Value()) : oldExec[1].Value(); @@ -46,6 +46,7 @@ void TranslationContext::sSaveexec(const RdnaInstruction& inst, IrOpcode operati const IrU32 newExecHi(negateResult ? ir.BitwiseNot(highCombined) : highCombined); ir.SetExecLo(newExecLo.Value()); ir.SetExecHi(newExecHi.Value()); + if (writeResult) writeU32Pair(inst.destination, {newExecLo, newExecHi}); const IrU1 nonZero(ir.LogicalOr(ir.INotEqual(newExecLo.Value(), ir.Constant(0u)), ir.INotEqual(newExecHi.Value(), ir.Constant(0u)))); ir.SetScc(nonZero.Value()); ir.SetExec(threadBit({newExecLo, newExecHi}).Value()); @@ -54,12 +55,13 @@ void TranslationContext::sSaveexec(const RdnaInstruction& inst, IrOpcode operati const IrU32 oldExec(ir.GetExecLo()); if (writeFirst) writeRawU32(inst.destination, oldExec); const IrU32 source = readU32(sourceAt(inst, 0u)); - if (!writeFirst && writeDestination) writeRawU32(inst.destination, oldExec); + if (!writeFirst && !writeResult) writeRawU32(inst.destination, oldExec); IrValue& execOperand = negateExec ? ir.BitwiseNot(oldExec.Value()) : oldExec.Value(); IrValue& sourceOperand = negateSource ? ir.BitwiseNot(source.Value()) : source.Value(); IrValue& combined = ir.Emit(operation, IrType::U32, {&execOperand, &sourceOperand}); const IrU32 newExec(negateResult ? ir.BitwiseNot(combined) : combined); ir.SetExecLo(newExec.Value()); + if (writeResult) writeRawU32(inst.destination, newExec); const IrU1 nonZero(ir.INotEqual(newExec.Value(), ir.Constant(0u))); ir.SetScc(nonZero.Value()); ir.SetExec(threadBit({newExec, IrU32(ir.GetExecHi())}).Value()); @@ -72,8 +74,8 @@ void TranslationContext::addU32(const RdnaInstruction& inst, bool vector, bool u const IrU32 sum(ir.Emit(IrOpcode::CompositeExtractU32x2, IrType::U32, {&firstAdd, &ir.Constant(0u)})); const IrU32 firstCarry(ir.Emit(IrOpcode::CompositeExtractU32x2, IrType::U32, {&firstAdd, &ir.Constant(1u)})); if (!useCarryIn) { - writeRawU32(inst.destination, sum); const IrU1 carryOut(ir.INotEqual(firstCarry.Value(), ir.Constant(0u))); + writeRawU32(inst.destination, inst.destination.clamp ? IrU32(ir.Select(carryOut.Value(), ir.Constant(0xffffffffu), sum.Value())) : sum); if (vector) { writeMask(inst.destination2, carryOut); return; @@ -87,7 +89,7 @@ void TranslationContext::addU32(const RdnaInstruction& inst, bool vector, bool u const IrU32 result(ir.Emit(IrOpcode::CompositeExtractU32x2, IrType::U32, {&secondAdd, &ir.Constant(0u)})); const IrU32 secondCarry(ir.Emit(IrOpcode::CompositeExtractU32x2, IrType::U32, {&secondAdd, &ir.Constant(1u)})); const IrU1 carryOut(ir.LogicalOr(ir.INotEqual(firstCarry.Value(), ir.Constant(0u)), ir.INotEqual(secondCarry.Value(), ir.Constant(0u)))); - writeRawU32(inst.destination, result); + writeRawU32(inst.destination, inst.destination.clamp ? IrU32(ir.Select(carryOut.Value(), ir.Constant(0xffffffffu), result.Value())) : result); if (vector) { writeMask(inst.destination2, carryOut); return; @@ -122,7 +124,7 @@ void TranslationContext::subbU32(const RdnaInstruction& inst, bool vector, bool const IrU32 result(ir.ISub(partial.Value(), borrowInU32.Value())); const IrU1 secondBorrow(ir.ULessThan(partial.Value(), borrowInU32.Value())); const IrU1 borrowOut(ir.LogicalOr(firstBorrow.Value(), secondBorrow.Value())); - writeRawU32(inst.destination, result); + writeRawU32(inst.destination, inst.destination.clamp ? IrU32(ir.Select(borrowOut.Value(), ir.Constant(0u), result.Value())) : result); if (vector) { writeMask(inst.destination2, borrowOut); return; @@ -130,6 +132,20 @@ void TranslationContext::subbU32(const RdnaInstruction& inst, bool vector, bool ir.SetScc(borrowOut.Value()); } +bool TranslationContext::vAddSubNcU32(const RdnaInstruction& inst, bool subtract, bool reverse) { + const IrU32 first = readU32(sourceAt(inst, 0u)); + const IrU32 second = readU32(sourceAt(inst, 1u)); + const IrU32& lhs = reverse ? second : first; + const IrU32& rhs = reverse ? first : second; + IrU32 result(subtract ? ir.ISub(lhs.Value(), rhs.Value()) : ir.IAdd(lhs.Value(), rhs.Value())); + if (inst.destination.clamp) { + const IrU1 carry(subtract ? ir.ULessThan(lhs.Value(), rhs.Value()) : ir.ULessThan(result.Value(), lhs.Value())); + result = IrU32(ir.Select(carry.Value(), ir.Constant(subtract ? 0u : 0xffffffffu), result.Value())); + } + writeOperand(inst.destination, &result.Value()); + return true; +} + void TranslationContext::sAbsdiffI32(const RdnaInstruction& inst) { const IrU32 lhs = readU32(sourceAt(inst, 0u)); const IrU32 rhs = readU32(sourceAt(inst, 1u)); @@ -154,6 +170,8 @@ void TranslationContext::sLshlAddU32(const RdnaInstruction& inst, std::uint32_t const IrU32 rhs = readU32(sourceAt(inst, 1u)); const IrU32 shifted(ir.ShiftLeftLogical(lhs.Value(), ir.Constant(shiftAmount))); const IrU32 result(ir.IAdd(shifted.Value(), rhs.Value())); + const IrU1 shiftedOut(ir.INotEqual(ir.ShiftRightLogical(lhs.Value(), ir.Constant(32u - shiftAmount)), ir.Constant(0u))); + ir.SetScc(ir.LogicalOr(shiftedOut.Value(), ir.ULessThan(result.Value(), shifted.Value()))); writeRawU32(inst.destination, result); } @@ -238,13 +256,17 @@ void TranslationContext::sWqm(const RdnaInstruction& inst, bool wide) { const std::array source = readU32Pair(sourceAt(inst, 0u)); const IrU64 wide64(ir.ConstructU64(source[0].Value(), source[1].Value())); const IrU64 result(ir.Emit(IrOpcode::WqmU64, IrType::U64, {&wide64.Value()})); - writeU32Pair(inst.destination, extractU64(result)); + const std::array words = extractU64(result); + writeU32Pair(inst.destination, words); + ir.SetScc(ir.LogicalOr(ir.INotEqual(words[0].Value(), ir.Constant(0u)), ir.INotEqual(words[1].Value(), ir.Constant(0u)))); return; } const IrU32 source = readU32(sourceAt(inst, 0u)); const IrU64 wide64(ir.ConstructU64(source.Value(), ir.Constant(0u))); const IrU64 result(ir.Emit(IrOpcode::WqmU64, IrType::U64, {&wide64.Value()})); - writeRawU32(inst.destination, extractU64(result)[0]); + const IrU32 low = extractU64(result)[0]; + writeRawU32(inst.destination, low); + ir.SetScc(ir.INotEqual(low.Value(), ir.Constant(0u))); } // M0 holds a uniform register offset. Vector registers are SSA values with fixed indices, so an indexed @@ -258,11 +280,11 @@ IrU32 TranslationContext::readRelativeVector(const RdnaOperand& base, IrValue& o IrValue& hit = ir.IEqual(offset, ir.Constant(reg - base.reg)); result = IrU32(ir.Select(hit, ir.GetVectorReg(static_cast(reg)), result.Value())); } - return result; + return applyBitSourceModifiers(base, result); } void TranslationContext::writeRelativeVector(const RdnaOperand& base, IrValue& offset, IrU32 value, const char* name) { - const RdnaOperand destination = plainOperand(base); + const RdnaOperand& destination = base; if (destination.kind != RdnaOperandKind::VectorRegister) { throw std::runtime_error(std::string(name) + " destination is not a vector register"); } @@ -334,8 +356,9 @@ void TranslationContext::vPermlane16B32(const RdnaInstruction& inst, bool x16) { const IrU32 value = readU32(sourceAt(inst, 0u)); const IrU32 selectLow = readU32(sourceAt(inst, 1u)); const IrU32 selectHigh = readU32(sourceAt(inst, 2u)); + const IrU32 previous = readRawU32(plainOperand(inst.destination)); const PermlaneFlags flags{x16, inst.destination.opSel, inst.destination.opSelHi}; - const IrU32 result(ir.Emit(IrOpcode::Permlane16U32, IrType::U32, {&value.Value(), &selectLow.Value(), &selectHigh.Value(), &ir.GetExec()}, flags)); + const IrU32 result(ir.Emit(IrOpcode::Permlane16U32, IrType::U32, {&value.Value(), &selectLow.Value(), &selectHigh.Value(), &ir.GetExec(), &previous.Value()}, flags)); writeRawU32(inst.destination, result); } diff --git a/core/shader/recompiler/Translation/src/ConvertInstructions.cpp b/core/shader/recompiler/Translation/src/ConvertInstructions.cpp index 0a03104de..91978c218 100644 --- a/core/shader/recompiler/Translation/src/ConvertInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ConvertInstructions.cpp @@ -104,18 +104,29 @@ void TranslationContext::vCvt16F16(const RdnaInstruction& inst, bool signedValue write16Bits(inst.destination, convertF32ToU32Saturated(value, 65536.0f, 65535.0f, 0xffffu)); } +IrU32 TranslationContext::convertFlooredF32ToI32(IrF32 source, IrF32 floored) { + const IrU32 converted = convertF32ToI32Saturated(floored, -2147483648.0f, 2147483648.0f, 2147483520.0f, 0x80000000u, 0x7fffffffu); + const IrU1 nan(ir.Emit(IrOpcode::FPIsNan32, IrType::U1, {&source.Value()})); + const IrU32 sign(ir.ShiftRightLogical(ir.BitCastU32(source.Value()), ir.Constant(31u))); + const IrU32 nanResult(ir.IAdd(ir.Constant(0x7fffffffu), sign.Value())); + return IrU32(ir.Select(nan.Value(), nanResult.Value(), converted.Value())); +} + void TranslationContext::vCvtRpiI32F32(const RdnaInstruction& inst) { const IrF32 source(*readOperand(sourceAt(inst, 0u), IrType::F32)); - const IrF32 biased(ir.Emit(IrOpcode::FPAdd32, IrType::F32, {&source.Value(), &ir.ConstantF32(0.5f)})); - const IrF32 rounded(ir.Emit(IrOpcode::FPFloor32, IrType::F32, {&biased.Value()})); - const IrU32 result = convertF32ToI32Saturated(rounded, -2147483648.0f, 2147483648.0f, 2147483520.0f, 0x80000000u, 0x7fffffffu); + const IrF32 floored(ir.Emit(IrOpcode::FPFloor32, IrType::F32, {&source.Value()})); + const IrF32 fraction(ir.Emit(IrOpcode::FPSub32, IrType::F32, {&source.Value(), &floored.Value()})); + const IrU1 roundUp(ir.Emit(IrOpcode::FPOrdGreaterThanEqual32, IrType::U1, {&fraction.Value(), &ir.ConstantF32(0.5f)})); + const IrF32 increment = selectF32(roundUp, IrF32(ir.ConstantF32(1.0f)), IrF32(ir.ConstantF32(0.0f))); + const IrF32 rounded(ir.Emit(IrOpcode::FPAdd32, IrType::F32, {&floored.Value(), &increment.Value()})); + const IrU32 result = convertFlooredF32ToI32(source, rounded); writeOperand(inst.destination, &result.Value()); } void TranslationContext::vCvtFlrI32F32(const RdnaInstruction& inst) { const IrF32 source(*readOperand(sourceAt(inst, 0u), IrType::F32)); const IrF32 rounded(ir.Emit(IrOpcode::FPFloor32, IrType::F32, {&source.Value()})); - const IrU32 result = convertF32ToI32Saturated(rounded, -2147483648.0f, 2147483648.0f, 2147483520.0f, 0x80000000u, 0x7fffffffu); + const IrU32 result = convertFlooredF32ToI32(source, rounded); writeOperand(inst.destination, &result.Value()); } @@ -145,23 +156,29 @@ void TranslationContext::vCvtOffF32I4(const RdnaInstruction& inst) { } void TranslationContext::vCvtPkrtzF16F32(const RdnaInstruction& inst) { - const IrF32 lhs = applyF32ResultModifiers(inst.destination, IrF32(*readOperand(sourceAt(inst, 0u), IrType::F32))); - const IrF32 rhs = applyF32ResultModifiers(inst.destination, IrF32(*readOperand(sourceAt(inst, 1u), IrType::F32))); + const IrF32 lhs(*readOperand(sourceAt(inst, 0u), IrType::F32)); + const IrF32 rhs(*readOperand(sourceAt(inst, 1u), IrType::F32)); const IrU32 result(ir.Emit(IrOpcode::PackFloat2x16Rtz, IrType::U32, {&lhs.Value(), &rhs.Value()})); writeOperand(inst.destination, &result.Value()); } -void TranslationContext::vCvtPknormF32(const RdnaInstruction& inst, IrOpcode opcode) { - IrValue* lhs = readOperand(sourceAt(inst, 0u), IrType::F32); - IrValue* rhs = readOperand(sourceAt(inst, 1u), IrType::F32); - IrValue& pair = ir.Emit(IrOpcode::CompositeConstructF32x2, IrType::F32x2, {lhs, rhs}); - const IrU32 result(ir.Emit(opcode, IrType::U32, {&pair})); +void TranslationContext::vCvtPknormF32(const RdnaInstruction& inst, bool signedValue) { + const IrU32 low = normF32(readU32(sourceAt(inst, 0u)), signedValue); + const IrU32 high = normF32(readU32(sourceAt(inst, 1u)), signedValue); + const IrU32 result(ir.BitwiseOr(low.Value(), ir.ShiftLeftLogical(high.Value(), ir.Constant(16u)))); writeOperand(inst.destination, &result.Value()); } void TranslationContext::vCvtPkU8F32(const RdnaInstruction& inst) { const IrF32 source(*readOperand(sourceAt(inst, 0u), IrType::F32)); - const IrU32 byteValue = convertF32ToU32Saturated(source, 255.0f, 255.0f, 255u); + const IrU32 truncated = convertF32ToU32Saturated(source, 255.0f, 255.0f, 255u); + IrValue& whole = ir.Emit(IrOpcode::ConvertF32U32, IrType::F32, {&truncated.Value()}); + IrValue& fraction = ir.Emit(IrOpcode::FPSub32, IrType::F32, {&source.Value(), &whole}); + const IrU1 odd(ir.INotEqual(ir.BitwiseAnd(truncated.Value(), ir.Constant(1u)), ir.Constant(0u))); + const IrU1 aboveHalf(ir.Emit(IrOpcode::FPOrdGreaterThan32, IrType::U1, {&fraction, &ir.ConstantF32(0.5f)})); + const IrU1 tie(ir.LogicalAnd(ir.Emit(IrOpcode::FPOrdEqual32, IrType::U1, {&fraction, &ir.ConstantF32(0.5f)}), odd.Value())); + const IrU1 roundUp(ir.LogicalAnd(ir.LogicalOr(aboveHalf.Value(), tie.Value()), ir.ULessThan(truncated.Value(), ir.Constant(255u)))); + const IrU32 byteValue(ir.Select(roundUp.Value(), ir.IAdd(truncated.Value(), ir.Constant(1u)), truncated.Value())); const IrU32 index(ir.BitwiseAnd(readU32(sourceAt(inst, 1u)).Value(), ir.Constant(3u))); const IrU32 shift(ir.ShiftLeftLogical(index.Value(), ir.Constant(3u))); const IrU32 mask(ir.ShiftLeftLogical(ir.Constant(0xffu), shift.Value())); @@ -171,8 +188,8 @@ void TranslationContext::vCvtPkU8F32(const RdnaInstruction& inst) { } void TranslationContext::vPackB32F16(const RdnaInstruction& inst) { - const IrU32 low = readF16LaneBits(sourceAt(inst, 0u), false); - const IrU32 high(ir.ShiftLeftLogical(readF16LaneBits(sourceAt(inst, 1u), false).Value(), ir.Constant(16u))); + const IrU32 low = readF16Bits(sourceAt(inst, 0u)); + const IrU32 high(ir.ShiftLeftLogical(readF16Bits(sourceAt(inst, 1u)).Value(), ir.Constant(16u))); const IrU32 result(ir.BitwiseOr(low.Value(), high.Value())); writeOperand(inst.destination, &result.Value()); } diff --git a/core/shader/recompiler/Translation/src/EmbeddedVertexFetch.cpp b/core/shader/recompiler/Translation/src/EmbeddedVertexFetch.cpp index 18da75e2f..4fda5df13 100644 --- a/core/shader/recompiler/Translation/src/EmbeddedVertexFetch.cpp +++ b/core/shader/recompiler/Translation/src/EmbeddedVertexFetch.cpp @@ -319,6 +319,7 @@ EmbeddedFetchPlan EmbeddedVertexFetchAnalyzer::Analyze(const RdnaProgram& progra dst.constant = inst.source0.value; } break; + case RdnaOpcode::SGetregB32: case RdnaOpcode::SCmovkI32: if (isScalarOperand(inst.destination)) { clearScalarRange(sgprs, inst.destination, 1u); diff --git a/core/shader/recompiler/Translation/src/Float64Instructions.cpp b/core/shader/recompiler/Translation/src/Float64Instructions.cpp index 9df840a8a..622c6b41b 100644 --- a/core/shader/recompiler/Translation/src/Float64Instructions.cpp +++ b/core/shader/recompiler/Translation/src/Float64Instructions.cpp @@ -22,13 +22,128 @@ bool TranslationContext::float64Operation(const RdnaInstruction& inst, IrOpcode } const IrType type = IrOpcodeType(opcode); IrValue* result = count == 1u ? &ir.Emit(opcode, type, {args[0]}) : count == 2u ? &ir.Emit(opcode, type, {args[0], args[1]}) : &ir.Emit(opcode, type, {args[0], args[1], args[2]}); + if (type == IrType::U64) { + writeF64Result(inst.destination, *result); + return true; + } RdnaOperand destination = inst.destination; destination.omod = 0u; - if (type == IrType::U64 && destination.clamp) { - result = &ir.Emit(IrOpcode::FPSaturate64, IrType::U64, {result}); - } writeOperand(destination, result); return true; } +void TranslationContext::writeF64Result(const RdnaOperand& operand, IrValue& value) { + RdnaOperand destination = operand; + destination.omod = 0u; + IrValue* result = &value; + if (destination.clamp) { + result = &ir.Emit(IrOpcode::FPSaturate64, IrType::U64, {result}); + } + writeOperand(destination, result); +} + +bool TranslationContext::vDivScaleF64(const RdnaInstruction& inst) { + const auto value = readF64Bits(sourceAt(inst, 0u)); + const auto denominator = readF64Bits(sourceAt(inst, 1u)); + const auto numerator = readF64Bits(sourceAt(inst, 2u)); + const auto exponent = [&](IrU32 high) { + return IrU32(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&high.Value(), &ir.Constant(20u), &ir.Constant(11u)})); + }; + const IrU32 denominatorExponent = exponent(denominator[1]); + const IrU32 numeratorExponent = exponent(numerator[1]); + const IrU32 difference(ir.ISub(numeratorExponent.Value(), denominatorExponent.Value())); + const IrU1 nearMax(ir.Emit(IrOpcode::SGreaterThanEqual32, IrType::U1, {&difference.Value(), &ir.Constant(768u)})); + const IrU1 nearMin(ir.Emit(IrOpcode::SLessThanEqual32, IrType::U1, {&difference.Value(), &ir.Constant(static_cast(-768))})); + const IrU1 denominatorDenormal(ir.IEqual(denominatorExponent.Value(), ir.Constant(0u))); + const IrU1 denominatorHuge(ir.Emit(IrOpcode::UGreaterThanEqual32, IrType::U1, {&denominatorExponent.Value(), &ir.Constant(2045u)})); + const IrU1 numeratorTiny(ir.Emit(IrOpcode::ULessThanEqual32, IrType::U1, {&numeratorExponent.Value(), &ir.Constant(53u)})); + const IrU1 isDenominator(ir.LogicalAnd(ir.IEqual(value[0].Value(), denominator[0].Value()), ir.IEqual(value[1].Value(), denominator[1].Value()))); + const IrU1 notNearMax(ir.LogicalNot(nearMax.Value())); + const IrU1 notDenormal(ir.LogicalNot(denominatorDenormal.Value())); + const IrU1 normalRange(ir.LogicalAnd(notNearMax.Value(), notDenormal.Value())); + const IrU1 belowHuge(ir.LogicalAnd(normalRange.Value(), ir.LogicalNot(denominatorHuge.Value()))); + const IrU1 scaleUp(ir.LogicalOr( + ir.LogicalOr(ir.LogicalAnd(nearMax.Value(), isDenominator.Value()), ir.LogicalAnd(notNearMax.Value(), denominatorDenormal.Value())), + ir.LogicalAnd(belowHuge.Value(), ir.LogicalOr(ir.LogicalAnd(nearMin.Value(), ir.LogicalNot(isDenominator.Value())), + ir.LogicalAnd(ir.LogicalNot(nearMin.Value()), numeratorTiny.Value()))))); + const IrU1 scaleDown(ir.LogicalAnd(ir.LogicalAnd(normalRange.Value(), denominatorHuge.Value()), + ir.LogicalOr(ir.LogicalNot(nearMin.Value()), isDenominator.Value()))); + IrValue& bits = ir.ConstructU64(value[0].Value(), value[1].Value()); + IrValue& power = ir.Select(scaleUp.Value(), ir.Constant(128u), ir.Constant(static_cast(-128))); + IrValue& scaled = ir.Emit(IrOpcode::FPLdexp64, IrType::U64, {&bits, &power}); + const auto magnitude = [&](IrU32 high) { return IrU32(ir.BitwiseAnd(high.Value(), ir.Constant(0x7fffffffu))); }; + const IrU1 valueNan(ir.LogicalOr(ir.UGreaterThan(magnitude(value[1]).Value(), ir.Constant(0x7ff00000u)), + ir.LogicalAnd(ir.IEqual(magnitude(value[1]).Value(), ir.Constant(0x7ff00000u)), ir.INotEqual(value[0].Value(), ir.Constant(0u))))); + const IrU1 rescale(ir.LogicalAnd(ir.LogicalOr(scaleUp.Value(), scaleDown.Value()), ir.LogicalNot(valueNan.Value()))); + const auto isZero = [&](const std::array& word) { + return IrU1(ir.IEqual(ir.BitwiseOr(magnitude(word[1]).Value(), word[0].Value()), ir.Constant(0u))); + }; + const IrU1 zero(ir.LogicalOr(isZero(denominator).Value(), isZero(numerator).Value())); + std::array result{IrU32(ir.CompositeExtract(scaled, 0u)), IrU32(ir.CompositeExtract(scaled, 1u))}; + result[0] = IrU32(ir.Select(rescale.Value(), result[0].Value(), value[0].Value())); + result[1] = IrU32(ir.Select(rescale.Value(), result[1].Value(), value[1].Value())); + result[0] = IrU32(ir.Select(zero.Value(), ir.Constant(0u), result[0].Value())); + result[1] = IrU32(ir.Select(zero.Value(), ir.Constant(0xfff80000u), result[1].Value())); + writeOperand(inst.destination, &ir.ConstructU64(result[0].Value(), result[1].Value())); + writeMask(inst.destination2, IrU1(ir.LogicalOr(nearMax.Value(), ir.LogicalAnd(notDenormal.Value(), nearMin.Value())))); + return true; +} + +bool TranslationContext::vDivFmasF64(const RdnaInstruction& inst) { + RdnaOperand vcc{}; + vcc.kind = RdnaOperandKind::VccLo; + const IrU1 scale = readMask(vcc); + const auto addend = readF64Bits(sourceAt(inst, 2u)); + IrValue& addendExponent = ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&addend[1].Value(), &ir.Constant(20u), &ir.Constant(11u)}); + const IrU1 up(ir.Emit(IrOpcode::UGreaterThanEqual32, IrType::U1, {&addendExponent, &ir.Constant(1024u)})); + IrValue& power = ir.Select(scale.Value(), ir.Select(up.Value(), ir.Constant(128u), ir.Constant(static_cast(-128))), ir.Constant(0u)); + IrValue& result = ir.Emit(IrOpcode::FPFmaScale64, IrType::U64, + {&readF64(sourceAt(inst, 0u)).Value(), &readF64(sourceAt(inst, 1u)).Value(), &ir.ConstructU64(addend[0].Value(), addend[1].Value()), &power}); + writeF64Result(inst.destination, result); + return true; +} + +bool TranslationContext::vDivFixupF64(const RdnaInstruction& inst) { + const auto quotient = readF64Bits(sourceAt(inst, 0u)); + const auto denominator = readF64Bits(sourceAt(inst, 1u)); + const auto numerator = readF64Bits(sourceAt(inst, 2u)); + const auto magnitude = [&](IrU32 high) { return IrU32(ir.BitwiseAnd(high.Value(), ir.Constant(0x7fffffffu))); }; + const auto lowNonzero = [&](const std::array& word) { return IrU1(ir.INotEqual(word[0].Value(), ir.Constant(0u))); }; + const auto isNan = [&](const std::array& word) { + return IrU1(ir.LogicalOr(ir.UGreaterThan(magnitude(word[1]).Value(), ir.Constant(0x7ff00000u)), + ir.LogicalAnd(ir.IEqual(magnitude(word[1]).Value(), ir.Constant(0x7ff00000u)), lowNonzero(word).Value()))); + }; + const auto isInf = [&](const std::array& word) { + return IrU1(ir.LogicalAnd(ir.IEqual(magnitude(word[1]).Value(), ir.Constant(0x7ff00000u)), ir.LogicalNot(lowNonzero(word).Value()))); + }; + const auto isZero = [&](const std::array& word) { + return IrU1(ir.IEqual(ir.BitwiseOr(magnitude(word[1]).Value(), word[0].Value()), ir.Constant(0u))); + }; + const auto exponent = [&](IrU32 high) { + return IrU32(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&high.Value(), &ir.Constant(20u), &ir.Constant(11u)})); + }; + const IrU32 sign(ir.BitwiseAnd(ir.BitwiseXor(denominator[1].Value(), numerator[1].Value()), ir.Constant(0x80000000u))); + const std::array zero{IrU32(ir.Constant(0u)), sign}; + const std::array infinity{IrU32(ir.Constant(0u)), IrU32(ir.BitwiseOr(sign.Value(), ir.Constant(0x7ff00000u)))}; + const std::array defaultNan{IrU32(ir.Constant(0u)), IrU32(ir.Constant(0xfff80000u))}; + const auto quiet = [&](const std::array& word) { + return std::array{word[0], IrU32(ir.BitwiseOr(word[1].Value(), ir.Constant(0x00080000u)))}; + }; + const IrU32 difference(ir.ISub(exponent(numerator[1]).Value(), exponent(denominator[1]).Value())); + const IrU1 underflow(ir.Emit(IrOpcode::SLessThan32, IrType::U1, {&difference.Value(), &ir.Constant(static_cast(-1075))})); + std::array result{quotient[0], IrU32(ir.BitwiseOr(sign.Value(), magnitude(quotient[1]).Value()))}; + const auto choose = [&](IrU1 condition, const std::array& word) { + result = {IrU32(ir.Select(condition.Value(), word[0].Value(), result[0].Value())), IrU32(ir.Select(condition.Value(), word[1].Value(), result[1].Value()))}; + }; + choose(isNan(quotient), infinity); + choose(underflow, zero); + choose(IrU1(ir.LogicalOr(isInf(denominator).Value(), isZero(numerator).Value())), zero); + choose(IrU1(ir.LogicalOr(isZero(denominator).Value(), isInf(numerator).Value())), infinity); + choose(IrU1(ir.LogicalOr(ir.LogicalAnd(isZero(denominator).Value(), isZero(numerator).Value()), ir.LogicalAnd(isInf(denominator).Value(), isInf(numerator).Value()))), defaultNan); + choose(isNan(denominator), quiet(denominator)); + choose(isNan(numerator), quiet(numerator)); + writeF64Result(inst.destination, ir.ConstructU64(result[0].Value(), result[1].Value())); + return true; +} + } diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index 4307ca9c9..958edd283 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -16,7 +16,7 @@ bool TranslationContext::packedFloat16(const RdnaInstruction& inst, IrOpcode opc const IrF32 lhs = readF16LaneAsF32(sourceAt(inst, 0u), high, true); const IrF32 rhs = readF16LaneAsF32(sourceAt(inst, 1u), high, true); if (accumulator) { - const IrF32 acc = readF16LaneAsF32(inst.destination, high, true); + const IrF32 acc = readF16LaneAsF32(accumulatorOperand(inst), high, true); return applyF32ResultModifiers(inst.destination, IrF32(ir.Emit(opcode, IrType::F32, {&lhs.Value(), &rhs.Value(), &acc.Value()}))); } if (inst.sourceCount == 3u) { @@ -25,7 +25,6 @@ bool TranslationContext::packedFloat16(const RdnaInstruction& inst, IrOpcode opc } return applyF32ResultModifiers(inst.destination, IrF32(ir.Emit(opcode, IrType::F32, {&lhs.Value(), &rhs.Value()}))); }; - const RdnaOperand raw = plainOperand(inst.destination); IrU32 result = packHalf2x16(translateLane(false), translateLane(true)); if (quietSnan) { const auto quietSnanLane = [&](const RdnaOperand& operand, bool high) { @@ -44,25 +43,96 @@ bool TranslationContext::packedFloat16(const RdnaInstruction& inst, IrOpcode opc }; result = packU16Lanes(overrideLane(false), overrideLane(true)); } - writeOperand(raw, &result.Value()); + writeRawU32(inst.destination, result); return true; } -bool TranslationContext::float16Unary(const RdnaInstruction& inst, IrOpcode opcode, bool invalidNegative) { - const IrF32 argument = readF16AsF32(sourceAt(inst, 0u)); - const IrF32 result(ir.Emit(opcode, IrType::F32, {&argument.Value()})); - if (!invalidNegative) { - writeF16(inst.destination, result); - return true; +bool TranslationContext::float16Unary(const RdnaInstruction& inst, IrOpcode opcode) { + const RdnaOperand& operand = sourceAt(inst, 0u); + const IrF32 argument = readF16AsF32(operand); + const auto [result, invalid] = unaryFloatSpecials(opcode, argument, IrF32(ir.Emit(opcode, IrType::F32, {&argument.Value()}))); + IrU32 bits = packHalf2x16(applyF16ResultModifiers(inst.destination, result), IrF32(ir.ConstantF32(0.0f))); + if (opcode == IrOpcode::FPFract32) { + bits = IrU32(ir.Select(ir.IEqual(bits.Value(), ir.Constant(0x3c00u)), ir.Constant(0x3bffu), bits.Value())); } - const IrU1 negative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&argument.Value(), &ir.ConstantF32(0.0f)})); - const IrF32 modified = applyF32ResultModifiers(inst.destination, result); - const IrU32 bits = packHalf2x16(modified, IrF32(ir.ConstantF32(0.0f))); - const IrU32 invalid(ir.Constant(inst.destination.clamp ? 0u : 0xfe00u)); - write16Bits(inst.destination, IrU32(ir.Select(negative.Value(), invalid.Value(), bits.Value()))); + const IrU32 half = readF16Bits(operand); + const IrU1 nan(ir.UGreaterThan(ir.BitwiseAnd(half.Value(), ir.Constant(0x7fffu)), ir.Constant(0x7c00u))); + const IrU32 special(ir.Select(nan.Value(), ir.BitwiseOr(half.Value(), ir.Constant(0x0200u)), ir.Constant(0xfe00u))); + const IrU32 value(ir.Select(ir.LogicalOr(nan.Value(), invalid.Value()), inst.destination.clamp ? ir.Constant(0u) : special.Value(), bits.Value())); + write16Bits(inst.destination, value); return true; } +std::pair TranslationContext::unaryFloatSpecials(IrOpcode opcode, IrF32 argument, IrF32 result) { + const IrU32 bits(ir.BitCastU32(argument.Value())); + const IrU32 magnitude(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu))); + const IrU32 sign(ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u))); + const bool flushes = opcode == IrOpcode::FPRecip32 || opcode == IrOpcode::FPRecipIFlag32 || opcode == IrOpcode::FPRecipSqrt32 || opcode == IrOpcode::FPSqrt || opcode == IrOpcode::FPLog2 || opcode == IrOpcode::FPExp2; + const IrU1 zero(flushes ? ir.ULessThan(magnitude.Value(), ir.Constant(0x00800000u)) : ir.IEqual(magnitude.Value(), ir.Constant(0u))); + const IrU1 infinite(ir.IEqual(magnitude.Value(), ir.Constant(0x7f800000u))); + const IrU1 negative(ir.LogicalAnd(ir.INotEqual(sign.Value(), ir.Constant(0u)), ir.LogicalNot(zero.Value()))); + const IrU32 signedInfinity(ir.BitwiseOr(sign.Value(), ir.Constant(0x7f800000u))); + const auto pick = [&](IrU1 condition, IrValue& value, IrU32 current) { return IrU32(ir.Select(condition.Value(), value, current.Value())); }; + IrU32 value(ir.BitCastU32(result.Value())); + IrU1 invalid(ir.ConstantBool(false)); + switch (opcode) { + case IrOpcode::FPRecip32: + case IrOpcode::FPRecipIFlag32: + value = pick(infinite, sign.Value(), value); + value = pick(zero, signedInfinity.Value(), value); + break; + case IrOpcode::FPRecipSqrt32: + value = pick(infinite, ir.Constant(0u), value); + value = pick(zero, signedInfinity.Value(), value); + invalid = negative; + break; + case IrOpcode::FPSqrt: + value = pick(infinite, bits.Value(), value); + value = pick(zero, sign.Value(), value); + invalid = negative; + break; + case IrOpcode::FPLog2: + value = pick(infinite, bits.Value(), value); + value = pick(IrU1(ir.IEqual(bits.Value(), ir.Constant(0x3f800000u))), ir.Constant(0u), value); + value = pick(zero, ir.Constant(0xff800000u), value); + invalid = negative; + break; + case IrOpcode::FPExp2: { + const IrU1 overflow(ir.LogicalAnd(ir.IEqual(sign.Value(), ir.Constant(0u)), ir.UGreaterThan(magnitude.Value(), ir.Constant(0x42ffffffu)))); + const IrU1 underflow(ir.LogicalAnd(negative.Value(), ir.UGreaterThan(magnitude.Value(), ir.Constant(0x42fc0000u)))); + value = pick(overflow, ir.Constant(0x7f800000u), value); + value = pick(underflow, ir.Constant(0u), value); + value = pick(zero, ir.Constant(0x3f800000u), value); + break; + } + case IrOpcode::FPFract32: { + const IrU32 unsignedValue(ir.BitwiseAnd(value.Value(), ir.Constant(0x7fffffffu))); + value = pick(IrU1(ir.UGreaterThan(unsignedValue.Value(), ir.Constant(0x3f7fffffu))), ir.Constant(0x3f7fffffu), unsignedValue); + invalid = infinite; + break; + } + case IrOpcode::FPSin: + case IrOpcode::FPCos: { + const IrF32 whole(ir.Emit(IrOpcode::FPTrunc32, IrType::F32, {&argument.Value()})); + const IrF32 difference(ir.Emit(IrOpcode::FPSub32, IrType::F32, {&argument.Value(), &whole.Value()})); + const IrF32 fraction(ir.Emit(IrOpcode::FPAbs32, IrType::F32, {&difference.Value()})); + const auto fractionIs = [&](float cycle) { return IrU1(ir.Emit(IrOpcode::FPOrdEqual32, IrType::U1, {&fraction.Value(), &ir.ConstantF32(cycle)})); }; + if (opcode == IrOpcode::FPSin) { + const IrU1 cardinal(ir.LogicalOr(fractionIs(0.0f).Value(), fractionIs(0.5f).Value())); + value = pick(cardinal, ir.Constant(0u), value); + value = pick(zero, bits.Value(), value); + } else { + value = pick(IrU1(ir.LogicalOr(fractionIs(0.25f).Value(), fractionIs(0.75f).Value())), ir.Constant(0u), value); + } + invalid = infinite; + break; + } + default: + break; + } + return {IrF32(ir.BitCastF32(value.Value())), invalid}; +} + bool TranslationContext::vDivFixupF16(const RdnaInstruction& inst) { std::array bits{}; std::array magnitude{}; @@ -95,33 +165,6 @@ bool TranslationContext::vDivFixupF16(const RdnaInstruction& inst) { return true; } -bool TranslationContext::float16Trig(const RdnaInstruction& inst, IrOpcode opcode) { - const IrF32 argument = readF16AsF32(sourceAt(inst, 0u)); - const IrU32 magnitude(ir.BitwiseAnd(ir.BitCastU32(argument.Value()), ir.Constant(0x7fffffffu))); - const IrF32 raw(ir.Emit(opcode, IrType::F32, {&argument.Value()})); - const IrF32 fraction(ir.Emit(IrOpcode::FPFract32, IrType::F32, {&argument.Value()})); - const IrF32 zero(ir.ConstantF32(0.0f)); - IrF32 result = raw; - if (opcode == IrOpcode::FPSin) { - const IrU1 whole(ir.Emit(IrOpcode::FPOrdEqual32, IrType::U1, {&fraction.Value(), &zero.Value()})); - const IrU1 half(ir.Emit(IrOpcode::FPOrdEqual32, IrType::U1, {&fraction.Value(), &ir.ConstantF32(0.5f)})); - const IrU1 nonzero(ir.INotEqual(magnitude.Value(), ir.Constant(0u))); - const IrU1 cardinal(ir.LogicalOr(ir.LogicalAnd(whole.Value(), nonzero.Value()), half.Value())); - result = selectF32(cardinal, zero, raw); - } else { - const IrU1 quarter(ir.Emit(IrOpcode::FPOrdEqual32, IrType::U1, {&fraction.Value(), &ir.ConstantF32(0.25f)})); - const IrU1 threeQuarters(ir.Emit(IrOpcode::FPOrdEqual32, IrType::U1, {&fraction.Value(), &ir.ConstantF32(0.75f)})); - const IrU1 quarterCycle(ir.LogicalOr(quarter.Value(), threeQuarters.Value())); - result = selectF32(quarterCycle, zero, raw); - } - const IrU1 infinite(ir.IEqual(magnitude.Value(), ir.Constant(0x7f800000u))); - const IrF32 modified = applyF32ResultModifiers(inst.destination, result); - const IrU32 bits = packHalf2x16(modified, zero); - const IrU32 invalid(ir.Constant(inst.destination.clamp ? 0u : 0xfe00u)); - write16Bits(inst.destination, IrU32(ir.Select(infinite.Value(), invalid.Value(), bits.Value()))); - return true; -} - bool TranslationContext::float16Binary(const RdnaInstruction& inst, IrOpcode opcode, bool reverse) { const IrF32 lhs = readF16AsF32(sourceAt(inst, reverse ? 1u : 0u)); const IrF32 rhs = readF16AsF32(sourceAt(inst, reverse ? 0u : 1u)); @@ -172,12 +215,42 @@ IrU32 TranslationContext::normF16(IrU32 bits, bool signedValue) { return IrU32(ir.BitwiseAnd(ir.Select(nan.Value(), ir.Constant(0u), value.Value()), ir.Constant(0xffffu))); } +IrU32 TranslationContext::normF32(IrU32 bits, bool signedValue) { + const IrU32 magnitude(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu))); + const IrU1 negative(ir.INotEqual(ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u)), ir.Constant(0u))); + const IrU1 nan(ir.UGreaterThan(magnitude.Value(), ir.Constant(0x7f800000u))); + const IrU1 saturated(ir.Emit(IrOpcode::UGreaterThanEqual32, IrType::U1, {&magnitude.Value(), &ir.Constant(0x3f800000u)})); + const std::uint32_t scale = signedValue ? 32767u : 65535u; + const IrU32 exponent(ir.ShiftRightLogical(magnitude.Value(), ir.Constant(23u))); + const IrU32 mantissa(ir.BitwiseOr(ir.BitwiseAnd(magnitude.Value(), ir.Constant(0x7fffffu)), ir.Constant(0x800000u))); + const IrU32 shift(ir.Emit(IrOpcode::UMin32, IrType::U32, {&ir.ISub(ir.Constant(149u), exponent.Value()), &ir.Constant(63u)})); + const IrU64 product(ir.Emit(IrOpcode::IMul64, IrType::U64, {&ir.ConstructU64(mantissa.Value(), ir.Constant(0u)), &ir.ConstantU64(scale)})); + const IrU64 halves(ir.Emit(IrOpcode::ShiftRightLogical64, IrType::U64, {&product.Value(), &shift.Value()})); + const IrU1 sticky(ir.Emit(IrOpcode::INotEqual64, IrType::U1, {&ir.Emit(IrOpcode::ShiftLeftLogical64, IrType::U64, {&halves.Value(), &shift.Value()}), &product.Value()})); + const IrU32 doubled = extractU64(halves)[0]; + const IrU32 truncated(ir.ShiftRightLogical(doubled.Value(), ir.Constant(1u))); + const IrU1 roundUp(ir.LogicalAnd(ir.INotEqual(ir.BitwiseAnd(doubled.Value(), ir.Constant(1u)), ir.Constant(0u)), + ir.LogicalOr(sticky.Value(), ir.INotEqual(ir.BitwiseAnd(truncated.Value(), ir.Constant(1u)), ir.Constant(0u))))); + IrU32 value(ir.Select(saturated.Value(), ir.Constant(scale), ir.IAdd(truncated.Value(), ir.Select(roundUp.Value(), ir.Constant(1u), ir.Constant(0u))))); + if (signedValue) { + value = IrU32(ir.Select(negative.Value(), ir.ISub(ir.Constant(0u), value.Value()), value.Value())); + } else { + value = IrU32(ir.Select(negative.Value(), ir.Constant(0u), value.Value())); + } + return IrU32(ir.BitwiseAnd(ir.Select(nan.Value(), ir.Constant(0u), value.Value()), ir.Constant(0xffffu))); +} + bool TranslationContext::vLdexpF16(const RdnaInstruction& inst) { - const IrF32 value = readF16AsF32(sourceAt(inst, 0u)); + const IrU32 bits = readF16Bits(sourceAt(inst, 0u)); + const IrF16 half(ir.Emit(IrOpcode::BitCastF16U16, IrType::F16, {&ir.Emit(IrOpcode::ConvertU16U32, IrType::U16, {&bits.Value()})})); + const IrF32 value(ir.Emit(IrOpcode::ConvertF32F16, IrType::F32, {&half.Value()})); const IrU32 exponent(ir.Emit(IrOpcode::BitFieldSExtract, IrType::U32, {&readU32(sourceAt(inst, 1u)).Value(), &ir.Constant(0u), &ir.Constant(16u)})); const IrU32 clamped(ir.Emit(IrOpcode::SMax32, IrType::U32, {&ir.Emit(IrOpcode::SMin32, IrType::U32, {&exponent.Value(), &ir.Constant(64u)}), &ir.Constant(static_cast(-64))})); IrValue& power = ir.BitCastF32(ir.ShiftLeftLogical(ir.IAdd(clamped.Value(), ir.Constant(127u)), ir.Constant(23u))); - writeF16(inst.destination, IrF32(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&value.Value(), &power}))); + const IrF16 scaled(ir.Emit(IrOpcode::ConvertF16F32, IrType::F16, {&ir.Emit(IrOpcode::FPMul32, IrType::F32, {&value.Value(), &power})})); + const IrU32 result(ir.Emit(IrOpcode::ConvertU32U16, IrType::U32, {&ir.Emit(IrOpcode::BitCastU16F16, IrType::U16, {&scaled.Value()})})); + const IrU1 nan(ir.UGreaterThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffu)), ir.Constant(0x7c00u))); + write16Bits(inst.destination, IrU32(ir.Select(nan.Value(), ir.BitwiseOr(bits.Value(), ir.Constant(0x200u)), result.Value()))); return true; } @@ -239,6 +312,25 @@ bool TranslationContext::vMulLegacyF32(const RdnaInstruction& inst, bool accumul return true; } +bool TranslationContext::vMullitF32(const RdnaInstruction& inst) { + const IrU32 lhs = readU32(sourceAt(inst, 0u)); + const IrU32 rhs = readU32(sourceAt(inst, 1u)); + const IrU32 limit = readU32(sourceAt(inst, 2u)); + const auto magnitude = [&](const IrU32& bits) { return IrU32(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu))); }; + const auto isNan = [&](const IrU32& bits) { return IrU1(ir.UGreaterThan(magnitude(bits).Value(), ir.Constant(0x7f800000u))); }; + const auto isZero = [&](const IrU32& bits) { return IrU1(ir.IEqual(magnitude(bits).Value(), ir.Constant(0u))); }; + const IrU1 limitNotPositive(ir.LogicalOr(isZero(limit).Value(), ir.INotEqual(ir.BitwiseAnd(limit.Value(), ir.Constant(0x80000000u)), ir.Constant(0u)))); + const IrU1 rhsNegativeMax(ir.UGreaterThan(rhs.Value(), ir.Constant(0xff7ffffeu))); + IrU1 lowest(ir.LogicalOr(rhsNegativeMax.Value(), ir.LogicalOr(isNan(limit).Value(), limitNotPositive.Value()))); + lowest = IrU1(ir.LogicalOr(lowest.Value(), isNan(rhs).Value())); + const IrU32 product(ir.BitCastU32(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&ir.BitCastF32(lhs.Value()), &ir.BitCastF32(rhs.Value())}))); + IrU32 result(ir.Select(isNan(lhs).Value(), ir.BitwiseOr(lhs.Value(), ir.Constant(0x00400000u)), product.Value())); + result = IrU32(ir.Select(ir.LogicalOr(isZero(lhs).Value(), isZero(rhs).Value()), ir.Constant(0u), result.Value())); + result = IrU32(ir.Select(lowest.Value(), ir.Constant(0xff7fffffu), result.Value())); + writeOperand(inst.destination, &result.Value()); + return true; +} + void TranslationContext::emitFloat16ClassCompare(const RdnaInstruction& inst, bool cmpx) { const IrU32 bits = readF16Bits(sourceAt(inst, 0u)); const IrU32 mask = readU32(sourceAt(inst, 1u)); @@ -365,9 +457,13 @@ bool TranslationContext::vDivFixupF32(const RdnaInstruction& inst) { } bool TranslationContext::floatUnary(const RdnaInstruction& inst, IrOpcode opcode) { - IrValue* argument = readOperand(sourceAt(inst, 0u), IrOpcodeArgumentType(opcode, 0u)); - IrValue& result = ir.Emit(opcode, IrOpcodeType(opcode), {argument}); - writeOperand(inst.destination, &result); + const IrU32 bits = readU32(sourceAt(inst, 0u)); + const IrF32 argument(ir.BitCastF32(bits.Value())); + const auto [result, invalid] = unaryFloatSpecials(opcode, argument, IrF32(ir.Emit(opcode, IrType::F32, {&argument.Value()}))); + const IrU1 nan(ir.UGreaterThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu)), ir.Constant(0x7f800000u))); + const IrU32 special(ir.Select(nan.Value(), ir.BitwiseOr(bits.Value(), ir.Constant(0x00400000u)), ir.Constant(0xffc00000u))); + const IrF32 value(ir.Select(ir.LogicalOr(nan.Value(), invalid.Value()), ir.BitCastF32(special.Value()), result.Value())); + writeOperand(inst.destination, &value.Value()); return true; } @@ -394,6 +490,17 @@ bool TranslationContext::floatTernary(const RdnaInstruction& inst, IrOpcode opco return true; } +bool TranslationContext::vFmaLegacyF32(const RdnaInstruction& inst) { + IrValue* lhs = readOperand(sourceAt(inst, 0u), IrType::F32); + IrValue* rhs = readOperand(sourceAt(inst, 1u), IrType::F32); + IrValue* addend = readOperand(sourceAt(inst, 2u), IrType::F32); + const auto isZero = [&](IrValue* value) { return IrU1(ir.IEqual(ir.BitwiseAnd(ir.BitCastU32(*value), ir.Constant(0x7fffffffu)), ir.Constant(0u))); }; + const IrU1 zero(ir.LogicalOr(isZero(lhs).Value(), isZero(rhs).Value())); + const auto factor = [&](IrValue* value) { return &ir.Emit(IrOpcode::SelectF32, IrType::F32, {&zero.Value(), &ir.ConstantF32(0.0f), value}); }; + writeOperand(inst.destination, &ir.Emit(IrOpcode::FPFma32, IrType::F32, {factor(lhs), factor(rhs), addend})); + return true; +} + bool TranslationContext::vFrexpMantF32(const RdnaInstruction& inst) { const IrU32 bits = readU32(sourceAt(inst, 0u)); const IrU32 exponent(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&bits.Value(), &ir.Constant(23u), &ir.Constant(8u)})); @@ -410,7 +517,25 @@ bool TranslationContext::vFrexpMantF32(const RdnaInstruction& inst) { const IrU32 zeroOrSubnormal(ir.Select(zero.Value(), bits.Value(), subnormal.Value())); const IrU32 finite(ir.Select(exponentNonZero.Value(), normal.Value(), zeroOrSubnormal.Value())); const IrU1 exponentAllOnes(ir.IEqual(exponent.Value(), ir.Constant(0xffu))); - const IrU32 result(ir.Select(exponentAllOnes.Value(), bits.Value(), finite.Value())); + const IrU32 special(ir.Select(zero.Value(), bits.Value(), ir.BitwiseOr(bits.Value(), ir.Constant(0x00400000u)))); + const IrU32 result(ir.Select(exponentAllOnes.Value(), special.Value(), finite.Value())); + writeOperand(inst.destination, &ir.BitCastF32(result.Value())); + return true; +} + +bool TranslationContext::vLdexpF32(const RdnaInstruction& inst) { + const IrU32 bits = readU32(sourceAt(inst, 0u)); + const IrU32 offset = readU32(sourceAt(inst, 1u)); + const IrU32 sign(ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u))); + const IrU32 exponent(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&bits.Value(), &ir.Constant(23u), &ir.Constant(8u)})); + const IrU32 clamped(ir.Emit(IrOpcode::SMax32, IrType::U32, {&ir.Emit(IrOpcode::SMin32, IrType::U32, {&offset.Value(), &ir.Constant(512u)}), &ir.Constant(static_cast(-512))})); + const IrU32 scaled(ir.IAdd(exponent.Value(), clamped.Value())); + const IrU32 normal(ir.BitwiseOr(ir.BitwiseAnd(bits.Value(), ir.Constant(0x807fffffu)), ir.ShiftLeftLogical(scaled.Value(), ir.Constant(23u)))); + const IrU32 overflow(ir.BitwiseOr(sign.Value(), ir.Constant(0x7f800000u))); + IrU32 result(ir.Select(ir.Emit(IrOpcode::SGreaterThanEqual32, IrType::U1, {&scaled.Value(), &ir.Constant(255u)}), overflow.Value(), normal.Value())); + result = IrU32(ir.Select(ir.Emit(IrOpcode::SLessThanEqual32, IrType::U1, {&scaled.Value(), &ir.Constant(0u)}), sign.Value(), result.Value())); + result = IrU32(ir.Select(ir.IEqual(exponent.Value(), ir.Constant(0xffu)), bits.Value(), result.Value())); + result = IrU32(ir.Select(ir.IEqual(exponent.Value(), ir.Constant(0u)), sign.Value(), result.Value())); writeOperand(inst.destination, &ir.BitCastF32(result.Value())); return true; } @@ -433,6 +558,18 @@ bool TranslationContext::vDot2cF32F16(const RdnaInstruction& inst) { return true; } +bool TranslationContext::vDot2F32F16(const RdnaInstruction& inst) { + const IrF32 aLow = readF16LaneAsF32(sourceAt(inst, 0u), false, true); + const IrF32 aHigh = readF16LaneAsF32(sourceAt(inst, 0u), true, true); + const IrF32 bLow = readF16LaneAsF32(sourceAt(inst, 1u), false, true); + const IrF32 bHigh = readF16LaneAsF32(sourceAt(inst, 1u), true, true); + IrValue* accumulator = readOperand(sourceAt(inst, 2u), IrType::F32); + const IrF32 low(ir.Emit(IrOpcode::FPFma32, IrType::F32, {&aLow.Value(), &bLow.Value(), accumulator})); + const IrF32 result(ir.Emit(IrOpcode::FPFma32, IrType::F32, {&aHigh.Value(), &bHigh.Value(), &low.Value()})); + writeOperand(inst.destination, &result.Value()); + return true; +} + bool TranslationContext::vCubeidF32(const RdnaInstruction& inst) { return floatCube(inst, 0u); } @@ -450,22 +587,34 @@ bool TranslationContext::vCubemaF32(const RdnaInstruction& inst) { } bool TranslationContext::floatCube(const RdnaInstruction& inst, std::uint32_t resultKind) { - const IrF32 x = readMixF32(sourceAt(inst, 0u)); - const IrF32 y = readMixF32(sourceAt(inst, 1u)); - const IrF32 z = readMixF32(sourceAt(inst, 2u)); - const IrF32 nx(ir.Emit(IrOpcode::FPNeg32, IrType::F32, {&x.Value()})); - const IrF32 ny(ir.Emit(IrOpcode::FPNeg32, IrType::F32, {&y.Value()})); - const IrF32 nz(ir.Emit(IrOpcode::FPNeg32, IrType::F32, {&z.Value()})); - const IrF32 ax(ir.Emit(IrOpcode::FPAbs32, IrType::F32, {&x.Value()})); - const IrF32 ay(ir.Emit(IrOpcode::FPAbs32, IrType::F32, {&y.Value()})); - const IrF32 az(ir.Emit(IrOpcode::FPAbs32, IrType::F32, {&z.Value()})); + const IrF32 x(ir.BitCastF32(readU32(sourceAt(inst, 0u)).Value())); + const IrF32 y(ir.BitCastF32(readU32(sourceAt(inst, 1u)).Value())); + const IrF32 z(ir.BitCastF32(readU32(sourceAt(inst, 2u)).Value())); + const IrF32 nx(ir.BitCastF32(ir.BitwiseXor(ir.BitCastU32(x.Value()), ir.Constant(0x80000000u)))); + const IrF32 ny(ir.BitCastF32(ir.BitwiseXor(ir.BitCastU32(y.Value()), ir.Constant(0x80000000u)))); + const IrF32 nz(ir.BitCastF32(ir.BitwiseXor(ir.BitCastU32(z.Value()), ir.Constant(0x80000000u)))); + const auto exponentZero = [&](IrF32 value) { + return IrU1(ir.IEqual(ir.BitwiseAnd(ir.BitCastU32(value.Value()), ir.Constant(0x7f800000u)), ir.Constant(0u))); + }; + const auto flushed = [&](IrF32 value) { + return IrF32(ir.BitCastF32(ir.Select(exponentZero(value).Value(), ir.Constant(0u), ir.BitCastU32(value.Value())))); + }; + const auto magnitude = [&](IrF32 value) { + return IrF32(ir.BitCastF32(ir.BitwiseAnd(ir.BitCastU32(value.Value()), ir.Constant(0x7fffffffu)))); + }; + const IrF32 fx = flushed(x); + const IrF32 fy = flushed(y); + const IrF32 fz = flushed(z); + const IrF32 ax = magnitude(fx); + const IrF32 ay = magnitude(fy); + const IrF32 az = magnitude(fz); const IrU1 zDominatesX(ir.Emit(IrOpcode::FPOrdGreaterThanEqual32, IrType::U1, {&az.Value(), &ax.Value()})); const IrU1 zDominatesY(ir.Emit(IrOpcode::FPOrdGreaterThanEqual32, IrType::U1, {&az.Value(), &ay.Value()})); const IrU1 zFace(ir.LogicalAnd(zDominatesX.Value(), zDominatesY.Value())); const IrU1 yFace(ir.Emit(IrOpcode::FPOrdGreaterThanEqual32, IrType::U1, {&ay.Value(), &ax.Value()})); - const IrU1 xNegative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&x.Value(), &ir.ConstantF32(0.0f)})); - const IrU1 yNegative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&y.Value(), &ir.ConstantF32(0.0f)})); - const IrU1 zNegative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&z.Value(), &ir.ConstantF32(0.0f)})); + const IrU1 xNegative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&fx.Value(), &ir.ConstantF32(0.0f)})); + const IrU1 yNegative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&fy.Value(), &ir.ConstantF32(0.0f)})); + const IrU1 zNegative(ir.Emit(IrOpcode::FPOrdLessThan32, IrType::U1, {&fz.Value(), &ir.ConstantF32(0.0f)})); const auto selectFace = [&](IrF32 xValue, IrF32 yValue, IrF32 zValue) { return selectF32(zFace, zValue, selectF32(yFace, yValue, xValue)); }; @@ -488,11 +637,10 @@ bool TranslationContext::floatCube(const RdnaInstruction& inst, std::uint32_t re result = selectFace(ny, selectF32(yNegative, nz, z), ny); break; case 3u: { - const IrF32 two(ir.ConstantF32(2.0f)); - const IrF32 xTwo(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&x.Value(), &two.Value()})); - const IrF32 yTwo(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&y.Value(), &two.Value()})); - const IrF32 zTwo(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&z.Value(), &two.Value()})); - result = selectFace(xTwo, yTwo, zTwo); + const IrF32 major = selectFace(x, y, z); + const IrF32 twice(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&major.Value(), &ir.ConstantF32(2.0f)})); + const IrU1 nan(ir.UGreaterThan(ir.BitCastU32(magnitude(major).Value()), ir.Constant(0x7f800000u))); + result = selectF32(nan, major, selectF32(exponentZero(major), IrF32(ir.ConstantF32(0.0f)), twice)); break; } default: diff --git a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp index b02afbaa4..cff0d65b7 100644 --- a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp +++ b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp @@ -153,6 +153,10 @@ void includeInstructionVectorRegisters(const RdnaInstruction& instruction, std:: includeVector(instruction.source1); includeVector(instruction.source2); includeVector(instruction.source3); + if (instruction.family == RdnaInstructionFamily::FLAT) { + const bool compare = instruction.op == RdnaOpcode::FlatAtomicCmpswap || instruction.op == RdnaOpcode::FlatAtomicCmpswapX2 || instruction.op == RdnaOpcode::FlatAtomicFcmpswap || instruction.op == RdnaOpcode::FlatAtomicFcmpswapX2; + includeVector(instruction.source2, std::max(instruction.dataDwordCount, 1u) * (compare ? 2u : 1u)); + } if (instruction.family == RdnaInstructionFamily::DS) { switch (instruction.op) { case RdnaOpcode::DsWriteB64: diff --git a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp index 81974fcbe..ba6b25f67 100644 --- a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp +++ b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp @@ -25,6 +25,33 @@ bool TranslationContext::integer16Binary(const RdnaInstruction& inst, IrOpcode o return true; } +IrU32 TranslationContext::saturateU16Result(const RdnaInstruction& inst, const IrU32& value, bool sign) { + if (!inst.destination.clamp) { + return value; + } + if (!sign) { + return IrU32(ir.Emit(IrOpcode::UMin32, IrType::U32, {&value.Value(), &ir.Constant(0xffffu)})); + } + const IrU32 upper(ir.Emit(IrOpcode::SMin32, IrType::U32, {&value.Value(), &ir.Constant(0x7fffu)})); + return IrU32(ir.Emit(IrOpcode::SMax32, IrType::U32, {&upper.Value(), &ir.Constant(0xffff8000u)})); +} + +IrU32 TranslationContext::wideAdd(const RdnaInstruction& inst, const IrU32& low, const IrU32& high, const IrU32& addend, bool sign) { + const IrU32 sum(ir.IAdd(low.Value(), addend.Value())); + if (!inst.destination.clamp) { + return sum; + } + const IrU32 carry(ir.Select(ir.ULessThan(sum.Value(), low.Value()), ir.Constant(1u), ir.Constant(0u))); + IrU32 top(ir.IAdd(high.Value(), carry.Value())); + if (!sign) { + return IrU32(ir.Select(ir.INotEqual(top.Value(), ir.Constant(0u)), ir.Constant(0xffffffffu), sum.Value())); + } + top = IrU32(ir.IAdd(top.Value(), ir.ShiftRightArithmetic(addend.Value(), ir.Constant(31u)))); + const IrU1 overflow(ir.INotEqual(top.Value(), ir.ShiftRightArithmetic(sum.Value(), ir.Constant(31u)))); + IrValue& saturated = ir.BitwiseXor(ir.ShiftRightArithmetic(top.Value(), ir.Constant(31u)), ir.Constant(0x7fffffffu)); + return IrU32(ir.Select(overflow.Value(), saturated, sum.Value())); +} + bool TranslationContext::vMed3I16(const RdnaInstruction& inst) { const IrU32 first = readU16AsU32(sourceAt(inst, 0u), true); const IrU32 second = readU16AsU32(sourceAt(inst, 1u), true); @@ -46,11 +73,14 @@ bool TranslationContext::integer16Ternary(const RdnaInstruction& inst, IrOpcode bool TranslationContext::integer16Mad(const RdnaInstruction& inst, bool sign, bool wide) { const IrU32 lhs = readU16AsU32(sourceAt(inst, 0u), sign); const IrU32 rhs = readU16AsU32(sourceAt(inst, 1u), sign); - const IrU32 addend = wide ? readU32(sourceAt(inst, 2u)) : readU16AsU32(sourceAt(inst, 2u), sign); - const IrU32 result(ir.IAdd(ir.IMul(lhs.Value(), rhs.Value()), addend.Value())); + const IrU32 product(ir.IMul(lhs.Value(), rhs.Value())); if (wide) { + const IrU32 high(sign ? ir.ShiftRightArithmetic(product.Value(), ir.Constant(31u)) : ir.Constant(0u)); + const IrU32 result = wideAdd(inst, product, high, readU32(sourceAt(inst, 2u)), sign); writeOperand(inst.destination, &result.Value()); } else { + const IrU32 addend = readU16AsU32(sourceAt(inst, 2u), sign); + const IrU32 result = saturateU16Result(inst, IrU32(ir.IAdd(product.Value(), addend.Value())), sign); write16Bits(inst.destination, IrU32(ir.BitwiseAnd(result.Value(), ir.Constant(0xffffu)))); } return true; @@ -316,8 +346,15 @@ bool TranslationContext::integer24(const RdnaInstruction& inst, bool sign, bool const IrU32 rhs(ir.Emit(extractOpcode, IrType::U32, {&rhsSource.Value(), &ir.Constant(0u), &ir.Constant(24u)})); const IrOpcode multiplyOpcode = high ? (sign ? IrOpcode::SMulHi : IrOpcode::UMulHi) : IrOpcode::IMul32; IrU32 result(ir.Emit(multiplyOpcode, IrType::U32, {&lhs.Value(), &rhs.Value()})); + if (inst.destination.clamp && !high && !addend) { + const IrU32 top(ir.Emit(sign ? IrOpcode::SMulHi : IrOpcode::UMulHi, IrType::U32, {&lhs.Value(), &rhs.Value()})); + IrValue& expected = sign ? ir.ShiftRightArithmetic(result.Value(), ir.Constant(31u)) : ir.Constant(0u); + IrValue& saturated = sign ? ir.BitwiseXor(ir.ShiftRightArithmetic(top.Value(), ir.Constant(31u)), ir.Constant(0x7fffffffu)) : ir.Constant(0xffffffffu); + result = IrU32(ir.Select(ir.INotEqual(top.Value(), expected), saturated, result.Value())); + } if (addend) { - result = IrU32(ir.IAdd(result.Value(), readU32(sourceAt(inst, 2u)).Value())); + const IrU32 productHigh(inst.destination.clamp ? ir.Emit(sign ? IrOpcode::SMulHi : IrOpcode::UMulHi, IrType::U32, {&lhs.Value(), &rhs.Value()}) : ir.Constant(0u)); + result = wideAdd(inst, result, productHigh, readU32(sourceAt(inst, 2u)), sign); } writeOperand(inst.destination, &result.Value()); return true; @@ -336,10 +373,24 @@ bool TranslationContext::vMad64x32(const RdnaInstruction& inst, bool sign) { const IrU32 carryLowU32(ir.Select(carryLow.Value(), ir.Constant(1u), ir.Constant(0u))); const IrU32 high(ir.IAdd(high0.Value(), carryLowU32.Value())); const IrU1 carry1(ir.ULessThan(high.Value(), high0.Value())); - const IrU64 result(ir.ConstructU64(low.Value(), high.Value())); + const IrU1 carry(ir.LogicalOr(carry0.Value(), carry1.Value())); + IrU32 resultLow = low; + IrU32 resultHigh = high; + if (inst.destination.clamp) { + if (sign) { + IrValue& flipped = ir.BitwiseAnd(ir.BitwiseXor(mulHigh.Value(), high.Value()), ir.BitwiseXor(add[1].Value(), high.Value())); + const IrU1 overflow(ir.INotEqual(ir.ShiftRightLogical(flipped, ir.Constant(31u)), ir.Constant(0u))); + IrValue& addSign = ir.ShiftRightArithmetic(add[1].Value(), ir.Constant(31u)); + resultLow = IrU32(ir.Select(overflow.Value(), ir.BitwiseXor(addSign, ir.Constant(0xffffffffu)), low.Value())); + resultHigh = IrU32(ir.Select(overflow.Value(), ir.BitwiseXor(addSign, ir.Constant(0x7fffffffu)), high.Value())); + } else { + resultLow = IrU32(ir.Select(carry.Value(), ir.Constant(0xffffffffu), low.Value())); + resultHigh = IrU32(ir.Select(carry.Value(), ir.Constant(0xffffffffu), high.Value())); + } + } + const IrU64 result(ir.ConstructU64(resultLow.Value(), resultHigh.Value())); writeOperand(inst.destination, &result.Value()); if (inst.destination2.kind != RdnaOperandKind::Null && inst.destination2.kind != RdnaOperandKind::Unknown) { - const IrU1 carry(ir.LogicalOr(carry0.Value(), carry1.Value())); if (!sign) { writeMask(inst.destination2, carry); return true; @@ -357,7 +408,7 @@ bool TranslationContext::vSadU32(const RdnaInstruction& inst) { const IrU32 lo(ir.Emit(IrOpcode::UMin32, IrType::U32, {&lhs.Value(), &rhs.Value()})); const IrU32 hi(ir.Emit(IrOpcode::UMax32, IrType::U32, {&lhs.Value(), &rhs.Value()})); const IrU32 addend = readU32(sourceAt(inst, 2u)); - const IrU32 result(ir.IAdd(ir.ISub(hi.Value(), lo.Value()), addend.Value())); + const IrU32 result = wideAdd(inst, IrU32(ir.ISub(hi.Value(), lo.Value())), IrU32(ir.Constant(0u)), addend, false); writeOperand(inst.destination, &result.Value()); return true; } @@ -385,7 +436,7 @@ bool TranslationContext::subwordSad(const RdnaInstruction& inst, std::uint32_t f sum = IrU32(ir.ShiftLeftLogical(sum.Value(), ir.Constant(sumShift))); } const IrU32 addend = readU32(sourceAt(inst, 2u)); - const IrU32 result(ir.IAdd(sum.Value(), addend.Value())); + const IrU32 result = wideAdd(inst, sum, IrU32(ir.Constant(0u)), addend, false); writeOperand(inst.destination, &result.Value()); return true; } @@ -403,14 +454,15 @@ bool TranslationContext::vQsadU8(const RdnaInstruction& inst, bool masked, bool const std::array accumulator = readU32Pair(sourceAt(inst, 2u)); if (wide) { const std::array accumulatorHigh = readU32Pair(offsetOperand(sourceAt(inst, 2u), 2u)); - writeU32Pair(inst.destination, {IrU32(ir.IAdd(accumulator[0].Value(), sad(0u).Value())), IrU32(ir.IAdd(accumulator[1].Value(), sad(8u).Value()))}); + const IrU32 zero(ir.Constant(0u)); + writeU32Pair(inst.destination, {wideAdd(inst, sad(0u), zero, accumulator[0], false), wideAdd(inst, sad(8u), zero, accumulator[1], false)}); writeU32Pair(offsetOperand(inst.destination, 2u), - {IrU32(ir.IAdd(accumulatorHigh[0].Value(), sad(16u).Value())), IrU32(ir.IAdd(accumulatorHigh[1].Value(), sad(24u).Value()))}); + {wideAdd(inst, sad(16u), zero, accumulatorHigh[0], false), wideAdd(inst, sad(24u), zero, accumulatorHigh[1], false)}); return true; } const auto packed = [&](const IrU32& addend, std::uint32_t shift) { - const IrU32 low(ir.IAdd(addend.Value(), sad(shift).Value())); - const IrU32 high(ir.IAdd(ir.ShiftRightLogical(addend.Value(), ir.Constant(16u)), sad(shift + 8u).Value())); + const IrU32 low = saturateU16Result(inst, IrU32(ir.IAdd(ir.BitwiseAnd(addend.Value(), ir.Constant(0xffffu)), sad(shift).Value())), false); + const IrU32 high = saturateU16Result(inst, IrU32(ir.IAdd(ir.ShiftRightLogical(addend.Value(), ir.Constant(16u)), sad(shift + 8u).Value())), false); return IrU32(ir.BitwiseOr(ir.BitwiseAnd(low.Value(), ir.Constant(0xffffu)), ir.ShiftLeftLogical(high.Value(), ir.Constant(16u)))); }; writeU32Pair(inst.destination, {packed(accumulator[0], 0u), packed(accumulator[1], 16u)}); @@ -600,15 +652,21 @@ bool TranslationContext::sBfmB64(const RdnaInstruction& inst) { return true; } +IrU32 TranslationContext::extractBits32(IrU32 source, IrU32 offset, IrU32 rawCount, bool sign) { + const IrU32 count(ir.Emit(IrOpcode::UMin32, IrType::U32, {&rawCount.Value(), &ir.Constant(32u)})); + const IrU32 discard(ir.BitwiseAnd(ir.ISub(ir.Constant(32u), count.Value()), ir.Constant(31u))); + const IrU32 shifted(sign ? ir.ShiftRightArithmetic(source.Value(), offset.Value()) : ir.ShiftRightLogical(source.Value(), offset.Value())); + const IrU32 aligned(ir.ShiftLeftLogical(shifted.Value(), discard.Value())); + const IrU32 field(sign ? ir.ShiftRightArithmetic(aligned.Value(), discard.Value()) : ir.ShiftRightLogical(aligned.Value(), discard.Value())); + return IrU32(ir.Select(ir.INotEqual(count.Value(), ir.Constant(0u)), field.Value(), ir.Constant(0u))); +} + bool TranslationContext::sBfeU32(const RdnaInstruction& inst, bool sign) { const IrU32 source = readU32(sourceAt(inst, 0u)); const IrU32 field = readU32(sourceAt(inst, 1u)); const IrU32 offset(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&field.Value(), &ir.Constant(0u), &ir.Constant(5u)})); const IrU32 rawCount(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&field.Value(), &ir.Constant(16u), &ir.Constant(7u)})); - const IrU32 available(ir.ISub(ir.Constant(32u), offset.Value())); - const IrU32 count(ir.Emit(IrOpcode::UMin32, IrType::U32, {&rawCount.Value(), &available.Value()})); - const IrOpcode opcode = sign ? IrOpcode::BitFieldSExtract : IrOpcode::BitFieldUExtract; - const IrU32 result(ir.Emit(opcode, IrType::U32, {&source.Value(), &offset.Value(), &count.Value()})); + const IrU32 result = extractBits32(source, offset, rawCount, sign); writeOperand(inst.destination, &result.Value()); ir.SetScc(ir.INotEqual(result.Value(), ir.Constant(0u))); return true; @@ -684,15 +742,12 @@ bool TranslationContext::vAlignbitB32(const RdnaInstruction& inst) { bool TranslationContext::vAlignbyteB32(const RdnaInstruction& inst) { const IrU32 hi = readU32(sourceAt(inst, 0u)); const IrU32 lo = readU32(sourceAt(inst, 1u)); - const IrU32 byteOffset(ir.BitwiseAnd(readU32(sourceAt(inst, 2u)).Value(), ir.Constant(31u))); + const IrU32 byteOffset(ir.BitwiseAnd(readU32(sourceAt(inst, 2u)).Value(), ir.Constant(3u))); const IrU32 bitOffset(ir.ShiftLeftLogical(byteOffset.Value(), ir.Constant(3u))); const IrU64 concatenated(ir.ConstructU64(lo.Value(), hi.Value())); - const IrU32 maskedBitOffset(ir.BitwiseAnd(bitOffset.Value(), ir.Constant(63u))); - const IrU64 shifted(ir.Emit(IrOpcode::ShiftRightLogical64, IrType::U64, {&concatenated.Value(), &maskedBitOffset.Value()})); - const IrU1 inRange(ir.ULessThan(byteOffset.Value(), ir.Constant(8u))); + const IrU64 shifted(ir.Emit(IrOpcode::ShiftRightLogical64, IrType::U64, {&concatenated.Value(), &bitOffset.Value()})); const std::array extracted = extractU64(shifted); - const IrU32 result(ir.Select(inRange.Value(), extracted[0].Value(), ir.Constant(0u))); - writeOperand(inst.destination, &result.Value()); + writeOperand(inst.destination, &extracted[0].Value()); return true; } @@ -802,17 +857,9 @@ bool TranslationContext::vCndmaskB32(const RdnaInstruction& inst) { const IrU1 condition = readMask(maskOperand); const RdnaOperand& falseOperand = sourceAt(inst, 0u); const RdnaOperand& trueOperand = sourceAt(inst, 1u); - IrValue* result = nullptr; - if (falseOperand.negate || falseOperand.absolute || trueOperand.negate || trueOperand.absolute) { - IrValue* falseValue = readOperand(falseOperand, IrType::F32); - IrValue* trueValue = readOperand(trueOperand, IrType::F32); - result = &ir.Emit(IrOpcode::SelectF32, IrType::F32, {&condition.Value(), trueValue, falseValue}); - } else { - const IrU32 falseValue = readU32(falseOperand); - const IrU32 trueValue = readU32(trueOperand); - result = &ir.Select(condition.Value(), trueValue.Value(), falseValue.Value()); - } - writeOperand(inst.destination, result); + const IrU32 falseValue = readU32(falseOperand); + const IrU32 trueValue = readU32(trueOperand); + writeOperand(inst.destination, &ir.Select(condition.Value(), trueValue.Value(), falseValue.Value())); return true; } diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/FlatInstructions.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/FlatInstructions.cpp index 13de61432..9fbd72b3e 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/FlatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/FlatInstructions.cpp @@ -55,7 +55,8 @@ MemoryInfo flatMemoryInfoFromInstruction(const RdnaInstruction& inst) { } bool TranslationContext::flatLoad(const RdnaInstruction& inst) { - const MemoryInfo memory = flatMemoryInfoFromInstruction(inst); + MemoryInfo memory = flatMemoryInfoFromInstruction(inst); + memory.coherent = inst.glc || inst.dlc; IrOpcode opcode; switch (memory.dataBits) { case 8u: @@ -96,7 +97,7 @@ bool TranslationContext::flatLoad(const RdnaInstruction& inst) { return true; } -bool TranslationContext::globalLoadAddtid(const RdnaInstruction& inst) { +bool TranslationContext::globalAddtid(const RdnaInstruction& inst, bool write) { MemoryInfo memory = flatMemoryInfoFromInstruction(inst); memory.addressIsFull = false; const IrU32 baseLow = readU32(inst.source0); @@ -105,13 +106,19 @@ bool TranslationContext::globalLoadAddtid(const RdnaInstruction& inst) { IrValue& lane = ir.Emit(IrOpcode::LaneId, IrOpcodeType(IrOpcode::LaneId), {}); IrValue& laneOffset = ir.ShiftLeftLogical(lane, ir.Constant(2u)); IrValue& active = ir.GetExec(); + if (write) { + const IrU32 data = readU32(inst.destination); + (void)ir.Emit(IrOpcode::StoreAddressU32, IrType::Void, {resource, &laneOffset, &ir.Constant(0u), &data.Value(), &active}, addMemoryInfo(memory, inst.programCounter)); + return true; + } IrValue& loaded = ir.Emit(IrOpcode::LoadAddressU32, IrOpcodeType(IrOpcode::LoadAddressU32), {resource, &laneOffset, &ir.Constant(0u), &active}, addMemoryInfo(memory, inst.programCounter)); writeOperand(inst.destination, &loaded); return true; } bool TranslationContext::flatStore(const RdnaInstruction& inst) { - const MemoryInfo memory = flatMemoryInfoFromInstruction(inst); + MemoryInfo memory = flatMemoryInfoFromInstruction(inst); + memory.coherent = inst.glc || inst.dlc; IrOpcode opcode; switch (memory.dataBits) { case 8u: @@ -141,4 +148,34 @@ bool TranslationContext::flatStore(const RdnaInstruction& inst) { return true; } +bool TranslationContext::flatAtomic(const RdnaInstruction& inst, IrOpcode opcode) { + const MemoryInfo memory = flatMemoryInfoFromInstruction(inst); + const AddressOperands address = readAddressOperands(inst, 0u); + const MemoryFlags flags = addMemoryInfo(memory, inst.programCounter); + IrValue& active = ir.GetExec(); + const bool compare = opcode == IrOpcode::AddressAtomicCmpSwap32 || opcode == IrOpcode::AddressAtomicCmpSwap64 || opcode == IrOpcode::AddressAtomicFCmpSwap32 || opcode == IrOpcode::AddressAtomicFCmpSwap64; + IrValue* result; + if (IrOpcodeType(opcode) == IrType::U64) { + const IrU64 value = readU64(inst.source2); + if (compare) { + const IrU64 comparator = readU64(offsetOperand(inst.source2, 2u)); + result = &ir.Emit(opcode, IrOpcodeType(opcode), {address.resource, address.low, address.high, &value.Value(), &comparator.Value(), &active}, flags); + } else { + result = &ir.Emit(opcode, IrOpcodeType(opcode), {address.resource, address.low, address.high, &value.Value(), &active}, flags); + } + } else { + const IrU32 value = readU32(inst.source2); + if (compare) { + const IrU32 comparator = readU32(offsetOperand(inst.source2, 1u)); + result = &ir.Emit(opcode, IrOpcodeType(opcode), {address.resource, address.low, address.high, &value.Value(), &comparator.Value(), &active}, flags); + } else { + result = &ir.Emit(opcode, IrOpcodeType(opcode), {address.resource, address.low, address.high, &value.Value(), &active}, flags); + } + } + if (inst.glc) { + writeOperand(inst.destination, result); + } + return true; +} + } diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp index 76cc041ea..2f0bf639b 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp @@ -61,6 +61,7 @@ bool TranslationContext::imageBvhIntersectRay(const RdnaInstruction& inst) { memory.kind = ResourceKind::Global; memory.dataDwords = 4u; memory.imageSampleFlags = inst.imageA16 ? RdnaImageSampleFlagA16 : 0u; + memory.addressIsFull = inst.op == RdnaOpcode::ImageBvh64IntersectRay; IrValue* descriptor = constructU32x4(inst.source1, 4u); IrValue* address = makeImageAddress(inst, inst.source0); IrValue& result = ir.Emit(IrOpcode::ImageBvhIntersectRay, IrOpcodeType(IrOpcode::ImageBvhIntersectRay), {descriptor, address, &ir.GetExec()}, addMemoryInfo(memory, inst.programCounter)); diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp index 1031f7aa8..66bc02885 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp @@ -14,6 +14,10 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { case RdnaOpcode::SGl1Inv: case RdnaOpcode::SDcacheInv: case RdnaOpcode::SDcacheWb: + case RdnaOpcode::SAtcProbe: + case RdnaOpcode::SAtcProbeBuffer: + case RdnaOpcode::SDcacheDiscard: + case RdnaOpcode::SDcacheDiscardX2: case RdnaOpcode::BufferGl0Inv: case RdnaOpcode::BufferGl1Inv: emitControlNop(); @@ -117,6 +121,11 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { return bufferAtomic(inst, IrOpcode::BufferAtomicIAdd32); case RdnaOpcode::BufferAtomicSub: return bufferAtomic(inst, IrOpcode::BufferAtomicISub32); + case RdnaOpcode::BufferAtomicCsub: + if (!inst.glc) { + throw std::runtime_error("buffer_atomic_csub without glc is not supported, at pc " + std::to_string(inst.programCounter)); + } + return bufferAtomic(inst, IrOpcode::BufferAtomicUSubSat32); case RdnaOpcode::BufferAtomicSmin: return bufferAtomic(inst, IrOpcode::BufferAtomicSMin32); case RdnaOpcode::BufferAtomicUmin: @@ -188,7 +197,9 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { case RdnaOpcode::FlatLoadDwordx4: return flatLoad(inst); case RdnaOpcode::GlobalLoadDwordAddtid: - return globalLoadAddtid(inst); + return globalAddtid(inst, false); + case RdnaOpcode::GlobalStoreDwordAddtid: + return globalAddtid(inst, true); case RdnaOpcode::FlatStoreByte: case RdnaOpcode::FlatStoreShort: case RdnaOpcode::FlatStoreByteD16Hi: @@ -198,6 +209,75 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { case RdnaOpcode::FlatStoreDwordx3: case RdnaOpcode::FlatStoreDwordx4: return flatStore(inst); + case RdnaOpcode::FlatAtomicSwap: + return flatAtomic(inst, IrOpcode::AddressAtomicSwap32); + case RdnaOpcode::FlatAtomicCmpswap: + return flatAtomic(inst, IrOpcode::AddressAtomicCmpSwap32); + case RdnaOpcode::FlatAtomicAdd: + return flatAtomic(inst, IrOpcode::AddressAtomicIAdd32); + case RdnaOpcode::FlatAtomicSub: + return flatAtomic(inst, IrOpcode::AddressAtomicISub32); + case RdnaOpcode::GlobalAtomicCsub: + if (!inst.glc) { + throw std::runtime_error("global_atomic_csub without glc is not supported, at pc " + std::to_string(inst.programCounter)); + } + return flatAtomic(inst, IrOpcode::AddressAtomicUSubSat32); + case RdnaOpcode::FlatAtomicSmin: + return flatAtomic(inst, IrOpcode::AddressAtomicSMin32); + case RdnaOpcode::FlatAtomicUmin: + return flatAtomic(inst, IrOpcode::AddressAtomicUMin32); + case RdnaOpcode::FlatAtomicSmax: + return flatAtomic(inst, IrOpcode::AddressAtomicSMax32); + case RdnaOpcode::FlatAtomicUmax: + return flatAtomic(inst, IrOpcode::AddressAtomicUMax32); + case RdnaOpcode::FlatAtomicAnd: + return flatAtomic(inst, IrOpcode::AddressAtomicAnd32); + case RdnaOpcode::FlatAtomicOr: + return flatAtomic(inst, IrOpcode::AddressAtomicOr32); + case RdnaOpcode::FlatAtomicXor: + return flatAtomic(inst, IrOpcode::AddressAtomicXor32); + case RdnaOpcode::FlatAtomicInc: + return flatAtomic(inst, IrOpcode::AddressAtomicInc32); + case RdnaOpcode::FlatAtomicDec: + return flatAtomic(inst, IrOpcode::AddressAtomicDec32); + case RdnaOpcode::FlatAtomicSwapX2: + return flatAtomic(inst, IrOpcode::AddressAtomicSwap64); + case RdnaOpcode::FlatAtomicCmpswapX2: + return flatAtomic(inst, IrOpcode::AddressAtomicCmpSwap64); + case RdnaOpcode::FlatAtomicAddX2: + return flatAtomic(inst, IrOpcode::AddressAtomicIAdd64); + case RdnaOpcode::FlatAtomicSubX2: + return flatAtomic(inst, IrOpcode::AddressAtomicISub64); + case RdnaOpcode::FlatAtomicSminX2: + return flatAtomic(inst, IrOpcode::AddressAtomicSMin64); + case RdnaOpcode::FlatAtomicUminX2: + return flatAtomic(inst, IrOpcode::AddressAtomicUMin64); + case RdnaOpcode::FlatAtomicSmaxX2: + return flatAtomic(inst, IrOpcode::AddressAtomicSMax64); + case RdnaOpcode::FlatAtomicUmaxX2: + return flatAtomic(inst, IrOpcode::AddressAtomicUMax64); + case RdnaOpcode::FlatAtomicAndX2: + return flatAtomic(inst, IrOpcode::AddressAtomicAnd64); + case RdnaOpcode::FlatAtomicOrX2: + return flatAtomic(inst, IrOpcode::AddressAtomicOr64); + case RdnaOpcode::FlatAtomicXorX2: + return flatAtomic(inst, IrOpcode::AddressAtomicXor64); + case RdnaOpcode::FlatAtomicFcmpswap: + return flatAtomic(inst, IrOpcode::AddressAtomicFCmpSwap32); + case RdnaOpcode::FlatAtomicFmin: + return flatAtomic(inst, IrOpcode::AddressAtomicFMin32); + case RdnaOpcode::FlatAtomicFmax: + return flatAtomic(inst, IrOpcode::AddressAtomicFMax32); + case RdnaOpcode::FlatAtomicFcmpswapX2: + return flatAtomic(inst, IrOpcode::AddressAtomicFCmpSwap64); + case RdnaOpcode::FlatAtomicFminX2: + return flatAtomic(inst, IrOpcode::AddressAtomicFMin64); + case RdnaOpcode::FlatAtomicFmaxX2: + return flatAtomic(inst, IrOpcode::AddressAtomicFMax64); + case RdnaOpcode::FlatAtomicIncX2: + return flatAtomic(inst, IrOpcode::AddressAtomicInc64); + case RdnaOpcode::FlatAtomicDecX2: + return flatAtomic(inst, IrOpcode::AddressAtomicDec64); case RdnaOpcode::DsAddU32: return dsAtomic(inst, IrOpcode::SharedAtomicIAdd32, false); @@ -276,6 +356,76 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { return dsAtomic2(inst, IrOpcode::SharedAtomicCmpstF32, true); case RdnaOpcode::DsWrapRtnB32: return dsAtomic2(inst, IrOpcode::SharedAtomicWrap32, true); + case RdnaOpcode::DsAddU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicIAdd64, false); + case RdnaOpcode::DsAddRtnU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicIAdd64, true); + case RdnaOpcode::DsSubU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicISub64, false); + case RdnaOpcode::DsSubRtnU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicISub64, true); + case RdnaOpcode::DsRsubU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicRsub64, false); + case RdnaOpcode::DsRsubRtnU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicRsub64, true); + case RdnaOpcode::DsIncU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicInc64, false); + case RdnaOpcode::DsIncRtnU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicInc64, true); + case RdnaOpcode::DsDecU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicDec64, false); + case RdnaOpcode::DsDecRtnU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicDec64, true); + case RdnaOpcode::DsMinI64: + return dsAtomic64(inst, IrOpcode::SharedAtomicSMin64, false); + case RdnaOpcode::DsMinRtnI64: + return dsAtomic64(inst, IrOpcode::SharedAtomicSMin64, true); + case RdnaOpcode::DsMaxI64: + return dsAtomic64(inst, IrOpcode::SharedAtomicSMax64, false); + case RdnaOpcode::DsMaxRtnI64: + return dsAtomic64(inst, IrOpcode::SharedAtomicSMax64, true); + case RdnaOpcode::DsMinU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicUMin64, false); + case RdnaOpcode::DsMinRtnU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicUMin64, true); + case RdnaOpcode::DsMaxU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicUMax64, false); + case RdnaOpcode::DsMaxRtnU64: + return dsAtomic64(inst, IrOpcode::SharedAtomicUMax64, true); + case RdnaOpcode::DsAndB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicAnd64, false); + case RdnaOpcode::DsAndRtnB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicAnd64, true); + case RdnaOpcode::DsOrB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicOr64, false); + case RdnaOpcode::DsOrRtnB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicOr64, true); + case RdnaOpcode::DsXorB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicXor64, false); + case RdnaOpcode::DsXorRtnB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicXor64, true); + case RdnaOpcode::DsMskorB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicMskor64, false); + case RdnaOpcode::DsMskorRtnB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicMskor64, true); + case RdnaOpcode::DsCmpstB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicCmpst64, false); + case RdnaOpcode::DsCmpstRtnB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicCmpst64, true); + case RdnaOpcode::DsCmpstF64: + return dsAtomic64(inst, IrOpcode::SharedAtomicCmpstF64, false); + case RdnaOpcode::DsCmpstRtnF64: + return dsAtomic64(inst, IrOpcode::SharedAtomicCmpstF64, true); + case RdnaOpcode::DsMinF64: + return dsAtomic64(inst, IrOpcode::SharedAtomicFMin64, false); + case RdnaOpcode::DsMinRtnF64: + return dsAtomic64(inst, IrOpcode::SharedAtomicFMin64, true); + case RdnaOpcode::DsMaxF64: + return dsAtomic64(inst, IrOpcode::SharedAtomicFMax64, false); + case RdnaOpcode::DsMaxRtnF64: + return dsAtomic64(inst, IrOpcode::SharedAtomicFMax64, true); + case RdnaOpcode::DsWrxchgRtnB64: + return dsAtomic64(inst, IrOpcode::SharedAtomicSwap64, true); case RdnaOpcode::DsNop: emitControlNop(); return true; @@ -356,6 +506,30 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { case RdnaOpcode::ImageSampleDO: case RdnaOpcode::ImageSampleLzO: case RdnaOpcode::ImageSampleO: + case RdnaOpcode::ImageSampleCd: + case RdnaOpcode::ImageSampleCdCl: + case RdnaOpcode::ImageSampleCCd: + case RdnaOpcode::ImageSampleCCdCl: + case RdnaOpcode::ImageSampleCdO: + case RdnaOpcode::ImageSampleCdClO: + case RdnaOpcode::ImageSampleCCdO: + case RdnaOpcode::ImageSampleCCdClO: + case RdnaOpcode::ImageSampleDG16: + case RdnaOpcode::ImageSampleDClG16: + case RdnaOpcode::ImageSampleCDG16: + case RdnaOpcode::ImageSampleCDClG16: + case RdnaOpcode::ImageSampleDOG16: + case RdnaOpcode::ImageSampleDClOG16: + case RdnaOpcode::ImageSampleCDOG16: + case RdnaOpcode::ImageSampleCDClOG16: + case RdnaOpcode::ImageSampleCdG16: + case RdnaOpcode::ImageSampleCdClG16: + case RdnaOpcode::ImageSampleCCdG16: + case RdnaOpcode::ImageSampleCCdClG16: + case RdnaOpcode::ImageSampleCdOG16: + case RdnaOpcode::ImageSampleCdClOG16: + case RdnaOpcode::ImageSampleCCdOG16: + case RdnaOpcode::ImageSampleCCdClOG16: return imageSample(inst); case RdnaOpcode::ImageGather4Lz: case RdnaOpcode::ImageGather4C: @@ -432,6 +606,7 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { case RdnaOpcode::ImageGetLod: return imageGetLod(inst); case RdnaOpcode::ImageBvhIntersectRay: + case RdnaOpcode::ImageBvh64IntersectRay: return imageBvhIntersectRay(inst); default: diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp index 88e1afe14..369b78f6c 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryResourceHelpers.cpp @@ -3,6 +3,7 @@ #include "RdnaDecoder/RdnaImageOpDecoder.hpp" #include #include +#include #include namespace ShaderRecompiler { @@ -109,6 +110,24 @@ IrValue* TranslationContext::makeImageAddress(const RdnaInstruction& inst, const components[index] = &readRawU32(offsetOperand(plainBase, index)).Value(); } } + if ((inst.imageSampleFlags & RdnaImageSampleFlagCd) != 0u) { + const auto mask = ballotMask(IrU1(ir.GetExec())); + IrValue& lane = ir.Emit(IrOpcode::LaneId, IrType::U32, {}); + IrValue& quad = ir.BitwiseAnd(lane, ir.Constant(~3u)); + IrValue& word = program.WaveSize() == 64u ? ir.Select(ir.ULessThan(lane, ir.Constant(32u)), mask[0].Value(), mask[1].Value()) : mask[0].Value(); + IrValue& active = ir.BitwiseAnd(ir.ShiftRightLogical(word, ir.BitwiseAnd(quad, ir.Constant(31u))), ir.Constant(15u)); + IrValue& first = ir.Emit(IrOpcode::FindILsb32, IrType::U32, {&active}); + IrValue& source = ir.Select(ir.INotEqual(active, ir.Constant(0u)), ir.IAdd(quad, first), lane); + IrValue& address = ir.ShiftLeftLogical(source, ir.Constant(2u)); + IrValue& exec = ir.GetExec(); + const std::uint32_t gradientStart = std::popcount(inst.imageSampleFlags & (RdnaImageSampleFlagOffset | RdnaImageSampleFlagBias | RdnaImageSampleFlagCompare)); + const std::uint32_t gradients = 2u * ((inst.imageSampleFlags & RdnaImageSampleGradientCountMask) >> RdnaImageSampleGradientCountShift); + const std::uint32_t firstDword = GetRdnaImageAddressComponentLayout(inst.imageSampleFlags, gradientStart).bitOffset / 32u; + const std::uint32_t lastDword = GetRdnaImageAddressComponentLayout(inst.imageSampleFlags, gradientStart + gradients - 1u).bitOffset / 32u; + for (std::uint32_t index = firstDword; index <= lastDword; ++index) { + components[index] = &ir.Emit(IrOpcode::BpermuteU32, IrOpcodeType(IrOpcode::BpermuteU32), {components[index], &address, &exec}); + } + } return &ir.Emit(IrOpcode::MakeImageAddress, IrOpcodeType(IrOpcode::MakeImageAddress), {components[0], components[1], components[2], components[3], components[4], components[5], components[6], components[7], components[8], components[9], components[10], components[11], components[12]}); diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/SharedMemoryInstructions.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/SharedMemoryInstructions.cpp index 019d6596a..11dc351fa 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/SharedMemoryInstructions.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/SharedMemoryInstructions.cpp @@ -182,6 +182,25 @@ bool TranslationContext::dsAtomic2(const RdnaInstruction& inst, IrOpcode opcode, return true; } +bool TranslationContext::dsAtomic64(const RdnaInstruction& inst, IrOpcode opcode, bool returnsValue) { + if (inst.gds) { + throw std::runtime_error("64-bit GDS atomics are not supported"); + } + const MemoryInfo memory = sharedMemoryInfoFromInstruction(inst); + const IrU32 address = readU32(inst.source0); + const IrU64 data0 = readU64(inst.source1); + IrValue& active = ir.GetExec(); + IrValue* old; + if (IrOpcodeOperandCount(opcode) == 4u) { + const IrU64 data1 = readU64(inst.source2); + old = &ir.Emit(opcode, IrOpcodeType(opcode), {&address.Value(), &data0.Value(), &data1.Value(), &active}, addMemoryInfo(memory, inst.programCounter)); + } else { + old = &ir.Emit(opcode, IrOpcodeType(opcode), {&address.Value(), &data0.Value(), &active}, addMemoryInfo(memory, inst.programCounter)); + } + if (returnsValue) writeOperand(inst.destination, old); + return true; +} + bool TranslationContext::dsAppendConsume(const RdnaInstruction& inst, IrOpcode opcode) { const MemoryInfo memory = sharedMemoryInfoFromInstruction(inst); const IrU32 address = readU32(makeM0Operand()); diff --git a/core/shader/recompiler/Translation/src/ScalarInstructions.cpp b/core/shader/recompiler/Translation/src/ScalarInstructions.cpp index 272b59d21..1a3051635 100644 --- a/core/shader/recompiler/Translation/src/ScalarInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ScalarInstructions.cpp @@ -66,6 +66,16 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) { emitControlNop(); return true; } + case RdnaOpcode::SGetregB32: { + const std::uint32_t field = inst.source0.value; + const std::uint32_t offset = (field >> 6u) & 0x1fu; + const std::uint32_t size = ((field >> 11u) & 0x1fu) + 1u; + if ((field & 0x3fu) != 1u || offset + size > 4u) { + throw std::runtime_error("s_getreg_b32 at pc " + std::to_string(inst.programCounter) + " reads hardware register " + std::to_string(field & 0x3fu) + " bits " + std::to_string(offset) + ".." + std::to_string(offset + size - 1u) + ": only the MODE round mode fields are modeled"); + } + writeRawU32(inst.destination, IrU32(ir.Constant(0u))); + return true; + } case RdnaOpcode::SCmovkI32: { const IrU32 previous = readU32(inst.destination); writeRawU32(inst.destination, IrU32(ir.Select(ir.GetScc(), ir.Constant(inst.source0.value), previous.Value()))); @@ -105,10 +115,10 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) { sSaveexec(inst, IrOpcode::LogicalXor, false, false, false, true); return true; case RdnaOpcode::SAndn1WrexecB32: - sSaveexec(inst, IrOpcode::LogicalAnd, false, true, false, false, false); + sSaveexec(inst, IrOpcode::LogicalAnd, false, true, false, false, true); return true; case RdnaOpcode::SAndn2WrexecB32: - sSaveexec(inst, IrOpcode::LogicalAnd, true, false, false, false, false); + sSaveexec(inst, IrOpcode::LogicalAnd, true, false, false, false, true); return true; case RdnaOpcode::SAndSaveexecB64: sSaveexec(inst, IrOpcode::LogicalAnd, false, false, true); @@ -141,10 +151,10 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) { sSaveexec(inst, IrOpcode::LogicalOr, false, true, true); return true; case RdnaOpcode::SAndn1WrexecB64: - sSaveexec(inst, IrOpcode::LogicalAnd, false, true, true, false, false); + sSaveexec(inst, IrOpcode::LogicalAnd, false, true, true, false, true); return true; case RdnaOpcode::SAndn2WrexecB64: - sSaveexec(inst, IrOpcode::LogicalAnd, true, false, true, false, false); + sSaveexec(inst, IrOpcode::LogicalAnd, true, false, true, false, true); return true; case RdnaOpcode::SAddU32: addU32(inst, false, false); @@ -397,6 +407,12 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) { case RdnaOpcode::SDecperflevel: emitControlNop(); return true; + case RdnaOpcode::SRoundMode: + if (inst.source0.value != 0u) { + throw std::runtime_error("s_round_mode " + std::to_string(inst.source0.value) + " at pc " + std::to_string(inst.programCounter) + " selects a rounding mode other than round to nearest even"); + } + emitControlNop(); + return true; case RdnaOpcode::SDenormMode: throw std::runtime_error("s_denorm_mode " + std::to_string(inst.source0.value) + " at pc " + std::to_string(inst.programCounter) + ": the recompiler does not model denormal modes"); case RdnaOpcode::SWaitcntDepctr: diff --git a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp index 235393ebe..d0b6568c0 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp @@ -5,6 +5,15 @@ namespace ShaderRecompiler { +namespace { + +DppMoveFlags dppFlags(const RdnaOperand& operand) { + const auto control = operand.dpp8 ? DppMoveFlags::Lanes8 | operand.dppCtrl : operand.dppCtrl; + return {control, static_cast(operand.dppRowMask), static_cast(operand.dppBankMask), operand.dppFetchInactive, operand.dppBoundCtrl}; +} + +} + const RdnaOperand& TranslationContext::sourceAt(const RdnaInstruction& inst, std::uint32_t index) { switch (index) { case 0u: return inst.source0; @@ -26,6 +35,7 @@ RdnaOperand TranslationContext::destinationOperand(const RdnaInstruction& inst) continue; } destination.dpp = true; + destination.dpp8 = source.dpp8; destination.dppCtrl = source.dppCtrl; destination.dppRowMask = source.dppRowMask; destination.dppBankMask = source.dppBankMask; @@ -111,6 +121,7 @@ RdnaOperand TranslationContext::plainOperand(const RdnaOperand& operand) { result.dppFetchInactive = false; result.dppBoundCtrl = false; result.dpp = false; + result.dpp8 = false; return result; } @@ -223,7 +234,7 @@ void TranslationContext::writeOperand(const RdnaOperand& operand, IrValue* value IrU32 TranslationContext::applyBitSourceModifiers(const RdnaOperand& operand, IrU32 value) { if (operand.dpp) { - const DppMoveFlags flags{static_cast(operand.dppCtrl), static_cast(operand.dppRowMask), static_cast(operand.dppBankMask), operand.dppFetchInactive, operand.dppBoundCtrl}; + const auto flags = dppFlags(operand); value = IrU32(ir.Emit(IrOpcode::DppMoveU32, IrType::U32, {&value.Value(), &ir.GetExec()}, flags)); } if (operand.sdwaSel != 6u) { @@ -252,7 +263,16 @@ IrF32 TranslationContext::applyF32ResultModifiers(const RdnaOperand& operand, Ir case 2u: multiplier = 4.0f; break; default: break; } - value = IrF32(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&value.Value(), &ir.ConstantF32(multiplier)})); + const IrF32 scaled(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&value.Value(), &ir.ConstantF32(multiplier)})); + IrValue& bits = ir.BitCastU32(value.Value()); + IrValue& magnitude = ir.BitwiseAnd(bits, ir.Constant(0x7fffffffu)); + IrValue* result = &ir.BitCastU32(scaled.Value()); + if (operand.omod == 3u) { + result = &ir.Select(ir.ULessThan(magnitude, ir.Constant(0x01000000u)), ir.BitwiseAnd(bits, ir.Constant(0x80000000u)), *result); + } + result = &ir.Select(ir.ULessThan(magnitude, ir.Constant(0x00800000u)), ir.Constant(0u), *result); + result = &ir.Select(ir.UGreaterThan(magnitude, ir.Constant(0x7f800000u)), bits, *result); + value = IrF32(ir.BitCastF32(*result)); } if (operand.clamp) { value = IrF32(ir.Emit(IrOpcode::FPSaturate32, IrType::F32, {&value.Value()})); @@ -260,6 +280,16 @@ IrF32 TranslationContext::applyF32ResultModifiers(const RdnaOperand& operand, Ir return value; } +IrF32 TranslationContext::applyF16ResultModifiers(const RdnaOperand& operand, IrF32 value) { + if (!operand.clamp) { + return value; + } + const IrF32 zero(ir.ConstantF32(0.0f)); + const IrU1 positive(ir.Emit(IrOpcode::FPOrdGreaterThan32, IrType::U1, {&value.Value(), &zero.Value()})); + const IrF32 limited(ir.Emit(IrOpcode::FPMin32, IrType::F32, {&value.Value(), &ir.ConstantF32(1.0f)})); + return selectF32(positive, limited, zero); +} + IrU32 TranslationContext::readScalarCode(std::uint32_t code) { if (code < NumScalarRegs) { return IrU32(ir.GetScalarReg(static_cast(code))); @@ -355,7 +385,7 @@ void TranslationContext::writeRawU32(const RdnaOperand& operand, IrU32 value) { const VectorReg reg = static_cast(operand.reg); IrValue& old = ir.GetVectorReg(reg); if (operand.dpp) { - const DppMoveFlags flags{static_cast(operand.dppCtrl), static_cast(operand.dppRowMask), static_cast(operand.dppBankMask), operand.dppFetchInactive, operand.dppBoundCtrl}; + const auto flags = dppFlags(operand); value = IrU32(ir.Emit(IrOpcode::DppUpdateU32, IrType::U32, {&value.Value(), &old, &ir.GetExec()}, flags)); } else { value = IrU32(ir.Select(ir.GetExec(), value.Value(), old)); diff --git a/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp b/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp index 903d821ee..fc3a74a66 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp @@ -157,7 +157,7 @@ void TranslationContext::write16Bits(const RdnaOperand& operand, IrU32 value) { } void TranslationContext::writeF16(const RdnaOperand& operand, IrF32 value) { - const IrF16 half(ir.Emit(IrOpcode::ConvertF16F32, IrType::F16, {&value.Value()})); + const IrF16 half(ir.Emit(IrOpcode::ConvertF16F32, IrType::F16, {&applyF16ResultModifiers(operand, value).Value()})); const IrU16 bits(ir.Emit(IrOpcode::BitCastU16F16, IrType::U16, {&half.Value()})); write16Bits(operand, IrU32(ir.Emit(IrOpcode::ConvertU32U16, IrType::U32, {&bits.Value()}))); } diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index 9e2d42450..cf769044f 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -542,6 +542,8 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { return vMulLegacyF32(inst, false); case RdnaOpcode::VMacLegacyF32: return vMulLegacyF32(inst, true); + case RdnaOpcode::VMullitF32: + return vMullitF32(inst); case RdnaOpcode::VCmpClassF32: emitFloatClassCompare(inst, false); return true; @@ -708,10 +710,10 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { vCvtPkrtzF16F32(inst); return true; case RdnaOpcode::VCvtPknormI16F32: - vCvtPknormF32(inst, IrOpcode::PackSnorm2x16); + vCvtPknormF32(inst, true); return true; case RdnaOpcode::VCvtPknormU16F32: - vCvtPknormF32(inst, IrOpcode::PackUnorm2x16); + vCvtPknormF32(inst, false); return true; case RdnaOpcode::VCvtPkU8F32: vCvtPkU8F32(inst); @@ -816,29 +818,29 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { case RdnaOpcode::VMadMixhiF16: return float16Ternary(inst, IrOpcode::FPFma32, false, true); case RdnaOpcode::VRcpF16: - return float16Unary(inst, IrOpcode::FPRecip32, false); + return float16Unary(inst, IrOpcode::FPRecip32); case RdnaOpcode::VSqrtF16: - return float16Unary(inst, IrOpcode::FPSqrt, true); + return float16Unary(inst, IrOpcode::FPSqrt); case RdnaOpcode::VRsqF16: - return float16Unary(inst, IrOpcode::FPRecipSqrt32, true); + return float16Unary(inst, IrOpcode::FPRecipSqrt32); case RdnaOpcode::VLogF16: - return float16Unary(inst, IrOpcode::FPLog2, true); + return float16Unary(inst, IrOpcode::FPLog2); case RdnaOpcode::VExpF16: - return float16Unary(inst, IrOpcode::FPExp2, false); + return float16Unary(inst, IrOpcode::FPExp2); case RdnaOpcode::VFloorF16: - return float16Unary(inst, IrOpcode::FPFloor32, false); + return float16Unary(inst, IrOpcode::FPFloor32); case RdnaOpcode::VCeilF16: - return float16Unary(inst, IrOpcode::FPCeil32, false); + return float16Unary(inst, IrOpcode::FPCeil32); case RdnaOpcode::VTruncF16: - return float16Unary(inst, IrOpcode::FPTrunc32, false); + return float16Unary(inst, IrOpcode::FPTrunc32); case RdnaOpcode::VRndneF16: - return float16Unary(inst, IrOpcode::FPRoundEven32, false); + return float16Unary(inst, IrOpcode::FPRoundEven32); case RdnaOpcode::VFractF16: - return float16Unary(inst, IrOpcode::FPFract32, false); + return float16Unary(inst, IrOpcode::FPFract32); case RdnaOpcode::VSinF16: - return float16Trig(inst, IrOpcode::FPSin); + return float16Unary(inst, IrOpcode::FPSin); case RdnaOpcode::VCosF16: - return float16Trig(inst, IrOpcode::FPCos); + return float16Unary(inst, IrOpcode::FPCos); case RdnaOpcode::VMin3F16: return float16Ternary(inst, IrOpcode::FPMinTri32, false, false); case RdnaOpcode::VMax3F16: @@ -871,6 +873,14 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { return float64Operation(inst, IrOpcode::FPFloor64); case RdnaOpcode::VFractF64: return float64Operation(inst, IrOpcode::FPFract64); + case RdnaOpcode::VRcpF64: + return float64Operation(inst, IrOpcode::FPRcp64); + case RdnaOpcode::VRsqF64: + return float64Operation(inst, IrOpcode::FPRsq64); + case RdnaOpcode::VSqrtF64: + return float64Operation(inst, IrOpcode::FPSqrt64); + case RdnaOpcode::VTrigPreopF64: + return float64Operation(inst, IrOpcode::FPTrigPreop64); case RdnaOpcode::VFrexpMantF64: return float64Operation(inst, IrOpcode::FPFrexpMant64); case RdnaOpcode::VFrexpExpI32F64: @@ -920,7 +930,6 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { case RdnaOpcode::VSubrevF32: return floatBinary(inst, IrOpcode::FPSub32, true); case RdnaOpcode::VMulF32: - case RdnaOpcode::VMullitF32: return floatBinary(inst, IrOpcode::FPMul32, false); case RdnaOpcode::VMinF32: return floatBinary(inst, IrOpcode::FPMin32, false); @@ -932,16 +941,23 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { return vDivFmasF32(inst); case RdnaOpcode::VDivFixupF32: return vDivFixupF32(inst); + case RdnaOpcode::VDivScaleF64: + return vDivScaleF64(inst); + case RdnaOpcode::VDivFmasF64: + return vDivFmasF64(inst); + case RdnaOpcode::VDivFixupF64: + return vDivFixupF64(inst); case RdnaOpcode::VLdexpF32: - return floatBinary(inst, IrOpcode::FPLdexp, false); + return vLdexpF32(inst); case RdnaOpcode::VMacF32: return floatTernary(inst, roundsProductSeparately(inst) ? IrOpcode::FPMad32 : IrOpcode::FPFma32, true, true); case RdnaOpcode::VMadmkF32: case RdnaOpcode::VMadakF32: case RdnaOpcode::VMadF32: - case RdnaOpcode::VMadLegacyF32: case RdnaOpcode::VFmaF32: return floatTernary(inst, roundsProductSeparately(inst) ? IrOpcode::FPMad32 : IrOpcode::FPFma32, false, true); + case RdnaOpcode::VMadLegacyF32: + return vFmaLegacyF32(inst); case RdnaOpcode::VMin3F32: return floatTernary(inst, IrOpcode::FPMinTri32, false, false); case RdnaOpcode::VMax3F32: @@ -950,6 +966,8 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { return floatTernary(inst, IrOpcode::FPMedTri32, false, false); case RdnaOpcode::VDot2cF32F16: return vDot2cF32F16(inst); + case RdnaOpcode::VDot2F32F16: + return vDot2F32F16(inst); case RdnaOpcode::VDot4cI32I8: return integerDot(inst, 8u, true, true); case RdnaOpcode::VDot2I32I16: @@ -980,11 +998,11 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { case RdnaOpcode::VMulHiI32: return simpleInteger(inst, IrOpcode::SMulHi, IrType::U32, false, false, false); case RdnaOpcode::VAddNcU32: - return simpleInteger(inst, IrOpcode::IAdd32, IrType::U32, false, false, false); + return vAddSubNcU32(inst, false, false); case RdnaOpcode::VSubNcU32: - return simpleInteger(inst, IrOpcode::ISub32, IrType::U32, false, false, false); + return vAddSubNcU32(inst, true, false); case RdnaOpcode::VSubrevNcU32: - return simpleInteger(inst, IrOpcode::ISub32, IrType::U32, true, false, false); + return vAddSubNcU32(inst, true, true); case RdnaOpcode::VMinI32: return simpleInteger(inst, IrOpcode::SMin32, IrType::U32, false, false, false); case RdnaOpcode::VMaxI32: diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md index cc8f15a80..847b63cf7 100644 --- a/docs/dev/BUILD.md +++ b/docs/dev/BUILD.md @@ -46,6 +46,10 @@ Build configuration parameters: SDL and FreeType settings forced by the root `CMakeLists.txt` cannot be overridden with `-D`. +## Pipeline statistics + +Set `APS5_PIPELINE_STATS=1` to capture and print driver statistics for each newly created graphics or compute pipeline. This requires `VK_KHR_pipeline_executable_properties` and `pipelineExecutableInfo`; an unsupported device fails with an error. Statistic names and units are driver-specific. Capturing statistics can increase pipeline compilation cost. The setting is disabled by default. + ## Shader recompiler The shader recompilation logic in [core/shader/recompiler](../../core/shader/recompiler) is isolated from the rest of the project and is a pure function of its input data, designed for integration into any other project. The current CMake target also includes cache support and links a supplied runtime target, glslang, and optionally SPIRV-Tools. diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a44e110ec..8122ef8fa 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -12,66 +12,118 @@ Throughout the project, every function at every stage either **does exactly what it's supposed to or throws an exception**. Everywhere... except: - [libSceSaveDataDialog.native](../../core/libs/prx/libSceSaveDataDialog.native/Export.cpp) +- [libSceMsgDialog.native](../../core/libs/prx/libSceMsgDialog.native/Export.cpp) - dialogs finish at `sceMsgDialogOpen` without UI and answer with button 1, so `sceMsgDialogClose` returns `NOT_RUNNING`; its result before `sceMsgDialogInitialize` follows libSceMsgDialog - [libSceCommonDialog](../../core/libs/prx/libSceCommonDialog/Export.cpp) - [libSceErrorDialog](../../core/libs/prx/libSceErrorDialog/Export.cpp) - the error dialog runs the state machine but shows nothing; the error code passed to `sceErrorDialogOpen` is only logged - [libSceHmd](../../core/libs/prx/libSceHmd/Export.cpp) implements only the disconnected-headset path: initialization succeeds, device queries report `NotDetected`, and opening a device returns `DeviceDisconnected`. Headset support, tracking, rendering and additional HMD exports are not implemented; SDK-level ABI compatibility and in-game behaviour remain unverified. - [libSceAudioPropagation](../../core/libs/prx/libSceAudioPropagation/Export.cpp) implements only the no-acoustic-propagation path: the system keeps a 16-byte header in the title's CPU memory (no GPU memory), rooms, portals, sources and materials are tracked handles, attributes are validated and have no effect, no rays are requested and sources report no audio paths. Ray results, path calculation and audio paths are accepted only when empty and throw otherwise; `sceAudioPropagationSourceRender` writes silence (zeroes) to each source's output buffer for the given size, without a dry signal. Objects still alive when their system is destroyed become unusable, and their later destroy or unregister is a no-op: PPSA21567 releases its reference-counted room, portal and source owners just before `sceAudioPropagationSystemDestroy`, so the order for every object is not proven. Occlusion, reflections and reverb from level geometry are absent; in-game behaviour remains unverified. -- [libSceNpCommerce](../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls do nothing +- [libSceNpCommerce](../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls and `sceNpCommerceSetPsStoreIconLayout` do nothing - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) validates the priority and CPU affinity that the open param requests for the VideoOut service thread but does not apply them: the port's present and vblank threads are host threads, and guest priorities and affinities do not reach host scheduling - The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212). - [libSceAvPlayer](../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement. +- [libSceNpTrophy2](../../core/libs/prx/libSceNpTrophy2/src/GameInfo.cpp): `sceNpTrophy2RegisterUnlockCallback` accepts a callback that is never called, as no trophy is ever unlocked, and `sceNpTrophy2UnregisterUnlockCallback` only returns success. - [sceAvPlayerSetTrickSpeed](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) with a negative speed runs the clock backwards but delivers no frames; when a forward speed is set again, playback resumes from the rewound time. +- [ulobjmgr](../../core/libs/prx/ulobjmgr/Export.cpp) registers no object: `_sceUlobjmgrRegisterObject` always hands out id 0 and `_sceUlobjmgrUnregisterObject` releases nothing, as shadPS4 does ### Unknown function info +- [sceAvPlayerAddSource](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) - requests up to 4 additional decode-ahead video framebuffers from the memory replacement beyond `num_output_video_framebuffers` when memory is available; the console's internal buffering behaviour is unknown +- [sceVideoOutRegisterBuffers2](../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) (libSceVideoOut) - buffer option 8 (STRICT_COLORIMETRY) is presented as option 0, with the stored values unchanged; what the console changes for it is unknown. Only options 0 and 8 are accepted - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the open param's first word is unknown (PPSA21564 passes 16; it is not the byte size, since the affinity mask is at offset 16); only 16 is accepted. Whether the param continues past offset 24 is also unknown - [sceRtcParseDateTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - accepted format assumed to be the ISO 8601 subset `YYYY-MM-DDTHH:MM:SS[.ffffff]` (T/t/space separator, optional fractional seconds) with the sibling RFC 3339 zone suffix (`Z` or `±HH:MM`); a string without a zone is taken as UTC, and anything outside this shape throws instead of returning a parse error, unverified against hardware - [AMPR WriteAddressFromCounterPairOnCompletion](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - the pair of an even counter n is assumed to give counter n in the low 32 bits and n + 1 in the high 32 bits. Wait compare values, wait flush and counter index ranges follow the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation; the flush is accepted and has no effect, as commands run in order - [AMPR command sizes](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - commands use the emulator's own encoding with an 8-byte header, so `Nop(n)` takes 8 + 4n bytes where the console record is n dwords including its header (`NopWithData(n)`: (n + 1) dwords). The measure functions agree with the appended sizes, but a title that pads to an exact offset or the end of the buffer with `Nop(remaining / 4)` gets `SCE_AMPR_ERROR_BUFFER_FULL` or a shifted offset. The at-start flag of the `_04_00` writes is ignored; commands run in order, each after the previous one completes - [AMPR markers](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - signatures taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, not confirmed on a title: `sceAmprCommandBufferSetMarkerWithColor` takes the color by pointer, the push and measure variants by value. Colors are not recorded +- [sceFontGetRenderScaledKerning](../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - arguments, the cleared output and the bound renderer check taken from `cellFontGetRenderScaledKerning` in [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellFont.cpp) (PS3 predecessor of the library), not confirmed on a title. The kerning is the `sceFontGetKerning` one at the render scale +- [sceFontGetPixelResolution](../../core/libs/prx/libSceFont/src/Library.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), not confirmed on a title. The value is the one the library's driver reports (64 for FreeType); the argument checks and the cleared output follow `sceFontGetLibrary` +- [sceSaveDataTransferringMountPs4, sceSaveDataDirNameSearchPs4](../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - arguments assumed to be those of `sceSaveDataTransferringMount` and `sceSaveDataDirNameSearch`: KytyPS5 binds both NIDs to the PS5 functions, and [prosper](https://github.com/mattias800/prosper) captured the same result layout on a title for the search. There is no PS4 save area, so the mount returns `NOT_FOUND` and the search reports zero hits - [sceAgcSetSubmitMode](../../core/libs/prx/libSceAgc/Misc/src/Suspend.cpp) (libSceAgc) - mode values unknown; only 0 is accepted +- [sceAgcGetIsTrinityMode](../../core/libs/prx/libSceAgc/Misc/src/Platform.cpp) (libSceAgc) - signature from the only PPSA26344 call (a pointer to a one-byte flag, result ignored); a null pointer throws because its error code is unknown - [zARR5aCmkoY](../../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature - [qj7QZpgr9Uw](../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name - [fd5Bp5tGTgo](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name - [dolOmWH+huQ](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name +- [dbOlWdppb4o](../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - unknown name; same arguments as `sceAgcCreateInterpolantMapping`; the `SPI_PS_INPUT_CNTL` bits for `is_f16` 2 inputs follow KytyPS5, not confirmed on hardware - [V++UgBtQhn0](../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - unknown name - [gQkqkLttcpw](../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown name, signature +- [sceAgcDcbPrimeUtcl2](../../core/libs/prx/libSceAgc/DcbState/src/Display.cpp) (libSceAgc) - assumed to write the packet `sceAgcAcbPrimeUtcl2` writes: PRIME_UTCL2 has the same 5 dwords on the graphics and compute rings. Not confirmed on a title. Both write a NOP of that size, as there is no TLB to prime - [sceKernelInternalMemoryGetModuleSegmentInfo](../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature - [sceKernelSyncOnAddressWait](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the only known caller passes a null timeout and a name string as the fourth argument; the timeout is assumed to point to microseconds like the other kernel waits, and the name is ignored +- [sceKernelSyncOnAddressWait32 / sceKernelSyncOnAddressWait64](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - libc's `__std_atomic_wait_direct_32/64` pass a microsecond timeout or null and ignore the result; the by-value comparand, return values and Wait64's 8-byte alignment are assumed - [sceKernelMapNamedFlexibleMemoryInternal](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - flag 0x8000 unknown; only the sceKernelMapNamedFlexibleMemory flags are accepted +- [Guest arena](../../core/libs/prx/libc/src/GuestArena.cpp) (libc) - the application map area is assumed to end at 0xFC_0000_0000, as `SCE_KERNEL_APP_MAP_AREA_END_ADDR` does on the PS4. On Windows, host allocations made in the arena before libc loads stay, and a fixed guest mapping over one throws - [sceLibcInternalBacktraceForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature - [sceLibcInternalHeapErrorReportForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature +- [_sceLibcInternalThreadAtexit, _sceLibcInternalThreadDtors](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - FreeBSD `__cxa_thread_atexit` semantics assumed; a null or non-image destructor and an allocation failure throw +- [_sceLibcInternalForceTlsDestructor](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - unknown return type and behaviour; a title's `libc.prx` calls it from `__cxa_finalize` with the module handle of a non-null dso - [__progname](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export - [pthread_barrierattr_setpshared](../../core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp) (libkernel) - PTHREAD_PROCESS_SHARED throws: FreeBSD 9.0 libthr rejects it with EINVAL and 11.0 accepts it, and which one the console follows is unknown - [sceSslClose](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature - [sceSslGetSerialNumber](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature - [X+4jdIS75P0](../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown name, signature +- [sceAudioOut2Set3DLatency](../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - assumed to take `(user_id, latency)`: KytyPS5 reads `(user_id, output, latency_us)`, but the only known call sets just edi and esi; only `(0xFF, 2)` is accepted +- [sceAudioOut2MasteringInit](../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) - flag values unknown; only 0 is accepted - [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - effect of a negative mix level unknown; only 0 to 32768 (0 dB) is accepted - [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - no source applies the level (shadPS4 only stores it); it is assumed to be a linear gain on the port's own output, and the -9 dB default (11626) is applied to every pad speaker port, including those of titles that never call the function +- [sceAudioOutGetLastOutputTime](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - signature and behaviour taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [fpPS4](https://github.com/red-prig/fpPS4) reimplementations of the PS4 library, not confirmed on a PS5 title: the time is the process time at which `sceAudioOutOutput` or `sceAudioOutOutputs` accepted the port's last block, not the time the block reached the device - [AOWqIYsgVHs](../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport) - unknown name, signature -- [GQTObcITIXI](../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - unknown name, signature +- [scePsmlMfsrGetContextBufferRequirement1100, scePsmlMfsrCreateContext1100, scePsmlMfsrGetDispatchMfsrPacket1100](../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml_debug) - argument layouts unknown; only 0x8A810001 (not initialized) is returned - [BnMAMrsfVWo](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libc) - unknown name, signature +- [scePngEncEncode](../../core/libs/prx/libScePngEnc/Export.cpp) (libScePngEnc) - whether rows may stay unfiltered when `filter_type` names a subset of the filters is unknown; as in shadPS4, they may only for 0 and for the all-filters mask - AudioIn, NpSessionSignaling and PlayerInvitationDialog exports added without an implementation have assumed signatures - [libSceAudioPropagation](../../core/libs/prx/libSceAudioPropagation/Export.cpp) (libSceAudioPropagation) - signatures and struct descriptors recovered from PPSA21567's calls (#481), not from documentation. The system options layout is unknown (only checked for non-null); the fourth argument of `sceAudioPropagationSourceCalculateAudioPaths` is unknown; `sceAudioPropagationSourceGetAudioPath` is assumed to output an 8-byte path handle. The meaning of the `RenderInfo` word at +0x28 is unknown (PPSA21567 and PPSA21564 pass 2); only 2 is accepted. Both titles pass one `RenderInfo` per call: a count of 0 throws, and larger counts are handled element by element without proof. No error code is known, so invalid input throws instead of returning one - [sceAvPlayerStartEx](../../core/libs/prx/libSceAvPlayer/Export.cpp) (libSceAvPlayer) - start info layout unknown; it is ignored and playback starts as with `sceAvPlayerStart` - [sceAvPlayerInit / sceAvPlayerInitEx](../../core/libs/prx/libSceAvPlayer/src/Player.cpp) (libSceAvPlayer) - behaviour without a memory replacement unknown; frame and sample buffers then come from the guest heap - [SceAvPlayerVideoEx](../../core/libs/SceTypes.hpp) (libSceAvPlayer) - frame rate field and encoding unknown; it is left zero in frame and stream info +- [sceRazorCpuPushMarkerStatic, sceRazorCpuPopMarker](../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - signatures from PPSA26344's calls; (name, color, flags) assumed for the push; both do nothing without a capture +- [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: the layer field is not checked and MPEG-2.5 stops at 64 kbps +- [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title; a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts - [AJM Opus decoder](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout (`u32 channels, u32 sample rate, u32`, seen as `2, 48000, 0`) and the little-endian `u16` byte count before each packet come from one title (The Smurfs Dreams); only 48000 Hz and a zero third word are accepted +- [sceHttpParseResponseHeader](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - unknown signature +- [sceHttpParseStatusLine](../../core/libs/prx/libSceHttp/src/Parse.cpp) (libSceHttp) - behaviour and error codes follow the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title; unlike it, no byte past `lineLen` is read +- [sceHttp2GetMemoryPoolStats](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - unknown signature +- [sceNetGetMemoryPoolStats](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - unknown signature +- [sceHttpSetRequestStatusCallback](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - no reference implements it (shadPS4 only logs and returns 0); when the callback runs and what status it gets is unknown +- [sce::Np::CppWebApi::Common::IntrusivePtr](../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x18-byte layout (object, deleter, `LibContext*`) and the offset of the 32-bit reference count in the pointee (0x00, 0x30 for `Vector`) come from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws on the last reference of a pointer without a deleter: destroying the object needs its destructor and the `LibContext` allocator, which are not implemented +- [sceNpSessionSignalingGetMemoryInfo](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature +- [sceNpSessionSignalingGetConnectionStatistics](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature +- [sce::Np::CppWebApi::Common::String](../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x20-byte layout (32-bit reference count, buffer at 0x08, buffer size at 0x10, `LibContext*` at 0x18) comes from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws for a non-null buffer: releasing it needs the `LibContext` allocator, which is not implemented +- [sceSystemServiceDisableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature +- [sceSystemServiceReenableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature +- [sceSystemServiceParamGetString](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the system name (parameter 6) is the fixed `PS5`, not the console's name; other parameters and buffers of 1 to 64 bytes throw +- [sceVoiceSetMuteFlag](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature +- [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf` +- [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown. +- [_sceUlobjmgrRegisterObject](../../core/libs/prx/ulobjmgr/Export.cpp) (ulobjmgr) - parameters taken from shadPS4 (PS4): a nonzero 64-bit object, a nonzero 32-bit kind and a 32-bit id output; the meaning of the first two and the PS5 signature are unverified. The id bound of `_sceUlobjmgrUnregisterObject` (below 0x4000) and the raw `EINVAL` (22) return also come from shadPS4 ### Functional -- The shader recompiler [ignores `s_setreg_b32`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): writes to MODE (float rounding and denormal controls) and the other hardware registers have no effect. +- [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; its existing size formula and the 512-byte object layouts remain unverified. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`. +- [MIMG `tfe` and `lwe`](../../core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp) never write the status VGPR after the data. On hardware it is written only when a texel fetch hits an unmapped page of a partially resident texture, or, for `lwe` on sample/gather, when the LOD is below the T# `MIN_LOD_WARN`; texture memory is always resident here, and `lwe` on sample, gather and `image_get_lod` throws. +- [Image atomics](../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) on 32_SINT and 32_FLOAT surfaces act on the raw dword through an R32_UINT storage view; their float results are assumed to match those measured on 32_UINT surfaces. Other formats throw. +- The shader recompiler [ignores `s_setreg_b32`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): writes to MODE (float rounding and denormal controls) and the other hardware registers have no effect, and `s_getreg_b32` reads of the MODE round fields report round to nearest even even after `s_setreg_b32` wrote another mode. The initial round mode from the shader's program registers is not read either. - [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx). - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. +- [COND_EXEC](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp) reads its condition after the earlier packets of its queue complete. Without PFP_SYNC_ME the console's graphics PFP reads it ahead of the ME, so a title that races the read sees a later value here. +- A [flip inside a COND_EXEC range](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp) throws: the flip is reserved with the video output when the buffer is submitted, and how the console withdraws a skipped one is unknown. +- A [COND_EXEC range](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) must hold whole packets and end inside its command buffer. A chained INDIRECT_BUFFER or a REWIND inside a range throws, as what the CP does with the skipped dwords then is unknown. +- [v_mullit_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted, a positive denormal src2 counts as positive): the recompiler does not read MODE, so other IEEE/denormal settings are not modelled. +- [v_fma_legacy_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (VOP3 0x140) is assumed to be fused, which has not been measured on hardware. LLVM decodes this encoding as `v_mad_legacy_f32` for gfx1013, and `v_mac_legacy_f32` (VOP2 0x06) was measured to round its product before the add, so 0x140 may do the same. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. - [libSceJpegEnc](../../core/libs/prx/libSceJpegEnc/Export.cpp) encodes 4:2:2 sampling requests with 4:2:0 chroma subsampling, and grayscale requests as a 3-component JPEG with neutral chroma instead of a single-component one: the [JPEG encoder](../../core/Decoder/Jpeg/src/Jpeg.cpp) (stb) only produces 3-component 4:2:0 and 4:4:4 images. -- `--to-intel` does not lower RDPRU/MCOMMIT; the [matcher](../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp) fails the relink instead. SHA-1 instructions with a memory operand and SHA-256 instructions with a RIP-relative operand fail the relink. +- [PA_CL_CLIP_CNTL](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `DX_LINEAR_ATTR_CLIP_ENA` (bit 24) clear is drawn like bit 24 set, with Vulkan's clipping of `NoPerspective` outputs; what a clear bit changes on the console is undocumented. +- [v_rcp, v_rsq, v_sqrt, v_log, v_exp, v_sin and v_cos](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (f32 and f16) use the driver's approximation for finite results, not the console's: the low bits can differ. Only NaN, invalid, zero and infinite results and the exact sin/cos zeros are chosen on the bits. v_sin_f32/v_cos_f32 with inputs beyond ±256 cycles were not compared with the console. +- `--to-intel` does not lower RDPRU/MCOMMIT; the [matcher](../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp) fails the relink instead. SHA-1 and SHA-256 instructions with a RIP-relative operand fail the relink. - The length-changing path of the [instruction rewriter](../../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust VEX/0F38/0F3A RIP-relative operands, data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length. - The Linux placement of the `--to-intel` stubs in the [ELF patcher](../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) is covered only by a synthetic test. - The libc [SSE4a trap emulation](../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it. - `--to-intel` guest module trampolines are covered only by a synthetic relinker test; no game title has been verified with them on Linux or Windows. - `--to-intel` replaces the approximate VEX.128 register forms of VRSQRTPS and VRCPPS with [correctly rounded](../../core/relinker/codegen/src/x86/ReciprocalLowering.cpp) 1/sqrt(x) and 1/x, not with the AMD approximation: its tables are not public. Intel's approximation of 1/sqrt(1.0) is 0x3F7FF000, and two Newton-Raphson steps on it settle on 0.99999994 instead of 1.0; a renormalised identity quaternion then turns into a NaN axis in PPSA21564, which the console does not show. Refined results now reach the exact value; unrefined ones can still differ from the console in the low bits. Each site costs an out-of-line stub (two jumps, a spill below the red zone, SQRTPS/DIVPS). The 256-bit, scalar, legacy and memory forms keep the native instruction, as does a 4-byte site with no movable instruction after it for the jump (5 sites in PPSA21564). - [sceKeyboardGetKey2Char](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates only the 101-key (US) arrangement and throws for the 106-key (Japanese) one; Ctrl and Alt do not change the character. +- [sceAudioOut2Set3DLatency](../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) and [sceAudioOut2MasteringInit](../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) have no effect: there is no 3D rendering or mastering stage. - [libSceAudiodec](../../core/libs/prx/libSceAudiodec/Export.cpp) throws for the 24-bit PCM word size (`iBwPcm` 0): its sample layout is unknown. ATRAC9 decoding is covered only by configuration and error tests, as no ATRAC9 encoder is available for a fixture. -- [libScePngEnc](../../core/libs/prx/libScePngEnc/Export.cpp) honours `filter_type` only as all filters (adaptive) or a single filter: the [PNG encoder](../../core/Decoder/Png/src/Png.cpp) (stb) cannot restrict adaptive filtering to a subset, so the first filter in the mask is used. +- [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) throws when the original file length is requested (`parse_ofl` non-zero): reading it from the frame's Xing/Info, VBRI or Fraunhofer data is not implemented. +- The [output modifier](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) (`mul:2`, `mul:4`, `div:2`) follows the hardware with the IEEE mode off and f32 denormals flushed, the LLVM graphics default; the shader's MODE register is not read, and the hardware ignores the modifier in the other modes. On f16 results it is ignored, as on the hardware while f16 denormals are enabled; with them flushed the hardware applies it. - [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations; its parameter ABI remains unverified. +- [64-bit LDS atomics](../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (`ds_*_u64`, `ds_*_b64`, `ds_*_f64`) read and write the two dwords under a workgroup lock stored after the guest LDS, as LDS is a 32-bit array without 64-bit atomics. They are atomic with respect to each other only: 32-bit accesses to the same dwords issued concurrently are not ordered by the lock. 64-bit GDS atomics throw. `ds_min/max(_rtn)_f64` and `ds_cmpst(_rtn)_f64` follow the NaN and signed-zero rules measured for the f32 forms; the f64 forms were not measured on hardware. +- [Thread exit destructors](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) run when a libkernel thread finishes; on the main or a host thread only `_sceLibcInternalThreadDtors` runs them, as `exit` does, else they are dropped diff --git a/docs/user/USAGE.md b/docs/user/USAGE.md index 590b1efe4..7cb429298 100644 --- a/docs/user/USAGE.md +++ b/docs/user/USAGE.md @@ -2,7 +2,7 @@ ## Input and conversion -Use an clean ELF executable. Place its bundled ELF modules in `sce_module/` or `sce_modules/` beside the input executable. Exactly one of these directories must exist; both present or both absent is an error. +Use a clean ELF executable. Place its bundled ELF modules in `sce_module/`, `sce_modules/`, or `prx/` beside the input executable. `prx/` can coexist with either `sce_module/` or `sce_modules/`. Both `sce_module/` and `sce_modules/` present, or all three absent, is an error. ```text source/ @@ -68,7 +68,9 @@ app0/ ``` -Use `sce_modules/` instead of `sce_module/` if that is the input directory name. Relinker writes converted modules under `app0/` and prints their exact paths. Place app resources in `app0/` separately. Copy the built system libraries from `build/core/libs/libs/*.prx` into `libs/`; use libraries built for the target OS. A custom `--rpath` changes the system library location. +Use `sce_modules/` or `prx/` instead of `sce_module/` if that is the input directory name. Relinker preserves each module's directory under `app0/` and prints its exact path. Place app resources in `app0/` separately. Copy the built system libraries from `build/core/libs/libs/*.prx` into `libs/`; use libraries built for the target OS. A custom `--rpath` changes the system library location. + +On Windows, direct memory (`sceKernelAllocateDirectMemory`, up to 13824 MiB per title) is committed in full when the title allocates it, not when its pages are first used. The system commit limit (installed memory plus page file size, the second value of Committed in Task Manager) must cover it together with all other committed memory. Otherwise the allocation throws `create direct memory backing of 0x bytes ( MiB)` with the Windows error; enlarge the page file or close other applications. Linux: diff --git a/tools/progress.py b/tools/progress.py index 3777369ff..81ceeea05 100644 --- a/tools/progress.py +++ b/tools/progress.py @@ -10,7 +10,7 @@ PRX = ROOT / "core" / "libs" / "prx" OPCODES = ROOT / "core" / "shader" / "recompiler" / "RdnaDecoder" / "include" / "RdnaDecoder" / "RdnaOpcode.hpp" ISA = Path(__file__).resolve().parent / "rdna_isa.txt" SOURCE = f'https://github.com/{os.environ.get("GITHUB_REPOSITORY", "boykopovar/AnyPS5")}/blob/main' -DEFINITION = re.compile(r"\bAPS5_VABI\s+(\w+)\s*\([^;{]*\)\s*(?:noexcept\s*)?\{") +DEFINITION = re.compile(r"\bAPS5_VABI\s+(\w+)\s*\([^;{]*\)\s*(?:noexcept\s*)?(?:try\s*)?\{") STUB = "NotImplemented_nid_no_patch" STUB_WRAPPER = re.compile(r"\bstatic\s+(?:\[\[noreturn\]\]\s+)?void\s+(\w+)\s*\([^;{]*\)\s*\{") FLAT_SEGMENTS = ("GLOBAL_", "SCRATCH_") @@ -26,6 +26,7 @@ OPCODE_ALIASES = { "VMadMixhiF16": "V_FMA_MIXHI_F16", } OPCODE_VARIANTS = { + "SEndpgm": ("S_ENDPGM_SAVED", "S_ENDPGM_ORDERED_PS_DONE"), "VFmaF32": ("V_FMA_MIX_F32",), "SAddI32": ("S_ADDK_I32",), "SCmpEqI32": ("S_CMPK_EQ_I32",), @@ -49,6 +50,8 @@ OPCODE_VARIANTS = { "VMacF32": ("V_FMAC_F32",), "VMadmkF32": ("V_FMAMK_F32",), "VMadakF32": ("V_FMAAK_F32",), + "VMacLegacyF32": ("V_FMAC_LEGACY_F32",), + "VMadLegacyF32": ("V_FMA_LEGACY_F32",), "ImageSample": ("IMAGE_SAMPLE_L", "IMAGE_SAMPLE_B", "IMAGE_SAMPLE_C_LZ", "IMAGE_SAMPLE_L_O", "IMAGE_SAMPLE_D_CL_O"), } REPORT_ROWS = 100