#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp" #include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #include #else #include #endif using AgcDriver::DisplayBuffer; using AgcDriver::Graphics::DccKeys; using AgcDriver::Graphics::GuestTextureResource; using AgcDriver::Graphics::TextureDimension; using AgcDriver::Graphics::TextureTileMode; namespace { int failures = 0; void Expect(bool condition, const std::string& what) { if (condition) return; std::fprintf(stderr, "FAIL: %s\n", what.c_str()); ++failures; } template std::string Rejection(TAction action) { try { action(); } catch (const std::exception& error) { return error.what(); } return {}; } void ExpectPixels(const DisplayBuffer& buffer, DccKeys keys, std::array expected, const char* what) { const auto pixel = AgcDriver::DisplayBufferClearPixel(buffer, keys); for (std::size_t i = 0; i < pixel.size(); ++i) { if (std::to_integer(pixel[i]) == expected[i]) continue; Expect(false, std::string(what) + ": byte " + std::to_string(i) + " is " + std::to_string(std::to_integer(pixel[i])) + ", expected " + std::to_string(expected[i])); return; } } struct PatternBit { std::uint32_t x; std::uint32_t y; }; constexpr PatternBit X(std::uint32_t bit) { return {1u << bit, 0}; } constexpr PatternBit Y(std::uint32_t bit) { return {0, 1u << bit}; } constexpr PatternBit operator^(PatternBit a, PatternBit b) { return {a.x ^ b.x, a.y ^ b.y}; } using DccPattern = std::array; constexpr DccPattern PipeAligned{X(3), Y(4), X(6), Y(6), X(7), Y(7), X(8), Y(8), X(3) ^ Y(3), X(4) ^ Y(4), X(6) ^ Y(5), X(5) ^ Y(6)}; constexpr DccPattern Displayable{X(3), Y(3), X(4), Y(4), X(5), Y(5), X(6), Y(6), X(7), Y(7), X(8), Y(8)}; std::size_t PatternKey(const DccPattern& pattern, std::uint32_t width, std::uint32_t x, std::uint32_t y) { std::size_t offset = 0; for (std::size_t bit = 0; bit < pattern.size(); ++bit) offset |= static_cast((std::popcount(x & pattern[bit].x) + std::popcount(y & pattern[bit].y)) & 1) << bit; return (static_cast(y / 512u) * ((width + 511u) / 512u) + x / 512u) * 4096u + offset; } void Retile(std::span render, std::span display, std::uint32_t width, std::uint32_t height) { for (std::uint32_t y = 0; y < (height + 511u) / 512u * 512u; y += 8) { for (std::uint32_t x = 0; x < (width + 511u) / 512u * 512u; x += 8) display[PatternKey(Displayable, width, x, y)] = render[PatternKey(PipeAligned, width, x, y)]; } } struct KeyHistogram { std::size_t uncompressed = 0; std::size_t clear0000 = 0; std::size_t firstDiffering = 0; std::size_t zeroRuns = 0; bool operator==(const KeyHistogram&) const = default; }; KeyHistogram Histogram(std::span keys) { KeyHistogram histogram; for (std::size_t key = 0; key < keys.size(); ++key) { histogram.uncompressed += keys[key] == 0xff; histogram.clear0000 += keys[key] == 0x00; histogram.zeroRuns += keys[key] == 0x00 && (key == 0 || keys[key - 1] != 0x00); if (histogram.firstDiffering == 0 && keys[key] != keys[0]) histogram.firstDiffering = key; } return histogram; } std::string Describe(const KeyHistogram& histogram) { return std::to_string(histogram.uncompressed) + " x 0xff, " + std::to_string(histogram.clear0000) + " x 0x00, first differing key " + std::to_string(histogram.firstDiffering) + ", " + std::to_string(histogram.zeroRuns) + " runs of 0x00"; } std::uint64_t TiledBytes(std::uint32_t width, std::uint32_t height) { return static_cast((width + 127u) / 128u) * ((height + 127u) / 128u) * 65536u; } GuestTextureResource Surface(std::uint32_t width, std::uint32_t height, std::uint64_t dccAddress, bool pipeAligned) { GuestTextureResource surface{}; surface.baseAddress = 0x10000; surface.width = width; surface.height = height; surface.mipCount = 1; surface.tileMode = TextureTileMode::kR64KBX; surface.dimension = TextureDimension::k2D; surface.format = AgcDriver::Graphics::FindGuestColorTargetFormat(VK_FORMAT_B8G8R8A8_UNORM, 4).value(); surface.dccAddress = dccAddress; surface.dccPipeAligned = pipeAligned; return surface; } std::uint8_t* ReserveKeys(std::size_t writable, std::size_t reserved) { #ifdef _WIN32 auto* block = static_cast(VirtualAlloc(nullptr, reserved, MEM_RESERVE, PAGE_NOACCESS)); if (block == nullptr || VirtualAlloc(block, writable, MEM_COMMIT, PAGE_READWRITE) == nullptr) return nullptr; #else void* mapped = mmap(nullptr, reserved, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (mapped == MAP_FAILED || mprotect(mapped, writable, PROT_READ | PROT_WRITE) != 0) return nullptr; auto* block = static_cast(mapped); #endif return block; } void ReleaseKeys(std::uint8_t* block, std::size_t reserved) { #ifdef _WIN32 static_cast(reserved); VirtualFree(block, 0, MEM_RELEASE); #else munmap(block, reserved); #endif } void KeyCountTests() { struct Extent { std::uint32_t width; std::uint32_t height; std::size_t keys; }; for (const auto& extent : {Extent{1920, 1080, 49152}, Extent{3840, 2160, 163840}, Extent{1280, 720, 24576}, Extent{259, 137, 4096}, Extent{128, 128, 4096}, Extent{2560, 1440, 61440}}) { const auto surfaceBytes = TiledBytes(extent.width, extent.height); const auto name = std::to_string(extent.width) + "x" + std::to_string(extent.height); const auto keys = AgcDriver::Graphics::DccKeyCount(TextureTileMode::kR64KBX, 4, extent.width, extent.height, surfaceBytes); Expect(keys == extent.keys, name + " SW_64KB_R_X 4-byte DCC covers " + std::to_string(keys) + " key bytes, expected " + std::to_string(extent.keys)); if (extent.keys == surfaceBytes / 256) continue; const auto aligned = AgcDriver::Graphics::DccKeyCount(Surface(extent.width, extent.height, 0, true), surfaceBytes); const auto unaligned = AgcDriver::Graphics::DccKeyCount(Surface(extent.width, extent.height, 0, false), surfaceBytes); Expect(aligned == extent.keys && unaligned == surfaceBytes / 256, name + " surface descriptors' DCC covers " + std::to_string(aligned) + " key bytes pipe-aligned and " + std::to_string(unaligned) + " unaligned, expected " + std::to_string(extent.keys) + " and " + std::to_string(surfaceBytes / 256)); } Expect(TiledBytes(2560, 1440) / 256 == 61440, "2560x1440 does not fill its meta blocks"); const auto bytes = TiledBytes(1920, 1080); Expect(AgcDriver::Graphics::DccKeyCount(TextureTileMode::kD64KBX, 4, 1920, 1080, bytes) == bytes / 256, "an SW_64KB_D_X surface's DCC left one key per 256 bytes"); Expect(AgcDriver::Graphics::DccKeyCount(TextureTileMode::kR64KBX, 8, 1920, 1080, 2 * bytes) == 2 * bytes / 256, "an 8-byte surface's DCC left one key per 256 bytes"); const auto surface = Surface(1920, 1080, 0, true); auto shape = surface; shape.mipCount = 2; Expect(AgcDriver::Graphics::DccKeyCount(shape, bytes + 65536) == (bytes + 65536) / 256, "a mip chain's DCC left one key per 256 bytes"); shape = surface; shape.dimension = TextureDimension::k2DArray; shape.depthOrLastArray = 1; Expect(AgcDriver::Graphics::DccKeyCount(shape, 2 * bytes) == 2 * bytes / 256, "a two-layer surface's DCC left one key per 256 bytes"); shape = surface; shape.format = AgcDriver::Graphics::FindGuestTextureFormat(VK_FORMAT_R16G16B16A16_SFLOAT, 8).value(); Expect(AgcDriver::Graphics::DccKeyCount(shape, 2 * bytes) == 2 * bytes / 256, "an 8-byte surface descriptor's DCC left one key per 256 bytes"); Expect(AgcDriver::DisplayBufferKeyBytes({65536, 0x8000000000000000ull, 1920, 1080}) == 49152 && AgcDriver::DisplayBufferKeyBytes({65536, 0x8000000000000000ull, 259, 137}) == 4096, "display buffers do not read the console's DCC extent"); } void RetileTests() { constexpr std::uint32_t width = 1920; constexpr std::uint32_t height = 1080; const auto surfaceBytes = TiledBytes(width, height); const auto driverKeys = static_cast(surfaceBytes / 256); std::vector render(49152); std::vector display(render.size()); std::fill_n(render.begin(), driverKeys, 0xff); Retile(render, display, width, height); const auto reported = Histogram(std::span(display).first(driverKeys)); Expect(reported == KeyHistogram{33552, 1008, 32784, 105}, "a retile of 0xff over the first 34560 render keys does not give the PPSA12544 display keys: " + Describe(reported)); std::fill(render.begin(), render.end(), 0x00); std::fill(display.begin(), display.end(), 0x00); const auto surface = Surface(width, height, reinterpret_cast(render.data()), true); AgcDriver::Graphics::MarkDccUncompressed(surface.dccAddress, surfaceBytes, AgcDriver::Graphics::DccKeyCount(surface, surfaceBytes)); Retile(render, display, width, height); DisplayBuffer buffer{65536, 0x8000000000000000ull, width, height}; buffer.dccAddress = reinterpret_cast(display.data()); auto keys = DccKeys::Unreadable; const auto message = Rejection([&] { keys = AgcDriver::DisplayBufferKeys(buffer); }); Expect(message.empty() && keys == DccKeys::Uncompressed, "the driver's store over a 1920x1080 render DCC, retiled, does not present: " + message + " (display keys over the first " + std::to_string(driverKeys) + ": " + Describe(Histogram(std::span(display).first(driverKeys))) + ")"); constexpr std::size_t reserved = 65536; constexpr std::size_t writable = 0x9000; auto* small = ReserveKeys(writable, reserved); Expect(small != nullptr, "cannot reserve metadata with an inaccessible tail"); if (small == nullptr) return; const auto stored = Rejection([&] { AgcDriver::Graphics::MarkDccUncompressed(reinterpret_cast(small), surfaceBytes, AgcDriver::Graphics::DccKeyCount(surface, surfaceBytes)); }); Expect(stored.find("not writable over the 0xc000 key bytes") != std::string::npos && small[0] == 0x00, "a key store over metadata smaller than the console's extent did not throw: " + stored); const auto unaligned = Rejection([&] { AgcDriver::Graphics::MarkDccUncompressed(reinterpret_cast(small), surfaceBytes, AgcDriver::Graphics::DccKeyCount(Surface(width, height, reinterpret_cast(small), false), surfaceBytes)); }); Expect(unaligned.empty() && std::all_of(small, small + driverKeys, [](std::uint8_t key) { return key == 0xff; }) && small[driverKeys] == 0x00, "an unaligned DCC key store over metadata smaller than the console's extent did not cover one key per 256 bytes: " + unaligned); ReleaseKeys(small, reserved); } DccKeys ReadKeys(DisplayBuffer buffer, std::vector& keys) { buffer.dccAddress = reinterpret_cast(keys.data()); return AgcDriver::DisplayBufferKeys(buffer); } void ExtentTests() { const DisplayBuffer full{65536, 0x8000000000000000ull, 1920, 1080}; std::vector keys(49152); for (const auto& [code, expected] : {std::pair{std::uint8_t{0xff}, DccKeys::Uncompressed}, std::pair{std::uint8_t{0x00}, DccKeys::Clear0000}, std::pair{std::uint8_t{0xc0}, DccKeys::Clear1111}, std::pair{std::uint8_t{0x20}, DccKeys::ClearRegister}}) { std::fill(keys.begin(), keys.end(), code); auto read = DccKeys::Unreadable; const auto message = Rejection([&] { read = ReadKeys(full, keys); }); Expect(message.empty() && read == expected, std::string("uniform ") + AgcDriver::Graphics::DccKeysName(expected) + " keys over the extent read " + AgcDriver::Graphics::DccKeysName(read) + ": " + message); } std::fill(keys.begin(), keys.end(), 0xff); const auto corner = PatternKey(Displayable, 1920, 1919, 1079); Expect(corner == 46205, "pixel (1919, 1079) is not displayable key 46205"); keys[corner] = 0x00; auto message = Rejection([&] { ReadKeys(full, keys); }); Expect(message.find("mixed") != std::string::npos && message.find("key 46205 (pixels 1912..1919 x 1072..1079 in the displayable order) is 0x00, key 0 is 0xff") != std::string::npos, "a 0000 key past one key per 256 bytes was not read or not named: " + message); std::fill(keys.begin(), keys.end(), 0x40); keys[0] = 0x80; message = Rejection([&] { ReadKeys(full, keys); }); Expect(message.find("key 1 (pixels 8..15 x 0..7 in the displayable order) is 0x40, key 0 is 0x80") != std::string::npos, "a 1110 key 0 among 0001 keys was not named: " + message); const DisplayBuffer odd{65536, 0x8000000000000000ull, 259, 137}; keys.assign(4096, 0x40); keys.back() = 0xff; message = Rejection([&] { ReadKeys(odd, keys); }); Expect(message.find("key 4095 (pixels 504..511 x 504..511 in the displayable order) is 0xff") != std::string::npos, "259x137: an uncompressed padding key among 0001 keys was ignored: " + message); Expect(!Rejection([&] { AgcDriver::DisplayBufferKeys(full); }).empty(), "a display buffer without DCC metadata had keys read"); } } int main() { constexpr std::uint64_t bgra = 0x8000000000000000ull; constexpr std::uint64_t rgba = 0x8000000022000000ull; constexpr std::uint64_t tenBit = 0x0100000000000000ull; DisplayBuffer buffer{65536, bgra, 131, 3, 0, 0, 0x7f0000, 0x11223344}; ExpectPixels(buffer, DccKeys::Clear0000, {0, 0, 0, 0}, "8-bit 0000"); ExpectPixels(buffer, DccKeys::Clear0001, {0, 0, 0, 255}, "8-bit 0001"); ExpectPixels(buffer, DccKeys::Clear1110, {255, 255, 255, 0}, "8-bit 1110"); ExpectPixels(buffer, DccKeys::Clear1111, {255, 255, 255, 255}, "8-bit 1111"); ExpectPixels(buffer, DccKeys::ClearRegister, {0x44, 0x33, 0x22, 0x11}, "B8G8R8A8 register clear"); buffer.pixelFormat = rgba; ExpectPixels(buffer, DccKeys::ClearRegister, {0x22, 0x33, 0x44, 0x11}, "R8G8B8A8 register clear"); ExpectPixels(buffer, DccKeys::Clear0001, {0, 0, 0, 255}, "R8G8B8A8 0001"); buffer.pixelFormat = rgba | tenBit; ExpectPixels(buffer, DccKeys::Clear1110, {255, 255, 255, 255}, "A2B10G10R10 1110"); ExpectPixels(buffer, DccKeys::Clear0001, {0, 0, 0, 255}, "A2B10G10R10 0001"); buffer.dccClearColor = 0x3ff; ExpectPixels(buffer, DccKeys::ClearRegister, {0, 0, 255, 255}, "A2B10G10R10 register clear"); buffer.pixelFormat = bgra | tenBit; ExpectPixels(buffer, DccKeys::ClearRegister, {255, 0, 0, 255}, "A2R10G10B10 register clear"); for (const auto keys : {DccKeys::Uncompressed, DccKeys::Mixed, DccKeys::Unreadable}) { const auto message = Rejection([&] { AgcDriver::DisplayBufferClearPixel(buffer, keys); }); Expect(message.find(AgcDriver::Graphics::DccKeysName(keys)) != std::string::npos && message.find("0x7f0000") != std::string::npos, std::string("keys without a clear value were presented as one: ") + AgcDriver::Graphics::DccKeysName(keys)); } buffer.dccClearColor = 0x100000000ull; Expect(Rejection([&] { AgcDriver::DisplayBufferClearPixel(buffer, DccKeys::ClearRegister); }).find("register clear color") != std::string::npos, "a register clear color wider than the texel was truncated"); buffer.dccClearColor = 0; buffer.dccAddress = 0; Expect(!Rejection([&] { AgcDriver::DisplayBufferClearPixel(buffer, DccKeys::Clear0000); }).empty(), "a buffer without DCC metadata was presented as fast-cleared"); buffer.dccClearColor = 1; Expect(!Rejection([&] { AgcDriver::DisplayBufferSize(buffer); }).empty(), "a DCC clear color without metadata was accepted"); buffer.dccClearColor = 0; buffer.dccAddress = 0x7f0000; buffer.tilingMode = 1; Expect(!Rejection([&] { AgcDriver::DisplayBufferSize(buffer); }).empty(), "a linear display buffer with DCC metadata was accepted"); buffer.tilingMode = 0; Expect(AgcDriver::DisplayBufferSize(buffer) == 2 * 65536, "DCC metadata changed the display footprint"); KeyCountTests(); RetileTests(); ExtentTests(); if (failures != 0) return 1; std::puts("display buffer DCC tests passed"); return 0; }