Pool.cs
1 using Ryujinx.Graphics.Gpu.Memory; 2 using Ryujinx.Memory.Tracking; 3 using System; 4 using System.Runtime.InteropServices; 5 6 namespace Ryujinx.Graphics.Gpu.Image 7 { 8 /// <summary> 9 /// Represents a pool of GPU resources, such as samplers or textures. 10 /// </summary> 11 /// <typeparam name="T1">Type of the GPU resource</typeparam> 12 /// <typeparam name="T2">Type of the descriptor</typeparam> 13 abstract class Pool<T1, T2> : IDisposable where T2 : unmanaged 14 { 15 protected const int DescriptorSize = 0x20; 16 17 protected GpuContext Context; 18 protected PhysicalMemory PhysicalMemory; 19 protected int SequenceNumber; 20 protected int ModifiedSequenceNumber; 21 22 protected T1[] Items; 23 protected T2[] DescriptorCache; 24 25 /// <summary> 26 /// The maximum ID value of resources on the pool (inclusive). 27 /// </summary> 28 /// <remarks> 29 /// The maximum amount of resources on the pool is equal to this value plus one. 30 /// </remarks> 31 public int MaximumId { get; } 32 33 /// <summary> 34 /// The address of the pool in guest memory. 35 /// </summary> 36 public ulong Address { get; } 37 38 /// <summary> 39 /// The size of the pool in bytes. 40 /// </summary> 41 public ulong Size { get; } 42 43 private readonly MultiRegionHandle _memoryTracking; 44 private readonly Action<ulong, ulong> _modifiedDelegate; 45 46 private int _modifiedSequenceOffset; 47 private bool _modified; 48 49 /// <summary> 50 /// Creates a new instance of the GPU resource pool. 51 /// </summary> 52 /// <param name="context">GPU context that the pool belongs to</param> 53 /// <param name="physicalMemory">Physical memory where the resource descriptors are mapped</param> 54 /// <param name="address">Address of the pool in physical memory</param> 55 /// <param name="maximumId">Maximum index of an item on the pool (inclusive)</param> 56 public Pool(GpuContext context, PhysicalMemory physicalMemory, ulong address, int maximumId) 57 { 58 Context = context; 59 PhysicalMemory = physicalMemory; 60 MaximumId = maximumId; 61 62 int count = maximumId + 1; 63 64 ulong size = (ulong)(uint)count * DescriptorSize; 65 66 Items = new T1[count]; 67 DescriptorCache = new T2[count]; 68 69 Address = address; 70 Size = size; 71 72 _memoryTracking = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Pool, RegionFlags.None); 73 _memoryTracking.RegisterPreciseAction(address, size, PreciseAction); 74 _modifiedDelegate = RegionModified; 75 } 76 77 /// <summary> 78 /// Gets the descriptor for a given ID. 79 /// </summary> 80 /// <param name="id">ID of the descriptor. This is effectively a zero-based index</param> 81 /// <returns>The descriptor</returns> 82 public T2 GetDescriptor(int id) 83 { 84 return PhysicalMemory.Read<T2>(Address + (ulong)id * DescriptorSize); 85 } 86 87 /// <summary> 88 /// Gets a reference to the descriptor for a given ID. 89 /// </summary> 90 /// <param name="id">ID of the descriptor. This is effectively a zero-based index</param> 91 /// <returns>A reference to the descriptor</returns> 92 public ref readonly T2 GetDescriptorRef(int id) 93 { 94 return ref GetDescriptorRefAddress(Address + (ulong)id * DescriptorSize); 95 } 96 97 /// <summary> 98 /// Gets a reference to the descriptor for a given address. 99 /// </summary> 100 /// <param name="address">Address of the descriptor</param> 101 /// <returns>A reference to the descriptor</returns> 102 public ref readonly T2 GetDescriptorRefAddress(ulong address) 103 { 104 return ref MemoryMarshal.Cast<byte, T2>(PhysicalMemory.GetSpan(address, DescriptorSize))[0]; 105 } 106 107 /// <summary> 108 /// Gets the GPU resource with the given ID. 109 /// </summary> 110 /// <param name="id">ID of the resource. This is effectively a zero-based index</param> 111 /// <returns>The GPU resource with the given ID</returns> 112 public abstract T1 Get(int id); 113 114 /// <summary> 115 /// Gets the cached item with the given ID, or null if there is no cached item for the specified ID. 116 /// </summary> 117 /// <param name="id">ID of the item. This is effectively a zero-based index</param> 118 /// <returns>The cached item with the given ID</returns> 119 public T1 GetCachedItem(int id) 120 { 121 if (!IsValidId(id)) 122 { 123 return default; 124 } 125 126 return Items[id]; 127 } 128 129 /// <summary> 130 /// Checks if a given ID is valid and inside the range of the pool. 131 /// </summary> 132 /// <param name="id">ID of the descriptor. This is effectively a zero-based index</param> 133 /// <returns>True if the specified ID is valid, false otherwise</returns> 134 public bool IsValidId(int id) 135 { 136 return (uint)id <= MaximumId; 137 } 138 139 /// <summary> 140 /// Synchronizes host memory with guest memory. 141 /// This causes invalidation of pool entries, 142 /// if a modification of entries by the CPU is detected. 143 /// </summary> 144 public void SynchronizeMemory() 145 { 146 _modified = false; 147 _memoryTracking.QueryModified(_modifiedDelegate); 148 149 if (_modified) 150 { 151 UpdateModifiedSequence(); 152 } 153 } 154 155 /// <summary> 156 /// Indicate that a region of the pool was modified, and must be loaded from memory. 157 /// </summary> 158 /// <param name="mAddress">Start address of the modified region</param> 159 /// <param name="mSize">Size of the modified region</param> 160 private void RegionModified(ulong mAddress, ulong mSize) 161 { 162 _modified = true; 163 164 if (mAddress < Address) 165 { 166 mAddress = Address; 167 } 168 169 ulong maxSize = Address + Size - mAddress; 170 171 if (mSize > maxSize) 172 { 173 mSize = maxSize; 174 } 175 176 InvalidateRangeImpl(mAddress, mSize); 177 } 178 179 /// <summary> 180 /// Updates the modified sequence number using the current sequence number and offset, 181 /// indicating that it has been modified. 182 /// </summary> 183 protected void UpdateModifiedSequence() 184 { 185 ModifiedSequenceNumber = SequenceNumber + _modifiedSequenceOffset; 186 } 187 188 /// <summary> 189 /// An action to be performed when a precise memory access occurs to this resource. 190 /// Makes sure that the dirty flags are checked. 191 /// </summary> 192 /// <param name="address">Address of the memory action</param> 193 /// <param name="size">Size in bytes</param> 194 /// <param name="write">True if the access was a write, false otherwise</param> 195 private bool PreciseAction(ulong address, ulong size, bool write) 196 { 197 if (write && Context.SequenceNumber == SequenceNumber) 198 { 199 if (ModifiedSequenceNumber == SequenceNumber + _modifiedSequenceOffset) 200 { 201 // The modified sequence number is offset when PreciseActions occur so that 202 // users checking it will see an increment and know the pool has changed since 203 // their last look, even though the main SequenceNumber has not been changed. 204 205 _modifiedSequenceOffset++; 206 } 207 208 // Force the pool to be checked again the next time it is used. 209 SequenceNumber--; 210 } 211 212 return false; 213 } 214 215 /// <summary> 216 /// Checks if the pool was modified by comparing the current <seealso cref="ModifiedSequenceNumber"/> with a cached one. 217 /// </summary> 218 /// <param name="sequenceNumber">Cached modified sequence number</param> 219 /// <returns>True if the pool was modified, false otherwise</returns> 220 public bool WasModified(ref int sequenceNumber) 221 { 222 if (sequenceNumber != ModifiedSequenceNumber) 223 { 224 sequenceNumber = ModifiedSequenceNumber; 225 226 return true; 227 } 228 229 return false; 230 } 231 232 protected abstract void InvalidateRangeImpl(ulong address, ulong size); 233 234 protected abstract void Delete(T1 item); 235 236 /// <summary> 237 /// Performs the disposal of all resources stored on the pool. 238 /// It's an error to try using the pool after disposal. 239 /// </summary> 240 public virtual void Dispose() 241 { 242 if (Items != null) 243 { 244 for (int index = 0; index < Items.Length; index++) 245 { 246 Delete(Items[index]); 247 } 248 249 Items = null; 250 } 251 _memoryTracking.Dispose(); 252 } 253 } 254 }