using Ryujinx.Common.Logging; using Ryujinx.Graphics.Device; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Engine.GPFifo; using System; using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu.Engine.MME { /// /// Macro High-level emulation. /// class MacroHLE : IMacroEE { private const int ColorLayerCountOffset = 0x818; private const int ColorStructSize = 0x40; private const int ZetaLayerCountOffset = 0x1230; private readonly GPFifoProcessor _processor; private readonly MacroHLEFunctionName _functionName; /// /// Arguments FIFO. /// public Queue Fifo { get; } /// /// Creates a new instance of the HLE macro handler. /// /// GPU context the macro is being executed on /// GPU memory manager /// 3D engine where this macro is being called /// Name of the HLE macro function to be called public MacroHLE(GPFifoProcessor processor, MacroHLEFunctionName functionName) { _processor = processor; _functionName = functionName; Fifo = new Queue(); } /// /// Executes a macro program until it exits. /// /// Code of the program to execute /// GPU state at the time of the call /// Optional argument passed to the program, 0 if not used public void Execute(ReadOnlySpan code, IDeviceState state, int arg0) { switch (_functionName) { case MacroHLEFunctionName.ClearColor: ClearColor(state, arg0); break; case MacroHLEFunctionName.ClearDepthStencil: ClearDepthStencil(state, arg0); break; case MacroHLEFunctionName.MultiDrawElementsIndirectCount: MultiDrawElementsIndirectCount(state, arg0); break; default: throw new NotImplementedException(_functionName.ToString()); } } /// /// Clears one bound color target. /// /// GPU state at the time of the call /// First argument of the call private void ClearColor(IDeviceState state, int arg0) { int index = (arg0 >> 6) & 0xf; int layerCount = state.Read(ColorLayerCountOffset + index * ColorStructSize); _processor.ThreedClass.Clear(arg0, layerCount); } /// /// Clears the current depth-stencil target. /// /// GPU state at the time of the call /// First argument of the call private void ClearDepthStencil(IDeviceState state, int arg0) { int layerCount = state.Read(ZetaLayerCountOffset); _processor.ThreedClass.Clear(arg0, layerCount); } /// /// Performs a indirect multi-draw, with parameters from a GPU buffer. /// /// GPU state at the time of the call /// First argument of the call private void MultiDrawElementsIndirectCount(IDeviceState state, int arg0) { int arg1 = FetchParam().Word; int arg2 = FetchParam().Word; int arg3 = FetchParam().Word; int startDraw = arg0; int endDraw = arg1; var topology = (PrimitiveTopology)arg2; int paddingWords = arg3; int stride = paddingWords * 4 + 0x14; ulong parameterBufferGpuVa = FetchParam().GpuVa; int maxDrawCount = endDraw - startDraw; if (startDraw != 0) { int drawCount = _processor.MemoryManager.Read(parameterBufferGpuVa, tracked: true); // Calculate maximum draw count based on the previous draw count and current draw count. if ((uint)drawCount <= (uint)startDraw) { // The start draw is past our total draw count, so all draws were already performed. maxDrawCount = 0; } else { // Perform just the missing number of draws. maxDrawCount = (int)Math.Min((uint)maxDrawCount, (uint)(drawCount - startDraw)); } } if (maxDrawCount == 0) { Fifo.Clear(); return; } int indirectBufferSize = maxDrawCount * stride; ulong indirectBufferGpuVa = 0; int indexCount = 0; for (int i = 0; i < maxDrawCount; i++) { var count = FetchParam(); var instanceCount = FetchParam(); var firstIndex = FetchParam(); var baseVertex = FetchParam(); var baseInstance = FetchParam(); if (i == 0) { indirectBufferGpuVa = count.GpuVa; } indexCount = Math.Max(indexCount, count.Word + firstIndex.Word); if (i != maxDrawCount - 1) { for (int j = 0; j < paddingWords; j++) { FetchParam(); } } } // It should be empty at this point, but clear it just to be safe. Fifo.Clear(); var bufferCache = _processor.MemoryManager.Physical.BufferCache; var parameterBuffer = bufferCache.GetGpuBufferRange(_processor.MemoryManager, parameterBufferGpuVa, 4); var indirectBuffer = bufferCache.GetGpuBufferRange(_processor.MemoryManager, indirectBufferGpuVa, (ulong)indirectBufferSize); _processor.ThreedClass.MultiDrawIndirectCount(indexCount, topology, indirectBuffer, parameterBuffer, maxDrawCount, stride); } /// /// Fetches a arguments from the arguments FIFO. /// /// The call argument, or a 0 value with null address if the FIFO is empty private FifoWord FetchParam() { if (!Fifo.TryDequeue(out var value)) { Logger.Warning?.Print(LogClass.Gpu, "Macro attempted to fetch an inexistent argument."); return new FifoWord(0UL, 0); } return value; } /// /// Performs a GPU method call. /// /// Current GPU state /// Address, in words, of the method /// Call argument private static void Send(IDeviceState state, int methAddr, int value) { state.Write(methAddr * 4, value); } } }