using Ryujinx.Graphics.Gpu.Memory; using System; namespace Ryujinx.Graphics.Gpu.Image { /// <summary> /// Represents a pool of GPU resources, such as samplers or textures. /// </summary> /// <typeparam name="T">Type of the GPU resource</typeparam> abstract class Pool<T> : IDisposable { protected const int DescriptorSize = 0x20; protected GpuContext Context; protected T[] Items; /// <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 (ulong, ulong)[] _modifiedRanges; public Pool(GpuContext context, ulong address, int maximumId) { Context = context; MaximumId = maximumId; int count = maximumId + 1; ulong size = (ulong)(uint)count * DescriptorSize;; Items = new T[count]; Address = address; Size = size; _modifiedRanges = new (ulong, ulong)[size / PhysicalMemory.PageSize]; } /// <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 T Get(int id); /// <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() { int count = Context.PhysicalMemory.QueryModified(Address, Size, ResourceName.TexturePool, _modifiedRanges); for (int index = 0; index < count; index++) { (ulong mAddress, ulong mSize) = _modifiedRanges[index]; if (mAddress < Address) { mAddress = Address; } ulong maxSize = Address + Size - mAddress; if (mSize > maxSize) { mSize = maxSize; } InvalidateRangeImpl(mAddress, mSize); } } private void InvalidateRangeInternal(ulong offset, int size) { InvalidateRangeImpl(Address + offset, (ulong)size); } /// <summary> /// Invalidates a range of memory of the GPU resource pool. /// Entries that falls inside the speicified range will be invalidated, /// causing all the data to be reloaded from guest memory. /// </summary> /// <param name="address">The start address of the range to invalidate</param> /// <param name="size">The size of the range to invalidate</param> public void InvalidateRange(ulong address, ulong size) { ulong endAddress = address + size; ulong texturePoolEndAddress = Address + Size; // If the range being invalidated is not overlapping the texture pool range, // then we don't have anything to do, exit early. if (address >= texturePoolEndAddress || endAddress <= Address) { return; } if (address < Address) { address = Address; } if (endAddress > texturePoolEndAddress) { endAddress = texturePoolEndAddress; } size = endAddress - address; InvalidateRangeImpl(address, size); } protected abstract void InvalidateRangeImpl(ulong address, ulong size); protected abstract void Delete(T 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 void Dispose() { if (Items != null) { for (int index = 0; index < Items.Length; index++) { Delete(Items[index]); } Items = null; } } } }