diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 47fc7d218..d9b0bb31d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -335,6 +335,12 @@ private: std::uint64_t entryGeneration = 0; }; std::vector imageRecords; + struct PreviousSampled { + std::array words{}; + bool depthCompare = false; + std::shared_ptr texture; + }; + PreviousSampled previousSampled; // The next record resolveImageBinding consumes (records follow the deferredImages order). std::size_t nextImageRecord = 0; // Stage B: the record's texture when the fastRevalidate predicate proves it current under the diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 9b67d16de..b5d4296a3 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1421,8 +1421,10 @@ void ShaderResources::buildComplete() { try { // The lookups run in plan order: Revalidate walks the bindings the same way, and consecutive // storage elements of one mip chain share the previous element's image. + previousSampled = {}; for (auto& source : samplerSources) source.singleLevelImage = source.mipmappedImage = false; for (const auto& deferred : deferredImages) resolveImageBinding(*deferred.binding, bindings[deferred.index], std::span>(samplers).subspan(deferred.firstSampler, deferred.samplerCount)); + previousSampled = {}; deferredImages.clear(); applyAnisoOverride(); // The records served their purpose: each holds the cache entry's objects as of stage A, @@ -2940,8 +2942,23 @@ bool ShaderResources::precollectImages() { const auto elementWords = binding.guestDescriptor.size() / binding.count; for (std::uint32_t element = 0; element < binding.count; ++element) { const auto words = std::span(binding.guestDescriptor).subspan(static_cast(element) * elementWords, elementWords); + const bool sampled = binding.kind == ShaderRecompiler::DescriptorKind::SampledImage; + if (sampled && !imageRecords.empty()) { + const auto& previous = imageRecords.back(); + if (previous.sampled && previous.decoded && words.size() == 8 && std::equal(words.begin(), words.end(), previous.words.begin()) && (binding.imageDepthCompare.empty() || !binding.imageDepthCompare.at(element))) { + ImageRecord repeated; + repeated.sampled = true; + repeated.decoded = previous.decoded; + repeated.words = previous.words; + repeated.resource = previous.resource; + repeated.guestBytes = previous.guestBytes; + repeated.components = previous.components; + imageRecords.push_back(repeated); + continue; + } + } ImageRecord record; - record.sampled = binding.kind == ShaderRecompiler::DescriptorKind::SampledImage; + record.sampled = sampled; if (words[0] != 0u || (words[1] & 0xffu) != 0u) { const auto* memo = words.size() == 8 ? &decodedImage(context.device, words) : nullptr; record.resource = memo != nullptr ? memo->resource : DecodeTextureResource(words); @@ -3064,11 +3081,13 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind const VkComponentMapping components = ViewComponents(resource); const auto guestBytes = record != nullptr && record->decoded ? record->guestBytes : DescribeSurface(resource).guestBytes; std::shared_ptr texture; - if (record != nullptr && record->texture != nullptr) { + const bool depthCompare = !binding.imageDepthCompare.empty() && binding.imageDepthCompare.at(element); + if (previousSampled.texture != nullptr && previousSampled.depthCompare == depthCompare && words.size() == 8 && std::equal(words.begin(), words.end(), previousSampled.words.begin())) texture = previousSampled.texture; + if (texture == nullptr && record != nullptr && record->texture != nullptr) { texture = fastTexture(*record); (texture != nullptr ? counters.fastHits : counters.fastMisses).fetch_add(1, std::memory_order_relaxed); } - if (texture == nullptr) texture = cachedTexture(context, words, resource, components, guestBytes, !binding.imageDepthCompare.empty() && binding.imageDepthCompare.at(element)); + if (texture == nullptr) texture = cachedTexture(context, words, resource, components, guestBytes, depthCompare); if (element < binding.imageUnnormalized.size() && binding.imageUnnormalized[element]) { const auto range = texture->SampledViewRange(firstLayer); const bool singleLevel = range.levels == 1u && range.layers == 1u && resource.baseLevel == 0u && EffectiveMinLod(resource) == 0.0f; @@ -3085,6 +3104,11 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind (singleLevel ? source.singleLevelImage : source.mipmappedImage) = true; } } + if (words.size() == 8) { + std::copy(words.begin(), words.end(), previousSampled.words.begin()); + previousSampled.depthCompare = depthCompare; + previousSampled.texture = texture; + } textures.push_back(std::move(texture)); textureFirstLayer.push_back(firstLayer); describedRanges.push_back({"texture", resource.baseAddress, guestBytes, resource.width, resource.height, resource.format, static_cast(resource.tileMode), resource.dccAddress}); @@ -3094,6 +3118,7 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind reportTextureCounters(); return; } + previousSampled = {}; // Consecutive identical storage descriptors address successive mips of one texture (dynamic-mip // storage writes). std::uint32_t mipOffset = 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 0e9f974d7..ded56f104 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -3763,6 +3763,24 @@ void imageMemoTests(const Device& device, Recorder& recorder) { Require(sample(first) == remade, "the remade texture is not the cache's"); const auto replaced = sample(second); Require(replaced != other && sample(second) == replaced && sample(first) == remade, "the cache does not answer with the replacements"); + const auto firstWords = linearTextureWords(reinterpret_cast(first), width, height, 56); + const auto secondWords = linearTextureWords(reinterpret_cast(second), width, height, 56); + const auto bindElements = [&](std::initializer_list> elements, std::vector depthCompare) { + program.bindings[0].count = static_cast(elements.size()); + program.bindings[0].guestDescriptor.clear(); + for (const auto& words : elements) program.bindings[0].guestDescriptor.insert(program.bindings[0].guestDescriptor.end(), words.begin(), words.end()); + program.bindings[0].imageDepthCompare = std::move(depthCompare); + program.bindings[0].imageSamplers.assign(elements.size(), 0u); + ShaderResources resources(context, compiled); + Require(resources.SampledTextures().size() == elements.size(), "the build has the wrong number of sampled textures"); + kept.insert(kept.end(), resources.SampledTextures().begin(), resources.SampledTextures().end()); + return resources.SampledTextures(); + }; + const auto repeated = bindElements({firstWords, firstWords, secondWords, firstWords}, {}); + Require(repeated[0] == remade && repeated[1] == remade && repeated[2] == replaced && repeated[3] == remade, "repeated elements of one build do not bind the cache's textures"); + program.bindings[0].count = 1; + program.bindings[0].imageDepthCompare.clear(); + program.bindings[0].imageSamplers.assign(1, 0u); recorder.Submit(); device.WaitQueue(); recorder.Sync();