60cf3dfebc
This fixes a regression caused by #980, that was causing a crash on New Super Lucky's Tale. As always, this need feedback on possible regression on any games. Fix #2343.
190 lines
6.6 KiB
C#
190 lines
6.6 KiB
C#
using Ryujinx.Graphics.Gpu.State;
|
|
using System;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
|
{
|
|
/// <summary>
|
|
/// Represents a GPU General Purpose FIFO command processor.
|
|
/// </summary>
|
|
class GPFifoProcessor
|
|
{
|
|
private const int MacrosCount = 0x80;
|
|
private const int MacroIndexMask = MacrosCount - 1;
|
|
|
|
private readonly GpuContext _context;
|
|
|
|
/// <summary>
|
|
/// Internal GPFIFO state.
|
|
/// </summary>
|
|
private struct DmaState
|
|
{
|
|
public int Method;
|
|
public int SubChannel;
|
|
public int MethodCount;
|
|
public bool NonIncrementing;
|
|
public bool IncrementOnce;
|
|
}
|
|
|
|
private DmaState _state;
|
|
|
|
private readonly GpuState[] _subChannels;
|
|
private readonly GPFifoClass _fifoClass;
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of the GPU General Purpose FIFO command processor.
|
|
/// </summary>
|
|
/// <param name="context">GPU context</param>
|
|
public GPFifoProcessor(GpuContext context)
|
|
{
|
|
_context = context;
|
|
|
|
_fifoClass = new GPFifoClass(context, this);
|
|
_subChannels = new GpuState[8];
|
|
|
|
for (int index = 0; index < _subChannels.Length; index++)
|
|
{
|
|
_subChannels[index] = new GpuState();
|
|
|
|
_context.Methods.RegisterCallbacks(_subChannels[index]);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes a command buffer.
|
|
/// </summary>
|
|
/// <param name="commandBuffer">Command buffer</param>
|
|
public void Process(ReadOnlySpan<int> commandBuffer)
|
|
{
|
|
for (int index = 0; index < commandBuffer.Length; index++)
|
|
{
|
|
int command = commandBuffer[index];
|
|
|
|
if (_state.MethodCount != 0)
|
|
{
|
|
Send(new MethodParams(_state.Method, command, _state.SubChannel, _state.MethodCount));
|
|
|
|
if (!_state.NonIncrementing)
|
|
{
|
|
_state.Method++;
|
|
}
|
|
|
|
if (_state.IncrementOnce)
|
|
{
|
|
_state.NonIncrementing = true;
|
|
}
|
|
|
|
_state.MethodCount--;
|
|
}
|
|
else
|
|
{
|
|
CompressedMethod meth = Unsafe.As<int, CompressedMethod>(ref command);
|
|
|
|
if (TryFastUniformBufferUpdate(meth, commandBuffer, index))
|
|
{
|
|
index += meth.MethodCount;
|
|
continue;
|
|
}
|
|
|
|
switch (meth.SecOp)
|
|
{
|
|
case SecOp.IncMethod:
|
|
case SecOp.NonIncMethod:
|
|
case SecOp.OneInc:
|
|
_state.Method = meth.MethodAddress;
|
|
_state.SubChannel = meth.MethodSubchannel;
|
|
_state.MethodCount = meth.MethodCount;
|
|
_state.IncrementOnce = meth.SecOp == SecOp.OneInc;
|
|
_state.NonIncrementing = meth.SecOp == SecOp.NonIncMethod;
|
|
break;
|
|
case SecOp.ImmdDataMethod:
|
|
Send(new MethodParams(meth.MethodAddress, meth.ImmdData, meth.MethodSubchannel, 1));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to perform a fast constant buffer data update.
|
|
/// If successful, all data will be copied at once, and <see cref="CompressedMethod.MethodCount"/> + 1
|
|
/// command buffer entries will be consumed.
|
|
/// </summary>
|
|
/// <param name="meth">Compressed method to be checked</param>
|
|
/// <param name="commandBuffer">Command buffer where <paramref name="meth"/> is contained</param>
|
|
/// <param name="offset">Offset at <paramref name="commandBuffer"/> where <paramref name="meth"/> is located</param>
|
|
/// <returns>True if the fast copy was successful, false otherwise</returns>
|
|
private bool TryFastUniformBufferUpdate(CompressedMethod meth, ReadOnlySpan<int> commandBuffer, int offset)
|
|
{
|
|
int availableCount = commandBuffer.Length - offset;
|
|
|
|
if (meth.MethodCount < availableCount &&
|
|
meth.SecOp == SecOp.NonIncMethod &&
|
|
meth.MethodAddress == (int)MethodOffset.UniformBufferUpdateData)
|
|
{
|
|
GpuState state = _subChannels[meth.MethodSubchannel];
|
|
|
|
_context.Methods.UniformBufferUpdate(state, commandBuffer.Slice(offset + 1, meth.MethodCount));
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends a uncompressed method for processing by the graphics pipeline.
|
|
/// </summary>
|
|
/// <param name="meth">Method to be processed</param>
|
|
private void Send(MethodParams meth)
|
|
{
|
|
if ((MethodOffset)meth.Method == MethodOffset.BindChannel)
|
|
{
|
|
_subChannels[meth.SubChannel].ClearCallbacks();
|
|
|
|
_context.Methods.RegisterCallbacks(_subChannels[meth.SubChannel]);
|
|
}
|
|
else if (meth.Method < 0x60)
|
|
{
|
|
// TODO: check if macros are shared between subchannels or not. For now let's assume they are.
|
|
_fifoClass.Write(meth.Method * 4, meth.Argument);
|
|
}
|
|
else if (meth.Method < 0xe00)
|
|
{
|
|
_subChannels[meth.SubChannel].CallMethod(meth);
|
|
}
|
|
else
|
|
{
|
|
int macroIndex = (meth.Method >> 1) & MacroIndexMask;
|
|
|
|
if ((meth.Method & 1) != 0)
|
|
{
|
|
_fifoClass.MmePushArgument(macroIndex, meth.Argument);
|
|
}
|
|
else
|
|
{
|
|
_fifoClass.MmeStart(macroIndex, meth.Argument);
|
|
}
|
|
|
|
if (meth.IsLastCall)
|
|
{
|
|
_fifoClass.CallMme(macroIndex, _subChannels[meth.SubChannel]);
|
|
|
|
_context.Methods.PerformDeferredDraws();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the shadow ram control value of all sub-channels.
|
|
/// </summary>
|
|
/// <param name="control">New shadow ram control value</param>
|
|
public void SetShadowRamControl(ShadowRamControl control)
|
|
{
|
|
for (int i = 0; i < _subChannels.Length; i++)
|
|
{
|
|
_subChannels[i].ShadowRamControl = control;
|
|
}
|
|
}
|
|
}
|
|
}
|