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:
commit
74b5c9103b
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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{};
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue