diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp index d2e9dff3a6..5e05ec233f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp @@ -39,6 +39,7 @@ 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); std::optional KnownPendingDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes); +std::optional CurrentUniformMetadataByte(std::uint64_t address, std::size_t bytes); // 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); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp index 98d65e8c03..3aece028bc 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp @@ -17,12 +17,14 @@ class StorageTexture; class Texture; struct GuestTextureResource; +std::optional MultisampledCmaskKeys(std::uint8_t code); + class MultisampledCmask { public: MultisampledCmask() = default; MultisampledCmask(std::uint64_t cmaskAddress, std::uint64_t cmaskBytes) : address(cmaskAddress), bytes(cmaskBytes) {} void NoteFill(std::uint64_t fillAddress, std::size_t fillBytes, std::uint32_t pattern); - void SeedKeys(DccKeys seeded); + void SeedCode(std::optional code); void CheckAddress(std::uint64_t cmaskAddress) const; bool TakeClear(); DccKeys Keys() const { return keys; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp index f8f4d371ec..7491f9bc8a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp @@ -580,6 +580,25 @@ DccKeys CurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes, st return currentDccKeys(metaAddress, keyCount, memoized); } +std::optional CurrentUniformMetadataByte(std::uint64_t address, std::size_t bytes) { + if (address == 0 || bytes == 0 || !GuestMemory::Accessible(reinterpret_cast(address), bytes)) return std::nullopt; + if (GuestMemory::GpuMutex().HeldByThisThread()) { + if (AnyShadowedOverlaps(address, bytes)) PublishShadow(address, bytes, PublishScope::Whole, PublishReason::Keys); + if (auto* recorder = Recorder::Active(); recorder != nullptr && (recorder->PendingWriteOverlaps(address, bytes) || AnyShadowedOverlaps(address, bytes))) { + const auto landed = KeysAfterPendingWrites(*recorder, address, bytes); + if (landed.mixed) return std::nullopt; + if (!landed.unknown && landed.value >= 0 && !AnyShadowedOverlaps(address, bytes)) return static_cast(landed.value); + Recorder::CountSync(2); + recorder->Sync(); + } + } + std::vector data(bytes); + GuestMemory::Read(address, data); + const auto first = data.front(); + if (!std::all_of(data.begin(), data.end(), [&](std::byte value) { return value == first; })) return std::nullopt; + return std::to_integer(first); +} + std::optional KnownPendingDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes) { const auto count = DccKeyBytes(surfaceBytes); if (metaAddress == 0 || count == 0 || !GuestMemory::GpuMutex().HeldByThisThread()) return std::nullopt; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp index 5fc7544a28..a1abe86657 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp @@ -454,7 +454,7 @@ MultisampleTarget& acquire(const Context& context, const ColorTarget& use) { targets().push_back(std::make_unique(context, use)); auto& created = *targets().back(); guard(context, use, created.active, created.metadataValid); - if (use.cmaskFastClear && use.dccAddress == 0) created.cmask.SeedKeys(CurrentDccKeys(use.cmaskAddress, static_cast(use.cmaskBytes) * 256u)); + if (use.cmaskFastClear && use.dccAddress == 0) created.cmask.SeedCode(CurrentUniformMetadataByte(use.cmaskAddress, static_cast(use.cmaskBytes))); created.ApplyFastClear(use); return created; } @@ -523,6 +523,14 @@ void UpdateResidentImageUnmapObserver_nid_no_patch() { GuestAllocations::GuestAllocationsSetUnmapObserver_nid_postfix(anyGuarded().load(std::memory_order_acquire) || HasResidentDepthGuards_nid_no_patch() ? &retireUnmapped : nullptr); } +std::optional MultisampledCmaskKeys(std::uint8_t code) { + const auto tile = code & 0xfu; + if ((code >> 4u) != tile) return std::nullopt; + if (tile == 0u) return DccKeys::Clear0000; + if ((tile >> 2u) == 3u) return DccKeys::Uncompressed; + return std::nullopt; +} + void MultisampledCmask::NoteFill(std::uint64_t fillAddress, std::size_t fillBytes, std::uint32_t pattern) { if (address == 0 || fillAddress >= address + bytes || address >= fillAddress + fillBytes) return; if (fillAddress > address || fillAddress + fillBytes < address + bytes) { @@ -532,24 +540,24 @@ void MultisampledCmask::NoteFill(std::uint64_t fillAddress, std::size_t fillByte return; } refusal.clear(); - if (pattern == 0) { - keys = DccKeys::Clear0000; - } else if (pattern == 0xffffffffu) { - keys = DccKeys::Uncompressed; + const auto code = static_cast(pattern); + if (const auto filled = MultisampledCmaskKeys(code); filled && pattern == code * 0x01010101u) { + keys = *filled; } else { - char text[192]; - std::snprintf(text, sizeof(text), "a fill of the CMASK 0x%llx+0x%llx of a multisampled color target with 0x%08x leaves its tiles mixed (neither fast-cleared 0 nor expanded 0xffffffff)", static_cast(address), static_cast(bytes), pattern); + char text[224]; + std::snprintf(text, sizeof(text), "a fill of the CMASK 0x%llx+0x%llx of a multisampled color target with 0x%08x leaves its tiles mixed (neither a register color clear 0 nor a color-expanded code such as 0xcc or 0xff)", static_cast(address), static_cast(bytes), pattern); refusal = text; } } -void MultisampledCmask::SeedKeys(DccKeys seeded) { - if (seeded == DccKeys::Clear0000 || seeded == DccKeys::Uncompressed) { - keys = seeded; +void MultisampledCmask::SeedCode(std::optional code) { + if (const auto seeded = code ? MultisampledCmaskKeys(*code) : std::nullopt) { + keys = *seeded; return; } char text[192]; - std::snprintf(text, sizeof(text), "the CMASK 0x%llx of a multisampled color target reads as %s, which is not modeled", static_cast(address), DccKeysName(seeded)); + if (code) std::snprintf(text, sizeof(text), "the CMASK 0x%llx of a multisampled color target reads as code 0x%02x, which is not modeled", static_cast(address), *code); + else std::snprintf(text, sizeof(text), "the CMASK 0x%llx of a multisampled color target reads as mixed, which is not modeled", static_cast(address)); refusal = text; } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index cbba137501..773bc2986b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -3061,12 +3061,28 @@ void multisampleTests() { cmask.CheckAddress(0x10000); expectFailure([&] { cmask.CheckAddress(0x20000); }, "used CMASK 0x20000"); MultisampledCmask seeded(0x30000, 0x1000); - seeded.SeedKeys(DccKeys::Clear0000); + seeded.SeedCode(0x00u); Require(seeded.TakeClear(), "a fast-cleared seed did not clear its first use"); Require(!seeded.TakeClear(), "a subsequent use applied the clear again"); MultisampledCmask mixedSeed(0x30000, 0x1000); - mixedSeed.SeedKeys(DccKeys::Mixed); - expectFailure([&] { mixedSeed.TakeClear(); }, "not modeled"); + mixedSeed.SeedCode(std::nullopt); + expectFailure([&] { mixedSeed.TakeClear(); }, "reads as mixed, which is not modeled"); + for (const std::uint8_t expanded : {0xccu, 0xddu, 0xeeu, 0xffu}) { + MultisampledCmask expandedSeed(0x30000, 0x1000); + expandedSeed.SeedCode(expanded); + Require(!expandedSeed.TakeClear() && expandedSeed.Keys() == DccKeys::Uncompressed, "a color-expanded CMASK seed applied a fast clear or was refused"); + MultisampledCmask expandedFill(0x30000, 0x1000); + expandedFill.NoteFill(0x30000, 0x1000, expanded * 0x01010101u); + Require(!expandedFill.TakeClear() && expandedFill.Keys() == DccKeys::Uncompressed, "a color-expanded CMASK fill applied a fast clear or was refused"); + } + for (const std::uint8_t unmodeled : {0x11u, 0x33u, 0x44u, 0x88u, 0xcdu}) { + MultisampledCmask unmodeledSeed(0x30000, 0x1000); + unmodeledSeed.SeedCode(unmodeled); + expectFailure([&] { unmodeledSeed.TakeClear(); }, "reads as code 0x"); + } + MultisampledCmask unevenFill(0x30000, 0x1000); + unevenFill.NoteFill(0x30000, 0x1000, 0xccccccdcu); + expectFailure([&] { unevenFill.TakeClear(); }, "leaves its tiles mixed"); MultisampledCmask unaddressed; unaddressed.NoteFill(0, 0x1000, 0); Require(!unaddressed.TakeClear(), "a target without a CMASK cleared");