aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Gpu/Image/Pool.cs
blob: ddd6980773e485e7068378631723fc28e4a4c556 (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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.Gpu.Memory;
using System;
using System.Runtime.InteropServices;

namespace Ryujinx.Graphics.Gpu.Image
{
    /// <summary>
    /// Represents a pool of GPU resources, such as samplers or textures.
    /// </summary>
    /// <typeparam name="T1">Type of the GPU resource</typeparam>
    /// <typeparam name="T2">Type of the descriptor</typeparam>
    abstract class Pool<T1, T2> : IDisposable where T2 : unmanaged
    {
        protected const int DescriptorSize = 0x20;

        protected GpuContext Context;
        protected PhysicalMemory PhysicalMemory;
        protected int SequenceNumber;
        protected int ModifiedSequenceNumber;

        protected T1[] Items;
        protected T2[] DescriptorCache;

        /// <summary>
        /// The maximum ID value of resources on the pool (inclusive).
        /// </summary>
        /// <remarks>
        /// The maximum amount of resources on the pool is equal to this value plus one.
        /// </remarks>
        public int MaximumId { get; }

        /// <summary>
        /// The address of the pool in guest memory.
        /// </summary>
        public ulong Address { get; }

        /// <summary>
        /// The size of the pool in bytes.
        /// </summary>
        public ulong Size { get; }

        private readonly CpuMultiRegionHandle _memoryTracking;
        private readonly Action<ulong, ulong> _modifiedDelegate;

        private int _modifiedSequenceOffset;
        private bool _modified;

        /// <summary>
        /// Creates a new instance of the GPU resource pool.
        /// </summary>
        /// <param name="context">GPU context that the pool belongs to</param>
        /// <param name="physicalMemory">Physical memory where the resource descriptors are mapped</param>
        /// <param name="address">Address of the pool in physical memory</param>
        /// <param name="maximumId">Maximum index of an item on the pool (inclusive)</param>
        public Pool(GpuContext context, PhysicalMemory physicalMemory, ulong address, int maximumId)
        {
            Context = context;
            PhysicalMemory = physicalMemory;
            MaximumId = maximumId;

            int count = maximumId + 1;

            ulong size = (ulong)(uint)count * DescriptorSize;

            Items = new T1[count];
            DescriptorCache = new T2[count];

            Address = address;
            Size    = size;

            _memoryTracking = physicalMemory.BeginGranularTracking(address, size);
            _memoryTracking.RegisterPreciseAction(address, size, PreciseAction);
            _modifiedDelegate = RegionModified;
        }

        /// <summary>
        /// Gets the descriptor for a given ID.
        /// </summary>
        /// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
        /// <returns>The descriptor</returns>
        public T2 GetDescriptor(int id)
        {
            return PhysicalMemory.Read<T2>(Address + (ulong)id * DescriptorSize);
        }

        /// <summary>
        /// Gets a reference to the descriptor for a given ID.
        /// </summary>
        /// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
        /// <returns>A reference to the descriptor</returns>
        public ref readonly T2 GetDescriptorRef(int id)
        {
            return ref MemoryMarshal.Cast<byte, T2>(PhysicalMemory.GetSpan(Address + (ulong)id * DescriptorSize, DescriptorSize))[0];
        }

        /// <summary>
        /// Gets the GPU resource with the given ID.
        /// </summary>
        /// <param name="id">ID of the resource. This is effectively a zero-based index</param>
        /// <returns>The GPU resource with the given ID</returns>
        public abstract T1 Get(int id);

        /// <summary>
        /// Checks if a given ID is valid and inside the range of the pool.
        /// </summary>
        /// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
        /// <returns>True if the specified ID is valid, false otherwise</returns>
        public bool IsValidId(int id)
        {
            return (uint)id <= MaximumId;
        }

        /// <summary>
        /// Synchronizes host memory with guest memory.
        /// This causes invalidation of pool entries,
        /// if a modification of entries by the CPU is detected.
        /// </summary>
        public void SynchronizeMemory()
        {
            _modified = false;
            _memoryTracking.QueryModified(_modifiedDelegate);

            if (_modified)
            {
                UpdateModifiedSequence();
            }
        }

        /// <summary>
        /// Indicate that a region of the pool was modified, and must be loaded from memory.
        /// </summary>
        /// <param name="mAddress">Start address of the modified region</param>
        /// <param name="mSize">Size of the modified region</param>
        private void RegionModified(ulong mAddress, ulong mSize)
        {
            _modified = true;

            if (mAddress < Address)
            {
                mAddress = Address;
            }

            ulong maxSize = Address + Size - mAddress;

            if (mSize > maxSize)
            {
                mSize = maxSize;
            }

            InvalidateRangeImpl(mAddress, mSize);
        }

        /// <summary>
        /// Updates the modified sequence number using the current sequence number and offset,
        /// indicating that it has been modified.
        /// </summary>
        protected void UpdateModifiedSequence()
        {
            ModifiedSequenceNumber = SequenceNumber + _modifiedSequenceOffset;
        }

        /// <summary>
        /// An action to be performed when a precise memory access occurs to this resource.
        /// Makes sure that the dirty flags are checked.
        /// </summary>
        /// <param name="address">Address of the memory action</param>
        /// <param name="size">Size in bytes</param>
        /// <param name="write">True if the access was a write, false otherwise</param>
        private bool PreciseAction(ulong address, ulong size, bool write)
        {
            if (write && Context.SequenceNumber == SequenceNumber)
            {
                if (ModifiedSequenceNumber == SequenceNumber + _modifiedSequenceOffset)
                {
                    // The modified sequence number is offset when PreciseActions occur so that
                    // users checking it will see an increment and know the pool has changed since
                    // their last look, even though the main SequenceNumber has not been changed.

                    _modifiedSequenceOffset++;
                }

                // Force the pool to be checked again the next time it is used.
                SequenceNumber--;
            }

            return false;
        }

        protected abstract void InvalidateRangeImpl(ulong address, ulong size);

        protected abstract void Delete(T1 item);

        /// <summary>
        /// Performs the disposal of all resources stored on the pool.
        /// It's an error to try using the pool after disposal.
        /// </summary>
        public virtual void Dispose()
        {
            if (Items != null)
            {
                for (int index = 0; index < Items.Length; index++)
                {
                    Delete(Items[index]);
                }

                Items = null;
            }
            _memoryTracking.Dispose();
        }
    }
}