aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Gpu
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Graphics.Gpu')
-rw-r--r--Ryujinx.Graphics.Gpu/ClassId.cs11
-rw-r--r--Ryujinx.Graphics.Gpu/Constants.cs14
-rw-r--r--Ryujinx.Graphics.Gpu/Debugging.cs25
-rw-r--r--Ryujinx.Graphics.Gpu/DmaPusher.cs181
-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
-rw-r--r--Ryujinx.Graphics.Gpu/GpuContext.cs100
-rw-r--r--Ryujinx.Graphics.Gpu/GraphicsConfig.cs12
-rw-r--r--Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs62
-rw-r--r--Ryujinx.Graphics.Gpu/Image/FormatInfo.cs31
-rw-r--r--Ryujinx.Graphics.Gpu/Image/FormatTable.cs201
-rw-r--r--Ryujinx.Graphics.Gpu/Image/Pool.cs99
-rw-r--r--Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs9
-rw-r--r--Ryujinx.Graphics.Gpu/Image/Sampler.cs52
-rw-r--r--Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs132
-rw-r--r--Ryujinx.Graphics.Gpu/Image/SamplerPool.cs61
-rw-r--r--Ryujinx.Graphics.Gpu/Image/Texture.cs719
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs17
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs95
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureComponent.cs35
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs119
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs11
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureInfo.cs101
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureManager.cs669
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs53
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TexturePool.cs219
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs73
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs13
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureTarget.cs49
-rw-r--r--Ryujinx.Graphics.Gpu/MacroInterpreter.cs415
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/Buffer.cs99
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs8
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/BufferManager.cs530
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/IPhysicalMemory.cs15
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/IRange.cs10
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs12
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs54
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs265
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/RangeList.cs208
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs10
-rw-r--r--Ryujinx.Graphics.Gpu/MethodParams.cs24
-rw-r--r--Ryujinx.Graphics.Gpu/NvGpuFifo.cs150
-rw-r--r--Ryujinx.Graphics.Gpu/NvGpuFifoMeth.cs12
-rw-r--r--Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj14
-rw-r--r--Ryujinx.Graphics.Gpu/State/BlendState.cs15
-rw-r--r--Ryujinx.Graphics.Gpu/State/Bool.cs17
-rw-r--r--Ryujinx.Graphics.Gpu/State/ClearColors.cs10
-rw-r--r--Ryujinx.Graphics.Gpu/State/Condition.cs11
-rw-r--r--Ryujinx.Graphics.Gpu/State/ConditionState.cs8
-rw-r--r--Ryujinx.Graphics.Gpu/State/CopyBufferParams.cs12
-rw-r--r--Ryujinx.Graphics.Gpu/State/CopyBufferSwizzle.cs22
-rw-r--r--Ryujinx.Graphics.Gpu/State/CopyBufferTexture.cs13
-rw-r--r--Ryujinx.Graphics.Gpu/State/CopyRegion.cs14
-rw-r--r--Ryujinx.Graphics.Gpu/State/CopyTexture.cs15
-rw-r--r--Ryujinx.Graphics.Gpu/State/CopyTextureControl.cs12
-rw-r--r--Ryujinx.Graphics.Gpu/State/DepthBiasState.cs9
-rw-r--r--Ryujinx.Graphics.Gpu/State/FaceState.cs11
-rw-r--r--Ryujinx.Graphics.Gpu/State/GpuState.cs425
-rw-r--r--Ryujinx.Graphics.Gpu/State/GpuVa.cs18
-rw-r--r--Ryujinx.Graphics.Gpu/State/IndexBufferState.cs13
-rw-r--r--Ryujinx.Graphics.Gpu/State/Inline2MemoryParams.cs17
-rw-r--r--Ryujinx.Graphics.Gpu/State/MemoryLayout.cs32
-rw-r--r--Ryujinx.Graphics.Gpu/State/MethodOffset.cs62
-rw-r--r--Ryujinx.Graphics.Gpu/State/PoolState.cs8
-rw-r--r--Ryujinx.Graphics.Gpu/State/PrimitiveRestartState.cs8
-rw-r--r--Ryujinx.Graphics.Gpu/State/PrimitiveTopology.cs50
-rw-r--r--Ryujinx.Graphics.Gpu/State/ReportCounterType.cs25
-rw-r--r--Ryujinx.Graphics.Gpu/State/ReportMode.cs8
-rw-r--r--Ryujinx.Graphics.Gpu/State/ReportState.cs9
-rw-r--r--Ryujinx.Graphics.Gpu/State/ResetCounterType.cs21
-rw-r--r--Ryujinx.Graphics.Gpu/State/RtColorMask.cs27
-rw-r--r--Ryujinx.Graphics.Gpu/State/RtColorState.cs13
-rw-r--r--Ryujinx.Graphics.Gpu/State/RtDepthStencilState.cs10
-rw-r--r--Ryujinx.Graphics.Gpu/State/RtFormat.cs137
-rw-r--r--Ryujinx.Graphics.Gpu/State/ShaderState.cs19
-rw-r--r--Ryujinx.Graphics.Gpu/State/ShaderType.cs11
-rw-r--r--Ryujinx.Graphics.Gpu/State/Size3D.cs9
-rw-r--r--Ryujinx.Graphics.Gpu/State/StateWriteFlags.cs34
-rw-r--r--Ryujinx.Graphics.Gpu/State/StencilBackMasks.cs9
-rw-r--r--Ryujinx.Graphics.Gpu/State/StencilBackTestState.cs14
-rw-r--r--Ryujinx.Graphics.Gpu/State/StencilTestState.cs17
-rw-r--r--Ryujinx.Graphics.Gpu/State/UniformBufferState.cs9
-rw-r--r--Ryujinx.Graphics.Gpu/State/VertexAttribState.cs22
-rw-r--r--Ryujinx.Graphics.Gpu/State/VertexBufferDrawState.cs8
-rw-r--r--Ryujinx.Graphics.Gpu/State/VertexBufferState.cs19
-rw-r--r--Ryujinx.Graphics.Gpu/State/ViewportExtents.cs12
-rw-r--r--Ryujinx.Graphics.Gpu/State/ViewportTransform.cs36
102 files changed, 8251 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Gpu/ClassId.cs b/Ryujinx.Graphics.Gpu/ClassId.cs
new file mode 100644
index 00000000..3cde0ac0
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/ClassId.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu
+{
+ enum ClassId
+ {
+ Engine2D = 0x902d,
+ Engine3D = 0xb197,
+ EngineCompute = 0xb1c0,
+ EngineInline2Memory = 0xa140,
+ EngineDma = 0xb0b5
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Constants.cs b/Ryujinx.Graphics.Gpu/Constants.cs
new file mode 100644
index 00000000..501933fb
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Constants.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gpu
+{
+ static class Constants
+ {
+ public const int TotalCpUniformBuffers = 8;
+ public const int TotalCpStorageBuffers = 16;
+ public const int TotalGpUniformBuffers = 18;
+ public const int TotalGpStorageBuffers = 16;
+ public const int TotalRenderTargets = 8;
+ public const int TotalShaderStages = 5;
+ public const int TotalVertexBuffers = 16;
+ public const int TotalViewports = 8;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Debugging.cs b/Ryujinx.Graphics.Gpu/Debugging.cs
new file mode 100644
index 00000000..cb99cee1
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Debugging.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu
+{
+ static class Debugging
+ {
+ public static void PrintTexInfo(string prefix, Image.Texture tex)
+ {
+ if (tex == null)
+ {
+ Console.WriteLine(prefix + " null");
+
+ return;
+ }
+
+ string range = $"{tex.Address:X}..{(tex.Address + tex.Size):X}";
+
+ int debugId = tex.HostTexture.GetStorageDebugId();
+
+ string str = $"{prefix} p {debugId:X8} {tex.Info.Target} {tex.Info.FormatInfo.Format} {tex.Info.Width}x{tex.Info.Height}x{tex.Info.DepthOrLayers} mips {tex.Info.Levels} addr {range}";
+
+ Console.WriteLine(str);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/DmaPusher.cs b/Ryujinx.Graphics.Gpu/DmaPusher.cs
new file mode 100644
index 00000000..ae9cc868
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/DmaPusher.cs
@@ -0,0 +1,181 @@
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace Ryujinx.Graphics.Gpu
+{
+ public class DmaPusher
+ {
+ private ConcurrentQueue<ulong> _ibBuffer;
+
+ private ulong _dmaPut;
+ private ulong _dmaGet;
+
+ private struct DmaState
+ {
+ public int Method;
+ public int SubChannel;
+ public int MethodCount;
+ public bool NonIncrementing;
+ public bool IncrementOnce;
+ public int LengthPending;
+ }
+
+ private DmaState _state;
+
+ private bool _sliEnable;
+ private bool _sliActive;
+
+ private bool _ibEnable;
+ private bool _nonMain;
+
+ private ulong _dmaMGet;
+
+ private GpuContext _context;
+
+ private AutoResetEvent _event;
+
+ internal DmaPusher(GpuContext context)
+ {
+ _context = context;
+
+ _ibBuffer = new ConcurrentQueue<ulong>();
+
+ _ibEnable = true;
+
+ _event = new AutoResetEvent(false);
+ }
+
+ public void Push(ulong entry)
+ {
+ _ibBuffer.Enqueue(entry);
+
+ _event.Set();
+ }
+
+ public bool WaitForCommands()
+ {
+ return _event.WaitOne(8);
+ }
+
+ public void DispatchCalls()
+ {
+ while (Step());
+ }
+
+ private bool Step()
+ {
+ if (_dmaGet != _dmaPut)
+ {
+ int word = _context.MemoryAccessor.ReadInt32(_dmaGet);
+
+ _dmaGet += 4;
+
+ if (!_nonMain)
+ {
+ _dmaMGet = _dmaGet;
+ }
+
+ if (_state.LengthPending != 0)
+ {
+ _state.LengthPending = 0;
+ _state.MethodCount = word & 0xffffff;
+ }
+ else if (_state.MethodCount != 0)
+ {
+ if (!_sliEnable || _sliActive)
+ {
+ CallMethod(word);
+ }
+
+ if (!_state.NonIncrementing)
+ {
+ _state.Method++;
+ }
+
+ if (_state.IncrementOnce)
+ {
+ _state.NonIncrementing = true;
+ }
+
+ _state.MethodCount--;
+ }
+ else
+ {
+ int submissionMode = (word >> 29) & 7;
+
+ switch (submissionMode)
+ {
+ case 1:
+ // Incrementing.
+ SetNonImmediateState(word);
+
+ _state.NonIncrementing = false;
+ _state.IncrementOnce = false;
+
+ break;
+
+ case 3:
+ // Non-incrementing.
+ SetNonImmediateState(word);
+
+ _state.NonIncrementing = true;
+ _state.IncrementOnce = false;
+
+ break;
+
+ case 4:
+ // Immediate.
+ _state.Method = (word >> 0) & 0x1fff;
+ _state.SubChannel = (word >> 13) & 7;
+ _state.NonIncrementing = true;
+ _state.IncrementOnce = false;
+
+ CallMethod((word >> 16) & 0x1fff);
+
+ break;
+
+ case 5:
+ // Increment-once.
+ SetNonImmediateState(word);
+
+ _state.NonIncrementing = false;
+ _state.IncrementOnce = true;
+
+ break;
+ }
+ }
+ }
+ else if (_ibEnable && _ibBuffer.TryDequeue(out ulong entry))
+ {
+ ulong length = (entry >> 42) & 0x1fffff;
+
+ _dmaGet = entry & 0xfffffffffc;
+ _dmaPut = _dmaGet + length * 4;
+
+ _nonMain = (entry & (1UL << 41)) != 0;
+ }
+ else
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void SetNonImmediateState(int word)
+ {
+ _state.Method = (word >> 0) & 0x1fff;
+ _state.SubChannel = (word >> 13) & 7;
+ _state.MethodCount = (word >> 16) & 0x1fff;
+ }
+
+ private void CallMethod(int argument)
+ {
+ _context.Fifo.CallMethod(new MethodParams(
+ _state.Method,
+ argument,
+ _state.SubChannel,
+ _state.MethodCount));
+ }
+ }
+} \ No newline at end of file
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
diff --git a/Ryujinx.Graphics.Gpu/GpuContext.cs b/Ryujinx.Graphics.Gpu/GpuContext.cs
new file mode 100644
index 00000000..1e865e6a
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/GpuContext.cs
@@ -0,0 +1,100 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Gpu.Engine;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Gpu.State;
+using System;
+
+namespace Ryujinx.Graphics.Gpu
+{
+ public class GpuContext
+ {
+ public IRenderer Renderer { get; }
+
+ internal GpuState State { get; }
+
+ internal IPhysicalMemory PhysicalMemory { get; private set; }
+
+ public MemoryManager MemoryManager { get; }
+
+ internal MemoryAccessor MemoryAccessor { get; }
+
+ internal Methods Methods { get; }
+
+ internal NvGpuFifo Fifo { get; }
+
+ public DmaPusher DmaPusher { get; }
+
+ internal int SequenceNumber { get; private set; }
+
+ private Lazy<Capabilities> _caps;
+
+ internal Capabilities Capabilities => _caps.Value;
+
+ public GpuContext(IRenderer renderer)
+ {
+ Renderer = renderer;
+
+ State = new GpuState();
+
+ MemoryManager = new MemoryManager();
+
+ MemoryAccessor = new MemoryAccessor(this);
+
+ Methods = new Methods(this);
+
+ Fifo = new NvGpuFifo(this);
+
+ DmaPusher = new DmaPusher(this);
+
+ _caps = new Lazy<Capabilities>(GetCapabilities);
+ }
+
+ internal void AdvanceSequence()
+ {
+ SequenceNumber++;
+ }
+
+ public ITexture GetTexture(
+ ulong address,
+ int width,
+ int height,
+ int stride,
+ bool isLinear,
+ int gobBlocksInY,
+ Format format,
+ int bytesPerPixel)
+ {
+ FormatInfo formatInfo = new FormatInfo(format, 1, 1, bytesPerPixel);
+
+ TextureInfo info = new TextureInfo(
+ address,
+ width,
+ height,
+ 1,
+ 1,
+ 1,
+ 1,
+ stride,
+ isLinear,
+ gobBlocksInY,
+ 1,
+ 1,
+ Target.Texture2D,
+ formatInfo);
+
+ return Methods.GetTexture(address)?.HostTexture;
+ }
+
+ private Capabilities GetCapabilities()
+ {
+ return Renderer.GetCapabilities();
+ }
+
+ public void SetVmm(IPhysicalMemory mm)
+ {
+ PhysicalMemory = mm;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/GraphicsConfig.cs b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
new file mode 100644
index 00000000..c5eaa0b3
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gpu
+{
+ public static class GraphicsConfig
+ {
+ public static string ShadersDumpPath;
+
+ public static bool FastGpuTime = true;
+
+ public static bool DisableTUpdate;
+ public static bool DisableBUpdate;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
new file mode 100644
index 00000000..33ed7881
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
@@ -0,0 +1,62 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ class AutoDeleteCache : IEnumerable<Texture>
+ {
+ private const int MaxCapacity = 2048;
+
+ private LinkedList<Texture> _textures;
+
+ public AutoDeleteCache()
+ {
+ _textures = new LinkedList<Texture>();
+ }
+
+ public void Add(Texture texture)
+ {
+ texture.IncrementReferenceCount();
+
+ texture.CacheNode = _textures.AddLast(texture);
+
+ if (_textures.Count > MaxCapacity)
+ {
+ Texture oldestTexture = _textures.First.Value;
+
+ _textures.RemoveFirst();
+
+ oldestTexture.DecrementReferenceCount();
+
+ oldestTexture.CacheNode = null;
+ }
+ }
+
+ public void Lift(Texture texture)
+ {
+ if (texture.CacheNode != null)
+ {
+ if (texture.CacheNode != _textures.Last)
+ {
+ _textures.Remove(texture.CacheNode);
+
+ texture.CacheNode = _textures.AddLast(texture);
+ }
+ }
+ else
+ {
+ Add(texture);
+ }
+ }
+
+ public IEnumerator<Texture> GetEnumerator()
+ {
+ return _textures.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _textures.GetEnumerator();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs b/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs
new file mode 100644
index 00000000..a728c66e
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs
@@ -0,0 +1,31 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ struct FormatInfo
+ {
+ private static FormatInfo _rgba8 = new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4);
+
+ public static FormatInfo Default => _rgba8;
+
+ public Format Format { get; }
+
+ public int BlockWidth { get; }
+ public int BlockHeight { get; }
+ public int BytesPerPixel { get; }
+
+ public bool IsCompressed => (BlockWidth | BlockHeight) != 1;
+
+ public FormatInfo(
+ Format format,
+ int blockWidth,
+ int blockHeight,
+ int bytesPerPixel)
+ {
+ Format = format;
+ BlockWidth = blockWidth;
+ BlockHeight = blockHeight;
+ BytesPerPixel = bytesPerPixel;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/FormatTable.cs b/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
new file mode 100644
index 00000000..9f5b26d4
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
@@ -0,0 +1,201 @@
+using Ryujinx.Graphics.GAL;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ static class FormatTable
+ {
+ private static Dictionary<uint, FormatInfo> _textureFormats = new Dictionary<uint, FormatInfo>()
+ {
+ { 0x2491d, new FormatInfo(Format.R8Unorm, 1, 1, 1) },
+ { 0x1249d, new FormatInfo(Format.R8Snorm, 1, 1, 1) },
+ { 0x4921d, new FormatInfo(Format.R8Uint, 1, 1, 1) },
+ { 0x36d9d, new FormatInfo(Format.R8Sint, 1, 1, 1) },
+ { 0x7ff9b, new FormatInfo(Format.R16Float, 1, 1, 2) },
+ { 0x2491b, new FormatInfo(Format.R16Unorm, 1, 1, 2) },
+ { 0x1249b, new FormatInfo(Format.R16Snorm, 1, 1, 2) },
+ { 0x4921b, new FormatInfo(Format.R16Uint, 1, 1, 2) },
+ { 0x36d9b, new FormatInfo(Format.R16Sint, 1, 1, 2) },
+ { 0x7ff8f, new FormatInfo(Format.R32Float, 1, 1, 4) },
+ { 0x4920f, new FormatInfo(Format.R32Uint, 1, 1, 4) },
+ { 0x36d8f, new FormatInfo(Format.R32Sint, 1, 1, 4) },
+ { 0x24918, new FormatInfo(Format.R8G8Unorm, 1, 1, 2) },
+ { 0x12498, new FormatInfo(Format.R8G8Snorm, 1, 1, 2) },
+ { 0x49218, new FormatInfo(Format.R8G8Uint, 1, 1, 2) },
+ { 0x36d98, new FormatInfo(Format.R8G8Sint, 1, 1, 2) },
+ { 0x7ff8c, new FormatInfo(Format.R16G16Float, 1, 1, 4) },
+ { 0x2490c, new FormatInfo(Format.R16G16Unorm, 1, 1, 4) },
+ { 0x1248c, new FormatInfo(Format.R16G16Snorm, 1, 1, 4) },
+ { 0x4920c, new FormatInfo(Format.R16G16Uint, 1, 1, 4) },
+ { 0x36d8c, new FormatInfo(Format.R16G16Sint, 1, 1, 4) },
+ { 0x7ff84, new FormatInfo(Format.R32G32Float, 1, 1, 8) },
+ { 0x49204, new FormatInfo(Format.R32G32Uint, 1, 1, 8) },
+ { 0x36d84, new FormatInfo(Format.R32G32Sint, 1, 1, 8) },
+ { 0x7ff82, new FormatInfo(Format.R32G32B32Float, 1, 1, 12) },
+ { 0x49202, new FormatInfo(Format.R32G32B32Uint, 1, 1, 12) },
+ { 0x36d82, new FormatInfo(Format.R32G32B32Sint, 1, 1, 12) },
+ { 0x24908, new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4) },
+ { 0x12488, new FormatInfo(Format.R8G8B8A8Snorm, 1, 1, 4) },
+ { 0x49208, new FormatInfo(Format.R8G8B8A8Uint, 1, 1, 4) },
+ { 0x36d88, new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4) },
+ { 0x7ff83, new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8) },
+ { 0x24903, new FormatInfo(Format.R16G16B16A16Unorm, 1, 1, 8) },
+ { 0x12483, new FormatInfo(Format.R16G16B16A16Snorm, 1, 1, 8) },
+ { 0x49203, new FormatInfo(Format.R16G16B16A16Uint, 1, 1, 8) },
+ { 0x36d83, new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8) },
+ { 0x7ff81, new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16) },
+ { 0x49201, new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16) },
+ { 0x36d81, new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16) },
+ { 0x2493a, new FormatInfo(Format.D16Unorm, 1, 1, 2) },
+ { 0x7ffaf, new FormatInfo(Format.D32Float, 1, 1, 4) },
+ { 0x24a29, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4) },
+ { 0x253b0, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8) },
+ { 0xa4908, new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4) },
+ { 0x24912, new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2) },
+ { 0x24914, new FormatInfo(Format.R5G5B5A1Unorm, 1, 1, 2) },
+ { 0x24915, new FormatInfo(Format.R5G6B5Unorm, 1, 1, 2) },
+ { 0x24909, new FormatInfo(Format.R10G10B10A2Unorm, 1, 1, 4) },
+ { 0x49209, new FormatInfo(Format.R10G10B10A2Uint, 1, 1, 4) },
+ { 0x7ffa1, new FormatInfo(Format.R11G11B10Float, 1, 1, 4) },
+ { 0x7ffa0, new FormatInfo(Format.R9G9B9E5Float, 1, 1, 4) },
+ { 0x24924, new FormatInfo(Format.Bc1RgbaUnorm, 4, 4, 8) },
+ { 0x24925, new FormatInfo(Format.Bc2Unorm, 4, 4, 16) },
+ { 0x24926, new FormatInfo(Format.Bc3Unorm, 4, 4, 16) },
+ { 0xa4924, new FormatInfo(Format.Bc1RgbaSrgb, 4, 4, 8) },
+ { 0xa4925, new FormatInfo(Format.Bc2Srgb, 4, 4, 16) },
+ { 0xa4926, new FormatInfo(Format.Bc3Srgb, 4, 4, 16) },
+ { 0x24927, new FormatInfo(Format.Bc4Unorm, 4, 4, 8) },
+ { 0x124a7, new FormatInfo(Format.Bc4Snorm, 4, 4, 8) },
+ { 0x24928, new FormatInfo(Format.Bc5Unorm, 4, 4, 16) },
+ { 0x124a8, new FormatInfo(Format.Bc5Snorm, 4, 4, 16) },
+ { 0x24917, new FormatInfo(Format.Bc7Unorm, 4, 4, 16) },
+ { 0xa4917, new FormatInfo(Format.Bc7Srgb, 4, 4, 16) },
+ { 0x7ff90, new FormatInfo(Format.Bc6HUfloat, 4, 4, 16) },
+ { 0x7ff91, new FormatInfo(Format.Bc6HSfloat, 4, 4, 16) },
+ { 0x24940, new FormatInfo(Format.Astc4x4Unorm, 4, 4, 16) },
+ { 0x24950, new FormatInfo(Format.Astc5x4Unorm, 5, 4, 16) },
+ { 0x24941, new FormatInfo(Format.Astc5x5Unorm, 5, 5, 16) },
+ { 0x24951, new FormatInfo(Format.Astc6x5Unorm, 6, 5, 16) },
+ { 0x24942, new FormatInfo(Format.Astc6x6Unorm, 6, 6, 16) },
+ { 0x24955, new FormatInfo(Format.Astc8x5Unorm, 8, 5, 16) },
+ { 0x24952, new FormatInfo(Format.Astc8x6Unorm, 8, 6, 16) },
+ { 0x24944, new FormatInfo(Format.Astc8x8Unorm, 8, 8, 16) },
+ { 0x24956, new FormatInfo(Format.Astc10x5Unorm, 10, 5, 16) },
+ { 0x24957, new FormatInfo(Format.Astc10x6Unorm, 10, 6, 16) },
+ { 0x24953, new FormatInfo(Format.Astc10x8Unorm, 10, 8, 16) },
+ { 0x24945, new FormatInfo(Format.Astc10x10Unorm, 10, 10, 16) },
+ { 0x24954, new FormatInfo(Format.Astc12x10Unorm, 12, 10, 16) },
+ { 0x24946, new FormatInfo(Format.Astc12x12Unorm, 12, 12, 16) },
+ { 0xa4940, new FormatInfo(Format.Astc4x4Srgb, 4, 4, 16) },
+ { 0xa4950, new FormatInfo(Format.Astc5x4Srgb, 5, 4, 16) },
+ { 0xa4941, new FormatInfo(Format.Astc5x5Srgb, 5, 5, 16) },
+ { 0xa4951, new FormatInfo(Format.Astc6x5Srgb, 6, 5, 16) },
+ { 0xa4942, new FormatInfo(Format.Astc6x6Srgb, 6, 6, 16) },
+ { 0xa4955, new FormatInfo(Format.Astc8x5Srgb, 8, 5, 16) },
+ { 0xa4952, new FormatInfo(Format.Astc8x6Srgb, 8, 6, 16) },
+ { 0xa4944, new FormatInfo(Format.Astc8x8Srgb, 8, 8, 16) },
+ { 0xa4956, new FormatInfo(Format.Astc10x5Srgb, 10, 5, 16) },
+ { 0xa4957, new FormatInfo(Format.Astc10x6Srgb, 10, 6, 16) },
+ { 0xa4953, new FormatInfo(Format.Astc10x8Srgb, 10, 8, 16) },
+ { 0xa4945, new FormatInfo(Format.Astc10x10Srgb, 10, 10, 16) },
+ { 0xa4954, new FormatInfo(Format.Astc12x10Srgb, 12, 10, 16) },
+ { 0xa4946, new FormatInfo(Format.Astc12x12Srgb, 12, 12, 16) },
+ { 0x24913, new FormatInfo(Format.A1B5G5R5Unorm, 1, 1, 2) }
+ };
+
+ private static Dictionary<ulong, Format> _attribFormats = new Dictionary<ulong, Format>()
+ {
+ { 0x13a00000, Format.R8Unorm },
+ { 0x0ba00000, Format.R8Snorm },
+ { 0x23a00000, Format.R8Uint },
+ { 0x1ba00000, Format.R8Sint },
+ { 0x3b600000, Format.R16Float },
+ { 0x13600000, Format.R16Unorm },
+ { 0x0b600000, Format.R16Snorm },
+ { 0x23600000, Format.R16Uint },
+ { 0x1b600000, Format.R16Sint },
+ { 0x3a400000, Format.R32Float },
+ { 0x22400000, Format.R32Uint },
+ { 0x1a400000, Format.R32Sint },
+ { 0x13000000, Format.R8G8Unorm },
+ { 0x0b000000, Format.R8G8Snorm },
+ { 0x23000000, Format.R8G8Uint },
+ { 0x1b000000, Format.R8G8Sint },
+ { 0x39e00000, Format.R16G16Float },
+ { 0x11e00000, Format.R16G16Unorm },
+ { 0x09e00000, Format.R16G16Snorm },
+ { 0x21e00000, Format.R16G16Uint },
+ { 0x19e00000, Format.R16G16Sint },
+ { 0x38800000, Format.R32G32Float },
+ { 0x20800000, Format.R32G32Uint },
+ { 0x18800000, Format.R32G32Sint },
+ { 0x12600000, Format.R8G8B8Unorm },
+ { 0x0a600000, Format.R8G8B8Snorm },
+ { 0x22600000, Format.R8G8B8Uint },
+ { 0x1a600000, Format.R8G8B8Sint },
+ { 0x38a00000, Format.R16G16B16Float },
+ { 0x10a00000, Format.R16G16B16Unorm },
+ { 0x08a00000, Format.R16G16B16Snorm },
+ { 0x20a00000, Format.R16G16B16Uint },
+ { 0x18a00000, Format.R16G16B16Sint },
+ { 0x38400000, Format.R32G32B32Float },
+ { 0x20400000, Format.R32G32B32Uint },
+ { 0x18400000, Format.R32G32B32Sint },
+ { 0x11400000, Format.R8G8B8A8Unorm },
+ { 0x09400000, Format.R8G8B8A8Snorm },
+ { 0x21400000, Format.R8G8B8A8Uint },
+ { 0x19400000, Format.R8G8B8A8Sint },
+ { 0x38600000, Format.R16G16B16A16Float },
+ { 0x10600000, Format.R16G16B16A16Unorm },
+ { 0x08600000, Format.R16G16B16A16Snorm },
+ { 0x20600000, Format.R16G16B16A16Uint },
+ { 0x18600000, Format.R16G16B16A16Sint },
+ { 0x38200000, Format.R32G32B32A32Float },
+ { 0x20200000, Format.R32G32B32A32Uint },
+ { 0x18200000, Format.R32G32B32A32Sint },
+ { 0x16000000, Format.R10G10B10A2Unorm },
+ { 0x26000000, Format.R10G10B10A2Uint },
+ { 0x3e200000, Format.R11G11B10Float },
+ { 0x2ba00000, Format.R8Uscaled },
+ { 0x33a00000, Format.R8Sscaled },
+ { 0x2b600000, Format.R16Uscaled },
+ { 0x33600000, Format.R16Sscaled },
+ { 0x2a400000, Format.R32Uscaled },
+ { 0x32400000, Format.R32Sscaled },
+ { 0x2b000000, Format.R8G8Uscaled },
+ { 0x33000000, Format.R8G8Sscaled },
+ { 0x29e00000, Format.R16G16Uscaled },
+ { 0x31e00000, Format.R16G16Sscaled },
+ { 0x28800000, Format.R32G32Uscaled },
+ { 0x30800000, Format.R32G32Sscaled },
+ { 0x2a600000, Format.R8G8B8Uscaled },
+ { 0x32600000, Format.R8G8B8Sscaled },
+ { 0x28a00000, Format.R16G16B16Uscaled },
+ { 0x30a00000, Format.R16G16B16Sscaled },
+ { 0x28400000, Format.R32G32B32Uscaled },
+ { 0x30400000, Format.R32G32B32Sscaled },
+ { 0x29400000, Format.R8G8B8A8Uscaled },
+ { 0x31400000, Format.R8G8B8A8Sscaled },
+ { 0x28600000, Format.R16G16B16A16Uscaled },
+ { 0x30600000, Format.R16G16B16A16Sscaled },
+ { 0x28200000, Format.R32G32B32A32Uscaled },
+ { 0x30200000, Format.R32G32B32A32Sscaled },
+ { 0x0e000000, Format.R10G10B10A2Snorm },
+ { 0x1e000000, Format.R10G10B10A2Sint },
+ { 0x2e000000, Format.R10G10B10A2Uscaled },
+ { 0x36000000, Format.R10G10B10A2Sscaled }
+ };
+
+ public static bool TryGetTextureFormat(uint encoded, bool isSrgb, out FormatInfo format)
+ {
+ encoded |= (isSrgb ? 1u << 19 : 0u);
+
+ return _textureFormats.TryGetValue(encoded, out format);
+ }
+
+ public static bool TryGetAttribFormat(uint encoded, out Format format)
+ {
+ return _attribFormats.TryGetValue(encoded, out format);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/Pool.cs b/Ryujinx.Graphics.Gpu/Image/Pool.cs
new file mode 100644
index 00000000..196fc137
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/Pool.cs
@@ -0,0 +1,99 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ abstract class Pool<T> : IDisposable
+ {
+ protected const int DescriptorSize = 0x20;
+
+ protected GpuContext Context;
+
+ protected T[] Items;
+
+ public ulong Address { get; }
+ public ulong Size { get; }
+
+ public Pool(GpuContext context, ulong address, int maximumId)
+ {
+ Context = context;
+
+ int count = maximumId + 1;
+
+ ulong size = (ulong)(uint)count * DescriptorSize;;
+
+ Items = new T[count];
+
+ Address = address;
+ Size = size;
+ }
+
+ public abstract T Get(int id);
+
+ public void SynchronizeMemory()
+ {
+ (ulong, ulong)[] modifiedRanges = Context.PhysicalMemory.GetModifiedRanges(Address, Size);
+
+ for (int index = 0; index < modifiedRanges.Length; index++)
+ {
+ (ulong mAddress, ulong mSize) = modifiedRanges[index];
+
+ if (mAddress < Address)
+ {
+ mAddress = Address;
+ }
+
+ ulong maxSize = Address + Size - mAddress;
+
+ if (mSize > maxSize)
+ {
+ mSize = maxSize;
+ }
+
+ InvalidateRangeImpl(mAddress, mSize);
+ }
+ }
+
+ public void InvalidateRange(ulong address, ulong size)
+ {
+ ulong endAddress = address + size;
+
+ ulong texturePoolEndAddress = Address + Size;
+
+ // If the range being invalidated is not overlapping the texture pool range,
+ // then we don't have anything to do, exit early.
+ if (address >= texturePoolEndAddress || endAddress <= Address)
+ {
+ return;
+ }
+
+ if (address < Address)
+ {
+ address = Address;
+ }
+
+ if (endAddress > texturePoolEndAddress)
+ {
+ endAddress = texturePoolEndAddress;
+ }
+
+ InvalidateRangeImpl(address, size);
+ }
+
+ protected abstract void InvalidateRangeImpl(ulong address, ulong size);
+
+ protected abstract void Delete(T item);
+
+ public void Dispose()
+ {
+ if (Items != null)
+ {
+ for (int index = 0; index < Items.Length; index++)
+ {
+ Delete(Items[index]);
+ }
+
+ Items = null;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs b/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs
new file mode 100644
index 00000000..f14a17f2
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ enum ReductionFilter
+ {
+ Average,
+ Minimum,
+ Maximum
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/Sampler.cs b/Ryujinx.Graphics.Gpu/Image/Sampler.cs
new file mode 100644
index 00000000..06fedd8a
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/Sampler.cs
@@ -0,0 +1,52 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Color;
+using Ryujinx.Graphics.GAL.Sampler;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ class Sampler : IDisposable
+ {
+ public ISampler HostSampler { get; }
+
+ public Sampler(GpuContext context, SamplerDescriptor descriptor)
+ {
+ MinFilter minFilter = descriptor.UnpackMinFilter();
+ MagFilter magFilter = descriptor.UnpackMagFilter();
+
+ AddressMode addressU = descriptor.UnpackAddressU();
+ AddressMode addressV = descriptor.UnpackAddressV();
+ AddressMode addressP = descriptor.UnpackAddressP();
+
+ CompareMode compareMode = descriptor.UnpackCompareMode();
+ CompareOp compareOp = descriptor.UnpackCompareOp();
+
+ ColorF color = new ColorF(0, 0, 0, 0);
+
+ float minLod = descriptor.UnpackMinLod();
+ float maxLod = descriptor.UnpackMaxLod();
+ float mipLodBias = descriptor.UnpackMipLodBias();
+
+ float maxAnisotropy = descriptor.UnpackMaxAnisotropy();
+
+ HostSampler = context.Renderer.CreateSampler(new SamplerCreateInfo(
+ minFilter,
+ magFilter,
+ addressU,
+ addressV,
+ addressP,
+ compareMode,
+ compareOp,
+ color,
+ minLod,
+ maxLod,
+ mipLodBias,
+ maxAnisotropy));
+ }
+
+ public void Dispose()
+ {
+ HostSampler.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs
new file mode 100644
index 00000000..0a43dd1b
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs
@@ -0,0 +1,132 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Sampler;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ struct SamplerDescriptor
+ {
+ private static readonly float[] _f5ToF32ConversionLut = new float[]
+ {
+ 0.0f,
+ 0.055555556f,
+ 0.1f,
+ 0.13636364f,
+ 0.16666667f,
+ 0.1923077f,
+ 0.21428572f,
+ 0.23333333f,
+ 0.25f,
+ 0.2777778f,
+ 0.3f,
+ 0.3181818f,
+ 0.33333334f,
+ 0.34615386f,
+ 0.35714287f,
+ 0.36666667f,
+ 0.375f,
+ 0.3888889f,
+ 0.4f,
+ 0.4090909f,
+ 0.41666666f,
+ 0.42307693f,
+ 0.42857143f,
+ 0.43333334f,
+ 0.4375f,
+ 0.44444445f,
+ 0.45f,
+ 0.45454547f,
+ 0.45833334f,
+ 0.46153846f,
+ 0.4642857f,
+ 0.46666667f
+ };
+
+ private static readonly float[] _maxAnisotropyLut = new float[]
+ {
+ 1, 2, 4, 6, 8, 10, 12, 16
+ };
+
+ private const float Frac8ToF32 = 1.0f / 256.0f;
+
+ public uint Word0;
+ public uint Word1;
+ public uint Word2;
+ public uint Word3;
+ public uint BorderColorR;
+ public uint BorderColorG;
+ public uint BorderColorB;
+ public uint BorderColorA;
+
+ public AddressMode UnpackAddressU()
+ {
+ return (AddressMode)(Word0 & 7);
+ }
+
+ public AddressMode UnpackAddressV()
+ {
+ return (AddressMode)((Word0 >> 3) & 7);
+ }
+
+ public AddressMode UnpackAddressP()
+ {
+ return (AddressMode)((Word0 >> 6) & 7);
+ }
+
+ public CompareMode UnpackCompareMode()
+ {
+ return (CompareMode)((Word0 >> 9) & 1);
+ }
+
+ public CompareOp UnpackCompareOp()
+ {
+ return (CompareOp)(((Word0 >> 10) & 7) + 1);
+ }
+
+ public float UnpackMaxAnisotropy()
+ {
+ return _maxAnisotropyLut[(Word0 >> 20) & 7];
+ }
+
+ public MagFilter UnpackMagFilter()
+ {
+ return (MagFilter)(Word1 & 3);
+ }
+
+ public MinFilter UnpackMinFilter()
+ {
+ int minFilter = (int)(Word1 >> 4) & 3;
+ int mipFilter = (int)(Word1 >> 6) & 3;
+
+ return (MinFilter)(minFilter + (mipFilter - 1) * 2);
+ }
+
+ public ReductionFilter UnpackReductionFilter()
+ {
+ return (ReductionFilter)((Word1 >> 10) & 3);
+ }
+
+ public float UnpackMipLodBias()
+ {
+ int fixedValue = (int)(Word1 >> 12) & 0x1fff;
+
+ fixedValue = (fixedValue << 19) >> 19;
+
+ return fixedValue * Frac8ToF32;
+ }
+
+ public float UnpackLodSnap()
+ {
+ return _f5ToF32ConversionLut[(Word1 >> 26) & 0x1f];
+ }
+
+ public float UnpackMinLod()
+ {
+ return (Word2 & 0xfff) * Frac8ToF32;
+ }
+
+ public float UnpackMaxLod()
+ {
+ return ((Word2 >> 12) & 0xfff) * Frac8ToF32;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
new file mode 100644
index 00000000..970a0983
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ class SamplerPool : Pool<Sampler>
+ {
+ public SamplerPool(GpuContext context, ulong address, int maximumId) : base(context, address, maximumId) { }
+
+ public override Sampler Get(int id)
+ {
+ if ((uint)id >= Items.Length)
+ {
+ return null;
+ }
+
+ SynchronizeMemory();
+
+ Sampler sampler = Items[id];
+
+ if (sampler == null)
+ {
+ ulong address = Address + (ulong)(uint)id * DescriptorSize;
+
+ Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);
+
+ SamplerDescriptor descriptor = MemoryMarshal.Cast<byte, SamplerDescriptor>(data)[0];
+
+ sampler = new Sampler(Context, descriptor);
+
+ Items[id] = sampler;
+ }
+
+ return sampler;
+ }
+
+ protected override void InvalidateRangeImpl(ulong address, ulong size)
+ {
+ ulong endAddress = address + size;
+
+ for (; address < endAddress; address += DescriptorSize)
+ {
+ int id = (int)((address - Address) / DescriptorSize);
+
+ Sampler sampler = Items[id];
+
+ if (sampler != null)
+ {
+ sampler.Dispose();
+
+ Items[id] = null;
+ }
+ }
+ }
+
+ protected override void Delete(Sampler item)
+ {
+ item?.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
new file mode 100644
index 00000000..32db8688
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -0,0 +1,719 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Texture;
+using Ryujinx.Graphics.Texture.Astc;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ class Texture : IRange<Texture>
+ {
+ private GpuContext _context;
+
+ private TextureInfo _info;
+
+ private SizeInfo _sizeInfo;
+
+ public Format Format => _info.FormatInfo.Format;
+
+ public TextureInfo Info => _info;
+
+ private int _depth;
+ private int _layers;
+ private int _firstLayer;
+ private int _firstLevel;
+
+ private bool _hasData;
+
+ private ITexture _arrayViewTexture;
+ private Target _arrayViewTarget;
+
+ private Texture _viewStorage;
+
+ private List<Texture> _views;
+
+ public ITexture HostTexture { get; private set; }
+
+ public LinkedListNode<Texture> CacheNode { get; set; }
+
+ public bool Modified { get; set; }
+
+ public ulong Address => _info.Address;
+ public ulong EndAddress => _info.Address + Size;
+
+ public ulong Size => (ulong)_sizeInfo.TotalSize;
+
+ private int _referenceCount;
+
+ private int _sequenceNumber;
+
+ private Texture(
+ GpuContext context,
+ TextureInfo info,
+ SizeInfo sizeInfo,
+ int firstLayer,
+ int firstLevel)
+ {
+ InitializeTexture(context, info, sizeInfo);
+
+ _firstLayer = firstLayer;
+ _firstLevel = firstLevel;
+
+ _hasData = true;
+ }
+
+ public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo)
+ {
+ InitializeTexture(context, info, sizeInfo);
+
+ TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, context.Capabilities);
+
+ HostTexture = _context.Renderer.CreateTexture(createInfo);
+ }
+
+ private void InitializeTexture(GpuContext context, TextureInfo info, SizeInfo sizeInfo)
+ {
+ _context = context;
+ _sizeInfo = sizeInfo;
+
+ SetInfo(info);
+
+ _viewStorage = this;
+
+ _views = new List<Texture>();
+ }
+
+ public Texture CreateView(TextureInfo info, SizeInfo sizeInfo, int firstLayer, int firstLevel)
+ {
+ Texture texture = new Texture(
+ _context,
+ info,
+ sizeInfo,
+ _firstLayer + firstLayer,
+ _firstLevel + firstLevel);
+
+ TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, _context.Capabilities);
+
+ texture.HostTexture = HostTexture.CreateView(createInfo, firstLayer, firstLevel);
+
+ _viewStorage.AddView(texture);
+
+ return texture;
+ }
+
+ private void AddView(Texture texture)
+ {
+ _views.Add(texture);
+
+ texture._viewStorage = this;
+ }
+
+ private void RemoveView(Texture texture)
+ {
+ _views.Remove(texture);
+
+ texture._viewStorage = null;
+ }
+
+ public void ChangeSize(int width, int height, int depthOrLayers)
+ {
+ width <<= _firstLevel;
+ height <<= _firstLevel;
+
+ if (_info.Target == Target.Texture3D)
+ {
+ depthOrLayers <<= _firstLevel;
+ }
+ else
+ {
+ depthOrLayers = _viewStorage._info.DepthOrLayers;
+ }
+
+ _viewStorage.RecreateStorageOrView(width, height, depthOrLayers);
+
+ foreach (Texture view in _viewStorage._views)
+ {
+ int viewWidth = Math.Max(1, width >> view._firstLevel);
+ int viewHeight = Math.Max(1, height >> view._firstLevel);
+
+ int viewDepthOrLayers;
+
+ if (view._info.Target == Target.Texture3D)
+ {
+ viewDepthOrLayers = Math.Max(1, depthOrLayers >> view._firstLevel);
+ }
+ else
+ {
+ viewDepthOrLayers = view._info.DepthOrLayers;
+ }
+
+ view.RecreateStorageOrView(viewWidth, viewHeight, viewDepthOrLayers);
+ }
+ }
+
+ private void RecreateStorageOrView(int width, int height, int depthOrLayers)
+ {
+ SetInfo(new TextureInfo(
+ _info.Address,
+ width,
+ height,
+ depthOrLayers,
+ _info.Levels,
+ _info.SamplesInX,
+ _info.SamplesInY,
+ _info.Stride,
+ _info.IsLinear,
+ _info.GobBlocksInY,
+ _info.GobBlocksInZ,
+ _info.GobBlocksInTileX,
+ _info.Target,
+ _info.FormatInfo,
+ _info.DepthStencilMode,
+ _info.SwizzleR,
+ _info.SwizzleG,
+ _info.SwizzleB,
+ _info.SwizzleA));
+
+ TextureCreateInfo createInfo = TextureManager.GetCreateInfo(_info, _context.Capabilities);
+
+ if (_viewStorage != this)
+ {
+ ReplaceStorage(_viewStorage.HostTexture.CreateView(createInfo, _firstLayer, _firstLevel));
+ }
+ else
+ {
+ ITexture newStorage = _context.Renderer.CreateTexture(createInfo);
+
+ HostTexture.CopyTo(newStorage);
+
+ ReplaceStorage(newStorage);
+ }
+ }
+
+ public void SynchronizeMemory()
+ {
+ if (_sequenceNumber == _context.SequenceNumber && _hasData)
+ {
+ return;
+ }
+
+ _sequenceNumber = _context.SequenceNumber;
+
+ bool modified = _context.PhysicalMemory.GetModifiedRanges(Address, Size).Length != 0;
+
+ if (!modified && _hasData)
+ {
+ return;
+ }
+
+ ulong pageSize = (uint)_context.PhysicalMemory.GetPageSize();
+
+ ulong pageMask = pageSize - 1;
+
+ ulong rangeAddress = Address & ~pageMask;
+
+ ulong rangeSize = (EndAddress - Address + pageMask) & ~pageMask;
+
+ _context.Methods.InvalidateRange(rangeAddress, rangeSize);
+
+ Span<byte> data = _context.PhysicalMemory.Read(Address, Size);
+
+ if (_info.IsLinear)
+ {
+ data = LayoutConverter.ConvertLinearStridedToLinear(
+ _info.Width,
+ _info.Height,
+ _info.FormatInfo.BlockWidth,
+ _info.FormatInfo.BlockHeight,
+ _info.Stride,
+ _info.FormatInfo.BytesPerPixel,
+ data);
+ }
+ else
+ {
+ data = LayoutConverter.ConvertBlockLinearToLinear(
+ _info.Width,
+ _info.Height,
+ _depth,
+ _info.Levels,
+ _layers,
+ _info.FormatInfo.BlockWidth,
+ _info.FormatInfo.BlockHeight,
+ _info.FormatInfo.BytesPerPixel,
+ _info.GobBlocksInY,
+ _info.GobBlocksInZ,
+ _info.GobBlocksInTileX,
+ _sizeInfo,
+ data);
+ }
+
+ if (!_context.Capabilities.SupportsAstcCompression && _info.FormatInfo.Format.IsAstc())
+ {
+ int blockWidth = _info.FormatInfo.BlockWidth;
+ int blockHeight = _info.FormatInfo.BlockHeight;
+
+ data = AstcDecoder.DecodeToRgba8(
+ data,
+ blockWidth,
+ blockHeight,
+ 1,
+ _info.Width,
+ _info.Height,
+ _depth);
+ }
+
+ HostTexture.SetData(data);
+
+ _hasData = true;
+ }
+
+ public void Flush()
+ {
+ byte[] data = HostTexture.GetData(0);
+
+ _context.PhysicalMemory.Write(Address, data);
+ }
+
+ public bool IsPerfectMatch(TextureInfo info, TextureSearchFlags flags)
+ {
+ if (!FormatMatches(info, (flags & TextureSearchFlags.Strict) != 0))
+ {
+ return false;
+ }
+
+ if (!LayoutMatches(info))
+ {
+ return false;
+ }
+
+ if (!SizeMatches(info, (flags & TextureSearchFlags.Strict) == 0))
+ {
+ return false;
+ }
+
+ if ((flags & TextureSearchFlags.Sampler) != 0)
+ {
+ if (!SamplerParamsMatches(info))
+ {
+ return false;
+ }
+ }
+
+ if ((flags & TextureSearchFlags.IgnoreMs) != 0)
+ {
+ bool msTargetCompatible = _info.Target == Target.Texture2DMultisample &&
+ info.Target == Target.Texture2D;
+
+ if (!msTargetCompatible && !TargetAndSamplesCompatible(info))
+ {
+ return false;
+ }
+ }
+ else if (!TargetAndSamplesCompatible(info))
+ {
+ return false;
+ }
+
+ return _info.Address == info.Address && _info.Levels == info.Levels;
+ }
+
+ private bool FormatMatches(TextureInfo info, bool strict)
+ {
+ // D32F and R32F texture have the same representation internally,
+ // however the R32F format is used to sample from depth textures.
+ if (_info.FormatInfo.Format == Format.D32Float &&
+ info.FormatInfo.Format == Format.R32Float && !strict)
+ {
+ return true;
+ }
+
+ if (_info.FormatInfo.Format == Format.R8G8B8A8Srgb &&
+ info.FormatInfo.Format == Format.R8G8B8A8Unorm && !strict)
+ {
+ return true;
+ }
+
+ if (_info.FormatInfo.Format == Format.R8G8B8A8Unorm &&
+ info.FormatInfo.Format == Format.R8G8B8A8Srgb && !strict)
+ {
+ return true;
+ }
+
+ return _info.FormatInfo.Format == info.FormatInfo.Format;
+ }
+
+ private bool LayoutMatches(TextureInfo info)
+ {
+ if (_info.IsLinear != info.IsLinear)
+ {
+ return false;
+ }
+
+ // For linear textures, gob block sizes are ignored.
+ // For block linear textures, the stride is ignored.
+ if (info.IsLinear)
+ {
+ return _info.Stride == info.Stride;
+ }
+ else
+ {
+ return _info.GobBlocksInY == info.GobBlocksInY &&
+ _info.GobBlocksInZ == info.GobBlocksInZ;
+ }
+ }
+
+ public bool SizeMatches(TextureInfo info)
+ {
+ return SizeMatches(info, alignSizes: false);
+ }
+
+ public bool SizeMatches(TextureInfo info, int level)
+ {
+ return Math.Max(1, _info.Width >> level) == info.Width &&
+ Math.Max(1, _info.Height >> level) == info.Height &&
+ Math.Max(1, _info.GetDepth() >> level) == info.GetDepth();
+ }
+
+ private bool SizeMatches(TextureInfo info, bool alignSizes)
+ {
+ if (_info.GetLayers() != info.GetLayers())
+ {
+ return false;
+ }
+
+ if (alignSizes)
+ {
+ Size size0 = GetAlignedSize(_info);
+ Size size1 = GetAlignedSize(info);
+
+ return size0.Width == size1.Width &&
+ size0.Height == size1.Height &&
+ size0.Depth == size1.Depth;
+ }
+ else
+ {
+ return _info.Width == info.Width &&
+ _info.Height == info.Height &&
+ _info.GetDepth() == info.GetDepth();
+ }
+ }
+
+ private bool SamplerParamsMatches(TextureInfo info)
+ {
+ return _info.DepthStencilMode == info.DepthStencilMode &&
+ _info.SwizzleR == info.SwizzleR &&
+ _info.SwizzleG == info.SwizzleG &&
+ _info.SwizzleB == info.SwizzleB &&
+ _info.SwizzleA == info.SwizzleA;
+ }
+
+ private bool TargetAndSamplesCompatible(TextureInfo info)
+ {
+ return _info.Target == info.Target &&
+ _info.SamplesInX == info.SamplesInX &&
+ _info.SamplesInY == info.SamplesInY;
+ }
+
+ public bool IsViewCompatible(TextureInfo info, ulong size, out int firstLayer, out int firstLevel)
+ {
+ // Out of range.
+ if (info.Address < Address || info.Address + size > EndAddress)
+ {
+ firstLayer = 0;
+ firstLevel = 0;
+
+ return false;
+ }
+
+ int offset = (int)(info.Address - Address);
+
+ if (!_sizeInfo.FindView(offset, (int)size, out firstLayer, out firstLevel))
+ {
+ return false;
+ }
+
+ if (!ViewLayoutCompatible(info, firstLevel))
+ {
+ return false;
+ }
+
+ if (!ViewFormatCompatible(info))
+ {
+ return false;
+ }
+
+ if (!ViewSizeMatches(info, firstLevel))
+ {
+ return false;
+ }
+
+ if (!ViewTargetCompatible(info))
+ {
+ return false;
+ }
+
+ return _info.SamplesInX == info.SamplesInX &&
+ _info.SamplesInY == info.SamplesInY;
+ }
+
+ private bool ViewLayoutCompatible(TextureInfo info, int level)
+ {
+ if (_info.IsLinear != info.IsLinear)
+ {
+ return false;
+ }
+
+ // For linear textures, gob block sizes are ignored.
+ // For block linear textures, the stride is ignored.
+ if (info.IsLinear)
+ {
+ int width = Math.Max(1, _info.Width >> level);
+
+ int stride = width * _info.FormatInfo.BytesPerPixel;
+
+ stride = BitUtils.AlignUp(stride, 32);
+
+ return stride == info.Stride;
+ }
+ else
+ {
+ int height = Math.Max(1, _info.Height >> level);
+ int depth = Math.Max(1, _info.GetDepth() >> level);
+
+ (int gobBlocksInY, int gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
+ height,
+ depth,
+ _info.FormatInfo.BlockHeight,
+ _info.GobBlocksInY,
+ _info.GobBlocksInZ);
+
+ return gobBlocksInY == info.GobBlocksInY &&
+ gobBlocksInZ == info.GobBlocksInZ;
+ }
+ }
+
+ private bool ViewFormatCompatible(TextureInfo info)
+ {
+ return TextureCompatibility.FormatCompatible(_info.FormatInfo, info.FormatInfo);
+ }
+
+ private bool ViewSizeMatches(TextureInfo info, int level)
+ {
+ Size size = GetAlignedSize(_info, level);
+
+ Size otherSize = GetAlignedSize(info);
+
+ return size.Width == otherSize.Width &&
+ size.Height == otherSize.Height &&
+ size.Depth == otherSize.Depth;
+ }
+
+ private bool ViewTargetCompatible(TextureInfo info)
+ {
+ switch (_info.Target)
+ {
+ case Target.Texture1D:
+ case Target.Texture1DArray:
+ return info.Target == Target.Texture1D ||
+ info.Target == Target.Texture1DArray;
+
+ case Target.Texture2D:
+ return info.Target == Target.Texture2D ||
+ info.Target == Target.Texture2DArray;
+
+ case Target.Texture2DArray:
+ case Target.Cubemap:
+ case Target.CubemapArray:
+ return info.Target == Target.Texture2D ||
+ info.Target == Target.Texture2DArray ||
+ info.Target == Target.Cubemap ||
+ info.Target == Target.CubemapArray;
+
+ case Target.Texture2DMultisample:
+ case Target.Texture2DMultisampleArray:
+ return info.Target == Target.Texture2DMultisample ||
+ info.Target == Target.Texture2DMultisampleArray;
+
+ case Target.Texture3D:
+ return info.Target == Target.Texture3D;
+ }
+
+ return false;
+ }
+
+ private static Size GetAlignedSize(TextureInfo info, int level = 0)
+ {
+ int width = Math.Max(1, info.Width >> level);
+ int height = Math.Max(1, info.Height >> level);
+
+ if (info.IsLinear)
+ {
+ return SizeCalculator.GetLinearAlignedSize(
+ width,
+ height,
+ info.FormatInfo.BlockWidth,
+ info.FormatInfo.BlockHeight,
+ info.FormatInfo.BytesPerPixel);
+ }
+ else
+ {
+ int depth = Math.Max(1, info.GetDepth() >> level);
+
+ (int gobBlocksInY, int gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
+ height,
+ depth,
+ info.FormatInfo.BlockHeight,
+ info.GobBlocksInY,
+ info.GobBlocksInZ);
+
+ return SizeCalculator.GetBlockLinearAlignedSize(
+ width,
+ height,
+ depth,
+ info.FormatInfo.BlockWidth,
+ info.FormatInfo.BlockHeight,
+ info.FormatInfo.BytesPerPixel,
+ gobBlocksInY,
+ gobBlocksInZ,
+ info.GobBlocksInTileX);
+ }
+ }
+
+ public ITexture GetTargetTexture(Target target)
+ {
+ if (target == _info.Target)
+ {
+ return HostTexture;
+ }
+
+ if (_arrayViewTexture == null && IsSameDimensionsTarget(target))
+ {
+ TextureCreateInfo createInfo = new TextureCreateInfo(
+ _info.Width,
+ _info.Height,
+ target == Target.CubemapArray ? 6 : 1,
+ _info.Levels,
+ _info.Samples,
+ _info.FormatInfo.BlockWidth,
+ _info.FormatInfo.BlockHeight,
+ _info.FormatInfo.BytesPerPixel,
+ _info.FormatInfo.Format,
+ _info.DepthStencilMode,
+ target,
+ _info.SwizzleR,
+ _info.SwizzleG,
+ _info.SwizzleB,
+ _info.SwizzleA);
+
+ ITexture viewTexture = HostTexture.CreateView(createInfo, 0, 0);
+
+ _arrayViewTexture = viewTexture;
+ _arrayViewTarget = target;
+
+ return viewTexture;
+ }
+ else if (_arrayViewTarget == target)
+ {
+ return _arrayViewTexture;
+ }
+
+ return null;
+ }
+
+ private bool IsSameDimensionsTarget(Target target)
+ {
+ switch (_info.Target)
+ {
+ case Target.Texture1D:
+ case Target.Texture1DArray:
+ return target == Target.Texture1D ||
+ target == Target.Texture1DArray;
+
+ case Target.Texture2D:
+ case Target.Texture2DArray:
+ return target == Target.Texture2D ||
+ target == Target.Texture2DArray;
+
+ case Target.Cubemap:
+ case Target.CubemapArray:
+ return target == Target.Cubemap ||
+ target == Target.CubemapArray;
+
+ case Target.Texture2DMultisample:
+ case Target.Texture2DMultisampleArray:
+ return target == Target.Texture2DMultisample ||
+ target == Target.Texture2DMultisampleArray;
+
+ case Target.Texture3D:
+ return target == Target.Texture3D;
+ }
+
+ return false;
+ }
+
+ public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture)
+ {
+ ReplaceStorage(hostTexture);
+
+ parent._viewStorage.AddView(this);
+
+ SetInfo(info);
+ }
+
+ private void SetInfo(TextureInfo info)
+ {
+ _info = info;
+
+ _depth = info.GetDepth();
+ _layers = info.GetLayers();
+ }
+
+ private void ReplaceStorage(ITexture hostTexture)
+ {
+ DisposeTextures();
+
+ HostTexture = hostTexture;
+ }
+
+ public bool OverlapsWith(ulong address, ulong size)
+ {
+ return Address < address + size && address < EndAddress;
+ }
+
+ public void Invalidate()
+ {
+ // _hasData = false;
+ }
+
+ public void IncrementReferenceCount()
+ {
+ _referenceCount++;
+ }
+
+ public void DecrementReferenceCount()
+ {
+ if (--_referenceCount == 0)
+ {
+ if (_viewStorage != this)
+ {
+ _viewStorage.RemoveView(this);
+ }
+
+ _context.Methods.TextureManager.RemoveTextureFromCache(this);
+
+ DisposeTextures();
+ }
+ }
+
+ private void DisposeTextures()
+ {
+ HostTexture.Dispose();
+
+ _arrayViewTexture?.Dispose();
+ _arrayViewTexture = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
new file mode 100644
index 00000000..19090ab3
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
@@ -0,0 +1,17 @@
+using Ryujinx.Graphics.GAL.Texture;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ struct TextureBindingInfo
+ {
+ public Target Target { get; }
+
+ public int Handle { get; }
+
+ public TextureBindingInfo(Target target, int handle)
+ {
+ Target = target;
+ Handle = handle;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
new file mode 100644
index 00000000..52472164
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
@@ -0,0 +1,95 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ static class TextureCompatibility
+ {
+ private enum FormatClass
+ {
+ Unclassified,
+ BCn64,
+ BCn128,
+ Bc1Rgb,
+ Bc1Rgba,
+ Bc2,
+ Bc3,
+ Bc4,
+ Bc5,
+ Bc6,
+ Bc7
+ }
+
+ public static bool FormatCompatible(FormatInfo lhs, FormatInfo rhs)
+ {
+ if (IsDsFormat(lhs.Format) || IsDsFormat(rhs.Format))
+ {
+ return lhs.Format == rhs.Format;
+ }
+
+ if (lhs.Format.IsAstc() || rhs.Format.IsAstc())
+ {
+ return lhs.Format == rhs.Format;
+ }
+
+ if (lhs.IsCompressed && rhs.IsCompressed)
+ {
+ FormatClass lhsClass = GetFormatClass(lhs.Format);
+ FormatClass rhsClass = GetFormatClass(rhs.Format);
+
+ return lhsClass == rhsClass;
+ }
+ else
+ {
+ return lhs.BytesPerPixel == rhs.BytesPerPixel;
+ }
+ }
+
+ private static FormatClass GetFormatClass(Format format)
+ {
+ switch (format)
+ {
+ case Format.Bc1RgbSrgb:
+ case Format.Bc1RgbUnorm:
+ return FormatClass.Bc1Rgb;
+ case Format.Bc1RgbaSrgb:
+ case Format.Bc1RgbaUnorm:
+ return FormatClass.Bc1Rgba;
+ case Format.Bc2Srgb:
+ case Format.Bc2Unorm:
+ return FormatClass.Bc2;
+ case Format.Bc3Srgb:
+ case Format.Bc3Unorm:
+ return FormatClass.Bc3;
+ case Format.Bc4Snorm:
+ case Format.Bc4Unorm:
+ return FormatClass.Bc4;
+ case Format.Bc5Snorm:
+ case Format.Bc5Unorm:
+ return FormatClass.Bc5;
+ case Format.Bc6HSfloat:
+ case Format.Bc6HUfloat:
+ return FormatClass.Bc6;
+ case Format.Bc7Srgb:
+ case Format.Bc7Unorm:
+ return FormatClass.Bc7;
+ }
+
+ return FormatClass.Unclassified;
+ }
+
+ private static bool IsDsFormat(Format format)
+ {
+ switch (format)
+ {
+ case Format.D16Unorm:
+ case Format.D24X8Unorm:
+ case Format.D24UnormS8Uint:
+ case Format.D32Float:
+ case Format.D32FloatS8Uint:
+ return true;
+ }
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs b/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs
new file mode 100644
index 00000000..37b3f65e
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs
@@ -0,0 +1,35 @@
+using Ryujinx.Graphics.GAL.Texture;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ enum TextureComponent
+ {
+ Zero = 0,
+ Red = 2,
+ Green = 3,
+ Blue = 4,
+ Alpha = 5,
+ OneSI = 6,
+ OneF = 7
+ }
+
+ static class TextureComponentConverter
+ {
+ public static SwizzleComponent Convert(this TextureComponent component)
+ {
+ switch (component)
+ {
+ case TextureComponent.Zero: return SwizzleComponent.Zero;
+ case TextureComponent.Red: return SwizzleComponent.Red;
+ case TextureComponent.Green: return SwizzleComponent.Green;
+ case TextureComponent.Blue: return SwizzleComponent.Blue;
+ case TextureComponent.Alpha: return SwizzleComponent.Alpha;
+ case TextureComponent.OneSI:
+ case TextureComponent.OneF:
+ return SwizzleComponent.One;
+ }
+
+ return SwizzleComponent.Zero;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
new file mode 100644
index 00000000..79e4f55e
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
@@ -0,0 +1,119 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ struct TextureDescriptor
+ {
+ public uint Word0;
+ public uint Word1;
+ public uint Word2;
+ public uint Word3;
+ public uint Word4;
+ public uint Word5;
+ public uint Word6;
+ public uint Word7;
+
+ public uint UnpackFormat()
+ {
+ return Word0 & 0x8007ffff;
+ }
+
+ public TextureComponent UnpackSwizzleR()
+ {
+ return(TextureComponent)((Word0 >> 19) & 7);
+ }
+
+ public TextureComponent UnpackSwizzleG()
+ {
+ return(TextureComponent)((Word0 >> 22) & 7);
+ }
+
+ public TextureComponent UnpackSwizzleB()
+ {
+ return(TextureComponent)((Word0 >> 25) & 7);
+ }
+
+ public TextureComponent UnpackSwizzleA()
+ {
+ return(TextureComponent)((Word0 >> 28) & 7);
+ }
+
+ public ulong UnpackAddress()
+ {
+ return Word1 | ((ulong)(Word2 & 0xffff) << 32);
+ }
+
+ public TextureDescriptorType UnpackTextureDescriptorType()
+ {
+ return (TextureDescriptorType)((Word2 >> 21) & 7);
+ }
+
+ public int UnpackStride()
+ {
+ return (int)(Word3 & 0xffff) << 5;
+ }
+
+ public int UnpackGobBlocksInX()
+ {
+ return 1 << (int)(Word3 & 7);
+ }
+
+ public int UnpackGobBlocksInY()
+ {
+ return 1 << (int)((Word3 >> 3) & 7);
+ }
+
+ public int UnpackGobBlocksInZ()
+ {
+ return 1 << (int)((Word3 >> 6) & 7);
+ }
+
+ public int UnpackGobBlocksInTileX()
+ {
+ return 1 << (int)((Word3 >> 10) & 7);
+ }
+
+ public int UnpackLevels()
+ {
+ return (int)(Word3 >> 28) + 1;
+ }
+
+ public int UnpackWidth()
+ {
+ return (int)(Word4 & 0xffff) + 1;
+ }
+
+ public bool UnpackSrgb()
+ {
+ return (Word4 & (1 << 22)) != 0;
+ }
+
+ public TextureTarget UnpackTextureTarget()
+ {
+ return (TextureTarget)((Word4 >> 23) & 0xf);
+ }
+
+ public int UnpackHeight()
+ {
+ return (int)(Word5 & 0xffff) + 1;
+ }
+
+ public int UnpackDepth()
+ {
+ return (int)((Word5 >> 16) & 0x3fff) + 1;
+ }
+
+ public int UnpackBaseLevel()
+ {
+ return (int)(Word7 & 0xf);
+ }
+
+ public int UnpackMaxLevelInclusive()
+ {
+ return (int)((Word7 >> 4) & 0xf);
+ }
+
+ public TextureMsaaMode UnpackTextureMsaaMode()
+ {
+ return (TextureMsaaMode)((Word7 >> 8) & 0xf);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs
new file mode 100644
index 00000000..6f6048a6
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ enum TextureDescriptorType
+ {
+ Buffer,
+ LinearColorKey,
+ Linear,
+ BlockLinear,
+ BlockLinearColorKey
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
new file mode 100644
index 00000000..639abdd8
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
@@ -0,0 +1,101 @@
+using Ryujinx.Graphics.GAL.Texture;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ struct TextureInfo
+ {
+ public ulong Address { get; }
+
+ public int Width { get; }
+ public int Height { get; }
+ public int DepthOrLayers { get; }
+ public int Levels { get; }
+ public int SamplesInX { get; }
+ public int SamplesInY { get; }
+ public int Stride { get; }
+ public bool IsLinear { get; }
+ public int GobBlocksInY { get; }
+ public int GobBlocksInZ { get; }
+ public int GobBlocksInTileX { get; }
+
+ public int Samples => SamplesInX * SamplesInY;
+
+ public Target Target { get; }
+
+ public FormatInfo FormatInfo { get; }
+
+ public DepthStencilMode DepthStencilMode { get; }
+
+ public SwizzleComponent SwizzleR { get; }
+ public SwizzleComponent SwizzleG { get; }
+ public SwizzleComponent SwizzleB { get; }
+ public SwizzleComponent SwizzleA { get; }
+
+ public TextureInfo(
+ ulong address,
+ int width,
+ int height,
+ int depthOrLayers,
+ int levels,
+ int samplesInX,
+ int samplesInY,
+ int stride,
+ bool isLinear,
+ int gobBlocksInY,
+ int gobBlocksInZ,
+ int gobBlocksInTileX,
+ Target target,
+ FormatInfo formatInfo,
+ DepthStencilMode depthStencilMode = DepthStencilMode.Depth,
+ SwizzleComponent swizzleR = SwizzleComponent.Red,
+ SwizzleComponent swizzleG = SwizzleComponent.Green,
+ SwizzleComponent swizzleB = SwizzleComponent.Blue,
+ SwizzleComponent swizzleA = SwizzleComponent.Alpha)
+ {
+ Address = address;
+ Width = width;
+ Height = height;
+ DepthOrLayers = depthOrLayers;
+ Levels = levels;
+ SamplesInX = samplesInX;
+ SamplesInY = samplesInY;
+ Stride = stride;
+ IsLinear = isLinear;
+ GobBlocksInY = gobBlocksInY;
+ GobBlocksInZ = gobBlocksInZ;
+ GobBlocksInTileX = gobBlocksInTileX;
+ Target = target;
+ FormatInfo = formatInfo;
+ DepthStencilMode = depthStencilMode;
+ SwizzleR = swizzleR;
+ SwizzleG = swizzleG;
+ SwizzleB = swizzleB;
+ SwizzleA = swizzleA;
+ }
+
+ public int GetDepth()
+ {
+ return Target == Target.Texture3D ? DepthOrLayers : 1;
+ }
+
+ public int GetLayers()
+ {
+ if (Target == Target.Texture2DArray || Target == Target.Texture2DMultisampleArray)
+ {
+ return DepthOrLayers;
+ }
+ else if (Target == Target.CubemapArray)
+ {
+ return DepthOrLayers * 6;
+ }
+ else if (Target == Target.Cubemap)
+ {
+ return 6;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
new file mode 100644
index 00000000..56dff9ad
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -0,0 +1,669 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+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 Ryujinx.Graphics.Texture;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ class TextureManager
+ {
+ private GpuContext _context;
+ private BufferManager _bufferManager;
+
+ private SamplerPool _samplerPool;
+
+ private ulong _texturePoolAddress;
+ private int _texturePoolMaximumId;
+
+ private TexturePoolCache _texturePoolCache;
+
+ private Texture[] _rtColors;
+ private Texture _rtColor3D;
+
+ private Texture _rtDepthStencil;
+
+ private ITexture[] _rtHostColors;
+
+ private ITexture _rtHostDs;
+
+ private RangeList<Texture> _textures;
+
+ private AutoDeleteCache _cache;
+
+ private TextureBindingInfo[][] _bindings;
+
+ private struct TextureStatePerStage
+ {
+ public ITexture Texture;
+ public ISampler Sampler;
+ }
+
+ private TextureStatePerStage[][] _textureState;
+
+ private int _textureBufferIndex;
+
+ public TextureManager(GpuContext context, BufferManager bufferManager)
+ {
+ _context = context;
+ _bufferManager = bufferManager;
+
+ _texturePoolCache = new TexturePoolCache(context, this);
+
+ _rtColors = new Texture[Constants.TotalRenderTargets];
+
+ _rtHostColors = new ITexture[Constants.TotalRenderTargets];
+
+ _textures = new RangeList<Texture>();
+
+ _cache = new AutoDeleteCache();
+
+ _bindings = new TextureBindingInfo[Constants.TotalShaderStages][];
+
+ _textureState = new TextureStatePerStage[Constants.TotalShaderStages][];
+ }
+
+ public void BindTextures(int stage, TextureBindingInfo[] bindings)
+ {
+ _bindings[stage] = bindings;
+
+ _textureState[stage] = new TextureStatePerStage[bindings.Length];
+ }
+
+ public void SetTextureBufferIndex(int index)
+ {
+ _textureBufferIndex = index;
+ }
+
+ public void SetSamplerPool(ulong gpuVa, int maximumId)
+ {
+ ulong address = _context.MemoryManager.Translate(gpuVa);
+
+ if (_samplerPool != null)
+ {
+ if (_samplerPool.Address == address)
+ {
+ return;
+ }
+
+ _samplerPool.Dispose();
+ }
+
+ _samplerPool = new SamplerPool(_context, address, maximumId);
+ }
+
+ public void SetTexturePool(ulong gpuVa, int maximumId)
+ {
+ ulong address = _context.MemoryManager.Translate(gpuVa);
+
+ _texturePoolAddress = address;
+ _texturePoolMaximumId = maximumId;
+ }
+
+ public void SetRenderTargetColor(int index, Texture color)
+ {
+ _rtColors[index] = color;
+
+ _rtColor3D = null;
+ }
+
+ public void SetRenderTargetColor3D(Texture color)
+ {
+ _rtColor3D = color;
+ }
+
+ public void SetRenderTargetDepthStencil(Texture depthStencil)
+ {
+ _rtDepthStencil = depthStencil;
+ }
+
+ public void CommitBindings()
+ {
+ UpdateTextures();
+ UpdateRenderTargets();
+ }
+
+ private void UpdateTextures()
+ {
+ TexturePool texturePool = _texturePoolCache.FindOrCreate(
+ _texturePoolAddress,
+ _texturePoolMaximumId);
+
+ for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
+ {
+ int stageIndex = (int)stage - 1;
+
+ if (_bindings[stageIndex] == null)
+ {
+ continue;
+ }
+
+ for (int index = 0; index < _bindings[stageIndex].Length; index++)
+ {
+ TextureBindingInfo binding = _bindings[stageIndex][index];
+
+ int packedId = ReadPackedId(stageIndex, binding.Handle);
+
+ int textureId = (packedId >> 0) & 0xfffff;
+ int samplerId = (packedId >> 20) & 0xfff;
+
+ Texture texture = texturePool.Get(textureId);
+
+ ITexture hostTexture = texture?.GetTargetTexture(binding.Target);
+
+ if (_textureState[stageIndex][index].Texture != hostTexture)
+ {
+ _textureState[stageIndex][index].Texture = hostTexture;
+
+ _context.Renderer.GraphicsPipeline.BindTexture(index, stage, hostTexture);
+ }
+
+ Sampler sampler = _samplerPool.Get(samplerId);
+
+ ISampler hostSampler = sampler?.HostSampler;
+
+ if (_textureState[stageIndex][index].Sampler != hostSampler)
+ {
+ _textureState[stageIndex][index].Sampler = hostSampler;
+
+ _context.Renderer.GraphicsPipeline.BindSampler(index, stage, hostSampler);
+ }
+ }
+ }
+ }
+
+ private void UpdateRenderTargets()
+ {
+ bool anyChanged = false;
+
+ if (_rtHostDs != _rtDepthStencil?.HostTexture)
+ {
+ _rtHostDs = _rtDepthStencil?.HostTexture;
+
+ anyChanged = true;
+ }
+
+ if (_rtColor3D == null)
+ {
+ for (int index = 0; index < _rtColors.Length; index++)
+ {
+ ITexture hostTexture = _rtColors[index]?.HostTexture;
+
+ if (_rtHostColors[index] != hostTexture)
+ {
+ _rtHostColors[index] = hostTexture;
+
+ anyChanged = true;
+ }
+ }
+
+ if (anyChanged)
+ {
+ _context.Renderer.GraphicsPipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
+ }
+ }
+ else
+ {
+ if (_rtHostColors[0] != _rtColor3D.HostTexture)
+ {
+ _rtHostColors[0] = _rtColor3D.HostTexture;
+
+ anyChanged = true;
+ }
+
+ if (anyChanged)
+ {
+ _context.Renderer.GraphicsPipeline.SetRenderTargets(_rtColor3D.HostTexture, _rtHostDs);
+ }
+ }
+ }
+
+ private int ReadPackedId(int stage, int wordOffset)
+ {
+ ulong address = _bufferManager.GetGraphicsUniformBufferAddress(stage, _textureBufferIndex);
+
+ address += (uint)wordOffset * 4;
+
+ return BitConverter.ToInt32(_context.PhysicalMemory.Read(address, 4));
+ }
+
+ public Texture FindOrCreateTexture(CopyTexture copyTexture)
+ {
+ ulong address = _context.MemoryManager.Translate(copyTexture.Address.Pack());
+
+ if (address == MemoryManager.BadAddress)
+ {
+ return null;
+ }
+
+ int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY();
+ int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ();
+
+ FormatInfo formatInfo = copyTexture.Format.Convert();
+
+ TextureInfo info = new TextureInfo(
+ address,
+ copyTexture.Width,
+ copyTexture.Height,
+ copyTexture.Depth,
+ 1,
+ 1,
+ 1,
+ copyTexture.Stride,
+ copyTexture.LinearLayout,
+ gobBlocksInY,
+ gobBlocksInZ,
+ 1,
+ Target.Texture2D,
+ formatInfo);
+
+ Texture texture = FindOrCreateTexture(info, TextureSearchFlags.IgnoreMs);
+
+ texture.SynchronizeMemory();
+
+ return texture;
+ }
+
+ public Texture FindOrCreateTexture(RtColorState colorState, int samplesInX, int samplesInY)
+ {
+ ulong address = _context.MemoryManager.Translate(colorState.Address.Pack());
+
+ if (address == MemoryManager.BadAddress)
+ {
+ return null;
+ }
+
+ bool isLinear = colorState.MemoryLayout.UnpackIsLinear();
+
+ int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY();
+ int gobBlocksInZ = colorState.MemoryLayout.UnpackGobBlocksInZ();
+
+ Target target;
+
+ if (colorState.MemoryLayout.UnpackIsTarget3D())
+ {
+ target = Target.Texture3D;
+ }
+ else if ((samplesInX | samplesInY) != 1)
+ {
+ target = colorState.Depth > 1
+ ? Target.Texture2DMultisampleArray
+ : Target.Texture2DMultisample;
+ }
+ else
+ {
+ target = colorState.Depth > 1
+ ? Target.Texture2DArray
+ : Target.Texture2D;
+ }
+
+ FormatInfo formatInfo = colorState.Format.Convert();
+
+ int width, stride;
+
+ // For linear textures, the width value is actually the stride.
+ // We can easily get the width by dividing the stride by the bpp,
+ // since the stride is the total number of bytes occupied by a
+ // line. The stride should also meet alignment constraints however,
+ // so the width we get here is the aligned width.
+ if (isLinear)
+ {
+ width = colorState.WidthOrStride / formatInfo.BytesPerPixel;
+ stride = colorState.WidthOrStride;
+ }
+ else
+ {
+ width = colorState.WidthOrStride;
+ stride = 0;
+ }
+
+ TextureInfo info = new TextureInfo(
+ address,
+ width,
+ colorState.Height,
+ colorState.Depth,
+ 1,
+ samplesInX,
+ samplesInY,
+ stride,
+ isLinear,
+ gobBlocksInY,
+ gobBlocksInZ,
+ 1,
+ target,
+ formatInfo);
+
+ Texture texture = FindOrCreateTexture(info);
+
+ texture.SynchronizeMemory();
+
+ return texture;
+ }
+
+ public Texture FindOrCreateTexture(RtDepthStencilState dsState, Size3D size, int samplesInX, int samplesInY)
+ {
+ ulong address = _context.MemoryManager.Translate(dsState.Address.Pack());
+
+ if (address == MemoryManager.BadAddress)
+ {
+ return null;
+ }
+
+ int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
+ int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
+
+ Target target = (samplesInX | samplesInY) != 1
+ ? Target.Texture2DMultisample
+ : Target.Texture2D;
+
+ FormatInfo formatInfo = dsState.Format.Convert();
+
+ TextureInfo info = new TextureInfo(
+ address,
+ size.Width,
+ size.Height,
+ size.Depth,
+ 1,
+ samplesInX,
+ samplesInY,
+ 0,
+ false,
+ gobBlocksInY,
+ gobBlocksInZ,
+ 1,
+ target,
+ formatInfo);
+
+ Texture texture = FindOrCreateTexture(info);
+
+ texture.SynchronizeMemory();
+
+ return texture;
+ }
+
+ public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None)
+ {
+ bool isSamplerTexture = (flags & TextureSearchFlags.Sampler) != 0;
+
+ // Try to find a perfect texture match, with the same address and parameters.
+ Texture[] sameAddressOverlaps = _textures.FindOverlaps(info.Address);
+
+ foreach (Texture overlap in sameAddressOverlaps)
+ {
+ if (overlap.IsPerfectMatch(info, flags))
+ {
+ if (!isSamplerTexture)
+ {
+ // If not a sampler texture, it is managed by the auto delete
+ // cache, ensure that it is on the "top" of the list to avoid
+ // deletion.
+ _cache.Lift(overlap);
+ }
+ else if (!overlap.SizeMatches(info))
+ {
+ // If this is used for sampling, the size must match,
+ // otherwise the shader would sample garbage data.
+ // To fix that, we create a new texture with the correct
+ // size, and copy the data from the old one to the new one.
+ overlap.ChangeSize(info.Width, info.Height, info.DepthOrLayers);
+ }
+
+ return overlap;
+ }
+ }
+
+ // Calculate texture sizes, used to find all overlapping textures.
+ SizeInfo sizeInfo;
+
+ if (info.IsLinear)
+ {
+ sizeInfo = SizeCalculator.GetLinearTextureSize(
+ info.Stride,
+ info.Height,
+ info.FormatInfo.BlockHeight);
+ }
+ else
+ {
+ sizeInfo = SizeCalculator.GetBlockLinearTextureSize(
+ info.Width,
+ info.Height,
+ info.GetDepth(),
+ info.Levels,
+ info.GetLayers(),
+ info.FormatInfo.BlockWidth,
+ info.FormatInfo.BlockHeight,
+ info.FormatInfo.BytesPerPixel,
+ info.GobBlocksInY,
+ info.GobBlocksInZ,
+ info.GobBlocksInTileX);
+ }
+
+ // Find view compatible matches.
+ ulong size = (ulong)sizeInfo.TotalSize;
+
+ Texture[] overlaps = _textures.FindOverlaps(info.Address, size);
+
+ Texture texture = null;
+
+ foreach (Texture overlap in overlaps)
+ {
+ if (overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel))
+ {
+ if (!isSamplerTexture)
+ {
+ info = AdjustSizes(overlap, info, firstLevel);
+ }
+
+ texture = overlap.CreateView(info, sizeInfo, firstLayer, firstLevel);
+
+ // The size only matters (and is only really reliable) when the
+ // texture is used on a sampler, because otherwise the size will be
+ // aligned.
+ if (!overlap.SizeMatches(info, firstLevel) && isSamplerTexture)
+ {
+ texture.ChangeSize(info.Width, info.Height, info.DepthOrLayers);
+ }
+
+ break;
+ }
+ }
+
+ // No match, create a new texture.
+ if (texture == null)
+ {
+ texture = new Texture(_context, info, sizeInfo);
+
+ // We need to synchronize before copying the old view data to the texture,
+ // otherwise the copied data would be overwritten by a future synchronization.
+ texture.SynchronizeMemory();
+
+ foreach (Texture overlap in overlaps)
+ {
+ if (texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel))
+ {
+ TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, firstLevel);
+
+ TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities);
+
+ ITexture newView = texture.HostTexture.CreateView(createInfo, firstLayer, firstLevel);
+
+ overlap.HostTexture.CopyTo(newView);
+
+ overlap.ReplaceView(texture, overlapInfo, newView);
+ }
+ }
+ }
+
+ // Sampler textures are managed by the texture pool, all other textures
+ // are managed by the auto delete cache.
+ if (!isSamplerTexture)
+ {
+ _cache.Add(texture);
+ }
+
+ _textures.Add(texture);
+
+ return texture;
+ }
+
+ private static TextureInfo AdjustSizes(Texture parent, TextureInfo info, int firstLevel)
+ {
+ // When the texture is used as view of another texture, we must
+ // ensure that the sizes are valid, otherwise data uploads would fail
+ // (and the size wouldn't match the real size used on the host API).
+ // Given a parent texture from where the view is created, we have the
+ // following rules:
+ // - The view size must be equal to the parent size, divided by (2 ^ l),
+ // where l is the first mipmap level of the view. The division result must
+ // be rounded down, and the result must be clamped to 1.
+ // - If the parent format is compressed, and the view format isn't, the
+ // view size is calculated as above, but the width and height of the
+ // view must be also divided by the compressed format block width and height.
+ // - If the parent format is not compressed, and the view is, the view
+ // size is calculated as described on the first point, but the width and height
+ // of the view must be also multiplied by the block width and height.
+ int width = Math.Max(1, parent.Info.Width >> firstLevel);
+ int height = Math.Max(1, parent.Info.Height >> firstLevel);
+
+ if (parent.Info.FormatInfo.IsCompressed && !info.FormatInfo.IsCompressed)
+ {
+ width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth);
+ height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight);
+ }
+ else if (!parent.Info.FormatInfo.IsCompressed && info.FormatInfo.IsCompressed)
+ {
+ width *= info.FormatInfo.BlockWidth;
+ height *= info.FormatInfo.BlockHeight;
+ }
+
+ int depthOrLayers;
+
+ if (info.Target == Target.Texture3D)
+ {
+ depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel);
+ }
+ else
+ {
+ depthOrLayers = info.DepthOrLayers;
+ }
+
+ return new TextureInfo(
+ info.Address,
+ width,
+ height,
+ depthOrLayers,
+ info.Levels,
+ info.SamplesInX,
+ info.SamplesInY,
+ info.Stride,
+ info.IsLinear,
+ info.GobBlocksInY,
+ info.GobBlocksInZ,
+ info.GobBlocksInTileX,
+ info.Target,
+ info.FormatInfo,
+ info.DepthStencilMode,
+ info.SwizzleR,
+ info.SwizzleG,
+ info.SwizzleB,
+ info.SwizzleA);
+ }
+
+ public static TextureCreateInfo GetCreateInfo(TextureInfo info, Capabilities caps)
+ {
+ FormatInfo formatInfo = info.FormatInfo;
+
+ if (!caps.SupportsAstcCompression)
+ {
+ if (formatInfo.Format.IsAstcUnorm())
+ {
+ formatInfo = new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4);
+ }
+ else if (formatInfo.Format.IsAstcSrgb())
+ {
+ formatInfo = new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4);
+ }
+ }
+
+ int width = info.Width / info.SamplesInX;
+ int height = info.Height / info.SamplesInY;
+
+ int depth = info.GetDepth() * info.GetLayers();
+
+ return new TextureCreateInfo(
+ width,
+ height,
+ depth,
+ info.Levels,
+ info.Samples,
+ formatInfo.BlockWidth,
+ formatInfo.BlockHeight,
+ formatInfo.BytesPerPixel,
+ formatInfo.Format,
+ info.DepthStencilMode,
+ info.Target,
+ info.SwizzleR,
+ info.SwizzleG,
+ info.SwizzleB,
+ info.SwizzleA);
+ }
+
+ public Texture Find2(ulong address)
+ {
+ Texture[] ts = _textures.FindOverlaps(address, 1);
+
+ if (ts.Length == 2)
+ {
+ return ts[1];
+ }
+
+ if (ts.Length == 0)
+ {
+ ts = _textures.FindOverlaps(address - 1, 2);
+ }
+
+ if (ts.Length == 0)
+ {
+ return null;
+ }
+
+ return ts[0];
+ }
+
+ public void InvalidateRange(ulong address, ulong size)
+ {
+ Texture[] overlaps = _textures.FindOverlaps(address, size);
+
+ foreach (Texture overlap in overlaps)
+ {
+ overlap.Invalidate();
+ }
+
+ _samplerPool?.InvalidateRange(address, size);
+
+ _texturePoolCache.InvalidateRange(address, size);
+ }
+
+ public void Flush()
+ {
+ foreach (Texture texture in _cache)
+ {
+ if (texture.Info.IsLinear && texture.Modified)
+ {
+ texture.Flush();
+
+ texture.Modified = false;
+ }
+ }
+ }
+
+ public void RemoveTextureFromCache(Texture texture)
+ {
+ _textures.Remove(texture);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs b/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs
new file mode 100644
index 00000000..13421067
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs
@@ -0,0 +1,53 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ enum TextureMsaaMode
+ {
+ Ms1x1 = 0,
+ Ms2x2 = 2,
+ Ms4x2 = 4,
+ Ms2x1 = 5,
+ Ms4x4 = 6
+ }
+
+ static class TextureMsaaModeConverter
+ {
+ public static int SamplesCount(this TextureMsaaMode msaaMode)
+ {
+ switch (msaaMode)
+ {
+ case TextureMsaaMode.Ms2x1: return 2;
+ case TextureMsaaMode.Ms2x2: return 4;
+ case TextureMsaaMode.Ms4x2: return 8;
+ case TextureMsaaMode.Ms4x4: return 16;
+ }
+
+ return 1;
+ }
+
+ public static int SamplesInX(this TextureMsaaMode msaaMode)
+ {
+ switch (msaaMode)
+ {
+ case TextureMsaaMode.Ms2x1: return 2;
+ case TextureMsaaMode.Ms2x2: return 2;
+ case TextureMsaaMode.Ms4x2: return 4;
+ case TextureMsaaMode.Ms4x4: return 4;
+ }
+
+ return 1;
+ }
+
+ public static int SamplesInY(this TextureMsaaMode msaaMode)
+ {
+ switch (msaaMode)
+ {
+ case TextureMsaaMode.Ms2x1: return 1;
+ case TextureMsaaMode.Ms2x2: return 2;
+ case TextureMsaaMode.Ms4x2: return 2;
+ case TextureMsaaMode.Ms4x4: return 4;
+ }
+
+ return 1;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
new file mode 100644
index 00000000..558f4def
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -0,0 +1,219 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Gpu.Memory;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ class TexturePool : Pool<Texture>
+ {
+ private TextureManager _textureManager;
+
+ public LinkedListNode<TexturePool> CacheNode { get; set; }
+
+ private struct TextureContainer
+ {
+ public Texture Texture0 { get; set; }
+ public Texture Texture1 { get; set; }
+ }
+
+ public TexturePool(
+ GpuContext context,
+ TextureManager textureManager,
+ ulong address,
+ int maximumId) : base(context, address, maximumId)
+ {
+ _textureManager = textureManager;
+ }
+
+ public override Texture Get(int id)
+ {
+ if ((uint)id >= Items.Length)
+ {
+ return null;
+ }
+
+ SynchronizeMemory();
+
+ Texture texture = Items[id];
+
+ if (texture == null)
+ {
+ ulong address = Address + (ulong)(uint)id * DescriptorSize;
+
+ Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);
+
+ TextureDescriptor descriptor = MemoryMarshal.Cast<byte, TextureDescriptor>(data)[0];
+
+ TextureInfo info = GetInfo(descriptor);
+
+ // Bad address. We can't add a texture with a invalid address
+ // to the cache.
+ if (info.Address == MemoryManager.BadAddress)
+ {
+ return null;
+ }
+
+ texture = _textureManager.FindOrCreateTexture(info, TextureSearchFlags.Sampler);
+
+ texture.IncrementReferenceCount();
+
+ Items[id] = texture;
+ }
+ else
+ {
+ // Memory is automatically synchronized on texture creation.
+ texture.SynchronizeMemory();
+ }
+
+ return texture;
+ }
+
+ protected override void InvalidateRangeImpl(ulong address, ulong size)
+ {
+ ulong endAddress = address + size;
+
+ for (; address < endAddress; address += DescriptorSize)
+ {
+ int id = (int)((address - Address) / DescriptorSize);
+
+ Texture texture = Items[id];
+
+ if (texture != null)
+ {
+ Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);
+
+ TextureDescriptor descriptor = MemoryMarshal.Cast<byte, TextureDescriptor>(data)[0];
+
+ // If the descriptors are the same, the texture is the same,
+ // we don't need to remove as it was not modified. Just continue.
+ if (texture.IsPerfectMatch(GetInfo(descriptor), TextureSearchFlags.Strict))
+ {
+ continue;
+ }
+
+ texture.DecrementReferenceCount();
+
+ Items[id] = null;
+ }
+ }
+ }
+
+ private TextureInfo GetInfo(TextureDescriptor descriptor)
+ {
+ ulong address = Context.MemoryManager.Translate(descriptor.UnpackAddress());
+
+ int width = descriptor.UnpackWidth();
+ int height = descriptor.UnpackHeight();
+ int depthOrLayers = descriptor.UnpackDepth();
+ int levels = descriptor.UnpackLevels();
+
+ TextureMsaaMode msaaMode = descriptor.UnpackTextureMsaaMode();
+
+ int samplesInX = msaaMode.SamplesInX();
+ int samplesInY = msaaMode.SamplesInY();
+
+ int stride = descriptor.UnpackStride();
+
+ TextureDescriptorType descriptorType = descriptor.UnpackTextureDescriptorType();
+
+ bool isLinear = descriptorType == TextureDescriptorType.Linear;
+
+ Target target = descriptor.UnpackTextureTarget().Convert((samplesInX | samplesInY) != 1);
+
+ uint format = descriptor.UnpackFormat();
+ bool srgb = descriptor.UnpackSrgb();
+
+ if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo))
+ {
+ // TODO: Warning.
+
+ formatInfo = FormatInfo.Default;
+ }
+
+ int gobBlocksInY = descriptor.UnpackGobBlocksInY();
+ int gobBlocksInZ = descriptor.UnpackGobBlocksInZ();
+
+ int gobBlocksInTileX = descriptor.UnpackGobBlocksInTileX();
+
+ SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert();
+ SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert();
+ SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert();
+ SwizzleComponent swizzleA = descriptor.UnpackSwizzleA().Convert();
+
+ DepthStencilMode depthStencilMode = GetDepthStencilMode(
+ formatInfo.Format,
+ swizzleR,
+ swizzleG,
+ swizzleB,
+ swizzleA);
+
+ return new TextureInfo(
+ address,
+ width,
+ height,
+ depthOrLayers,
+ levels,
+ samplesInX,
+ samplesInY,
+ stride,
+ isLinear,
+ gobBlocksInY,
+ gobBlocksInZ,
+ gobBlocksInTileX,
+ target,
+ formatInfo,
+ depthStencilMode,
+ swizzleR,
+ swizzleG,
+ swizzleB,
+ swizzleA);
+ }
+
+ private static DepthStencilMode GetDepthStencilMode(Format format, params SwizzleComponent[] components)
+ {
+ // R = Depth, G = Stencil.
+ // On 24-bits depth formats, this is inverted (Stencil is R etc).
+ // NVN setup:
+ // For depth, A is set to 1.0f, the other components are set to Depth.
+ // For stencil, all components are set to Stencil.
+ SwizzleComponent component = components[0];
+
+ for (int index = 1; index < 4 && !IsRG(component); index++)
+ {
+ component = components[index];
+ }
+
+ if (!IsRG(component))
+ {
+ return DepthStencilMode.Depth;
+ }
+
+ if (format == Format.D24X8Unorm || format == Format.D24UnormS8Uint)
+ {
+ return component == SwizzleComponent.Red
+ ? DepthStencilMode.Stencil
+ : DepthStencilMode.Depth;
+ }
+ else
+ {
+ return component == SwizzleComponent.Red
+ ? DepthStencilMode.Depth
+ : DepthStencilMode.Stencil;
+ }
+ }
+
+ private static bool IsRG(SwizzleComponent component)
+ {
+ return component == SwizzleComponent.Red ||
+ component == SwizzleComponent.Green;
+ }
+
+ protected override void Delete(Texture item)
+ {
+ item?.DecrementReferenceCount();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs b/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs
new file mode 100644
index 00000000..8e8313ae
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs
@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ class TexturePoolCache
+ {
+ private const int MaxCapacity = 4;
+
+ private GpuContext _context;
+ private TextureManager _textureManager;
+
+ private LinkedList<TexturePool> _pools;
+
+ public TexturePoolCache(GpuContext context, TextureManager textureManager)
+ {
+ _context = context;
+ _textureManager = textureManager;
+
+ _pools = new LinkedList<TexturePool>();
+ }
+
+ public TexturePool FindOrCreate(ulong address, int maximumId)
+ {
+ TexturePool pool;
+
+ // First we try to find the pool.
+ for (LinkedListNode<TexturePool> node = _pools.First; node != null; node = node.Next)
+ {
+ pool = node.Value;
+
+ if (pool.Address == address)
+ {
+ if (pool.CacheNode != _pools.Last)
+ {
+ _pools.Remove(pool.CacheNode);
+
+ pool.CacheNode = _pools.AddLast(pool);
+ }
+
+ return pool;
+ }
+ }
+
+ // If not found, create a new one.
+ pool = new TexturePool(_context, _textureManager, address, maximumId);
+
+ pool.CacheNode = _pools.AddLast(pool);
+
+ if (_pools.Count > MaxCapacity)
+ {
+ TexturePool oldestPool = _pools.First.Value;
+
+ _pools.RemoveFirst();
+
+ oldestPool.Dispose();
+
+ oldestPool.CacheNode = null;
+ }
+
+ return pool;
+ }
+
+ public void InvalidateRange(ulong address, ulong size)
+ {
+ for (LinkedListNode<TexturePool> node = _pools.First; node != null; node = node.Next)
+ {
+ TexturePool pool = node.Value;
+
+ pool.InvalidateRange(address, size);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs b/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs
new file mode 100644
index 00000000..a5c951b5
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ [Flags]
+ enum TextureSearchFlags
+ {
+ None = 0,
+ IgnoreMs = 1 << 0,
+ Strict = 1 << 1 | Sampler,
+ Sampler = 1 << 2
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs b/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs
new file mode 100644
index 00000000..8f513903
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs
@@ -0,0 +1,49 @@
+using Ryujinx.Graphics.GAL.Texture;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+ enum TextureTarget
+ {
+ Texture1D,
+ Texture2D,
+ Texture3D,
+ Cubemap,
+ Texture1DArray,
+ Texture2DArray,
+ TextureBuffer,
+ Texture2DLinear,
+ CubemapArray
+ }
+
+ static class TextureTargetConverter
+ {
+ public static Target Convert(this TextureTarget target, bool isMultisample)
+ {
+ if (isMultisample)
+ {
+ switch (target)
+ {
+ case TextureTarget.Texture2D: return Target.Texture2DMultisample;
+ case TextureTarget.Texture2DArray: return Target.Texture2DMultisampleArray;
+ }
+ }
+ else
+ {
+ switch (target)
+ {
+ case TextureTarget.Texture1D: return Target.Texture1D;
+ case TextureTarget.Texture2D: return Target.Texture2D;
+ case TextureTarget.Texture2DLinear: return Target.Texture2D;
+ case TextureTarget.Texture3D: return Target.Texture3D;
+ case TextureTarget.Texture1DArray: return Target.Texture1DArray;
+ case TextureTarget.Texture2DArray: return Target.Texture2DArray;
+ case TextureTarget.Cubemap: return Target.Cubemap;
+ case TextureTarget.CubemapArray: return Target.CubemapArray;
+ case TextureTarget.TextureBuffer: return Target.TextureBuffer;
+ }
+ }
+
+ return Target.Texture1D;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/MacroInterpreter.cs b/Ryujinx.Graphics.Gpu/MacroInterpreter.cs
new file mode 100644
index 00000000..93d5c2ca
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/MacroInterpreter.cs
@@ -0,0 +1,415 @@
+using Ryujinx.Common.Logging;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu
+{
+ class MacroInterpreter
+ {
+ private enum AssignmentOperation
+ {
+ IgnoreAndFetch = 0,
+ Move = 1,
+ MoveAndSetMaddr = 2,
+ FetchAndSend = 3,
+ MoveAndSend = 4,
+ FetchAndSetMaddr = 5,
+ MoveAndSetMaddrThenFetchAndSend = 6,
+ MoveAndSetMaddrThenSendHigh = 7
+ }
+
+ private enum AluOperation
+ {
+ AluReg = 0,
+ AddImmediate = 1,
+ BitfieldReplace = 2,
+ BitfieldExtractLslImm = 3,
+ BitfieldExtractLslReg = 4,
+ ReadImmediate = 5
+ }
+
+ private enum AluRegOperation
+ {
+ Add = 0,
+ AddWithCarry = 1,
+ Subtract = 2,
+ SubtractWithBorrow = 3,
+ BitwiseExclusiveOr = 8,
+ BitwiseOr = 9,
+ BitwiseAnd = 10,
+ BitwiseAndNot = 11,
+ BitwiseNotAnd = 12
+ }
+
+ private GpuContext _context;
+
+ private NvGpuFifo _pFifo;
+
+ public Queue<int> Fifo { get; private set; }
+
+ private int[] _gprs;
+
+ private int _methAddr;
+ private int _methIncr;
+
+ private bool _carry;
+
+ private int _opCode;
+
+ private int _pipeOp;
+
+ private int _pc;
+
+ public MacroInterpreter(GpuContext context, NvGpuFifo pFifo)
+ {
+ _context = context;
+ _pFifo = pFifo;
+
+ Fifo = new Queue<int>();
+
+ _gprs = new int[8];
+ }
+
+ public void Execute(int[] mme, int position, int param)
+ {
+ Reset();
+
+ _gprs[1] = param;
+
+ _pc = position;
+
+ FetchOpCode(mme);
+
+ while (Step(mme));
+
+ // Due to the delay slot, we still need to execute
+ // one more instruction before we actually exit.
+ Step(mme);
+ }
+
+ private void Reset()
+ {
+ for (int index = 0; index < _gprs.Length; index++)
+ {
+ _gprs[index] = 0;
+ }
+
+ _methAddr = 0;
+ _methIncr = 0;
+
+ _carry = false;
+ }
+
+ private bool Step(int[] mme)
+ {
+ int baseAddr = _pc - 1;
+
+ FetchOpCode(mme);
+
+ if ((_opCode & 7) < 7)
+ {
+ // Operation produces a value.
+ AssignmentOperation asgOp = (AssignmentOperation)((_opCode >> 4) & 7);
+
+ int result = GetAluResult();
+
+ switch (asgOp)
+ {
+ // Fetch parameter and ignore result.
+ case AssignmentOperation.IgnoreAndFetch:
+ {
+ SetDstGpr(FetchParam());
+
+ break;
+ }
+
+ // Move result.
+ case AssignmentOperation.Move:
+ {
+ SetDstGpr(result);
+
+ break;
+ }
+
+ // Move result and use as Method Address.
+ case AssignmentOperation.MoveAndSetMaddr:
+ {
+ SetDstGpr(result);
+
+ SetMethAddr(result);
+
+ break;
+ }
+
+ // Fetch parameter and send result.
+ case AssignmentOperation.FetchAndSend:
+ {
+ SetDstGpr(FetchParam());
+
+ Send(result);
+
+ break;
+ }
+
+ // Move and send result.
+ case AssignmentOperation.MoveAndSend:
+ {
+ SetDstGpr(result);
+
+ Send(result);
+
+ break;
+ }
+
+ // Fetch parameter and use result as Method Address.
+ case AssignmentOperation.FetchAndSetMaddr:
+ {
+ SetDstGpr(FetchParam());
+
+ SetMethAddr(result);
+
+ break;
+ }
+
+ // Move result and use as Method Address, then fetch and send parameter.
+ case AssignmentOperation.MoveAndSetMaddrThenFetchAndSend:
+ {
+ SetDstGpr(result);
+
+ SetMethAddr(result);
+
+ Send(FetchParam());
+
+ break;
+ }
+
+ // Move result and use as Method Address, then send bits 17:12 of result.
+ case AssignmentOperation.MoveAndSetMaddrThenSendHigh:
+ {
+ SetDstGpr(result);
+
+ SetMethAddr(result);
+
+ Send((result >> 12) & 0x3f);
+
+ break;
+ }
+ }
+ }
+ else
+ {
+ // Branch.
+ bool onNotZero = ((_opCode >> 4) & 1) != 0;
+
+ bool taken = onNotZero
+ ? GetGprA() != 0
+ : GetGprA() == 0;
+
+ if (taken)
+ {
+ _pc = baseAddr + GetImm();
+
+ bool noDelays = (_opCode & 0x20) != 0;
+
+ if (noDelays)
+ {
+ FetchOpCode(mme);
+ }
+
+ return true;
+ }
+ }
+
+ bool exit = (_opCode & 0x80) != 0;
+
+ return !exit;
+ }
+
+ private void FetchOpCode(int[] mme)
+ {
+ _opCode = _pipeOp;
+ _pipeOp = mme[_pc++];
+ }
+
+ private int GetAluResult()
+ {
+ AluOperation op = (AluOperation)(_opCode & 7);
+
+ switch (op)
+ {
+ case AluOperation.AluReg:
+ {
+ AluRegOperation aluOp = (AluRegOperation)((_opCode >> 17) & 0x1f);
+
+ return GetAluResult(aluOp, GetGprA(), GetGprB());
+ }
+
+ case AluOperation.AddImmediate:
+ {
+ return GetGprA() + GetImm();
+ }
+
+ case AluOperation.BitfieldReplace:
+ case AluOperation.BitfieldExtractLslImm:
+ case AluOperation.BitfieldExtractLslReg:
+ {
+ int bfSrcBit = (_opCode >> 17) & 0x1f;
+ int bfSize = (_opCode >> 22) & 0x1f;
+ int bfDstBit = (_opCode >> 27) & 0x1f;
+
+ int bfMask = (1 << bfSize) - 1;
+
+ int dst = GetGprA();
+ int src = GetGprB();
+
+ switch (op)
+ {
+ case AluOperation.BitfieldReplace:
+ {
+ src = (int)((uint)src >> bfSrcBit) & bfMask;
+
+ dst &= ~(bfMask << bfDstBit);
+
+ dst |= src << bfDstBit;
+
+ return dst;
+ }
+
+ case AluOperation.BitfieldExtractLslImm:
+ {
+ src = (int)((uint)src >> dst) & bfMask;
+
+ return src << bfDstBit;
+ }
+
+ case AluOperation.BitfieldExtractLslReg:
+ {
+ src = (int)((uint)src >> bfSrcBit) & bfMask;
+
+ return src << dst;
+ }
+ }
+
+ break;
+ }
+
+ case AluOperation.ReadImmediate:
+ {
+ return Read(GetGprA() + GetImm());
+ }
+ }
+
+ throw new ArgumentException(nameof(_opCode));
+ }
+
+ private int GetAluResult(AluRegOperation aluOp, int a, int b)
+ {
+ switch (aluOp)
+ {
+ case AluRegOperation.Add:
+ {
+ ulong result = (ulong)a + (ulong)b;
+
+ _carry = result > 0xffffffff;
+
+ return (int)result;
+ }
+
+ case AluRegOperation.AddWithCarry:
+ {
+ ulong result = (ulong)a + (ulong)b + (_carry ? 1UL : 0UL);
+
+ _carry = result > 0xffffffff;
+
+ return (int)result;
+ }
+
+ case AluRegOperation.Subtract:
+ {
+ ulong result = (ulong)a - (ulong)b;
+
+ _carry = result < 0x100000000;
+
+ return (int)result;
+ }
+
+ case AluRegOperation.SubtractWithBorrow:
+ {
+ ulong result = (ulong)a - (ulong)b - (_carry ? 0UL : 1UL);
+
+ _carry = result < 0x100000000;
+
+ return (int)result;
+ }
+
+ case AluRegOperation.BitwiseExclusiveOr: return a ^ b;
+ case AluRegOperation.BitwiseOr: return a | b;
+ case AluRegOperation.BitwiseAnd: return a & b;
+ case AluRegOperation.BitwiseAndNot: return a & ~b;
+ case AluRegOperation.BitwiseNotAnd: return ~(a & b);
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(aluOp));
+ }
+
+ private int GetImm()
+ {
+ // Note: The immediate is signed, the sign-extension is intended here.
+ return _opCode >> 14;
+ }
+
+ private void SetMethAddr(int value)
+ {
+ _methAddr = (value >> 0) & 0xfff;
+ _methIncr = (value >> 12) & 0x3f;
+ }
+
+ private void SetDstGpr(int value)
+ {
+ _gprs[(_opCode >> 8) & 7] = value;
+ }
+
+ private int GetGprA()
+ {
+ return GetGprValue((_opCode >> 11) & 7);
+ }
+
+ private int GetGprB()
+ {
+ return GetGprValue((_opCode >> 14) & 7);
+ }
+
+ private int GetGprValue(int index)
+ {
+ return index != 0 ? _gprs[index] : 0;
+ }
+
+ private int FetchParam()
+ {
+ int value;
+
+ if (!Fifo.TryDequeue(out value))
+ {
+ Logger.PrintWarning(LogClass.Gpu, "Macro attempted to fetch an inexistent argument.");
+
+ return 0;
+ }
+
+ return value;
+ }
+
+ private int Read(int reg)
+ {
+ return _context.State.Read(reg);
+ }
+
+ private void Send(int value)
+ {
+ MethodParams meth = new MethodParams(_methAddr, value);
+
+ _context.State.CallMethod(meth);
+
+ _methAddr += _methIncr;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
new file mode 100644
index 00000000..30bd1ac0
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
@@ -0,0 +1,99 @@
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ class Buffer : IRange<Buffer>, IDisposable
+ {
+ private GpuContext _context;
+
+ private IBuffer _buffer;
+
+ public ulong Address { get; }
+ public ulong Size { get; }
+
+ public ulong EndAddress => Address + Size;
+
+ private int[] _sequenceNumbers;
+
+ public Buffer(GpuContext context, ulong address, ulong size)
+ {
+ _context = context;
+ Address = address;
+ Size = size;
+
+ _buffer = context.Renderer.CreateBuffer((int)size);
+
+ _sequenceNumbers = new int[size / MemoryManager.PageSize];
+
+ Invalidate();
+ }
+
+ public BufferRange GetRange(ulong address, ulong size)
+ {
+ int offset = (int)(address - Address);
+
+ return new BufferRange(_buffer, offset, (int)size);
+ }
+
+ public bool OverlapsWith(ulong address, ulong size)
+ {
+ return Address < address + size && address < EndAddress;
+ }
+
+ public void SynchronizeMemory(ulong address, ulong size)
+ {
+ int currentSequenceNumber = _context.SequenceNumber;
+
+ bool needsSync = false;
+
+ ulong buffOffset = address - Address;
+
+ ulong buffEndOffset = (buffOffset + size + MemoryManager.PageMask) & ~MemoryManager.PageMask;
+
+ int startIndex = (int)(buffOffset / MemoryManager.PageSize);
+ int endIndex = (int)(buffEndOffset / MemoryManager.PageSize);
+
+ for (int index = startIndex; index < endIndex; index++)
+ {
+ if (_sequenceNumbers[index] != currentSequenceNumber)
+ {
+ _sequenceNumbers[index] = currentSequenceNumber;
+
+ needsSync = true;
+ }
+ }
+
+ if (!needsSync)
+ {
+ return;
+ }
+
+ (ulong, ulong)[] modifiedRanges = _context.PhysicalMemory.GetModifiedRanges(address, size);
+
+ for (int index = 0; index < modifiedRanges.Length; index++)
+ {
+ (ulong mAddress, ulong mSize) = modifiedRanges[index];
+
+ int offset = (int)(mAddress - Address);
+
+ _buffer.SetData(offset, _context.PhysicalMemory.Read(mAddress, mSize));
+ }
+ }
+
+ public void CopyTo(Buffer destination, int dstOffset)
+ {
+ _buffer.CopyTo(destination._buffer, 0, dstOffset, (int)Size);
+ }
+
+ public void Invalidate()
+ {
+ _buffer.SetData(0, _context.PhysicalMemory.Read(Address, Size));
+ }
+
+ public void Dispose()
+ {
+ _buffer.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs b/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs
new file mode 100644
index 00000000..2a074bd3
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ struct BufferBounds
+ {
+ public ulong Address;
+ public ulong Size;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
new file mode 100644
index 00000000..eb2e0ca9
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
@@ -0,0 +1,530 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.InputAssembler;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ class BufferManager
+ {
+ private const ulong BufferAlignmentSize = 0x1000;
+ private const ulong BufferAlignmentMask = BufferAlignmentSize - 1;
+
+ private GpuContext _context;
+
+ private RangeList<Buffer> _buffers;
+
+ private IndexBuffer _indexBuffer;
+
+ private VertexBuffer[] _vertexBuffers;
+
+ private class BuffersPerStage
+ {
+ public uint EnableMask { get; set; }
+
+ public BufferBounds[] Buffers { get; }
+
+ public BuffersPerStage(int count)
+ {
+ Buffers = new BufferBounds[count];
+ }
+
+ public void Bind(int index, ulong address, ulong size)
+ {
+ Buffers[index].Address = address;
+ Buffers[index].Size = size;
+ }
+ }
+
+ private BuffersPerStage _cpStorageBuffers;
+ private BuffersPerStage _cpUniformBuffers;
+ private BuffersPerStage[] _gpStorageBuffers;
+ private BuffersPerStage[] _gpUniformBuffers;
+
+ private bool _gpStorageBuffersDirty;
+ private bool _gpUniformBuffersDirty;
+
+ private bool _indexBufferDirty;
+ private bool _vertexBuffersDirty;
+
+ private bool _rebind;
+
+ public BufferManager(GpuContext context)
+ {
+ _context = context;
+
+ _buffers = new RangeList<Buffer>();
+
+ _vertexBuffers = new VertexBuffer[Constants.TotalVertexBuffers];
+
+ _cpStorageBuffers = new BuffersPerStage(Constants.TotalCpStorageBuffers);
+ _cpUniformBuffers = new BuffersPerStage(Constants.TotalCpUniformBuffers);
+
+ _gpStorageBuffers = new BuffersPerStage[Constants.TotalShaderStages];
+ _gpUniformBuffers = new BuffersPerStage[Constants.TotalShaderStages];
+
+ for (int index = 0; index < Constants.TotalShaderStages; index++)
+ {
+ _gpStorageBuffers[index] = new BuffersPerStage(Constants.TotalGpStorageBuffers);
+ _gpUniformBuffers[index] = new BuffersPerStage(Constants.TotalGpUniformBuffers);
+ }
+ }
+
+ public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type)
+ {
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _indexBuffer.Address = address;
+ _indexBuffer.Size = size;
+ _indexBuffer.Type = type;
+
+ _indexBufferDirty = true;
+ }
+
+ public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor)
+ {
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _vertexBuffers[index].Address = address;
+ _vertexBuffers[index].Size = size;
+ _vertexBuffers[index].Stride = stride;
+ _vertexBuffers[index].Divisor = divisor;
+
+ _vertexBuffersDirty = true;
+ }
+
+ public void SetComputeStorageBuffer(int index, ulong gpuVa, ulong size)
+ {
+ // TODO: Improve
+ size += gpuVa & 0x3fUL;
+
+ gpuVa &= ~0x3fUL;
+
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _cpStorageBuffers.Bind(index, address, size);
+ }
+
+ public void SetGraphicsStorageBuffer(int stage, int index, ulong gpuVa, ulong size)
+ {
+ // TODO: Improve
+ size += gpuVa & 0x3fUL;
+
+ gpuVa &= ~0x3fUL;
+
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _gpStorageBuffers[stage].Bind(index, address, size);
+
+ _gpStorageBuffersDirty = true;
+ }
+
+ public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size)
+ {
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _cpUniformBuffers.Bind(index, address, size);
+ }
+
+ public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size)
+ {
+ ulong address = TranslateAndCreateBuffer(gpuVa, size);
+
+ _gpUniformBuffers[stage].Bind(index, address, size);
+
+ _gpUniformBuffersDirty = true;
+ }
+
+ public void SetComputeStorageBufferEnableMask(uint mask)
+ {
+ _cpStorageBuffers.EnableMask = mask;
+ }
+
+ public void SetGraphicsStorageBufferEnableMask(int stage, uint mask)
+ {
+ _gpStorageBuffers[stage].EnableMask = mask;
+
+ _gpStorageBuffersDirty = true;
+ }
+
+ public void SetComputeUniformBufferEnableMask(uint mask)
+ {
+ _cpUniformBuffers.EnableMask = mask;
+ }
+
+ public void SetGraphicsUniformBufferEnableMask(int stage, uint mask)
+ {
+ _gpUniformBuffers[stage].EnableMask = mask;
+
+ _gpUniformBuffersDirty = true;
+ }
+
+ private ulong TranslateAndCreateBuffer(ulong gpuVa, ulong size)
+ {
+ if (gpuVa == 0)
+ {
+ return 0;
+ }
+
+ ulong address = _context.MemoryManager.Translate(gpuVa);
+
+ if (address == MemoryManager.BadAddress)
+ {
+ return 0;
+ }
+
+ ulong endAddress = address + size;
+
+ ulong alignedAddress = address & ~BufferAlignmentMask;
+
+ ulong alignedEndAddress = (endAddress + BufferAlignmentMask) & ~BufferAlignmentMask;
+
+ // The buffer must have the size of at least one page.
+ if (alignedEndAddress == alignedAddress)
+ {
+ alignedEndAddress += BufferAlignmentSize;
+ }
+
+ CreateBuffer(alignedAddress, alignedEndAddress - alignedAddress);
+
+ return address;
+ }
+
+ private void CreateBuffer(ulong address, ulong size)
+ {
+ Buffer[] overlaps = _buffers.FindOverlaps(address, size);
+
+ if (overlaps.Length != 0)
+ {
+ // The buffer already exists. We can just return the existing buffer
+ // if the buffer we need is fully contained inside the overlapping buffer.
+ // Otherwise, we must delete the overlapping buffers and create a bigger buffer
+ // that fits all the data we need. We also need to copy the contents from the
+ // old buffer(s) to the new buffer.
+ ulong endAddress = address + size;
+
+ if (overlaps[0].Address > address || overlaps[0].EndAddress < endAddress)
+ {
+ foreach (Buffer buffer in overlaps)
+ {
+ address = Math.Min(address, buffer.Address);
+ endAddress = Math.Max(endAddress, buffer.EndAddress);
+
+ buffer.SynchronizeMemory(buffer.Address, buffer.Size);
+
+ _buffers.Remove(buffer);
+ }
+
+ Buffer newBuffer = new Buffer(_context, address, endAddress - address);
+
+ _buffers.Add(newBuffer);
+
+ foreach (Buffer buffer in overlaps)
+ {
+ int dstOffset = (int)(buffer.Address - newBuffer.Address);
+
+ buffer.CopyTo(newBuffer, dstOffset);
+
+ buffer.Dispose();
+ }
+
+ _rebind = true;
+ }
+ }
+ else
+ {
+ // No overlap, just create a new buffer.
+ Buffer buffer = new Buffer(_context, address, size);
+
+ _buffers.Add(buffer);
+ }
+ }
+
+ public ulong GetComputeUniformBufferAddress(int index)
+ {
+ return _cpUniformBuffers.Buffers[index].Address;
+ }
+
+ public ulong GetGraphicsUniformBufferAddress(int stage, int index)
+ {
+ return _gpUniformBuffers[stage].Buffers[index].Address;
+ }
+
+ public void CommitComputeBindings()
+ {
+ uint enableMask = _cpStorageBuffers.EnableMask;
+
+ for (int index = 0; (enableMask >> index) != 0; index++)
+ {
+ if ((enableMask & (1u << index)) == 0)
+ {
+ continue;
+ }
+
+ BufferBounds bounds = _cpStorageBuffers.Buffers[index];
+
+ if (bounds.Address == 0)
+ {
+ continue;
+ }
+
+ BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
+
+ _context.Renderer.ComputePipeline.SetStorageBuffer(index, buffer);
+ }
+
+ enableMask = _cpUniformBuffers.EnableMask;
+
+ for (int index = 0; (enableMask >> index) != 0; index++)
+ {
+ if ((enableMask & (1u << index)) == 0)
+ {
+ continue;
+ }
+
+ BufferBounds bounds = _cpUniformBuffers.Buffers[index];
+
+ if (bounds.Address == 0)
+ {
+ continue;
+ }
+
+ BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
+
+ _context.Renderer.ComputePipeline.SetUniformBuffer(index, buffer);
+
+ if (index == 0)
+ {
+ // TODO: Improve
+ Span<byte> data = _context.PhysicalMemory.Read(bounds.Address + 0x310, 0x100);
+
+ Span<int> words = MemoryMarshal.Cast<byte, int>(data);
+
+ for (int offset = 0; offset < 0x40; offset += 4)
+ {
+ words[offset] &= 0x3f;
+ }
+
+ buffer = GetBufferRange(bounds.Address + 0x310, 0x100);
+
+ buffer.Buffer.SetData(buffer.Offset, data);
+ }
+ }
+ }
+
+ public void CommitBindings()
+ {
+ if (_indexBufferDirty || _rebind)
+ {
+ _indexBufferDirty = false;
+
+ if (_indexBuffer.Address != 0)
+ {
+ BufferRange buffer = GetBufferRange(_indexBuffer.Address, _indexBuffer.Size);
+
+ _context.Renderer.GraphicsPipeline.BindIndexBuffer(buffer, _indexBuffer.Type);
+ }
+ }
+ else if (_indexBuffer.Address != 0)
+ {
+ SynchronizeBufferRange(_indexBuffer.Address, _indexBuffer.Size);
+ }
+
+ if (_vertexBuffersDirty || _rebind)
+ {
+ _vertexBuffersDirty = false;
+
+ VertexBufferDescriptor[] vertexBuffers = new VertexBufferDescriptor[Constants.TotalVertexBuffers];
+
+ for (int index = 0; index < Constants.TotalVertexBuffers; index++)
+ {
+ VertexBuffer vb = _vertexBuffers[index];
+
+ if (vb.Address == 0)
+ {
+ continue;
+ }
+
+ BufferRange buffer = GetBufferRange(vb.Address, vb.Size);
+
+ vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
+ }
+
+ _context.Renderer.GraphicsPipeline.BindVertexBuffers(vertexBuffers);
+ }
+ else
+ {
+ for (int index = 0; index < Constants.TotalVertexBuffers; index++)
+ {
+ VertexBuffer vb = _vertexBuffers[index];
+
+ if (vb.Address == 0)
+ {
+ continue;
+ }
+
+ SynchronizeBufferRange(vb.Address, vb.Size);
+ }
+ }
+
+ if (_gpStorageBuffersDirty || _rebind)
+ {
+ _gpStorageBuffersDirty = false;
+
+ BindBuffers(_gpStorageBuffers, isStorage: true);
+ }
+ else
+ {
+ UpdateBuffers(_gpStorageBuffers);
+ }
+
+ if (_gpUniformBuffersDirty || _rebind)
+ {
+ _gpUniformBuffersDirty = false;
+
+ BindBuffers(_gpUniformBuffers, isStorage: false);
+ }
+ else
+ {
+ UpdateBuffers(_gpUniformBuffers);
+ }
+
+ _rebind = false;
+ }
+
+ private void BindBuffers(BuffersPerStage[] bindings, bool isStorage)
+ {
+ BindOrUpdateBuffers(bindings, bind: true, isStorage);
+ }
+
+ private void UpdateBuffers(BuffersPerStage[] bindings)
+ {
+ BindOrUpdateBuffers(bindings, bind: false);
+ }
+
+ private void BindOrUpdateBuffers(BuffersPerStage[] bindings, bool bind, bool isStorage = false)
+ {
+ for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
+ {
+ uint enableMask = bindings[(int)stage - 1].EnableMask;
+
+ if (enableMask == 0)
+ {
+ continue;
+ }
+
+ for (int index = 0; (enableMask >> index) != 0; index++)
+ {
+ if ((enableMask & (1u << index)) == 0)
+ {
+ continue;
+ }
+
+ BufferBounds bounds = bindings[(int)stage - 1].Buffers[index];
+
+ if (bounds.Address == 0)
+ {
+ continue;
+ }
+
+ if (bind)
+ {
+ BindBuffer(index, stage, bounds, isStorage);
+ }
+ else
+ {
+ SynchronizeBufferRange(bounds.Address, bounds.Size);
+ }
+ }
+ }
+ }
+
+ private void BindBuffer(int index, ShaderStage stage, BufferBounds bounds, bool isStorage)
+ {
+ BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
+
+ BufferRange[] buffers = new BufferRange[] { buffer };
+
+ if (isStorage)
+ {
+ _context.Renderer.GraphicsPipeline.BindStorageBuffers(index, stage, buffers);
+ }
+ else
+ {
+ _context.Renderer.GraphicsPipeline.BindUniformBuffers(index, stage, buffers);
+ }
+
+ if (!isStorage && index == 0)
+ {
+ // TODO: Improve
+ Span<byte> data = _context.PhysicalMemory.Read(bounds.Address + 0x110, 0x100);
+
+ Span<int> words = MemoryMarshal.Cast<byte, int>(data);
+
+ for (int offset = 0; offset < 0x40; offset += 4)
+ {
+ words[offset] &= 0x3f;
+ }
+
+ buffer = GetBufferRange(bounds.Address + 0x110, 0x100);
+
+ buffer.Buffer.SetData(buffer.Offset, data);
+ }
+ }
+
+ public void CopyBuffer(GpuVa srcVa, GpuVa dstVa, ulong size)
+ {
+ ulong srcAddress = TranslateAndCreateBuffer(srcVa.Pack(), size);
+ ulong dstAddress = TranslateAndCreateBuffer(dstVa.Pack(), size);
+
+ BufferRange srcBuffer = GetBufferRange(srcAddress, size);
+ BufferRange dstBuffer = GetBufferRange(dstAddress, size);
+
+ srcBuffer.Buffer.CopyTo(
+ dstBuffer.Buffer,
+ srcBuffer.Offset,
+ dstBuffer.Offset,
+ (int)size);
+ }
+
+ private BufferRange GetBufferRange(ulong address, ulong size)
+ {
+ Buffer buffer;
+
+ if (size != 0)
+ {
+ buffer = _buffers.FindFirstOverlap(address, size);
+
+ buffer.SynchronizeMemory(address, size);
+ }
+ else
+ {
+ buffer = _buffers.FindFirstOverlap(address, 1);
+ }
+
+ return buffer.GetRange(address, size);
+ }
+
+ private void SynchronizeBufferRange(ulong address, ulong size)
+ {
+ if (size != 0)
+ {
+ Buffer buffer = _buffers.FindFirstOverlap(address, size);
+
+ buffer.SynchronizeMemory(address, size);
+ }
+ }
+
+ public void InvalidateRange(ulong address, ulong size)
+ {
+ Buffer[] overlappingBuffers = _buffers.FindOverlaps(address, size);
+
+ foreach (Buffer buffer in overlappingBuffers)
+ {
+ buffer.Invalidate();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/IPhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/IPhysicalMemory.cs
new file mode 100644
index 00000000..5f21704d
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/IPhysicalMemory.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ public interface IPhysicalMemory
+ {
+ int GetPageSize();
+
+ Span<byte> Read(ulong address, ulong size);
+
+ void Write(ulong address, Span<byte> data);
+
+ (ulong, ulong)[] GetModifiedRanges(ulong address, ulong size);
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/IRange.cs b/Ryujinx.Graphics.Gpu/Memory/IRange.cs
new file mode 100644
index 00000000..ee3d5c0b
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/IRange.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ interface IRange<T>
+ {
+ ulong Address { get; }
+ ulong Size { get; }
+
+ bool OverlapsWith(ulong address, ulong size);
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs b/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs
new file mode 100644
index 00000000..ce2a2c74
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs
@@ -0,0 +1,12 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ struct IndexBuffer
+ {
+ public ulong Address;
+ public ulong Size;
+
+ public IndexType Type;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs
new file mode 100644
index 00000000..500c36e5
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ class MemoryAccessor
+ {
+ private GpuContext _context;
+
+ public MemoryAccessor(GpuContext context)
+ {
+ _context = context;
+ }
+
+ public Span<byte> Read(ulong gpuVa, ulong maxSize)
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ ulong size = Math.Min(_context.MemoryManager.GetSubSize(gpuVa), maxSize);
+
+ return _context.PhysicalMemory.Read(processVa, size);
+ }
+
+ public T Read<T>(ulong gpuVa) where T : struct
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ ulong size = (uint)Marshal.SizeOf<T>();
+
+ return MemoryMarshal.Cast<byte, T>(_context.PhysicalMemory.Read(processVa, size))[0];
+ }
+
+ public int ReadInt32(ulong gpuVa)
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ return BitConverter.ToInt32(_context.PhysicalMemory.Read(processVa, 4));
+ }
+
+ public void Write(ulong gpuVa, int value)
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ _context.PhysicalMemory.Write(processVa, BitConverter.GetBytes(value));
+ }
+
+ public void Write(ulong gpuVa, Span<byte> data)
+ {
+ ulong processVa = _context.MemoryManager.Translate(gpuVa);
+
+ _context.PhysicalMemory.Write(processVa, data);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
new file mode 100644
index 00000000..d1a3e69c
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
@@ -0,0 +1,265 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ public class MemoryManager
+ {
+ private const ulong AddressSpaceSize = 1UL << 40;
+
+ public const ulong BadAddress = ulong.MaxValue;
+
+ private const int PtLvl0Bits = 14;
+ private const int PtLvl1Bits = 14;
+ private const int PtPageBits = 12;
+
+ private const ulong PtLvl0Size = 1UL << PtLvl0Bits;
+ private const ulong PtLvl1Size = 1UL << PtLvl1Bits;
+ public const ulong PageSize = 1UL << PtPageBits;
+
+ private const ulong PtLvl0Mask = PtLvl0Size - 1;
+ private const ulong PtLvl1Mask = PtLvl1Size - 1;
+ public const ulong PageMask = PageSize - 1;
+
+ private const int PtLvl0Bit = PtPageBits + PtLvl1Bits;
+ private const int PtLvl1Bit = PtPageBits;
+
+ private const ulong PteUnmapped = 0xffffffff_ffffffff;
+ private const ulong PteReserved = 0xffffffff_fffffffe;
+
+ private ulong[][] _pageTable;
+
+ public MemoryManager()
+ {
+ _pageTable = new ulong[PtLvl0Size][];
+ }
+
+ public ulong Map(ulong pa, ulong va, ulong size)
+ {
+ lock (_pageTable)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, pa + offset);
+ }
+ }
+
+ return va;
+ }
+
+ public ulong Map(ulong pa, ulong size)
+ {
+ lock (_pageTable)
+ {
+ ulong va = GetFreePosition(size);
+
+ if (va != PteUnmapped)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, pa + offset);
+ }
+ }
+
+ return va;
+ }
+ }
+
+ public ulong MapLow(ulong pa, ulong size)
+ {
+ lock (_pageTable)
+ {
+ ulong va = GetFreePosition(size, 1, PageSize);
+
+ if (va != PteUnmapped && va <= uint.MaxValue && (va + size) <= uint.MaxValue)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, pa + offset);
+ }
+ }
+ else
+ {
+ va = PteUnmapped;
+ }
+
+ return va;
+ }
+ }
+
+ public ulong ReserveFixed(ulong va, ulong size)
+ {
+ lock (_pageTable)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ if (IsPageInUse(va + offset))
+ {
+ return PteUnmapped;
+ }
+ }
+
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, PteReserved);
+ }
+ }
+
+ return va;
+ }
+
+ public ulong Reserve(ulong size, ulong alignment)
+ {
+ lock (_pageTable)
+ {
+ ulong address = GetFreePosition(size, alignment);
+
+ if (address != PteUnmapped)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(address + offset, PteReserved);
+ }
+ }
+
+ return address;
+ }
+ }
+
+ public void Free(ulong va, ulong size)
+ {
+ lock (_pageTable)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ SetPte(va + offset, PteUnmapped);
+ }
+ }
+ }
+
+ private ulong GetFreePosition(ulong size, ulong alignment = 1, ulong start = 1UL << 32)
+ {
+ // Note: Address 0 is not considered valid by the driver,
+ // when 0 is returned it's considered a mapping error.
+ ulong address = start;
+ ulong freeSize = 0;
+
+ if (alignment == 0)
+ {
+ alignment = 1;
+ }
+
+ alignment = (alignment + PageMask) & ~PageMask;
+
+ while (address + freeSize < AddressSpaceSize)
+ {
+ if (!IsPageInUse(address + freeSize))
+ {
+ freeSize += PageSize;
+
+ if (freeSize >= size)
+ {
+ return address;
+ }
+ }
+ else
+ {
+ address += freeSize + PageSize;
+ freeSize = 0;
+
+ ulong remainder = address % alignment;
+
+ if (remainder != 0)
+ {
+ address = (address - remainder) + alignment;
+ }
+ }
+ }
+
+ return PteUnmapped;
+ }
+
+ internal ulong GetSubSize(ulong gpuVa)
+ {
+ ulong size = 0;
+
+ while (GetPte(gpuVa + size) != PteUnmapped)
+ {
+ size += PageSize;
+ }
+
+ return size;
+ }
+
+ internal ulong Translate(ulong gpuVa)
+ {
+ ulong baseAddress = GetPte(gpuVa);
+
+ if (baseAddress == PteUnmapped || baseAddress == PteReserved)
+ {
+ return PteUnmapped;
+ }
+
+ return baseAddress + (gpuVa & PageMask);
+ }
+
+ public bool IsRegionFree(ulong va, ulong size)
+ {
+ for (ulong offset = 0; offset < size; offset += PageSize)
+ {
+ if (IsPageInUse(va + offset))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private bool IsPageInUse(ulong va)
+ {
+ if (va >> PtLvl0Bits + PtLvl1Bits + PtPageBits != 0)
+ {
+ return false;
+ }
+
+ ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask;
+ ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask;
+
+ if (_pageTable[l0] == null)
+ {
+ return false;
+ }
+
+ return _pageTable[l0][l1] != PteUnmapped;
+ }
+
+ private ulong GetPte(ulong address)
+ {
+ ulong l0 = (address >> PtLvl0Bit) & PtLvl0Mask;
+ ulong l1 = (address >> PtLvl1Bit) & PtLvl1Mask;
+
+ if (_pageTable[l0] == null)
+ {
+ return PteUnmapped;
+ }
+
+ return _pageTable[l0][l1];
+ }
+
+ private void SetPte(ulong address, ulong tgtAddr)
+ {
+ ulong l0 = (address >> PtLvl0Bit) & PtLvl0Mask;
+ ulong l1 = (address >> PtLvl1Bit) & PtLvl1Mask;
+
+ if (_pageTable[l0] == null)
+ {
+ _pageTable[l0] = new ulong[PtLvl1Size];
+
+ for (ulong index = 0; index < PtLvl1Size; index++)
+ {
+ _pageTable[l0][index] = PteUnmapped;
+ }
+ }
+
+ _pageTable[l0][l1] = tgtAddr;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/RangeList.cs b/Ryujinx.Graphics.Gpu/Memory/RangeList.cs
new file mode 100644
index 00000000..6114f15d
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/RangeList.cs
@@ -0,0 +1,208 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ class RangeList<T> where T : IRange<T>
+ {
+ private List<T> _items;
+
+ public RangeList()
+ {
+ _items = new List<T>();
+ }
+
+ public void Add(T item)
+ {
+ lock (_items)
+ {
+ int index = BinarySearch(item.Address);
+
+ if (index < 0)
+ {
+ index = ~index;
+ }
+
+ _items.Insert(index, item);
+ }
+ }
+
+ public bool Remove(T item)
+ {
+ lock (_items)
+ {
+ int index = BinarySearch(item.Address);
+
+ if (index >= 0)
+ {
+ while (index > 0 && _items[index - 1].Address == item.Address)
+ {
+ index--;
+ }
+
+ while (index < _items.Count)
+ {
+ if (_items[index].Equals(item))
+ {
+ _items.RemoveAt(index);
+
+ return true;
+ }
+
+ if (_items[index].Address > item.Address)
+ {
+ break;
+ }
+
+ index++;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public T FindFirstOverlap(T item)
+ {
+ return FindFirstOverlap(item.Address, item.Size);
+ }
+
+ public T FindFirstOverlap(ulong address, ulong size)
+ {
+ lock (_items)
+ {
+ int index = BinarySearch(address, size);
+
+ if (index < 0)
+ {
+ return default(T);
+ }
+
+ return _items[index];
+ }
+ }
+
+ public T[] FindOverlaps(T item)
+ {
+ return FindOverlaps(item.Address, item.Size);
+ }
+
+ public T[] FindOverlaps(ulong address, ulong size)
+ {
+ List<T> overlapsList = new List<T>();
+
+ ulong endAddress = address + size;
+
+ lock (_items)
+ {
+ foreach (T item in _items)
+ {
+ if (item.Address >= endAddress)
+ {
+ break;
+ }
+
+ if (item.OverlapsWith(address, size))
+ {
+ overlapsList.Add(item);
+ }
+ }
+ }
+
+ return overlapsList.ToArray();
+ }
+
+ public T[] FindOverlaps(ulong address)
+ {
+ List<T> overlapsList = new List<T>();
+
+ lock (_items)
+ {
+ int index = BinarySearch(address);
+
+ if (index >= 0)
+ {
+ while (index > 0 && _items[index - 1].Address == address)
+ {
+ index--;
+ }
+
+ while (index < _items.Count)
+ {
+ T overlap = _items[index++];
+
+ if (overlap.Address != address)
+ {
+ break;
+ }
+
+ overlapsList.Add(overlap);
+ }
+ }
+ }
+
+ return overlapsList.ToArray();
+ }
+
+ private int BinarySearch(ulong address)
+ {
+ int left = 0;
+ int right = _items.Count - 1;
+
+ while (left <= right)
+ {
+ int range = right - left;
+
+ int middle = left + (range >> 1);
+
+ T item = _items[middle];
+
+ if (item.Address == address)
+ {
+ return middle;
+ }
+
+ if (address < item.Address)
+ {
+ right = middle - 1;
+ }
+ else
+ {
+ left = middle + 1;
+ }
+ }
+
+ return ~left;
+ }
+
+ private int BinarySearch(ulong address, ulong size)
+ {
+ int left = 0;
+ int right = _items.Count - 1;
+
+ while (left <= right)
+ {
+ int range = right - left;
+
+ int middle = left + (range >> 1);
+
+ T item = _items[middle];
+
+ if (item.OverlapsWith(address, size))
+ {
+ return middle;
+ }
+
+ if (address < item.Address)
+ {
+ right = middle - 1;
+ }
+ else
+ {
+ left = middle + 1;
+ }
+ }
+
+ return ~left;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs b/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs
new file mode 100644
index 00000000..1cb854d6
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ struct VertexBuffer
+ {
+ public ulong Address;
+ public ulong Size;
+ public int Stride;
+ public int Divisor;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/MethodParams.cs b/Ryujinx.Graphics.Gpu/MethodParams.cs
new file mode 100644
index 00000000..fea4eb30
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/MethodParams.cs
@@ -0,0 +1,24 @@
+namespace Ryujinx.Graphics
+{
+ struct MethodParams
+ {
+ public int Method { get; private set; }
+ public int Argument { get; private set; }
+ public int SubChannel { get; private set; }
+ public int MethodCount { get; private set; }
+
+ public bool IsLastCall => MethodCount <= 1;
+
+ public MethodParams(
+ int method,
+ int argument,
+ int subChannel = 0,
+ int methodCount = 0)
+ {
+ Method = method;
+ Argument = argument;
+ SubChannel = subChannel;
+ MethodCount = methodCount;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/NvGpuFifo.cs b/Ryujinx.Graphics.Gpu/NvGpuFifo.cs
new file mode 100644
index 00000000..f4deea54
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/NvGpuFifo.cs
@@ -0,0 +1,150 @@
+namespace Ryujinx.Graphics.Gpu
+{
+ class NvGpuFifo
+ {
+ private const int MacrosCount = 0x80;
+ private const int MacroIndexMask = MacrosCount - 1;
+
+ // 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 MmeWords = 256 * 256;
+
+ private GpuContext _context;
+
+ private struct CachedMacro
+ {
+ public int Position { get; private set; }
+
+ private bool _executionPending;
+ private int _argument;
+
+ private MacroInterpreter _interpreter;
+
+ public CachedMacro(GpuContext context, NvGpuFifo fifo, int position)
+ {
+ Position = position;
+
+ _executionPending = false;
+ _argument = 0;
+
+ _interpreter = new MacroInterpreter(context, fifo);
+ }
+
+ public void StartExecution(int argument)
+ {
+ _argument = argument;
+
+ _executionPending = true;
+ }
+
+ public void Execute(int[] mme)
+ {
+ if (_executionPending)
+ {
+ _executionPending = false;
+
+ _interpreter?.Execute(mme, Position, _argument);
+ }
+ }
+
+ public void PushArgument(int argument)
+ {
+ _interpreter?.Fifo.Enqueue(argument);
+ }
+ }
+
+ private int _currMacroPosition;
+ private int _currMacroBindIndex;
+
+ private CachedMacro[] _macros;
+
+ private int[] _mme;
+
+ private ClassId[] _subChannels;
+
+ public NvGpuFifo(GpuContext context)
+ {
+ _context = context;
+
+ _macros = new CachedMacro[MacrosCount];
+
+ _mme = new int[MmeWords];
+
+ _subChannels = new ClassId[8];
+ }
+
+ public void CallMethod(MethodParams meth)
+ {
+ if ((NvGpuFifoMeth)meth.Method == NvGpuFifoMeth.BindChannel)
+ {
+ _subChannels[meth.SubChannel] = (ClassId)meth.Argument;
+ }
+ else if (meth.Method < 0x60)
+ {
+ switch ((NvGpuFifoMeth)meth.Method)
+ {
+ case NvGpuFifoMeth.WaitForIdle:
+ {
+ _context.Renderer.FlushPipelines();
+
+ break;
+ }
+
+ case NvGpuFifoMeth.SetMacroUploadAddress:
+ {
+ _currMacroPosition = meth.Argument;
+
+ break;
+ }
+
+ case NvGpuFifoMeth.SendMacroCodeData:
+ {
+ _mme[_currMacroPosition++] = meth.Argument;
+
+ break;
+ }
+
+ case NvGpuFifoMeth.SetMacroBindingIndex:
+ {
+ _currMacroBindIndex = meth.Argument;
+
+ break;
+ }
+
+ case NvGpuFifoMeth.BindMacro:
+ {
+ int position = meth.Argument;
+
+ _macros[_currMacroBindIndex++] = new CachedMacro(_context, this, position);
+
+ break;
+ }
+ }
+ }
+ else if (meth.Method < 0xe00)
+ {
+ _context.State.CallMethod(meth);
+ }
+ else
+ {
+ int macroIndex = (meth.Method >> 1) & MacroIndexMask;
+
+ if ((meth.Method & 1) != 0)
+ {
+ _macros[macroIndex].PushArgument(meth.Argument);
+ }
+ else
+ {
+ _macros[macroIndex].StartExecution(meth.Argument);
+ }
+
+ if (meth.IsLastCall)
+ {
+ _macros[macroIndex].Execute(_mme);
+
+ _context.Methods.PerformDeferredDraws();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/NvGpuFifoMeth.cs b/Ryujinx.Graphics.Gpu/NvGpuFifoMeth.cs
new file mode 100644
index 00000000..09aaa6c3
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/NvGpuFifoMeth.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gpu
+{
+ enum NvGpuFifoMeth
+ {
+ BindChannel = 0,
+ WaitForIdle = 0x44,
+ SetMacroUploadAddress = 0x45,
+ SendMacroCodeData = 0x46,
+ SetMacroBindingIndex = 0x47,
+ BindMacro = 0x48
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj b/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
new file mode 100644
index 00000000..88761ddf
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <ItemGroup>
+ <ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
+ <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
+ <ProjectReference Include="..\Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj" />
+ <ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
+ </ItemGroup>
+
+ <PropertyGroup>
+ <TargetFramework>netcoreapp3.0</TargetFramework>
+ </PropertyGroup>
+
+</Project>
diff --git a/Ryujinx.Graphics.Gpu/State/BlendState.cs b/Ryujinx.Graphics.Gpu/State/BlendState.cs
new file mode 100644
index 00000000..cf22dc1b
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/BlendState.cs
@@ -0,0 +1,15 @@
+using Ryujinx.Graphics.GAL.Blend;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct BlendState
+ {
+ public Bool SeparateAlpha;
+ public BlendOp ColorOp;
+ public BlendFactor ColorSrcFactor;
+ public BlendFactor ColorDstFactor;
+ public BlendOp AlphaOp;
+ public BlendFactor AlphaSrcFactor;
+ public BlendFactor AlphaDstFactor;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/Bool.cs b/Ryujinx.Graphics.Gpu/State/Bool.cs
new file mode 100644
index 00000000..8aadcfcc
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/Bool.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct Bool
+ {
+ private uint _value;
+
+ public bool IsTrue()
+ {
+ return (_value & 1) != 0;
+ }
+
+ public bool IsFalse()
+ {
+ return (_value & 1) == 0;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/ClearColors.cs b/Ryujinx.Graphics.Gpu/State/ClearColors.cs
new file mode 100644
index 00000000..584c4791
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ClearColors.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct ClearColors
+ {
+ public float Red;
+ public float Green;
+ public float Blue;
+ public float Alpha;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/Condition.cs b/Ryujinx.Graphics.Gpu/State/Condition.cs
new file mode 100644
index 00000000..41cce519
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/Condition.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ enum Condition
+ {
+ Never,
+ Always,
+ ResultNonZero,
+ Equal,
+ NotEqual
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/ConditionState.cs b/Ryujinx.Graphics.Gpu/State/ConditionState.cs
new file mode 100644
index 00000000..24302c60
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ConditionState.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct ConditionState
+ {
+ public GpuVa Address;
+ public Condition Condition;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/CopyBufferParams.cs b/Ryujinx.Graphics.Gpu/State/CopyBufferParams.cs
new file mode 100644
index 00000000..2876dedd
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/CopyBufferParams.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct CopyBufferParams
+ {
+ public GpuVa SrcAddress;
+ public GpuVa DstAddress;
+ public int SrcStride;
+ public int DstStride;
+ public int XCount;
+ public int YCount;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/CopyBufferSwizzle.cs b/Ryujinx.Graphics.Gpu/State/CopyBufferSwizzle.cs
new file mode 100644
index 00000000..24071372
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/CopyBufferSwizzle.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct CopyBufferSwizzle
+ {
+ public uint Swizzle;
+
+ public int UnpackComponentSize()
+ {
+ return (int)((Swizzle >> 16) & 3) + 1;
+ }
+
+ public int UnpackSrcComponentsCount()
+ {
+ return (int)((Swizzle >> 20) & 7) + 1;
+ }
+
+ public int UnpackDstComponentsCount()
+ {
+ return (int)((Swizzle >> 24) & 7) + 1;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/CopyBufferTexture.cs b/Ryujinx.Graphics.Gpu/State/CopyBufferTexture.cs
new file mode 100644
index 00000000..cb63c607
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/CopyBufferTexture.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct CopyBufferTexture
+ {
+ public MemoryLayout MemoryLayout;
+ public int Width;
+ public int Height;
+ public int Depth;
+ public int RegionZ;
+ public ushort RegionX;
+ public ushort RegionY;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/CopyRegion.cs b/Ryujinx.Graphics.Gpu/State/CopyRegion.cs
new file mode 100644
index 00000000..cad9a5db
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/CopyRegion.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct CopyRegion
+ {
+ public int DstX;
+ public int DstY;
+ public int DstWidth;
+ public int DstHeight;
+ public long SrcWidthRF;
+ public long SrcHeightRF;
+ public long SrcXF;
+ public long SrcYF;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/CopyTexture.cs b/Ryujinx.Graphics.Gpu/State/CopyTexture.cs
new file mode 100644
index 00000000..363d84a2
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/CopyTexture.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct CopyTexture
+ {
+ public RtFormat Format;
+ public bool LinearLayout;
+ public MemoryLayout MemoryLayout;
+ public int Depth;
+ public int Layer;
+ public int Stride;
+ public int Width;
+ public int Height;
+ public GpuVa Address;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/CopyTextureControl.cs b/Ryujinx.Graphics.Gpu/State/CopyTextureControl.cs
new file mode 100644
index 00000000..c49cf276
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/CopyTextureControl.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct CopyTextureControl
+ {
+ public uint Packed;
+
+ public bool UnpackLinearFilter()
+ {
+ return (Packed & (1u << 4)) != 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/DepthBiasState.cs b/Ryujinx.Graphics.Gpu/State/DepthBiasState.cs
new file mode 100644
index 00000000..c88d27dd
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/DepthBiasState.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct DepthBiasState
+ {
+ public Bool PointEnable;
+ public Bool LineEnable;
+ public Bool FillEnable;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/FaceState.cs b/Ryujinx.Graphics.Gpu/State/FaceState.cs
new file mode 100644
index 00000000..53763032
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/FaceState.cs
@@ -0,0 +1,11 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct FaceState
+ {
+ public Bool CullEnable;
+ public FrontFace FrontFace;
+ public Face CullFace;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/GpuState.cs b/Ryujinx.Graphics.Gpu/State/GpuState.cs
new file mode 100644
index 00000000..4e4241ca
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/GpuState.cs
@@ -0,0 +1,425 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ class GpuState
+ {
+ private const int RegistersCount = 0xe00;
+
+ public delegate void MethodCallback(int argument);
+
+ private int[] _backingMemory;
+
+ private struct Register
+ {
+ public MethodCallback Callback;
+
+ public StateWriteFlags WriteFlag;
+ }
+
+ private Register[] _registers;
+
+ public StateWriteFlags StateWriteFlags { get; set; }
+
+ public GpuState()
+ {
+ _backingMemory = new int[RegistersCount];
+
+ _registers = new Register[RegistersCount];
+
+ StateWriteFlags = StateWriteFlags.Any;
+
+ InitializeDefaultState();
+ InitializeStateWatchers();
+ }
+
+ public bool ExitEarly;
+
+ public void CallMethod(MethodParams meth)
+ {
+ if (ExitEarly)
+ {
+ return;
+ }
+
+ Register register = _registers[meth.Method];
+
+ if (_backingMemory[meth.Method] != meth.Argument)
+ {
+ StateWriteFlags |= register.WriteFlag;
+ }
+
+ _backingMemory[meth.Method] = meth.Argument;
+
+ MethodCallback callback = register.Callback;
+
+ if (callback != null)
+ {
+ callback(meth.Argument);
+ }
+ }
+
+ public int Read(int offset)
+ {
+ return _backingMemory[offset];
+ }
+
+ public void RegisterCopyBufferCallback(MethodCallback callback)
+ {
+ RegisterCallback(0xc0, callback);
+ }
+
+ public void RegisterCopyTextureCallback(MethodCallback callback)
+ {
+ RegisterCallback(0x237, callback);
+ }
+
+ public void RegisterDrawEndCallback(MethodCallback callback)
+ {
+ RegisterCallback(0x585, callback);
+ }
+
+ public void RegisterDrawBeginCallback(MethodCallback callback)
+ {
+ RegisterCallback(0x586, callback);
+ }
+
+ public void RegisterSetIndexCountCallback(MethodCallback callback)
+ {
+ RegisterCallback(0x5f8, callback);
+ }
+
+ public void RegisterClearCallback(MethodCallback callback)
+ {
+ RegisterCallback(0x674, callback);
+ }
+
+ public void RegisterReportCallback(MethodCallback callback)
+ {
+ RegisterCallback(0x6c3, callback);
+ }
+
+ public void RegisterUniformBufferUpdateCallback(MethodCallback callback)
+ {
+ for (int index = 0; index < 16; index++)
+ {
+ RegisterCallback(0x8e4 + index, callback);
+ }
+ }
+
+ public void RegisterUniformBufferBind0Callback(MethodCallback callback)
+ {
+ RegisterCallback(0x904, callback);
+ }
+
+ public void RegisterUniformBufferBind1Callback(MethodCallback callback)
+ {
+ RegisterCallback(0x90c, callback);
+ }
+
+ public void RegisterUniformBufferBind2Callback(MethodCallback callback)
+ {
+ RegisterCallback(0x914, callback);
+ }
+
+ public void RegisterUniformBufferBind3Callback(MethodCallback callback)
+ {
+ RegisterCallback(0x91c, callback);
+ }
+
+ public void RegisterUniformBufferBind4Callback(MethodCallback callback)
+ {
+ RegisterCallback(0x924, callback);
+ }
+
+ public CopyTexture GetCopyDstTexture()
+ {
+ return Get<CopyTexture>(MethodOffset.CopyDstTexture);
+ }
+
+ public CopyTexture GetCopySrcTexture()
+ {
+ return Get<CopyTexture>(MethodOffset.CopySrcTexture);
+ }
+
+ public RtColorState GetRtColorState(int index)
+ {
+ return Get<RtColorState>(MethodOffset.RtColorState + 16 * index);
+ }
+
+ public CopyTextureControl GetCopyTextureControl()
+ {
+ return Get<CopyTextureControl>(MethodOffset.CopyTextureControl);
+ }
+
+ public CopyRegion GetCopyRegion()
+ {
+ return Get<CopyRegion>(MethodOffset.CopyRegion);
+ }
+
+ public ViewportTransform GetViewportTransform(int index)
+ {
+ return Get<ViewportTransform>(MethodOffset.ViewportTransform + 8 * index);
+ }
+
+ public ViewportExtents GetViewportExtents(int index)
+ {
+ return Get<ViewportExtents>(MethodOffset.ViewportExtents + 4 * index);
+ }
+
+ public VertexBufferDrawState GetVertexBufferDrawState()
+ {
+ return Get<VertexBufferDrawState>(MethodOffset.VertexBufferDrawState);
+ }
+
+ public ClearColors GetClearColors()
+ {
+ return Get<ClearColors>(MethodOffset.ClearColors);
+ }
+
+ public float GetClearDepthValue()
+ {
+ return Get<float>(MethodOffset.ClearDepthValue);
+ }
+
+ public int GetClearStencilValue()
+ {
+ return _backingMemory[(int)MethodOffset.ClearStencilValue];
+ }
+
+ public StencilBackMasks GetStencilBackMasks()
+ {
+ return Get<StencilBackMasks>(MethodOffset.StencilBackMasks);
+ }
+
+ public RtDepthStencilState GetRtDepthStencilState()
+ {
+ return Get<RtDepthStencilState>(MethodOffset.RtDepthStencilState);
+ }
+
+ public VertexAttribState GetVertexAttribState(int index)
+ {
+ return Get<VertexAttribState>(MethodOffset.VertexAttribState + index);
+ }
+
+ public Size3D GetRtDepthStencilSize()
+ {
+ return Get<Size3D>(MethodOffset.RtDepthStencilSize);
+ }
+
+ public Bool GetDepthTestEnable()
+ {
+ return Get<Bool>(MethodOffset.DepthTestEnable);
+ }
+
+ public CompareOp GetDepthTestFunc()
+ {
+ return Get<CompareOp>(MethodOffset.DepthTestFunc);
+ }
+
+ public Bool GetDepthWriteEnable()
+ {
+ return Get<Bool>(MethodOffset.DepthWriteEnable);
+ }
+
+ public Bool GetBlendEnable(int index)
+ {
+ return Get<Bool>(MethodOffset.BlendEnable + index);
+ }
+
+ public StencilTestState GetStencilTestState()
+ {
+ return Get<StencilTestState>(MethodOffset.StencilTestState);
+ }
+
+ public int GetBaseVertex()
+ {
+ return _backingMemory[(int)MethodOffset.FirstVertex];
+ }
+
+ public int GetBaseInstance()
+ {
+ return _backingMemory[(int)MethodOffset.FirstInstance];
+ }
+
+ public PoolState GetSamplerPoolState()
+ {
+ return Get<PoolState>(MethodOffset.SamplerPoolState);
+ }
+
+ public PoolState GetTexturePoolState()
+ {
+ return Get<PoolState>(MethodOffset.TexturePoolState);
+ }
+
+ public StencilBackTestState GetStencilBackTestState()
+ {
+ return Get<StencilBackTestState>(MethodOffset.StencilBackTestState);
+ }
+
+ public TextureMsaaMode GetRtMsaaMode()
+ {
+ return Get<TextureMsaaMode>(MethodOffset.RtMsaaMode);
+ }
+
+ public GpuVa GetShaderBaseAddress()
+ {
+ return Get<GpuVa>(MethodOffset.ShaderBaseAddress);
+ }
+
+ public PrimitiveRestartState GetPrimitiveRestartState()
+ {
+ return Get<PrimitiveRestartState>(MethodOffset.PrimitiveRestartState);
+ }
+
+ public IndexBufferState GetIndexBufferState()
+ {
+ return Get<IndexBufferState>(MethodOffset.IndexBufferState);
+ }
+
+ public FaceState GetFaceState()
+ {
+ return Get<FaceState>(MethodOffset.FaceState);
+ }
+
+ public ReportState GetReportState()
+ {
+ return Get<ReportState>(MethodOffset.ReportState);
+ }
+
+ public VertexBufferState GetVertexBufferState(int index)
+ {
+ return Get<VertexBufferState>(MethodOffset.VertexBufferState + 4 * index);
+ }
+
+ public BlendState GetBlendState(int index)
+ {
+ return Get<BlendState>(MethodOffset.BlendState + 8 * index);
+ }
+
+ public GpuVa GetVertexBufferEndAddress(int index)
+ {
+ return Get<GpuVa>(MethodOffset.VertexBufferEndAddress + 2 * index);
+ }
+
+ public ShaderState GetShaderState(int index)
+ {
+ return Get<ShaderState>(MethodOffset.ShaderState + 16 * index);
+ }
+
+ public UniformBufferState GetUniformBufferState()
+ {
+ return Get<UniformBufferState>(MethodOffset.UniformBufferState);
+ }
+
+ public void SetUniformBufferOffset(int offset)
+ {
+ _backingMemory[(int)MethodOffset.UniformBufferState + 3] = offset;
+ }
+
+ public int GetTextureBufferIndex()
+ {
+ return _backingMemory[(int)MethodOffset.TextureBufferIndex];
+ }
+
+ private void InitializeDefaultState()
+ {
+ // Depth ranges.
+ for (int index = 0; index < 8; index++)
+ {
+ _backingMemory[(int)MethodOffset.ViewportExtents + index * 4 + 2] = 0;
+ _backingMemory[(int)MethodOffset.ViewportExtents + index * 4 + 3] = 0x3F800000;
+ }
+
+ // Default front stencil mask.
+ _backingMemory[0x4e7] = 0xff;
+
+ // Default color mask.
+ _backingMemory[(int)MethodOffset.RtColorMask] = 0x1111;
+ }
+
+ private void InitializeStateWatchers()
+ {
+ SetWriteStateFlag(MethodOffset.RtColorState, StateWriteFlags.RtColorState, 16 * 8);
+
+ SetWriteStateFlag(MethodOffset.ViewportTransform, StateWriteFlags.ViewportTransform, 8 * 8);
+ SetWriteStateFlag(MethodOffset.ViewportExtents, StateWriteFlags.ViewportTransform, 4 * 8);
+
+ SetWriteStateFlag<VertexBufferDrawState>(MethodOffset.VertexBufferDrawState, StateWriteFlags.VertexBufferState);
+
+ SetWriteStateFlag<DepthBiasState>(MethodOffset.DepthBiasState, StateWriteFlags.DepthBiasState);
+
+ SetWriteStateFlag(MethodOffset.DepthBiasFactor, StateWriteFlags.DepthBiasState, 1);
+ SetWriteStateFlag(MethodOffset.DepthBiasUnits, StateWriteFlags.DepthBiasState, 1);
+ SetWriteStateFlag(MethodOffset.DepthBiasClamp, StateWriteFlags.DepthBiasState, 1);
+
+ SetWriteStateFlag<RtDepthStencilState>(MethodOffset.RtDepthStencilState, StateWriteFlags.RtDepthStencilState);
+ SetWriteStateFlag<Size3D> (MethodOffset.RtDepthStencilSize, StateWriteFlags.RtDepthStencilState);
+
+ SetWriteStateFlag(MethodOffset.DepthTestEnable, StateWriteFlags.DepthTestState, 1);
+ SetWriteStateFlag(MethodOffset.DepthWriteEnable, StateWriteFlags.DepthTestState, 1);
+ SetWriteStateFlag(MethodOffset.DepthTestFunc, StateWriteFlags.DepthTestState, 1);
+
+ SetWriteStateFlag(MethodOffset.VertexAttribState, StateWriteFlags.VertexAttribState, 16);
+
+ SetWriteStateFlag<StencilBackMasks> (MethodOffset.StencilBackMasks, StateWriteFlags.StencilTestState);
+ SetWriteStateFlag<StencilTestState> (MethodOffset.StencilTestState, StateWriteFlags.StencilTestState);
+ SetWriteStateFlag<StencilBackTestState>(MethodOffset.StencilBackTestState, StateWriteFlags.StencilTestState);
+
+ SetWriteStateFlag<PoolState>(MethodOffset.SamplerPoolState, StateWriteFlags.SamplerPoolState);
+ SetWriteStateFlag<PoolState>(MethodOffset.TexturePoolState, StateWriteFlags.TexturePoolState);
+
+ SetWriteStateFlag<ShaderState>(MethodOffset.ShaderBaseAddress, StateWriteFlags.ShaderState);
+
+ SetWriteStateFlag<PrimitiveRestartState>(MethodOffset.PrimitiveRestartState, StateWriteFlags.PrimitiveRestartState);
+
+ SetWriteStateFlag<IndexBufferState>(MethodOffset.IndexBufferState, StateWriteFlags.IndexBufferState);
+
+ SetWriteStateFlag<FaceState>(MethodOffset.FaceState, StateWriteFlags.FaceState);
+
+ SetWriteStateFlag<RtColorMask>(MethodOffset.RtColorMask, StateWriteFlags.RtColorMask);
+
+ SetWriteStateFlag(MethodOffset.VertexBufferInstanced, StateWriteFlags.VertexBufferState, 16);
+ SetWriteStateFlag(MethodOffset.VertexBufferState, StateWriteFlags.VertexBufferState, 4 * 16);
+ SetWriteStateFlag(MethodOffset.VertexBufferEndAddress, StateWriteFlags.VertexBufferState, 2 * 16);
+
+ SetWriteStateFlag(MethodOffset.BlendEnable, StateWriteFlags.BlendState, 8);
+ SetWriteStateFlag(MethodOffset.BlendState, StateWriteFlags.BlendState, 8 * 8);
+
+ SetWriteStateFlag(MethodOffset.ShaderState, StateWriteFlags.ShaderState, 16 * 6);
+
+ SetWriteStateFlag(MethodOffset.TextureBufferIndex, StateWriteFlags.TexturePoolState, 1);
+ }
+
+ private void SetWriteStateFlag<T>(MethodOffset offset, StateWriteFlags flag)
+ {
+ SetWriteStateFlag(offset, flag, Marshal.SizeOf<T>());
+ }
+
+ private void SetWriteStateFlag(MethodOffset offset, StateWriteFlags flag, int size)
+ {
+ for (int index = 0; index < size; index++)
+ {
+ _registers[(int)offset + index].WriteFlag = flag;
+ }
+ }
+
+ public void RegisterCallback(MethodOffset offset, MethodCallback callback)
+ {
+ _registers[(int)offset].Callback = callback;
+ }
+
+ private void RegisterCallback(int offset, MethodCallback callback)
+ {
+ _registers[offset].Callback = callback;
+ }
+
+ public T Get<T>(MethodOffset offset) where T : struct
+ {
+ return MemoryMarshal.Cast<int, T>(_backingMemory.AsSpan().Slice((int)offset))[0];
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/GpuVa.cs b/Ryujinx.Graphics.Gpu/State/GpuVa.cs
new file mode 100644
index 00000000..01ad70b7
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/GpuVa.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct GpuVa
+ {
+ public uint High;
+ public uint Low;
+
+ public ulong Pack()
+ {
+ return Low | ((ulong)High << 32);
+ }
+
+ public bool IsNullPtr()
+ {
+ return (Low | High) == 0;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/IndexBufferState.cs b/Ryujinx.Graphics.Gpu/State/IndexBufferState.cs
new file mode 100644
index 00000000..8a07bb52
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/IndexBufferState.cs
@@ -0,0 +1,13 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct IndexBufferState
+ {
+ public GpuVa Address;
+ public GpuVa EndAddress;
+ public IndexType Type;
+ public int First;
+ public int Count;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/Inline2MemoryParams.cs b/Ryujinx.Graphics.Gpu/State/Inline2MemoryParams.cs
new file mode 100644
index 00000000..10d24900
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/Inline2MemoryParams.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct Inline2MemoryParams
+ {
+ public int LineLengthIn;
+ public int LineCount;
+ public GpuVa DstAddress;
+ public int DstStride;
+ public MemoryLayout DstMemoryLayout;
+ public int DstWidth;
+ public int DstHeight;
+ public int DstDepth;
+ public int DstZ;
+ public int DstX;
+ public int DstY;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/MemoryLayout.cs b/Ryujinx.Graphics.Gpu/State/MemoryLayout.cs
new file mode 100644
index 00000000..8e53a36a
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/MemoryLayout.cs
@@ -0,0 +1,32 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct MemoryLayout
+ {
+ public uint Packed;
+
+ public int UnpackGobBlocksInX()
+ {
+ return 1 << (int)(Packed & 0xf);
+ }
+
+ public int UnpackGobBlocksInY()
+ {
+ return 1 << (int)((Packed >> 4) & 0xf);
+ }
+
+ public int UnpackGobBlocksInZ()
+ {
+ return 1 << (int)((Packed >> 8) & 0xf);
+ }
+
+ public bool UnpackIsLinear()
+ {
+ return (Packed & 0x1000) != 0;
+ }
+
+ public bool UnpackIsTarget3D()
+ {
+ return (Packed & 0x10000) != 0;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/MethodOffset.cs b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs
new file mode 100644
index 00000000..91dffe9f
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs
@@ -0,0 +1,62 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ enum MethodOffset
+ {
+ Inline2MemoryParams = 0x60,
+ Inline2MemoryExecute = 0x6c,
+ Inline2MemoryPushData = 0x6d,
+ CopyDstTexture = 0x80,
+ CopySrcTexture = 0x8c,
+ DispatchParamsAddress = 0xad,
+ Dispatch = 0xaf,
+ CopyBufferParams = 0x100,
+ CopyBufferSwizzle = 0x1c2,
+ CopyBufferDstTexture = 0x1c3,
+ CopyBufferSrcTexture = 0x1ca,
+ RtColorState = 0x200,
+ CopyTextureControl = 0x223,
+ CopyRegion = 0x22c,
+ ViewportTransform = 0x280,
+ ViewportExtents = 0x300,
+ VertexBufferDrawState = 0x35d,
+ ClearColors = 0x360,
+ ClearDepthValue = 0x364,
+ ClearStencilValue = 0x368,
+ DepthBiasState = 0x370,
+ StencilBackMasks = 0x3d5,
+ InvalidateTextures = 0x3dd,
+ RtDepthStencilState = 0x3f8,
+ VertexAttribState = 0x458,
+ RtDepthStencilSize = 0x48a,
+ DepthTestEnable = 0x4b3,
+ DepthWriteEnable = 0x4ba,
+ DepthTestFunc = 0x4c3,
+ BlendEnable = 0x4d8,
+ StencilTestState = 0x4e0,
+ FirstVertex = 0x50d,
+ FirstInstance = 0x50e,
+ ResetCounter = 0x54c,
+ RtDepthStencilEnable = 0x54e,
+ ConditionState = 0x554,
+ SamplerPoolState = 0x557,
+ DepthBiasFactor = 0x55b,
+ TexturePoolState = 0x55d,
+ StencilBackTestState = 0x565,
+ DepthBiasUnits = 0x56f,
+ RtMsaaMode = 0x574,
+ ShaderBaseAddress = 0x582,
+ PrimitiveRestartState = 0x591,
+ IndexBufferState = 0x5f2,
+ DepthBiasClamp = 0x61f,
+ VertexBufferInstanced = 0x620,
+ FaceState = 0x646,
+ RtColorMask = 0x680,
+ ReportState = 0x6c0,
+ VertexBufferState = 0x700,
+ BlendState = 0x780,
+ VertexBufferEndAddress = 0x7c0,
+ ShaderState = 0x800,
+ UniformBufferState = 0x8e0,
+ TextureBufferIndex = 0x982
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/PoolState.cs b/Ryujinx.Graphics.Gpu/State/PoolState.cs
new file mode 100644
index 00000000..3d51eb7e
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/PoolState.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct PoolState
+ {
+ public GpuVa Address;
+ public int MaximumId;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/PrimitiveRestartState.cs b/Ryujinx.Graphics.Gpu/State/PrimitiveRestartState.cs
new file mode 100644
index 00000000..21405be7
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/PrimitiveRestartState.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct PrimitiveRestartState
+ {
+ public bool Enable;
+ public int Index;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/PrimitiveTopology.cs b/Ryujinx.Graphics.Gpu/State/PrimitiveTopology.cs
new file mode 100644
index 00000000..02df9ac1
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/PrimitiveTopology.cs
@@ -0,0 +1,50 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ enum PrimitiveType
+ {
+ Points,
+ Lines,
+ LineLoop,
+ LineStrip,
+ Triangles,
+ TriangleStrip,
+ TriangleFan,
+ Quads,
+ QuadStrip,
+ Polygon,
+ LinesAdjacency,
+ LineStripAdjacency,
+ TrianglesAdjacency,
+ TriangleStripAdjacency,
+ Patches
+ }
+
+ static class PrimitiveTypeConverter
+ {
+ public static PrimitiveTopology Convert(this PrimitiveType topology)
+ {
+ switch (topology)
+ {
+ case PrimitiveType.Points: return PrimitiveTopology.Points;
+ case PrimitiveType.Lines: return PrimitiveTopology.Lines;
+ case PrimitiveType.LineLoop: return PrimitiveTopology.LineLoop;
+ case PrimitiveType.LineStrip: return PrimitiveTopology.LineStrip;
+ case PrimitiveType.Triangles: return PrimitiveTopology.Triangles;
+ case PrimitiveType.TriangleStrip: return PrimitiveTopology.TriangleStrip;
+ case PrimitiveType.TriangleFan: return PrimitiveTopology.TriangleFan;
+ case PrimitiveType.Quads: return PrimitiveTopology.Quads;
+ case PrimitiveType.QuadStrip: return PrimitiveTopology.QuadStrip;
+ case PrimitiveType.Polygon: return PrimitiveTopology.Polygon;
+ case PrimitiveType.LinesAdjacency: return PrimitiveTopology.LinesAdjacency;
+ case PrimitiveType.LineStripAdjacency: return PrimitiveTopology.LineStripAdjacency;
+ case PrimitiveType.TrianglesAdjacency: return PrimitiveTopology.TrianglesAdjacency;
+ case PrimitiveType.TriangleStripAdjacency: return PrimitiveTopology.TriangleStripAdjacency;
+ case PrimitiveType.Patches: return PrimitiveTopology.Patches;
+ }
+
+ return PrimitiveTopology.Triangles;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs b/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs
new file mode 100644
index 00000000..38a0de71
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ReportCounterType.cs
@@ -0,0 +1,25 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ enum ReportCounterType
+ {
+ Zero = 0,
+ InputVertices = 1,
+ InputPrimitives = 3,
+ VertexShaderInvocations = 5,
+ GeometryShaderInvocations = 7,
+ GeometryShaderPrimitives = 9,
+ TransformFeedbackPrimitivesWritten = 0xb,
+ ClipperInputPrimitives = 0xf,
+ ClipperOutputPrimitives = 0x11,
+ PrimitivesGenerated = 0x12,
+ FragmentShaderInvocations = 0x13,
+ SamplesPassed = 0x15,
+ TessControlShaderInvocations = 0x1b,
+ TessEvaluationShaderInvocations = 0x1d,
+ TessEvaluationShaderPrimitives = 0x1f,
+ ZcullStats0 = 0x2a,
+ ZcullStats1 = 0x2c,
+ ZcullStats2 = 0x2e,
+ ZcullStats3 = 0x30
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/ReportMode.cs b/Ryujinx.Graphics.Gpu/State/ReportMode.cs
new file mode 100644
index 00000000..84af3d75
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ReportMode.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ enum ReportMode
+ {
+ Semaphore = 0,
+ Counter = 2
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/ReportState.cs b/Ryujinx.Graphics.Gpu/State/ReportState.cs
new file mode 100644
index 00000000..212281aa
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ReportState.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct ReportState
+ {
+ public GpuVa Address;
+ public int Payload;
+ public uint Control;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/ResetCounterType.cs b/Ryujinx.Graphics.Gpu/State/ResetCounterType.cs
new file mode 100644
index 00000000..49b3b6da
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ResetCounterType.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ enum ResetCounterType
+ {
+ SamplesPassed = 1,
+ ZcullStats = 2,
+ TransformFeedbackPrimitivesWritten = 0x10,
+ InputVertices = 0x12,
+ InputPrimitives = 0x13,
+ VertexShaderInvocations = 0x15,
+ TessControlShaderInvocations = 0x16,
+ TessEvaluationShaderInvocations = 0x17,
+ TessEvaluationShaderPrimitives = 0x18,
+ GeometryShaderInvocations = 0x1a,
+ GeometryShaderPrimitives = 0x1b,
+ ClipperInputPrimitives = 0x1c,
+ ClipperOutputPrimitives = 0x1d,
+ FragmentShaderInvocations = 0x1e,
+ PrimitivesGenerated = 0x1f
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/RtColorMask.cs b/Ryujinx.Graphics.Gpu/State/RtColorMask.cs
new file mode 100644
index 00000000..5992673f
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/RtColorMask.cs
@@ -0,0 +1,27 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct RtColorMask
+ {
+ public uint Packed;
+
+ public bool UnpackRed()
+ {
+ return (Packed & 0x1) != 0;
+ }
+
+ public bool UnpackGreen()
+ {
+ return (Packed & 0x10) != 0;
+ }
+
+ public bool UnpackBlue()
+ {
+ return (Packed & 0x100) != 0;
+ }
+
+ public bool UnpackAlpha()
+ {
+ return (Packed & 0x1000) != 0;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/RtColorState.cs b/Ryujinx.Graphics.Gpu/State/RtColorState.cs
new file mode 100644
index 00000000..bb6ae208
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/RtColorState.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct RtColorState
+ {
+ public GpuVa Address;
+ public int WidthOrStride;
+ public int Height;
+ public RtFormat Format;
+ public MemoryLayout MemoryLayout;
+ public int Depth;
+ public int LayerSize;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/RtDepthStencilState.cs b/Ryujinx.Graphics.Gpu/State/RtDepthStencilState.cs
new file mode 100644
index 00000000..bfa812cb
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/RtDepthStencilState.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct RtDepthStencilState
+ {
+ public GpuVa Address;
+ public RtFormat Format;
+ public MemoryLayout MemoryLayout;
+ public int LayerSize;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/RtFormat.cs b/Ryujinx.Graphics.Gpu/State/RtFormat.cs
new file mode 100644
index 00000000..960da445
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/RtFormat.cs
@@ -0,0 +1,137 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ enum RtFormat
+ {
+ D32Float = 0xa,
+ D16Unorm = 0x13,
+ D24UnormS8Uint = 0x14,
+ S8Uint = 0x17,
+ D32FloatS8Uint = 0x19,
+ R32G32B32A32Float = 0xc0,
+ R32G32B32A32Sint = 0xc1,
+ R32G32B32A32Uint = 0xc2,
+ R32G32B32X32Float = 0xc3,
+ R32G32B32X32Sint = 0xc4,
+ R32G32B32X32Uint = 0xc5,
+ R16G16B16X16Unorm = 0xc6,
+ R16G16B16X16Snorm = 0xc7,
+ R16G16B16X16Sint = 0xc8,
+ R16G16B16X16Uint = 0xc9,
+ R16G16B16A16Float = 0xca,
+ R32G32Float = 0xcb,
+ R32G32Sint = 0xcc,
+ R32G32Uint = 0xcd,
+ R16G16B16X16Float = 0xce,
+ B8G8R8A8Unorm = 0xcf,
+ B8G8R8A8Srgb = 0xd0,
+ R10G10B10A2Unorm = 0xd1,
+ R10G10B10A2Uint = 0xd2,
+ R8G8B8A8Unorm = 0xd5,
+ R8G8B8A8Srgb = 0xd6,
+ R8G8B8X8Snorm = 0xd7,
+ R8G8B8X8Sint = 0xd8,
+ R8G8B8X8Uint = 0xd9,
+ R16G16Unorm = 0xda,
+ R16G16Snorm = 0xdb,
+ R16G16Sint = 0xdc,
+ R16G16Uint = 0xdd,
+ R16G16Float = 0xde,
+ R11G11B10Float = 0xe0,
+ R32Sint = 0xe3,
+ R32Uint = 0xe4,
+ R32Float = 0xe5,
+ B8G8R8X8Unorm = 0xe6,
+ B8G8R8X8Srgb = 0xe7,
+ B5G6R5Unorm = 0xe8,
+ B5G5R5A1Unorm = 0xe9,
+ R8G8Unorm = 0xea,
+ R8G8Snorm = 0xeb,
+ R8G8Sint = 0xec,
+ R8G8Uint = 0xed,
+ R16Unorm = 0xee,
+ R16Snorm = 0xef,
+ R16Sint = 0xf0,
+ R16Uint = 0xf1,
+ R16Float = 0xf2,
+ R8Unorm = 0xf3,
+ R8Snorm = 0xf4,
+ R8Sint = 0xf5,
+ R8Uint = 0xf6,
+ B5G5R5X1Unorm = 0xf8,
+ R8G8B8X8Unorm = 0xf9,
+ R8G8B8X8Srgb = 0xfa
+ }
+
+ static class RtFormatConverter
+ {
+ public static FormatInfo Convert(this RtFormat format)
+ {
+ switch (format)
+ {
+ case RtFormat.D32Float: return new FormatInfo(Format.D32Float, 1, 1, 4);
+ case RtFormat.D16Unorm: return new FormatInfo(Format.D16Unorm, 1, 1, 2);
+ case RtFormat.D24UnormS8Uint: return new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4);
+ case RtFormat.S8Uint: return new FormatInfo(Format.S8Uint, 1, 1, 1);
+ case RtFormat.D32FloatS8Uint: return new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8);
+ case RtFormat.R32G32B32A32Float: return new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16);
+ case RtFormat.R32G32B32A32Sint: return new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16);
+ case RtFormat.R32G32B32A32Uint: return new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16);
+ case RtFormat.R32G32B32X32Float: return new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16);
+ case RtFormat.R32G32B32X32Sint: return new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16);
+ case RtFormat.R32G32B32X32Uint: return new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16);
+ case RtFormat.R16G16B16X16Unorm: return new FormatInfo(Format.R16G16B16A16Unorm, 1, 1, 8);
+ case RtFormat.R16G16B16X16Snorm: return new FormatInfo(Format.R16G16B16A16Snorm, 1, 1, 8);
+ case RtFormat.R16G16B16X16Sint: return new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8);
+ case RtFormat.R16G16B16X16Uint: return new FormatInfo(Format.R16G16B16A16Uint, 1, 1, 8);
+ case RtFormat.R16G16B16A16Float: return new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8);
+ case RtFormat.R32G32Float: return new FormatInfo(Format.R32G32Float, 1, 1, 8);
+ case RtFormat.R32G32Sint: return new FormatInfo(Format.R32G32Sint, 1, 1, 8);
+ case RtFormat.R32G32Uint: return new FormatInfo(Format.R32G32Uint, 1, 1, 8);
+ case RtFormat.R16G16B16X16Float: return new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8);
+ case RtFormat.B8G8R8A8Unorm: return new FormatInfo(Format.B8G8R8A8Unorm, 1, 1, 4);
+ case RtFormat.B8G8R8A8Srgb: return new FormatInfo(Format.B8G8R8A8Srgb, 1, 1, 4);
+ case RtFormat.R10G10B10A2Unorm: return new FormatInfo(Format.R10G10B10A2Unorm, 1, 1, 4);
+ case RtFormat.R10G10B10A2Uint: return new FormatInfo(Format.R10G10B10A2Uint, 1, 1, 4);
+ case RtFormat.R8G8B8A8Unorm: return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4);
+ case RtFormat.R8G8B8A8Srgb: return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4);
+ case RtFormat.R8G8B8X8Snorm: return new FormatInfo(Format.R8G8B8A8Snorm, 1, 1, 4);
+ case RtFormat.R8G8B8X8Sint: return new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4);
+ case RtFormat.R8G8B8X8Uint: return new FormatInfo(Format.R8G8B8A8Uint, 1, 1, 4);
+ case RtFormat.R16G16Unorm: return new FormatInfo(Format.R16G16Unorm, 1, 1, 4);
+ case RtFormat.R16G16Snorm: return new FormatInfo(Format.R16G16Snorm, 1, 1, 4);
+ case RtFormat.R16G16Sint: return new FormatInfo(Format.R16G16Sint, 1, 1, 4);
+ case RtFormat.R16G16Uint: return new FormatInfo(Format.R16G16Uint, 1, 1, 4);
+ case RtFormat.R16G16Float: return new FormatInfo(Format.R16G16Float, 1, 1, 4);
+ case RtFormat.R11G11B10Float: return new FormatInfo(Format.R11G11B10Float, 1, 1, 4);
+ case RtFormat.R32Sint: return new FormatInfo(Format.R32Sint, 1, 1, 4);
+ case RtFormat.R32Uint: return new FormatInfo(Format.R32Uint, 1, 1, 4);
+ case RtFormat.R32Float: return new FormatInfo(Format.R32Float, 1, 1, 4);
+ case RtFormat.B8G8R8X8Unorm: return new FormatInfo(Format.B8G8R8A8Unorm, 1, 1, 4);
+ case RtFormat.B8G8R8X8Srgb: return new FormatInfo(Format.B8G8R8A8Srgb, 1, 1, 4);
+ case RtFormat.B5G6R5Unorm: return new FormatInfo(Format.B5G6R5Unorm, 1, 1, 2);
+ case RtFormat.B5G5R5A1Unorm: return new FormatInfo(Format.B5G5R5A1Unorm, 1, 1, 2);
+ case RtFormat.R8G8Unorm: return new FormatInfo(Format.R8G8Unorm, 1, 1, 2);
+ case RtFormat.R8G8Snorm: return new FormatInfo(Format.R8G8Snorm, 1, 1, 2);
+ case RtFormat.R8G8Sint: return new FormatInfo(Format.R8G8Sint, 1, 1, 2);
+ case RtFormat.R8G8Uint: return new FormatInfo(Format.R8G8Uint, 1, 1, 2);
+ case RtFormat.R16Unorm: return new FormatInfo(Format.R16Unorm, 1, 1, 2);
+ case RtFormat.R16Snorm: return new FormatInfo(Format.R16Snorm, 1, 1, 2);
+ case RtFormat.R16Sint: return new FormatInfo(Format.R16Sint, 1, 1, 2);
+ case RtFormat.R16Uint: return new FormatInfo(Format.R16Uint, 1, 1, 2);
+ case RtFormat.R16Float: return new FormatInfo(Format.R16Float, 1, 1, 2);
+ case RtFormat.R8Unorm: return new FormatInfo(Format.R8Unorm, 1, 1, 1);
+ case RtFormat.R8Snorm: return new FormatInfo(Format.R8Snorm, 1, 1, 1);
+ case RtFormat.R8Sint: return new FormatInfo(Format.R8Sint, 1, 1, 1);
+ case RtFormat.R8Uint: return new FormatInfo(Format.R8Uint, 1, 1, 1);
+ case RtFormat.B5G5R5X1Unorm: return new FormatInfo(Format.B5G5R5X1Unorm, 1, 1, 2);
+ case RtFormat.R8G8B8X8Unorm: return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4);
+ case RtFormat.R8G8B8X8Srgb: return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4);
+ }
+
+ return FormatInfo.Default;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/State/ShaderState.cs b/Ryujinx.Graphics.Gpu/State/ShaderState.cs
new file mode 100644
index 00000000..536d7dcf
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ShaderState.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct ShaderState
+ {
+ public uint Control;
+ public uint Offset;
+ public uint Unknown0x8;
+ public int MaxRegisters;
+ public ShaderType Type;
+ public uint Unknown0x14;
+ public uint Unknown0x18;
+ public uint Unknown0x1c;
+
+ public bool UnpackEnable()
+ {
+ return (Control & 1) != 0;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/ShaderType.cs b/Ryujinx.Graphics.Gpu/State/ShaderType.cs
new file mode 100644
index 00000000..703159f4
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ShaderType.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ enum ShaderType
+ {
+ Vertex,
+ TessellationControl,
+ TessellationEvaluation,
+ Geometry,
+ Fragment
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/Size3D.cs b/Ryujinx.Graphics.Gpu/State/Size3D.cs
new file mode 100644
index 00000000..82771389
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/Size3D.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct Size3D
+ {
+ public int Width;
+ public int Height;
+ public int Depth;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/StateWriteFlags.cs b/Ryujinx.Graphics.Gpu/State/StateWriteFlags.cs
new file mode 100644
index 00000000..32d5127a
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/StateWriteFlags.cs
@@ -0,0 +1,34 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ enum StateWriteFlags
+ {
+ InputAssemblerGroup =
+ VertexAttribState |
+ PrimitiveRestartState |
+ IndexBufferState |
+ VertexBufferState,
+
+ RenderTargetGroup =
+ RtColorState |
+ RtDepthStencilState,
+
+ RtColorState = 1 << 0,
+ ViewportTransform = 1 << 1,
+ DepthBiasState = 1 << 2,
+ RtDepthStencilState = 1 << 3,
+ DepthTestState = 1 << 4,
+ VertexAttribState = 1 << 5,
+ StencilTestState = 1 << 6,
+ SamplerPoolState = 1 << 7,
+ TexturePoolState = 1 << 8,
+ PrimitiveRestartState = 1 << 9,
+ IndexBufferState = 1 << 10,
+ FaceState = 1 << 11,
+ RtColorMask = 1 << 12,
+ VertexBufferState = 1 << 13,
+ BlendState = 1 << 14,
+ ShaderState = 1 << 15,
+
+ Any = -1
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/StencilBackMasks.cs b/Ryujinx.Graphics.Gpu/State/StencilBackMasks.cs
new file mode 100644
index 00000000..d3779f87
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/StencilBackMasks.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct StencilBackMasks
+ {
+ public int FuncRef;
+ public int Mask;
+ public int FuncMask;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/StencilBackTestState.cs b/Ryujinx.Graphics.Gpu/State/StencilBackTestState.cs
new file mode 100644
index 00000000..18b31eaa
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/StencilBackTestState.cs
@@ -0,0 +1,14 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.DepthStencil;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct StencilBackTestState
+ {
+ public Bool TwoSided;
+ public StencilOp BackSFail;
+ public StencilOp BackDpFail;
+ public StencilOp BackDpPass;
+ public CompareOp BackFunc;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/StencilTestState.cs b/Ryujinx.Graphics.Gpu/State/StencilTestState.cs
new file mode 100644
index 00000000..b60f002f
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/StencilTestState.cs
@@ -0,0 +1,17 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.DepthStencil;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct StencilTestState
+ {
+ public Bool Enable;
+ public StencilOp FrontSFail;
+ public StencilOp FrontDpFail;
+ public StencilOp FrontDpPass;
+ public CompareOp FrontFunc;
+ public int FrontFuncRef;
+ public int FrontFuncMask;
+ public int FrontMask;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/UniformBufferState.cs b/Ryujinx.Graphics.Gpu/State/UniformBufferState.cs
new file mode 100644
index 00000000..80e4b6bc
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/UniformBufferState.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct UniformBufferState
+ {
+ public int Size;
+ public GpuVa Address;
+ public int Offset;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/VertexAttribState.cs b/Ryujinx.Graphics.Gpu/State/VertexAttribState.cs
new file mode 100644
index 00000000..0e327a75
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/VertexAttribState.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct VertexAttribState
+ {
+ public uint Attribute;
+
+ public int UnpackBufferIndex()
+ {
+ return (int)(Attribute & 0x1f);
+ }
+
+ public int UnpackOffset()
+ {
+ return (int)((Attribute >> 7) & 0x3fff);
+ }
+
+ public uint UnpackFormat()
+ {
+ return Attribute & 0x3fe00000;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/VertexBufferDrawState.cs b/Ryujinx.Graphics.Gpu/State/VertexBufferDrawState.cs
new file mode 100644
index 00000000..c2f3b7bb
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/VertexBufferDrawState.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct VertexBufferDrawState
+ {
+ public int First;
+ public int Count;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/VertexBufferState.cs b/Ryujinx.Graphics.Gpu/State/VertexBufferState.cs
new file mode 100644
index 00000000..a8bd2c92
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/VertexBufferState.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct VertexBufferState
+ {
+ public uint Control;
+ public GpuVa Address;
+ public int Divisor;
+
+ public int UnpackStride()
+ {
+ return (int)(Control & 0xfff);
+ }
+
+ public bool UnpackEnable()
+ {
+ return (Control & (1 << 12)) != 0;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/ViewportExtents.cs b/Ryujinx.Graphics.Gpu/State/ViewportExtents.cs
new file mode 100644
index 00000000..4be2db90
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ViewportExtents.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct ViewportExtents
+ {
+ public ushort X;
+ public ushort Width;
+ public ushort Y;
+ public ushort Height;
+ public float DepthNear;
+ public float DepthFar;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/State/ViewportTransform.cs b/Ryujinx.Graphics.Gpu/State/ViewportTransform.cs
new file mode 100644
index 00000000..335b039c
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ViewportTransform.cs
@@ -0,0 +1,36 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.State
+{
+ struct ViewportTransform
+ {
+ public float ScaleX;
+ public float ScaleY;
+ public float ScaleZ;
+ public float TranslateX;
+ public float TranslateY;
+ public float TranslateZ;
+ public uint Swizzle;
+ public uint SubpixelPrecisionBias;
+
+ public ViewportSwizzle UnpackSwizzleX()
+ {
+ return (ViewportSwizzle)(Swizzle & 7);
+ }
+
+ public ViewportSwizzle UnpackSwizzleY()
+ {
+ return (ViewportSwizzle)((Swizzle >> 4) & 7);
+ }
+
+ public ViewportSwizzle UnpackSwizzleZ()
+ {
+ return (ViewportSwizzle)((Swizzle >> 8) & 7);
+ }
+
+ public ViewportSwizzle UnpackSwizzleW()
+ {
+ return (ViewportSwizzle)((Swizzle >> 12) & 7);
+ }
+ }
+}