From 24e33365f0de1fb09bf9f9557712f9d4dcea73c8 Mon Sep 17 00:00:00 2001 From: Joey Fisher Date: Thu, 8 Oct 2026 12:02:20 -0600 Subject: [PATCH 1/2] perf(agc): key sampled textures without the streaming-feedback fields MIN_LOD_WARN, MIP_STATS_COUNTER_EN and MIP_STATS_COUNTER_ID are ignored by the texture decoder, but the cache key held the raw T# words. A title that rotates the counter fields per use got a cache entry per variant of the same texture, and the extra entries evicted live ones under the budget. --- core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index d79c2e918..d22e1e310 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -83,6 +83,8 @@ TextureKey MakeTextureKey(VkDevice device, std::span words, TextureKey key{device, {}, {static_cast(components.r), static_cast(components.g), static_cast(components.b), static_cast(components.a)}}; key.depthCompare = depthCompare; std::copy(words.begin(), words.end(), key.words.begin()); + key.words[5] &= ~((0xfffu << 8u) | (1u << 25u)); + key.words[6] &= ~0xffu; return key; } From 36997299e9efc7cb27d598676b086e8c9d556de4 Mon Sep 17 00:00:00 2001 From: Joey Fisher Date: Thu, 8 Oct 2026 16:42:17 -0600 Subject: [PATCH 2/2] test(agc): check that streaming-feedback T# fields share a sampled texture entry --- .../Graphics/include/ShaderResources.hpp | 1 + .../Graphics/src/ShaderResources.cpp | 8 +++++++ .../tests/GuestTextureResource.cpp | 21 +++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index c4f06ee68..4908474e2 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -37,6 +37,7 @@ struct TextureCacheUse { TextureCacheUse TextureCacheUsage(); std::uint64_t SampledTextureCacheBudget(const Context& context); std::shared_ptr CachedSampledTexture(const Context& context, std::span words); +bool SampledTexturesShareEntry(std::span first, std::span second); // The cached storage image of a surface (render targets use it as their resident image); brought up // to date with guest memory before it is returned. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index d22e1e310..fbcb35ae2 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -780,6 +780,14 @@ std::shared_ptr CachedSampledTexture(const Context& context, std::span< return cachedTexture(context, words, resource, ViewComponents(resource)); } +bool SampledTexturesShareEntry(std::span first, std::span second) { + Require(first.size() == 8 && second.size() == 8, "sampled texture keys take eight descriptor words"); + const VkComponentMapping components{VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A}; + const auto firstKey = MakeTextureKey(VK_NULL_HANDLE, first, components); + const auto secondKey = MakeTextureKey(VK_NULL_HANDLE, second, components); + return firstKey == secondKey && TextureKeyHash{}(firstKey) == TextureKeyHash{}(secondKey); +} + void FlushCachedTextures(VkDevice device) { Require(device != VK_NULL_HANDLE, "cannot flush textures without a Vulkan device"); GuestMemory::AssertGpuLockHeld("FlushCachedTextures"); diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp index 44b30348e..fb5c04cc9 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp @@ -1,6 +1,7 @@ #include "GraphicsTests.hpp" #include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" #include #include @@ -408,4 +409,24 @@ void RunGuestTextureResourceTests() { Require(!MatchesGuestDimension(Shape::Image3D, TextureDimension::k2D), "3D shape must never match a guest dimension"); Require(!MatchesGuestDimension(Shape::Image3D, TextureDimension::k2DArray), "3D shape must never match a guest dimension"); Require(!MatchesGuestDimension(Shape::Image3D, TextureDimension::kCube), "3D shape must never match a guest dimension"); + + Fields streamed = base; + streamed.minLodWarn = 0xabc; + streamed.mipStatsCntEn = true; + streamed.mipStatsCntId = 0x5a; + Require(SampledTexturesShareEntry(pack(base), pack(streamed)), "T#s differing only in MIN_LOD_WARN, MIP_STATS_COUNTER_EN and MIP_STATS_COUNTER_ID must share a sampled texture entry"); + for (const auto& field : {&Fields::minLodWarn, &Fields::mipStatsCntId}) { + Fields one = base; + one.*field = 1; + Require(SampledTexturesShareEntry(pack(base), pack(one)), "a T# differing in one streaming-feedback field must share a sampled texture entry"); + } + Fields moved = streamed; + moved.base40 = base.base40 + 1; + Require(!SampledTexturesShareEntry(pack(base), pack(moved)), "T#s with different base addresses must not share a sampled texture entry"); + Fields reformatted = streamed; + reformatted.format = 57; + Require(!SampledTexturesShareEntry(pack(base), pack(reformatted)), "T#s with different formats must not share a sampled texture entry"); + Fields cornered = base; + cornered.cornerSample = true; + Require(!SampledTexturesShareEntry(pack(base), pack(cornered)), "T#s differing in a field next to the feedback fields must not share a sampled texture entry"); }