mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-06 13:30:00 +00:00
perf(agc): drop the guest byte snapshot of a large cached texture the write tracker covers
Every snapshot texture in the sampled texture cache kept a host copy of its guest bytes, used only when the write tracker reports the surface written, to tell a rewrite of the same bytes from a change. The cache budget counts the GPU allocation alone, so the host copies grew with it: PPSA05684 held 2.7 GB of them in its first level. A snapshot larger than 256 KiB is released after the upload when the write tracker (a write generation or an untracked key) can prove the surface unchanged. A tracked write to such a surface re-uploads it instead of comparing bytes; smaller snapshots, and surfaces the tracker cannot vouch for, keep their copy as before. agc_driver_large_tracked_snapshot samples a 1 MiB surface, committed and partially resident: an unchanged surface is reused without reading guest memory, a CPU write is uploaded, and a driver store of the same bytes is uploaded again because no snapshot is left to compare. AI-assisted: yes
This commit is contained in:
1 parent
47a266cf97
commit
6849eeaec0
3 files changed
+71
-12
No files matched your search
@@ -1599,6 +1599,11 @@ if(BUILD_TESTING)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_shader_disk_cache_large_file PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_driver_large_tracked_snapshot COMMAND agc_driver_partially_resident_snapshot_tests --large-tracked)
|
||||
set_tests_properties(agc_driver_large_tracked_snapshot PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_large_tracked_snapshot PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
if(WIN32)
|
||||
add_test(NAME agc_driver_untracked_snapshot_hint COMMAND agc_driver_partially_resident_snapshot_tests --untracked-hint)
|
||||
set_tests_properties(agc_driver_untracked_snapshot_hint PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30
|
||||
|
||||
@@ -118,6 +118,7 @@ struct CachedTexture {
|
||||
// The untrackedKey the snapshot was last read or compared equal at, for a surface outside the
|
||||
// write watch; zero when none was taken.
|
||||
std::array<std::uint64_t, 4> untracked{};
|
||||
std::uint64_t surfaceBytes = 0;
|
||||
};
|
||||
|
||||
// Entries in use order (front = most recent) with a hash index by key: a lookup is O(1) and the
|
||||
@@ -729,18 +730,19 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
|
||||
if (profile) LookupOutcomes::Add(*keys != DccKeys::Uncompressed ? LookupOutcomes::SampledHitClearedView : LookupOutcomes::SampledHitView, start);
|
||||
return it->texture;
|
||||
}
|
||||
} else if (source == nullptr && it->bytes.size() == guestBytes && it->keys == *keys) {
|
||||
} else if (source == nullptr && it->surfaceBytes == guestBytes && it->keys == *keys) {
|
||||
// Unwritten pages need no comparison; partially resident textures compare only committed
|
||||
// pages. The compare goes through the flush hook: it waits for recorded work over the
|
||||
// surface (counted, and named for the [hooksync] line).
|
||||
const auto equalsCommitted = [&] {
|
||||
if (it->bytes.empty()) return false;
|
||||
if (Recorder::SnapshotWriteOverlaps(address, bytes)) counters.pendingReads.fetch_add(1, std::memory_order_relaxed);
|
||||
const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::TextureCompare);
|
||||
return GuestMemory::EqualsCommitted(address, it->bytes);
|
||||
};
|
||||
const auto unchanged = [&] {
|
||||
if (it->committed != committed) return false;
|
||||
return committed.has_value() ? !Recorder::SnapshotWriteOverlaps(address, it->bytes.size()) && GuestMemory::UnchangedCommittedSince(*committed, it->generation) : GuestMemory::UnchangedSince(address, it->bytes.size(), it->generation);
|
||||
return committed.has_value() ? !Recorder::SnapshotWriteOverlaps(address, static_cast<std::size_t>(it->surfaceBytes)) && GuestMemory::UnchangedCommittedSince(*committed, it->generation) : GuestMemory::UnchangedSince(address, static_cast<std::size_t>(it->surfaceBytes), it->generation);
|
||||
};
|
||||
const auto kept = [&] {
|
||||
if (untracked[0] == 0) return false;
|
||||
@@ -764,6 +766,7 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
|
||||
}
|
||||
CachedTexture entry{key, address, std::vector<std::byte>(source != nullptr ? 0u : bytes), nullptr, *keys, snapshotGeneration};
|
||||
entry.committed = std::move(committed);
|
||||
entry.surfaceBytes = bytes;
|
||||
if (source != nullptr) {
|
||||
entry.source = source;
|
||||
entry.sourceVersion = source->Version();
|
||||
@@ -792,6 +795,8 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
|
||||
}
|
||||
entry.texture = std::make_shared<Texture>(context, *context.detiler, resource, components, entry.bytes, depthCompare);
|
||||
entry.accounted = entry.texture->AllocationBytes();
|
||||
constexpr std::size_t SnapshotCompareBytes = 256u << 10u;
|
||||
if (entry.bytes.size() > SnapshotCompareBytes && (entry.generation != 0 || entry.untracked[0] != 0)) std::vector<std::byte>().swap(entry.bytes);
|
||||
counters.snapshots.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
const auto budget = sampledBudget(context, cache);
|
||||
|
||||
@@ -45,6 +45,8 @@ constexpr std::uint32_t TileR64KBX = 0x1b;
|
||||
constexpr std::uint32_t Type2D = 9;
|
||||
constexpr std::size_t BlockBytes = 65536;
|
||||
constexpr std::size_t SurfaceBytes = std::size_t{Side} * Side * 4u;
|
||||
constexpr std::uint32_t LargeSide = 512;
|
||||
constexpr std::size_t LargeSurfaceBytes = std::size_t{LargeSide} * LargeSide * 4u;
|
||||
constexpr int Skipped = 77;
|
||||
|
||||
class Device {
|
||||
@@ -148,16 +150,16 @@ private:
|
||||
|
||||
class ReservedSurface {
|
||||
public:
|
||||
ReservedSurface() {
|
||||
block = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(SurfaceBytes, BlockBytes));
|
||||
explicit ReservedSurface(std::size_t bytes) : size(bytes) {
|
||||
block = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(size, BlockBytes));
|
||||
Require(block != nullptr, "partially resident snapshot: cannot reserve the guest surface");
|
||||
GuestAllocations::Mutation().Add(block, SurfaceBytes, true, true, true);
|
||||
GuestAllocations::Mutation().Add(block, size, true, true, true);
|
||||
}
|
||||
|
||||
~ReservedSurface() {
|
||||
GuestAllocations::Mutation().Remove(block);
|
||||
Decommit(0, SurfaceBytes);
|
||||
GuestArena::GuestArenaRelease_nid_postfix(block, SurfaceBytes);
|
||||
Decommit(0, size);
|
||||
GuestArena::GuestArenaRelease_nid_postfix(block, size);
|
||||
}
|
||||
|
||||
ReservedSurface(const ReservedSurface&) = delete;
|
||||
@@ -188,6 +190,7 @@ public:
|
||||
std::uint64_t Address() const { return static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(block)); }
|
||||
|
||||
private:
|
||||
std::size_t size = 0;
|
||||
std::uint8_t* block = nullptr;
|
||||
};
|
||||
|
||||
@@ -199,11 +202,11 @@ void CountReads(std::uint64_t address, std::size_t bytes) {
|
||||
if (address < surfaceEnd.load() && address + bytes > surfaceBegin.load()) surfaceReads.fetch_add(1);
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 8> Descriptor(std::uint64_t address) {
|
||||
std::array<std::uint32_t, 8> Descriptor(std::uint64_t address, std::uint32_t side = Side) {
|
||||
return {
|
||||
static_cast<std::uint32_t>(address >> 8u),
|
||||
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Side - 1u) & 3u) << 30u),
|
||||
((Side - 1u) >> 2u) | ((Side - 1u) << 14u),
|
||||
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((side - 1u) & 3u) << 30u),
|
||||
((side - 1u) >> 2u) | ((side - 1u) << 14u),
|
||||
0xfacu | (TileR64KBX << 20u) | (Type2D << 28u),
|
||||
0u,
|
||||
0u,
|
||||
@@ -262,6 +265,48 @@ void Run(const Context& context, ReservedSurface& surface) {
|
||||
AgcDriver::GuestMemory::SetFlushHook(nullptr);
|
||||
}
|
||||
|
||||
void LargeTracked(const Context& context, ReservedSurface& surface, std::size_t committedBytes) {
|
||||
const auto address = surface.Address();
|
||||
const auto words = Descriptor(address, LargeSide);
|
||||
const auto layout = committedBytes == LargeSurfaceBytes ? std::string("committed") : std::string("partially resident");
|
||||
Require(DescribeSurface(DecodeTextureResource(words)).guestBytes == LargeSurfaceBytes, "the large test descriptor does not describe the reserved surface");
|
||||
surface.Commit(0, committedBytes);
|
||||
std::memset(surface.Data(), 0x5a, committedBytes);
|
||||
surfaceBegin = address;
|
||||
surfaceEnd = address + LargeSurfaceBytes;
|
||||
std::shared_ptr<Texture> previous;
|
||||
const auto sample = [&](const std::string& when, bool uploaded) {
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
surfaceReads = 0;
|
||||
auto texture = CachedSampledTexture(context, words);
|
||||
Require(texture != nullptr && !texture->ViewsStorageImage(), "the large " + layout + " surface was not sampled through a snapshot " + when);
|
||||
const auto reads = surfaceReads.load();
|
||||
if (uploaded && (reads == 0 || texture == previous)) throw std::runtime_error("sampling the large " + layout + " surface " + when + " kept the texture uploaded before it");
|
||||
if (!uploaded && (reads != 0 || texture != previous)) throw std::runtime_error("sampling the large " + layout + " surface " + when + " uploaded it again although the write tracker saw no write (" + std::to_string(reads) + " reads)");
|
||||
previous = texture;
|
||||
};
|
||||
AgcDriver::GuestMemory::SetFlushHook(&CountReads);
|
||||
try {
|
||||
sample("at its first use", true);
|
||||
sample("again", false);
|
||||
surface.Data()[0x100] = 0x11;
|
||||
sample("after a CPU write", true);
|
||||
sample("after the upload of the written surface", false);
|
||||
surface.Data()[committedBytes - 0x40] = 0x22;
|
||||
sample("after a CPU write to its last committed block", true);
|
||||
sample("after the upload of the written block", false);
|
||||
AgcDriver::GuestMemory::MarkWritten(address + 0x200, 4);
|
||||
sample("after a driver store of the bytes already there, which a snapshot it did not keep cannot tell from a change", true);
|
||||
sample("after the upload that followed the driver store", false);
|
||||
} catch (...) {
|
||||
AgcDriver::GuestMemory::SetFlushHook(nullptr);
|
||||
throw;
|
||||
}
|
||||
AgcDriver::GuestMemory::SetFlushHook(nullptr);
|
||||
ClearCachedTextures(context.device);
|
||||
surface.Decommit(0, committedBytes);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void UntrackedHint(const Context& context, ReservedSurface& surface) {
|
||||
const auto address = surface.Address();
|
||||
@@ -368,9 +413,13 @@ int main(int argc, char** argv) {
|
||||
auto context = device->GetContext();
|
||||
TextureDetiler detiler(context);
|
||||
context.detiler = &detiler;
|
||||
ReservedSurface surface;
|
||||
const bool large = argc == 2 && std::string_view(argv[1]) == "--large-tracked";
|
||||
ReservedSurface surface(large ? LargeSurfaceBytes : SurfaceBytes);
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--pending-stores") PendingStores(context, surface);
|
||||
if (large) {
|
||||
LargeTracked(context, surface, LargeSurfaceBytes);
|
||||
LargeTracked(context, surface, BlockBytes);
|
||||
} else if (argc == 2 && std::string_view(argv[1]) == "--pending-stores") PendingStores(context, surface);
|
||||
else if (argc == 2 && std::string_view(argv[1]) == "--untracked-hint") {
|
||||
#ifdef _WIN32
|
||||
UntrackedHint(context, surface);
|
||||
|
||||
Reference in new issue
Block a user