mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 16:43:05 -05:00
The console's DCC of a single-level 4-byte SW_64KB_R_X surface is ceil(W/512) * ceil(H/512) * 4096 bytes, 49152 for 1920x1080, but the driver stored and read one key per 256 surface bytes (34560). PPSA26344 retiles its render DCC into the displayable copy it registers (cs_dcc_retile_32bpp); a retile of the stale tail gives the mixed keys PPSA12544 stops on. DccKeyCount gives that extent. Color target and storage image key stores cover it when the DCC is pipe-aligned (CB_COLOR_ATTRIB3 bit 30, T# word 6 bit 19), whose CB keys reach its last meta block, and throw where it is not writable; unaligned DCC keeps one key per 256 bytes, as do texture and color target key reads. DisplayBufferKeys reads the display DCC over the extent, presents only when every key agrees, names the first differing key's pixels and checks a pending image against those keys.
291 lines
16 KiB
C++
291 lines
16 KiB
C++
#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp"
|
|
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
|
|
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <bit>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <span>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
#ifdef _WIN32
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#include <windows.h>
|
|
#else
|
|
#include <sys/mman.h>
|
|
#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<typename TAction>
|
|
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<unsigned, 4> 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<unsigned>(pixel[i]) == expected[i]) continue;
|
|
Expect(false, std::string(what) + ": byte " + std::to_string(i) + " is " + std::to_string(std::to_integer<unsigned>(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<PatternBit, 12>;
|
|
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::size_t>((std::popcount(x & pattern[bit].x) + std::popcount(y & pattern[bit].y)) & 1) << bit;
|
|
return (static_cast<std::size_t>(y / 512u) * ((width + 511u) / 512u) + x / 512u) * 4096u + offset;
|
|
}
|
|
|
|
void Retile(std::span<const std::uint8_t> render, std::span<std::uint8_t> 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<const std::uint8_t> 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<std::uint64_t>((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<std::uint8_t*>(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<std::uint8_t*>(mapped);
|
|
#endif
|
|
return block;
|
|
}
|
|
|
|
void ReleaseKeys(std::uint8_t* block, std::size_t reserved) {
|
|
#ifdef _WIN32
|
|
static_cast<void>(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<std::size_t>(surfaceBytes / 256);
|
|
std::vector<std::uint8_t> render(49152);
|
|
std::vector<std::uint8_t> 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<std::uint64_t>(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<std::uint64_t>(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<std::uint64_t>(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<std::uint64_t>(small), surfaceBytes, AgcDriver::Graphics::DccKeyCount(Surface(width, height, reinterpret_cast<std::uint64_t>(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<std::uint8_t>& keys) {
|
|
buffer.dccAddress = reinterpret_cast<std::uint64_t>(keys.data());
|
|
return AgcDriver::DisplayBufferKeys(buffer);
|
|
}
|
|
|
|
void ExtentTests() {
|
|
const DisplayBuffer full{65536, 0x8000000000000000ull, 1920, 1080};
|
|
std::vector<std::uint8_t> 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;
|
|
}
|