mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-06 13:30:00 +00:00
fix(agc): read a multisampled target's CMASK by its MSAA codes
A multisampled color target with a CMASK fast clear seeds its state from
the CMASK when it is first used, and later fills of the CMASK update it.
Both read the bytes as DCC keys: 0x00 is a clear, 0xff uncompressed, and
anything else refuses ("reads as mixed"). But an MSAA CMASK nibble is
FMASK code | color code << 2 (Mesa ac_descriptors.h CMASK_MSAA_CODE), so
0xcc (FMASK cleared to fragment 0, color expanded), and the 2x and 4x
"FMASK uncompressed, color expanded" codes 0xdd and 0xee, are states with
no pending fast clear that were refused. Ruffle (PPSA68091) clears its
target's CMASK to 0xcc every frame and stopped on the refusal whenever
that clear landed before the target's first use.
The seed now reads the uniform CMASK byte (after pending writes, like the
key read) and both paths classify it by those codes: 0x00 (register color
clear, FMASK cleared) is the fast clear as before, any color-expanded code
(0xc to 0xf in both nibbles) is "no fast clear", and other codes, or bytes
that differ, are still refused.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
821fc29537
commit
12b20e17f6
5 files changed
+61
-15
No files matched your search
@@ -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<DccKeys> KnownPendingDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes);
|
||||
std::optional<std::uint8_t> 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);
|
||||
|
||||
@@ -17,12 +17,14 @@ class StorageTexture;
|
||||
class Texture;
|
||||
struct GuestTextureResource;
|
||||
|
||||
std::optional<DccKeys> 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<std::uint8_t> code);
|
||||
void CheckAddress(std::uint64_t cmaskAddress) const;
|
||||
bool TakeClear();
|
||||
DccKeys Keys() const { return keys; }
|
||||
|
||||
@@ -580,6 +580,25 @@ DccKeys CurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceBytes, st
|
||||
return currentDccKeys(metaAddress, keyCount, memoized);
|
||||
}
|
||||
|
||||
std::optional<std::uint8_t> CurrentUniformMetadataByte(std::uint64_t address, std::size_t bytes) {
|
||||
if (address == 0 || bytes == 0 || !GuestMemory::Accessible(reinterpret_cast<const void*>(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<std::uint8_t>(landed.value);
|
||||
Recorder::CountSync(2);
|
||||
recorder->Sync();
|
||||
}
|
||||
}
|
||||
std::vector<std::byte> 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<std::uint8_t>(first);
|
||||
}
|
||||
|
||||
std::optional<DccKeys> 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;
|
||||
|
||||
@@ -454,7 +454,7 @@ MultisampleTarget& acquire(const Context& context, const ColorTarget& use) {
|
||||
targets().push_back(std::make_unique<MultisampleTarget>(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<std::uint64_t>(use.cmaskBytes) * 256u));
|
||||
if (use.cmaskFastClear && use.dccAddress == 0) created.cmask.SeedCode(CurrentUniformMetadataByte(use.cmaskAddress, static_cast<std::size_t>(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<DccKeys> 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<std::uint8_t>(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<unsigned long long>(address), static_cast<unsigned long long>(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<unsigned long long>(address), static_cast<unsigned long long>(bytes), pattern);
|
||||
refusal = text;
|
||||
}
|
||||
}
|
||||
|
||||
void MultisampledCmask::SeedKeys(DccKeys seeded) {
|
||||
if (seeded == DccKeys::Clear0000 || seeded == DccKeys::Uncompressed) {
|
||||
keys = seeded;
|
||||
void MultisampledCmask::SeedCode(std::optional<std::uint8_t> 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<unsigned long long>(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<unsigned long long>(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<unsigned long long>(address));
|
||||
refusal = text;
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in new issue
Block a user