diff --git a/Source/Core/VideoBackends/D3D/DXTexture.cpp b/Source/Core/VideoBackends/D3D/DXTexture.cpp
index 7a260eae4a..5d791b5246 100644
--- a/Source/Core/VideoBackends/D3D/DXTexture.cpp
+++ b/Source/Core/VideoBackends/D3D/DXTexture.cpp
@@ -45,9 +45,10 @@ std::unique_ptr<DXTexture> DXTexture::Create(const TextureConfig& config, std::s
   if (config.IsComputeImage())
     bindflags |= D3D11_BIND_UNORDERED_ACCESS;
 
-  CD3D11_TEXTURE2D_DESC desc(tex_format, config.width, config.height, config.layers, config.levels,
-                             bindflags, D3D11_USAGE_DEFAULT, 0, config.samples, 0,
-                             config.IsCubeMap() ? D3D11_RESOURCE_MISC_TEXTURECUBE : 0);
+  CD3D11_TEXTURE2D_DESC desc(
+      tex_format, config.width, config.height, config.layers, config.levels, bindflags,
+      D3D11_USAGE_DEFAULT, 0, config.samples, 0,
+      config.type == AbstractTextureType::Texture_CubeMap ? D3D11_RESOURCE_MISC_TEXTURECUBE : 0);
   ComPtr<ID3D11Texture2D> d3d_texture;
   HRESULT hr = D3D::device->CreateTexture2D(&desc, nullptr, d3d_texture.GetAddressOf());
   if (FAILED(hr))
@@ -72,7 +73,8 @@ std::unique_ptr<DXTexture> DXTexture::CreateAdopted(ComPtr<ID3D11Texture2D> text
   // Convert to our texture config format.
   TextureConfig config(desc.Width, desc.Height, desc.MipLevels, desc.ArraySize,
                        desc.SampleDesc.Count,
-                       D3DCommon::GetAbstractFormatForDXGIFormat(desc.Format), 0);
+                       D3DCommon::GetAbstractFormatForDXGIFormat(desc.Format), 0,
+                       AbstractTextureType::Texture_2DArray);
   if (desc.BindFlags & D3D11_BIND_RENDER_TARGET)
     config.flags |= AbstractTextureFlag_RenderTarget;
   if (desc.BindFlags & D3D11_BIND_UNORDERED_ACCESS)
@@ -89,13 +91,33 @@ std::unique_ptr<DXTexture> DXTexture::CreateAdopted(ComPtr<ID3D11Texture2D> text
 
 bool DXTexture::CreateSRV()
 {
+  D3D_SRV_DIMENSION dimension = D3D_SRV_DIMENSION_TEXTURE2DARRAY;
+  if (m_config.type == AbstractTextureType::Texture_2DArray)
+  {
+    if (m_config.IsMultisampled())
+      dimension = D3D_SRV_DIMENSION_TEXTURE2DMSARRAY;
+    else
+      dimension = D3D_SRV_DIMENSION_TEXTURE2DARRAY;
+  }
+  else if (m_config.type == AbstractTextureType::Texture_2D)
+  {
+    if (m_config.IsMultisampled())
+      dimension = D3D_SRV_DIMENSION_TEXTURE2DMS;
+    else
+      dimension = D3D_SRV_DIMENSION_TEXTURE2D;
+  }
+  else if (m_config.type == AbstractTextureType::Texture_CubeMap)
+  {
+    dimension = D3D_SRV_DIMENSION_TEXTURECUBE;
+  }
+  else
+  {
+    PanicAlertFmt("Failed to create D3D SRV - unhandled type");
+    return false;
+  }
   const CD3D11_SHADER_RESOURCE_VIEW_DESC desc(
-      m_texture.Get(),
-      m_config.IsCubeMap()      ? D3D11_SRV_DIMENSION_TEXTURECUBE :
-      m_config.IsMultisampled() ? D3D11_SRV_DIMENSION_TEXTURE2DMSARRAY :
-                                  D3D11_SRV_DIMENSION_TEXTURE2DARRAY,
-      D3DCommon::GetSRVFormatForAbstractFormat(m_config.format), 0, m_config.levels, 0,
-      m_config.layers);
+      m_texture.Get(), dimension, D3DCommon::GetSRVFormatForAbstractFormat(m_config.format), 0,
+      m_config.levels, 0, m_config.layers);
   DEBUG_ASSERT(!m_srv);
   HRESULT hr = D3D::device->CreateShaderResourceView(m_texture.Get(), &desc, m_srv.GetAddressOf());
   if (FAILED(hr))
diff --git a/Source/Core/VideoBackends/D3D12/DX12Texture.cpp b/Source/Core/VideoBackends/D3D12/DX12Texture.cpp
index 400867d458..4a1faf6f97 100644
--- a/Source/Core/VideoBackends/D3D12/DX12Texture.cpp
+++ b/Source/Core/VideoBackends/D3D12/DX12Texture.cpp
@@ -140,7 +140,8 @@ std::unique_ptr<DXTexture> DXTexture::CreateAdopted(ID3D12Resource* resource)
   }
 
   TextureConfig config(static_cast<u32>(desc.Width), desc.Height, desc.MipLevels,
-                       desc.DepthOrArraySize, desc.SampleDesc.Count, format, 0);
+                       desc.DepthOrArraySize, desc.SampleDesc.Count, format, 0,
+                       AbstractTextureType::Texture_2DArray);
   if (desc.Flags &
       (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL))
   {
@@ -165,14 +166,34 @@ bool DXTexture::CreateSRVDescriptor()
     return false;
   }
 
-  D3D12_SHADER_RESOURCE_VIEW_DESC desc = {
-      D3DCommon::GetSRVFormatForAbstractFormat(m_config.format),
-      m_config.IsCubeMap()      ? D3D12_SRV_DIMENSION_TEXTURECUBE :
-      m_config.IsMultisampled() ? D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY :
-                                  D3D12_SRV_DIMENSION_TEXTURE2DARRAY,
-      D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING};
+  D3D12_SRV_DIMENSION dimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
+  if (m_config.type == AbstractTextureType::Texture_2DArray)
+  {
+    if (m_config.IsMultisampled())
+      dimension = D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY;
+    else
+      dimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
+  }
+  else if (m_config.type == AbstractTextureType::Texture_2D)
+  {
+    if (m_config.IsMultisampled())
+      dimension = D3D12_SRV_DIMENSION_TEXTURE2DMS;
+    else
+      dimension = D3D12_SRV_DIMENSION_TEXTURE2D;
+  }
+  else if (m_config.type == AbstractTextureType::Texture_CubeMap)
+  {
+    dimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
+  }
+  else
+  {
+    PanicAlertFmt("Failed to allocate SRV - unhandled type");
+    return false;
+  }
+  D3D12_SHADER_RESOURCE_VIEW_DESC desc = {D3DCommon::GetSRVFormatForAbstractFormat(m_config.format),
+                                          dimension, D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING};
 
-  if (m_config.IsCubeMap())
+  if (m_config.type == AbstractTextureType::Texture_CubeMap)
   {
     desc.TextureCube.MostDetailedMip = 0;
     desc.TextureCube.MipLevels = m_config.levels;
diff --git a/Source/Core/VideoBackends/Metal/MTLGfx.mm b/Source/Core/VideoBackends/Metal/MTLGfx.mm
index acc79683ef..d594117de5 100644
--- a/Source/Core/VideoBackends/Metal/MTLGfx.mm
+++ b/Source/Core/VideoBackends/Metal/MTLGfx.mm
@@ -36,14 +36,29 @@ bool Metal::Gfx::IsHeadless() const
 
 // MARK: Texture Creation
 
+static MTLTextureType FromAbstract(AbstractTextureType type, bool multisample)
+{
+  switch (type)
+  {
+  case AbstractTextureType::Texture_2D:
+    return multisample ? MTLTextureType2DMultisample : MTLTextureType2D;
+  case AbstractTextureType::Texture_2DArray:
+    return multisample ? MTLTextureType2DMultisampleArray : MTLTextureType2DArray;
+  case AbstractTextureType::Texture_CubeMap:
+    return MTLTextureTypeCube;
+  }
+
+  ASSERT(false);
+  return MTLTextureType2DArray;
+}
+
 std::unique_ptr<AbstractTexture> Metal::Gfx::CreateTexture(const TextureConfig& config,
                                                            std::string_view name)
 {
   @autoreleasepool
   {
     MRCOwned<MTLTextureDescriptor*> desc = MRCTransfer([MTLTextureDescriptor new]);
-    [desc setTextureType:config.samples > 1 ? MTLTextureType2DMultisampleArray :
-                                              MTLTextureType2DArray];
+    [desc setTextureType:FromAbstract(config.type, config.samples > 1)];
     [desc setPixelFormat:Util::FromAbstract(config.format)];
     [desc setWidth:config.width];
     [desc setHeight:config.height];
@@ -490,8 +505,8 @@ void Metal::Gfx::SetupSurface()
 
   [m_layer setDrawableSize:{static_cast<double>(info.width), static_cast<double>(info.height)}];
 
-  TextureConfig cfg(info.width, info.height, 1, 1, 1, info.format,
-                    AbstractTextureFlag_RenderTarget);
+  TextureConfig cfg(info.width, info.height, 1, 1, 1, info.format, AbstractTextureFlag_RenderTarget,
+                    AbstractTextureType::Texture_2DArray);
   m_bb_texture = std::make_unique<Texture>(nullptr, cfg);
   m_backbuffer = std::make_unique<Framebuffer>(
       m_bb_texture.get(), nullptr, std::vector<AbstractTexture*>{}, info.width, info.height, 1, 1);
diff --git a/Source/Core/VideoBackends/OGL/OGLTexture.cpp b/Source/Core/VideoBackends/OGL/OGLTexture.cpp
index 70c38d14ff..58f1b10a04 100644
--- a/Source/Core/VideoBackends/OGL/OGLTexture.cpp
+++ b/Source/Core/VideoBackends/OGL/OGLTexture.cpp
@@ -140,29 +140,61 @@ OGLTexture::OGLTexture(const TextureConfig& tex_config, std::string_view name)
   glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, m_config.levels - 1);
 
   GLenum gl_internal_format = GetGLInternalFormatForTextureFormat(m_config.format, true);
-  if (g_ogl_config.bSupportsTextureStorage && m_config.IsCubeMap())
+  if (g_ogl_config.bSupportsTextureStorage && m_config.type == AbstractTextureType::Texture_CubeMap)
   {
     glTexStorage2D(target, m_config.levels, gl_internal_format, m_config.width, m_config.height);
   }
   else if (tex_config.IsMultisampled())
   {
     ASSERT(g_ogl_config.bSupportsMSAA);
-    if (g_ogl_config.SupportedMultisampleTexStorage != MultisampleTexStorageType::TexStorageNone)
+    if (m_config.type == AbstractTextureType::Texture_2DArray)
     {
-      glTexStorage3DMultisample(target, tex_config.samples, gl_internal_format, m_config.width,
+      if (g_ogl_config.SupportedMultisampleTexStorage != MultisampleTexStorageType::TexStorageNone)
+      {
+        glTexStorage3DMultisample(target, tex_config.samples, gl_internal_format, m_config.width,
+                                  m_config.height, m_config.layers, GL_FALSE);
+      }
+      else
+      {
+        ASSERT(!g_ogl_config.bIsES);
+        glTexImage3DMultisample(target, tex_config.samples, gl_internal_format, m_config.width,
                                 m_config.height, m_config.layers, GL_FALSE);
+      }
+    }
+    else if (m_config.type == AbstractTextureType::Texture_2D)
+    {
+      if (g_ogl_config.SupportedMultisampleTexStorage != MultisampleTexStorageType::TexStorageNone)
+      {
+        glTexStorage2DMultisample(target, tex_config.samples, gl_internal_format, m_config.width,
+                                  m_config.height, GL_FALSE);
+      }
+      else
+      {
+        ASSERT(!g_ogl_config.bIsES);
+        glTexImage2DMultisample(target, tex_config.samples, gl_internal_format, m_config.width,
+                                m_config.height, GL_FALSE);
+      }
     }
     else
     {
-      ASSERT(!g_ogl_config.bIsES);
-      glTexImage3DMultisample(target, tex_config.samples, gl_internal_format, m_config.width,
-                              m_config.height, m_config.layers, GL_FALSE);
+      ASSERT(false);
     }
   }
   else if (g_ogl_config.bSupportsTextureStorage)
   {
-    glTexStorage3D(target, m_config.levels, gl_internal_format, m_config.width, m_config.height,
-                   m_config.layers);
+    if (m_config.type == AbstractTextureType::Texture_2DArray)
+    {
+      glTexStorage3D(target, m_config.levels, gl_internal_format, m_config.width, m_config.height,
+                     m_config.layers);
+    }
+    else if (m_config.type == AbstractTextureType::Texture_2D)
+    {
+      glTexStorage2D(target, m_config.levels, gl_internal_format, m_config.width, m_config.height);
+    }
+    else
+    {
+      ASSERT(false);
+    }
   }
 
   if (m_config.IsRenderTarget())
@@ -271,7 +303,7 @@ void OGLTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8
   GLenum gl_internal_format = GetGLInternalFormatForTextureFormat(m_config.format, false);
   if (IsCompressedFormat(m_config.format))
   {
-    if (m_config.IsCubeMap())
+    if (m_config.type == AbstractTextureType::Texture_CubeMap)
     {
       if (g_ogl_config.bSupportsTextureStorage)
       {
@@ -285,7 +317,20 @@ void OGLTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8
                                width, height, 0, static_cast<GLsizei>(buffer_size), buffer);
       }
     }
-    else
+    else if (m_config.type == AbstractTextureType::Texture_2D)
+    {
+      if (g_ogl_config.bSupportsTextureStorage)
+      {
+        glCompressedTexSubImage2D(target, level, 0, 0, width, height, gl_internal_format,
+                                  static_cast<GLsizei>(buffer_size), buffer);
+      }
+      else
+      {
+        glCompressedTexImage2D(target, level, gl_internal_format, width, height, 0,
+                               static_cast<GLsizei>(buffer_size), buffer);
+      }
+    }
+    else if (m_config.type == AbstractTextureType::Texture_2DArray)
     {
       if (g_ogl_config.bSupportsTextureStorage)
       {
@@ -298,12 +343,16 @@ void OGLTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8
                                static_cast<GLsizei>(buffer_size), buffer);
       }
     }
+    else
+    {
+      PanicAlertFmt("Failed to handle compressed texture load - unhandled type");
+    }
   }
   else
   {
     GLenum gl_format = GetGLFormatForTextureFormat(m_config.format);
     GLenum gl_type = GetGLTypeForTextureFormat(m_config.format);
-    if (m_config.IsCubeMap())
+    if (m_config.type == AbstractTextureType::Texture_CubeMap)
     {
       if (g_ogl_config.bSupportsTextureStorage)
       {
@@ -316,7 +365,19 @@ void OGLTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8
                      height, 0, gl_format, gl_type, buffer);
       }
     }
-    else
+    else if (m_config.type == AbstractTextureType::Texture_2D)
+    {
+      if (g_ogl_config.bSupportsTextureStorage)
+      {
+        glTexSubImage2D(target, level, 0, 0, width, height, gl_format, gl_type, buffer);
+      }
+      else
+      {
+        glTexImage2D(target, level, gl_internal_format, width, height, 0, gl_format, gl_type,
+                     buffer);
+      }
+    }
+    else if (m_config.type == AbstractTextureType::Texture_2DArray)
     {
       if (g_ogl_config.bSupportsTextureStorage)
       {
@@ -328,6 +389,10 @@ void OGLTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8
                      buffer);
       }
     }
+    else
+    {
+      PanicAlertFmt("Failed to handle texture load - unhandled type");
+    }
   }
 
   if (row_length != width)
diff --git a/Source/Core/VideoBackends/OGL/OGLTexture.h b/Source/Core/VideoBackends/OGL/OGLTexture.h
index 91879a6564..eea458c5a1 100644
--- a/Source/Core/VideoBackends/OGL/OGLTexture.h
+++ b/Source/Core/VideoBackends/OGL/OGLTexture.h
@@ -34,9 +34,26 @@ public:
   GLuint GetGLTextureId() const { return m_texId; }
   GLenum GetGLTarget() const
   {
-    return m_config.IsCubeMap() ? GL_TEXTURE_CUBE_MAP :
-           IsMultisampled()     ? GL_TEXTURE_2D_MULTISAMPLE_ARRAY :
-                                  GL_TEXTURE_2D_ARRAY;
+    if (m_config.type == AbstractTextureType::Texture_2DArray)
+    {
+      if (m_config.IsMultisampled())
+        return GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
+      else
+        return GL_TEXTURE_2D_ARRAY;
+    }
+    else if (m_config.type == AbstractTextureType::Texture_2D)
+    {
+      if (m_config.IsMultisampled())
+        return GL_TEXTURE_2D_MULTISAMPLE;
+      else
+        return GL_TEXTURE_2D;
+    }
+    else if (m_config.type == AbstractTextureType::Texture_CubeMap)
+    {
+      return GL_TEXTURE_CUBE_MAP;
+    }
+
+    return GL_TEXTURE_2D_ARRAY;
   }
   static GLenum GetGLInternalFormatForTextureFormat(AbstractTextureFormat format, bool storage);
   GLenum GetGLFormatForImageTexture() const;
diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp
index 4afd627614..332cd41cbf 100644
--- a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp
+++ b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp
@@ -60,15 +60,17 @@ void StateTracker::DestroyInstance()
 bool StateTracker::Initialize()
 {
   // Create a dummy texture which can be used in place of a real binding.
-  m_dummy_texture =
-      VKTexture::Create(TextureConfig(1, 1, 1, 1, 1, AbstractTextureFormat::RGBA8, 0), "");
+  m_dummy_texture = VKTexture::Create(TextureConfig(1, 1, 1, 1, 1, AbstractTextureFormat::RGBA8, 0,
+                                                    AbstractTextureType::Texture_2DArray),
+                                      "");
   if (!m_dummy_texture)
     return false;
   m_dummy_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
                                       VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
   // Create a dummy compute texture which can be used in place of a real binding
   m_dummy_compute_texture = VKTexture::Create(
-      TextureConfig(1, 1, 1, 1, 1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_ComputeImage),
+      TextureConfig(1, 1, 1, 1, 1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_ComputeImage,
+                    AbstractTextureType::Texture_2DArray),
       "");
   if (!m_dummy_compute_texture)
     return false;
diff --git a/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp b/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp
index 48102df64d..58e2e439e8 100644
--- a/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp
+++ b/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp
@@ -428,8 +428,9 @@ bool SwapChain::SetupSwapChainImages()
                                 images.data());
   ASSERT(res == VK_SUCCESS);
 
-  const TextureConfig texture_config(TextureConfig(
-      m_width, m_height, 1, m_layers, 1, m_texture_format, AbstractTextureFlag_RenderTarget));
+  const TextureConfig texture_config(
+      TextureConfig(m_width, m_height, 1, m_layers, 1, m_texture_format,
+                    AbstractTextureFlag_RenderTarget, AbstractTextureType::Texture_2DArray));
   const VkRenderPass load_render_pass = g_object_cache->GetRenderPass(
       m_surface_format.format, VK_FORMAT_UNDEFINED, 1, VK_ATTACHMENT_LOAD_OP_LOAD);
   const VkRenderPass clear_render_pass = g_object_cache->GetRenderPass(
diff --git a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp
index e80ce4f2f7..6756305d1e 100644
--- a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp
+++ b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp
@@ -70,8 +70,9 @@ std::unique_ptr<VKTexture> VKTexture::Create(const TextureConfig& tex_config, st
 
   VkImageCreateInfo image_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
                                   nullptr,
-                                  tex_config.IsCubeMap() ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT :
-                                                           static_cast<VkImageCreateFlags>(0),
+                                  tex_config.type == AbstractTextureType::Texture_CubeMap ?
+                                      VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT :
+                                      static_cast<VkImageCreateFlags>(0),
                                   VK_IMAGE_TYPE_2D,
                                   GetVkFormatForHostTextureFormat(tex_config.format),
                                   {tex_config.width, tex_config.height, 1},
@@ -107,8 +108,26 @@ std::unique_ptr<VKTexture> VKTexture::Create(const TextureConfig& tex_config, st
 
   std::unique_ptr<VKTexture> texture = std::make_unique<VKTexture>(
       tex_config, alloc, image, name, VK_IMAGE_LAYOUT_UNDEFINED, ComputeImageLayout::Undefined);
-  if (!texture->CreateView(tex_config.IsCubeMap() ? VK_IMAGE_VIEW_TYPE_CUBE :
-                                                    VK_IMAGE_VIEW_TYPE_2D_ARRAY))
+
+  VkImageViewType image_view_type = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
+  if (tex_config.type == AbstractTextureType::Texture_CubeMap)
+  {
+    image_view_type = VK_IMAGE_VIEW_TYPE_CUBE;
+  }
+  else if (tex_config.type == AbstractTextureType::Texture_2D)
+  {
+    image_view_type = VK_IMAGE_VIEW_TYPE_2D;
+  }
+  else if (tex_config.type == AbstractTextureType::Texture_2DArray)
+  {
+    image_view_type = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
+  }
+  else
+  {
+    PanicAlertFmt("Unhandled texture type.");
+    return nullptr;
+  }
+  if (!texture->CreateView(image_view_type))
     return nullptr;
 
   return texture;
diff --git a/Source/Core/VideoCommon/AbstractTexture.cpp b/Source/Core/VideoCommon/AbstractTexture.cpp
index 416af06123..08eec74fa3 100644
--- a/Source/Core/VideoCommon/AbstractTexture.cpp
+++ b/Source/Core/VideoCommon/AbstractTexture.cpp
@@ -37,7 +37,8 @@ bool AbstractTexture::Save(const std::string& filename, unsigned int level, int
   // Use a temporary staging texture for the download. Certainly not optimal,
   // but this is not a frequently-executed code path..
   TextureConfig readback_texture_config(level_width, level_height, 1, 1, 1,
-                                        AbstractTextureFormat::RGBA8, 0);
+                                        AbstractTextureFormat::RGBA8, 0,
+                                        AbstractTextureType::Texture_2DArray);
   auto readback_texture =
       g_gfx->CreateStagingTexture(StagingTextureType::Readback, readback_texture_config);
   if (!readback_texture)
diff --git a/Source/Core/VideoCommon/FrameDumper.cpp b/Source/Core/VideoCommon/FrameDumper.cpp
index 06bbe1b1c9..d45d9933be 100644
--- a/Source/Core/VideoCommon/FrameDumper.cpp
+++ b/Source/Core/VideoCommon/FrameDumper.cpp
@@ -82,7 +82,7 @@ bool FrameDumper::CheckFrameDumpRenderTexture(u32 target_width, u32 target_heigh
   m_frame_dump_render_texture.reset();
   m_frame_dump_render_texture = g_gfx->CreateTexture(
       TextureConfig(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8,
-                    AbstractTextureFlag_RenderTarget),
+                    AbstractTextureFlag_RenderTarget, AbstractTextureType::Texture_2DArray),
       "Frame dump render texture");
   if (!m_frame_dump_render_texture)
   {
@@ -102,9 +102,10 @@ bool FrameDumper::CheckFrameDumpReadbackTexture(u32 target_width, u32 target_hei
     return true;
 
   rbtex.reset();
-  rbtex = g_gfx->CreateStagingTexture(
-      StagingTextureType::Readback,
-      TextureConfig(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0));
+  rbtex = g_gfx->CreateStagingTexture(StagingTextureType::Readback,
+                                      TextureConfig(target_width, target_height, 1, 1, 1,
+                                                    AbstractTextureFormat::RGBA8, 0,
+                                                    AbstractTextureType::Texture_2DArray));
   if (!rbtex)
     return false;
 
diff --git a/Source/Core/VideoCommon/FramebufferManager.cpp b/Source/Core/VideoCommon/FramebufferManager.cpp
index 4bdc3616a9..9790551afb 100644
--- a/Source/Core/VideoCommon/FramebufferManager.cpp
+++ b/Source/Core/VideoCommon/FramebufferManager.cpp
@@ -152,13 +152,15 @@ static u32 CalculateEFBLayers()
 TextureConfig FramebufferManager::GetEFBColorTextureConfig(u32 width, u32 height)
 {
   return TextureConfig(width, height, 1, CalculateEFBLayers(), g_ActiveConfig.iMultisamples,
-                       GetEFBColorFormat(), AbstractTextureFlag_RenderTarget);
+                       GetEFBColorFormat(), AbstractTextureFlag_RenderTarget,
+                       AbstractTextureType::Texture_2DArray);
 }
 
 TextureConfig FramebufferManager::GetEFBDepthTextureConfig(u32 width, u32 height)
 {
   return TextureConfig(width, height, 1, CalculateEFBLayers(), g_ActiveConfig.iMultisamples,
-                       GetEFBDepthFormat(), AbstractTextureFlag_RenderTarget);
+                       GetEFBDepthFormat(), AbstractTextureFlag_RenderTarget,
+                       AbstractTextureType::Texture_2DArray);
 }
 
 FramebufferState FramebufferManager::GetEFBFramebufferState() const
@@ -254,7 +256,8 @@ bool FramebufferManager::CreateEFBFramebuffer()
       flags |= AbstractTextureFlag_RenderTarget;
     m_efb_resolve_color_texture = g_gfx->CreateTexture(
         TextureConfig(efb_color_texture_config.width, efb_color_texture_config.height, 1,
-                      efb_color_texture_config.layers, 1, efb_color_texture_config.format, flags),
+                      efb_color_texture_config.layers, 1, efb_color_texture_config.format, flags,
+                      AbstractTextureType::Texture_2DArray),
         "EFB color resolve texture");
     if (!m_efb_resolve_color_texture)
       return false;
@@ -274,7 +277,7 @@ bool FramebufferManager::CreateEFBFramebuffer()
     m_efb_depth_resolve_texture = g_gfx->CreateTexture(
         TextureConfig(efb_depth_texture_config.width, efb_depth_texture_config.height, 1,
                       efb_depth_texture_config.layers, 1, GetEFBDepthCopyFormat(),
-                      AbstractTextureFlag_RenderTarget),
+                      AbstractTextureFlag_RenderTarget, AbstractTextureType::Texture_2DArray),
         "EFB depth resolve texture");
     if (!m_efb_depth_resolve_texture)
       return false;
@@ -695,7 +698,8 @@ bool FramebufferManager::CreateReadbackFramebuffer()
   {
     const TextureConfig color_config(IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_WIDTH,
                                      IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_HEIGHT, 1,
-                                     1, 1, GetEFBColorFormat(), AbstractTextureFlag_RenderTarget);
+                                     1, 1, GetEFBColorFormat(), AbstractTextureFlag_RenderTarget,
+                                     AbstractTextureType::Texture_2DArray);
     m_efb_color_cache.texture = g_gfx->CreateTexture(color_config, "EFB color cache");
     if (!m_efb_color_cache.texture)
       return false;
@@ -717,7 +721,8 @@ bool FramebufferManager::CreateReadbackFramebuffer()
     const TextureConfig depth_config(IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_WIDTH,
                                      IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_HEIGHT, 1,
                                      1, 1, GetEFBDepthCopyFormat(),
-                                     AbstractTextureFlag_RenderTarget);
+                                     AbstractTextureFlag_RenderTarget,
+                                     AbstractTextureType::Texture_2DArray);
     m_efb_depth_cache.texture = g_gfx->CreateTexture(depth_config, "EFB depth cache");
     if (!m_efb_depth_cache.texture)
       return false;
@@ -729,12 +734,14 @@ bool FramebufferManager::CreateReadbackFramebuffer()
   }
 
   // Staging texture use the full EFB dimensions, as this is the buffer for the whole cache.
-  m_efb_color_cache.readback_texture = g_gfx->CreateStagingTexture(
-      StagingTextureType::Mutable,
-      TextureConfig(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, GetEFBColorFormat(), 0));
+  m_efb_color_cache.readback_texture =
+      g_gfx->CreateStagingTexture(StagingTextureType::Mutable,
+                                  TextureConfig(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, GetEFBColorFormat(),
+                                                0, AbstractTextureType::Texture_2DArray));
   m_efb_depth_cache.readback_texture = g_gfx->CreateStagingTexture(
       StagingTextureType::Mutable,
-      TextureConfig(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, GetEFBDepthCopyFormat(), 0));
+      TextureConfig(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, GetEFBDepthCopyFormat(), 0,
+                    AbstractTextureType::Texture_2DArray));
   if (!m_efb_color_cache.readback_texture || !m_efb_depth_cache.readback_texture)
     return false;
 
@@ -1116,14 +1123,15 @@ void FramebufferManager::DoSaveState(PointerWrap& p)
   AbstractTexture* depth_texture = ResolveEFBDepthTexture(m_efb_depth_texture->GetRect(), true);
 
   // We don't want to save these as rendertarget textures, just the data itself when deserializing.
-  const TextureConfig color_texture_config(color_texture->GetWidth(), color_texture->GetHeight(),
-                                           color_texture->GetLevels(), color_texture->GetLayers(),
-                                           1, GetEFBColorFormat(), 0);
+  const TextureConfig color_texture_config(
+      color_texture->GetWidth(), color_texture->GetHeight(), color_texture->GetLevels(),
+      color_texture->GetLayers(), 1, GetEFBColorFormat(), 0, AbstractTextureType::Texture_2DArray);
   g_texture_cache->SerializeTexture(color_texture, color_texture_config, p);
 
   const TextureConfig depth_texture_config(depth_texture->GetWidth(), depth_texture->GetHeight(),
                                            depth_texture->GetLevels(), depth_texture->GetLayers(),
-                                           1, GetEFBDepthCopyFormat(), 0);
+                                           1, GetEFBDepthCopyFormat(), 0,
+                                           AbstractTextureType::Texture_2DArray);
   g_texture_cache->SerializeTexture(depth_texture, depth_texture_config, p);
 }
 
diff --git a/Source/Core/VideoCommon/OnScreenDisplay.cpp b/Source/Core/VideoCommon/OnScreenDisplay.cpp
index 0e07dbc397..bfa8bc5321 100644
--- a/Source/Core/VideoCommon/OnScreenDisplay.cpp
+++ b/Source/Core/VideoCommon/OnScreenDisplay.cpp
@@ -90,7 +90,8 @@ static float DrawMessage(int index, Message& msg, const ImVec2& position, int ti
       {
         const u32 width = msg.icon->width;
         const u32 height = msg.icon->height;
-        TextureConfig tex_config(width, height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0);
+        TextureConfig tex_config(width, height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0,
+                                 AbstractTextureType::Texture_2DArray);
         msg.texture = g_gfx->CreateTexture(tex_config);
         if (msg.texture)
         {
diff --git a/Source/Core/VideoCommon/OnScreenUI.cpp b/Source/Core/VideoCommon/OnScreenUI.cpp
index 88141736e4..fdb5d49af7 100644
--- a/Source/Core/VideoCommon/OnScreenUI.cpp
+++ b/Source/Core/VideoCommon/OnScreenUI.cpp
@@ -77,7 +77,8 @@ bool OnScreenUI::Initialize(u32 width, u32 height, float scale)
     io.Fonts->GetTexDataAsRGBA32(&font_tex_pixels, &font_tex_width, &font_tex_height);
 
     TextureConfig font_tex_config(font_tex_width, font_tex_height, 1, 1, 1,
-                                  AbstractTextureFormat::RGBA8, 0);
+                                  AbstractTextureFormat::RGBA8, 0,
+                                  AbstractTextureType::Texture_2DArray);
     std::unique_ptr<AbstractTexture> font_tex =
         g_gfx->CreateTexture(font_tex_config, "ImGui font texture");
     if (!font_tex)
@@ -362,7 +363,8 @@ void OnScreenUI::DrawChallenges()
         continue;
       const u32 width = icon->width;
       const u32 height = icon->height;
-      TextureConfig tex_config(width, height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0);
+      TextureConfig tex_config(width, height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0,
+                               AbstractTextureType::Texture_2DArray);
       auto res = m_challenge_texture_map.insert_or_assign(name, g_gfx->CreateTexture(tex_config));
       res.first->second->Load(0, width, height, width, icon->rgba_data.data(),
                               sizeof(u32) * width * height);
diff --git a/Source/Core/VideoCommon/PostProcessing.cpp b/Source/Core/VideoCommon/PostProcessing.cpp
index 615d853a45..1458e3c59f 100644
--- a/Source/Core/VideoCommon/PostProcessing.cpp
+++ b/Source/Core/VideoCommon/PostProcessing.cpp
@@ -530,7 +530,8 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
     {
       const TextureConfig intermediary_color_texture_config(
           target_width, target_height, 1, target_layers, src_tex->GetSamples(),
-          s_intermediary_buffer_format, AbstractTextureFlag_RenderTarget);
+          s_intermediary_buffer_format, AbstractTextureFlag_RenderTarget,
+          AbstractTextureType::Texture_2DArray);
       m_intermediary_color_texture = g_gfx->CreateTexture(intermediary_color_texture_config,
                                                           "Intermediary post process texture");
 
diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp
index 2c749c7703..9294748397 100644
--- a/Source/Core/VideoCommon/TextureCacheBase.cpp
+++ b/Source/Core/VideoCommon/TextureCacheBase.cpp
@@ -415,7 +415,8 @@ void TextureCacheBase::ScaleTextureCacheEntryTo(RcTcacheEntry& entry, u32 new_wi
   }
 
   const TextureConfig newconfig(new_width, new_height, 1, entry->GetNumLayers(), 1,
-                                AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget);
+                                AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget,
+                                AbstractTextureType::Texture_2DArray);
   std::optional<TexPoolEntry> new_texture = AllocateTexture(newconfig);
   if (!new_texture)
   {
@@ -445,7 +446,8 @@ bool TextureCacheBase::CheckReadbackTexture(u32 width, u32 height, AbstractTextu
     return true;
   }
 
-  TextureConfig staging_config(std::max(width, 128u), std::max(height, 128u), 1, 1, 1, format, 0);
+  TextureConfig staging_config(std::max(width, 128u), std::max(height, 128u), 1, 1, 1, format, 0,
+                               AbstractTextureType::Texture_2DArray);
   m_readback_texture.reset();
   m_readback_texture = g_gfx->CreateStagingTexture(StagingTextureType::Readback, staging_config);
   return m_readback_texture != nullptr;
@@ -1680,7 +1682,8 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry(
     const u32 texLevels = no_mips ? 1 : (u32)calculate_max_levels();
     const auto& first_level = assets_data[0]->m_texture.m_slices[0].m_levels[0];
     const TextureConfig config(first_level.width, first_level.height, texLevels,
-                               static_cast<u32>(assets_data.size()), 1, first_level.format, 0);
+                               static_cast<u32>(assets_data.size()), 1, first_level.format, 0,
+                               AbstractTextureType::Texture_2DArray);
     entry = AllocateCacheEntry(config);
     if (!entry) [[unlikely]]
       return entry;
@@ -1710,7 +1713,8 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry(
     const u32 width = texture_info.GetRawWidth();
     const u32 height = texture_info.GetRawHeight();
 
-    const TextureConfig config(width, height, texLevels, 1, 1, AbstractTextureFormat::RGBA8, 0);
+    const TextureConfig config(width, height, texLevels, 1, 1, AbstractTextureFormat::RGBA8, 0,
+                               AbstractTextureType::Texture_2DArray);
     entry = AllocateCacheEntry(config);
     if (!entry) [[unlikely]]
       return entry;
@@ -1896,7 +1900,8 @@ RcTcacheEntry TextureCacheBase::GetXFBTexture(u32 address, u32 width, u32 height
 
   // Create a new VRAM texture, and fill it with the data from guest RAM.
   entry = AllocateCacheEntry(TextureConfig(width, height, 1, 1, 1, AbstractTextureFormat::RGBA8,
-                                           AbstractTextureFlag_RenderTarget));
+                                           AbstractTextureFlag_RenderTarget,
+                                           AbstractTextureType::Texture_2DArray));
 
   // Compute total texture size. XFB textures aren't tiled, so this is simple.
   const u32 total_size = height * stride;
@@ -2357,7 +2362,8 @@ void TextureCacheBase::CopyRenderTargetToTexture(
   {
     // create the texture
     const TextureConfig config(scaled_tex_w, scaled_tex_h, 1, g_framebuffer_manager->GetEFBLayers(),
-                               1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget);
+                               1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget,
+                               AbstractTextureType::Texture_2DArray);
     entry = AllocateCacheEntry(config);
     if (entry)
     {
@@ -2842,7 +2848,8 @@ void TextureCacheBase::ReleaseToPool(TCacheEntry* entry)
 bool TextureCacheBase::CreateUtilityTextures()
 {
   constexpr TextureConfig encoding_texture_config(
-      EFB_WIDTH * 4, 1024, 1, 1, 1, AbstractTextureFormat::BGRA8, AbstractTextureFlag_RenderTarget);
+      EFB_WIDTH * 4, 1024, 1, 1, 1, AbstractTextureFormat::BGRA8, AbstractTextureFlag_RenderTarget,
+      AbstractTextureType::Texture_2DArray);
   m_efb_encoding_texture = g_gfx->CreateTexture(encoding_texture_config, "EFB encoding texture");
   if (!m_efb_encoding_texture)
     return false;
@@ -2854,7 +2861,8 @@ bool TextureCacheBase::CreateUtilityTextures()
   if (g_ActiveConfig.backend_info.bSupportsGPUTextureDecoding)
   {
     constexpr TextureConfig decoding_texture_config(
-        1024, 1024, 1, 1, 1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_ComputeImage);
+        1024, 1024, 1, 1, 1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_ComputeImage,
+        AbstractTextureType::Texture_2DArray);
     m_decoding_texture =
         g_gfx->CreateTexture(decoding_texture_config, "GPU texture decoding texture");
     if (!m_decoding_texture)
diff --git a/Source/Core/VideoCommon/TextureConfig.h b/Source/Core/VideoCommon/TextureConfig.h
index 75d7387f17..462b3dddff 100644
--- a/Source/Core/VideoCommon/TextureConfig.h
+++ b/Source/Core/VideoCommon/TextureConfig.h
@@ -39,16 +39,22 @@ enum AbstractTextureFlag : u32
 {
   AbstractTextureFlag_RenderTarget = (1 << 0),  // Texture is used as a framebuffer.
   AbstractTextureFlag_ComputeImage = (1 << 1),  // Texture is used as a compute image.
-  AbstractTextureFlag_CubeMap = (1 << 2),       // Texture is used as a cube map.
+};
+
+enum class AbstractTextureType
+{
+  Texture_2DArray,  // Used as a 2D texture array
+  Texture_2D,       // Used as a normal 2D texture
+  Texture_CubeMap,  // Used as a cube map texture
 };
 
 struct TextureConfig
 {
   constexpr TextureConfig() = default;
   constexpr TextureConfig(u32 width_, u32 height_, u32 levels_, u32 layers_, u32 samples_,
-                          AbstractTextureFormat format_, u32 flags_)
+                          AbstractTextureFormat format_, u32 flags_, AbstractTextureType type_)
       : width(width_), height(height_), levels(levels_), layers(layers_), samples(samples_),
-        format(format_), flags(flags_)
+        format(format_), flags(flags_), type(type_)
   {
   }
 
@@ -62,7 +68,6 @@ struct TextureConfig
   bool IsMultisampled() const { return samples > 1; }
   bool IsRenderTarget() const { return (flags & AbstractTextureFlag_RenderTarget) != 0; }
   bool IsComputeImage() const { return (flags & AbstractTextureFlag_ComputeImage) != 0; }
-  bool IsCubeMap() const { return (flags & AbstractTextureFlag_CubeMap) != 0; }
 
   u32 width = 0;
   u32 height = 0;
@@ -71,6 +76,7 @@ struct TextureConfig
   u32 samples = 1;
   AbstractTextureFormat format = AbstractTextureFormat::RGBA8;
   u32 flags = 0;
+  AbstractTextureType type = AbstractTextureType::Texture_2DArray;
 };
 
 namespace std