diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 2b29fc45f74cd04a56674da084d53b562284b265..089daf96f1546cf15f770f3e646b2ee24ef4a080 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -1014,8 +1014,11 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
         texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc);
         Surface surface = res_cache.GetTextureSurface(texture, entry);
         if (surface != nullptr) {
-            state.texture_units[current_bindpoint].texture = surface->Texture().handle;
-            state.texture_units[current_bindpoint].target = surface->Target();
+            const GLuint handle =
+                entry.IsArray() ? surface->TextureLayer().handle : surface->Texture().handle;
+            const GLenum target = entry.IsArray() ? surface->TargetLayer() : surface->Target();
+            state.texture_units[current_bindpoint].texture = handle;
+            state.texture_units[current_bindpoint].target = target;
             state.texture_units[current_bindpoint].swizzle.r =
                 MaxwellToGL::SwizzleSource(texture.tic.x_source);
             state.texture_units[current_bindpoint].swizzle.g =
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 75b4fe88dc9dbc4796af397e91d80eb123f88472..d3dcb9a46bc4552833eae3e56dcabfff00497804 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -44,6 +44,17 @@ struct FormatTuple {
     bool compressed;
 };
 
+static void ApplyTextureDefaults(GLenum target, u32 max_mip_level) {
+    glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, max_mip_level - 1);
+    if (max_mip_level == 1) {
+        glTexParameterf(target, GL_TEXTURE_LOD_BIAS, 1000.0);
+    }
+}
+
 void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
     auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
     const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr_)};
@@ -530,6 +541,9 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
     glActiveTexture(GL_TEXTURE0);
 
     const auto& format_tuple = GetFormatTuple(params.pixel_format, params.component_type);
+    gl_internal_format = format_tuple.internal_format;
+    gl_is_compressed = format_tuple.compressed;
+
     if (!format_tuple.compressed) {
         // Only pre-create the texture for non-compressed textures.
         switch (params.target) {
@@ -558,15 +572,7 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
         }
     }
 
-    glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-    glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-    glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-    glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MAX_LEVEL,
-                    params.max_mip_level - 1);
-    if (params.max_mip_level == 1) {
-        glTexParameterf(SurfaceTargetToGL(params.target), GL_TEXTURE_LOD_BIAS, 1000.0);
-    }
+    ApplyTextureDefaults(SurfaceTargetToGL(params.target), params.max_mip_level);
 
     LabelGLObject(GL_TEXTURE, texture.handle, params.addr,
                   SurfaceParams::SurfaceTargetName(params.target));
@@ -864,6 +870,31 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
     glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
 }
 
+void CachedSurface::EnsureTextureView() {
+    if (texture_view.handle != 0)
+        return;
+    // Compressed texture are not being created with immutable storage
+    UNIMPLEMENTED_IF(gl_is_compressed);
+
+    const GLenum target{TargetLayer()};
+
+    texture_view.Create();
+    glTextureView(texture_view.handle, target, texture.handle, gl_internal_format, 0,
+                  params.max_mip_level, 0, 1);
+
+    OpenGLState cur_state = OpenGLState::GetCurState();
+    const auto& old_tex = cur_state.texture_units[0];
+    SCOPE_EXIT({
+        cur_state.texture_units[0] = old_tex;
+        cur_state.Apply();
+    });
+    cur_state.texture_units[0].texture = texture_view.handle;
+    cur_state.texture_units[0].target = target;
+    cur_state.Apply();
+
+    ApplyTextureDefaults(target, params.max_mip_level);
+}
+
 MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 192, 64));
 void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle) {
     if (params.type == SurfaceType::Fill)
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index c710aa245a5f89c7a407bbdd8b8a17ef1ad6d4b9..7223700c48c7f7f082cbaaadd59abe45fcf209f1 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -293,10 +293,31 @@ public:
         return texture;
     }
 
+    const OGLTexture& TextureLayer() {
+        if (params.is_layered) {
+            return Texture();
+        }
+        EnsureTextureView();
+        return texture_view;
+    }
+
     GLenum Target() const {
         return gl_target;
     }
 
+    GLenum TargetLayer() const {
+        using VideoCore::Surface::SurfaceTarget;
+        switch (params.target) {
+        case SurfaceTarget::Texture1D:
+            return GL_TEXTURE_1D_ARRAY;
+        case SurfaceTarget::Texture2D:
+            return GL_TEXTURE_2D_ARRAY;
+        case SurfaceTarget::TextureCubemap:
+            return GL_TEXTURE_CUBE_MAP_ARRAY;
+        }
+        return Target();
+    }
+
     const SurfaceParams& GetSurfaceParams() const {
         return params;
     }
@@ -311,11 +332,16 @@ public:
 private:
     void UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle, GLuint draw_fb_handle);
 
+    void EnsureTextureView();
+
     OGLTexture texture;
+    OGLTexture texture_view;
     std::vector<std::vector<u8>> gl_buffer;
-    SurfaceParams params;
-    GLenum gl_target;
-    std::size_t cached_size_in_bytes;
+    SurfaceParams params{};
+    GLenum gl_target{};
+    GLenum gl_internal_format{};
+    bool gl_is_compressed{};
+    std::size_t cached_size_in_bytes{};
 };
 
 class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {