diff --git a/Ryujinx.Graphics.GAL/IComputePipeline.cs b/Ryujinx.Graphics.GAL/IComputePipeline.cs
deleted file mode 100644
index e2d0b06eab..0000000000
--- a/Ryujinx.Graphics.GAL/IComputePipeline.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Ryujinx.Graphics.GAL
-{
-    public interface IComputePipeline
-    {
-        void Dispatch(int groupsX, int groupsY, int groupsZ);
-
-        void SetProgram(IProgram program);
-
-        void SetStorageBuffer(int index, BufferRange buffer);
-        void SetUniformBuffer(int index, BufferRange buffer);
-    }
-}
diff --git a/Ryujinx.Graphics.GAL/IGraphicsPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs
similarity index 84%
rename from Ryujinx.Graphics.GAL/IGraphicsPipeline.cs
rename to Ryujinx.Graphics.GAL/IPipeline.cs
index 13e6ab1a6f..c331373b1b 100644
--- a/Ryujinx.Graphics.GAL/IGraphicsPipeline.cs
+++ b/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -6,19 +6,21 @@ using Ryujinx.Graphics.Shader;
 
 namespace Ryujinx.Graphics.GAL
 {
-    public interface IGraphicsPipeline
+    public interface IPipeline
     {
         void BindBlendState(int index, BlendDescriptor blend);
 
         void BindIndexBuffer(BufferRange buffer, IndexType type);
 
+        void BindImage(int index, ShaderStage stage, ITexture texture);
+
         void BindProgram(IProgram program);
 
         void BindSampler(int index, ShaderStage stage, ISampler sampler);
         void BindTexture(int index, ShaderStage stage, ITexture texture);
 
-        void BindStorageBuffers(int index, ShaderStage stage, BufferRange[] buffers);
-        void BindUniformBuffers(int index, ShaderStage stage, BufferRange[] buffers);
+        void BindStorageBuffer(int index, ShaderStage stage, BufferRange buffer);
+        void BindUniformBuffer(int index, ShaderStage stage, BufferRange buffer);
 
         void BindVertexAttribs(VertexAttribDescriptor[] vertexAttribs);
         void BindVertexBuffers(VertexBufferDescriptor[] vertexBuffers);
@@ -33,6 +35,8 @@ namespace Ryujinx.Graphics.GAL
             int   stencilValue,
             int   stencilMask);
 
+        void Dispatch(int groupsX, int groupsY, int groupsZ);
+
         void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance);
         void DrawIndexed(
             int indexCount,
@@ -40,8 +44,6 @@ namespace Ryujinx.Graphics.GAL
             int firstIndex,
             int firstVertex,
             int firstInstance);
-        void DrawIndirect       (BufferRange buffer, ulong offset, int drawCount, int stride);
-        void DrawIndexedIndirect(BufferRange buffer, ulong offset, int drawCount, int stride);
 
         void SetBlendColor(ColorF color);
 
@@ -65,5 +67,8 @@ namespace Ryujinx.Graphics.GAL
         void SetStencilTest(StencilTestDescriptor stencilTest);
 
         void SetViewports(int first, Viewport[] viewports);
+
+        void TextureBarrier();
+        void TextureBarrierTiled();
     }
 }
diff --git a/Ryujinx.Graphics.GAL/IRenderer.cs b/Ryujinx.Graphics.GAL/IRenderer.cs
index 609f05f584..ebe6785c03 100644
--- a/Ryujinx.Graphics.GAL/IRenderer.cs
+++ b/Ryujinx.Graphics.GAL/IRenderer.cs
@@ -6,8 +6,7 @@ namespace Ryujinx.Graphics.GAL
 {
     public interface IRenderer
     {
-        IComputePipeline  ComputePipeline  { get; }
-        IGraphicsPipeline GraphicsPipeline { get; }
+        IPipeline Pipeline { get; }
 
         IWindow Window { get; }
 
diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute.cs b/Ryujinx.Graphics.Gpu/Engine/Compute.cs
index c8627435fa..b5acca1cfe 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Compute.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Compute.cs
@@ -1,3 +1,5 @@
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Gpu.Image;
 using Ryujinx.Graphics.Gpu.State;
 using Ryujinx.Graphics.Shader;
 using System;
@@ -23,10 +25,46 @@ namespace Ryujinx.Graphics.Gpu.Engine
                 dispatchParams.UnpackBlockSizeY(),
                 dispatchParams.UnpackBlockSizeZ());
 
-            _context.Renderer.ComputePipeline.SetProgram(cs.Interface);
+            _context.Renderer.Pipeline.BindProgram(cs.Interface);
+
+            PoolState samplerPool = _context.State.GetSamplerPoolState();
+
+            _textureManager.SetComputeSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId);
+
+            PoolState texturePool = _context.State.GetTexturePoolState();
+
+            _textureManager.SetComputeTexturePool(texturePool.Address.Pack(), texturePool.MaximumId);
+
+            _textureManager.SetComputeTextureBufferIndex(_context.State.GetTextureBufferIndex());
 
             ShaderProgramInfo info = cs.Shader.Info;
 
+            var textureBindings = new TextureBindingInfo[info.Textures.Count];
+
+            for (int index = 0; index < info.Textures.Count; index++)
+            {
+                var descriptor = info.Textures[index];
+
+                Target target = GetTarget(descriptor.Type);
+
+                textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
+            }
+
+            _textureManager.SetComputeTextures(textureBindings);
+
+            var imageBindings = new TextureBindingInfo[info.Images.Count];
+
+            for (int index = 0; index < info.Images.Count; index++)
+            {
+                var descriptor = info.Images[index];
+
+                Target target = GetTarget(descriptor.Type);
+
+                imageBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
+            }
+
+            _textureManager.SetComputeImages(imageBindings);
+
             uint sbEnableMask = 0;
             uint ubEnableMask = dispatchParams.UnpackUniformBuffersEnableMask();
 
@@ -73,8 +111,9 @@ namespace Ryujinx.Graphics.Gpu.Engine
             _bufferManager.SetComputeUniformBufferEnableMask(ubEnableMask);
 
             _bufferManager.CommitComputeBindings();
+            _textureManager.CommitComputeBindings();
 
-            _context.Renderer.ComputePipeline.Dispatch(
+            _context.Renderer.Pipeline.Dispatch(
                 dispatchParams.UnpackGridSizeX(),
                 dispatchParams.UnpackGridSizeY(),
                 dispatchParams.UnpackGridSizeZ());
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs
index b4680fa57c..2072f3fc95 100644
--- a/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs
@@ -26,7 +26,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
                     clearColor.Blue,
                     clearColor.Alpha);
 
-                _context.Renderer.GraphicsPipeline.ClearRenderTargetColor(
+                _context.Renderer.Pipeline.ClearRenderTargetColor(
                     index,
                     componentMask,
                     color);
@@ -44,7 +44,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
                     stencilMask = _context.State.GetStencilTestState().FrontMask;
                 }
 
-                _context.Renderer.GraphicsPipeline.ClearRenderTargetDepthStencil(
+                _context.Renderer.Pipeline.ClearRenderTargetDepthStencil(
                     depthValue,
                     clearDepth,
                     stencilValue,
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs b/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs
index dd360113b4..c340aeb860 100644
--- a/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs
@@ -61,7 +61,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
 
                 int firstVertex = _context.State.GetBaseVertex();
 
-                _context.Renderer.GraphicsPipeline.DrawIndexed(
+                _context.Renderer.Pipeline.DrawIndexed(
                     _indexCount,
                     1,
                     _firstIndex,
@@ -72,7 +72,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
             {
                 VertexBufferDrawState drawState = _context.State.GetVertexBufferDrawState();
 
-                _context.Renderer.GraphicsPipeline.Draw(
+                _context.Renderer.Pipeline.Draw(
                     drawState.Count,
                     1,
                     drawState.First,
@@ -84,7 +84,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
         {
             PrimitiveType type = (PrimitiveType)(argument & 0xffff);
 
-            _context.Renderer.GraphicsPipeline.SetPrimitiveTopology(type.Convert());
+            _context.Renderer.Pipeline.SetPrimitiveTopology(type.Convert());
 
             PrimitiveType = type;
 
@@ -112,7 +112,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
 
                 if (_instancedIndexed)
                 {
-                    _context.Renderer.GraphicsPipeline.DrawIndexed(
+                    _context.Renderer.Pipeline.DrawIndexed(
                         _instancedIndexCount,
                         _instanceIndex + 1,
                         _instancedFirstIndex,
@@ -121,7 +121,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
                 }
                 else
                 {
-                    _context.Renderer.GraphicsPipeline.Draw(
+                    _context.Renderer.Pipeline.Draw(
                         _instancedDrawStateCount,
                         _instanceIndex + 1,
                         _instancedDrawStateFirst,
diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
index db72a861a6..c35d663447 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
@@ -21,6 +21,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
         private BufferManager  _bufferManager;
         private TextureManager _textureManager;
 
+        public BufferManager  BufferManager  => _bufferManager;
         public TextureManager TextureManager => _textureManager;
 
         private bool _isAnyVbInstanced;
@@ -33,7 +34,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
             _shaderCache = new ShaderCache(_context);
 
             _bufferManager  = new BufferManager(context);
-            _textureManager = new TextureManager(context, _bufferManager);
+            _textureManager = new TextureManager(context);
 
             RegisterCallbacks();
         }
@@ -61,7 +62,10 @@ namespace Ryujinx.Graphics.Gpu.Engine
             _context.State.RegisterUniformBufferBind3Callback(UniformBufferBind3);
             _context.State.RegisterUniformBufferBind4Callback(UniformBufferBind4);
 
-            _context.State.RegisterCallback(MethodOffset.InvalidateTextures, InvalidateTextures);
+            _context.State.RegisterCallback(MethodOffset.TextureBarrier,      TextureBarrier);
+            _context.State.RegisterCallback(MethodOffset.InvalidateTextures,  InvalidateTextures);
+            _context.State.RegisterCallback(MethodOffset.TextureBarrierTiled, TextureBarrierTiled);
+
 
             _context.State.RegisterCallback(MethodOffset.ResetCounter, ResetCounter);
 
@@ -153,18 +157,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
         private void CommitBindings()
         {
             _bufferManager.CommitBindings();
-            _textureManager.CommitBindings();
-        }
-
-        public void InvalidateRange(ulong address, ulong size)
-        {
-            _bufferManager.InvalidateRange(address, size);
-            _textureManager.InvalidateRange(address, size);
-        }
-
-        public void InvalidateTextureRange(ulong address, ulong size)
-        {
-            _textureManager.InvalidateRange(address, size);
+            _textureManager.CommitGraphicsBindings();
         }
 
         private void UpdateRenderTargetGroupState()
@@ -272,7 +265,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
 
         private void UpdateDepthTestState()
         {
-            _context.Renderer.GraphicsPipeline.SetDepthTest(new DepthTestDescriptor(
+            _context.Renderer.Pipeline.SetDepthTest(new DepthTestDescriptor(
                 _context.State.GetDepthTestEnable().IsTrue(),
                 _context.State.GetDepthWriteEnable().IsTrue(),
                 _context.State.GetDepthTestFunc()));
@@ -305,7 +298,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
                     extents.DepthFar);
             }
 
-            _context.Renderer.GraphicsPipeline.SetViewports(0, viewports);
+            _context.Renderer.Pipeline.SetViewports(0, viewports);
         }
 
         private void UpdateDepthBiasState()
@@ -322,7 +315,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
             enables |= (polygonOffset.LineEnable.IsTrue()  ? PolygonModeMask.Line  : 0);
             enables |= (polygonOffset.FillEnable.IsTrue()  ? PolygonModeMask.Fill  : 0);
 
-            _context.Renderer.GraphicsPipeline.SetDepthBias(enables, factor, units, clamp);
+            _context.Renderer.Pipeline.SetDepthBias(enables, factor, units, clamp);
         }
 
         private void UpdateStencilTestState()
@@ -360,7 +353,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
                 backMask     = test.FrontMask;
             }
 
-            _context.Renderer.GraphicsPipeline.SetStencilTest(new StencilTestDescriptor(
+            _context.Renderer.Pipeline.SetStencilTest(new StencilTestDescriptor(
                 test.Enable.IsTrue(),
                 test.FrontFunc,
                 test.FrontSFail,
@@ -382,16 +375,16 @@ namespace Ryujinx.Graphics.Gpu.Engine
         {
             PoolState samplerPool = _context.State.GetSamplerPoolState();
 
-            _textureManager.SetSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId);
+            _textureManager.SetGraphicsSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId);
         }
 
         private void UpdateTexturePoolState()
         {
             PoolState texturePool = _context.State.GetTexturePoolState();
 
-            _textureManager.SetTexturePool(texturePool.Address.Pack(), texturePool.MaximumId);
+            _textureManager.SetGraphicsTexturePool(texturePool.Address.Pack(), texturePool.MaximumId);
 
-            _textureManager.SetTextureBufferIndex(_context.State.GetTextureBufferIndex());
+            _textureManager.SetGraphicsTextureBufferIndex(_context.State.GetTextureBufferIndex());
         }
 
         private void UpdateInputAssemblerGroupState()
@@ -439,14 +432,14 @@ namespace Ryujinx.Graphics.Gpu.Engine
                     format);
             }
 
-            _context.Renderer.GraphicsPipeline.BindVertexAttribs(vertexAttribs);
+            _context.Renderer.Pipeline.BindVertexAttribs(vertexAttribs);
         }
 
         private void UpdatePrimitiveRestartState()
         {
             PrimitiveRestartState primitiveRestart = _context.State.Get<PrimitiveRestartState>(MethodOffset.PrimitiveRestartState);
 
-            _context.Renderer.GraphicsPipeline.SetPrimitiveRestart(
+            _context.Renderer.Pipeline.SetPrimitiveRestart(
                 primitiveRestart.Enable,
                 primitiveRestart.Index);
         }
@@ -593,9 +586,9 @@ namespace Ryujinx.Graphics.Gpu.Engine
         {
             FaceState face = _context.State.GetFaceState();
 
-            _context.Renderer.GraphicsPipeline.SetFaceCulling(face.CullEnable.IsTrue(), face.CullFace);
+            _context.Renderer.Pipeline.SetFaceCulling(face.CullEnable.IsTrue(), face.CullFace);
 
-            _context.Renderer.GraphicsPipeline.SetFrontFace(face.FrontFace);
+            _context.Renderer.Pipeline.SetFrontFace(face.FrontFace);
         }
 
         private void UpdateRtColorMask()
@@ -616,7 +609,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
                 componentMasks[index] = componentMask;
             }
 
-            _context.Renderer.GraphicsPipeline.SetRenderTargetColorMasks(componentMasks);
+            _context.Renderer.Pipeline.SetRenderTargetColorMasks(componentMasks);
         }
 
         private void UpdateBlendState()
@@ -638,7 +631,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
                     blend.AlphaSrcFactor,
                     blend.AlphaDstFactor);
 
-                _context.Renderer.GraphicsPipeline.BindBlendState(index, descriptor);
+                _context.Renderer.Pipeline.BindBlendState(index, descriptor);
             }
         }
 
@@ -696,12 +689,25 @@ namespace Ryujinx.Graphics.Gpu.Engine
                 {
                     var descriptor = info.Textures[index];
 
-                    Target target = GetTarget(descriptor.Target);
+                    Target target = GetTarget(descriptor.Type);
 
                     textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
                 }
 
-                _textureManager.BindTextures(stage, textureBindings);
+                _textureManager.SetGraphicsTextures(stage, textureBindings);
+
+                var imageBindings = new TextureBindingInfo[info.Images.Count];
+
+                for (int index = 0; index < info.Images.Count; index++)
+                {
+                    var descriptor = info.Images[index];
+
+                    Target target = GetTarget(descriptor.Type);
+
+                    imageBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
+                }
+
+                _textureManager.SetGraphicsImages(stage, imageBindings);
 
                 uint sbEnableMask = 0;
                 uint ubEnableMask = 0;
@@ -734,40 +740,43 @@ namespace Ryujinx.Graphics.Gpu.Engine
                 _bufferManager.SetGraphicsUniformBufferEnableMask(stage, ubEnableMask);
             }
 
-            _context.Renderer.GraphicsPipeline.BindProgram(gs.Interface);
+            _context.Renderer.Pipeline.BindProgram(gs.Interface);
         }
 
-        private static Target GetTarget(Shader.TextureTarget target)
+        private static Target GetTarget(SamplerType type)
         {
-            target &= ~Shader.TextureTarget.Shadow;
+            type &= ~SamplerType.Shadow;
 
-            switch (target)
+            switch (type)
             {
-                case Shader.TextureTarget.Texture1D:
+                case SamplerType.Texture1D:
                     return Target.Texture1D;
 
-                case Shader.TextureTarget.Texture1D | Shader.TextureTarget.Array:
+                case SamplerType.TextureBuffer:
+                    return Target.TextureBuffer;
+
+                case SamplerType.Texture1D | SamplerType.Array:
                     return Target.Texture1DArray;
 
-                case Shader.TextureTarget.Texture2D:
+                case SamplerType.Texture2D:
                     return Target.Texture2D;
 
-                case Shader.TextureTarget.Texture2D | Shader.TextureTarget.Array:
+                case SamplerType.Texture2D | SamplerType.Array:
                     return Target.Texture2DArray;
 
-                case Shader.TextureTarget.Texture2D | Shader.TextureTarget.Multisample:
+                case SamplerType.Texture2D | SamplerType.Multisample:
                     return Target.Texture2DMultisample;
 
-                case Shader.TextureTarget.Texture2D | Shader.TextureTarget.Multisample | Shader.TextureTarget.Array:
+                case SamplerType.Texture2D | SamplerType.Multisample | SamplerType.Array:
                     return Target.Texture2DMultisampleArray;
 
-                case Shader.TextureTarget.Texture3D:
+                case SamplerType.Texture3D:
                     return Target.Texture3D;
 
-                case Shader.TextureTarget.TextureCube:
+                case SamplerType.TextureCube:
                     return Target.Cubemap;
 
-                case Shader.TextureTarget.TextureCube | Shader.TextureTarget.Array:
+                case SamplerType.TextureCube | SamplerType.Array:
                     return Target.CubemapArray;
             }
 
@@ -776,9 +785,19 @@ namespace Ryujinx.Graphics.Gpu.Engine
             return Target.Texture2D;
         }
 
+        private void TextureBarrier(int argument)
+        {
+            _context.Renderer.Pipeline.TextureBarrier();
+        }
+
         private void InvalidateTextures(int argument)
         {
             _textureManager.Flush();
         }
+
+        private void TextureBarrierTiled(int argument)
+        {
+            _context.Renderer.Pipeline.TextureBarrierTiled();
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Engine/ShaderCache.cs
index 79a84a6d16..d280ea6f58 100644
--- a/Ryujinx.Graphics.Gpu/Engine/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/ShaderCache.cs
@@ -107,7 +107,8 @@ namespace Ryujinx.Graphics.Gpu.Engine
             ShaderProgram program;
 
             const TranslationFlags flags =
-                TranslationFlags.Compute |
+                TranslationFlags.Compute   |
+                TranslationFlags.DebugMode |
                 TranslationFlags.Unspecialized;
 
             TranslationConfig translationConfig = new TranslationConfig(0x10000, _dumper.CurrentDumpIndex, flags);
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index 32db868829..7ebf01b86b 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -217,8 +217,6 @@ namespace Ryujinx.Graphics.Gpu.Image
 
             ulong rangeSize = (EndAddress - Address + pageMask) & ~pageMask;
 
-            _context.Methods.InvalidateRange(rangeAddress, rangeSize);
-
             Span<byte> data = _context.PhysicalMemory.Read(Address, Size);
 
             if (_info.IsLinear)
@@ -683,11 +681,6 @@ namespace Ryujinx.Graphics.Gpu.Image
             return Address < address + size && address < EndAddress;
         }
 
-        public void Invalidate()
-        {
-            // _hasData = false;
-        }
-
         public void IncrementReferenceCount()
         {
             _referenceCount++;
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
new file mode 100644
index 0000000000..2d0e828efa
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
@@ -0,0 +1,231 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    class TextureBindingsManager
+    {
+        private GpuContext _context;
+
+        private bool _isCompute;
+
+        private SamplerPool _samplerPool;
+
+        private ulong _texturePoolAddress;
+        private int   _texturePoolMaximumId;
+
+        private TexturePoolCache _texturePoolCache;
+
+        private TextureBindingInfo[][] _textureBindings;
+        private TextureBindingInfo[][] _imageBindings;
+
+        private struct TextureStatePerStage
+        {
+            public ITexture Texture;
+            public ISampler Sampler;
+        }
+
+        private TextureStatePerStage[][] _textureState;
+        private TextureStatePerStage[][] _imageState;
+
+        private int _textureBufferIndex;
+
+        private bool _rebind;
+
+        public TextureBindingsManager(GpuContext context, bool isCompute)
+        {
+            _context   = context;
+            _isCompute = isCompute;
+
+            _texturePoolCache = new TexturePoolCache(context);
+
+            int stages = isCompute ? 1 : Constants.TotalShaderStages;
+
+            _textureBindings = new TextureBindingInfo[stages][];
+            _imageBindings   = new TextureBindingInfo[stages][];
+
+            _textureState = new TextureStatePerStage[stages][];
+            _imageState   = new TextureStatePerStage[stages][];
+        }
+
+        public void SetTextures(int stage, TextureBindingInfo[] bindings)
+        {
+            _textureBindings[stage] = bindings;
+
+            _textureState[stage] = new TextureStatePerStage[bindings.Length];
+        }
+
+        public void SetImages(int stage, TextureBindingInfo[] bindings)
+        {
+            _imageBindings[stage] = bindings;
+
+            _imageState[stage] = new TextureStatePerStage[bindings.Length];
+        }
+
+        public void SetTextureBufferIndex(int index)
+        {
+            _textureBufferIndex = index;
+        }
+
+        public void SetSamplerPool(ulong gpuVa, int maximumId)
+        {
+            ulong address = _context.MemoryManager.Translate(gpuVa);
+
+            if (_samplerPool != null)
+            {
+                if (_samplerPool.Address == address)
+                {
+                    return;
+                }
+
+                _samplerPool.Dispose();
+            }
+
+            _samplerPool = new SamplerPool(_context, address, maximumId);
+        }
+
+        public void SetTexturePool(ulong gpuVa, int maximumId)
+        {
+            ulong address = _context.MemoryManager.Translate(gpuVa);
+
+            _texturePoolAddress   = address;
+            _texturePoolMaximumId = maximumId;
+        }
+
+        public void CommitBindings()
+        {
+            TexturePool texturePool = _texturePoolCache.FindOrCreate(
+                _texturePoolAddress,
+                _texturePoolMaximumId);
+
+            if (_isCompute)
+            {
+                CommitTextureBindings(texturePool, ShaderStage.Compute, 0);
+                CommitImageBindings  (texturePool, ShaderStage.Compute, 0);
+            }
+            else
+            {
+                for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
+                {
+                    int stageIndex = (int)stage - 1;
+
+                    CommitTextureBindings(texturePool, stage, stageIndex);
+                    CommitImageBindings  (texturePool, stage, stageIndex);
+                }
+            }
+
+            _rebind = false;
+        }
+
+        private void CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex)
+        {
+            if (_textureBindings[stageIndex] == null)
+            {
+                return;
+            }
+
+            for (int index = 0; index < _textureBindings[stageIndex].Length; index++)
+            {
+                TextureBindingInfo binding = _textureBindings[stageIndex][index];
+
+                int packedId = ReadPackedId(stageIndex, binding.Handle);
+
+                int textureId = UnpackTextureId(packedId);
+                int samplerId = UnpackSamplerId(packedId);
+
+                Texture texture = pool.Get(textureId);
+
+                ITexture hostTexture = texture?.GetTargetTexture(binding.Target);
+
+                if (_textureState[stageIndex][index].Texture != hostTexture || _rebind)
+                {
+                    _textureState[stageIndex][index].Texture = hostTexture;
+
+                    _context.Renderer.Pipeline.BindTexture(index, stage, hostTexture);
+                }
+
+                Sampler sampler = _samplerPool.Get(samplerId);
+
+                ISampler hostSampler = sampler?.HostSampler;
+
+                if (_textureState[stageIndex][index].Sampler != hostSampler || _rebind)
+                {
+                    _textureState[stageIndex][index].Sampler = hostSampler;
+
+                    _context.Renderer.Pipeline.BindSampler(index, stage, hostSampler);
+                }
+            }
+        }
+
+        private void CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex)
+        {
+            if (_imageBindings[stageIndex] == null)
+            {
+                return;
+            }
+
+            for (int index = 0; index < _imageBindings[stageIndex].Length; index++)
+            {
+                TextureBindingInfo binding = _imageBindings[stageIndex][index];
+
+                int packedId = ReadPackedId(stageIndex, binding.Handle);
+
+                int textureId = UnpackTextureId(packedId);
+
+                Texture texture = pool.Get(textureId);
+
+                ITexture hostTexture = texture?.GetTargetTexture(binding.Target);
+
+                if (_imageState[stageIndex][index].Texture != hostTexture || _rebind)
+                {
+                    _imageState[stageIndex][index].Texture = hostTexture;
+
+                    _context.Renderer.Pipeline.BindImage(index, stage, hostTexture);
+                }
+            }
+        }
+
+        private int ReadPackedId(int stage, int wordOffset)
+        {
+            ulong address;
+
+            var bufferManager = _context.Methods.BufferManager;
+
+            if (_isCompute)
+            {
+                address = bufferManager.GetComputeUniformBufferAddress(_textureBufferIndex);
+            }
+            else
+            {
+                address = bufferManager.GetGraphicsUniformBufferAddress(stage, _textureBufferIndex);
+            }
+
+            address += (uint)wordOffset * 4;
+
+            return BitConverter.ToInt32(_context.PhysicalMemory.Read(address, 4));
+        }
+
+        private static int UnpackTextureId(int packedId)
+        {
+            return (packedId >> 0) & 0xfffff;
+        }
+
+        private static int UnpackSamplerId(int packedId)
+        {
+            return (packedId >> 20) & 0xfff;
+        }
+
+        public void InvalidatePoolRange(ulong address, ulong size)
+        {
+            _samplerPool?.InvalidateRange(address, size);
+
+            _texturePoolCache.InvalidateRange(address, size);
+        }
+
+        public void Rebind()
+        {
+            _rebind = true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index 23416ddd36..91a9cfd1f0 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -4,7 +4,6 @@ using Ryujinx.Graphics.GAL.Texture;
 using Ryujinx.Graphics.Gpu.Image;
 using Ryujinx.Graphics.Gpu.Memory;
 using Ryujinx.Graphics.Gpu.State;
-using Ryujinx.Graphics.Shader;
 using Ryujinx.Graphics.Texture;
 using System;
 
@@ -12,15 +11,10 @@ namespace Ryujinx.Graphics.Gpu.Image
 {
     class TextureManager
     {
-        private GpuContext    _context;
-        private BufferManager _bufferManager;
+        private GpuContext _context;
 
-        private SamplerPool _samplerPool;
-
-        private ulong _texturePoolAddress;
-        private int   _texturePoolMaximumId;
-
-        private TexturePoolCache _texturePoolCache;
+        private TextureBindingsManager _cpBindingsManager;
+        private TextureBindingsManager _gpBindingsManager;
 
         private Texture[] _rtColors;
         private Texture   _rtColor3D;
@@ -35,24 +29,12 @@ namespace Ryujinx.Graphics.Gpu.Image
 
         private AutoDeleteCache _cache;
 
-        private TextureBindingInfo[][] _bindings;
-
-        private struct TextureStatePerStage
+        public TextureManager(GpuContext context)
         {
-            public ITexture Texture;
-            public ISampler Sampler;
-        }
+            _context = context;
 
-        private TextureStatePerStage[][] _textureState;
-
-        private int _textureBufferIndex;
-
-        public TextureManager(GpuContext context, BufferManager bufferManager)
-        {
-            _context       = context;
-            _bufferManager = bufferManager;
-
-            _texturePoolCache = new TexturePoolCache(context, this);
+            _cpBindingsManager = new TextureBindingsManager(context, isCompute: true);
+            _gpBindingsManager = new TextureBindingsManager(context, isCompute: false);
 
             _rtColors = new Texture[Constants.TotalRenderTargets];
 
@@ -61,47 +43,56 @@ namespace Ryujinx.Graphics.Gpu.Image
             _textures = new RangeList<Texture>();
 
             _cache = new AutoDeleteCache();
-
-            _bindings = new TextureBindingInfo[Constants.TotalShaderStages][];
-
-            _textureState = new TextureStatePerStage[Constants.TotalShaderStages][];
         }
 
-        public void BindTextures(int stage, TextureBindingInfo[] bindings)
+        public void SetComputeTextures(TextureBindingInfo[] bindings)
         {
-            _bindings[stage] = bindings;
-
-            _textureState[stage] = new TextureStatePerStage[bindings.Length];
+            _cpBindingsManager.SetTextures(0, bindings);
         }
 
-        public void SetTextureBufferIndex(int index)
+        public void SetGraphicsTextures(int stage, TextureBindingInfo[] bindings)
         {
-            _textureBufferIndex = index;
+            _gpBindingsManager.SetTextures(stage, bindings);
         }
 
-        public void SetSamplerPool(ulong gpuVa, int maximumId)
+        public void SetComputeImages(TextureBindingInfo[] bindings)
         {
-            ulong address = _context.MemoryManager.Translate(gpuVa);
-
-            if (_samplerPool != null)
-            {
-                if (_samplerPool.Address == address)
-                {
-                    return;
-                }
-
-                _samplerPool.Dispose();
-            }
-
-            _samplerPool = new SamplerPool(_context, address, maximumId);
+            _cpBindingsManager.SetImages(0, bindings);
         }
 
-        public void SetTexturePool(ulong gpuVa, int maximumId)
+        public void SetGraphicsImages(int stage, TextureBindingInfo[] bindings)
         {
-            ulong address = _context.MemoryManager.Translate(gpuVa);
+            _gpBindingsManager.SetImages(stage, bindings);
+        }
 
-            _texturePoolAddress   = address;
-            _texturePoolMaximumId = maximumId;
+        public void SetComputeTextureBufferIndex(int index)
+        {
+            _cpBindingsManager.SetTextureBufferIndex(index);
+        }
+
+        public void SetGraphicsTextureBufferIndex(int index)
+        {
+            _gpBindingsManager.SetTextureBufferIndex(index);
+        }
+
+        public void SetComputeSamplerPool(ulong gpuVa, int maximumId)
+        {
+            _cpBindingsManager.SetSamplerPool(gpuVa, maximumId);
+        }
+
+        public void SetGraphicsSamplerPool(ulong gpuVa, int maximumId)
+        {
+            _gpBindingsManager.SetSamplerPool(gpuVa, maximumId);
+        }
+
+        public void SetComputeTexturePool(ulong gpuVa, int maximumId)
+        {
+            _cpBindingsManager.SetTexturePool(gpuVa, maximumId);
+        }
+
+        public void SetGraphicsTexturePool(ulong gpuVa, int maximumId)
+        {
+            _gpBindingsManager.SetTexturePool(gpuVa, maximumId);
         }
 
         public void SetRenderTargetColor(int index, Texture color)
@@ -121,59 +112,22 @@ namespace Ryujinx.Graphics.Gpu.Image
             _rtDepthStencil = depthStencil;
         }
 
-        public void CommitBindings()
+        public void CommitComputeBindings()
         {
-            UpdateTextures();
-            UpdateRenderTargets();
+            // Evert time we switch between graphics and compute work,
+            // we must rebind everything.
+            // Since compute work happens less often, we always do that
+            // before and after the compute dispatch.
+            _cpBindingsManager.Rebind();
+            _cpBindingsManager.CommitBindings();
+            _gpBindingsManager.Rebind();
         }
 
-        private void UpdateTextures()
+        public void CommitGraphicsBindings()
         {
-            TexturePool texturePool = _texturePoolCache.FindOrCreate(
-                _texturePoolAddress,
-                _texturePoolMaximumId);
+            _gpBindingsManager.CommitBindings();
 
-            for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
-            {
-                int stageIndex = (int)stage - 1;
-
-                if (_bindings[stageIndex] == null)
-                {
-                    continue;
-                }
-
-                for (int index = 0; index < _bindings[stageIndex].Length; index++)
-                {
-                    TextureBindingInfo binding = _bindings[stageIndex][index];
-
-                    int packedId = ReadPackedId(stageIndex, binding.Handle);
-
-                    int textureId = (packedId >> 0)  & 0xfffff;
-                    int samplerId = (packedId >> 20) & 0xfff;
-
-                    Texture texture = texturePool.Get(textureId);
-
-                    ITexture hostTexture = texture?.GetTargetTexture(binding.Target);
-
-                    if (_textureState[stageIndex][index].Texture != hostTexture)
-                    {
-                        _textureState[stageIndex][index].Texture = hostTexture;
-
-                        _context.Renderer.GraphicsPipeline.BindTexture(index, stage, hostTexture);
-                    }
-
-                    Sampler sampler = _samplerPool.Get(samplerId);
-
-                    ISampler hostSampler = sampler?.HostSampler;
-
-                    if (_textureState[stageIndex][index].Sampler != hostSampler)
-                    {
-                        _textureState[stageIndex][index].Sampler = hostSampler;
-
-                        _context.Renderer.GraphicsPipeline.BindSampler(index, stage, hostSampler);
-                    }
-                }
-            }
+            UpdateRenderTargets();
         }
 
         private void UpdateRenderTargets()
@@ -203,7 +157,7 @@ namespace Ryujinx.Graphics.Gpu.Image
 
                 if (anyChanged)
                 {
-                    _context.Renderer.GraphicsPipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
+                    _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
                 }
             }
             else
@@ -217,20 +171,11 @@ namespace Ryujinx.Graphics.Gpu.Image
 
                 if (anyChanged)
                 {
-                    _context.Renderer.GraphicsPipeline.SetRenderTargets(_rtColor3D.HostTexture, _rtHostDs);
+                    _context.Renderer.Pipeline.SetRenderTargets(_rtColor3D.HostTexture, _rtHostDs);
                 }
             }
         }
 
-        private int ReadPackedId(int stage, int wordOffset)
-        {
-            ulong address = _bufferManager.GetGraphicsUniformBufferAddress(stage, _textureBufferIndex);
-
-            address += (uint)wordOffset * 4;
-
-            return BitConverter.ToInt32(_context.PhysicalMemory.Read(address, 4));
-        }
-
         public Texture FindOrCreateTexture(CopyTexture copyTexture)
         {
             ulong address = _context.MemoryManager.Translate(copyTexture.Address.Pack());
@@ -645,20 +590,6 @@ namespace Ryujinx.Graphics.Gpu.Image
             return ts[0];
         }
 
-        public void InvalidateRange(ulong address, ulong size)
-        {
-            Texture[] overlaps = _textures.FindOverlaps(address, size);
-
-            foreach (Texture overlap in overlaps)
-            {
-                overlap.Invalidate();
-            }
-
-            _samplerPool?.InvalidateRange(address, size);
-
-            _texturePoolCache.InvalidateRange(address, size);
-        }
-
         public void Flush()
         {
             foreach (Texture texture in _cache)
diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
index 558f4def4d..8512e3708d 100644
--- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -9,8 +9,6 @@ namespace Ryujinx.Graphics.Gpu.Image
 {
     class TexturePool : Pool<Texture>
     {
-        private TextureManager _textureManager;
-
         public LinkedListNode<TexturePool> CacheNode { get; set; }
 
         private struct TextureContainer
@@ -20,13 +18,9 @@ namespace Ryujinx.Graphics.Gpu.Image
         }
 
         public TexturePool(
-            GpuContext     context,
-            TextureManager textureManager,
-            ulong          address,
-            int            maximumId) : base(context, address, maximumId)
-        {
-            _textureManager = textureManager;
-        }
+            GpuContext context,
+            ulong      address,
+            int        maximumId) : base(context, address, maximumId) { }
 
         public override Texture Get(int id)
         {
@@ -56,7 +50,7 @@ namespace Ryujinx.Graphics.Gpu.Image
                     return null;
                 }
 
-                texture = _textureManager.FindOrCreateTexture(info, TextureSearchFlags.Sampler);
+                texture = Context.Methods.TextureManager.FindOrCreateTexture(info, TextureSearchFlags.Sampler);
 
                 texture.IncrementReferenceCount();
 
diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs b/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs
index 8e8313ae30..9ab7e292d9 100644
--- a/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs
@@ -6,15 +6,13 @@ namespace Ryujinx.Graphics.Gpu.Image
     {
         private const int MaxCapacity = 4;
 
-        private GpuContext     _context;
-        private TextureManager _textureManager;
+        private GpuContext _context;
 
         private LinkedList<TexturePool> _pools;
 
-        public TexturePoolCache(GpuContext context, TextureManager textureManager)
+        public TexturePoolCache(GpuContext context)
         {
-            _context        = context;
-            _textureManager = textureManager;
+            _context = context;
 
             _pools = new LinkedList<TexturePool>();
         }
@@ -42,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Image
             }
 
             // If not found, create a new one.
-            pool = new TexturePool(_context, _textureManager, address, maximumId);
+            pool = new TexturePool(_context, address, maximumId);
 
             pool.CacheNode = _pools.AddLast(pool);
 
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
index eb2e0ca9c7..3ceee20646 100644
--- a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
@@ -272,7 +272,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
 
                 BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
 
-                _context.Renderer.ComputePipeline.SetStorageBuffer(index, buffer);
+                _context.Renderer.Pipeline.BindStorageBuffer(index, ShaderStage.Compute, buffer);
             }
 
             enableMask = _cpUniformBuffers.EnableMask;
@@ -293,7 +293,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
 
                 BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
 
-                _context.Renderer.ComputePipeline.SetUniformBuffer(index, buffer);
+                _context.Renderer.Pipeline.BindUniformBuffer(index, ShaderStage.Compute, buffer);
 
                 if (index == 0)
                 {
@@ -312,6 +312,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
                     buffer.Buffer.SetData(buffer.Offset, data);
                 }
             }
+
+            // Force rebind after doing compute work.
+            _rebind = true;
         }
 
         public void CommitBindings()
@@ -324,7 +327,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
                 {
                     BufferRange buffer = GetBufferRange(_indexBuffer.Address, _indexBuffer.Size);
 
-                    _context.Renderer.GraphicsPipeline.BindIndexBuffer(buffer, _indexBuffer.Type);
+                    _context.Renderer.Pipeline.BindIndexBuffer(buffer, _indexBuffer.Type);
                 }
             }
             else if (_indexBuffer.Address != 0)
@@ -352,7 +355,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
                     vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
                 }
 
-                _context.Renderer.GraphicsPipeline.BindVertexBuffers(vertexBuffers);
+                _context.Renderer.Pipeline.BindVertexBuffers(vertexBuffers);
             }
             else
             {
@@ -445,15 +448,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
         {
             BufferRange buffer = GetBufferRange(bounds.Address, bounds.Size);
 
-            BufferRange[] buffers = new BufferRange[] { buffer };
-
             if (isStorage)
             {
-                _context.Renderer.GraphicsPipeline.BindStorageBuffers(index, stage, buffers);
+                _context.Renderer.Pipeline.BindStorageBuffer(index, stage, buffer);
             }
             else
             {
-                _context.Renderer.GraphicsPipeline.BindUniformBuffers(index, stage, buffers);
+                _context.Renderer.Pipeline.BindUniformBuffer(index, stage, buffer);
             }
 
             if (!isStorage && index == 0)
diff --git a/Ryujinx.Graphics.Gpu/State/MethodOffset.cs b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs
index 91dffe9f37..3637d8744e 100644
--- a/Ryujinx.Graphics.Gpu/State/MethodOffset.cs
+++ b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs
@@ -23,8 +23,10 @@ namespace Ryujinx.Graphics.Gpu.State
         ClearDepthValue        = 0x364,
         ClearStencilValue      = 0x368,
         DepthBiasState         = 0x370,
+        TextureBarrier         = 0x378,
         StencilBackMasks       = 0x3d5,
         InvalidateTextures     = 0x3dd,
+        TextureBarrierTiled    = 0x3df,
         RtDepthStencilState    = 0x3f8,
         VertexAttribState      = 0x458,
         RtDepthStencilSize     = 0x48a,
diff --git a/Ryujinx.Graphics.OpenGL/ComputePipeline.cs b/Ryujinx.Graphics.OpenGL/ComputePipeline.cs
deleted file mode 100644
index bee96832d0..0000000000
--- a/Ryujinx.Graphics.OpenGL/ComputePipeline.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-using OpenTK.Graphics.OpenGL;
-using Ryujinx.Graphics.GAL;
-using Ryujinx.Graphics.Shader;
-using System;
-
-namespace Ryujinx.Graphics.OpenGL
-{
-    class ComputePipeline : IComputePipeline
-    {
-        private Renderer _renderer;
-
-        private Program _program;
-
-        public ComputePipeline(Renderer renderer)
-        {
-            _renderer = renderer;
-        }
-
-        public void Dispatch(int groupsX, int groupsY, int groupsZ)
-        {
-            BindProgram();
-
-            GL.DispatchCompute(groupsX, groupsY, groupsZ);
-
-            UnbindProgram();
-        }
-
-        public void SetProgram(IProgram program)
-        {
-            _program = (Program)program;
-        }
-
-        public void SetStorageBuffer(int index, BufferRange buffer)
-        {
-            BindProgram();
-
-            BindBuffer(index, buffer, isStorage: true);
-
-            UnbindProgram();
-        }
-
-        public void SetUniformBuffer(int index, BufferRange buffer)
-        {
-            BindProgram();
-
-            BindBuffer(index, buffer, isStorage: false);
-
-            UnbindProgram();
-        }
-
-        private void BindBuffer(int index, BufferRange buffer, bool isStorage)
-        {
-            int bindingPoint = isStorage
-                ? _program.GetStorageBufferBindingPoint(ShaderStage.Compute, index)
-                : _program.GetUniformBufferBindingPoint(ShaderStage.Compute, index);
-
-            if (bindingPoint == -1)
-            {
-                return;
-            }
-
-            BufferRangeTarget target = isStorage
-                ? BufferRangeTarget.ShaderStorageBuffer
-                : BufferRangeTarget.UniformBuffer;
-
-            if (buffer.Buffer == null)
-            {
-                GL.BindBufferRange(target, bindingPoint, 0, IntPtr.Zero, 0);
-
-                return;
-            }
-
-            int bufferHandle = ((Buffer)buffer.Buffer).Handle;
-
-            IntPtr bufferOffset = (IntPtr)buffer.Offset;
-
-            GL.BindBufferRange(
-                target,
-                bindingPoint,
-                bufferHandle,
-                bufferOffset,
-                buffer.Size);
-        }
-
-        private void BindProgram()
-        {
-            _program.Bind();
-        }
-
-        private void UnbindProgram()
-        {
-            ((GraphicsPipeline)_renderer.GraphicsPipeline).RebindProgram();
-        }
-    }
-}
diff --git a/Ryujinx.Graphics.OpenGL/GraphicsPipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs
similarity index 90%
rename from Ryujinx.Graphics.OpenGL/GraphicsPipeline.cs
rename to Ryujinx.Graphics.OpenGL/Pipeline.cs
index e904efed9e..e804eff576 100644
--- a/Ryujinx.Graphics.OpenGL/GraphicsPipeline.cs
+++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -4,12 +4,13 @@ using Ryujinx.Graphics.GAL.Blend;
 using Ryujinx.Graphics.GAL.Color;
 using Ryujinx.Graphics.GAL.DepthStencil;
 using Ryujinx.Graphics.GAL.InputAssembler;
+using Ryujinx.Graphics.OpenGL.Formats;
 using Ryujinx.Graphics.Shader;
 using System;
 
 namespace Ryujinx.Graphics.OpenGL
 {
-    class GraphicsPipeline : IGraphicsPipeline
+    class Pipeline : IPipeline
     {
         private Program _program;
 
@@ -33,7 +34,7 @@ namespace Ryujinx.Graphics.OpenGL
 
         private uint[] _componentMasks;
 
-        internal GraphicsPipeline()
+        internal Pipeline()
         {
             _clipOrigin = ClipOrigin.LowerLeft;
         }
@@ -62,6 +63,29 @@ namespace Ryujinx.Graphics.OpenGL
             GL.Enable(IndexedEnableCap.Blend, index);
         }
 
+        public void BindImage(int index, ShaderStage stage, ITexture texture)
+        {
+            int unit = _program.GetImageUnit(stage, index);
+
+            if (unit != -1 && texture != null)
+            {
+                TextureView view = (TextureView)texture;
+
+                FormatInfo formatInfo = FormatTable.GetFormatInfo(view.Format);
+
+                SizedInternalFormat format = (SizedInternalFormat)formatInfo.PixelInternalFormat;
+
+                GL.BindImageTexture(
+                    unit,
+                    view.Handle,
+                    0,
+                    true,
+                    0,
+                    TextureAccess.ReadWrite,
+                    format);
+            }
+        }
+
         public void BindIndexBuffer(BufferRange buffer, IndexType type)
         {
             _elementsType = type.Convert();
@@ -107,53 +131,48 @@ namespace Ryujinx.Graphics.OpenGL
             }
         }
 
-        public void BindStorageBuffers(int index, ShaderStage stage, BufferRange[] buffers)
+        public void BindStorageBuffer(int index, ShaderStage stage, BufferRange buffer)
         {
-            BindBuffers(index, stage, buffers, isStorage: true);
+            BindBuffer(index, stage, buffer, isStorage: true);
         }
 
-        public void BindUniformBuffers(int index, ShaderStage stage, BufferRange[] buffers)
+        public void BindUniformBuffer(int index, ShaderStage stage, BufferRange buffer)
         {
-            BindBuffers(index, stage, buffers, isStorage: false);
+            BindBuffer(index, stage, buffer, isStorage: false);
         }
 
-        private void BindBuffers(int index, ShaderStage stage, BufferRange[] buffers, bool isStorage)
+        private void BindBuffer(int index, ShaderStage stage, BufferRange buffer, bool isStorage)
         {
-            for (int bufferIndex = 0; bufferIndex < buffers.Length; bufferIndex++, index++)
+            int bindingPoint = isStorage
+                ? _program.GetStorageBufferBindingPoint(stage, index)
+                : _program.GetUniformBufferBindingPoint(stage, index);
+
+            if (bindingPoint == -1)
             {
-                int bindingPoint = isStorage
-                    ? _program.GetStorageBufferBindingPoint(stage, index)
-                    : _program.GetUniformBufferBindingPoint(stage, index);
-
-                if (bindingPoint == -1)
-                {
-                    continue;
-                }
-
-                BufferRange buffer = buffers[bufferIndex];
-
-                BufferRangeTarget target = isStorage
-                    ? BufferRangeTarget.ShaderStorageBuffer
-                    : BufferRangeTarget.UniformBuffer;
-
-                if (buffer.Buffer == null)
-                {
-                    GL.BindBufferRange(target, bindingPoint, 0, IntPtr.Zero, 0);
-
-                    continue;
-                }
-
-                int bufferHandle = ((Buffer)buffer.Buffer).Handle;
-
-                IntPtr bufferOffset = (IntPtr)buffer.Offset;
-
-                GL.BindBufferRange(
-                    target,
-                    bindingPoint,
-                    bufferHandle,
-                    bufferOffset,
-                    buffer.Size);
+                return;
             }
+
+            BufferRangeTarget target = isStorage
+                ? BufferRangeTarget.ShaderStorageBuffer
+                : BufferRangeTarget.UniformBuffer;
+
+            if (buffer.Buffer == null)
+            {
+                GL.BindBufferRange(target, bindingPoint, 0, IntPtr.Zero, 0);
+
+                return;
+            }
+
+            int bufferHandle = ((Buffer)buffer.Buffer).Handle;
+
+            IntPtr bufferOffset = (IntPtr)buffer.Offset;
+
+            GL.BindBufferRange(
+                target,
+                bindingPoint,
+                bufferHandle,
+                bufferOffset,
+                buffer.Size);
         }
 
         public void BindVertexAttribs(VertexAttribDescriptor[] vertexAttribs)
@@ -264,6 +283,11 @@ namespace Ryujinx.Graphics.OpenGL
             }
         }
 
+        public void Dispatch(int groupsX, int groupsY, int groupsZ)
+        {
+            GL.DispatchCompute(groupsX, groupsY, groupsZ);
+        }
+
         public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance)
         {
             if (!_program.IsLinked)
@@ -803,6 +827,16 @@ namespace Ryujinx.Graphics.OpenGL
             SetOrigin(flipY ? ClipOrigin.UpperLeft : ClipOrigin.LowerLeft);
         }
 
+        public void TextureBarrier()
+        {
+            GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit);
+        }
+
+        public void TextureBarrierTiled()
+        {
+            GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit);
+        }
+
         private void SetOrigin(ClipOrigin origin)
         {
             if (_clipOrigin != origin)
diff --git a/Ryujinx.Graphics.OpenGL/Program.cs b/Ryujinx.Graphics.OpenGL/Program.cs
index 1f95b449df..61b1645ada 100644
--- a/Ryujinx.Graphics.OpenGL/Program.cs
+++ b/Ryujinx.Graphics.OpenGL/Program.cs
@@ -6,8 +6,17 @@ namespace Ryujinx.Graphics.OpenGL
 {
     class Program : IProgram
     {
-        private const int StageShift   = 5;
-        private const int SbStageShift = 4;
+        private const int ShaderStages = 6;
+
+        private const int UbStageShift  = 5;
+        private const int SbStageShift  = 4;
+        private const int TexStageShift = 5;
+        private const int ImgStageShift = 3;
+
+        private const int UbsPerStage  = 1 << UbStageShift;
+        private const int SbsPerStage  = 1 << SbStageShift;
+        private const int TexsPerStage = 1 << TexStageShift;
+        private const int ImgsPerStage = 1 << ImgStageShift;
 
         public int Handle { get; private set; }
 
@@ -16,12 +25,14 @@ namespace Ryujinx.Graphics.OpenGL
         private int[] _ubBindingPoints;
         private int[] _sbBindingPoints;
         private int[] _textureUnits;
+        private int[] _imageUnits;
 
         public Program(IShader[] shaders)
         {
-            _ubBindingPoints = new int[32 * 6];
-            _sbBindingPoints = new int[16 * 6];
-            _textureUnits    = new int[32 * 6];
+            _ubBindingPoints = new int[UbsPerStage  * ShaderStages];
+            _sbBindingPoints = new int[SbsPerStage  * ShaderStages];
+            _textureUnits    = new int[TexsPerStage * ShaderStages];
+            _imageUnits      = new int[ImgsPerStage * ShaderStages];
 
             for (int index = 0; index < _ubBindingPoints.Length; index++)
             {
@@ -38,6 +49,11 @@ namespace Ryujinx.Graphics.OpenGL
                 _textureUnits[index] = -1;
             }
 
+            for (int index = 0; index < _imageUnits.Length; index++)
+            {
+                _imageUnits[index] = -1;
+            }
+
             Handle = GL.CreateProgram();
 
             for (int index = 0; index < shaders.Length; index++)
@@ -79,7 +95,7 @@ namespace Ryujinx.Graphics.OpenGL
 
                     GL.UniformBlockBinding(Handle, location, ubBindingPoint);
 
-                    int bpIndex = (int)shader.Stage << StageShift | descriptor.Slot;
+                    int bpIndex = (int)shader.Stage << UbStageShift | descriptor.Slot;
 
                     _ubBindingPoints[bpIndex] = ubBindingPoint;
 
@@ -117,7 +133,27 @@ namespace Ryujinx.Graphics.OpenGL
 
                     GL.Uniform1(location, textureUnit);
 
-                    int uIndex = (int)shader.Stage << StageShift | samplerIndex++;
+                    int uIndex = (int)shader.Stage << TexStageShift | samplerIndex++;
+
+                    _textureUnits[uIndex] = textureUnit;
+
+                    textureUnit++;
+                }
+
+                int imageIndex = 0;
+
+                foreach (TextureDescriptor descriptor in shader.Info.Images)
+                {
+                    int location = GL.GetUniformLocation(Handle, descriptor.Name);
+
+                    if (location < 0)
+                    {
+                        continue;
+                    }
+
+                    GL.Uniform1(location, textureUnit);
+
+                    int uIndex = (int)shader.Stage << ImgStageShift | imageIndex++;
 
                     _textureUnits[uIndex] = textureUnit;
 
@@ -133,7 +169,7 @@ namespace Ryujinx.Graphics.OpenGL
 
         public int GetUniformBufferBindingPoint(ShaderStage stage, int index)
         {
-            return _ubBindingPoints[(int)stage << StageShift | index];
+            return _ubBindingPoints[(int)stage << UbStageShift | index];
         }
 
         public int GetStorageBufferBindingPoint(ShaderStage stage, int index)
@@ -143,7 +179,12 @@ namespace Ryujinx.Graphics.OpenGL
 
         public int GetTextureUnit(ShaderStage stage, int index)
         {
-            return _textureUnits[(int)stage << StageShift | index];
+            return _textureUnits[(int)stage << TexStageShift | index];
+        }
+
+        public int GetImageUnit(ShaderStage stage, int index)
+        {
+            return _textureUnits[(int)stage << ImgStageShift | index];
         }
 
         private void CheckProgramLink()
diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs
index 56ba762445..1baee04b12 100644
--- a/Ryujinx.Graphics.OpenGL/Renderer.cs
+++ b/Ryujinx.Graphics.OpenGL/Renderer.cs
@@ -8,8 +8,7 @@ namespace Ryujinx.Graphics.OpenGL
 {
     public class Renderer : IRenderer
     {
-        public IComputePipeline  ComputePipeline  { get; }
-        public IGraphicsPipeline GraphicsPipeline { get; }
+        public IPipeline Pipeline { get; }
 
         private Counters _counters;
 
@@ -21,8 +20,7 @@ namespace Ryujinx.Graphics.OpenGL
 
         public Renderer()
         {
-            ComputePipeline  = new ComputePipeline(this);
-            GraphicsPipeline = new GraphicsPipeline();
+            Pipeline = new Pipeline();
 
             _counters = new Counters();
 
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs
index 322bfbf56e..abfe55a5a4 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs
@@ -12,6 +12,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
         public List<BufferDescriptor>  CBufferDescriptors { get; }
         public List<BufferDescriptor>  SBufferDescriptors { get; }
         public List<TextureDescriptor> TextureDescriptors { get; }
+        public List<TextureDescriptor> ImageDescriptors   { get; }
 
         public OperandManager OperandManager { get; }
 
@@ -28,6 +29,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             CBufferDescriptors = new List<BufferDescriptor>();
             SBufferDescriptors = new List<BufferDescriptor>();
             TextureDescriptors = new List<TextureDescriptor>();
+            ImageDescriptors   = new List<TextureDescriptor>();
 
             OperandManager = new OperandManager();
 
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
index 3644b21a61..ab10d91a64 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
@@ -88,6 +88,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
                 context.AppendLine();
             }
 
+            if (info.Images.Count != 0)
+            {
+                DeclareImages(context, info);
+
+                context.AppendLine();
+            }
+
             if (context.Config.Stage != ShaderStage.Compute)
             {
                 if (info.IAttributes.Count != 0)
@@ -204,7 +211,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
                     continue;
                 }
 
-                string samplerTypeName = GetSamplerTypeName(texOp.Target);
+                string samplerTypeName = GetSamplerTypeName(texOp.Type);
 
                 context.AppendLine("uniform " + samplerTypeName + " " + samplerName + ";");
             }
@@ -221,17 +228,47 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
                 {
                     AstOperand operand = texOp.GetSource(0) as AstOperand;
 
-                    desc = new TextureDescriptor(samplerName, texOp.Target, operand.CbufSlot, operand.CbufOffset);
+                    desc = new TextureDescriptor(samplerName, texOp.Type, operand.CbufSlot, operand.CbufOffset);
                 }
                 else
                 {
-                    desc = new TextureDescriptor(samplerName, texOp.Target, texOp.Handle);
+                    desc = new TextureDescriptor(samplerName, texOp.Type, texOp.Handle);
                 }
 
                 context.TextureDescriptors.Add(desc);
             }
         }
 
+        private static void DeclareImages(CodeGenContext context, StructuredProgramInfo info)
+        {
+            Dictionary<string, AstTextureOperation> images = new Dictionary<string, AstTextureOperation>();
+
+            foreach (AstTextureOperation texOp in info.Images.OrderBy(x => x.Handle))
+            {
+                string imageName = OperandManager.GetImageName(context.Config.Stage, texOp);
+
+                if (!images.TryAdd(imageName, texOp))
+                {
+                    continue;
+                }
+
+                string imageTypeName = GetImageTypeName(texOp.Type);
+
+                context.AppendLine("writeonly uniform " + imageTypeName + " " + imageName + ";");
+            }
+
+            foreach (KeyValuePair<string, AstTextureOperation> kv in images)
+            {
+                string imageName = kv.Key;
+
+                AstTextureOperation texOp = kv.Value;
+
+                TextureDescriptor desc = new TextureDescriptor(imageName, texOp.Type, texOp.Handle);
+
+                context.ImageDescriptors.Add(desc);
+            }
+        }
+
         private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info)
         {
             string suffix = context.Config.Stage == ShaderStage.Geometry ? "[]" : string.Empty;
@@ -284,36 +321,65 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             }
         }
 
-        private static string GetSamplerTypeName(TextureTarget type)
+        private static string GetSamplerTypeName(SamplerType type)
         {
             string typeName;
 
-            switch (type & TextureTarget.Mask)
+            switch (type & SamplerType.Mask)
             {
-                case TextureTarget.Texture1D:   typeName = "sampler1D";   break;
-                case TextureTarget.Texture2D:   typeName = "sampler2D";   break;
-                case TextureTarget.Texture3D:   typeName = "sampler3D";   break;
-                case TextureTarget.TextureCube: typeName = "samplerCube"; break;
+                case SamplerType.Texture1D:     typeName = "sampler1D";     break;
+                case SamplerType.TextureBuffer: typeName = "samplerBuffer"; break;
+                case SamplerType.Texture2D:     typeName = "sampler2D";     break;
+                case SamplerType.Texture3D:     typeName = "sampler3D";     break;
+                case SamplerType.TextureCube:   typeName = "samplerCube";   break;
 
                 default: throw new ArgumentException($"Invalid sampler type \"{type}\".");
             }
 
-            if ((type & TextureTarget.Multisample) != 0)
+            if ((type & SamplerType.Multisample) != 0)
             {
                 typeName += "MS";
             }
 
-            if ((type & TextureTarget.Array) != 0)
+            if ((type & SamplerType.Array) != 0)
             {
                 typeName += "Array";
             }
 
-            if ((type & TextureTarget.Shadow) != 0)
+            if ((type & SamplerType.Shadow) != 0)
             {
                 typeName += "Shadow";
             }
 
             return typeName;
         }
+
+        private static string GetImageTypeName(SamplerType type)
+        {
+            string typeName;
+
+            switch (type & SamplerType.Mask)
+            {
+                case SamplerType.Texture1D:     typeName = "image1D";     break;
+                case SamplerType.TextureBuffer: typeName = "imageBuffer"; break;
+                case SamplerType.Texture2D:     typeName = "image2D";     break;
+                case SamplerType.Texture3D:     typeName = "image3D";     break;
+                case SamplerType.TextureCube:   typeName = "imageCube";   break;
+
+                default: throw new ArgumentException($"Invalid sampler type \"{type}\".");
+            }
+
+            if ((type & SamplerType.Multisample) != 0)
+            {
+                typeName += "MS";
+            }
+
+            if ((type & SamplerType.Array) != 0)
+            {
+                typeName += "Array";
+            }
+
+            return typeName;
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs
index 67de3b438d..a06b0cc8ce 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs
@@ -5,6 +5,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
         public const string LocalNamePrefix = "temp";
 
         public const string SamplerNamePrefix = "tex";
+        public const string ImageNamePrefix   = "img";
 
         public const string IAttributePrefix = "in_attr";
         public const string OAttributePrefix = "out_attr";
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs
index 65246d9750..b5407eb863 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs
@@ -21,6 +21,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
                 context.CBufferDescriptors.ToArray(),
                 context.SBufferDescriptors.ToArray(),
                 context.TextureDescriptors.ToArray(),
+                context.ImageDescriptors.ToArray(),
                 context.GetCode());
         }
 
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslProgram.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslProgram.cs
index 7807cb4959..31b7f31260 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslProgram.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslProgram.cs
@@ -5,6 +5,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
         public BufferDescriptor[]  CBufferDescriptors { get; }
         public BufferDescriptor[]  SBufferDescriptors { get; }
         public TextureDescriptor[] TextureDescriptors { get; }
+        public TextureDescriptor[] ImageDescriptors   { get; }
 
         public string Code { get; }
 
@@ -12,11 +13,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             BufferDescriptor[]  cBufferDescriptors,
             BufferDescriptor[]  sBufferDescriptors,
             TextureDescriptor[] textureDescriptors,
+            TextureDescriptor[] imageDescriptors,
             string              code)
         {
             CBufferDescriptors = cBufferDescriptors;
             SBufferDescriptors = sBufferDescriptors;
             TextureDescriptors = textureDescriptors;
+            ImageDescriptors   = imageDescriptors;
             Code               = code;
         }
     }
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs
index 350de3480d..3bf31c1639 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs
@@ -87,6 +87,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
             {
                 switch (inst)
                 {
+                    case Instruction.ImageStore:
+                        return InstGenMemory.ImageStore(context, operation);
+
                     case Instruction.LoadAttribute:
                         return InstGenMemory.LoadAttribute(context, operation);
 
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
index 6989e99748..6dfbe61abb 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
@@ -48,6 +48,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
             Add(Instruction.ExponentB2,               InstType.CallUnary,      "exp2");
             Add(Instruction.Floor,                    InstType.CallUnary,      "floor");
             Add(Instruction.FusedMultiplyAdd,         InstType.CallTernary,    "fma");
+            Add(Instruction.ImageLoad,                InstType.Special);
+            Add(Instruction.ImageStore,               InstType.Special);
             Add(Instruction.IsNan,                    InstType.CallUnary,      "isnan");
             Add(Instruction.LoadAttribute,            InstType.Special);
             Add(Instruction.LoadConstant,             InstType.Special);
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
index 5c9ec830c9..f2f6ae0c96 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
@@ -9,6 +9,80 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
 {
     static class InstGenMemory
     {
+        public static string ImageStore(CodeGenContext context, AstOperation operation)
+        {
+            AstTextureOperation texOp = (AstTextureOperation)operation;
+
+            bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
+
+            bool isArray = (texOp.Type & SamplerType.Array) != 0;
+
+            string texCall = "imageStore";
+
+            string imageName = OperandManager.GetImageName(context.Config.Stage, texOp);
+
+            texCall += "(" + imageName;
+
+            int coordsCount = texOp.Type.GetDimensions();
+
+            int pCount = coordsCount;
+
+            int arrayIndexElem = -1;
+
+            if (isArray)
+            {
+                arrayIndexElem = pCount++;
+            }
+
+            int srcIndex = isBindless ? 1 : 0;
+
+            string Src(VariableType type)
+            {
+                return GetSoureExpr(context, texOp.GetSource(srcIndex++), type);
+            }
+
+            void Append(string str)
+            {
+                texCall += ", " + str;
+            }
+
+            if (pCount > 1)
+            {
+                string[] elems = new string[pCount];
+
+                for (int index = 0; index < pCount; index++)
+                {
+                    elems[index] = Src(VariableType.S32);
+                }
+
+                Append("ivec" + pCount + "(" + string.Join(", ", elems) + ")");
+            }
+            else
+            {
+                Append(Src(VariableType.S32));
+            }
+
+            string[] cElems = new string[4];
+
+            for (int index = 0; index < 4; index++)
+            {
+                if (srcIndex < texOp.SourcesCount)
+                {
+                    cElems[index] = Src(VariableType.F32);
+                }
+                else
+                {
+                    cElems[index] = NumberFormatter.FormatFloat(0);
+                }
+            }
+
+            Append("vec4(" + string.Join(", ", cElems) + ")");
+
+            texCall += ")";
+
+            return texCall;
+        }
+
         public static string LoadAttribute(CodeGenContext context, AstOperation operation)
         {
             IAstNode src1 = operation.GetSource(0);
@@ -98,9 +172,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
             bool hasOffset   = (texOp.Flags & TextureFlags.Offset)    != 0;
             bool hasOffsets  = (texOp.Flags & TextureFlags.Offsets)   != 0;
 
-            bool isArray       = (texOp.Target & TextureTarget.Array)       != 0;
-            bool isMultisample = (texOp.Target & TextureTarget.Multisample) != 0;
-            bool isShadow      = (texOp.Target & TextureTarget.Shadow)      != 0;
+            bool isArray       = (texOp.Type & SamplerType.Array)       != 0;
+            bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
+            bool isShadow      = (texOp.Type & SamplerType.Shadow)      != 0;
 
             // This combination is valid, but not available on GLSL.
             // For now, ignore the LOD level and do a normal sample.
@@ -134,7 +208,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
 
             texCall += "(" + samplerName;
 
-            int coordsCount = texOp.Target.GetDimensions();
+            int coordsCount = texOp.Type.GetDimensions();
 
             int pCount = coordsCount;
 
@@ -147,7 +221,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
 
             // The sampler 1D shadow overload expects a
             // dummy value on the middle of the vector, who knows why...
-            bool hasDummy1DShadowElem = texOp.Target == (TextureTarget.Texture1D | TextureTarget.Shadow);
+            bool hasDummy1DShadowElem = texOp.Type == (SamplerType.Texture1D | SamplerType.Shadow);
 
             if (hasDummy1DShadowElem)
             {
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
index 8809592085..fe3073967c 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
@@ -241,6 +241,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
             return GetShaderStagePrefix(stage) + "_" + DefaultNames.SamplerNamePrefix + suffix;
         }
 
+        public static string GetImageName(ShaderStage stage, AstTextureOperation texOp)
+        {
+            string suffix = texOp.Handle.ToString();
+
+            return GetShaderStagePrefix(stage) + "_" + DefaultNames.ImageNamePrefix + suffix;
+        }
+
         public static string GetShaderStagePrefix(ShaderStage stage)
         {
             int index = (int)stage;
diff --git a/Ryujinx.Graphics.Shader/Decoders/ImageComponents.cs b/Ryujinx.Graphics.Shader/Decoders/ImageComponents.cs
new file mode 100644
index 0000000000..b8a4f6d512
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Decoders/ImageComponents.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+    enum ImageComponents
+    {
+        Red   = 1 << 0,
+        Green = 1 << 1,
+        Blue  = 1 << 2,
+        Alpha = 1 << 3
+
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Decoders/ImageDimensions.cs b/Ryujinx.Graphics.Shader/Decoders/ImageDimensions.cs
new file mode 100644
index 0000000000..ecf41a82fc
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Decoders/ImageDimensions.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+    enum ImageDimensions
+    {
+        Image1D,
+        ImageBuffer,
+        Image1DArray,
+        Image2D,
+        Image2DArray,
+        Image3D
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeImage.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeImage.cs
new file mode 100644
index 0000000000..42fe677bdb
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeImage.cs
@@ -0,0 +1,48 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+    class OpCodeImage : OpCode
+    {
+        public Register Ra { get; }
+        public Register Rb { get; }
+        public Register Rc { get; }
+
+        public ImageComponents Components { get; }
+        public IntegerSize     Size       { get; }
+
+        public bool ByteAddress { get; }
+
+        public ImageDimensions Dimensions { get; }
+
+        public int Immediate { get; }
+
+        public bool UseComponents { get; }
+        public bool IsBindless    { get; }
+
+        public OpCodeImage(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+        {
+            Ra = new Register(opCode.Extract(8,  8), RegisterType.Gpr);
+            Rb = new Register(opCode.Extract(0,  8), RegisterType.Gpr);
+            Rc = new Register(opCode.Extract(39, 8), RegisterType.Gpr);
+
+            UseComponents = !opCode.Extract(52);
+
+            if (UseComponents)
+            {
+                Components = (ImageComponents)opCode.Extract(20, 4);
+            }
+            else
+            {
+                Size = (IntegerSize)opCode.Extract(20, 4);
+            }
+
+            ByteAddress = !opCode.Extract(23);
+
+            Dimensions = (ImageDimensions)opCode.Extract(33, 3);
+
+            Immediate  =  opCode.Extract(36, 13);
+            IsBindless = !opCode.Extract(51);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs
index 3fd1de8652..5128dae391 100644
--- a/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs
+++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTable.cs
@@ -143,6 +143,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
             Set("111000101001xx", InstEmit.Ssy,    typeof(OpCodeSsy));
             Set("1110111101010x", InstEmit.St,     typeof(OpCodeMemory));
             Set("1110111011011x", InstEmit.Stg,    typeof(OpCodeMemory));
+            Set("11101011001xxx", InstEmit.Sust,   typeof(OpCodeImage));
             Set("1111000011111x", InstEmit.Sync,   typeof(OpCodeSync));
             Set("110000xxxx111x", InstEmit.Tex,    typeof(OpCodeTex));
             Set("1101111010111x", InstEmit.TexB,   typeof(OpCodeTexB));
diff --git a/Ryujinx.Graphics.Shader/Decoders/TexelLoadTarget.cs b/Ryujinx.Graphics.Shader/Decoders/TexelLoadTarget.cs
index 478cac44e8..e5a0c004b5 100644
--- a/Ryujinx.Graphics.Shader/Decoders/TexelLoadTarget.cs
+++ b/Ryujinx.Graphics.Shader/Decoders/TexelLoadTarget.cs
@@ -2,14 +2,14 @@ namespace Ryujinx.Graphics.Shader.Decoders
 {
     enum TexelLoadTarget
     {
-        Texture1DLodZero             = 0x0,
-        Texture1DLodLevel            = 0x1,
-        Texture2DLodZero             = 0x2,
-        Texture2DLodZeroOffset       = 0x4,
-        Texture2DLodLevel            = 0x5,
+        Texture1DLodZero            = 0x0,
+        Texture1DLodLevel           = 0x1,
+        Texture2DLodZero            = 0x2,
+        Texture2DLodZeroOffset      = 0x4,
+        Texture2DLodLevel           = 0x5,
         Texture2DLodZeroMultisample = 0x6,
-        Texture3DLodZero             = 0x7,
-        Texture2DArrayLodZero        = 0x8,
-        Texture2DLodLevelOffset      = 0xc
+        Texture3DLodZero            = 0x7,
+        Texture2DArrayLodZero       = 0x8,
+        Texture2DLodLevelOffset     = 0xc
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
index 554deb7bae..3967278943 100644
--- a/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
@@ -10,6 +10,96 @@ namespace Ryujinx.Graphics.Shader.Instructions
 {
     static partial class InstEmit
     {
+        public static void Sust(EmitterContext context)
+        {
+            OpCodeImage op = (OpCodeImage)context.CurrOp;
+
+            int raIndex = op.Ra.Index;
+            int rbIndex = op.Rb.Index;
+
+            Operand Ra()
+            {
+                if (raIndex > RegisterConsts.RegisterZeroIndex)
+                {
+                    return Const(0);
+                }
+
+                return context.Copy(Register(raIndex++, RegisterType.Gpr));
+            }
+
+            Operand Rb()
+            {
+                if (rbIndex > RegisterConsts.RegisterZeroIndex)
+                {
+                    return Const(0);
+                }
+
+                return context.Copy(Register(rbIndex++, 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)));
+            }
+
+            SamplerType type = GetSamplerType(op.Dimensions);
+
+            int coordsCount = type.GetDimensions();
+
+            for (int index = 0; index < coordsCount; index++)
+            {
+                sourcesList.Add(Ra());
+            }
+
+            if (isArray)
+            {
+                sourcesList.Add(arrayIndex);
+
+                type |= SamplerType.Array;
+            }
+
+            if (op.UseComponents)
+            {
+                int componentMask = (int)op.Components;
+
+                for (int compMask = componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
+                {
+                    if ((compMask & 1) != 0)
+                    {
+                        sourcesList.Add(Rb());
+                    }
+                }
+            }
+            else
+            {
+                // TODO.
+            }
+
+            Operand[] sources = sourcesList.ToArray();
+
+            int handle = !op.IsBindless ? op.Immediate : 0;
+
+            TextureFlags flags = op.IsBindless ? TextureFlags.Bindless : TextureFlags.None;
+
+            TextureOperation operation = new TextureOperation(
+                Instruction.ImageStore,
+                type,
+                flags,
+                handle,
+                0,
+                null,
+                sources);
+
+            context.Add(operation);
+        }
+
         public static void Tex(EmitterContext context)
         {
             Tex(context, TextureFlags.None);
@@ -74,15 +164,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
                 }
             }
 
-            TextureTarget  type;
+            SamplerType  type;
             TextureFlags flags;
 
             if (op is OpCodeTexs texsOp)
             {
-                type  = GetTextureType (texsOp.Target);
-                flags = GetTextureFlags(texsOp.Target);
+                type  = GetSamplerType (texsOp.Target);
+                flags = GetSamplerFlags(texsOp.Target);
 
-                if ((type & TextureTarget.Array) != 0)
+                if ((type & SamplerType.Array) != 0)
                 {
                     Operand arrayIndex = Ra();
 
@@ -91,7 +181,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
                     sourcesList.Add(arrayIndex);
 
-                    if ((type & TextureTarget.Shadow) != 0)
+                    if ((type & SamplerType.Shadow) != 0)
                     {
                         sourcesList.Add(Rb());
                     }
@@ -149,8 +239,8 @@ namespace Ryujinx.Graphics.Shader.Instructions
             }
             else if (op is OpCodeTlds tldsOp)
             {
-                type  = GetTextureType (tldsOp.Target);
-                flags = GetTextureFlags(tldsOp.Target) | TextureFlags.IntCoords;
+                type  = GetSamplerType (tldsOp.Target);
+                flags = GetSamplerFlags(tldsOp.Target) | TextureFlags.IntCoords;
 
                 switch (tldsOp.Target)
                 {
@@ -217,14 +307,14 @@ namespace Ryujinx.Graphics.Shader.Instructions
                     sourcesList.Add(Ra());
                 }
 
-                type  = TextureTarget.Texture2D;
+                type  = SamplerType.Texture2D;
                 flags = TextureFlags.Gather;
 
                 if (tld4sOp.HasDepthCompare)
                 {
                     sourcesList.Add(Rb());
 
-                    type |= TextureTarget.Shadow;
+                    type |= SamplerType.Shadow;
                 }
 
                 if (tld4sOp.HasOffset)
@@ -338,7 +428,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
 
             List<Operand> sourcesList = new List<Operand>();
 
-            TextureTarget type = GetTextureType(op.Dimensions);
+            SamplerType type = GetSamplerType(op.Dimensions);
 
             TextureFlags flags = TextureFlags.Gather;
 
@@ -353,7 +443,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
             {
                 sourcesList.Add(arrayIndex);
 
-                type |= TextureTarget.Array;
+                type |= SamplerType.Array;
             }
 
             Operand[] packedOffs = new Operand[2];
@@ -365,7 +455,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
             {
                 sourcesList.Add(Rb());
 
-                type |= TextureTarget.Shadow;
+                type |= SamplerType.Shadow;
             }
 
             if (op.Offset != TextureGatherOffset.None)
@@ -446,7 +536,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
             // TODO: Validate and use property.
             Instruction inst = Instruction.TextureSize;
 
-            TextureTarget type = TextureTarget.Texture2D;
+            SamplerType type = SamplerType.Texture2D;
 
             TextureFlags flags = bindless ? TextureFlags.Bindless : TextureFlags.None;
 
@@ -551,7 +641,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
                 sourcesList.Add(Rb());
             }
 
-            TextureTarget type = GetTextureType(op.Dimensions);
+            SamplerType type = GetSamplerType(op.Dimensions);
 
             int coordsCount = type.GetDimensions();
 
@@ -564,7 +654,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
             {
                 sourcesList.Add(arrayIndex);
 
-                type |= TextureTarget.Array;
+                type |= SamplerType.Array;
             }
 
             bool hasLod = op.LodMode > TextureLodMode.LodZero;
@@ -577,7 +667,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
             {
                 sourcesList.Add(Rb());
 
-                type |= TextureTarget.Shadow;
+                type |= SamplerType.Shadow;
             }
 
             if ((op.LodMode == TextureLodMode.LodZero  ||
@@ -611,7 +701,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
             {
                 sourcesList.Add(Rb());
 
-                type |= TextureTarget.Multisample;
+                type |= SamplerType.Multisample;
             }
 
             Operand[] sources = sourcesList.ToArray();
@@ -650,83 +740,109 @@ namespace Ryujinx.Graphics.Shader.Instructions
             }
         }
 
-        private static TextureTarget GetTextureType(TextureDimensions dimensions)
+        private static SamplerType GetSamplerType(ImageDimensions target)
+        {
+            switch (target)
+            {
+                case ImageDimensions.Image1D:
+                    return SamplerType.Texture1D;
+
+                case ImageDimensions.ImageBuffer:
+                    return SamplerType.TextureBuffer;
+
+                case ImageDimensions.Image1DArray:
+                    return SamplerType.Texture1D | SamplerType.Array;
+
+                case ImageDimensions.Image2D:
+                    return SamplerType.Texture2D;
+
+                case ImageDimensions.Image2DArray:
+                    return SamplerType.Texture2D | SamplerType.Array;
+
+                case ImageDimensions.Image3D:
+                    return SamplerType.Texture3D;
+            }
+
+            throw new ArgumentException($"Invalid image target \"{target}\".");
+        }
+
+        private static SamplerType GetSamplerType(TextureDimensions dimensions)
         {
             switch (dimensions)
             {
-                case TextureDimensions.Texture1D:   return TextureTarget.Texture1D;
-                case TextureDimensions.Texture2D:   return TextureTarget.Texture2D;
-                case TextureDimensions.Texture3D:   return TextureTarget.Texture3D;
-                case TextureDimensions.TextureCube: return TextureTarget.TextureCube;
+                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}\".");
         }
 
-        private static TextureTarget GetTextureType(Decoders.TextureTarget type)
+        private static SamplerType GetSamplerType(Decoders.TextureTarget type)
         {
             switch (type)
             {
                 case Decoders.TextureTarget.Texture1DLodZero:
-                    return TextureTarget.Texture1D;
+                    return SamplerType.Texture1D;
 
                 case Decoders.TextureTarget.Texture2D:
                 case Decoders.TextureTarget.Texture2DLodZero:
                 case Decoders.TextureTarget.Texture2DLodLevel:
-                    return TextureTarget.Texture2D;
+                    return SamplerType.Texture2D;
 
                 case Decoders.TextureTarget.Texture2DDepthCompare:
                 case Decoders.TextureTarget.Texture2DLodLevelDepthCompare:
                 case Decoders.TextureTarget.Texture2DLodZeroDepthCompare:
-                    return TextureTarget.Texture2D | TextureTarget.Shadow;
+                    return SamplerType.Texture2D | SamplerType.Shadow;
 
                 case Decoders.TextureTarget.Texture2DArray:
                 case Decoders.TextureTarget.Texture2DArrayLodZero:
-                    return TextureTarget.Texture2D | TextureTarget.Array;
+                    return SamplerType.Texture2D | SamplerType.Array;
 
                 case Decoders.TextureTarget.Texture2DArrayLodZeroDepthCompare:
-                    return TextureTarget.Texture2D | TextureTarget.Array | TextureTarget.Shadow;
+                    return SamplerType.Texture2D | SamplerType.Array | SamplerType.Shadow;
 
                 case Decoders.TextureTarget.Texture3D:
                 case Decoders.TextureTarget.Texture3DLodZero:
-                    return TextureTarget.Texture3D;
+                    return SamplerType.Texture3D;
 
                 case Decoders.TextureTarget.TextureCube:
                 case Decoders.TextureTarget.TextureCubeLodLevel:
-                    return TextureTarget.TextureCube;
+                    return SamplerType.TextureCube;
             }
 
             throw new ArgumentException($"Invalid texture type \"{type}\".");
         }
 
-        private static TextureTarget GetTextureType(TexelLoadTarget type)
+        private static SamplerType GetSamplerType(TexelLoadTarget type)
         {
             switch (type)
             {
                 case TexelLoadTarget.Texture1DLodZero:
                 case TexelLoadTarget.Texture1DLodLevel:
-                    return TextureTarget.Texture1D;
+                    return SamplerType.Texture1D;
 
                 case TexelLoadTarget.Texture2DLodZero:
                 case TexelLoadTarget.Texture2DLodZeroOffset:
                 case TexelLoadTarget.Texture2DLodLevel:
                 case TexelLoadTarget.Texture2DLodLevelOffset:
-                    return TextureTarget.Texture2D;
+                    return SamplerType.Texture2D;
 
                 case TexelLoadTarget.Texture2DLodZeroMultisample:
-                    return TextureTarget.Texture2D | TextureTarget.Multisample;
+                    return SamplerType.Texture2D | SamplerType.Multisample;
 
                 case TexelLoadTarget.Texture3DLodZero:
-                    return TextureTarget.Texture3D;
+                    return SamplerType.Texture3D;
 
                 case TexelLoadTarget.Texture2DArrayLodZero:
-                    return TextureTarget.Texture2D | TextureTarget.Array;
+                    return SamplerType.Texture2D | SamplerType.Array;
             }
 
             throw new ArgumentException($"Invalid texture type \"{type}\".");
         }
 
-        private static TextureFlags GetTextureFlags(Decoders.TextureTarget type)
+        private static TextureFlags GetSamplerFlags(Decoders.TextureTarget type)
         {
             switch (type)
             {
@@ -752,7 +868,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
             throw new ArgumentException($"Invalid texture type \"{type}\".");
         }
 
-        private static TextureFlags GetTextureFlags(TexelLoadTarget type)
+        private static TextureFlags GetSamplerFlags(TexelLoadTarget type)
         {
             switch (type)
             {
diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
index e580965e70..74194f35b4 100644
--- a/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
+++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
@@ -45,6 +45,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
         ExponentB2,
         Floor,
         FusedMultiplyAdd,
+        ImageLoad,
+        ImageStore,
         IsNan,
         LoadAttribute,
         LoadConstant,
diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs
index 0c768345c5..b96c55e880 100644
--- a/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs
+++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs
@@ -2,21 +2,21 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
 {
     class TextureOperation : Operation
     {
-        public TextureTarget Target { get; }
-        public TextureFlags  Flags  { get; }
+        public SamplerType  Type  { get; }
+        public TextureFlags Flags { get; }
 
         public int Handle { get; }
 
         public TextureOperation(
             Instruction      inst,
-            TextureTarget    target,
+            SamplerType      type,
             TextureFlags     flags,
             int              handle,
             int              compIndex,
             Operand          dest,
             params Operand[] sources) : base(inst, compIndex, dest, sources)
         {
-            Target = target;
+            Type   = type;
             Flags  = flags;
             Handle = handle;
         }
diff --git a/Ryujinx.Graphics.Shader/SamplerType.cs b/Ryujinx.Graphics.Shader/SamplerType.cs
new file mode 100644
index 0000000000..42854c9786
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/SamplerType.cs
@@ -0,0 +1,37 @@
+using System;
+
+namespace Ryujinx.Graphics.Shader
+{
+    [Flags]
+    public enum SamplerType
+    {
+        Texture1D,
+        TextureBuffer,
+        Texture2D,
+        Texture3D,
+        TextureCube,
+
+        Mask = 0xff,
+
+        Array       = 1 << 8,
+        Multisample = 1 << 9,
+        Shadow      = 1 << 10
+    }
+
+    static class SamplerTypeExtensions
+    {
+        public static int GetDimensions(this SamplerType type)
+        {
+            switch (type & SamplerType.Mask)
+            {
+                case SamplerType.Texture1D:     return 1;
+                case SamplerType.TextureBuffer: return 1;
+                case SamplerType.Texture2D:     return 2;
+                case SamplerType.Texture3D:     return 3;
+                case SamplerType.TextureCube:   return 3;
+            }
+
+            throw new ArgumentException($"Invalid texture type \"{type}\".");
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs b/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
index 841e636dd6..1ff602a240 100644
--- a/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
+++ b/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
@@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Shader
         public ReadOnlyCollection<BufferDescriptor>  CBuffers { get; }
         public ReadOnlyCollection<BufferDescriptor>  SBuffers { get; }
         public ReadOnlyCollection<TextureDescriptor> Textures { get; }
+        public ReadOnlyCollection<TextureDescriptor> Images   { get; }
 
         public ReadOnlyCollection<InterpolationQualifier> InterpolationQualifiers { get; }
 
@@ -17,12 +18,14 @@ namespace Ryujinx.Graphics.Shader
             BufferDescriptor[]       cBuffers,
             BufferDescriptor[]       sBuffers,
             TextureDescriptor[]      textures,
+            TextureDescriptor[]      images,
             InterpolationQualifier[] interpolationQualifiers,
             bool                     usesInstanceId)
         {
             CBuffers = Array.AsReadOnly(cBuffers);
             SBuffers = Array.AsReadOnly(sBuffers);
             Textures = Array.AsReadOnly(textures);
+            Images   = Array.AsReadOnly(images);
 
             InterpolationQualifiers = Array.AsReadOnly(interpolationQualifiers);
 
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
index c9bd575091..7261b9ff1f 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
@@ -4,20 +4,20 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
 {
     class AstTextureOperation : AstOperation
     {
-        public TextureTarget Target { get; }
-        public TextureFlags  Flags  { get; }
+        public SamplerType  Type  { get; }
+        public TextureFlags Flags { get; }
 
         public int Handle { get; }
 
         public AstTextureOperation(
             Instruction       inst,
-            TextureTarget     target,
+            SamplerType       type,
             TextureFlags      flags,
             int               handle,
             int               compMask,
             params IAstNode[] sources) : base(inst, compMask, sources)
         {
-            Target = target;
+            Type = type;
             Flags  = flags;
             Handle = handle;
         }
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
index dc82262137..53ca6700fc 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
@@ -51,6 +51,19 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
                 sources[index] = context.GetOperandUse(operation.GetSource(index));
             }
 
+            int componentMask = 1 << operation.ComponentIndex;
+
+            AstTextureOperation GetAstTextureOperation(TextureOperation texOp)
+            {
+                return new AstTextureOperation(
+                    inst,
+                    texOp.Type,
+                    texOp.Flags,
+                    texOp.Handle,
+                    componentMask,
+                    sources);
+            }
+
             if (operation.Dest != null)
             {
                 AstOperand dest = context.GetOperandDef(operation.Dest);
@@ -108,21 +121,20 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
                     dest.VarType = InstructionInfo.GetDestVarType(inst);
                 }
 
-                int componentMask = 1 << operation.ComponentIndex;
-
                 IAstNode source;
 
                 if (operation is TextureOperation texOp)
                 {
-                    AstTextureOperation astTexOp = new AstTextureOperation(
-                        inst,
-                        texOp.Target,
-                        texOp.Flags,
-                        texOp.Handle,
-                        componentMask,
-                        sources);
+                    AstTextureOperation astTexOp = GetAstTextureOperation(texOp);
 
-                    context.Info.Samplers.Add(astTexOp);
+                    if (texOp.Inst == Instruction.ImageLoad)
+                    {
+                        context.Info.Images.Add(astTexOp);
+                    }
+                    else
+                    {
+                        context.Info.Samplers.Add(astTexOp);
+                    }
 
                     source = astTexOp;
                 }
@@ -143,6 +155,14 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
             {
                 context.AddNode(new AstComment(((CommentNode)operation).Comment));
             }
+            else if (operation is TextureOperation texOp)
+            {
+                AstTextureOperation astTexOp = GetAstTextureOperation(texOp);
+
+                context.Info.Images.Add(astTexOp);
+
+                context.AddNode(astTexOp);
+            }
             else
             {
                 if (inst == Instruction.StoreStorage)
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
index 27fd1487a0..1094fba2ba 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs
@@ -19,6 +19,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
         public bool UsesInstanceId { get; set; }
 
         public HashSet<AstTextureOperation> Samplers { get; }
+        public HashSet<AstTextureOperation> Images   { get; }
 
         public StructuredProgramInfo(AstBlock mainBlock)
         {
@@ -35,6 +36,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
             InterpolationQualifiers = new InterpolationQualifier[32];
 
             Samplers = new HashSet<AstTextureOperation>();
+            Images   = new HashSet<AstTextureOperation>();
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/TextureDescriptor.cs b/Ryujinx.Graphics.Shader/TextureDescriptor.cs
index 484fa1bcc7..bfcdde590d 100644
--- a/Ryujinx.Graphics.Shader/TextureDescriptor.cs
+++ b/Ryujinx.Graphics.Shader/TextureDescriptor.cs
@@ -4,7 +4,7 @@ namespace Ryujinx.Graphics.Shader
     {
         public string Name { get; }
 
-        public TextureTarget Target { get; }
+        public SamplerType Type { get; }
 
         public int HandleIndex { get; }
 
@@ -13,10 +13,10 @@ namespace Ryujinx.Graphics.Shader
         public int CbufSlot   { get; }
         public int CbufOffset { get; }
 
-        public TextureDescriptor(string name, TextureTarget target, int hIndex)
+        public TextureDescriptor(string name, SamplerType type, int hIndex)
         {
             Name        = name;
-            Target      = target;
+            Type        = type;
             HandleIndex = hIndex;
 
             IsBindless = false;
@@ -25,10 +25,10 @@ namespace Ryujinx.Graphics.Shader
             CbufOffset = 0;
         }
 
-        public TextureDescriptor(string name, TextureTarget target, int cbufSlot, int cbufOffset)
+        public TextureDescriptor(string name, SamplerType type, int cbufSlot, int cbufOffset)
         {
             Name        = name;
-            Target      = target;
+            Type        = type;
             HandleIndex = 0;
 
             IsBindless = true;
diff --git a/Ryujinx.Graphics.Shader/TextureTarget.cs b/Ryujinx.Graphics.Shader/TextureTarget.cs
deleted file mode 100644
index 3642ef239f..0000000000
--- a/Ryujinx.Graphics.Shader/TextureTarget.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-
-namespace Ryujinx.Graphics.Shader
-{
-    [Flags]
-    public enum TextureTarget
-    {
-        Texture1D,
-        Texture2D,
-        Texture3D,
-        TextureCube,
-
-        Mask = 0xff,
-
-        Array       = 1 << 8,
-        Multisample = 1 << 9,
-        Shadow      = 1 << 10
-    }
-
-    static class TextureTargetExtensions
-    {
-        public static int GetDimensions(this TextureTarget type)
-        {
-            switch (type & TextureTarget.Mask)
-            {
-                case TextureTarget.Texture1D:   return 1;
-                case TextureTarget.Texture2D:   return 2;
-                case TextureTarget.Texture3D:   return 3;
-                case TextureTarget.TextureCube: return 3;
-            }
-
-            throw new ArgumentException($"Invalid texture type \"{type}\".");
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Translation/Translator.cs b/Ryujinx.Graphics.Shader/Translation/Translator.cs
index 4838b1e29c..aaf618e9b1 100644
--- a/Ryujinx.Graphics.Shader/Translation/Translator.cs
+++ b/Ryujinx.Graphics.Shader/Translation/Translator.cs
@@ -81,6 +81,7 @@ namespace Ryujinx.Graphics.Shader.Translation
                 program.CBufferDescriptors,
                 program.SBufferDescriptors,
                 program.TextureDescriptors,
+                program.ImageDescriptors,
                 sInfo.InterpolationQualifiers,
                 sInfo.UsesInstanceId);