Ryujinx/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs
riperiperi cda659955c
Texture Sync, incompatible overlap handling, data flush improvements. (#2971)
* Initial test for texture sync

* WIP new texture flushing setup

* Improve rules for incompatible overlaps

Fixes a lot of issues with Unreal Engine games. Still a few minor issues (some caused by dma fast path?) Needs docs and cleanup.

* Cleanup, improvements

Improve rules for fast DMA

* Small tweak to group together flushes of overlapping handles.

* Fixes, flush overlapping texture data for ASTC and BC4/5 compressed textures.

Fixes the new Life is Strange game.

* Flush overlaps before init data, fix 3d texture size/overlap stuff

* Fix 3D Textures, faster single layer flush

Note: nosy people can no longer merge this with Vulkan. (unless they are nosy enough to implement the new backend methods)

* Remove unused method

* Minor cleanup

* More cleanup

* Use the More Fun and Hopefully No Driver Bugs method for getting compressed tex too

This one's for metro

* Address feedback, ASTC+ETC to FormatClass

* Change offset to use Span slice rather than IntPtr Add

* Fix this too
2022-01-09 13:28:48 -03:00

227 lines
8.6 KiB
C#

using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.Gpu.Engine.MME;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
{
/// <summary>
/// Represents a GPU General Purpose FIFO class.
/// </summary>
class GPFifoClass : IDeviceState
{
private readonly GpuContext _context;
private readonly GPFifoProcessor _parent;
private readonly DeviceState<GPFifoClassState> _state;
private const int MacrosCount = 0x80;
// Note: The size of the macro memory is unknown, we just make
// a guess here and use 256kb as the size. Increase if needed.
private const int MacroCodeSize = 256 * 256;
private readonly Macro[] _macros;
private readonly int[] _macroCode;
/// <summary>
/// Creates a new instance of the GPU General Purpose FIFO class.
/// </summary>
/// <param name="context">GPU context</param>
/// <param name="parent">Parent GPU General Purpose FIFO processor</param>
public GPFifoClass(GpuContext context, GPFifoProcessor parent)
{
_context = context;
_parent = parent;
_state = new DeviceState<GPFifoClassState>(new Dictionary<string, RwCallback>
{
{ nameof(GPFifoClassState.Semaphored), new RwCallback(Semaphored, null) },
{ nameof(GPFifoClassState.Syncpointb), new RwCallback(Syncpointb, null) },
{ nameof(GPFifoClassState.WaitForIdle), new RwCallback(WaitForIdle, null) },
{ nameof(GPFifoClassState.SetReference), new RwCallback(SetReference, null) },
{ nameof(GPFifoClassState.LoadMmeInstructionRam), new RwCallback(LoadMmeInstructionRam, null) },
{ nameof(GPFifoClassState.LoadMmeStartAddressRam), new RwCallback(LoadMmeStartAddressRam, null) },
{ nameof(GPFifoClassState.SetMmeShadowRamControl), new RwCallback(SetMmeShadowRamControl, null) }
});
_macros = new Macro[MacrosCount];
_macroCode = new int[MacroCodeSize];
}
/// <summary>
/// Reads data from the class registers.
/// </summary>
/// <param name="offset">Register byte offset</param>
/// <returns>Data at the specified offset</returns>
public int Read(int offset) => _state.Read(offset);
/// <summary>
/// Writes data to the class registers.
/// </summary>
/// <param name="offset">Register byte offset</param>
/// <param name="data">Data to be written</param>
public void Write(int offset, int data) => _state.Write(offset, data);
/// <summary>
/// Writes a GPU counter to guest memory.
/// </summary>
/// <param name="argument">Method call argument</param>
public void Semaphored(int argument)
{
ulong address = ((ulong)_state.State.SemaphorebOffsetLower << 2) |
((ulong)_state.State.SemaphoreaOffsetUpper << 32);
int value = _state.State.SemaphorecPayload;
SemaphoredOperation operation = _state.State.SemaphoredOperation;
// TODO: Acquire operations (Wait), interrupts for invalid combinations.
if (operation == SemaphoredOperation.Release)
{
_parent.MemoryManager.Write(address, value);
}
else if (operation == SemaphoredOperation.Reduction)
{
bool signed = _state.State.SemaphoredFormat == SemaphoredFormat.Signed;
int mem = _parent.MemoryManager.Read<int>(address);
switch (_state.State.SemaphoredReduction)
{
case SemaphoredReduction.Min:
value = signed ? Math.Min(mem, value) : (int)Math.Min((uint)mem, (uint)value);
break;
case SemaphoredReduction.Max:
value = signed ? Math.Max(mem, value) : (int)Math.Max((uint)mem, (uint)value);
break;
case SemaphoredReduction.Xor:
value ^= mem;
break;
case SemaphoredReduction.And:
value &= mem;
break;
case SemaphoredReduction.Or:
value |= mem;
break;
case SemaphoredReduction.Add:
value += mem;
break;
case SemaphoredReduction.Inc:
value = (uint)mem < (uint)value ? mem + 1 : 0;
break;
case SemaphoredReduction.Dec:
value = (uint)mem > 0 && (uint)mem <= (uint)value ? mem - 1 : value;
break;
}
_parent.MemoryManager.Write(address, value);
}
}
/// <summary>
/// Apply a fence operation on a syncpoint.
/// </summary>
/// <param name="argument">Method call argument</param>
public void Syncpointb(int argument)
{
SyncpointbOperation operation = _state.State.SyncpointbOperation;
uint syncpointId = (uint)_state.State.SyncpointbSyncptIndex;
if (operation == SyncpointbOperation.Wait)
{
uint threshold = (uint)_state.State.SyncpointaPayload;
_context.Synchronization.WaitOnSyncpoint(syncpointId, threshold, Timeout.InfiniteTimeSpan);
}
else if (operation == SyncpointbOperation.Incr)
{
_context.CreateHostSyncIfNeeded(true);
_context.Synchronization.IncrementSyncpoint(syncpointId);
}
_context.AdvanceSequence();
}
/// <summary>
/// Waits for the GPU to be idle.
/// </summary>
/// <param name="argument">Method call argument</param>
public void WaitForIdle(int argument)
{
_parent.PerformDeferredDraws();
_context.Renderer.Pipeline.Barrier();
_context.CreateHostSyncIfNeeded(false);
}
/// <summary>
/// Used as an indirect data barrier on NVN. When used, access to previously written data must be coherent.
/// </summary>
/// <param name="argument">Method call argument</param>
public void SetReference(int argument)
{
_context.Renderer.Pipeline.CommandBufferBarrier();
_context.CreateHostSyncIfNeeded(false);
}
/// <summary>
/// Sends macro code/data to the MME.
/// </summary>
/// <param name="argument">Method call argument</param>
public void LoadMmeInstructionRam(int argument)
{
_macroCode[_state.State.LoadMmeInstructionRamPointer++] = argument;
}
/// <summary>
/// Binds a macro index to a position for the MME
/// </summary>
/// <param name="argument">Method call argument</param>
public void LoadMmeStartAddressRam(int argument)
{
_macros[_state.State.LoadMmeStartAddressRamPointer++] = new Macro(argument);
}
/// <summary>
/// Changes the shadow RAM control.
/// </summary>
/// <param name="argument">Method call argument</param>
public void SetMmeShadowRamControl(int argument)
{
_parent.SetShadowRamControl(argument);
}
/// <summary>
/// Pushes an argument to a macro.
/// </summary>
/// <param name="index">Index of the macro</param>
/// <param name="gpuVa">GPU virtual address where the command word is located</param>
/// <param name="argument">Argument to be pushed to the macro</param>
public void MmePushArgument(int index, ulong gpuVa, int argument)
{
_macros[index].PushArgument(gpuVa, argument);
}
/// <summary>
/// Prepares a macro for execution.
/// </summary>
/// <param name="index">Index of the macro</param>
/// <param name="argument">Initial argument passed to the macro</param>
public void MmeStart(int index, int argument)
{
_macros[index].StartExecution(_context, _parent, _macroCode, argument);
}
/// <summary>
/// Executes a macro.
/// </summary>
/// <param name="index">Index of the macro</param>
/// <param name="state">Current GPU state</param>
public void CallMme(int index, IDeviceState state)
{
_macros[index].Execute(_macroCode, state);
}
}
}