aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs
blob: 50872ab632c848f4e2f1c4c67dc59772abcfee02 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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>
        /// <param name="bindingsArrayCache">Cache of texture array bindings</param>
        /// <returns>The found or newly created texture pool</returns>
        public T FindOrCreate(GpuChannel channel, ulong address, int maximumId, TextureBindingsArrayCache bindingsArrayCache)
        {
            // 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;
                bindingsArrayCache.RemoveAllWithPool(oldestPool);
            }

            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);
                        _pools.AddLast(pool.CacheNode);
                    }

                    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();
        }
    }
}