From fa02afeb4795b8d99bea24e054208db003fa2f2c Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Fri, 23 Oct 2026 22:07:40 +0200 Subject: [PATCH] fix(agc): bound the draw cache by bytes --- .../include/Driver/Draw/DrawCache.hpp | 2 +- .../Execution/include/Driver/Driver.hpp | 3 + .../Execution/include/ShaderMemory.hpp | 1 + .../src/Driver/Dispatch/DispatchCache.cpp | 3 +- .../Execution/src/Driver/Draw/DrawCache.cpp | 16 ++- .../src/Driver/Draw/DrawCacheStatistics.cpp | 9 +- .../Execution/src/ShaderMemory.cpp | 4 + .../tests/execution/ImageTableCacheBounds.cpp | 102 ++++++++++++++++++ docs/dev/ENVIRONMENT.md | 1 + 9 files changed, 136 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp index d3f3da21fa..7ae26d17b2 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp @@ -98,7 +98,7 @@ struct DrawEntryCounters { std::uint64_t registerKeyLookups = 0, registerKeyHits = 0, decodeSkipped = 0, decodePartial = 0, facadeMismatches = 0, verifyDecodes = 0, verifyDecodeMismatches = 0; double keyUs = 0; - std::uint64_t absentNewRegisters = 0, absentUserWords = 0, absentEvicted = 0, absentNeverInserted = 0, evictions = 0; + std::uint64_t absentNewRegisters = 0, absentUserWords = 0, absentEvicted = 0, absentNeverInserted = 0, evictions = 0, byteEvictions = 0; std::array registerKeyMissChanges{}; std::uint64_t differingSameRuns = 0, differingWords = 0, differingRunsChanged = 0; std::uint64_t dataHits = 0, dataStagesReused = 0, dataStagesCompiled = 0, dataVerified = 0; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index 922c68099c..23a3557f9a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -96,7 +96,9 @@ public: std::uint64_t variantBytes = 0; }; DispatchCacheFootprint DispatchCacheSize(); + DispatchCacheFootprint DrawCacheSize(); static std::uint64_t DispatchCacheBytes(); + static std::uint64_t DrawCacheBytes(); static ShaderMemory::PendingWrite QueryPendingSpan(std::uint64_t address, std::size_t bytes, std::span known); std::shared_ptr RegisteredShaders(); @@ -408,6 +410,7 @@ private: std::uint64_t drawCacheHits = 0, drawCacheEvictions = 0, drawCacheVariants = 0, drawCacheVariantBytes = 0; std::optional drawKeySightings; std::optional drawShapeSightings; + std::uint64_t drawCacheByteEvictions = 0; DrawEntryCounters drawEntryCounters; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp index dacb784275..8420887e1b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp @@ -81,6 +81,7 @@ public: }; static SupplyCounts Supplies(); [[nodiscard]] std::vector> SuppliedBlocks() const; + [[nodiscard]] std::size_t Bytes() const; struct VouchCounts { std::uint64_t calls, blocks, refused, words; }; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp index 732d7733b4..a691951f22 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp @@ -65,7 +65,8 @@ Driver::DispatchCacheFootprint Driver::DispatchCacheSize() { } std::uint64_t Driver::variantBytes(const DispatchVariant& variant) { - return sizeof(DispatchVariant) + variant.words.size() * sizeof(std::uint32_t) + variant.shared.size() * sizeof(SharedWords) + variant.runs.size() * sizeof(std::pair) + variant.captured.size() * sizeof(ShaderRecompiler::MemoryRegion) + (variant.vertexInfo != nullptr ? sizeof(ShaderRecompiler::ShaderVertexStageInfo) : 0); + const auto held = [](const auto& values) { return static_cast(values.capacity() * sizeof(values[0])); }; + return sizeof(DispatchVariant) + held(variant.runs) + held(variant.words) + held(variant.shared) + held(variant.dataPositions) + held(variant.dataSlots) + held(variant.runBases) + held(variant.shaderDataPatches) + held(variant.captured) + held(variant.spans) + (variant.vertexInfo != nullptr ? sizeof(ShaderRecompiler::ShaderVertexStageInfo) : 0) + (variant.memory != nullptr ? variant.memory->Bytes() : 0); } void Driver::accountVariant(const DispatchVariant& variant, bool added) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp index 8d05f4693f..c42ca7d2e4 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp @@ -65,6 +65,15 @@ std::size_t Driver::drawCacheEntries() { return entries; } +std::uint64_t Driver::DrawCacheBytes() { + static const std::uint64_t bytes = [] { + const char* text = std::getenv("APS5_DRAW_CACHE_BYTES"); + const auto parsed = text != nullptr ? std::strtoull(text, nullptr, 10) : 0ull; + return parsed != 0 ? static_cast(parsed) : std::uint64_t{256} << 20u; + }(); + return bytes; +} + void Driver::accountDrawVariant(const DispatchVariant& variant, bool added) { if (added) { ++drawCacheVariants; @@ -180,7 +189,8 @@ void Driver::insertDrawEntry(std::uint64_t key, std::vectororder); found->second = std::move(replacement); } - while (drawCache.size() > drawCacheEntries()) { + while (drawCache.size() > drawCacheEntries() || (drawCache.size() > 1 && drawCacheVariantBytes > DrawCacheBytes())) { + const bool overBytes = drawCache.size() <= drawCacheEntries(); const auto last = drawCache.find(drawOrder.back()); for (const auto& variants : last->second->stages) { for (const auto& variant : variants) accountDrawVariant(*variant, false); @@ -191,6 +201,10 @@ void Driver::insertDrawEntry(std::uint64_t key, std::vector(counters.hits) / static_cast(counters.lookups) : 0.0, count(misses), count(counters.absent), count(counters.dataHits), count(counters.dataStagesReused), count(counters.dataStagesCompiled), count(counters.dataVerified), count(counters.absentNewRegisters), count(counters.absentUserWords), count(counters.absentEvicted), count(counters.absentNeverInserted), miss(DrawMiss::FrontDiffering), miss(DrawMiss::FragmentDiffering), miss(DrawMiss::OtherDiffering), miss(DrawMiss::Layout), miss(DrawMiss::Gate), miss(DrawMiss::Stages), count(counters.differingSameRuns), counters.differingSameRuns != 0 ? static_cast(counters.differingWords) / static_cast(counters.differingSameRuns) : 0.0, count(counters.differingRunsChanged), drawCache.size(), count(counters.evictions)); + std::snprintf(line, sizeof(line), "[draw-cache] 10 s: %llu lookups, %llu hits (%.1f%%), %llu stage misses, %llu without an entry, %llu data-only hits (stages: %llu reused, %llu compiled; %llu verified); no entry: new registers %llu, user words/pointers only %llu, evicted %llu, seen but never inserted %llu; stage misses: front differing %llu, fragment differing %llu, other differing %llu, layout %llu, gate %llu, stage count %llu; differing captures: same runs %llu (%.1f words changed each), other runs %llu; %zu entries, %llu evicted (%llu over the byte budget)\n", count(counters.lookups), count(counters.hits), counters.lookups != 0 ? 100.0 * static_cast(counters.hits) / static_cast(counters.lookups) : 0.0, count(misses), count(counters.absent), count(counters.dataHits), count(counters.dataStagesReused), count(counters.dataStagesCompiled), count(counters.dataVerified), count(counters.absentNewRegisters), count(counters.absentUserWords), count(counters.absentEvicted), count(counters.absentNeverInserted), miss(DrawMiss::FrontDiffering), miss(DrawMiss::FragmentDiffering), miss(DrawMiss::OtherDiffering), miss(DrawMiss::Layout), miss(DrawMiss::Gate), miss(DrawMiss::Stages), count(counters.differingSameRuns), counters.differingSameRuns != 0 ? static_cast(counters.differingWords) / static_cast(counters.differingSameRuns) : 0.0, count(counters.differingRunsChanged), drawCache.size(), count(counters.evictions), count(counters.byteEvictions)); text += line; std::snprintf(line, sizeof(line), "[draw-cache] register-key miss component changes (10 s): stable registers %llu, user data %llu, auxiliary registers %llu, device %llu, first shader set %llu, unchanged components %llu\n", count(counters.registerKeyMissChanges[0]), count(counters.registerKeyMissChanges[1]), count(counters.registerKeyMissChanges[2]), count(counters.registerKeyMissChanges[3]), count(counters.registerKeyMissChanges[4]), count(counters.registerKeyMissChanges[5])); text += line; @@ -46,6 +46,11 @@ DrawEntryCounters Driver::DrawCacheCounters() { return drawEntryCounters; } +Driver::DispatchCacheFootprint Driver::DrawCacheSize() { + std::lock_guard cacheLock(drawCacheMutex); + return {drawCache.size(), drawCacheVariantBytes}; +} + std::string Driver::profileDrawCacheLines(const DrawEntryCounters& counters) { std::string text; const auto append = [&](const char* format, auto... values) { @@ -62,7 +67,7 @@ std::string Driver::profileDrawCacheLines(const DrawEntryCounters& counters) { std::snprintf(item, sizeof(item), "%s%llu", rank == 0 ? "" : " / ", count(counters.variantHitsByRank[rank])); ranks += item; } - append("[draw-cache] %llu lookups (10 s): %llu no entry, %llu validated in %.1f us each (key, lookup and every stage): %llu hits (every stage equal; stage hits by variant rank 1..k %s), misses by reason: front stage differing %llu, fragment differing %llu, other stage differing %llu, layout (push offset) %llu, gate %llu, stage count %llu; %llu stage validations (%llu equal, %.2f variants compared each); %llu inserts (%llu shape updates, %llu variants inserted, %llu evicted beyond k, %llu already present, %llu unstable), %zu entries (%llu variants, ~%.1f MiB), %llu evictions in total, %llu LRU moves; verify: %llu hits captured again, %llu stages differed\n", count(counters.lookups), count(counters.absent), count(validated), validated != 0 ? counters.validateUs / static_cast(validated) : 0.0, count(counters.hits), ranks.c_str(), miss(DrawMiss::FrontDiffering), miss(DrawMiss::FragmentDiffering), miss(DrawMiss::OtherDiffering), miss(DrawMiss::Layout), miss(DrawMiss::Gate), miss(DrawMiss::Stages), count(counters.stageValidations), count(counters.stageEqual), counters.stageValidations != 0 ? static_cast(counters.variantsCompared) / static_cast(counters.stageValidations) : 0.0, count(counters.inserts), count(counters.shapeUpdates), count(counters.variantsInserted), count(counters.variantsEvicted), count(counters.present), count(counters.unstable), drawCache.size(), count(drawCacheVariants), static_cast(drawCacheVariantBytes) / (1024.0 * 1024.0), count(drawCacheEvictions), count(counters.touches), count(counters.verifyHits), count(counters.verifyMismatches)); + append("[draw-cache] %llu lookups (10 s): %llu no entry, %llu validated in %.1f us each (key, lookup and every stage): %llu hits (every stage equal; stage hits by variant rank 1..k %s), misses by reason: front stage differing %llu, fragment differing %llu, other stage differing %llu, layout (push offset) %llu, gate %llu, stage count %llu; %llu stage validations (%llu equal, %.2f variants compared each); %llu inserts (%llu shape updates, %llu variants inserted, %llu evicted beyond k, %llu already present, %llu unstable), %zu entries (%llu variants, ~%.1f MiB of a %.1f MiB budget), %llu evictions in total (%llu over the byte budget), %llu LRU moves; verify: %llu hits captured again, %llu stages differed\n", count(counters.lookups), count(counters.absent), count(validated), validated != 0 ? counters.validateUs / static_cast(validated) : 0.0, count(counters.hits), ranks.c_str(), miss(DrawMiss::FrontDiffering), miss(DrawMiss::FragmentDiffering), miss(DrawMiss::OtherDiffering), miss(DrawMiss::Layout), miss(DrawMiss::Gate), miss(DrawMiss::Stages), count(counters.stageValidations), count(counters.stageEqual), counters.stageValidations != 0 ? static_cast(counters.variantsCompared) / static_cast(counters.stageValidations) : 0.0, count(counters.inserts), count(counters.shapeUpdates), count(counters.variantsInserted), count(counters.variantsEvicted), count(counters.present), count(counters.unstable), drawCache.size(), count(drawCacheVariants), static_cast(drawCacheVariantBytes) / (1024.0 * 1024.0), static_cast(DrawCacheBytes()) / (1024.0 * 1024.0), count(drawCacheEvictions), count(drawCacheByteEvictions), count(counters.touches), count(counters.verifyHits), count(counters.verifyMismatches)); append("[draw-cache] register key (10 s): %llu lookups, %llu hits, key build %.1f us each; decode skipped %llu, partial (state/pixel/programs from the entry) %llu; facade log != table %llu; verify: %llu decodes compared, %llu differed\n", count(counters.registerKeyLookups), count(counters.registerKeyHits), counters.registerKeyLookups != 0 ? counters.keyUs / static_cast(counters.registerKeyLookups) : 0.0, count(counters.decodeSkipped), count(counters.decodePartial), count(counters.facadeMismatches), count(counters.verifyDecodes), count(counters.verifyDecodeMismatches)); append("[draw-cache] stage data hits (10 s): %llu stages (%llu words refreshed), verified %llu; stage inserts with data positions %llu (%.1f positions each)\n", count(counters.stageDataHits), count(counters.stageDataWords), count(counters.stageDataVerified), count(counters.stageDataInserts), counters.stageDataInserts != 0 ? static_cast(counters.stageDataPositions) / static_cast(counters.stageDataInserts) : 0.0); return text; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp index f0e0b777f2..80439c337e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp @@ -179,6 +179,10 @@ ShaderMemory::~ShaderMemory() { } } +std::size_t ShaderMemory::Bytes() const { + return sizeof(ShaderMemory) + initial.capacity() * sizeof(initial[0]) + pages.capacity() * sizeof(pages[0]) + pages.size() * sizeof(Page) + supplied.capacity() * sizeof(Supplied) + spilled.capacity() * sizeof(spilled[0]); +} + void ShaderMemory::WordMask::SetRun(std::size_t first, std::size_t count) { while (count != 0) { const auto shift = first % 64u; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableCacheBounds.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableCacheBounds.cpp index 765947dc99..104fb882b6 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableCacheBounds.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableCacheBounds.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,7 @@ #include #include #include +#include #include namespace { @@ -66,6 +68,14 @@ constexpr std::array WideSlots{SrtSlots - 1u, SrtSlots - 2u, Srt constexpr std::array WideTableCode{0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0xf4080300u, 0xfa000020u, 0xf4080700u, 0xfa000030u, 0x7e200500u, 0x9310b010u, 0xf42c0502u, 0x20000000u, 0xf09c0f08u, 0x00450000u, 0xe0700000u, 0x80070000u, 0xbf810000u}; constexpr std::array TableCode{0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0xf4080300u, 0xfa000020u, 0xf4080700u, 0xfa000030u, 0x7e200500u, 0x8f108510u, 0xf42c0502u, 0x20000000u, 0xf09c0f08u, 0x00450000u, 0xe0700000u, 0x80070000u, 0xbf810000u}; +constexpr std::size_t VertexOffset = 256; +constexpr std::size_t PixelOffset = 512; +constexpr std::size_t DrawSrtBytes = 64; +constexpr std::size_t DrawKeys = SrtSlots * SrtBytes / DrawSrtBytes; +constexpr std::size_t ColorBytes = 1024; +constexpr std::array TriangleVertexCode{0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u, 0x7e060280u, 0x7e0802f2u, 0xf80008cfu, 0x04030201u, 0xbf810000u}; +constexpr std::array TablePixelCode{0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0xd7650002u, 0x000100c1u, 0x7e200502u, 0x8f108510u, 0xbf8cc07fu, 0xf42c0502u, 0x20000000u, 0x7e000280u, 0x7e020280u, 0xbf8cc07fu, 0xf09c0f08u, 0x00450000u, 0xbf8c3f70u, 0xf800180fu, 0x03020100u, 0xbf810000u}; + class GuestBlock { public: GuestBlock() { @@ -166,6 +176,95 @@ void SrtMovingPerDispatch(const GuestBlock& block) { Require(most <= Driver::DispatchCacheBytes(), "image table cache bounds: dispatches reading a 2 MiB table through " + std::to_string(SrtSlots) + " SRTs kept " + std::to_string(most >> 20u) + " MiB of captured words in the dispatch cache, over its budget of " + std::to_string(Driver::DispatchCacheBytes() >> 20u) + " MiB"); } +void Draw(const GuestBlock& block, std::size_t key) { + const auto color = block.At(OutputOffset); + const auto vertex = block.At(VertexOffset); + const auto pixel = block.At(PixelOffset); + const auto srt = block.At(SrtOffset + key * DrawSrtBytes); + const std::vector> context{ + {0x2d5, 0x2000}, {0x1b6, 0}, {0x207, 0}, {0x200, 0}, {0x203, 0x800}, {0x2dc, 0xaa00}, {0x2f8, 0}, {0x292, 2}, {0x293, 0}, + {0x80, 0}, {0x8d, 0}, {0x83, 0xffff}, {0x8c, 0xa}, {0x2f9, 0x2d}, {0x313, 0x6000}, {0x30e, 0xffffffff}, {0x30f, 0xffffffff}, + {0x206, 0x43f}, {0x204, 0x80000}, {0x205, 0x240}, {0x8e, 0xf}, {0x8f, 0xf}, {0x202, 0xcc0010}, + {0x1c4, 0}, {0x1c5, 9}, {0x1c3, 4}, {0x31c, 0x28028}, {0x31b, 0}, {0x31d, 0}, {0x3b0, (63u << 14u) | 3u}, {0x3b8, 0x9000000}, {0x1e0, 0}, + {0xc, 0}, {0xd, 0x40040}, {0x81, 0x80000000}, {0x82, 0x40040}, {0x90, 0x80000000}, {0x91, 0x40040}, {0x94, 0x80000000}, {0x95, 0x40040}, + {0x318, static_cast(color >> 8u)}, {0x390, static_cast(color >> 40u)}, + {0x10f, std::bit_cast(32.0f)}, {0x110, std::bit_cast(32.0f)}, {0x111, std::bit_cast(-2.0f)}, {0x112, std::bit_cast(2.0f)}, + {0x113, std::bit_cast(1.0f)}, {0x114, 0}, {0xb4, 0}, {0xb5, std::bit_cast(1.0f)}, {0x2fe, 0}, {0x302, 0}, {0x306, 0}, {0x30a, 0}, + }; + const std::vector> shader{ + {0xc8, static_cast(vertex >> 8u)}, {0xc9, static_cast(vertex >> 40u)}, {0x8b, 0}, {0x8c, 0}, + {0x008, static_cast(pixel >> 8u)}, {0x009, static_cast(pixel >> 40u)}, {0x00b, 2u << 1u}, + {0x00c, static_cast(srt)}, {0x00d, static_cast(srt >> 32u)}, + }; + std::vector words; + const auto append = [&](const std::vector& packet) { words.insert(words.end(), packet.begin(), packet.end()); }; + append(Pm4(0x79, {0x242, 4})); + for (const auto& [offset, value] : context) append(Pm4(0x69, {offset, value})); + for (const auto& [offset, value] : shader) append(Pm4(0x76, {offset, value})); + append(Pm4(0x2d, {3, 2})); + Packet dcb{words.data(), static_cast(words.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&dcb) == 0, "image table draw cache bounds: the draw submission failed"); + AgcDriverWaitIdle_nid_postfix(); +} + +unsigned BrightestColor(const GuestBlock& block) { + AgcDriver::GuestMemory::FlushGpuWrites(block.At(OutputOffset), ColorBytes); + const auto* bytes = block.Bytes(OutputOffset); + return *std::max_element(bytes, bytes + ColorBytes); +} + +void SrtMovingPerDraw(const GuestBlock& block) { + using AgcDriver::DriverDetail::Driver; + std::memcpy(block.Bytes(VertexOffset), TriangleVertexCode.data(), sizeof(TriangleVertexCode)); + std::memcpy(block.Bytes(PixelOffset), TablePixelCode.data(), sizeof(TablePixelCode)); + const auto fill = [&](std::uint32_t surface) { + for (std::uint32_t record = 0; record < Records; ++record) SetRecord(block, record, surface * Aliases); + }; + fill(3u); + for (std::size_t key = 0; key < DrawKeys; ++key) { + auto* srt = reinterpret_cast(block.Bytes(SrtOffset + key * DrawSrtBytes)); + std::fill(srt, srt + DrawSrtBytes / 4u, 0u); + const auto heap = V(block.At(HeapOffset + key * Stride), Stride, Records - static_cast(key)); + std::copy(heap.begin(), heap.end(), srt); + } + auto& driver = Driver::Get(); + const auto budget = Driver::DrawCacheBytes(); + const auto before = driver.DrawCacheCounters(); + std::uint64_t most = 0; + std::size_t entries = 0; + for (std::size_t key = 0; key < DrawKeys; ++key) { + for (std::size_t sight = 0; sight < 2; ++sight) { + Draw(block, key); + const auto size = driver.DrawCacheSize(); + Require(size.variantBytes <= budget, "image table draw cache bounds: after draw " + std::to_string(2 * key + sight) + " the draw cache kept " + std::to_string(size.entries) + " entries holding " + std::to_string(size.variantBytes >> 10u) + " KiB, over its budget of " + std::to_string(budget >> 10u) + " KiB"); + most = std::max(most, size.variantBytes); + entries = std::max(entries, size.entries); + } + } + const auto filled = driver.DrawCacheCounters(); + const auto inserts = filled.inserts - before.inserts; + const auto evictions = filled.evictions - before.evictions; + const auto byteEvictions = filled.byteEvictions - before.byteEvictions; + const auto counts = " (" + std::to_string(inserts) + " inserts, " + std::to_string(evictions) + " evictions, " + std::to_string(byteEvictions) + " over the byte budget, at most " + std::to_string(entries) + " entries holding " + std::to_string(most >> 10u) + " KiB)"; + Require(BrightestColor(block) == 3u, "image table draw cache bounds: the draws through the table wrote " + std::to_string(BrightestColor(block)) + ", expected 3"); + Require(entries >= 2 && inserts >= DrawKeys / 2, "image table draw cache bounds: the draws were not kept in the draw cache" + counts); + Require(inserts * (most / entries) > 2 * budget, "image table draw cache bounds: the draws did not insert more than twice the budget" + counts); + Require(byteEvictions >= 1 && byteEvictions == evictions, "image table draw cache bounds: the draw cache was not bounded by its byte budget" + counts); + + fill(9u); + std::memset(block.Bytes(OutputOffset), 0, ColorBytes); + Draw(block, 0); + const auto rebuilt = driver.DrawCacheCounters(); + Require(rebuilt.absent - filled.absent == 1 && rebuilt.inserts - filled.inserts == 1, "image table draw cache bounds: the first key, evicted, was not looked up without an entry and inserted again (" + std::to_string(rebuilt.absent - filled.absent) + " absent, " + std::to_string(rebuilt.inserts - filled.inserts) + " inserts)"); + Require(BrightestColor(block) == 9u, "image table draw cache bounds: the evicted key drawn again wrote " + std::to_string(BrightestColor(block)) + ", expected 9"); + std::memset(block.Bytes(OutputOffset), 0, ColorBytes); + Draw(block, 0); + const auto hit = driver.DrawCacheCounters(); + Require(hit.hits - rebuilt.hits == 1, "image table draw cache bounds: the evicted key, inserted again, was not hit by its next draw"); + Require(BrightestColor(block) == 9u, "image table draw cache bounds: the hit on the rebuilt key wrote " + std::to_string(BrightestColor(block)) + ", expected 9"); + std::puts(("image table draw cache bounds passed" + counts).c_str()); +} + std::array TextureWords(const GuestBlock& block, std::uint32_t element) { const auto address = block.At(TexturesOffset + static_cast(element / Aliases) * TextureBytes); return {static_cast(address >> 8u), static_cast((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (3u << 30u), 7u << 14u, 0xfacu | (Type2D << 28u), 0u, 0u, element % Aliases, 0u}; @@ -337,9 +436,11 @@ void SetBudgets() { #ifdef _WIN32 _putenv_s("APS5_RESOURCE_CACHE_TABLE_ELEMENTS", "8192"); _putenv_s("APS5_DISPATCH_CACHE_BYTES", "16777216"); + _putenv_s("APS5_DRAW_CACHE_BYTES", "16777216"); #else setenv("APS5_RESOURCE_CACHE_TABLE_ELEMENTS", "8192", 1); setenv("APS5_DISPATCH_CACHE_BYTES", "16777216", 1); + setenv("APS5_DRAW_CACHE_BYTES", "16777216", 1); #endif } @@ -398,6 +499,7 @@ int main(int argc, char** argv) { } TablesChangingPerDispatch(block); SrtMovingPerDispatch(block); + SrtMovingPerDraw(block); AgcDriverShutdown_nid_postfix(); std::puts("image table cache bounds tests passed"); return 0; diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index 27fc474b9c..640010cdbc 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -116,6 +116,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_DISPMOVE_MAX_KIB` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | 1024 | upper bound in KiB on the read-only buffers a dispatch-cache hit moves into its template, above which the dispatch is prepared again | | `APS5_DRAIN_ALL` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp` | | drains every packet, disabling the batched recording path | | `APS5_DRAIN_COMPLETION_LABELS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp` | | records completion labels immediately instead of deferring them with the batch, and drains a label recorded while a copied write-back is pending instead of storing it behind the write-backs | +| `APS5_DRAW_CACHE_BYTES` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp` | 268435456 | size of the draw cache in bytes, evicting least-recently-used entries past it (0 keeps the default of 256 MiB) | | `APS5_DRAW_CACHE_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp` | 4096 | number of entries in the draw cache; 0 keeps the default 4096 | | `APS5_DRAW_DRAIN` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp` | | treats draw packets as drains, waiting for them instead of recording ahead | | `APS5_DRAW_INPUTS_IN_PLACE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | binds draw vertex and index ranges in place from host imports also with NVIDIA's Windows driver, which copies them by default |