From e9a91bc5cc2c39b476ba8946f66930f5ab5608b2 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Tue, 6 Apr 2021 20:14:55 -0300
Subject: [PATCH] shader: Interact texture buffers with buffer cache

---
 .../backend/spirv/emit_context.cpp            |  54 +++----
 .../backend/spirv/emit_context.h              |   2 +-
 src/shader_recompiler/shader_info.h           |   2 +-
 src/video_core/buffer_cache/buffer_cache.h    | 138 ++++++++++++++++++
 .../renderer_opengl/gl_buffer_cache.h         |   1 +
 .../renderer_opengl/gl_texture_cache.cpp      |   4 +
 .../renderer_opengl/gl_texture_cache.h        |   2 +
 .../renderer_vulkan/pipeline_helper.h         |  26 ++--
 .../renderer_vulkan/vk_buffer_cache.cpp       |  57 ++++++--
 .../renderer_vulkan/vk_buffer_cache.h         |  18 +++
 .../renderer_vulkan/vk_compute_pipeline.cpp   |  30 ++--
 .../renderer_vulkan/vk_graphics_pipeline.cpp  |  33 ++++-
 .../renderer_vulkan/vk_texture_cache.cpp      |  63 ++------
 .../renderer_vulkan/vk_texture_cache.h        |  30 ++--
 .../texture_cache/image_view_base.cpp         |   9 ++
 .../texture_cache/image_view_base.h           |   1 +
 src/video_core/texture_cache/texture_cache.h  |  13 +-
 17 files changed, 334 insertions(+), 149 deletions(-)

diff --git a/src/shader_recompiler/backend/spirv/emit_context.cpp b/src/shader_recompiler/backend/spirv/emit_context.cpp
index d01633628a..b738e00cc2 100644
--- a/src/shader_recompiler/backend/spirv/emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_context.cpp
@@ -130,8 +130,8 @@ EmitContext::EmitContext(const Profile& profile_, IR::Program& program, u32& bin
     DefineSharedMemory(program);
     DefineConstantBuffers(program.info, binding);
     DefineStorageBuffers(program.info, binding);
-    DefineTextures(program.info, binding);
     DefineTextureBuffers(program.info, binding);
+    DefineTextures(program.info, binding);
     DefineAttributeMemAccess(program.info);
     DefineLabels(program);
 }
@@ -516,6 +516,32 @@ void EmitContext::DefineStorageBuffers(const Info& info, u32& binding) {
     }
 }
 
+void EmitContext::DefineTextureBuffers(const Info& info, u32& binding) {
+    if (info.texture_buffer_descriptors.empty()) {
+        return;
+    }
+    const spv::ImageFormat format{spv::ImageFormat::Unknown};
+    image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format);
+    sampled_texture_buffer_type = TypeSampledImage(image_buffer_type);
+
+    const Id type{TypePointer(spv::StorageClass::UniformConstant, sampled_texture_buffer_type)};
+    texture_buffers.reserve(info.texture_buffer_descriptors.size());
+    for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) {
+        if (desc.count != 1) {
+            throw NotImplementedException("Array of texture buffers");
+        }
+        const Id id{AddGlobalVariable(type, spv::StorageClass::UniformConstant)};
+        Decorate(id, spv::Decoration::Binding, binding);
+        Decorate(id, spv::Decoration::DescriptorSet, 0U);
+        Name(id, fmt::format("texbuf{}_{:02x}", desc.cbuf_index, desc.cbuf_offset));
+        texture_buffers.insert(texture_buffers.end(), desc.count, id);
+        if (profile.supported_spirv >= 0x00010400) {
+            interfaces.push_back(id);
+        }
+        binding += desc.count;
+    }
+}
+
 void EmitContext::DefineTextures(const Info& info, u32& binding) {
     textures.reserve(info.texture_descriptors.size());
     for (const TextureDescriptor& desc : info.texture_descriptors) {
@@ -544,32 +570,6 @@ void EmitContext::DefineTextures(const Info& info, u32& binding) {
     }
 }
 
-void EmitContext::DefineTextureBuffers(const Info& info, u32& binding) {
-    if (info.texture_buffer_descriptors.empty()) {
-        return;
-    }
-    const spv::ImageFormat format{spv::ImageFormat::Unknown};
-    image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format);
-    sampled_texture_buffer_type = TypeSampledImage(image_buffer_type);
-
-    const Id type{TypePointer(spv::StorageClass::UniformConstant, sampled_texture_buffer_type)};
-    texture_buffers.reserve(info.texture_buffer_descriptors.size());
-    for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) {
-        if (desc.count != 1) {
-            throw NotImplementedException("Array of texture buffers");
-        }
-        const Id id{AddGlobalVariable(type, spv::StorageClass::UniformConstant)};
-        Decorate(id, spv::Decoration::Binding, binding);
-        Decorate(id, spv::Decoration::DescriptorSet, 0U);
-        Name(id, fmt::format("texbuf{}_{:02x}", desc.cbuf_index, desc.cbuf_offset));
-        texture_buffers.insert(texture_buffers.end(), desc.count, id);
-        if (profile.supported_spirv >= 0x00010400) {
-            interfaces.push_back(id);
-        }
-        binding += desc.count;
-    }
-}
-
 void EmitContext::DefineLabels(IR::Program& program) {
     for (IR::Block* const block : program.blocks) {
         block->SetDefinition(OpLabel());
diff --git a/src/shader_recompiler/backend/spirv/emit_context.h b/src/shader_recompiler/backend/spirv/emit_context.h
index 2a10e94e53..f1ac4430ce 100644
--- a/src/shader_recompiler/backend/spirv/emit_context.h
+++ b/src/shader_recompiler/backend/spirv/emit_context.h
@@ -154,8 +154,8 @@ private:
     void DefineSharedMemory(const IR::Program& program);
     void DefineConstantBuffers(const Info& info, u32& binding);
     void DefineStorageBuffers(const Info& info, u32& binding);
-    void DefineTextures(const Info& info, u32& binding);
     void DefineTextureBuffers(const Info& info, u32& binding);
+    void DefineTextures(const Info& info, u32& binding);
     void DefineAttributeMemAccess(const Info& info);
     void DefineLabels(IR::Program& program);
 
diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h
index e6f0de8d83..4cc7311988 100644
--- a/src/shader_recompiler/shader_info.h
+++ b/src/shader_recompiler/shader_info.h
@@ -119,8 +119,8 @@ struct Info {
     boost::container::static_vector<ConstantBufferDescriptor, MAX_CBUFS>
         constant_buffer_descriptors;
     boost::container::static_vector<StorageBufferDescriptor, MAX_SSBOS> storage_buffers_descriptors;
-    TextureDescriptors texture_descriptors;
     TextureBufferDescriptors texture_buffer_descriptors;
+    TextureDescriptors texture_descriptors;
 };
 
 } // namespace Shader
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 7373cb62d7..6701aab82d 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -31,6 +31,7 @@
 #include "video_core/engines/maxwell_3d.h"
 #include "video_core/memory_manager.h"
 #include "video_core/rasterizer_interface.h"
+#include "video_core/surface.h"
 #include "video_core/texture_cache/slot_vector.h"
 #include "video_core/texture_cache/types.h"
 
@@ -42,11 +43,14 @@ MICROPROFILE_DECLARE(GPU_DownloadMemory);
 
 using BufferId = SlotId;
 
+using VideoCore::Surface::PixelFormat;
+
 constexpr u32 NUM_VERTEX_BUFFERS = 32;
 constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4;
 constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18;
 constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8;
 constexpr u32 NUM_STORAGE_BUFFERS = 16;
+constexpr u32 NUM_TEXTURE_BUFFERS = 16;
 constexpr u32 NUM_STAGES = 5;
 
 using namespace Common::Literals;
@@ -66,6 +70,7 @@ class BufferCache {
         P::HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT;
     static constexpr bool NEEDS_BIND_UNIFORM_INDEX = P::NEEDS_BIND_UNIFORM_INDEX;
     static constexpr bool NEEDS_BIND_STORAGE_INDEX = P::NEEDS_BIND_STORAGE_INDEX;
+    static constexpr bool NEEDS_BIND_TEXTURE_BUFFER_INDEX = P::NEEDS_BIND_TEXTURE_BUFFER_INDEX;
     static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
 
     static constexpr BufferId NULL_BUFFER_ID{0};
@@ -96,6 +101,10 @@ class BufferCache {
         BufferId buffer_id;
     };
 
+    struct TextureBufferBinding : Binding {
+        PixelFormat format;
+    };
+
     static constexpr Binding NULL_BINDING{
         .cpu_addr = 0,
         .size = 0,
@@ -142,11 +151,21 @@ public:
     void BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
                                    bool is_written);
 
+    void UnbindGraphicsTextureBuffers(size_t stage);
+
+    void BindGraphicsTextureBuffer(size_t stage, size_t tbo_index, GPUVAddr gpu_addr, u32 size,
+                                   PixelFormat format);
+
     void UnbindComputeStorageBuffers();
 
     void BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
                                   bool is_written);
 
+    void UnbindComputeTextureBuffers();
+
+    void BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_addr, u32 size,
+                                  PixelFormat format);
+
     void FlushCachedWrites();
 
     /// Return true when there are uncommitted buffers to be downloaded
@@ -254,12 +273,16 @@ private:
 
     void BindHostGraphicsStorageBuffers(size_t stage);
 
+    void BindHostGraphicsTextureBuffers(size_t stage);
+
     void BindHostTransformFeedbackBuffers();
 
     void BindHostComputeUniformBuffers();
 
     void BindHostComputeStorageBuffers();
 
+    void BindHostComputeTextureBuffers();
+
     void DoUpdateGraphicsBuffers(bool is_indexed);
 
     void DoUpdateComputeBuffers();
@@ -274,6 +297,8 @@ private:
 
     void UpdateStorageBuffers(size_t stage);
 
+    void UpdateTextureBuffers(size_t stage);
+
     void UpdateTransformFeedbackBuffers();
 
     void UpdateTransformFeedbackBuffer(u32 index);
@@ -282,6 +307,8 @@ private:
 
     void UpdateComputeStorageBuffers();
 
+    void UpdateComputeTextureBuffers();
+
     void MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size);
 
     [[nodiscard]] BufferId FindBuffer(VAddr cpu_addr, u32 size);
@@ -323,6 +350,9 @@ private:
 
     [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr) const;
 
+    [[nodiscard]] TextureBufferBinding GetTextureBufferBinding(GPUVAddr gpu_addr, u32 size,
+                                                               PixelFormat format);
+
     [[nodiscard]] std::span<const u8> ImmediateBufferWithData(VAddr cpu_addr, size_t size);
 
     [[nodiscard]] std::span<u8> ImmediateBuffer(size_t wanted_capacity);
@@ -347,10 +377,12 @@ private:
     std::array<Binding, NUM_VERTEX_BUFFERS> vertex_buffers;
     std::array<std::array<Binding, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES> uniform_buffers;
     std::array<std::array<Binding, NUM_STORAGE_BUFFERS>, NUM_STAGES> storage_buffers;
+    std::array<std::array<TextureBufferBinding, NUM_TEXTURE_BUFFERS>, NUM_STAGES> texture_buffers;
     std::array<Binding, NUM_TRANSFORM_FEEDBACK_BUFFERS> transform_feedback_buffers;
 
     std::array<Binding, NUM_COMPUTE_UNIFORM_BUFFERS> compute_uniform_buffers;
     std::array<Binding, NUM_STORAGE_BUFFERS> compute_storage_buffers;
+    std::array<TextureBufferBinding, NUM_TEXTURE_BUFFERS> compute_texture_buffers;
 
     std::array<u32, NUM_STAGES> enabled_uniform_buffers{};
     u32 enabled_compute_uniform_buffers = 0;
@@ -360,6 +392,9 @@ private:
     u32 enabled_compute_storage_buffers = 0;
     u32 written_compute_storage_buffers = 0;
 
+    std::array<u32, NUM_STAGES> enabled_texture_buffers{};
+    u32 enabled_compute_texture_buffers = 0;
+
     std::array<u32, NUM_STAGES> fast_bound_uniform_buffers{};
 
     std::array<u32, 16> uniform_cache_hits{};
@@ -619,6 +654,7 @@ void BufferCache<P>::BindHostStageBuffers(size_t stage) {
     MICROPROFILE_SCOPE(GPU_BindUploadBuffers);
     BindHostGraphicsUniformBuffers(stage);
     BindHostGraphicsStorageBuffers(stage);
+    BindHostGraphicsTextureBuffers(stage);
 }
 
 template <class P>
@@ -626,6 +662,7 @@ void BufferCache<P>::BindHostComputeBuffers() {
     MICROPROFILE_SCOPE(GPU_BindUploadBuffers);
     BindHostComputeUniformBuffers();
     BindHostComputeStorageBuffers();
+    BindHostComputeTextureBuffers();
 }
 
 template <class P>
@@ -660,6 +697,18 @@ void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index,
     storage_buffers[stage][ssbo_index] = StorageBufferBinding(ssbo_addr);
 }
 
+template <class P>
+void BufferCache<P>::UnbindGraphicsTextureBuffers(size_t stage) {
+    enabled_texture_buffers[stage] = 0;
+}
+
+template <class P>
+void BufferCache<P>::BindGraphicsTextureBuffer(size_t stage, size_t tbo_index, GPUVAddr gpu_addr,
+                                               u32 size, PixelFormat format) {
+    enabled_texture_buffers[stage] |= 1U << tbo_index;
+    texture_buffers[stage][tbo_index] = GetTextureBufferBinding(gpu_addr, size, format);
+}
+
 template <class P>
 void BufferCache<P>::UnbindComputeStorageBuffers() {
     enabled_compute_storage_buffers = 0;
@@ -680,6 +729,18 @@ void BufferCache<P>::BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index,
     compute_storage_buffers[ssbo_index] = StorageBufferBinding(ssbo_addr);
 }
 
+template <class P>
+void BufferCache<P>::UnbindComputeTextureBuffers() {
+    enabled_compute_texture_buffers = 0;
+}
+
+template <class P>
+void BufferCache<P>::BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_addr, u32 size,
+                                              PixelFormat format) {
+    enabled_compute_texture_buffers |= 1U << tbo_index;
+    compute_texture_buffers[tbo_index] = GetTextureBufferBinding(gpu_addr, size, format);
+}
+
 template <class P>
 void BufferCache<P>::FlushCachedWrites() {
     for (const BufferId buffer_id : cached_write_buffer_ids) {
@@ -988,6 +1049,26 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
     });
 }
 
+template <class P>
+void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
+    u32 binding_index = 0;
+    ForEachEnabledBit(enabled_texture_buffers[stage], [&](u32 index) {
+        const TextureBufferBinding& binding = texture_buffers[stage][index];
+        Buffer& buffer = slot_buffers[binding.buffer_id];
+        const u32 size = binding.size;
+        SynchronizeBuffer(buffer, binding.cpu_addr, size);
+
+        const u32 offset = buffer.Offset(binding.cpu_addr);
+        const PixelFormat format = binding.format;
+        if constexpr (NEEDS_BIND_TEXTURE_BUFFER_INDEX) {
+            runtime.BindTextureBuffer(binding_index, buffer, offset, size, format);
+            ++binding_index;
+        } else {
+            runtime.BindTextureBuffer(buffer, offset, size, format);
+        }
+    });
+}
+
 template <class P>
 void BufferCache<P>::BindHostTransformFeedbackBuffers() {
     if (maxwell3d.regs.tfb_enabled == 0) {
@@ -1050,6 +1131,26 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
     });
 }
 
+template <class P>
+void BufferCache<P>::BindHostComputeTextureBuffers() {
+    u32 binding_index = 0;
+    ForEachEnabledBit(enabled_compute_texture_buffers, [&](u32 index) {
+        const TextureBufferBinding& binding = compute_texture_buffers[index];
+        Buffer& buffer = slot_buffers[binding.buffer_id];
+        const u32 size = binding.size;
+        SynchronizeBuffer(buffer, binding.cpu_addr, size);
+
+        const u32 offset = buffer.Offset(binding.cpu_addr);
+        const PixelFormat format = binding.format;
+        if constexpr (NEEDS_BIND_TEXTURE_BUFFER_INDEX) {
+            runtime.BindTextureBuffer(binding_index, buffer, offset, size, format);
+            ++binding_index;
+        } else {
+            runtime.BindTextureBuffer(buffer, offset, size, format);
+        }
+    });
+}
+
 template <class P>
 void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
     if (is_indexed) {
@@ -1060,6 +1161,7 @@ void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
     for (size_t stage = 0; stage < NUM_STAGES; ++stage) {
         UpdateUniformBuffers(stage);
         UpdateStorageBuffers(stage);
+        UpdateTextureBuffers(stage);
     }
 }
 
@@ -1067,6 +1169,7 @@ template <class P>
 void BufferCache<P>::DoUpdateComputeBuffers() {
     UpdateComputeUniformBuffers();
     UpdateComputeStorageBuffers();
+    UpdateComputeTextureBuffers();
 }
 
 template <class P>
@@ -1166,6 +1269,14 @@ void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
     });
 }
 
+template <class P>
+void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
+    ForEachEnabledBit(enabled_texture_buffers[stage], [&](u32 index) {
+        Binding& binding = texture_buffers[stage][index];
+        binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
+    });
+}
+
 template <class P>
 void BufferCache<P>::UpdateTransformFeedbackBuffers() {
     if (maxwell3d.regs.tfb_enabled == 0) {
@@ -1227,6 +1338,14 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
     });
 }
 
+template <class P>
+void BufferCache<P>::UpdateComputeTextureBuffers() {
+    ForEachEnabledBit(enabled_compute_texture_buffers, [&](u32 index) {
+        Binding& binding = compute_texture_buffers[index];
+        binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
+    });
+}
+
 template <class P>
 void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) {
     Buffer& buffer = slot_buffers[buffer_id];
@@ -1581,6 +1700,25 @@ typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr s
     return binding;
 }
 
+template <class P>
+typename BufferCache<P>::TextureBufferBinding BufferCache<P>::GetTextureBufferBinding(
+    GPUVAddr gpu_addr, u32 size, PixelFormat format) {
+    const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+    TextureBufferBinding binding;
+    if (!cpu_addr || size == 0) {
+        binding.cpu_addr = 0;
+        binding.size = 0;
+        binding.buffer_id = NULL_BUFFER_ID;
+        binding.format = PixelFormat::Invalid;
+    } else {
+        binding.cpu_addr = *cpu_addr;
+        binding.size = size;
+        binding.buffer_id = BufferId{};
+        binding.format = format;
+    }
+    return binding;
+}
+
 template <class P>
 std::span<const u8> BufferCache<P>::ImmediateBufferWithData(VAddr cpu_addr, size_t size) {
     u8* const base_pointer = cpu_memory.GetPointer(cpu_addr);
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index fe91aa4528..ddcce5e97a 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -155,6 +155,7 @@ struct BufferCacheParams {
     static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT = true;
     static constexpr bool NEEDS_BIND_UNIFORM_INDEX = true;
     static constexpr bool NEEDS_BIND_STORAGE_INDEX = true;
+    static constexpr bool NEEDS_BIND_TEXTURE_BUFFER_INDEX = true;
     static constexpr bool USE_MEMORY_MAPS = false;
 };
 
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index ff0f03e997..a8bf842183 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -1016,6 +1016,10 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
     default_handle = Handle(info.type);
 }
 
+ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info,
+                     const VideoCommon::ImageViewInfo& view_info)
+    : VideoCommon::ImageViewBase{info, view_info} {}
+
 ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageParams& params)
     : VideoCommon::ImageViewBase{params}, views{runtime.null_image_views} {}
 
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index cf3b789e38..817b0e6503 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -182,6 +182,8 @@ class ImageView : public VideoCommon::ImageViewBase {
 
 public:
     explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageViewInfo&, ImageId, Image&);
+    explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info,
+                       const VideoCommon::ImageViewInfo& view_info);
     explicit ImageView(TextureCacheRuntime&, const VideoCommon::NullImageParams&);
 
     [[nodiscard]] GLuint Handle(ImageViewType query_type) const noexcept {
diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h
index decf0d32c4..cff93cc601 100644
--- a/src/video_core/renderer_vulkan/pipeline_helper.h
+++ b/src/video_core/renderer_vulkan/pipeline_helper.h
@@ -24,7 +24,8 @@ struct TextureHandle {
         [[likely]] if (via_header_index) {
             image = data;
             sampler = data;
-        } else {
+        }
+        else {
             const Tegra::Texture::TextureHandle handle{data};
             image = handle.tic_id;
             sampler = via_header_index ? image : handle.tsc_id.Value();
@@ -90,12 +91,12 @@ public:
         for ([[maybe_unused]] const auto& desc : info.storage_buffers_descriptors) {
             Add(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, stage);
         }
+        for ([[maybe_unused]] const auto& desc : info.texture_buffer_descriptors) {
+            Add(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, stage);
+        }
         for ([[maybe_unused]] const auto& desc : info.texture_descriptors) {
             Add(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, stage);
         }
-        for (const auto& desc : info.texture_buffer_descriptors) {
-            Add(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, stage);
-        }
     }
 
 private:
@@ -156,20 +157,15 @@ inline VideoCommon::ImageViewType CastType(Shader::TextureType type) {
     return {};
 }
 
-inline void PushImageDescriptors(const Shader::Info& info, const VkSampler* samplers,
-                                 const ImageId* image_view_ids, TextureCache& texture_cache,
-                                 VKUpdateDescriptorQueue& update_descriptor_queue, size_t& index) {
+inline void PushImageDescriptors(const Shader::Info& info, const VkSampler*& samplers,
+                                 const ImageId*& image_view_ids, TextureCache& texture_cache,
+                                 VKUpdateDescriptorQueue& update_descriptor_queue) {
+    image_view_ids += info.texture_buffer_descriptors.size();
     for (const auto& desc : info.texture_descriptors) {
-        const VkSampler sampler{samplers[index]};
-        ImageView& image_view{texture_cache.GetImageView(image_view_ids[index])};
+        const VkSampler sampler{*(samplers++)};
+        ImageView& image_view{texture_cache.GetImageView(*(image_view_ids++))};
         const VkImageView vk_image_view{image_view.Handle(CastType(desc.type))};
         update_descriptor_queue.AddSampledImage(vk_image_view, sampler);
-        ++index;
-    }
-    for (const auto& desc : info.texture_buffer_descriptors) {
-        ImageView& image_view{texture_cache.GetImageView(image_view_ids[index])};
-        update_descriptor_queue.AddTexelBuffer(image_view.BufferView());
-        ++index;
     }
 }
 
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 0def1e7697..cdda56ab12 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -67,25 +67,50 @@ Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params)
 
 Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_,
                VAddr cpu_addr_, u64 size_bytes_)
-    : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_) {
-    buffer = runtime.device.GetLogical().CreateBuffer(VkBufferCreateInfo{
-        .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
-        .pNext = nullptr,
-        .flags = 0,
-        .size = SizeBytes(),
-        .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
-                 VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
-                 VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
-                 VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
-                 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
-        .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
-        .queueFamilyIndexCount = 0,
-        .pQueueFamilyIndices = nullptr,
-    });
+    : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_),
+      device{&runtime.device},
+      buffer{device->GetLogical().CreateBuffer({
+          .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+          .pNext = nullptr,
+          .flags = 0,
+          .size = SizeBytes(),
+          .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
+                   VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
+                   VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
+                   VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
+                   VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+          .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+          .queueFamilyIndexCount = 0,
+          .pQueueFamilyIndices = nullptr,
+      })},
+      commit{runtime.memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal)} {
     if (runtime.device.HasDebuggingToolAttached()) {
         buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str());
     }
-    commit = runtime.memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal);
+}
+
+VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format) {
+    const auto it{std::ranges::find_if(views, [offset, size, format](const BufferView& view) {
+        return offset == view.offset && size == view.size && format == view.format;
+    })};
+    if (it != views.end()) {
+        return *it->handle;
+    }
+    views.push_back({
+        .offset = offset,
+        .size = size,
+        .format = format,
+        .handle = device->GetLogical().CreateBufferView({
+            .sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO,
+            .pNext = nullptr,
+            .flags = 0,
+            .buffer = *buffer,
+            .format = MaxwellToVK::SurfaceFormat(*device, FormatType::Buffer, false, format).format,
+            .offset = offset,
+            .range = size,
+        }),
+    });
+    return *views.back().handle;
 }
 
 BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& memory_allocator_,
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 3bb81d5b3d..ea17406dc0 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -9,6 +9,7 @@
 #include "video_core/renderer_vulkan/vk_compute_pass.h"
 #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
 #include "video_core/renderer_vulkan/vk_update_descriptor.h"
+#include "video_core/surface.h"
 #include "video_core/vulkan_common/vulkan_memory_allocator.h"
 #include "video_core/vulkan_common/vulkan_wrapper.h"
 
@@ -26,6 +27,8 @@ public:
     explicit Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_,
                     VAddr cpu_addr_, u64 size_bytes_);
 
+    [[nodiscard]] VkBufferView View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format);
+
     [[nodiscard]] VkBuffer Handle() const noexcept {
         return *buffer;
     }
@@ -35,8 +38,17 @@ public:
     }
 
 private:
+    struct BufferView {
+        u32 offset;
+        u32 size;
+        VideoCore::Surface::PixelFormat format;
+        vk::BufferView handle;
+    };
+
+    const Device* device{};
     vk::Buffer buffer;
     MemoryCommit commit;
+    std::vector<BufferView> views;
 };
 
 class BufferCacheRuntime {
@@ -87,6 +99,11 @@ public:
         BindBuffer(buffer, offset, size);
     }
 
+    void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
+                           VideoCore::Surface::PixelFormat format) {
+        update_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format));
+    }
+
 private:
     void BindBuffer(VkBuffer buffer, u32 offset, u32 size) {
         update_descriptor_queue.AddBuffer(buffer, offset, size);
@@ -123,6 +140,7 @@ struct BufferCacheParams {
     static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT = false;
     static constexpr bool NEEDS_BIND_UNIFORM_INDEX = false;
     static constexpr bool NEEDS_BIND_STORAGE_INDEX = false;
+    static constexpr bool NEEDS_BIND_TEXTURE_BUFFER_INDEX = false;
     static constexpr bool USE_MEMORY_MAPS = true;
 };
 
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
index 9922cbd0f6..ac47b1f3ce 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
@@ -80,8 +80,6 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
                                               desc.is_written);
         ++ssbo_index;
     }
-    buffer_cache.UpdateComputeBuffers();
-    buffer_cache.BindHostComputeBuffers();
 
     texture_cache.SynchronizeComputeDescriptors();
 
@@ -99,6 +97,10 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
         const u32 raw_handle{gpu_memory.Read<u32>(addr)};
         return TextureHandle(raw_handle, via_header_index);
     }};
+    for (const auto& desc : info.texture_buffer_descriptors) {
+        const TextureHandle handle{read_handle(desc.cbuf_index, desc.cbuf_offset)};
+        image_view_indices.push_back(handle.image);
+    }
     for (const auto& desc : info.texture_descriptors) {
         const TextureHandle handle{read_handle(desc.cbuf_index, desc.cbuf_offset)};
         image_view_indices.push_back(handle.image);
@@ -106,16 +108,26 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
         Sampler* const sampler = texture_cache.GetComputeSampler(handle.sampler);
         samplers.push_back(sampler->Handle());
     }
-    for (const auto& desc : info.texture_buffer_descriptors) {
-        const TextureHandle handle{read_handle(desc.cbuf_index, desc.cbuf_offset)};
-        image_view_indices.push_back(handle.image);
-    }
     const std::span indices_span(image_view_indices.data(), image_view_indices.size());
     texture_cache.FillComputeImageViews(indices_span, image_view_ids);
 
-    size_t image_index{};
-    PushImageDescriptors(info, samplers.data(), image_view_ids.data(), texture_cache,
-                         update_descriptor_queue, image_index);
+    buffer_cache.UnbindComputeTextureBuffers();
+    ImageId* texture_buffer_ids{image_view_ids.data()};
+    size_t index{};
+    for (const auto& desc : info.texture_buffer_descriptors) {
+        ASSERT(desc.count == 1);
+        ImageView& image_view = texture_cache.GetImageView(*texture_buffer_ids);
+        buffer_cache.BindComputeTextureBuffer(index, image_view.GpuAddr(), image_view.BufferSize(),
+                                              image_view.format);
+        ++texture_buffer_ids;
+        ++index;
+    }
+    buffer_cache.UpdateComputeBuffers();
+    buffer_cache.BindHostComputeBuffers();
+
+    const VkSampler* samplers_it{samplers.data()};
+    const ImageId* views_it{image_view_ids.data()};
+    PushImageDescriptors(info, samplers_it, views_it, texture_cache, update_descriptor_queue);
 
     if (!is_built.load(std::memory_order::relaxed)) {
         // Wait for the pipeline to be built
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index afdd8b3715..893258b4aa 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -175,6 +175,10 @@ void GraphicsPipeline::Configure(bool is_indexed) {
             const u32 raw_handle{gpu_memory.Read<u32>(addr)};
             return TextureHandle(raw_handle, via_header_index);
         }};
+        for (const auto& desc : info.texture_buffer_descriptors) {
+            const TextureHandle handle{read_handle(desc.cbuf_index, desc.cbuf_offset)};
+            image_view_indices.push_back(handle.image);
+        }
         for (const auto& desc : info.texture_descriptors) {
             const TextureHandle handle{read_handle(desc.cbuf_index, desc.cbuf_offset)};
             image_view_indices.push_back(handle.image);
@@ -182,24 +186,37 @@ void GraphicsPipeline::Configure(bool is_indexed) {
             Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.sampler)};
             samplers.push_back(sampler->Handle());
         }
-        for (const auto& desc : info.texture_buffer_descriptors) {
-            const TextureHandle handle{read_handle(desc.cbuf_index, desc.cbuf_offset)};
-            image_view_indices.push_back(handle.image);
-        }
     }
     const std::span indices_span(image_view_indices.data(), image_view_indices.size());
-    buffer_cache.UpdateGraphicsBuffers(is_indexed);
     texture_cache.FillGraphicsImageViews(indices_span, image_view_ids);
 
+    ImageId* texture_buffer_index{image_view_ids.data()};
+    for (size_t stage = 0; stage < Maxwell::MaxShaderStage; ++stage) {
+        const Shader::Info& info{stage_infos[stage]};
+        buffer_cache.UnbindGraphicsTextureBuffers(stage);
+        size_t index{};
+        for (const auto& desc : info.texture_buffer_descriptors) {
+            ASSERT(desc.count == 1);
+            ImageView& image_view = texture_cache.GetImageView(*texture_buffer_index);
+            buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(),
+                                                   image_view.BufferSize(), image_view.format);
+            ++index;
+            ++texture_buffer_index;
+        }
+        texture_buffer_index += info.texture_descriptors.size();
+    }
+    buffer_cache.UpdateGraphicsBuffers(is_indexed);
+
     buffer_cache.BindHostGeometryBuffers(is_indexed);
 
     update_descriptor_queue.Acquire();
 
-    size_t index{};
+    const VkSampler* samplers_it{samplers.data()};
+    const ImageId* views_it{image_view_ids.data()};
     for (size_t stage = 0; stage < Maxwell::MaxShaderStage; ++stage) {
         buffer_cache.BindHostStageBuffers(stage);
-        PushImageDescriptors(stage_infos[stage], samplers.data(), image_view_ids.data(),
-                             texture_cache, update_descriptor_queue, index);
+        PushImageDescriptors(stage_infos[stage], samplers_it, views_it, texture_cache,
+                             update_descriptor_queue);
     }
     texture_cache.UpdateRenderTargets(false);
     scheduler.RequestRenderpass(texture_cache.GetFramebuffer());
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 1bbc542a1c..e42b091c5f 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -15,10 +15,10 @@
 #include "video_core/renderer_vulkan/maxwell_to_vk.h"
 #include "video_core/renderer_vulkan/vk_compute_pass.h"
 #include "video_core/renderer_vulkan/vk_rasterizer.h"
+#include "video_core/renderer_vulkan/vk_render_pass_cache.h"
 #include "video_core/renderer_vulkan/vk_scheduler.h"
 #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
 #include "video_core/renderer_vulkan/vk_texture_cache.h"
-#include "video_core/renderer_vulkan/vk_render_pass_cache.h"
 #include "video_core/vulkan_common/vulkan_device.h"
 #include "video_core/vulkan_common/vulkan_memory_allocator.h"
 #include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -162,25 +162,6 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
     return device.GetLogical().CreateImage(MakeImageCreateInfo(device, info));
 }
 
-[[nodiscard]] vk::Buffer MakeBuffer(const Device& device, const ImageInfo& info) {
-    if (info.type != ImageType::Buffer) {
-        return vk::Buffer{};
-    }
-    const size_t bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format);
-    return device.GetLogical().CreateBuffer(VkBufferCreateInfo{
-        .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
-        .pNext = nullptr,
-        .flags = 0,
-        .size = info.size.width * bytes_per_block,
-        .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
-                 VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
-                 VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT,
-        .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
-        .queueFamilyIndexCount = 0,
-        .pQueueFamilyIndices = nullptr,
-    });
-}
-
 [[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) {
     switch (VideoCore::Surface::GetFormatType(format)) {
     case VideoCore::Surface::SurfaceType::ColorTexture:
@@ -813,13 +794,9 @@ u64 TextureCacheRuntime::GetDeviceLocalMemory() const {
 Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_addr_,
              VAddr cpu_addr_)
     : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime.scheduler},
-      image(MakeImage(runtime.device, info)), buffer(MakeBuffer(runtime.device, info)),
+      image(MakeImage(runtime.device, info)),
+      commit(runtime.memory_allocator.Commit(image, MemoryUsage::DeviceLocal)),
       aspect_mask(ImageAspectMask(info.format)) {
-    if (image) {
-        commit = runtime.memory_allocator.Commit(image, MemoryUsage::DeviceLocal);
-    } else {
-        commit = runtime.memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal);
-    }
     if (IsPixelFormatASTC(info.format) && !runtime.device.IsOptimalAstcSupported()) {
         if (Settings::values.accelerate_astc.GetValue()) {
             flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
@@ -828,11 +805,7 @@ Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_
         }
     }
     if (runtime.device.HasDebuggingToolAttached()) {
-        if (image) {
-            image.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
-        } else {
-            buffer.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
-        }
+        image.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
     }
     static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{
         .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
@@ -884,19 +857,6 @@ void Image::UploadMemory(const StagingBufferRef& map, std::span<const BufferImag
     });
 }
 
-void Image::UploadMemory(const StagingBufferRef& map,
-                         std::span<const VideoCommon::BufferCopy> copies) {
-    // TODO: Move this to another API
-    scheduler->RequestOutsideRenderPassOperationContext();
-    std::vector vk_copies = TransformBufferCopies(copies, map.offset);
-    const VkBuffer src_buffer = map.buffer;
-    const VkBuffer dst_buffer = *buffer;
-    scheduler->Record([src_buffer, dst_buffer, vk_copies](vk::CommandBuffer cmdbuf) {
-        // TODO: Barriers
-        cmdbuf.CopyBuffer(src_buffer, dst_buffer, vk_copies);
-    });
-}
-
 void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferImageCopy> copies) {
     std::vector vk_copies = TransformBufferImageCopies(copies, map.offset, aspect_mask);
     scheduler->RequestOutsideRenderPassOperationContext();
@@ -1032,19 +992,16 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
         UNIMPLEMENTED();
         break;
     case VideoCommon::ImageViewType::Buffer:
-        buffer_view = device->GetLogical().CreateBufferView(VkBufferViewCreateInfo{
-            .sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO,
-            .pNext = nullptr,
-            .flags = 0,
-            .buffer = image.Buffer(),
-            .format = format_info.format,
-            .offset = 0, // TODO: Redesign buffer cache to support this
-            .range = image.guest_size_bytes,
-        });
+        UNREACHABLE();
         break;
     }
 }
 
+ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info,
+                     const VideoCommon::ImageViewInfo& view_info, GPUVAddr gpu_addr_)
+    : VideoCommon::ImageViewBase{info, view_info}, gpu_addr{gpu_addr_},
+      buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {}
+
 ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageParams& params)
     : VideoCommon::ImageViewBase{params} {}
 
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 189ee5a68e..498e76a1ce 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -41,9 +41,9 @@ struct TextureCacheRuntime {
 
     void Finish();
 
-    [[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size);
+    StagingBufferRef UploadStagingBuffer(size_t size);
 
-    [[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size);
+    StagingBufferRef DownloadStagingBuffer(size_t size);
 
     void BlitImage(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& src,
                    const Region2D& dst_region, const Region2D& src_region,
@@ -54,7 +54,7 @@ struct TextureCacheRuntime {
 
     void ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view);
 
-    [[nodiscard]] bool CanAccelerateImageUpload(Image&) const noexcept {
+    bool CanAccelerateImageUpload(Image&) const noexcept {
         return false;
     }
 
@@ -92,8 +92,6 @@ public:
     void UploadMemory(const StagingBufferRef& map,
                       std::span<const VideoCommon::BufferImageCopy> copies);
 
-    void UploadMemory(const StagingBufferRef& map, std::span<const VideoCommon::BufferCopy> copies);
-
     void DownloadMemory(const StagingBufferRef& map,
                         std::span<const VideoCommon::BufferImageCopy> copies);
 
@@ -101,10 +99,6 @@ public:
         return *image;
     }
 
-    [[nodiscard]] VkBuffer Buffer() const noexcept {
-        return *buffer;
-    }
-
     [[nodiscard]] VkImageAspectFlags AspectMask() const noexcept {
         return aspect_mask;
     }
@@ -121,7 +115,6 @@ public:
 private:
     VKScheduler* scheduler;
     vk::Image image;
-    vk::Buffer buffer;
     MemoryCommit commit;
     vk::ImageView image_view;
     std::vector<vk::ImageView> storage_image_views;
@@ -132,6 +125,8 @@ private:
 class ImageView : public VideoCommon::ImageViewBase {
 public:
     explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageViewInfo&, ImageId, Image&);
+    explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo&,
+                       const VideoCommon::ImageViewInfo&, GPUVAddr);
     explicit ImageView(TextureCacheRuntime&, const VideoCommon::NullImageParams&);
 
     [[nodiscard]] VkImageView DepthView();
@@ -142,10 +137,6 @@ public:
         return *image_views[static_cast<size_t>(query_type)];
     }
 
-    [[nodiscard]] VkBufferView BufferView() const noexcept {
-        return *buffer_view;
-    }
-
     [[nodiscard]] VkImage ImageHandle() const noexcept {
         return image_handle;
     }
@@ -162,6 +153,14 @@ public:
         return samples;
     }
 
+    [[nodiscard]] GPUVAddr GpuAddr() const noexcept {
+        return gpu_addr;
+    }
+
+    [[nodiscard]] u32 BufferSize() const noexcept {
+        return buffer_size;
+    }
+
 private:
     [[nodiscard]] vk::ImageView MakeDepthStencilView(VkImageAspectFlags aspect_mask);
 
@@ -169,11 +168,12 @@ private:
     std::array<vk::ImageView, VideoCommon::NUM_IMAGE_VIEW_TYPES> image_views;
     vk::ImageView depth_view;
     vk::ImageView stencil_view;
-    vk::BufferView buffer_view;
     VkImage image_handle = VK_NULL_HANDLE;
     VkImageView render_target = VK_NULL_HANDLE;
     PixelFormat image_format = PixelFormat::Invalid;
     VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
+    GPUVAddr gpu_addr = 0;
+    u32 buffer_size = 0;
 };
 
 class ImageAlloc : public VideoCommon::ImageAllocBase {};
diff --git a/src/video_core/texture_cache/image_view_base.cpp b/src/video_core/texture_cache/image_view_base.cpp
index e8d632f9e5..450becbebe 100644
--- a/src/video_core/texture_cache/image_view_base.cpp
+++ b/src/video_core/texture_cache/image_view_base.cpp
@@ -36,6 +36,15 @@ ImageViewBase::ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_i
     }
 }
 
+ImageViewBase::ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_info)
+    : format{info.format}, type{ImageViewType::Buffer}, size{
+                                                            .width = info.size.width,
+                                                            .height = 1,
+                                                            .depth = 1,
+                                                        } {
+    ASSERT_MSG(view_info.type == ImageViewType::Buffer, "Expected texture buffer");
+}
+
 ImageViewBase::ImageViewBase(const NullImageParams&) {}
 
 } // namespace VideoCommon
diff --git a/src/video_core/texture_cache/image_view_base.h b/src/video_core/texture_cache/image_view_base.h
index 73954167ef..903f715c51 100644
--- a/src/video_core/texture_cache/image_view_base.h
+++ b/src/video_core/texture_cache/image_view_base.h
@@ -27,6 +27,7 @@ DECLARE_ENUM_FLAG_OPERATORS(ImageViewFlagBits)
 struct ImageViewBase {
     explicit ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_info,
                            ImageId image_id);
+    explicit ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_info);
     explicit ImageViewBase(const NullImageParams&);
 
     [[nodiscard]] bool IsBuffer() const noexcept {
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 85ce06d56c..5e8d994826 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -968,9 +968,6 @@ void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging)
         auto copies = UnswizzleImage(gpu_memory, gpu_addr, image.info, unswizzled_data);
         ConvertImage(unswizzled_data, image.info, mapped_span, copies);
         image.UploadMemory(staging, copies);
-    } else if (image.info.type == ImageType::Buffer) {
-        const std::array copies{UploadBufferCopy(gpu_memory, gpu_addr, image, mapped_span)};
-        image.UploadMemory(staging, copies);
     } else {
         const auto copies = UnswizzleImage(gpu_memory, gpu_addr, image.info, mapped_span);
         image.UploadMemory(staging, copies);
@@ -993,7 +990,12 @@ ImageViewId TextureCache<P>::FindImageView(const TICEntry& config) {
 template <class P>
 ImageViewId TextureCache<P>::CreateImageView(const TICEntry& config) {
     const ImageInfo info(config);
-    const GPUVAddr image_gpu_addr = config.Address() - config.BaseLayer() * info.layer_stride;
+    if (info.type == ImageType::Buffer) {
+        const ImageViewInfo view_info(config, 0);
+        return slot_image_views.insert(runtime, info, view_info, config.Address());
+    }
+    const u32 layer_offset = config.BaseLayer() * info.layer_stride;
+    const GPUVAddr image_gpu_addr = config.Address() - layer_offset;
     const ImageId image_id = FindOrInsertImage(info, image_gpu_addr);
     if (!image_id) {
         return NULL_IMAGE_VIEW_ID;
@@ -1801,6 +1803,9 @@ void TextureCache<P>::PrepareImageView(ImageViewId image_view_id, bool is_modifi
         return;
     }
     const ImageViewBase& image_view = slot_image_views[image_view_id];
+    if (image_view.IsBuffer()) {
+        return;
+    }
     PrepareImage(image_view.image_id, is_modification, invalidate);
 }