diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute.cs b/Ryujinx.Graphics.Gpu/Engine/Compute.cs
index fc257f99..920cf0dd 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Compute.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Compute.cs
@@ -29,7 +29,26 @@ namespace Ryujinx.Graphics.Gpu.Engine
 
             int sharedMemorySize = Math.Min(qmd.SharedMemorySize, _context.Capabilities.MaximumComputeSharedMemorySize);
 
+            uint sbEnableMask = 0;
+            uint ubEnableMask = 0;
+
+            for (int index = 0; index < Constants.TotalCpUniformBuffers; index++)
+            {
+                if (!qmd.ConstantBufferValid(index))
+                {
+                    continue;
+                }
+
+                ubEnableMask |= 1u << index;
+
+                ulong gpuVa = (uint)qmd.ConstantBufferAddrLower(index) | (ulong)qmd.ConstantBufferAddrUpper(index) << 32;
+                ulong size = (ulong)qmd.ConstantBufferSize(index);
+
+                BufferManager.SetComputeUniformBuffer(index, gpuVa, size);
+            }
+
             ComputeShader cs = ShaderCache.GetComputeShader(
+                state,
                 shaderGpuVa,
                 qmd.CtaThreadDimension0,
                 qmd.CtaThreadDimension1,
@@ -49,25 +68,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
 
             TextureManager.SetComputeTextureBufferIndex(state.Get<int>(MethodOffset.TextureBufferIndex));
 
-            ShaderProgramInfo info = cs.Shader.Program.Info;
-
-            uint sbEnableMask = 0;
-            uint ubEnableMask = 0;
-
-            for (int index = 0; index < Constants.TotalCpUniformBuffers; index++)
-            {
-                if (!qmd.ConstantBufferValid(index))
-                {
-                    continue;
-                }
-
-                ubEnableMask |= 1u << index;
-
-                ulong gpuVa = (uint)qmd.ConstantBufferAddrLower(index) | (ulong)qmd.ConstantBufferAddrUpper(index) << 32;
-                ulong size = (ulong)qmd.ConstantBufferSize(index);
-
-                BufferManager.SetComputeUniformBuffer(index, gpuVa, size);
-            }
+            ShaderProgramInfo info = cs.Shader.Program.Info;            
 
             for (int index = 0; index < info.CBuffers.Count; index++)
             {
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index 27cbc6ff..b127690b 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -207,6 +207,17 @@ namespace Ryujinx.Graphics.Gpu.Image
             UpdateRenderTargets();
         }
 
+        /// <summary>
+        /// Gets a texture descriptor used on the compute pipeline.
+        /// </summary>
+        /// <param name="state">Current GPU state</param>
+        /// <param name="handle">Shader "fake" handle of the texture</param>
+        /// <returns>The texture descriptor</returns>
+        public TextureDescriptor GetComputeTextureDescriptor(GpuState state, int handle)
+        {
+            return _cpBindingsManager.GetTextureDescriptor(state, 0, handle);
+        }
+
         /// <summary>
         /// Gets a texture descriptor used on the graphics pipeline.
         /// </summary>
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index 7ad00f32..6bbc3b11 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -50,6 +50,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <remarks>
         /// This automatically translates, compiles and adds the code to the cache if not present.
         /// </remarks>
+        /// <param name="state">Current GPU state</param>
         /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
         /// <param name="localSizeX">Local group size X of the computer shader</param>
         /// <param name="localSizeY">Local group size Y of the computer shader</param>
@@ -58,6 +59,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
         /// <returns>Compiled compute shader code</returns>
         public ComputeShader GetComputeShader(
+            GpuState state,
             ulong gpuVa,
             int localSizeX,
             int localSizeY,
@@ -79,6 +81,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
             }
 
             CachedShader shader = TranslateComputeShader(
+                state,
                 gpuVa,
                 localSizeX,
                 localSizeY,
@@ -241,6 +244,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <summary>
         /// Translates the binary Maxwell shader code to something that the host API accepts.
         /// </summary>
+        /// <param name="state">Current GPU state</param>
         /// <param name="gpuVa">GPU virtual address of the binary shader code</param>
         /// <param name="localSizeX">Local group size X of the computer shader</param>
         /// <param name="localSizeY">Local group size Y of the computer shader</param>
@@ -249,6 +253,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
         /// <returns>Compiled compute shader code</returns>
         private CachedShader TranslateComputeShader(
+            GpuState state,
             ulong gpuVa,
             int localSizeX,
             int localSizeY,
@@ -265,12 +270,20 @@ namespace Ryujinx.Graphics.Gpu.Shader
             {
                 return info switch
                 {
-                    QueryInfoName.ComputeLocalSizeX => localSizeX,
-                    QueryInfoName.ComputeLocalSizeY => localSizeY,
-                    QueryInfoName.ComputeLocalSizeZ => localSizeZ,
-                    QueryInfoName.ComputeLocalMemorySize => localMemorySize,
-                    QueryInfoName.ComputeSharedMemorySize => sharedMemorySize,
-                    _ => QueryInfoCommon(info)
+                    QueryInfoName.ComputeLocalSizeX
+                        => localSizeX,
+                    QueryInfoName.ComputeLocalSizeY
+                        => localSizeY,
+                    QueryInfoName.ComputeLocalSizeZ
+                        => localSizeZ,
+                    QueryInfoName.ComputeLocalMemorySize
+                        => localMemorySize,
+                    QueryInfoName.ComputeSharedMemorySize
+                        => sharedMemorySize,
+                    QueryInfoName.TextureFormat
+                        => (int)QueryComputeTextureFormat(state, index),
+                    _
+                        => QueryInfoCommon(info)
                 };
             }
 
@@ -317,10 +330,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
             {
                 return info switch
                 {
-                    QueryInfoName.IsTextureBuffer => Convert.ToInt32(QueryIsTextureBuffer(state, (int)stage - 1, index)),
-                    QueryInfoName.IsTextureRectangle => Convert.ToInt32(QueryIsTextureRectangle(state, (int)stage - 1, index)),
-                    QueryInfoName.PrimitiveTopology => (int)GetPrimitiveTopology(),
-                    _ => QueryInfoCommon(info)
+                    QueryInfoName.IsTextureBuffer
+                        => Convert.ToInt32(QueryIsTextureBuffer(state, (int)stage - 1, index)),
+                    QueryInfoName.IsTextureRectangle
+                        => Convert.ToInt32(QueryIsTextureRectangle(state, (int)stage - 1, index)),
+                    QueryInfoName.PrimitiveTopology
+                        => (int)QueryPrimitiveTopology(),
+                    QueryInfoName.TextureFormat
+                        => (int)QueryGraphicsTextureFormat(state, (int)stage - 1, index),
+                    _
+                        => QueryInfoCommon(info)
                 };
             }
 
@@ -378,7 +397,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// This is required by geometry shaders.
         /// </summary>
         /// <returns>Primitive topology</returns>
-        private InputTopology GetPrimitiveTopology()
+        private InputTopology QueryPrimitiveTopology()
         {
             switch (_context.Methods.PrimitiveType)
             {
@@ -414,7 +433,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <returns>True if the texture is a buffer texture, false otherwise</returns>
         private bool QueryIsTextureBuffer(GpuState state, int stageIndex, int index)
         {
-            return GetTextureDescriptor(state, stageIndex, index).UnpackTextureTarget() == TextureTarget.TextureBuffer;
+            return GetGraphicsTextureDescriptor(state, stageIndex, index).UnpackTextureTarget() == TextureTarget.TextureBuffer;
         }
 
         /// <summary>
@@ -428,7 +447,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
         /// <returns>True if the texture is a rectangle texture, false otherwise</returns>
         private bool QueryIsTextureRectangle(GpuState state, int stageIndex, int index)
         {
-            var descriptor = GetTextureDescriptor(state, stageIndex, index);
+            var descriptor = GetGraphicsTextureDescriptor(state, stageIndex, index);
 
             TextureTarget target = descriptor.UnpackTextureTarget();
 
@@ -439,15 +458,106 @@ namespace Ryujinx.Graphics.Gpu.Shader
         }
 
         /// <summary>
-        /// Gets the texture descriptor for a given texture on the pool.
+        /// Queries the format of a given texture.
+        /// </summary>
+        /// <param name="state">Current GPU state</param>
+        /// <param name="index">Index of the texture (this is the shader "fake" handle)</param>
+        /// <returns>The texture format</returns>
+        private TextureFormat QueryComputeTextureFormat(GpuState state, int index)
+        {
+            return QueryTextureFormat(GetComputeTextureDescriptor(state, index));
+        }
+
+        /// <summary>
+        /// Queries the format of a given texture.
         /// </summary>
         /// <param name="state">Current GPU state</param>
         /// <param name="stageIndex">Index of the shader stage</param>
         /// <param name="index">Index of the texture (this is the shader "fake" handle)</param>
-        /// <returns>Texture descriptor</returns>
-        private TextureDescriptor GetTextureDescriptor(GpuState state, int stageIndex, int index)
+        /// <returns>The texture format</returns>
+        private TextureFormat QueryGraphicsTextureFormat(GpuState state, int stageIndex, int index)
         {
-            return _context.Methods.TextureManager.GetGraphicsTextureDescriptor(state, stageIndex, index);
+            return QueryTextureFormat(GetGraphicsTextureDescriptor(state, stageIndex, index));
+        }
+
+        /// <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 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="handle">Index of the texture (this is the shader "fake" handle)</param>
+        /// <returns>Texture descriptor</returns>
+        private TextureDescriptor GetComputeTextureDescriptor(GpuState state, int handle)
+        {
+            return _context.Methods.TextureManager.GetComputeTextureDescriptor(state, handle);
+        }
+
+        /// <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</param>
+        /// <param name="handle">Index of the texture (this is the shader "fake" handle)</param>
+        /// <returns>Texture descriptor</returns>
+        private TextureDescriptor GetGraphicsTextureDescriptor(GpuState state, int stageIndex, int handle)
+        {
+            return _context.Methods.TextureManager.GetGraphicsTextureDescriptor(state, stageIndex, handle);
         }
 
         /// <summary>
@@ -459,9 +569,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
         {
             return info switch
             {
-                QueryInfoName.StorageBufferOffsetAlignment => _context.Capabilities.StorageBufferOffsetAlignment,
-                QueryInfoName.SupportsNonConstantTextureOffset => Convert.ToInt32(_context.Capabilities.SupportsNonConstantTextureOffset),
-                _ => 0
+                QueryInfoName.StorageBufferOffsetAlignment
+                    => _context.Capabilities.StorageBufferOffsetAlignment,
+                QueryInfoName.SupportsNonConstantTextureOffset
+                    => Convert.ToInt32(_context.Capabilities.SupportsNonConstantTextureOffset),
+                _
+                    => 0
             };
         }
 
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
index b1291906..36aff3fc 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
@@ -326,9 +326,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
                     continue;
                 }
 
-                string imageTypeName = GetImageTypeName(texOp.Type);
+                string layout = texOp.Format.ToGlslFormat();
 
-                context.AppendLine("writeonly uniform " + imageTypeName + " " + imageName + ";");
+                if (!string.IsNullOrEmpty(layout))
+                {
+                    layout = "layout(" + layout + ") ";
+                }
+
+                string imageTypeName = GetImageTypeName(texOp.Type, texOp.Format.GetComponentType());
+
+                context.AppendLine("uniform " + layout + imageTypeName + " " + imageName + ";");
             }
 
             foreach (KeyValuePair<string, AstTextureOperation> kv in images)
@@ -455,7 +462,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             return typeName;
         }
 
-        private static string GetImageTypeName(SamplerType type)
+        private static string GetImageTypeName(SamplerType type, VariableType componentType)
         {
             string typeName;
 
@@ -480,6 +487,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
                 typeName += "Array";
             }
 
+            switch (componentType)
+            {
+                case VariableType.U32: typeName = 'u' + typeName; break;
+                case VariableType.S32: typeName = 'i' + typeName; break;
+            }
+
             return typeName;
         }
     }
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs
index fe982770..f1537c3d 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs
@@ -4,6 +4,7 @@ using System;
 
 using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper;
 using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenMemory;
+using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenPacking;
 using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
 
 namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
@@ -115,53 +116,56 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
             {
                 switch (inst)
                 {
+                    case Instruction.ImageLoad:
+                        return ImageLoadOrStore(context, operation);
+
                     case Instruction.ImageStore:
-                        return InstGenMemory.ImageStore(context, operation);
+                        return ImageLoadOrStore(context, operation);
 
                     case Instruction.LoadAttribute:
-                        return InstGenMemory.LoadAttribute(context, operation);
+                        return LoadAttribute(context, operation);
 
                     case Instruction.LoadConstant:
-                        return InstGenMemory.LoadConstant(context, operation);
+                        return LoadConstant(context, operation);
 
                     case Instruction.LoadLocal:
-                        return InstGenMemory.LoadLocal(context, operation);
+                        return LoadLocal(context, operation);
 
                     case Instruction.LoadShared:
-                        return InstGenMemory.LoadShared(context, operation);
+                        return LoadShared(context, operation);
 
                     case Instruction.LoadStorage:
-                        return InstGenMemory.LoadStorage(context, operation);
+                        return LoadStorage(context, operation);
 
                     case Instruction.Lod:
-                        return InstGenMemory.Lod(context, operation);
+                        return Lod(context, operation);
 
                     case Instruction.PackDouble2x32:
-                        return InstGenPacking.PackDouble2x32(context, operation);
+                        return PackDouble2x32(context, operation);
 
                     case Instruction.PackHalf2x16:
-                        return InstGenPacking.PackHalf2x16(context, operation);
+                        return PackHalf2x16(context, operation);
 
                     case Instruction.StoreLocal:
-                        return InstGenMemory.StoreLocal(context, operation);
+                        return StoreLocal(context, operation);
 
                     case Instruction.StoreShared:
-                        return InstGenMemory.StoreShared(context, operation);
+                        return StoreShared(context, operation);
 
                     case Instruction.StoreStorage:
-                        return InstGenMemory.StoreStorage(context, operation);
+                        return StoreStorage(context, operation);
 
                     case Instruction.TextureSample:
-                        return InstGenMemory.TextureSample(context, operation);
+                        return TextureSample(context, operation);
 
                     case Instruction.TextureSize:
-                        return InstGenMemory.TextureSize(context, operation);
+                        return TextureSize(context, operation);
 
                     case Instruction.UnpackDouble2x32:
-                        return InstGenPacking.UnpackDouble2x32(context, operation);
+                        return UnpackDouble2x32(context, operation);
 
                     case Instruction.UnpackHalf2x16:
-                        return InstGenPacking.UnpackHalf2x16(context, operation);
+                        return UnpackHalf2x16(context, operation);
                 }
             }
 
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
index 74828702..d05c77df 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
@@ -9,16 +9,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
 {
     static class InstGenMemory
     {
-        public static string ImageStore(CodeGenContext context, AstOperation operation)
+        public static string ImageLoadOrStore(CodeGenContext context, AstOperation operation)
         {
             AstTextureOperation texOp = (AstTextureOperation)operation;
 
             bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
 
-            bool isArray   = (texOp.Type & SamplerType.Array)   != 0;
+            bool isArray   = (texOp.Type & SamplerType.Array) != 0;
             bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
 
-            string texCall = "imageStore";
+            string texCall = texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore";
 
             int srcIndex = isBindless ? 1 : 0;
 
@@ -40,14 +40,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
 
             int coordsCount = texOp.Type.GetDimensions();
 
-            int pCount = coordsCount;
-
-            int arrayIndexElem = -1;
-
-            if (isArray)
-            {
-                arrayIndexElem = pCount++;
-            }
+            int pCount = coordsCount + (isArray ? 1 : 0);
 
             void Append(string str)
             {
@@ -70,23 +63,40 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
                 Append(Src(VariableType.S32));
             }
 
-            string[] cElems = new string[4];
-
-            for (int index = 0; index < 4; index++)
+            if (texOp.Inst == Instruction.ImageStore)
             {
-                if (srcIndex < texOp.SourcesCount)
+                VariableType type = texOp.Format.GetComponentType();
+
+                string[] cElems = new string[4];
+
+                for (int index = 0; index < 4; index++)
                 {
-                    cElems[index] = Src(VariableType.F32);
+                    if (srcIndex < texOp.SourcesCount)
+                    {
+                        cElems[index] = Src(type);
+                    }
+                    else
+                    {
+                        cElems[index] = type switch
+                        {
+                            VariableType.S32 => NumberFormatter.FormatInt(0),
+                            VariableType.U32 => NumberFormatter.FormatUint(0),
+                            _                => NumberFormatter.FormatFloat(0)
+                        };
+                    }
                 }
-                else
+
+                string prefix = type switch
                 {
-                    cElems[index] = NumberFormatter.FormatFloat(0);
-                }
+                    VariableType.S32 => "i",
+                    VariableType.U32 => "u",
+                    _                => string.Empty
+                };
+
+                Append(prefix + "vec4(" + string.Join(", ", cElems) + ")");
             }
 
-            Append("vec4(" + string.Join(", ", cElems) + ")");
-
-            texCall += ")";
+            texCall += ")" + (texOp.Inst == Instruction.ImageLoad ? GetMask(texOp.Index) : "");
 
             return texCall;
         }
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
index 8d092d4e..e9a37d87 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
@@ -280,6 +280,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
                 {
                     return GetOperandVarType((AstOperand)operation.GetSource(0));
                 }
+                else if (operation is AstTextureOperation texOp &&
+                         (texOp.Inst == Instruction.ImageLoad ||
+                          texOp.Inst == Instruction.ImageStore))
+                {
+                    return texOp.Format.GetComponentType();
+                }
 
                 return GetDestVarType(operation.Inst);
             }
diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs
index 2f46ee32..02a8be01 100644
--- a/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs
+++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs
@@ -219,6 +219,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
             Set("1110111101010x", InstEmit.St,      typeof(OpCodeMemory));
             Set("1110111011011x", InstEmit.Stg,     typeof(OpCodeMemory));
             Set("1110111101011x", InstEmit.Sts,     typeof(OpCodeMemory));
+            Set("11101011000xxx", InstEmit.Suld,    typeof(OpCodeImage));
             Set("11101011001xxx", InstEmit.Sust,    typeof(OpCodeImage));
             Set("1111000011111x", InstEmit.Sync,    typeof(OpCodeBranchPop));
             Set("110000xxxx111x", InstEmit.Tex,     typeof(OpCodeTex));
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
index 7b9794ea..0ff2740b 100644
--- a/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
@@ -4,12 +4,152 @@ using Ryujinx.Graphics.Shader.Translation;
 using System;
 using System.Collections.Generic;
 
+using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
 using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
 
 namespace Ryujinx.Graphics.Shader.Instructions
 {
     static partial class InstEmit
     {
+        public static void Suld(EmitterContext context)
+        {
+            OpCodeImage op = (OpCodeImage)context.CurrOp;
+
+            SamplerType type = ConvertSamplerType(op.Dimensions);
+
+            if (type == SamplerType.None)
+            {
+                context.Config.PrintLog("Invalid image store sampler type.");
+
+                return;
+            }
+
+            // Rb is Rd on the SULD instruction.
+            int rdIndex = op.Rb.Index;
+            int raIndex = op.Ra.Index;
+
+            Operand Ra()
+            {
+                if (raIndex > RegisterConsts.RegisterZeroIndex)
+                {
+                    return Const(0);
+                }
+
+                return context.Copy(Register(raIndex++, RegisterType.Gpr));
+            }
+
+            bool isArray = op.Dimensions == ImageDimensions.Image1DArray ||
+                           op.Dimensions == ImageDimensions.Image2DArray;
+
+            Operand arrayIndex = isArray ? Ra() : null;
+
+            List<Operand> sourcesList = new List<Operand>();
+
+            if (op.IsBindless)
+            {
+                sourcesList.Add(context.Copy(Register(op.Rc)));
+            }
+
+            int coordsCount = type.GetDimensions();
+
+            for (int index = 0; index < coordsCount; index++)
+            {
+                sourcesList.Add(Ra());
+            }
+
+            if (isArray)
+            {
+                sourcesList.Add(arrayIndex);
+
+                type |= SamplerType.Array;
+            }
+
+            Operand[] sources = sourcesList.ToArray();
+
+            int handle = !op.IsBindless ? op.Immediate : 0;
+
+            TextureFlags flags = op.IsBindless ? TextureFlags.Bindless : TextureFlags.None;
+
+            if (op.UseComponents)
+            {
+                int componentMask = (int)op.Components;
+
+                for (int compMask = componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
+                {
+                    if ((compMask & 1) == 0)
+                    {
+                        continue;
+                    }
+
+                    if (rdIndex == RegisterConsts.RegisterZeroIndex)
+                    {
+                        break;
+                    }
+
+                    Operand rd = Register(rdIndex++, RegisterType.Gpr);
+
+                    TextureOperation operation = new TextureOperation(
+                        Instruction.ImageLoad,
+                        type,
+                        flags,
+                        handle,
+                        compIndex,
+                        rd,
+                        sources);
+
+                    if (!op.IsBindless)
+                    {
+                        operation.Format = GetTextureFormat(context, handle);
+                    }
+
+                    context.Add(operation);
+                }
+            }
+            else
+            {
+                if (op.ByteAddress)
+                {
+                    int xIndex = op.IsBindless ? 1 : 0;
+
+                    sources[xIndex] = context.ShiftRightS32(sources[xIndex], Const(GetComponentSizeInBytesLog2(op.Size)));
+                }
+
+                int components = GetComponents(op.Size);
+
+                for (int compIndex = 0; compIndex < components; compIndex++)
+                {
+                    if (rdIndex == RegisterConsts.RegisterZeroIndex)
+                    {
+                        break;
+                    }
+
+                    Operand rd = Register(rdIndex++, RegisterType.Gpr);
+
+                    TextureOperation operation = new TextureOperation(
+                        Instruction.ImageLoad,
+                        type,
+                        flags,
+                        handle,
+                        compIndex,
+                        rd,
+                        sources)
+                    {
+                        Format = GetTextureFormat(op.Size)
+                    };
+
+                    context.Add(operation);
+
+                    switch (op.Size)
+                    {
+                        case IntegerSize.U8:  context.Copy(rd, ZeroExtendTo32(context, rd, 8));  break;
+                        case IntegerSize.U16: context.Copy(rd, ZeroExtendTo32(context, rd, 16)); break;
+                        case IntegerSize.S8:  context.Copy(rd, SignExtendTo32(context, rd, 8));  break;
+                        case IntegerSize.S16: context.Copy(rd, SignExtendTo32(context, rd, 16)); break;
+                    }
+                }
+            }
+        }
+
         public static void Sust(EmitterContext context)
         {
             OpCodeImage op = (OpCodeImage)context.CurrOp;
@@ -72,6 +212,8 @@ namespace Ryujinx.Graphics.Shader.Instructions
                 type |= SamplerType.Array;
             }
 
+            TextureFormat format = TextureFormat.Unknown;
+
             if (op.UseComponents)
             {
                 int componentMask = (int)op.Components;
@@ -83,12 +225,33 @@ namespace Ryujinx.Graphics.Shader.Instructions
                         sourcesList.Add(Rb());
                     }
                 }
+
+                if (!op.IsBindless)
+                {
+                    format = GetTextureFormat(context, op.Immediate);
+                }
             }
             else
             {
-                context.Config.PrintLog("Unsized image store not supported.");
+                if (op.ByteAddress)
+                {
+                    int xIndex = op.IsBindless ? 1 : 0;
+
+                    sourcesList[xIndex] = context.ShiftRightS32(sourcesList[xIndex], Const(GetComponentSizeInBytesLog2(op.Size)));
+                }
+
+                int components = GetComponents(op.Size);
+
+                for (int compIndex = 0; compIndex < components; compIndex++)
+                {
+                    sourcesList.Add(Rb());
+                }
+
+                format = GetTextureFormat(op.Size);
             }
 
+            System.Console.WriteLine(format.ToString());
+
             Operand[] sources = sourcesList.ToArray();
 
             int handle = !op.IsBindless ? op.Immediate : 0;
@@ -102,7 +265,10 @@ namespace Ryujinx.Graphics.Shader.Instructions
                 handle,
                 0,
                 null,
-                sources);
+                sources)
+            {
+                Format = format
+            };
 
             context.Add(operation);
         }
@@ -880,43 +1046,87 @@ namespace Ryujinx.Graphics.Shader.Instructions
             }
         }
 
-        private static SamplerType ConvertSamplerType(ImageDimensions target)
+        private static int GetComponents(IntegerSize size)
         {
-            switch (target)
+            return size switch
             {
-                case ImageDimensions.Image1D:
-                    return SamplerType.Texture1D;
+                IntegerSize.B64   => 2,
+                IntegerSize.B128  => 4,
+                IntegerSize.UB128 => 4,
+                _                 => 1
+            };
+        }
 
-                case ImageDimensions.ImageBuffer:
-                    return SamplerType.TextureBuffer;
+        private static int GetComponentSizeInBytesLog2(IntegerSize size)
+        {
+            return size switch
+            {
+                IntegerSize.U8    => 0,
+                IntegerSize.S8    => 0,
+                IntegerSize.U16   => 1,
+                IntegerSize.S16   => 1,
+                IntegerSize.B32   => 2,
+                IntegerSize.B64   => 3,
+                IntegerSize.B128  => 4,
+                IntegerSize.UB128 => 4,
+                _                 => 2
+            };
+        }
 
-                case ImageDimensions.Image1DArray:
-                    return SamplerType.Texture1D | SamplerType.Array;
+        private static TextureFormat GetTextureFormat(EmitterContext context, int handle)
+        {
+            var format = (TextureFormat)context.Config.QueryInfo(QueryInfoName.TextureFormat, handle);
 
-                case ImageDimensions.Image2D:
-                    return SamplerType.Texture2D;
+            if (format == TextureFormat.Unknown)
+            {
+                context.Config.PrintLog($"Unknown format for texture {handle}.");
 
-                case ImageDimensions.Image2DArray:
-                    return SamplerType.Texture2D | SamplerType.Array;
-
-                case ImageDimensions.Image3D:
-                    return SamplerType.Texture3D;
+                format = TextureFormat.R8G8B8A8Unorm;
             }
 
-            return SamplerType.None;
+            return format;
+        }
+
+        private static TextureFormat GetTextureFormat(IntegerSize size)
+        {
+            return size switch
+            {
+                IntegerSize.U8    => TextureFormat.R8Uint,
+                IntegerSize.S8    => TextureFormat.R8Sint,
+                IntegerSize.U16   => TextureFormat.R16Uint,
+                IntegerSize.S16   => TextureFormat.R16Sint,
+                IntegerSize.B32   => TextureFormat.R32Uint,
+                IntegerSize.B64   => TextureFormat.R32G32Uint,
+                IntegerSize.B128  => TextureFormat.R32G32B32A32Uint,
+                IntegerSize.UB128 => TextureFormat.R32G32B32A32Uint,
+                _                 => TextureFormat.R32Uint
+            };
+        }
+
+        private static SamplerType ConvertSamplerType(ImageDimensions target)
+        {
+            return target switch
+            {
+                ImageDimensions.Image1D      => SamplerType.Texture1D,
+                ImageDimensions.ImageBuffer  => SamplerType.TextureBuffer,
+                ImageDimensions.Image1DArray => SamplerType.Texture1D | SamplerType.Array,
+                ImageDimensions.Image2D      => SamplerType.Texture2D,
+                ImageDimensions.Image2DArray => SamplerType.Texture2D | SamplerType.Array,
+                ImageDimensions.Image3D      => SamplerType.Texture3D,
+                _                            => SamplerType.None
+            };
         }
 
         private static SamplerType ConvertSamplerType(TextureDimensions dimensions)
         {
-            switch (dimensions)
+            return dimensions switch
             {
-                case TextureDimensions.Texture1D:   return SamplerType.Texture1D;
-                case TextureDimensions.Texture2D:   return SamplerType.Texture2D;
-                case TextureDimensions.Texture3D:   return SamplerType.Texture3D;
-                case TextureDimensions.TextureCube: return SamplerType.TextureCube;
-            }
-
-            throw new ArgumentException($"Invalid texture dimensions \"{dimensions}\".");
+                TextureDimensions.Texture1D   => SamplerType.Texture1D,
+                TextureDimensions.Texture2D   => SamplerType.Texture2D,
+                TextureDimensions.Texture3D   => SamplerType.Texture3D,
+                TextureDimensions.TextureCube => SamplerType.TextureCube,
+                _ => throw new ArgumentException($"Invalid texture dimensions \"{dimensions}\".")
+            };
         }
 
         private static SamplerType ConvertSamplerType(TextureTarget type)
diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs
index 718d2c2e..06541f90 100644
--- a/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs
+++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs
@@ -7,6 +7,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
 
         public int Handle { get; private set; }
 
+        public TextureFormat Format { get; set; }
+
         public TextureOperation(
             Instruction      inst,
             SamplerType      type,
diff --git a/Ryujinx.Graphics.Shader/QueryInfoName.cs b/Ryujinx.Graphics.Shader/QueryInfoName.cs
index 887c0d7d..41f42cbd 100644
--- a/Ryujinx.Graphics.Shader/QueryInfoName.cs
+++ b/Ryujinx.Graphics.Shader/QueryInfoName.cs
@@ -11,6 +11,7 @@ namespace Ryujinx.Graphics.Shader
         IsTextureRectangle,
         PrimitiveTopology,
         StorageBufferOffsetAlignment,
-        SupportsNonConstantTextureOffset
+        SupportsNonConstantTextureOffset,
+        TextureFormat
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
index 5473978e..a3fa3e3a 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
@@ -4,8 +4,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
 {
     class AstTextureOperation : AstOperation
     {
-        public SamplerType  Type  { get; }
-        public TextureFlags Flags { get; }
+        public SamplerType   Type   { get; }
+        public TextureFormat Format { get; }
+        public TextureFlags  Flags  { get; }
 
         public int Handle    { get; }
         public int ArraySize { get; }
@@ -13,6 +14,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
         public AstTextureOperation(
             Instruction       inst,
             SamplerType       type,
+            TextureFormat     format,
             TextureFlags      flags,
             int               handle,
             int               arraySize,
@@ -20,6 +22,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
             params IAstNode[] sources) : base(inst, index, sources)
         {
             Type      = type;
+            Format    = format;
             Flags     = flags;
             Handle    = handle;
             ArraySize = arraySize;
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
index 4758b08f..f1dd08f2 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
@@ -57,6 +57,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
                 return new AstTextureOperation(
                     inst,
                     texOp.Type,
+                    texOp.Format,
                     texOp.Flags,
                     texOp.Handle,
                     4, // TODO: Non-hardcoded array size.
@@ -118,6 +119,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
 
                 if (operation is TextureOperation texOp)
                 {
+                    if (texOp.Inst == Instruction.ImageLoad || texOp.Inst == Instruction.ImageStore)
+                    {
+                        dest.VarType = texOp.Format.GetComponentType();
+                    }
+
                     AstTextureOperation astTexOp = GetAstTextureOperation(texOp);
 
                     if (texOp.Inst == Instruction.ImageLoad)
diff --git a/Ryujinx.Graphics.Shader/TextureFormat.cs b/Ryujinx.Graphics.Shader/TextureFormat.cs
new file mode 100644
index 00000000..fb4e5b03
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/TextureFormat.cs
@@ -0,0 +1,128 @@
+using Ryujinx.Graphics.Shader.StructuredIr;
+
+namespace Ryujinx.Graphics.Shader
+{
+    public enum TextureFormat
+    {
+        Unknown,
+        R8Unorm,
+        R8Snorm,
+        R8Uint,
+        R8Sint,
+        R16Float,
+        R16Unorm,
+        R16Snorm,
+        R16Uint,
+        R16Sint,
+        R32Float,
+        R32Uint,
+        R32Sint,
+        R8G8Unorm,
+        R8G8Snorm,
+        R8G8Uint,
+        R8G8Sint,
+        R16G16Float,
+        R16G16Unorm,
+        R16G16Snorm,
+        R16G16Uint,
+        R16G16Sint,
+        R32G32Float,
+        R32G32Uint,
+        R32G32Sint,
+        R8G8B8A8Unorm,
+        R8G8B8A8Snorm,
+        R8G8B8A8Uint,
+        R8G8B8A8Sint,
+        R16G16B16A16Float,
+        R16G16B16A16Unorm,
+        R16G16B16A16Snorm,
+        R16G16B16A16Uint,
+        R16G16B16A16Sint,
+        R32G32B32A32Float,
+        R32G32B32A32Uint,
+        R32G32B32A32Sint,
+        R10G10B10A2Unorm,
+        R10G10B10A2Uint,
+        R11G11B10Float
+    }
+
+    static class TextureFormatExtensions
+    {
+        public static string ToGlslFormat(this TextureFormat format)
+        {
+            return format switch
+            {
+                TextureFormat.R8Unorm           => "r8",
+                TextureFormat.R8Snorm           => "r8_snorm",
+                TextureFormat.R8Uint            => "r8ui",
+                TextureFormat.R8Sint            => "r8i",
+                TextureFormat.R16Float          => "r16f",
+                TextureFormat.R16Unorm          => "r16",
+                TextureFormat.R16Snorm          => "r16_snorm",
+                TextureFormat.R16Uint           => "r16ui",
+                TextureFormat.R16Sint           => "r16i",
+                TextureFormat.R32Float          => "r32f",
+                TextureFormat.R32Uint           => "r32ui",
+                TextureFormat.R32Sint           => "r32i",
+                TextureFormat.R8G8Unorm         => "rg8",
+                TextureFormat.R8G8Snorm         => "rg8_snorm",
+                TextureFormat.R8G8Uint          => "rg8ui",
+                TextureFormat.R8G8Sint          => "rg8i",
+                TextureFormat.R16G16Float       => "rg16f",
+                TextureFormat.R16G16Unorm       => "rg16",
+                TextureFormat.R16G16Snorm       => "rg16_snorm",
+                TextureFormat.R16G16Uint        => "rg16ui",
+                TextureFormat.R16G16Sint        => "rg16i",
+                TextureFormat.R32G32Float       => "rg32f",
+                TextureFormat.R32G32Uint        => "rg32ui",
+                TextureFormat.R32G32Sint        => "rg32i",
+                TextureFormat.R8G8B8A8Unorm     => "rgba8",
+                TextureFormat.R8G8B8A8Snorm     => "rgba8_snorm",
+                TextureFormat.R8G8B8A8Uint      => "rgba8ui",
+                TextureFormat.R8G8B8A8Sint      => "rgba8i",
+                TextureFormat.R16G16B16A16Float => "rgba16f",
+                TextureFormat.R16G16B16A16Unorm => "rgba16",
+                TextureFormat.R16G16B16A16Snorm => "rgba16_snorm",
+                TextureFormat.R16G16B16A16Uint  => "rgba16ui",
+                TextureFormat.R16G16B16A16Sint  => "rgba16i",
+                TextureFormat.R32G32B32A32Float => "rgba32f",
+                TextureFormat.R32G32B32A32Uint  => "rgba32ui",
+                TextureFormat.R32G32B32A32Sint  => "rgba32i",
+                TextureFormat.R10G10B10A2Unorm  => "rgb10_a2",
+                TextureFormat.R10G10B10A2Uint   => "rgb10_a2ui",
+                TextureFormat.R11G11B10Float    => "r11f_g11f_b10f",
+                _                               => string.Empty
+            };
+        }
+
+        public static VariableType GetComponentType(this TextureFormat format)
+        {
+            switch (format)
+            {
+                case TextureFormat.R8Uint:
+                case TextureFormat.R16Uint:
+                case TextureFormat.R32Uint:
+                case TextureFormat.R8G8Uint:
+                case TextureFormat.R16G16Uint:
+                case TextureFormat.R32G32Uint:
+                case TextureFormat.R8G8B8A8Uint:
+                case TextureFormat.R16G16B16A16Uint:
+                case TextureFormat.R32G32B32A32Uint:
+                case TextureFormat.R10G10B10A2Uint:
+                    return VariableType.U32;
+                case TextureFormat.R8Sint:
+                case TextureFormat.R16Sint:
+                case TextureFormat.R32Sint:
+                case TextureFormat.R8G8Sint:
+                case TextureFormat.R16G16Sint:
+                case TextureFormat.R32G32Sint:
+                case TextureFormat.R8G8B8A8Sint:
+                case TextureFormat.R16G16B16A16Sint:
+                case TextureFormat.R32G32B32A32Sint:
+                    return VariableType.S32;
+            };
+
+            return VariableType.F32;
+        }
+    }
+}
diff --git a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
index 60660847..3035ebf6 100644
--- a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
+++ b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
@@ -102,6 +102,8 @@ namespace Ryujinx.Graphics.Shader.Translation
                         return 16;
                     case QueryInfoName.SupportsNonConstantTextureOffset:
                         return Convert.ToInt32(true);
+                    case QueryInfoName.TextureFormat:
+                        return (int)TextureFormat.R8G8B8A8Unorm;
                 }
             }