ImageIndex: Consolidate layer/cube face.

In terms of the Texture or Image resource, a cube face
refers to a layer of a 2D texture. This layer has a special
meaning for cube textures, but it is represented as a layer
with a layer index. Cube array textures are no different,
they just use a different indexing scheme for the array
layers.

This also cleans up the ImageIndex helper to have a class
structure with private data, and cleans up a few cases to
use generic Make functions and iterators where they were
setting properties of the index directly.

This will make it easier to have ImageIndexes address
entire levels of a Cube map in the future, and makes the
layer count logic in Vulkan cleaner.

Bug: angleproject:2318
Change-Id: Iea9842e233f974a9896282ca224cb001f7882bd1
Reviewed-on: https://chromium-review.googlesource.com/987525
Reviewed-by: Luc Ferron <lucferron@chromium.org>
Reviewed-by: Yuly Novikov <ynovikov@chromium.org>
Commit-Queue: Jamie Madill <jmadill@chromium.org>
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index aea253a..4b4f347 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -3493,28 +3493,7 @@
     if (texture != 0)
     {
         Texture *textureObj = getTexture(texture);
-
-        ImageIndex index = ImageIndex::MakeInvalid();
-
-        if (textarget == TextureTarget::_2D)
-        {
-            index = ImageIndex::Make2D(level);
-        }
-        else if (textarget == TextureTarget::Rectangle)
-        {
-            index = ImageIndex::MakeRectangle(level);
-        }
-        else if (textarget == TextureTarget::_2DMultisample)
-        {
-            ASSERT(level == 0);
-            index = ImageIndex::Make2DMultisample();
-        }
-        else
-        {
-            ASSERT(TextureTargetToType(textarget) == TextureType::CubeMap);
-            index = ImageIndex::MakeCube(textarget, level);
-        }
-
+        ImageIndex index    = ImageIndex::MakeFromTarget(textarget, level);
         framebuffer->setAttachment(this, GL_TEXTURE, attachment, index, textureObj);
     }
     else
@@ -3537,7 +3516,7 @@
     {
         Renderbuffer *renderbufferObject = getRenderbuffer(renderbuffer);
 
-        framebuffer->setAttachment(this, GL_RENDERBUFFER, attachment, gl::ImageIndex::MakeInvalid(),
+        framebuffer->setAttachment(this, GL_RENDERBUFFER, attachment, gl::ImageIndex(),
                                    renderbufferObject);
     }
     else
@@ -3560,19 +3539,7 @@
     if (texture != 0)
     {
         Texture *textureObject = getTexture(texture);
-
-        ImageIndex index = ImageIndex::MakeInvalid();
-
-        if (textureObject->getType() == TextureType::_3D)
-        {
-            index = ImageIndex::Make3D(level, layer);
-        }
-        else
-        {
-            ASSERT(textureObject->getType() == TextureType::_2DArray);
-            index = ImageIndex::Make2DArray(level, layer);
-        }
-
+        ImageIndex index       = ImageIndex::MakeFromType(textureObject->getType(), level, layer);
         framebuffer->setAttachment(this, GL_TEXTURE, attachment, index, textureObject);
     }
     else
diff --git a/src/libANGLE/Framebuffer.cpp b/src/libANGLE/Framebuffer.cpp
index 666c0ff..f2ec3a6 100644
--- a/src/libANGLE/Framebuffer.cpp
+++ b/src/libANGLE/Framebuffer.cpp
@@ -146,8 +146,8 @@
 
         // ES3.1 (section 9.4) requires that the value of TEXTURE_FIXED_SAMPLE_LOCATIONS should be
         // the same for all attached textures.
-        bool fixedSampleloc = texture->getFixedSampleLocations(attachmentImageIndex.target,
-                                                               attachmentImageIndex.mipIndex);
+        bool fixedSampleloc = texture->getFixedSampleLocations(
+            attachmentImageIndex.getTarget(), attachmentImageIndex.getLevelIndex());
         if (fixedSampleLocations->valid() && fixedSampleloc != fixedSampleLocations->value())
         {
             return false;
@@ -642,16 +642,16 @@
 
     const Context *proxyContext = display->getProxyContext();
 
-    setAttachmentImpl(proxyContext, GL_FRAMEBUFFER_DEFAULT, GL_BACK, ImageIndex::MakeInvalid(),
-                      surface, FramebufferAttachment::kDefaultNumViews,
+    setAttachmentImpl(proxyContext, GL_FRAMEBUFFER_DEFAULT, GL_BACK, ImageIndex(), surface,
+                      FramebufferAttachment::kDefaultNumViews,
                       FramebufferAttachment::kDefaultBaseViewIndex,
                       FramebufferAttachment::kDefaultMultiviewLayout,
                       FramebufferAttachment::kDefaultViewportOffsets);
 
     if (surface->getConfig()->depthSize > 0)
     {
-        setAttachmentImpl(proxyContext, GL_FRAMEBUFFER_DEFAULT, GL_DEPTH, ImageIndex::MakeInvalid(),
-                          surface, FramebufferAttachment::kDefaultNumViews,
+        setAttachmentImpl(proxyContext, GL_FRAMEBUFFER_DEFAULT, GL_DEPTH, ImageIndex(), surface,
+                          FramebufferAttachment::kDefaultNumViews,
                           FramebufferAttachment::kDefaultBaseViewIndex,
                           FramebufferAttachment::kDefaultMultiviewLayout,
                           FramebufferAttachment::kDefaultViewportOffsets);
@@ -659,11 +659,11 @@
 
     if (surface->getConfig()->stencilSize > 0)
     {
-        setAttachmentImpl(
-            proxyContext, GL_FRAMEBUFFER_DEFAULT, GL_STENCIL, ImageIndex::MakeInvalid(), surface,
-            FramebufferAttachment::kDefaultNumViews, FramebufferAttachment::kDefaultBaseViewIndex,
-            FramebufferAttachment::kDefaultMultiviewLayout,
-            FramebufferAttachment::kDefaultViewportOffsets);
+        setAttachmentImpl(proxyContext, GL_FRAMEBUFFER_DEFAULT, GL_STENCIL, ImageIndex(), surface,
+                          FramebufferAttachment::kDefaultNumViews,
+                          FramebufferAttachment::kDefaultBaseViewIndex,
+                          FramebufferAttachment::kDefaultMultiviewLayout,
+                          FramebufferAttachment::kDefaultViewportOffsets);
     }
     mState.mDrawBufferTypeMask.setIndex(getDrawbufferWriteType(0), 0);
 }
@@ -1589,7 +1589,7 @@
         }
         else
         {
-            return ImageIndex::MakeInvalid();
+            return ImageIndex();
         }
     };
 
@@ -1599,14 +1599,14 @@
         setAttachmentImpl(context, depth.type(), GL_DEPTH_ATTACHMENT,
                           getImageIndexIfTextureAttachment(depth), depth.getResource(), numViews,
                           baseViewIndex, multiviewLayout, viewportOffsets);
-        setAttachmentImpl(context, GL_NONE, GL_STENCIL_ATTACHMENT, ImageIndex::MakeInvalid(),
-                          nullptr, numViews, baseViewIndex, multiviewLayout, viewportOffsets);
+        setAttachmentImpl(context, GL_NONE, GL_STENCIL_ATTACHMENT, ImageIndex(), nullptr, numViews,
+                          baseViewIndex, multiviewLayout, viewportOffsets);
     }
     else if (mState.mWebGLStencilAttachment.isAttached())
     {
         const auto &stencil = mState.mWebGLStencilAttachment;
-        setAttachmentImpl(context, GL_NONE, GL_DEPTH_ATTACHMENT, ImageIndex::MakeInvalid(), nullptr,
-                          numViews, baseViewIndex, multiviewLayout, viewportOffsets);
+        setAttachmentImpl(context, GL_NONE, GL_DEPTH_ATTACHMENT, ImageIndex(), nullptr, numViews,
+                          baseViewIndex, multiviewLayout, viewportOffsets);
         setAttachmentImpl(context, stencil.type(), GL_STENCIL_ATTACHMENT,
                           getImageIndexIfTextureAttachment(stencil), stencil.getResource(),
                           numViews, baseViewIndex, multiviewLayout, viewportOffsets);
@@ -1625,10 +1625,10 @@
     }
     else
     {
-        setAttachmentImpl(context, GL_NONE, GL_DEPTH_ATTACHMENT, ImageIndex::MakeInvalid(), nullptr,
-                          numViews, baseViewIndex, multiviewLayout, viewportOffsets);
-        setAttachmentImpl(context, GL_NONE, GL_STENCIL_ATTACHMENT, ImageIndex::MakeInvalid(),
-                          nullptr, numViews, baseViewIndex, multiviewLayout, viewportOffsets);
+        setAttachmentImpl(context, GL_NONE, GL_DEPTH_ATTACHMENT, ImageIndex(), nullptr, numViews,
+                          baseViewIndex, multiviewLayout, viewportOffsets);
+        setAttachmentImpl(context, GL_NONE, GL_STENCIL_ATTACHMENT, ImageIndex(), nullptr, numViews,
+                          baseViewIndex, multiviewLayout, viewportOffsets);
     }
 }
 
@@ -1736,7 +1736,7 @@
 
 void Framebuffer::resetAttachment(const Context *context, GLenum binding)
 {
-    setAttachment(context, GL_NONE, binding, ImageIndex::MakeInvalid(), nullptr);
+    setAttachment(context, GL_NONE, binding, ImageIndex(), nullptr);
 }
 
 Error Framebuffer::syncState(const Context *context)
@@ -1879,12 +1879,11 @@
     if (readAttachment->isTextureWithId(copyTextureID))
     {
         const auto &imageIndex = readAttachment->getTextureImageIndex();
-        if (imageIndex.mipIndex == copyTextureLevel)
+        if (imageIndex.getLevelIndex() == copyTextureLevel)
         {
             // Check 3D/Array texture layers.
-            return imageIndex.layerIndex == ImageIndex::ENTIRE_LEVEL ||
-                   copyTextureLayer == ImageIndex::ENTIRE_LEVEL ||
-                   imageIndex.layerIndex == copyTextureLayer;
+            return !imageIndex.hasLayer() || copyTextureLayer == ImageIndex::kEntireLevel ||
+                   imageIndex.getLayerIndex() == copyTextureLayer;
         }
     }
     return false;
diff --git a/src/libANGLE/Framebuffer.h b/src/libANGLE/Framebuffer.h
index 4e99a89..6342163 100644
--- a/src/libANGLE/Framebuffer.h
+++ b/src/libANGLE/Framebuffer.h
@@ -40,13 +40,13 @@
 class Context;
 class ContextState;
 class Framebuffer;
+class ImageIndex;
 class Renderbuffer;
 class State;
 class Texture;
 class TextureCapsMap;
 struct Caps;
 struct Extensions;
-struct ImageIndex;
 struct Rectangle;
 
 class FramebufferState final : angle::NonCopyable
diff --git a/src/libANGLE/FramebufferAttachment.cpp b/src/libANGLE/FramebufferAttachment.cpp
index baac201..917d259 100644
--- a/src/libANGLE/FramebufferAttachment.cpp
+++ b/src/libANGLE/FramebufferAttachment.cpp
@@ -53,9 +53,7 @@
         FramebufferAttachment::kDefaultViewportOffsets, FramebufferAttachment::kDefaultNumViews);
 }
 
-FramebufferAttachment::Target::Target()
-    : mBinding(GL_NONE),
-      mTextureIndex(ImageIndex::MakeInvalid())
+FramebufferAttachment::Target::Target() : mBinding(GL_NONE), mTextureIndex()
 {
 }
 
@@ -236,21 +234,21 @@
     ASSERT(mType == GL_TEXTURE);
 
     const auto &index = mTarget.textureIndex();
-    return index.type == TextureType::CubeMap ? index.target : TextureTarget::InvalidEnum;
+    return index.getType() == TextureType::CubeMap ? index.getTarget() : TextureTarget::InvalidEnum;
 }
 
 GLint FramebufferAttachment::mipLevel() const
 {
     ASSERT(type() == GL_TEXTURE);
-    return mTarget.textureIndex().mipIndex;
+    return mTarget.textureIndex().getLevelIndex();
 }
 
 GLint FramebufferAttachment::layer() const
 {
     ASSERT(mType == GL_TEXTURE);
 
-    const auto &index = mTarget.textureIndex();
-    return index.hasLayer() ? index.layerIndex : 0;
+    const gl::ImageIndex &index = mTarget.textureIndex();
+    return (index.has3DLayer() ? index.getLayerIndex() : 0);
 }
 
 GLsizei FramebufferAttachment::getNumViews() const
@@ -370,10 +368,10 @@
 
     // Because gl::Texture cannot support tracking individual layer dirtiness, we only handle
     // initializing entire mip levels for 2D array textures.
-    if (imageIndex.type == TextureType::_2DArray && imageIndex.hasLayer())
+    if (imageIndex.getType() == TextureType::_2DArray && imageIndex.hasLayer())
     {
-        ImageIndex fullMipIndex = imageIndex;
-        fullMipIndex.layerIndex = ImageIndex::ENTIRE_LEVEL;
+        ImageIndex fullMipIndex =
+            ImageIndex::Make2DArray(imageIndex.getLevelIndex(), ImageIndex::kEntireLevel);
         return getAttachmentImpl()->initializeContents(context, fullMipIndex);
     }
     else
diff --git a/src/libANGLE/Image.cpp b/src/libANGLE/Image.cpp
index e0e40b0..bfdb926 100644
--- a/src/libANGLE/Image.cpp
+++ b/src/libANGLE/Image.cpp
@@ -26,7 +26,7 @@
 {
     if (eglTarget == EGL_GL_RENDERBUFFER)
     {
-        return gl::ImageIndex::MakeInvalid();
+        return gl::ImageIndex();
     }
 
     gl::TextureTarget target = egl_gl::EGLImageTargetToTextureTarget(eglTarget);
@@ -40,7 +40,7 @@
     else
     {
         ASSERT(layer == 0);
-        return gl::ImageIndex::MakeGeneric(target, mip);
+        return gl::ImageIndex::MakeFromTarget(target, mip);
     }
 }
 }  // anonymous namespace
diff --git a/src/libANGLE/ImageIndex.cpp b/src/libANGLE/ImageIndex.cpp
index d46c0c5..251a41e 100644
--- a/src/libANGLE/ImageIndex.cpp
+++ b/src/libANGLE/ImageIndex.cpp
@@ -15,179 +15,242 @@
 
 namespace gl
 {
+namespace
+{
+GLint TextureTargetToLayer(TextureTarget target)
+{
+    switch (target)
+    {
+        case TextureTarget::CubeMapPositiveX:
+            return 0;
+        case TextureTarget::CubeMapNegativeX:
+            return 1;
+        case TextureTarget::CubeMapPositiveY:
+            return 2;
+        case TextureTarget::CubeMapNegativeY:
+            return 3;
+        case TextureTarget::CubeMapPositiveZ:
+            return 4;
+        case TextureTarget::CubeMapNegativeZ:
+            return 5;
+        case TextureTarget::External:
+            return ImageIndex::kEntireLevel;
+        case TextureTarget::Rectangle:
+            return ImageIndex::kEntireLevel;
+        case TextureTarget::_2D:
+            return ImageIndex::kEntireLevel;
+        case TextureTarget::_2DArray:
+            return ImageIndex::kEntireLevel;
+        case TextureTarget::_2DMultisample:
+            return ImageIndex::kEntireLevel;
+        case TextureTarget::_3D:
+            return ImageIndex::kEntireLevel;
+        default:
+            UNREACHABLE();
+            return 0;
+    }
+}
+
+TextureTarget TextureTypeToTarget(TextureType type, GLint layerIndex)
+{
+    if (type == TextureType::CubeMap)
+    {
+        return CubeFaceIndexToTextureTarget(layerIndex);
+    }
+    else
+    {
+        return NonCubeTextureTypeToTarget(type);
+    }
+}
+}  // anonymous namespace
+
+ImageIndex::ImageIndex()
+    : mType(TextureType::InvalidEnum), mLevelIndex(0), mLayerIndex(0), mLayerCount(kEntireLevel)
+{
+}
 
 ImageIndex::ImageIndex(const ImageIndex &other) = default;
+
 ImageIndex &ImageIndex::operator=(const ImageIndex &other) = default;
 
-bool ImageIndex::is3D() const
+bool ImageIndex::hasLayer() const
 {
-    return type == TextureType::_3D || type == TextureType::_2DArray;
+    return mLayerIndex != kEntireLevel;
+}
+
+bool ImageIndex::has3DLayer() const
+{
+    // It's quicker to check != CubeMap than calling usesTex3D, which checks multiple types. This
+    // ASSERT validates the check gives the same result.
+    ASSERT(!hasLayer() || (mType != TextureType::CubeMap == usesTex3D()));
+    return (hasLayer() && mType != TextureType::CubeMap);
+}
+
+bool ImageIndex::usesTex3D() const
+{
+    return mType == TextureType::_3D || mType == TextureType::_2DArray;
+}
+
+TextureTarget ImageIndex::getTarget() const
+{
+    return TextureTypeToTarget(mType, mLayerIndex);
 }
 
 GLint ImageIndex::cubeMapFaceIndex() const
 {
-    ASSERT(type == TextureType::CubeMap);
-    ASSERT(TextureTargetToType(target) == TextureType::CubeMap);
-    return static_cast<GLint>(CubeMapTextureTargetToFaceIndex(target));
+    ASSERT(mType == TextureType::CubeMap);
+    ASSERT(mLayerIndex == kEntireLevel || mLayerIndex < 6);
+    return mLayerIndex;
 }
 
 bool ImageIndex::valid() const
 {
-    return type != TextureType::InvalidEnum;
+    return mType != TextureType::InvalidEnum;
 }
 
-ImageIndex ImageIndex::Make2D(GLint mipIndex)
+ImageIndex ImageIndex::Make2D(GLint levelIndex)
 {
-    return ImageIndex(TextureType::_2D, TextureTarget::_2D, mipIndex, ENTIRE_LEVEL, 1);
+    return ImageIndex(TextureType::_2D, levelIndex, kEntireLevel, 1);
 }
 
-ImageIndex ImageIndex::MakeRectangle(GLint mipIndex)
+ImageIndex ImageIndex::MakeRectangle(GLint levelIndex)
 {
-    return ImageIndex(TextureType::Rectangle, TextureTarget::Rectangle, mipIndex, ENTIRE_LEVEL, 1);
+    return ImageIndex(TextureType::Rectangle, levelIndex, kEntireLevel, 1);
 }
 
-ImageIndex ImageIndex::MakeCube(TextureTarget target, GLint mipIndex)
+ImageIndex ImageIndex::MakeCube(TextureTarget target, GLint levelIndex)
 {
     ASSERT(TextureTargetToType(target) == TextureType::CubeMap);
-    return ImageIndex(TextureType::CubeMap, target, mipIndex, ENTIRE_LEVEL, 1);
+    return ImageIndex(TextureType::CubeMap, levelIndex, TextureTargetToLayer(target), 1);
 }
 
-ImageIndex ImageIndex::Make2DArray(GLint mipIndex, GLint layerIndex)
+ImageIndex ImageIndex::Make2DArray(GLint levelIndex, GLint layerIndex)
 {
-    return ImageIndex(TextureType::_2DArray, TextureTarget::_2DArray, mipIndex, layerIndex, 1);
+    return ImageIndex(TextureType::_2DArray, levelIndex, layerIndex, 1);
 }
 
-ImageIndex ImageIndex::Make2DArrayRange(GLint mipIndex, GLint layerIndex, GLint numLayers)
+ImageIndex ImageIndex::Make2DArrayRange(GLint levelIndex, GLint layerIndex, GLint numLayers)
 {
-    return ImageIndex(TextureType::_2DArray, TextureTarget::_2DArray, mipIndex, layerIndex,
-                      numLayers);
+    return ImageIndex(TextureType::_2DArray, levelIndex, layerIndex, numLayers);
 }
 
-ImageIndex ImageIndex::Make3D(GLint mipIndex, GLint layerIndex)
+ImageIndex ImageIndex::Make3D(GLint levelIndex, GLint layerIndex)
 {
-    return ImageIndex(TextureType::_3D, TextureTarget::_3D, mipIndex, layerIndex, 1);
+    return ImageIndex(TextureType::_3D, levelIndex, layerIndex, 1);
 }
 
-ImageIndex ImageIndex::MakeGeneric(TextureTarget target, GLint mipIndex)
+ImageIndex ImageIndex::MakeFromTarget(TextureTarget target, GLint levelIndex)
 {
-    return ImageIndex(TextureTargetToType(target), target, mipIndex, ENTIRE_LEVEL, 1);
+    return ImageIndex(TextureTargetToType(target), levelIndex, TextureTargetToLayer(target), 1);
+}
+
+ImageIndex ImageIndex::MakeFromType(TextureType type,
+                                    GLint levelIndex,
+                                    GLint layerIndex,
+                                    GLint layerCount)
+{
+    return ImageIndex(type, levelIndex, layerIndex, layerCount);
 }
 
 ImageIndex ImageIndex::Make2DMultisample()
 {
-    return ImageIndex(TextureType::_2DMultisample, TextureTarget::_2DMultisample, 0, ENTIRE_LEVEL,
-                      1);
+    return ImageIndex(TextureType::_2DMultisample, 0, kEntireLevel, 1);
 }
 
-ImageIndex ImageIndex::MakeInvalid()
+bool ImageIndex::operator<(const ImageIndex &b) const
 {
-    return ImageIndex(TextureType::InvalidEnum, TextureTarget::InvalidEnum, -1, -1, -1);
+    return std::tie(mType, mLevelIndex, mLayerIndex, mLayerCount) <
+           std::tie(b.mType, b.mLevelIndex, b.mLayerIndex, b.mLayerCount);
 }
 
-bool operator<(const ImageIndex &a, const ImageIndex &b)
+bool ImageIndex::operator==(const ImageIndex &b) const
 {
-    return std::tie(a.type, a.target, a.mipIndex, a.layerIndex, a.numLayers) <
-           std::tie(b.type, b.target, b.mipIndex, b.layerIndex, b.numLayers);
+    return std::tie(mType, mLevelIndex, mLayerIndex, mLayerCount) ==
+           std::tie(b.mType, b.mLevelIndex, b.mLayerIndex, b.mLayerCount);
 }
 
-bool operator==(const ImageIndex &a, const ImageIndex &b)
+bool ImageIndex::operator!=(const ImageIndex &b) const
 {
-    return std::tie(a.type, a.target, a.mipIndex, a.layerIndex, a.numLayers) ==
-           std::tie(b.type, b.target, b.mipIndex, b.layerIndex, b.numLayers);
+    return !(*this == b);
 }
 
-bool operator!=(const ImageIndex &a, const ImageIndex &b)
-{
-    return !(a == b);
-}
-
-ImageIndex::ImageIndex(TextureType typeIn,
-                       TextureTarget targetIn,
-                       GLint mipIndexIn,
-                       GLint layerIndexIn,
-                       GLint numLayersIn)
-    : type(typeIn),
-      target(targetIn),
-      mipIndex(mipIndexIn),
-      layerIndex(layerIndexIn),
-      numLayers(numLayersIn)
+ImageIndex::ImageIndex(TextureType type, GLint levelIndex, GLint layerIndex, GLint layerCount)
+    : mType(type), mLevelIndex(levelIndex), mLayerIndex(layerIndex), mLayerCount(layerCount)
 {}
 
+ImageIndexIterator ImageIndex::getLayerIterator(GLint layerCount) const
+{
+    ASSERT(mType != TextureType::_2D && !hasLayer());
+    return ImageIndexIterator::MakeGeneric(mType, mLevelIndex, mLevelIndex + 1, 0, layerCount);
+}
+
 ImageIndexIterator::ImageIndexIterator(const ImageIndexIterator &other) = default;
 
 ImageIndexIterator ImageIndexIterator::Make2D(GLint minMip, GLint maxMip)
 {
-    return ImageIndexIterator(
-        TextureType::_2D, TextureTarget::_2D, TextureTarget::_2D, Range<GLint>(minMip, maxMip),
-        Range<GLint>(ImageIndex::ENTIRE_LEVEL, ImageIndex::ENTIRE_LEVEL), nullptr);
+    return ImageIndexIterator(TextureType::_2D, Range<GLint>(minMip, maxMip),
+                              Range<GLint>(ImageIndex::kEntireLevel, ImageIndex::kEntireLevel),
+                              nullptr);
 }
 
 ImageIndexIterator ImageIndexIterator::MakeRectangle(GLint minMip, GLint maxMip)
 {
-    return ImageIndexIterator(TextureType::Rectangle, TextureTarget::Rectangle,
-                              TextureTarget::Rectangle, Range<GLint>(minMip, maxMip),
-                              Range<GLint>(ImageIndex::ENTIRE_LEVEL, ImageIndex::ENTIRE_LEVEL),
+    return ImageIndexIterator(TextureType::Rectangle, Range<GLint>(minMip, maxMip),
+                              Range<GLint>(ImageIndex::kEntireLevel, ImageIndex::kEntireLevel),
                               nullptr);
 }
 
 ImageIndexIterator ImageIndexIterator::MakeCube(GLint minMip, GLint maxMip)
 {
-    return ImageIndexIterator(TextureType::CubeMap, kCubeMapTextureTargetMin,
-                              kCubeMapTextureTargetMax, Range<GLint>(minMip, maxMip),
-                              Range<GLint>(ImageIndex::ENTIRE_LEVEL, ImageIndex::ENTIRE_LEVEL),
-                              nullptr);
+    return ImageIndexIterator(TextureType::CubeMap, Range<GLint>(minMip, maxMip),
+                              Range<GLint>(0, 6), nullptr);
 }
 
 ImageIndexIterator ImageIndexIterator::Make3D(GLint minMip, GLint maxMip,
                                               GLint minLayer, GLint maxLayer)
 {
-    return ImageIndexIterator(TextureType::_3D, TextureTarget::_3D, TextureTarget::_3D,
-                              Range<GLint>(minMip, maxMip), Range<GLint>(minLayer, maxLayer),
-                              nullptr);
+    return ImageIndexIterator(TextureType::_3D, Range<GLint>(minMip, maxMip),
+                              Range<GLint>(minLayer, maxLayer), nullptr);
 }
 
 ImageIndexIterator ImageIndexIterator::Make2DArray(GLint minMip, GLint maxMip,
                                                    const GLsizei *layerCounts)
 {
-    return ImageIndexIterator(TextureType::_2DArray, TextureTarget::_2DArray,
-                              TextureTarget::_2DArray, Range<GLint>(minMip, maxMip),
+    return ImageIndexIterator(TextureType::_2DArray, Range<GLint>(minMip, maxMip),
                               Range<GLint>(0, IMPLEMENTATION_MAX_2D_ARRAY_TEXTURE_LAYERS),
                               layerCounts);
 }
 
 ImageIndexIterator ImageIndexIterator::Make2DMultisample()
 {
-    return ImageIndexIterator(TextureType::_2DMultisample, TextureTarget::_2DMultisample,
-                              TextureTarget::_2DMultisample, Range<GLint>(0, 0),
-                              Range<GLint>(ImageIndex::ENTIRE_LEVEL, ImageIndex::ENTIRE_LEVEL),
+    return ImageIndexIterator(TextureType::_2DMultisample, Range<GLint>(0, 0),
+                              Range<GLint>(ImageIndex::kEntireLevel, ImageIndex::kEntireLevel),
                               nullptr);
 }
 
-ImageIndexIterator ImageIndexIterator::MakeGeneric(TextureType type, GLint minMip, GLint maxMip)
+ImageIndexIterator ImageIndexIterator::MakeGeneric(TextureType type,
+                                                   GLint minMip,
+                                                   GLint maxMip,
+                                                   GLint minLayer,
+                                                   GLint maxLayer)
 {
     if (type == TextureType::CubeMap)
     {
         return MakeCube(minMip, maxMip);
     }
 
-    TextureTarget target = NonCubeTextureTypeToTarget(type);
-    return ImageIndexIterator(type, target, target, Range<GLint>(minMip, maxMip),
-                              Range<GLint>(ImageIndex::ENTIRE_LEVEL, ImageIndex::ENTIRE_LEVEL),
+    return ImageIndexIterator(type, Range<GLint>(minMip, maxMip), Range<GLint>(minLayer, maxLayer),
                               nullptr);
 }
 
 ImageIndexIterator::ImageIndexIterator(TextureType type,
-                                       angle::EnumIterator<TextureTarget> targetLow,
-                                       angle::EnumIterator<TextureTarget> targetHigh,
                                        const Range<GLint> &mipRange,
                                        const Range<GLint> &layerRange,
                                        const GLsizei *layerCounts)
-    : mTargetLow(targetLow),
-      mTargetHigh(targetHigh),
-      mMipRange(mipRange),
+    : mMipRange(mipRange),
       mLayerRange(layerRange),
       mLayerCounts(layerCounts),
-      mCurrentIndex(type, *targetLow, mipRange.low(), layerRange.low(), 1)
+      mCurrentIndex(type, mipRange.low(), layerRange.low(), 1)
 {}
 
 GLint ImageIndexIterator::maxLayer() const
@@ -195,8 +258,9 @@
     if (mLayerCounts)
     {
         ASSERT(mCurrentIndex.hasLayer());
-        return (mCurrentIndex.mipIndex < mMipRange.high()) ? mLayerCounts[mCurrentIndex.mipIndex]
-                                                           : 0;
+        return (mCurrentIndex.getLevelIndex() < mMipRange.high())
+                   ? mLayerCounts[mCurrentIndex.getLevelIndex()]
+                   : 0;
     }
     return mLayerRange.high();
 }
@@ -211,26 +275,18 @@
     // Iterate layers in the inner loop for now. We can add switchable
     // layer or mip iteration if we need it.
 
-    angle::EnumIterator<TextureTarget> currentTarget = mCurrentIndex.target;
-    if (currentTarget != mTargetHigh)
+    if (mCurrentIndex.hasLayer() && mCurrentIndex.getLayerIndex() < maxLayer() - 1)
     {
-        ++currentTarget;
-        mCurrentIndex.target = *currentTarget;
+        mCurrentIndex.mLayerIndex++;
     }
-    else if (mCurrentIndex.hasLayer() && mCurrentIndex.layerIndex < maxLayer() - 1)
+    else if (mCurrentIndex.mLevelIndex < mMipRange.high() - 1)
     {
-        mCurrentIndex.target = *mTargetLow;
-        mCurrentIndex.layerIndex++;
-    }
-    else if (mCurrentIndex.mipIndex < mMipRange.high() - 1)
-    {
-        mCurrentIndex.target     = *mTargetLow;
-        mCurrentIndex.layerIndex = mLayerRange.low();
-        mCurrentIndex.mipIndex++;
+        mCurrentIndex.mLayerIndex = mLayerRange.low();
+        mCurrentIndex.mLevelIndex++;
     }
     else
     {
-        mCurrentIndex = ImageIndex::MakeInvalid();
+        mCurrentIndex = ImageIndex();
     }
 
     return previousIndex;
diff --git a/src/libANGLE/ImageIndex.h b/src/libANGLE/ImageIndex.h
index af6fca3..3c86a6a 100644
--- a/src/libANGLE/ImageIndex.h
+++ b/src/libANGLE/ImageIndex.h
@@ -19,50 +19,57 @@
 
 class ImageIndexIterator;
 
-struct ImageIndex
+class ImageIndex
 {
-    TextureType type;
-    TextureTarget target;
-
-    GLint mipIndex;
-
-    GLint layerIndex;
-    GLint numLayers;
-
+  public:
+    ImageIndex();
     ImageIndex(const ImageIndex &other);
     ImageIndex &operator=(const ImageIndex &other);
 
-    bool hasLayer() const { return layerIndex != ENTIRE_LEVEL; }
-    bool is3D() const;
+    TextureType getType() const { return mType; }
+    GLint getLevelIndex() const { return mLevelIndex; }
+    GLint getLayerIndex() const { return mLayerIndex; }
+    GLint getLayerCount() const { return mLayerCount; }
+
+    bool hasLayer() const;
+    bool has3DLayer() const;
+    bool usesTex3D() const;
     GLint cubeMapFaceIndex() const;
     bool valid() const;
+    TextureTarget getTarget() const;
 
-    static ImageIndex Make2D(GLint mipIndex);
-    static ImageIndex MakeRectangle(GLint mipIndex);
-    static ImageIndex MakeCube(TextureTarget target, GLint mipIndex);
-    static ImageIndex Make2DArray(GLint mipIndex, GLint layerIndex);
-    static ImageIndex Make2DArrayRange(GLint mipIndex, GLint layerIndex, GLint numLayers);
-    static ImageIndex Make3D(GLint mipIndex, GLint layerIndex = ENTIRE_LEVEL);
-    static ImageIndex MakeGeneric(TextureTarget target, GLint mipIndex);
+    static ImageIndex Make2D(GLint levelIndex);
+    static ImageIndex MakeRectangle(GLint levelIndex);
+    static ImageIndex MakeCube(TextureTarget target, GLint levelIndex);
+    static ImageIndex Make2DArray(GLint levelIndex, GLint layerIndex);
+    static ImageIndex Make2DArrayRange(GLint levelIndex, GLint layerIndex, GLint layerCount);
+    static ImageIndex Make3D(GLint levelIndex, GLint layerIndex = kEntireLevel);
+    static ImageIndex MakeFromTarget(TextureTarget target, GLint levelIndex);
+    static ImageIndex MakeFromType(TextureType type,
+                                   GLint levelIndex,
+                                   GLint layerIndex = kEntireLevel,
+                                   GLint layerCount = 1);
     static ImageIndex Make2DMultisample();
 
-    static ImageIndex MakeInvalid();
+    static constexpr GLint kEntireLevel = static_cast<GLint>(-1);
 
-    static const GLint ENTIRE_LEVEL = static_cast<GLint>(-1);
+    bool operator<(const ImageIndex &b) const;
+    bool operator==(const ImageIndex &b) const;
+    bool operator!=(const ImageIndex &b) const;
+
+    // Only valid for 3D/Cube textures with layers.
+    ImageIndexIterator getLayerIterator(GLint layerCount) const;
 
   private:
     friend class ImageIndexIterator;
 
-    ImageIndex(TextureType typeIn,
-               TextureTarget targetIn,
-               GLint mipIndexIn,
-               GLint layerIndexIn,
-               GLint numLayersIn);
-};
+    ImageIndex(TextureType type, GLint leveIndex, GLint layerIndex, GLint layerCount);
 
-bool operator<(const ImageIndex &a, const ImageIndex &b);
-bool operator==(const ImageIndex &a, const ImageIndex &b);
-bool operator!=(const ImageIndex &a, const ImageIndex &b);
+    TextureType mType;
+    GLint mLevelIndex;
+    GLint mLayerIndex;
+    GLint mLayerCount;
+};
 
 // To be used like this:
 //
@@ -82,7 +89,11 @@
     static ImageIndexIterator Make3D(GLint minMip, GLint maxMip, GLint minLayer, GLint maxLayer);
     static ImageIndexIterator Make2DArray(GLint minMip, GLint maxMip, const GLsizei *layerCounts);
     static ImageIndexIterator Make2DMultisample();
-    static ImageIndexIterator MakeGeneric(TextureType type, GLint minMip, GLint maxMip);
+    static ImageIndexIterator MakeGeneric(TextureType type,
+                                          GLint minMip,
+                                          GLint maxMip,
+                                          GLint minLayer,
+                                          GLint maxLayer);
 
     ImageIndex next();
     ImageIndex current() const;
@@ -90,23 +101,18 @@
 
   private:
     ImageIndexIterator(TextureType type,
-                       angle::EnumIterator<TextureTarget> targetLow,
-                       angle::EnumIterator<TextureTarget> targetHigh,
                        const Range<GLint> &mipRange,
                        const Range<GLint> &layerRange,
                        const GLsizei *layerCounts);
 
     GLint maxLayer() const;
 
-    const angle::EnumIterator<TextureTarget> mTargetLow;
-    const angle::EnumIterator<TextureTarget> mTargetHigh;
     const Range<GLint> mMipRange;
     const Range<GLint> mLayerRange;
     const GLsizei *const mLayerCounts;
 
     ImageIndex mCurrentIndex;
 };
-
-}
+}  // namespace gl
 
 #endif // LIBANGLE_IMAGE_INDEX_H_
diff --git a/src/libANGLE/ImageIndexIterator_unittest.cpp b/src/libANGLE/ImageIndexIterator_unittest.cpp
index 2b656e2..9ffb195 100644
--- a/src/libANGLE/ImageIndexIterator_unittest.cpp
+++ b/src/libANGLE/ImageIndexIterator_unittest.cpp
@@ -32,15 +32,16 @@
         ImageIndex current = iter.current();
         ImageIndex nextIndex = iter.next();
 
-        EXPECT_EQ(TextureType::_2D, nextIndex.type);
-        EXPECT_EQ(TextureTarget::_2D, nextIndex.target);
-        EXPECT_EQ(mip, nextIndex.mipIndex);
+        EXPECT_EQ(TextureType::_2D, nextIndex.getType());
+        EXPECT_EQ(TextureTarget::_2D, nextIndex.getTarget());
+        EXPECT_EQ(mip, nextIndex.getLevelIndex());
         EXPECT_FALSE(nextIndex.hasLayer());
+        EXPECT_FALSE(nextIndex.has3DLayer());
 
         // Also test current
-        EXPECT_EQ(current.type, nextIndex.type);
-        EXPECT_EQ(current.mipIndex, nextIndex.mipIndex);
-        EXPECT_EQ(current.layerIndex, nextIndex.layerIndex);
+        EXPECT_EQ(current.getType(), nextIndex.getType());
+        EXPECT_EQ(current.getLevelIndex(), nextIndex.getLevelIndex());
+        EXPECT_EQ(current.getLayerIndex(), nextIndex.getLayerIndex());
     }
 
     EXPECT_FALSE(iter.hasNext());
@@ -59,10 +60,11 @@
             EXPECT_TRUE(iter.hasNext());
             ImageIndex nextIndex = iter.next();
 
-            EXPECT_EQ(TextureType::CubeMap, nextIndex.type);
-            EXPECT_EQ(target, nextIndex.target);
-            EXPECT_EQ(mip, nextIndex.mipIndex);
-            EXPECT_FALSE(nextIndex.hasLayer());
+            EXPECT_EQ(TextureType::CubeMap, nextIndex.getType());
+            EXPECT_EQ(target, nextIndex.getTarget());
+            EXPECT_EQ(mip, nextIndex.getLevelIndex());
+            EXPECT_TRUE(nextIndex.hasLayer());
+            EXPECT_FALSE(nextIndex.has3DLayer());
         }
     }
 
@@ -82,11 +84,12 @@
             EXPECT_TRUE(iter.hasNext());
             ImageIndex nextIndex = iter.next();
 
-            EXPECT_EQ(TextureType::_3D, nextIndex.type);
-            EXPECT_EQ(TextureTarget::_3D, nextIndex.target);
-            EXPECT_EQ(mip, nextIndex.mipIndex);
-            EXPECT_EQ(layer, nextIndex.layerIndex);
+            EXPECT_EQ(TextureType::_3D, nextIndex.getType());
+            EXPECT_EQ(TextureTarget::_3D, nextIndex.getTarget());
+            EXPECT_EQ(mip, nextIndex.getLevelIndex());
+            EXPECT_EQ(layer, nextIndex.getLayerIndex());
             EXPECT_TRUE(nextIndex.hasLayer());
+            EXPECT_TRUE(nextIndex.has3DLayer());
         }
     }
 
@@ -109,15 +112,47 @@
             EXPECT_TRUE(iter.hasNext());
             ImageIndex nextIndex = iter.next();
 
-            EXPECT_EQ(TextureType::_2DArray, nextIndex.type);
-            EXPECT_EQ(TextureTarget::_2DArray, nextIndex.target);
-            EXPECT_EQ(mip, nextIndex.mipIndex);
-            EXPECT_EQ(layer, nextIndex.layerIndex);
+            EXPECT_EQ(TextureType::_2DArray, nextIndex.getType());
+            EXPECT_EQ(TextureTarget::_2DArray, nextIndex.getTarget());
+            EXPECT_EQ(mip, nextIndex.getLevelIndex());
+            EXPECT_EQ(layer, nextIndex.getLayerIndex());
             EXPECT_TRUE(nextIndex.hasLayer());
+            EXPECT_TRUE(nextIndex.has3DLayer());
         }
     }
 
     EXPECT_FALSE(iter.hasNext());
 }
 
+TEST(ImageIndexTest, LayerIterator2DArray)
+{
+    GLsizei layerCounts[] = {1, 3, 5, 2};
+
+    ASSERT_GE(0, minMip);
+    ASSERT_EQ(ArraySize(layerCounts), static_cast<size_t>(maxMip));
+
+    for (GLint mip = minMip; mip < maxMip; mip++)
+    {
+        // Make a layer iterator.
+        ImageIndex mipImageIndex = ImageIndex::Make2DArray(mip, ImageIndex::kEntireLevel);
+        ImageIndexIterator iter  = mipImageIndex.getLayerIterator(layerCounts[mip]);
+
+        for (GLint layer = 0; layer < layerCounts[mip]; layer++)
+        {
+            EXPECT_TRUE(iter.hasNext());
+            ImageIndex nextIndex = iter.next();
+
+            EXPECT_EQ(TextureType::_2DArray, nextIndex.getType());
+            EXPECT_EQ(TextureTarget::_2DArray, nextIndex.getTarget());
+            EXPECT_EQ(mip, nextIndex.getLevelIndex());
+            EXPECT_EQ(layer, nextIndex.getLayerIndex());
+            EXPECT_TRUE(nextIndex.hasLayer());
+            EXPECT_TRUE(nextIndex.has3DLayer());
+            EXPECT_LT(nextIndex.getLayerIndex(), layerCounts[mip]);
+        }
+
+        EXPECT_FALSE(iter.hasNext());
+    }
+}
+
 } // namespace
diff --git a/src/libANGLE/Texture.cpp b/src/libANGLE/Texture.cpp
index d7953ae..f6e364f 100644
--- a/src/libANGLE/Texture.cpp
+++ b/src/libANGLE/Texture.cpp
@@ -513,7 +513,7 @@
 
 const ImageDesc &TextureState::getImageDesc(const ImageIndex &imageIndex) const
 {
-    return getImageDesc(imageIndex.target, imageIndex.mipIndex);
+    return getImageDesc(imageIndex.getTarget(), imageIndex.getLevelIndex());
 }
 
 void TextureState::setImageDescChain(GLuint baseLevel,
@@ -944,7 +944,7 @@
     ANGLE_TRY(releaseTexImageInternal(context));
     ANGLE_TRY(orphanImages(context));
 
-    ImageIndex index = ImageIndex::MakeGeneric(target, level);
+    ImageIndex index = ImageIndex::MakeFromTarget(target, level);
 
     ANGLE_TRY(mTexture->setImage(context, index, internalFormat, size, format, type, unpackState,
                                  pixels));
@@ -969,7 +969,7 @@
 
     ANGLE_TRY(ensureSubImageInitialized(context, target, level, area));
 
-    ImageIndex index = ImageIndex::MakeGeneric(target, level);
+    ImageIndex index = ImageIndex::MakeFromTarget(target, level);
 
     return mTexture->setSubImage(context, index, area, format, type, unpackState, pixels);
 }
@@ -989,7 +989,7 @@
     ANGLE_TRY(releaseTexImageInternal(context));
     ANGLE_TRY(orphanImages(context));
 
-    ImageIndex index = ImageIndex::MakeGeneric(target, level);
+    ImageIndex index = ImageIndex::MakeFromTarget(target, level);
 
     ANGLE_TRY(mTexture->setCompressedImage(context, index, internalFormat, size, unpackState,
                                            imageSize, pixels));
@@ -1014,7 +1014,7 @@
 
     ANGLE_TRY(ensureSubImageInitialized(context, target, level, area));
 
-    ImageIndex index = ImageIndex::MakeGeneric(target, level);
+    ImageIndex index = ImageIndex::MakeFromTarget(target, level);
 
     return mTexture->setCompressedSubImage(context, index, area, format, unpackState, imageSize,
                                            pixels);
@@ -1040,7 +1040,7 @@
     Box destBox(0, 0, 0, sourceArea.width, sourceArea.height, 1);
     ANGLE_TRY(ensureSubImageInitialized(context, target, level, destBox));
 
-    ImageIndex index = ImageIndex::MakeGeneric(target, level);
+    ImageIndex index = ImageIndex::MakeFromTarget(target, level);
 
     ANGLE_TRY(mTexture->copyImage(context, index, sourceArea, internalFormat, source));
 
@@ -1072,7 +1072,7 @@
     Box destBox(destOffset.x, destOffset.y, destOffset.y, sourceArea.width, sourceArea.height, 1);
     ANGLE_TRY(ensureSubImageInitialized(context, target, level, destBox));
 
-    ImageIndex index = ImageIndex::MakeGeneric(target, level);
+    ImageIndex index = ImageIndex::MakeFromTarget(target, level);
 
     return mTexture->copySubImage(context, index, destOffset, sourceArea, source);
 }
@@ -1099,7 +1099,7 @@
     // Note: we don't have a way to notify which portions of the image changed currently.
     ANGLE_TRY(source->ensureInitialized(context));
 
-    ImageIndex index = ImageIndex::MakeGeneric(target, level);
+    ImageIndex index = ImageIndex::MakeFromTarget(target, level);
 
     ANGLE_TRY(mTexture->copyTexture(context, index, internalFormat, type, sourceLevel, unpackFlipY,
                                     unpackPremultiplyAlpha, unpackUnmultiplyAlpha, source));
@@ -1135,7 +1135,7 @@
     Box destBox(destOffset.x, destOffset.y, destOffset.y, sourceArea.width, sourceArea.height, 1);
     ANGLE_TRY(ensureSubImageInitialized(context, target, level, destBox));
 
-    ImageIndex index = ImageIndex::MakeGeneric(target, level);
+    ImageIndex index = ImageIndex::MakeFromTarget(target, level);
 
     return mTexture->copySubTexture(context, index, destOffset, sourceLevel, sourceArea,
                                     unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha,
@@ -1242,11 +1242,12 @@
     if (context->isRobustResourceInitEnabled())
     {
         ImageIndexIterator it =
-            ImageIndexIterator::MakeGeneric(mState.mType, baseLevel, baseLevel + 1);
+            ImageIndexIterator::MakeGeneric(mState.mType, baseLevel, baseLevel + 1,
+                                            ImageIndex::kEntireLevel, ImageIndex::kEntireLevel);
         while (it.hasNext())
         {
             const ImageIndex index = it.next();
-            const ImageDesc &desc  = mState.getImageDesc(index.target, index.mipIndex);
+            const ImageDesc &desc  = mState.getImageDesc(index.getTarget(), index.getLevelIndex());
 
             if (desc.initState == InitState::MayNeedInit)
             {
@@ -1396,7 +1397,7 @@
 
 GLsizei Texture::getAttachmentSamples(const ImageIndex &imageIndex) const
 {
-    return getSamples(imageIndex.target, 0);
+    return getSamples(imageIndex.getTarget(), 0);
 }
 
 void Texture::setCrop(const gl::Rectangle& rect)
@@ -1483,11 +1484,13 @@
     bool anyDirty = false;
 
     ImageIndexIterator it =
-        ImageIndexIterator::MakeGeneric(mState.mType, 0, IMPLEMENTATION_MAX_TEXTURE_LEVELS + 1);
+        ImageIndexIterator::MakeGeneric(mState.mType, 0, IMPLEMENTATION_MAX_TEXTURE_LEVELS + 1,
+                                        ImageIndex::kEntireLevel, ImageIndex::kEntireLevel);
     while (it.hasNext())
     {
         const ImageIndex index = it.next();
-        ImageDesc &desc = mState.mImageDescs[GetImageDescIndex(index.target, index.mipIndex)];
+        ImageDesc &desc =
+            mState.mImageDescs[GetImageDescIndex(index.getTarget(), index.getLevelIndex())];
         if (desc.initState == InitState::MayNeedInit)
         {
             ASSERT(mState.mInitState == InitState::MayNeedInit);
@@ -1519,7 +1522,7 @@
 {
     ImageDesc newDesc = mState.getImageDesc(imageIndex);
     newDesc.initState = initState;
-    mState.setImageDesc(imageIndex.target, imageIndex.mipIndex, newDesc);
+    mState.setImageDesc(imageIndex.getTarget(), imageIndex.getLevelIndex(), newDesc);
 }
 
 Error Texture::ensureSubImageInitialized(const Context *context,
@@ -1534,7 +1537,7 @@
 
     // Pre-initialize the texture contents if necessary.
     // TODO(jmadill): Check if area overlaps the entire texture.
-    ImageIndex imageIndex  = ImageIndex::MakeGeneric(target, static_cast<GLint>(level));
+    ImageIndex imageIndex  = ImageIndex::MakeFromTarget(target, static_cast<GLint>(level));
     const auto &desc       = mState.getImageDesc(imageIndex);
     if (desc.initState == InitState::MayNeedInit)
     {
diff --git a/src/libANGLE/renderer/d3d/ImageD3D.h b/src/libANGLE/renderer/d3d/ImageD3D.h
index aca10f1..6c932e1 100644
--- a/src/libANGLE/renderer/d3d/ImageD3D.h
+++ b/src/libANGLE/renderer/d3d/ImageD3D.h
@@ -20,7 +20,7 @@
 {
 class Context;
 class Framebuffer;
-struct ImageIndex;
+class ImageIndex;
 struct Box;
 struct Extents;
 struct Offset;
diff --git a/src/libANGLE/renderer/d3d/TextureD3D.cpp b/src/libANGLE/renderer/d3d/TextureD3D.cpp
index ccb9515..d2cdb4b 100644
--- a/src/libANGLE/renderer/d3d/TextureD3D.cpp
+++ b/src/libANGLE/renderer/d3d/TextureD3D.cpp
@@ -242,8 +242,8 @@
         else
         {
             gl::Box fullImageArea(0, 0, 0, image->getWidth(), image->getHeight(), image->getDepth());
-            ANGLE_TRY(
-                image->loadData(context, fullImageArea, unpack, type, pixelData, index.is3D()));
+            ANGLE_TRY(image->loadData(context, fullImageArea, unpack, type, pixelData,
+                                      index.usesTex3D()));
         }
 
         mDirtyImages = true;
@@ -277,7 +277,7 @@
             return mTexStorage->setData(context, index, image, &area, type, unpack, pixelData);
         }
 
-        ANGLE_TRY(image->loadData(context, area, unpack, type, pixelData, index.is3D()));
+        ANGLE_TRY(image->loadData(context, area, unpack, type, pixelData, index.usesTex3D()));
         ANGLE_TRY(commitRegion(context, index, area));
         mDirtyImages = true;
     }
@@ -577,7 +577,7 @@
 
 bool TextureD3D::canCreateRenderTargetForImage(const gl::ImageIndex &index) const
 {
-    if (index.type == gl::TextureType::_2DMultisample)
+    if (index.getType() == gl::TextureType::_2DMultisample)
         return true;
 
     ImageD3D *image = getImage(index);
@@ -669,17 +669,22 @@
     // Special case for D3D11 3D textures. We can't create render targets for individual layers of a
     // 3D texture, so force the clear to the entire mip. There shouldn't ever be a case where we
     // would lose existing data.
-    if (imageIndex.type == gl::TextureType::_3D)
+    if (imageIndexIn.getType() == gl::TextureType::_3D)
     {
-        imageIndex.layerIndex = gl::ImageIndex::ENTIRE_LEVEL;
+        imageIndex =
+            gl::ImageIndex::Make3D(imageIndexIn.getLevelIndex(), gl::ImageIndex::kEntireLevel);
     }
-    else if (imageIndex.type == gl::TextureType::_2DArray &&
-             imageIndex.layerIndex == gl::ImageIndex::ENTIRE_LEVEL)
+    else if (imageIndexIn.getType() == gl::TextureType::_2DArray && !imageIndexIn.hasLayer())
     {
-        GLsizei layerCount = getLayerCount(imageIndex.mipIndex);
-        for (imageIndex.layerIndex = 0; imageIndex.layerIndex < layerCount; ++imageIndex.layerIndex)
+        std::array<GLint, gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS> tempLayerCounts;
+
+        GLint levelIndex            = imageIndexIn.getLevelIndex();
+        tempLayerCounts[levelIndex] = getLayerCount(levelIndex);
+        gl::ImageIndexIterator iterator =
+            gl::ImageIndexIterator::Make2DArray(levelIndex, levelIndex + 1, tempLayerCounts.data());
+        while (iterator.hasNext())
         {
-            ANGLE_TRY(initializeContents(context, imageIndex));
+            ANGLE_TRY(initializeContents(context, iterator.next()));
         }
         return gl::NoError();
     }
@@ -777,10 +782,10 @@
 
 ImageD3D *TextureD3D_2D::getImage(const gl::ImageIndex &index) const
 {
-    ASSERT(index.mipIndex < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
+    ASSERT(index.getLevelIndex() < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
     ASSERT(!index.hasLayer());
-    ASSERT(index.type == gl::TextureType::_2D);
-    return mImageArray[index.mipIndex].get();
+    ASSERT(index.getType() == gl::TextureType::_2D);
+    return mImageArray[index.getLevelIndex()].get();
 }
 
 GLsizei TextureD3D_2D::getLayerCount(int level) const
@@ -832,32 +837,33 @@
                                   const gl::PixelUnpackState &unpack,
                                   const uint8_t *pixels)
 {
-    ASSERT(index.target == gl::TextureTarget::_2D && size.depth == 1);
+    ASSERT(index.getTarget() == gl::TextureTarget::_2D && size.depth == 1);
 
     const gl::InternalFormat &internalFormatInfo = gl::GetInternalFormatInfo(internalFormat, type);
 
     bool fastUnpacked = false;
 
-    ANGLE_TRY(redefineImage(context, index.mipIndex, internalFormatInfo.sizedInternalFormat, size,
-                            false));
+    ANGLE_TRY(redefineImage(context, index.getLevelIndex(), internalFormatInfo.sizedInternalFormat,
+                            size, false));
 
     // Attempt a fast gpu copy of the pixel data to the surface
     gl::Buffer *unpackBuffer =
         context->getGLState().getTargetBuffer(gl::BufferBinding::PixelUnpack);
     if (isFastUnpackable(unpackBuffer, internalFormatInfo.sizedInternalFormat) &&
-        isLevelComplete(index.mipIndex))
+        isLevelComplete(index.getLevelIndex()))
     {
         // Will try to create RT storage if it does not exist
         RenderTargetD3D *destRenderTarget = nullptr;
         ANGLE_TRY(getRenderTarget(context, index, &destRenderTarget));
 
-        gl::Box destArea(0, 0, 0, getWidth(index.mipIndex), getHeight(index.mipIndex), 1);
+        gl::Box destArea(0, 0, 0, getWidth(index.getLevelIndex()), getHeight(index.getLevelIndex()),
+                         1);
 
         ANGLE_TRY(fastUnpackPixels(context, unpack, pixels, destArea,
                                    internalFormatInfo.sizedInternalFormat, type, destRenderTarget));
 
         // Ensure we don't overwrite our newly initialized data
-        mImageArray[index.mipIndex]->markClean();
+        mImageArray[index.getLevelIndex()]->markClean();
 
         fastUnpacked = true;
     }
@@ -878,16 +884,16 @@
                                      const gl::PixelUnpackState &unpack,
                                      const uint8_t *pixels)
 {
-    ASSERT(index.target == gl::TextureTarget::_2D && area.depth == 1 && area.z == 0);
+    ASSERT(index.getTarget() == gl::TextureTarget::_2D && area.depth == 1 && area.z == 0);
 
     gl::Buffer *unpackBuffer =
         context->getGLState().getTargetBuffer(gl::BufferBinding::PixelUnpack);
-    GLenum mipFormat = getInternalFormat(index.mipIndex);
-    if (isFastUnpackable(unpackBuffer, mipFormat) && isLevelComplete(index.mipIndex))
+    GLenum mipFormat = getInternalFormat(index.getLevelIndex());
+    if (isFastUnpackable(unpackBuffer, mipFormat) && isLevelComplete(index.getLevelIndex()))
     {
         RenderTargetD3D *renderTarget = nullptr;
         ANGLE_TRY(getRenderTarget(context, index, &renderTarget));
-        ASSERT(!mImageArray[index.mipIndex]->isDirty());
+        ASSERT(!mImageArray[index.getLevelIndex()]->isDirty());
 
         return fastUnpackPixels(context, unpack, pixels, area, mipFormat, type, renderTarget);
     }
@@ -905,10 +911,10 @@
                                             size_t imageSize,
                                             const uint8_t *pixels)
 {
-    ASSERT(index.target == gl::TextureTarget::_2D && size.depth == 1);
+    ASSERT(index.getTarget() == gl::TextureTarget::_2D && size.depth == 1);
 
     // compressed formats don't have separate sized internal formats-- we can just use the compressed format directly
-    ANGLE_TRY(redefineImage(context, index.mipIndex, internalFormat, size, false));
+    ANGLE_TRY(redefineImage(context, index.getLevelIndex(), internalFormat, size, false));
 
     return setCompressedImageImpl(context, index, unpack, pixels, 0);
 }
@@ -921,7 +927,7 @@
                                                size_t imageSize,
                                                const uint8_t *pixels)
 {
-    ASSERT(index.target == gl::TextureTarget::_2D && area.depth == 1 && area.z == 0);
+    ASSERT(index.getTarget() == gl::TextureTarget::_2D && area.depth == 1 && area.z == 0);
     ANGLE_TRY(TextureD3D::subImageCompressed(context, index, area, format, unpack, pixels, 0));
 
     return commitRegion(context, index, area);
@@ -933,12 +939,12 @@
                                    GLenum internalFormat,
                                    gl::Framebuffer *source)
 {
-    ASSERT(index.target == gl::TextureTarget::_2D);
+    ASSERT(index.getTarget() == gl::TextureTarget::_2D);
 
     const gl::InternalFormat &internalFormatInfo =
         gl::GetInternalFormatInfo(internalFormat, GL_UNSIGNED_BYTE);
     gl::Extents sourceExtents(origSourceArea.width, origSourceArea.height, 1);
-    ANGLE_TRY(redefineImage(context, index.mipIndex, internalFormatInfo.sizedInternalFormat,
+    ANGLE_TRY(redefineImage(context, index.getLevelIndex(), internalFormatInfo.sizedInternalFormat,
                             sourceExtents, false));
 
     gl::Extents fbSize = source->getReadColorbuffer()->getSize();
@@ -971,19 +977,19 @@
     // so we should use the non-rendering copy path.
     if (!canCreateRenderTargetForImage(index) || mRenderer->getWorkarounds().zeroMaxLodWorkaround)
     {
-        ANGLE_TRY(mImageArray[index.mipIndex]->copyFromFramebuffer(context, destOffset, sourceArea,
-                                                                   source));
+        ANGLE_TRY(mImageArray[index.getLevelIndex()]->copyFromFramebuffer(context, destOffset,
+                                                                          sourceArea, source));
         mDirtyImages = true;
     }
     else
     {
         ANGLE_TRY(ensureRenderTarget(context));
 
-        if (sourceArea.width != 0 && sourceArea.height != 0 && isValidLevel(index.mipIndex))
+        if (sourceArea.width != 0 && sourceArea.height != 0 && isValidLevel(index.getLevelIndex()))
         {
-            ANGLE_TRY(updateStorageLevel(context, index.mipIndex));
+            ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
             ANGLE_TRY(mRenderer->copyImage2D(context, source, sourceArea, internalFormat,
-                                             destOffset, mTexStorage, index.mipIndex));
+                                             destOffset, mTexStorage, index.getLevelIndex()));
         }
     }
 
@@ -996,7 +1002,7 @@
                                       const gl::Rectangle &origSourceArea,
                                       gl::Framebuffer *source)
 {
-    ASSERT(index.target == gl::TextureTarget::_2D && origDestOffset.z == 0);
+    ASSERT(index.getTarget() == gl::TextureTarget::_2D && origDestOffset.z == 0);
 
     gl::Extents fbSize = source->getReadColorbuffer()->getSize();
     gl::Rectangle sourceArea;
@@ -1015,20 +1021,20 @@
     // so we should use the non-rendering copy path.
     if (!canCreateRenderTargetForImage(index) || mRenderer->getWorkarounds().zeroMaxLodWorkaround)
     {
-        ANGLE_TRY(mImageArray[index.mipIndex]->copyFromFramebuffer(context, destOffset, sourceArea,
-                                                                   source));
+        ANGLE_TRY(mImageArray[index.getLevelIndex()]->copyFromFramebuffer(context, destOffset,
+                                                                          sourceArea, source));
         mDirtyImages = true;
     }
     else
     {
         ANGLE_TRY(ensureRenderTarget(context));
 
-        if (isValidLevel(index.mipIndex))
+        if (isValidLevel(index.getLevelIndex()))
         {
-            ANGLE_TRY(updateStorageLevel(context, index.mipIndex));
+            ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
             ANGLE_TRY(mRenderer->copyImage2D(context, source, sourceArea,
                                              gl::GetUnsizedFormat(getBaseLevelInternalFormat()),
-                                             destOffset, mTexStorage, index.mipIndex));
+                                             destOffset, mTexStorage, index.getLevelIndex()));
         }
     }
 
@@ -1045,7 +1051,7 @@
                                      bool unpackUnmultiplyAlpha,
                                      const gl::Texture *source)
 {
-    ASSERT(index.target == gl::TextureTarget::_2D);
+    ASSERT(index.getTarget() == gl::TextureTarget::_2D);
 
     gl::TextureType sourceType = source->getType();
 
@@ -1054,22 +1060,22 @@
         static_cast<int>(source->getWidth(NonCubeTextureTypeToTarget(sourceType), sourceLevel)),
         static_cast<int>(source->getHeight(NonCubeTextureTypeToTarget(sourceType), sourceLevel)),
         1);
-    ANGLE_TRY(redefineImage(context, index.mipIndex, internalFormatInfo.sizedInternalFormat, size,
-                            false));
+    ANGLE_TRY(redefineImage(context, index.getLevelIndex(), internalFormatInfo.sizedInternalFormat,
+                            size, false));
 
     gl::Rectangle sourceRect(0, 0, size.width, size.height);
     gl::Offset destOffset(0, 0, 0);
 
-    if (!isSRGB(index.mipIndex) && canCreateRenderTargetForImage(index))
+    if (!isSRGB(index.getLevelIndex()) && canCreateRenderTargetForImage(index))
     {
         ANGLE_TRY(ensureRenderTarget(context));
-        ASSERT(isValidLevel(index.mipIndex));
-        ANGLE_TRY(updateStorageLevel(context, index.mipIndex));
+        ASSERT(isValidLevel(index.getLevelIndex()));
+        ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
 
         ANGLE_TRY(mRenderer->copyTexture(
             context, source, static_cast<GLint>(sourceLevel), sourceRect, internalFormatInfo.format,
-            internalFormatInfo.type, destOffset, mTexStorage, index.target, index.mipIndex,
-            unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));
+            internalFormatInfo.type, destOffset, mTexStorage, index.getTarget(),
+            index.getLevelIndex(), unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));
     }
     else
     {
@@ -1103,20 +1109,20 @@
                                         bool unpackUnmultiplyAlpha,
                                         const gl::Texture *source)
 {
-    ASSERT(index.target == gl::TextureTarget::_2D);
+    ASSERT(index.getTarget() == gl::TextureTarget::_2D);
 
-    if (!isSRGB(index.mipIndex) && canCreateRenderTargetForImage(index))
+    if (!isSRGB(index.getLevelIndex()) && canCreateRenderTargetForImage(index))
     {
         ANGLE_TRY(ensureRenderTarget(context));
-        ASSERT(isValidLevel(index.mipIndex));
-        ANGLE_TRY(updateStorageLevel(context, index.mipIndex));
+        ASSERT(isValidLevel(index.getLevelIndex()));
+        ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
 
         const gl::InternalFormat &internalFormatInfo =
-            gl::GetSizedInternalFormatInfo(getInternalFormat(index.mipIndex));
+            gl::GetSizedInternalFormatInfo(getInternalFormat(index.getLevelIndex()));
         ANGLE_TRY(mRenderer->copyTexture(
             context, source, static_cast<GLint>(sourceLevel), sourceArea, internalFormatInfo.format,
-            internalFormatInfo.type, destOffset, mTexStorage, index.target, index.mipIndex,
-            unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));
+            internalFormatInfo.type, destOffset, mTexStorage, index.getTarget(),
+            index.getLevelIndex(), unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));
     }
     else
     {
@@ -1292,7 +1298,7 @@
 
     // ensure the underlying texture is created
     ANGLE_TRY(ensureRenderTarget(context));
-    ANGLE_TRY(updateStorageLevel(context, index.mipIndex));
+    ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
 
     return mTexStorage->getRenderTarget(context, index, outRT);
 }
@@ -1347,7 +1353,7 @@
 
 bool TextureD3D_2D::isImageComplete(const gl::ImageIndex &index) const
 {
-    return isLevelComplete(index.mipIndex);
+    return isLevelComplete(index.getLevelIndex());
 }
 
 // Constructs a native texture resource from the texture images
@@ -1527,8 +1533,8 @@
 
 bool TextureD3D_2D::isValidIndex(const gl::ImageIndex &index) const
 {
-    return (mTexStorage && index.type == gl::TextureType::_2D && index.mipIndex >= 0 &&
-            index.mipIndex < mTexStorage->getLevelCount());
+    return (mTexStorage && index.getType() == gl::TextureType::_2D && index.getLevelIndex() >= 0 &&
+            index.getLevelIndex() < mTexStorage->getLevelCount());
 }
 
 void TextureD3D_2D::markAllImagesDirty()
@@ -1580,9 +1586,9 @@
 
 ImageD3D *TextureD3D_Cube::getImage(const gl::ImageIndex &index) const
 {
-    ASSERT(index.mipIndex < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
-    ASSERT(gl::TextureTargetToType(index.target) == gl::TextureType::CubeMap);
-    return mImageArray[index.cubeMapFaceIndex()][index.mipIndex].get();
+    ASSERT(index.getLevelIndex() < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
+    ASSERT(gl::TextureTargetToType(index.getTarget()) == gl::TextureType::CubeMap);
+    return mImageArray[index.cubeMapFaceIndex()][index.getLevelIndex()].get();
 }
 
 GLsizei TextureD3D_Cube::getLayerCount(int level) const
@@ -1629,7 +1635,7 @@
     ASSERT(size.depth == 1);
 
     const gl::InternalFormat &internalFormatInfo = gl::GetInternalFormatInfo(internalFormat, type);
-    ANGLE_TRY(redefineImage(context, index.cubeMapFaceIndex(), index.mipIndex,
+    ANGLE_TRY(redefineImage(context, index.cubeMapFaceIndex(), index.getLevelIndex(),
                             internalFormatInfo.sizedInternalFormat, size, false));
 
     return setImageImpl(context, index, type, unpack, pixels, 0);
@@ -1658,8 +1664,8 @@
     ASSERT(size.depth == 1);
 
     // compressed formats don't have separate sized internal formats-- we can just use the compressed format directly
-    ANGLE_TRY(redefineImage(context, index.cubeMapFaceIndex(), index.mipIndex, internalFormat, size,
-                            false));
+    ANGLE_TRY(redefineImage(context, index.cubeMapFaceIndex(), index.getLevelIndex(),
+                            internalFormat, size, false));
 
     return setCompressedImageImpl(context, index, unpack, pixels, 0);
 }
@@ -1689,7 +1695,7 @@
         gl::GetInternalFormatInfo(internalFormat, GL_UNSIGNED_BYTE);
 
     gl::Extents size(origSourceArea.width, origSourceArea.height, 1);
-    ANGLE_TRY(redefineImage(context, faceIndex, index.mipIndex,
+    ANGLE_TRY(redefineImage(context, faceIndex, index.getLevelIndex(),
                             internalFormatInfo.sizedInternalFormat, size, false));
 
     gl::Extents fbSize = source->getReadColorbuffer()->getSize();
@@ -1722,8 +1728,8 @@
     // so we should use the non-rendering copy path.
     if (!canCreateRenderTargetForImage(index) || mRenderer->getWorkarounds().zeroMaxLodWorkaround)
     {
-        ANGLE_TRY(mImageArray[faceIndex][index.mipIndex]->copyFromFramebuffer(context, destOffset,
-                                                                              sourceArea, source));
+        ANGLE_TRY(mImageArray[faceIndex][index.getLevelIndex()]->copyFromFramebuffer(
+            context, destOffset, sourceArea, source));
         mDirtyImages = true;
     }
     else
@@ -1732,12 +1738,12 @@
 
         ASSERT(size.width == size.height);
 
-        if (size.width > 0 && isValidFaceLevel(faceIndex, index.mipIndex))
+        if (size.width > 0 && isValidFaceLevel(faceIndex, index.getLevelIndex()))
         {
-            ANGLE_TRY(updateStorageFaceLevel(context, faceIndex, index.mipIndex));
+            ANGLE_TRY(updateStorageFaceLevel(context, faceIndex, index.getLevelIndex()));
             ANGLE_TRY(mRenderer->copyImageCube(context, source, sourceArea, internalFormat,
-                                               destOffset, mTexStorage, index.target,
-                                               index.mipIndex));
+                                               destOffset, mTexStorage, index.getTarget(),
+                                               index.getLevelIndex()));
         }
     }
 
@@ -1766,19 +1772,19 @@
     // so we should use the non-rendering copy path.
     if (!canCreateRenderTargetForImage(index) || mRenderer->getWorkarounds().zeroMaxLodWorkaround)
     {
-        ANGLE_TRY(mImageArray[faceIndex][index.mipIndex]->copyFromFramebuffer(context, destOffset,
-                                                                              sourceArea, source));
+        ANGLE_TRY(mImageArray[faceIndex][index.getLevelIndex()]->copyFromFramebuffer(
+            context, destOffset, sourceArea, source));
         mDirtyImages = true;
     }
     else
     {
         ANGLE_TRY(ensureRenderTarget(context));
-        if (isValidFaceLevel(faceIndex, index.mipIndex))
+        if (isValidFaceLevel(faceIndex, index.getLevelIndex()))
         {
-            ANGLE_TRY(updateStorageFaceLevel(context, faceIndex, index.mipIndex));
+            ANGLE_TRY(updateStorageFaceLevel(context, faceIndex, index.getLevelIndex()));
             ANGLE_TRY(mRenderer->copyImageCube(
                 context, source, sourceArea, gl::GetUnsizedFormat(getBaseLevelInternalFormat()),
-                destOffset, mTexStorage, index.target, index.mipIndex));
+                destOffset, mTexStorage, index.getTarget(), index.getLevelIndex()));
         }
     }
 
@@ -1795,7 +1801,7 @@
                                        bool unpackUnmultiplyAlpha,
                                        const gl::Texture *source)
 {
-    ASSERT(gl::TextureTargetToType(index.target) == gl::TextureType::CubeMap);
+    ASSERT(gl::TextureTargetToType(index.getTarget()) == gl::TextureType::CubeMap);
 
     gl::TextureTarget sourceTarget = NonCubeTextureTypeToTarget(source->getType());
 
@@ -1804,22 +1810,22 @@
     const gl::InternalFormat &internalFormatInfo = gl::GetInternalFormatInfo(internalFormat, type);
     gl::Extents size(static_cast<int>(source->getWidth(sourceTarget, sourceLevel)),
                      static_cast<int>(source->getHeight(sourceTarget, sourceLevel)), 1);
-    ANGLE_TRY(redefineImage(context, faceIndex, index.mipIndex,
+    ANGLE_TRY(redefineImage(context, faceIndex, index.getLevelIndex(),
                             internalFormatInfo.sizedInternalFormat, size, false));
 
     gl::Rectangle sourceRect(0, 0, size.width, size.height);
     gl::Offset destOffset(0, 0, 0);
 
-    if (!isSRGB(index.mipIndex, faceIndex) && canCreateRenderTargetForImage(index))
+    if (!isSRGB(index.getLevelIndex(), faceIndex) && canCreateRenderTargetForImage(index))
     {
         ANGLE_TRY(ensureRenderTarget(context));
-        ASSERT(isValidFaceLevel(faceIndex, index.mipIndex));
-        ANGLE_TRY(updateStorageFaceLevel(context, faceIndex, index.mipIndex));
+        ASSERT(isValidFaceLevel(faceIndex, index.getLevelIndex()));
+        ANGLE_TRY(updateStorageFaceLevel(context, faceIndex, index.getLevelIndex()));
 
         ANGLE_TRY(mRenderer->copyTexture(
             context, source, static_cast<GLint>(sourceLevel), sourceRect, internalFormatInfo.format,
-            internalFormatInfo.type, destOffset, mTexStorage, index.target, index.mipIndex,
-            unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));
+            internalFormatInfo.type, destOffset, mTexStorage, index.getTarget(),
+            index.getLevelIndex(), unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));
     }
     else
     {
@@ -1853,22 +1859,22 @@
                                           bool unpackUnmultiplyAlpha,
                                           const gl::Texture *source)
 {
-    ASSERT(gl::TextureTargetToType(index.target) == gl::TextureType::CubeMap);
+    ASSERT(gl::TextureTargetToType(index.getTarget()) == gl::TextureType::CubeMap);
 
     GLint faceIndex = index.cubeMapFaceIndex();
 
-    if (!isSRGB(index.mipIndex, faceIndex) && canCreateRenderTargetForImage(index))
+    if (!isSRGB(index.getLevelIndex(), faceIndex) && canCreateRenderTargetForImage(index))
     {
         ANGLE_TRY(ensureRenderTarget(context));
-        ASSERT(isValidFaceLevel(faceIndex, index.mipIndex));
-        ANGLE_TRY(updateStorageFaceLevel(context, faceIndex, index.mipIndex));
+        ASSERT(isValidFaceLevel(faceIndex, index.getLevelIndex()));
+        ANGLE_TRY(updateStorageFaceLevel(context, faceIndex, index.getLevelIndex()));
 
         const gl::InternalFormat &internalFormatInfo =
-            gl::GetSizedInternalFormatInfo(getInternalFormat(index.mipIndex, faceIndex));
+            gl::GetSizedInternalFormatInfo(getInternalFormat(index.getLevelIndex(), faceIndex));
         ANGLE_TRY(mRenderer->copyTexture(
             context, source, static_cast<GLint>(sourceLevel), sourceArea, internalFormatInfo.format,
-            internalFormatInfo.type, destOffset, mTexStorage, index.target, index.mipIndex,
-            unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));
+            internalFormatInfo.type, destOffset, mTexStorage, index.getTarget(),
+            index.getLevelIndex(), unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));
     }
     else
     {
@@ -2000,11 +2006,11 @@
                                            const gl::ImageIndex &index,
                                            RenderTargetD3D **outRT)
 {
-    ASSERT(gl::TextureTargetToType(index.target) == gl::TextureType::CubeMap);
+    ASSERT(gl::TextureTargetToType(index.getTarget()) == gl::TextureType::CubeMap);
 
     // ensure the underlying texture is created
     ANGLE_TRY(ensureRenderTarget(context));
-    ANGLE_TRY(updateStorageFaceLevel(context, index.cubeMapFaceIndex(), index.mipIndex));
+    ANGLE_TRY(updateStorageFaceLevel(context, index.cubeMapFaceIndex(), index.getLevelIndex()));
 
     return mTexStorage->getRenderTarget(context, index, outRT);
 }
@@ -2169,7 +2175,7 @@
 
 bool TextureD3D_Cube::isImageComplete(const gl::ImageIndex &index) const
 {
-    return isFaceLevelComplete(index.cubeMapFaceIndex(), index.mipIndex);
+    return isFaceLevelComplete(index.cubeMapFaceIndex(), index.getLevelIndex());
 }
 
 gl::Error TextureD3D_Cube::updateStorageFaceLevel(const gl::Context *context,
@@ -2236,9 +2242,9 @@
 
 bool TextureD3D_Cube::isValidIndex(const gl::ImageIndex &index) const
 {
-    return (mTexStorage && index.type == gl::TextureType::CubeMap &&
-            gl::TextureTargetToType(index.target) == gl::TextureType::CubeMap &&
-            index.mipIndex >= 0 && index.mipIndex < mTexStorage->getLevelCount());
+    return (mTexStorage && index.getType() == gl::TextureType::CubeMap &&
+            gl::TextureTargetToType(index.getTarget()) == gl::TextureType::CubeMap &&
+            index.getLevelIndex() >= 0 && index.getLevelIndex() < mTexStorage->getLevelCount());
 }
 
 void TextureD3D_Cube::markAllImagesDirty()
@@ -2287,10 +2293,10 @@
 
 ImageD3D *TextureD3D_3D::getImage(const gl::ImageIndex &index) const
 {
-    ASSERT(index.mipIndex < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
+    ASSERT(index.getLevelIndex() < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
     ASSERT(!index.hasLayer());
-    ASSERT(index.type == gl::TextureType::_3D);
-    return mImageArray[index.mipIndex].get();
+    ASSERT(index.getType() == gl::TextureType::_3D);
+    return mImageArray[index.getLevelIndex()].get();
 }
 
 GLsizei TextureD3D_3D::getLayerCount(int level) const
@@ -2353,11 +2359,11 @@
                                   const gl::PixelUnpackState &unpack,
                                   const uint8_t *pixels)
 {
-    ASSERT(index.target == gl::TextureTarget::_3D);
+    ASSERT(index.getTarget() == gl::TextureTarget::_3D);
     const gl::InternalFormat &internalFormatInfo = gl::GetInternalFormatInfo(internalFormat, type);
 
-    ANGLE_TRY(redefineImage(context, index.mipIndex, internalFormatInfo.sizedInternalFormat, size,
-                            false));
+    ANGLE_TRY(redefineImage(context, index.getLevelIndex(), internalFormatInfo.sizedInternalFormat,
+                            size, false));
 
     bool fastUnpacked = false;
 
@@ -2365,20 +2371,20 @@
     gl::Buffer *unpackBuffer =
         context->getGLState().getTargetBuffer(gl::BufferBinding::PixelUnpack);
     if (isFastUnpackable(unpackBuffer, internalFormatInfo.sizedInternalFormat) && !size.empty() &&
-        isLevelComplete(index.mipIndex))
+        isLevelComplete(index.getLevelIndex()))
     {
         // Will try to create RT storage if it does not exist
         RenderTargetD3D *destRenderTarget = nullptr;
         ANGLE_TRY(getRenderTarget(context, index, &destRenderTarget));
 
-        gl::Box destArea(0, 0, 0, getWidth(index.mipIndex), getHeight(index.mipIndex),
-                         getDepth(index.mipIndex));
+        gl::Box destArea(0, 0, 0, getWidth(index.getLevelIndex()), getHeight(index.getLevelIndex()),
+                         getDepth(index.getLevelIndex()));
 
         ANGLE_TRY(fastUnpackPixels(context, unpack, pixels, destArea,
                                    internalFormatInfo.sizedInternalFormat, type, destRenderTarget));
 
         // Ensure we don't overwrite our newly initialized data
-        mImageArray[index.mipIndex]->markClean();
+        mImageArray[index.getLevelIndex()]->markClean();
 
         fastUnpacked = true;
     }
@@ -2399,17 +2405,17 @@
                                      const gl::PixelUnpackState &unpack,
                                      const uint8_t *pixels)
 {
-    ASSERT(index.target == gl::TextureTarget::_3D);
+    ASSERT(index.getTarget() == gl::TextureTarget::_3D);
 
     // Attempt a fast gpu copy of the pixel data to the surface if the app bound an unpack buffer
     gl::Buffer *unpackBuffer =
         context->getGLState().getTargetBuffer(gl::BufferBinding::PixelUnpack);
-    GLenum mipFormat = getInternalFormat(index.mipIndex);
-    if (isFastUnpackable(unpackBuffer, mipFormat) && isLevelComplete(index.mipIndex))
+    GLenum mipFormat = getInternalFormat(index.getLevelIndex());
+    if (isFastUnpackable(unpackBuffer, mipFormat) && isLevelComplete(index.getLevelIndex()))
     {
         RenderTargetD3D *destRenderTarget = nullptr;
         ANGLE_TRY(getRenderTarget(context, index, &destRenderTarget));
-        ASSERT(!mImageArray[index.mipIndex]->isDirty());
+        ASSERT(!mImageArray[index.getLevelIndex()]->isDirty());
 
         return fastUnpackPixels(context, unpack, pixels, area, mipFormat, type, destRenderTarget);
     }
@@ -2427,10 +2433,10 @@
                                             size_t imageSize,
                                             const uint8_t *pixels)
 {
-    ASSERT(index.target == gl::TextureTarget::_3D);
+    ASSERT(index.getTarget() == gl::TextureTarget::_3D);
 
     // compressed formats don't have separate sized internal formats-- we can just use the compressed format directly
-    ANGLE_TRY(redefineImage(context, index.mipIndex, internalFormat, size, false));
+    ANGLE_TRY(redefineImage(context, index.getLevelIndex(), internalFormat, size, false));
 
     return setCompressedImageImpl(context, index, unpack, pixels, 0);
 }
@@ -2443,7 +2449,7 @@
                                                size_t imageSize,
                                                const uint8_t *pixels)
 {
-    ASSERT(index.target == gl::TextureTarget::_3D);
+    ASSERT(index.getTarget() == gl::TextureTarget::_3D);
 
     ANGLE_TRY(TextureD3D::subImageCompressed(context, index, area, format, unpack, pixels, 0));
     return commitRegion(context, index, area);
@@ -2465,7 +2471,7 @@
                                       const gl::Rectangle &sourceArea,
                                       gl::Framebuffer *source)
 {
-    ASSERT(index.target == gl::TextureTarget::_3D);
+    ASSERT(index.getTarget() == gl::TextureTarget::_3D);
 
     gl::Extents fbSize = source->getReadColorbuffer()->getSize();
     gl::Rectangle clippedSourceArea;
@@ -2483,18 +2489,19 @@
     // date before the copy and then copy back to the storage afterwards if needed.
     // TODO: Investigate 3D blits in D3D11.
 
-    bool syncTexStorage = mTexStorage && isLevelComplete(index.mipIndex);
+    bool syncTexStorage = mTexStorage && isLevelComplete(index.getLevelIndex());
     if (syncTexStorage)
     {
-        ANGLE_TRY(mImageArray[index.mipIndex]->copyFromTexStorage(context, index, mTexStorage));
+        ANGLE_TRY(
+            mImageArray[index.getLevelIndex()]->copyFromTexStorage(context, index, mTexStorage));
     }
-    ANGLE_TRY(mImageArray[index.mipIndex]->copyFromFramebuffer(context, clippedDestOffset,
-                                                               clippedSourceArea, source));
+    ANGLE_TRY(mImageArray[index.getLevelIndex()]->copyFromFramebuffer(context, clippedDestOffset,
+                                                                      clippedSourceArea, source));
     mDirtyImages = true;
 
     if (syncTexStorage)
     {
-        ANGLE_TRY(updateStorageLevel(context, index.mipIndex));
+        ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
     }
 
     return gl::NoError();
@@ -2580,7 +2587,7 @@
     }
     else
     {
-        ANGLE_TRY(updateStorageLevel(context, index.mipIndex));
+        ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
     }
 
     return mTexStorage->getRenderTarget(context, index, outRT);
@@ -2726,7 +2733,7 @@
 
 bool TextureD3D_3D::isImageComplete(const gl::ImageIndex &index) const
 {
-    return isLevelComplete(index.mipIndex);
+    return isLevelComplete(index.getLevelIndex());
 }
 
 gl::Error TextureD3D_3D::updateStorageLevel(const gl::Context *context, int level)
@@ -2779,7 +2786,8 @@
 gl::ImageIndexIterator TextureD3D_3D::imageIterator() const
 {
     return gl::ImageIndexIterator::Make3D(0, mTexStorage->getLevelCount(),
-                                          gl::ImageIndex::ENTIRE_LEVEL, gl::ImageIndex::ENTIRE_LEVEL);
+                                          gl::ImageIndex::kEntireLevel,
+                                          gl::ImageIndex::kEntireLevel);
 }
 
 gl::ImageIndex TextureD3D_3D::getImageIndex(GLint mip, GLint /*layer*/) const
@@ -2790,8 +2798,8 @@
 
 bool TextureD3D_3D::isValidIndex(const gl::ImageIndex &index) const
 {
-    return (mTexStorage && index.type == gl::TextureType::_3D && index.mipIndex >= 0 &&
-            index.mipIndex < mTexStorage->getLevelCount());
+    return (mTexStorage && index.getType() == gl::TextureType::_3D && index.getLevelIndex() >= 0 &&
+            index.getLevelIndex() < mTexStorage->getLevelCount());
 }
 
 void TextureD3D_3D::markAllImagesDirty()
@@ -2842,12 +2850,14 @@
 
 ImageD3D *TextureD3D_2DArray::getImage(const gl::ImageIndex &index) const
 {
-    ASSERT(index.mipIndex < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
-    ASSERT(index.layerIndex != gl::ImageIndex::ENTIRE_LEVEL);
-    ASSERT((index.layerIndex == 0 && mLayerCounts[index.mipIndex] == 0) ||
-           index.layerIndex < mLayerCounts[index.mipIndex]);
-    ASSERT(index.type == gl::TextureType::_2DArray);
-    return (mImageArray[index.mipIndex] ? mImageArray[index.mipIndex][index.layerIndex] : nullptr);
+    ASSERT(index.getLevelIndex() < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
+    ASSERT(index.hasLayer());
+    ASSERT((index.getLayerIndex() == 0 && mLayerCounts[index.getLevelIndex()] == 0) ||
+           index.getLayerIndex() < mLayerCounts[index.getLevelIndex()]);
+    ASSERT(index.getType() == gl::TextureType::_2DArray);
+    return (mImageArray[index.getLevelIndex()]
+                ? mImageArray[index.getLevelIndex()][index.getLayerIndex()]
+                : nullptr);
 }
 
 GLsizei TextureD3D_2DArray::getLayerCount(int level) const
@@ -2893,11 +2903,12 @@
                                        const gl::PixelUnpackState &unpack,
                                        const uint8_t *pixels)
 {
-    ASSERT(index.target == gl::TextureTarget::_2DArray);
+    ASSERT(index.getTarget() == gl::TextureTarget::_2DArray);
 
     const gl::InternalFormat &formatInfo = gl::GetInternalFormatInfo(internalFormat, type);
 
-    ANGLE_TRY(redefineImage(context, index.mipIndex, formatInfo.sizedInternalFormat, size, false));
+    ANGLE_TRY(
+        redefineImage(context, index.getLevelIndex(), formatInfo.sizedInternalFormat, size, false));
 
     GLsizei inputDepthPitch              = 0;
     ANGLE_TRY_RESULT(formatInfo.computeDepthPitch(type, size.width, size.height, unpack.alignment,
@@ -2907,7 +2918,7 @@
     for (int i = 0; i < size.depth; i++)
     {
         const ptrdiff_t layerOffset = (inputDepthPitch * i);
-        gl::ImageIndex layerIndex   = gl::ImageIndex::Make2DArray(index.mipIndex, i);
+        gl::ImageIndex layerIndex   = gl::ImageIndex::Make2DArray(index.getLevelIndex(), i);
         ANGLE_TRY(setImageImpl(context, layerIndex, type, unpack, pixels, layerOffset));
     }
 
@@ -2922,9 +2933,9 @@
                                           const gl::PixelUnpackState &unpack,
                                           const uint8_t *pixels)
 {
-    ASSERT(index.target == gl::TextureTarget::_2DArray);
+    ASSERT(index.getTarget() == gl::TextureTarget::_2DArray);
     const gl::InternalFormat &formatInfo =
-        gl::GetInternalFormatInfo(getInternalFormat(index.mipIndex), type);
+        gl::GetInternalFormatInfo(getInternalFormat(index.getLevelIndex()), type);
     GLsizei inputDepthPitch              = 0;
     ANGLE_TRY_RESULT(formatInfo.computeDepthPitch(type, area.width, area.height, unpack.alignment,
                                                   unpack.rowLength, unpack.imageHeight),
@@ -2937,7 +2948,7 @@
 
         gl::Box layerArea(area.x, area.y, 0, area.width, area.height, 1);
 
-        gl::ImageIndex layerIndex = gl::ImageIndex::Make2DArray(index.mipIndex, layer);
+        gl::ImageIndex layerIndex = gl::ImageIndex::Make2DArray(index.getLevelIndex(), layer);
         ANGLE_TRY(TextureD3D::subImage(context, layerIndex, layerArea, format, type, unpack, pixels,
                                        layerOffset));
     }
@@ -2953,10 +2964,10 @@
                                                  size_t imageSize,
                                                  const uint8_t *pixels)
 {
-    ASSERT(index.target == gl::TextureTarget::_2DArray);
+    ASSERT(index.getTarget() == gl::TextureTarget::_2DArray);
 
     // compressed formats don't have separate sized internal formats-- we can just use the compressed format directly
-    ANGLE_TRY(redefineImage(context, index.mipIndex, internalFormat, size, false));
+    ANGLE_TRY(redefineImage(context, index.getLevelIndex(), internalFormat, size, false));
 
     const gl::InternalFormat &formatInfo = gl::GetSizedInternalFormatInfo(internalFormat);
     GLsizei inputDepthPitch              = 0;
@@ -2968,7 +2979,7 @@
     {
         const ptrdiff_t layerOffset = (inputDepthPitch * i);
 
-        gl::ImageIndex layerIndex = gl::ImageIndex::Make2DArray(index.mipIndex, i);
+        gl::ImageIndex layerIndex = gl::ImageIndex::Make2DArray(index.getLevelIndex(), i);
         ANGLE_TRY(setCompressedImageImpl(context, layerIndex, unpack, pixels, layerOffset));
     }
 
@@ -2983,7 +2994,7 @@
                                                     size_t imageSize,
                                                     const uint8_t *pixels)
 {
-    ASSERT(index.target == gl::TextureTarget::_2DArray);
+    ASSERT(index.getTarget() == gl::TextureTarget::_2DArray);
 
     const gl::InternalFormat &formatInfo = gl::GetSizedInternalFormatInfo(format);
     GLsizei inputDepthPitch              = 0;
@@ -2998,7 +3009,7 @@
 
         gl::Box layerArea(area.x, area.y, 0, area.width, area.height, 1);
 
-        gl::ImageIndex layerIndex = gl::ImageIndex::Make2DArray(index.mipIndex, layer);
+        gl::ImageIndex layerIndex = gl::ImageIndex::Make2DArray(index.getLevelIndex(), layer);
         ANGLE_TRY(TextureD3D::subImageCompressed(context, layerIndex, layerArea, format, unpack,
                                                  pixels, layerOffset));
         ANGLE_TRY(commitRegion(context, layerIndex, layerArea));
@@ -3023,7 +3034,7 @@
                                            const gl::Rectangle &sourceArea,
                                            gl::Framebuffer *source)
 {
-    ASSERT(index.target == gl::TextureTarget::_2DArray);
+    ASSERT(index.getTarget() == gl::TextureTarget::_2DArray);
 
     gl::Extents fbSize = source->getReadColorbuffer()->getSize();
     gl::Rectangle clippedSourceArea;
@@ -3039,7 +3050,7 @@
     if (!canCreateRenderTargetForImage(index))
     {
         gl::Offset destLayerOffset(clippedDestOffset.x, clippedDestOffset.y, 0);
-        ANGLE_TRY(mImageArray[index.mipIndex][clippedDestOffset.z]->copyFromFramebuffer(
+        ANGLE_TRY(mImageArray[index.getLevelIndex()][clippedDestOffset.z]->copyFromFramebuffer(
             context, destLayerOffset, clippedSourceArea, source));
         mDirtyImages = true;
     }
@@ -3047,13 +3058,13 @@
     {
         ANGLE_TRY(ensureRenderTarget(context));
 
-        if (isValidLevel(index.mipIndex))
+        if (isValidLevel(index.getLevelIndex()))
         {
-            ANGLE_TRY(updateStorageLevel(context, index.mipIndex));
+            ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
             ANGLE_TRY(
                 mRenderer->copyImage2DArray(context, source, clippedSourceArea,
                                             gl::GetUnsizedFormat(getInternalFormat(getBaseLevel())),
-                                            clippedDestOffset, mTexStorage, index.mipIndex));
+                                            clippedDestOffset, mTexStorage, index.getLevelIndex()));
         }
     }
     return gl::NoError();
@@ -3149,7 +3160,7 @@
 {
     // ensure the underlying texture is created
     ANGLE_TRY(ensureRenderTarget(context));
-    ANGLE_TRY(updateStorageLevel(context, index.mipIndex));
+    ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
     return mTexStorage->getRenderTarget(context, index, outRT);
 }
 
@@ -3298,7 +3309,7 @@
 
 bool TextureD3D_2DArray::isImageComplete(const gl::ImageIndex &index) const
 {
-    return isLevelComplete(index.mipIndex);
+    return isLevelComplete(index.getLevelIndex());
 }
 
 gl::Error TextureD3D_2DArray::updateStorageLevel(const gl::Context *context, int level)
@@ -3412,19 +3423,20 @@
 bool TextureD3D_2DArray::isValidIndex(const gl::ImageIndex &index) const
 {
     // Check for having a storage and the right type of index
-    if (!mTexStorage || index.type != gl::TextureType::_2DArray)
+    if (!mTexStorage || index.getType() != gl::TextureType::_2DArray)
     {
         return false;
     }
 
     // Check the mip index
-    if (index.mipIndex < 0 || index.mipIndex >= mTexStorage->getLevelCount())
+    if (index.getLevelIndex() < 0 || index.getLevelIndex() >= mTexStorage->getLevelCount())
     {
         return false;
     }
 
     // Check the layer index
-    return (!index.hasLayer() || (index.layerIndex >= 0 && index.layerIndex < mLayerCounts[index.mipIndex]));
+    return (!index.hasLayer() || (index.getLayerIndex() >= 0 &&
+                                  index.getLayerIndex() < mLayerCounts[index.getLevelIndex()]));
 }
 
 void TextureD3D_2DArray::markAllImagesDirty()
@@ -3601,7 +3613,7 @@
 
 bool TextureD3D_External::isImageComplete(const gl::ImageIndex &index) const
 {
-    return (index.mipIndex == 0) ? (mTexStorage != nullptr) : false;
+    return (index.getLevelIndex() == 0) ? (mTexStorage != nullptr) : false;
 }
 
 gl::Error TextureD3D_External::initializeStorage(const gl::Context *context, bool renderTarget)
@@ -3646,7 +3658,8 @@
 
 bool TextureD3D_External::isValidIndex(const gl::ImageIndex &index) const
 {
-    return (mTexStorage && index.type == gl::TextureType::External && index.mipIndex == 0);
+    return (mTexStorage && index.getType() == gl::TextureType::External &&
+            index.getLevelIndex() == 0);
 }
 
 void TextureD3D_External::markAllImagesDirty()
@@ -3806,7 +3819,8 @@
 
 bool TextureD3D_2DMultisample::isValidIndex(const gl::ImageIndex &index) const
 {
-    return (mTexStorage && index.type == gl::TextureType::_2DMultisample && index.mipIndex == 0);
+    return (mTexStorage && index.getType() == gl::TextureType::_2DMultisample &&
+            index.getLevelIndex() == 0);
 }
 
 GLsizei TextureD3D_2DMultisample::getLayerCount(int level) const
diff --git a/src/libANGLE/renderer/d3d/TextureStorage.h b/src/libANGLE/renderer/d3d/TextureStorage.h
index 1d743f7..c801645 100644
--- a/src/libANGLE/renderer/d3d/TextureStorage.h
+++ b/src/libANGLE/renderer/d3d/TextureStorage.h
@@ -18,7 +18,7 @@
 namespace gl
 {
 class Context;
-struct ImageIndex;
+class ImageIndex;
 struct Box;
 struct PixelUnpackState;
 }  // namespace gl
diff --git a/src/libANGLE/renderer/d3d/d3d11/Image11.cpp b/src/libANGLE/renderer/d3d/d3d11/Image11.cpp
index 11c2756..527a29c 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Image11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Image11.cpp
@@ -30,7 +30,7 @@
       mStagingSubresource(0),
       mRecoverFromStorage(false),
       mAssociatedStorage(nullptr),
-      mAssociatedImageIndex(gl::ImageIndex::MakeInvalid()),
+      mAssociatedImageIndex(),
       mRecoveredFromStorageCount(0)
 {
 }
@@ -224,7 +224,7 @@
 
         mRecoverFromStorage   = false;
         mAssociatedStorage    = nullptr;
-        mAssociatedImageIndex = gl::ImageIndex::MakeInvalid();
+        mAssociatedImageIndex = gl::ImageIndex();
     }
 }
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
index 0af9963..cab05b6 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
@@ -2375,7 +2375,7 @@
         const TextureHelper11 *destResource = nullptr;
         ANGLE_TRY(destStorage11->getResource(context, &destResource));
 
-        gl::ImageIndex destIndex = gl::ImageIndex::MakeGeneric(destTarget, destLevel);
+        gl::ImageIndex destIndex = gl::ImageIndex::MakeFromTarget(destTarget, destLevel);
         UINT destSubresource     = destStorage11->getSubresourceIndex(destIndex);
 
         D3D11_BOX sourceBox{
@@ -2396,7 +2396,7 @@
         const d3d11::SharedSRV *sourceSRV = nullptr;
         ANGLE_TRY(sourceStorage11->getSRVLevels(context, sourceLevel, sourceLevel, &sourceSRV));
 
-        gl::ImageIndex destIndex             = gl::ImageIndex::MakeGeneric(destTarget, destLevel);
+        gl::ImageIndex destIndex = gl::ImageIndex::MakeFromTarget(destTarget, destLevel);
         RenderTargetD3D *destRenderTargetD3D = nullptr;
         ANGLE_TRY(destStorage11->getRenderTarget(context, destIndex, &destRenderTargetD3D));
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.h b/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
index d1b8bfd..78e2675 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.h
@@ -26,7 +26,7 @@
 namespace gl
 {
 class FramebufferAttachment;
-struct ImageIndex;
+class ImageIndex;
 }
 
 namespace rx
diff --git a/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp b/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
index fa4060e..7f5edb9 100644
--- a/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
@@ -34,8 +34,8 @@
 {
 bool ImageIndexConflictsWithSRV(const gl::ImageIndex &index, D3D11_SHADER_RESOURCE_VIEW_DESC desc)
 {
-    unsigned mipLevel  = index.mipIndex;
-    gl::TextureType textureType = index.type;
+    unsigned mipLevel           = index.getLevelIndex();
+    gl::TextureType textureType = index.getType();
 
     switch (desc.ViewDimension)
     {
@@ -45,7 +45,7 @@
             unsigned int maxSrvMip = desc.Texture2D.MipLevels + desc.Texture2D.MostDetailedMip;
             maxSrvMip              = allLevels ? INT_MAX : maxSrvMip;
 
-            unsigned mipMin = index.mipIndex;
+            unsigned mipMin = index.getLevelIndex();
             unsigned mipMax = INT_MAX;
 
             return textureType == gl::TextureType::_2D &&
@@ -55,7 +55,7 @@
 
         case D3D11_SRV_DIMENSION_TEXTURE2DARRAY:
         {
-            GLint layerIndex = index.layerIndex;
+            GLint layerIndex = index.getLayerIndex();
 
             bool allLevels = (desc.Texture2DArray.MipLevels == std::numeric_limits<UINT>::max());
             unsigned int maxSrvMip =
diff --git a/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp b/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
index 02d83c4..7a14e7e 100644
--- a/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
@@ -199,8 +199,8 @@
 
 UINT TextureStorage11::getSubresourceIndex(const gl::ImageIndex &index) const
 {
-    UINT mipSlice    = static_cast<UINT>(index.mipIndex + mTopLevel);
-    UINT arraySlice  = static_cast<UINT>(index.hasLayer() ? index.layerIndex : 0);
+    UINT mipSlice    = static_cast<UINT>(index.getLevelIndex() + mTopLevel);
+    UINT arraySlice  = static_cast<UINT>(index.hasLayer() ? index.getLayerIndex() : 0);
     UINT subresource = D3D11CalcSubresource(mipSlice, arraySlice, mMipLevels);
     ASSERT(subresource != std::numeric_limits<UINT>::max());
     return subresource;
@@ -511,7 +511,7 @@
 {
     ASSERT(srcTexture.valid());
 
-    const GLint level = index.mipIndex;
+    const GLint level = index.getLevelIndex();
 
     markLevelDirty(level);
 
@@ -578,7 +578,7 @@
 
     // If the zero-LOD workaround is active and we want to update a level greater than zero, then we
     // should update the mipmapped texture, even if mapmaps are currently disabled.
-    if (index.mipIndex > 0 && mRenderer->getWorkarounds().zeroMaxLodWorkaround)
+    if (index.getLevelIndex() > 0 && mRenderer->getWorkarounds().zeroMaxLodWorkaround)
     {
         ANGLE_TRY(getMippedResource(context, &srcTexture));
     }
@@ -630,9 +630,9 @@
                                            const gl::ImageIndex &sourceIndex,
                                            const gl::ImageIndex &destIndex)
 {
-    ASSERT(sourceIndex.layerIndex == destIndex.layerIndex);
+    ASSERT(sourceIndex.getLayerIndex() == destIndex.getLayerIndex());
 
-    markLevelDirty(destIndex.mipIndex);
+    markLevelDirty(destIndex.getLevelIndex());
 
     RenderTargetD3D *source = nullptr;
     ANGLE_TRY(getRenderTarget(context, sourceIndex, &source));
@@ -709,7 +709,7 @@
 {
     ASSERT(!image->isDirty());
 
-    markLevelDirty(index.mipIndex);
+    markLevelDirty(index.getLevelIndex());
 
     const TextureHelper11 *resource = nullptr;
     ANGLE_TRY(getResource(context, &resource));
@@ -720,8 +720,8 @@
     const gl::InternalFormat &internalFormatInfo =
         gl::GetInternalFormatInfo(image->getInternalFormat(), type);
 
-    gl::Box levelBox(0, 0, 0, getLevelWidth(index.mipIndex), getLevelHeight(index.mipIndex),
-                     getLevelDepth(index.mipIndex));
+    gl::Box levelBox(0, 0, 0, getLevelWidth(index.getLevelIndex()),
+                     getLevelHeight(index.getLevelIndex()), getLevelDepth(index.getLevelIndex()));
     bool fullUpdate = (destBox == nullptr || *destBox == levelBox);
     ASSERT(internalFormatInfo.depthBits == 0 || fullUpdate);
 
@@ -743,7 +743,7 @@
                      srcDepthPitch);
     GLuint srcSkipBytes = 0;
     ANGLE_TRY_RESULT(
-        internalFormatInfo.computeSkipBytes(srcRowPitch, srcDepthPitch, unpack, index.is3D()),
+        internalFormatInfo.computeSkipBytes(srcRowPitch, srcDepthPitch, unpack, index.usesTex3D()),
         srcSkipBytes);
 
     const d3d11::Format &d3d11Format =
@@ -995,7 +995,7 @@
 
 void TextureStorage11_2D::associateImage(Image11 *image, const gl::ImageIndex &index)
 {
-    const GLint level = index.mipIndex;
+    const GLint level = index.getLevelIndex();
 
     ASSERT(0 <= level && level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
     if (0 <= level && level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS)
@@ -1007,7 +1007,7 @@
 void TextureStorage11_2D::verifyAssociatedImageValid(const gl::ImageIndex &index,
                                                      Image11 *expectedImage)
 {
-    const GLint level = index.mipIndex;
+    const GLint level = index.getLevelIndex();
 
     ASSERT(0 <= level && level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
     // This validation check should never return false. It means the Image/TextureStorage
@@ -1018,7 +1018,7 @@
 // disassociateImage allows an Image to end its association with a Storage.
 void TextureStorage11_2D::disassociateImage(const gl::ImageIndex &index, Image11 *expectedImage)
 {
-    const GLint level = index.mipIndex;
+    const GLint level = index.getLevelIndex();
 
     ASSERT(0 <= level && level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
     ASSERT(mAssociatedImages[level] == expectedImage);
@@ -1031,7 +1031,7 @@
                                                       const gl::ImageIndex &index,
                                                       Image11 *incomingImage)
 {
-    const GLint level = index.mipIndex;
+    const GLint level = index.getLevelIndex();
 
     ASSERT(0 <= level && level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
 
@@ -1129,7 +1129,7 @@
 {
     ASSERT(!index.hasLayer());
 
-    const int level = index.mipIndex;
+    const int level = index.getLevelIndex();
     ASSERT(level >= 0 && level < getLevelCount());
 
     // In GL ES 2.0, the application can only render to level zero of the texture (Section 4.4.3 of
@@ -1433,20 +1433,20 @@
 
 void TextureStorage11_External::associateImage(Image11 *image, const gl::ImageIndex &index)
 {
-    ASSERT(index.mipIndex == 0);
+    ASSERT(index.getLevelIndex() == 0);
     mAssociatedImage = image;
 }
 
 void TextureStorage11_External::verifyAssociatedImageValid(const gl::ImageIndex &index,
                                                            Image11 *expectedImage)
 {
-    ASSERT(index.mipIndex == 0 && mAssociatedImage == expectedImage);
+    ASSERT(index.getLevelIndex() == 0 && mAssociatedImage == expectedImage);
 }
 
 void TextureStorage11_External::disassociateImage(const gl::ImageIndex &index,
                                                   Image11 *expectedImage)
 {
-    ASSERT(index.mipIndex == 0);
+    ASSERT(index.getLevelIndex() == 0);
     ASSERT(mAssociatedImage == expectedImage);
     mAssociatedImage = nullptr;
 }
@@ -1455,7 +1455,7 @@
                                                             const gl::ImageIndex &index,
                                                             Image11 *incomingImage)
 {
-    ASSERT(index.mipIndex == 0);
+    ASSERT(index.getLevelIndex() == 0);
 
     if (mAssociatedImage != nullptr && mAssociatedImage != incomingImage)
     {
@@ -1607,7 +1607,7 @@
                                                      RenderTargetD3D **outRT)
 {
     ASSERT(!index.hasLayer());
-    ASSERT(index.mipIndex == 0);
+    ASSERT(index.getLevelIndex() == 0);
 
     ANGLE_TRY(checkForUpdatedRenderTarget(context));
 
@@ -1866,7 +1866,7 @@
 {
     UINT arraySlice = index.cubeMapFaceIndex();
     if (mRenderer->getWorkarounds().zeroMaxLodWorkaround && mUseLevelZeroTexture &&
-        index.mipIndex == 0)
+        index.getLevelIndex() == 0)
     {
         UINT subresource = D3D11CalcSubresource(0, arraySlice, 1);
         ASSERT(subresource != std::numeric_limits<UINT>::max());
@@ -1874,7 +1874,7 @@
     }
     else
     {
-        UINT mipSlice    = static_cast<UINT>(index.mipIndex + mTopLevel);
+        UINT mipSlice    = static_cast<UINT>(index.getLevelIndex() + mTopLevel);
         UINT subresource = D3D11CalcSubresource(mipSlice, arraySlice, mMipLevels);
         ASSERT(subresource != std::numeric_limits<UINT>::max());
         return subresource;
@@ -1980,7 +1980,7 @@
 
 void TextureStorage11_Cube::associateImage(Image11 *image, const gl::ImageIndex &index)
 {
-    const GLint level       = index.mipIndex;
+    const GLint level       = index.getLevelIndex();
     const GLint layerTarget = index.cubeMapFaceIndex();
 
     ASSERT(0 <= level && level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
@@ -1998,7 +1998,7 @@
 void TextureStorage11_Cube::verifyAssociatedImageValid(const gl::ImageIndex &index,
                                                        Image11 *expectedImage)
 {
-    const GLint level       = index.mipIndex;
+    const GLint level       = index.getLevelIndex();
     const GLint layerTarget = index.cubeMapFaceIndex();
 
     ASSERT(0 <= level && level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
@@ -2011,7 +2011,7 @@
 // disassociateImage allows an Image to end its association with a Storage.
 void TextureStorage11_Cube::disassociateImage(const gl::ImageIndex &index, Image11 *expectedImage)
 {
-    const GLint level       = index.mipIndex;
+    const GLint level       = index.getLevelIndex();
     const GLint layerTarget = index.cubeMapFaceIndex();
 
     ASSERT(0 <= level && level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
@@ -2026,7 +2026,7 @@
                                                         const gl::ImageIndex &index,
                                                         Image11 *incomingImage)
 {
-    const GLint level       = index.mipIndex;
+    const GLint level       = index.getLevelIndex();
     const GLint layerTarget = index.cubeMapFaceIndex();
 
     ASSERT(0 <= level && level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
@@ -2123,7 +2123,7 @@
 {
     D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
     srvDesc.Format                         = resourceFormat;
-    srvDesc.Texture2DArray.MostDetailedMip = mTopLevel + index.mipIndex;
+    srvDesc.Texture2DArray.MostDetailedMip = mTopLevel + index.getLevelIndex();
     srvDesc.Texture2DArray.MipLevels       = 1;
     srvDesc.Texture2DArray.FirstArraySlice = index.cubeMapFaceIndex();
     srvDesc.Texture2DArray.ArraySize       = 1;
@@ -2147,7 +2147,7 @@
                                                  RenderTargetD3D **outRT)
 {
     const int faceIndex = index.cubeMapFaceIndex();
-    const int level     = index.mipIndex;
+    const int level     = index.getLevelIndex();
 
     ASSERT(level >= 0 && level < getLevelCount());
     ASSERT(faceIndex >= 0 && faceIndex < static_cast<GLint>(gl::CUBE_FACE_COUNT));
@@ -2156,7 +2156,7 @@
     {
         if (mRenderer->getWorkarounds().zeroMaxLodWorkaround)
         {
-            ASSERT(index.mipIndex == 0);
+            ASSERT(index.getLevelIndex() == 0);
             ANGLE_TRY(useLevelZeroWorkaroundTexture(context, true));
         }
 
@@ -2411,8 +2411,8 @@
     while (itCopy.hasNext())
     {
         gl::ImageIndex index = itCopy.next();
-        gl::Box wholeArea(0, 0, 0, getLevelWidth(index.mipIndex), getLevelHeight(index.mipIndex),
-                          1);
+        gl::Box wholeArea(0, 0, 0, getLevelWidth(index.getLevelIndex()),
+                          getLevelHeight(index.getLevelIndex()), 1);
         gl::Extents wholeSize(wholeArea.width, wholeArea.height, 1);
         UINT subresource = getSubresourceIndex(index);
         ANGLE_TRY(mRenderer->getBlitter()->copyDepthStencil(
@@ -2508,7 +2508,7 @@
 
 void TextureStorage11_3D::associateImage(Image11 *image, const gl::ImageIndex &index)
 {
-    const GLint level = index.mipIndex;
+    const GLint level = index.getLevelIndex();
 
     ASSERT(0 <= level && level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
 
@@ -2521,7 +2521,7 @@
 void TextureStorage11_3D::verifyAssociatedImageValid(const gl::ImageIndex &index,
                                                      Image11 *expectedImage)
 {
-    const GLint level = index.mipIndex;
+    const GLint level = index.getLevelIndex();
 
     ASSERT(0 <= level && level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
     // This validation check should never return false. It means the Image/TextureStorage
@@ -2532,7 +2532,7 @@
 // disassociateImage allows an Image to end its association with a Storage.
 void TextureStorage11_3D::disassociateImage(const gl::ImageIndex &index, Image11 *expectedImage)
 {
-    const GLint level = index.mipIndex;
+    const GLint level = index.getLevelIndex();
 
     ASSERT(0 <= level && level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
     ASSERT(mAssociatedImages[level] == expectedImage);
@@ -2545,7 +2545,7 @@
                                                       const gl::ImageIndex &index,
                                                       Image11 *incomingImage)
 {
-    const GLint level = index.mipIndex;
+    const GLint level = index.getLevelIndex();
 
     ASSERT((0 <= level && level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS));
 
@@ -2654,7 +2654,7 @@
                                                const gl::ImageIndex &index,
                                                RenderTargetD3D **outRT)
 {
-    const int mipLevel = index.mipIndex;
+    const int mipLevel = index.getLevelIndex();
     ASSERT(mipLevel >= 0 && mipLevel < getLevelCount());
 
     ASSERT(mFormatInfo.rtvFormat != DXGI_FORMAT_UNKNOWN);
@@ -2694,7 +2694,7 @@
         return gl::NoError();
     }
 
-    const int layer = index.layerIndex;
+    const int layer = index.getLayerIndex();
 
     LevelLayerKey key(mipLevel, layer);
     if (mLevelLayerRenderTargets.find(key) == mLevelLayerRenderTargets.end())
@@ -2831,9 +2831,9 @@
 
 void TextureStorage11_2DArray::associateImage(Image11 *image, const gl::ImageIndex &index)
 {
-    const GLint level       = index.mipIndex;
-    const GLint layerTarget = index.layerIndex;
-    const GLint numLayers   = index.numLayers;
+    const GLint level       = index.getLevelIndex();
+    const GLint layerTarget = index.getLayerIndex();
+    const GLint numLayers   = index.getLayerCount();
 
     ASSERT(0 <= level && level < getLevelCount());
 
@@ -2847,9 +2847,9 @@
 void TextureStorage11_2DArray::verifyAssociatedImageValid(const gl::ImageIndex &index,
                                                           Image11 *expectedImage)
 {
-    const GLint level       = index.mipIndex;
-    const GLint layerTarget = index.layerIndex;
-    const GLint numLayers   = index.numLayers;
+    const GLint level       = index.getLevelIndex();
+    const GLint layerTarget = index.getLayerIndex();
+    const GLint numLayers   = index.getLayerCount();
 
     LevelLayerRangeKey key(level, layerTarget, numLayers);
 
@@ -2864,9 +2864,9 @@
 void TextureStorage11_2DArray::disassociateImage(const gl::ImageIndex &index,
                                                  Image11 *expectedImage)
 {
-    const GLint level       = index.mipIndex;
-    const GLint layerTarget = index.layerIndex;
-    const GLint numLayers   = index.numLayers;
+    const GLint level       = index.getLevelIndex();
+    const GLint layerTarget = index.getLayerIndex();
+    const GLint numLayers   = index.getLayerCount();
 
     LevelLayerRangeKey key(level, layerTarget, numLayers);
 
@@ -2882,9 +2882,9 @@
                                                            const gl::ImageIndex &index,
                                                            Image11 *incomingImage)
 {
-    const GLint level       = index.mipIndex;
-    const GLint layerTarget = index.layerIndex;
-    const GLint numLayers   = index.numLayers;
+    const GLint level       = index.getLevelIndex();
+    const GLint layerTarget = index.getLayerIndex();
+    const GLint numLayers   = index.getLayerCount();
 
     LevelLayerRangeKey key(level, layerTarget, numLayers);
 
@@ -3000,10 +3000,10 @@
     D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
     srvDesc.Format                         = resourceFormat;
     srvDesc.ViewDimension                  = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
-    srvDesc.Texture2DArray.MostDetailedMip = mTopLevel + index.mipIndex;
+    srvDesc.Texture2DArray.MostDetailedMip = mTopLevel + index.getLevelIndex();
     srvDesc.Texture2DArray.MipLevels       = 1;
-    srvDesc.Texture2DArray.FirstArraySlice = index.layerIndex;
-    srvDesc.Texture2DArray.ArraySize       = index.numLayers;
+    srvDesc.Texture2DArray.FirstArraySlice = index.getLayerIndex();
+    srvDesc.Texture2DArray.ArraySize       = index.getLayerCount();
 
     ANGLE_TRY(mRenderer->allocateResource(srvDesc, texture.get(), srv));
 
@@ -3016,9 +3016,9 @@
 {
     ASSERT(index.hasLayer());
 
-    const int mipLevel  = index.mipIndex;
-    const int layer     = index.layerIndex;
-    const int numLayers = index.numLayers;
+    const int mipLevel  = index.getLevelIndex();
+    const int layer     = index.getLayerIndex();
+    const int numLayers = index.getLayerCount();
 
     ASSERT(mipLevel >= 0 && mipLevel < getLevelCount());
 
@@ -3286,7 +3286,7 @@
 {
     ASSERT(!index.hasLayer());
 
-    const int level = index.mipIndex;
+    const int level = index.getLevelIndex();
     ASSERT(level == 0);
 
     ASSERT(outRT);
diff --git a/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h b/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
index 7736a8f..e515557 100644
--- a/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
@@ -22,8 +22,8 @@
 
 namespace gl
 {
-struct ImageIndex;
-}
+class ImageIndex;
+}  // namespace gl
 
 namespace rx
 {
diff --git a/src/libANGLE/renderer/d3d/d3d9/Image9.cpp b/src/libANGLE/renderer/d3d/d3d9/Image9.cpp
index f3d71c7..56871be 100644
--- a/src/libANGLE/renderer/d3d/d3d9/Image9.cpp
+++ b/src/libANGLE/renderer/d3d/d3d9/Image9.cpp
@@ -470,7 +470,8 @@
 
     TextureStorage9 *storage9 = GetAs<TextureStorage9>(storage);
     IDirect3DSurface9 *destSurface = nullptr;
-    ANGLE_TRY(storage9->getSurfaceLevel(context, index.target, index.mipIndex, true, &destSurface));
+    ANGLE_TRY(storage9->getSurfaceLevel(context, index.getTarget(), index.getLevelIndex(), true,
+                                        &destSurface));
 
     gl::Error error = copyToSurface(destSurface, region);
     SafeRelease(destSurface);
diff --git a/src/libANGLE/renderer/d3d/d3d9/TextureStorage9.cpp b/src/libANGLE/renderer/d3d/d3d9/TextureStorage9.cpp
index 12ffa0e..1b7d9fa 100644
--- a/src/libANGLE/renderer/d3d/d3d9/TextureStorage9.cpp
+++ b/src/libANGLE/renderer/d3d/d3d9/TextureStorage9.cpp
@@ -192,9 +192,9 @@
                                               const gl::ImageIndex &index,
                                               RenderTargetD3D **outRT)
 {
-    ASSERT(index.mipIndex < getLevelCount());
+    ASSERT(index.getLevelIndex() < getLevelCount());
 
-    if (!mRenderTargets[index.mipIndex] && isRenderTarget())
+    if (!mRenderTargets[index.getLevelIndex()] && isRenderTarget())
     {
         IDirect3DBaseTexture9 *baseTexture = nullptr;
         gl::Error error                    = getBaseTexture(context, &baseTexture);
@@ -204,24 +204,25 @@
         }
 
         IDirect3DSurface9 *surface = nullptr;
-        error = getSurfaceLevel(context, gl::TextureTarget::_2D, index.mipIndex, false, &surface);
+        error = getSurfaceLevel(context, gl::TextureTarget::_2D, index.getLevelIndex(), false,
+                                &surface);
         if (error.isError())
         {
             return error;
         }
 
-        size_t textureMipLevel = mTopLevel + index.mipIndex;
+        size_t textureMipLevel = mTopLevel + index.getLevelIndex();
         size_t mipWidth        = std::max<size_t>(mTextureWidth >> textureMipLevel, 1u);
         size_t mipHeight       = std::max<size_t>(mTextureHeight >> textureMipLevel, 1u);
 
         baseTexture->AddRef();
-        mRenderTargets[index.mipIndex] = new TextureRenderTarget9(
+        mRenderTargets[index.getLevelIndex()] = new TextureRenderTarget9(
             baseTexture, textureMipLevel, surface, mInternalFormat, static_cast<GLsizei>(mipWidth),
             static_cast<GLsizei>(mipHeight), 1, 0);
     }
 
     ASSERT(outRT);
-    *outRT = mRenderTargets[index.mipIndex];
+    *outRT = mRenderTargets[index.getLevelIndex()];
     return gl::NoError();
 }
 
@@ -230,15 +231,16 @@
                                              const gl::ImageIndex &destIndex)
 {
     IDirect3DSurface9 *upper = nullptr;
-    gl::Error error =
-        getSurfaceLevel(context, gl::TextureTarget::_2D, sourceIndex.mipIndex, false, &upper);
+    gl::Error error = getSurfaceLevel(context, gl::TextureTarget::_2D, sourceIndex.getLevelIndex(),
+                                      false, &upper);
     if (error.isError())
     {
         return error;
     }
 
     IDirect3DSurface9 *lower = nullptr;
-    error = getSurfaceLevel(context, gl::TextureTarget::_2D, destIndex.mipIndex, true, &lower);
+    error =
+        getSurfaceLevel(context, gl::TextureTarget::_2D, destIndex.getLevelIndex(), true, &lower);
     if (error.isError())
     {
         SafeRelease(upper);
@@ -363,7 +365,7 @@
                                                     RenderTargetD3D **outRT)
 {
     ASSERT(!index.hasLayer());
-    ASSERT(index.mipIndex == 0);
+    ASSERT(index.getLevelIndex() == 0);
 
     return mImage->getRenderTarget(context, outRT);
 }
@@ -516,10 +518,10 @@
                                                 RenderTargetD3D **outRT)
 {
     ASSERT(outRT);
-    ASSERT(index.mipIndex == 0);
+    ASSERT(index.getLevelIndex() == 0);
 
-    ASSERT(index.type == gl::TextureType::CubeMap &&
-           gl::TextureTargetToType(index.target) == gl::TextureType::CubeMap);
+    ASSERT(index.getType() == gl::TextureType::CubeMap &&
+           gl::TextureTargetToType(index.getTarget()) == gl::TextureType::CubeMap);
     const size_t renderTargetIndex = index.cubeMapFaceIndex();
 
     if (mRenderTarget[renderTargetIndex] == nullptr && isRenderTarget())
@@ -532,7 +534,8 @@
         }
 
         IDirect3DSurface9 *surface = nullptr;
-        error = getSurfaceLevel(context, index.target, mTopLevel + index.mipIndex, false, &surface);
+        error = getSurfaceLevel(context, index.getTarget(), mTopLevel + index.getLevelIndex(),
+                                false, &surface);
         if (error.isError())
         {
             return error;
@@ -540,7 +543,7 @@
 
         baseTexture->AddRef();
         mRenderTarget[renderTargetIndex] = new TextureRenderTarget9(
-            baseTexture, mTopLevel + index.mipIndex, surface, mInternalFormat,
+            baseTexture, mTopLevel + index.getLevelIndex(), surface, mInternalFormat,
             static_cast<GLsizei>(mTextureWidth), static_cast<GLsizei>(mTextureHeight), 1, 0);
     }
 
@@ -553,15 +556,16 @@
                                                const gl::ImageIndex &destIndex)
 {
     IDirect3DSurface9 *upper = nullptr;
-    gl::Error error =
-        getSurfaceLevel(context, sourceIndex.target, sourceIndex.mipIndex, false, &upper);
+    gl::Error error = getSurfaceLevel(context, sourceIndex.getTarget(), sourceIndex.getLevelIndex(),
+                                      false, &upper);
     if (error.isError())
     {
         return error;
     }
 
     IDirect3DSurface9 *lower = nullptr;
-    error = getSurfaceLevel(context, destIndex.target, destIndex.mipIndex, true, &lower);
+    error =
+        getSurfaceLevel(context, destIndex.getTarget(), destIndex.getLevelIndex(), true, &lower);
     if (error.isError())
     {
         SafeRelease(upper);
diff --git a/src/libANGLE/renderer/gl/BlitGL.cpp b/src/libANGLE/renderer/gl/BlitGL.cpp
index b79348a..09e50c5 100644
--- a/src/libANGLE/renderer/gl/BlitGL.cpp
+++ b/src/libANGLE/renderer/gl/BlitGL.cpp
@@ -744,8 +744,8 @@
         for (GLenum bindTarget : bindTargets)
         {
             mFunctions->framebufferTexture2D(GL_FRAMEBUFFER, bindTarget,
-                                             ToGLenum(imageIndex.target), source->getTextureID(),
-                                             imageIndex.mipIndex);
+                                             ToGLenum(imageIndex.getTarget()),
+                                             source->getTextureID(), imageIndex.getLevelIndex());
         }
 
         GLenum status = mFunctions->checkFramebufferStatus(GL_FRAMEBUFFER);
@@ -769,7 +769,7 @@
             for (GLenum bindTarget : bindTargets)
             {
                 mFunctions->framebufferTexture(GL_FRAMEBUFFER, bindTarget, source->getTextureID(),
-                                               imageIndex.mipIndex);
+                                               imageIndex.getLevelIndex());
             }
 
             GLenum status = mFunctions->checkFramebufferStatus(GL_FRAMEBUFFER);
@@ -789,17 +789,17 @@
             GLint layerCount = numTextureLayers;
             if (imageIndex.hasLayer())
             {
-                firstLayer = imageIndex.layerIndex;
-                layerCount = imageIndex.numLayers;
+                firstLayer = imageIndex.getLayerIndex();
+                layerCount = imageIndex.getLayerCount();
             }
 
             for (GLint layer = 0; layer < layerCount; layer++)
             {
                 for (GLenum bindTarget : bindTargets)
                 {
-                    mFunctions->framebufferTextureLayer(GL_FRAMEBUFFER, bindTarget,
-                                                        source->getTextureID(), imageIndex.mipIndex,
-                                                        layer + firstLayer);
+                    mFunctions->framebufferTextureLayer(
+                        GL_FRAMEBUFFER, bindTarget, source->getTextureID(),
+                        imageIndex.getLevelIndex(), layer + firstLayer);
                 }
 
                 GLenum status = mFunctions->checkFramebufferStatus(GL_FRAMEBUFFER);
diff --git a/src/libANGLE/renderer/gl/BlitGL.h b/src/libANGLE/renderer/gl/BlitGL.h
index eed6d04..8b9f287 100644
--- a/src/libANGLE/renderer/gl/BlitGL.h
+++ b/src/libANGLE/renderer/gl/BlitGL.h
@@ -19,7 +19,7 @@
 namespace gl
 {
 class Framebuffer;
-struct ImageIndex;
+class ImageIndex;
 }
 
 namespace rx
diff --git a/src/libANGLE/renderer/gl/ClearMultiviewGL.cpp b/src/libANGLE/renderer/gl/ClearMultiviewGL.cpp
index 01f3d78..ad78da7 100644
--- a/src/libANGLE/renderer/gl/ClearMultiviewGL.cpp
+++ b/src/libANGLE/renderer/gl/ClearMultiviewGL.cpp
@@ -154,13 +154,14 @@
         }
 
         const auto &imageIndex = attachment->getTextureImageIndex();
-        ASSERT(imageIndex.type == gl::TextureType::_2DArray);
+        ASSERT(imageIndex.getType() == gl::TextureType::_2DArray);
 
         GLenum colorAttachment =
             static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + static_cast<int>(drawBufferId));
         const TextureGL *textureGL = GetImplAs<TextureGL>(attachment->getTexture());
         mFunctions->framebufferTextureLayer(GL_DRAW_FRAMEBUFFER, colorAttachment,
-                                            textureGL->getTextureID(), imageIndex.mipIndex, layer);
+                                            textureGL->getTextureID(), imageIndex.getLevelIndex(),
+                                            layer);
     }
 
     const gl::FramebufferAttachment *depthStencilAttachment = state.getDepthStencilAttachment();
@@ -169,29 +170,32 @@
     if (depthStencilAttachment != nullptr)
     {
         const auto &imageIndex = depthStencilAttachment->getTextureImageIndex();
-        ASSERT(imageIndex.type == gl::TextureType::_2DArray);
+        ASSERT(imageIndex.getType() == gl::TextureType::_2DArray);
 
         const TextureGL *textureGL = GetImplAs<TextureGL>(depthStencilAttachment->getTexture());
         mFunctions->framebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
-                                            textureGL->getTextureID(), imageIndex.mipIndex, layer);
+                                            textureGL->getTextureID(), imageIndex.getLevelIndex(),
+                                            layer);
     }
     else if (depthAttachment != nullptr)
     {
         const auto &imageIndex = depthAttachment->getTextureImageIndex();
-        ASSERT(imageIndex.type == gl::TextureType::_2DArray);
+        ASSERT(imageIndex.getType() == gl::TextureType::_2DArray);
 
         const TextureGL *textureGL = GetImplAs<TextureGL>(depthAttachment->getTexture());
         mFunctions->framebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
-                                            textureGL->getTextureID(), imageIndex.mipIndex, layer);
+                                            textureGL->getTextureID(), imageIndex.getLevelIndex(),
+                                            layer);
     }
     else if (stencilAttachment != nullptr)
     {
         const auto &imageIndex = stencilAttachment->getTextureImageIndex();
-        ASSERT(imageIndex.type == gl::TextureType::_2DArray);
+        ASSERT(imageIndex.getType() == gl::TextureType::_2DArray);
 
         const TextureGL *textureGL = GetImplAs<TextureGL>(stencilAttachment->getTexture());
         mFunctions->framebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
-                                            textureGL->getTextureID(), imageIndex.mipIndex, layer);
+                                            textureGL->getTextureID(), imageIndex.getLevelIndex(),
+                                            layer);
     }
 }
 
diff --git a/src/libANGLE/renderer/gl/FramebufferGL.cpp b/src/libANGLE/renderer/gl/FramebufferGL.cpp
index 914902a..5432e4a 100644
--- a/src/libANGLE/renderer/gl/FramebufferGL.cpp
+++ b/src/libANGLE/renderer/gl/FramebufferGL.cpp
@@ -113,8 +113,8 @@
         return false;
     }
     const ImageIndex &imageIndex = attachment.getTextureImageIndex();
-    int numLayers =
-        static_cast<int>(attachment.getTexture()->getDepth(imageIndex.target, imageIndex.mipIndex));
+    int numLayers                = static_cast<int>(
+        attachment.getTexture()->getDepth(imageIndex.getTarget(), imageIndex.getLevelIndex()));
     return (attachment.getNumViews() == numLayers);
 }
 
diff --git a/src/libANGLE/renderer/gl/TextureGL.cpp b/src/libANGLE/renderer/gl/TextureGL.cpp
index c0707c4..562a1d4 100644
--- a/src/libANGLE/renderer/gl/TextureGL.cpp
+++ b/src/libANGLE/renderer/gl/TextureGL.cpp
@@ -152,8 +152,8 @@
     const gl::Buffer *unpackBuffer =
         context->getGLState().getTargetBuffer(gl::BufferBinding::PixelUnpack);
 
-    gl::TextureTarget target = index.target;
-    size_t level             = static_cast<size_t>(index.mipIndex);
+    gl::TextureTarget target = index.getTarget();
+    size_t level             = static_cast<size_t>(index.getLevelIndex());
 
     if (mWorkarounds.unpackOverlappingRowsSeparatelyUnpackBuffer && unpackBuffer &&
         unpack.rowLength != 0 && unpack.rowLength < size.width)
@@ -257,7 +257,7 @@
                                  const gl::PixelUnpackState &unpack,
                                  const uint8_t *pixels)
 {
-    ASSERT(TextureTargetToType(index.target) == getType());
+    ASSERT(TextureTargetToType(index.getTarget()) == getType());
 
     const gl::Buffer *unpackBuffer =
         context->getGLState().getTargetBuffer(gl::BufferBinding::PixelUnpack);
@@ -265,8 +265,8 @@
     nativegl::TexSubImageFormat texSubImageFormat =
         nativegl::GetTexSubImageFormat(mFunctions, mWorkarounds, format, type);
 
-    gl::TextureTarget target = index.target;
-    size_t level             = static_cast<size_t>(index.mipIndex);
+    gl::TextureTarget target = index.getTarget();
+    size_t level             = static_cast<size_t>(index.getLevelIndex());
 
     ASSERT(getLevelInfo(target, level).lumaWorkaround.enabled ==
            GetLevelInfo(format, texSubImageFormat.format).lumaWorkaround.enabled);
@@ -466,8 +466,8 @@
                                         size_t imageSize,
                                         const uint8_t *pixels)
 {
-    gl::TextureTarget target = index.target;
-    size_t level             = static_cast<size_t>(index.mipIndex);
+    gl::TextureTarget target = index.getTarget();
+    size_t level             = static_cast<size_t>(index.getLevelIndex());
     ASSERT(TextureTargetToType(target) == getType());
 
     nativegl::CompressedTexImageFormat compressedTexImageFormat =
@@ -507,8 +507,8 @@
                                            size_t imageSize,
                                            const uint8_t *pixels)
 {
-    gl::TextureTarget target = index.target;
-    size_t level             = static_cast<size_t>(index.mipIndex);
+    gl::TextureTarget target = index.getTarget();
+    size_t level             = static_cast<size_t>(index.getLevelIndex());
     ASSERT(TextureTargetToType(target) == getType());
 
     nativegl::CompressedTexSubImageFormat compressedTexSubImageFormat =
@@ -546,8 +546,8 @@
                                GLenum internalFormat,
                                gl::Framebuffer *source)
 {
-    gl::TextureTarget target = index.target;
-    size_t level             = static_cast<size_t>(index.mipIndex);
+    gl::TextureTarget target = index.getTarget();
+    size_t level             = static_cast<size_t>(index.getLevelIndex());
     GLenum type = GL_NONE;
     ANGLE_TRY(source->getImplementationColorReadType(context, &type));
     nativegl::CopyTexImageImageFormat copyTexImageFormat =
@@ -648,8 +648,8 @@
                                   const gl::Rectangle &origSourceArea,
                                   gl::Framebuffer *source)
 {
-    gl::TextureTarget target                 = index.target;
-    size_t level                             = static_cast<size_t>(index.mipIndex);
+    gl::TextureTarget target                 = index.getTarget();
+    size_t level                             = static_cast<size_t>(index.getLevelIndex());
     const FramebufferGL *sourceFramebufferGL = GetImplAs<FramebufferGL>(source);
 
     // Clip source area to framebuffer.
@@ -712,8 +712,8 @@
                                  bool unpackUnmultiplyAlpha,
                                  const gl::Texture *source)
 {
-    gl::TextureTarget target             = index.target;
-    size_t level                         = static_cast<size_t>(index.mipIndex);
+    gl::TextureTarget target             = index.getTarget();
+    size_t level                         = static_cast<size_t>(index.getLevelIndex());
     const TextureGL *sourceGL            = GetImplAs<TextureGL>(source);
     const gl::ImageDesc &sourceImageDesc =
         sourceGL->mState.getImageDesc(NonCubeTextureTypeToTarget(source->getType()), sourceLevel);
@@ -737,8 +737,8 @@
                                     bool unpackUnmultiplyAlpha,
                                     const gl::Texture *source)
 {
-    gl::TextureTarget target                 = index.target;
-    size_t level                             = static_cast<size_t>(index.mipIndex);
+    gl::TextureTarget target                 = index.getTarget();
+    size_t level                             = static_cast<size_t>(index.getLevelIndex());
     const gl::InternalFormat &destFormatInfo = *mState.getImageDesc(target, level).format.info;
     return copySubTextureHelper(context, target, level, destOffset, sourceLevel, sourceArea,
                                 destFormatInfo.format, destFormatInfo.type, unpackFlipY,
@@ -1436,7 +1436,7 @@
                                         const gl::ImageIndex &imageIndex)
 {
     GLenum nativeInternalFormat =
-        getLevelInfo(imageIndex.target, imageIndex.mipIndex).nativeInternalFormat;
+        getLevelInfo(imageIndex.getTarget(), imageIndex.getLevelIndex()).nativeInternalFormat;
     if (nativegl::SupportsNativeRendering(mFunctions, mState.getType(), nativeInternalFormat))
     {
         int levelDepth = mState.getImageDesc(imageIndex).size.depth;
@@ -1477,16 +1477,16 @@
         if (nativegl::UseTexImage2D(getType()))
         {
             mFunctions->compressedTexSubImage2D(
-                ToGLenum(imageIndex.target), imageIndex.mipIndex, 0, 0, desc.size.width,
+                ToGLenum(imageIndex.getTarget()), imageIndex.getLevelIndex(), 0, 0, desc.size.width,
                 desc.size.height, nativeSubImageFormat.format, imageSize, zero->data());
         }
         else
         {
             ASSERT(nativegl::UseTexImage3D(getType()));
-            mFunctions->compressedTexSubImage3D(ToGLenum(imageIndex.target), imageIndex.mipIndex, 0,
-                                                0, 0, desc.size.width, desc.size.height,
-                                                desc.size.depth, nativeSubImageFormat.format,
-                                                imageSize, zero->data());
+            mFunctions->compressedTexSubImage3D(
+                ToGLenum(imageIndex.getTarget()), imageIndex.getLevelIndex(), 0, 0, 0,
+                desc.size.width, desc.size.height, desc.size.depth, nativeSubImageFormat.format,
+                imageSize, zero->data());
         }
     }
     else
@@ -1505,16 +1505,16 @@
 
         if (nativegl::UseTexImage2D(getType()))
         {
-            mFunctions->texSubImage2D(ToGLenum(imageIndex.target), imageIndex.mipIndex, 0, 0,
-                                      desc.size.width, desc.size.height,
+            mFunctions->texSubImage2D(ToGLenum(imageIndex.getTarget()), imageIndex.getLevelIndex(),
+                                      0, 0, desc.size.width, desc.size.height,
                                       nativeSubImageFormat.format, nativeSubImageFormat.type,
                                       zero->data());
         }
         else
         {
             ASSERT(nativegl::UseTexImage3D(getType()));
-            mFunctions->texSubImage3D(ToGLenum(imageIndex.target), imageIndex.mipIndex, 0, 0, 0,
-                                      desc.size.width, desc.size.height, desc.size.depth,
+            mFunctions->texSubImage3D(ToGLenum(imageIndex.getTarget()), imageIndex.getLevelIndex(),
+                                      0, 0, 0, desc.size.width, desc.size.height, desc.size.depth,
                                       nativeSubImageFormat.format, nativeSubImageFormat.type,
                                       zero->data());
         }
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
index 6cda5d5..00735f7 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
@@ -190,7 +190,7 @@
     VkDevice device      = contextVk->getDevice();
 
     // TODO(jmadill): support multi-level textures.
-    ASSERT(index.mipIndex == 0);
+    ASSERT(index.getLevelIndex() == 0);
 
     // Convert internalFormat to sized internal format.
     const gl::InternalFormat &formatInfo = gl::GetInternalFormatInfo(internalFormat, type);
@@ -212,7 +212,7 @@
     }
 
     // TODO(jmadill): Cube map textures. http://anglebug.com/2318
-    if (index.target != gl::TextureTarget::_2D)
+    if (index.getTarget() != gl::TextureTarget::_2D)
     {
         UNIMPLEMENTED();
         return gl::InternalError();
@@ -375,10 +375,10 @@
                                                FramebufferAttachmentRenderTarget **rtOut)
 {
     // TODO(jmadill): Handle cube textures. http://anglebug.com/2318
-    ASSERT(imageIndex.type == gl::TextureType::_2D);
+    ASSERT(imageIndex.getType() == gl::TextureType::_2D);
 
     // Non-zero mip level attachments are an ES 3.0 feature.
-    ASSERT(imageIndex.mipIndex == 0 && imageIndex.layerIndex == gl::ImageIndex::ENTIRE_LEVEL);
+    ASSERT(imageIndex.getLevelIndex() == 0 && !imageIndex.hasLayer());
 
     ContextVk *contextVk = vk::GetImpl(context);
     RendererVk *renderer = contextVk->getRenderer();
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index cdb79c9..29f11f5 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -2473,7 +2473,7 @@
         if (readColorAttachment && drawColorAttachment)
         {
             if (!(readColorAttachment->type() == GL_TEXTURE &&
-                  readColorAttachment->getTextureImageIndex().type == TextureType::_2D) &&
+                  readColorAttachment->getTextureImageIndex().getType() == TextureType::_2D) &&
                 readColorAttachment->type() != GL_RENDERBUFFER &&
                 readColorAttachment->type() != GL_FRAMEBUFFER_DEFAULT)
             {
@@ -2489,7 +2489,7 @@
                 if (attachment)
                 {
                     if (!(attachment->type() == GL_TEXTURE &&
-                          attachment->getTextureImageIndex().type == TextureType::_2D) &&
+                          attachment->getTextureImageIndex().getType() == TextureType::_2D) &&
                         attachment->type() != GL_RENDERBUFFER &&
                         attachment->type() != GL_FRAMEBUFFER_DEFAULT)
                     {