Browse Source

Refactor shader GPU state and memory access (#1203)

* Refactor shader GPU state and memory access

* Fix NVDEC project build

* Address PR feedback and add missing XML comments
gdkchan 6 năm trước cách đây
mục cha
commit
b8eb6abecc
35 tập tin đã thay đổi với 629 bổ sung680 xóa
  1. 1 1
      Ryujinx.Graphics.Gpu/DmaPusher.cs
  2. 2 2
      Ryujinx.Graphics.Gpu/Engine/Compute.cs
  3. 1 1
      Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs
  4. 1 1
      Ryujinx.Graphics.Gpu/Engine/Methods.cs
  5. 1 1
      Ryujinx.Graphics.Gpu/Image/Texture.cs
  6. 1 1
      Ryujinx.Graphics.Gpu/Memory/Buffer.cs
  7. 7 10
      Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs
  8. 0 41
      Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
  9. 2 2
      Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
  10. 0 31
      Ryujinx.Graphics.Gpu/Shader/ComputeShader.cs
  11. 264 0
      Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
  12. 0 28
      Ryujinx.Graphics.Gpu/Shader/GraphicsShader.cs
  13. 46 0
      Ryujinx.Graphics.Gpu/Shader/ShaderBundle.cs
  14. 75 315
      Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
  15. 11 4
      Ryujinx.Graphics.Gpu/Shader/ShaderCodeHolder.cs
  16. 27 26
      Ryujinx.Graphics.Gpu/Shader/ShaderDumper.cs
  17. 18 3
      Ryujinx.Graphics.Nvdec/VDec/VideoDecoder.cs
  18. 6 6
      Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
  19. 9 25
      Ryujinx.Graphics.Shader/Decoders/Decoder.cs
  20. 67 0
      Ryujinx.Graphics.Shader/IGpuAccessor.cs
  21. 2 2
      Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs
  22. 1 1
      Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs
  23. 11 11
      Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs
  24. 1 1
      Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs
  25. 8 8
      Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
  26. 1 1
      Ryujinx.Graphics.Shader/Instructions/InstEmitVote.cs
  27. 0 17
      Ryujinx.Graphics.Shader/QueryInfoName.cs
  28. 3 1
      Ryujinx.Graphics.Shader/ShaderProgram.cs
  29. 3 3
      Ryujinx.Graphics.Shader/Translation/Lowering.cs
  30. 2 2
      Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs
  31. 6 52
      Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
  32. 16 22
      Ryujinx.Graphics.Shader/Translation/ShaderHeader.cs
  33. 19 43
      Ryujinx.Graphics.Shader/Translation/Translator.cs
  34. 0 17
      Ryujinx.Graphics.Shader/Translation/TranslatorCallbacks.cs
  35. 17 1
      Ryujinx.ShaderTools/Program.cs

+ 1 - 1
Ryujinx.Graphics.Gpu/DmaPusher.cs

@@ -47,7 +47,7 @@ namespace Ryujinx.Graphics.Gpu
             {
                 if (Words == null)
                 {
-                    Words = MemoryMarshal.Cast<byte, int>(context.MemoryAccessor.GetSpan(EntryAddress, EntryCount * 4)).ToArray();
+                    Words = MemoryMarshal.Cast<byte, int>(context.MemoryAccessor.GetSpan(EntryAddress, (int)EntryCount * 4)).ToArray();
                 }
             }
 

+ 2 - 2
Ryujinx.Graphics.Gpu/Engine/Compute.cs

@@ -47,7 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
                 BufferManager.SetComputeUniformBuffer(index, gpuVa, size);
             }
 
-            ComputeShader cs = ShaderCache.GetComputeShader(
+            ShaderBundle cs = ShaderCache.GetComputeShader(
                 state,
                 shaderGpuVa,
                 qmd.CtaThreadDimension0,
@@ -68,7 +68,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
 
             TextureManager.SetComputeTextureBufferIndex(state.Get<int>(MethodOffset.TextureBufferIndex));
 
-            ShaderProgramInfo info = cs.Shader.Program.Info;            
+            ShaderProgramInfo info = cs.Shaders[0].Program.Info;            
 
             for (int index = 0; index < info.CBuffers.Count; index++)
             {

+ 1 - 1
Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs

@@ -65,7 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
                     ulong srcAddress = srcBaseAddress + (ulong)srcOffset;
                     ulong dstAddress = dstBaseAddress + (ulong)dstOffset;
 
-                    ReadOnlySpan<byte> pixel = _context.PhysicalMemory.GetSpan(srcAddress, (ulong)srcBpp);
+                    ReadOnlySpan<byte> pixel = _context.PhysicalMemory.GetSpan(srcAddress, srcBpp);
 
                     _context.PhysicalMemory.Write(dstAddress, pixel);
                 }

+ 1 - 1
Ryujinx.Graphics.Gpu/Engine/Methods.cs

@@ -839,7 +839,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
                 addressesArray[index] = baseAddress + shader.Offset;
             }
 
-            GraphicsShader gs = ShaderCache.GetGraphicsShader(state, addresses);
+            ShaderBundle gs = ShaderCache.GetGraphicsShader(state, addresses);
 
             _vsUsesInstanceId = gs.Shaders[0]?.Program.Info.UsesInstanceId ?? false;
 

+ 1 - 1
Ryujinx.Graphics.Gpu/Image/Texture.cs

@@ -315,7 +315,7 @@ namespace Ryujinx.Graphics.Gpu.Image
                 return;
             }
 
-            ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Address, Size);
+            ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Address, (int)Size);
 
             // If the texture was modified by the host GPU, we do partial invalidation
             // of the texture by getting GPU data and merging in the pages of memory

+ 1 - 1
Ryujinx.Graphics.Gpu/Memory/Buffer.cs

@@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
 
                 int offset = (int)(mAddress - Address);
 
-                HostBuffer.SetData(offset, _context.PhysicalMemory.GetSpan(mAddress, mSize));
+                HostBuffer.SetData(offset, _context.PhysicalMemory.GetSpan(mAddress, (int)mSize));
             }
         }
 

+ 7 - 10
Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 
 namespace Ryujinx.Graphics.Gpu.Memory
@@ -25,7 +26,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
         /// <param name="gpuVa">GPU virtual address where the data is located</param>
         /// <param name="size">Size of the data in bytes</param>
         /// <returns>Byte array with the data</returns>
-        public byte[] ReadBytes(ulong gpuVa, ulong size)
+        public byte[] ReadBytes(ulong gpuVa, int size)
         {
             return GetSpan(gpuVa, size).ToArray();
         }
@@ -35,14 +36,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
         /// This reads as much data as possible, up to the specified maximum size.
         /// </summary>
         /// <param name="gpuVa">GPU virtual address where the data is located</param>
-        /// <param name="maxSize">Maximum size of the data</param>
+        /// <param name="size">Size of the data</param>
         /// <returns>The span of the data at the specified memory location</returns>
-        public ReadOnlySpan<byte> GetSpan(ulong gpuVa, ulong maxSize)
+        public ReadOnlySpan<byte> GetSpan(ulong gpuVa, int size)
         {
             ulong processVa = _context.MemoryManager.Translate(gpuVa);
 
-            ulong size = _context.MemoryManager.GetSubSize(gpuVa, maxSize);
-
             return _context.PhysicalMemory.GetSpan(processVa, size);
         }
 
@@ -52,13 +51,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
         /// <typeparam name="T">Type of the structure</typeparam>
         /// <param name="gpuVa">GPU virtual address where the structure is located</param>
         /// <returns>The structure at the specified memory location</returns>
-        public T Read<T>(ulong gpuVa) where T : struct
+        public T Read<T>(ulong gpuVa) where T : unmanaged
         {
             ulong processVa = _context.MemoryManager.Translate(gpuVa);
 
-            ulong size = (uint)Marshal.SizeOf<T>();
-
-            return MemoryMarshal.Cast<byte, T>(_context.PhysicalMemory.GetSpan(processVa, size))[0];
+            return MemoryMarshal.Cast<byte, T>(_context.PhysicalMemory.GetSpan(processVa, Unsafe.SizeOf<T>()))[0];
         }
 
         /// <summary>
@@ -114,7 +111,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
         /// </summary>
         /// <param name="gpuVa">GPU virtual address to write the data into</param>
         /// <param name="data">The data to be written</param>
-        public void Write(ulong gpuVa, Span<byte> data)
+        public void Write(ulong gpuVa, ReadOnlySpan<byte> data)
         {
             ulong processVa = _context.MemoryManager.Translate(gpuVa);
 

+ 0 - 41
Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs

@@ -240,28 +240,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
             return PteUnmapped;
         }
 
-        /// <summary>
-        /// Gets the number of mapped or reserved pages on a given region.
-        /// </summary>
-        /// <param name="gpuVa">Start GPU virtual address of the region</param>
-        /// <param name="maxSize">Maximum size of the data</param>
-        /// <returns>Mapped size in bytes of the specified region</returns>
-        internal ulong GetSubSize(ulong gpuVa, ulong maxSize)
-        {
-            ulong size = 0;
-
-            while (GetPte(gpuVa + size) != PteUnmapped)
-            {
-                size += PageSize;
-                if (size >= maxSize)
-                {
-                    return maxSize;
-                }
-            }
-
-            return size;
-        }
-
         /// <summary>
         /// Translates a GPU virtual address to a CPU virtual address.
         /// </summary>
@@ -279,25 +257,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
             return baseAddress + (gpuVa & PageMask);
         }
 
-        /// <summary>
-        /// Checks if a given memory region is currently unmapped.
-        /// </summary>
-        /// <param name="gpuVa">Start GPU virtual address of the region</param>
-        /// <param name="size">Size in bytes of the region</param>
-        /// <returns>True if the region is unmapped (free), false otherwise</returns>
-        public bool IsRegionFree(ulong gpuVa, ulong size)
-        {
-            for (ulong offset = 0; offset < size; offset += PageSize)
-            {
-                if (IsPageInUse(gpuVa + offset))
-                {
-                    return false;
-                }
-            }
-
-            return true;
-        }
-
         /// <summary>
         /// Checks if a given memory page is mapped or reserved.
         /// </summary>

+ 2 - 2
Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs

@@ -28,9 +28,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
         /// <param name="address">Start address of the range</param>
         /// <param name="size">Size in bytes to be range</param>
         /// <returns>A read only span of the data at the specified memory location</returns>
-        public ReadOnlySpan<byte> GetSpan(ulong address, ulong size)
+        public ReadOnlySpan<byte> GetSpan(ulong address, int size)
         {
-            return _cpuMemory.GetSpan(address, (int)size);
+            return _cpuMemory.GetSpan(address, size);
         }
 
         /// <summary>

+ 0 - 31
Ryujinx.Graphics.Gpu/Shader/ComputeShader.cs

@@ -1,31 +0,0 @@
-using Ryujinx.Graphics.GAL;
-
-namespace Ryujinx.Graphics.Gpu.Shader
-{
-    /// <summary>
-    /// Cached compute shader code.
-    /// </summary>
-    class ComputeShader
-    {
-        /// <summary>
-        /// Host shader program object.
-        /// </summary>
-        public IProgram HostProgram { get; }
-
-        /// <summary>
-        /// Cached shader.
-        /// </summary>
-        public CachedShader Shader { get; }
-
-        /// <summary>
-        /// Creates a new instance of the compute shader.
-        /// </summary>
-        /// <param name="hostProgram">Host shader program</param>
-        /// <param name="shader">Cached shader</param>
-        public ComputeShader(IProgram hostProgram, CachedShader shader)
-        {
-            HostProgram = hostProgram;
-            Shader      = shader;
-        }
-    }
-}

+ 264 - 0
Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs

@@ -0,0 +1,264 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+    /// <summary>
+    /// Represents a GPU state and memory accessor.
+    /// </summary>
+    class GpuAccessor : IGpuAccessor
+    {
+        private readonly GpuContext _context;
+        private readonly GpuState _state;
+        private readonly int _stageIndex;
+        private readonly bool _compute;
+        private readonly int _localSizeX;
+        private readonly int _localSizeY;
+        private readonly int _localSizeZ;
+        private readonly int _localMemorySize;
+        private readonly int _sharedMemorySize;
+
+        /// <summary>
+        /// Creates a new instance of the GPU state accessor for graphics shader translation.
+        /// </summary>
+        /// <param name="context">GPU context</param>
+        /// <param name="state">Current GPU state</param>
+        /// <param name="stageIndex">Graphics shader stage index (0 = Vertex, 4 = Fragment)</param>
+        public GpuAccessor(GpuContext context, GpuState state, int stageIndex)
+        {
+            _context = context;
+            _state = state;
+            _stageIndex = stageIndex;
+        }
+
+        /// <summary>
+        /// Creates a new instance of the GPU state accessor for compute shader translation.
+        /// </summary>
+        /// <param name="context">GPU context</param>
+        /// <param name="state">Current GPU state</param>
+        /// <param name="localSizeX">Local group size X of the compute shader</param>
+        /// <param name="localSizeY">Local group size Y of the compute shader</param>
+        /// <param name="localSizeZ">Local group size Z of the compute shader</param>
+        /// <param name="localMemorySize">Local memory size of the compute shader</param>
+        /// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
+        public GpuAccessor(
+            GpuContext context,
+            GpuState state,
+            int localSizeX,
+            int localSizeY,
+            int localSizeZ,
+            int localMemorySize,
+            int sharedMemorySize)
+        {
+            _context = context;
+            _state = state;
+            _compute = true;
+            _localSizeX = localSizeX;
+            _localSizeY = localSizeY;
+            _localSizeZ = localSizeZ;
+            _localMemorySize = localMemorySize;
+            _sharedMemorySize = sharedMemorySize;
+        }
+
+        /// <summary>
+        /// Prints a log message.
+        /// </summary>
+        /// <param name="message">Message to print</param>
+        public void Log(string message)
+        {
+            Logger.PrintWarning(LogClass.Gpu, $"Shader translator: {message}");
+        }
+
+        /// <summary>
+        /// Reads data from GPU memory.
+        /// </summary>
+        /// <typeparam name="T">Type of the data to be read</typeparam>
+        /// <param name="address">GPU virtual address of the data</param>
+        /// <returns>Data at the memory location</returns>
+        public T MemoryRead<T>(ulong address) where T : unmanaged
+        {
+            return _context.MemoryAccessor.Read<T>(address);
+        }
+
+        /// <summary>
+        /// Queries Local Size X for compute shaders.
+        /// </summary>
+        /// <returns>Local Size X</returns>
+        public int QueryComputeLocalSizeX() => _localSizeX;
+
+        /// <summary>
+        /// Queries Local Size Y for compute shaders.
+        /// </summary>
+        /// <returns>Local Size Y</returns>
+        public int QueryComputeLocalSizeY() => _localSizeY;
+
+        /// <summary>
+        /// Queries Local Size Z for compute shaders.
+        /// </summary>
+        /// <returns>Local Size Z</returns>
+        public int QueryComputeLocalSizeZ() => _localSizeZ;
+
+        /// <summary>
+        /// Queries Local Memory size in bytes for compute shaders.
+        /// </summary>
+        /// <returns>Local Memory size in bytes</returns>
+        public int QueryComputeLocalMemorySize() => _localMemorySize;
+
+        /// <summary>
+        /// Queries Shared Memory size in bytes for compute shaders.
+        /// </summary>
+        /// <returns>Shared Memory size in bytes</returns>
+        public int QueryComputeSharedMemorySize() => _sharedMemorySize;
+
+        /// <summary>
+        /// Queries texture target information.
+        /// </summary>
+        /// <param name="handle">Texture handle</param>
+        /// <returns>True if the texture is a buffer texture, false otherwise</returns>
+        public bool QueryIsTextureBuffer(int handle)
+        {
+            return GetTextureDescriptor(handle).UnpackTextureTarget() == TextureTarget.TextureBuffer;
+        }
+
+        /// <summary>
+        /// Queries texture target information.
+        /// </summary>
+        /// <param name="handle">Texture handle</param>
+        /// <returns>True if the texture is a rectangle texture, false otherwise</returns>
+        public bool QueryIsTextureRectangle(int handle)
+        {
+            var descriptor = GetTextureDescriptor(handle);
+
+            TextureTarget target = descriptor.UnpackTextureTarget();
+
+            bool is2DTexture = target == TextureTarget.Texture2D ||
+                               target == TextureTarget.Texture2DRect;
+
+            return !descriptor.UnpackTextureCoordNormalized() && is2DTexture;
+        }
+
+        /// <summary>
+        /// Queries current primitive topology for geometry shaders.
+        /// </summary>
+        /// <returns>Current primitive topology</returns>
+        public InputTopology QueryPrimitiveTopology()
+        {
+            switch (_context.Methods.PrimitiveType)
+            {
+                case PrimitiveType.Points:
+                    return InputTopology.Points;
+                case PrimitiveType.Lines:
+                case PrimitiveType.LineLoop:
+                case PrimitiveType.LineStrip:
+                    return InputTopology.Lines;
+                case PrimitiveType.LinesAdjacency:
+                case PrimitiveType.LineStripAdjacency:
+                    return InputTopology.LinesAdjacency;
+                case PrimitiveType.Triangles:
+                case PrimitiveType.TriangleStrip:
+                case PrimitiveType.TriangleFan:
+                    return InputTopology.Triangles;
+                case PrimitiveType.TrianglesAdjacency:
+                case PrimitiveType.TriangleStripAdjacency:
+                    return InputTopology.TrianglesAdjacency;
+            }
+
+            return InputTopology.Points;
+        }
+
+        /// <summary>
+        /// Queries host storage buffer alignment required.
+        /// </summary>
+        /// <returns>Host storage buffer alignment in bytes</returns>
+        public int QueryStorageBufferOffsetAlignment() => _context.Capabilities.StorageBufferOffsetAlignment;
+
+        /// <summary>
+        /// Queries host GPU non-constant texture offset support.
+        /// </summary>
+        /// <returns>True if the GPU and driver supports non-constant texture offsets, false otherwise</returns>
+        public bool QuerySupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset;
+
+        /// <summary>
+        /// Queries texture format information, for shaders using image load or store.
+        /// </summary>
+        /// <remarks>
+        /// This only returns non-compressed color formats.
+        /// If the format of the texture is a compressed, depth or unsupported format, then a default value is returned.
+        /// </remarks>
+        /// <param name="handle">Texture handle</param>
+        /// <returns>Color format of the non-compressed texture</returns>
+        public TextureFormat QueryTextureFormat(int handle)
+        {
+            var descriptor = GetTextureDescriptor(handle);
+
+            if (!FormatTable.TryGetTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb(), out FormatInfo formatInfo))
+            {
+                return TextureFormat.Unknown;
+            }
+
+            return formatInfo.Format switch
+            {
+                Format.R8Unorm           => TextureFormat.R8Unorm,
+                Format.R8Snorm           => TextureFormat.R8Snorm,
+                Format.R8Uint            => TextureFormat.R8Uint,
+                Format.R8Sint            => TextureFormat.R8Sint,
+                Format.R16Float          => TextureFormat.R16Float,
+                Format.R16Unorm          => TextureFormat.R16Unorm,
+                Format.R16Snorm          => TextureFormat.R16Snorm,
+                Format.R16Uint           => TextureFormat.R16Uint,
+                Format.R16Sint           => TextureFormat.R16Sint,
+                Format.R32Float          => TextureFormat.R32Float,
+                Format.R32Uint           => TextureFormat.R32Uint,
+                Format.R32Sint           => TextureFormat.R32Sint,
+                Format.R8G8Unorm         => TextureFormat.R8G8Unorm,
+                Format.R8G8Snorm         => TextureFormat.R8G8Snorm,
+                Format.R8G8Uint          => TextureFormat.R8G8Uint,
+                Format.R8G8Sint          => TextureFormat.R8G8Sint,
+                Format.R16G16Float       => TextureFormat.R16G16Float,
+                Format.R16G16Unorm       => TextureFormat.R16G16Unorm,
+                Format.R16G16Snorm       => TextureFormat.R16G16Snorm,
+                Format.R16G16Uint        => TextureFormat.R16G16Uint,
+                Format.R16G16Sint        => TextureFormat.R16G16Sint,
+                Format.R32G32Float       => TextureFormat.R32G32Float,
+                Format.R32G32Uint        => TextureFormat.R32G32Uint,
+                Format.R32G32Sint        => TextureFormat.R32G32Sint,
+                Format.R8G8B8A8Unorm     => TextureFormat.R8G8B8A8Unorm,
+                Format.R8G8B8A8Snorm     => TextureFormat.R8G8B8A8Snorm,
+                Format.R8G8B8A8Uint      => TextureFormat.R8G8B8A8Uint,
+                Format.R8G8B8A8Sint      => TextureFormat.R8G8B8A8Sint,
+                Format.R16G16B16A16Float => TextureFormat.R16G16B16A16Float,
+                Format.R16G16B16A16Unorm => TextureFormat.R16G16B16A16Unorm,
+                Format.R16G16B16A16Snorm => TextureFormat.R16G16B16A16Snorm,
+                Format.R16G16B16A16Uint  => TextureFormat.R16G16B16A16Uint,
+                Format.R16G16B16A16Sint  => TextureFormat.R16G16B16A16Sint,
+                Format.R32G32B32A32Float => TextureFormat.R32G32B32A32Float,
+                Format.R32G32B32A32Uint  => TextureFormat.R32G32B32A32Uint,
+                Format.R32G32B32A32Sint  => TextureFormat.R32G32B32A32Sint,
+                Format.R10G10B10A2Unorm  => TextureFormat.R10G10B10A2Unorm,
+                Format.R10G10B10A2Uint   => TextureFormat.R10G10B10A2Uint,
+                Format.R11G11B10Float    => TextureFormat.R11G11B10Float,
+                _                        => TextureFormat.Unknown
+            };
+        }
+
+        /// <summary>
+        /// Gets the texture descriptor for a given texture on the pool.
+        /// </summary>
+        /// <param name="handle">Index of the texture (this is the shader "fake" handle)</param>
+        /// <returns>Texture descriptor</returns>
+        private Image.TextureDescriptor GetTextureDescriptor(int handle)
+        {
+            if (_compute)
+            {
+                return _context.Methods.TextureManager.GetComputeTextureDescriptor(_state, handle);
+            }
+            else
+            {
+                return _context.Methods.TextureManager.GetGraphicsTextureDescriptor(_state, _stageIndex, handle);
+            }
+        }
+    }
+}

+ 0 - 28
Ryujinx.Graphics.Gpu/Shader/GraphicsShader.cs

@@ -1,28 +0,0 @@
-using Ryujinx.Graphics.GAL;
-
-namespace Ryujinx.Graphics.Gpu.Shader
-{
-    /// <summary>
-    /// Cached graphics shader code for all stages.
-    /// </summary>
-    class GraphicsShader
-    {
-        /// <summary>
-        /// Host shader program object.
-        /// </summary>
-        public IProgram HostProgram { get; set; }
-
-        /// <summary>
-        /// Compiled shader for each shader stage.
-        /// </summary>
-        public CachedShader[] Shaders { get; }
-
-        /// <summary>
-        /// Creates a new instance of cached graphics shader.
-        /// </summary>
-        public GraphicsShader()
-        {
-            Shaders = new CachedShader[Constants.ShaderStages];
-        }
-    }
-}

+ 46 - 0
Ryujinx.Graphics.Gpu/Shader/ShaderBundle.cs

@@ -0,0 +1,46 @@
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+    /// <summary>
+    /// Represents a program composed of one or more shader stages (for graphics shaders),
+    /// or a single shader (for compute shaders).
+    /// </summary>
+    class ShaderBundle : IDisposable
+    {
+        /// <summary>
+        /// Host shader program object.
+        /// </summary>
+        public IProgram HostProgram { get; }
+
+        /// <summary>
+        /// Compiled shader for each shader stage.
+        /// </summary>
+        public ShaderCodeHolder[] Shaders { get; }
+
+        /// <summary>
+        /// Creates a new instance of the shader bundle.
+        /// </summary>
+        /// <param name="hostProgram">Host program with all the shader stages</param>
+        /// <param name="shaders">Shaders</param>
+        public ShaderBundle(IProgram hostProgram, params ShaderCodeHolder[] shaders)
+        {
+            HostProgram = hostProgram;
+            Shaders = shaders;
+        }
+
+        /// <summary>
+        /// Dispose of the host shader resources.
+        /// </summary>
+        public void Dispose()
+        {
+            HostProgram.Dispose();
+
+            foreach (ShaderCodeHolder holder in Shaders)
+            {
+                holder?.HostShader.Dispose();
+            }
+        }
+    }
+}

+ 75 - 315
Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs

@@ -1,33 +1,25 @@
-using Ryujinx.Common.Logging;
 using Ryujinx.Graphics.GAL;
-using Ryujinx.Graphics.Gpu.Image;
 using Ryujinx.Graphics.Gpu.State;
 using Ryujinx.Graphics.Shader;
 using Ryujinx.Graphics.Shader.Translation;
 using System;
 using System.Collections.Generic;
-using System.Runtime.InteropServices;
 
 namespace Ryujinx.Graphics.Gpu.Shader
 {
-    using TextureDescriptor = Image.TextureDescriptor;
-
     /// <summary>
     /// Memory cache of shader code.
     /// </summary>
     class ShaderCache : IDisposable
     {
-        private const int MaxProgramSize = 0x100000;
-
         private const TranslationFlags DefaultFlags = TranslationFlags.DebugMode;
 
-        private GpuContext _context;
-
-        private ShaderDumper _dumper;
+        private readonly GpuContext _context;
 
-        private Dictionary<ulong, List<ComputeShader>> _cpPrograms;
+        private readonly ShaderDumper _dumper;
 
-        private Dictionary<ShaderAddresses, List<GraphicsShader>> _gpPrograms;
+        private readonly Dictionary<ulong, List<ShaderBundle>> _cpPrograms;
+        private readonly Dictionary<ShaderAddresses, List<ShaderBundle>> _gpPrograms;
 
         /// <summary>
         /// Creates a new instance of the shader cache.
@@ -39,9 +31,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
 
             _dumper = new ShaderDumper();
 
-            _cpPrograms = new Dictionary<ulong, List<ComputeShader>>();
-
-            _gpPrograms = new Dictionary<ShaderAddresses, List<GraphicsShader>>();
+            _cpPrograms = new Dictionary<ulong, List<ShaderBundle>>();
+            _gpPrograms = new Dictionary<ShaderAddresses, List<ShaderBundle>>();
         }
 
         /// <summary>
@@ -58,7 +49,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <param name="localMemorySize">Local memory size of the compute shader</param>
         /// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
         /// <returns>Compiled compute shader code</returns>
-        public ComputeShader GetComputeShader(
+        public ShaderBundle GetComputeShader(
             GpuState state,
             ulong gpuVa,
             int localSizeX,
@@ -67,20 +58,20 @@ namespace Ryujinx.Graphics.Gpu.Shader
             int localMemorySize,
             int sharedMemorySize)
         {
-            bool isCached = _cpPrograms.TryGetValue(gpuVa, out List<ComputeShader> list);
+            bool isCached = _cpPrograms.TryGetValue(gpuVa, out List<ShaderBundle> list);
 
             if (isCached)
             {
-                foreach (ComputeShader cachedCpShader in list)
+                foreach (ShaderBundle cachedCpShader in list)
                 {
-                    if (!IsShaderDifferent(cachedCpShader, gpuVa))
+                    if (IsShaderEqual(cachedCpShader, gpuVa))
                     {
                         return cachedCpShader;
                     }
                 }
             }
 
-            CachedShader shader = TranslateComputeShader(
+            ShaderCodeHolder shader = TranslateComputeShader(
                 state,
                 gpuVa,
                 localSizeX,
@@ -93,11 +84,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
 
             IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader });
 
-            ComputeShader cpShader = new ComputeShader(hostProgram, shader);
+            ShaderBundle cpShader = new ShaderBundle(hostProgram, shader);
 
             if (!isCached)
             {
-                list = new List<ComputeShader>();
+                list = new List<ShaderBundle>();
 
                 _cpPrograms.Add(gpuVa, list);
             }
@@ -117,42 +108,42 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <param name="state">Current GPU state</param>
         /// <param name="addresses">Addresses of the shaders for each stage</param>
         /// <returns>Compiled graphics shader code</returns>
-        public GraphicsShader GetGraphicsShader(GpuState state, ShaderAddresses addresses)
+        public ShaderBundle GetGraphicsShader(GpuState state, ShaderAddresses addresses)
         {
-            bool isCached = _gpPrograms.TryGetValue(addresses, out List<GraphicsShader> list);
+            bool isCached = _gpPrograms.TryGetValue(addresses, out List<ShaderBundle> list);
 
             if (isCached)
             {
-                foreach (GraphicsShader cachedGpShaders in list)
+                foreach (ShaderBundle cachedGpShaders in list)
                 {
-                    if (!IsShaderDifferent(cachedGpShaders, addresses))
+                    if (IsShaderEqual(cachedGpShaders, addresses))
                     {
                         return cachedGpShaders;
                     }
                 }
             }
 
-            GraphicsShader gpShaders = new GraphicsShader();
+            ShaderCodeHolder[] shaders = new ShaderCodeHolder[Constants.ShaderStages];
 
             if (addresses.VertexA != 0)
             {
-                gpShaders.Shaders[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex, addresses.VertexA);
+                shaders[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex, addresses.VertexA);
             }
             else
             {
-                gpShaders.Shaders[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex);
+                shaders[0] = TranslateGraphicsShader(state, ShaderStage.Vertex, addresses.Vertex);
             }
 
-            gpShaders.Shaders[1] = TranslateGraphicsShader(state, ShaderStage.TessellationControl,    addresses.TessControl);
-            gpShaders.Shaders[2] = TranslateGraphicsShader(state, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
-            gpShaders.Shaders[3] = TranslateGraphicsShader(state, ShaderStage.Geometry,               addresses.Geometry);
-            gpShaders.Shaders[4] = TranslateGraphicsShader(state, ShaderStage.Fragment,               addresses.Fragment);
+            shaders[1] = TranslateGraphicsShader(state, ShaderStage.TessellationControl,    addresses.TessControl);
+            shaders[2] = TranslateGraphicsShader(state, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
+            shaders[3] = TranslateGraphicsShader(state, ShaderStage.Geometry,               addresses.Geometry);
+            shaders[4] = TranslateGraphicsShader(state, ShaderStage.Fragment,               addresses.Fragment);
 
             List<IShader> hostShaders = new List<IShader>();
 
-            for (int stage = 0; stage < gpShaders.Shaders.Length; stage++)
+            for (int stage = 0; stage < Constants.ShaderStages; stage++)
             {
-                ShaderProgram program = gpShaders.Shaders[stage]?.Program;
+                ShaderProgram program = shaders[stage]?.Program;
 
                 if (program == null)
                 {
@@ -161,16 +152,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
 
                 IShader hostShader = _context.Renderer.CompileShader(program);
 
-                gpShaders.Shaders[stage].HostShader = hostShader;
+                shaders[stage].HostShader = hostShader;
 
                 hostShaders.Add(hostShader);
             }
 
-            gpShaders.HostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray());
+            IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray());
+
+            ShaderBundle gpShaders = new ShaderBundle(hostProgram, shaders);
 
             if (!isCached)
             {
-                list = new List<GraphicsShader>();
+                list = new List<ShaderBundle>();
 
                 _gpPrograms.Add(addresses, list);
             }
@@ -181,27 +174,27 @@ namespace Ryujinx.Graphics.Gpu.Shader
         }
 
         /// <summary>
-        /// Checks if compute shader code in memory is different from the cached shader.
+        /// Checks if compute shader code in memory is equal to the cached shader.
         /// </summary>
         /// <param name="cpShader">Cached compute shader</param>
         /// <param name="gpuVa">GPU virtual address of the shader code in memory</param>
         /// <returns>True if the code is different, false otherwise</returns>
-        private bool IsShaderDifferent(ComputeShader cpShader, ulong gpuVa)
+        private bool IsShaderEqual(ShaderBundle cpShader, ulong gpuVa)
         {
-            return IsShaderDifferent(cpShader.Shader, gpuVa);
+            return IsShaderEqual(cpShader.Shaders[0], gpuVa);
         }
 
         /// <summary>
-        /// Checks if graphics shader code from all stages in memory is different from the cached shaders.
+        /// Checks if graphics shader code from all stages in memory are equal to the cached shaders.
         /// </summary>
         /// <param name="gpShaders">Cached graphics shaders</param>
         /// <param name="addresses">GPU virtual addresses of all enabled shader stages</param>
         /// <returns>True if the code is different, false otherwise</returns>
-        private bool IsShaderDifferent(GraphicsShader gpShaders, ShaderAddresses addresses)
+        private bool IsShaderEqual(ShaderBundle gpShaders, ShaderAddresses addresses)
         {
             for (int stage = 0; stage < gpShaders.Shaders.Length; stage++)
             {
-                CachedShader shader = gpShaders.Shaders[stage];
+                ShaderCodeHolder shader = gpShaders.Shaders[stage];
 
                 ulong gpuVa = 0;
 
@@ -214,13 +207,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
                     case 4: gpuVa = addresses.Fragment;       break;
                 }
 
-                if (IsShaderDifferent(shader, gpuVa))
+                if (!IsShaderEqual(shader, gpuVa, addresses.VertexA))
                 {
-                    return true;
+                    return false;
                 }
             }
 
-            return false;
+            return true;
         }
 
         /// <summary>
@@ -228,17 +221,27 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// </summary>
         /// <param name="shader">Cached shader to compare with</param>
         /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
+        /// <param name="gpuVaA">Optional GPU virtual address of the "Vertex A" binary shader code</param>
         /// <returns>True if the code is different, false otherwise</returns>
-        private bool IsShaderDifferent(CachedShader shader, ulong gpuVa)
+        private bool IsShaderEqual(ShaderCodeHolder shader, ulong gpuVa, ulong gpuVaA = 0)
         {
             if (shader == null)
             {
-                return false;
+                return true;
             }
 
-            ReadOnlySpan<byte> memoryCode = _context.MemoryAccessor.GetSpan(gpuVa, (ulong)shader.Code.Length * 4);
+            ReadOnlySpan<byte> memoryCode = _context.MemoryAccessor.GetSpan(gpuVa, shader.Code.Length);
 
-            return !MemoryMarshal.Cast<byte, int>(memoryCode).SequenceEqual(shader.Code);
+            bool equals = memoryCode.SequenceEqual(shader.Code);
+
+            if (equals && shader.Code2 != null)
+            {
+                memoryCode = _context.MemoryAccessor.GetSpan(gpuVaA, shader.Code2.Length);
+
+                equals = memoryCode.SequenceEqual(shader.Code2);
+            }
+
+            return equals;
         }
 
         /// <summary>
@@ -252,7 +255,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <param name="localMemorySize">Local memory size of the compute shader</param>
         /// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
         /// <returns>Compiled compute shader code</returns>
-        private CachedShader TranslateComputeShader(
+        private ShaderCodeHolder TranslateComputeShader(
             GpuState state,
             ulong gpuVa,
             int localSizeX,
@@ -266,40 +269,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
                 return null;
             }
 
-            int QueryInfo(QueryInfoName info, int index)
-            {
-                return info switch
-                {
-                    QueryInfoName.ComputeLocalSizeX
-                        => localSizeX,
-                    QueryInfoName.ComputeLocalSizeY
-                        => localSizeY,
-                    QueryInfoName.ComputeLocalSizeZ
-                        => localSizeZ,
-                    QueryInfoName.ComputeLocalMemorySize
-                        => localMemorySize,
-                    QueryInfoName.ComputeSharedMemorySize
-                        => sharedMemorySize,
-                    QueryInfoName.IsTextureBuffer
-                        => Convert.ToInt32(QueryIsTextureBuffer(state, 0, index, compute: true)),
-                    QueryInfoName.IsTextureRectangle
-                        => Convert.ToInt32(QueryIsTextureRectangle(state, 0, index, compute: true)),
-                    QueryInfoName.TextureFormat
-                        => (int)QueryTextureFormat(state, 0, index, compute: true),
-                    _
-                        => QueryInfoCommon(info)
-                };
-            }
-
-            TranslatorCallbacks callbacks = new TranslatorCallbacks(QueryInfo, PrintLog);
+            GpuAccessor gpuAccessor = new GpuAccessor(_context, state, localSizeX, localSizeY, localSizeZ, localMemorySize, sharedMemorySize);
 
             ShaderProgram program;
 
-            ReadOnlySpan<byte> code = _context.MemoryAccessor.GetSpan(gpuVa, MaxProgramSize);
+            program = Translator.Translate(gpuVa, gpuAccessor, DefaultFlags | TranslationFlags.Compute);
 
-            program = Translator.Translate(code, callbacks, DefaultFlags | TranslationFlags.Compute);
-
-            int[] codeCached = MemoryMarshal.Cast<byte, int>(code.Slice(0, program.Size)).ToArray();
+            byte[] code = _context.MemoryAccessor.ReadBytes(gpuVa, program.Size);
 
             _dumper.Dump(code, compute: true, out string fullPath, out string codePath);
 
@@ -309,7 +285,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
                 program.Prepend("// " + fullPath);
             }
 
-            return new CachedShader(program, codeCached);
+            return new ShaderCodeHolder(program, code);
         }
 
         /// <summary>
@@ -323,45 +299,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <param name="gpuVa">GPU virtual address of the shader code</param>
         /// <param name="gpuVaA">Optional GPU virtual address of the "Vertex A" shader code</param>
         /// <returns>Compiled graphics shader code</returns>
-        private CachedShader TranslateGraphicsShader(GpuState state, ShaderStage stage, ulong gpuVa, ulong gpuVaA = 0)
+        private ShaderCodeHolder TranslateGraphicsShader(GpuState state, ShaderStage stage, ulong gpuVa, ulong gpuVaA = 0)
         {
             if (gpuVa == 0)
             {
                 return null;
             }
 
-            int QueryInfo(QueryInfoName info, int index)
-            {
-                return info switch
-                {
-                    QueryInfoName.IsTextureBuffer
-                        => Convert.ToInt32(QueryIsTextureBuffer(state, (int)stage - 1, index, compute: false)),
-                    QueryInfoName.IsTextureRectangle
-                        => Convert.ToInt32(QueryIsTextureRectangle(state, (int)stage - 1, index, compute: false)),
-                    QueryInfoName.PrimitiveTopology
-                        => (int)QueryPrimitiveTopology(),
-                    QueryInfoName.TextureFormat
-                        => (int)QueryTextureFormat(state, (int)stage - 1, index, compute: false),
-                    _
-                        => QueryInfoCommon(info)
-                };
-            }
-
-            TranslatorCallbacks callbacks = new TranslatorCallbacks(QueryInfo, PrintLog);
-
-            ShaderProgram program;
-
-            int[] codeCached = null;
+            GpuAccessor gpuAccessor = new GpuAccessor(_context, state, (int)stage - 1);
 
             if (gpuVaA != 0)
             {
-                ReadOnlySpan<byte> codeA = _context.MemoryAccessor.GetSpan(gpuVaA, MaxProgramSize);
-                ReadOnlySpan<byte> codeB = _context.MemoryAccessor.GetSpan(gpuVa,  MaxProgramSize);
+                ShaderProgram program = Translator.Translate(gpuVaA, gpuVa, gpuAccessor, DefaultFlags);
 
-                program = Translator.Translate(codeA, codeB, callbacks, DefaultFlags);
-
-                // TODO: We should also take "codeA" into account.
-                codeCached = MemoryMarshal.Cast<byte, int>(codeB.Slice(0, program.Size)).ToArray();
+                byte[] codeA = _context.MemoryAccessor.ReadBytes(gpuVaA, program.SizeA);
+                byte[] codeB = _context.MemoryAccessor.ReadBytes(gpuVa,  program.Size);
 
                 _dumper.Dump(codeA, compute: false, out string fullPathA, out string codePathA);
                 _dumper.Dump(codeB, compute: false, out string fullPathB, out string codePathB);
@@ -373,14 +325,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
                     program.Prepend("// " + codePathA);
                     program.Prepend("// " + fullPathA);
                 }
+
+                return new ShaderCodeHolder(program, codeB, codeA);
             }
             else
             {
-                ReadOnlySpan<byte> code = _context.MemoryAccessor.GetSpan(gpuVa, MaxProgramSize);
-
-                program = Translator.Translate(code, callbacks, DefaultFlags);
+                ShaderProgram program = Translator.Translate(gpuVa, gpuAccessor, DefaultFlags);
 
-                codeCached = MemoryMarshal.Cast<byte, int>(code.Slice(0, program.Size)).ToArray();
+                byte[] code = _context.MemoryAccessor.ReadBytes(gpuVa, program.Size);
 
                 _dumper.Dump(code, compute: false, out string fullPath, out string codePath);
 
@@ -389,195 +341,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
                     program.Prepend("// " + codePath);
                     program.Prepend("// " + fullPath);
                 }
-            }
-
-            ulong address = _context.MemoryManager.Translate(gpuVa);
-
-            return new CachedShader(program, codeCached);
-        }
-
-        /// <summary>
-        /// Gets the primitive topology for the current draw.
-        /// This is required by geometry shaders.
-        /// </summary>
-        /// <returns>Primitive topology</returns>
-        private InputTopology QueryPrimitiveTopology()
-        {
-            switch (_context.Methods.PrimitiveType)
-            {
-                case PrimitiveType.Points:
-                    return InputTopology.Points;
-                case PrimitiveType.Lines:
-                case PrimitiveType.LineLoop:
-                case PrimitiveType.LineStrip:
-                    return InputTopology.Lines;
-                case PrimitiveType.LinesAdjacency:
-                case PrimitiveType.LineStripAdjacency:
-                    return InputTopology.LinesAdjacency;
-                case PrimitiveType.Triangles:
-                case PrimitiveType.TriangleStrip:
-                case PrimitiveType.TriangleFan:
-                    return InputTopology.Triangles;
-                case PrimitiveType.TrianglesAdjacency:
-                case PrimitiveType.TriangleStripAdjacency:
-                    return InputTopology.TrianglesAdjacency;
-            }
-
-            return InputTopology.Points;
-        }
-
-        /// <summary>
-        /// Check if the target of a given texture is texture buffer.
-        /// This is required as 1D textures and buffer textures shares the same sampler type on binary shader code,
-        /// but not on GLSL.
-        /// </summary>
-        /// <param name="state">Current GPU state</param>
-        /// <param name="stageIndex">Index of the shader stage</param>
-        /// <param name="handle">Index of the texture (this is the shader "fake" handle)</param>
-        /// <param name="compute">Indicates whenever the texture descriptor is for the compute or graphics engine</param>
-        /// <returns>True if the texture is a buffer texture, false otherwise</returns>
-        private bool QueryIsTextureBuffer(GpuState state, int stageIndex, int handle, bool compute)
-        {
-            return GetTextureDescriptor(state, stageIndex, handle, compute).UnpackTextureTarget() == TextureTarget.TextureBuffer;
-        }
-
-        /// <summary>
-        /// Check if the target of a given texture is texture rectangle.
-        /// This is required as 2D textures and rectangle textures shares the same sampler type on binary shader code,
-        /// but not on GLSL.
-        /// </summary>
-        /// <param name="state">Current GPU state</param>
-        /// <param name="stageIndex">Index of the shader stage</param>
-        /// <param name="handle">Index of the texture (this is the shader "fake" handle)</param>
-        /// <param name="compute">Indicates whenever the texture descriptor is for the compute or graphics engine</param>
-        /// <returns>True if the texture is a rectangle texture, false otherwise</returns>
-        private bool QueryIsTextureRectangle(GpuState state, int stageIndex, int handle, bool compute)
-        {
-            var descriptor = GetTextureDescriptor(state, stageIndex, handle, compute);
-
-            TextureTarget target = descriptor.UnpackTextureTarget();
-
-            bool is2DTexture = target == TextureTarget.Texture2D ||
-                               target == TextureTarget.Texture2DRect;
 
-            return !descriptor.UnpackTextureCoordNormalized() && is2DTexture;
-        }
-
-        /// <summary>
-        /// Queries the format of a given texture.
-        /// </summary>
-        /// <param name="state">Current GPU state</param>
-        /// <param name="stageIndex">Index of the shader stage. This is ignored if <paramref name="compute"/> is true</param>
-        /// <param name="handle">Index of the texture (this is the shader "fake" handle)</param>
-        /// <param name="compute">Indicates whenever the texture descriptor is for the compute or graphics engine</param>
-        /// <returns>The texture format</returns>
-        private TextureFormat QueryTextureFormat(GpuState state, int stageIndex, int handle, bool compute)
-        {
-            return QueryTextureFormat(GetTextureDescriptor(state, stageIndex, handle, compute));
-        }
-
-        /// <summary>
-        /// Queries the format of a given texture.
-        /// </summary>
-        /// <param name="descriptor">Descriptor of the texture from the texture pool</param>
-        /// <returns>The texture format</returns>
-        private static TextureFormat QueryTextureFormat(TextureDescriptor descriptor)
-        {
-            if (!FormatTable.TryGetTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb(), out FormatInfo formatInfo))
-            {
-                return TextureFormat.Unknown;
+                return new ShaderCodeHolder(program, code);
             }
-
-            return formatInfo.Format switch
-            {
-                Format.R8Unorm           => TextureFormat.R8Unorm,
-                Format.R8Snorm           => TextureFormat.R8Snorm,
-                Format.R8Uint            => TextureFormat.R8Uint,
-                Format.R8Sint            => TextureFormat.R8Sint,
-                Format.R16Float          => TextureFormat.R16Float,
-                Format.R16Unorm          => TextureFormat.R16Unorm,
-                Format.R16Snorm          => TextureFormat.R16Snorm,
-                Format.R16Uint           => TextureFormat.R16Uint,
-                Format.R16Sint           => TextureFormat.R16Sint,
-                Format.R32Float          => TextureFormat.R32Float,
-                Format.R32Uint           => TextureFormat.R32Uint,
-                Format.R32Sint           => TextureFormat.R32Sint,
-                Format.R8G8Unorm         => TextureFormat.R8G8Unorm,
-                Format.R8G8Snorm         => TextureFormat.R8G8Snorm,
-                Format.R8G8Uint          => TextureFormat.R8G8Uint,
-                Format.R8G8Sint          => TextureFormat.R8G8Sint,
-                Format.R16G16Float       => TextureFormat.R16G16Float,
-                Format.R16G16Unorm       => TextureFormat.R16G16Unorm,
-                Format.R16G16Snorm       => TextureFormat.R16G16Snorm,
-                Format.R16G16Uint        => TextureFormat.R16G16Uint,
-                Format.R16G16Sint        => TextureFormat.R16G16Sint,
-                Format.R32G32Float       => TextureFormat.R32G32Float,
-                Format.R32G32Uint        => TextureFormat.R32G32Uint,
-                Format.R32G32Sint        => TextureFormat.R32G32Sint,
-                Format.R8G8B8A8Unorm     => TextureFormat.R8G8B8A8Unorm,
-                Format.R8G8B8A8Snorm     => TextureFormat.R8G8B8A8Snorm,
-                Format.R8G8B8A8Uint      => TextureFormat.R8G8B8A8Uint,
-                Format.R8G8B8A8Sint      => TextureFormat.R8G8B8A8Sint,
-                Format.R16G16B16A16Float => TextureFormat.R16G16B16A16Float,
-                Format.R16G16B16A16Unorm => TextureFormat.R16G16B16A16Unorm,
-                Format.R16G16B16A16Snorm => TextureFormat.R16G16B16A16Snorm,
-                Format.R16G16B16A16Uint  => TextureFormat.R16G16B16A16Uint,
-                Format.R16G16B16A16Sint  => TextureFormat.R16G16B16A16Sint,
-                Format.R32G32B32A32Float => TextureFormat.R32G32B32A32Float,
-                Format.R32G32B32A32Uint  => TextureFormat.R32G32B32A32Uint,
-                Format.R32G32B32A32Sint  => TextureFormat.R32G32B32A32Sint,
-                Format.R10G10B10A2Unorm  => TextureFormat.R10G10B10A2Unorm,
-                Format.R10G10B10A2Uint   => TextureFormat.R10G10B10A2Uint,
-                Format.R11G11B10Float    => TextureFormat.R11G11B10Float,
-                _                        => TextureFormat.Unknown
-            };
-        }
-
-        /// <summary>
-        /// Gets the texture descriptor for a given texture on the pool.
-        /// </summary>
-        /// <param name="state">Current GPU state</param>
-        /// <param name="stageIndex">Index of the shader stage. This is ignored if <paramref name="compute"/> is true</param>
-        /// <param name="handle">Index of the texture (this is the shader "fake" handle)</param>
-        /// <param name="compute">Indicates whenever the texture descriptor is for the compute or graphics engine</param>
-        /// <returns>Texture descriptor</returns>
-        private TextureDescriptor GetTextureDescriptor(GpuState state, int stageIndex, int handle, bool compute)
-        {
-            if (compute)
-            {
-                return _context.Methods.TextureManager.GetComputeTextureDescriptor(state, handle);
-            }
-            else
-            {
-                return _context.Methods.TextureManager.GetGraphicsTextureDescriptor(state, stageIndex, handle);
-            }
-        }
-
-        /// <summary>
-        /// Returns information required by both compute and graphics shader compilation.
-        /// </summary>
-        /// <param name="info">Information queried</param>
-        /// <returns>Requested information</returns>
-        private int QueryInfoCommon(QueryInfoName info)
-        {
-            return info switch
-            {
-                QueryInfoName.StorageBufferOffsetAlignment
-                    => _context.Capabilities.StorageBufferOffsetAlignment,
-                QueryInfoName.SupportsNonConstantTextureOffset
-                    => Convert.ToInt32(_context.Capabilities.SupportsNonConstantTextureOffset),
-                _
-                    => 0
-            };
-        }
-
-        /// <summary>
-        /// Prints a warning from the shader code translator.
-        /// </summary>
-        /// <param name="message">Warning message</param>
-        private static void PrintLog(string message)
-        {
-            Logger.PrintWarning(LogClass.Gpu, $"Shader translator: {message}");
         }
 
         /// <summary>
@@ -586,25 +352,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// </summary>
         public void Dispose()
         {
-            foreach (List<ComputeShader> list in _cpPrograms.Values)
+            foreach (List<ShaderBundle> list in _cpPrograms.Values)
             {
-                foreach (ComputeShader shader in list)
+                foreach (ShaderBundle bundle in list)
                 {
-                    shader.HostProgram.Dispose();
-                    shader.Shader?.HostShader.Dispose();
+                    bundle.Dispose();
                 }
             }
 
-            foreach (List<GraphicsShader> list in _gpPrograms.Values)
+            foreach (List<ShaderBundle> list in _gpPrograms.Values)
             {
-                foreach (GraphicsShader shader in list)
+                foreach (ShaderBundle bundle in list)
                 {
-                    shader.HostProgram.Dispose();
-
-                    foreach (CachedShader cachedShader in shader.Shaders)
-                    {
-                        cachedShader?.HostShader.Dispose();
-                    }
+                    bundle.Dispose();
                 }
             }
         }

+ 11 - 4
Ryujinx.Graphics.Gpu/Shader/CachedShader.cs → Ryujinx.Graphics.Gpu/Shader/ShaderCodeHolder.cs

@@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
     /// <summary>
     /// Cached shader code for a single shader stage.
     /// </summary>
-    class CachedShader
+    class ShaderCodeHolder
     {
         /// <summary>
         /// Shader program containing translated code.
@@ -21,17 +21,24 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <summary>
         /// Maxwell binary shader code.
         /// </summary>
-        public int[] Code { get; }
+        public byte[] Code { get; }
 
         /// <summary>
-        /// Creates a new instace of the cached shader.
+        /// Optional maxwell binary shader code for "Vertex A" shader.
+        /// </summary>
+        public byte[] Code2 { get; }
+
+        /// <summary>
+        /// Creates a new instace of the shader code holder.
         /// </summary>
         /// <param name="program">Shader program</param>
         /// <param name="code">Maxwell binary shader code</param>
-        public CachedShader(ShaderProgram program, int[] code)
+        /// <param name="code2">Optional binary shader code of the "Vertex A" shader, when combined with "Vertex B"</param>
+        public ShaderCodeHolder(ShaderProgram program, byte[] code, byte[] code2 = null)
         {
             Program = program;
             Code    = code;
+            Code2   = code2;
         }
     }
 }

+ 27 - 26
Ryujinx.Graphics.Gpu/Shader/ShaderDumper.cs

@@ -1,4 +1,3 @@
-using Ryujinx.Graphics.Shader.Translation;
 using System;
 using System.IO;
 
@@ -11,13 +10,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
     {
         private string _runtimeDir;
         private string _dumpPath;
-        private int    _dumpIndex;
 
-        public int CurrentDumpIndex => _dumpIndex;
+        /// <summary>
+        /// Current index of the shader dump binary file.
+        /// This is incremented after each save, in order to give unique names to the files.
+        /// </summary>
+        public int CurrentDumpIndex { get; private set; }
 
+        /// <summary>
+        /// Creates a new instance of the shader dumper.
+        /// </summary>
         public ShaderDumper()
         {
-            _dumpIndex = 1;
+            CurrentDumpIndex = 1;
         }
 
         /// <summary>
@@ -27,7 +32,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <param name="compute">True for compute shader code, false for graphics shader code</param>
         /// <param name="fullPath">Output path for the shader code with header included</param>
         /// <param name="codePath">Output path for the shader code without header</param>
-        public void Dump(ReadOnlySpan<byte> code, bool compute, out string fullPath, out string codePath)
+        public void Dump(byte[] code, bool compute, out string fullPath, out string codePath)
         {
             _dumpPath = GraphicsConfig.ShadersDumpPath;
 
@@ -39,38 +44,34 @@ namespace Ryujinx.Graphics.Gpu.Shader
                 return;
             }
 
-            string fileName = "Shader" + _dumpIndex.ToString("d4") + ".bin";
+            string fileName = "Shader" + CurrentDumpIndex.ToString("d4") + ".bin";
 
             fullPath = Path.Combine(FullDir(), fileName);
             codePath = Path.Combine(CodeDir(), fileName);
 
-            _dumpIndex++;
+            CurrentDumpIndex++;
 
-            code = Translator.ExtractCode(code, compute, out int headerSize);
+            using MemoryStream stream = new MemoryStream(code);
+            BinaryReader codeReader = new BinaryReader(stream);
 
-            using (MemoryStream stream = new MemoryStream(code.ToArray()))
-            {
-                BinaryReader codeReader = new BinaryReader(stream);
+            using FileStream fullFile = File.Create(fullPath);
+            using FileStream codeFile = File.Create(codePath);
+            BinaryWriter fullWriter = new BinaryWriter(fullFile);
+            BinaryWriter codeWriter = new BinaryWriter(codeFile);
 
-                using (FileStream fullFile = File.Create(fullPath))
-                using (FileStream codeFile = File.Create(codePath))
-                {
-                    BinaryWriter fullWriter = new BinaryWriter(fullFile);
-                    BinaryWriter codeWriter = new BinaryWriter(codeFile);
+            int headerSize = compute ? 0 : 0x50;
 
-                    fullWriter.Write(codeReader.ReadBytes(headerSize));
+            fullWriter.Write(codeReader.ReadBytes(headerSize));
 
-                    byte[] temp = codeReader.ReadBytes(code.Length - headerSize);
+            byte[] temp = codeReader.ReadBytes(code.Length - headerSize);
 
-                    fullWriter.Write(temp);
-                    codeWriter.Write(temp);
+            fullWriter.Write(temp);
+            codeWriter.Write(temp);
 
-                    // Align to meet nvdisasm requirements.
-                    while (codeFile.Length % 0x20 != 0)
-                    {
-                        codeWriter.Write(0);
-                    }
-                }
+            // Align to meet nvdisasm requirements.
+            while (codeFile.Length % 0x20 != 0)
+            {
+                codeWriter.Write(0);
             }
         }
 

+ 18 - 3
Ryujinx.Graphics.Nvdec/VDec/VideoDecoder.cs

@@ -1,6 +1,8 @@
 using Ryujinx.Graphics.Gpu;
+using Ryujinx.Graphics.Gpu.Memory;
 using Ryujinx.Graphics.Vic;
 using System;
+using System.Runtime.InteropServices;
 
 namespace Ryujinx.Graphics.VDec
 {
@@ -70,7 +72,7 @@ namespace Ryujinx.Graphics.VDec
                     ScalingMatrix8 = gpu.MemoryAccessor.ReadBytes(_decoderContextAddress + 0x220, 2 * 64)
                 };
 
-                byte[] frameData = gpu.MemoryAccessor.ReadBytes(_frameDataAddress, (ulong)frameDataSize);
+                byte[] frameData = gpu.MemoryAccessor.ReadBytes(_frameDataAddress, frameDataSize);
 
                 _h264Decoder.Decode(Params, matrices, frameData);
             }
@@ -86,7 +88,7 @@ namespace Ryujinx.Graphics.VDec
                     Ref2Key = (long)gpu.MemoryManager.Translate(_vpxRef2LumaAddress)
                 };
 
-                Vp9FrameHeader header = gpu.MemoryAccessor.Read<Vp9FrameHeader>(_decoderContextAddress + 0x48);
+                Vp9FrameHeader header = ReadStruct<Vp9FrameHeader>(gpu.MemoryAccessor, _decoderContextAddress + 0x48);
 
                 Vp9ProbabilityTables probs = new Vp9ProbabilityTables()
                 {
@@ -117,7 +119,7 @@ namespace Ryujinx.Graphics.VDec
                     MvHpProbs             = gpu.MemoryAccessor.ReadBytes(_vpxProbTablesAddress + 0x54a, 0x2)
                 };
 
-                byte[] frameData = gpu.MemoryAccessor.ReadBytes(_frameDataAddress, (ulong)frameDataSize);
+                byte[] frameData = gpu.MemoryAccessor.ReadBytes(_frameDataAddress, frameDataSize);
 
                 _vp9Decoder.Decode(keys, header, probs, frameData);
             }
@@ -127,6 +129,19 @@ namespace Ryujinx.Graphics.VDec
             }
         }
 
+        private T ReadStruct<T>(MemoryAccessor accessor, ulong address) where T : struct
+        {
+            byte[] data = accessor.ReadBytes(address, Marshal.SizeOf<T>());
+
+            unsafe
+            {
+                fixed (byte* ptr = data)
+                {
+                    return Marshal.PtrToStructure<T>((IntPtr)ptr);
+                }
+            }
+        }
+
         private void SetDecoderCtxAddr(int[] arguments)
         {
             _decoderContextAddress = GetAddress(arguments);

+ 6 - 6
Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs

@@ -34,7 +34,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
 
             if (context.Config.Stage == ShaderStage.Geometry)
             {
-                string inPrimitive = ((InputTopology)context.Config.QueryInfo(QueryInfoName.PrimitiveTopology)).ToGlslString();
+                string inPrimitive = context.Config.GpuAccessor.QueryPrimitiveTopology().ToGlslString();
 
                 context.AppendLine($"layout ({inPrimitive}) in;");
 
@@ -48,7 +48,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
 
             if (context.Config.Stage == ShaderStage.Compute)
             {
-                int localMemorySize = BitUtils.DivRoundUp(context.Config.QueryInfo(QueryInfoName.ComputeLocalMemorySize), 4);
+                int localMemorySize = BitUtils.DivRoundUp(context.Config.GpuAccessor.QueryComputeLocalMemorySize(), 4);
 
                 if (localMemorySize != 0)
                 {
@@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
                     context.AppendLine();
                 }
 
-                int sharedMemorySize = BitUtils.DivRoundUp(context.Config.QueryInfo(QueryInfoName.ComputeSharedMemorySize), 4);
+                int sharedMemorySize = BitUtils.DivRoundUp(context.Config.GpuAccessor.QueryComputeSharedMemorySize(), 4);
 
                 if (sharedMemorySize != 0)
                 {
@@ -124,9 +124,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             }
             else
             {
-                string localSizeX = NumberFormatter.FormatInt(context.Config.QueryInfo(QueryInfoName.ComputeLocalSizeX));
-                string localSizeY = NumberFormatter.FormatInt(context.Config.QueryInfo(QueryInfoName.ComputeLocalSizeY));
-                string localSizeZ = NumberFormatter.FormatInt(context.Config.QueryInfo(QueryInfoName.ComputeLocalSizeZ));
+                string localSizeX = NumberFormatter.FormatInt(context.Config.GpuAccessor.QueryComputeLocalSizeX());
+                string localSizeY = NumberFormatter.FormatInt(context.Config.GpuAccessor.QueryComputeLocalSizeY());
+                string localSizeZ = NumberFormatter.FormatInt(context.Config.GpuAccessor.QueryComputeLocalSizeZ());
 
                 context.AppendLine(
                     "layout (" +

+ 9 - 25
Ryujinx.Graphics.Shader/Decoders/Decoder.cs

@@ -1,6 +1,5 @@
 using Ryujinx.Graphics.Shader.Instructions;
 using System;
-using System.Buffers.Binary;
 using System.Collections.Generic;
 using System.Linq;
 
@@ -10,7 +9,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
 {
     static class Decoder
     {
-        public static Block[] Decode(ReadOnlySpan<byte> code, ulong headerSize)
+        public static Block[] Decode(IGpuAccessor gpuAccessor, ulong startAddress)
         {
             List<Block> blocks = new List<Block>();
 
@@ -18,8 +17,6 @@ namespace Ryujinx.Graphics.Shader.Decoders
 
             Dictionary<ulong, Block> visited = new Dictionary<ulong, Block>();
 
-            ulong maxAddress = (ulong)code.Length - headerSize;
-
             Block GetBlock(ulong blkAddress)
             {
                 if (!visited.TryGetValue(blkAddress, out Block block))
@@ -56,7 +53,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
                 }
 
                 // If we have a block after the current one, set the limit address.
-                ulong limitAddress = maxAddress;
+                ulong limitAddress = ulong.MaxValue;
 
                 if (nBlkIndex != blocks.Count)
                 {
@@ -74,7 +71,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
                     }
                 }
 
-                FillBlock(code, currBlock, limitAddress, headerSize);
+                FillBlock(gpuAccessor, currBlock, limitAddress, startAddress);
 
                 if (currBlock.OpCodes.Count != 0)
                 {
@@ -82,11 +79,6 @@ namespace Ryujinx.Graphics.Shader.Decoders
                     // including those from SSY/PBK instructions.
                     foreach (OpCodePush pushOp in currBlock.PushOpCodes)
                     {
-                        if (pushOp.GetAbsoluteAddress() >= maxAddress)
-                        {
-                            return null;
-                        }
-
                         GetBlock(pushOp.GetAbsoluteAddress());
                     }
 
@@ -98,11 +90,6 @@ namespace Ryujinx.Graphics.Shader.Decoders
 
                     if (lastOp is OpCodeBranch opBr)
                     {
-                        if (opBr.GetAbsoluteAddress() >= maxAddress)
-                        {
-                            return null;
-                        }
-
                         currBlock.Branch = GetBlock(opBr.GetAbsoluteAddress());
                     }
                     else if (lastOp is OpCodeBranchIndir opBrIndir)
@@ -141,7 +128,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
                 }
 
                 // Do we have a block after the current one?
-                if (!IsExit(currBlock.GetLastOp()) && currBlock.BrIndir != null && currBlock.EndAddress < maxAddress)
+                if (!IsExit(currBlock.GetLastOp()) && currBlock.BrIndir != null)
                 {
                     bool targetVisited = visited.ContainsKey(currBlock.EndAddress);
 
@@ -203,10 +190,10 @@ namespace Ryujinx.Graphics.Shader.Decoders
         }
 
         private static void FillBlock(
-            ReadOnlySpan<byte> code,
-            Block              block,
-            ulong              limitAddress,
-            ulong              startAddress)
+            IGpuAccessor gpuAccessor,
+            Block        block,
+            ulong        limitAddress,
+            ulong        startAddress)
         {
             ulong address = block.Address;
 
@@ -225,14 +212,11 @@ namespace Ryujinx.Graphics.Shader.Decoders
                     continue;
                 }
 
-                uint word0 = BinaryPrimitives.ReadUInt32LittleEndian(code.Slice((int)(startAddress + address)));
-                uint word1 = BinaryPrimitives.ReadUInt32LittleEndian(code.Slice((int)(startAddress + address + 4)));
-
                 ulong opAddress = address;
 
                 address += 8;
 
-                long opCode = word0 | (long)word1 << 32;
+                long opCode = gpuAccessor.MemoryRead<long>(startAddress + opAddress);
 
                 (InstEmitter emitter, OpCodeTable.OpActivator opActivator) = OpCodeTable.GetEmitter(opCode);
 

+ 67 - 0
Ryujinx.Graphics.Shader/IGpuAccessor.cs

@@ -0,0 +1,67 @@
+namespace Ryujinx.Graphics.Shader
+{
+    public interface IGpuAccessor
+    {
+        public void Log(string message)
+        {
+            // No default log output.
+        }
+
+        T MemoryRead<T>(ulong address) where T : unmanaged;
+
+        public int QueryComputeLocalSizeX()
+        {
+            return 1;
+        }
+
+        public int QueryComputeLocalSizeY()
+        {
+            return 1;
+        }
+
+        public int QueryComputeLocalSizeZ()
+        {
+            return 1;
+        }
+
+        public int QueryComputeLocalMemorySize()
+        {
+            return 0x1000;
+        }
+
+        public int QueryComputeSharedMemorySize()
+        {
+            return 0xc000;
+        }
+
+        public bool QueryIsTextureBuffer(int handle)
+        {
+            return false;
+        }
+
+        public bool QueryIsTextureRectangle(int handle)
+        {
+            return false;
+        }
+
+        public InputTopology QueryPrimitiveTopology()
+        {
+            return InputTopology.Points;
+        }
+
+        public int QueryStorageBufferOffsetAlignment()
+        {
+            return 16;
+        }
+
+        public bool QuerySupportsNonConstantTextureOffset()
+        {
+            return true;
+        }
+
+        public TextureFormat QueryTextureFormat(int handle)
+        {
+            return TextureFormat.R8G8B8A8Unorm;
+        }
+    }
+}

+ 2 - 2
Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs

@@ -72,7 +72,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
             if (intType == IntegerType.U64)
             {
-                context.Config.PrintLog("Unimplemented 64-bits F2I.");
+                context.Config.GpuAccessor.Log("Unimplemented 64-bits F2I.");
 
                 return;
             }
@@ -184,7 +184,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
             if (srcType == IntegerType.U64 || dstType == IntegerType.U64)
             {
-                context.Config.PrintLog("Invalid I2I encoding.");
+                context.Config.GpuAccessor.Log("Invalid I2I encoding.");
 
                 return;
             }

+ 1 - 1
Ryujinx.Graphics.Shader/Instructions/InstEmitFArith.cs

@@ -431,7 +431,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
                 if (scale.AsFloat() == 1)
                 {
-                    context.Config.PrintLog($"Invalid FP multiply scale \"{op.Scale}\".");
+                    context.Config.GpuAccessor.Log($"Invalid FP multiply scale \"{op.Scale}\".");
                 }
 
                 if (isFP64)

+ 11 - 11
Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs

@@ -88,7 +88,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
             }
             else
             {
-                context.Config.PrintLog($"Invalid barrier mode: {op.Mode}.");
+                context.Config.GpuAccessor.Log($"Invalid barrier mode: {op.Mode}.");
             }
         }
 
@@ -141,7 +141,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
             if (op.Size > IntegerSize.B64)
             {
-                context.Config.PrintLog($"Invalid LDC size: {op.Size}.");
+                context.Config.GpuAccessor.Log($"Invalid LDC size: {op.Size}.");
             }
 
             bool isSmallInt = op.Size < IntegerSize.B32;
@@ -209,7 +209,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
             if (!(emit || cut))
             {
-                context.Config.PrintLog("Invalid OUT encoding.");
+                context.Config.GpuAccessor.Log("Invalid OUT encoding.");
             }
 
             if (emit)
@@ -274,7 +274,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
                     }
                     else
                     {
-                        context.Config.PrintLog($"Invalid reduction type: {type}.");
+                        context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
                     }
                     break;
                 case AtomicOp.BitwiseAnd:
@@ -284,7 +284,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
                     }
                     else
                     {
-                        context.Config.PrintLog($"Invalid reduction type: {type}.");
+                        context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
                     }
                     break;
                 case AtomicOp.BitwiseExclusiveOr:
@@ -294,7 +294,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
                     }
                     else
                     {
-                        context.Config.PrintLog($"Invalid reduction type: {type}.");
+                        context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
                     }
                     break;
                 case AtomicOp.BitwiseOr:
@@ -304,7 +304,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
                     }
                     else
                     {
-                        context.Config.PrintLog($"Invalid reduction type: {type}.");
+                        context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
                     }
                     break;
                 case AtomicOp.Maximum:
@@ -318,7 +318,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
                     }
                     else
                     {
-                        context.Config.PrintLog($"Invalid reduction type: {type}.");
+                        context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
                     }
                     break;
                 case AtomicOp.Minimum:
@@ -332,7 +332,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
                     }
                     else
                     {
-                        context.Config.PrintLog($"Invalid reduction type: {type}.");
+                        context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
                     }
                     break;
             }
@@ -346,7 +346,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
             if (op.Size > IntegerSize.B128)
             {
-                context.Config.PrintLog($"Invalid load size: {op.Size}.");
+                context.Config.GpuAccessor.Log($"Invalid load size: {op.Size}.");
             }
 
             bool isSmallInt = op.Size < IntegerSize.B32;
@@ -432,7 +432,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
             if (op.Size > IntegerSize.B128)
             {
-                context.Config.PrintLog($"Invalid store size: {op.Size}.");
+                context.Config.GpuAccessor.Log($"Invalid store size: {op.Size}.");
             }
 
             bool isSmallInt = op.Size < IntegerSize.B32;

+ 1 - 1
Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs

@@ -32,7 +32,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
             if (isCC)
             {
                 // TODO: Support Register to condition code flags copy.
-                context.Config.PrintLog("R2P.CC not implemented.");
+                context.Config.GpuAccessor.Log("R2P.CC not implemented.");
             }
             else
             {

+ 8 - 8
Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs

@@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
             if (type == SamplerType.None)
             {
-                context.Config.PrintLog("Invalid image store sampler type.");
+                context.Config.GpuAccessor.Log("Invalid image store sampler type.");
 
                 return;
             }
@@ -158,7 +158,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
             if (type == SamplerType.None)
             {
-                context.Config.PrintLog("Invalid image store sampler type.");
+                context.Config.GpuAccessor.Log("Invalid image store sampler type.");
 
                 return;
             }
@@ -344,7 +344,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
                 if (type == SamplerType.None)
                 {
-                    context.Config.PrintLog("Invalid texture sampler type.");
+                    context.Config.GpuAccessor.Log("Invalid texture sampler type.");
 
                     return;
                 }
@@ -423,14 +423,14 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
                 if (type == SamplerType.None)
                 {
-                    context.Config.PrintLog("Invalid texel fetch sampler type.");
+                    context.Config.GpuAccessor.Log("Invalid texel fetch sampler type.");
 
                     return;
                 }
 
                 flags = ConvertTextureFlags(tldsOp.Target) | TextureFlags.IntCoords;
 
-                if (tldsOp.Target == TexelLoadTarget.Texture1DLodZero && context.Config.QueryInfoBool(QueryInfoName.IsTextureBuffer, tldsOp.Immediate))
+                if (tldsOp.Target == TexelLoadTarget.Texture1DLodZero && context.Config.GpuAccessor.QueryIsTextureBuffer(tldsOp.Immediate))
                 {
                     type   = SamplerType.TextureBuffer;
                     flags &= ~TextureFlags.LodLevel;
@@ -1020,7 +1020,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
             if (type == SamplerType.Texture1D && flags == TextureFlags.IntCoords && !isBindless)
             {
-                bool isTypeBuffer = context.Config.QueryInfoBool(QueryInfoName.IsTextureBuffer, op.Immediate);
+                bool isTypeBuffer = context.Config.GpuAccessor.QueryIsTextureBuffer(op.Immediate);
 
                 if (isTypeBuffer)
                 {
@@ -1093,11 +1093,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
         private static TextureFormat GetTextureFormat(EmitterContext context, int handle)
         {
-            var format = (TextureFormat)context.Config.QueryInfo(QueryInfoName.TextureFormat, handle);
+            var format = context.Config.GpuAccessor.QueryTextureFormat(handle);
 
             if (format == TextureFormat.Unknown)
             {
-                context.Config.PrintLog($"Unknown format for texture {handle}.");
+                context.Config.GpuAccessor.Log($"Unknown format for texture {handle}.");
 
                 format = TextureFormat.R8G8B8A8Unorm;
             }

+ 1 - 1
Ryujinx.Graphics.Shader/Instructions/InstEmitVote.cs

@@ -36,7 +36,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
             }
             else
             {
-                context.Config.PrintLog($"Invalid vote operation: {op.VoteOp}.");
+                context.Config.GpuAccessor.Log($"Invalid vote operation: {op.VoteOp}.");
             }
 
             if (!op.Rd.IsRZ)

+ 0 - 17
Ryujinx.Graphics.Shader/QueryInfoName.cs

@@ -1,17 +0,0 @@
-namespace Ryujinx.Graphics.Shader
-{
-    public enum QueryInfoName
-    {
-        ComputeLocalSizeX,
-        ComputeLocalSizeY,
-        ComputeLocalSizeZ,
-        ComputeLocalMemorySize,
-        ComputeSharedMemorySize,
-        IsTextureBuffer,
-        IsTextureRectangle,
-        PrimitiveTopology,
-        StorageBufferOffsetAlignment,
-        SupportsNonConstantTextureOffset,
-        TextureFormat
-    }
-}

+ 3 - 1
Ryujinx.Graphics.Shader/ShaderProgram.cs

@@ -10,13 +10,15 @@ namespace Ryujinx.Graphics.Shader
 
         public string Code { get; private set; }
 
+        public int SizeA { get; }
         public int Size { get; }
 
-        internal ShaderProgram(ShaderProgramInfo info, ShaderStage stage, string code, int size)
+        internal ShaderProgram(ShaderProgramInfo info, ShaderStage stage, string code, int size, int sizeA)
         {
             Info  = info;
             Stage = stage;
             Code  = code;
+            SizeA = sizeA;
             Size  = size;
         }
 

+ 3 - 3
Ryujinx.Graphics.Shader/Translation/Lowering.cs

@@ -79,7 +79,7 @@ namespace Ryujinx.Graphics.Shader.Translation
                 sbSlot        = PrependOperation(Instruction.ConditionalSelect, inRange, Const(slot), sbSlot);
             }
 
-            Operand alignMask = Const(-config.QueryInfo(QueryInfoName.StorageBufferOffsetAlignment));
+            Operand alignMask = Const(-config.GpuAccessor.QueryStorageBufferOffsetAlignment());
 
             Operand baseAddrTrunc = PrependOperation(Instruction.BitwiseAnd,    sbBaseAddrLow, alignMask);
             Operand byteOffset    = PrependOperation(Instruction.Subtract,      addrLow, baseAddrTrunc);
@@ -131,9 +131,9 @@ namespace Ryujinx.Graphics.Shader.Translation
             bool hasOffset  = (texOp.Flags & TextureFlags.Offset)  != 0;
             bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
 
-            bool hasInvalidOffset = (hasOffset || hasOffsets) && !config.QueryInfoBool(QueryInfoName.SupportsNonConstantTextureOffset);
+            bool hasInvalidOffset = (hasOffset || hasOffsets) && !config.GpuAccessor.QuerySupportsNonConstantTextureOffset();
 
-            bool isRect = config.QueryInfoBool(QueryInfoName.IsTextureRectangle, texOp.Handle);
+            bool isRect = config.GpuAccessor.QueryIsTextureRectangle(texOp.Handle);
 
             if (!(hasInvalidOffset || isRect))
             {

+ 2 - 2
Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs

@@ -69,7 +69,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
 
                 Operand baseAddrTrunc = Local();
 
-                Operand alignMask = Const(-config.QueryInfo(QueryInfoName.StorageBufferOffsetAlignment));
+                Operand alignMask = Const(-config.GpuAccessor.QueryStorageBufferOffsetAlignment());
 
                 Operation andOp = new Operation(Instruction.BitwiseAnd, baseAddrTrunc, baseAddrLow, alignMask);
 
@@ -140,7 +140,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
 
                 Operand baseAddrTrunc = Local();
 
-                Operand alignMask = Const(-config.QueryInfo(QueryInfoName.StorageBufferOffsetAlignment));
+                Operand alignMask = Const(-config.GpuAccessor.QueryStorageBufferOffsetAlignment());
 
                 Operation andOp = new Operation(Instruction.BitwiseAnd, baseAddrTrunc, baseAddrLow, alignMask);
 

+ 6 - 52
Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs

@@ -18,11 +18,11 @@ namespace Ryujinx.Graphics.Shader.Translation
         public bool         OmapSampleMask { get; }
         public bool         OmapDepth      { get; }
 
-        public TranslationFlags Flags { get; }
+        public IGpuAccessor GpuAccessor { get; }
 
-        private TranslatorCallbacks _callbacks;
+        public TranslationFlags Flags { get; }
 
-        public ShaderConfig(TranslationFlags flags, TranslatorCallbacks callbacks)
+        public ShaderConfig(IGpuAccessor gpuAccessor, TranslationFlags flags)
         {
             Stage             = ShaderStage.Compute;
             OutputTopology    = OutputTopology.PointList;
@@ -32,11 +32,11 @@ namespace Ryujinx.Graphics.Shader.Translation
             OmapTargets       = null;
             OmapSampleMask    = false;
             OmapDepth         = false;
+            GpuAccessor       = gpuAccessor;
             Flags             = flags;
-            _callbacks        = callbacks;
         }
 
-        public ShaderConfig(ShaderHeader header, TranslationFlags flags, TranslatorCallbacks callbacks)
+        public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationFlags flags)
         {
             Stage             = header.Stage;
             OutputTopology    = header.OutputTopology;
@@ -46,8 +46,8 @@ namespace Ryujinx.Graphics.Shader.Translation
             OmapTargets       = header.OmapTargets;
             OmapSampleMask    = header.OmapSampleMask;
             OmapDepth         = header.OmapDepth;
+            GpuAccessor       = gpuAccessor;
             Flags             = flags;
-            _callbacks        = callbacks;
         }
 
         public int GetDepthRegister()
@@ -68,51 +68,5 @@ namespace Ryujinx.Graphics.Shader.Translation
             // The depth register is always two registers after the last color output.
             return count + 1;
         }
-
-        public bool QueryInfoBool(QueryInfoName info, int index = 0)
-        {
-            return Convert.ToBoolean(QueryInfo(info, index));
-        }
-
-        public int QueryInfo(QueryInfoName info, int index = 0)
-        {
-            if (_callbacks.QueryInfo != null)
-            {
-                return _callbacks.QueryInfo(info, index);
-            }
-            else
-            {
-                switch (info)
-                {
-                    case QueryInfoName.ComputeLocalSizeX:
-                    case QueryInfoName.ComputeLocalSizeY:
-                    case QueryInfoName.ComputeLocalSizeZ:
-                        return 1;
-                    case QueryInfoName.ComputeLocalMemorySize:
-                        return 0x1000;
-                    case QueryInfoName.ComputeSharedMemorySize:
-                        return 0xc000;
-                    case QueryInfoName.IsTextureBuffer:
-                        return Convert.ToInt32(false);
-                    case QueryInfoName.IsTextureRectangle:
-                        return Convert.ToInt32(false);
-                    case QueryInfoName.PrimitiveTopology:
-                        return (int)InputTopology.Points;
-                    case QueryInfoName.StorageBufferOffsetAlignment:
-                        return 16;
-                    case QueryInfoName.SupportsNonConstantTextureOffset:
-                        return Convert.ToInt32(true);
-                    case QueryInfoName.TextureFormat:
-                        return (int)TextureFormat.R8G8B8A8Unorm;
-                }
-            }
-
-            return 0;
-        }
-
-        public void PrintLog(string message)
-        {
-            _callbacks.PrintLog?.Invoke(message);
-        }
     }
 }

+ 16 - 22
Ryujinx.Graphics.Shader/Translation/ShaderHeader.cs

@@ -1,6 +1,5 @@
 using Ryujinx.Graphics.Shader.Decoders;
 using System;
-using System.Runtime.InteropServices;
 
 namespace Ryujinx.Graphics.Shader.Translation
 {
@@ -110,15 +109,13 @@ namespace Ryujinx.Graphics.Shader.Translation
         public bool         OmapSampleMask { get; }
         public bool         OmapDepth      { get; }
 
-        public ShaderHeader(ReadOnlySpan<byte> code)
+        public ShaderHeader(IGpuAccessor gpuAccessor, ulong address)
         {
-            ReadOnlySpan<int> header = MemoryMarshal.Cast<byte, int>(code);
-
-            int commonWord0 = header[0];
-            int commonWord1 = header[1];
-            int commonWord2 = header[2];
-            int commonWord3 = header[3];
-            int commonWord4 = header[4];
+            int commonWord0 = gpuAccessor.MemoryRead<int>(address + 0);
+            int commonWord1 = gpuAccessor.MemoryRead<int>(address + 4);
+            int commonWord2 = gpuAccessor.MemoryRead<int>(address + 8);
+            int commonWord3 = gpuAccessor.MemoryRead<int>(address + 12);
+            int commonWord4 = gpuAccessor.MemoryRead<int>(address + 16);
 
             SphType = commonWord0.Extract(0, 5);
             Version = commonWord0.Extract(5, 5);
@@ -163,22 +160,19 @@ namespace Ryujinx.Graphics.Shader.Translation
 
             ImapTypes = new ImapPixelType[32];
 
-            for (int i = 0; i < 8; i++)
+            for (ulong i = 0; i < 32; i++)
             {
-                for (int j = 0; j < 4; j++)
-                {
-                    byte imap = (byte)(header[6 + i] >> (j * 8));
-
-                    ImapTypes[i * 4 + j] = new ImapPixelType(
-                        (PixelImap)((imap >> 0) & 3),
-                        (PixelImap)((imap >> 2) & 3),
-                        (PixelImap)((imap >> 4) & 3),
-                        (PixelImap)((imap >> 6) & 3));
-                }
+                byte imap = gpuAccessor.MemoryRead<byte>(address + 0x18 + i);
+
+                ImapTypes[i] = new ImapPixelType(
+                    (PixelImap)((imap >> 0) & 3),
+                    (PixelImap)((imap >> 2) & 3),
+                    (PixelImap)((imap >> 4) & 3),
+                    (PixelImap)((imap >> 6) & 3));
             }
 
-            int type2OmapTarget = header[18];
-            int type2Omap       = header[19];
+            int type2OmapTarget = gpuAccessor.MemoryRead<int>(address + 0x48);
+            int type2Omap       = gpuAccessor.MemoryRead<int>(address + 0x4c);
 
             OmapTargets = new OmapTarget[8];
 

+ 19 - 43
Ryujinx.Graphics.Shader/Translation/Translator.cs

@@ -14,46 +14,22 @@ namespace Ryujinx.Graphics.Shader.Translation
     {
         private const int HeaderSize = 0x50;
 
-        public static ReadOnlySpan<byte> ExtractCode(ReadOnlySpan<byte> code, bool compute, out int headerSize)
+        public static ShaderProgram Translate(ulong address, IGpuAccessor gpuAccessor, TranslationFlags flags)
         {
-            headerSize = compute ? 0 : HeaderSize;
-
-            Block[] cfg = Decoder.Decode(code, (ulong)headerSize);
-
-            if (cfg == null)
-            {
-                return code;
-            }
-
-            ulong endAddress = 0;
-
-            foreach (Block block in cfg)
-            {
-                if (endAddress < block.EndAddress)
-                {
-                    endAddress = block.EndAddress;
-                }
-            }
-
-            return code.Slice(0, headerSize + (int)endAddress);
-        }
-
-        public static ShaderProgram Translate(ReadOnlySpan<byte> code, TranslatorCallbacks callbacks, TranslationFlags flags)
-        {
-            Operation[] ops = DecodeShader(code, callbacks, flags, out ShaderConfig config, out int size);
+            Operation[] ops = DecodeShader(address, gpuAccessor, flags, out ShaderConfig config, out int size);
 
             return Translate(ops, config, size);
         }
 
-        public static ShaderProgram Translate(ReadOnlySpan<byte> vpACode, ReadOnlySpan<byte> vpBCode, TranslatorCallbacks callbacks, TranslationFlags flags)
+        public static ShaderProgram Translate(ulong addressA, ulong addressB, IGpuAccessor gpuAccessor, TranslationFlags flags)
         {
-            Operation[] vpAOps = DecodeShader(vpACode, callbacks, flags, out _, out _);
-            Operation[] vpBOps = DecodeShader(vpBCode, callbacks, flags, out ShaderConfig config, out int sizeB);
+            Operation[] opsA = DecodeShader(addressA, gpuAccessor, flags, out _, out int sizeA);
+            Operation[] opsB = DecodeShader(addressB, gpuAccessor, flags, out ShaderConfig config, out int sizeB);
 
-            return Translate(Combine(vpAOps, vpBOps), config, sizeB);
+            return Translate(Combine(opsA, opsB), config, sizeB, sizeA);
         }
 
-        private static ShaderProgram Translate(Operation[] ops, ShaderConfig config, int size)
+        private static ShaderProgram Translate(Operation[] ops, ShaderConfig config, int size, int sizeA = 0)
         {
             BasicBlock[] blocks = ControlFlowGraph.MakeCfg(ops);
 
@@ -83,34 +59,34 @@ namespace Ryujinx.Graphics.Shader.Translation
 
             string glslCode = program.Code;
 
-            return new ShaderProgram(spInfo, config.Stage, glslCode, size);
+            return new ShaderProgram(spInfo, config.Stage, glslCode, size, sizeA);
         }
 
         private static Operation[] DecodeShader(
-            ReadOnlySpan<byte>  code,
-            TranslatorCallbacks callbacks,
-            TranslationFlags    flags,
-            out ShaderConfig    config,
-            out int             size)
+            ulong            address,
+            IGpuAccessor     gpuAccessor,
+            TranslationFlags flags,
+            out ShaderConfig config,
+            out int          size)
         {
             Block[] cfg;
 
             if ((flags & TranslationFlags.Compute) != 0)
             {
-                config = new ShaderConfig(flags, callbacks);
+                config = new ShaderConfig(gpuAccessor, flags);
 
-                cfg = Decoder.Decode(code, 0);
+                cfg = Decoder.Decode(gpuAccessor, address);
             }
             else
             {
-                config = new ShaderConfig(new ShaderHeader(code), flags, callbacks);
+                config = new ShaderConfig(new ShaderHeader(gpuAccessor, address), gpuAccessor, flags);
 
-                cfg = Decoder.Decode(code, HeaderSize);
+                cfg = Decoder.Decode(gpuAccessor, address + HeaderSize);
             }
 
             if (cfg == null)
             {
-                config.PrintLog("Invalid branch detected, failed to build CFG.");
+                gpuAccessor.Log("Invalid branch detected, failed to build CFG.");
 
                 size = 0;
 
@@ -150,7 +126,7 @@ namespace Ryujinx.Graphics.Shader.Translation
                         {
                             instName = "???";
 
-                            config.PrintLog($"Invalid instruction at 0x{op.Address:X6} (0x{op.RawOpCode:X16}).");
+                            gpuAccessor.Log($"Invalid instruction at 0x{op.Address:X6} (0x{op.RawOpCode:X16}).");
                         }
 
                         string dbgComment = $"0x{op.Address:X6}: 0x{op.RawOpCode:X16} {instName}";

+ 0 - 17
Ryujinx.Graphics.Shader/Translation/TranslatorCallbacks.cs

@@ -1,17 +0,0 @@
-using System;
-
-namespace Ryujinx.Graphics.Shader.Translation
-{
-    public struct TranslatorCallbacks
-    {
-        internal Func<QueryInfoName, int, int> QueryInfo { get; }
-
-        internal Action<string> PrintLog { get; }
-
-        public TranslatorCallbacks(Func<QueryInfoName, int, int> queryInfoCallback, Action<string> printLogCallback)
-        {
-            QueryInfo = queryInfoCallback;
-            PrintLog  = printLogCallback;
-        }
-    }
-}

+ 17 - 1
Ryujinx.ShaderTools/Program.cs

@@ -2,11 +2,27 @@
 using Ryujinx.Graphics.Shader.Translation;
 using System;
 using System.IO;
+using System.Runtime.InteropServices;
 
 namespace Ryujinx.ShaderTools
 {
     class Program
     {
+        private class GpuAccessor : IGpuAccessor
+        {
+            private readonly byte[] _data;
+
+            public GpuAccessor(byte[] data)
+            {
+                _data = data;
+            }
+
+            public T MemoryRead<T>(ulong address) where T : unmanaged
+            {
+                return MemoryMarshal.Cast<byte, T>(new ReadOnlySpan<byte>(_data).Slice((int)address))[0];
+            }
+        }
+
         static void Main(string[] args)
         {
             if (args.Length == 1 || args.Length == 2)
@@ -20,7 +36,7 @@ namespace Ryujinx.ShaderTools
 
                 byte[] data = File.ReadAllBytes(args[^1]);
 
-                string code = Translator.Translate(data, new TranslatorCallbacks(null, null), flags).Code;
+                string code = Translator.Translate(0, new GpuAccessor(data), flags).Code;
 
                 Console.WriteLine(code);
             }