diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp index 5c3ef004f..24a7be45b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp @@ -28,6 +28,8 @@ std::size_t DisplayBufferSize(const DisplayBuffer& buffer); std::vector DecodeDisplayBuffer(const DisplayBuffer& buffer, std::span source); std::vector ReadDisplayBuffer(const DisplayBuffer& buffer); std::array DisplayBufferClearPixel(const DisplayBuffer& buffer, Graphics::DccKeys keys); +std::size_t DisplayBufferKeyBytes(const DisplayBuffer& buffer); +Graphics::DccKeys DisplayBufferKeys(const DisplayBuffer& buffer); } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/DisplayBuffer.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/DisplayBuffer.cpp index 459b6e228..8bc53af7d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/DisplayBuffer.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/DisplayBuffer.cpp @@ -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 #include #include #include @@ -10,6 +11,7 @@ #include #include #include +#include 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 displayableKeyPixel(const DisplayBuffer& buffer, std::size_t key) { + const auto pitch = (buffer.width + MetaBlockEdge - 1u) / MetaBlockEdge; + const auto block = static_cast(key / MetaBlockKeys); + const auto offset = static_cast(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::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(buffer.dccAddress), count, runs.c_str()); +} + } std::size_t DisplayBufferSize(const DisplayBuffer& buffer) { @@ -117,6 +153,35 @@ std::array 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(buffer.dccAddress), count)) { + const auto* bytes = reinterpret_cast(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(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(buffer.address), Graphics::DccKeysName(keys), static_cast(buffer.dccAddress), count, differing); + throw std::runtime_error(message); +} + std::vector ReadDisplayBuffer(const DisplayBuffer& buffer) { const auto size = DisplayBufferSize(buffer); static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 448b51a7a..46d037fe7 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -1952,37 +1952,30 @@ std::shared_ptr 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(buffer.address), Graphics::DccKeysName(keys), static_cast(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(buffer.address), static_cast(buffer.dccAddress), static_cast(resident.Descriptor().baseAddress), static_cast(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> 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; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp index 8808fb712..109a02b39 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp @@ -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 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. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp index 6397574f3..aa0765d12 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp @@ -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; }; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index e54f660f7..123dbe52a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -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; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp index c6deb8e28..5e4729b1f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp @@ -392,13 +392,27 @@ std::size_t DccKeyBytes(std::uint64_t surfaceBytes) { return static_cast(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(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(surfaceBytes / KeyBytes); if (metaAddress == 0 || count == 0 || !GuestMemory::Accessible(reinterpret_cast(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(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(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(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(surfaceBytes / KeyBytes); - if (metaAddress == 0 || count == 0 || !GuestMemory::Accessible(reinterpret_cast(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(surfaceBytes / KeyBytes); - if (metaAddress == 0 || count == 0 || !GuestMemory::Accessible(reinterpret_cast(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 diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 6a821c3d0..646daec81 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -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 clearTexel(const ColorTarget& color, DccKeys keys) { std::array texel{}; const auto elementBytes = static_cast(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; } } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp index 9875b4d56..f97d82c06 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp @@ -115,7 +115,6 @@ GuestTextureResource DecodeTextureResource(std::span words) static_cast(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(metaPipeAligned); static_cast(writeCompress); if (dccColorTransf) { static bool reported = false; @@ -193,6 +192,7 @@ GuestTextureResource DecodeTextureResource(std::span words) result.dstSelW = static_cast(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()); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index e16378102..dc0ab6fb9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -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(dccHigh->second & 0xffu)) << 40u) | (static_cast(read(cx, 0x325 + stride)) << 8u); color.dccAlphaOnMsb = DccAlphaOnMsb(color.format, swap); + color.dccPipeAligned = ((attrib3 >> 30u) & 1u) != 0; } else { static bool reported = false; if (!reported) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index ccd60a9b0..86c3338eb 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -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(descriptor.baseAddress), static_cast(guestBytes), static_cast(descriptor.dccAddress), static_cast(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(descriptor.baseAddress), static_cast(guestBytes), static_cast(descriptor.dccAddress), static_cast(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& 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(to - from)); settle(true); @@ -3593,7 +3593,7 @@ void StorageTexture::writeBackLayers(const std::vector& 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(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& 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. diff --git a/core/libs/prx/libSceAgcDriver/tests/DisplayBufferDcc.cpp b/core/libs/prx/libSceAgcDriver/tests/DisplayBufferDcc.cpp index b2173b7ec..f5f1f159f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/DisplayBufferDcc.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/DisplayBufferDcc.cpp @@ -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 #include +#include #include #include +#include #include #include +#include +#include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif using AgcDriver::DisplayBuffer; using AgcDriver::Graphics::DccKeys; +using AgcDriver::Graphics::GuestTextureResource; +using AgcDriver::Graphics::TextureDimension; +using AgcDriver::Graphics::TextureTileMode; namespace { @@ -38,6 +55,193 @@ void ExpectPixels(const DisplayBuffer& buffer, DccKeys keys, std::array; +constexpr DccPattern PipeAligned{X(3), Y(4), X(6), Y(6), X(7), Y(7), X(8), Y(8), X(3) ^ Y(3), X(4) ^ Y(4), X(6) ^ Y(5), X(5) ^ Y(6)}; +constexpr DccPattern Displayable{X(3), Y(3), X(4), Y(4), X(5), Y(5), X(6), Y(6), X(7), Y(7), X(8), Y(8)}; + +std::size_t PatternKey(const DccPattern& pattern, std::uint32_t width, std::uint32_t x, std::uint32_t y) { + std::size_t offset = 0; + for (std::size_t bit = 0; bit < pattern.size(); ++bit) offset |= static_cast((std::popcount(x & pattern[bit].x) + std::popcount(y & pattern[bit].y)) & 1) << bit; + return (static_cast(y / 512u) * ((width + 511u) / 512u) + x / 512u) * 4096u + offset; +} + +void Retile(std::span render, std::span display, std::uint32_t width, std::uint32_t height) { + for (std::uint32_t y = 0; y < (height + 511u) / 512u * 512u; y += 8) { + for (std::uint32_t x = 0; x < (width + 511u) / 512u * 512u; x += 8) display[PatternKey(Displayable, width, x, y)] = render[PatternKey(PipeAligned, width, x, y)]; + } +} + +struct KeyHistogram { + std::size_t uncompressed = 0; + std::size_t clear0000 = 0; + std::size_t firstDiffering = 0; + std::size_t zeroRuns = 0; + bool operator==(const KeyHistogram&) const = default; +}; + +KeyHistogram Histogram(std::span keys) { + KeyHistogram histogram; + for (std::size_t key = 0; key < keys.size(); ++key) { + histogram.uncompressed += keys[key] == 0xff; + histogram.clear0000 += keys[key] == 0x00; + histogram.zeroRuns += keys[key] == 0x00 && (key == 0 || keys[key - 1] != 0x00); + if (histogram.firstDiffering == 0 && keys[key] != keys[0]) histogram.firstDiffering = key; + } + return histogram; +} + +std::string Describe(const KeyHistogram& histogram) { + return std::to_string(histogram.uncompressed) + " x 0xff, " + std::to_string(histogram.clear0000) + " x 0x00, first differing key " + std::to_string(histogram.firstDiffering) + ", " + std::to_string(histogram.zeroRuns) + " runs of 0x00"; +} + +std::uint64_t TiledBytes(std::uint32_t width, std::uint32_t height) { + return static_cast((width + 127u) / 128u) * ((height + 127u) / 128u) * 65536u; +} + +GuestTextureResource Surface(std::uint32_t width, std::uint32_t height, std::uint64_t dccAddress, bool pipeAligned) { + GuestTextureResource surface{}; + surface.baseAddress = 0x10000; + surface.width = width; + surface.height = height; + surface.mipCount = 1; + surface.tileMode = TextureTileMode::kR64KBX; + surface.dimension = TextureDimension::k2D; + surface.format = AgcDriver::Graphics::FindGuestColorTargetFormat(VK_FORMAT_B8G8R8A8_UNORM, 4).value(); + surface.dccAddress = dccAddress; + surface.dccPipeAligned = pipeAligned; + return surface; +} + +std::uint8_t* ReserveKeys(std::size_t writable, std::size_t reserved) { +#ifdef _WIN32 + auto* block = static_cast(VirtualAlloc(nullptr, reserved, MEM_RESERVE, PAGE_NOACCESS)); + if (block == nullptr || VirtualAlloc(block, writable, MEM_COMMIT, PAGE_READWRITE) == nullptr) return nullptr; +#else + void* mapped = mmap(nullptr, reserved, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mapped == MAP_FAILED || mprotect(mapped, writable, PROT_READ | PROT_WRITE) != 0) return nullptr; + auto* block = static_cast(mapped); +#endif + return block; +} + +void ReleaseKeys(std::uint8_t* block, std::size_t reserved) { +#ifdef _WIN32 + static_cast(reserved); + VirtualFree(block, 0, MEM_RELEASE); +#else + munmap(block, reserved); +#endif +} + +void KeyCountTests() { + struct Extent { + std::uint32_t width; + std::uint32_t height; + std::size_t keys; + }; + for (const auto& extent : {Extent{1920, 1080, 49152}, Extent{3840, 2160, 163840}, Extent{1280, 720, 24576}, Extent{259, 137, 4096}, Extent{128, 128, 4096}, Extent{2560, 1440, 61440}}) { + const auto surfaceBytes = TiledBytes(extent.width, extent.height); + const auto name = std::to_string(extent.width) + "x" + std::to_string(extent.height); + const auto keys = AgcDriver::Graphics::DccKeyCount(TextureTileMode::kR64KBX, 4, extent.width, extent.height, surfaceBytes); + Expect(keys == extent.keys, name + " SW_64KB_R_X 4-byte DCC covers " + std::to_string(keys) + " key bytes, expected " + std::to_string(extent.keys)); + if (extent.keys == surfaceBytes / 256) continue; + const auto aligned = AgcDriver::Graphics::DccKeyCount(Surface(extent.width, extent.height, 0, true), surfaceBytes); + const auto unaligned = AgcDriver::Graphics::DccKeyCount(Surface(extent.width, extent.height, 0, false), surfaceBytes); + Expect(aligned == extent.keys && unaligned == surfaceBytes / 256, name + " surface descriptors' DCC covers " + std::to_string(aligned) + " key bytes pipe-aligned and " + std::to_string(unaligned) + " unaligned, expected " + std::to_string(extent.keys) + " and " + std::to_string(surfaceBytes / 256)); + } + Expect(TiledBytes(2560, 1440) / 256 == 61440, "2560x1440 does not fill its meta blocks"); + const auto bytes = TiledBytes(1920, 1080); + Expect(AgcDriver::Graphics::DccKeyCount(TextureTileMode::kD64KBX, 4, 1920, 1080, bytes) == bytes / 256, "an SW_64KB_D_X surface's DCC left one key per 256 bytes"); + Expect(AgcDriver::Graphics::DccKeyCount(TextureTileMode::kR64KBX, 8, 1920, 1080, 2 * bytes) == 2 * bytes / 256, "an 8-byte surface's DCC left one key per 256 bytes"); + const auto surface = Surface(1920, 1080, 0, true); + auto shape = surface; + shape.mipCount = 2; + Expect(AgcDriver::Graphics::DccKeyCount(shape, bytes + 65536) == (bytes + 65536) / 256, "a mip chain's DCC left one key per 256 bytes"); + shape = surface; + shape.dimension = TextureDimension::k2DArray; + shape.depthOrLastArray = 1; + Expect(AgcDriver::Graphics::DccKeyCount(shape, 2 * bytes) == 2 * bytes / 256, "a two-layer surface's DCC left one key per 256 bytes"); + shape = surface; + shape.format = AgcDriver::Graphics::FindGuestTextureFormat(VK_FORMAT_R16G16B16A16_SFLOAT, 8).value(); + Expect(AgcDriver::Graphics::DccKeyCount(shape, 2 * bytes) == 2 * bytes / 256, "an 8-byte surface descriptor's DCC left one key per 256 bytes"); + Expect(AgcDriver::DisplayBufferKeyBytes({65536, 0x8000000000000000ull, 1920, 1080}) == 49152 && AgcDriver::DisplayBufferKeyBytes({65536, 0x8000000000000000ull, 259, 137}) == 4096, "display buffers do not read the console's DCC extent"); +} + +void RetileTests() { + constexpr std::uint32_t width = 1920; + constexpr std::uint32_t height = 1080; + const auto surfaceBytes = TiledBytes(width, height); + const auto driverKeys = static_cast(surfaceBytes / 256); + std::vector render(49152); + std::vector display(render.size()); + std::fill_n(render.begin(), driverKeys, 0xff); + Retile(render, display, width, height); + const auto reported = Histogram(std::span(display).first(driverKeys)); + Expect(reported == KeyHistogram{33552, 1008, 32784, 105}, "a retile of 0xff over the first 34560 render keys does not give the PPSA12544 display keys: " + Describe(reported)); + std::fill(render.begin(), render.end(), 0x00); + std::fill(display.begin(), display.end(), 0x00); + const auto surface = Surface(width, height, reinterpret_cast(render.data()), true); + AgcDriver::Graphics::MarkDccUncompressed(surface.dccAddress, surfaceBytes, AgcDriver::Graphics::DccKeyCount(surface, surfaceBytes)); + Retile(render, display, width, height); + DisplayBuffer buffer{65536, 0x8000000000000000ull, width, height}; + buffer.dccAddress = reinterpret_cast(display.data()); + auto keys = DccKeys::Unreadable; + const auto message = Rejection([&] { keys = AgcDriver::DisplayBufferKeys(buffer); }); + Expect(message.empty() && keys == DccKeys::Uncompressed, "the driver's store over a 1920x1080 render DCC, retiled, does not present: " + message + " (display keys over the first " + std::to_string(driverKeys) + ": " + Describe(Histogram(std::span(display).first(driverKeys))) + ")"); + + constexpr std::size_t reserved = 65536; + constexpr std::size_t writable = 0x9000; + auto* small = ReserveKeys(writable, reserved); + Expect(small != nullptr, "cannot reserve metadata with an inaccessible tail"); + if (small == nullptr) return; + const auto stored = Rejection([&] { AgcDriver::Graphics::MarkDccUncompressed(reinterpret_cast(small), surfaceBytes, AgcDriver::Graphics::DccKeyCount(surface, surfaceBytes)); }); + Expect(stored.find("not writable over the 0xc000 key bytes") != std::string::npos && small[0] == 0x00, "a key store over metadata smaller than the console's extent did not throw: " + stored); + const auto unaligned = Rejection([&] { AgcDriver::Graphics::MarkDccUncompressed(reinterpret_cast(small), surfaceBytes, AgcDriver::Graphics::DccKeyCount(Surface(width, height, reinterpret_cast(small), false), surfaceBytes)); }); + Expect(unaligned.empty() && std::all_of(small, small + driverKeys, [](std::uint8_t key) { return key == 0xff; }) && small[driverKeys] == 0x00, "an unaligned DCC key store over metadata smaller than the console's extent did not cover one key per 256 bytes: " + unaligned); + ReleaseKeys(small, reserved); +} + +DccKeys ReadKeys(DisplayBuffer buffer, std::vector& keys) { + buffer.dccAddress = reinterpret_cast(keys.data()); + return AgcDriver::DisplayBufferKeys(buffer); +} + +void ExtentTests() { + const DisplayBuffer full{65536, 0x8000000000000000ull, 1920, 1080}; + std::vector keys(49152); + for (const auto& [code, expected] : {std::pair{std::uint8_t{0xff}, DccKeys::Uncompressed}, std::pair{std::uint8_t{0x00}, DccKeys::Clear0000}, std::pair{std::uint8_t{0xc0}, DccKeys::Clear1111}, std::pair{std::uint8_t{0x20}, DccKeys::ClearRegister}}) { + std::fill(keys.begin(), keys.end(), code); + auto read = DccKeys::Unreadable; + const auto message = Rejection([&] { read = ReadKeys(full, keys); }); + Expect(message.empty() && read == expected, std::string("uniform ") + AgcDriver::Graphics::DccKeysName(expected) + " keys over the extent read " + AgcDriver::Graphics::DccKeysName(read) + ": " + message); + } + std::fill(keys.begin(), keys.end(), 0xff); + const auto corner = PatternKey(Displayable, 1920, 1919, 1079); + Expect(corner == 46205, "pixel (1919, 1079) is not displayable key 46205"); + keys[corner] = 0x00; + auto message = Rejection([&] { ReadKeys(full, keys); }); + Expect(message.find("mixed") != std::string::npos && message.find("key 46205 (pixels 1912..1919 x 1072..1079 in the displayable order) is 0x00, key 0 is 0xff") != std::string::npos, "a 0000 key past one key per 256 bytes was not read or not named: " + message); + std::fill(keys.begin(), keys.end(), 0x40); + keys[0] = 0x80; + message = Rejection([&] { ReadKeys(full, keys); }); + Expect(message.find("key 1 (pixels 8..15 x 0..7 in the displayable order) is 0x40, key 0 is 0x80") != std::string::npos, "a 1110 key 0 among 0001 keys was not named: " + message); + const DisplayBuffer odd{65536, 0x8000000000000000ull, 259, 137}; + keys.assign(4096, 0x40); + keys.back() = 0xff; + message = Rejection([&] { ReadKeys(odd, keys); }); + Expect(message.find("key 4095 (pixels 504..511 x 504..511 in the displayable order) is 0xff") != std::string::npos, "259x137: an uncompressed padding key among 0001 keys was ignored: " + message); + Expect(!Rejection([&] { AgcDriver::DisplayBufferKeys(full); }).empty(), "a display buffer without DCC metadata had keys read"); +} + } int main() { @@ -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; diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 547221197..220231386 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -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 tiledBlock(blockAlignment + tiledBytes + keyCount); + const auto tiledAddress = (reinterpret_cast(tiledBlock.data()) + blockAlignment - 1) / blockAlignment * blockAlignment; + auto* tiledTexels = reinterpret_cast(tiledAddress); + auto* tiledKeys = tiledTexels + tiledBytes; + for (const bool pipeAligned : {true, false}) { + auto tiled = queue; + tiled.context[0x3b8] |= (static_cast(AgcDriver::Graphics::ColorTileMode::RenderTarget) << 14u) | (pipeAligned ? 1u << 30u : 0u); + tiled.context[0x3b0] = ((tiledSide - 1u) << 14u) | (tiledSide - 1u); + tiled.context[0x318] = static_cast(tiledAddress >> 8u); + tiled.context[0x390] = static_cast(tiledAddress >> 40u); + tiled.context[0x325] = static_cast((tiledAddress + tiledBytes) >> 8u); + tiled.context[0x3a8] = static_cast((tiledAddress + tiledBytes) >> 40u); + for (const auto offset : {0xdu, 0x82u, 0x91u, 0x95u}) tiled.context[offset] = (tiledSide << 16u) | tiledSide; + tiled.context[0x10f] = std::bit_cast(64.0f); + tiled.context[0x110] = std::bit_cast(64.0f); + tiled.context[0x111] = std::bit_cast(-64.0f); + tiled.context[0x112] = std::bit_cast(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::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 cmaskMemory{}; diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp index 7e0252e71..44b30348e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp @@ -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 #include #include #include #include +#include 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 storageKeys(extentKeys + 256); + const auto keysAddress = (reinterpret_cast(storageKeys.data()) + 255u) / 256u * 256u; + const auto* keys = reinterpret_cast(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(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::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"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index a7d691a7a..196642f7d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -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 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"); diff --git a/core/libs/prx/libSceVideoOut/tests/Flip.cpp b/core/libs/prx/libSceVideoOut/tests/Flip.cpp index 046e79283..ab54f4035 100644 --- a/core/libs/prx/libSceVideoOut/tests/Flip.cpp +++ b/core/libs/prx/libSceVideoOut/tests/Flip.cpp @@ -327,7 +327,7 @@ void testCompressedPresentation() { std::vector allocation(6 * 65536 + 65535); const auto storage = alignedBuffer(allocation); fillBuffer(storage, 259, 137); - std::vector keys(storage.size() / 256); + std::vector keys(4096); VideoOutBuffers buffer{storage.data(), keys.data(), {nullptr, nullptr}}; VideoOutBufferAttribute2 attribute{}; sceVideoOutSetBufferAttribute2(&attribute, 0x8000000000000000ull, 0, 259, 137, 0, 0x208, 0xff102030); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index acd007b3c..237cef4bf 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -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.