From 224a36eba787e1fbc05248cde84b1336595d56ec Mon Sep 17 00:00:00 2001 From: TarkusTK <90155059+TarkusR@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:22:19 +0200 Subject: [PATCH] [Gpu] Stop retrying array uploads that overrun their allocation (#476) A 2D-array texture whose Depth times the per-slice stride runs past its real allocation fails a slice read partway through the upload loop, and falls through to the single-slice path after already detiling the layers it did read. That fall-through builds the texture with ArrayLayers defaulting to 1, so the presenter caches it under a one-layer key while the next draw looks it up with ArrayLayers = Depth. The two never match, so the texture misses the cache and repeats the whole read-and-detile on every draw, throwing the result away each time. Detiling is per-texel swizzle math, so one such texture retried a few times per frame is expensive: it measured 568-879 ms of every second in Demon's Souls, against a 1.4 second frame. An allocation that is too short stays too short, so remembering the address and not retrying it costs nothing and repairs the cache key as a side effect: with the array upload skipped, arrayUploadLayers is 1, which is exactly what the fall-through texture reports. Tested on Demon's Souls (PPSA01342): 0.7 fps to 3.6-4.1 fps, CPU detile time per second from ~700 ms to 0, and arrayed textures go from missing the cache on every draw to hitting it every time. 28 of the 29 array uploads in that run already succeeded and are unaffected; only the one overrunning texture now falls back to its base slice. 495 tests pass. --- src/SharpEmu.Libs/Agc/AgcExports.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/SharpEmu.Libs/Agc/AgcExports.cs b/src/SharpEmu.Libs/Agc/AgcExports.cs index 8dc7f67..fbd07ce 100644 --- a/src/SharpEmu.Libs/Agc/AgcExports.cs +++ b/src/SharpEmu.Libs/Agc/AgcExports.cs @@ -221,6 +221,7 @@ public static partial class AgcExports (ulong Es, ulong State, ulong AliasAlignment), IGuestCompiledShader> _depthOnlyVertexShaderCache = new(); private static readonly Dictionary _shaderHeadersByCode = new(); + private static readonly ConcurrentDictionary _arrayUploadUnsupported = new(); private static readonly bool _traceAgc = string.Equals( Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", @@ -7984,7 +7985,8 @@ public static partial class AgcExports descriptor.Address != 0 && (descriptor.Type == Gen5TextureType2DArray || descriptor.Type == Gen5TextureType1DArray) && - descriptor.Depth > 1; + descriptor.Depth > 1 && + !_arrayUploadUnsupported.ContainsKey(descriptor.Address); var arrayUploadLayers = wantsArrayUpload ? descriptor.Depth : 1u; // Upload-known (not plain availability): the presenter's answer goes @@ -8213,6 +8215,8 @@ public static partial class AgcExports return true; } } + + _arrayUploadUnsupported.TryAdd(descriptor.Address, 0); } var source = new byte[(int)physicalSourceByteCount];