rjx-mirror/Ryujinx.Graphics.Gpu/GpuChannel.cs
riperiperi 187372cbde
Prune ForceDirty and CheckModified caches on unmap (#3862)
* Prune ForceDirty and CheckModified caches on unmap

Since we're now using this for modified checks on the HLE indirect draw method, I'm worried that leaving these to forever gather cache entries isn't the best idea for performance in the long term, and it could keep old buffer objects alive for longer than they should be.

This PR adds the ability to prune invalid entries before checking these caches, and queues it whenever gpu memory is unmapped. It also aligns modified checks to the page size, as I figured it would be possible for a huge number of overlapping over a game's runtime.

This prevents Super Mario Odyssey from having 10s of thousands of entries in the modified cache in Metro Kingdom, and them duplicating when entering and leaving a building (should be cleared, as they were unmapped).

* Address Feedback
2022-11-18 14:58:24 +00:00

139 lines
5.2 KiB
C#

using Ryujinx.Graphics.Gpu.Engine.GPFifo;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using System;
using System.Threading;
namespace Ryujinx.Graphics.Gpu
{
/// <summary>
/// Represents a GPU channel.
/// </summary>
public class GpuChannel : IDisposable
{
private readonly GpuContext _context;
private readonly GPFifoDevice _device;
private readonly GPFifoProcessor _processor;
private MemoryManager _memoryManager;
/// <summary>
/// Channel buffer bindings manager.
/// </summary>
internal BufferManager BufferManager { get; }
/// <summary>
/// Channel texture bindings manager.
/// </summary>
internal TextureManager TextureManager { get; }
/// <summary>
/// Current channel memory manager.
/// </summary>
internal MemoryManager MemoryManager => _memoryManager;
/// <summary>
/// Creates a new instance of a GPU channel.
/// </summary>
/// <param name="context">GPU context that the channel belongs to</param>
internal GpuChannel(GpuContext context)
{
_context = context;
_device = context.GPFifo;
_processor = new GPFifoProcessor(context, this);
BufferManager = new BufferManager(context, this);
TextureManager = new TextureManager(context, this);
}
/// <summary>
/// Binds a memory manager to the channel.
/// All submitted and in-flight commands will use the specified memory manager for any memory operations.
/// </summary>
/// <param name="memoryManager">The new memory manager to be bound</param>
public void BindMemory(MemoryManager memoryManager)
{
var oldMemoryManager = Interlocked.Exchange(ref _memoryManager, memoryManager ?? throw new ArgumentNullException(nameof(memoryManager)));
memoryManager.Physical.IncrementReferenceCount();
if (oldMemoryManager != null)
{
oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
oldMemoryManager.Physical.DecrementReferenceCount();
oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler;
}
memoryManager.Physical.BufferCache.NotifyBuffersModified += BufferManager.Rebind;
memoryManager.MemoryUnmapped += MemoryUnmappedHandler;
// Since the memory manager changed, make sure we will get pools from addresses of the new memory manager.
TextureManager.ReloadPools();
MemoryManager.Physical.BufferCache.QueuePrune();
}
/// <summary>
/// Memory mappings change event handler.
/// </summary>
/// <param name="sender">Memory manager where the mappings changed</param>
/// <param name="e">Information about the region that is being changed</param>
private void MemoryUnmappedHandler(object sender, UnmapEventArgs e)
{
TextureManager.ReloadPools();
MemoryManager.Physical.BufferCache.QueuePrune();
}
/// <summary>
/// Writes data directly to the state of the specified class.
/// </summary>
/// <param name="classId">ID of the class to write the data into</param>
/// <param name="offset">State offset in bytes</param>
/// <param name="value">Value to be written</param>
public void Write(ClassId classId, int offset, uint value)
{
_processor.Write(classId, offset, (int)value);
}
/// <summary>
/// Push a GPFIFO entry in the form of a prefetched command buffer.
/// It is intended to be used by nvservices to handle special cases.
/// </summary>
/// <param name="commandBuffer">The command buffer containing the prefetched commands</param>
public void PushHostCommandBuffer(int[] commandBuffer)
{
_device.PushHostCommandBuffer(_processor, commandBuffer);
}
/// <summary>
/// Pushes GPFIFO entries.
/// </summary>
/// <param name="entries">GPFIFO entries</param>
public void PushEntries(ReadOnlySpan<ulong> entries)
{
_device.PushEntries(_processor, entries);
}
/// <summary>
/// Disposes the GPU channel.
/// It's an error to use the GPU channel after disposal.
/// </summary>
public void Dispose()
{
_context.DeferredActions.Enqueue(Destroy);
}
/// <summary>
/// Performs disposal of the host GPU resources used by this channel, that are not shared.
/// This must only be called from the render thread.
/// </summary>
private void Destroy()
{
TextureManager.Dispose();
var oldMemoryManager = Interlocked.Exchange(ref _memoryManager, null);
if (oldMemoryManager != null)
{
oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
oldMemoryManager.Physical.DecrementReferenceCount();
}
}
}
}