//
// Copyright 2014 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

// TextureD3D.cpp: Implementations of the Texture interfaces shared betweeen the D3D backends.

#include "libANGLE/renderer/d3d/TextureD3D.h"

#include "common/mathutil.h"
#include "common/utilities.h"
#include "libANGLE/Buffer.h"
#include "libANGLE/Config.h"
#include "libANGLE/Context.h"
#include "libANGLE/Framebuffer.h"
#include "libANGLE/Image.h"
#include "libANGLE/Surface.h"
#include "libANGLE/Texture.h"
#include "libANGLE/formatutils.h"
#include "libANGLE/renderer/BufferImpl.h"
#include "libANGLE/renderer/d3d/BufferD3D.h"
#include "libANGLE/renderer/d3d/ContextD3D.h"
#include "libANGLE/renderer/d3d/EGLImageD3D.h"
#include "libANGLE/renderer/d3d/ImageD3D.h"
#include "libANGLE/renderer/d3d/RenderTargetD3D.h"
#include "libANGLE/renderer/d3d/SurfaceD3D.h"
#include "libANGLE/renderer/d3d/TextureStorage.h"

namespace rx
{

namespace
{

angle::Result GetUnpackPointer(const gl::Context *context,
                               const gl::PixelUnpackState &unpack,
                               gl::Buffer *unpackBuffer,
                               const uint8_t *pixels,
                               ptrdiff_t layerOffset,
                               const uint8_t **pointerOut)
{
    if (unpackBuffer)
    {
        // Do a CPU readback here, if we have an unpack buffer bound and the fast GPU path is not supported
        ptrdiff_t offset = reinterpret_cast<ptrdiff_t>(pixels);

        // TODO: this is the only place outside of renderer that asks for a buffers raw data.
        // This functionality should be moved into renderer and the getData method of BufferImpl removed.
        BufferD3D *bufferD3D = GetImplAs<BufferD3D>(unpackBuffer);
        ASSERT(bufferD3D);
        const uint8_t *bufferData = nullptr;
        ANGLE_TRY(bufferD3D->getData(context, &bufferData));
        *pointerOut = bufferData + offset;
    }
    else
    {
        *pointerOut = pixels;
    }

    // Offset the pointer for 2D array layer (if it's valid)
    if (*pointerOut != nullptr)
    {
        *pointerOut += layerOffset;
    }

    return angle::Result::Continue();
}

bool IsRenderTargetUsage(GLenum usage)
{
    return (usage == GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
}

}

TextureD3D::TextureD3D(const gl::TextureState &state, RendererD3D *renderer)
    : TextureImpl(state),
      mRenderer(renderer),
      mDirtyImages(true),
      mImmutable(false),
      mTexStorage(nullptr),
      mBaseLevel(0)
{
}

TextureD3D::~TextureD3D()
{
    ASSERT(!mTexStorage);
}

angle::Result TextureD3D::getNativeTexture(const gl::Context *context, TextureStorage **outStorage)
{
    // ensure the underlying texture is created
    ANGLE_TRY(initializeStorage(context, false));

    if (mTexStorage)
    {
        ANGLE_TRY(updateStorage(context));
    }

    ASSERT(outStorage);

    *outStorage = mTexStorage;
    return angle::Result::Continue();
}

angle::Result TextureD3D::getImageAndSyncFromStorage(const gl::Context *context,
                                                     const gl::ImageIndex &index,
                                                     ImageD3D **outImage)
{
    ImageD3D *image = getImage(index);
    if (mTexStorage && mTexStorage->isRenderTarget())
    {
        ANGLE_TRY(image->copyFromTexStorage(context, index, mTexStorage));
        mDirtyImages = true;
    }
    *outImage = image;
    return angle::Result::Continue();
}

GLint TextureD3D::getLevelZeroWidth() const
{
    ASSERT(gl::CountLeadingZeros(static_cast<uint32_t>(getBaseLevelWidth())) > getBaseLevel());
    return getBaseLevelWidth() << mBaseLevel;
}

GLint TextureD3D::getLevelZeroHeight() const
{
    ASSERT(gl::CountLeadingZeros(static_cast<uint32_t>(getBaseLevelHeight())) > getBaseLevel());
    return getBaseLevelHeight() << mBaseLevel;
}

GLint TextureD3D::getLevelZeroDepth() const
{
    return getBaseLevelDepth();
}

GLint TextureD3D::getBaseLevelWidth() const
{
    const ImageD3D *baseImage = getBaseLevelImage();
    return (baseImage ? baseImage->getWidth() : 0);
}

GLint TextureD3D::getBaseLevelHeight() const
{
    const ImageD3D *baseImage = getBaseLevelImage();
    return (baseImage ? baseImage->getHeight() : 0);
}

GLint TextureD3D::getBaseLevelDepth() const
{
    const ImageD3D *baseImage = getBaseLevelImage();
    return (baseImage ? baseImage->getDepth() : 0);
}

// Note: "base level image" is loosely defined to be any image from the base level,
// where in the base of 2D array textures and cube maps there are several. Don't use
// the base level image for anything except querying texture format and size.
GLenum TextureD3D::getBaseLevelInternalFormat() const
{
    const ImageD3D *baseImage = getBaseLevelImage();
    return (baseImage ? baseImage->getInternalFormat() : GL_NONE);
}

gl::Error TextureD3D::setStorage(const gl::Context *context,
                                 gl::TextureType type,
                                 size_t levels,
                                 GLenum internalFormat,
                                 const gl::Extents &size)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3D::setStorageMultisample(const gl::Context *context,
                                            gl::TextureType type,
                                            GLsizei samples,
                                            GLint internalFormat,
                                            const gl::Extents &size,
                                            bool fixedSampleLocations)
{
    UNREACHABLE();
    return gl::InternalError();
}

bool TextureD3D::shouldUseSetData(const ImageD3D *image) const
{
    if (!mRenderer->getWorkarounds().setDataFasterThanImageUpload)
    {
        return false;
    }

    if (image->isDirty())
    {
        return false;
    }

    gl::InternalFormat internalFormat = gl::GetSizedInternalFormatInfo(image->getInternalFormat());

    // We can only handle full updates for depth-stencil textures, so to avoid complications
    // disable them entirely.
    if (internalFormat.depthBits > 0 || internalFormat.stencilBits > 0)
    {
        return false;
    }

    // TODO(jmadill): Handle compressed internal formats
    return (mTexStorage && !internalFormat.compressed);
}

angle::Result TextureD3D::setImageImpl(const gl::Context *context,
                                       const gl::ImageIndex &index,
                                       GLenum type,
                                       const gl::PixelUnpackState &unpack,
                                       const uint8_t *pixels,
                                       ptrdiff_t layerOffset)
{
    ImageD3D *image = getImage(index);
    gl::Buffer *unpackBuffer =
        context->getGLState().getTargetBuffer(gl::BufferBinding::PixelUnpack);
    ASSERT(image);

    // No-op
    if (image->getWidth() == 0 || image->getHeight() == 0 || image->getDepth() == 0)
    {
        return angle::Result::Continue();
    }

    // We no longer need the "GLenum format" parameter to TexImage to determine what data format "pixels" contains.
    // From our image internal format we know how many channels to expect, and "type" gives the format of pixel's components.
    const uint8_t *pixelData = nullptr;
    ANGLE_TRY(GetUnpackPointer(context, unpack, unpackBuffer, pixels, layerOffset, &pixelData));

    if (pixelData != nullptr)
    {
        if (shouldUseSetData(image))
        {
            ANGLE_TRY(
                mTexStorage->setData(context, index, image, nullptr, type, unpack, pixelData));
        }
        else
        {
            gl::Box fullImageArea(0, 0, 0, image->getWidth(), image->getHeight(), image->getDepth());
            ANGLE_TRY(image->loadData(context, fullImageArea, unpack, type, pixelData,
                                      index.usesTex3D()));
        }

        mDirtyImages = true;
    }

    return angle::Result::Continue();
}

angle::Result TextureD3D::subImage(const gl::Context *context,
                                   const gl::ImageIndex &index,
                                   const gl::Box &area,
                                   GLenum format,
                                   GLenum type,
                                   const gl::PixelUnpackState &unpack,
                                   gl::Buffer *unpackBuffer,
                                   const uint8_t *pixels,
                                   ptrdiff_t layerOffset)
{
    // CPU readback & copy where direct GPU copy is not supported
    const uint8_t *pixelData = nullptr;
    ANGLE_TRY(GetUnpackPointer(context, unpack, unpackBuffer, pixels, layerOffset, &pixelData));

    if (pixelData != nullptr)
    {
        ImageD3D *image = getImage(index);
        ASSERT(image);

        if (shouldUseSetData(image))
        {
            return mTexStorage->setData(context, index, image, &area, type, unpack, pixelData);
        }

        ANGLE_TRY(image->loadData(context, area, unpack, type, pixelData, index.usesTex3D()));
        ANGLE_TRY(commitRegion(context, index, area));
        mDirtyImages = true;
    }

    return angle::Result::Continue();
}

angle::Result TextureD3D::setCompressedImageImpl(const gl::Context *context,
                                                 const gl::ImageIndex &index,
                                                 const gl::PixelUnpackState &unpack,
                                                 const uint8_t *pixels,
                                                 ptrdiff_t layerOffset)
{
    ImageD3D *image = getImage(index);
    ASSERT(image);

    if (image->getWidth() == 0 || image->getHeight() == 0 || image->getDepth() == 0)
    {
        return angle::Result::Continue();
    }

    // We no longer need the "GLenum format" parameter to TexImage to determine what data format "pixels" contains.
    // From our image internal format we know how many channels to expect, and "type" gives the format of pixel's components.
    const uint8_t *pixelData = nullptr;
    gl::Buffer *unpackBuffer =
        context->getGLState().getTargetBuffer(gl::BufferBinding::PixelUnpack);
    ANGLE_TRY(GetUnpackPointer(context, unpack, unpackBuffer, pixels, layerOffset, &pixelData));

    if (pixelData != nullptr)
    {
        gl::Box fullImageArea(0, 0, 0, image->getWidth(), image->getHeight(), image->getDepth());
        ANGLE_TRY(image->loadCompressedData(context, fullImageArea, pixelData));

        mDirtyImages = true;
    }

    return angle::Result::Continue();
}

angle::Result TextureD3D::subImageCompressed(const gl::Context *context,
                                             const gl::ImageIndex &index,
                                             const gl::Box &area,
                                             GLenum format,
                                             const gl::PixelUnpackState &unpack,
                                             const uint8_t *pixels,
                                             ptrdiff_t layerOffset)
{
    const uint8_t *pixelData = nullptr;
    gl::Buffer *unpackBuffer =
        context->getGLState().getTargetBuffer(gl::BufferBinding::PixelUnpack);
    ANGLE_TRY(GetUnpackPointer(context, unpack, unpackBuffer, pixels, layerOffset, &pixelData));

    if (pixelData != nullptr)
    {
        ImageD3D *image = getImage(index);
        ASSERT(image);

        ANGLE_TRY(image->loadCompressedData(context, area, pixelData));

        mDirtyImages = true;
    }

    return angle::Result::Continue();
}

bool TextureD3D::isFastUnpackable(const gl::Buffer *unpackBuffer, GLenum sizedInternalFormat)
{
    return unpackBuffer != nullptr &&
           mRenderer->supportsFastCopyBufferToTexture(sizedInternalFormat);
}

angle::Result TextureD3D::fastUnpackPixels(const gl::Context *context,
                                           const gl::PixelUnpackState &unpack,
                                           const uint8_t *pixels,
                                           const gl::Box &destArea,
                                           GLenum sizedInternalFormat,
                                           GLenum type,
                                           RenderTargetD3D *destRenderTarget)
{
    bool check = (unpack.skipRows != 0 || unpack.skipPixels != 0 || unpack.imageHeight != 0 ||
                  unpack.skipImages != 0);
    ANGLE_CHECK_HR(GetImplAs<ContextD3D>(context), !check,
                   "Unimplemented pixel store parameters in fastUnpackPixels", E_FAIL);

    // No-op
    if (destArea.width <= 0 && destArea.height <= 0 && destArea.depth <= 0)
    {
        return angle::Result::Continue();
    }

    // In order to perform the fast copy through the shader, we must have the right format, and be able
    // to create a render target.
    ASSERT(mRenderer->supportsFastCopyBufferToTexture(sizedInternalFormat));

    uintptr_t offset = reinterpret_cast<uintptr_t>(pixels);

    ANGLE_TRY(mRenderer->fastCopyBufferToTexture(context, unpack, static_cast<unsigned int>(offset),
                                                 destRenderTarget, sizedInternalFormat, type,
                                                 destArea));

    return angle::Result::Continue();
}

GLint TextureD3D::creationLevels(GLsizei width, GLsizei height, GLsizei depth) const
{
    if ((gl::isPow2(width) && gl::isPow2(height) && gl::isPow2(depth)) ||
        mRenderer->getNativeExtensions().textureNPOT)
    {
        // Maximum number of levels
        return gl::log2(std::max(std::max(width, height), depth)) + 1;
    }
    else
    {
        // OpenGL ES 2.0 without GL_OES_texture_npot does not permit NPOT mipmaps.
        return 1;
    }
}

TextureStorage *TextureD3D::getStorage()
{
    ASSERT(mTexStorage);
    return mTexStorage;
}

ImageD3D *TextureD3D::getBaseLevelImage() const
{
    if (mBaseLevel >= gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS)
    {
        return nullptr;
    }
    return getImage(getImageIndex(mBaseLevel, 0));
}

gl::Error TextureD3D::setImageExternal(const gl::Context *context,
                                       gl::TextureType type,
                                       egl::Stream *stream,
                                       const egl::Stream::GLTextureDescription &desc)
{
    // Only external images can accept external textures
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3D::generateMipmap(const gl::Context *context)
{
    const GLuint baseLevel = mState.getEffectiveBaseLevel();
    const GLuint maxLevel = mState.getMipmapMaxLevel();
    ASSERT(maxLevel > baseLevel);  // Should be checked before calling this.

    if (mTexStorage && mRenderer->getWorkarounds().zeroMaxLodWorkaround)
    {
        // Switch to using the mipmapped texture.
        TextureStorage *textureStorage = nullptr;
        ANGLE_TRY(getNativeTexture(context, &textureStorage));
        ANGLE_TRY(textureStorage->useLevelZeroWorkaroundTexture(context, false));
    }

    // Set up proper mipmap chain in our Image array.
    ANGLE_TRY(initMipmapImages(context));

    if (mTexStorage && mTexStorage->supportsNativeMipmapFunction())
    {
        ANGLE_TRY(updateStorage(context));

        // Generate the mipmap chain using the ad-hoc DirectX function.
        ANGLE_TRY(mRenderer->generateMipmapUsingD3D(context, mTexStorage, mState));
    }
    else
    {
        // Generate the mipmap chain, one level at a time.
        ANGLE_TRY(generateMipmapUsingImages(context, maxLevel));
    }

    return gl::NoError();
}

angle::Result TextureD3D::generateMipmapUsingImages(const gl::Context *context,
                                                    const GLuint maxLevel)
{
    // We know that all layers have the same dimension, for the texture to be complete
    GLint layerCount = static_cast<GLint>(getLayerCount(mBaseLevel));

    // When making mipmaps with the setData workaround enabled, the texture storage has
    // the image data already. For non-render-target storage, we have to pull it out into
    // an image layer.
    if (mRenderer->getWorkarounds().setDataFasterThanImageUpload && mTexStorage)
    {
        if (!mTexStorage->isRenderTarget())
        {
            // Copy from the storage mip 0 to Image mip 0
            for (GLint layer = 0; layer < layerCount; ++layer)
            {
                gl::ImageIndex srcIndex = getImageIndex(mBaseLevel, layer);

                ImageD3D *image = getImage(srcIndex);
                ANGLE_TRY(image->copyFromTexStorage(context, srcIndex, mTexStorage));
            }
        }
        else
        {
            ANGLE_TRY(updateStorage(context));
        }
    }

    // TODO: Decouple this from zeroMaxLodWorkaround. This is a 9_3 restriction, unrelated to zeroMaxLodWorkaround.
    // The restriction is because Feature Level 9_3 can't create SRVs on individual levels of the texture.
    // As a result, even if the storage is a rendertarget, we can't use the GPU to generate the mipmaps without further work.
    // The D3D9 renderer works around this by copying each level of the texture into its own single-layer GPU texture (in Blit9::boxFilter).
    // Feature Level 9_3 could do something similar, or it could continue to use CPU-side mipmap generation, or something else.
    bool renderableStorage = (mTexStorage && mTexStorage->isRenderTarget() && !(mRenderer->getWorkarounds().zeroMaxLodWorkaround));

    for (GLint layer = 0; layer < layerCount; ++layer)
    {
        for (GLuint mip = mBaseLevel + 1; mip <= maxLevel; ++mip)
        {
            ASSERT(getLayerCount(mip) == layerCount);

            gl::ImageIndex sourceIndex = getImageIndex(mip - 1, layer);
            gl::ImageIndex destIndex = getImageIndex(mip, layer);

            if (renderableStorage)
            {
                // GPU-side mipmapping
                ANGLE_TRY(mTexStorage->generateMipmap(context, sourceIndex, destIndex));
            }
            else
            {
                // CPU-side mipmapping
                ANGLE_TRY(
                    mRenderer->generateMipmap(context, getImage(destIndex), getImage(sourceIndex)));
            }
        }
    }

    mDirtyImages = true;

    if (mTexStorage)
    {
        ANGLE_TRY(updateStorage(context));
    }

    return angle::Result::Continue();
}

bool TextureD3D::isBaseImageZeroSize() const
{
    ImageD3D *baseImage = getBaseLevelImage();

    if (!baseImage || baseImage->getWidth() <= 0 || baseImage->getHeight() <= 0)
    {
        return true;
    }

    if (baseImage->getType() == gl::TextureType::_3D && baseImage->getDepth() <= 0)
    {
        return true;
    }

    if (baseImage->getType() == gl::TextureType::_2DArray && getLayerCount(getBaseLevel()) <= 0)
    {
        return true;
    }

    return false;
}

angle::Result TextureD3D::ensureRenderTarget(const gl::Context *context)
{
    ANGLE_TRY(initializeStorage(context, true));

    // initializeStorage can fail with NoError if the texture is not complete. This is not
    // an error for incomplete sampling, but it is a big problem for rendering.
    if (!mTexStorage)
    {
        ANGLE_HR_UNREACHABLE(GetImplAs<ContextD3D>(context));
        return angle::Result::Stop();
    }

    if (!isBaseImageZeroSize())
    {
        ASSERT(mTexStorage);
        if (!mTexStorage->isRenderTarget())
        {
            TexStoragePointer newRenderTargetStorage(context);
            ANGLE_TRY(createCompleteStorage(true, &newRenderTargetStorage));

            ANGLE_TRY(mTexStorage->copyToStorage(context, newRenderTargetStorage.get()));
            ANGLE_TRY(setCompleteTexStorage(context, newRenderTargetStorage.get()));
            newRenderTargetStorage.release();
        }
    }

    return angle::Result::Continue();
}

bool TextureD3D::canCreateRenderTargetForImage(const gl::ImageIndex &index) const
{
    if (index.getType() == gl::TextureType::_2DMultisample ||
        index.getType() == gl::TextureType::_2DMultisampleArray)
    {
        ASSERT(index.getType() != gl::TextureType::_2DMultisampleArray || index.hasLayer());
        return true;
    }

    ImageD3D *image = getImage(index);
    ASSERT(image);
    bool levelsComplete = (isImageComplete(index) && isImageComplete(getImageIndex(0, 0)));
    return (image->isRenderableFormat() && levelsComplete);
}

angle::Result TextureD3D::commitRegion(const gl::Context *context,
                                       const gl::ImageIndex &index,
                                       const gl::Box &region)
{
    if (mTexStorage)
    {
        ASSERT(isValidIndex(index));
        ImageD3D *image = getImage(index);
        ANGLE_TRY(image->copyToStorage(context, mTexStorage, index, region));
        image->markClean();
    }

    return angle::Result::Continue();
}

gl::Error TextureD3D::getAttachmentRenderTarget(const gl::Context *context,
                                                GLenum /*binding*/,
                                                const gl::ImageIndex &imageIndex,
                                                FramebufferAttachmentRenderTarget **rtOut)
{
    RenderTargetD3D *rtD3D = nullptr;
    gl::Error error        = getRenderTarget(context, imageIndex, &rtD3D);
    *rtOut = static_cast<FramebufferAttachmentRenderTarget *>(rtD3D);
    return error;
}

gl::Error TextureD3D::setBaseLevel(const gl::Context *context, GLuint baseLevel)
{
    const int oldStorageWidth  = std::max(1, getLevelZeroWidth());
    const int oldStorageHeight = std::max(1, getLevelZeroHeight());
    const int oldStorageDepth  = std::max(1, getLevelZeroDepth());
    const int oldStorageFormat = getBaseLevelInternalFormat();
    mBaseLevel                 = baseLevel;

    // When the base level changes, the texture storage might not be valid anymore, since it could
    // have been created based on the dimensions of the previous specified level range.
    const int newStorageWidth  = std::max(1, getLevelZeroWidth());
    const int newStorageHeight = std::max(1, getLevelZeroHeight());
    const int newStorageDepth = std::max(1, getLevelZeroDepth());
    const int newStorageFormat = getBaseLevelInternalFormat();
    if (mTexStorage &&
        (newStorageWidth != oldStorageWidth || newStorageHeight != oldStorageHeight ||
         newStorageDepth != oldStorageDepth || newStorageFormat != oldStorageFormat))
    {
        markAllImagesDirty();
        ANGLE_TRY(releaseTexStorage(context));
    }

    return gl::NoError();
}

gl::Error TextureD3D::syncState(const gl::Context *context, const gl::Texture::DirtyBits &dirtyBits)
{
    // TODO(geofflang): Use dirty bits
    return gl::NoError();
}

angle::Result TextureD3D::releaseTexStorage(const gl::Context *context)
{
    if (!mTexStorage)
    {
        return angle::Result::Continue();
    }

    onStateChange(context, angle::SubjectMessage::DEPENDENT_DIRTY_BITS);

    auto err = mTexStorage->onDestroy(context);
    SafeDelete(mTexStorage);
    return err;
}

gl::Error TextureD3D::onDestroy(const gl::Context *context)
{
    return releaseTexStorage(context);
}

gl::Error TextureD3D::initializeContents(const gl::Context *context,
                                         const gl::ImageIndex &imageIndexIn)
{
    gl::ImageIndex imageIndex = imageIndexIn;

    // 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 (imageIndexIn.getType() == gl::TextureType::_3D)
    {
        imageIndex =
            gl::ImageIndex::Make3D(imageIndexIn.getLevelIndex(), gl::ImageIndex::kEntireLevel);
    }
    else if (imageIndexIn.getType() == gl::TextureType::_2DArray && !imageIndexIn.hasLayer())
    {
        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, iterator.next()));
        }
        return gl::NoError();
    }
    else if (imageIndexIn.getType() == gl::TextureType::_2DMultisampleArray &&
             !imageIndexIn.hasLayer())
    {
        std::array<GLint, gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS> tempLayerCounts;

        ASSERT(imageIndexIn.getLevelIndex() == 0);
        tempLayerCounts[0] = getLayerCount(0);
        gl::ImageIndexIterator iterator =
            gl::ImageIndexIterator::Make2DMultisampleArray(tempLayerCounts.data());
        while (iterator.hasNext())
        {
            ANGLE_TRY(initializeContents(context, iterator.next()));
        }
        return gl::NoError();
    }

    // Force image clean.
    ImageD3D *image = getImage(imageIndex);
    if (image)
    {
        image->markClean();
    }

    // Fast path: can use a render target clear.
    // We don't use the fast path with the zero max lod workaround because it would introduce a race
    // between the rendertarget and the staging images.
    if (canCreateRenderTargetForImage(imageIndex) &&
        !mRenderer->getWorkarounds().zeroMaxLodWorkaround)
    {
        ANGLE_TRY(ensureRenderTarget(context));
        ASSERT(mTexStorage);
        RenderTargetD3D *renderTarget = nullptr;
        ANGLE_TRY(mTexStorage->getRenderTarget(context, imageIndex, &renderTarget));
        ANGLE_TRY(mRenderer->initRenderTarget(context, renderTarget));
        return gl::NoError();
    }

    // Slow path: non-renderable texture or the texture levels aren't set up.
    const auto &formatInfo = gl::GetSizedInternalFormatInfo(image->getInternalFormat());

    GLuint imageBytes = 0;
    ANGLE_TRY_CHECKED_MATH(
        formatInfo.computeRowPitch(formatInfo.type, image->getWidth(), 1, 0, &imageBytes));
    imageBytes *= image->getHeight() * image->getDepth();

    gl::PixelUnpackState zeroDataUnpackState;
    zeroDataUnpackState.alignment = 1;

    angle::MemoryBuffer *zeroBuffer = nullptr;
    ANGLE_TRY_ALLOCATION(context->getZeroFilledBuffer(imageBytes, &zeroBuffer));

    if (shouldUseSetData(image))
    {
        ANGLE_TRY(mTexStorage->setData(context, imageIndex, image, nullptr, formatInfo.type,
                                       zeroDataUnpackState, zeroBuffer->data()));
    }
    else
    {
        gl::Box fullImageArea(0, 0, 0, image->getWidth(), image->getHeight(), image->getDepth());
        ANGLE_TRY(image->loadData(context, fullImageArea, zeroDataUnpackState, formatInfo.type,
                                  zeroBuffer->data(), false));

        // Force an update to the tex storage so we avoid problems with subImage and dirty regions.
        if (mTexStorage)
        {
            ANGLE_TRY(commitRegion(context, imageIndex, fullImageArea));
            image->markClean();
        }
        else
        {
            mDirtyImages = true;
        }
    }
    return gl::NoError();
}

TextureD3D_2D::TextureD3D_2D(const gl::TextureState &state, RendererD3D *renderer)
    : TextureD3D(state, renderer)
{
    mEGLImageTarget = false;
    for (auto &image : mImageArray)
    {
        image.reset(renderer->createImage());
    }
}

gl::Error TextureD3D_2D::onDestroy(const gl::Context *context)
{
    // Delete the Images before the TextureStorage. Images might be relying on the TextureStorage
    // for some of their data. If TextureStorage is deleted before the Images, then their data will
    // be wastefully copied back from the GPU before we delete the Images.
    for (auto &image : mImageArray)
    {
        image.reset();
    }
    return TextureD3D::onDestroy(context);
}

TextureD3D_2D::~TextureD3D_2D()
{
}

ImageD3D *TextureD3D_2D::getImage(int level, int layer) const
{
    ASSERT(level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
    ASSERT(layer == 0);
    return mImageArray[level].get();
}

ImageD3D *TextureD3D_2D::getImage(const gl::ImageIndex &index) const
{
    ASSERT(index.getLevelIndex() < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
    ASSERT(!index.hasLayer());
    ASSERT(index.getType() == gl::TextureType::_2D);
    return mImageArray[index.getLevelIndex()].get();
}

GLsizei TextureD3D_2D::getLayerCount(int level) const
{
    ASSERT(level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
    return 1;
}

GLsizei TextureD3D_2D::getWidth(GLint level) const
{
    if (level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS)
        return mImageArray[level]->getWidth();
    else
        return 0;
}

GLsizei TextureD3D_2D::getHeight(GLint level) const
{
    if (level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS)
        return mImageArray[level]->getHeight();
    else
        return 0;
}

GLenum TextureD3D_2D::getInternalFormat(GLint level) const
{
    if (level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS)
        return mImageArray[level]->getInternalFormat();
    else
        return GL_NONE;
}

bool TextureD3D_2D::isDepth(GLint level) const
{
    return gl::GetSizedInternalFormatInfo(getInternalFormat(level)).depthBits > 0;
}

bool TextureD3D_2D::isSRGB(GLint level) const
{
    return gl::GetSizedInternalFormatInfo(getInternalFormat(level)).colorEncoding == GL_SRGB;
}

gl::Error TextureD3D_2D::setImage(const gl::Context *context,
                                  const gl::ImageIndex &index,
                                  GLenum internalFormat,
                                  const gl::Extents &size,
                                  GLenum format,
                                  GLenum type,
                                  const gl::PixelUnpackState &unpack,
                                  const uint8_t *pixels)
{
    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.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.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.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.getLevelIndex()]->markClean();

        fastUnpacked = true;
    }

    if (!fastUnpacked)
    {
        ANGLE_TRY(setImageImpl(context, index, type, unpack, pixels, 0));
    }

    return gl::NoError();
}

gl::Error TextureD3D_2D::setSubImage(const gl::Context *context,
                                     const gl::ImageIndex &index,
                                     const gl::Box &area,
                                     GLenum format,
                                     GLenum type,
                                     const gl::PixelUnpackState &unpack,
                                     gl::Buffer *unpackBuffer,
                                     const uint8_t *pixels)
{
    ASSERT(index.getTarget() == gl::TextureTarget::_2D && area.depth == 1 && area.z == 0);

    GLenum mipFormat = getInternalFormat(index.getLevelIndex());
    if (isFastUnpackable(unpackBuffer, mipFormat) && isLevelComplete(index.getLevelIndex()))
    {
        RenderTargetD3D *renderTarget = nullptr;
        ANGLE_TRY(getRenderTarget(context, index, &renderTarget));
        ASSERT(!mImageArray[index.getLevelIndex()]->isDirty());

        return fastUnpackPixels(context, unpack, pixels, area, mipFormat, type, renderTarget);
    }
    else
    {
        return TextureD3D::subImage(context, index, area, format, type, unpack, unpackBuffer,
                                    pixels, 0);
    }
}

gl::Error TextureD3D_2D::setCompressedImage(const gl::Context *context,
                                            const gl::ImageIndex &index,
                                            GLenum internalFormat,
                                            const gl::Extents &size,
                                            const gl::PixelUnpackState &unpack,
                                            size_t imageSize,
                                            const uint8_t *pixels)
{
    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.getLevelIndex(), internalFormat, size, false));

    return setCompressedImageImpl(context, index, unpack, pixels, 0);
}

gl::Error TextureD3D_2D::setCompressedSubImage(const gl::Context *context,
                                               const gl::ImageIndex &index,
                                               const gl::Box &area,
                                               GLenum format,
                                               const gl::PixelUnpackState &unpack,
                                               size_t imageSize,
                                               const uint8_t *pixels)
{
    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);
}

gl::Error TextureD3D_2D::copyImage(const gl::Context *context,
                                   const gl::ImageIndex &index,
                                   const gl::Rectangle &origSourceArea,
                                   GLenum internalFormat,
                                   gl::Framebuffer *source)
{
    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.getLevelIndex(), internalFormatInfo.sizedInternalFormat,
                            sourceExtents, false));

    gl::Extents fbSize = source->getReadColorbuffer()->getSize();

    // Does the read area extend beyond the framebuffer?
    bool outside = origSourceArea.x < 0 || origSourceArea.y < 0 ||
                   origSourceArea.x + origSourceArea.width > fbSize.width ||
                   origSourceArea.y + origSourceArea.height > fbSize.height;

    // WebGL requires that pixels that would be outside the framebuffer are treated as zero values,
    // so clear the mip level to 0 prior to making the copy if any pixel would be sampled outside.
    // Same thing for robust resource init.
    if (outside &&
        (context->getExtensions().webglCompatibility || context->isRobustResourceInitEnabled()))
    {
        ANGLE_TRY(initializeContents(context, index));
    }

    gl::Rectangle sourceArea;
    if (!ClipRectangle(origSourceArea, gl::Rectangle(0, 0, fbSize.width, fbSize.height),
                       &sourceArea))
    {
        // Empty source area, nothing to do.
        return gl::NoError();
    }

    gl::Offset destOffset(sourceArea.x - origSourceArea.x, sourceArea.y - origSourceArea.y, 0);

    // If the zero max LOD workaround is active, then we can't sample from individual layers of the framebuffer in shaders,
    // so we should use the non-rendering copy path.
    if (!canCreateRenderTargetForImage(index) || mRenderer->getWorkarounds().zeroMaxLodWorkaround)
    {
        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.getLevelIndex()))
        {
            ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
            ANGLE_TRY(mRenderer->copyImage2D(context, source, sourceArea, internalFormat,
                                             destOffset, mTexStorage, index.getLevelIndex()));
        }
    }

    return gl::NoError();
}

gl::Error TextureD3D_2D::copySubImage(const gl::Context *context,
                                      const gl::ImageIndex &index,
                                      const gl::Offset &origDestOffset,
                                      const gl::Rectangle &origSourceArea,
                                      gl::Framebuffer *source)
{
    ASSERT(index.getTarget() == gl::TextureTarget::_2D && origDestOffset.z == 0);

    gl::Extents fbSize = source->getReadColorbuffer()->getSize();
    gl::Rectangle sourceArea;
    if (!ClipRectangle(origSourceArea, gl::Rectangle(0, 0, fbSize.width, fbSize.height),
                       &sourceArea))
    {
        return gl::NoError();
    }
    const gl::Offset destOffset(origDestOffset.x + sourceArea.x - origSourceArea.x,
                                origDestOffset.y + sourceArea.y - origSourceArea.y, 0);

    // can only make our texture storage to a render target if level 0 is defined (with a width & height) and
    // the current level we're copying to is defined (with appropriate format, width & height)

    // If the zero max LOD workaround is active, then we can't sample from individual layers of the framebuffer in shaders,
    // so we should use the non-rendering copy path.
    if (!canCreateRenderTargetForImage(index) || mRenderer->getWorkarounds().zeroMaxLodWorkaround)
    {
        ANGLE_TRY(mImageArray[index.getLevelIndex()]->copyFromFramebuffer(context, destOffset,
                                                                          sourceArea, source));
        mDirtyImages = true;
    }
    else
    {
        ANGLE_TRY(ensureRenderTarget(context));

        if (isValidLevel(index.getLevelIndex()))
        {
            ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
            ANGLE_TRY(mRenderer->copyImage2D(context, source, sourceArea,
                                             gl::GetUnsizedFormat(getBaseLevelInternalFormat()),
                                             destOffset, mTexStorage, index.getLevelIndex()));
        }
    }

    return gl::NoError();
}

gl::Error TextureD3D_2D::copyTexture(const gl::Context *context,
                                     const gl::ImageIndex &index,
                                     GLenum internalFormat,
                                     GLenum type,
                                     size_t sourceLevel,
                                     bool unpackFlipY,
                                     bool unpackPremultiplyAlpha,
                                     bool unpackUnmultiplyAlpha,
                                     const gl::Texture *source)
{
    ASSERT(index.getTarget() == gl::TextureTarget::_2D);

    gl::TextureType sourceType = source->getType();

    const gl::InternalFormat &internalFormatInfo = gl::GetInternalFormatInfo(internalFormat, type);
    gl::Extents size(
        static_cast<int>(source->getWidth(NonCubeTextureTypeToTarget(sourceType), sourceLevel)),
        static_cast<int>(source->getHeight(NonCubeTextureTypeToTarget(sourceType), sourceLevel)),
        1);
    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.getLevelIndex()) && canCreateRenderTargetForImage(index))
    {
        ANGLE_TRY(ensureRenderTarget(context));
        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.getTarget(),
            index.getLevelIndex(), unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));
    }
    else
    {
        gl::ImageIndex sourceImageIndex = gl::ImageIndex::Make2D(static_cast<GLint>(sourceLevel));
        TextureD3D *sourceD3D           = GetImplAs<TextureD3D>(source);
        ImageD3D *sourceImage           = nullptr;
        ANGLE_TRY(sourceD3D->getImageAndSyncFromStorage(context, sourceImageIndex, &sourceImage));

        ImageD3D *destImage           = nullptr;
        ANGLE_TRY(getImageAndSyncFromStorage(context, index, &destImage));

        ANGLE_TRY(mRenderer->copyImage(context, destImage, sourceImage, sourceRect, destOffset,
                                       unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));

        mDirtyImages = true;

        gl::Box destRegion(destOffset, size);
        ANGLE_TRY(commitRegion(context, index, destRegion));
    }

    return gl::NoError();
}

gl::Error TextureD3D_2D::copySubTexture(const gl::Context *context,
                                        const gl::ImageIndex &index,
                                        const gl::Offset &destOffset,
                                        size_t sourceLevel,
                                        const gl::Rectangle &sourceArea,
                                        bool unpackFlipY,
                                        bool unpackPremultiplyAlpha,
                                        bool unpackUnmultiplyAlpha,
                                        const gl::Texture *source)
{
    ASSERT(index.getTarget() == gl::TextureTarget::_2D);

    if (!isSRGB(index.getLevelIndex()) && canCreateRenderTargetForImage(index))
    {
        ANGLE_TRY(ensureRenderTarget(context));
        ASSERT(isValidLevel(index.getLevelIndex()));
        ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));

        const gl::InternalFormat &internalFormatInfo =
            gl::GetSizedInternalFormatInfo(getInternalFormat(index.getLevelIndex()));
        ANGLE_TRY(mRenderer->copyTexture(
            context, source, static_cast<GLint>(sourceLevel), sourceArea, internalFormatInfo.format,
            internalFormatInfo.type, destOffset, mTexStorage, index.getTarget(),
            index.getLevelIndex(), unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));
    }
    else
    {
        gl::ImageIndex sourceImageIndex = gl::ImageIndex::Make2D(static_cast<GLint>(sourceLevel));
        TextureD3D *sourceD3D           = GetImplAs<TextureD3D>(source);
        ImageD3D *sourceImage           = nullptr;
        ANGLE_TRY(sourceD3D->getImageAndSyncFromStorage(context, sourceImageIndex, &sourceImage));

        ImageD3D *destImage           = nullptr;
        ANGLE_TRY(getImageAndSyncFromStorage(context, index, &destImage));

        ANGLE_TRY(mRenderer->copyImage(context, destImage, sourceImage, sourceArea, destOffset,
                                       unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));

        mDirtyImages = true;

        gl::Box destRegion(destOffset.x, destOffset.y, 0, sourceArea.width, sourceArea.height, 1);
        ANGLE_TRY(commitRegion(context, index, destRegion));
    }

    return gl::NoError();
}

gl::Error TextureD3D_2D::copyCompressedTexture(const gl::Context *context,
                                               const gl::Texture *source)
{
    gl::TextureTarget sourceTarget = NonCubeTextureTypeToTarget(source->getType());
    GLint sourceLevel   = 0;

    GLint destLevel = 0;

    GLenum sizedInternalFormat =
        source->getFormat(sourceTarget, sourceLevel).info->sizedInternalFormat;
    gl::Extents size(static_cast<int>(source->getWidth(sourceTarget, sourceLevel)),
                     static_cast<int>(source->getHeight(sourceTarget, sourceLevel)), 1);
    ANGLE_TRY(redefineImage(context, destLevel, sizedInternalFormat, size, false));

    ANGLE_TRY(initializeStorage(context, false));
    ASSERT(mTexStorage);

    ANGLE_TRY(
        mRenderer->copyCompressedTexture(context, source, sourceLevel, mTexStorage, destLevel));

    return gl::NoError();
}

gl::Error TextureD3D_2D::setStorage(const gl::Context *context,
                                    gl::TextureType type,
                                    size_t levels,
                                    GLenum internalFormat,
                                    const gl::Extents &size)
{
    ASSERT(type == gl::TextureType::_2D && size.depth == 1);

    for (size_t level = 0; level < levels; level++)
    {
        gl::Extents levelSize(std::max(1, size.width >> level),
                              std::max(1, size.height >> level),
                              1);
        ANGLE_TRY(redefineImage(context, level, internalFormat, levelSize, true));
    }

    for (size_t level = levels; level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; level++)
    {
        ANGLE_TRY(redefineImage(context, level, GL_NONE, gl::Extents(0, 0, 1), true));
    }

    // TODO(geofflang): Verify storage creation had no errors
    bool renderTarget = IsRenderTargetUsage(mState.getUsage());
    TexStoragePointer storage(context);
    storage.reset(mRenderer->createTextureStorage2D(internalFormat, renderTarget, size.width,
                                                    size.height, static_cast<int>(levels), false));

    ANGLE_TRY(setCompleteTexStorage(context, storage.get()));
    storage.release();

    ANGLE_TRY(updateStorage(context));

    mImmutable = true;

    return gl::NoError();
}

gl::Error TextureD3D_2D::bindTexImage(const gl::Context *context, egl::Surface *surface)
{
    GLenum internalformat = surface->getConfig()->renderTargetFormat;

    gl::Extents size(surface->getWidth(), surface->getHeight(), 1);
    ANGLE_TRY(redefineImage(context, 0, internalformat, size, true));

    ANGLE_TRY(releaseTexStorage(context));

    SurfaceD3D *surfaceD3D = GetImplAs<SurfaceD3D>(surface);
    ASSERT(surfaceD3D);

    mTexStorage = mRenderer->createTextureStorage2D(surfaceD3D->getSwapChain());
    mEGLImageTarget = false;

    mDirtyImages = false;
    mImageArray[0]->markClean();

    mTexStorage->setSubject(this);

    return gl::NoError();
}

gl::Error TextureD3D_2D::releaseTexImage(const gl::Context *context)
{
    if (mTexStorage)
    {
        ANGLE_TRY(releaseTexStorage(context));
    }

    for (int i = 0; i < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++)
    {
        ANGLE_TRY(redefineImage(context, i, GL_NONE, gl::Extents(0, 0, 1), true));
    }

    return gl::NoError();
}

gl::Error TextureD3D_2D::setEGLImageTarget(const gl::Context *context,
                                           gl::TextureType type,
                                           egl::Image *image)
{
    EGLImageD3D *eglImaged3d = GetImplAs<EGLImageD3D>(image);

    // Set the properties of the base mip level from the EGL image
    const auto &format = image->getFormat();
    gl::Extents size(static_cast<int>(image->getWidth()), static_cast<int>(image->getHeight()), 1);
    ANGLE_TRY(redefineImage(context, 0, format.info->sizedInternalFormat, size, true));

    // Clear all other images.
    for (size_t level = 1; level < mImageArray.size(); level++)
    {
        ANGLE_TRY(redefineImage(context, level, GL_NONE, gl::Extents(0, 0, 1), true));
    }

    ANGLE_TRY(releaseTexStorage(context));
    mImageArray[0]->markClean();

    // Pass in the RenderTargetD3D here: createTextureStorage can't generate an error.
    RenderTargetD3D *renderTargetD3D = nullptr;
    ANGLE_TRY(eglImaged3d->getRenderTarget(context, &renderTargetD3D));

    mTexStorage     = mRenderer->createTextureStorageEGLImage(eglImaged3d, renderTargetD3D);
    mEGLImageTarget = true;

    return gl::NoError();
}

angle::Result TextureD3D_2D::initMipmapImages(const gl::Context *context)
{
    const GLuint baseLevel = mState.getEffectiveBaseLevel();
    const GLuint maxLevel  = mState.getMipmapMaxLevel();
    // Purge array levels baseLevel + 1 through q and reset them to represent the generated mipmap
    // levels.
    for (GLuint level = baseLevel + 1; level <= maxLevel; level++)
    {
        gl::Extents levelSize(std::max(getLevelZeroWidth() >> level, 1),
                              std::max(getLevelZeroHeight() >> level, 1), 1);

        ANGLE_TRY(redefineImage(context, level, getBaseLevelInternalFormat(), levelSize, false));
    }
    return angle::Result::Continue();
}

gl::Error TextureD3D_2D::getRenderTarget(const gl::Context *context,
                                         const gl::ImageIndex &index,
                                         RenderTargetD3D **outRT)
{
    ASSERT(!index.hasLayer());

    // ensure the underlying texture is created
    ANGLE_TRY(ensureRenderTarget(context));
    ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));

    return mTexStorage->getRenderTarget(context, index, outRT);
}

bool TextureD3D_2D::isValidLevel(int level) const
{
    return (mTexStorage ? (level >= 0 && level < mTexStorage->getLevelCount()) : false);
}

bool TextureD3D_2D::isLevelComplete(int level) const
{
    if (isImmutable())
    {
        return true;
    }

    GLsizei width  = getLevelZeroWidth();
    GLsizei height = getLevelZeroHeight();

    if (width <= 0 || height <= 0)
    {
        return false;
    }

    // The base image level is complete if the width and height are positive
    if (level == static_cast<int>(getBaseLevel()))
    {
        return true;
    }

    ASSERT(level >= 0 && level <= static_cast<int>(mImageArray.size()) &&
           mImageArray[level] != nullptr);
    ImageD3D *image = mImageArray[level].get();

    if (image->getInternalFormat() != getBaseLevelInternalFormat())
    {
        return false;
    }

    if (image->getWidth() != std::max(1, width >> level))
    {
        return false;
    }

    if (image->getHeight() != std::max(1, height >> level))
    {
        return false;
    }

    return true;
}

bool TextureD3D_2D::isImageComplete(const gl::ImageIndex &index) const
{
    return isLevelComplete(index.getLevelIndex());
}

// Constructs a native texture resource from the texture images
angle::Result TextureD3D_2D::initializeStorage(const gl::Context *context, bool renderTarget)
{
    // Only initialize the first time this texture is used as a render target or shader resource
    if (mTexStorage)
    {
        return angle::Result::Continue();
    }

    // do not attempt to create storage for nonexistant data
    if (!isLevelComplete(getBaseLevel()))
    {
        return angle::Result::Continue();
    }

    bool createRenderTarget = (renderTarget || IsRenderTargetUsage(mState.getUsage()));

    TexStoragePointer storage(context);
    ANGLE_TRY(createCompleteStorage(createRenderTarget, &storage));

    ANGLE_TRY(setCompleteTexStorage(context, storage.get()));
    storage.release();

    ASSERT(mTexStorage);

    // flush image data to the storage
    ANGLE_TRY(updateStorage(context));

    return angle::Result::Continue();
}

angle::Result TextureD3D_2D::createCompleteStorage(bool renderTarget,
                                                   TexStoragePointer *outStorage) const
{
    GLsizei width         = getLevelZeroWidth();
    GLsizei height        = getLevelZeroHeight();
    GLenum internalFormat = getBaseLevelInternalFormat();

    ASSERT(width > 0 && height > 0);

    // use existing storage level count, when previously specified by TexStorage*D
    GLint levels = (mTexStorage ? mTexStorage->getLevelCount() : creationLevels(width, height, 1));

    bool hintLevelZeroOnly = false;
    if (mRenderer->getWorkarounds().zeroMaxLodWorkaround)
    {
        // If any of the CPU images (levels >= 1) are dirty, then the textureStorage2D should use the mipped texture to begin with.
        // Otherwise, it should use the level-zero-only texture.
        hintLevelZeroOnly = true;
        for (int level = 1; level < levels && hintLevelZeroOnly; level++)
        {
            hintLevelZeroOnly = !(mImageArray[level]->isDirty() && isLevelComplete(level));
        }
    }

    // TODO(geofflang): Determine if the texture creation succeeded
    outStorage->reset(mRenderer->createTextureStorage2D(internalFormat, renderTarget, width, height,
                                                        levels, hintLevelZeroOnly));
    (*outStorage)->setSubject(this);

    return angle::Result::Continue();
}

angle::Result TextureD3D_2D::setCompleteTexStorage(const gl::Context *context,
                                                   TextureStorage *newCompleteTexStorage)
{
    if (newCompleteTexStorage && newCompleteTexStorage->isManaged())
    {
        for (int level = 0; level < newCompleteTexStorage->getLevelCount(); level++)
        {
            ANGLE_TRY(
                mImageArray[level]->setManagedSurface2D(context, newCompleteTexStorage, level));
        }
    }

    ANGLE_TRY(releaseTexStorage(context));
    mTexStorage = newCompleteTexStorage;

    mTexStorage->setSubject(this);

    mDirtyImages = true;

    return angle::Result::Continue();
}

angle::Result TextureD3D_2D::updateStorage(const gl::Context *context)
{
    if (!mDirtyImages)
    {
        return angle::Result::Continue();
    }

    ASSERT(mTexStorage != nullptr);
    GLint storageLevels = mTexStorage->getLevelCount();
    for (int level = 0; level < storageLevels; level++)
    {
        if (mImageArray[level]->isDirty() && isLevelComplete(level))
        {
            ANGLE_TRY(updateStorageLevel(context, level));
        }
    }

    mDirtyImages = false;
    return angle::Result::Continue();
}

angle::Result TextureD3D_2D::updateStorageLevel(const gl::Context *context, int level)
{
    ASSERT(level <= static_cast<int>(mImageArray.size()) && mImageArray[level] != nullptr);
    ASSERT(isLevelComplete(level));

    if (mImageArray[level]->isDirty())
    {
        gl::ImageIndex index = gl::ImageIndex::Make2D(level);
        gl::Box region(0, 0, 0, getWidth(level), getHeight(level), 1);
        ANGLE_TRY(commitRegion(context, index, region));
    }

    return angle::Result::Continue();
}

angle::Result TextureD3D_2D::redefineImage(const gl::Context *context,
                                           size_t level,
                                           GLenum internalformat,
                                           const gl::Extents &size,
                                           bool forceRelease)
{
    ASSERT(size.depth == 1);

    // If there currently is a corresponding storage texture image, it has these parameters
    const int storageWidth     = std::max(1, getLevelZeroWidth() >> level);
    const int storageHeight    = std::max(1, getLevelZeroHeight() >> level);
    const GLenum storageFormat = getBaseLevelInternalFormat();

    mImageArray[level]->redefine(gl::TextureType::_2D, internalformat, size, forceRelease);
    mDirtyImages = mDirtyImages || mImageArray[level]->isDirty();

    if (mTexStorage)
    {
        const size_t storageLevels = mTexStorage->getLevelCount();

        // If the storage was from an EGL image, copy it back into local images to preserve it
        // while orphaning
        if (level != 0 && mEGLImageTarget)
        {
            ANGLE_TRY(mImageArray[0]->copyFromTexStorage(context, gl::ImageIndex::Make2D(0),
                                                         mTexStorage));
        }

        if ((level >= storageLevels && storageLevels != 0) || size.width != storageWidth ||
            size.height != storageHeight ||
            internalformat != storageFormat)  // Discard mismatched storage
        {
            ANGLE_TRY(releaseTexStorage(context));
            markAllImagesDirty();
        }
    }

    // Can't be an EGL image target after being redefined
    mEGLImageTarget = false;

    return angle::Result::Continue();
}

gl::ImageIndexIterator TextureD3D_2D::imageIterator() const
{
    return gl::ImageIndexIterator::Make2D(0, mTexStorage->getLevelCount());
}

gl::ImageIndex TextureD3D_2D::getImageIndex(GLint mip, GLint /*layer*/) const
{
    // "layer" does not apply to 2D Textures.
    return gl::ImageIndex::Make2D(mip);
}

bool TextureD3D_2D::isValidIndex(const gl::ImageIndex &index) const
{
    return (mTexStorage && index.getType() == gl::TextureType::_2D && index.getLevelIndex() >= 0 &&
            index.getLevelIndex() < mTexStorage->getLevelCount());
}

void TextureD3D_2D::markAllImagesDirty()
{
    for (size_t i = 0; i < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++)
    {
        mImageArray[i]->markDirty();
    }
    mDirtyImages = true;
}

TextureD3D_Cube::TextureD3D_Cube(const gl::TextureState &state, RendererD3D *renderer)
    : TextureD3D(state, renderer)
{
    for (auto &face : mImageArray)
    {
        for (auto &image : face)
        {
            image.reset(renderer->createImage());
        }
    }
}

gl::Error TextureD3D_Cube::onDestroy(const gl::Context *context)
{
    // Delete the Images before the TextureStorage. Images might be relying on the TextureStorage
    // for some of their data. If TextureStorage is deleted before the Images, then their data will
    // be wastefully copied back from the GPU before we delete the Images.
    for (auto &face : mImageArray)
    {
        for (auto &image : face)
        {
            image.reset();
        }
    }
    return TextureD3D::onDestroy(context);
}

TextureD3D_Cube::~TextureD3D_Cube()
{
}

ImageD3D *TextureD3D_Cube::getImage(int level, int layer) const
{
    ASSERT(level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
    ASSERT(layer >= 0 && static_cast<size_t>(layer) < gl::kCubeFaceCount);
    return mImageArray[layer][level].get();
}

ImageD3D *TextureD3D_Cube::getImage(const gl::ImageIndex &index) const
{
    ASSERT(index.getLevelIndex() < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
    ASSERT(gl::IsCubeMapFaceTarget(index.getTarget()));
    return mImageArray[index.cubeMapFaceIndex()][index.getLevelIndex()].get();
}

GLsizei TextureD3D_Cube::getLayerCount(int level) const
{
    ASSERT(level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
    return gl::kCubeFaceCount;
}

GLenum TextureD3D_Cube::getInternalFormat(GLint level, GLint layer) const
{
    if (level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS)
        return mImageArray[layer][level]->getInternalFormat();
    else
        return GL_NONE;
}

bool TextureD3D_Cube::isDepth(GLint level, GLint layer) const
{
    return gl::GetSizedInternalFormatInfo(getInternalFormat(level, layer)).depthBits > 0;
}

bool TextureD3D_Cube::isSRGB(GLint level, GLint layer) const
{
    return gl::GetSizedInternalFormatInfo(getInternalFormat(level, layer)).colorEncoding == GL_SRGB;
}

gl::Error TextureD3D_Cube::setEGLImageTarget(const gl::Context *context,
                                             gl::TextureType type,
                                             egl::Image *image)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3D_Cube::setImage(const gl::Context *context,
                                    const gl::ImageIndex &index,
                                    GLenum internalFormat,
                                    const gl::Extents &size,
                                    GLenum format,
                                    GLenum type,
                                    const gl::PixelUnpackState &unpack,
                                    const uint8_t *pixels)
{
    ASSERT(size.depth == 1);

    const gl::InternalFormat &internalFormatInfo = gl::GetInternalFormatInfo(internalFormat, type);
    ANGLE_TRY(redefineImage(context, index.cubeMapFaceIndex(), index.getLevelIndex(),
                            internalFormatInfo.sizedInternalFormat, size, false));

    return setImageImpl(context, index, type, unpack, pixels, 0);
}

gl::Error TextureD3D_Cube::setSubImage(const gl::Context *context,
                                       const gl::ImageIndex &index,
                                       const gl::Box &area,
                                       GLenum format,
                                       GLenum type,
                                       const gl::PixelUnpackState &unpack,
                                       gl::Buffer *unpackBuffer,
                                       const uint8_t *pixels)
{
    ASSERT(area.depth == 1 && area.z == 0);
    return TextureD3D::subImage(context, index, area, format, type, unpack, unpackBuffer, pixels,
                                0);
}

gl::Error TextureD3D_Cube::setCompressedImage(const gl::Context *context,
                                              const gl::ImageIndex &index,
                                              GLenum internalFormat,
                                              const gl::Extents &size,
                                              const gl::PixelUnpackState &unpack,
                                              size_t imageSize,
                                              const uint8_t *pixels)
{
    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.getLevelIndex(),
                            internalFormat, size, false));

    return setCompressedImageImpl(context, index, unpack, pixels, 0);
}

gl::Error TextureD3D_Cube::setCompressedSubImage(const gl::Context *context,
                                                 const gl::ImageIndex &index,
                                                 const gl::Box &area,
                                                 GLenum format,
                                                 const gl::PixelUnpackState &unpack,
                                                 size_t imageSize,
                                                 const uint8_t *pixels)
{
    ASSERT(area.depth == 1 && area.z == 0);

    ANGLE_TRY(TextureD3D::subImageCompressed(context, index, area, format, unpack, pixels, 0));
    return commitRegion(context, index, area);
}

gl::Error TextureD3D_Cube::copyImage(const gl::Context *context,
                                     const gl::ImageIndex &index,
                                     const gl::Rectangle &origSourceArea,
                                     GLenum internalFormat,
                                     gl::Framebuffer *source)
{
    GLint faceIndex = index.cubeMapFaceIndex();
    const gl::InternalFormat &internalFormatInfo =
        gl::GetInternalFormatInfo(internalFormat, GL_UNSIGNED_BYTE);

    gl::Extents size(origSourceArea.width, origSourceArea.height, 1);
    ANGLE_TRY(redefineImage(context, faceIndex, index.getLevelIndex(),
                            internalFormatInfo.sizedInternalFormat, size, false));

    gl::Extents fbSize = source->getReadColorbuffer()->getSize();

    // Does the read area extend beyond the framebuffer?
    bool outside = origSourceArea.x < 0 || origSourceArea.y < 0 ||
                   origSourceArea.x + origSourceArea.width > fbSize.width ||
                   origSourceArea.y + origSourceArea.height > fbSize.height;

    // WebGL requires that pixels that would be outside the framebuffer are treated as zero values,
    // so clear the mip level to 0 prior to making the copy if any pixel would be sampled outside.
    // Same thing for robust resource init.
    if (outside &&
        (context->getExtensions().webglCompatibility || context->isRobustResourceInitEnabled()))
    {
        ANGLE_TRY(initializeContents(context, index));
    }

    gl::Rectangle sourceArea;
    if (!ClipRectangle(origSourceArea, gl::Rectangle(0, 0, fbSize.width, fbSize.height),
                       &sourceArea))
    {
        // Empty source area, nothing to do.
        return gl::NoError();
    }

    gl::Offset destOffset(sourceArea.x - origSourceArea.x, sourceArea.y - origSourceArea.y, 0);

    // If the zero max LOD workaround is active, then we can't sample from individual layers of the framebuffer in shaders,
    // so we should use the non-rendering copy path.
    if (!canCreateRenderTargetForImage(index) || mRenderer->getWorkarounds().zeroMaxLodWorkaround)
    {
        ANGLE_TRY(mImageArray[faceIndex][index.getLevelIndex()]->copyFromFramebuffer(
            context, destOffset, sourceArea, source));
        mDirtyImages = true;
    }
    else
    {
        ANGLE_TRY(ensureRenderTarget(context));

        ASSERT(size.width == size.height);

        if (size.width > 0 && isValidFaceLevel(faceIndex, index.getLevelIndex()))
        {
            ANGLE_TRY(updateStorageFaceLevel(context, faceIndex, index.getLevelIndex()));
            ANGLE_TRY(mRenderer->copyImageCube(context, source, sourceArea, internalFormat,
                                               destOffset, mTexStorage, index.getTarget(),
                                               index.getLevelIndex()));
        }
    }

    return gl::NoError();
}

gl::Error TextureD3D_Cube::copySubImage(const gl::Context *context,
                                        const gl::ImageIndex &index,
                                        const gl::Offset &origDestOffset,
                                        const gl::Rectangle &origSourceArea,
                                        gl::Framebuffer *source)
{
    gl::Extents fbSize = source->getReadColorbuffer()->getSize();
    gl::Rectangle sourceArea;
    if (!ClipRectangle(origSourceArea, gl::Rectangle(0, 0, fbSize.width, fbSize.height),
                       &sourceArea))
    {
        return gl::NoError();
    }
    const gl::Offset destOffset(origDestOffset.x + sourceArea.x - origSourceArea.x,
                                origDestOffset.y + sourceArea.y - origSourceArea.y, 0);

    GLint faceIndex = index.cubeMapFaceIndex();

    // If the zero max LOD workaround is active, then we can't sample from individual layers of the framebuffer in shaders,
    // so we should use the non-rendering copy path.
    if (!canCreateRenderTargetForImage(index) || mRenderer->getWorkarounds().zeroMaxLodWorkaround)
    {
        ANGLE_TRY(mImageArray[faceIndex][index.getLevelIndex()]->copyFromFramebuffer(
            context, destOffset, sourceArea, source));
        mDirtyImages = true;
    }
    else
    {
        ANGLE_TRY(ensureRenderTarget(context));
        if (isValidFaceLevel(faceIndex, index.getLevelIndex()))
        {
            ANGLE_TRY(updateStorageFaceLevel(context, faceIndex, index.getLevelIndex()));
            ANGLE_TRY(mRenderer->copyImageCube(
                context, source, sourceArea, gl::GetUnsizedFormat(getBaseLevelInternalFormat()),
                destOffset, mTexStorage, index.getTarget(), index.getLevelIndex()));
        }
    }

    return gl::NoError();
}

gl::Error TextureD3D_Cube::copyTexture(const gl::Context *context,
                                       const gl::ImageIndex &index,
                                       GLenum internalFormat,
                                       GLenum type,
                                       size_t sourceLevel,
                                       bool unpackFlipY,
                                       bool unpackPremultiplyAlpha,
                                       bool unpackUnmultiplyAlpha,
                                       const gl::Texture *source)
{
    ASSERT(gl::IsCubeMapFaceTarget(index.getTarget()));

    gl::TextureTarget sourceTarget = NonCubeTextureTypeToTarget(source->getType());

    GLint faceIndex = index.cubeMapFaceIndex();

    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.getLevelIndex(),
                            internalFormatInfo.sizedInternalFormat, size, false));

    gl::Rectangle sourceRect(0, 0, size.width, size.height);
    gl::Offset destOffset(0, 0, 0);

    if (!isSRGB(index.getLevelIndex(), faceIndex) && canCreateRenderTargetForImage(index))
    {
        ANGLE_TRY(ensureRenderTarget(context));
        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.getTarget(),
            index.getLevelIndex(), unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));
    }
    else
    {
        gl::ImageIndex sourceImageIndex = gl::ImageIndex::Make2D(static_cast<GLint>(sourceLevel));
        TextureD3D *sourceD3D           = GetImplAs<TextureD3D>(source);
        ImageD3D *sourceImage           = nullptr;
        ANGLE_TRY(sourceD3D->getImageAndSyncFromStorage(context, sourceImageIndex, &sourceImage));

        ImageD3D *destImage = nullptr;
        ANGLE_TRY(getImageAndSyncFromStorage(context, index, &destImage));

        ANGLE_TRY(mRenderer->copyImage(context, destImage, sourceImage, sourceRect, destOffset,
                                       unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));

        mDirtyImages = true;

        gl::Box destRegion(destOffset, size);
        ANGLE_TRY(commitRegion(context, index, destRegion));
    }

    return gl::NoError();
}

gl::Error TextureD3D_Cube::copySubTexture(const gl::Context *context,
                                          const gl::ImageIndex &index,
                                          const gl::Offset &destOffset,
                                          size_t sourceLevel,
                                          const gl::Rectangle &sourceArea,
                                          bool unpackFlipY,
                                          bool unpackPremultiplyAlpha,
                                          bool unpackUnmultiplyAlpha,
                                          const gl::Texture *source)
{
    ASSERT(gl::IsCubeMapFaceTarget(index.getTarget()));

    GLint faceIndex = index.cubeMapFaceIndex();

    if (!isSRGB(index.getLevelIndex(), faceIndex) && canCreateRenderTargetForImage(index))
    {
        ANGLE_TRY(ensureRenderTarget(context));
        ASSERT(isValidFaceLevel(faceIndex, index.getLevelIndex()));
        ANGLE_TRY(updateStorageFaceLevel(context, faceIndex, index.getLevelIndex()));

        const gl::InternalFormat &internalFormatInfo =
            gl::GetSizedInternalFormatInfo(getInternalFormat(index.getLevelIndex(), faceIndex));
        ANGLE_TRY(mRenderer->copyTexture(
            context, source, static_cast<GLint>(sourceLevel), sourceArea, internalFormatInfo.format,
            internalFormatInfo.type, destOffset, mTexStorage, index.getTarget(),
            index.getLevelIndex(), unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));
    }
    else
    {
        gl::ImageIndex sourceImageIndex = gl::ImageIndex::Make2D(static_cast<GLint>(sourceLevel));
        TextureD3D *sourceD3D           = GetImplAs<TextureD3D>(source);
        ImageD3D *sourceImage           = nullptr;
        ANGLE_TRY(sourceD3D->getImageAndSyncFromStorage(context, sourceImageIndex, &sourceImage));

        ImageD3D *destImage = nullptr;
        ANGLE_TRY(getImageAndSyncFromStorage(context, index, &destImage));

        ANGLE_TRY(mRenderer->copyImage(context, destImage, sourceImage, sourceArea, destOffset,
                                       unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha));

        mDirtyImages = true;

        gl::Box destRegion(destOffset.x, destOffset.y, 0, sourceArea.width, sourceArea.height, 1);
        ANGLE_TRY(commitRegion(context, index, destRegion));
    }

    return gl::NoError();
}

gl::Error TextureD3D_Cube::setStorage(const gl::Context *context,
                                      gl::TextureType type,
                                      size_t levels,
                                      GLenum internalFormat,
                                      const gl::Extents &size)
{
    ASSERT(size.width == size.height);
    ASSERT(size.depth == 1);

    for (size_t level = 0; level < levels; level++)
    {
        GLsizei mipSize = std::max(1, size.width >> level);
        for (size_t faceIndex = 0; faceIndex < gl::kCubeFaceCount; faceIndex++)
        {
            mImageArray[faceIndex][level]->redefine(gl::TextureType::CubeMap, internalFormat,
                                                    gl::Extents(mipSize, mipSize, 1), true);
        }
    }

    for (size_t level = levels; level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; level++)
    {
        for (size_t faceIndex = 0; faceIndex < gl::kCubeFaceCount; faceIndex++)
        {
            mImageArray[faceIndex][level]->redefine(gl::TextureType::CubeMap, GL_NONE,
                                                    gl::Extents(0, 0, 0), true);
        }
    }

    // TODO(geofflang): Verify storage creation had no errors
    bool renderTarget = IsRenderTargetUsage(mState.getUsage());

    TexStoragePointer storage(context);
    storage.reset(mRenderer->createTextureStorageCube(internalFormat, renderTarget, size.width,
                                                      static_cast<int>(levels), false));

    ANGLE_TRY(setCompleteTexStorage(context, storage.get()));
    storage.release();

    ANGLE_TRY(updateStorage(context));

    mImmutable = true;

    return gl::NoError();
}

// Tests for cube texture completeness. [OpenGL ES 2.0.24] section 3.7.10 page 81.
bool TextureD3D_Cube::isCubeComplete() const
{
    int    baseWidth  = getBaseLevelWidth();
    int    baseHeight = getBaseLevelHeight();
    GLenum baseFormat = getBaseLevelInternalFormat();

    if (baseWidth <= 0 || baseWidth != baseHeight)
    {
        return false;
    }

    for (size_t faceIndex = 1; faceIndex < gl::kCubeFaceCount; faceIndex++)
    {
        const ImageD3D &faceBaseImage = *mImageArray[faceIndex][getBaseLevel()];

        if (faceBaseImage.getWidth()          != baseWidth  ||
            faceBaseImage.getHeight()         != baseHeight ||
            faceBaseImage.getInternalFormat() != baseFormat )
        {
            return false;
        }
    }

    return true;
}

gl::Error TextureD3D_Cube::bindTexImage(const gl::Context *context, egl::Surface *surface)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3D_Cube::releaseTexImage(const gl::Context *context)
{
    UNREACHABLE();
    return gl::InternalError();
}

angle::Result TextureD3D_Cube::initMipmapImages(const gl::Context *context)
{
    const GLuint baseLevel = mState.getEffectiveBaseLevel();
    const GLuint maxLevel  = mState.getMipmapMaxLevel();
    // Purge array levels baseLevel + 1 through q and reset them to represent the generated mipmap
    // levels.
    for (size_t faceIndex = 0; faceIndex < gl::kCubeFaceCount; faceIndex++)
    {
        for (GLuint level = baseLevel + 1; level <= maxLevel; level++)
        {
            int faceLevelSize =
                (std::max(mImageArray[faceIndex][baseLevel]->getWidth() >> (level - baseLevel), 1));
            ANGLE_TRY(redefineImage(context, faceIndex, level,
                                    mImageArray[faceIndex][baseLevel]->getInternalFormat(),
                                    gl::Extents(faceLevelSize, faceLevelSize, 1), false));
        }
    }
    return angle::Result::Continue();
}

gl::Error TextureD3D_Cube::getRenderTarget(const gl::Context *context,
                                           const gl::ImageIndex &index,
                                           RenderTargetD3D **outRT)
{
    ASSERT(gl::IsCubeMapFaceTarget(index.getTarget()));

    // ensure the underlying texture is created
    ANGLE_TRY(ensureRenderTarget(context));
    ANGLE_TRY(updateStorageFaceLevel(context, index.cubeMapFaceIndex(), index.getLevelIndex()));

    return mTexStorage->getRenderTarget(context, index, outRT);
}

angle::Result TextureD3D_Cube::initializeStorage(const gl::Context *context, bool renderTarget)
{
    // Only initialize the first time this texture is used as a render target or shader resource
    if (mTexStorage)
    {
        return angle::Result::Continue();
    }

    // do not attempt to create storage for nonexistant data
    if (!isFaceLevelComplete(0, getBaseLevel()))
    {
        return angle::Result::Continue();
    }

    bool createRenderTarget = (renderTarget || IsRenderTargetUsage(mState.getUsage()));

    TexStoragePointer storage(context);
    ANGLE_TRY(createCompleteStorage(createRenderTarget, &storage));

    ANGLE_TRY(setCompleteTexStorage(context, storage.get()));
    storage.release();

    ASSERT(mTexStorage);

    // flush image data to the storage
    ANGLE_TRY(updateStorage(context));

    return angle::Result::Continue();
}

angle::Result TextureD3D_Cube::createCompleteStorage(bool renderTarget,
                                                     TexStoragePointer *outStorage) const
{
    GLsizei size = getLevelZeroWidth();

    ASSERT(size > 0);

    // use existing storage level count, when previously specified by TexStorage*D
    GLint levels = (mTexStorage ? mTexStorage->getLevelCount() : creationLevels(size, size, 1));

    bool hintLevelZeroOnly = false;
    if (mRenderer->getWorkarounds().zeroMaxLodWorkaround)
    {
        // If any of the CPU images (levels >= 1) are dirty, then the textureStorage should use the mipped texture to begin with.
        // Otherwise, it should use the level-zero-only texture.
        hintLevelZeroOnly = true;
        for (size_t faceIndex = 0; faceIndex < gl::kCubeFaceCount && hintLevelZeroOnly; faceIndex++)
        {
            for (int level = 1; level < levels && hintLevelZeroOnly; level++)
            {
                hintLevelZeroOnly = !(mImageArray[faceIndex][level]->isDirty() && isFaceLevelComplete(faceIndex, level));
            }
        }
    }

    // TODO (geofflang): detect if storage creation succeeded
    outStorage->reset(mRenderer->createTextureStorageCube(
        getBaseLevelInternalFormat(), renderTarget, size, levels, hintLevelZeroOnly));

    return angle::Result::Continue();
}

angle::Result TextureD3D_Cube::setCompleteTexStorage(const gl::Context *context,
                                                     TextureStorage *newCompleteTexStorage)
{
    if (newCompleteTexStorage && newCompleteTexStorage->isManaged())
    {
        for (size_t faceIndex = 0; faceIndex < gl::kCubeFaceCount; faceIndex++)
        {
            for (int level = 0; level < newCompleteTexStorage->getLevelCount(); level++)
            {
                ANGLE_TRY(mImageArray[faceIndex][level]->setManagedSurfaceCube(
                    context, newCompleteTexStorage, faceIndex, level));
            }
        }
    }

    ANGLE_TRY(releaseTexStorage(context));
    mTexStorage = newCompleteTexStorage;

    mDirtyImages = true;
    return angle::Result::Continue();
}

angle::Result TextureD3D_Cube::updateStorage(const gl::Context *context)
{
    if (!mDirtyImages)
    {
        return angle::Result::Continue();
    }

    ASSERT(mTexStorage != nullptr);
    GLint storageLevels = mTexStorage->getLevelCount();
    for (size_t face = 0; face < gl::kCubeFaceCount; face++)
    {
        for (int level = 0; level < storageLevels; level++)
        {
            if (mImageArray[face][level]->isDirty() && isFaceLevelComplete(face, level))
            {
                ANGLE_TRY(updateStorageFaceLevel(context, face, level));
            }
        }
    }

    mDirtyImages = false;
    return angle::Result::Continue();
}

bool TextureD3D_Cube::isValidFaceLevel(int faceIndex, int level) const
{
    return (mTexStorage ? (level >= 0 && level < mTexStorage->getLevelCount()) : 0);
}

bool TextureD3D_Cube::isFaceLevelComplete(int faceIndex, int level) const
{
    if (getBaseLevel() >= gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS)
    {
        return false;
    }
    ASSERT(level >= 0 && static_cast<size_t>(faceIndex) < gl::kCubeFaceCount &&
           level < static_cast<int>(mImageArray[faceIndex].size()) &&
           mImageArray[faceIndex][level] != nullptr);

    if (isImmutable())
    {
        return true;
    }

    int levelZeroSize = getLevelZeroWidth();

    if (levelZeroSize <= 0)
    {
        return false;
    }

    // "isCubeComplete" checks for base level completeness and we must call that
    // to determine if any face at level 0 is complete. We omit that check here
    // to avoid re-checking cube-completeness for every face at level 0.
    if (level == 0)
    {
        return true;
    }

    // Check that non-zero levels are consistent with the base level.
    const ImageD3D *faceLevelImage = mImageArray[faceIndex][level].get();

    if (faceLevelImage->getInternalFormat() != getBaseLevelInternalFormat())
    {
        return false;
    }

    if (faceLevelImage->getWidth() != std::max(1, levelZeroSize >> level))
    {
        return false;
    }

    return true;
}

bool TextureD3D_Cube::isImageComplete(const gl::ImageIndex &index) const
{
    return isFaceLevelComplete(index.cubeMapFaceIndex(), index.getLevelIndex());
}

angle::Result TextureD3D_Cube::updateStorageFaceLevel(const gl::Context *context,
                                                      int faceIndex,
                                                      int level)
{
    ASSERT(level >= 0 && static_cast<size_t>(faceIndex) < gl::kCubeFaceCount &&
           level < static_cast<int>(mImageArray[faceIndex].size()) &&
           mImageArray[faceIndex][level] != nullptr);
    ImageD3D *image = mImageArray[faceIndex][level].get();

    if (image->isDirty())
    {
        gl::TextureTarget faceTarget = gl::CubeFaceIndexToTextureTarget(faceIndex);
        gl::ImageIndex index         = gl::ImageIndex::MakeCubeMapFace(faceTarget, level);
        gl::Box region(0, 0, 0, image->getWidth(), image->getHeight(), 1);
        ANGLE_TRY(commitRegion(context, index, region));
    }

    return angle::Result::Continue();
}

angle::Result TextureD3D_Cube::redefineImage(const gl::Context *context,
                                             int faceIndex,
                                             GLint level,
                                             GLenum internalformat,
                                             const gl::Extents &size,
                                             bool forceRelease)
{
    // If there currently is a corresponding storage texture image, it has these parameters
    const int storageWidth     = std::max(1, getLevelZeroWidth() >> level);
    const int storageHeight    = std::max(1, getLevelZeroHeight() >> level);
    const GLenum storageFormat = getBaseLevelInternalFormat();

    mImageArray[faceIndex][level]->redefine(gl::TextureType::CubeMap, internalformat, size,
                                            forceRelease);
    mDirtyImages = mDirtyImages || mImageArray[faceIndex][level]->isDirty();

    if (mTexStorage)
    {
        const int storageLevels = mTexStorage->getLevelCount();

        if ((level >= storageLevels && storageLevels != 0) || size.width != storageWidth ||
            size.height != storageHeight ||
            internalformat != storageFormat)  // Discard mismatched storage
        {
            markAllImagesDirty();
            ANGLE_TRY(releaseTexStorage(context));
        }
    }

    return angle::Result::Continue();
}

gl::ImageIndexIterator TextureD3D_Cube::imageIterator() const
{
    return gl::ImageIndexIterator::MakeCube(0, mTexStorage->getLevelCount());
}

gl::ImageIndex TextureD3D_Cube::getImageIndex(GLint mip, GLint layer) const
{
    // The "layer" of the image index corresponds to the cube face
    return gl::ImageIndex::MakeCubeMapFace(gl::CubeFaceIndexToTextureTarget(layer), mip);
}

bool TextureD3D_Cube::isValidIndex(const gl::ImageIndex &index) const
{
    return (mTexStorage && index.getType() == gl::TextureType::CubeMap &&
            gl::IsCubeMapFaceTarget(index.getTarget()) && index.getLevelIndex() >= 0 &&
            index.getLevelIndex() < mTexStorage->getLevelCount());
}

void TextureD3D_Cube::markAllImagesDirty()
{
    for (int dirtyLevel = 0; dirtyLevel < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; dirtyLevel++)
    {
        for (size_t dirtyFace = 0; dirtyFace < gl::kCubeFaceCount; dirtyFace++)
        {
            mImageArray[dirtyFace][dirtyLevel]->markDirty();
        }
    }
    mDirtyImages = true;
}

TextureD3D_3D::TextureD3D_3D(const gl::TextureState &state, RendererD3D *renderer)
    : TextureD3D(state, renderer)
{
    for (int i = 0; i < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; ++i)
    {
        mImageArray[i].reset(renderer->createImage());
    }
}

gl::Error TextureD3D_3D::onDestroy(const gl::Context *context)
{
    // Delete the Images before the TextureStorage. Images might be relying on the TextureStorage
    // for some of their data. If TextureStorage is deleted before the Images, then their data will
    // be wastefully copied back from the GPU before we delete the Images.
    for (auto &image : mImageArray)
    {
        image.reset();
    }
    return TextureD3D::onDestroy(context);
}

TextureD3D_3D::~TextureD3D_3D()
{
}

ImageD3D *TextureD3D_3D::getImage(int level, int layer) const
{
    ASSERT(level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
    ASSERT(layer == 0);
    return mImageArray[level].get();
}

ImageD3D *TextureD3D_3D::getImage(const gl::ImageIndex &index) const
{
    ASSERT(index.getLevelIndex() < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
    ASSERT(!index.hasLayer());
    ASSERT(index.getType() == gl::TextureType::_3D);
    return mImageArray[index.getLevelIndex()].get();
}

GLsizei TextureD3D_3D::getLayerCount(int level) const
{
    ASSERT(level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
    return 1;
}

GLsizei TextureD3D_3D::getWidth(GLint level) const
{
    if (level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS)
        return mImageArray[level]->getWidth();
    else
        return 0;
}

GLsizei TextureD3D_3D::getHeight(GLint level) const
{
    if (level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS)
        return mImageArray[level]->getHeight();
    else
        return 0;
}

GLsizei TextureD3D_3D::getDepth(GLint level) const
{
    if (level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS)
        return mImageArray[level]->getDepth();
    else
        return 0;
}

GLenum TextureD3D_3D::getInternalFormat(GLint level) const
{
    if (level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS)
        return mImageArray[level]->getInternalFormat();
    else
        return GL_NONE;
}

bool TextureD3D_3D::isDepth(GLint level) const
{
    return gl::GetSizedInternalFormatInfo(getInternalFormat(level)).depthBits > 0;
}

gl::Error TextureD3D_3D::setEGLImageTarget(const gl::Context *context,
                                           gl::TextureType type,
                                           egl::Image *image)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3D_3D::setImage(const gl::Context *context,
                                  const gl::ImageIndex &index,
                                  GLenum internalFormat,
                                  const gl::Extents &size,
                                  GLenum format,
                                  GLenum type,
                                  const gl::PixelUnpackState &unpack,
                                  const uint8_t *pixels)
{
    ASSERT(index.getTarget() == gl::TextureTarget::_3D);
    const gl::InternalFormat &internalFormatInfo = gl::GetInternalFormatInfo(internalFormat, type);

    ANGLE_TRY(redefineImage(context, index.getLevelIndex(), internalFormatInfo.sizedInternalFormat,
                            size, false));

    bool fastUnpacked = false;

    // 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);
    if (isFastUnpackable(unpackBuffer, internalFormatInfo.sizedInternalFormat) && !size.empty() &&
        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.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.getLevelIndex()]->markClean();

        fastUnpacked = true;
    }

    if (!fastUnpacked)
    {
        ANGLE_TRY(setImageImpl(context, index, type, unpack, pixels, 0));
    }

    return gl::NoError();
}

gl::Error TextureD3D_3D::setSubImage(const gl::Context *context,
                                     const gl::ImageIndex &index,
                                     const gl::Box &area,
                                     GLenum format,
                                     GLenum type,
                                     const gl::PixelUnpackState &unpack,
                                     gl::Buffer *unpackBuffer,
                                     const uint8_t *pixels)
{
    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
    GLenum mipFormat = getInternalFormat(index.getLevelIndex());
    if (isFastUnpackable(unpackBuffer, mipFormat) && isLevelComplete(index.getLevelIndex()))
    {
        RenderTargetD3D *destRenderTarget = nullptr;
        ANGLE_TRY(getRenderTarget(context, index, &destRenderTarget));
        ASSERT(!mImageArray[index.getLevelIndex()]->isDirty());

        return fastUnpackPixels(context, unpack, pixels, area, mipFormat, type, destRenderTarget);
    }
    else
    {
        return TextureD3D::subImage(context, index, area, format, type, unpack, unpackBuffer,
                                    pixels, 0);
    }
}

gl::Error TextureD3D_3D::setCompressedImage(const gl::Context *context,
                                            const gl::ImageIndex &index,
                                            GLenum internalFormat,
                                            const gl::Extents &size,
                                            const gl::PixelUnpackState &unpack,
                                            size_t imageSize,
                                            const uint8_t *pixels)
{
    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.getLevelIndex(), internalFormat, size, false));

    return setCompressedImageImpl(context, index, unpack, pixels, 0);
}

gl::Error TextureD3D_3D::setCompressedSubImage(const gl::Context *context,
                                               const gl::ImageIndex &index,
                                               const gl::Box &area,
                                               GLenum format,
                                               const gl::PixelUnpackState &unpack,
                                               size_t imageSize,
                                               const uint8_t *pixels)
{
    ASSERT(index.getTarget() == gl::TextureTarget::_3D);

    ANGLE_TRY(TextureD3D::subImageCompressed(context, index, area, format, unpack, pixels, 0));
    return commitRegion(context, index, area);
}

gl::Error TextureD3D_3D::copyImage(const gl::Context *context,
                                   const gl::ImageIndex &index,
                                   const gl::Rectangle &sourceArea,
                                   GLenum internalFormat,
                                   gl::Framebuffer *source)
{
    UNIMPLEMENTED();
    return gl::InternalError() << "Copying 3D textures is unimplemented.";
}

gl::Error TextureD3D_3D::copySubImage(const gl::Context *context,
                                      const gl::ImageIndex &index,
                                      const gl::Offset &destOffset,
                                      const gl::Rectangle &sourceArea,
                                      gl::Framebuffer *source)
{
    ASSERT(index.getTarget() == gl::TextureTarget::_3D);

    gl::Extents fbSize = source->getReadColorbuffer()->getSize();
    gl::Rectangle clippedSourceArea;
    if (!ClipRectangle(sourceArea, gl::Rectangle(0, 0, fbSize.width, fbSize.height),
                       &clippedSourceArea))
    {
        return gl::NoError();
    }
    const gl::Offset clippedDestOffset(destOffset.x + clippedSourceArea.x - sourceArea.x,
                                       destOffset.y + clippedSourceArea.y - sourceArea.y,
                                       destOffset.z);

    // Currently, copying directly to the storage is not possible because it's not possible to
    // create an SRV from a single layer of a 3D texture.  Instead, make sure the image is up to
    // 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.getLevelIndex());
    if (syncTexStorage)
    {
        ANGLE_TRY(
            mImageArray[index.getLevelIndex()]->copyFromTexStorage(context, index, mTexStorage));
    }
    ANGLE_TRY(mImageArray[index.getLevelIndex()]->copyFromFramebuffer(context, clippedDestOffset,
                                                                      clippedSourceArea, source));
    mDirtyImages = true;

    if (syncTexStorage)
    {
        ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
    }

    return gl::NoError();
}

gl::Error TextureD3D_3D::setStorage(const gl::Context *context,
                                    gl::TextureType type,
                                    size_t levels,
                                    GLenum internalFormat,
                                    const gl::Extents &size)
{
    ASSERT(type == gl::TextureType::_3D);

    for (size_t level = 0; level < levels; level++)
    {
        gl::Extents levelSize(std::max(1, size.width >> level),
                              std::max(1, size.height >> level),
                              std::max(1, size.depth >> level));
        mImageArray[level]->redefine(gl::TextureType::_3D, internalFormat, levelSize, true);
    }

    for (size_t level = levels; level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; level++)
    {
        mImageArray[level]->redefine(gl::TextureType::_3D, GL_NONE, gl::Extents(0, 0, 0), true);
    }

    // TODO(geofflang): Verify storage creation had no errors
    bool renderTarget = IsRenderTargetUsage(mState.getUsage());
    TexStoragePointer storage(context);
    storage.reset(mRenderer->createTextureStorage3D(internalFormat, renderTarget, size.width,
                                                    size.height, size.depth,
                                                    static_cast<int>(levels)));

    ANGLE_TRY(setCompleteTexStorage(context, storage.get()));
    storage.release();

    ANGLE_TRY(updateStorage(context));

    mImmutable = true;

    return gl::NoError();
}

gl::Error TextureD3D_3D::bindTexImage(const gl::Context *context, egl::Surface *surface)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3D_3D::releaseTexImage(const gl::Context *context)
{
    UNREACHABLE();
    return gl::InternalError();
}

angle::Result TextureD3D_3D::initMipmapImages(const gl::Context *context)
{
    const GLuint baseLevel = mState.getEffectiveBaseLevel();
    const GLuint maxLevel  = mState.getMipmapMaxLevel();
    // Purge array levels baseLevel + 1 through q and reset them to represent the generated mipmap
    // levels.
    for (GLuint level = baseLevel + 1; level <= maxLevel; level++)
    {
        gl::Extents levelSize(std::max(getLevelZeroWidth() >> level, 1),
                              std::max(getLevelZeroHeight() >> level, 1),
                              std::max(getLevelZeroDepth() >> level, 1));
        ANGLE_TRY(redefineImage(context, level, getBaseLevelInternalFormat(), levelSize, false));
    }

    return angle::Result::Continue();
}

gl::Error TextureD3D_3D::getRenderTarget(const gl::Context *context,
                                         const gl::ImageIndex &index,
                                         RenderTargetD3D **outRT)
{
    // ensure the underlying texture is created
    ANGLE_TRY(ensureRenderTarget(context));

    if (index.hasLayer())
    {
        ANGLE_TRY(updateStorage(context));
    }
    else
    {
        ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
    }

    return mTexStorage->getRenderTarget(context, index, outRT);
}

angle::Result TextureD3D_3D::initializeStorage(const gl::Context *context, bool renderTarget)
{
    // Only initialize the first time this texture is used as a render target or shader resource
    if (mTexStorage)
    {
        return angle::Result::Continue();
    }

    // do not attempt to create storage for nonexistant data
    if (!isLevelComplete(getBaseLevel()))
    {
        return angle::Result::Continue();
    }

    bool createRenderTarget = (renderTarget || IsRenderTargetUsage(mState.getUsage()));

    TexStoragePointer storage(context);
    ANGLE_TRY(createCompleteStorage(createRenderTarget, &storage));

    ANGLE_TRY(setCompleteTexStorage(context, storage.get()));
    storage.release();

    ASSERT(mTexStorage);

    // flush image data to the storage
    ANGLE_TRY(updateStorage(context));

    return angle::Result::Continue();
}

angle::Result TextureD3D_3D::createCompleteStorage(bool renderTarget,
                                                   TexStoragePointer *outStorage) const
{
    GLsizei width         = getLevelZeroWidth();
    GLsizei height        = getLevelZeroHeight();
    GLsizei depth         = getLevelZeroDepth();
    GLenum internalFormat = getBaseLevelInternalFormat();

    ASSERT(width > 0 && height > 0 && depth > 0);

    // use existing storage level count, when previously specified by TexStorage*D
    GLint levels = (mTexStorage ? mTexStorage->getLevelCount() : creationLevels(width, height, depth));

    // TODO: Verify creation of the storage succeeded
    outStorage->reset(mRenderer->createTextureStorage3D(internalFormat, renderTarget, width, height,
                                                        depth, levels));

    return angle::Result::Continue();
}

angle::Result TextureD3D_3D::setCompleteTexStorage(const gl::Context *context,
                                                   TextureStorage *newCompleteTexStorage)
{
    ANGLE_TRY(releaseTexStorage(context));
    mTexStorage = newCompleteTexStorage;
    mDirtyImages = true;

    // We do not support managed 3D storage, as that is D3D9/ES2-only
    ASSERT(!mTexStorage->isManaged());

    return angle::Result::Continue();
}

angle::Result TextureD3D_3D::updateStorage(const gl::Context *context)
{
    if (!mDirtyImages)
    {
        return angle::Result::Continue();
    }

    ASSERT(mTexStorage != nullptr);
    GLint storageLevels = mTexStorage->getLevelCount();
    for (int level = 0; level < storageLevels; level++)
    {
        if (mImageArray[level]->isDirty() && isLevelComplete(level))
        {
            ANGLE_TRY(updateStorageLevel(context, level));
        }
    }

    mDirtyImages = false;
    return angle::Result::Continue();
}

bool TextureD3D_3D::isValidLevel(int level) const
{
    return (mTexStorage ? (level >= 0 && level < mTexStorage->getLevelCount()) : 0);
}

bool TextureD3D_3D::isLevelComplete(int level) const
{
    ASSERT(level >= 0 && level < static_cast<int>(mImageArray.size()) &&
           mImageArray[level] != nullptr);

    if (isImmutable())
    {
        return true;
    }

    GLsizei width  = getLevelZeroWidth();
    GLsizei height = getLevelZeroHeight();
    GLsizei depth  = getLevelZeroDepth();

    if (width <= 0 || height <= 0 || depth <= 0)
    {
        return false;
    }

    if (level == static_cast<int>(getBaseLevel()))
    {
        return true;
    }

    ImageD3D *levelImage = mImageArray[level].get();

    if (levelImage->getInternalFormat() != getBaseLevelInternalFormat())
    {
        return false;
    }

    if (levelImage->getWidth() != std::max(1, width >> level))
    {
        return false;
    }

    if (levelImage->getHeight() != std::max(1, height >> level))
    {
        return false;
    }

    if (levelImage->getDepth() != std::max(1, depth >> level))
    {
        return false;
    }

    return true;
}

bool TextureD3D_3D::isImageComplete(const gl::ImageIndex &index) const
{
    return isLevelComplete(index.getLevelIndex());
}

angle::Result TextureD3D_3D::updateStorageLevel(const gl::Context *context, int level)
{
    ASSERT(level >= 0 && level < static_cast<int>(mImageArray.size()) &&
           mImageArray[level] != nullptr);
    ASSERT(isLevelComplete(level));

    if (mImageArray[level]->isDirty())
    {
        gl::ImageIndex index = gl::ImageIndex::Make3D(level);
        gl::Box region(0, 0, 0, getWidth(level), getHeight(level), getDepth(level));
        ANGLE_TRY(commitRegion(context, index, region));
    }

    return angle::Result::Continue();
}

angle::Result TextureD3D_3D::redefineImage(const gl::Context *context,
                                           GLint level,
                                           GLenum internalformat,
                                           const gl::Extents &size,
                                           bool forceRelease)
{
    // If there currently is a corresponding storage texture image, it has these parameters
    const int storageWidth  = std::max(1, getLevelZeroWidth() >> level);
    const int storageHeight = std::max(1, getLevelZeroHeight() >> level);
    const int storageDepth  = std::max(1, getLevelZeroDepth() >> level);
    const GLenum storageFormat = getBaseLevelInternalFormat();

    mImageArray[level]->redefine(gl::TextureType::_3D, internalformat, size, forceRelease);
    mDirtyImages = mDirtyImages || mImageArray[level]->isDirty();

    if (mTexStorage)
    {
        const int storageLevels = mTexStorage->getLevelCount();

        if ((level >= storageLevels && storageLevels != 0) || size.width != storageWidth ||
            size.height != storageHeight || size.depth != storageDepth ||
            internalformat != storageFormat)  // Discard mismatched storage
        {
            markAllImagesDirty();
            ANGLE_TRY(releaseTexStorage(context));
        }
    }

    return angle::Result::Continue();
}

gl::ImageIndexIterator TextureD3D_3D::imageIterator() const
{
    return gl::ImageIndexIterator::Make3D(0, mTexStorage->getLevelCount(),
                                          gl::ImageIndex::kEntireLevel,
                                          gl::ImageIndex::kEntireLevel);
}

gl::ImageIndex TextureD3D_3D::getImageIndex(GLint mip, GLint /*layer*/) const
{
    // The "layer" here does not apply to 3D images. We use one Image per mip.
    return gl::ImageIndex::Make3D(mip);
}

bool TextureD3D_3D::isValidIndex(const gl::ImageIndex &index) const
{
    return (mTexStorage && index.getType() == gl::TextureType::_3D && index.getLevelIndex() >= 0 &&
            index.getLevelIndex() < mTexStorage->getLevelCount());
}

void TextureD3D_3D::markAllImagesDirty()
{
    for (int i = 0; i < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++)
    {
        mImageArray[i]->markDirty();
    }
    mDirtyImages = true;
}

GLint TextureD3D_3D::getLevelZeroDepth() const
{
    ASSERT(gl::CountLeadingZeros(static_cast<uint32_t>(getBaseLevelDepth())) > getBaseLevel());
    return getBaseLevelDepth() << getBaseLevel();
}

TextureD3D_2DArray::TextureD3D_2DArray(const gl::TextureState &state, RendererD3D *renderer)
    : TextureD3D(state, renderer)
{
    for (int level = 0; level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; ++level)
    {
        mLayerCounts[level] = 0;
        mImageArray[level]  = nullptr;
    }
}

gl::Error TextureD3D_2DArray::onDestroy(const gl::Context *context)
{
    // Delete the Images before the TextureStorage. Images might be relying on the TextureStorage
    // for some of their data. If TextureStorage is deleted before the Images, then their data will
    // be wastefully copied back from the GPU before we delete the Images.
    deleteImages();
    return TextureD3D::onDestroy(context);
}

TextureD3D_2DArray::~TextureD3D_2DArray()
{
}

ImageD3D *TextureD3D_2DArray::getImage(int level, int layer) const
{
    ASSERT(level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
    ASSERT((layer == 0 && mLayerCounts[level] == 0) ||
           layer < mLayerCounts[level]);
    return (mImageArray[level] ? mImageArray[level][layer] : nullptr);
}

ImageD3D *TextureD3D_2DArray::getImage(const gl::ImageIndex &index) const
{
    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
{
    ASSERT(level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
    return mLayerCounts[level];
}

GLsizei TextureD3D_2DArray::getWidth(GLint level) const
{
    return (level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS && mLayerCounts[level] > 0) ? mImageArray[level][0]->getWidth() : 0;
}

GLsizei TextureD3D_2DArray::getHeight(GLint level) const
{
    return (level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS && mLayerCounts[level] > 0) ? mImageArray[level][0]->getHeight() : 0;
}

GLenum TextureD3D_2DArray::getInternalFormat(GLint level) const
{
    return (level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS && mLayerCounts[level] > 0) ? mImageArray[level][0]->getInternalFormat() : GL_NONE;
}

bool TextureD3D_2DArray::isDepth(GLint level) const
{
    return gl::GetSizedInternalFormatInfo(getInternalFormat(level)).depthBits > 0;
}

gl::Error TextureD3D_2DArray::setEGLImageTarget(const gl::Context *context,
                                                gl::TextureType type,
                                                egl::Image *image)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3D_2DArray::setImage(const gl::Context *context,
                                       const gl::ImageIndex &index,
                                       GLenum internalFormat,
                                       const gl::Extents &size,
                                       GLenum format,
                                       GLenum type,
                                       const gl::PixelUnpackState &unpack,
                                       const uint8_t *pixels)
{
    ASSERT(index.getTarget() == gl::TextureTarget::_2DArray);

    const gl::InternalFormat &formatInfo = gl::GetInternalFormatInfo(internalFormat, type);

    ANGLE_TRY(
        redefineImage(context, index.getLevelIndex(), formatInfo.sizedInternalFormat, size, false));

    GLuint inputDepthPitch = 0;
    ANGLE_TRY_CHECKED_MATH(formatInfo.computeDepthPitch(type, size.width, size.height,
                                                        unpack.alignment, unpack.rowLength,
                                                        unpack.imageHeight, &inputDepthPitch));

    for (int i = 0; i < size.depth; i++)
    {
        const ptrdiff_t layerOffset = (inputDepthPitch * i);
        gl::ImageIndex layerIndex   = gl::ImageIndex::Make2DArray(index.getLevelIndex(), i);
        ANGLE_TRY(setImageImpl(context, layerIndex, type, unpack, pixels, layerOffset));
    }

    return gl::NoError();
}

gl::Error TextureD3D_2DArray::setSubImage(const gl::Context *context,
                                          const gl::ImageIndex &index,
                                          const gl::Box &area,
                                          GLenum format,
                                          GLenum type,
                                          const gl::PixelUnpackState &unpack,
                                          gl::Buffer *unpackBuffer,
                                          const uint8_t *pixels)
{
    ASSERT(index.getTarget() == gl::TextureTarget::_2DArray);
    const gl::InternalFormat &formatInfo =
        gl::GetInternalFormatInfo(getInternalFormat(index.getLevelIndex()), type);
    GLuint inputDepthPitch = 0;
    ANGLE_TRY_CHECKED_MATH(formatInfo.computeDepthPitch(type, area.width, area.height,
                                                        unpack.alignment, unpack.rowLength,
                                                        unpack.imageHeight, &inputDepthPitch));

    for (int i = 0; i < area.depth; i++)
    {
        int layer = area.z + i;
        const ptrdiff_t layerOffset = (inputDepthPitch * i);

        gl::Box layerArea(area.x, area.y, 0, area.width, area.height, 1);

        gl::ImageIndex layerIndex = gl::ImageIndex::Make2DArray(index.getLevelIndex(), layer);
        ANGLE_TRY(TextureD3D::subImage(context, layerIndex, layerArea, format, type, unpack,
                                       unpackBuffer, pixels, layerOffset));
    }

    return gl::NoError();
}

gl::Error TextureD3D_2DArray::setCompressedImage(const gl::Context *context,
                                                 const gl::ImageIndex &index,
                                                 GLenum internalFormat,
                                                 const gl::Extents &size,
                                                 const gl::PixelUnpackState &unpack,
                                                 size_t imageSize,
                                                 const uint8_t *pixels)
{
    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.getLevelIndex(), internalFormat, size, false));

    const gl::InternalFormat &formatInfo = gl::GetSizedInternalFormatInfo(internalFormat);
    GLuint inputDepthPitch               = 0;
    ANGLE_TRY_CHECKED_MATH(formatInfo.computeDepthPitch(GL_UNSIGNED_BYTE, size.width, size.height,
                                                        1, 0, 0, &inputDepthPitch));

    for (int i = 0; i < size.depth; i++)
    {
        const ptrdiff_t layerOffset = (inputDepthPitch * i);

        gl::ImageIndex layerIndex = gl::ImageIndex::Make2DArray(index.getLevelIndex(), i);
        ANGLE_TRY(setCompressedImageImpl(context, layerIndex, unpack, pixels, layerOffset));
    }

    return gl::NoError();
}

gl::Error TextureD3D_2DArray::setCompressedSubImage(const gl::Context *context,
                                                    const gl::ImageIndex &index,
                                                    const gl::Box &area,
                                                    GLenum format,
                                                    const gl::PixelUnpackState &unpack,
                                                    size_t imageSize,
                                                    const uint8_t *pixels)
{
    ASSERT(index.getTarget() == gl::TextureTarget::_2DArray);

    const gl::InternalFormat &formatInfo = gl::GetSizedInternalFormatInfo(format);
    GLuint inputDepthPitch               = 0;
    ANGLE_TRY_CHECKED_MATH(formatInfo.computeDepthPitch(GL_UNSIGNED_BYTE, area.width, area.height,
                                                        1, 0, 0, &inputDepthPitch));

    for (int i = 0; i < area.depth; i++)
    {
        int layer = area.z + i;
        const ptrdiff_t layerOffset = (inputDepthPitch * i);

        gl::Box layerArea(area.x, area.y, 0, area.width, area.height, 1);

        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));
    }

    return gl::NoError();
}

gl::Error TextureD3D_2DArray::copyImage(const gl::Context *context,
                                        const gl::ImageIndex &index,
                                        const gl::Rectangle &sourceArea,
                                        GLenum internalFormat,
                                        gl::Framebuffer *source)
{
    UNIMPLEMENTED();
    return gl::InternalError() << "Copying 2D array textures is unimplemented.";
}

gl::Error TextureD3D_2DArray::copySubImage(const gl::Context *context,
                                           const gl::ImageIndex &index,
                                           const gl::Offset &destOffset,
                                           const gl::Rectangle &sourceArea,
                                           gl::Framebuffer *source)
{
    ASSERT(index.getTarget() == gl::TextureTarget::_2DArray);

    gl::Extents fbSize = source->getReadColorbuffer()->getSize();
    gl::Rectangle clippedSourceArea;
    if (!ClipRectangle(sourceArea, gl::Rectangle(0, 0, fbSize.width, fbSize.height),
                       &clippedSourceArea))
    {
        return gl::NoError();
    }
    const gl::Offset clippedDestOffset(destOffset.x + clippedSourceArea.x - sourceArea.x,
                                       destOffset.y + clippedSourceArea.y - sourceArea.y,
                                       destOffset.z);

    if (!canCreateRenderTargetForImage(index))
    {
        gl::Offset destLayerOffset(clippedDestOffset.x, clippedDestOffset.y, 0);
        ANGLE_TRY(mImageArray[index.getLevelIndex()][clippedDestOffset.z]->copyFromFramebuffer(
            context, destLayerOffset, clippedSourceArea, source));
        mDirtyImages = true;
    }
    else
    {
        ANGLE_TRY(ensureRenderTarget(context));

        if (isValidLevel(index.getLevelIndex()))
        {
            ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
            ANGLE_TRY(
                mRenderer->copyImage2DArray(context, source, clippedSourceArea,
                                            gl::GetUnsizedFormat(getInternalFormat(getBaseLevel())),
                                            clippedDestOffset, mTexStorage, index.getLevelIndex()));
        }
    }
    return gl::NoError();
}

gl::Error TextureD3D_2DArray::setStorage(const gl::Context *context,
                                         gl::TextureType type,
                                         size_t levels,
                                         GLenum internalFormat,
                                         const gl::Extents &size)
{
    ASSERT(type == gl::TextureType::_2DArray);

    deleteImages();

    for (size_t level = 0; level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; level++)
    {
        gl::Extents levelLayerSize(std::max(1, size.width >> level),
                                   std::max(1, size.height >> level),
                                   1);

        mLayerCounts[level] = (level < levels ? size.depth : 0);

        if (mLayerCounts[level] > 0)
        {
            // Create new images for this level
            mImageArray[level] = new ImageD3D*[mLayerCounts[level]];

            for (int layer = 0; layer < mLayerCounts[level]; layer++)
            {
                mImageArray[level][layer] = mRenderer->createImage();
                mImageArray[level][layer]->redefine(gl::TextureType::_2DArray, internalFormat,
                                                    levelLayerSize, true);
            }
        }
    }

    // TODO(geofflang): Verify storage creation had no errors
    bool renderTarget = IsRenderTargetUsage(mState.getUsage());
    TexStoragePointer storage(context);
    storage.reset(mRenderer->createTextureStorage2DArray(internalFormat, renderTarget, size.width,
                                                         size.height, size.depth,
                                                         static_cast<int>(levels)));

    ANGLE_TRY(setCompleteTexStorage(context, storage.get()));
    storage.release();

    ANGLE_TRY(updateStorage(context));

    mImmutable = true;

    return gl::NoError();
}

gl::Error TextureD3D_2DArray::bindTexImage(const gl::Context *context, egl::Surface *surface)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3D_2DArray::releaseTexImage(const gl::Context *context)
{
    UNREACHABLE();
    return gl::InternalError();
}

angle::Result TextureD3D_2DArray::initMipmapImages(const gl::Context *context)
{
    const GLuint baseLevel = mState.getEffectiveBaseLevel();
    const GLuint maxLevel  = mState.getMipmapMaxLevel();
    int baseWidth     = getLevelZeroWidth();
    int baseHeight    = getLevelZeroHeight();
    int baseDepth     = getLayerCount(getBaseLevel());
    GLenum baseFormat = getBaseLevelInternalFormat();

    // Purge array levels baseLevel + 1 through q and reset them to represent the generated mipmap
    // levels.
    for (GLuint level = baseLevel + 1u; level <= maxLevel; level++)
    {
        ASSERT((baseWidth >> level) > 0 || (baseHeight >> level) > 0);
        gl::Extents levelLayerSize(std::max(baseWidth >> level, 1),
                                   std::max(baseHeight >> level, 1),
                                   baseDepth);
        ANGLE_TRY(redefineImage(context, level, baseFormat, levelLayerSize, false));
    }

    return angle::Result::Continue();
}

gl::Error TextureD3D_2DArray::getRenderTarget(const gl::Context *context,
                                              const gl::ImageIndex &index,
                                              RenderTargetD3D **outRT)
{
    // ensure the underlying texture is created
    ANGLE_TRY(ensureRenderTarget(context));
    ANGLE_TRY(updateStorageLevel(context, index.getLevelIndex()));
    return mTexStorage->getRenderTarget(context, index, outRT);
}

angle::Result TextureD3D_2DArray::initializeStorage(const gl::Context *context, bool renderTarget)
{
    // Only initialize the first time this texture is used as a render target or shader resource
    if (mTexStorage)
    {
        return angle::Result::Continue();
    }

    // do not attempt to create storage for nonexistant data
    if (!isLevelComplete(getBaseLevel()))
    {
        return angle::Result::Continue();
    }

    bool createRenderTarget = (renderTarget || IsRenderTargetUsage(mState.getUsage()));

    TexStoragePointer storage(context);
    ANGLE_TRY(createCompleteStorage(createRenderTarget, &storage));

    ANGLE_TRY(setCompleteTexStorage(context, storage.get()));
    storage.release();

    ASSERT(mTexStorage);

    // flush image data to the storage
    ANGLE_TRY(updateStorage(context));

    return angle::Result::Continue();
}

angle::Result TextureD3D_2DArray::createCompleteStorage(bool renderTarget,
                                                        TexStoragePointer *outStorage) const
{
    GLsizei width         = getLevelZeroWidth();
    GLsizei height        = getLevelZeroHeight();
    GLsizei depth         = getLayerCount(getBaseLevel());
    GLenum internalFormat = getBaseLevelInternalFormat();

    ASSERT(width > 0 && height > 0 && depth > 0);

    // use existing storage level count, when previously specified by TexStorage*D
    GLint levels = (mTexStorage ? mTexStorage->getLevelCount() : creationLevels(width, height, 1));

    // TODO(geofflang): Verify storage creation succeeds
    outStorage->reset(mRenderer->createTextureStorage2DArray(internalFormat, renderTarget, width,
                                                             height, depth, levels));

    return angle::Result::Continue();
}

angle::Result TextureD3D_2DArray::setCompleteTexStorage(const gl::Context *context,
                                                        TextureStorage *newCompleteTexStorage)
{
    ANGLE_TRY(releaseTexStorage(context));
    mTexStorage = newCompleteTexStorage;
    mDirtyImages = true;

    // We do not support managed 2D array storage, as managed storage is ES2/D3D9 only
    ASSERT(!mTexStorage->isManaged());

    return angle::Result::Continue();
}

angle::Result TextureD3D_2DArray::updateStorage(const gl::Context *context)
{
    if (!mDirtyImages)
    {
        return angle::Result::Continue();
    }

    ASSERT(mTexStorage != nullptr);
    GLint storageLevels = mTexStorage->getLevelCount();
    for (int level = 0; level < storageLevels; level++)
    {
        if (isLevelComplete(level))
        {
            ANGLE_TRY(updateStorageLevel(context, level));
        }
    }

    mDirtyImages = false;
    return angle::Result::Continue();
}

bool TextureD3D_2DArray::isValidLevel(int level) const
{
    return (mTexStorage ? (level >= 0 && level < mTexStorage->getLevelCount()) : 0);
}

bool TextureD3D_2DArray::isLevelComplete(int level) const
{
    ASSERT(level >= 0 && level < (int)ArraySize(mImageArray));

    if (isImmutable())
    {
        return true;
    }

    GLsizei width  = getLevelZeroWidth();
    GLsizei height = getLevelZeroHeight();

    if (width <= 0 || height <= 0)
    {
        return false;
    }

    // Layers check needs to happen after the above checks, otherwise out-of-range base level may be
    // queried.
    GLsizei layers = getLayerCount(getBaseLevel());

    if (layers <= 0)
    {
        return false;
    }

    if (level == static_cast<int>(getBaseLevel()))
    {
        return true;
    }

    if (getInternalFormat(level) != getInternalFormat(getBaseLevel()))
    {
        return false;
    }

    if (getWidth(level) != std::max(1, width >> level))
    {
        return false;
    }

    if (getHeight(level) != std::max(1, height >> level))
    {
        return false;
    }

    if (getLayerCount(level) != layers)
    {
        return false;
    }

    return true;
}

bool TextureD3D_2DArray::isImageComplete(const gl::ImageIndex &index) const
{
    return isLevelComplete(index.getLevelIndex());
}

angle::Result TextureD3D_2DArray::updateStorageLevel(const gl::Context *context, int level)
{
    ASSERT(level >= 0 && level < static_cast<int>(ArraySize(mLayerCounts)));
    ASSERT(isLevelComplete(level));

    for (int layer = 0; layer < mLayerCounts[level]; layer++)
    {
        ASSERT(mImageArray[level] != nullptr && mImageArray[level][layer] != nullptr);
        if (mImageArray[level][layer]->isDirty())
        {
            gl::ImageIndex index = gl::ImageIndex::Make2DArray(level, layer);
            gl::Box region(0, 0, 0, getWidth(level), getHeight(level), 1);
            ANGLE_TRY(commitRegion(context, index, region));
        }
    }

    return angle::Result::Continue();
}

void TextureD3D_2DArray::deleteImages()
{
    for (int level = 0; level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; ++level)
    {
        for (int layer = 0; layer < mLayerCounts[level]; ++layer)
        {
            delete mImageArray[level][layer];
        }
        delete[] mImageArray[level];
        mImageArray[level]  = nullptr;
        mLayerCounts[level] = 0;
    }
}

angle::Result TextureD3D_2DArray::redefineImage(const gl::Context *context,
                                                GLint level,
                                                GLenum internalformat,
                                                const gl::Extents &size,
                                                bool forceRelease)
{
    // If there currently is a corresponding storage texture image, it has these parameters
    const int storageWidth  = std::max(1, getLevelZeroWidth() >> level);
    const int storageHeight = std::max(1, getLevelZeroHeight() >> level);
    const GLuint baseLevel  = getBaseLevel();
    const GLenum storageFormat = getBaseLevelInternalFormat();

    int storageDepth = 0;
    if (baseLevel < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS)
    {
        storageDepth = getLayerCount(baseLevel);
    }

    // Only reallocate the layers if the size doesn't match
    if (size.depth != mLayerCounts[level])
    {
        for (int layer = 0; layer < mLayerCounts[level]; layer++)
        {
            SafeDelete(mImageArray[level][layer]);
        }
        SafeDeleteArray(mImageArray[level]);
        mLayerCounts[level] = size.depth;

        if (size.depth > 0)
        {
            mImageArray[level] = new ImageD3D*[size.depth];
            for (int layer = 0; layer < mLayerCounts[level]; layer++)
            {
                mImageArray[level][layer] = mRenderer->createImage();
            }
        }
    }

    if (size.depth > 0)
    {
        for (int layer = 0; layer < mLayerCounts[level]; layer++)
        {
            mImageArray[level][layer]->redefine(gl::TextureType::_2DArray, internalformat,
                                                gl::Extents(size.width, size.height, 1),
                                                forceRelease);
            mDirtyImages = mDirtyImages || mImageArray[level][layer]->isDirty();
        }
    }

    if (mTexStorage)
    {
        const int storageLevels = mTexStorage->getLevelCount();

        if ((level >= storageLevels && storageLevels != 0) || size.width != storageWidth ||
            size.height != storageHeight || size.depth != storageDepth ||
            internalformat != storageFormat)  // Discard mismatched storage
        {
            markAllImagesDirty();
            ANGLE_TRY(releaseTexStorage(context));
        }
    }

    return angle::Result::Continue();
}

gl::ImageIndexIterator TextureD3D_2DArray::imageIterator() const
{
    return gl::ImageIndexIterator::Make2DArray(0, mTexStorage->getLevelCount(), mLayerCounts);
}

gl::ImageIndex TextureD3D_2DArray::getImageIndex(GLint mip, GLint layer) const
{
    return gl::ImageIndex::Make2DArray(mip, layer);
}

bool TextureD3D_2DArray::isValidIndex(const gl::ImageIndex &index) const
{
    // Check for having a storage and the right type of index
    if (!mTexStorage || index.getType() != gl::TextureType::_2DArray)
    {
        return false;
    }

    // Check the mip index
    if (index.getLevelIndex() < 0 || index.getLevelIndex() >= mTexStorage->getLevelCount())
    {
        return false;
    }

    // Check the layer index
    return (!index.hasLayer() || (index.getLayerIndex() >= 0 &&
                                  index.getLayerIndex() < mLayerCounts[index.getLevelIndex()]));
}

void TextureD3D_2DArray::markAllImagesDirty()
{
    for (int dirtyLevel = 0; dirtyLevel < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; dirtyLevel++)
    {
        for (int dirtyLayer = 0; dirtyLayer < mLayerCounts[dirtyLevel]; dirtyLayer++)
        {
            mImageArray[dirtyLevel][dirtyLayer]->markDirty();
        }
    }
    mDirtyImages = true;
}

TextureD3DImmutableBase::TextureD3DImmutableBase(const gl::TextureState &state,
                                                 RendererD3D *renderer)
    : TextureD3D(state, renderer)
{
}

TextureD3DImmutableBase::~TextureD3DImmutableBase()
{
}

ImageD3D *TextureD3DImmutableBase::getImage(const gl::ImageIndex &index) const
{
    return nullptr;
}

gl::Error TextureD3DImmutableBase::setImage(const gl::Context *context,
                                            const gl::ImageIndex &index,
                                            GLenum internalFormat,
                                            const gl::Extents &size,
                                            GLenum format,
                                            GLenum type,
                                            const gl::PixelUnpackState &unpack,
                                            const uint8_t *pixels)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3DImmutableBase::setSubImage(const gl::Context *context,
                                               const gl::ImageIndex &index,
                                               const gl::Box &area,
                                               GLenum format,
                                               GLenum type,
                                               const gl::PixelUnpackState &unpack,
                                               gl::Buffer *unpackBuffer,
                                               const uint8_t *pixels)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3DImmutableBase::setCompressedImage(const gl::Context *context,
                                                      const gl::ImageIndex &index,
                                                      GLenum internalFormat,
                                                      const gl::Extents &size,
                                                      const gl::PixelUnpackState &unpack,
                                                      size_t imageSize,
                                                      const uint8_t *pixels)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3DImmutableBase::setCompressedSubImage(const gl::Context *context,
                                                         const gl::ImageIndex &index,
                                                         const gl::Box &area,
                                                         GLenum format,
                                                         const gl::PixelUnpackState &unpack,
                                                         size_t imageSize,
                                                         const uint8_t *pixels)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3DImmutableBase::copyImage(const gl::Context *context,
                                             const gl::ImageIndex &index,
                                             const gl::Rectangle &sourceArea,
                                             GLenum internalFormat,
                                             gl::Framebuffer *source)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3DImmutableBase::copySubImage(const gl::Context *context,
                                                const gl::ImageIndex &index,
                                                const gl::Offset &destOffset,
                                                const gl::Rectangle &sourceArea,
                                                gl::Framebuffer *source)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3DImmutableBase::bindTexImage(const gl::Context *context, egl::Surface *surface)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3DImmutableBase::releaseTexImage(const gl::Context *context)
{
    UNREACHABLE();
    return gl::InternalError();
}

TextureD3D_External::TextureD3D_External(const gl::TextureState &state, RendererD3D *renderer)
    : TextureD3DImmutableBase(state, renderer)
{
}

TextureD3D_External::~TextureD3D_External()
{
}

GLsizei TextureD3D_External::getLayerCount(int level) const
{
    return 1;
}

gl::Error TextureD3D_External::setImageExternal(const gl::Context *context,
                                                gl::TextureType type,
                                                egl::Stream *stream,
                                                const egl::Stream::GLTextureDescription &desc)
{
    ASSERT(type == gl::TextureType::External);

    ANGLE_TRY(releaseTexStorage(context));

    // If the stream is null, the external image is unbound and we release the storage
    if (stream != nullptr)
    {
        mTexStorage = mRenderer->createTextureStorageExternal(stream, desc);
    }

    return gl::NoError();
}

gl::Error TextureD3D_External::setEGLImageTarget(const gl::Context *context,
                                                 gl::TextureType type,
                                                 egl::Image *image)
{
    EGLImageD3D *eglImaged3d = GetImplAs<EGLImageD3D>(image);

    // Pass in the RenderTargetD3D here: createTextureStorage can't generate an error.
    RenderTargetD3D *renderTargetD3D = nullptr;
    ANGLE_TRY(eglImaged3d->getRenderTarget(context, &renderTargetD3D));

    ANGLE_TRY(releaseTexStorage(context));
    mTexStorage = mRenderer->createTextureStorageEGLImage(eglImaged3d, renderTargetD3D);

    return gl::NoError();
}

angle::Result TextureD3D_External::initMipmapImages(const gl::Context *context)
{
    ANGLE_HR_UNREACHABLE(GetImplAs<ContextD3D>(context));
    return angle::Result::Stop();
}

gl::Error TextureD3D_External::getRenderTarget(const gl::Context *context,
                                               const gl::ImageIndex &index,
                                               RenderTargetD3D **outRT)
{
    UNREACHABLE();
    return gl::InternalError();
}

bool TextureD3D_External::isImageComplete(const gl::ImageIndex &index) const
{
    return (index.getLevelIndex() == 0) ? (mTexStorage != nullptr) : false;
}

angle::Result TextureD3D_External::initializeStorage(const gl::Context *context, bool renderTarget)
{
    // Texture storage is created when an external image is bound
    ASSERT(mTexStorage);
    return angle::Result::Continue();
}

angle::Result TextureD3D_External::createCompleteStorage(bool renderTarget,
                                                         TexStoragePointer *outStorage) const
{
    UNREACHABLE();
    return angle::Result::Continue();
}

angle::Result TextureD3D_External::setCompleteTexStorage(const gl::Context *context,
                                                         TextureStorage *newCompleteTexStorage)
{
    UNREACHABLE();
    return angle::Result::Continue();
}

angle::Result TextureD3D_External::updateStorage(const gl::Context *context)
{
    // Texture storage does not need to be updated since it is already loaded with the latest
    // external image
    ASSERT(mTexStorage);
    return angle::Result::Continue();
}

gl::ImageIndexIterator TextureD3D_External::imageIterator() const
{
    return gl::ImageIndexIterator::Make2D(0, mTexStorage->getLevelCount());
}

gl::ImageIndex TextureD3D_External::getImageIndex(GLint mip, GLint /*layer*/) const
{
    // "layer" does not apply to 2D Textures.
    return gl::ImageIndex::Make2D(mip);
}

bool TextureD3D_External::isValidIndex(const gl::ImageIndex &index) const
{
    return (mTexStorage && index.getType() == gl::TextureType::External &&
            index.getLevelIndex() == 0);
}

void TextureD3D_External::markAllImagesDirty()
{
    UNREACHABLE();
}

TextureD3D_2DMultisample::TextureD3D_2DMultisample(const gl::TextureState &state,
                                                   RendererD3D *renderer)
    : TextureD3DImmutableBase(state, renderer)
{
}

TextureD3D_2DMultisample::~TextureD3D_2DMultisample()
{
}

gl::Error TextureD3D_2DMultisample::setStorageMultisample(const gl::Context *context,
                                                          gl::TextureType type,
                                                          GLsizei samples,
                                                          GLint internalFormat,
                                                          const gl::Extents &size,
                                                          bool fixedSampleLocations)
{
    ASSERT(type == gl::TextureType::_2DMultisample && size.depth == 1);

    // We allocate storage immediately instead of doing it lazily like other TextureD3D classes do.
    // This requires less state in this class.
    TexStoragePointer storage(context);
    storage.reset(mRenderer->createTextureStorage2DMultisample(internalFormat, size.width,
                                                               size.height, static_cast<int>(0),
                                                               samples, fixedSampleLocations));

    ANGLE_TRY(setCompleteTexStorage(context, storage.get()));
    storage.release();

    mImmutable = true;

    return gl::NoError();
}

gl::Error TextureD3D_2DMultisample::setEGLImageTarget(const gl::Context *context,
                                                      gl::TextureType type,
                                                      egl::Image *image)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3D_2DMultisample::getRenderTarget(const gl::Context *context,
                                                    const gl::ImageIndex &index,
                                                    RenderTargetD3D **outRT)
{
    ASSERT(!index.hasLayer());

    // ensure the underlying texture is created
    ANGLE_TRY(ensureRenderTarget(context));

    return mTexStorage->getRenderTarget(context, index, outRT);
}

gl::ImageIndexIterator TextureD3D_2DMultisample::imageIterator() const
{
    return gl::ImageIndexIterator::Make2DMultisample();
}

gl::ImageIndex TextureD3D_2DMultisample::getImageIndex(GLint mip, GLint layer) const
{
    return gl::ImageIndex::Make2DMultisample();
}

bool TextureD3D_2DMultisample::isValidIndex(const gl::ImageIndex &index) const
{
    return (mTexStorage && index.getType() == gl::TextureType::_2DMultisample &&
            index.getLevelIndex() == 0);
}

GLsizei TextureD3D_2DMultisample::getLayerCount(int level) const
{
    return 1;
}

void TextureD3D_2DMultisample::markAllImagesDirty()
{
}

angle::Result TextureD3D_2DMultisample::initializeStorage(const gl::Context *context,
                                                          bool renderTarget)
{
    // initializeStorage should only be called in a situation where the texture already has storage
    // associated with it (storage is created in setStorageMultisample).
    ASSERT(mTexStorage);
    return angle::Result::Continue();
}

angle::Result TextureD3D_2DMultisample::createCompleteStorage(bool renderTarget,
                                                              TexStoragePointer *outStorage) const
{
    UNREACHABLE();
    outStorage->reset(mTexStorage);
    return angle::Result::Continue();
}

angle::Result TextureD3D_2DMultisample::setCompleteTexStorage(const gl::Context *context,
                                                              TextureStorage *newCompleteTexStorage)
{
    // These textures are immutable, so this should only be ever called once.
    ASSERT(!mTexStorage);
    mTexStorage = newCompleteTexStorage;
    return angle::Result::Continue();
}

angle::Result TextureD3D_2DMultisample::updateStorage(const gl::Context *context)
{
    return angle::Result::Continue();
}

angle::Result TextureD3D_2DMultisample::initMipmapImages(const gl::Context *context)
{
    UNREACHABLE();
    return angle::Result::Continue();
}

bool TextureD3D_2DMultisample::isImageComplete(const gl::ImageIndex &index) const
{
    return true;
}

TextureD3D_2DMultisampleArray::TextureD3D_2DMultisampleArray(const gl::TextureState &state,
                                                             RendererD3D *renderer)
    : TextureD3DImmutableBase(state, renderer)
{
}

TextureD3D_2DMultisampleArray::~TextureD3D_2DMultisampleArray()
{
}

gl::Error TextureD3D_2DMultisampleArray::setStorageMultisample(const gl::Context *context,
                                                               gl::TextureType type,
                                                               GLsizei samples,
                                                               GLint internalFormat,
                                                               const gl::Extents &size,
                                                               bool fixedSampleLocations)
{
    ASSERT(type == gl::TextureType::_2DMultisampleArray);

    mLayerCount = size.depth;

    TexStoragePointer storage(context);
    storage.reset(mRenderer->createTextureStorage2DMultisampleArray(
        internalFormat, size.width, size.height, size.depth, static_cast<int>(0), samples,
        fixedSampleLocations));

    ANGLE_TRY(setCompleteTexStorage(context, storage.get()));
    storage.release();

    mImmutable = true;

    return gl::NoError();
}

gl::Error TextureD3D_2DMultisampleArray::setEGLImageTarget(const gl::Context *context,
                                                           gl::TextureType type,
                                                           egl::Image *image)
{
    UNREACHABLE();
    return gl::InternalError();
}

gl::Error TextureD3D_2DMultisampleArray::getRenderTarget(const gl::Context *context,
                                                         const gl::ImageIndex &index,
                                                         RenderTargetD3D **outRT)
{
    // ensure the underlying texture is created
    ANGLE_TRY(ensureRenderTarget(context));

    return mTexStorage->getRenderTarget(context, index, outRT);
}

gl::ImageIndexIterator TextureD3D_2DMultisampleArray::imageIterator() const
{
    return gl::ImageIndexIterator::Make2DMultisampleArray(&mLayerCount);
}

gl::ImageIndex TextureD3D_2DMultisampleArray::getImageIndex(GLint mip, GLint layer) const
{
    return gl::ImageIndex::Make2DMultisampleArray(layer);
}

bool TextureD3D_2DMultisampleArray::isValidIndex(const gl::ImageIndex &index) const
{
    return (mTexStorage && index.getType() == gl::TextureType::_2DMultisampleArray &&
            index.getLevelIndex() == 0);
}

GLsizei TextureD3D_2DMultisampleArray::getLayerCount(int level) const
{
    return mLayerCount;
}

void TextureD3D_2DMultisampleArray::markAllImagesDirty()
{
}

angle::Result TextureD3D_2DMultisampleArray::initializeStorage(const gl::Context *context,
                                                               bool renderTarget)
{
    // initializeStorage should only be called in a situation where the texture already has storage
    // associated with it (storage is created in setStorageMultisample).
    ASSERT(mTexStorage);
    return angle::Result::Continue();
}

angle::Result TextureD3D_2DMultisampleArray::createCompleteStorage(
    bool renderTarget,
    TexStoragePointer *outStorage) const
{
    UNREACHABLE();
    outStorage->reset(mTexStorage);
    return angle::Result::Continue();
}

angle::Result TextureD3D_2DMultisampleArray::setCompleteTexStorage(
    const gl::Context *context,
    TextureStorage *newCompleteTexStorage)
{
    // These textures are immutable, so this should only be ever called once.
    ASSERT(!mTexStorage);
    mTexStorage = newCompleteTexStorage;
    return angle::Result::Continue();
}

angle::Result TextureD3D_2DMultisampleArray::updateStorage(const gl::Context *context)
{
    return angle::Result::Continue();
}

angle::Result TextureD3D_2DMultisampleArray::initMipmapImages(const gl::Context *context)
{
    UNIMPLEMENTED();
    return angle::Result::Continue();
}

bool TextureD3D_2DMultisampleArray::isImageComplete(const gl::ImageIndex &index) const
{
    return true;
}

}  // namespace rx
