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

#include "libANGLE/renderer/wgpu/wgpu_utils.h"

#include "common/log_utils.h"
#include "libANGLE/renderer/renderer_utils.h"
#include "libANGLE/renderer/wgpu/ContextWgpu.h"
#include "libANGLE/renderer/wgpu/DisplayWgpu.h"
#include "libANGLE/renderer/wgpu/wgpu_pipeline_state.h"

namespace rx
{

namespace webgpu
{
DisplayWgpu *GetDisplay(const gl::Context *context)
{
    ContextWgpu *contextWgpu = GetImpl(context);
    return contextWgpu->getDisplay();
}

const DawnProcTable *GetProcs(const gl::Context *context)
{
    DisplayWgpu *display = GetDisplay(context);
    return display->getProcs();
}

const DawnProcTable *GetProcs(const ContextWgpu *context)
{
    DisplayWgpu *display = context->getDisplay();
    return display->getProcs();
}

const angle::FeaturesWgpu &GetFeatures(const gl::Context *context)
{
    DisplayWgpu *display = GetDisplay(context);
    return display->getFeatures();
}

const angle::FeaturesWgpu &GetFeatures(const ContextWgpu *context)
{
    DisplayWgpu *display = context->getDisplay();
    return display->getFeatures();
}

webgpu::DeviceHandle GetDevice(const gl::Context *context)
{
    DisplayWgpu *display = GetDisplay(context);
    return display->getDevice();
}

webgpu::InstanceHandle GetInstance(const gl::Context *context)
{
    DisplayWgpu *display = GetDisplay(context);
    return display->getInstance();
}

PackedRenderPassColorAttachment CreateNewClearColorAttachment(const gl::ColorF &clearValue,
                                                              uint32_t depthSlice,
                                                              TextureViewHandle textureView)
{
    PackedRenderPassColorAttachment colorAttachment;
    colorAttachment.view       = textureView;
    colorAttachment.depthSlice = depthSlice;
    colorAttachment.loadOp     = WGPULoadOp_Clear;
    colorAttachment.storeOp    = WGPUStoreOp_Store;
    colorAttachment.clearValue = clearValue;

    return colorAttachment;
}

PackedRenderPassDepthStencilAttachment CreateNewDepthStencilAttachment(
    float depthClearValue,
    uint32_t stencilClearValue,
    TextureViewHandle textureView,
    bool hasDepthValue,
    bool hasStencilValue)
{
    PackedRenderPassDepthStencilAttachment depthStencilAttachment;
    depthStencilAttachment.view = textureView;
    // WebGPU requires that depth/stencil attachments have a load op if the correlated ReadOnly
    // value is set to false, so we make sure to set the value here to to support cases where only a
    // depth or stencil mask is set.
    depthStencilAttachment.depthReadOnly   = !hasDepthValue;
    depthStencilAttachment.stencilReadOnly = !hasStencilValue;
    if (hasDepthValue)
    {
        depthStencilAttachment.depthLoadOp     = WGPULoadOp_Clear;
        depthStencilAttachment.depthStoreOp    = WGPUStoreOp_Store;
        depthStencilAttachment.depthClearValue = depthClearValue;
    }
    if (hasStencilValue)
    {
        depthStencilAttachment.stencilLoadOp     = WGPULoadOp_Clear;
        depthStencilAttachment.stencilStoreOp    = WGPUStoreOp_Store;
        depthStencilAttachment.stencilClearValue = stencilClearValue;
    }

    return depthStencilAttachment;
}

bool IsWgpuError(WGPUWaitStatus waitStatus)
{
    return waitStatus != WGPUWaitStatus_Success;
}

bool IsWgpuError(WGPUMapAsyncStatus mapAsyncStatus)
{
    return mapAsyncStatus != WGPUMapAsyncStatus_Success;
}

ClearValuesArray::ClearValuesArray() : mValues{}, mEnabled{} {}
ClearValuesArray::~ClearValuesArray() = default;

ClearValuesArray::ClearValuesArray(const ClearValuesArray &other)          = default;
ClearValuesArray &ClearValuesArray::operator=(const ClearValuesArray &rhs) = default;

void ClearValuesArray::store(uint32_t index, const ClearValues &clearValues)
{
    mValues[index] = clearValues;
    mEnabled.set(index);
}

gl::DrawBufferMask ClearValuesArray::getColorMask() const
{
    return gl::DrawBufferMask(mEnabled.bits() & kUnpackedColorBuffersMask);
}

bool operator==(const PackedRenderPassColorAttachment &a, const PackedRenderPassColorAttachment &b)
{
    return std::tie(a.view, a.depthSlice, a.loadOp, a.storeOp, a.clearValue) ==
           std::tie(b.view, b.depthSlice, b.loadOp, b.storeOp, b.clearValue);
}

bool operator==(const PackedRenderPassDepthStencilAttachment &a,
                const PackedRenderPassDepthStencilAttachment &b)
{
    return std::tie(a.view, a.depthLoadOp, a.depthStoreOp, a.depthReadOnly, a.depthClearValue,
                    a.stencilLoadOp, a.stencilStoreOp, a.stencilReadOnly, a.stencilClearValue) ==
           std::tie(b.view, b.depthLoadOp, b.depthStoreOp, b.depthReadOnly, b.depthClearValue,
                    b.stencilLoadOp, b.stencilStoreOp, b.stencilReadOnly, b.stencilClearValue);
}

bool operator==(const PackedRenderPassDescriptor &a, const PackedRenderPassDescriptor &b)
{
    return std::tie(a.colorAttachments, a.depthStencilAttachment) ==
           std::tie(b.colorAttachments, b.depthStencilAttachment);
}

bool operator!=(const PackedRenderPassDescriptor &a, const PackedRenderPassDescriptor &b)
{
    return !(a == b);
}

RenderPassEncoderHandle CreateRenderPass(const DawnProcTable *wgpu,
                                         webgpu::CommandEncoderHandle commandEncoder,
                                         const webgpu::PackedRenderPassDescriptor &packedDesc)
{
    WGPURenderPassDescriptor renderPassDesc = WGPU_RENDER_PASS_DESCRIPTOR_INIT;

    angle::FixedVector<WGPURenderPassColorAttachment, gl::IMPLEMENTATION_MAX_DRAW_BUFFERS>
        colorAttachments;
    for (size_t i = 0; i < packedDesc.colorAttachments.size(); i++)
    {
        const webgpu::PackedRenderPassColorAttachment &packedColorAttachment =
            packedDesc.colorAttachments[i];
        WGPURenderPassColorAttachment colorAttachment = WGPU_RENDER_PASS_COLOR_ATTACHMENT_INIT;

        colorAttachment.view          = packedColorAttachment.view.get();
        colorAttachment.depthSlice    = packedColorAttachment.depthSlice;
        colorAttachment.resolveTarget = nullptr;
        colorAttachment.loadOp        = packedColorAttachment.loadOp;
        colorAttachment.storeOp       = packedColorAttachment.storeOp;
        colorAttachment.clearValue    = {
            packedColorAttachment.clearValue.red, packedColorAttachment.clearValue.green,
            packedColorAttachment.clearValue.blue, packedColorAttachment.clearValue.alpha};

        colorAttachments.push_back(colorAttachment);
    }
    renderPassDesc.colorAttachments     = colorAttachments.data();
    renderPassDesc.colorAttachmentCount = colorAttachments.size();

    WGPURenderPassDepthStencilAttachment depthStencilAttachment =
        WGPU_RENDER_PASS_DEPTH_STENCIL_ATTACHMENT_INIT;
    if (packedDesc.depthStencilAttachment.has_value())
    {
        const webgpu::PackedRenderPassDepthStencilAttachment &packedDepthStencilAttachment =
            packedDesc.depthStencilAttachment.value();

        depthStencilAttachment.view              = packedDepthStencilAttachment.view.get();
        depthStencilAttachment.depthLoadOp       = packedDepthStencilAttachment.depthLoadOp;
        depthStencilAttachment.depthStoreOp      = packedDepthStencilAttachment.depthStoreOp;
        depthStencilAttachment.depthReadOnly     = packedDepthStencilAttachment.depthReadOnly;
        depthStencilAttachment.depthClearValue   = packedDepthStencilAttachment.depthClearValue;
        depthStencilAttachment.stencilLoadOp     = packedDepthStencilAttachment.stencilLoadOp;
        depthStencilAttachment.stencilStoreOp    = packedDepthStencilAttachment.stencilStoreOp;
        depthStencilAttachment.stencilReadOnly   = packedDepthStencilAttachment.stencilReadOnly;
        depthStencilAttachment.stencilClearValue = packedDepthStencilAttachment.stencilClearValue;

        renderPassDesc.depthStencilAttachment = &depthStencilAttachment;
    }

    return RenderPassEncoderHandle::Acquire(
        wgpu, wgpu->commandEncoderBeginRenderPass(commandEncoder.get(), &renderPassDesc));
}

void GenerateCaps(const WGPULimits &limitsWgpu,
                  gl::Caps *glCaps,
                  gl::TextureCapsMap *glTextureCapsMap,
                  gl::Extensions *glExtensions,
                  gl::Limitations *glLimitations,
                  egl::Caps *eglCaps,
                  egl::DisplayExtensions *eglExtensions,
                  gl::Version *maxSupportedESVersion)
{
    // WebGPU does not support separate front/back stencil masks.
    glLimitations->noSeparateStencilRefsAndMasks = true;

    // OpenGL ES extensions
    glExtensions->debugMarkerEXT              = true;
    glExtensions->textureUsageANGLE           = true;
    glExtensions->translatedShaderSourceANGLE = true;
    glExtensions->vertexArrayObjectOES        = true;
    glExtensions->elementIndexUintOES         = true;

    glExtensions->textureStorageEXT = true;
    glExtensions->rgb8Rgba8OES      = true;

    glExtensions->EGLImageOES                  = true;
    glExtensions->EGLImageExternalOES          = true;
    glExtensions->EGLImageExternalEssl3OES     = true;
    glExtensions->EGLImageExternalWrapModesEXT = true;
    glExtensions->requiredInternalformatOES    = true;

    // OpenGL ES caps
    glCaps->maxElementIndex       = std::numeric_limits<GLuint>::max() - 1;
    glCaps->max3DTextureSize      = rx::LimitToInt(limitsWgpu.maxTextureDimension3D);
    glCaps->max2DTextureSize      = rx::LimitToInt(limitsWgpu.maxTextureDimension2D);
    glCaps->maxArrayTextureLayers = rx::LimitToInt(limitsWgpu.maxTextureArrayLayers);
    glCaps->maxLODBias            = 0.0f;
    glCaps->maxCubeMapTextureSize = rx::LimitToInt(limitsWgpu.maxTextureDimension2D);
    glCaps->maxRenderbufferSize   = rx::LimitToInt(limitsWgpu.maxTextureDimension2D);
    glCaps->minAliasedPointSize   = 1.0f;
    glCaps->maxAliasedPointSize   = 1.0f;
    glCaps->minAliasedLineWidth   = 1.0f;
    glCaps->maxAliasedLineWidth   = 1.0f;

    // "descriptor.sampleCount must be either 1 or 4."
    constexpr uint32_t kMaxSampleCount = 4;

    glCaps->maxDrawBuffers         = rx::LimitToInt(limitsWgpu.maxColorAttachments);
    glCaps->maxFramebufferWidth    = rx::LimitToInt(limitsWgpu.maxTextureDimension2D);
    glCaps->maxFramebufferHeight   = rx::LimitToInt(limitsWgpu.maxTextureDimension2D);
    glCaps->maxFramebufferSamples  = kMaxSampleCount;
    glCaps->maxColorAttachments    = rx::LimitToInt(limitsWgpu.maxColorAttachments);
    glCaps->maxViewportWidth       = rx::LimitToInt(limitsWgpu.maxTextureDimension2D);
    glCaps->maxViewportHeight      = glCaps->maxViewportWidth;
    glCaps->maxSampleMaskWords     = 1;
    glCaps->maxColorTextureSamples = kMaxSampleCount;
    glCaps->maxDepthTextureSamples = kMaxSampleCount;
    glCaps->maxIntegerSamples      = kMaxSampleCount;
    glCaps->maxServerWaitTimeout   = 0;

    glCaps->maxVertexAttribRelativeOffset = (1u << kAttributeOffsetMaxBits) - 1;
    glCaps->maxVertexAttribBindings =
        rx::LimitToInt(std::min(limitsWgpu.maxVertexBuffers, limitsWgpu.maxVertexAttributes));
    glCaps->maxVertexAttribStride =
        rx::LimitToInt(std::min(limitsWgpu.maxVertexBufferArrayStride,
                                static_cast<uint32_t>(std::numeric_limits<uint16_t>::max())));
    glCaps->maxElementsIndices  = std::numeric_limits<GLint>::max();
    glCaps->maxElementsVertices = std::numeric_limits<GLint>::max();
    glCaps->vertexHighpFloat.setIEEEFloat();
    glCaps->vertexMediumpFloat.setIEEEHalfFloat();
    glCaps->vertexLowpFloat.setIEEEHalfFloat();
    glCaps->fragmentHighpFloat.setIEEEFloat();
    glCaps->fragmentMediumpFloat.setIEEEHalfFloat();
    glCaps->fragmentLowpFloat.setIEEEHalfFloat();
    glCaps->vertexHighpInt.setTwosComplementInt(32);
    glCaps->vertexMediumpInt.setTwosComplementInt(16);
    glCaps->vertexLowpInt.setTwosComplementInt(16);
    glCaps->fragmentHighpInt.setTwosComplementInt(32);
    glCaps->fragmentMediumpInt.setTwosComplementInt(16);
    glCaps->fragmentLowpInt.setTwosComplementInt(16);

    // Clamp the maxUniformBlockSize to 64KB (majority of devices support up to this size
    // currently), on AMD the maxUniformBufferRange is near uint32_t max.
    GLuint maxUniformBlockSize = static_cast<GLuint>(
        std::min(static_cast<uint64_t>(0x10000), limitsWgpu.maxUniformBufferBindingSize));

    const GLuint maxUniformVectors    = maxUniformBlockSize / (sizeof(GLfloat) * 4);
    const GLuint maxUniformComponents = maxUniformVectors * 4;

    const int32_t maxPerStageUniformBuffers = rx::LimitToInt(
        limitsWgpu.maxUniformBuffersPerShaderStage - kReservedPerStageDefaultUniformSlotCount);

    // There is no additional limit to the combined number of components.  We can have up to a
    // maximum number of uniform buffers, each having the maximum number of components.  Note that
    // this limit includes both components in and out of uniform buffers.
    //
    // This value is limited to INT_MAX to avoid overflow when queried from glGetIntegerv().
    const uint64_t maxCombinedUniformComponents =
        std::min<uint64_t>(static_cast<uint64_t>(maxPerStageUniformBuffers +
                                                 kReservedPerStageDefaultUniformSlotCount) *
                               maxUniformComponents,
                           std::numeric_limits<GLint>::max());

    for (gl::ShaderType shaderType : gl::AllShaderTypes())
    {
        glCaps->maxShaderUniformBlocks[shaderType] = maxPerStageUniformBuffers;
        glCaps->maxShaderTextureImageUnits[shaderType] =
            rx::LimitToInt(limitsWgpu.maxSamplersPerShaderStage);
        glCaps->maxShaderStorageBlocks[shaderType]             = 0;
        glCaps->maxShaderUniformComponents[shaderType]         = 0;
        glCaps->maxShaderAtomicCounterBuffers[shaderType]      = 0;
        glCaps->maxShaderAtomicCounters[shaderType]            = 0;
        glCaps->maxShaderImageUniforms[shaderType]             = 0;
        glCaps->maxCombinedShaderUniformComponents[shaderType] = maxCombinedUniformComponents;
    }

    const GLint maxVaryingComponents = rx::LimitToInt(limitsWgpu.maxInterStageShaderVariables * 4);

    glCaps->maxVertexAttributes = rx::LimitToInt(
        limitsWgpu.maxVertexBuffers);  // WebGPU has maxVertexBuffers and maxVertexAttributes but
                                       // since each vertex attribute can use a unique buffer, we
                                       // are limited by the total number of vertex buffers
    glCaps->maxVertexUniformVectors =
        maxUniformVectors;  // Uniforms are implemented using a uniform buffer, so the max number of
                            // uniforms we can support is the max buffer range divided by the size
                            // of a single uniform (4X float).
    glCaps->maxVertexOutputComponents = maxVaryingComponents;

    glCaps->maxFragmentUniformVectors     = maxUniformVectors;
    glCaps->maxFragmentInputComponents    = maxVaryingComponents;
    glCaps->minProgramTextureGatherOffset = 0;
    glCaps->maxProgramTextureGatherOffset = 0;
    glCaps->minProgramTexelOffset         = -8;
    glCaps->maxProgramTexelOffset         = 7;

    glCaps->maxComputeWorkGroupCount       = {0, 0, 0};
    glCaps->maxComputeWorkGroupSize        = {0, 0, 0};
    glCaps->maxComputeWorkGroupInvocations = 0;
    glCaps->maxComputeSharedMemorySize     = 0;

    // Only 2 stages (vertex+fragment) are supported.
    constexpr uint32_t kShaderStageCount = 2;

    glCaps->maxUniformBufferBindings = maxPerStageUniformBuffers * kShaderStageCount;
    glCaps->maxUniformBlockSize      = rx::LimitToInt(limitsWgpu.maxBufferSize);
    glCaps->uniformBufferOffsetAlignment =
        rx::LimitToInt(limitsWgpu.minUniformBufferOffsetAlignment);
    glCaps->maxCombinedUniformBlocks = glCaps->maxUniformBufferBindings;
    glCaps->maxVaryingComponents     = maxVaryingComponents;
    glCaps->maxVaryingVectors        = rx::LimitToInt(limitsWgpu.maxInterStageShaderVariables);
    glCaps->maxCombinedTextureImageUnits =
        rx::LimitToInt(limitsWgpu.maxSamplersPerShaderStage * kShaderStageCount);
    glCaps->maxCombinedShaderOutputResources = 0;

    glCaps->maxUniformLocations                = maxUniformVectors;
    glCaps->maxAtomicCounterBufferBindings     = 0;
    glCaps->maxAtomicCounterBufferSize         = 0;
    glCaps->maxCombinedAtomicCounterBuffers    = 0;
    glCaps->maxCombinedAtomicCounters          = 0;
    glCaps->maxImageUnits                      = 0;
    glCaps->maxCombinedImageUniforms           = 0;
    glCaps->maxShaderStorageBufferBindings     = 0;
    glCaps->maxShaderStorageBlockSize          = 0;
    glCaps->maxCombinedShaderStorageBlocks     = 0;
    glCaps->shaderStorageBufferOffsetAlignment = 0;

    glCaps->maxTransformFeedbackInterleavedComponents = 0;
    glCaps->maxTransformFeedbackSeparateAttributes    = 0;
    glCaps->maxTransformFeedbackSeparateComponents    = 0;

    glCaps->lineWidthGranularity    = 0.0f;
    glCaps->minMultisampleLineWidth = 0.0f;
    glCaps->maxMultisampleLineWidth = 0.0f;

    glCaps->maxTextureBufferSize         = 0;
    glCaps->textureBufferOffsetAlignment = 0;

    glCaps->maxSamples = kMaxSampleCount;

    // Max version
    *maxSupportedESVersion = gl::Version(3, 2);

    // OpenGL ES texture caps
    InitMinimumTextureCapsMap(*maxSupportedESVersion, *glExtensions, glTextureCapsMap);

    // EGL caps
    eglCaps->textureNPOT = true;

    // EGL extensions
    eglExtensions->createContextRobustness            = true;
    eglExtensions->postSubBuffer                      = true;
    eglExtensions->createContext                      = true;
    eglExtensions->image                              = true;
    eglExtensions->imageBase                          = true;
    eglExtensions->glTexture2DImage                   = true;
    eglExtensions->glRenderbufferImage                = true;
    eglExtensions->getAllProcAddresses                = true;
    eglExtensions->noConfigContext                    = true;
    eglExtensions->createContextNoError               = true;
    eglExtensions->createContextWebGLCompatibility    = true;
    eglExtensions->createContextBindGeneratesResource = true;
    eglExtensions->pixelFormatFloat                   = true;
    eglExtensions->surfacelessContext                 = true;
    eglExtensions->displayTextureShareGroup           = true;
    eglExtensions->displaySemaphoreShareGroup         = true;
    eglExtensions->createContextClientArrays          = true;
    eglExtensions->programCacheControlANGLE           = true;
    eglExtensions->robustResourceInitializationANGLE  = true;
    eglExtensions->webgpuTextureClientBuffer          = true;
}

bool IsStripPrimitiveTopology(WGPUPrimitiveTopology topology)
{
    switch (topology)
    {
        case WGPUPrimitiveTopology_LineStrip:
        case WGPUPrimitiveTopology_TriangleStrip:
            return true;

        default:
            return false;
    }
}

ErrorScope::ErrorScope(const DawnProcTable *procTable,
                       webgpu::InstanceHandle instance,
                       webgpu::DeviceHandle device,
                       WGPUErrorFilter errorType)
    : mProcTable(procTable), mInstance(instance), mDevice(device)
{
    mProcTable->devicePushErrorScope(mDevice.get(), errorType);
    mActive = true;
}

ErrorScope::~ErrorScope()
{
    ANGLE_UNUSED_VARIABLE(PopScope(nullptr, nullptr, nullptr, 0));
}

angle::Result ErrorScope::PopScope(ContextWgpu *context,
                                   const char *file,
                                   const char *function,
                                   unsigned int line)
{
    if (!mActive)
    {
        return angle::Result::Continue;
    }
    mActive = false;

    struct PopScopeContext
    {
        ContextWgpu *context = nullptr;
        const char *file     = nullptr;
        const char *function = nullptr;
        unsigned int line    = 0;
        bool hadError        = false;
    };

    WGPUPopErrorScopeCallbackInfo callbackInfo = WGPU_POP_ERROR_SCOPE_CALLBACK_INFO_INIT;
    callbackInfo.mode                          = WGPUCallbackMode_AllowSpontaneous;
    callbackInfo.callback = [](WGPUPopErrorScopeStatus status, WGPUErrorType type,
                               struct WGPUStringView message, void *userdata1, void *userdata2) {
        PopScopeContext *ctx = reinterpret_cast<PopScopeContext *>(userdata1);
        ASSERT(userdata2 == nullptr);

        if (type == WGPUErrorType_NoError)
        {
            return;
        }

        if (ctx)
        {
            ASSERT(ctx->file);
            ASSERT(ctx->function);
            std::string msgStr(message.data, message.length);
            ctx->context->handleError(GL_INVALID_OPERATION, msgStr.c_str(), ctx->file,
                                      ctx->function, ctx->line);
            ctx->hadError = true;
        }
        else
        {
            ERR() << "Unhandled WebGPU error: " << std::string(message.data, message.length);
        }
    };

    const angle::FeaturesWgpu &features = GetFeatures(context);
    if (features.avoidWaitAny.enabled)
    {
        // End the error scope but don't wait on it. The error messages will be printed later.
        mProcTable->devicePopErrorScope(mDevice.get(), callbackInfo);
        return angle::Result::Continue;
    }
    else
    {
        PopScopeContext popScopeContext{context, file, function, line, false};
        callbackInfo.userdata1 = &popScopeContext;

        WGPUFutureWaitInfo future = WGPU_FUTURE_WAIT_INFO_INIT;
        future.future             = mProcTable->devicePopErrorScope(mDevice.get(), callbackInfo);
        mProcTable->instanceWaitAny(mInstance.get(), 1, &future, -1);

        return popScopeContext.hadError ? angle::Result::Stop : angle::Result::Continue;
    }
}

}  // namespace webgpu

namespace wgpu_gl
{
gl::LevelIndex GetLevelIndex(webgpu::LevelIndex levelWgpu, gl::LevelIndex baseLevel)
{
    return gl::LevelIndex(levelWgpu.get() + baseLevel.get());
}

gl::Extents GetExtents(WGPUExtent3D wgpuExtent)
{
    gl::Extents glExtent;
    glExtent.width  = wgpuExtent.width;
    glExtent.height = wgpuExtent.height;
    glExtent.depth  = wgpuExtent.depthOrArrayLayers;
    return glExtent;
}
}  // namespace wgpu_gl

namespace gl_wgpu
{
webgpu::LevelIndex getLevelIndex(gl::LevelIndex levelGl, gl::LevelIndex baseLevel)
{
    ASSERT(baseLevel <= levelGl);
    return webgpu::LevelIndex(levelGl.get() - baseLevel.get());
}

WGPUExtent3D GetExtent3D(const gl::Extents &glExtent)
{
    WGPUExtent3D wgpuExtent       = WGPU_EXTENT_3D_INIT;
    wgpuExtent.width              = glExtent.width;
    wgpuExtent.height             = glExtent.height;
    wgpuExtent.depthOrArrayLayers = glExtent.depth;
    return wgpuExtent;
}

WGPUPrimitiveTopology GetPrimitiveTopology(gl::PrimitiveMode mode)
{
    switch (mode)
    {
        case gl::PrimitiveMode::Points:
            return WGPUPrimitiveTopology_PointList;
        case gl::PrimitiveMode::Lines:
            return WGPUPrimitiveTopology_LineList;
        case gl::PrimitiveMode::LineLoop:
            return WGPUPrimitiveTopology_LineStrip;  // Emulated
        case gl::PrimitiveMode::LineStrip:
            return WGPUPrimitiveTopology_LineStrip;
        case gl::PrimitiveMode::Triangles:
            return WGPUPrimitiveTopology_TriangleList;
        case gl::PrimitiveMode::TriangleStrip:
            return WGPUPrimitiveTopology_TriangleStrip;
        case gl::PrimitiveMode::TriangleFan:
            return WGPUPrimitiveTopology_TriangleList;  // Emulated
        default:
            UNREACHABLE();
            return WGPUPrimitiveTopology_Undefined;
    }
}

WGPUIndexFormat GetIndexFormat(gl::DrawElementsType drawElementsType)
{
    switch (drawElementsType)
    {
        case gl::DrawElementsType::UnsignedByte:
            return WGPUIndexFormat_Uint16;  // Emulated
        case gl::DrawElementsType::UnsignedShort:
            return WGPUIndexFormat_Uint16;
        case gl::DrawElementsType::UnsignedInt:
            return WGPUIndexFormat_Uint32;

        default:
            UNREACHABLE();
            return WGPUIndexFormat_Undefined;
    }
}

WGPUFrontFace GetFrontFace(GLenum frontFace)
{
    switch (frontFace)
    {
        case GL_CW:
            return WGPUFrontFace_CW;
        case GL_CCW:
            return WGPUFrontFace_CCW;

        default:
            UNREACHABLE();
            return WGPUFrontFace_Undefined;
    }
}

WGPUCullMode GetCullMode(gl::CullFaceMode mode, bool cullFaceEnabled)
{
    if (!cullFaceEnabled)
    {
        return WGPUCullMode_None;
    }

    switch (mode)
    {
        case gl::CullFaceMode::Front:
            return WGPUCullMode_Front;
        case gl::CullFaceMode::Back:
            return WGPUCullMode_Back;
        case gl::CullFaceMode::FrontAndBack:
            UNIMPLEMENTED();
            return WGPUCullMode_None;  // Emulated
        default:
            UNREACHABLE();
            return WGPUCullMode_None;
    }
}

WGPUColorWriteMask GetColorWriteMask(bool r, bool g, bool b, bool a)
{
    return (r ? WGPUColorWriteMask_Red : WGPUColorWriteMask_None) |
           (g ? WGPUColorWriteMask_Green : WGPUColorWriteMask_None) |
           (b ? WGPUColorWriteMask_Blue : WGPUColorWriteMask_None) |
           (a ? WGPUColorWriteMask_Alpha : WGPUColorWriteMask_None);
}

WGPUBlendFactor GetBlendFactor(gl::BlendFactorType blendFactor)
{
    switch (blendFactor)
    {
        case gl::BlendFactorType::Zero:
            return WGPUBlendFactor_Zero;

        case gl::BlendFactorType::One:
            return WGPUBlendFactor_One;

        case gl::BlendFactorType::SrcColor:
            return WGPUBlendFactor_Src;

        case gl::BlendFactorType::OneMinusSrcColor:
            return WGPUBlendFactor_OneMinusSrc;

        case gl::BlendFactorType::SrcAlpha:
            return WGPUBlendFactor_SrcAlpha;

        case gl::BlendFactorType::OneMinusSrcAlpha:
            return WGPUBlendFactor_OneMinusSrcAlpha;

        case gl::BlendFactorType::DstAlpha:
            return WGPUBlendFactor_DstAlpha;

        case gl::BlendFactorType::OneMinusDstAlpha:
            return WGPUBlendFactor_OneMinusDstAlpha;

        case gl::BlendFactorType::DstColor:
            return WGPUBlendFactor_Dst;

        case gl::BlendFactorType::OneMinusDstColor:
            return WGPUBlendFactor_OneMinusDst;

        case gl::BlendFactorType::SrcAlphaSaturate:
            return WGPUBlendFactor_SrcAlphaSaturated;

        case gl::BlendFactorType::ConstantColor:
            return WGPUBlendFactor_Constant;

        case gl::BlendFactorType::OneMinusConstantColor:
            return WGPUBlendFactor_OneMinusConstant;

        case gl::BlendFactorType::ConstantAlpha:
            UNIMPLEMENTED();
            return WGPUBlendFactor_Undefined;

        case gl::BlendFactorType::OneMinusConstantAlpha:
            UNIMPLEMENTED();
            return WGPUBlendFactor_Undefined;

        case gl::BlendFactorType::Src1Alpha:
            return WGPUBlendFactor_Src1Alpha;

        case gl::BlendFactorType::Src1Color:
            return WGPUBlendFactor_Src1;

        case gl::BlendFactorType::OneMinusSrc1Color:
            return WGPUBlendFactor_OneMinusSrc1;

        case gl::BlendFactorType::OneMinusSrc1Alpha:
            return WGPUBlendFactor_OneMinusSrc1Alpha;

        default:
            UNREACHABLE();
            return WGPUBlendFactor_Undefined;
    }
}

WGPUBlendOperation GetBlendEquation(gl::BlendEquationType blendEquation)
{
    switch (blendEquation)
    {
        case gl::BlendEquationType::Add:
            return WGPUBlendOperation_Add;

        case gl::BlendEquationType::Min:
            return WGPUBlendOperation_Min;

        case gl::BlendEquationType::Max:
            return WGPUBlendOperation_Max;

        case gl::BlendEquationType::Subtract:
            return WGPUBlendOperation_Subtract;

        case gl::BlendEquationType::ReverseSubtract:
            return WGPUBlendOperation_ReverseSubtract;

        case gl::BlendEquationType::Multiply:
        case gl::BlendEquationType::Screen:
        case gl::BlendEquationType::Overlay:
        case gl::BlendEquationType::Darken:
        case gl::BlendEquationType::Lighten:
        case gl::BlendEquationType::Colordodge:
        case gl::BlendEquationType::Colorburn:
        case gl::BlendEquationType::Hardlight:
        case gl::BlendEquationType::Softlight:
        case gl::BlendEquationType::Unused2:
        case gl::BlendEquationType::Difference:
        case gl::BlendEquationType::Unused3:
        case gl::BlendEquationType::Exclusion:
        case gl::BlendEquationType::HslHue:
        case gl::BlendEquationType::HslSaturation:
        case gl::BlendEquationType::HslColor:
        case gl::BlendEquationType::HslLuminosity:
            // EXT_blend_equation_advanced
            UNIMPLEMENTED();
            return WGPUBlendOperation_Undefined;

        default:
            UNREACHABLE();
            return WGPUBlendOperation_Undefined;
    }
}

WGPUTextureViewDimension GetWgpuTextureViewDimension(gl::TextureType textureType)
{
    switch (textureType)
    {
        case gl::TextureType::_2D:
        case gl::TextureType::_2DMultisample:
            return WGPUTextureViewDimension_2D;
        case gl::TextureType::_2DArray:
        case gl::TextureType::_2DMultisampleArray:
            return WGPUTextureViewDimension_2DArray;
        case gl::TextureType::_3D:
            return WGPUTextureViewDimension_3D;
        case gl::TextureType::CubeMap:
            return WGPUTextureViewDimension_Cube;
        case gl::TextureType::CubeMapArray:
            return WGPUTextureViewDimension_CubeArray;
        default:
            UNIMPLEMENTED();
            return WGPUTextureViewDimension_Undefined;
    }
}

WGPUTextureDimension GetWgpuTextureDimension(gl::TextureType glTextureType)
{
    switch (glTextureType)
    {
        // See https://www.w3.org/TR/webgpu/#dom-gputexture-createview.
        case gl::TextureType::_2D:
        case gl::TextureType::_2DArray:
        case gl::TextureType::_2DMultisample:
        case gl::TextureType::_2DMultisampleArray:
        case gl::TextureType::CubeMap:
        case gl::TextureType::CubeMapArray:
        case gl::TextureType::Rectangle:
        case gl::TextureType::External:
        case gl::TextureType::Buffer:
            return WGPUTextureDimension_2D;
        case gl::TextureType::_3D:
        case gl::TextureType::VideoImage:
            return WGPUTextureDimension_3D;
        default:
            UNREACHABLE();
            return WGPUTextureDimension_Undefined;
    }
}

WGPUCompareFunction GetCompareFunc(const GLenum glCompareFunc, bool testEnabled)
{
    if (!testEnabled)
    {
        return WGPUCompareFunction_Always;
    }

    switch (glCompareFunc)
    {
        case GL_NEVER:
            return WGPUCompareFunction_Never;
        case GL_LESS:
            return WGPUCompareFunction_Less;
        case GL_EQUAL:
            return WGPUCompareFunction_Equal;
        case GL_LEQUAL:
            return WGPUCompareFunction_LessEqual;
        case GL_GREATER:
            return WGPUCompareFunction_Greater;
        case GL_NOTEQUAL:
            return WGPUCompareFunction_NotEqual;
        case GL_GEQUAL:
            return WGPUCompareFunction_GreaterEqual;
        case GL_ALWAYS:
            return WGPUCompareFunction_Always;
        default:
            UNREACHABLE();
            return WGPUCompareFunction_Always;
    }
}

WGPUTextureSampleType GetTextureSampleType(gl::SamplerFormat samplerFormat)
{
    switch (samplerFormat)
    {
        case gl::SamplerFormat::Float:
            return WGPUTextureSampleType_Float;
        case gl::SamplerFormat::Unsigned:
            return WGPUTextureSampleType_Uint;
        case gl::SamplerFormat::Signed:
            return WGPUTextureSampleType_Sint;
        case gl::SamplerFormat::Shadow:
            return WGPUTextureSampleType_Depth;
        default:
            UNIMPLEMENTED();
            return WGPUTextureSampleType_Undefined;
    }
}

WGPUStencilOperation GetStencilOp(const GLenum glStencilOp)
{
    switch (glStencilOp)
    {
        case GL_KEEP:
            return WGPUStencilOperation_Keep;
        case GL_ZERO:
            return WGPUStencilOperation_Zero;
        case GL_REPLACE:
            return WGPUStencilOperation_Replace;
        case GL_INCR:
            return WGPUStencilOperation_IncrementClamp;
        case GL_DECR:
            return WGPUStencilOperation_DecrementClamp;
        case GL_INCR_WRAP:
            return WGPUStencilOperation_IncrementWrap;
        case GL_DECR_WRAP:
            return WGPUStencilOperation_DecrementWrap;
        case GL_INVERT:
            return WGPUStencilOperation_Invert;
        default:
            UNREACHABLE();
            return WGPUStencilOperation_Keep;
    }
}

WGPUFilterMode GetFilter(const GLenum filter)
{
    switch (filter)
    {
        case GL_LINEAR_MIPMAP_LINEAR:
        case GL_LINEAR_MIPMAP_NEAREST:
        case GL_LINEAR:
            return WGPUFilterMode_Linear;
        case GL_NEAREST_MIPMAP_LINEAR:
        case GL_NEAREST_MIPMAP_NEAREST:
        case GL_NEAREST:
            return WGPUFilterMode_Nearest;
        default:
            UNREACHABLE();
            return WGPUFilterMode_Undefined;
    }
}

WGPUMipmapFilterMode GetSamplerMipmapMode(const GLenum filter)
{
    switch (filter)
    {
        case GL_LINEAR_MIPMAP_LINEAR:
        case GL_NEAREST_MIPMAP_LINEAR:
            return WGPUMipmapFilterMode_Linear;
        // GL_LINEAR and GL_NEAREST do not map directly to WebGPU but can be easily emulated,
        // see below.
        case GL_LINEAR:
        case GL_NEAREST:
        case GL_NEAREST_MIPMAP_NEAREST:
        case GL_LINEAR_MIPMAP_NEAREST:
            return WGPUMipmapFilterMode_Nearest;
        default:
            UNREACHABLE();
            return WGPUMipmapFilterMode_Undefined;
    }
}

WGPUAddressMode GetSamplerAddressMode(const GLenum wrap)
{
    switch (wrap)
    {
        case GL_REPEAT:
            return WGPUAddressMode_Repeat;
        case GL_MIRRORED_REPEAT:
            return WGPUAddressMode_MirrorRepeat;
        case GL_CLAMP_TO_BORDER:
            // Not in WebGPU and not available in ES 3.0 or before.
            UNIMPLEMENTED();
            return WGPUAddressMode_ClampToEdge;
        case GL_CLAMP_TO_EDGE:
            return WGPUAddressMode_ClampToEdge;
        case GL_MIRROR_CLAMP_TO_EDGE_EXT:
            // Not in WebGPU and not available in ES 3.0 or before.
            return WGPUAddressMode_ClampToEdge;
        default:
            UNREACHABLE();
            return WGPUAddressMode_Undefined;
    }
}

WGPUCompareFunction GetSamplerCompareFunc(const gl::SamplerState *samplerState)
{
    if (samplerState->getCompareMode() != GL_COMPARE_REF_TO_TEXTURE)
    {
        return WGPUCompareFunction_Undefined;
    }

    return GetCompareFunc(samplerState->getCompareFunc(), /*testEnabled=*/true);
}

WGPUSamplerDescriptor GetWgpuSamplerDesc(const gl::SamplerState *samplerState)
{
    WGPUMipmapFilterMode wgpuMipmapFilterMode = GetSamplerMipmapMode(samplerState->getMinFilter());
    // Negative values don't seem to make a difference to the behavior of GLES, a min lod of 0.0
    // functions the same.
    float wgpuLodMinClamp =
        gl::clamp(samplerState->getMinLod(), webgpu::kWGPUMinLod, webgpu::kWGPUMaxLod);
    float wgpuLodMaxClamp =
        gl::clamp(samplerState->getMaxLod(), webgpu::kWGPUMinLod, webgpu::kWGPUMaxLod);

    if (!gl::IsMipmapFiltered(samplerState->getMinFilter()))
    {
        // Similarly to Vulkan, GL_NEAREST and GL_LINEAR do not map directly to WGPU, so
        // they must be emulated (See "Mapping of OpenGL to Vulkan filter modes")
        wgpuMipmapFilterMode = WGPUMipmapFilterMode_Nearest;
        wgpuLodMinClamp      = 0.0f;
        wgpuLodMaxClamp      = 0.25f;
    }

    WGPUSamplerDescriptor samplerDesc = WGPU_SAMPLER_DESCRIPTOR_INIT;
    samplerDesc.addressModeU = GetSamplerAddressMode(samplerState->getWrapS());
    samplerDesc.addressModeV = GetSamplerAddressMode(samplerState->getWrapT());
    samplerDesc.addressModeW = GetSamplerAddressMode(samplerState->getWrapR());
    samplerDesc.magFilter    = GetFilter(samplerState->getMagFilter());
    samplerDesc.minFilter    = GetFilter(samplerState->getMinFilter());
    samplerDesc.mipmapFilter = wgpuMipmapFilterMode;
    samplerDesc.lodMinClamp  = wgpuLodMinClamp;
    samplerDesc.lodMaxClamp  = wgpuLodMaxClamp;
    samplerDesc.compare      = GetSamplerCompareFunc(samplerState);
    // TODO(anglebug.com/389145696): there's no way to get the supported maxAnisotropy value from
    // WGPU, so there's no way to communicate to the GL client whether anisotropy is even supported
    // as an extension, let alone what the max value is.
    samplerDesc.maxAnisotropy = static_cast<uint16_t>(std::floor(samplerState->getMaxAnisotropy()));

    return samplerDesc;
}

uint32_t GetFirstIndexForDrawCall(gl::DrawElementsType indexType, const void *indices)
{
    const size_t indexSize                = gl::GetDrawElementsTypeSize(indexType);
    const uintptr_t indexBufferByteOffset = reinterpret_cast<uintptr_t>(indices);
    if (indexBufferByteOffset % indexSize != 0)
    {
        // WebGPU only allows offsetting index buffers by multiples of the index size
        UNIMPLEMENTED();
    }

    return static_cast<uint32_t>(indexBufferByteOffset / indexSize);
}

}  // namespace gl_wgpu
}  // namespace rx
