// Copyright 2020 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/CopyTextureForBrowserHelper.h"

#include <utility>

#include "dawn/native/ValidationUtils_autogen.h"
#include "src/dawn/common/Strings.h"
#include "src/dawn/native/BindGroup.h"
#include "src/dawn/native/BindGroupLayout.h"
#include "src/dawn/native/Buffer.h"
#include "src/dawn/native/CommandBuffer.h"
#include "src/dawn/native/CommandEncoder.h"
#include "src/dawn/native/CommandValidation.h"
#include "src/dawn/native/Device.h"
#include "src/dawn/native/ExternalTexture.h"
#include "src/dawn/native/InternalPipelineStore.h"
#include "src/dawn/native/Queue.h"
#include "src/dawn/native/RenderPassEncoder.h"
#include "src/dawn/native/RenderPipeline.h"
#include "src/dawn/native/Sampler.h"
#include "src/dawn/native/Texture.h"
#include "src/dawn/native/utils/WGPUHelpers.h"
#include "src/utils/compiler.h"

namespace dawn::native {
namespace {
static const char sCopyForBrowserShader[] = DAWN_MULTILINE(
    struct GammaTransferParamsInternal {
        G: f32,
        A: f32,
        B: f32,
        C: f32,
        D: f32,
        E: f32,
        F: f32,
        padding: u32,
    };

    struct Uniforms {                                                     // offset   align   size
        scale: vec2f,                                                     // 0        8       8
        offset: vec2f,                                                    // 8        8       8
        steps_mask: u32,                                                  // 16       4       4
        // implicit padding;                                              // 20               12
        conversion_matrix: mat3x3<f32>,                                   // 32       16      48
        gamma_decoding_params: GammaTransferParamsInternal,               // 80       4       32
        gamma_encoding_params: GammaTransferParamsInternal,               // 112      4       32
        gamma_decoding_for_dst_srgb_params: GammaTransferParamsInternal,  // 144      4       32
    };

    @binding(0) @group(0) var<uniform> uniforms : Uniforms;

    struct VertexOutputs {
        @location(0) texcoords : vec2f,
        @builtin(position) position : vec4f,
    };

    // Chromium uses unified equation to construct gamma decoding function
    // and gamma encoding function.
    // The logic is:
    //  if x < D
    //      linear = C * x + F
    //  nonlinear = pow(A * x + B, G) + E
    // (https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/color_transform.cc;l=541)
    // Expand the equation with sign() to make it handle all gamma conversions.
    fn gamma_conversion(v: f32, params: GammaTransferParamsInternal) -> f32 {
        // Linear part: C * x + F
        if (abs(v) < params.D) {
            return sign(v) * (params.C * abs(v) + params.F);
        }

        // Gamma part: pow(A * x + B, G) + E
        return sign(v) * (pow(params.A * abs(v) + params.B, params.G) + params.E);
    }

    @vertex
    fn vs_main(
        @builtin(vertex_index) VertexIndex : u32
    ) -> VertexOutputs {
        var texcoord = array(
            vec2f(-0.5, 0.0),
            vec2f( 1.5, 0.0),
            vec2f( 0.5, 2.0));

        var output : VertexOutputs;
        output.position = vec4f((texcoord[VertexIndex] * 2.0 - vec2f(1.0, 1.0)), 0.0, 1.0);
        output.texcoords = texcoord[VertexIndex] * uniforms.scale + uniforms.offset;

        return output;
    }

    @binding(1) @group(0) var mySampler: sampler;

    // Resource used in copyTexture entry point only.
    @binding(2) @group(0) var mySourceTexture: texture_2d<f32>;

    // Resource used in copyExternalTexture entry point only.
    @binding(2) @group(0) var mySourceExternalTexture: texture_external;

    fn discardIfOutsideOfCopy(texcoord : vec2f) {
        var clampedTexcoord =
            clamp(texcoord, vec2f(0.0, 0.0), vec2f(1.0, 1.0));
        if (!all(clampedTexcoord == texcoord)) {
            discard;
        }
    }

    fn transform(srcColor : vec4f) -> vec4f {
        var color = srcColor;
        let kUnpremultiplyStep = 0x01u;
        let kDecodeToLinearStep = 0x02u;
        let kConvertToDstGamutStep = 0x04u;
        let kEncodeToGammaStep = 0x08u;
        let kPremultiplyStep = 0x10u;
        let kDecodeForSrgbDstFormat = 0x20u;
        let kClearSrcAlphaToOne = 0x40u;

        // Unpremultiply step. Applying color space conversion op on premultiplied source texture
        // also needs to unpremultiply first.
        // This step is exclusive with clear src alpha to one step.
        if (bool(uniforms.steps_mask & kUnpremultiplyStep)) {
            if (color.a != 0.0) {
                color = vec4f(color.rgb / color.a, color.a);
            }
        }

        // Linearize the source color using the source color space’s
        // transfer function if it is non-linear.
        if (bool(uniforms.steps_mask & kDecodeToLinearStep)) {
            color = vec4f(gamma_conversion(color.r, uniforms.gamma_decoding_params),
                                gamma_conversion(color.g, uniforms.gamma_decoding_params),
                                gamma_conversion(color.b, uniforms.gamma_decoding_params),
                                color.a);
        }

        // Convert unpremultiplied, linear source colors to the destination gamut by
        // multiplying by a 3x3 matrix. Calculate transformFromXYZD50 * transformToXYZD50
        // in CPU side and upload the final result in uniforms.
        if (bool(uniforms.steps_mask & kConvertToDstGamutStep)) {
            color = vec4f(uniforms.conversion_matrix * color.rgb, color.a);
        }

        // Encode that color using the inverse of the destination color
        // space’s transfer function if it is non-linear.
        if (bool(uniforms.steps_mask & kEncodeToGammaStep)) {
            color = vec4f(gamma_conversion(color.r, uniforms.gamma_encoding_params),
                                gamma_conversion(color.g, uniforms.gamma_encoding_params),
                                gamma_conversion(color.b, uniforms.gamma_encoding_params),
                                color.a);
        }

        // Premultiply step.
        // This step is exclusive with clear src alpha to one step.
        if (bool(uniforms.steps_mask & kPremultiplyStep)) {
            color = vec4f(color.rgb * color.a, color.a);
        }

        // Decode for copying from non-srgb formats to srgb formats
        if (bool(uniforms.steps_mask & kDecodeForSrgbDstFormat)) {
            color = vec4f(gamma_conversion(color.r, uniforms.gamma_decoding_for_dst_srgb_params),
                                gamma_conversion(color.g, uniforms.gamma_decoding_for_dst_srgb_params),
                                gamma_conversion(color.b, uniforms.gamma_decoding_for_dst_srgb_params),
                                color.a);
        }

        // Clear alpha to one step.
        // This step is exclusive with premultiply/unpremultiply step.
        if (bool(uniforms.steps_mask & kClearSrcAlphaToOne)) {
            color.a = 1.0;
        }

        return color;
    }

    @fragment
    fn copyTexture(@location(0) texcoord : vec2f) -> @location(0) vec4f {
        discardIfOutsideOfCopy(texcoord);

        var color = textureSample(mySourceTexture, mySampler, texcoord);
        return transform(color);
    }

    @fragment
    fn copyExternalTexture(@location(0) texcoord : vec2f) -> @location(0) vec4f {
        discardIfOutsideOfCopy(texcoord);

        var color = textureSampleBaseClampToEdge(mySourceExternalTexture, mySampler, texcoord);
        return transform(color);  // NOLINT(build/include_what_you_use)
    }
);

// Follow the same order of skcms_TransferFunction
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/include/third_party/skcms/skcms.h;l=46;
struct GammaTransferParamsInternal {
    float G = 0.0;
    float A = 0.0;
    float B = 0.0;
    float C = 0.0;
    float D = 0.0;
    float E = 0.0;
    float F = 0.0;
    uint32_t padding = 0;
};

struct Uniform {
    float scaleX;
    float scaleY;
    float offsetX;
    float offsetY;
    uint32_t stepsMask = 0;
    const std::array<uint32_t, 3> padding = {};  // 12 bytes padding
    std::array<float, 12> conversionMatrix = {};
    GammaTransferParamsInternal gammaDecodingParams = {};
    GammaTransferParamsInternal gammaEncodingParams = {};
    GammaTransferParamsInternal gammaDecodingForDstSrgbParams = {};
};
static_assert(sizeof(Uniform) == 176);

enum class SourceTextureType { Texture2D, ExternalTexture };

struct TextureInfo {
    Origin3D origin;
    Extent3D size;
};

// TODO(crbug.com/dawn/856): Expand copyTextureForBrowser to support any
// non-depth, non-stencil, non-compressed texture format pair copy.
MaybeError ValidateCopyTextureSourceFormat(const wgpu::TextureFormat srcFormat) {
    switch (srcFormat) {
        case wgpu::TextureFormat::BGRA8Unorm:
        case wgpu::TextureFormat::RGBA8Unorm:
        case wgpu::TextureFormat::RGBA16Float:
            break;
        default:
            return DAWN_VALIDATION_ERROR("Source texture format (%s) is not supported.", srcFormat);
    }

    return {};
}

RenderPipelineBase* GetCachedCopyTexturePipeline(InternalPipelineStore* store,
                                                 wgpu::TextureFormat dstFormat) {
    auto pipeline = store->copyTextureForBrowserPipelines.find(dstFormat);
    if (pipeline != store->copyTextureForBrowserPipelines.end()) {
        return pipeline->second.Get();
    }
    return nullptr;
}

RenderPipelineBase* GetCachedCopyExternalTexturePipeline(InternalPipelineStore* store,
                                                         wgpu::TextureFormat dstFormat) {
    auto pipeline = store->copyExternalTextureForBrowserPipelines.find(dstFormat);
    if (pipeline != store->copyExternalTextureForBrowserPipelines.end()) {
        return pipeline->second.Get();
    }
    return nullptr;
}

ResultOrError<Ref<RenderPipelineBase>> CreateCopyForBrowserPipeline(
    DeviceBase* device,
    wgpu::TextureFormat dstFormat,
    ShaderModuleBase* shaderModule,
    const char* fragmentEntryPoint) {
    // Prepare vertex stage.
    VertexState vertex = {};
    vertex.module = shaderModule;
    vertex.entryPoint = "vs_main";

    // Prepare frgament stage.
    FragmentState fragment = {};
    fragment.module = shaderModule;
    fragment.entryPoint = fragmentEntryPoint;

    // Prepare color state.
    ColorTargetState target = {};
    target.format = dstFormat;

    // Create RenderPipeline.
    RenderPipelineDescriptor renderPipelineDesc = {};

    // Generate the layout based on shader modules.
    renderPipelineDesc.layout = nullptr;

    renderPipelineDesc.vertex = vertex;
    renderPipelineDesc.fragment = &fragment;

    renderPipelineDesc.primitive.topology = wgpu::PrimitiveTopology::TriangleList;

    fragment.targetCount = 1;
    fragment.targets = &target;

    return device->CreateRenderPipeline(&renderPipelineDesc);
}

ResultOrError<ShaderModuleBase*> GetOrCreateCopyForBrowserShaderModule(
    DeviceBase* device,
    InternalPipelineStore* store) {
    if (store->copyForBrowser == nullptr) {
        DAWN_TRY_ASSIGN(store->copyForBrowser,
                        utils::CreateShaderModule(device, sCopyForBrowserShader));
    }

    return store->copyForBrowser.Get();
}

ResultOrError<RenderPipelineBase*> GetOrCreateCopyTextureForBrowserPipeline(
    DeviceBase* device,
    wgpu::TextureFormat dstFormat) {
    InternalPipelineStore* store = device->GetInternalPipelineStore();
    if (GetCachedCopyTexturePipeline(store, dstFormat) == nullptr) {
        ShaderModuleBase* shaderModule;
        DAWN_TRY_ASSIGN(shaderModule, GetOrCreateCopyForBrowserShaderModule(device, store));
        Ref<RenderPipelineBase> pipeline;
        DAWN_TRY_ASSIGN(
            pipeline, CreateCopyForBrowserPipeline(device, dstFormat, shaderModule, "copyTexture"));
        store->copyTextureForBrowserPipelines.emplace(dstFormat, std::move(pipeline));
    }

    return GetCachedCopyTexturePipeline(store, dstFormat);
}

ResultOrError<RenderPipelineBase*> GetOrCreateCopyExternalTextureForBrowserPipeline(
    DeviceBase* device,
    wgpu::TextureFormat dstFormat) {
    InternalPipelineStore* store = device->GetInternalPipelineStore();
    if (GetCachedCopyExternalTexturePipeline(store, dstFormat) == nullptr) {
        ShaderModuleBase* shaderModule;
        DAWN_TRY_ASSIGN(shaderModule, GetOrCreateCopyForBrowserShaderModule(device, store));
        Ref<RenderPipelineBase> pipeline;
        DAWN_TRY_ASSIGN(pipeline, CreateCopyForBrowserPipeline(device, dstFormat, shaderModule,
                                                               "copyExternalTexture"));
        store->copyExternalTextureForBrowserPipelines.emplace(dstFormat, std::move(pipeline));
    }

    return GetCachedCopyExternalTexturePipeline(store, dstFormat);
}

// Whether the format of dst texture of CopyTextureForBrowser() is srgb or non-srgb.
bool IsSrgbDstFormat(wgpu::TextureFormat format) {
    switch (format) {
        case wgpu::TextureFormat::RGBA8UnormSrgb:
        case wgpu::TextureFormat::BGRA8UnormSrgb:
            return true;
        default:
            return false;
    }
}

template <typename T>
MaybeError DoCopyForBrowser(DeviceBase* device,
                            const TextureInfo* sourceInfo,
                            T* sourceResource,
                            const TexelCopyTextureInfo* destination,
                            const Extent3D* copySize,
                            const CopyTextureForBrowserOptions* options,
                            RenderPipelineBase* pipeline) {
    // TODO(crbug.com/dawn/856): In D3D12 and Vulkan, compatible texture format can directly
    // copy to each other. This can be a potential fast path.

    // Noop copy
    if (copySize->width == 0 || copySize->height == 0 || copySize->depthOrArrayLayers == 0) {
        return {};
    }

    // Prepare bind group layout.
    Ref<BindGroupLayoutBase> layout;
    DAWN_TRY_ASSIGN(layout, pipeline->GetBindGroupLayout(0));

    // Prepare binding 0 resource: uniform buffer.
    Uniform uniformData = {
        copySize->width / static_cast<float>(sourceInfo->size.width),
        copySize->height / static_cast<float>(sourceInfo->size.height),  // scale
        sourceInfo->origin.x / static_cast<float>(sourceInfo->size.width),
        sourceInfo->origin.y / static_cast<float>(sourceInfo->size.height)  // offset
    };

    // The NDC to framebuffer space transform maps inverts the Y coordinate such that NDC [-1, 1]
    // (resp [-1, -1]) maps to framebuffer space [0, 0] (resp [0, height-1]). So we need to undo
    // this flip when converting positions to texcoords.
    // https://www.w3.org/TR/webgpu/#coordinate-systems
    if (!options->flipY) {
        uniformData.scaleY *= -1.0;
        uniformData.offsetY += copySize->height / static_cast<float>(sourceInfo->size.height);
    }

    uint32_t stepsMask = 0u;

    // Steps to do color space conversion
    // From https://skia.org/docs/user/color/
    // - unpremultiply if the source color is premultiplied; Alpha is not involved in color
    // management, and we need to divide it out if it’s multiplied in.
    // - linearize the source color using the source color space’s transfer function
    // - convert those unpremultiplied, linear source colors to XYZ D50 gamut by multiplying by
    // a 3x3 matrix.
    // - convert those XYZ D50 colors to the destination gamut by multiplying by a 3x3 matrix.
    // - encode that color using the inverse of the destination color space’s transfer function.
    // - premultiply by alpha if the destination is premultiplied.
    // The reason to choose XYZ D50 as intermediate color space:
    // From http://www.brucelindbloom.com/index.html?WorkingSpaceInfo.html
    // "Since the Lab TIFF specification, the ICC profile specification and
    // Adobe Photoshop all use a D50"
    constexpr uint32_t kUnpremultiplyStep = 0x01;
    constexpr uint32_t kDecodeToLinearStep = 0x02;
    constexpr uint32_t kConvertToDstGamutStep = 0x04;
    constexpr uint32_t kEncodeToGammaStep = 0x08;
    constexpr uint32_t kPremultiplyStep = 0x10;
    constexpr uint32_t kDecodeForSrgbDstFormat = 0x20;
    constexpr uint32_t kClearSrcAlphaToOne = 0x40;

    if (options->srcAlphaMode == wgpu::AlphaMode::Premultiplied) {
        if (options->needsColorSpaceConversion ||
            options->dstAlphaMode == wgpu::AlphaMode::Unpremultiplied) {
            stepsMask |= kUnpremultiplyStep;
        }
    } else if (options->srcAlphaMode == wgpu::AlphaMode::Opaque) {
        // Simply clear src alpha channel to 1.0
        stepsMask |= kClearSrcAlphaToOne;
    }

    if (options->needsColorSpaceConversion) {
        stepsMask |= kDecodeToLinearStep;
        const float* decodingParams = options->srcTransferFunctionParameters;

        uniformData.gammaDecodingParams = {decodingParams[0],
                                           DAWN_UNSAFE_TODO(decodingParams[1]),
                                           DAWN_UNSAFE_TODO(decodingParams[2]),
                                           DAWN_UNSAFE_TODO(decodingParams[3]),
                                           DAWN_UNSAFE_TODO(decodingParams[4]),
                                           DAWN_UNSAFE_TODO(decodingParams[5]),
                                           DAWN_UNSAFE_TODO(decodingParams[6])};

        stepsMask |= kConvertToDstGamutStep;
        const float* matrix = options->conversionMatrix;
        uniformData.conversionMatrix = {{
            matrix[0],
            DAWN_UNSAFE_TODO(matrix[1]),
            DAWN_UNSAFE_TODO(matrix[2]),
            0.0,
            DAWN_UNSAFE_TODO(matrix[3]),
            DAWN_UNSAFE_TODO(matrix[4]),
            DAWN_UNSAFE_TODO(matrix[5]),
            0.0,
            DAWN_UNSAFE_TODO(matrix[6]),
            DAWN_UNSAFE_TODO(matrix[7]),
            DAWN_UNSAFE_TODO(matrix[8]),
            0.0,
        }};

        stepsMask |= kEncodeToGammaStep;
        const float* encodingParams = options->dstTransferFunctionParameters;

        uniformData.gammaEncodingParams = {encodingParams[0],
                                           DAWN_UNSAFE_TODO(encodingParams[1]),
                                           DAWN_UNSAFE_TODO(encodingParams[2]),
                                           DAWN_UNSAFE_TODO(encodingParams[3]),
                                           DAWN_UNSAFE_TODO(encodingParams[4]),
                                           DAWN_UNSAFE_TODO(encodingParams[5]),
                                           DAWN_UNSAFE_TODO(encodingParams[6])};
    }

    if (options->dstAlphaMode == wgpu::AlphaMode::Premultiplied) {
        if (options->needsColorSpaceConversion ||
            options->srcAlphaMode == wgpu::AlphaMode::Unpremultiplied) {
            stepsMask |= kPremultiplyStep;
        }
    }

    // Copy to *-srgb texture should keep the bytes exactly the same as copy
    // to non-srgb texture. Add an extra decode-to-linear step so that after the
    // sampler of *-srgb format texture applying encoding, the bytes keeps the same
    // as non-srgb format texture.
    // NOTE: CopyTextureForBrowser() doesn't need to accept *-srgb format texture as
    // source input. But above operation also valid for *-srgb format texture input and
    // non-srgb format dst texture.
    // TODO(crbug.com/dawn/1195): Reinterpret to non-srgb texture view on *-srgb texture
    // and use it as render attachment when possible.
    // TODO(crbug.com/dawn/1195): Opt the condition for this extra step. It is possible to
    // bypass this extra step in some cases.
    bool isSrgbDstFormat = IsSrgbDstFormat(destination->texture->GetFormat().format);
    if (isSrgbDstFormat) {
        stepsMask |= kDecodeForSrgbDstFormat;
        // Get gamma-linear conversion params from https://en.wikipedia.org/wiki/SRGB with some
        // mathematics. Order: {G, A, B, C, D, E, F, }
        uniformData.gammaDecodingForDstSrgbParams = {
            2.4f, 1.0f / 1.055f, 0.055f / 1.055f, 1.0f / 12.92f, 4.045e-02f, 0.0f, 0.0f};
    }

    // Upload uniform data
    uniformData.stepsMask = stepsMask;

    Ref<BufferBase> uniformBuffer;
    DAWN_TRY_ASSIGN(
        uniformBuffer,
        utils::CreateBufferFromData(device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Uniform,
                                    {uniformData}));

    // Prepare binding 1 resource: sampler
    // Use default configuration, filterMode set to Nearest for min and mag.
    SamplerDescriptor samplerDesc = {};
    Ref<SamplerBase> sampler;
    DAWN_TRY_ASSIGN(sampler, device->CreateSampler(&samplerDesc));

    // Create bind group after all binding entries are set.
    UsageValidationMode mode =
        options->internalUsage ? UsageValidationMode::Internal : UsageValidationMode::Default;
    Ref<BindGroupBase> bindGroup;
    DAWN_TRY_ASSIGN(bindGroup, utils::MakeBindGroup(
                                   device, layout,
                                   {{0, uniformBuffer}, {1, sampler}, {2, sourceResource}}, mode));

    // Create command encoder.
    CommandEncoderDescriptor commandEncoderDesc;
    DawnEncoderInternalUsageDescriptor internalUsageDesc;
    if (options->internalUsage) {
        internalUsageDesc.useInternalUsages = true;
        commandEncoderDesc.nextInChain = &internalUsageDesc;
    }
    Ref<CommandEncoder> encoder;
    DAWN_TRY_ASSIGN(encoder, device->CreateCommandEncoder(&commandEncoderDesc));

    // Prepare dst texture view as color Attachment.
    TextureViewDescriptor dstTextureViewDesc;
    dstTextureViewDesc.dimension = wgpu::TextureViewDimension::e2D;
    dstTextureViewDesc.baseMipLevel = destination->mipLevel;
    dstTextureViewDesc.mipLevelCount = 1;
    dstTextureViewDesc.baseArrayLayer = destination->origin.z;
    dstTextureViewDesc.arrayLayerCount = 1;
    Ref<TextureViewBase> dstView;

    DAWN_TRY_ASSIGN(dstView, device->CreateTextureView(destination->texture, &dstTextureViewDesc));
    // Prepare render pass color attachment descriptor.
    RenderPassColorAttachment colorAttachmentDesc;

    colorAttachmentDesc.view = dstView.Get();
    colorAttachmentDesc.loadOp = wgpu::LoadOp::Load;
    colorAttachmentDesc.storeOp = wgpu::StoreOp::Store;
    colorAttachmentDesc.clearValue = {0.0, 0.0, 0.0, 1.0};

    // Create render pass.
    RenderPassDescriptor renderPassDesc;
    renderPassDesc.colorAttachmentCount = 1;
    renderPassDesc.colorAttachments = &colorAttachmentDesc;
    Ref<RenderPassEncoder> passEncoder = encoder->BeginRenderPass(&renderPassDesc);

    // Start pipeline and encode commands to complete
    // the copy from src texture to dst texture with transformation.
    passEncoder->APISetPipeline(pipeline);
    passEncoder->APISetBindGroup(0, bindGroup.Get());
    passEncoder->APISetViewport(
        static_cast<float>(destination->origin.x), static_cast<float>(destination->origin.y),
        static_cast<float>(copySize->width), static_cast<float>(copySize->height), 0.0f, 1.0f);
    passEncoder->APIDraw(3);
    passEncoder->End();

    // Finsh encoding.
    Ref<CommandBufferBase> commandBuffer;
    DAWN_TRY_ASSIGN(commandBuffer, encoder->Finish());
    CommandBufferBase* submitCommandBuffer = commandBuffer.Get();

    // Submit command buffer.
    device->GetQueue()->APISubmit(1, &submitCommandBuffer);
    return {};
}

MaybeError ValidateCopyForBrowserDestination(DeviceBase* device,
                                             const TexelCopyTextureInfo& destination,
                                             const Extent3D& copySize,
                                             const CopyTextureForBrowserOptions& options) {
    DAWN_TRY(device->ValidateObject(destination.texture));
    DAWN_TRY(destination.texture->ValidateCanUseInSubmitNow());
    DAWN_TRY_CONTEXT(ValidateTexelCopyTextureInfo(device, destination, copySize),
                     "validating the TexelCopyTextureInfo for the destination");
    DAWN_TRY_CONTEXT(ValidateTextureCopyRange(device, destination, copySize),
                     "validating that the copy fits in the destination");

    UsageValidationMode mode =
        options.internalUsage ? UsageValidationMode::Internal : UsageValidationMode::Default;
    DAWN_TRY(ValidateCanUseAs(destination.texture, wgpu::TextureUsage::CopyDst, mode));
    DAWN_TRY(ValidateCanUseAs(destination.texture, wgpu::TextureUsage::RenderAttachment, mode));
    const Format& dstFormat = destination.texture->GetFormat();
    DAWN_INVALID_IF(dstFormat.aspects != Aspect::Color,
                    "The destination %s is not a color texture.", destination.texture);
    DAWN_INVALID_IF(dstFormat.GetAspectInfo(Aspect::Color).baseType != TextureComponentType::Float,
                    "The destination %s must not be an integer format.", destination.texture);
    DAWN_INVALID_IF(destination.texture->GetSampleCount() > 1,
                    "The destination texture sample count (%u) is not 1.",
                    destination.texture->GetSampleCount());

    // The valid destination formats are all color formats.
    DAWN_INVALID_IF(
        destination.aspect != wgpu::TextureAspect::All,
        "Destination %s aspect (%s) doesn't select all the aspects of the destination format.",
        destination.texture, destination.aspect);

    return {};
}

MaybeError ValidateCopyForBrowserOptions(const CopyTextureForBrowserOptions& options) {
    DAWN_INVALID_IF(options.nextInChain != nullptr, "nextInChain must be nullptr");

    DAWN_TRY(ValidateAlphaMode(options.srcAlphaMode));
    DAWN_TRY(ValidateAlphaMode(options.dstAlphaMode));

    if (options.needsColorSpaceConversion) {
        DAWN_INVALID_IF(options.srcTransferFunctionParameters == nullptr,
                        "srcTransferFunctionParameters is nullptr when doing color conversion");
        DAWN_INVALID_IF(options.conversionMatrix == nullptr,
                        "conversionMatrix is nullptr when doing color conversion");
        DAWN_INVALID_IF(options.dstTransferFunctionParameters == nullptr,
                        "dstTransferFunctionParameters is nullptr when doing color conversion");
    }
    return {};
}
}  // anonymous namespace

MaybeError ValidateCopyTextureForBrowser(DeviceBase* device,
                                         const TexelCopyTextureInfo* source,
                                         const TexelCopyTextureInfo* destination,
                                         const Extent3D* copySize,
                                         const CopyTextureForBrowserOptions* options) {
    // Validate source
    DAWN_TRY(device->ValidateObject(source->texture));
    DAWN_TRY(source->texture->ValidateCanUseInSubmitNow());
    DAWN_TRY_CONTEXT(ValidateTexelCopyTextureInfo(device, *source, *copySize),
                     "validating the TexelCopyTextureInfo for the source");
    DAWN_TRY_CONTEXT(ValidateTextureCopyRange(device, *source, *copySize),
                     "validating that the copy fits in the source");
    DAWN_INVALID_IF(source->origin.z > 0, "Source has a non-zero z origin (%u).", source->origin.z);
    DAWN_INVALID_IF(source->texture->GetSampleCount() > 1,
                    "The source texture sample count (%u) is not 1. ",
                    source->texture->GetSampleCount());
    DAWN_INVALID_IF(
        options->internalUsage && !device->HasFeature(Feature::DawnInternalUsages),
        "The internalUsage is true while the dawn-internal-usages feature is not enabled.");
    UsageValidationMode mode =
        options->internalUsage ? UsageValidationMode::Internal : UsageValidationMode::Default;
    DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::CopySrc, mode));
    DAWN_TRY(ValidateCanUseAs(source->texture, wgpu::TextureUsage::TextureBinding, mode));
    DAWN_TRY(ValidateCopyTextureSourceFormat(source->texture->GetFormat().format));

    // Validate destination
    DAWN_TRY(ValidateCopyForBrowserDestination(device, *destination, *copySize, *options));

    // Validate copy common rules and copySize.
    DAWN_INVALID_IF(copySize->depthOrArrayLayers > 1, "Copy is for more than one array layer (%u)",
                    copySize->depthOrArrayLayers);
    DAWN_TRY(
        ValidateTextureToTextureCopyCommonRestrictions(device, *source, *destination, *copySize));

    // Validate options
    DAWN_TRY(ValidateCopyForBrowserOptions(*options));

    return {};
}

MaybeError ValidateCopyExternalTextureForBrowser(DeviceBase* device,
                                                 const ImageCopyExternalTexture* source,
                                                 const TexelCopyTextureInfo* destination,
                                                 const Extent3D* copySize,
                                                 const CopyTextureForBrowserOptions* options) {
    // Validate source
    DAWN_TRY(device->ValidateObject(source->externalTexture));
    DAWN_TRY(source->externalTexture->ValidateCanUseInSubmitNow());

    Extent2D sourceSize = {};
    sourceSize.width = source->naturalSize.width;
    sourceSize.height = source->naturalSize.height;

    // All texture dimensions are in uint32_t so by doing checks in uint64_t we avoid
    // overflows.
    DAWN_INVALID_IF(
        static_cast<uint64_t>(source->origin.x) + static_cast<uint64_t>(copySize->width) >
                static_cast<uint64_t>(sourceSize.width) ||
            static_cast<uint64_t>(source->origin.y) + static_cast<uint64_t>(copySize->height) >
                static_cast<uint64_t>(sourceSize.height) ||
            static_cast<uint64_t>(source->origin.z) > 0,
        "Texture copy range (origin: %s, copySize: %s) touches outside of %s source size (%s).",
        source->origin, *copySize, source->externalTexture, sourceSize);
    DAWN_INVALID_IF(source->origin.z > 0, "Source has a non-zero z origin (%u).", source->origin.z);
    DAWN_INVALID_IF(
        options->internalUsage && !device->HasFeature(Feature::DawnInternalUsages),
        "The internalUsage is true while the dawn-internal-usages feature is not enabled.");

    // Validate destination
    DAWN_TRY(ValidateCopyForBrowserDestination(device, *destination, *copySize, *options));

    // Validate copySize
    DAWN_INVALID_IF(copySize->depthOrArrayLayers > 1, "Copy is for more than one array layer (%u)",
                    copySize->depthOrArrayLayers);

    // Validate options
    DAWN_TRY(ValidateCopyForBrowserOptions(*options));

    return {};
}

MaybeError DoCopyExternalTextureForBrowser(DeviceBase* device,
                                           const ImageCopyExternalTexture* source,
                                           const TexelCopyTextureInfo* destination,
                                           const Extent3D* copySize,
                                           const CopyTextureForBrowserOptions* options) {
    TextureInfo info;
    info.origin = source->origin;
    info.size = {source->naturalSize.width, source->naturalSize.height, 1};

    RenderPipelineBase* pipeline;
    DAWN_TRY_ASSIGN(pipeline, GetOrCreateCopyExternalTextureForBrowserPipeline(
                                  device, destination->texture->GetFormat().format));
    return DoCopyForBrowser<ExternalTextureBase>(device, &info, source->externalTexture,
                                                 destination, copySize, options, pipeline);
}

MaybeError DoCopyTextureForBrowser(DeviceBase* device,
                                   const TexelCopyTextureInfo* source,
                                   const TexelCopyTextureInfo* destination,
                                   const Extent3D* copySize,
                                   const CopyTextureForBrowserOptions* options) {
    TextureInfo info;
    info.origin = source->origin;
    info.size = source->texture->GetSize(source->aspect);

    Ref<TextureViewBase> srcTextureView = nullptr;
    TextureViewDescriptor srcTextureViewDesc = {};
    srcTextureViewDesc.dimension = wgpu::TextureViewDimension::e2D;
    srcTextureViewDesc.baseMipLevel = source->mipLevel;
    srcTextureViewDesc.mipLevelCount = 1;
    srcTextureViewDesc.arrayLayerCount = 1;
    DAWN_TRY_ASSIGN(srcTextureView,
                    device->CreateTextureView(source->texture, &srcTextureViewDesc));

    RenderPipelineBase* pipeline;
    DAWN_TRY_ASSIGN(pipeline, GetOrCreateCopyTextureForBrowserPipeline(
                                  device, destination->texture->GetFormat().format));

    return DoCopyForBrowser<TextureViewBase>(device, &info, srcTextureView.Get(), destination,
                                             copySize, options, pipeline);
}
}  // namespace dawn::native
