// Copyright 2017 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "src/dawn/native/opengl/TextureGL.h"

#include <algorithm>
#include <limits>
#include <utility>

#include "src/dawn/common/Assert.h"
#include "src/dawn/common/Constants.h"
#include "src/dawn/common/Math.h"
#include "src/dawn/native/ChainUtils.h"
#include "src/dawn/native/Device.h"
#include "src/dawn/native/EnumMaskIterator.h"
#include "src/dawn/native/Queue.h"
#include "src/dawn/native/opengl/BufferGL.h"
#include "src/dawn/native/opengl/CommandBufferGL.h"
#include "src/dawn/native/opengl/DeviceGL.h"
#include "src/dawn/native/opengl/SharedFenceGL.h"
#include "src/dawn/native/opengl/SharedTextureMemoryGL.h"
#include "src/dawn/native/opengl/UtilsGL.h"
#include "src/utils/compiler.h"

namespace dawn::native::opengl {

namespace {

GLenum TargetForTextureViewDimension(wgpu::TextureViewDimension dimension, uint32_t sampleCount) {
    switch (dimension) {
        case wgpu::TextureViewDimension::e1D:
        case wgpu::TextureViewDimension::e2D:
            return (sampleCount > 1) ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
        case wgpu::TextureViewDimension::e2DArray:
            if (sampleCount > 1) {
                return GL_TEXTURE_2D_MULTISAMPLE;
            }
            DAWN_ASSERT(sampleCount == 1);
            return GL_TEXTURE_2D_ARRAY;
        case wgpu::TextureViewDimension::Cube:
            DAWN_ASSERT(sampleCount == 1);
            return GL_TEXTURE_CUBE_MAP;
        case wgpu::TextureViewDimension::CubeArray:
            DAWN_ASSERT(sampleCount == 1);
            return GL_TEXTURE_CUBE_MAP_ARRAY;
        case wgpu::TextureViewDimension::e3D:
            DAWN_ASSERT(sampleCount == 1);
            return GL_TEXTURE_3D;

        case wgpu::TextureViewDimension::Undefined:
        default:
            DAWN_UNREACHABLE();
    }
}

// Note this only applies to Desktop OpenGL (texture views don't exist in GLES).
// In compatibility mode, validation should mean we never need texture views.
bool RequiresCreatingNewTextureView(
    const TextureBase* texture,
    const UnpackedPtr<TextureViewDescriptor>& textureViewDescriptor) {
    if (ToBackend(texture->GetDevice())->IsCompatibilityMode()) {
        return false;
    }

    constexpr wgpu::TextureUsage kShaderUsageNeedsView =
        wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::TextureBinding;
    constexpr wgpu::TextureUsage kUsageNeedsView =
        kShaderUsageNeedsView | wgpu::TextureUsage::RenderAttachment;
    if (!(texture->GetInternalUsage() & kUsageNeedsView)) {
        return false;
    }

    if (texture->GetFormat().format != textureViewDescriptor->format &&
        !texture->GetFormat().HasDepthOrStencil()) {
        // Color format reinterpretation required. Note: Depth/stencil formats don't support
        // reinterpretation.
        return true;
    }

    // Reinterpretation not required. Now, we only need a new view if the view dimension or
    // set of subresources for the shader is different from the base texture.
    if (!(texture->GetInternalUsage() & kShaderUsageNeedsView)) {
        return false;
    }

    if (texture->GetArrayLayers() != textureViewDescriptor->arrayLayerCount ||
        (texture->GetArrayLayers() == 1 && texture->GetDimension() == wgpu::TextureDimension::e2D &&
         textureViewDescriptor->dimension == wgpu::TextureViewDimension::e2DArray)) {
        // If the view has a different number of array layers, we need a new view.
        // And, if the original texture is a 2D texture with one array layer, we need a new
        // view to view it as a 2D array texture.
        return true;
    }

    if (ToBackend(texture)->GetGLFormat().format == GL_DEPTH_STENCIL &&
        (texture->GetUsage() & wgpu::TextureUsage::TextureBinding) &&
        textureViewDescriptor->aspect == wgpu::TextureAspect::StencilOnly) {
        // We need a separate view for one of the depth or stencil planes
        // because each glTextureView needs it's own handle to set
        // GL_DEPTH_STENCIL_TEXTURE_MODE. Choose the stencil aspect for the
        // extra handle since it is likely sampled less often.
        return true;
    }

    // Note: For formats with <4 channels, this could be refined to consider that some channels are
    // nonexistent. We don't bother to optimize that, because such swizzles are not actually useful.
    // (Also, this code is only reached on Desktop GL anyway.)
    if (auto* swizzleDesc = textureViewDescriptor.Get<TextureComponentSwizzleDescriptor>()) {
        auto swizzle = swizzleDesc->swizzle.WithTrivialFrontendDefaults();
        if (*ToCppAPI(&swizzle) != kRGBASwizzle) {
            return true;
        }
    }

    return false;
}

MaybeError AllocateTexture(const OpenGLFunctions& gl,
                           GLenum target,
                           GLsizei samples,
                           GLuint levels,
                           const GLFormat& format,
                           const Extent3D& size) {
    if (format.isSupportedForTextureStorage || target == GL_TEXTURE_2D_MULTISAMPLE) {
        // glTextureView() requires the value of GL_TEXTURE_IMMUTABLE_FORMAT for origtexture to
        // be GL_TRUE, so the storage of the texture must be allocated with glTexStorage*D.
        // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTextureView.xhtml

        // There is no fallback for multisampled textures. They must be allocated with
        // glTexStorage2DMultisample
        switch (target) {
            case GL_TEXTURE_2D_ARRAY:
            case GL_TEXTURE_CUBE_MAP_ARRAY:
            case GL_TEXTURE_3D:
                DAWN_GL_TRY_ALWAYS_CHECK(
                    gl, TexStorage3D(target, levels, format.internalFormat, size.width, size.height,
                                     size.depthOrArrayLayers));
                break;
            case GL_TEXTURE_2D:
            case GL_TEXTURE_CUBE_MAP:
                DAWN_GL_TRY_ALWAYS_CHECK(gl, TexStorage2D(target, levels, format.internalFormat,
                                                          size.width, size.height));
                break;
            case GL_TEXTURE_2D_MULTISAMPLE:
                DAWN_GL_TRY_ALWAYS_CHECK(
                    gl, TexStorage2DMultisample(target, samples, format.internalFormat, size.width,
                                                size.height, true));
                break;
            default:
                DAWN_UNREACHABLE();
        }
    } else {
        // Allocate the texture using multiple glTexImage. This should only happen in compat and the
        // resulting texture will not be usable with glTextureView

        // When using glTexImage, make sure there is no unpack buffer bound or data would be copied
        // from whatever buffer happens to be bound.
        DAWN_GL_TRY_ALWAYS_CHECK(gl, BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0));

        // Array texture formats have the same depth for all mip levels.
        bool constantSizeForAllLevels =
            target == GL_TEXTURE_2D_ARRAY || target == GL_TEXTURE_CUBE_MAP_ARRAY;

        for (GLuint level = 0; level < levels; level++) {
            Extent3D levelSize{
                std::max(size.width >> level, 1u), std::max(size.height >> level, 1u),
                constantSizeForAllLevels ? size.depthOrArrayLayers
                                         : std::max(size.depthOrArrayLayers >> level, 1u)};

            switch (target) {
                case GL_TEXTURE_2D_ARRAY:
                case GL_TEXTURE_CUBE_MAP_ARRAY:
                case GL_TEXTURE_3D:
                    DAWN_GL_TRY_ALWAYS_CHECK(
                        gl, TexImage3D(target, level, format.format, levelSize.width,
                                       levelSize.height, levelSize.depthOrArrayLayers, 0,
                                       format.format, format.type, nullptr));
                    break;
                case GL_TEXTURE_2D:
                    DAWN_GL_TRY_ALWAYS_CHECK(
                        gl, TexImage2D(target, level, format.format, levelSize.width,
                                       levelSize.height, 0, format.format, format.type, nullptr));
                    break;
                case GL_TEXTURE_CUBE_MAP:
                    for (size_t faceIdx = 0; faceIdx < 6; faceIdx++) {
                        GLenum faceTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + faceIdx;
                        DAWN_GL_TRY_ALWAYS_CHECK(
                            gl,
                            TexImage2D(faceTarget, level, format.format, levelSize.width,
                                       levelSize.height, 0, format.format, format.type, nullptr));
                    }
                    break;
                default:
                    DAWN_UNREACHABLE();
            }
        }
    }

    return {};
}

MaybeError FramebufferTextureHelper(const OpenGLFunctions& gl,
                                    GLenum textarget,
                                    GLenum target,
                                    GLenum attachment,
                                    GLuint textureHandle,
                                    GLuint mipLevel,
                                    GLuint arrayLayer) {
    switch (textarget) {
        case GL_TEXTURE_2D_ARRAY:
        case GL_TEXTURE_CUBE_MAP_ARRAY:
        case GL_TEXTURE_3D:
            DAWN_GL_TRY(gl, FramebufferTextureLayer(target, attachment, textureHandle, mipLevel,
                                                    arrayLayer));
            break;
        case GL_TEXTURE_CUBE_MAP: {
            GLenum cubeTexTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + arrayLayer;
            DAWN_GL_TRY(gl, FramebufferTexture2D(target, attachment, cubeTexTarget, textureHandle,
                                                 mipLevel));
            break;
        }
        default:
            DAWN_ASSERT(textarget == GL_TEXTURE_2D || textarget == GL_TEXTURE_2D_MULTISAMPLE);
            DAWN_GL_TRY(
                gl, FramebufferTexture2D(target, attachment, textarget, textureHandle, mipLevel));
            break;
    }
    return {};
}

}  // namespace

// Texture

// static
ResultOrError<Ref<Texture>> Texture::Create(Device* device,
                                            const UnpackedPtr<TextureDescriptor>& descriptor) {
    // Wrap the handle in a Texture class early so that it is deleted if initialization fails
    Ref<Texture> texture = AcquireRef(new Texture(device, descriptor, 0, OwnsHandle::Yes));

    bool clear = device->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting);
    DAWN_TRY(device->EnqueueGL([texture, clear](const OpenGLFunctions& gl) -> MaybeError {
        DAWN_GL_TRY(gl, GenTextures(1, &texture->mHandle));

        GLenum target = texture->GetGLTarget();
        uint32_t levels = texture->GetNumMipLevels();
        const GLFormat& glFormat = texture->GetGLFormat();

        DAWN_GL_TRY(gl, BindTexture(target, texture->mHandle));
        DAWN_TRY(AllocateTexture(gl, target, texture->GetSampleCount(), levels, glFormat,
                                 texture->GetBaseSize()));

        // The texture is not complete if it uses mipmapping and not all levels up to
        // MAX_LEVEL have been defined.
        DAWN_GL_TRY(gl, TexParameteri(target, GL_TEXTURE_MAX_LEVEL, levels - 1));

        if (clear) {
            DAWN_TRY(texture->ClearTexture(gl, texture->GetAllSubresources(),
                                           TextureBase::ClearValue::NonZero));
        }
        return {};
    }));
    return std::move(texture);
}

// static
ResultOrError<Ref<Texture>> Texture::CreateFromSharedTextureMemory(
    SharedTextureMemory* memory,
    const UnpackedPtr<TextureDescriptor>& descriptor) {
    Device* device = ToBackend(memory->GetDevice());

    Ref<Texture> texture = AcquireRef(new Texture(device, descriptor, 0, OwnsHandle::Yes));
    DAWN_TRY(device->EnqueueGL([texture, memory = Ref<SharedTextureMemory>(memory)](
                                   const OpenGLFunctions& gl) -> MaybeError {
        DAWN_TRY_ASSIGN(texture->mHandle, memory->GenerateGLTexture(gl));
        return {};
    }));

    texture->mSharedResourceMemoryContents = memory->GetContents();
    return texture;
}

Texture::Texture(Device* device,
                 const UnpackedPtr<TextureDescriptor>& descriptor,
                 GLuint handle,
                 OwnsHandle ownsHandle)
    : TextureBase(device, descriptor), mHandle(handle), mOwnsHandle(ownsHandle) {
    mTarget = TargetForTextureViewDimension(GetCompatibilityTextureBindingViewDimension(),
                                            descriptor->sampleCount);
}

Texture::~Texture() {}

void Texture::DestroyImpl(DestroyReason reason) {
    TextureBase::DestroyImpl(reason);
    if (mOwnsHandle == OwnsHandle::Yes) {
        IgnoreErrors(
            ToBackend(GetDevice())
                ->EnqueueDestroyGL(this, &Texture::GetHandle, reason,
                                   [](const OpenGLFunctions& gl, GLuint handle) -> MaybeError {
                                       DAWN_GL_TRY_IGNORE_ERRORS(gl, DeleteTextures(1, &handle));
                                       return {};
                                   }));
    }
}

GLuint Texture::GetHandle() const {
    return mHandle;
}

GLenum Texture::GetGLTarget() const {
    return mTarget;
}

const GLFormat& Texture::GetGLFormat() const {
    return ToBackend(GetDevice())->GetGLFormat(GetFormat());
}

MaybeError Texture::ClearTexture(const OpenGLFunctions& gl,
                                 const SubresourceRange& range,
                                 TextureBase::ClearValue clearValue) {
    Device* device = ToBackend(GetDevice());

    uint8_t clearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0 : 1;
    float fClearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0.f : 1.f;

    if (GetFormat().IsRenderable()) {
        if (range.aspects & (Aspect::Depth | Aspect::Stencil)) {
            GLfloat depth = fClearColor;
            GLint stencil = clearColor;
            if (range.aspects & Aspect::Depth) {
                DAWN_GL_TRY(gl, DepthMask(GL_TRUE));
            }
            if (range.aspects & Aspect::Stencil) {
                DAWN_GL_TRY(gl, StencilMask(GetStencilMaskFromStencilFormat(GetFormat().format)));
            }

            auto DoClear = [&](Aspect aspects) -> MaybeError {
                if (aspects == (Aspect::Depth | Aspect::Stencil)) {
                    DAWN_GL_TRY(gl, ClearBufferfi(GL_DEPTH_STENCIL, 0, depth, stencil));
                } else if (aspects == Aspect::Depth) {
                    DAWN_GL_TRY(gl, ClearBufferfv(GL_DEPTH, 0, &depth));
                } else if (aspects == Aspect::Stencil) {
                    DAWN_GL_TRY(gl, ClearBufferiv(GL_STENCIL, 0, &stencil));
                } else {
                    DAWN_UNREACHABLE();
                }
                return {};
            };

            GLuint framebuffer = 0;
            DAWN_GL_TRY(gl, GenFramebuffers(1, &framebuffer));
            DAWN_GL_TRY(gl, BindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer));
            DAWN_GL_TRY(gl, Disable(GL_SCISSOR_TEST));

            GLenum attachment;
            if (range.aspects == (Aspect::Depth | Aspect::Stencil)) {
                attachment = GL_DEPTH_STENCIL_ATTACHMENT;
            } else if (range.aspects == Aspect::Depth) {
                attachment = GL_DEPTH_ATTACHMENT;
            } else if (range.aspects == Aspect::Stencil) {
                attachment = GL_STENCIL_ATTACHMENT;
            } else {
                DAWN_UNREACHABLE();
            }

            for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
                 ++level) {
                for (uint32_t layer = range.baseArrayLayer;
                     layer < range.baseArrayLayer + range.layerCount; ++layer) {
                    Aspect aspectsToClear = Aspect::None;
                    for (Aspect aspect : IterateEnumMask(range.aspects)) {
                        if (clearValue == TextureBase::ClearValue::Zero &&
                            IsSubresourceContentInitialized(
                                SubresourceRange::SingleMipAndLayer(level, layer, aspect))) {
                            // Skip lazy clears if already initialized.
                            continue;
                        }
                        aspectsToClear |= aspect;
                    }

                    if (aspectsToClear == Aspect::None) {
                        continue;
                    }
                    DAWN_TRY(FramebufferTextureHelper(gl, mTarget, GL_DRAW_FRAMEBUFFER, attachment,
                                                      GetHandle(), level, layer));
                    DAWN_TRY(DoClear(aspectsToClear));
                }
            }

            DAWN_GL_TRY(gl, Enable(GL_SCISSOR_TEST));
            DAWN_GL_TRY(gl, DeleteFramebuffers(1, &framebuffer));
        } else {
            DAWN_ASSERT(range.aspects == Aspect::Color);

            // For gl.ClearBufferiv/uiv calls
            constexpr std::array<GLuint, 4> kClearColorDataUint0 = {0u, 0u, 0u, 0u};
            constexpr std::array<GLuint, 4> kClearColorDataUint1 = {1u, 1u, 1u, 1u};
            std::array<GLuint, 4> clearColorData;
            clearColorData.fill((clearValue == TextureBase::ClearValue::Zero) ? 0u : 1u);

            // For gl.ClearBufferfv calls
            constexpr std::array<GLfloat, 4> kClearColorDataFloat0 = {0.f, 0.f, 0.f, 0.f};
            constexpr std::array<GLfloat, 4> kClearColorDataFloat1 = {1.f, 1.f, 1.f, 1.f};
            std::array<GLfloat, 4> fClearColorData;
            fClearColorData.fill((clearValue == TextureBase::ClearValue::Zero) ? 0.f : 1.f);

            static constexpr uint32_t MAX_TEXEL_SIZE = 16;
            const TexelBlockInfo& blockInfo = GetFormat().GetAspectInfo(Aspect::Color).block;
            DAWN_ASSERT(blockInfo.byteSize <= MAX_TEXEL_SIZE);

            // For gl.ClearTexSubImage calls
            constexpr std::array<GLbyte, MAX_TEXEL_SIZE> kClearColorDataBytes0 = {
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
            constexpr std::array<GLbyte, MAX_TEXEL_SIZE> kClearColorDataBytes255 = {
                -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};

            TextureComponentType baseType = GetFormat().GetAspectInfo(Aspect::Color).baseType;

            const GLFormat& glFormat = GetGLFormat();
            const auto dimension = GetDimension();
            for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
                 ++level) {
                Extent3D mipSize = GetMipLevelSingleSubresourcePhysicalSize(level, Aspect::Color);
                for (uint32_t layer = range.baseArrayLayer;
                     layer < range.baseArrayLayer + range.layerCount; ++layer) {
                    if (clearValue == TextureBase::ClearValue::Zero &&
                        IsSubresourceContentInitialized(
                            SubresourceRange::SingleMipAndLayer(level, layer, Aspect::Color))) {
                        // Skip lazy clears if already initialized.
                        continue;
                    }
                    if (gl.IsAtLeastGL(4, 4)) {
                        DAWN_GL_TRY(gl, ClearTexSubImage(mHandle, static_cast<GLint>(level), 0, 0,
                                                         static_cast<GLint>(layer), mipSize.width,
                                                         mipSize.height, mipSize.depthOrArrayLayers,
                                                         glFormat.format, glFormat.type,
                                                         clearValue == TextureBase::ClearValue::Zero
                                                             ? kClearColorDataBytes0.data()
                                                             : kClearColorDataBytes255.data()));
                        continue;
                    }

                    GLuint framebuffer = 0;
                    DAWN_GL_TRY(gl, GenFramebuffers(1, &framebuffer));
                    DAWN_GL_TRY(gl, BindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer));

                    GLenum attachment = GL_COLOR_ATTACHMENT0;
                    DAWN_GL_TRY(gl, DrawBuffers(1, &attachment));

                    DAWN_GL_TRY(gl, Disable(GL_SCISSOR_TEST));
                    DAWN_GL_TRY(gl, ColorMask(true, true, true, true));

                    auto DoClear = [&]() -> MaybeError {
                        switch (baseType) {
                            case TextureComponentType::Float: {
                                DAWN_GL_TRY(
                                    gl, ClearBufferfv(GL_COLOR, 0,
                                                      clearValue == TextureBase::ClearValue::Zero
                                                          ? kClearColorDataFloat0.data()
                                                          : kClearColorDataFloat1.data()));
                                break;
                            }
                            case TextureComponentType::Uint: {
                                DAWN_GL_TRY(
                                    gl, ClearBufferuiv(GL_COLOR, 0,
                                                       clearValue == TextureBase::ClearValue::Zero
                                                           ? kClearColorDataUint0.data()
                                                           : kClearColorDataUint1.data()));
                                break;
                            }
                            case TextureComponentType::Sint: {
                                DAWN_GL_TRY(gl, ClearBufferiv(
                                                    GL_COLOR, 0,
                                                    reinterpret_cast<const GLint*>(
                                                        clearValue == TextureBase::ClearValue::Zero
                                                            ? kClearColorDataUint0.data()
                                                            : kClearColorDataUint1.data())));
                                break;
                            }
                        }
                        return {};
                    };

                    if (dimension == wgpu::TextureDimension::e3D) {
                        uint32_t depth =
                            GetMipLevelSingleSubresourceVirtualSize(level, Aspect::Color)
                                .depthOrArrayLayers;
                        for (GLint z = 0; z < static_cast<GLint>(depth); ++z) {
                            DAWN_GL_TRY(gl, FramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, attachment,
                                                                    GetHandle(), level, z));
                            DAWN_TRY(DoClear());
                        }
                    } else {
                        DAWN_TRY(FramebufferTextureHelper(gl, mTarget, GL_DRAW_FRAMEBUFFER,
                                                          attachment, GetHandle(), level, layer));
                        DAWN_TRY(DoClear());
                    }

                    DAWN_GL_TRY(gl, Enable(GL_SCISSOR_TEST));
                    DAWN_GL_TRY(gl, DeleteFramebuffers(1, &framebuffer));
                    DAWN_GL_TRY(gl, BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0));
                }
            }
        }
    } else {
        DAWN_ASSERT(range.aspects == Aspect::Color);

        // create temp buffer with clear color to copy to the texture image
        const TexelBlockInfo& blockInfo = GetFormat().GetAspectInfo(Aspect::Color).block;
        DAWN_ASSERT(kTextureBytesPerRowAlignment % blockInfo.byteSize == 0);

        Extent3D largestMipSize =
            GetMipLevelSingleSubresourcePhysicalSize(range.baseMipLevel, Aspect::Color);
        uint32_t bytesPerRow =
            Align((largestMipSize.width / blockInfo.width) * blockInfo.byteSize, 4);

        // Make sure that we are not rounding
        DAWN_ASSERT(bytesPerRow % blockInfo.byteSize == 0);
        DAWN_ASSERT(largestMipSize.height % blockInfo.height == 0);

        uint64_t bufferSize64 = static_cast<uint64_t>(bytesPerRow) *
                                (largestMipSize.height / blockInfo.height) *
                                largestMipSize.depthOrArrayLayers;
        if (bufferSize64 > std::numeric_limits<size_t>::max()) {
            return DAWN_OUT_OF_MEMORY_ERROR("Unable to allocate buffer.");
        }
        size_t bufferSize = static_cast<size_t>(bufferSize64);

        dawn::native::BufferDescriptor descriptor = {};
        descriptor.mappedAtCreation = true;
        descriptor.usage = wgpu::BufferUsage::CopySrc;
        descriptor.size = bufferSize;

        // We don't count the lazy clear of srcBuffer because it is an internal buffer.
        // TODO(natlee@microsoft.com): use Dynamic Uploader here for temp buffer
        Ref<Buffer> srcBuffer;
        DAWN_TRY_ASSIGN(srcBuffer, Buffer::CreateInternalBuffer(device, &descriptor, false));

        // Fill the buffer with clear color
        DAWN_UNSAFE_TODO(memset(srcBuffer->GetMappedRange(0, bufferSize), clearColor, bufferSize));
        DAWN_TRY(srcBuffer->Unmap());

        DAWN_GL_TRY(gl, BindBuffer(GL_PIXEL_UNPACK_BUFFER, srcBuffer->GetHandle()));
        for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
             ++level) {
            TextureCopy textureCopy;
            textureCopy.texture = this;
            textureCopy.mipLevel = level;
            textureCopy.origin = {};
            textureCopy.aspect = Aspect::Color;

            TexelCopyBufferLayout dataLayout;
            dataLayout.offset = 0;
            dataLayout.bytesPerRow = bytesPerRow;
            dataLayout.rowsPerImage = largestMipSize.height / blockInfo.height;

            Extent3D mipSize = GetMipLevelSingleSubresourcePhysicalSize(level, Aspect::Color);

            for (uint32_t layer = range.baseArrayLayer;
                 layer < range.baseArrayLayer + range.layerCount; ++layer) {
                if (clearValue == TextureBase::ClearValue::Zero &&
                    IsSubresourceContentInitialized(
                        SubresourceRange::SingleMipAndLayer(level, layer, Aspect::Color))) {
                    // Skip lazy clears if already initialized.
                    continue;
                }

                textureCopy.origin.z = TexelCount{layer};
                DAWN_TRY(DoTexSubImage(gl, textureCopy, 0, dataLayout, mipSize));
            }
        }
        DAWN_GL_TRY(gl, BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0));
    }
    if (clearValue == TextureBase::ClearValue::Zero) {
        SetIsSubresourceContentInitialized(true, range);
        device->IncrementLazyClearCountForTesting();
    }
    return {};
}

MaybeError Texture::EnsureSubresourceContentInitialized(const OpenGLFunctions& gl,
                                                        const SubresourceRange& range) {
    if (!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
        return {};
    }
    if (!IsSubresourceContentInitialized(range)) {
        DAWN_TRY(ClearTexture(gl, range, TextureBase::ClearValue::Zero));
    }
    return {};
}

MaybeError Texture::SynchronizeTextureBeforeUse() {
    SharedTextureMemoryBase::PendingFenceList fences;
    SharedResourceMemoryContents* contents = GetSharedResourceMemoryContents();
    if (contents != nullptr) {
        contents->AcquirePendingFences(&fences);
    }
    for (const auto& fenceAndSignaledValue : fences) {
        SharedFence* fence = ToBackend(fenceAndSignaledValue.object).Get();
        DAWN_TRY(fence->ServerWait(fenceAndSignaledValue.signaledValue));
    }

    mLastSharedTextureMemoryUsageSerial = GetDevice()->GetQueue()->GetPendingCommandSerial();
    return {};
}

// TextureView

// static
ResultOrError<Ref<TextureView>> TextureView::Create(
    TextureBase* texture,
    const UnpackedPtr<TextureViewDescriptor>& descriptor) {
    // Wrap the handle in a TextureView class early so that it is deleted if initialization fails
    Ref<TextureView> view = AcquireRef(new TextureView(texture, descriptor));
    if (RequiresCreatingNewTextureView(texture, descriptor)) {
        view->mOwnsHandle = OwnsHandle::Yes;
    } else {
        view->mOwnsHandle = OwnsHandle::No;
    }
    DAWN_TRY(ToBackend(texture->GetDevice())
                 ->EnqueueGL([view, texture = Ref<Texture>(ToBackend(texture))](
                                 const OpenGLFunctions& gl) -> MaybeError {
                     if (texture->IsDestroyed()) {
                         view->mHandle = 0;
                     } else if (view->mOwnsHandle == OwnsHandle::Yes) {
                         GLuint handle = 0;
                         DAWN_ASSERT(gl.IsAtLeastGL(4, 3));
                         DAWN_GL_TRY(gl, GenTextures(1, &handle));
                         DAWN_GL_TRY(gl,
                                     TextureView(handle, view->GetGLTarget(), texture->GetHandle(),
                                                 view->GetInternalFormat(), view->GetBaseMipLevel(),
                                                 view->GetLevelCount(), view->GetBaseArrayLayer(),
                                                 view->GetLayerCount()));
                         view->mHandle = handle;
                     } else {
                         view->mHandle = texture->GetHandle();
                     }
                     return {};
                 }));
    return view;
}

TextureView::TextureView(TextureBase* texture, const UnpackedPtr<TextureViewDescriptor>& descriptor)
    : TextureViewBase(texture, descriptor) {
    mTarget = TargetForTextureViewDimension(descriptor->dimension, texture->GetSampleCount());
}

TextureView::~TextureView() {}

void TextureView::DestroyImpl(DestroyReason reason) {
    TextureViewBase::DestroyImpl(reason);
    if (mOwnsHandle == OwnsHandle::Yes) {
        IgnoreErrors(
            ToBackend(GetDevice())
                ->EnqueueDestroyGL(this, &TextureView::GetHandle, reason,
                                   [](const OpenGLFunctions& gl, GLuint handle) -> MaybeError {
                                       DAWN_GL_TRY_IGNORE_ERRORS(gl, DeleteTextures(1, &handle));
                                       return {};
                                   }));
    }
}

GLuint TextureView::GetHandle() const {
    DAWN_ASSERT(mHandle != 0);
    return mHandle;
}

GLenum TextureView::GetGLTarget() const {
    return mTarget;
}

MaybeError TextureView::BindToFramebuffer(const OpenGLFunctions& gl,
                                          GLenum target,
                                          GLenum attachment,
                                          GLuint depthSlice) {
    DAWN_ASSERT(depthSlice <
                static_cast<GLuint>(GetSingleSubresourceVirtualSize().depthOrArrayLayers));

    // Use the base texture where possible to minimize the amount of copying required on GLES.
    bool useOwnView = GetFormat().format != GetTexture()->GetFormat().format &&
                      !GetTexture()->GetFormat().HasDepthOrStencil();

    GLenum textarget;
    GLuint textureHandle, mipLevel, arrayLayer;
    if (useOwnView) {
        // Use our own texture handle and target which points to a subset of the texture's
        // subresources.
        textureHandle = GetHandle();
        textarget = GetGLTarget();
        mipLevel = 0;
        arrayLayer = 0;
    } else {
        // Use the texture's handle and target, with the view's base mip level and base array

        textureHandle = ToBackend(GetTexture())->GetHandle();
        textarget = ToBackend(GetTexture())->GetGLTarget();
        mipLevel = GetBaseMipLevel();
        // We have validated that the depthSlice in render pass's colorAttachments must be undefined
        // for 2d RTVs, which value is set to 0. For 3d RTVs, the baseArrayLayer must be 0. So here
        // we can simply use baseArrayLayer + depthSlice to specify the slice in RTVs without
        // checking the view's dimension.
        arrayLayer = GetBaseArrayLayer() + depthSlice;
    }

    DAWN_ASSERT(textureHandle != 0);

    return FramebufferTextureHelper(gl, textarget, target, attachment, textureHandle, mipLevel,
                                    arrayLayer);
}

GLenum TextureView::GetInternalFormat() const {
    // Depth/stencil don't support reinterpretation, and the aspect is specified at
    // bind time. In that case, we use the base texture format.
    const Format& format =
        GetFormat().HasDepthOrStencil() ? GetTexture()->GetFormat() : GetFormat();
    const GLFormat& glFormat = ToBackend(GetDevice())->GetGLFormat(format);
    return glFormat.internalFormat;
}

}  // namespace dawn::native::opengl
