From 2e5b5c2358caaf8dfd403a30924d49c31aa962a0 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Thu, 11 Jul 2019 20:09:53 -0300
Subject: [PATCH 01/11] gl_rasterizer: Split SetupTextures

---
 .../renderer_opengl/gl_rasterizer.cpp         | 50 +++++++++++--------
 .../renderer_opengl/gl_rasterizer.h           | 10 +++-
 2 files changed, 38 insertions(+), 22 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 01d89f47db..0f09022590 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -331,7 +331,7 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
         const auto stage_enum = static_cast<Maxwell::ShaderStage>(stage);
         SetupDrawConstBuffers(stage_enum, shader);
         SetupDrawGlobalMemory(stage_enum, shader);
-        const auto texture_buffer_usage{SetupTextures(stage_enum, shader, base_bindings)};
+        const auto texture_buffer_usage{SetupDrawTextures(stage_enum, shader, base_bindings)};
 
         const ProgramVariant variant{base_bindings, primitive_mode, texture_buffer_usage};
         const auto [program_handle, next_bindings] = shader->GetProgramHandle(variant);
@@ -981,8 +981,9 @@ void RasterizerOpenGL::SetupGlobalMemory(const GLShader::GlobalMemoryEntry& entr
     bind_ssbo_pushbuffer.Push(ssbo, buffer_offset, static_cast<GLsizeiptr>(size));
 }
 
-TextureBufferUsage RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader,
-                                                   BaseBindings base_bindings) {
+TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stage,
+                                                       const Shader& shader,
+                                                       BaseBindings base_bindings) {
     MICROPROFILE_SCOPE(OpenGL_Texture);
     const auto& gpu = system.GPU();
     const auto& maxwell3d = gpu.Maxwell3D();
@@ -1004,30 +1005,39 @@ TextureBufferUsage RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, c
         } else {
             texture = maxwell3d.GetStageTexture(stage, entry.GetOffset());
         }
-        const u32 current_bindpoint = base_bindings.sampler + bindpoint;
 
-        auto& unit{state.texture_units[current_bindpoint]};
-        unit.sampler = sampler_cache.GetSampler(texture.tsc);
-
-        if (const auto view{texture_cache.GetTextureSurface(texture, entry)}; view) {
-            if (view->GetSurfaceParams().IsBuffer()) {
-                // Record that this texture is a texture buffer.
-                texture_buffer_usage.set(bindpoint);
-            } else {
-                // Apply swizzle to textures that are not buffers.
-                view->ApplySwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source,
-                                   texture.tic.w_source);
-            }
-            state.texture_units[current_bindpoint].texture = view->GetTexture();
-        } else {
-            // Can occur when texture addr is null or its memory is unmapped/invalid
-            unit.texture = 0;
+        if (SetupTexture(shader, base_bindings.sampler + bindpoint, texture, entry)) {
+            texture_buffer_usage.set(bindpoint);
         }
     }
 
     return texture_buffer_usage;
 }
 
+bool RasterizerOpenGL::SetupTexture(const Shader& shader, u32 binding,
+                                    const Tegra::Texture::FullTextureInfo& texture,
+                                    const GLShader::SamplerEntry& entry) {
+    auto& unit{state.texture_units[binding]};
+    unit.sampler = sampler_cache.GetSampler(texture.tsc);
+
+    const auto view = texture_cache.GetTextureSurface(texture, entry);
+    if (!view) {
+        // Can occur when texture addr is null or its memory is unmapped/invalid
+        unit.texture = 0;
+        return false;
+    }
+    unit.texture = view->GetTexture();
+
+    if (view->GetSurfaceParams().IsBuffer()) {
+        return true;
+    }
+
+    // Apply swizzle to textures that are not buffers.
+    view->ApplySwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source,
+                       texture.tic.w_source);
+    return false;
+}
+
 void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
     const auto& regs = system.GPU().Maxwell3D().regs;
     const bool geometry_shaders_enabled =
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 9d20a4fbf5..23ab7aff06 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -32,6 +32,7 @@
 #include "video_core/renderer_opengl/gl_state.h"
 #include "video_core/renderer_opengl/gl_texture_cache.h"
 #include "video_core/renderer_opengl/utils.h"
+#include "video_core/textures/texture.h"
 
 namespace Core {
 class System;
@@ -137,8 +138,13 @@ private:
 
     /// Configures the current textures to use for the draw command. Returns shaders texture buffer
     /// usage.
-    TextureBufferUsage SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
-                                     const Shader& shader, BaseBindings base_bindings);
+    TextureBufferUsage SetupDrawTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
+                                         const Shader& shader, BaseBindings base_bindings);
+
+    /// Configures a texture. Returns true when the texture is a texture buffer.
+    bool SetupTexture(const Shader& shader, u32 binding,
+                      const Tegra::Texture::FullTextureInfo& texture,
+                      const GLShader::SamplerEntry& entry);
 
     /// Syncs the viewport and depth range to match the guest state
     void SyncViewport(OpenGLState& current_state);

From 3a450c1395cdb8b4f73687f8c49648e9190fc3a0 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Thu, 11 Jul 2019 21:54:07 -0300
Subject: [PATCH 02/11] kepler_compute: Implement texture queries

---
 src/video_core/engines/kepler_compute.cpp     | 53 +++++++++++++++++++
 src/video_core/engines/kepler_compute.h       | 23 ++++++--
 .../renderer_opengl/gl_rasterizer.cpp         | 22 +++++++-
 .../renderer_opengl/gl_rasterizer.h           |  2 +
 src/video_core/shader/node.h                  |  4 ++
 5 files changed, 99 insertions(+), 5 deletions(-)

diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index 08586d33ca..63d4491352 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <bitset>
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "core/core.h"
@@ -49,6 +50,33 @@ void KeplerCompute::CallMethod(const GPU::MethodCall& method_call) {
     }
 }
 
+Tegra::Texture::FullTextureInfo KeplerCompute::GetTexture(std::size_t offset) const {
+    const std::bitset<8> cbuf_mask = launch_description.const_buffer_enable_mask.Value();
+    ASSERT(cbuf_mask[regs.tex_cb_index]);
+
+    const auto& texinfo = launch_description.const_buffer_config[regs.tex_cb_index];
+    ASSERT(texinfo.Address() != 0);
+
+    const GPUVAddr address = texinfo.Address() + offset * sizeof(Texture::TextureHandle);
+    ASSERT(address < texinfo.Address() + texinfo.size);
+
+    const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(address)};
+    return GetTextureInfo(tex_handle, offset);
+}
+
+Texture::FullTextureInfo KeplerCompute::GetTextureInfo(const Texture::TextureHandle tex_handle,
+                                                       std::size_t offset) const {
+    return Texture::FullTextureInfo{static_cast<u32>(offset), GetTICEntry(tex_handle.tic_id),
+                                    GetTSCEntry(tex_handle.tsc_id)};
+}
+
+u32 KeplerCompute::AccessConstBuffer32(u64 const_buffer, u64 offset) const {
+    const auto& buffer = launch_description.const_buffer_config[const_buffer];
+    u32 result;
+    std::memcpy(&result, memory_manager.GetPointer(buffer.Address() + offset), sizeof(u32));
+    return result;
+}
+
 void KeplerCompute::ProcessLaunch() {
     const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address();
     memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description,
@@ -60,4 +88,29 @@ void KeplerCompute::ProcessLaunch() {
     rasterizer.DispatchCompute(code_addr);
 }
 
+Texture::TICEntry KeplerCompute::GetTICEntry(u32 tic_index) const {
+    const GPUVAddr tic_address_gpu{regs.tic.Address() + tic_index * sizeof(Texture::TICEntry)};
+
+    Texture::TICEntry tic_entry;
+    memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry));
+
+    const auto r_type{tic_entry.r_type.Value()};
+    const auto g_type{tic_entry.g_type.Value()};
+    const auto b_type{tic_entry.b_type.Value()};
+    const auto a_type{tic_entry.a_type.Value()};
+
+    // TODO(Subv): Different data types for separate components are not supported
+    DEBUG_ASSERT(r_type == g_type && r_type == b_type && r_type == a_type);
+
+    return tic_entry;
+}
+
+Texture::TSCEntry KeplerCompute::GetTSCEntry(u32 tsc_index) const {
+    const GPUVAddr tsc_address_gpu{regs.tsc.Address() + tsc_index * sizeof(Texture::TSCEntry)};
+
+    Texture::TSCEntry tsc_entry;
+    memory_manager.ReadBlockUnsafe(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry));
+    return tsc_entry;
+}
+
 } // namespace Tegra::Engines
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index 6a3309a2c5..90cf650d2f 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -12,6 +12,7 @@
 #include "common/common_types.h"
 #include "video_core/engines/engine_upload.h"
 #include "video_core/gpu.h"
+#include "video_core/textures/texture.h"
 
 namespace Core {
 class System;
@@ -111,7 +112,7 @@ public:
 
                 INSERT_PADDING_WORDS(0x3FE);
 
-                u32 texture_const_buffer_index;
+                u32 tex_cb_index;
 
                 INSERT_PADDING_WORDS(0x374);
             };
@@ -149,7 +150,7 @@ public:
         union {
             BitField<0, 8, u32> const_buffer_enable_mask;
             BitField<29, 2, u32> cache_layout;
-        } memory_config;
+        };
 
         INSERT_PADDING_WORDS(0x8);
 
@@ -194,6 +195,14 @@ public:
     /// Write the value to the register identified by method.
     void CallMethod(const GPU::MethodCall& method_call);
 
+    Tegra::Texture::FullTextureInfo GetTexture(std::size_t offset) const;
+
+    /// Given a Texture Handle, returns the TSC and TIC entries.
+    Texture::FullTextureInfo GetTextureInfo(const Texture::TextureHandle tex_handle,
+                                            std::size_t offset) const;
+
+    u32 AccessConstBuffer32(u64 const_buffer, u64 offset) const;
+
 private:
     Core::System& system;
     VideoCore::RasterizerInterface& rasterizer;
@@ -201,6 +210,12 @@ private:
     Upload::State upload_state;
 
     void ProcessLaunch();
+
+    /// Retrieves information about a specific TIC entry from the TIC buffer.
+    Texture::TICEntry GetTICEntry(u32 tic_index) const;
+
+    /// Retrieves information about a specific TSC entry from the TSC buffer.
+    Texture::TSCEntry GetTSCEntry(u32 tsc_index) const;
 };
 
 #define ASSERT_REG_POSITION(field_name, position)                                                  \
@@ -218,12 +233,12 @@ ASSERT_REG_POSITION(launch, 0xAF);
 ASSERT_REG_POSITION(tsc, 0x557);
 ASSERT_REG_POSITION(tic, 0x55D);
 ASSERT_REG_POSITION(code_loc, 0x582);
-ASSERT_REG_POSITION(texture_const_buffer_index, 0x982);
+ASSERT_REG_POSITION(tex_cb_index, 0x982);
 ASSERT_LAUNCH_PARAM_POSITION(program_start, 0x8);
 ASSERT_LAUNCH_PARAM_POSITION(grid_dim_x, 0xC);
 ASSERT_LAUNCH_PARAM_POSITION(shared_alloc, 0x11);
 ASSERT_LAUNCH_PARAM_POSITION(block_dim_x, 0x12);
-ASSERT_LAUNCH_PARAM_POSITION(memory_config, 0x14);
+ASSERT_LAUNCH_PARAM_POSITION(const_buffer_enable_mask, 0x14);
 ASSERT_LAUNCH_PARAM_POSITION(const_buffer_config, 0x1D);
 
 #undef ASSERT_REG_POSITION
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 0f09022590..5375ab9e06 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -801,6 +801,8 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
     }
 
     auto kernel = shader_cache.GetComputeKernel(code_addr);
+    SetupComputeImages(kernel);
+
     const auto [program, next_bindings] = kernel->GetProgramHandle({});
     state.draw.shader_program = program;
     state.draw.program_pipeline = 0;
@@ -922,7 +924,7 @@ void RasterizerOpenGL::SetupComputeConstBuffers(const Shader& kernel) {
     const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
     for (const auto& entry : kernel->GetShaderEntries().const_buffers) {
         const auto& config = launch_desc.const_buffer_config[entry.GetIndex()];
-        const std::bitset<8> mask = launch_desc.memory_config.const_buffer_enable_mask.Value();
+        const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value();
         Tegra::Engines::ConstBufferInfo buffer;
         buffer.address = config.Address();
         buffer.size = config.size;
@@ -1038,6 +1040,24 @@ bool RasterizerOpenGL::SetupTexture(const Shader& shader, u32 binding,
     return false;
 }
 
+void RasterizerOpenGL::SetupComputeImages(const Shader& shader) {
+    const auto& compute = system.GPU().KeplerCompute();
+    const auto& entries = shader->GetShaderEntries().images;
+    for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
+        const auto& entry = entries[bindpoint];
+        const auto texture = [&]() {
+            if (!entry.IsBindless()) {
+                return compute.GetTexture(entry.GetOffset());
+            }
+            const auto cbuf = entry.GetBindlessCBuf();
+            Tegra::Texture::TextureHandle tex_handle;
+            tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second);
+            return compute.GetTextureInfo(tex_handle, entry.GetOffset());
+        }();
+        UNIMPLEMENTED();
+    }
+}
+
 void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
     const auto& regs = system.GPU().Maxwell3D().regs;
     const bool geometry_shaders_enabled =
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 23ab7aff06..6fa1b7ec46 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -146,6 +146,8 @@ private:
                       const Tegra::Texture::FullTextureInfo& texture,
                       const GLShader::SamplerEntry& entry);
 
+    void SetupComputeImages(const Shader& shader);
+
     /// Syncs the viewport and depth range to match the guest state
     void SyncViewport(OpenGLState& current_state);
 
diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h
index 5db9313c48..0397f4c6e0 100644
--- a/src/video_core/shader/node.h
+++ b/src/video_core/shader/node.h
@@ -303,6 +303,10 @@ public:
         return is_bindless;
     }
 
+    std::pair<u32, u32> GetBindlessCBuf() const {
+        return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)};
+    }
+
     bool operator<(const Image& rhs) const {
         return std::tie(offset, index, type, is_bindless) <
                std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless);

From 2424eefad20b018bed72a0427cdeeabb08bea7b2 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Thu, 11 Jul 2019 21:59:59 -0300
Subject: [PATCH 03/11] texture_cache: Pass TIC to texture cache

---
 .../renderer_opengl/gl_rasterizer.cpp         |  2 +-
 .../texture_cache/surface_params.cpp          | 37 +++++++++----------
 src/video_core/texture_cache/surface_params.h |  5 +--
 src/video_core/texture_cache/texture_cache.h  |  8 ++--
 4 files changed, 25 insertions(+), 27 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 5375ab9e06..8a59b86e38 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -1022,7 +1022,7 @@ bool RasterizerOpenGL::SetupTexture(const Shader& shader, u32 binding,
     auto& unit{state.texture_units[binding]};
     unit.sampler = sampler_cache.GetSampler(texture.tsc);
 
-    const auto view = texture_cache.GetTextureSurface(texture, entry);
+    const auto view = texture_cache.GetImageSurface(texture.tic, entry);
     if (!view) {
         // Can occur when texture addr is null or its memory is unmapped/invalid
         unit.texture = 0;
diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp
index fd54724513..2f8bd399cf 100644
--- a/src/video_core/texture_cache/surface_params.cpp
+++ b/src/video_core/texture_cache/surface_params.cpp
@@ -61,18 +61,17 @@ constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) {
 }
 } // Anonymous namespace
 
-SurfaceParams SurfaceParams::CreateForTexture(Core::System& system,
-                                              const Tegra::Texture::FullTextureInfo& config,
-                                              const VideoCommon::Shader::Sampler& entry) {
+SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic,
+                                            const VideoCommon::Shader::Sampler& entry) {
     SurfaceParams params;
-    params.is_tiled = config.tic.IsTiled();
-    params.srgb_conversion = config.tic.IsSrgbConversionEnabled();
-    params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0,
-    params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0,
-    params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0,
-    params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1;
-    params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(),
-                                                       params.srgb_conversion);
+    params.is_tiled = tic.IsTiled();
+    params.srgb_conversion = tic.IsSrgbConversionEnabled();
+    params.block_width = params.is_tiled ? tic.BlockWidth() : 0,
+    params.block_height = params.is_tiled ? tic.BlockHeight() : 0,
+    params.block_depth = params.is_tiled ? tic.BlockDepth() : 0,
+    params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1;
+    params.pixel_format =
+        PixelFormatFromTextureFormat(tic.format, tic.r_type.Value(), params.srgb_conversion);
     params.type = GetFormatType(params.pixel_format);
     if (entry.IsShadow() && params.type == SurfaceType::ColorTexture) {
         switch (params.pixel_format) {
@@ -92,25 +91,25 @@ SurfaceParams SurfaceParams::CreateForTexture(Core::System& system,
         }
         params.type = GetFormatType(params.pixel_format);
     }
-    params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
+    params.component_type = ComponentTypeFromTexture(tic.r_type.Value());
     params.type = GetFormatType(params.pixel_format);
     // TODO: on 1DBuffer we should use the tic info.
-    if (!config.tic.IsBuffer()) {
+    if (!tic.IsBuffer()) {
         params.target = TextureType2SurfaceTarget(entry.GetType(), entry.IsArray());
-        params.width = config.tic.Width();
-        params.height = config.tic.Height();
-        params.depth = config.tic.Depth();
-        params.pitch = params.is_tiled ? 0 : config.tic.Pitch();
+        params.width = tic.Width();
+        params.height = tic.Height();
+        params.depth = tic.Depth();
+        params.pitch = params.is_tiled ? 0 : tic.Pitch();
         if (params.target == SurfaceTarget::TextureCubemap ||
             params.target == SurfaceTarget::TextureCubeArray) {
             params.depth *= 6;
         }
-        params.num_levels = config.tic.max_mip_level + 1;
+        params.num_levels = tic.max_mip_level + 1;
         params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap());
         params.is_layered = params.IsLayered();
     } else {
         params.target = SurfaceTarget::TextureBuffer;
-        params.width = config.tic.Width();
+        params.width = tic.Width();
         params.pitch = params.width * params.GetBytesPerPixel();
         params.height = 1;
         params.depth = 1;
diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h
index e7ef66ee23..ee2efa5941 100644
--- a/src/video_core/texture_cache/surface_params.h
+++ b/src/video_core/texture_cache/surface_params.h
@@ -23,9 +23,8 @@ using VideoCore::Surface::SurfaceCompression;
 class SurfaceParams {
 public:
     /// Creates SurfaceCachedParams from a texture configuration.
-    static SurfaceParams CreateForTexture(Core::System& system,
-                                          const Tegra::Texture::FullTextureInfo& config,
-                                          const VideoCommon::Shader::Sampler& entry);
+    static SurfaceParams CreateForImage(const Tegra::Texture::TICEntry& tic,
+                                        const VideoCommon::Shader::Sampler& entry);
 
     /// Creates SurfaceCachedParams for a depth buffer configuration.
     static SurfaceParams CreateForDepthBuffer(
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 2ec0203d13..623cce0685 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -89,14 +89,14 @@ public:
         }
     }
 
-    TView GetTextureSurface(const Tegra::Texture::FullTextureInfo& config,
-                            const VideoCommon::Shader::Sampler& entry) {
+    TView GetImageSurface(const Tegra::Texture::TICEntry& tic,
+                          const VideoCommon::Shader::Sampler& entry) {
         std::lock_guard lock{mutex};
-        const auto gpu_addr{config.tic.Address()};
+        const auto gpu_addr{tic.Address()};
         if (!gpu_addr) {
             return {};
         }
-        const auto params{SurfaceParams::CreateForTexture(system, config, entry)};
+        const auto params{SurfaceParams::CreateForImage(tic, entry)};
         const auto [surface, view] = GetSurface(gpu_addr, params, true, false);
         if (guard_samplers) {
             sampled_textures.push_back(surface);

From 5edf24b51025fef4d78e5f9d4038267e472b2f55 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Fri, 12 Jul 2019 02:00:04 -0300
Subject: [PATCH 04/11] gl_state: Add support for glBindImageTextures

---
 src/video_core/renderer_opengl/gl_state.cpp | 21 +++++++++++++++++++++
 src/video_core/renderer_opengl/gl_state.h   |  3 +++
 2 files changed, 24 insertions(+)

diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index f4777d0b07..a38f881826 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -545,6 +545,26 @@ void OpenGLState::ApplySamplers() const {
     }
 }
 
+void OpenGLState::ApplyImages() const {
+    bool has_delta{};
+    std::size_t first{};
+    std::size_t last{};
+    for (std::size_t i = 0; i < std::size(images); ++i) {
+        if (!UpdateValue(cur_state.images[i], images[i])) {
+            continue;
+        }
+        if (!has_delta) {
+            first = i;
+            has_delta = true;
+        }
+        last = i;
+    }
+    if (has_delta) {
+        glBindImageTextures(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1),
+                            images.data() + first);
+    }
+}
+
 void OpenGLState::Apply() {
     MICROPROFILE_SCOPE(OpenGL_State);
     ApplyFramebufferState();
@@ -576,6 +596,7 @@ void OpenGLState::Apply() {
     ApplyLogicOp();
     ApplyTextures();
     ApplySamplers();
+    ApplyImages();
     if (dirty.polygon_offset) {
         ApplyPolygonOffset();
         dirty.polygon_offset = false;
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index fdf9a8a12b..9748d60e2b 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -134,6 +134,8 @@ public:
     };
     std::array<TextureUnit, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> texture_units;
 
+    std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumImages> images{};
+
     struct {
         GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING
         GLuint draw_framebuffer; // GL_DRAW_FRAMEBUFFER_BINDING
@@ -220,6 +222,7 @@ public:
     void ApplyLogicOp() const;
     void ApplyTextures() const;
     void ApplySamplers() const;
+    void ApplyImages() const;
     void ApplyDepthClamp() const;
     void ApplyPolygonOffset() const;
     void ApplyAlphaTest() const;

From 6170337001cc11deabd57023a548c44de4242e9d Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Fri, 12 Jul 2019 02:01:27 -0300
Subject: [PATCH 05/11] gl_rasterizer: Implement image bindings

---
 src/video_core/engines/maxwell_3d.h           |   1 +
 .../renderer_opengl/gl_rasterizer.cpp         |   9 +-
 .../texture_cache/surface_params.cpp          | 105 +++++++++++++-----
 src/video_core/texture_cache/surface_params.h |   6 +-
 src/video_core/texture_cache/texture_cache.h  |  17 ++-
 5 files changed, 106 insertions(+), 32 deletions(-)

diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 0184342a07..3b3c82f411 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -62,6 +62,7 @@ public:
         static constexpr std::size_t NumVertexAttributes = 32;
         static constexpr std::size_t NumVaryings = 31;
         static constexpr std::size_t NumTextureSamplers = 32;
+        static constexpr std::size_t NumImages = 8; // TODO(Rodrigo): Investigate this number
         static constexpr std::size_t NumClipDistances = 8;
         static constexpr std::size_t MaxShaderProgram = 6;
         static constexpr std::size_t MaxShaderStage = 5;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 8a59b86e38..6636b3c74e 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -1022,7 +1022,7 @@ bool RasterizerOpenGL::SetupTexture(const Shader& shader, u32 binding,
     auto& unit{state.texture_units[binding]};
     unit.sampler = sampler_cache.GetSampler(texture.tsc);
 
-    const auto view = texture_cache.GetImageSurface(texture.tic, entry);
+    const auto view = texture_cache.GetTextureSurface(texture.tic, entry);
     if (!view) {
         // Can occur when texture addr is null or its memory is unmapped/invalid
         unit.texture = 0;
@@ -1054,7 +1054,12 @@ void RasterizerOpenGL::SetupComputeImages(const Shader& shader) {
             tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second);
             return compute.GetTextureInfo(tex_handle, entry.GetOffset());
         }();
-        UNIMPLEMENTED();
+        const auto view = texture_cache.GetImageSurface(texture.tic, entry);
+        if (!view) {
+            state.images[bindpoint] = 0;
+            continue;
+        }
+        state.images[bindpoint] = view->GetTexture();
     }
 }
 
diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp
index 2f8bd399cf..1e4d3fb795 100644
--- a/src/video_core/texture_cache/surface_params.cpp
+++ b/src/video_core/texture_cache/surface_params.cpp
@@ -24,45 +24,53 @@ using VideoCore::Surface::SurfaceTarget;
 using VideoCore::Surface::SurfaceTargetFromTextureType;
 using VideoCore::Surface::SurfaceType;
 
-SurfaceTarget TextureType2SurfaceTarget(Tegra::Shader::TextureType type, bool is_array) {
+namespace {
+
+SurfaceTarget TextureTypeToSurfaceTarget(Tegra::Shader::TextureType type, bool is_array) {
     switch (type) {
-    case Tegra::Shader::TextureType::Texture1D: {
-        if (is_array)
-            return SurfaceTarget::Texture1DArray;
-        else
-            return SurfaceTarget::Texture1D;
-    }
-    case Tegra::Shader::TextureType::Texture2D: {
-        if (is_array)
-            return SurfaceTarget::Texture2DArray;
-        else
-            return SurfaceTarget::Texture2D;
-    }
-    case Tegra::Shader::TextureType::Texture3D: {
+    case Tegra::Shader::TextureType::Texture1D:
+        return is_array ? SurfaceTarget::Texture1DArray : SurfaceTarget::Texture1D;
+    case Tegra::Shader::TextureType::Texture2D:
+        return is_array ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D;
+    case Tegra::Shader::TextureType::Texture3D:
         ASSERT(!is_array);
         return SurfaceTarget::Texture3D;
-    }
-    case Tegra::Shader::TextureType::TextureCube: {
-        if (is_array)
-            return SurfaceTarget::TextureCubeArray;
-        else
-            return SurfaceTarget::TextureCubemap;
-    }
-    default: {
+    case Tegra::Shader::TextureType::TextureCube:
+        return is_array ? SurfaceTarget::TextureCubeArray : SurfaceTarget::TextureCubemap;
+    default:
         UNREACHABLE();
         return SurfaceTarget::Texture2D;
     }
+}
+
+SurfaceTarget ImageTypeToSurfaceTarget(Tegra::Shader::ImageType type) {
+    switch (type) {
+    case Tegra::Shader::ImageType::Texture1D:
+        return SurfaceTarget::Texture1D;
+    case Tegra::Shader::ImageType::TextureBuffer:
+        return SurfaceTarget::TextureBuffer;
+    case Tegra::Shader::ImageType::Texture1DArray:
+        return SurfaceTarget::Texture1DArray;
+    case Tegra::Shader::ImageType::Texture2D:
+        return SurfaceTarget::Texture2D;
+    case Tegra::Shader::ImageType::Texture2DArray:
+        return SurfaceTarget::Texture2DArray;
+    case Tegra::Shader::ImageType::Texture3D:
+        return SurfaceTarget::Texture3D;
+    default:
+        UNREACHABLE();
+        return SurfaceTarget::Texture2D;
     }
 }
 
-namespace {
 constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) {
     return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile);
 }
+
 } // Anonymous namespace
 
-SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic,
-                                            const VideoCommon::Shader::Sampler& entry) {
+SurfaceParams SurfaceParams::CreateForTexture(const Tegra::Texture::TICEntry& tic,
+                                              const VideoCommon::Shader::Sampler& entry) {
     SurfaceParams params;
     params.is_tiled = tic.IsTiled();
     params.srgb_conversion = tic.IsSrgbConversionEnabled();
@@ -94,8 +102,17 @@ SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic,
     params.component_type = ComponentTypeFromTexture(tic.r_type.Value());
     params.type = GetFormatType(params.pixel_format);
     // TODO: on 1DBuffer we should use the tic info.
-    if (!tic.IsBuffer()) {
-        params.target = TextureType2SurfaceTarget(entry.GetType(), entry.IsArray());
+    if (tic.IsBuffer()) {
+        params.target = SurfaceTarget::TextureBuffer;
+        params.width = tic.Width();
+        params.pitch = params.width * params.GetBytesPerPixel();
+        params.height = 1;
+        params.depth = 1;
+        params.num_levels = 1;
+        params.emulated_levels = 1;
+        params.is_layered = false;
+    } else {
+        params.target = TextureTypeToSurfaceTarget(entry.GetType(), entry.IsArray());
         params.width = tic.Width();
         params.height = tic.Height();
         params.depth = tic.Depth();
@@ -107,7 +124,27 @@ SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic,
         params.num_levels = tic.max_mip_level + 1;
         params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap());
         params.is_layered = params.IsLayered();
-    } else {
+    }
+    return params;
+}
+
+SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic,
+                                            const VideoCommon::Shader::Image& entry) {
+    SurfaceParams params;
+    params.is_tiled = tic.IsTiled();
+    params.srgb_conversion = tic.IsSrgbConversionEnabled();
+    params.block_width = params.is_tiled ? tic.BlockWidth() : 0,
+    params.block_height = params.is_tiled ? tic.BlockHeight() : 0,
+    params.block_depth = params.is_tiled ? tic.BlockDepth() : 0,
+    params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1;
+    params.pixel_format =
+        PixelFormatFromTextureFormat(tic.format, tic.r_type.Value(), params.srgb_conversion);
+    params.type = GetFormatType(params.pixel_format);
+    params.component_type = ComponentTypeFromTexture(tic.r_type.Value());
+    params.type = GetFormatType(params.pixel_format);
+    params.target = ImageTypeToSurfaceTarget(entry.GetType());
+    // TODO: on 1DBuffer we should use the tic info.
+    if (tic.IsBuffer()) {
         params.target = SurfaceTarget::TextureBuffer;
         params.width = tic.Width();
         params.pitch = params.width * params.GetBytesPerPixel();
@@ -116,6 +153,18 @@ SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic,
         params.num_levels = 1;
         params.emulated_levels = 1;
         params.is_layered = false;
+    } else {
+        params.width = tic.Width();
+        params.height = tic.Height();
+        params.depth = tic.Depth();
+        params.pitch = params.is_tiled ? 0 : tic.Pitch();
+        if (params.target == SurfaceTarget::TextureCubemap ||
+            params.target == SurfaceTarget::TextureCubeArray) {
+            params.depth *= 6;
+        }
+        params.num_levels = tic.max_mip_level + 1;
+        params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap());
+        params.is_layered = params.IsLayered();
     }
     return params;
 }
diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h
index ee2efa5941..1011a4d8e7 100644
--- a/src/video_core/texture_cache/surface_params.h
+++ b/src/video_core/texture_cache/surface_params.h
@@ -23,8 +23,12 @@ using VideoCore::Surface::SurfaceCompression;
 class SurfaceParams {
 public:
     /// Creates SurfaceCachedParams from a texture configuration.
+    static SurfaceParams CreateForTexture(const Tegra::Texture::TICEntry& tic,
+                                          const VideoCommon::Shader::Sampler& entry);
+
+    /// Creates SurfaceCachedParams from an image configuration.
     static SurfaceParams CreateForImage(const Tegra::Texture::TICEntry& tic,
-                                        const VideoCommon::Shader::Sampler& entry);
+                                        const VideoCommon::Shader::Image& entry);
 
     /// Creates SurfaceCachedParams for a depth buffer configuration.
     static SurfaceParams CreateForDepthBuffer(
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 623cce0685..877c6635d6 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -89,8 +89,23 @@ public:
         }
     }
 
+    TView GetTextureSurface(const Tegra::Texture::TICEntry& tic,
+                            const VideoCommon::Shader::Sampler& entry) {
+        std::lock_guard lock{mutex};
+        const auto gpu_addr{tic.Address()};
+        if (!gpu_addr) {
+            return {};
+        }
+        const auto params{SurfaceParams::CreateForTexture(tic, entry)};
+        const auto [surface, view] = GetSurface(gpu_addr, params, true, false);
+        if (guard_samplers) {
+            sampled_textures.push_back(surface);
+        }
+        return view;
+    }
+
     TView GetImageSurface(const Tegra::Texture::TICEntry& tic,
-                          const VideoCommon::Shader::Sampler& entry) {
+                          const VideoCommon::Shader::Image& entry) {
         std::lock_guard lock{mutex};
         const auto gpu_addr{tic.Address()};
         if (!gpu_addr) {

From 04cdecb7a114471fec50deab86bcd160ec85feb4 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Thu, 11 Jul 2019 01:32:12 -0300
Subject: [PATCH 06/11] gl_state: Split textures and samplers into two arrays

---
 .../renderer_opengl/gl_rasterizer.cpp         |  9 +-
 src/video_core/renderer_opengl/gl_state.cpp   | 99 ++++++-------------
 src/video_core/renderer_opengl/gl_state.h     | 18 +---
 .../renderer_opengl/renderer_opengl.cpp       |  4 +-
 4 files changed, 39 insertions(+), 91 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 6636b3c74e..818e71754a 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -991,7 +991,7 @@ TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stag
     const auto& maxwell3d = gpu.Maxwell3D();
     const auto& entries = shader->GetShaderEntries().samplers;
 
-    ASSERT_MSG(base_bindings.sampler + entries.size() <= std::size(state.texture_units),
+    ASSERT_MSG(base_bindings.sampler + entries.size() <= std::size(state.textures),
                "Exceeded the number of active textures.");
 
     TextureBufferUsage texture_buffer_usage{0};
@@ -1019,16 +1019,15 @@ TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stag
 bool RasterizerOpenGL::SetupTexture(const Shader& shader, u32 binding,
                                     const Tegra::Texture::FullTextureInfo& texture,
                                     const GLShader::SamplerEntry& entry) {
-    auto& unit{state.texture_units[binding]};
-    unit.sampler = sampler_cache.GetSampler(texture.tsc);
+    state.samplers[binding] = sampler_cache.GetSampler(texture.tsc);
 
     const auto view = texture_cache.GetTextureSurface(texture.tic, entry);
     if (!view) {
         // Can occur when texture addr is null or its memory is unmapped/invalid
-        unit.texture = 0;
+        state.textures[binding] = 0;
         return false;
     }
-    unit.texture = view->GetTexture();
+    state.textures[binding] = view->GetTexture();
 
     if (view->GetSurfaceParams().IsBuffer()) {
         return true;
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index a38f881826..6eabf4fac3 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -34,6 +34,25 @@ bool UpdateTie(T1 current_value, const T2 new_value) {
     return changed;
 }
 
+template <typename T>
+std::optional<std::pair<GLuint, GLsizei>> UpdateArray(T& current_values, const T& new_values) {
+    std::optional<std::size_t> first;
+    std::size_t last;
+    for (std::size_t i = 0; i < std::size(current_values); ++i) {
+        if (!UpdateValue(current_values[i], new_values[i])) {
+            continue;
+        }
+        if (!first) {
+            first = i;
+        }
+        last = i;
+    }
+    if (!first) {
+        return std::nullopt;
+    }
+    return std::make_pair(static_cast<GLuint>(*first), static_cast<GLsizei>(last - *first + 1));
+}
+
 void Enable(GLenum cap, bool enable) {
     if (enable) {
         glEnable(cap);
@@ -134,10 +153,6 @@ OpenGLState::OpenGLState() {
     logic_op.enabled = false;
     logic_op.operation = GL_COPY;
 
-    for (auto& texture_unit : texture_units) {
-        texture_unit.Reset();
-    }
-
     draw.read_framebuffer = 0;
     draw.draw_framebuffer = 0;
     draw.vertex_array = 0;
@@ -496,72 +511,20 @@ void OpenGLState::ApplyAlphaTest() const {
 }
 
 void OpenGLState::ApplyTextures() const {
-    bool has_delta{};
-    std::size_t first{};
-    std::size_t last{};
-    std::array<GLuint, Maxwell::NumTextureSamplers> textures;
-
-    for (std::size_t i = 0; i < std::size(texture_units); ++i) {
-        const auto& texture_unit = texture_units[i];
-        auto& cur_state_texture_unit = cur_state.texture_units[i];
-        textures[i] = texture_unit.texture;
-        if (cur_state_texture_unit.texture == textures[i]) {
-            continue;
-        }
-        cur_state_texture_unit.texture = textures[i];
-        if (!has_delta) {
-            first = i;
-            has_delta = true;
-        }
-        last = i;
-    }
-    if (has_delta) {
-        glBindTextures(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1),
-                       textures.data() + first);
+    if (const auto update = UpdateArray(cur_state.textures, textures)) {
+        glBindTextures(update->first, update->second, textures.data() + update->first);
     }
 }
 
 void OpenGLState::ApplySamplers() const {
-    bool has_delta{};
-    std::size_t first{};
-    std::size_t last{};
-    std::array<GLuint, Maxwell::NumTextureSamplers> samplers;
-
-    for (std::size_t i = 0; i < std::size(samplers); ++i) {
-        samplers[i] = texture_units[i].sampler;
-        if (cur_state.texture_units[i].sampler == texture_units[i].sampler) {
-            continue;
-        }
-        cur_state.texture_units[i].sampler = texture_units[i].sampler;
-        if (!has_delta) {
-            first = i;
-            has_delta = true;
-        }
-        last = i;
-    }
-    if (has_delta) {
-        glBindSamplers(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1),
-                       samplers.data() + first);
+    if (const auto update = UpdateArray(cur_state.samplers, samplers)) {
+        glBindSamplers(update->first, update->second, samplers.data() + update->first);
     }
 }
 
 void OpenGLState::ApplyImages() const {
-    bool has_delta{};
-    std::size_t first{};
-    std::size_t last{};
-    for (std::size_t i = 0; i < std::size(images); ++i) {
-        if (!UpdateValue(cur_state.images[i], images[i])) {
-            continue;
-        }
-        if (!has_delta) {
-            first = i;
-            has_delta = true;
-        }
-        last = i;
-    }
-    if (has_delta) {
-        glBindImageTextures(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1),
-                            images.data() + first);
+    if (const auto update = UpdateArray(cur_state.images, images)) {
+        glBindImageTextures(update->first, update->second, images.data() + update->first);
     }
 }
 
@@ -627,18 +590,18 @@ void OpenGLState::EmulateViewportWithScissor() {
 }
 
 OpenGLState& OpenGLState::UnbindTexture(GLuint handle) {
-    for (auto& unit : texture_units) {
-        if (unit.texture == handle) {
-            unit.Unbind();
+    for (auto& texture : textures) {
+        if (texture == handle) {
+            texture = 0;
         }
     }
     return *this;
 }
 
 OpenGLState& OpenGLState::ResetSampler(GLuint handle) {
-    for (auto& unit : texture_units) {
-        if (unit.sampler == handle) {
-            unit.sampler = 0;
+    for (auto& sampler : samplers) {
+        if (sampler == handle) {
+            sampler = 0;
         }
     }
     return *this;
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index 9748d60e2b..949b13051a 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -118,22 +118,8 @@ public:
         GLenum operation;
     } logic_op;
 
-    // 3 texture units - one for each that is used in PICA fragment shader emulation
-    struct TextureUnit {
-        GLuint texture; // GL_TEXTURE_BINDING_2D
-        GLuint sampler; // GL_SAMPLER_BINDING
-
-        void Unbind() {
-            texture = 0;
-        }
-
-        void Reset() {
-            Unbind();
-            sampler = 0;
-        }
-    };
-    std::array<TextureUnit, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> texture_units;
-
+    std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> textures{};
+    std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> samplers{};
     std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumImages> images{};
 
     struct {
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index af9684839b..8391781521 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -342,7 +342,7 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
         ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v),
     }};
 
-    state.texture_units[0].texture = screen_info.display_texture;
+    state.textures[0] = screen_info.display_texture;
     // Workaround brigthness problems in SMO by enabling sRGB in the final output
     // if it has been used in the frame. Needed because of this bug in QT: QTBUG-50987
     state.framebuffer_srgb.enabled = OpenGLState::GetsRGBUsed();
@@ -352,7 +352,7 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
     // Restore default state
     state.framebuffer_srgb.enabled = false;
-    state.texture_units[0].texture = 0;
+    state.textures[0] = 0;
     state.AllDirty();
     state.Apply();
     // Clear sRGB state for the next frame

From 954fc02fdd639a3e83ade8776d23b50559cd2f73 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Fri, 12 Jul 2019 02:17:18 -0300
Subject: [PATCH 07/11] gl_rasterizer: Minor code changes

---
 .../renderer_opengl/gl_rasterizer.cpp         | 43 +++++++++++--------
 .../renderer_opengl/gl_rasterizer.h           |  8 +++-
 2 files changed, 31 insertions(+), 20 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 818e71754a..5454bacb09 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -998,17 +998,17 @@ TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stag
 
     for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
         const auto& entry = entries[bindpoint];
-        Tegra::Texture::FullTextureInfo texture;
-        if (entry.IsBindless()) {
+        const auto texture = [&]() {
+            if (!entry.IsBindless()) {
+                return maxwell3d.GetStageTexture(stage, entry.GetOffset());
+            }
             const auto cbuf = entry.GetBindlessCBuf();
             Tegra::Texture::TextureHandle tex_handle;
             tex_handle.raw = maxwell3d.AccessConstBuffer32(stage, cbuf.first, cbuf.second);
-            texture = maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset());
-        } else {
-            texture = maxwell3d.GetStageTexture(stage, entry.GetOffset());
-        }
+            return maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset());
+        }();
 
-        if (SetupTexture(shader, base_bindings.sampler + bindpoint, texture, entry)) {
+        if (SetupTexture(base_bindings.sampler + bindpoint, texture, entry)) {
             texture_buffer_usage.set(bindpoint);
         }
     }
@@ -1016,8 +1016,7 @@ TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stag
     return texture_buffer_usage;
 }
 
-bool RasterizerOpenGL::SetupTexture(const Shader& shader, u32 binding,
-                                    const Tegra::Texture::FullTextureInfo& texture,
+bool RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture,
                                     const GLShader::SamplerEntry& entry) {
     state.samplers[binding] = sampler_cache.GetSampler(texture.tsc);
 
@@ -1044,24 +1043,32 @@ void RasterizerOpenGL::SetupComputeImages(const Shader& shader) {
     const auto& entries = shader->GetShaderEntries().images;
     for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
         const auto& entry = entries[bindpoint];
-        const auto texture = [&]() {
+        const auto tic = [&]() {
             if (!entry.IsBindless()) {
-                return compute.GetTexture(entry.GetOffset());
+                return compute.GetTexture(entry.GetOffset()).tic;
             }
             const auto cbuf = entry.GetBindlessCBuf();
             Tegra::Texture::TextureHandle tex_handle;
             tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second);
-            return compute.GetTextureInfo(tex_handle, entry.GetOffset());
+            return compute.GetTextureInfo(tex_handle, entry.GetOffset()).tic;
         }();
-        const auto view = texture_cache.GetImageSurface(texture.tic, entry);
-        if (!view) {
-            state.images[bindpoint] = 0;
-            continue;
-        }
-        state.images[bindpoint] = view->GetTexture();
+        SetupImage(bindpoint, tic, entry);
     }
 }
 
+void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic,
+                                  const GLShader::ImageEntry& entry) {
+    const auto view = texture_cache.GetImageSurface(tic, entry);
+    if (!view) {
+        state.images[binding] = 0;
+        return;
+    }
+    if (!tic.IsBuffer()) {
+        view->ApplySwizzle(tic.x_source, tic.y_source, tic.z_source, tic.w_source);
+    }
+    state.images[binding] = view->GetTexture();
+}
+
 void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
     const auto& regs = system.GPU().Maxwell3D().regs;
     const bool geometry_shaders_enabled =
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 6fa1b7ec46..35265b4a27 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -142,12 +142,16 @@ private:
                                          const Shader& shader, BaseBindings base_bindings);
 
     /// Configures a texture. Returns true when the texture is a texture buffer.
-    bool SetupTexture(const Shader& shader, u32 binding,
-                      const Tegra::Texture::FullTextureInfo& texture,
+    bool SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture,
                       const GLShader::SamplerEntry& entry);
 
+    /// Configures images in a compute shader.
     void SetupComputeImages(const Shader& shader);
 
+    /// Configures an image.
+    void SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic,
+                    const GLShader::ImageEntry& entry);
+
     /// Syncs the viewport and depth range to match the guest state
     void SyncViewport(OpenGLState& current_state);
 

From 80ec2feee8731d661bc999c9f057159ec6286043 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Wed, 17 Jul 2019 21:50:21 -0300
Subject: [PATCH 08/11] gl_rasterizer: Add samplers to compute dispatches

---
 .../renderer_opengl/gl_rasterizer.cpp         | 36 +++++++++++++++++--
 .../renderer_opengl/gl_rasterizer.h           |  3 ++
 2 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 5454bacb09..fd4753af74 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -801,9 +801,11 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
     }
 
     auto kernel = shader_cache.GetComputeKernel(code_addr);
+    ProgramVariant variant;
+    variant.texture_buffer_usage = SetupComputeTextures(kernel);
     SetupComputeImages(kernel);
 
-    const auto [program, next_bindings] = kernel->GetProgramHandle({});
+    const auto [program, next_bindings] = kernel->GetProgramHandle(variant);
     state.draw.shader_program = program;
     state.draw.program_pipeline = 0;
 
@@ -818,8 +820,6 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
     SetupComputeConstBuffers(kernel);
     SetupComputeGlobalMemory(kernel);
 
-    // TODO(Rodrigo): Bind images and samplers
-
     buffer_cache.Unmap();
 
     bind_ubo_pushbuffer.Bind();
@@ -1016,6 +1016,36 @@ TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stag
     return texture_buffer_usage;
 }
 
+TextureBufferUsage RasterizerOpenGL::SetupComputeTextures(const Shader& kernel) {
+    MICROPROFILE_SCOPE(OpenGL_Texture);
+    const auto& compute = system.GPU().KeplerCompute();
+    const auto& entries = kernel->GetShaderEntries().samplers;
+
+    ASSERT_MSG(entries.size() <= std::size(state.textures),
+               "Exceeded the number of active textures.");
+
+    TextureBufferUsage texture_buffer_usage{0};
+
+    for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
+        const auto& entry = entries[bindpoint];
+        const auto texture = [&]() {
+            if (!entry.IsBindless()) {
+                return compute.GetTexture(entry.GetOffset());
+            }
+            const auto cbuf = entry.GetBindlessCBuf();
+            Tegra::Texture::TextureHandle tex_handle;
+            tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second);
+            return compute.GetTextureInfo(tex_handle, entry.GetOffset());
+        }();
+
+        if (SetupTexture(bindpoint, texture, entry)) {
+            texture_buffer_usage.set(bindpoint);
+        }
+    }
+
+    return texture_buffer_usage;
+}
+
 bool RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture,
                                     const GLShader::SamplerEntry& entry) {
     state.samplers[binding] = sampler_cache.GetSampler(texture.tsc);
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 35265b4a27..eada752e08 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -141,6 +141,9 @@ private:
     TextureBufferUsage SetupDrawTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
                                          const Shader& shader, BaseBindings base_bindings);
 
+    /// Configures the textures used in a compute shader. Returns texture buffer usage.
+    TextureBufferUsage SetupComputeTextures(const Shader& kernel);
+
     /// Configures a texture. Returns true when the texture is a texture buffer.
     bool SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture,
                       const GLShader::SamplerEntry& entry);

From 322d0200c80e36cb97b0d0d79f0f3e9eaa2f96af Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Thu, 18 Jul 2019 02:12:01 -0300
Subject: [PATCH 09/11] gl_rasterizer: Apply textures and images state

---
 src/video_core/renderer_opengl/gl_rasterizer.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index fd4753af74..70eece9af1 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -825,6 +825,8 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
     bind_ubo_pushbuffer.Bind();
     bind_ssbo_pushbuffer.Bind();
 
+    state.ApplyTextures();
+    state.ApplyImages();
     state.ApplyShaderProgram();
     state.ApplyProgramPipeline();
 

From 7228e22098dd97ac89b78484a4f3ee855e37f799 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Thu, 5 Sep 2019 23:25:15 -0300
Subject: [PATCH 10/11] texture_cache: Minor changes

---
 src/video_core/texture_cache/surface_base.h   | 12 +++++------
 src/video_core/texture_cache/surface_params.h |  2 --
 src/video_core/texture_cache/surface_view.cpp |  2 +-
 src/video_core/texture_cache/surface_view.h   | 20 +++++++++----------
 4 files changed, 17 insertions(+), 19 deletions(-)

diff --git a/src/video_core/texture_cache/surface_base.h b/src/video_core/texture_cache/surface_base.h
index bcce8d8634..5e497e49f8 100644
--- a/src/video_core/texture_cache/surface_base.h
+++ b/src/video_core/texture_cache/surface_base.h
@@ -195,18 +195,18 @@ public:
 
     virtual void DownloadTexture(std::vector<u8>& staging_buffer) = 0;
 
-    void MarkAsModified(const bool is_modified_, const u64 tick) {
+    void MarkAsModified(bool is_modified_, u64 tick) {
         is_modified = is_modified_ || is_target;
         modification_tick = tick;
     }
 
-    void MarkAsRenderTarget(const bool is_target, const u32 index) {
-        this->is_target = is_target;
-        this->index = index;
+    void MarkAsRenderTarget(bool is_target_, u32 index_) {
+        is_target = is_target_;
+        index = index_;
     }
 
-    void MarkAsPicked(const bool is_picked) {
-        this->is_picked = is_picked;
+    void MarkAsPicked(bool is_picked_) {
+        is_picked = is_picked_;
     }
 
     bool IsModified() const {
diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h
index 1011a4d8e7..c58e7f8a47 100644
--- a/src/video_core/texture_cache/surface_params.h
+++ b/src/video_core/texture_cache/surface_params.h
@@ -4,8 +4,6 @@
 
 #pragma once
 
-#include <map>
-
 #include "common/alignment.h"
 #include "common/bit_util.h"
 #include "common/cityhash.h"
diff --git a/src/video_core/texture_cache/surface_view.cpp b/src/video_core/texture_cache/surface_view.cpp
index 467696a4cf..57a1f5803d 100644
--- a/src/video_core/texture_cache/surface_view.cpp
+++ b/src/video_core/texture_cache/surface_view.cpp
@@ -10,7 +10,7 @@
 namespace VideoCommon {
 
 std::size_t ViewParams::Hash() const {
-    return static_cast<std::size_t>(base_layer) ^ static_cast<std::size_t>(num_layers << 16) ^
+    return static_cast<std::size_t>(base_layer) ^ (static_cast<std::size_t>(num_layers) << 16) ^
            (static_cast<std::size_t>(base_level) << 24) ^
            (static_cast<std::size_t>(num_levels) << 32) ^ (static_cast<std::size_t>(target) << 36);
 }
diff --git a/src/video_core/texture_cache/surface_view.h b/src/video_core/texture_cache/surface_view.h
index 04ca5639bc..b17fd11a9a 100644
--- a/src/video_core/texture_cache/surface_view.h
+++ b/src/video_core/texture_cache/surface_view.h
@@ -13,8 +13,8 @@
 namespace VideoCommon {
 
 struct ViewParams {
-    ViewParams(VideoCore::Surface::SurfaceTarget target, u32 base_layer, u32 num_layers,
-               u32 base_level, u32 num_levels)
+    constexpr explicit ViewParams(VideoCore::Surface::SurfaceTarget target, u32 base_layer,
+                                  u32 num_layers, u32 base_level, u32 num_levels)
         : target{target}, base_layer{base_layer}, num_layers{num_layers}, base_level{base_level},
           num_levels{num_levels} {}
 
@@ -22,12 +22,6 @@ struct ViewParams {
 
     bool operator==(const ViewParams& rhs) const;
 
-    VideoCore::Surface::SurfaceTarget target{};
-    u32 base_layer{};
-    u32 num_layers{};
-    u32 base_level{};
-    u32 num_levels{};
-
     bool IsLayered() const {
         switch (target) {
         case VideoCore::Surface::SurfaceTarget::Texture1DArray:
@@ -39,13 +33,19 @@ struct ViewParams {
             return false;
         }
     }
+
+    VideoCore::Surface::SurfaceTarget target{};
+    u32 base_layer{};
+    u32 num_layers{};
+    u32 base_level{};
+    u32 num_levels{};
 };
 
 class ViewBase {
 public:
-    ViewBase(const ViewParams& params) : params{params} {}
+    constexpr explicit ViewBase(const ViewParams& params) : params{params} {}
 
-    const ViewParams& GetViewParams() const {
+    constexpr const ViewParams& GetViewParams() const {
         return params;
     }
 

From 1f43e5296fcd2debaea672fd9740d2f07223406b Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Thu, 5 Sep 2019 23:26:05 -0300
Subject: [PATCH 11/11] gl_shader_decompiler: Keep track of written images and
 mark them as modified

---
 .../renderer_opengl/gl_rasterizer.cpp         |  3 ++
 .../renderer_opengl/gl_shader_decompiler.cpp  | 18 ++++---
 .../renderer_opengl/gl_shader_disk_cache.cpp  | 15 ++++--
 .../renderer_opengl/gl_texture_cache.h        | 22 ++++----
 src/video_core/shader/decode/image.cpp        | 40 +++++++--------
 src/video_core/shader/node.h                  | 50 ++++++++++++-------
 src/video_core/shader/shader_ir.h             |  8 +--
 7 files changed, 93 insertions(+), 63 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 70eece9af1..4e266cdad2 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -1098,6 +1098,9 @@ void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& t
     if (!tic.IsBuffer()) {
         view->ApplySwizzle(tic.x_source, tic.y_source, tic.z_source, tic.w_source);
     }
+    if (entry.IsWritten()) {
+        view->MarkAsModified(texture_cache.Tick());
+    }
     state.images[binding] = view->GetTexture();
 }
 
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index a5cc1a86f2..6edb2ca383 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -389,11 +389,10 @@ public:
         for (const auto& sampler : ir.GetSamplers()) {
             entries.samplers.emplace_back(sampler);
         }
-        for (const auto& image : ir.GetImages()) {
+        for (const auto& [offset, image] : ir.GetImages()) {
             entries.images.emplace_back(image);
         }
-        for (const auto& gmem_pair : ir.GetGlobalMemory()) {
-            const auto& [base, usage] = gmem_pair;
+        for (const auto& [base, usage] : ir.GetGlobalMemory()) {
             entries.global_memory_entries.emplace_back(base.cbuf_index, base.cbuf_offset,
                                                        usage.is_read, usage.is_written);
         }
@@ -706,7 +705,7 @@ private:
 
     void DeclareImages() {
         const auto& images{ir.GetImages()};
-        for (const auto& image : images) {
+        for (const auto& [offset, image] : images) {
             const std::string image_type = [&]() {
                 switch (image.GetType()) {
                 case Tegra::Shader::ImageType::Texture1D:
@@ -726,9 +725,16 @@ private:
                     return "image1D";
                 }
             }();
-            code.AddLine("layout (binding = IMAGE_BINDING_{}) coherent volatile writeonly uniform "
+            std::string qualifier = "coherent volatile";
+            if (image.IsRead() && !image.IsWritten()) {
+                qualifier += " readonly";
+            } else if (image.IsWritten() && !image.IsRead()) {
+                qualifier += " writeonly";
+            }
+
+            code.AddLine("layout (binding = IMAGE_BINDING_{}) {} uniform "
                          "{} {};",
-                         image.GetIndex(), image_type, GetImage(image));
+                         image.GetIndex(), qualifier, image_type, GetImage(image));
         }
         if (!images.empty()) {
             code.AddNewLine();
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
index 969fe9ced2..5450feedf4 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -341,13 +341,16 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
         u64 index{};
         u32 type{};
         u8 is_bindless{};
+        u8 is_read{};
+        u8 is_written{};
         if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) ||
-            !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless)) {
+            !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) ||
+            !LoadObjectFromPrecompiled(is_read) || !LoadObjectFromPrecompiled(is_written)) {
             return {};
         }
-        entry.entries.images.emplace_back(
-            static_cast<std::size_t>(offset), static_cast<std::size_t>(index),
-            static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0);
+        entry.entries.images.emplace_back(static_cast<u64>(offset), static_cast<std::size_t>(index),
+                                          static_cast<Tegra::Shader::ImageType>(type),
+                                          is_bindless != 0, is_written != 0, is_read != 0);
     }
 
     u32 global_memory_count{};
@@ -429,7 +432,9 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std:
         if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) ||
             !SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) ||
             !SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) ||
-            !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0))) {
+            !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0)) ||
+            !SaveObjectToPrecompiled(static_cast<u8>(image.IsRead() ? 1 : 0)) ||
+            !SaveObjectToPrecompiled(static_cast<u8>(image.IsWritten() ? 1 : 0))) {
             return false;
         }
     }
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 21324488aa..8e13ab38bb 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -78,6 +78,17 @@ public:
     /// Attaches this texture view to the current bound GL_DRAW_FRAMEBUFFER
     void Attach(GLenum attachment, GLenum target) const;
 
+    void ApplySwizzle(Tegra::Texture::SwizzleSource x_source,
+                      Tegra::Texture::SwizzleSource y_source,
+                      Tegra::Texture::SwizzleSource z_source,
+                      Tegra::Texture::SwizzleSource w_source);
+
+    void DecorateViewName(GPUVAddr gpu_addr, std::string prefix);
+
+    void MarkAsModified(u64 tick) {
+        surface.MarkAsModified(true, tick);
+    }
+
     GLuint GetTexture() const {
         if (is_proxy) {
             return surface.GetTexture();
@@ -89,13 +100,6 @@ public:
         return surface.GetSurfaceParams();
     }
 
-    void ApplySwizzle(Tegra::Texture::SwizzleSource x_source,
-                      Tegra::Texture::SwizzleSource y_source,
-                      Tegra::Texture::SwizzleSource z_source,
-                      Tegra::Texture::SwizzleSource w_source);
-
-    void DecorateViewName(GPUVAddr gpu_addr, std::string prefix);
-
 private:
     u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source,
                       Tegra::Texture::SwizzleSource y_source,
@@ -111,8 +115,8 @@ private:
     GLenum target{};
 
     OGLTextureView texture_view;
-    u32 swizzle;
-    bool is_proxy;
+    u32 swizzle{};
+    bool is_proxy{};
 };
 
 class TextureCacheOpenGL final : public TextureCacheBase {
diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp
index 77151a24be..008109a990 100644
--- a/src/video_core/shader/decode/image.cpp
+++ b/src/video_core/shader/decode/image.cpp
@@ -61,56 +61,54 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
         }
 
         const auto type{instr.sust.image_type};
-        const auto& image{instr.sust.is_immediate ? GetImage(instr.image, type)
-                                                  : GetBindlessImage(instr.gpr39, type)};
+        auto& image{instr.sust.is_immediate ? GetImage(instr.image, type)
+                                            : GetBindlessImage(instr.gpr39, type)};
+        image.MarkWrite();
+
         MetaImage meta{image, values};
         const Node store{Operation(OperationCode::ImageStore, meta, std::move(coords))};
         bb.push_back(store);
         break;
     }
     default:
-        UNIMPLEMENTED_MSG("Unhandled conversion instruction: {}", opcode->get().GetName());
+        UNIMPLEMENTED_MSG("Unhandled image instruction: {}", opcode->get().GetName());
     }
 
     return pc;
 }
 
-const Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) {
-    const auto offset{static_cast<std::size_t>(image.index.Value())};
+Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) {
+    const auto offset{static_cast<u64>(image.index.Value())};
 
     // If this image has already been used, return the existing mapping.
-    const auto itr{std::find_if(used_images.begin(), used_images.end(),
-                                [=](const Image& entry) { return entry.GetOffset() == offset; })};
-    if (itr != used_images.end()) {
-        ASSERT(itr->GetType() == type);
-        return *itr;
+    const auto it = used_images.find(offset);
+    if (it != used_images.end()) {
+        ASSERT(it->second.GetType() == type);
+        return it->second;
     }
 
     // Otherwise create a new mapping for this image.
     const std::size_t next_index{used_images.size()};
-    const Image entry{offset, next_index, type};
-    return *used_images.emplace(entry).first;
+    return used_images.emplace(offset, Image{offset, next_index, type}).first->second;
 }
 
-const Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg,
-                                        Tegra::Shader::ImageType type) {
+Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type) {
     const Node image_register{GetRegister(reg)};
     const auto [base_image, cbuf_index, cbuf_offset]{
         TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()))};
     const auto cbuf_key{(static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset)};
 
     // If this image has already been used, return the existing mapping.
-    const auto itr{std::find_if(used_images.begin(), used_images.end(),
-                                [=](const Image& entry) { return entry.GetOffset() == cbuf_key; })};
-    if (itr != used_images.end()) {
-        ASSERT(itr->GetType() == type);
-        return *itr;
+    const auto it = used_images.find(cbuf_key);
+    if (it != used_images.end()) {
+        ASSERT(it->second.GetType() == type);
+        return it->second;
     }
 
     // Otherwise create a new mapping for this image.
     const std::size_t next_index{used_images.size()};
-    const Image entry{cbuf_index, cbuf_offset, next_index, type};
-    return *used_images.emplace(entry).first;
+    return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type})
+        .first->second;
 }
 
 } // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h
index 0397f4c6e0..b29aedce8d 100644
--- a/src/video_core/shader/node.h
+++ b/src/video_core/shader/node.h
@@ -273,50 +273,64 @@ private:
     bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not.
 };
 
-class Image {
+class Image final {
 public:
-    explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type)
+    constexpr explicit Image(u64 offset, std::size_t index, Tegra::Shader::ImageType type)
         : offset{offset}, index{index}, type{type}, is_bindless{false} {}
 
-    explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index,
-                   Tegra::Shader::ImageType type)
+    constexpr explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index,
+                             Tegra::Shader::ImageType type)
         : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type},
           is_bindless{true} {}
 
-    explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type,
-                   bool is_bindless)
-        : offset{offset}, index{index}, type{type}, is_bindless{is_bindless} {}
+    constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type,
+                             bool is_bindless, bool is_written, bool is_read)
+        : offset{offset}, index{index}, type{type}, is_bindless{is_bindless},
+          is_written{is_written}, is_read{is_read} {}
 
-    std::size_t GetOffset() const {
+    void MarkRead() {
+        is_read = true;
+    }
+
+    void MarkWrite() {
+        is_written = true;
+    }
+
+    constexpr std::size_t GetOffset() const {
         return offset;
     }
 
-    std::size_t GetIndex() const {
+    constexpr std::size_t GetIndex() const {
         return index;
     }
 
-    Tegra::Shader::ImageType GetType() const {
+    constexpr Tegra::Shader::ImageType GetType() const {
         return type;
     }
 
-    bool IsBindless() const {
+    constexpr bool IsBindless() const {
         return is_bindless;
     }
 
-    std::pair<u32, u32> GetBindlessCBuf() const {
+    constexpr bool IsRead() const {
+        return is_read;
+    }
+
+    constexpr bool IsWritten() const {
+        return is_written;
+    }
+
+    constexpr std::pair<u32, u32> GetBindlessCBuf() const {
         return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)};
     }
 
-    bool operator<(const Image& rhs) const {
-        return std::tie(offset, index, type, is_bindless) <
-               std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless);
-    }
-
 private:
-    std::size_t offset{};
+    u64 offset{};
     std::size_t index{};
     Tegra::Shader::ImageType type{};
     bool is_bindless{};
+    bool is_read{};
+    bool is_written{};
 };
 
 struct GlobalMemoryBase {
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index bcc9b79b67..0f891eace6 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -95,7 +95,7 @@ public:
         return used_samplers;
     }
 
-    const std::set<Image>& GetImages() const {
+    const std::map<u64, Image>& GetImages() const {
         return used_images;
     }
 
@@ -272,10 +272,10 @@ private:
                                       bool is_shadow);
 
     /// Accesses an image.
-    const Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type);
+    Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type);
 
     /// Access a bindless image sampler.
-    const Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type);
+    Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type);
 
     /// Extracts a sequence of bits from a node
     Node BitfieldExtract(Node value, u32 offset, u32 bits);
@@ -356,7 +356,7 @@ private:
     std::set<Tegra::Shader::Attribute::Index> used_output_attributes;
     std::map<u32, ConstBuffer> used_cbufs;
     std::set<Sampler> used_samplers;
-    std::set<Image> used_images;
+    std::map<u64, Image> used_images;
     std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{};
     std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory;
     bool uses_layer{};