aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
diff options
context:
space:
mode:
authorTSR Berry <20988865+TSRBerry@users.noreply.github.com>2023-04-08 01:22:00 +0200
committerMary <thog@protonmail.com>2023-04-27 23:51:14 +0200
commitcee712105850ac3385cd0091a923438167433f9f (patch)
tree4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
parentcd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff)
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs')
-rw-r--r--src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs603
1 files changed, 603 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
new file mode 100644
index 00000000..5277e789
--- /dev/null
+++ b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -0,0 +1,603 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Texture;
+using Ryujinx.Memory.Range;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ /// <summary>
+ /// Texture pool.
+ /// </summary>
+ class TexturePool : Pool<Texture, TextureDescriptor>, IPool<TexturePool>
+ {
+ /// <summary>
+ /// A request to dereference a texture from a pool.
+ /// </summary>
+ private struct DereferenceRequest
+ {
+ /// <summary>
+ /// Whether the dereference is due to a mapping change or not.
+ /// </summary>
+ public readonly bool IsRemapped;
+
+ /// <summary>
+ /// The texture being dereferenced.
+ /// </summary>
+ public readonly Texture Texture;
+
+ /// <summary>
+ /// The ID of the pool entry this reference belonged to.
+ /// </summary>
+ public readonly int ID;
+
+ /// <summary>
+ /// Create a dereference request for a texture with a specific pool ID, and remapped flag.
+ /// </summary>
+ /// <param name="isRemapped">Whether the dereference is due to a mapping change or not</param>
+ /// <param name="texture">The texture being dereferenced</param>
+ /// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
+ private DereferenceRequest(bool isRemapped, Texture texture, int id)
+ {
+ IsRemapped = isRemapped;
+ Texture = texture;
+ ID = id;
+ }
+
+ /// <summary>
+ /// Create a dereference request for a texture removal.
+ /// </summary>
+ /// <param name="texture">The texture being removed</param>
+ /// <returns>A texture removal dereference request</returns>
+ public static DereferenceRequest Remove(Texture texture)
+ {
+ return new DereferenceRequest(false, texture, 0);
+ }
+
+ /// <summary>
+ /// Create a dereference request for a texture remapping with a specific pool ID.
+ /// </summary>
+ /// <param name="texture">The texture being remapped</param>
+ /// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
+ /// <returns>A remap dereference request</returns>
+ public static DereferenceRequest Remap(Texture texture, int id)
+ {
+ return new DereferenceRequest(true, texture, id);
+ }
+ }
+
+ private readonly GpuChannel _channel;
+ private readonly ConcurrentQueue<DereferenceRequest> _dereferenceQueue = new ConcurrentQueue<DereferenceRequest>();
+ private TextureDescriptor _defaultDescriptor;
+
+ /// <summary>
+ /// Linked list node used on the texture pool cache.
+ /// </summary>
+ public LinkedListNode<TexturePool> CacheNode { get; set; }
+
+ /// <summary>
+ /// 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>
+ /// <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>
+ public TexturePool(GpuContext context, GpuChannel channel, ulong address, int maximumId) : base(context, channel.MemoryManager.Physical, address, maximumId)
+ {
+ _channel = channel;
+ }
+
+ /// <summary>
+ /// Gets the texture descripor and texture with the given ID with no bounds check or synchronization.
+ /// </summary>
+ /// <param name="id">ID of the texture. This is effectively a zero-based index</param>
+ /// <param name="texture">The texture with the given ID</param>
+ /// <returns>The texture descriptor with the given ID</returns>
+ private ref readonly TextureDescriptor GetInternal(int id, out Texture texture)
+ {
+ texture = Items[id];
+
+ ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(id);
+
+ if (texture == null)
+ {
+ texture = PhysicalMemory.TextureCache.FindShortCache(descriptor);
+
+ if (texture == null)
+ {
+ TextureInfo info = GetInfo(descriptor, out int layerSize);
+
+ // The dereference queue can put our texture back on the cache.
+ if ((texture = ProcessDereferenceQueue(id)) != null)
+ {
+ return ref descriptor;
+ }
+
+ texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
+
+ // If this happens, then the texture address is invalid, we can't add it to the cache.
+ if (texture == null)
+ {
+ return ref descriptor;
+ }
+ }
+ else
+ {
+ texture.SynchronizeMemory();
+ }
+
+ Items[id] = texture;
+
+ texture.IncrementReferenceCount(this, id, descriptor.UnpackAddress());
+
+ DescriptorCache[id] = descriptor;
+ }
+ else
+ {
+ // On the path above (texture not yet in the pool), memory is automatically synchronized on texture creation.
+ texture.SynchronizeMemory();
+ }
+
+ return ref descriptor;
+ }
+
+ /// <summary>
+ /// Gets the texture with the given ID.
+ /// </summary>
+ /// <param name="id">ID of the texture. This is effectively a zero-based index</param>
+ /// <returns>The texture with the given ID</returns>
+ public override Texture Get(int id)
+ {
+ if ((uint)id >= Items.Length)
+ {
+ return null;
+ }
+
+ if (SequenceNumber != Context.SequenceNumber)
+ {
+ SequenceNumber = Context.SequenceNumber;
+
+ SynchronizeMemory();
+ }
+
+ GetInternal(id, out Texture texture);
+
+ return texture;
+ }
+
+ /// <summary>
+ /// Gets the texture descriptor and texture with the given ID.
+ /// </summary>
+ /// <remarks>
+ /// This method assumes that the pool has been manually synchronized before doing binding.
+ /// </remarks>
+ /// <param name="id">ID of the texture. This is effectively a zero-based index</param>
+ /// <param name="texture">The texture with the given ID</param>
+ /// <returns>The texture descriptor with the given ID</returns>
+ public ref readonly TextureDescriptor GetForBinding(int id, out Texture texture)
+ {
+ if ((uint)id >= Items.Length)
+ {
+ texture = null;
+ return ref _defaultDescriptor;
+ }
+
+ // When getting for binding, assume the pool has already been synchronized.
+
+ return ref GetInternal(id, out texture);
+ }
+
+ /// <summary>
+ /// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
+ /// </summary>
+ /// <returns>A number that increments each time a modification is detected</returns>
+ public int CheckModified()
+ {
+ if (SequenceNumber != Context.SequenceNumber)
+ {
+ SequenceNumber = Context.SequenceNumber;
+
+ SynchronizeMemory();
+ }
+
+ return ModifiedSequenceNumber;
+ }
+
+ /// <summary>
+ /// Forcibly remove a texture from this pool's items.
+ /// If deferred, the dereference will be queued to occur on the render thread.
+ /// </summary>
+ /// <param name="texture">The texture being removed</param>
+ /// <param name="id">The ID of the texture in this pool</param>
+ /// <param name="deferred">If true, queue the dereference to happen on the render thread, otherwise dereference immediately</param>
+ public void ForceRemove(Texture texture, int id, bool deferred)
+ {
+ var previous = Interlocked.Exchange(ref Items[id], null);
+
+ if (deferred)
+ {
+ if (previous != null)
+ {
+ _dereferenceQueue.Enqueue(DereferenceRequest.Remove(texture));
+ }
+ }
+ else
+ {
+ texture.DecrementReferenceCount();
+ }
+ }
+
+ /// <summary>
+ /// Queues a request to update a texture's mapping.
+ /// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped.
+ /// </summary>
+ /// <param name="texture">Texture with potential mapping change</param>
+ /// <param name="id">ID in cache of texture with potential mapping change</param>
+ public void QueueUpdateMapping(Texture texture, int id)
+ {
+ if (Interlocked.Exchange(ref Items[id], null) == texture)
+ {
+ _dereferenceQueue.Enqueue(DereferenceRequest.Remap(texture, id));
+ }
+ }
+
+ /// <summary>
+ /// Process the dereference queue, decrementing the reference count for each texture in it.
+ /// This is used to ensure that texture disposal happens on the render thread.
+ /// </summary>
+ /// <param name="id">The ID of the entry that triggered this method</param>
+ /// <returns>Texture that matches the entry ID if it has been readded to the cache.</returns>
+ private Texture ProcessDereferenceQueue(int id = -1)
+ {
+ while (_dereferenceQueue.TryDequeue(out DereferenceRequest request))
+ {
+ Texture texture = request.Texture;
+
+ // Unmapped storage textures can swap their ranges. The texture must be storage with no views or dependencies.
+ // TODO: Would need to update ranges on views, or guarantee that ones where the range changes can be instantly deleted.
+
+ if (request.IsRemapped && texture.Group.Storage == texture && !texture.HasViews && !texture.Group.HasCopyDependencies)
+ {
+ // Has the mapping for this texture changed?
+ ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(request.ID);
+
+ ulong address = descriptor.UnpackAddress();
+
+ MultiRange range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
+
+ // If the texture is not mapped at all, delete its reference.
+
+ if (range.Count == 1 && range.GetSubRange(0).Address == MemoryManager.PteUnmapped)
+ {
+ texture.DecrementReferenceCount();
+ continue;
+ }
+
+ Items[request.ID] = texture;
+
+ // Create a new pool reference, as the last one was removed on unmap.
+
+ texture.IncrementReferenceCount(this, request.ID, address);
+ texture.DecrementReferenceCount();
+
+ // Refetch the range. Changes since the last check could have been lost
+ // as the cache entry was not restored (required to queue mapping change).
+
+ range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
+
+ if (!range.Equals(texture.Range))
+ {
+ // Part of the texture was mapped or unmapped. Replace the range and regenerate tracking handles.
+ if (!_channel.MemoryManager.Physical.TextureCache.UpdateMapping(texture, range))
+ {
+ // Texture could not be remapped due to a collision, just delete it.
+ if (Interlocked.Exchange(ref Items[request.ID], null) != null)
+ {
+ // If this is null, a request was already queued to decrement reference.
+ texture.DecrementReferenceCount(this, request.ID);
+ }
+ continue;
+ }
+ }
+
+ if (request.ID == id)
+ {
+ return texture;
+ }
+ }
+ else
+ {
+ texture.DecrementReferenceCount();
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Implementation of the texture pool range invalidation.
+ /// </summary>
+ /// <param name="address">Start address of the range of the texture pool</param>
+ /// <param name="size">Size of the range being invalidated</param>
+ protected override void InvalidateRangeImpl(ulong address, ulong size)
+ {
+ ProcessDereferenceQueue();
+
+ ulong endAddress = address + size;
+
+ for (; address < endAddress; address += DescriptorSize)
+ {
+ int id = (int)((address - Address) / DescriptorSize);
+
+ Texture texture = Items[id];
+
+ if (texture != null)
+ {
+ ref TextureDescriptor cachedDescriptor = ref DescriptorCache[id];
+ ref readonly TextureDescriptor descriptor = ref GetDescriptorRefAddress(address);
+
+ // If the descriptors are the same, the texture is the same,
+ // we don't need to remove as it was not modified. Just continue.
+ if (descriptor.Equals(ref cachedDescriptor))
+ {
+ continue;
+ }
+
+ if (texture.HasOneReference())
+ {
+ _channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
+ }
+
+ if (Interlocked.Exchange(ref Items[id], null) != null)
+ {
+ texture.DecrementReferenceCount(this, id);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets texture information from a texture descriptor.
+ /// </summary>
+ /// <param name="descriptor">The texture descriptor</param>
+ /// <param name="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param>
+ /// <returns>The texture information</returns>
+ private TextureInfo GetInfo(in TextureDescriptor descriptor, out int layerSize)
+ {
+ int depthOrLayers = descriptor.UnpackDepth();
+ int levels = descriptor.UnpackLevels();
+
+ TextureMsaaMode msaaMode = descriptor.UnpackTextureMsaaMode();
+
+ int samplesInX = msaaMode.SamplesInX();
+ int samplesInY = msaaMode.SamplesInY();
+
+ int stride = descriptor.UnpackStride();
+
+ TextureDescriptorType descriptorType = descriptor.UnpackTextureDescriptorType();
+
+ bool isLinear = descriptorType == TextureDescriptorType.Linear;
+
+ Target target = descriptor.UnpackTextureTarget().Convert((samplesInX | samplesInY) != 1);
+
+ int width = target == Target.TextureBuffer ? descriptor.UnpackBufferTextureWidth() : descriptor.UnpackWidth();
+ int height = descriptor.UnpackHeight();
+
+ if (target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray)
+ {
+ // This is divided back before the backend texture is created.
+ width *= samplesInX;
+ height *= samplesInY;
+ }
+
+ // We use 2D targets for 1D textures as that makes texture cache
+ // management easier. We don't know the target for render target
+ // and copies, so those would normally use 2D targets, which are
+ // not compatible with 1D targets. By doing that we also allow those
+ // to match when looking for compatible textures on the cache.
+ if (target == Target.Texture1D)
+ {
+ target = Target.Texture2D;
+ height = 1;
+ }
+ else if (target == Target.Texture1DArray)
+ {
+ target = Target.Texture2DArray;
+ height = 1;
+ }
+
+ uint format = descriptor.UnpackFormat();
+ bool srgb = descriptor.UnpackSrgb();
+
+ ulong gpuVa = descriptor.UnpackAddress();
+
+ if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo))
+ {
+ if (gpuVa != 0 && (int)format > 0)
+ {
+ Logger.Error?.Print(LogClass.Gpu, $"Invalid texture format 0x{format:X} (sRGB: {srgb}).");
+ }
+
+ formatInfo = FormatInfo.Default;
+ }
+
+ int gobBlocksInY = descriptor.UnpackGobBlocksInY();
+ int gobBlocksInZ = descriptor.UnpackGobBlocksInZ();
+
+ int gobBlocksInTileX = descriptor.UnpackGobBlocksInTileX();
+
+ layerSize = 0;
+
+ int minLod = descriptor.UnpackBaseLevel();
+ int maxLod = descriptor.UnpackMaxLevelInclusive();
+
+ // Linear textures don't support mipmaps, so we don't handle this case here.
+ if ((minLod != 0 || maxLod + 1 != levels) && target != Target.TextureBuffer && !isLinear)
+ {
+ int depth = TextureInfo.GetDepth(target, depthOrLayers);
+ int layers = TextureInfo.GetLayers(target, depthOrLayers);
+
+ SizeInfo sizeInfo = SizeCalculator.GetBlockLinearTextureSize(
+ width,
+ height,
+ depth,
+ levels,
+ layers,
+ formatInfo.BlockWidth,
+ formatInfo.BlockHeight,
+ formatInfo.BytesPerPixel,
+ gobBlocksInY,
+ gobBlocksInZ,
+ gobBlocksInTileX);
+
+ layerSize = sizeInfo.LayerSize;
+
+ if (minLod != 0 && minLod < levels)
+ {
+ // If the base level is not zero, we additionally add the mip level offset
+ // to the address, this allows the texture manager to find the base level from the
+ // address if there is a overlapping texture on the cache that can contain the new texture.
+ gpuVa += (ulong)sizeInfo.GetMipOffset(minLod);
+
+ width = Math.Max(1, width >> minLod);
+ height = Math.Max(1, height >> minLod);
+
+ if (target == Target.Texture3D)
+ {
+ depthOrLayers = Math.Max(1, depthOrLayers >> minLod);
+ }
+
+ (gobBlocksInY, gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(height, depth, formatInfo.BlockHeight, gobBlocksInY, gobBlocksInZ);
+ }
+
+ levels = (maxLod - minLod) + 1;
+ }
+
+ SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert();
+ SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert();
+ SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert();
+ SwizzleComponent swizzleA = descriptor.UnpackSwizzleA().Convert();
+
+ DepthStencilMode depthStencilMode = GetDepthStencilMode(
+ formatInfo.Format,
+ swizzleR,
+ swizzleG,
+ swizzleB,
+ swizzleA);
+
+ if (formatInfo.Format.IsDepthOrStencil())
+ {
+ swizzleR = SwizzleComponent.Red;
+ swizzleG = SwizzleComponent.Red;
+ swizzleB = SwizzleComponent.Red;
+
+ if (depthStencilMode == DepthStencilMode.Depth)
+ {
+ swizzleA = SwizzleComponent.One;
+ }
+ else
+ {
+ swizzleA = SwizzleComponent.Red;
+ }
+ }
+
+ return new TextureInfo(
+ gpuVa,
+ width,
+ height,
+ depthOrLayers,
+ levels,
+ samplesInX,
+ samplesInY,
+ stride,
+ isLinear,
+ gobBlocksInY,
+ gobBlocksInZ,
+ gobBlocksInTileX,
+ target,
+ formatInfo,
+ depthStencilMode,
+ swizzleR,
+ swizzleG,
+ swizzleB,
+ swizzleA);
+ }
+
+ /// <summary>
+ /// Gets the texture depth-stencil mode, based on the swizzle components of each color channel.
+ /// The depth-stencil mode is determined based on how the driver sets those parameters.
+ /// </summary>
+ /// <param name="format">The format of the texture</param>
+ /// <param name="components">The texture swizzle components</param>
+ /// <returns>The depth-stencil mode</returns>
+ private static DepthStencilMode GetDepthStencilMode(Format format, params SwizzleComponent[] components)
+ {
+ // R = Depth, G = Stencil.
+ // On 24-bits depth formats, this is inverted (Stencil is R etc).
+ // NVN setup:
+ // For depth, A is set to 1.0f, the other components are set to Depth.
+ // For stencil, all components are set to Stencil.
+ SwizzleComponent component = components[0];
+
+ for (int index = 1; index < 4 && !IsRG(component); index++)
+ {
+ component = components[index];
+ }
+
+ if (!IsRG(component))
+ {
+ return DepthStencilMode.Depth;
+ }
+
+ if (format == Format.D24UnormS8Uint)
+ {
+ return component == SwizzleComponent.Red
+ ? DepthStencilMode.Stencil
+ : DepthStencilMode.Depth;
+ }
+ else
+ {
+ return component == SwizzleComponent.Red
+ ? DepthStencilMode.Depth
+ : DepthStencilMode.Stencil;
+ }
+ }
+
+ /// <summary>
+ /// Checks if the swizzle component is equal to the red or green channels.
+ /// </summary>
+ /// <param name="component">The swizzle component to check</param>
+ /// <returns>True if the swizzle component is equal to the red or green, false otherwise</returns>
+ private static bool IsRG(SwizzleComponent component)
+ {
+ return component == SwizzleComponent.Red ||
+ component == SwizzleComponent.Green;
+ }
+
+ /// <summary>
+ /// Decrements the reference count of the texture.
+ /// This indicates that the texture pool is not using it anymore.
+ /// </summary>
+ /// <param name="item">The texture to be deleted</param>
+ protected override void Delete(Texture item)
+ {
+ item?.DecrementReferenceCount(this);
+ }
+
+ public override void Dispose()
+ {
+ ProcessDereferenceQueue();
+
+ base.Dispose();
+ }
+ }
+} \ No newline at end of file