Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);

}

Expand Down
65 changes: 65 additions & 0 deletions core/libs/prx/libSceAgcDriver/Execution/src/DisplayBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
#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>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>

namespace AgcDriver {
namespace {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down
27 changes: 10 additions & 17 deletions core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1950,37 +1950,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);
}
Expand Down Expand Up @@ -2034,7 +2027,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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
1 change: 1 addition & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
76 changes: 59 additions & 17 deletions core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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]{};
Expand All @@ -444,32 +458,56 @@ 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;
Recorder::CountSync(2);
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) {
return keys == DccKeys::Clear0000 || keys == DccKeys::Clear0001 || keys == DccKeys::Clear1110 || keys == DccKeys::Clear1111 || keys == DccKeys::ClearRegister;
}

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
Expand All @@ -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
Expand Down
Loading
Loading