aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Gpu/Engine
diff options
context:
space:
mode:
authorgdk <gab.dark.100@gmail.com>2019-10-13 03:02:07 -0300
committerThog <thog@protonmail.com>2020-01-09 02:13:00 +0100
commit1876b346fea647e8284a66bb6d62c38801035cff (patch)
tree6eeff094298cda84d1613dc5ec0691e51d7b35f1 /Ryujinx.Graphics.Gpu/Engine
parentf617fb542a0e3d36012d77a4b5acbde7b08902f2 (diff)
Initial work
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Engine')
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Compute.cs83
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs126
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/ComputeShader.cs18
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/GraphicsShader.cs17
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs42
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/MethodClear.cs55
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs79
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs70
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs133
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/MethodReport.cs100
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs26
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs52
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs18
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Methods.cs784
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/ShaderAddresses.cs34
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/ShaderCache.cs228
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/ShaderDumper.cs126
17 files changed, 1991 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute.cs b/Ryujinx.Graphics.Gpu/Engine/Compute.cs
new file mode 100644
index 00000000..c8627435
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/Compute.cs
@@ -0,0 +1,83 @@
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ public void Dispatch(int argument)
+ {
+ uint dispatchParamsAddress = (uint)_context.State.Get<int>(MethodOffset.DispatchParamsAddress);
+
+ var dispatchParams = _context.MemoryAccessor.Read<ComputeParams>((ulong)dispatchParamsAddress << 8);
+
+ GpuVa shaderBaseAddress = _context.State.Get<GpuVa>(MethodOffset.ShaderBaseAddress);
+
+ ulong shaderGpuVa = shaderBaseAddress.Pack() + (uint)dispatchParams.ShaderOffset;
+
+ ComputeShader cs = _shaderCache.GetComputeShader(
+ shaderGpuVa,
+ dispatchParams.UnpackBlockSizeX(),
+ dispatchParams.UnpackBlockSizeY(),
+ dispatchParams.UnpackBlockSizeZ());
+
+ _context.Renderer.ComputePipeline.SetProgram(cs.Interface);
+
+ ShaderProgramInfo info = cs.Shader.Info;
+
+ uint sbEnableMask = 0;
+ uint ubEnableMask = dispatchParams.UnpackUniformBuffersEnableMask();
+
+ for (int index = 0; index < dispatchParams.UniformBuffers.Length; index++)
+ {
+ if ((ubEnableMask & (1 << index)) == 0)
+ {
+ continue;
+ }
+
+ ulong gpuVa = dispatchParams.UniformBuffers[index].PackAddress();
+ ulong size = dispatchParams.UniformBuffers[index].UnpackSize();
+
+ _bufferManager.SetComputeUniformBuffer(index, gpuVa, size);
+ }
+
+ for (int index = 0; index < info.SBuffers.Count; index++)
+ {
+ BufferDescriptor sb = info.SBuffers[index];
+
+ sbEnableMask |= 1u << sb.Slot;
+
+ ulong sbDescAddress = _bufferManager.GetComputeUniformBufferAddress(0);
+
+ int sbDescOffset = 0x310 + sb.Slot * 0x10;
+
+ sbDescAddress += (ulong)sbDescOffset;
+
+ Span<byte> sbDescriptorData = _context.PhysicalMemory.Read(sbDescAddress, 0x10);
+
+ SbDescriptor sbDescriptor = MemoryMarshal.Cast<byte, SbDescriptor>(sbDescriptorData)[0];
+
+ _bufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size);
+ }
+
+ ubEnableMask = 0;
+
+ for (int index = 0; index < info.CBuffers.Count; index++)
+ {
+ ubEnableMask |= 1u << info.CBuffers[index].Slot;
+ }
+
+ _bufferManager.SetComputeStorageBufferEnableMask(sbEnableMask);
+ _bufferManager.SetComputeUniformBufferEnableMask(ubEnableMask);
+
+ _bufferManager.CommitComputeBindings();
+
+ _context.Renderer.ComputePipeline.Dispatch(
+ dispatchParams.UnpackGridSizeX(),
+ dispatchParams.UnpackGridSizeY(),
+ dispatchParams.UnpackGridSizeZ());
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs b/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs
new file mode 100644
index 00000000..03582f05
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ struct UniformBufferParams
+ {
+ public int AddressLow;
+ public int AddressHighAndSize;
+
+ public ulong PackAddress()
+ {
+ return (uint)AddressLow | ((ulong)(AddressHighAndSize & 0xff) << 32);
+ }
+
+ public ulong UnpackSize()
+ {
+ return (ulong)((AddressHighAndSize >> 15) & 0x1ffff);
+ }
+ }
+
+ struct ComputeParams
+ {
+ public int Unknown0;
+ public int Unknown1;
+ public int Unknown2;
+ public int Unknown3;
+ public int Unknown4;
+ public int Unknown5;
+ public int Unknown6;
+ public int Unknown7;
+ public int ShaderOffset;
+ public int Unknown9;
+ public int Unknown10;
+ public int Unknown11;
+ public int GridSizeX;
+ public int GridSizeYZ;
+ public int Unknown14;
+ public int Unknown15;
+ public int Unknown16;
+ public int Unknown17;
+ public int BlockSizeX;
+ public int BlockSizeYZ;
+ public int UniformBuffersConfig;
+ public int Unknown21;
+ public int Unknown22;
+ public int Unknown23;
+ public int Unknown24;
+ public int Unknown25;
+ public int Unknown26;
+ public int Unknown27;
+ public int Unknown28;
+
+ private UniformBufferParams _uniformBuffer0;
+ private UniformBufferParams _uniformBuffer1;
+ private UniformBufferParams _uniformBuffer2;
+ private UniformBufferParams _uniformBuffer3;
+ private UniformBufferParams _uniformBuffer4;
+ private UniformBufferParams _uniformBuffer5;
+ private UniformBufferParams _uniformBuffer6;
+ private UniformBufferParams _uniformBuffer7;
+
+ public Span<UniformBufferParams> UniformBuffers
+ {
+ get
+ {
+ return MemoryMarshal.CreateSpan(ref _uniformBuffer0, 8);
+ }
+ }
+
+ public int Unknown45;
+ public int Unknown46;
+ public int Unknown47;
+ public int Unknown48;
+ public int Unknown49;
+ public int Unknown50;
+ public int Unknown51;
+ public int Unknown52;
+ public int Unknown53;
+ public int Unknown54;
+ public int Unknown55;
+ public int Unknown56;
+ public int Unknown57;
+ public int Unknown58;
+ public int Unknown59;
+ public int Unknown60;
+ public int Unknown61;
+ public int Unknown62;
+ public int Unknown63;
+
+ public int UnpackGridSizeX()
+ {
+ return GridSizeX & 0x7fffffff;
+ }
+
+ public int UnpackGridSizeY()
+ {
+ return GridSizeYZ & 0xffff;
+ }
+
+ public int UnpackGridSizeZ()
+ {
+ return (GridSizeYZ >> 16) & 0xffff;
+ }
+
+ public int UnpackBlockSizeX()
+ {
+ return (BlockSizeX >> 16) & 0xffff;
+ }
+
+ public int UnpackBlockSizeY()
+ {
+ return BlockSizeYZ & 0xffff;
+ }
+
+ public int UnpackBlockSizeZ()
+ {
+ return (BlockSizeYZ >> 16) & 0xffff;
+ }
+
+ public uint UnpackUniformBuffersEnableMask()
+ {
+ return (uint)UniformBuffersConfig & 0xff;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/ComputeShader.cs b/Ryujinx.Graphics.Gpu/Engine/ComputeShader.cs
new file mode 100644
index 00000000..cc7d4d99
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/ComputeShader.cs
@@ -0,0 +1,18 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ class ComputeShader
+ {
+ public IProgram Interface { get; set; }
+
+ public ShaderProgram Shader { get; }
+
+ public ComputeShader(IProgram program, ShaderProgram shader)
+ {
+ Interface = program;
+ Shader = shader;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/GraphicsShader.cs b/Ryujinx.Graphics.Gpu/Engine/GraphicsShader.cs
new file mode 100644
index 00000000..a8ccc05a
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/GraphicsShader.cs
@@ -0,0 +1,17 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ class GraphicsShader
+ {
+ public IProgram Interface { get; set; }
+
+ public ShaderProgram[] Shader { get; }
+
+ public GraphicsShader()
+ {
+ Shader = new ShaderProgram[5];
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs b/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs
new file mode 100644
index 00000000..d2816ac5
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs
@@ -0,0 +1,42 @@
+using Ryujinx.Graphics.Gpu.State;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ private Inline2MemoryParams _params;
+
+ private bool _isLinear;
+
+ private int _offset;
+ private int _size;
+
+ public void Execute(int argument)
+ {
+ _params = _context.State.Get<Inline2MemoryParams>(MethodOffset.Inline2MemoryParams);
+
+ _isLinear = (argument & 1) != 0;
+
+ _offset = 0;
+ _size = _params.LineLengthIn * _params.LineCount;
+ }
+
+ public void PushData(int argument)
+ {
+ if (_isLinear)
+ {
+ for (int shift = 0; shift < 32 && _offset < _size; shift += 8, _offset++)
+ {
+ ulong gpuVa = _params.DstAddress.Pack() + (ulong)_offset;
+
+ _context.MemoryAccessor.Write(gpuVa, new byte[] { (byte)(argument >> shift) });
+ }
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs
new file mode 100644
index 00000000..b4680fa5
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs
@@ -0,0 +1,55 @@
+using Ryujinx.Graphics.GAL.Color;
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ private void Clear(int argument)
+ {
+ UpdateState();
+
+ bool clearDepth = (argument & 1) != 0;
+ bool clearStencil = (argument & 2) != 0;
+
+ uint componentMask = (uint)((argument >> 2) & 0xf);
+
+ int index = (argument >> 6) & 0xf;
+
+ if (componentMask != 0)
+ {
+ ClearColors clearColor = _context.State.GetClearColors();
+
+ ColorF color = new ColorF(
+ clearColor.Red,
+ clearColor.Green,
+ clearColor.Blue,
+ clearColor.Alpha);
+
+ _context.Renderer.GraphicsPipeline.ClearRenderTargetColor(
+ index,
+ componentMask,
+ color);
+ }
+
+ if (clearDepth || clearStencil)
+ {
+ float depthValue = _context.State.GetClearDepthValue();
+ int stencilValue = _context.State.GetClearStencilValue();
+
+ int stencilMask = 0;
+
+ if (clearStencil)
+ {
+ stencilMask = _context.State.GetStencilTestState().FrontMask;
+ }
+
+ _context.Renderer.GraphicsPipeline.ClearRenderTargetDepthStencil(
+ depthValue,
+ clearDepth,
+ stencilValue,
+ stencilMask);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs b/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs
new file mode 100644
index 00000000..19ffb0e3
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs
@@ -0,0 +1,79 @@
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Texture;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ private void CopyBuffer(int argument)
+ {
+ var cbp = _context.State.Get<CopyBufferParams>(MethodOffset.CopyBufferParams);
+
+ var swizzle = _context.State.Get<CopyBufferSwizzle>(MethodOffset.CopyBufferSwizzle);
+
+ bool srcLinear = (argument & (1 << 7)) != 0;
+ bool dstLinear = (argument & (1 << 8)) != 0;
+ bool copy2D = (argument & (1 << 9)) != 0;
+
+ int size = cbp.XCount;
+
+ if (size == 0)
+ {
+ return;
+ }
+
+ if (copy2D)
+ {
+ // Buffer to texture copy.
+ int srcBpp = swizzle.UnpackSrcComponentsCount() * swizzle.UnpackComponentSize();
+ int dstBpp = swizzle.UnpackDstComponentsCount() * swizzle.UnpackComponentSize();
+
+ var dst = _context.State.Get<CopyBufferTexture>(MethodOffset.CopyBufferDstTexture);
+ var src = _context.State.Get<CopyBufferTexture>(MethodOffset.CopyBufferSrcTexture);
+
+ var srcCalculator = new OffsetCalculator(
+ src.Width,
+ src.Height,
+ cbp.SrcStride,
+ srcLinear,
+ src.MemoryLayout.UnpackGobBlocksInY(),
+ srcBpp);
+
+ var dstCalculator = new OffsetCalculator(
+ dst.Width,
+ dst.Height,
+ cbp.DstStride,
+ dstLinear,
+ dst.MemoryLayout.UnpackGobBlocksInY(),
+ dstBpp);
+
+ ulong srcBaseAddress = _context.MemoryManager.Translate(cbp.SrcAddress.Pack());
+ ulong dstBaseAddress = _context.MemoryManager.Translate(cbp.DstAddress.Pack());
+
+ for (int y = 0; y < cbp.YCount; y++)
+ for (int x = 0; x < cbp.XCount; x++)
+ {
+ int srcOffset = srcCalculator.GetOffset(src.RegionX + x, src.RegionY + y);
+ int dstOffset = dstCalculator.GetOffset(dst.RegionX + x, dst.RegionY + y);
+
+ ulong srcAddress = srcBaseAddress + (ulong)srcOffset;
+ ulong dstAddress = dstBaseAddress + (ulong)dstOffset;
+
+ Span<byte> pixel = _context.PhysicalMemory.Read(srcAddress, (ulong)srcBpp);
+
+ _context.PhysicalMemory.Write(dstAddress, pixel);
+ }
+ }
+ else
+ {
+ // Buffer to buffer copy.
+ _bufferManager.CopyBuffer(cbp.SrcAddress, cbp.DstAddress, (uint)size);
+
+ Span<byte> data = _context.MemoryAccessor.Read(cbp.SrcAddress.Pack(), (uint)size);
+
+ _context.MemoryAccessor.Write(cbp.DstAddress.Pack(), data);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs
new file mode 100644
index 00000000..e2c40805
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs
@@ -0,0 +1,70 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ private void CopyTexture(int argument)
+ {
+ CopyTexture dstCopyTexture = _context.State.GetCopyDstTexture();
+ CopyTexture srcCopyTexture = _context.State.GetCopySrcTexture();
+
+ Image.Texture srcTexture = _textureManager.FindOrCreateTexture(srcCopyTexture);
+
+ if (srcTexture == null)
+ {
+ return;
+ }
+
+ // When the source texture that was found has a depth format,
+ // we must enforce the target texture also has a depth format,
+ // as copies between depth and color formats are not allowed.
+ if (srcTexture.Format == Format.D32Float)
+ {
+ dstCopyTexture.Format = RtFormat.D32Float;
+ }
+
+ Image.Texture dstTexture = _textureManager.FindOrCreateTexture(dstCopyTexture);
+
+ if (dstTexture == null)
+ {
+ return;
+ }
+
+ CopyTextureControl control = _context.State.GetCopyTextureControl();
+
+ CopyRegion region = _context.State.GetCopyRegion();
+
+ int srcX1 = (int)(region.SrcXF >> 32);
+ int srcY1 = (int)(region.SrcYF >> 32);
+
+ int srcX2 = (int)((region.SrcXF + region.SrcWidthRF * region.DstWidth) >> 32);
+ int srcY2 = (int)((region.SrcYF + region.SrcHeightRF * region.DstHeight) >> 32);
+
+ int dstX1 = region.DstX;
+ int dstY1 = region.DstY;
+
+ int dstX2 = region.DstX + region.DstWidth;
+ int dstY2 = region.DstY + region.DstHeight;
+
+ Extents2D srcRegion = new Extents2D(
+ srcX1 / srcTexture.Info.SamplesInX,
+ srcY1 / srcTexture.Info.SamplesInY,
+ srcX2 / srcTexture.Info.SamplesInX,
+ srcY2 / srcTexture.Info.SamplesInY);
+
+ Extents2D dstRegion = new Extents2D(
+ dstX1 / dstTexture.Info.SamplesInX,
+ dstY1 / dstTexture.Info.SamplesInY,
+ dstX2 / dstTexture.Info.SamplesInX,
+ dstY2 / dstTexture.Info.SamplesInY);
+
+ bool linearFilter = control.UnpackLinearFilter();
+
+ srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter);
+
+ dstTexture.Modified = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs b/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs
new file mode 100644
index 00000000..dd360113
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs
@@ -0,0 +1,133 @@
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Gpu.Image;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ private bool _drawIndexed;
+
+ private int _firstIndex;
+ private int _indexCount;
+
+ private bool _instancedHasState;
+ private bool _instancedIndexed;
+
+ private int _instancedFirstIndex;
+ private int _instancedFirstVertex;
+ private int _instancedFirstInstance;
+ private int _instancedIndexCount;
+ private int _instancedDrawStateFirst;
+ private int _instancedDrawStateCount;
+
+ private int _instanceIndex;
+
+ public PrimitiveType PrimitiveType { get; private set; }
+
+ private void DrawEnd(int argument)
+ {
+ UpdateState();
+
+ bool instanced = _vsUsesInstanceId || _isAnyVbInstanced;
+
+ if (instanced)
+ {
+ if (!_instancedHasState)
+ {
+ _instancedHasState = true;
+
+ _instancedIndexed = _drawIndexed;
+
+ _instancedFirstIndex = _firstIndex;
+ _instancedFirstVertex = _context.State.GetBaseVertex();
+ _instancedFirstInstance = _context.State.GetBaseInstance();
+
+ _instancedIndexCount = _indexCount;
+
+ VertexBufferDrawState drawState = _context.State.GetVertexBufferDrawState();
+
+ _instancedDrawStateFirst = drawState.First;
+ _instancedDrawStateCount = drawState.Count;
+ }
+
+ return;
+ }
+
+ int firstInstance = _context.State.GetBaseInstance();
+
+ if (_drawIndexed)
+ {
+ _drawIndexed = false;
+
+ int firstVertex = _context.State.GetBaseVertex();
+
+ _context.Renderer.GraphicsPipeline.DrawIndexed(
+ _indexCount,
+ 1,
+ _firstIndex,
+ firstVertex,
+ firstInstance);
+ }
+ else
+ {
+ VertexBufferDrawState drawState = _context.State.GetVertexBufferDrawState();
+
+ _context.Renderer.GraphicsPipeline.Draw(
+ drawState.Count,
+ 1,
+ drawState.First,
+ firstInstance);
+ }
+ }
+
+ private void DrawBegin(int argument)
+ {
+ PrimitiveType type = (PrimitiveType)(argument & 0xffff);
+
+ _context.Renderer.GraphicsPipeline.SetPrimitiveTopology(type.Convert());
+
+ PrimitiveType = type;
+
+ if ((argument & (1 << 26)) != 0)
+ {
+ _instanceIndex++;
+ }
+ else if ((argument & (1 << 27)) == 0)
+ {
+ _instanceIndex = 0;
+ }
+ }
+
+ private void SetIndexCount(int argument)
+ {
+ _drawIndexed = true;
+ }
+
+ public void PerformDeferredDraws()
+ {
+ // Perform any pending instanced draw.
+ if (_instancedHasState)
+ {
+ _instancedHasState = false;
+
+ if (_instancedIndexed)
+ {
+ _context.Renderer.GraphicsPipeline.DrawIndexed(
+ _instancedIndexCount,
+ _instanceIndex + 1,
+ _instancedFirstIndex,
+ _instancedFirstVertex,
+ _instancedFirstInstance);
+ }
+ else
+ {
+ _context.Renderer.GraphicsPipeline.Draw(
+ _instancedDrawStateCount,
+ _instanceIndex + 1,
+ _instancedDrawStateFirst,
+ _instancedFirstInstance);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs b/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs
new file mode 100644
index 00000000..fd0a31a1
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs
@@ -0,0 +1,100 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.State;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ private ulong _runningCounter;
+
+ private void Report(int argument)
+ {
+ ReportMode mode = (ReportMode)(argument & 3);
+
+ ReportCounterType type = (ReportCounterType)((argument >> 23) & 0x1f);
+
+ switch (mode)
+ {
+ case ReportMode.Semaphore: ReportSemaphore(); break;
+ case ReportMode.Counter: ReportCounter(type); break;
+ }
+ }
+
+ private void ReportSemaphore()
+ {
+ ReportState state = _context.State.GetReportState();
+
+ _context.MemoryAccessor.Write(state.Address.Pack(), state.Payload);
+
+ _context.AdvanceSequence();
+ }
+
+ private struct CounterData
+ {
+ public ulong Counter;
+ public ulong Timestamp;
+ }
+
+ private void ReportCounter(ReportCounterType type)
+ {
+ CounterData counterData = new CounterData();
+
+ ulong counter = 0;
+
+ switch (type)
+ {
+ case ReportCounterType.Zero:
+ counter = 0;
+ break;
+ case ReportCounterType.SamplesPassed:
+ counter = _context.Renderer.GetCounter(CounterType.SamplesPassed);
+ break;
+ case ReportCounterType.PrimitivesGenerated:
+ counter = _context.Renderer.GetCounter(CounterType.PrimitivesGenerated);
+ break;
+ case ReportCounterType.TransformFeedbackPrimitivesWritten:
+ counter = _context.Renderer.GetCounter(CounterType.TransformFeedbackPrimitivesWritten);
+ break;
+ }
+
+ ulong ticks;
+
+ if (GraphicsConfig.FastGpuTime)
+ {
+ ticks = _runningCounter++;
+ }
+ else
+ {
+ ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds);
+ }
+
+ counterData.Counter = counter;
+ counterData.Timestamp = ticks;
+
+ Span<CounterData> counterDataSpan = MemoryMarshal.CreateSpan(ref counterData, 1);
+
+ Span<byte> data = MemoryMarshal.Cast<CounterData, byte>(counterDataSpan);
+
+ ReportState state = _context.State.GetReportState();
+
+ _context.MemoryAccessor.Write(state.Address.Pack(), data);
+ }
+
+ private static ulong ConvertNanosecondsToTicks(ulong nanoseconds)
+ {
+ // We need to divide first to avoid overflows.
+ // We fix up the result later by calculating the difference and adding
+ // that to the result.
+ ulong divided = nanoseconds / 625;
+
+ ulong rounded = divided * 625;
+
+ ulong errorBias = ((nanoseconds - rounded) * 384) / 625;
+
+ return divided * 384 + errorBias;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs b/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs
new file mode 100644
index 00000000..12a87845
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs
@@ -0,0 +1,26 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ private void ResetCounter(int argument)
+ {
+ ResetCounterType type = (ResetCounterType)argument;
+
+ switch (type)
+ {
+ case ResetCounterType.SamplesPassed:
+ _context.Renderer.ResetCounter(CounterType.SamplesPassed);
+ break;
+ case ResetCounterType.PrimitivesGenerated:
+ _context.Renderer.ResetCounter(CounterType.PrimitivesGenerated);
+ break;
+ case ResetCounterType.TransformFeedbackPrimitivesWritten:
+ _context.Renderer.ResetCounter(CounterType.TransformFeedbackPrimitivesWritten);
+ break;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs
new file mode 100644
index 00000000..a9ebce83
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs
@@ -0,0 +1,52 @@
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ private void UniformBufferBind0(int argument)
+ {
+ UniformBufferBind(argument, ShaderType.Vertex);
+ }
+
+ private void UniformBufferBind1(int argument)
+ {
+ UniformBufferBind(argument, ShaderType.TessellationControl);
+ }
+
+ private void UniformBufferBind2(int argument)
+ {
+ UniformBufferBind(argument, ShaderType.TessellationEvaluation);
+ }
+
+ private void UniformBufferBind3(int argument)
+ {
+ UniformBufferBind(argument, ShaderType.Geometry);
+ }
+
+ private void UniformBufferBind4(int argument)
+ {
+ UniformBufferBind(argument, ShaderType.Fragment);
+ }
+
+ private void UniformBufferBind(int argument, ShaderType type)
+ {
+ bool enable = (argument & 1) != 0;
+
+ int index = (argument >> 4) & 0x1f;
+
+ if (enable)
+ {
+ UniformBufferState uniformBuffer = _context.State.GetUniformBufferState();
+
+ ulong address = uniformBuffer.Address.Pack();
+
+ _bufferManager.SetGraphicsUniformBuffer((int)type, index, address, (uint)uniformBuffer.Size);
+ }
+ else
+ {
+ _bufferManager.SetGraphicsUniformBuffer((int)type, index, 0, 0);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs
new file mode 100644
index 00000000..f0b8e2d7
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs
@@ -0,0 +1,18 @@
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ private void UniformBufferUpdate(int argument)
+ {
+ UniformBufferState uniformBuffer = _context.State.GetUniformBufferState();
+
+ _context.MemoryAccessor.Write(uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset, argument);
+
+ _context.State.SetUniformBufferOffset(uniformBuffer.Offset + 4);
+
+ _context.AdvanceSequence();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
new file mode 100644
index 00000000..db72a861
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
@@ -0,0 +1,784 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Blend;
+using Ryujinx.Graphics.GAL.DepthStencil;
+using Ryujinx.Graphics.GAL.InputAssembler;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ partial class Methods
+ {
+ private GpuContext _context;
+
+ private ShaderCache _shaderCache;
+
+ private BufferManager _bufferManager;
+ private TextureManager _textureManager;
+
+ public TextureManager TextureManager => _textureManager;
+
+ private bool _isAnyVbInstanced;
+ private bool _vsUsesInstanceId;
+
+ public Methods(GpuContext context)
+ {
+ _context = context;
+
+ _shaderCache = new ShaderCache(_context);
+
+ _bufferManager = new BufferManager(context);
+ _textureManager = new TextureManager(context, _bufferManager);
+
+ RegisterCallbacks();
+ }
+
+ private void RegisterCallbacks()
+ {
+ _context.State.RegisterCopyBufferCallback(CopyBuffer);
+ _context.State.RegisterCopyTextureCallback(CopyTexture);
+
+ _context.State.RegisterDrawEndCallback(DrawEnd);
+
+ _context.State.RegisterDrawBeginCallback(DrawBegin);
+
+ _context.State.RegisterSetIndexCountCallback(SetIndexCount);
+
+ _context.State.RegisterClearCallback(Clear);
+
+ _context.State.RegisterReportCallback(Report);
+
+ _context.State.RegisterUniformBufferUpdateCallback(UniformBufferUpdate);
+
+ _context.State.RegisterUniformBufferBind0Callback(UniformBufferBind0);
+ _context.State.RegisterUniformBufferBind1Callback(UniformBufferBind1);
+ _context.State.RegisterUniformBufferBind2Callback(UniformBufferBind2);
+ _context.State.RegisterUniformBufferBind3Callback(UniformBufferBind3);
+ _context.State.RegisterUniformBufferBind4Callback(UniformBufferBind4);
+
+ _context.State.RegisterCallback(MethodOffset.InvalidateTextures, InvalidateTextures);
+
+ _context.State.RegisterCallback(MethodOffset.ResetCounter, ResetCounter);
+
+ _context.State.RegisterCallback(MethodOffset.Inline2MemoryExecute, Execute);
+ _context.State.RegisterCallback(MethodOffset.Inline2MemoryPushData, PushData);
+
+ _context.State.RegisterCallback(MethodOffset.Dispatch, Dispatch);
+ }
+
+ public Image.Texture GetTexture(ulong address) => _textureManager.Find2(address);
+
+ private void UpdateState()
+ {
+ if ((_context.State.StateWriteFlags & StateWriteFlags.Any) == 0)
+ {
+ CommitBindings();
+
+ return;
+ }
+
+ // Shaders must be the first one to be updated if modified, because
+ // some of the other state depends on information from the currently
+ // bound shaders.
+ if ((_context.State.StateWriteFlags & StateWriteFlags.ShaderState) != 0)
+ {
+ UpdateShaderState();
+ }
+
+ if ((_context.State.StateWriteFlags & StateWriteFlags.RenderTargetGroup) != 0)
+ {
+ UpdateRenderTargetGroupState();
+ }
+
+ if ((_context.State.StateWriteFlags & StateWriteFlags.DepthTestState) != 0)
+ {
+ UpdateDepthTestState();
+ }
+
+ if ((_context.State.StateWriteFlags & StateWriteFlags.ViewportTransform) != 0)
+ {
+ UpdateViewportTransform();
+ }
+
+ if ((_context.State.StateWriteFlags & StateWriteFlags.DepthBiasState) != 0)
+ {
+ UpdateDepthBiasState();
+ }
+
+ if ((_context.State.StateWriteFlags & StateWriteFlags.StencilTestState) != 0)
+ {
+ UpdateStencilTestState();
+ }
+
+ if ((_context.State.StateWriteFlags & StateWriteFlags.SamplerPoolState) != 0)
+ {
+ UpdateSamplerPoolState();
+ }
+
+ if ((_context.State.StateWriteFlags & StateWriteFlags.TexturePoolState) != 0)
+ {
+ UpdateTexturePoolState();
+ }
+
+ if ((_context.State.StateWriteFlags & StateWriteFlags.InputAssemblerGroup) != 0)
+ {
+ UpdateInputAssemblerGroupState();
+ }
+
+ if ((_context.State.StateWriteFlags & StateWriteFlags.FaceState) != 0)
+ {
+ UpdateFaceState();
+ }
+
+ if ((_context.State.StateWriteFlags & StateWriteFlags.RtColorMask) != 0)
+ {
+ UpdateRtColorMask();
+ }
+
+ if ((_context.State.StateWriteFlags & StateWriteFlags.BlendState) != 0)
+ {
+ UpdateBlendState();
+ }
+
+ _context.State.StateWriteFlags &= ~StateWriteFlags.Any;
+
+ CommitBindings();
+ }
+
+ private void CommitBindings()
+ {
+ _bufferManager.CommitBindings();
+ _textureManager.CommitBindings();
+ }
+
+ public void InvalidateRange(ulong address, ulong size)
+ {
+ _bufferManager.InvalidateRange(address, size);
+ _textureManager.InvalidateRange(address, size);
+ }
+
+ public void InvalidateTextureRange(ulong address, ulong size)
+ {
+ _textureManager.InvalidateRange(address, size);
+ }
+
+ private void UpdateRenderTargetGroupState()
+ {
+ TextureMsaaMode msaaMode = _context.State.GetRtMsaaMode();
+
+ int samplesInX = msaaMode.SamplesInX();
+ int samplesInY = msaaMode.SamplesInY();
+
+ Image.Texture color3D = Get3DRenderTarget(samplesInX, samplesInY);
+
+ if (color3D == null)
+ {
+ for (int index = 0; index < Constants.TotalRenderTargets; index++)
+ {
+ RtColorState colorState = _context.State.GetRtColorState(index);
+
+ if (!IsRtEnabled(colorState))
+ {
+ _textureManager.SetRenderTargetColor(index, null);
+
+ continue;
+ }
+
+ Image.Texture color = _textureManager.FindOrCreateTexture(
+ colorState,
+ samplesInX,
+ samplesInY);
+
+ _textureManager.SetRenderTargetColor(index, color);
+
+ color.Modified = true;
+ }
+ }
+ else
+ {
+ _textureManager.SetRenderTargetColor3D(color3D);
+
+ color3D.Modified = true;
+ }
+
+ bool dsEnable = _context.State.Get<bool>(MethodOffset.RtDepthStencilEnable);
+
+ Image.Texture depthStencil = null;
+
+ if (dsEnable)
+ {
+ var dsState = _context.State.GetRtDepthStencilState();
+ var dsSize = _context.State.GetRtDepthStencilSize();
+
+ depthStencil = _textureManager.FindOrCreateTexture(
+ dsState,
+ dsSize,
+ samplesInX,
+ samplesInY);
+ }
+
+ _textureManager.SetRenderTargetDepthStencil(depthStencil);
+ }
+
+ private Image.Texture Get3DRenderTarget(int samplesInX, int samplesInY)
+ {
+ RtColorState colorState0 = _context.State.GetRtColorState(0);
+
+ if (!IsRtEnabled(colorState0) || !colorState0.MemoryLayout.UnpackIsTarget3D() || colorState0.Depth != 1)
+ {
+ return null;
+ }
+
+ int slices = 1;
+ int unused = 0;
+
+ for (int index = 1; index < Constants.TotalRenderTargets; index++)
+ {
+ RtColorState colorState = _context.State.GetRtColorState(index);
+
+ if (!IsRtEnabled(colorState))
+ {
+ unused++;
+
+ continue;
+ }
+
+ if (colorState.MemoryLayout.UnpackIsTarget3D() && colorState.Depth == 1)
+ {
+ slices++;
+ }
+ }
+
+ if (slices + unused == Constants.TotalRenderTargets)
+ {
+ colorState0.Depth = slices;
+
+ return _textureManager.FindOrCreateTexture(colorState0, samplesInX, samplesInY);
+ }
+
+ return null;
+ }
+
+ private static bool IsRtEnabled(RtColorState colorState)
+ {
+ // Colors are disabled by writing 0 to the format.
+ return colorState.Format != 0 && colorState.WidthOrStride != 0;
+ }
+
+ private void UpdateDepthTestState()
+ {
+ _context.Renderer.GraphicsPipeline.SetDepthTest(new DepthTestDescriptor(
+ _context.State.GetDepthTestEnable().IsTrue(),
+ _context.State.GetDepthWriteEnable().IsTrue(),
+ _context.State.GetDepthTestFunc()));
+ }
+
+ private void UpdateViewportTransform()
+ {
+ Viewport[] viewports = new Viewport[Constants.TotalViewports];
+
+ for (int index = 0; index < Constants.TotalViewports; index++)
+ {
+ var transform = _context.State.Get<ViewportTransform>(MethodOffset.ViewportTransform + index * 8);
+ var extents = _context.State.Get<ViewportExtents> (MethodOffset.ViewportExtents + index * 4);
+
+ float x = transform.TranslateX - MathF.Abs(transform.ScaleX);
+ float y = transform.TranslateY - MathF.Abs(transform.ScaleY);
+
+ float width = transform.ScaleX * 2;
+ float height = transform.ScaleY * 2;
+
+ RectangleF region = new RectangleF(x, y, width, height);
+
+ viewports[index] = new Viewport(
+ region,
+ transform.UnpackSwizzleX(),
+ transform.UnpackSwizzleY(),
+ transform.UnpackSwizzleZ(),
+ transform.UnpackSwizzleW(),
+ extents.DepthNear,
+ extents.DepthFar);
+ }
+
+ _context.Renderer.GraphicsPipeline.SetViewports(0, viewports);
+ }
+
+ private void UpdateDepthBiasState()
+ {
+ var polygonOffset = _context.State.Get<DepthBiasState>(MethodOffset.DepthBiasState);
+
+ float factor = _context.State.Get<float>(MethodOffset.DepthBiasFactor);
+ float units = _context.State.Get<float>(MethodOffset.DepthBiasUnits);
+ float clamp = _context.State.Get<float>(MethodOffset.DepthBiasClamp);
+
+ PolygonModeMask enables = 0;
+
+ enables = (polygonOffset.PointEnable.IsTrue() ? PolygonModeMask.Point : 0);
+ enables |= (polygonOffset.LineEnable.IsTrue() ? PolygonModeMask.Line : 0);
+ enables |= (polygonOffset.FillEnable.IsTrue() ? PolygonModeMask.Fill : 0);
+
+ _context.Renderer.GraphicsPipeline.SetDepthBias(enables, factor, units, clamp);
+ }
+
+ private void UpdateStencilTestState()
+ {
+ StencilBackMasks backMasks = _context.State.GetStencilBackMasks();
+ StencilTestState test = _context.State.GetStencilTestState();
+ StencilBackTestState backTest = _context.State.GetStencilBackTestState();
+
+ CompareOp backFunc;
+ StencilOp backSFail;
+ StencilOp backDpPass;
+ StencilOp backDpFail;
+ int backFuncRef;
+ int backFuncMask;
+ int backMask;
+
+ if (backTest.TwoSided.IsTrue())
+ {
+ backFunc = backTest.BackFunc;
+ backSFail = backTest.BackSFail;
+ backDpPass = backTest.BackDpPass;
+ backDpFail = backTest.BackDpFail;
+ backFuncRef = backMasks.FuncRef;
+ backFuncMask = backMasks.FuncMask;
+ backMask = backMasks.Mask;
+ }
+ else
+ {
+ backFunc = test.FrontFunc;
+ backSFail = test.FrontSFail;
+ backDpPass = test.FrontDpPass;
+ backDpFail = test.FrontDpFail;
+ backFuncRef = test.FrontFuncRef;
+ backFuncMask = test.FrontFuncMask;
+ backMask = test.FrontMask;
+ }
+
+ _context.Renderer.GraphicsPipeline.SetStencilTest(new StencilTestDescriptor(
+ test.Enable.IsTrue(),
+ test.FrontFunc,
+ test.FrontSFail,
+ test.FrontDpPass,
+ test.FrontDpFail,
+ test.FrontFuncRef,
+ test.FrontFuncMask,
+ test.FrontMask,
+ backFunc,
+ backSFail,
+ backDpPass,
+ backDpFail,
+ backFuncRef,
+ backFuncMask,
+ backMask));
+ }
+
+ private void UpdateSamplerPoolState()
+ {
+ PoolState samplerPool = _context.State.GetSamplerPoolState();
+
+ _textureManager.SetSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId);
+ }
+
+ private void UpdateTexturePoolState()
+ {
+ PoolState texturePool = _context.State.GetTexturePoolState();
+
+ _textureManager.SetTexturePool(texturePool.Address.Pack(), texturePool.MaximumId);
+
+ _textureManager.SetTextureBufferIndex(_context.State.GetTextureBufferIndex());
+ }
+
+ private void UpdateInputAssemblerGroupState()
+ {
+ // Must be updated before the vertex buffer.
+ if ((_context.State.StateWriteFlags & StateWriteFlags.VertexAttribState) != 0)
+ {
+ UpdateVertexAttribState();
+ }
+
+ if ((_context.State.StateWriteFlags & StateWriteFlags.PrimitiveRestartState) != 0)
+ {
+ UpdatePrimitiveRestartState();
+ }
+
+ if ((_context.State.StateWriteFlags & StateWriteFlags.IndexBufferState) != 0)
+ {
+ UpdateIndexBufferState();
+ }
+
+ if ((_context.State.StateWriteFlags & StateWriteFlags.VertexBufferState) != 0)
+ {
+ UpdateVertexBufferState();
+ }
+ }
+
+ private void UpdateVertexAttribState()
+ {
+ VertexAttribDescriptor[] vertexAttribs = new VertexAttribDescriptor[16];
+
+ for (int index = 0; index < 16; index++)
+ {
+ VertexAttribState vertexAttrib = _context.State.GetVertexAttribState(index);
+
+ if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format))
+ {
+ // TODO: warning.
+
+ format = Format.R32G32B32A32Float;
+ }
+
+ vertexAttribs[index] = new VertexAttribDescriptor(
+ vertexAttrib.UnpackBufferIndex(),
+ vertexAttrib.UnpackOffset(),
+ format);
+ }
+
+ _context.Renderer.GraphicsPipeline.BindVertexAttribs(vertexAttribs);
+ }
+
+ private void UpdatePrimitiveRestartState()
+ {
+ PrimitiveRestartState primitiveRestart = _context.State.Get<PrimitiveRestartState>(MethodOffset.PrimitiveRestartState);
+
+ _context.Renderer.GraphicsPipeline.SetPrimitiveRestart(
+ primitiveRestart.Enable,
+ primitiveRestart.Index);
+ }
+
+ private void UpdateIndexBufferState()
+ {
+ IndexBufferState indexBuffer = _context.State.GetIndexBufferState();
+
+ _firstIndex = indexBuffer.First;
+ _indexCount = indexBuffer.Count;
+
+ if (_indexCount == 0)
+ {
+ return;
+ }
+
+ ulong gpuVa = indexBuffer.Address.Pack();
+
+ // Do not use the end address to calculate the size, because
+ // the result may be much larger than the real size of the index buffer.
+ ulong size = (ulong)(_firstIndex + _indexCount);
+
+ switch (indexBuffer.Type)
+ {
+ case IndexType.UShort: size *= 2; break;
+ case IndexType.UInt: size *= 4; break;
+ }
+
+ _bufferManager.SetIndexBuffer(gpuVa, size, indexBuffer.Type);
+
+ // The index buffer affects the vertex buffer size calculation, we
+ // need to ensure that they are updated.
+ UpdateVertexBufferState();
+ }
+
+ private uint GetIndexBufferMaxIndex(ulong gpuVa, ulong size, IndexType type)
+ {
+ ulong address = _context.MemoryManager.Translate(gpuVa);
+
+ Span<byte> data = _context.PhysicalMemory.Read(address, size);
+
+ uint maxIndex = 0;
+
+ switch (type)
+ {
+ case IndexType.UByte:
+ {
+ for (int index = 0; index < data.Length; index++)
+ {
+ if (maxIndex < data[index])
+ {
+ maxIndex = data[index];
+ }
+ }
+
+ break;
+ }
+
+ case IndexType.UShort:
+ {
+ Span<ushort> indices = MemoryMarshal.Cast<byte, ushort>(data);
+
+ for (int index = 0; index < indices.Length; index++)
+ {
+ if (maxIndex < indices[index])
+ {
+ maxIndex = indices[index];
+ }
+ }
+
+ break;
+ }
+
+ case IndexType.UInt:
+ {
+ Span<uint> indices = MemoryMarshal.Cast<byte, uint>(data);
+
+ for (int index = 0; index < indices.Length; index++)
+ {
+ if (maxIndex < indices[index])
+ {
+ maxIndex = indices[index];
+ }
+ }
+
+ break;
+ }
+ }
+
+ return maxIndex;
+ }
+
+ private void UpdateVertexBufferState()
+ {
+ _isAnyVbInstanced = false;
+
+ for (int index = 0; index < 16; index++)
+ {
+ VertexBufferState vertexBuffer = _context.State.GetVertexBufferState(index);
+
+ if (!vertexBuffer.UnpackEnable())
+ {
+ _bufferManager.SetVertexBuffer(index, 0, 0, 0, 0);
+
+ continue;
+ }
+
+ GpuVa endAddress = _context.State.GetVertexBufferEndAddress(index);
+
+ ulong address = vertexBuffer.Address.Pack();
+
+ int stride = vertexBuffer.UnpackStride();
+
+ bool instanced = _context.State.Get<bool>(MethodOffset.VertexBufferInstanced + index);
+
+ int divisor = instanced ? vertexBuffer.Divisor : 0;
+
+ _isAnyVbInstanced |= divisor != 0;
+
+ ulong size;
+
+ if (_drawIndexed || stride == 0 || instanced)
+ {
+ // This size may be (much) larger than the real vertex buffer size.
+ // Avoid calculating it this way, unless we don't have any other option.
+ size = endAddress.Pack() - address + 1;
+ }
+ else
+ {
+ // For non-indexed draws, we can guess the size from the vertex count
+ // and stride.
+ int firstInstance = _context.State.GetBaseInstance();
+
+ VertexBufferDrawState drawState = _context.State.GetVertexBufferDrawState();
+
+ size = (ulong)((firstInstance + drawState.First + drawState.Count) * stride);
+ }
+
+ _bufferManager.SetVertexBuffer(index, address, size, stride, divisor);
+ }
+ }
+
+ private void UpdateFaceState()
+ {
+ FaceState face = _context.State.GetFaceState();
+
+ _context.Renderer.GraphicsPipeline.SetFaceCulling(face.CullEnable.IsTrue(), face.CullFace);
+
+ _context.Renderer.GraphicsPipeline.SetFrontFace(face.FrontFace);
+ }
+
+ private void UpdateRtColorMask()
+ {
+ uint[] componentMasks = new uint[Constants.TotalRenderTargets];
+
+ for (int index = 0; index < Constants.TotalRenderTargets; index++)
+ {
+ RtColorMask colorMask = _context.State.Get<RtColorMask>(MethodOffset.RtColorMask + index);
+
+ uint componentMask = 0;
+
+ componentMask = (colorMask.UnpackRed() ? 1u : 0u);
+ componentMask |= (colorMask.UnpackGreen() ? 2u : 0u);
+ componentMask |= (colorMask.UnpackBlue() ? 4u : 0u);
+ componentMask |= (colorMask.UnpackAlpha() ? 8u : 0u);
+
+ componentMasks[index] = componentMask;
+ }
+
+ _context.Renderer.GraphicsPipeline.SetRenderTargetColorMasks(componentMasks);
+ }
+
+ private void UpdateBlendState()
+ {
+ BlendState[] blends = new BlendState[8];
+
+ for (int index = 0; index < 8; index++)
+ {
+ bool blendEnable = _context.State.GetBlendEnable(index).IsTrue();
+
+ BlendState blend = _context.State.GetBlendState(index);
+
+ BlendDescriptor descriptor = new BlendDescriptor(
+ blendEnable,
+ blend.ColorOp,
+ blend.ColorSrcFactor,
+ blend.ColorDstFactor,
+ blend.AlphaOp,
+ blend.AlphaSrcFactor,
+ blend.AlphaDstFactor);
+
+ _context.Renderer.GraphicsPipeline.BindBlendState(index, descriptor);
+ }
+ }
+
+ private struct SbDescriptor
+ {
+ public uint AddressLow;
+ public uint AddressHigh;
+ public int Size;
+ public int Padding;
+
+ public ulong PackAddress()
+ {
+ return AddressLow | ((ulong)AddressHigh << 32);
+ }
+ }
+
+ private void UpdateShaderState()
+ {
+ ShaderAddresses addresses = new ShaderAddresses();
+
+ Span<ShaderAddresses> addressesSpan = MemoryMarshal.CreateSpan(ref addresses, 1);
+
+ Span<ulong> addressesArray = MemoryMarshal.Cast<ShaderAddresses, ulong>(addressesSpan);
+
+ ulong baseAddress = _context.State.GetShaderBaseAddress().Pack();
+
+ for (int index = 0; index < 6; index++)
+ {
+ ShaderState shader = _context.State.GetShaderState(index);
+
+ if (!shader.UnpackEnable() && index != 1)
+ {
+ continue;
+ }
+
+ addressesArray[index] = baseAddress + shader.Offset;
+ }
+
+ GraphicsShader gs = _shaderCache.GetGraphicsShader(addresses);
+
+ _vsUsesInstanceId = gs.Shader[0].Info.UsesInstanceId;
+
+ for (int stage = 0; stage < Constants.TotalShaderStages; stage++)
+ {
+ ShaderProgramInfo info = gs.Shader[stage]?.Info;
+
+ if (info == null)
+ {
+ continue;
+ }
+
+ var textureBindings = new TextureBindingInfo[info.Textures.Count];
+
+ for (int index = 0; index < info.Textures.Count; index++)
+ {
+ var descriptor = info.Textures[index];
+
+ Target target = GetTarget(descriptor.Target);
+
+ textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
+ }
+
+ _textureManager.BindTextures(stage, textureBindings);
+
+ uint sbEnableMask = 0;
+ uint ubEnableMask = 0;
+
+ for (int index = 0; index < info.SBuffers.Count; index++)
+ {
+ BufferDescriptor sb = info.SBuffers[index];
+
+ sbEnableMask |= 1u << sb.Slot;
+
+ ulong sbDescAddress = _bufferManager.GetGraphicsUniformBufferAddress(stage, 0);
+
+ int sbDescOffset = 0x110 + stage * 0x100 + sb.Slot * 0x10;
+
+ sbDescAddress += (ulong)sbDescOffset;
+
+ Span<byte> sbDescriptorData = _context.PhysicalMemory.Read(sbDescAddress, 0x10);
+
+ SbDescriptor sbDescriptor = MemoryMarshal.Cast<byte, SbDescriptor>(sbDescriptorData)[0];
+
+ _bufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size);
+ }
+
+ for (int index = 0; index < info.CBuffers.Count; index++)
+ {
+ ubEnableMask |= 1u << info.CBuffers[index].Slot;
+ }
+
+ _bufferManager.SetGraphicsStorageBufferEnableMask(stage, sbEnableMask);
+ _bufferManager.SetGraphicsUniformBufferEnableMask(stage, ubEnableMask);
+ }
+
+ _context.Renderer.GraphicsPipeline.BindProgram(gs.Interface);
+ }
+
+ private static Target GetTarget(Shader.TextureTarget target)
+ {
+ target &= ~Shader.TextureTarget.Shadow;
+
+ switch (target)
+ {
+ case Shader.TextureTarget.Texture1D:
+ return Target.Texture1D;
+
+ case Shader.TextureTarget.Texture1D | Shader.TextureTarget.Array:
+ return Target.Texture1DArray;
+
+ case Shader.TextureTarget.Texture2D:
+ return Target.Texture2D;
+
+ case Shader.TextureTarget.Texture2D | Shader.TextureTarget.Array:
+ return Target.Texture2DArray;
+
+ case Shader.TextureTarget.Texture2D | Shader.TextureTarget.Multisample:
+ return Target.Texture2DMultisample;
+
+ case Shader.TextureTarget.Texture2D | Shader.TextureTarget.Multisample | Shader.TextureTarget.Array:
+ return Target.Texture2DMultisampleArray;
+
+ case Shader.TextureTarget.Texture3D:
+ return Target.Texture3D;
+
+ case Shader.TextureTarget.TextureCube:
+ return Target.Cubemap;
+
+ case Shader.TextureTarget.TextureCube | Shader.TextureTarget.Array:
+ return Target.CubemapArray;
+ }
+
+ // TODO: Warning.
+
+ return Target.Texture2D;
+ }
+
+ private void InvalidateTextures(int argument)
+ {
+ _textureManager.Flush();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/ShaderAddresses.cs b/Ryujinx.Graphics.Gpu/Engine/ShaderAddresses.cs
new file mode 100644
index 00000000..368b5a17
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/ShaderAddresses.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ struct ShaderAddresses : IEquatable<ShaderAddresses>
+ {
+ public ulong VertexA;
+ public ulong Vertex;
+ public ulong TessControl;
+ public ulong TessEvaluation;
+ public ulong Geometry;
+ public ulong Fragment;
+
+ public override bool Equals(object other)
+ {
+ return other is ShaderAddresses addresses && Equals(addresses);
+ }
+
+ public bool Equals(ShaderAddresses other)
+ {
+ return VertexA == other.VertexA &&
+ Vertex == other.Vertex &&
+ TessControl == other.TessControl &&
+ TessEvaluation == other.TessEvaluation &&
+ Geometry == other.Geometry &&
+ Fragment == other.Fragment;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(VertexA, Vertex, TessControl, TessEvaluation, Geometry, Fragment);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Engine/ShaderCache.cs
new file mode 100644
index 00000000..79a84a6d
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/ShaderCache.cs
@@ -0,0 +1,228 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ class ShaderCache
+ {
+ private const int MaxProgramSize = 0x100000;
+
+ private GpuContext _context;
+
+ private ShaderDumper _dumper;
+
+ private Dictionary<ulong, ComputeShader> _cpPrograms;
+
+ private Dictionary<ShaderAddresses, GraphicsShader> _gpPrograms;
+
+ public ShaderCache(GpuContext context)
+ {
+ _context = context;
+
+ _dumper = new ShaderDumper(context);
+
+ _cpPrograms = new Dictionary<ulong, ComputeShader>();
+
+ _gpPrograms = new Dictionary<ShaderAddresses, GraphicsShader>();
+ }
+
+ public ComputeShader GetComputeShader(ulong gpuVa, int localSizeX, int localSizeY, int localSizeZ)
+ {
+ if (!_cpPrograms.TryGetValue(gpuVa, out ComputeShader cpShader))
+ {
+ ShaderProgram shader = TranslateComputeShader(gpuVa);
+
+ shader.Replace(DefineNames.LocalSizeX, localSizeX.ToString(CultureInfo.InvariantCulture));
+ shader.Replace(DefineNames.LocalSizeY, localSizeY.ToString(CultureInfo.InvariantCulture));
+ shader.Replace(DefineNames.LocalSizeZ, localSizeZ.ToString(CultureInfo.InvariantCulture));
+
+ IShader hostShader = _context.Renderer.CompileShader(shader);
+
+ IProgram program = _context.Renderer.CreateProgram(new IShader[] { hostShader });
+
+ cpShader = new ComputeShader(program, shader);
+
+ _cpPrograms.Add(gpuVa, cpShader);
+ }
+
+ return cpShader;
+ }
+
+ public GraphicsShader GetGraphicsShader(ShaderAddresses addresses)
+ {
+ if (!_gpPrograms.TryGetValue(addresses, out GraphicsShader gpShader))
+ {
+ gpShader = new GraphicsShader();
+
+ if (addresses.VertexA != 0)
+ {
+ gpShader.Shader[0] = TranslateGraphicsShader(addresses.Vertex, addresses.VertexA);
+ }
+ else
+ {
+ gpShader.Shader[0] = TranslateGraphicsShader(addresses.Vertex);
+ }
+
+ gpShader.Shader[1] = TranslateGraphicsShader(addresses.TessControl);
+ gpShader.Shader[2] = TranslateGraphicsShader(addresses.TessEvaluation);
+ gpShader.Shader[3] = TranslateGraphicsShader(addresses.Geometry);
+ gpShader.Shader[4] = TranslateGraphicsShader(addresses.Fragment);
+
+ BackpropQualifiers(gpShader);
+
+ List<IShader> shaders = new List<IShader>();
+
+ for (int stage = 0; stage < gpShader.Shader.Length; stage++)
+ {
+ if (gpShader.Shader[stage] == null)
+ {
+ continue;
+ }
+
+ IShader shader = _context.Renderer.CompileShader(gpShader.Shader[stage]);
+
+ shaders.Add(shader);
+ }
+
+ gpShader.Interface = _context.Renderer.CreateProgram(shaders.ToArray());
+
+ _gpPrograms.Add(addresses, gpShader);
+ }
+
+ return gpShader;
+ }
+
+ private ShaderProgram TranslateComputeShader(ulong gpuVa)
+ {
+ if (gpuVa == 0)
+ {
+ return null;
+ }
+
+ ShaderProgram program;
+
+ const TranslationFlags flags =
+ TranslationFlags.Compute |
+ TranslationFlags.Unspecialized;
+
+ TranslationConfig translationConfig = new TranslationConfig(0x10000, _dumper.CurrentDumpIndex, flags);
+
+ Span<byte> code = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize);
+
+ program = Translator.Translate(code, translationConfig);
+
+ _dumper.Dump(gpuVa, compute : true);
+
+ return program;
+ }
+
+ private ShaderProgram TranslateGraphicsShader(ulong gpuVa, ulong gpuVaA = 0)
+ {
+ if (gpuVa == 0)
+ {
+ return null;
+ }
+
+ ShaderProgram program;
+
+ const TranslationFlags flags =
+ TranslationFlags.DebugMode |
+ TranslationFlags.Unspecialized;
+
+ TranslationConfig translationConfig = new TranslationConfig(0x10000, _dumper.CurrentDumpIndex, flags);
+
+ if (gpuVaA != 0)
+ {
+ Span<byte> codeA = _context.MemoryAccessor.Read(gpuVaA, MaxProgramSize);
+ Span<byte> codeB = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize);
+
+ program = Translator.Translate(codeA, codeB, translationConfig);
+
+ _dumper.Dump(gpuVaA, compute: false);
+ _dumper.Dump(gpuVa, compute: false);
+ }
+ else
+ {
+ Span<byte> code = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize);
+
+ program = Translator.Translate(code, translationConfig);
+
+ _dumper.Dump(gpuVa, compute: false);
+ }
+
+ if (program.Stage == ShaderStage.Geometry)
+ {
+ PrimitiveType primitiveType = _context.Methods.PrimitiveType;
+
+ string inPrimitive = "points";
+
+ switch (primitiveType)
+ {
+ case PrimitiveType.Points:
+ inPrimitive = "points";
+ break;
+ case PrimitiveType.Lines:
+ case PrimitiveType.LineLoop:
+ case PrimitiveType.LineStrip:
+ inPrimitive = "lines";
+ break;
+ case PrimitiveType.LinesAdjacency:
+ case PrimitiveType.LineStripAdjacency:
+ inPrimitive = "lines_adjacency";
+ break;
+ case PrimitiveType.Triangles:
+ case PrimitiveType.TriangleStrip:
+ case PrimitiveType.TriangleFan:
+ inPrimitive = "triangles";
+ break;
+ case PrimitiveType.TrianglesAdjacency:
+ case PrimitiveType.TriangleStripAdjacency:
+ inPrimitive = "triangles_adjacency";
+ break;
+ }
+
+ program.Replace(DefineNames.InputTopologyName, inPrimitive);
+ }
+
+ return program;
+ }
+
+ private void BackpropQualifiers(GraphicsShader program)
+ {
+ ShaderProgram fragmentShader = program.Shader[4];
+
+ bool isFirst = true;
+
+ for (int stage = 3; stage >= 0; stage--)
+ {
+ if (program.Shader[stage] == null)
+ {
+ continue;
+ }
+
+ // We need to iterate backwards, since we do name replacement,
+ // and it would otherwise replace a subset of the longer names.
+ for (int attr = 31; attr >= 0; attr--)
+ {
+ string iq = fragmentShader?.Info.InterpolationQualifiers[attr].ToGlslQualifier() ?? string.Empty;
+
+ if (isFirst && iq != string.Empty)
+ {
+ program.Shader[stage].Replace($"{DefineNames.OutQualifierPrefixName}{attr}", iq);
+ }
+ else
+ {
+ program.Shader[stage].Replace($"{DefineNames.OutQualifierPrefixName}{attr} ", string.Empty);
+ }
+ }
+
+ isFirst = false;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/ShaderDumper.cs b/Ryujinx.Graphics.Gpu/Engine/ShaderDumper.cs
new file mode 100644
index 00000000..fdcf0612
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Engine/ShaderDumper.cs
@@ -0,0 +1,126 @@
+using System.IO;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+ class ShaderDumper
+ {
+ private const int ShaderHeaderSize = 0x50;
+
+ private GpuContext _context;
+
+ private string _runtimeDir;
+ private string _dumpPath;
+ private int _dumpIndex;
+
+ public int CurrentDumpIndex => _dumpIndex;
+
+ public ShaderDumper(GpuContext context)
+ {
+ _context = context;
+
+ _dumpIndex = 1;
+ }
+
+ public void Dump(ulong gpuVa, bool compute)
+ {
+ _dumpPath = GraphicsConfig.ShadersDumpPath;
+
+ if (string.IsNullOrWhiteSpace(_dumpPath))
+ {
+ return;
+ }
+
+ string fileName = "Shader" + _dumpIndex.ToString("d4") + ".bin";
+
+ string fullPath = Path.Combine(FullDir(), fileName);
+ string codePath = Path.Combine(CodeDir(), fileName);
+
+ _dumpIndex++;
+
+ ulong headerSize = compute ? 0UL : ShaderHeaderSize;
+
+ using (FileStream fullFile = File.Create(fullPath))
+ using (FileStream codeFile = File.Create(codePath))
+ {
+ BinaryWriter fullWriter = new BinaryWriter(fullFile);
+ BinaryWriter codeWriter = new BinaryWriter(codeFile);
+
+ for (ulong i = 0; i < headerSize; i += 4)
+ {
+ fullWriter.Write(_context.MemoryAccessor.ReadInt32(gpuVa + i));
+ }
+
+ ulong offset = 0;
+
+ ulong instruction = 0;
+
+ // Dump until a NOP instruction is found.
+ while ((instruction >> 48 & 0xfff8) != 0x50b0)
+ {
+ uint word0 = (uint)_context.MemoryAccessor.ReadInt32(gpuVa + headerSize + offset + 0);
+ uint word1 = (uint)_context.MemoryAccessor.ReadInt32(gpuVa + headerSize + offset + 4);
+
+ instruction = word0 | (ulong)word1 << 32;
+
+ // Zero instructions (other kind of NOP) stop immediately,
+ // this is to avoid two rows of zeroes.
+ if (instruction == 0)
+ {
+ break;
+ }
+
+ fullWriter.Write(instruction);
+ codeWriter.Write(instruction);
+
+ offset += 8;
+ }
+
+ // Align to meet nvdisasm requirements.
+ while (offset % 0x20 != 0)
+ {
+ fullWriter.Write(0);
+ codeWriter.Write(0);
+
+ offset += 4;
+ }
+ }
+ }
+
+ private string FullDir()
+ {
+ return CreateAndReturn(Path.Combine(DumpDir(), "Full"));
+ }
+
+ private string CodeDir()
+ {
+ return CreateAndReturn(Path.Combine(DumpDir(), "Code"));
+ }
+
+ private string DumpDir()
+ {
+ if (string.IsNullOrEmpty(_runtimeDir))
+ {
+ int index = 1;
+
+ do
+ {
+ _runtimeDir = Path.Combine(_dumpPath, "Dumps" + index.ToString("d2"));
+
+ index++;
+ }
+ while (Directory.Exists(_runtimeDir));
+
+ Directory.CreateDirectory(_runtimeDir);
+ }
+
+ return _runtimeDir;
+ }
+
+ private static string CreateAndReturn(string dir)
+ {
+ Directory.CreateDirectory(dir);
+
+ return dir;
+ }
+ }
+} \ No newline at end of file