Merge pull request #788 from Celegans12/feat/videoout-dcc-visible-keys

fix(agc): store and read DCC keys over the console's metadata extent
This commit is contained in:
Dean 2026-10-09 02:59:52 +13:00 committed by GitHub
commit 74b5c9103b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 451 additions and 48 deletions

View File

@ -28,6 +28,8 @@ std::size_t DisplayBufferSize(const DisplayBuffer& buffer);
std::vector<std::byte> DecodeDisplayBuffer(const DisplayBuffer& buffer, std::span<const std::byte> source);
std::vector<std::byte> ReadDisplayBuffer(const DisplayBuffer& buffer);
std::array<std::byte, 4> DisplayBufferClearPixel(const DisplayBuffer& buffer, Graphics::DccKeys keys);
std::size_t DisplayBufferKeyBytes(const DisplayBuffer& buffer);
Graphics::DccKeys DisplayBufferKeys(const DisplayBuffer& buffer);
}

View File

@ -3,6 +3,7 @@
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
#include <algorithm>
#include <array>
#include <chrono>
#include <cstdio>
@ -10,6 +11,7 @@
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
namespace AgcDriver {
namespace {
@ -50,6 +52,40 @@ void decodeTexel(const std::byte* texel, std::byte* pixel, bool rgba, bool tenBi
pixel[3] = texel[3];
}
constexpr std::uint32_t MetaBlockEdge = 512;
constexpr std::size_t MetaBlockKeys = 4096;
constexpr std::uint32_t KeyEdge = 8;
std::array<std::uint32_t, 2> displayableKeyPixel(const DisplayBuffer& buffer, std::size_t key) {
const auto pitch = (buffer.width + MetaBlockEdge - 1u) / MetaBlockEdge;
const auto block = static_cast<std::uint32_t>(key / MetaBlockKeys);
const auto offset = static_cast<std::uint32_t>(key % MetaBlockKeys);
std::uint32_t x = 0;
std::uint32_t y = 0;
for (std::uint32_t bit = 0; bit < 6; ++bit) {
x |= ((offset >> (2u * bit)) & 1u) << bit;
y |= ((offset >> (2u * bit + 1u)) & 1u) << bit;
}
return {block % pitch * MetaBlockEdge + x * KeyEdge, block / pitch * MetaBlockEdge + y * KeyEdge};
}
void traceDisplayKeys(const DisplayBuffer& buffer, const std::uint8_t* keys, std::size_t count) {
static const bool trace = std::getenv("APS5_TRACE_DCC_KEYS") != nullptr;
if (!trace) return;
constexpr std::size_t MaxRuns = 256;
std::string runs;
std::size_t begin = 0;
for (std::size_t run = 0; begin < count && run < MaxRuns; ++run) {
const auto end = static_cast<std::size_t>(std::find_if(keys + begin, keys + count, [&](std::uint8_t key) { return key != keys[begin]; }) - keys);
char text[32];
std::snprintf(text, sizeof(text), "%s%02x x%zu", run == 0 ? "" : ", ", keys[begin], end - begin);
runs += text;
begin = end;
}
if (begin < count) runs += ", ...";
std::fprintf(stderr, "[dcc-keys] display keys 0x%llx+0x%zx: %s\n", static_cast<unsigned long long>(buffer.dccAddress), count, runs.c_str());
}
}
std::size_t DisplayBufferSize(const DisplayBuffer& buffer) {
@ -117,6 +153,35 @@ std::array<std::byte, 4> DisplayBufferClearPixel(const DisplayBuffer& buffer, Gr
return pixel;
}
std::size_t DisplayBufferKeyBytes(const DisplayBuffer& buffer) {
const auto bytes = DisplayBufferSize(buffer);
require(buffer.tilingMode == 0, "VideoOut: a linear display buffer has no DCC keys");
return Graphics::DccKeyCount(Graphics::TextureTileMode::kR64KBX, 4, buffer.width, buffer.height, bytes);
}
Graphics::DccKeys DisplayBufferKeys(const DisplayBuffer& buffer) {
const auto count = DisplayBufferKeyBytes(buffer);
require(buffer.dccAddress != 0, "VideoOut: a display buffer without DCC metadata has no keys");
const auto keys = Graphics::CurrentDccKeys(buffer.dccAddress, DisplayBufferSize(buffer), count);
if (keys == Graphics::DccKeys::Uncompressed || Graphics::IsDccClear(keys)) return keys;
char differing[128] = "";
if (keys == Graphics::DccKeys::Mixed && GuestMemory::Accessible(reinterpret_cast<const void*>(buffer.dccAddress), count)) {
const auto* bytes = reinterpret_cast<const std::uint8_t*>(buffer.dccAddress);
traceDisplayKeys(buffer, bytes, count);
const auto* other = std::find_if(bytes + 1, bytes + count, [&](std::uint8_t key) { return key != bytes[0]; });
if (other == bytes + count) {
std::snprintf(differing, sizeof(differing), ": every key is 0x%02x", bytes[0]);
} else {
const auto key = static_cast<std::size_t>(other - bytes);
const auto [x, y] = displayableKeyPixel(buffer, key);
std::snprintf(differing, sizeof(differing), ": key %zu (pixels %u..%u x %u..%u in the displayable order) is 0x%02x, key 0 is 0x%02x", key, x, x + KeyEdge - 1u, y, y + KeyEdge - 1u, *other, bytes[0]);
}
}
char message[384];
std::snprintf(message, sizeof(message), "VideoOut: display buffer 0x%llx reads %s DCC keys at 0x%llx over its 0x%zx key bytes%s: presenting DCC metadata that is not uniformly uncompressed or fast-cleared is not implemented", static_cast<unsigned long long>(buffer.address), Graphics::DccKeysName(keys), static_cast<unsigned long long>(buffer.dccAddress), count, differing);
throw std::runtime_error(message);
}
std::vector<std::byte> ReadDisplayBuffer(const DisplayBuffer& buffer) {
const auto size = DisplayBufferSize(buffer);
static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr;

View File

@ -1952,37 +1952,30 @@ std::shared_ptr<Graphics::StorageTexture> PresentableResident(const Graphics::Co
return resident;
}
Graphics::DccKeys DisplayKeys(const DisplayBuffer& buffer, std::size_t bytes) {
const auto keys = Graphics::CurrentDccKeys(buffer.dccAddress, bytes);
if (keys != Graphics::DccKeys::Uncompressed && !Graphics::IsDccClear(keys)) {
char message[256];
std::snprintf(message, sizeof(message), "VideoOut: display buffer 0x%llx reads %s DCC keys at 0x%llx: presenting DCC metadata that is not uniformly uncompressed or fast-cleared is not implemented", static_cast<unsigned long long>(buffer.address), Graphics::DccKeysName(keys), static_cast<unsigned long long>(buffer.dccAddress));
throw std::runtime_error(message);
}
return keys;
Graphics::DccKeys ResidentKeys(const Graphics::StorageTexture& resident) {
const auto& own = resident.Descriptor();
GuestMemory::CollectWritesUncached(own.dccAddress, Graphics::DccKeyBytes(resident.GuestBytes()));
return Graphics::ProvedClearKeys(own, resident.GuestBytes(), resident.KeyProof());
}
bool ResidentServesDisplay(const Graphics::StorageTexture& resident, const DisplayBuffer& buffer, std::size_t bytes) {
const auto keys = DisplayKeys(buffer, bytes);
bool ResidentServesDisplay(const Graphics::StorageTexture& resident, const DisplayBuffer& buffer) {
const auto keys = DisplayBufferKeys(buffer);
if (keys == Graphics::DccKeys::ClearRegister) {
char message[320];
std::snprintf(message, sizeof(message), "VideoOut: display buffer 0x%llx reads register-clear DCC keys at 0x%llx over the pending image 0x%llx (DCC 0x%llx, filled keys %s): whether its results precede the clear is not modeled", static_cast<unsigned long long>(buffer.address), static_cast<unsigned long long>(buffer.dccAddress), static_cast<unsigned long long>(resident.Descriptor().baseAddress), static_cast<unsigned long long>(resident.Descriptor().dccAddress), Graphics::DccKeysName(resident.FilledKeys()));
throw std::runtime_error(message);
}
if (Graphics::IsDccClear(resident.FilledKeys()) && resident.FilledKeys() == keys) return false;
return Graphics::StorageImageServesKeys(resident, buffer.dccAddress);
return Graphics::KeysServeSurface(resident.Descriptor().dccAddress, resident.UploadedKeys(), resident.FilledKeys(), buffer.dccAddress, [&] { return ResidentKeys(resident); }, [&] { return keys; });
}
bool ResidentKeysMoved(const Graphics::StorageTexture& resident) {
const auto& own = resident.Descriptor();
if (own.dccAddress == 0) return false;
GuestMemory::CollectWritesUncached(own.dccAddress, Graphics::DccKeyBytes(resident.GuestBytes()));
return Graphics::ProvedClearKeys(own, resident.GuestBytes(), resident.KeyProof()) != resident.UploadedKeys();
return resident.Descriptor().dccAddress != 0 && ResidentKeys(resident) != resident.UploadedKeys();
}
std::optional<std::array<std::byte, 4>> CompressedClearPixel(const DisplayBuffer& buffer, std::size_t bytes) {
GuestMemory::FlushGpuWrites(buffer.address, bytes);
const auto keys = DisplayKeys(buffer, bytes);
const auto keys = DisplayBufferKeys(buffer);
if (keys == Graphics::DccKeys::Uncompressed) return std::nullopt;
return DisplayBufferClearPixel(buffer, keys);
}
@ -2036,7 +2029,7 @@ bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) {
if (!NoResidentPresent()) {
bool pending = false;
resident = PresentableResident(graphicsContext(), buffer, filter, pending, convert);
if (resident != nullptr && buffer.dccAddress != 0 && !ResidentServesDisplay(*resident, buffer, bytes)) {
if (resident != nullptr && buffer.dccAddress != 0 && !ResidentServesDisplay(*resident, buffer)) {
resident.reset();
convert = false;
}

View File

@ -23,6 +23,8 @@ enum class DccKeys { Uncompressed, Clear0000, Clear0001, Clear1110, Clear1111, C
const char* DccKeysName(DccKeys keys);
std::size_t DccKeyBytes(std::uint64_t surfaceBytes);
std::size_t DccKeyCount(TextureTileMode tileMode, std::uint32_t elementBytes, std::uint32_t width, std::uint32_t height, std::uint64_t surfaceBytes);
std::size_t DccKeyCount(const GuestTextureResource& surface, std::uint64_t surfaceBytes);
template<typename ReadFollowed, typename ReadNamed>
bool KeysServeSurface(std::uint64_t followedDcc, DccKeys uploaded, DccKeys filled, std::uint64_t namedDcc, ReadFollowed&& readFollowed, ReadNamed&& readNamed) {
@ -35,15 +37,18 @@ bool KeysServeSurface(std::uint64_t followedDcc, DccKeys uploaded, DccKeys fille
DccKeys ReadDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes);
bool IsDccClear(DccKeys keys);
DccKeys CurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes);
DccKeys CurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes, std::size_t keyCount);
// Stores "uncompressed" keys over the surface's metadata on the CPU (a guest memory write: it waits
// for recorded GPU work that writes the keys first).
void MarkDccUncompressed(std::uint64_t metaAddress, std::uint64_t surfaceBytes);
void MarkDccUncompressed(std::uint64_t metaAddress, std::uint64_t surfaceBytes, std::size_t keyCount);
// The same after a write-back recorded under GuestMemory::GpuMutex: when the keys are in host-imported
// memory and a recorder is active, the store is a fill recorded into the open batch (ordered behind
// the title's key-writing kernels like the write-back itself, nothing waits on the CPU) and the range
// reads as uncompressed from ReadDccKeys while that batch is pending; otherwise the CPU store above.
// APS5_CPU_DCC_KEYS=1 always stores on the CPU.
void MarkDccUncompressed(const Context& context, std::uint64_t metaAddress, std::uint64_t surfaceBytes);
void MarkDccUncompressed(const Context& context, std::uint64_t metaAddress, std::uint64_t surfaceBytes, std::size_t keyCount);
// Fills `bytes` with the texel a 0000/0001/1110/1111 clear code stands for ("1" is 1.0 or the integer
// maximum; the alpha channel is the last one in memory when alphaOnMsb, else the first, and 3-channel
// formats have none). False when the format has no encoding here.

View File

@ -81,6 +81,7 @@ struct GuestTextureResource {
// DCC metadata of a compressed surface, or 0 (see DccMetadata.hpp).
std::uint64_t dccAddress = 0;
bool dccAlphaOnMsb = false;
bool dccPipeAligned = false;
std::uint32_t minLod = 0;
std::uint32_t allocatedMipCount = 0;
};

View File

@ -41,6 +41,7 @@ struct ColorTarget {
// DCC metadata of a compressed target (CB_COLOR_INFO DCC_ENABLE), or 0 (see DccMetadata.hpp).
std::uint64_t dccAddress = 0;
bool dccAlphaOnMsb = false;
bool dccPipeAligned = false;
std::uint64_t cmaskAddress = 0;
std::size_t cmaskBytes = 0;
std::uint64_t surfaceAddress = 0;

View File

@ -392,13 +392,27 @@ std::size_t DccKeyBytes(std::uint64_t surfaceBytes) {
return static_cast<std::size_t>(surfaceBytes / KeyBytes);
}
std::size_t DccKeyCount(TextureTileMode tileMode, std::uint32_t elementBytes, std::uint32_t width, std::uint32_t height, std::uint64_t surfaceBytes) {
const auto keys = DccKeyBytes(surfaceBytes);
if (tileMode != TextureTileMode::kR64KBX || elementBytes != 4) return keys;
constexpr std::uint64_t MetaBlockEdge = 512;
constexpr std::uint64_t MetaBlockBytes = 4096;
const auto console = (width + MetaBlockEdge - 1) / MetaBlockEdge * ((height + MetaBlockEdge - 1) / MetaBlockEdge) * MetaBlockBytes;
return std::max(keys, static_cast<std::size_t>(console));
}
std::size_t DccKeyCount(const GuestTextureResource& surface, std::uint64_t surfaceBytes) {
const bool single = surface.mipCount == 1 && surface.baseArray == 0 && surface.depthOrLastArray == 0 && (surface.dimension == TextureDimension::k2D || surface.dimension == TextureDimension::k2DArray);
if (!single || !surface.dccPipeAligned || surface.tileMode != TextureTileMode::kR64KBX) return DccKeyBytes(surfaceBytes);
return DccKeyCount(surface.tileMode, BytesPerElement(surface.format), surface.width, surface.height, surfaceBytes);
}
namespace {
// ReadDccKeys, saying in `memoized` whether the answer came from the pending-store memo rather
// than the bytes.
DccKeys readDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes, bool& memoized) {
DccKeys readDccKeys(std::uint64_t metaAddress, std::size_t count, bool& memoized) {
memoized = false;
const auto count = static_cast<std::size_t>(surfaceBytes / KeyBytes);
if (metaAddress == 0 || count == 0 || !GuestMemory::Accessible(reinterpret_cast<const void*>(metaAddress), count)) return DccKeys::Unreadable;
// A driver store still pending on the GPU is not in the bytes yet.
if (const auto memo = MemoizedKeys(metaAddress, metaAddress + count)) {
@ -430,7 +444,7 @@ DccKeys readDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes, bool&
DccKeys textureClearKeys(const GuestTextureResource& resource, std::uint64_t guestBytes, bool& memoized) {
memoized = false;
if (resource.dccAddress == 0) return DccKeys::Uncompressed;
const auto keys = readDccKeys(resource.dccAddress, guestBytes, memoized);
const auto keys = readDccKeys(resource.dccAddress, DccKeyBytes(guestBytes), memoized);
if (keys == DccKeys::Uncompressed) return keys;
if (IsConvertedTextureFormat(resource.format) && (keys == DccKeys::Clear0001 || keys == DccKeys::Clear1110)) throw std::runtime_error(std::string("AGC graphics: DCC clear code ") + DccKeysName(keys) + " of converted texture format " + std::to_string(resource.format) + " is not implemented");
std::byte probe[16]{};
@ -444,15 +458,7 @@ DccKeys textureClearKeys(const GuestTextureResource& resource, std::uint64_t gue
return keys;
}
}
DccKeys ReadDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes) {
bool memoized = false;
return readDccKeys(metaAddress, surfaceBytes, memoized);
}
DccKeys CurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes) {
const auto count = static_cast<std::size_t>(surfaceBytes / KeyBytes);
DccKeys currentDccKeys(std::uint64_t metaAddress, std::size_t count) {
if (metaAddress != 0 && count != 0 && GuestMemory::GpuMutex().HeldByThisThread()) {
if (auto* recorder = Recorder::Active(); recorder != nullptr && recorder->PendingWriteOverlaps(metaAddress, count)) {
if (const auto keys = PendingStoreKeys(*recorder, metaAddress, count)) return *keys;
@ -460,7 +466,35 @@ DccKeys CurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes) {
recorder->SyncThrough(metaAddress, count);
}
}
return ReadDccKeys(metaAddress, surfaceBytes);
bool memoized = false;
return readDccKeys(metaAddress, count, memoized);
}
std::size_t StoredKeyCount(std::uint64_t metaAddress, std::uint64_t surfaceBytes, std::size_t keyCount) {
const auto keys = DccKeyBytes(surfaceBytes);
Require(keyCount >= keys, "a DCC key range covers fewer keys than one per 256 surface bytes");
if (metaAddress == 0 || keyCount == 0) return 0;
if (GuestMemory::Accessible(reinterpret_cast<const void*>(metaAddress), keyCount, true)) return keyCount;
if (keyCount == keys) return 0;
char message[256];
std::snprintf(message, sizeof(message), "AGC graphics: DCC metadata 0x%llx is not writable over the 0x%zx key bytes of its surface (0x%zx at one per 256 bytes): metadata smaller than the console's DCC extent is not modeled", static_cast<unsigned long long>(metaAddress), keyCount, keys);
throw std::runtime_error(message);
}
}
DccKeys ReadDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes) {
bool memoized = false;
return readDccKeys(metaAddress, DccKeyBytes(surfaceBytes), memoized);
}
DccKeys CurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes) {
return currentDccKeys(metaAddress, DccKeyBytes(surfaceBytes));
}
DccKeys CurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes, std::size_t keyCount) {
Require(keyCount >= DccKeyBytes(surfaceBytes), "a DCC key range covers fewer keys than one per 256 surface bytes");
return currentDccKeys(metaAddress, keyCount);
}
bool IsDccClear(DccKeys keys) {
@ -468,8 +502,12 @@ bool IsDccClear(DccKeys keys) {
}
void MarkDccUncompressed(std::uint64_t metaAddress, std::uint64_t surfaceBytes) {
const auto count = static_cast<std::size_t>(surfaceBytes / KeyBytes);
if (metaAddress == 0 || count == 0 || !GuestMemory::Accessible(reinterpret_cast<const void*>(metaAddress), count, true)) return;
MarkDccUncompressed(metaAddress, surfaceBytes, DccKeyBytes(surfaceBytes));
}
void MarkDccUncompressed(std::uint64_t metaAddress, std::uint64_t surfaceBytes, std::size_t keyCount) {
const auto count = StoredKeyCount(metaAddress, surfaceBytes, keyCount);
if (count == 0) return;
// The bytes are trusted only when no recorded work writes them (lock-free, with or without
// GuestMemory::GpuMutex); otherwise the store runs and waits through the flush hook. A pending
// GPU store of the driver's own is no reason to skip: a title kernel recorded into the same
@ -479,8 +517,12 @@ void MarkDccUncompressed(std::uint64_t metaAddress, std::uint64_t surfaceBytes)
}
void MarkDccUncompressed(const Context& context, std::uint64_t metaAddress, std::uint64_t surfaceBytes) {
const auto count = static_cast<std::size_t>(surfaceBytes / KeyBytes);
if (metaAddress == 0 || count == 0 || !GuestMemory::Accessible(reinterpret_cast<const void*>(metaAddress), count, true)) return;
MarkDccUncompressed(context, metaAddress, surfaceBytes, DccKeyBytes(surfaceBytes));
}
void MarkDccUncompressed(const Context& context, std::uint64_t metaAddress, std::uint64_t surfaceBytes, std::size_t keyCount) {
const auto count = StoredKeyCount(metaAddress, surfaceBytes, keyCount);
if (count == 0) return;
auto* recorder = CpuKeysOnly() || !GuestMemory::GpuMutex().HeldByThisThread() ? nullptr : Recorder::Active();
// The bytes are scanned only when no recorded work writes them: a pending clear kernel's keys are
// not in memory yet, so the bytes could read as uncompressed while the kernel will store a clear

View File

@ -62,6 +62,7 @@ GuestTextureResource SurfaceForTarget(const ColorTarget& color) {
surface.dstSelW = 7;
surface.dccAddress = color.dccAddress;
surface.dccAlphaOnMsb = color.dccAlphaOnMsb;
surface.dccPipeAligned = color.dccPipeAligned;
return surface;
}
@ -69,6 +70,11 @@ GuestTextureResource SurfaceForTarget(const ColorTarget& color) {
namespace {
std::size_t colorKeyCount(const ColorTarget& color, std::size_t bytes) {
if (color.mipCount != 1 || color.depth != 1 || !color.dccPipeAligned) return DccKeyBytes(bytes);
return DccKeyCount(ColorTextureTileMode(color.tileMode), color.elementBytes, color.extent.width, color.extent.height, bytes);
}
std::array<std::byte, 16> clearTexel(const ColorTarget& color, DccKeys keys) {
std::array<std::byte, 16> texel{};
const auto elementBytes = static_cast<std::size_t>(color.elementBytes);
@ -102,7 +108,7 @@ void storeClearTexels(const Context& context, const ColorTarget& color, const st
const auto keys = ReadDccKeys(color.dccAddress, color.bytes);
if (!IsDccClear(keys)) return;
writeTexels(color, keys == DccKeys::ClearRegister ? texel : clearTexel(color, keys));
MarkDccUncompressed(context, color.dccAddress, color.bytes);
MarkDccUncompressed(context, color.dccAddress, color.bytes, colorKeyCount(color, color.bytes));
}
void materializeRegisterClear(const Context& context, const ColorTarget& color, StorageTexture& resident) {
@ -121,7 +127,7 @@ void materializeRegisterClear(const Context& context, const ColorTarget& color,
}
}
if (cleared) {
MarkDccUncompressed(context, color.dccAddress, color.bytes);
MarkDccUncompressed(context, color.dccAddress, color.bytes, colorKeyCount(color, color.bytes));
return;
}
storeClearTexels(context, color, texel);
@ -1990,7 +1996,8 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
if (binding.gpuTiling) GuestMemory::WriteChanged(binding.color.address, binding.tiled->Bytes(), binding.original);
else WriteColorTarget(binding.color, binding.transfer->Bytes());
// The stored texels are the whole target now, so later reads must see them rather than a fast clear.
MarkDccUncompressed(binding.color.dccAddress, ColorTargetLayout(binding.color.extent.width, binding.color.extent.height, binding.color.tileMode, binding.color.elementBytes).Bytes());
const auto targetBytes = ColorTargetLayout(binding.color.extent.width, binding.color.extent.height, binding.color.tileMode, binding.color.elementBytes).Bytes();
MarkDccUncompressed(binding.color.dccAddress, targetBytes, colorKeyCount(binding.color, targetBytes));
}
timer.phase(PhaseWriteBack);
if (profile && traceDraws) {
@ -2159,7 +2166,7 @@ void RunColorMetadataPass(const Context& context, const ColorMetadataPass& pass)
const bool current = keys == DccKeys::ClearRegister ? clearToTexel(*resident, texel, color.elementBytes, refusal) : StorageTexture::FindPending(color.address, color.bytes) == resident || resident->UploadedKeys() == keys;
if (current) {
resident->MarkDirty();
MarkDccUncompressed(context, color.dccAddress, color.bytes);
MarkDccUncompressed(context, color.dccAddress, color.bytes, colorKeyCount(color, color.bytes));
continue;
}
}

View File

@ -115,7 +115,6 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
static_cast<void>(maxCompBlkSize);
// Surfaces are always written uncompressed here (render targets and storage images bypass DCC), so
// only the fast-clear keys in DCC metadata change what a read returns (see DccMetadata.hpp).
static_cast<void>(metaPipeAligned);
static_cast<void>(writeCompress);
if (dccColorTransf) {
static bool reported = false;
@ -193,6 +192,7 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
result.dstSelW = static_cast<std::uint8_t>(dstSelW);
result.dccAddress = metaCompress ? metaAddr << 8u : 0u;
result.dccAlphaOnMsb = dccAlphaPos;
result.dccPipeAligned = metaPipeAligned;
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());

View File

@ -772,6 +772,7 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot) {
const auto dccHigh = find(cx, 0x3a8 + slot);
color.dccAddress = ((dccHigh == cx.end() ? 0ull : static_cast<std::uint64_t>(dccHigh->second & 0xffu)) << 40u) | (static_cast<std::uint64_t>(read(cx, 0x325 + stride)) << 8u);
color.dccAlphaOnMsb = DccAlphaOnMsb(color.format, swap);
color.dccPipeAligned = ((attrib3 >> 30u) & 1u) != 0;
} else {
static bool reported = false;
if (!reported) {

View File

@ -1472,7 +1472,7 @@ void traceKeyStore(const char* path, const GuestTextureResource& descriptor, std
static const bool trace = std::getenv("APS5_TRACE_DCC_KEYS") != nullptr;
if (!trace || descriptor.dccAddress == 0) return;
const auto packet = GuestMemory::CurrentPacket();
std::fprintf(stderr, "[dcc-keys] uncompressed store by %s (%s) for surface 0x%llx+0x%llx: keys 0x%llx+0x%llx (packet 0x%x queue 0x%x)\n", path, flushReason != nullptr ? flushReason : "?", static_cast<unsigned long long>(descriptor.baseAddress), static_cast<unsigned long long>(guestBytes), static_cast<unsigned long long>(descriptor.dccAddress), static_cast<unsigned long long>(guestBytes / 256), packet.opcode, packet.queue);
std::fprintf(stderr, "[dcc-keys] uncompressed store by %s (%s) for surface 0x%llx+0x%llx: keys 0x%llx+0x%llx (packet 0x%x queue 0x%x)\n", path, flushReason != nullptr ? flushReason : "?", static_cast<unsigned long long>(descriptor.baseAddress), static_cast<unsigned long long>(guestBytes), static_cast<unsigned long long>(descriptor.dccAddress), static_cast<unsigned long long>(DccKeyCount(descriptor, guestBytes)), packet.opcode, packet.queue);
}
}
@ -2215,7 +2215,7 @@ bool StorageTexture::overlaps(std::uint64_t address, std::size_t bytes) const {
void StorageTexture::MarkDirty() {
if (descriptor.dccAddress != 0 && IsDccClear(uploadedKeys) && !IsDccClear(filledKeys)) {
traceKeyStore("first write", descriptor, guestBytes);
MarkDccUncompressed(context, descriptor.dccAddress, guestBytes);
MarkDccUncompressed(context, descriptor.dccAddress, guestBytes, DccKeyCount(descriptor, guestBytes));
uploadedKeys = DccKeys::Uncompressed;
}
markLayersPending(0, trackedLayers);
@ -3485,7 +3485,7 @@ void StorageTexture::writeBackLayers(const std::vector<bool>& layers) {
if (profile) Profile().storageGpu += timer.lap();
originalValid = false;
traceKeyStore("block write-back", descriptor, guestBytes);
if (!IsDccClear(filledKeys)) MarkDccUncompressed(context, descriptor.dccAddress, guestBytes);
if (!IsDccClear(filledKeys)) MarkDccUncompressed(context, descriptor.dccAddress, guestBytes, DccKeyCount(descriptor, guestBytes));
uploadedKeys = DccKeys::Uncompressed;
for (const auto& [from, to] : keep) GuestMemory::MarkWritten(from, static_cast<std::size_t>(to - from));
settle(true);
@ -3593,7 +3593,7 @@ void StorageTexture::writeBackLayers(const std::vector<bool>& layers) {
// metadata is host-imported (no CPU wait for the title's key-writing kernels), else a CPU
// store (APS5_CPU_DCC_KEYS=1 keeps the CPU store; see DccMetadata.hpp).
traceKeyStore("layer write-back", descriptor, guestBytes);
if (!IsDccClear(filledKeys)) MarkDccUncompressed(context, descriptor.dccAddress, guestBytes);
if (!IsDccClear(filledKeys)) MarkDccUncompressed(context, descriptor.dccAddress, guestBytes, DccKeyCount(descriptor, guestBytes));
uploadedKeys = DccKeys::Uncompressed;
for (const auto& [from, to] : keep) GuestMemory::MarkWritten(from, static_cast<std::size_t>(to - from));
// The walk covers this surface's pages only: an adjacent image's later CPU write is stamped
@ -3703,7 +3703,7 @@ void StorageTexture::writeBackLayers(const std::vector<bool>& layers) {
// The texels now hold the whole image, so later reads must see them rather than a fast clear
// (the keys may be host-imported although the texels were not: then a recorded fill, else a CPU store).
traceKeyStore("cpu write-back", descriptor, guestBytes);
if (!IsDccClear(filledKeys)) MarkDccUncompressed(context, descriptor.dccAddress, guestBytes);
if (!IsDccClear(filledKeys)) MarkDccUncompressed(context, descriptor.dccAddress, guestBytes, DccKeyCount(descriptor, guestBytes));
uploadedKeys = DccKeys::Uncompressed;
// The store above is the only write to these pages, so `original` is current at a fresh
// generation, unless blocks were kept for the CPU: then the image is stale there.

View File

@ -1,13 +1,30 @@
#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 {
@ -38,6 +55,193 @@ void ExpectPixels(const DisplayBuffer& buffer, DccKeys keys, std::array<unsigned
}
}
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() {
@ -77,6 +281,9 @@ int main() {
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;

View File

@ -25,6 +25,7 @@
#include <span>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <vector>
@ -983,6 +984,43 @@ void metadataPassTests() {
Require(AgcDriver::Graphics::FillDccClear(tenBitPass->targets[0].format, clear.keys, tenBitPass->targets[0].dccAlphaOnMsb, std::as_writable_bytes(std::span(copied))) && std::ranges::all_of(copied, [&](std::uint32_t word) { return word == clear.texel; }), "a copied " + name + " target under " + clear.code + " keys was not filled with its 10/10/10/2 texel");
}
}
constexpr std::uint32_t tiledSide = 128;
constexpr std::size_t tiledBytes = tiledSide * tiledSide * 4;
constexpr std::size_t blockAlignment = 65536;
const auto keyCount = AgcDriver::Graphics::DccKeyCount(AgcDriver::Graphics::TextureTileMode::kR64KBX, 4, tiledSide, tiledSide, tiledBytes);
Require(keyCount == 4096 && AgcDriver::Graphics::DccKeyBytes(tiledBytes) == 256, "the 128x128 SW_64KB_R_X DCC extent changed");
std::vector<std::byte> tiledBlock(blockAlignment + tiledBytes + keyCount);
const auto tiledAddress = (reinterpret_cast<std::uintptr_t>(tiledBlock.data()) + blockAlignment - 1) / blockAlignment * blockAlignment;
auto* tiledTexels = reinterpret_cast<std::uint8_t*>(tiledAddress);
auto* tiledKeys = tiledTexels + tiledBytes;
for (const bool pipeAligned : {true, false}) {
auto tiled = queue;
tiled.context[0x3b8] |= (static_cast<std::uint32_t>(AgcDriver::Graphics::ColorTileMode::RenderTarget) << 14u) | (pipeAligned ? 1u << 30u : 0u);
tiled.context[0x3b0] = ((tiledSide - 1u) << 14u) | (tiledSide - 1u);
tiled.context[0x318] = static_cast<std::uint32_t>(tiledAddress >> 8u);
tiled.context[0x390] = static_cast<std::uint32_t>(tiledAddress >> 40u);
tiled.context[0x325] = static_cast<std::uint32_t>((tiledAddress + tiledBytes) >> 8u);
tiled.context[0x3a8] = static_cast<std::uint32_t>((tiledAddress + tiledBytes) >> 40u);
for (const auto offset : {0xdu, 0x82u, 0x91u, 0x95u}) tiled.context[offset] = (tiledSide << 16u) | tiledSide;
tiled.context[0x10f] = std::bit_cast<std::uint32_t>(64.0f);
tiled.context[0x110] = std::bit_cast<std::uint32_t>(64.0f);
tiled.context[0x111] = std::bit_cast<std::uint32_t>(-64.0f);
tiled.context[0x112] = std::bit_cast<std::uint32_t>(64.0f);
const auto tiledPass = DecodeColorMetadataPass(tiled);
const std::string alignment = pipeAligned ? "pipe-aligned" : "unaligned";
Require(tiledPass.has_value() && tiledPass->targets.size() == 1 && tiledPass->targets[0].tileMode == AgcDriver::Graphics::ColorTileMode::RenderTarget && tiledPass->targets[0].bytes == tiledBytes && tiledPass->targets[0].dccAddress == tiledAddress + tiledBytes && tiledPass->targets[0].dccPipeAligned == pipeAligned, "the 128x128 SW_64KB_R_X metadata pass target with " + alignment + " DCC changed");
const auto expected = pipeAligned ? keyCount : AgcDriver::Graphics::DccKeyBytes(tiledBytes);
for (const auto& [key, texel, code] : {std::tuple{std::uint8_t{0x20}, 0x11223344u, "register"}, std::tuple{std::uint8_t{0xc0}, 0xffffffffu, "1111"}}) {
std::memset(tiledTexels, 0x5a, tiledBytes);
std::memset(tiledKeys, key, keyCount);
AgcDriver::Graphics::RunColorMetadataPass(context, *tiledPass);
const auto stored = static_cast<std::size_t>(std::count(tiledKeys, tiledKeys + keyCount, std::uint8_t{0xff}));
bool filled = true;
for (std::size_t offset = 0; offset < tiledBytes; offset += 4) filled = filled && std::memcmp(tiledTexels + offset, &texel, 4) == 0;
Require(filled && stored == expected && std::all_of(tiledKeys, tiledKeys + expected, [](std::uint8_t value) { return value == 0xff; }), std::string("a ") + code + " fast clear eliminate of a 128x128 SW_64KB_R_X target with " + alignment + " DCC stored uncompressed keys over " + std::to_string(stored) + " of its " + std::to_string(keyCount) + " DCC key bytes, expected " + std::to_string(expected));
}
}
}
alignas(256) std::array<std::uint8_t, 4096> cmaskMemory{};

View File

@ -1,10 +1,13 @@
#include "GraphicsTests.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
#include <algorithm>
#include <array>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace {
@ -301,6 +304,28 @@ void RunGuestTextureResourceTests() {
const auto compressed = DecodeTextureResource(pack(meta));
Require(compressed.dccAddress == 0x100 && compressed.dccAlphaOnMsb, "DCC metadata was decoded wrongly");
Fields storage = base;
storage.base40 = 0x1000;
storage.width = 1920;
storage.height = 1080;
storage.tileModeRaw = 27;
storage.metaCompress = true;
constexpr std::uint64_t storageBytes = 15u * 9u * 65536u;
constexpr std::size_t extentKeys = 49152;
std::vector<std::uint8_t> storageKeys(extentKeys + 256);
const auto keysAddress = (reinterpret_cast<std::uintptr_t>(storageKeys.data()) + 255u) / 256u * 256u;
const auto* keys = reinterpret_cast<const std::uint8_t*>(keysAddress);
storage.metaAddr = keysAddress >> 8u;
for (const bool pipeAligned : {true, false}) {
storage.metaPipeAligned = pipeAligned;
const auto image = DecodeTextureResource(pack(storage));
const auto expected = pipeAligned ? extentKeys : static_cast<std::size_t>(storageBytes / 256u);
std::fill(storageKeys.begin(), storageKeys.end(), std::uint8_t{0x00});
MarkDccUncompressed(image.dccAddress, storageBytes, DccKeyCount(image, storageBytes));
const auto stored = static_cast<std::size_t>(std::count(keys, keys + extentKeys, std::uint8_t{0xff}));
Require(image.dccPipeAligned == pipeAligned && stored == expected && std::all_of(keys, keys + expected, [](std::uint8_t key) { return key == 0xff; }), std::string("a 1920x1080 SW_64KB_R_X storage image with ") + (pipeAligned ? "pipe-aligned" : "unaligned") + " DCC stored " + std::to_string(stored) + " uncompressed keys, expected " + std::to_string(expected));
}
Fields badSwizzle = base;
badSwizzle.bcSwizzle = 1;
rejectFields(badSwizzle, "BC swizzle");

View File

@ -2219,6 +2219,18 @@ void pendingKeyStoreTests(const Device& device, Recorder& recorder) {
recorder.NotePendingWrite(address, 2 * keyCount);
Require(CurrentDccKeys(address, surfaceBytes) == DccKeys::Uncompressed && !recorder.PendingWriteOverlaps(address, keyCount), "a later wider writer over a pending fill was not waited for");
recorder.Sync();
constexpr std::uint64_t displayBytes = 15u * 9u * 65536u;
constexpr std::size_t consoleKeys = 49152;
const auto displayKeys = DccKeyCount(TextureTileMode::kR64KBX, 4, 1920, 1080, displayBytes);
std::memset(keys, 0x00, consoleKeys);
MarkDccUncompressed(context, address, displayBytes, displayKeys);
Require(recorder.PendingWriteOverlaps(address, consoleKeys) && keys[consoleKeys - 1] == 0x00, "the uncompressed key store over a 1920x1080 surface did not stay pending");
Require(CurrentDccKeys(address + displayBytes / 256, (consoleKeys - displayBytes / 256) * 256) == DccKeys::Uncompressed && recorder.PendingWriteOverlaps(address, consoleKeys), "the keys past one per 256 bytes of a pending 1920x1080 store did not read as uncompressed without a wait");
Require(CurrentDccKeys(address, displayBytes, displayKeys) == DccKeys::Uncompressed && recorder.PendingWriteOverlaps(address, consoleKeys), "the keys of a pending 1920x1080 store did not read as uncompressed over the console's extent without a wait");
recorder.NotePendingWrite(address + 40000, 1);
Require(CurrentDccKeys(address, displayBytes, displayKeys) == DccKeys::Uncompressed && !recorder.PendingWriteOverlaps(address + 40000, 1), "a later writer of key 40000 over a pending 1920x1080 store was not waited for");
Require(std::all_of(keys, keys + consoleKeys, [](std::uint8_t key) { return key == 0xff; }), "the uncompressed key store over a 1920x1080 surface did not land on all 49152 keys");
recorder.Sync();
}
void sampleDumpTests(const Device& device, Recorder& recorder) {
@ -2335,6 +2347,7 @@ void metadataPassTests(const Device& device, Recorder& recorder) {
color.tileMode = ColorTileMode::RenderTarget;
color.elementBytes = 4;
color.dccAddress = address + surfaceBytes;
color.dccPipeAligned = true;
color.clearWords = {0x80402010u, 0};
const ColorMetadataPass pass{ColorMetadataPass::Mode::EliminateFastClear, {color}};
const auto memoryHolds = [&](std::array<std::uint8_t, 4> texel) {
@ -2350,13 +2363,15 @@ void metadataPassTests(const Device& device, Recorder& recorder) {
return true;
};
const auto keysUncompressed = [&] { return ReadDccKeys(color.dccAddress, surfaceBytes) == DccKeys::Uncompressed; };
const auto extent = DccKeyCount(TextureTileMode::kR64KBX, 4, side, side, surfaceBytes);
std::memset(texels, 0x55, surfaceBytes);
std::memset(keys, 0x20, keyCount);
std::memset(keys, 0x20, extent);
RunColorMetadataPass(context, pass);
Require(StorageTexture::FindPending(address, surfaceBytes) != nullptr && texels[0] == 0x55, "the register fast clear eliminate did not stay in the resident image");
Require(keysUncompressed(), "the register fast clear eliminate left the keys compressed");
Require(CurrentDccKeys(color.dccAddress, surfaceBytes, extent) == DccKeys::Uncompressed, "the register fast clear eliminate did not store uncompressed keys over the target's console DCC extent");
Require(memoryHolds({0x10, 0x20, 0x40, 0x80}), "the register fast clear eliminate did not store CB_COLOR_CLEAR_WORD");
Require(std::all_of(keys, keys + keyCount, [](std::uint8_t key) { return key == 0xff; }), "the stored keys are not uncompressed");
Require(std::all_of(keys, keys + extent, [](std::uint8_t key) { return key == 0xff; }), "the stored keys are not uncompressed");
std::memset(keys, 0xc0, keyCount);
RunColorMetadataPass(context, {ColorMetadataPass::Mode::DccDecompress, {color}});
Require(StorageTexture::FindPending(address, surfaceBytes) != nullptr, "the DCC decompress did not stay in the resident image");

View File

@ -327,7 +327,7 @@ void testCompressedPresentation() {
std::vector<std::byte> allocation(6 * 65536 + 65535);
const auto storage = alignedBuffer(allocation);
fillBuffer(storage, 259, 137);
std::vector<std::uint8_t> keys(storage.size() / 256);
std::vector<std::uint8_t> keys(4096);
VideoOutBuffers buffer{storage.data(), keys.data(), {nullptr, nullptr}};
VideoOutBufferAttribute2 attribute{};
sceVideoOutSetBufferAttribute2(&attribute, 0x8000000000000000ull, 0, 259, 137, 0, 0x208, 0xff102030);

View File

@ -300,7 +300,8 @@ Throughout the project, every function at every stage either **does exactly what
- [CMASK fast clears](../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - only a CMASK whose nibbles are all fast-cleared (0) or all expanded (0xF) is modeled, checked over the target's whole CMASK, 4 KiB per 1024x512-pixel metablock (the size PS5 titles clear, for every color format seen); the addressing within a metablock is not decoded. The first draw or fast-clear eliminate over a fast-cleared CMASK fills the whole target with `CB_COLOR_CLEAR_WORD` and stores those bytes as expanded, so a draw that covers only part of the target reads the clear value in the rest as the eliminate would leave it. On a DCC target the clear is in the DCC keys, so its CMASK must be all expanded. A mixed CMASK throws, as does a DCC target's CMASK that is not all expanded and a fast clear of a mipmapped, 3D or array color target
- [Host imports](../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver) on Linux skip ranges that are not mapped read-write, which are copied instead. The NVIDIA driver (595.91.07) refuses `VK_EXT_external_memory_host` imports of read-only or executable mappings (and shared mappings of regular files) with `VK_ERROR_OUT_OF_DEVICE_MEMORY`, after which every `vkAllocateMemory` on the device fails the same way. Read-only ranges are never imported, even on drivers that accept them. Shared mappings of regular files are not checked for.
- [Cube texture descriptors](../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp) with equal `BASE_ARRAY` and `DEPTH` expose six faces starting at `BASE_ARRAY`. Measured with `image_load`, `image_sample` and `image_get_resinfo` on RX 6600 (gfx1032), using 64x64 R32_UINT faces and base slices 0, 5 and 6. Other ranges containing fewer than six faces or incomplete cube arrays still throw: hardware load bounds and filtered-sample addressing differ. Seamless cube filtering and out-of-range cube-array sampling remain unimplemented. PS5 equivalence has not been measured on console hardware.
- [DCC display buffers](../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) are presented only from uniform uncompressed or fast-clear keys; other keys, and register-clear keys over a pending image, throw. Keys are read as one byte per 256 bytes (the driver's model), not in the console's layout.
- [DCC display buffers](../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) are presented only when all keys of the console's DCC extent agree on uncompressed or a fast-clear code, padding keys included; other keys, and register-clear keys over a pending image, throw.
- [DCC key stores](../../core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp) cover the console's DCC extent only for pipe-aligned DCC of single-level, single-layer 4-byte SW_64KB_R_X surfaces; other surfaces, and every texture and color target key read, cover one byte per 256 bytes.
- [8_SRGB color targets](../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) on a device that cannot attach and blend R8_SRGB render into an RGBA8_SRGB copy of the target, so the sRGB encode, decode and blend rounding are the device's; the console's are not measured.
- [8_SRGB color targets](../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) on such a device throw unless they are single-mip 2D surfaces with a resident image; linear targets have none.
- [COPY_DATA from the GPU clock](../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, `SRC_SEL` 9) writes the host steady clock in 10 ns ticks when the packet runs, the same counter as the `RELEASE_MEM` timestamp, not the console's GPU clock. The `COPY_DATA` cache policy fields are accepted and have no effect.