Преглед изворни кода

Add a sampler pool cache and improve texture pool cache (#3487)

* Add a sampler pool cache and improve texture pool cache

* Increase disposal timestamp delta more to be on the safe side

* Nits

* Use abstract class for PoolCache, remove factory callback
gdkchan пре 3 година
родитељ
комит
3c3bcd82fe

+ 15 - 0
Ryujinx.Graphics.Gpu/GpuChannel.cs

@@ -59,9 +59,24 @@ namespace Ryujinx.Graphics.Gpu
             {
             {
                 oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
                 oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
                 oldMemoryManager.Physical.DecrementReferenceCount();
                 oldMemoryManager.Physical.DecrementReferenceCount();
+                oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler;
             }
             }
 
 
             memoryManager.Physical.BufferCache.NotifyBuffersModified += BufferManager.Rebind;
             memoryManager.Physical.BufferCache.NotifyBuffersModified += BufferManager.Rebind;
+            memoryManager.MemoryUnmapped += MemoryUnmappedHandler;
+
+            // Since the memory manager changed, make sure we will get pools from addresses of the new memory manager.
+            TextureManager.ReloadPools();
+        }
+
+        /// <summary>
+        /// Memory mappings change event handler.
+        /// </summary>
+        /// <param name="sender">Memory manager where the mappings changed</param>
+        /// <param name="e">Information about the region that is being changed</param>
+        private void MemoryUnmappedHandler(object sender, UnmapEventArgs e)
+        {
+            TextureManager.ReloadPools();
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 129 - 0
Ryujinx.Graphics.Gpu/Image/PoolCache.cs

@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    /// <summary>
+    /// Resource pool interface.
+    /// </summary>
+    /// <typeparam name="T">Resource pool type</typeparam>
+    interface IPool<T>
+    {
+        /// <summary>
+        /// Start address of the pool in memory.
+        /// </summary>
+        ulong Address { get; }
+
+        /// <summary>
+        /// Linked list node used on the texture pool cache.
+        /// </summary>
+        LinkedListNode<T> CacheNode { get; set; }
+
+        /// <summary>
+        /// Timestamp set on the last use of the pool by the cache.
+        /// </summary>
+        ulong CacheTimestamp { get; set; }
+    }
+
+    /// <summary>
+    /// Pool cache.
+    /// This can keep multiple pools, and return the current one as needed.
+    /// </summary>
+    abstract class PoolCache<T> : IDisposable where T : IPool<T>, IDisposable
+    {
+        private const int MaxCapacity = 2;
+        private const ulong MinDeltaForRemoval = 20000;
+
+        private readonly GpuContext _context;
+        private readonly LinkedList<T> _pools;
+        private ulong _currentTimestamp;
+
+        /// <summary>
+        /// Constructs a new instance of the pool.
+        /// </summary>
+        /// <param name="context">GPU context that the texture pool belongs to</param>
+        public PoolCache(GpuContext context)
+        {
+            _context = context;
+            _pools = new LinkedList<T>();
+        }
+
+        /// <summary>
+        /// Increments the internal timestamp of the cache that is used to decide when old resources will be deleted.
+        /// </summary>
+        public void Tick()
+        {
+            _currentTimestamp++;
+        }
+
+        /// <summary>
+        /// Finds a cache texture pool, or creates a new one if not found.
+        /// </summary>
+        /// <param name="channel">GPU channel that the texture pool cache belongs to</param>
+        /// <param name="address">Start address of the texture pool</param>
+        /// <param name="maximumId">Maximum ID of the texture pool</param>
+        /// <returns>The found or newly created texture pool</returns>
+        public T FindOrCreate(GpuChannel channel, ulong address, int maximumId)
+        {
+            // Remove old entries from the cache, if possible.
+            while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval)
+            {
+                T oldestPool = _pools.First.Value;
+
+                _pools.RemoveFirst();
+                oldestPool.Dispose();
+                oldestPool.CacheNode = null;
+            }
+
+            T pool;
+
+            // Try to find the pool on the cache.
+            for (LinkedListNode<T> node = _pools.First; node != null; node = node.Next)
+            {
+                pool = node.Value;
+
+                if (pool.Address == address)
+                {
+                    if (pool.CacheNode != _pools.Last)
+                    {
+                        _pools.Remove(pool.CacheNode);
+
+                        pool.CacheNode = _pools.AddLast(pool);
+                    }
+
+                    pool.CacheTimestamp = _currentTimestamp;
+
+                    return pool;
+                }
+            }
+
+            // If not found, create a new one.
+            pool = CreatePool(_context, channel, address, maximumId);
+
+            pool.CacheNode = _pools.AddLast(pool);
+            pool.CacheTimestamp = _currentTimestamp;
+
+            return pool;
+        }
+
+        /// <summary>
+        /// Creates a new instance of the pool.
+        /// </summary>
+        /// <param name="context">GPU context that the pool belongs to</param>
+        /// <param name="channel">GPU channel that the pool belongs to</param>
+        /// <param name="address">Address of the pool in guest memory</param>
+        /// <param name="maximumId">Maximum ID of the pool (equal to maximum minus one)</param>
+        protected abstract T CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId);
+
+        public void Dispose()
+        {
+            foreach (T pool in _pools)
+            {
+                pool.Dispose();
+                pool.CacheNode = null;
+            }
+
+            _pools.Clear();
+        }
+    }
+}

+ 13 - 2
Ryujinx.Graphics.Gpu/Image/SamplerPool.cs

@@ -1,16 +1,27 @@
 using Ryujinx.Graphics.Gpu.Memory;
 using Ryujinx.Graphics.Gpu.Memory;
+using System.Collections.Generic;
 
 
 namespace Ryujinx.Graphics.Gpu.Image
 namespace Ryujinx.Graphics.Gpu.Image
 {
 {
     /// <summary>
     /// <summary>
     /// Sampler pool.
     /// Sampler pool.
     /// </summary>
     /// </summary>
-    class SamplerPool : Pool<Sampler, SamplerDescriptor>
+    class SamplerPool : Pool<Sampler, SamplerDescriptor>, IPool<SamplerPool>
     {
     {
         private float _forcedAnisotropy;
         private float _forcedAnisotropy;
 
 
         /// <summary>
         /// <summary>
-        /// Constructs a new instance of the sampler pool.
+        /// Linked list node used on the sampler pool cache.
+        /// </summary>
+        public LinkedListNode<SamplerPool> CacheNode { get; set; }
+
+        /// <summary>
+        /// Timestamp used by the sampler pool cache, updated on every use of this sampler pool.
+        /// </summary>
+        public ulong CacheTimestamp { get; set; }
+
+        /// <summary>
+        /// Creates a new instance of the sampler pool.
         /// </summary>
         /// </summary>
         /// <param name="context">GPU context that the sampler pool belongs to</param>
         /// <param name="context">GPU context that the sampler pool belongs to</param>
         /// <param name="physicalMemory">Physical memory where the sampler descriptors are mapped</param>
         /// <param name="physicalMemory">Physical memory where the sampler descriptors are mapped</param>

+ 30 - 0
Ryujinx.Graphics.Gpu/Image/SamplerPoolCache.cs

@@ -0,0 +1,30 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    /// <summary>
+    /// Sampler pool cache.
+    /// This can keep multiple sampler pools, and return the current one as needed.
+    /// It is useful for applications that uses multiple sampler pools.
+    /// </summary>
+    class SamplerPoolCache : PoolCache<SamplerPool>
+    {
+        /// <summary>
+        /// Constructs a new instance of the texture pool.
+        /// </summary>
+        /// <param name="context">GPU context that the texture pool belongs to</param>
+        public SamplerPoolCache(GpuContext context) : base(context)
+        {
+        }
+
+        /// <summary>
+        /// Creates a new instance of the sampler pool.
+        /// </summary>
+        /// <param name="context">GPU context that the sampler pool belongs to</param>
+        /// <param name="channel">GPU channel that the texture pool belongs to</param>
+        /// <param name="address">Address of the sampler pool in guest memory</param>
+        /// <param name="maximumId">Maximum sampler ID of the sampler pool (equal to maximum samplers minus one)</param>
+        protected override SamplerPool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId)
+        {
+            return new SamplerPool(context, channel.MemoryManager.Physical, address, maximumId);
+        }
+    }
+}

+ 93 - 71
Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs

@@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Gpu.Image
     /// <summary>
     /// <summary>
     /// Texture bindings manager.
     /// Texture bindings manager.
     /// </summary>
     /// </summary>
-    class TextureBindingsManager : IDisposable
+    class TextureBindingsManager
     {
     {
         private const int InitialTextureStateSize = 32;
         private const int InitialTextureStateSize = 32;
         private const int InitialImageStateSize = 8;
         private const int InitialImageStateSize = 8;
@@ -22,15 +22,17 @@ namespace Ryujinx.Graphics.Gpu.Image
 
 
         private readonly bool _isCompute;
         private readonly bool _isCompute;
 
 
-        private SamplerPool _samplerPool;
-
+        private ulong _texturePoolGpuVa;
+        private int _texturePoolMaximumId;
+        private TexturePool _texturePool;
+        private ulong _samplerPoolGpuVa;
+        private int _samplerPoolMaximumId;
         private SamplerIndex _samplerIndex;
         private SamplerIndex _samplerIndex;
-
-        private ulong _texturePoolAddress;
-        private int   _texturePoolMaximumId;
+        private SamplerPool _samplerPool;
 
 
         private readonly GpuChannel _channel;
         private readonly GpuChannel _channel;
         private readonly TexturePoolCache _texturePoolCache;
         private readonly TexturePoolCache _texturePoolCache;
+        private readonly SamplerPoolCache _samplerPoolCache;
 
 
         private TexturePool _cachedTexturePool;
         private TexturePool _cachedTexturePool;
         private SamplerPool _cachedSamplerPool;
         private SamplerPool _cachedSamplerPool;
@@ -72,16 +74,25 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// </summary>
         /// </summary>
         /// <param name="context">The GPU context that the texture bindings manager belongs to</param>
         /// <param name="context">The GPU context that the texture bindings manager belongs to</param>
         /// <param name="channel">The GPU channel that the texture bindings manager belongs to</param>
         /// <param name="channel">The GPU channel that the texture bindings manager belongs to</param>
-        /// <param name="poolCache">Texture pools cache used to get texture pools from</param>
+        /// <param name="texturePoolCache">Texture pools cache used to get texture pools from</param>
+        /// <param name="samplerPoolCache">Sampler pools cache used to get sampler pools from</param>
         /// <param name="scales">Array where the scales for the currently bound textures are stored</param>
         /// <param name="scales">Array where the scales for the currently bound textures are stored</param>
         /// <param name="isCompute">True if the bindings manager is used for the compute engine</param>
         /// <param name="isCompute">True if the bindings manager is used for the compute engine</param>
-        public TextureBindingsManager(GpuContext context, GpuChannel channel, TexturePoolCache poolCache, float[] scales, bool isCompute)
+        public TextureBindingsManager(
+            GpuContext context,
+            GpuChannel channel,
+            TexturePoolCache texturePoolCache,
+            SamplerPoolCache samplerPoolCache,
+            float[] scales,
+            bool isCompute)
         {
         {
-            _context          = context;
-            _channel          = channel;
-            _texturePoolCache = poolCache;
-            _scales           = scales;
-            _isCompute        = isCompute;
+            _context = context;
+            _channel = channel;
+            _texturePoolCache = texturePoolCache;
+            _samplerPoolCache = samplerPoolCache;
+
+            _scales = scales;
+            _isCompute = isCompute;
 
 
             int stages = isCompute ? 1 : Constants.ShaderStages;
             int stages = isCompute ? 1 : Constants.ShaderStages;
 
 
@@ -173,25 +184,10 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// <param name="samplerIndex">Type of the sampler pool indexing used for bound samplers</param>
         /// <param name="samplerIndex">Type of the sampler pool indexing used for bound samplers</param>
         public void SetSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex)
         public void SetSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex)
         {
         {
-            if (gpuVa != 0)
-            {
-                ulong address = _channel.MemoryManager.Translate(gpuVa);
-
-                if (_samplerPool != null && _samplerPool.Address == address && _samplerPool.MaximumId >= maximumId)
-                {
-                    return;
-                }
-
-                _samplerPool?.Dispose();
-                _samplerPool = new SamplerPool(_context, _channel.MemoryManager.Physical, address, maximumId);
-            }
-            else
-            {
-                _samplerPool?.Dispose();
-                _samplerPool = null;
-            }
-
+            _samplerPoolGpuVa = gpuVa;
+            _samplerPoolMaximumId = maximumId;
             _samplerIndex = samplerIndex;
             _samplerIndex = samplerIndex;
+            _samplerPool = null;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -201,18 +197,9 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// <param name="maximumId">Maximum ID of the pool (total count minus one)</param>
         /// <param name="maximumId">Maximum ID of the pool (total count minus one)</param>
         public void SetTexturePool(ulong gpuVa, int maximumId)
         public void SetTexturePool(ulong gpuVa, int maximumId)
         {
         {
-            if (gpuVa != 0)
-            {
-                ulong address = _channel.MemoryManager.Translate(gpuVa);
-
-                _texturePoolAddress = address;
-                _texturePoolMaximumId = maximumId;
-            }
-            else
-            {
-                _texturePoolAddress = 0;
-                _texturePoolMaximumId = 0;
-            }
+            _texturePoolGpuVa = gpuVa;
+            _texturePoolMaximumId = maximumId;
+            _texturePool = null;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -222,13 +209,9 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// <param name="samplerId">ID of the sampler</param>
         /// <param name="samplerId">ID of the sampler</param>
         public (Texture, Sampler) GetTextureAndSampler(int textureId, int samplerId)
         public (Texture, Sampler) GetTextureAndSampler(int textureId, int samplerId)
         {
         {
-            ulong texturePoolAddress = _texturePoolAddress;
-
-            TexturePool texturePool = texturePoolAddress != 0
-                ? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId)
-                : null;
+            (TexturePool texturePool, SamplerPool samplerPool) = GetPools();
 
 
-            return (texturePool.Get(textureId), _samplerPool.Get(samplerId));
+            return (texturePool.Get(textureId), samplerPool.Get(samplerId));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -340,13 +323,7 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
         /// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
         public bool CommitBindings(ShaderSpecializationState specState)
         public bool CommitBindings(ShaderSpecializationState specState)
         {
         {
-            ulong texturePoolAddress = _texturePoolAddress;
-
-            TexturePool texturePool = texturePoolAddress != 0
-                ? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId)
-                : null;
-
-            SamplerPool samplerPool = _samplerPool;
+            (TexturePool texturePool, SamplerPool samplerPool) = GetPools();
 
 
             // Check if the texture pool has been modified since bindings were last committed.
             // Check if the texture pool has been modified since bindings were last committed.
             // If it wasn't, then it's possible to avoid looking up textures again when the handle remains the same.
             // If it wasn't, then it's possible to avoid looking up textures again when the handle remains the same.
@@ -381,7 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Image
 
 
             if (_isCompute)
             if (_isCompute)
             {
             {
-                specStateMatches &= CommitTextureBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
+                specStateMatches &= CommitTextureBindings(texturePool, samplerPool, ShaderStage.Compute, 0, poolModified, specState);
                 specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
                 specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
             }
             }
             else
             else
@@ -390,7 +367,7 @@ namespace Ryujinx.Graphics.Gpu.Image
                 {
                 {
                     int stageIndex = (int)stage - 1;
                     int stageIndex = (int)stage - 1;
 
 
-                    specStateMatches &= CommitTextureBindings(texturePool, stage, stageIndex, poolModified, specState);
+                    specStateMatches &= CommitTextureBindings(texturePool, samplerPool, stage, stageIndex, poolModified, specState);
                     specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
                     specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
                 }
                 }
             }
             }
@@ -447,13 +424,20 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// Ensures that the texture bindings are visible to the host GPU.
         /// Ensures that the texture bindings are visible to the host GPU.
         /// Note: this actually performs the binding using the host graphics API.
         /// Note: this actually performs the binding using the host graphics API.
         /// </summary>
         /// </summary>
-        /// <param name="pool">The current texture pool</param>
+        /// <param name="texturePool">The current texture pool</param>
+        /// <param name="samplerPool">The current sampler pool</param>
         /// <param name="stage">The shader stage using the textures to be bound</param>
         /// <param name="stage">The shader stage using the textures to be bound</param>
         /// <param name="stageIndex">The stage number of the specified shader stage</param
         /// <param name="stageIndex">The stage number of the specified shader stage</param
         /// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param>
         /// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param>
         /// <param name="specState">Specialization state for the bound shader</param>
         /// <param name="specState">Specialization state for the bound shader</param>
         /// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
         /// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
-        private bool CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState)
+        private bool CommitTextureBindings(
+            TexturePool texturePool,
+            SamplerPool samplerPool,
+            ShaderStage stage,
+            int stageIndex,
+            bool poolModified,
+            ShaderSpecializationState specState)
         {
         {
             int textureCount = _textureBindingsCount[stageIndex];
             int textureCount = _textureBindingsCount[stageIndex];
             if (textureCount == 0)
             if (textureCount == 0)
@@ -461,9 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Image
                 return true;
                 return true;
             }
             }
 
 
-            var samplerPool = _samplerPool;
-
-            if (pool == null)
+            if (texturePool == null)
             {
             {
                 Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses textures, but texture pool was not set.");
                 Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses textures, but texture pool was not set.");
                 return true;
                 return true;
@@ -528,7 +510,7 @@ namespace Ryujinx.Graphics.Gpu.Image
                 state.TextureHandle = textureId;
                 state.TextureHandle = textureId;
                 state.SamplerHandle = samplerId;
                 state.SamplerHandle = samplerId;
 
 
-                ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture);
+                ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, out Texture texture);
 
 
                 specStateMatches &= specState.MatchesTexture(stage, index, descriptor);
                 specStateMatches &= specState.MatchesTexture(stage, index, descriptor);
 
 
@@ -820,20 +802,60 @@ namespace Ryujinx.Graphics.Gpu.Image
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Force all bound textures and images to be rebound the next time CommitBindings is called.
+        /// Gets the texture and sampler pool for the GPU virtual address that are currently set.
         /// </summary>
         /// </summary>
-        public void Rebind()
+        /// <returns>The texture and sampler pools</returns>
+        private (TexturePool, SamplerPool) GetPools()
         {
         {
-            Array.Clear(_textureState);
-            Array.Clear(_imageState);
+            MemoryManager memoryManager = _channel.MemoryManager;
+
+            TexturePool texturePool = _texturePool;
+            SamplerPool samplerPool = _samplerPool;
+
+            if (texturePool == null)
+            {
+                ulong poolAddress = memoryManager.Translate(_texturePoolGpuVa);
+
+                if (poolAddress != MemoryManager.PteUnmapped)
+                {
+                    texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId);
+                    _texturePool = texturePool;
+                }
+            }
+
+            if (samplerPool == null)
+            {
+                ulong poolAddress = memoryManager.Translate(_samplerPoolGpuVa);
+
+                if (poolAddress != MemoryManager.PteUnmapped)
+                {
+                    samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId);
+                    _samplerPool = samplerPool;
+                }
+            }
+
+            return (texturePool, samplerPool);
+        }
+
+        /// <summary>
+        /// Forces the texture and sampler pools to be re-loaded from the cache on next use.
+        /// </summary>
+        /// <remarks>
+        /// This should be called if the memory mappings change, to ensure the correct pools are being used.
+        /// </remarks>
+        public void ReloadPools()
+        {
+            _samplerPool = null;
+            _texturePool = null;
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Disposes all textures and samplers in the cache.
+        /// Force all bound textures and images to be rebound the next time CommitBindings is called.
         /// </summary>
         /// </summary>
-        public void Dispose()
+        public void Rebind()
         {
         {
-            _samplerPool?.Dispose();
+            Array.Clear(_textureState);
+            Array.Clear(_imageState);
         }
         }
     }
     }
 }
 }

+ 23 - 4
Ryujinx.Graphics.Gpu/Image/TextureManager.cs

@@ -16,6 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Image
         private readonly TextureBindingsManager _cpBindingsManager;
         private readonly TextureBindingsManager _cpBindingsManager;
         private readonly TextureBindingsManager _gpBindingsManager;
         private readonly TextureBindingsManager _gpBindingsManager;
         private readonly TexturePoolCache _texturePoolCache;
         private readonly TexturePoolCache _texturePoolCache;
+        private readonly SamplerPoolCache _samplerPoolCache;
 
 
         private readonly Texture[] _rtColors;
         private readonly Texture[] _rtColors;
         private readonly ITexture[] _rtHostColors;
         private readonly ITexture[] _rtHostColors;
@@ -41,13 +42,15 @@ namespace Ryujinx.Graphics.Gpu.Image
             _channel = channel;
             _channel = channel;
 
 
             TexturePoolCache texturePoolCache = new TexturePoolCache(context);
             TexturePoolCache texturePoolCache = new TexturePoolCache(context);
+            SamplerPoolCache samplerPoolCache = new SamplerPoolCache(context);
 
 
             float[] scales = new float[64];
             float[] scales = new float[64];
             new Span<float>(scales).Fill(1f);
             new Span<float>(scales).Fill(1f);
 
 
-            _cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: true);
-            _gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, scales, isCompute: false);
+            _cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, scales, isCompute: true);
+            _gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, scales, isCompute: false);
             _texturePoolCache = texturePoolCache;
             _texturePoolCache = texturePoolCache;
+            _samplerPoolCache = samplerPoolCache;
 
 
             _rtColors = new Texture[Constants.TotalRenderTargets];
             _rtColors = new Texture[Constants.TotalRenderTargets];
             _rtHostColors = new ITexture[Constants.TotalRenderTargets];
             _rtHostColors = new ITexture[Constants.TotalRenderTargets];
@@ -368,6 +371,10 @@ namespace Ryujinx.Graphics.Gpu.Image
             // we must rebind everything.
             // we must rebind everything.
             // Since compute work happens less often, we always do that
             // Since compute work happens less often, we always do that
             // before and after the compute dispatch.
             // before and after the compute dispatch.
+
+            _texturePoolCache.Tick();
+            _samplerPoolCache.Tick();
+
             _cpBindingsManager.Rebind();
             _cpBindingsManager.Rebind();
             bool result = _cpBindingsManager.CommitBindings(specState);
             bool result = _cpBindingsManager.CommitBindings(specState);
             _gpBindingsManager.Rebind();
             _gpBindingsManager.Rebind();
@@ -382,6 +389,9 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
         /// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
         public bool CommitGraphicsBindings(ShaderSpecializationState specState)
         public bool CommitGraphicsBindings(ShaderSpecializationState specState)
         {
         {
+            _texturePoolCache.Tick();
+            _samplerPoolCache.Tick();
+
             bool result = _gpBindingsManager.CommitBindings(specState);
             bool result = _gpBindingsManager.CommitBindings(specState);
 
 
             UpdateRenderTargets();
             UpdateRenderTargets();
@@ -501,6 +511,15 @@ namespace Ryujinx.Graphics.Gpu.Image
             _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
             _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
         }
         }
 
 
+        /// <summary>
+        /// Forces the texture and sampler pools to be re-loaded from the cache on next use.
+        /// </summary>
+        public void ReloadPools()
+        {
+            _cpBindingsManager.ReloadPools();
+            _gpBindingsManager.ReloadPools();
+        }
+
         /// <summary>
         /// <summary>
         /// Forces all textures, samplers, images and render targets to be rebound the next time
         /// Forces all textures, samplers, images and render targets to be rebound the next time
         /// CommitGraphicsBindings is called.
         /// CommitGraphicsBindings is called.
@@ -523,8 +542,8 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// </summary>
         /// </summary>
         public void Dispose()
         public void Dispose()
         {
         {
-            _cpBindingsManager.Dispose();
-            _gpBindingsManager.Dispose();
+            // Textures are owned by the texture cache, so we shouldn't dispose the texture pool cache.
+            _samplerPoolCache.Dispose();
 
 
             for (int i = 0; i < _rtColors.Length; i++)
             for (int i = 0; i < _rtColors.Length; i++)
             {
             {

+ 8 - 3
Ryujinx.Graphics.Gpu/Image/TexturePool.cs

@@ -10,19 +10,24 @@ namespace Ryujinx.Graphics.Gpu.Image
     /// <summary>
     /// <summary>
     /// Texture pool.
     /// Texture pool.
     /// </summary>
     /// </summary>
-    class TexturePool : Pool<Texture, TextureDescriptor>
+    class TexturePool : Pool<Texture, TextureDescriptor>, IPool<TexturePool>
     {
     {
         private readonly GpuChannel _channel;
         private readonly GpuChannel _channel;
         private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
         private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
         private TextureDescriptor _defaultDescriptor;
         private TextureDescriptor _defaultDescriptor;
 
 
         /// <summary>
         /// <summary>
-        /// Intrusive linked list node used on the texture pool cache.
+        /// Linked list node used on the texture pool cache.
         /// </summary>
         /// </summary>
         public LinkedListNode<TexturePool> CacheNode { get; set; }
         public LinkedListNode<TexturePool> CacheNode { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Constructs a new instance of the texture pool.
+        /// Timestamp used by the texture pool cache, updated on every use of this texture pool.
+        /// </summary>
+        public ulong CacheTimestamp { get; set; }
+
+        /// <summary>
+        /// Creates a new instance of the texture pool.
         /// </summary>
         /// </summary>
         /// <param name="context">GPU context that the texture pool belongs to</param>
         /// <param name="context">GPU context that the texture pool belongs to</param>
         /// <param name="channel">GPU channel that the texture pool belongs to</param>
         /// <param name="channel">GPU channel that the texture pool belongs to</param>

+ 9 - 55
Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs

@@ -1,6 +1,3 @@
-using System;
-using System.Collections.Generic;
-
 namespace Ryujinx.Graphics.Gpu.Image
 namespace Ryujinx.Graphics.Gpu.Image
 {
 {
     /// <summary>
     /// <summary>
@@ -8,69 +5,26 @@ namespace Ryujinx.Graphics.Gpu.Image
     /// This can keep multiple texture pools, and return the current one as needed.
     /// This can keep multiple texture pools, and return the current one as needed.
     /// It is useful for applications that uses multiple texture pools.
     /// It is useful for applications that uses multiple texture pools.
     /// </summary>
     /// </summary>
-    class TexturePoolCache
+    class TexturePoolCache : PoolCache<TexturePool>
     {
     {
-        private const int MaxCapacity = 4;
-
-        private readonly GpuContext _context;
-        private readonly LinkedList<TexturePool> _pools;
-
         /// <summary>
         /// <summary>
         /// Constructs a new instance of the texture pool.
         /// Constructs a new instance of the texture pool.
         /// </summary>
         /// </summary>
         /// <param name="context">GPU context that the texture pool belongs to</param>
         /// <param name="context">GPU context that the texture pool belongs to</param>
-        public TexturePoolCache(GpuContext context)
+        public TexturePoolCache(GpuContext context) : base(context)
         {
         {
-            _context = context;
-            _pools = new LinkedList<TexturePool>();
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Finds a cache texture pool, or creates a new one if not found.
+        /// Creates a new instance of the texture pool.
         /// </summary>
         /// </summary>
-        /// <param name="channel">GPU channel that the texture pool cache belongs to</param>
-        /// <param name="address">Start address of the texture pool</param>
-        /// <param name="maximumId">Maximum ID of the texture pool</param>
-        /// <returns>The found or newly created texture pool</returns>
-        public TexturePool FindOrCreate(GpuChannel channel, ulong address, int maximumId)
+        /// <param name="context">GPU context that the texture pool belongs to</param>
+        /// <param name="channel">GPU channel that the texture pool belongs to</param>
+        /// <param name="address">Address of the texture pool in guest memory</param>
+        /// <param name="maximumId">Maximum texture ID of the texture pool (equal to maximum textures minus one)</param>
+        protected override TexturePool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId)
         {
         {
-            TexturePool pool;
-
-            // First we try to find the pool.
-            for (LinkedListNode<TexturePool> node = _pools.First; node != null; node = node.Next)
-            {
-                pool = node.Value;
-
-                if (pool.Address == address)
-                {
-                    if (pool.CacheNode != _pools.Last)
-                    {
-                        _pools.Remove(pool.CacheNode);
-
-                        pool.CacheNode = _pools.AddLast(pool);
-                    }
-
-                    return pool;
-                }
-            }
-
-            // If not found, create a new one.
-            pool = new TexturePool(_context, channel, address, maximumId);
-
-            pool.CacheNode = _pools.AddLast(pool);
-
-            if (_pools.Count > MaxCapacity)
-            {
-                TexturePool oldestPool = _pools.First.Value;
-
-                _pools.RemoveFirst();
-
-                oldestPool.Dispose();
-
-                oldestPool.CacheNode = null;
-            }
-
-            return pool;
+            return new TexturePool(context, channel, address, maximumId);
         }
         }
     }
     }
 }
 }