diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 97014a6765..780526b66c 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -141,6 +141,8 @@ const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType compon
 
 GLenum GetTextureTarget(const SurfaceTarget& target) {
     switch (target) {
+    case SurfaceTarget::TextureBuffer:
+        return GL_TEXTURE_BUFFER;
     case SurfaceTarget::Texture1D:
         return GL_TEXTURE_1D;
     case SurfaceTarget::Texture2D:
@@ -191,7 +193,8 @@ void ApplyTextureDefaults(const SurfaceParams& params, GLuint texture) {
     }
 }
 
-OGLTexture CreateTexture(const SurfaceParams& params, GLenum target, GLenum internal_format) {
+OGLTexture CreateTexture(const SurfaceParams& params, GLenum target, GLenum internal_format,
+                         OGLBuffer& texture_buffer) {
     OGLTexture texture;
     texture.Create(target);
 
@@ -199,6 +202,11 @@ OGLTexture CreateTexture(const SurfaceParams& params, GLenum target, GLenum inte
     case SurfaceTarget::Texture1D:
         glTextureStorage1D(texture.handle, params.emulated_levels, internal_format, params.width);
         break;
+    case SurfaceTarget::TextureBuffer:
+        texture_buffer.Create();
+        glNamedBufferStorage(texture_buffer.handle, params.width * params.GetBytesPerPixel(),
+                             nullptr, GL_DYNAMIC_STORAGE_BIT);
+        glTextureBuffer(texture.handle, internal_format, texture_buffer.handle);
     case SurfaceTarget::Texture2D:
     case SurfaceTarget::TextureCubemap:
         glTextureStorage2D(texture.handle, params.emulated_levels, internal_format, params.width,
@@ -229,7 +237,7 @@ CachedSurface::CachedSurface(const GPUVAddr gpu_addr, const SurfaceParams& param
     type = tuple.type;
     is_compressed = tuple.compressed;
     target = GetTextureTarget(params.target);
-    texture = CreateTexture(params, target, internal_format);
+    texture = CreateTexture(params, target, internal_format, texture_buffer);
     DecorateSurfaceName();
     main_view = CreateViewInner(
         ViewParams(params.target, 0, params.is_layered ? params.depth : 1, 0, params.num_levels),
@@ -316,6 +324,11 @@ void CachedSurface::UploadTextureMipmap(u32 level, std::vector<u8>& staging_buff
             glTextureSubImage1D(texture.handle, level, 0, params.GetMipWidth(level), format, type,
                                 buffer);
             break;
+        case SurfaceTarget::TextureBuffer:
+            ASSERT(level == 0);
+            glNamedBufferSubData(texture_buffer.handle, 0,
+                                 params.GetMipWidth(level) * params.GetBytesPerPixel(), buffer);
+            break;
         case SurfaceTarget::Texture1DArray:
         case SurfaceTarget::Texture2D:
             glTextureSubImage2D(texture.handle, level, 0, 0, params.GetMipWidth(level),
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index d4c6e9a305..e7cc66fbbd 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -67,6 +67,7 @@ private:
     u32 view_count{};
 
     OGLTexture texture;
+    OGLBuffer texture_buffer;
 };
 
 class CachedSurfaceView final : public VideoCommon::ViewBase {
diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp
index 340ed2ca08..9c56e2b4f1 100644
--- a/src/video_core/texture_cache/surface_params.cpp
+++ b/src/video_core/texture_cache/surface_params.cpp
@@ -310,6 +310,8 @@ std::string SurfaceParams::TargetName() const {
     switch (target) {
     case SurfaceTarget::Texture1D:
         return "1D";
+    case SurfaceTarget::TextureBuffer:
+        return "TexBuffer";
     case SurfaceTarget::Texture2D:
         return "2D";
     case SurfaceTarget::Texture3D: