// Copyright 2023 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/BlitBufferToDepthStencil.h"

#include <string>
#include <string_view>
#include <utility>

#include "src/dawn/common/Assert.h"
#include "src/dawn/common/Strings.h"
#include "src/dawn/native/BindGroup.h"
#include "src/dawn/native/CommandBuffer.h"
#include "src/dawn/native/CommandEncoder.h"
#include "src/dawn/native/Device.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/utils/compiler.h"

namespace dawn::native {

namespace {

constexpr char kBlitRG8ToDepthShaders[] = DAWN_MULTILINE(
    @vertex fn vert_fullscreen_quad(
        @builtin(vertex_index) vertex_index : u32
    ) -> @builtin(position) vec4f {
        const pos = array(
            vec2f(-1.0, -1.0),
            vec2f( 3.0, -1.0),
            vec2f(-1.0,  3.0));
        return vec4f(pos[vertex_index], 0.0, 1.0);
    }

    struct Params {
    origin : vec2u
    };

    @group(0) @binding(0) var src_tex : texture_2d<u32>;
    @group(0) @binding(1) var<uniform> params : Params;

    @fragment fn blit_to_depth(
        @builtin(position) position : vec4f
    ) -> @builtin(frag_depth) f32 {
        // Load the source texel.
        let src_texel = textureLoad(
            src_tex, vec2u(position.xy) - params.origin, 0u);

        let depth_u16_val = (src_texel.y << 8u) + src_texel.x;

        const one_over_max : f32 = 1.0 / f32(0xFFFFu);
        return f32(depth_u16_val) * one_over_max;
    }
);

constexpr std::string_view kTexture2DHead = DAWN_MULTILINE(
    fn textureLoadGeneral(tex: texture_2d<u32>, coords: vec2u, level: u32) -> vec4<u32> {
        //
        return textureLoad(tex, coords, level);
    }
    @group(0) @binding(0) var src_tex : texture_2d<u32>;
);

constexpr std::string_view kTexture2DArrayHead = DAWN_MULTILINE(
    fn textureLoadGeneral(tex: texture_2d_array<u32>, coords: vec2u, level: u32) -> vec4<u32> {
        //
        return textureLoad(tex, coords, params.layer, level);
    }
    @group(0) @binding(0) var src_tex : texture_2d_array<u32>;
);

constexpr std::string_view kBlitStencilShaderCommon = DAWN_MULTILINE(
    struct Params {
        origin : vec2u,
        layer: u32,
    };
    @group(0) @binding(1) var<uniform> params : Params;

    struct VertexOutputs {
        @location(0) @interpolate(flat, either) stencil_val : u32,
        @builtin(position) position : vec4f,
    };

    // The instance_index here is not used for instancing.
    // It represents the current stencil mask we're testing in the
    // source.
    // This is a cheap way to get the stencil value into the shader
    // since WebGPU doesn't have immediate data.
    @vertex fn vert_fullscreen_quad(
        @builtin(vertex_index) vertex_index : u32,
        @builtin(instance_index) instance_index: u32,
    ) -> VertexOutputs {
        const pos = array(
            vec2f(-1.0, -1.0),
            vec2f( 3.0, -1.0),
            vec2f(-1.0,  3.0));
        return VertexOutputs(
            instance_index,
            vec4f(pos[vertex_index], 0.0, 1.0),
        );
    }

    // Do nothing (but also don't discard). Used for clearing
    // stencil to 0.
    @fragment fn frag_noop() {}

    // Discard the fragment if the source texture doesn't
    // have the stencil_val.
    @fragment fn frag_check_src_stencil(input : VertexOutputs) {
        // Load the source stencil value.
        let src_val : u32 = textureLoadGeneral(
            src_tex, vec2u(input.position.xy) - params.origin, 0u)[0];

        // Discard it if it doesn't contain the stencil reference.
        if ((src_val & input.stencil_val) == 0u) {
            discard;
        }
    }
);

ResultOrError<Ref<RenderPipelineBase>> GetOrCreateRG8ToDepth16UnormPipeline(DeviceBase* device) {
    InternalPipelineStore* store = device->GetInternalPipelineStore();
    if (store->blitRG8ToDepth16UnormPipeline != nullptr) {
        return store->blitRG8ToDepth16UnormPipeline;
    }

    ShaderSourceWGSL wgslDesc = {};
    ShaderModuleDescriptor shaderModuleDesc = {};
    shaderModuleDesc.nextInChain = &wgslDesc;
    wgslDesc.code = kBlitRG8ToDepthShaders;

    Ref<ShaderModuleBase> shaderModule;
    DAWN_TRY_ASSIGN(shaderModule, device->CreateShaderModule(&shaderModuleDesc));

    FragmentState fragmentState = {};
    fragmentState.module = shaderModule.Get();
    fragmentState.entryPoint = "blit_to_depth";

    DepthStencilState dsState = {};
    dsState.format = wgpu::TextureFormat::Depth16Unorm;
    dsState.depthWriteEnabled = wgpu::OptionalBool::True;
    dsState.depthCompare = wgpu::CompareFunction::Always;

    RenderPipelineDescriptor renderPipelineDesc = {};
    renderPipelineDesc.vertex.module = shaderModule.Get();
    renderPipelineDesc.vertex.entryPoint = "vert_fullscreen_quad";
    renderPipelineDesc.depthStencil = &dsState;
    renderPipelineDesc.fragment = &fragmentState;

    Ref<RenderPipelineBase> pipeline;
    DAWN_TRY_ASSIGN(pipeline, device->CreateRenderPipeline(&renderPipelineDesc));

    store->blitRG8ToDepth16UnormPipeline = pipeline;
    return pipeline;
}

ResultOrError<InternalPipelineStore::BlitR8ToStencilPipelines> GetOrCreateR8ToStencilPipelines(
    DeviceBase* device,
    wgpu::TextureFormat format,
    wgpu::TextureViewDimension viewDimension,
    BindGroupLayoutBase* bgl) {
    InternalPipelineStore* store = device->GetInternalPipelineStore();
    {
        auto it = store->blitR8ToStencilPipelines.find({format, viewDimension});
        if (it != store->blitR8ToStencilPipelines.end()) {
            return InternalPipelineStore::BlitR8ToStencilPipelines{it->second};
        }
    }

    Ref<PipelineLayoutBase> pipelineLayout;
    {
        PipelineLayoutDescriptor plDesc = {};
        plDesc.bindGroupLayoutCount = 1;

        plDesc.bindGroupLayouts = &bgl;
        DAWN_TRY_ASSIGN(pipelineLayout, device->CreatePipelineLayout(&plDesc));
    }

    ShaderSourceWGSL wgslDesc = {};
    ShaderModuleDescriptor shaderModuleDesc = {};
    shaderModuleDesc.nextInChain = &wgslDesc;

    std::string shader;
    switch (viewDimension) {
        case wgpu::TextureViewDimension::e2D:
            shader = kTexture2DHead;
            break;
        case wgpu::TextureViewDimension::e2DArray:
            shader = kTexture2DArrayHead;
            break;
        default:
            DAWN_UNREACHABLE();
    }
    shader += kBlitStencilShaderCommon;
    wgslDesc.code = shader.c_str();

    Ref<ShaderModuleBase> shaderModule;
    DAWN_TRY_ASSIGN(shaderModule, device->CreateShaderModule(&shaderModuleDesc));

    FragmentState fragmentState = {};
    fragmentState.module = shaderModule.Get();

    DepthStencilState dsState = {};
    dsState.format = format;
    dsState.depthWriteEnabled = wgpu::OptionalBool::False;
    dsState.depthCompare = wgpu::CompareFunction::Always;
    dsState.stencilFront.passOp = wgpu::StencilOperation::Replace;

    RenderPipelineDescriptor renderPipelineDesc = {};
    renderPipelineDesc.layout = pipelineLayout.Get();
    renderPipelineDesc.vertex.module = shaderModule.Get();
    renderPipelineDesc.vertex.entryPoint = "vert_fullscreen_quad";
    renderPipelineDesc.depthStencil = &dsState;
    renderPipelineDesc.fragment = &fragmentState;

    // Build a pipeline to clear stencil to 0. We need a pipeline, and not just a render pass load
    // op because the copy region may be a subregion of the stencil texture.
    Ref<RenderPipelineBase> clearPipeline;
    fragmentState.entryPoint = "frag_noop";
    DAWN_TRY_ASSIGN(clearPipeline, device->CreateRenderPipeline(&renderPipelineDesc));

    // Build 8 pipelines masked to replace each bit of the stencil.
    std::array<Ref<RenderPipelineBase>, 8> setStencilPipelines;
    fragmentState.entryPoint = "frag_check_src_stencil";
    for (uint32_t bit = 0; bit < 8; ++bit) {
        dsState.stencilWriteMask = 1u << bit;
        DAWN_TRY_ASSIGN(setStencilPipelines[bit],
                        device->CreateRenderPipeline(&renderPipelineDesc));
    }

    InternalPipelineStore::BlitR8ToStencilPipelines pipelines{std::move(clearPipeline),
                                                              std::move(setStencilPipelines)};
    store->blitR8ToStencilPipelines.emplace(std::make_pair(format, viewDimension), pipelines);
    return pipelines;
}

MaybeError BlitRG8ToDepth16Unorm(DeviceBase* device,
                                 CommandEncoder* commandEncoder,
                                 TextureBase* dataTexture,
                                 const TextureCopy& dst,
                                 const TexelExtent3D& copyExtent) {
    DAWN_ASSERT(device->IsLockedByCurrentThreadIfNeeded());
    DAWN_ASSERT(dst.texture->GetFormat().format == wgpu::TextureFormat::Depth16Unorm);
    DAWN_ASSERT(dataTexture->GetFormat().format == wgpu::TextureFormat::RG8Uint);

    // Allow internal usages since we need to use the destination
    // as a render attachment.
    auto scope = commandEncoder->MakeInternalUsageScope();

    Ref<RenderPipelineBase> pipeline;
    DAWN_TRY_ASSIGN(pipeline, GetOrCreateRG8ToDepth16UnormPipeline(device));

    Ref<BindGroupLayoutBase> bgl;
    DAWN_TRY_ASSIGN(bgl, pipeline->GetBindGroupLayout(0));

    for (TexelCount z{0}; z < copyExtent.depthOrArrayLayers; ++z) {
        Ref<TextureViewBase> srcView;
        {
            TextureViewDescriptor viewDesc = {};
            viewDesc.dimension = wgpu::TextureViewDimension::e2D;
            viewDesc.baseArrayLayer = static_cast<uint32_t>(z);
            viewDesc.arrayLayerCount = 1;
            viewDesc.mipLevelCount = 1;
            DAWN_TRY_ASSIGN(srcView, dataTexture->CreateView(&viewDesc));
        }

        Ref<TextureViewBase> dstView;
        {
            TextureViewDescriptor viewDesc = {};
            viewDesc.dimension = wgpu::TextureViewDimension::e2D;
            viewDesc.baseArrayLayer = static_cast<uint32_t>(dst.origin.z + z);
            viewDesc.arrayLayerCount = 1;
            viewDesc.baseMipLevel = dst.mipLevel;
            viewDesc.mipLevelCount = 1;
            DAWN_TRY_ASSIGN(dstView, dst.texture->CreateView(&viewDesc));
        }

        Ref<BufferBase> paramsBuffer;
        {
            BufferDescriptor bufferDesc = {};
            bufferDesc.size = sizeof(uint32_t) * 2;
            bufferDesc.usage = wgpu::BufferUsage::Uniform;
            bufferDesc.mappedAtCreation = true;
            DAWN_TRY_ASSIGN(paramsBuffer, device->CreateBuffer(&bufferDesc));

            uint32_t* params =
                static_cast<uint32_t*>(paramsBuffer->GetMappedRange(0, bufferDesc.size));
            params[0] = static_cast<uint32_t>(dst.origin.x);
            DAWN_UNSAFE_TODO(params[1]) = static_cast<uint32_t>(dst.origin.y);
            DAWN_TRY(paramsBuffer->Unmap());
        }

        Ref<BindGroupBase> bindGroup;
        {
            std::array<BindGroupEntry, 2> bgEntries = {};
            bgEntries[0].binding = 0;
            bgEntries[0].textureView = srcView.Get();
            bgEntries[1].binding = 1;
            bgEntries[1].buffer = paramsBuffer.Get();

            BindGroupDescriptor bgDesc = {};
            bgDesc.layout = bgl.Get();
            bgDesc.entryCount = bgEntries.size();
            bgDesc.entries = bgEntries.data();
            DAWN_TRY_ASSIGN(bindGroup, device->CreateBindGroup(&bgDesc));
        }

        RenderPassDepthStencilAttachment dsAttachment;
        dsAttachment.view = dstView.Get();
        dsAttachment.depthClearValue = 0.0;
        dsAttachment.depthLoadOp = wgpu::LoadOp::Load;
        dsAttachment.depthStoreOp = wgpu::StoreOp::Store;

        RenderPassDescriptor rpDesc = {};
        rpDesc.depthStencilAttachment = &dsAttachment;

        Ref<RenderPassEncoder> pass = commandEncoder->BeginRenderPass(&rpDesc);
        // Bind the resources.
        pass->APISetBindGroup(0, bindGroup.Get());
        // Discard all fragments outside the copy region.
        pass->APISetScissorRect(
            static_cast<uint32_t>(dst.origin.x), static_cast<uint32_t>(dst.origin.y),
            static_cast<uint32_t>(copyExtent.width), static_cast<uint32_t>(copyExtent.height));

        // Draw to perform the blit.
        pass->APISetPipeline(pipeline.Get());
        pass->APIDraw(3, 1, 0, 0);

        pass->End();
    }
    return {};
}

}  // anonymous namespace

MaybeError BlitR8ToStencil(DeviceBase* device,
                           CommandEncoder* commandEncoder,
                           TextureBase* dataTexture,
                           const TextureCopy& dst,
                           const Extent3D& copyExtent) {
    DAWN_ASSERT(device->IsLockedByCurrentThreadIfNeeded());
    const Format& format = dst.texture->GetFormat();
    DAWN_ASSERT(dst.aspect == Aspect::Stencil);

    // Allow internal usages since we need to use the destination
    // as a render attachment.
    auto scope = commandEncoder->MakeInternalUsageScope();

    // In compat mode view dimension needs to match texture binding view dimension.
    wgpu::TextureViewDimension textureViewDimension;
    if (!device->HasFlexibleTextureViews()) {
        textureViewDimension = dataTexture->GetCompatibilityTextureBindingViewDimension();
    } else {
        textureViewDimension = wgpu::TextureViewDimension::e2DArray;
    }

    // This bgl is the same for all the render pipelines.
    Ref<BindGroupLayoutBase> bgl;
    {
        std::array<BindGroupLayoutEntry, 2> bglEntries = {};
        // Binding 0: the r8uint texture.
        bglEntries[0].binding = 0;
        bglEntries[0].visibility = wgpu::ShaderStage::Fragment;
        bglEntries[0].texture.sampleType = wgpu::TextureSampleType::Uint;
        bglEntries[0].texture.viewDimension = textureViewDimension;
        // Binding 1: the params buffer.
        bglEntries[1].binding = 1;
        bglEntries[1].visibility = wgpu::ShaderStage::Fragment;
        bglEntries[1].buffer.type = wgpu::BufferBindingType::Uniform;
        bglEntries[1].buffer.minBindingSize = 4 * sizeof(uint32_t);

        BindGroupLayoutDescriptor bglDesc = {};
        bglDesc.entryCount = bglEntries.size();
        bglDesc.entries = bglEntries.data();

        DAWN_TRY_ASSIGN(bgl, device->CreateBindGroupLayout(&bglDesc));
    }

    InternalPipelineStore::BlitR8ToStencilPipelines pipelines;
    DAWN_TRY_ASSIGN(pipelines, GetOrCreateR8ToStencilPipelines(device, format.format,
                                                               textureViewDimension, bgl.Get()));

    // Build the params buffer, containing the copy dst origin.
    Ref<BufferBase> paramsBuffer;
    {
        BufferDescriptor bufferDesc = {};
        bufferDesc.size = sizeof(uint32_t) * 4;
        bufferDesc.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst;
        bufferDesc.mappedAtCreation = true;
        DAWN_TRY_ASSIGN(paramsBuffer, device->CreateBuffer(&bufferDesc));

        uint32_t* params = static_cast<uint32_t*>(paramsBuffer->GetMappedRange(0, bufferDesc.size));
        params[0] = static_cast<uint32_t>(dst.origin.x);
        DAWN_UNSAFE_TODO(params[1]) = static_cast<uint32_t>(dst.origin.y);
        DAWN_UNSAFE_TODO(params[2]) = 0;
        DAWN_TRY(paramsBuffer->Unmap());
    }

    Ref<TextureViewBase> srcView;
    {
        TextureViewDescriptor viewDesc = {};
        viewDesc.dimension = textureViewDimension;
        // Array layer in texture views used in bind groups must consist of the entire array.
        viewDesc.baseArrayLayer = 0;
        viewDesc.arrayLayerCount = dataTexture->GetArrayLayers();
        viewDesc.mipLevelCount = 1;
        DAWN_TRY_ASSIGN(srcView, dataTexture->CreateView(&viewDesc));
    }

    // For each layer, blit the stencil data.
    for (uint32_t z = 0; z < copyExtent.depthOrArrayLayers; ++z) {
        if (z >= 1) {
            // Pass the array layer info via the uniform buffer.
            commandEncoder->APIWriteBuffer(paramsBuffer.Get(), sizeof(uint32_t) * 2,
                                           reinterpret_cast<const uint8_t*>(&z), sizeof(uint32_t));
        }

        Ref<TextureViewBase> dstView;
        {
            TextureViewDescriptor viewDesc = {};
            viewDesc.dimension = textureViewDimension;
            viewDesc.baseArrayLayer = static_cast<uint32_t>(dst.origin.z) + z;
            viewDesc.arrayLayerCount = 1;
            viewDesc.baseMipLevel = dst.mipLevel;
            viewDesc.mipLevelCount = 1;
            DAWN_TRY_ASSIGN(dstView, dst.texture->CreateView(&viewDesc));
        }

        Ref<BindGroupBase> bindGroup;
        {
            std::array<BindGroupEntry, 2> bgEntries = {};
            bgEntries[0].binding = 0;
            bgEntries[0].textureView = srcView.Get();
            bgEntries[1].binding = 1;
            bgEntries[1].buffer = paramsBuffer.Get();

            BindGroupDescriptor bgDesc = {};
            bgDesc.layout = bgl.Get();
            bgDesc.entryCount = bgEntries.size();
            bgDesc.entries = bgEntries.data();
            DAWN_TRY_ASSIGN(bindGroup,
                            device->CreateBindGroup(&bgDesc, UsageValidationMode::Internal));
        }

        RenderPassDepthStencilAttachment dsAttachment;
        dsAttachment.depthClearValue = 0.0;
        dsAttachment.view = dstView.Get();
        if (format.HasDepth()) {
            dsAttachment.depthLoadOp = wgpu::LoadOp::Load;
            dsAttachment.depthStoreOp = wgpu::StoreOp::Store;
        }
        dsAttachment.stencilLoadOp = wgpu::LoadOp::Load;
        dsAttachment.stencilStoreOp = wgpu::StoreOp::Store;

        RenderPassDescriptor rpDesc = {};
        rpDesc.depthStencilAttachment = &dsAttachment;

        Ref<RenderPassEncoder> pass = commandEncoder->BeginRenderPass(&rpDesc);
        // Bind the resources.
        pass->APISetBindGroup(0, bindGroup.Get());
        // Discard all fragments outside the copy region.
        pass->APISetScissorRect(static_cast<uint32_t>(dst.origin.x),
                                static_cast<uint32_t>(dst.origin.y), copyExtent.width,
                                copyExtent.height);

        // Clear the copy region to 0.
        pass->APISetStencilReference(0);
        pass->APISetPipeline(pipelines.clearPipeline.Get());
        pass->APIDraw(3, 1, 0, 0);

        // Perform 8 draws. Each will load the source stencil data, and will
        // set the bit index in the destination stencil attachment if it the
        // source also has that bit using stencil operation `Replace`.
        // If it doesn't match, the fragment will be discarded.
        pass->APISetStencilReference(255);
        for (uint32_t bit = 0; bit < 8; ++bit) {
            pass->APISetPipeline(pipelines.setStencilPipelines[bit].Get());
            // Draw one instance, and use the stencil value as firstInstance.
            // This is a cheap way to get the stencil value into the shader
            // since WebGPU doesn't have immediate data.
            pass->APIDraw(3, 1, 0, 1u << bit);
        }
        pass->End();
    }
    return {};
}

MaybeError BlitStagingBufferToDepth(DeviceBase* device,
                                    BufferBase* buffer,
                                    const TexelCopyBufferLayout& src,
                                    const TextureCopy& dst,
                                    const Extent3D& copyExtent) {
    const Format& format = dst.texture->GetFormat();
    DAWN_ASSERT(format.format == wgpu::TextureFormat::Depth16Unorm);

    TextureDescriptor dataTextureDesc = {};
    dataTextureDesc.format = wgpu::TextureFormat::RG8Uint;
    dataTextureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding;
    dataTextureDesc.size = copyExtent;

    Ref<TextureBase> dataTexture;
    DAWN_TRY_ASSIGN(dataTexture, device->CreateTexture(&dataTextureDesc));
    {
        TextureCopy rg8Dst;
        rg8Dst.texture = dataTexture.Get();
        rg8Dst.mipLevel = 0;
        rg8Dst.origin = {};
        rg8Dst.aspect = Aspect::Color;
        DAWN_TRY(device->CopyFromStagingToTexture(buffer, src, rg8Dst, copyExtent));
    }

    Ref<CommandEncoderBase> commandEncoder;
    DAWN_TRY_ASSIGN(commandEncoder, device->CreateCommandEncoder());

    DAWN_TRY(
        BlitRG8ToDepth16Unorm(device, commandEncoder.Get(), dataTexture.Get(), dst, copyExtent));

    Ref<CommandBufferBase> commandBuffer;
    DAWN_TRY_ASSIGN(commandBuffer, commandEncoder->Finish());

    CommandBufferBase* commands = commandBuffer.Get();
    device->GetQueue()->APISubmit(1, &commands);
    return {};
}

MaybeError BlitBufferToDepth(DeviceBase* device,
                             CommandEncoder* commandEncoder,
                             BufferBase* buffer,
                             const TexelCopyBufferLayout& src,
                             const TextureCopy& dst,
                             const Extent3D& copyExtent) {
    const Format& format = dst.texture->GetFormat();
    DAWN_ASSERT(format.format == wgpu::TextureFormat::Depth16Unorm);

    TextureDescriptor dataTextureDesc = {};
    dataTextureDesc.format = wgpu::TextureFormat::RG8Uint;
    dataTextureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding;
    dataTextureDesc.size = copyExtent;

    Ref<TextureBase> dataTexture;
    DAWN_TRY_ASSIGN(dataTexture, device->CreateTexture(&dataTextureDesc));
    {
        TexelCopyBufferInfo bufferSrc;
        bufferSrc.buffer = buffer;
        bufferSrc.layout = src;

        TexelCopyTextureInfo textureDst;
        textureDst.texture = dataTexture.Get();
        commandEncoder->APICopyBufferToTexture(&bufferSrc, &textureDst, &copyExtent);
    }

    DAWN_TRY(BlitRG8ToDepth16Unorm(device, commandEncoder, dataTexture.Get(), dst, copyExtent));
    return {};
}

MaybeError BlitStagingBufferToStencil(DeviceBase* device,
                                      BufferBase* buffer,
                                      const TexelCopyBufferLayout& src,
                                      const TextureCopy& dst,
                                      const Extent3D& copyExtent) {
    TextureDescriptor dataTextureDesc = {};
    dataTextureDesc.format = wgpu::TextureFormat::R8Uint;
    dataTextureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding;
    dataTextureDesc.size = copyExtent;

    Ref<TextureBase> dataTexture;
    DAWN_TRY_ASSIGN(dataTexture, device->CreateTexture(&dataTextureDesc));
    {
        TextureCopy r8Dst;
        r8Dst.texture = dataTexture.Get();
        r8Dst.mipLevel = 0;
        r8Dst.origin = {};
        r8Dst.aspect = Aspect::Color;
        DAWN_TRY(device->CopyFromStagingToTexture(buffer, src, r8Dst, copyExtent));
    }

    Ref<CommandEncoderBase> commandEncoder;
    DAWN_TRY_ASSIGN(commandEncoder, device->CreateCommandEncoder());

    DAWN_TRY(BlitR8ToStencil(device, commandEncoder.Get(), dataTexture.Get(), dst, copyExtent));

    Ref<CommandBufferBase> commandBuffer;
    DAWN_TRY_ASSIGN(commandBuffer, commandEncoder->Finish());

    CommandBufferBase* commands = commandBuffer.Get();
    device->GetQueue()->APISubmit(1, &commands);
    return {};
}

MaybeError BlitBufferToStencil(DeviceBase* device,
                               CommandEncoder* commandEncoder,
                               BufferBase* buffer,
                               const TexelCopyBufferLayout& src,
                               const TextureCopy& dst,
                               const Extent3D& copyExtent) {
    TextureDescriptor dataTextureDesc = {};
    dataTextureDesc.format = wgpu::TextureFormat::R8Uint;
    dataTextureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding;
    dataTextureDesc.size = copyExtent;

    Ref<TextureBase> dataTexture;
    DAWN_TRY_ASSIGN(dataTexture, device->CreateTexture(&dataTextureDesc));
    {
        TexelCopyBufferInfo bufferSrc;
        bufferSrc.buffer = buffer;
        bufferSrc.layout = src;

        TexelCopyTextureInfo textureDst;
        textureDst.texture = dataTexture.Get();
        commandEncoder->APICopyBufferToTexture(&bufferSrc, &textureDst, &copyExtent);
    }

    DAWN_TRY(BlitR8ToStencil(device, commandEncoder, dataTexture.Get(), dst, copyExtent));
    return {};
}

}  // namespace dawn::native
