diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 0e11557c44..dcbf009c01 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -126,6 +126,13 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
             params.target = SurfaceTarget::Texture2D;
         }
         break;
+    case SurfaceTarget::TextureCubeArray:
+        params.depth = config.tic.Depth() * 6;
+        if (!entry.IsArray()) {
+            ASSERT(params.depth == 6);
+            params.target = SurfaceTarget::TextureCubemap;
+        }
+        break;
     default:
         LOG_CRITICAL(HW_GPU, "Unknown depth for target={}", static_cast<u32>(params.target));
         UNREACHABLE();
@@ -334,6 +341,8 @@ static GLenum SurfaceTargetToGL(SurfaceTarget target) {
         return GL_TEXTURE_2D_ARRAY;
     case SurfaceTarget::TextureCubemap:
         return GL_TEXTURE_CUBE_MAP;
+    case SurfaceTarget::TextureCubeArray:
+        return GL_TEXTURE_CUBE_MAP_ARRAY_ARB;
     }
     LOG_CRITICAL(Render_OpenGL, "Unimplemented texture target={}", static_cast<u32>(target));
     UNREACHABLE();
@@ -761,6 +770,7 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
             break;
         case SurfaceTarget::Texture3D:
         case SurfaceTarget::Texture2DArray:
+        case SurfaceTarget::TextureCubeArray:
             glTextureSubImage3D(dst_surface->Texture().handle, 0, 0, 0, 0, width, height,
                                 static_cast<GLsizei>(dst_params.depth), dest_format.format,
                                 dest_format.type, nullptr);
@@ -813,6 +823,7 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
             break;
         case SurfaceTarget::Texture3D:
         case SurfaceTarget::Texture2DArray:
+        case SurfaceTarget::TextureCubeArray:
             glTexStorage3D(SurfaceTargetToGL(params.target), params.max_mip_level,
                            format_tuple.internal_format, rect.GetWidth(), rect.GetHeight(),
                            params.depth);
@@ -1065,6 +1076,7 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
                                    &gl_buffer[mip_map][buffer_offset]);
             break;
         case SurfaceTarget::Texture2DArray:
+        case SurfaceTarget::TextureCubeArray:
             glCompressedTexImage3D(SurfaceTargetToGL(params.target), mip_map, tuple.internal_format,
                                    static_cast<GLsizei>(params.MipWidth(mip_map)),
                                    static_cast<GLsizei>(params.MipHeight(mip_map)),
@@ -1114,6 +1126,7 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
                             tuple.format, tuple.type, &gl_buffer[mip_map][buffer_offset]);
             break;
         case SurfaceTarget::Texture2DArray:
+        case SurfaceTarget::TextureCubeArray:
             glTexSubImage3D(SurfaceTargetToGL(params.target), mip_map, x0, y0, 0,
                             static_cast<GLsizei>(rect.GetWidth()),
                             static_cast<GLsizei>(rect.GetHeight()), params.depth, tuple.format,
@@ -1316,6 +1329,7 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
         break;
     case SurfaceTarget::TextureCubemap:
     case SurfaceTarget::Texture3D:
+    case SurfaceTarget::TextureCubeArray:
         AccurateCopySurface(old_surface, new_surface);
         break;
     default:
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index 46ad37897c..c0b6bc4e6e 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -49,6 +49,8 @@ struct SurfaceParams {
             return "Texture2DArray";
         case SurfaceTarget::TextureCubemap:
             return "TextureCubemap";
+        case SurfaceTarget::TextureCubeArray:
+            return "TextureCubeArray";
         default:
             LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
             UNREACHABLE();
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index e284a4604e..051ad39649 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -19,6 +19,8 @@ SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_t
         return SurfaceTarget::Texture3D;
     case Tegra::Texture::TextureType::TextureCubemap:
         return SurfaceTarget::TextureCubemap;
+    case Tegra::Texture::TextureType::TextureCubeArray:
+        return SurfaceTarget::TextureCubeArray;
     case Tegra::Texture::TextureType::Texture1DArray:
         return SurfaceTarget::Texture1DArray;
     case Tegra::Texture::TextureType::Texture2DArray:
@@ -39,6 +41,7 @@ bool SurfaceTargetIsLayered(SurfaceTarget target) {
     case SurfaceTarget::Texture1DArray:
     case SurfaceTarget::Texture2DArray:
     case SurfaceTarget::TextureCubemap:
+    case SurfaceTarget::TextureCubeArray:
         return true;
     default:
         LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index 0ef7849a49..dfdb8d122b 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -120,6 +120,7 @@ enum class SurfaceTarget {
     Texture1DArray,
     Texture2DArray,
     TextureCubemap,
+    TextureCubeArray,
 };
 
 /**