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

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

#include <algorithm>
#include <memory>
#include <string>
#include <string_view>
#include <utility>

#include "dawn/platform/DawnPlatform.h"
#include "src/dawn/common/GPUInfo.h"
#include "src/dawn/native/ChainUtils.h"
#include "src/dawn/native/Instance.h"
#include "src/dawn/native/opengl/ContextEGL.h"
#include "src/dawn/native/opengl/DeviceGL.h"
#include "src/dawn/native/opengl/DisplayEGL.h"
#include "src/dawn/native/opengl/SwapChainEGL.h"
#include "src/dawn/native/opengl/UtilsGL.h"
#include "src/utils/compiler.h"

namespace dawn::native::opengl {

namespace {

struct Vendor {
    const char* vendorName;
    uint32_t vendorId;
};

const Vendor kVendors[] = {{"ATI", gpu_info::kVendorID_AMD},
                           {"ARM", gpu_info::kVendorID_ARM},
                           {"Imagination", gpu_info::kVendorID_ImgTec},
                           {"Intel", gpu_info::kVendorID_Intel},
                           {"NVIDIA", gpu_info::kVendorID_Nvidia},
                           {"Qualcomm", gpu_info::kVendorID_QualcommPCI}};

uint32_t GetVendorIdFromVendors(const char* vendor) {
    uint32_t vendorId = 0;
    for (const auto& it : kVendors) {
        // Matching vendor name with vendor string
        if (DAWN_UNSAFE_TODO(strstr(vendor, it.vendorName)) != nullptr) {
            vendorId = it.vendorId;
            break;
        }
    }
    return vendorId;
}

uint32_t GetDeviceIdFromRender(std::string_view render) {
    uint32_t deviceId = 0;
    size_t pos = render.find("(0x");
    if (pos == std::string_view::npos) {
        pos = render.find("(0X");
    }
    if (pos == std::string_view::npos) {
        return deviceId;
    }
    render.remove_prefix(pos + 3);

    // The first character after the prefix must be hexadecimal, otherwise an invalid argument
    // exception is thrown.
    if (!render.empty() && std::isxdigit(static_cast<unsigned char>(*render.data()))) {
        deviceId = static_cast<uint32_t>(std::stoul(render.data(), nullptr, 16));
    }

    return deviceId;
}

bool IsANGLEDesktopGL(std::string_view renderer) {
    return renderer.find("ANGLE") != std::string::npos &&
           renderer.find("OpenGL") != std::string::npos &&
           renderer.find("OpenGL ES") == std::string::npos;
}

bool IsSwiftShader(std::string_view renderer) {
    return renderer.find("SwiftShader") != std::string::npos;
}

}  // anonymous namespace

// static
ResultOrError<Ref<PhysicalDevice>> PhysicalDevice::Create(wgpu::BackendType backendType,
                                                          Ref<DisplayEGL> display,
                                                          bool forceES31AndMinExtensions,
                                                          EGLint angleVirtualizationGroup) {
    // Create a temporary context and make it current during the creation of the PhysicalDevice so
    // that we can query the limits and other properties. Assumes that the limit are the same
    // irrespective of the context creation options.
    std::unique_ptr<ContextEGL> context;
    DAWN_TRY_ASSIGN(
        context, ContextEGL::Create(display, backendType, /*useRobustness*/ false,
                                    /*disableEGL15Robustness */ false,
                                    /*useANGLETextureSharing*/ false,
                                    /*forceES31AndMinExtensions*/ forceES31AndMinExtensions,
                                    /*bindContextOnlyDuringUse*/ true, angleVirtualizationGroup));

    ContextEGL::ScopedMakeCurrent scopedCurrentContext;
    DAWN_TRY_ASSIGN(scopedCurrentContext, context->MakeCurrent());
    // Needed to request extensions here to initialize supported gl extensions set
    context->RequestRequiredExtensionsExplicitly();

    Ref<PhysicalDevice> physicalDevice =
        AcquireRef(new PhysicalDevice(backendType, std::move(display), angleVirtualizationGroup));
    DAWN_TRY(physicalDevice->Initialize());

    DAWN_TRY(scopedCurrentContext.End());

    return physicalDevice;
}

PhysicalDevice::PhysicalDevice(wgpu::BackendType backendType,
                               Ref<DisplayEGL> display,
                               EGLint angleVirtualizationGroup)
    : PhysicalDeviceBase(backendType),
      mDisplay(std::move(display)),
      mAngleVirtualizationGroup(angleVirtualizationGroup) {}

DisplayEGL* PhysicalDevice::GetDisplay() const {
    return mDisplay.Get();
}

bool PhysicalDevice::SupportsExternalImages() const {
    // Via dawn::native::opengl::WrapExternalEGLImage
    return GetBackendType() == wgpu::BackendType::OpenGLES;
}

MaybeError PhysicalDevice::InitializeImpl() {
    DAWN_TRY(mFunctions.Initialize(mDisplay->egl->GetProcAddress));

    // In some cases (like like of EGL_KHR_create_context) we don't know before this point that we
    // got a GL context that supports the required version. Check it now.
    switch (GetBackendType()) {
        case wgpu::BackendType::OpenGLES:
            DAWN_INVALID_IF(!mFunctions.IsAtLeastGLES(3, 1), "OpenGL ES 3.1 is required.");
            break;
        case wgpu::BackendType::OpenGL:
            DAWN_INVALID_IF(!mFunctions.IsAtLeastGL(4, 4), "Desktop OpenGL 4.4 is required.");
            break;
        default:
            DAWN_UNREACHABLE();
    }

    if (mFunctions.GetVersion().IsES()) {
        DAWN_ASSERT(GetBackendType() == wgpu::BackendType::OpenGLES);
    } else {
        DAWN_ASSERT(GetBackendType() == wgpu::BackendType::OpenGL);
    }

    mName = reinterpret_cast<const char*>(mFunctions.GetString(GL_RENDERER));

    // Workaroud to find vendor id from vendor name
    const char* vendor = reinterpret_cast<const char*>(mFunctions.GetString(GL_VENDOR));
    mVendorId = GetVendorIdFromVendors(vendor);
    // Workaround to find device id from ANGLE render string
    if (mName.find("ANGLE") == 0) {
        mDeviceId = GetDeviceIdFromRender(mName);
    }

    mDriverDescription = std::string("OpenGL version ") +
                         reinterpret_cast<const char*>(mFunctions.GetString(GL_VERSION));

    if (mName.find("SwiftShader") != std::string::npos) {
        mAdapterType = wgpu::AdapterType::CPU;
    }

    return {};
}

void PhysicalDevice::InitializeSupportedFeaturesImpl() {
    EnableFeature(Feature::StaticSamplers);

    // TextureCompressionBC
    {
        // BC1, BC2 and BC3 are not supported in OpenGL or OpenGL ES core features.
        bool supportsS3TC =
            mFunctions.IsGLExtensionSupported("GL_EXT_texture_compression_s3tc") ||
            (mFunctions.IsGLExtensionSupported("GL_EXT_texture_compression_dxt1") &&
             mFunctions.IsGLExtensionSupported("GL_ANGLE_texture_compression_dxt3") &&
             mFunctions.IsGLExtensionSupported("GL_ANGLE_texture_compression_dxt5"));

        // COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT and
        // COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT requires both GL_EXT_texture_sRGB and
        // GL_EXT_texture_compression_s3tc on desktop OpenGL drivers.
        // (https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_sRGB.txt)
        bool supportsTextureSRGB = mFunctions.IsGLExtensionSupported("GL_EXT_texture_sRGB");

        // GL_EXT_texture_compression_s3tc_srgb is an extension in OpenGL ES.
        // NVidia GLES drivers don't support this extension, but they do support
        // GL_NV_sRGB_formats. (Note that GL_EXT_texture_sRGB does not exist on ES.
        // GL_EXT_sRGB does (core in ES 3.0), but it does not automatically provide S3TC
        // SRGB support even if S3TC is supported; see
        // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_sRGB.txt.)
        bool supportsS3TCSRGB =
            mFunctions.IsGLExtensionSupported("GL_EXT_texture_compression_s3tc_srgb") ||
            mFunctions.IsGLExtensionSupported("GL_NV_sRGB_formats");

        // BC4 and BC5
        bool supportsRGTC = mFunctions.IsAtLeastGL(3, 0) ||
                            mFunctions.IsGLExtensionSupported("GL_ARB_texture_compression_rgtc") ||
                            mFunctions.IsGLExtensionSupported("GL_EXT_texture_compression_rgtc");

        // BC6 and BC7
        bool supportsBPTC = mFunctions.IsAtLeastGL(4, 2) ||
                            mFunctions.IsGLExtensionSupported("GL_ARB_texture_compression_bptc") ||
                            mFunctions.IsGLExtensionSupported("GL_EXT_texture_compression_bptc");

        if (supportsS3TC && (supportsTextureSRGB || supportsS3TCSRGB) && supportsRGTC &&
            supportsBPTC) {
            EnableFeature(Feature::TextureCompressionBC);
        }
    }

    if (mFunctions.IsGLExtensionSupported("GL_KHR_texture_compression_astc_ldr")) {
        EnableFeature(Feature::TextureCompressionASTC);

        // GL_KHR_texture_compression_astc_sliced_3d is an extension in OpenGL ES 3.1.
        // https://registry.khronos.org/OpenGL/extensions/KHR/KHR_texture_compression_astc_sliced_3d.txt
        if (mFunctions.IsGLExtensionSupported("GL_KHR_texture_compression_astc_sliced_3d")) {
            EnableFeature(Feature::TextureCompressionASTCSliced3D);
        }
    }

    // ETC2 is core in ES 3.0.
    // However, ANGLE on Desktop GL does not support it.
    if (mFunctions.IsAtLeastGLES(3, 0) && !IsANGLEDesktopGL(mName)) {
        EnableFeature(Feature::TextureCompressionETC2);
    }

    if (mDisplay->egl->HasExt(EGLExt::DisplayTextureShareGroup)) {
        EnableFeature(dawn::native::Feature::ANGLETextureSharing);
    }

    if (mDisplay->egl->HasExt(EGLExt::ImageNativeBuffer) &&
        mDisplay->egl->HasExt(EGLExt::GetNativeClientBuffer)) {
        EnableFeature(dawn::native::Feature::SharedTextureMemoryAHardwareBuffer);
    }

    if (mDisplay->egl->HasExt(EGLExt::WaitSync) &&
        mFunctions.IsGLExtensionSupported("GL_OES_EGL_sync")) {
        if (mDisplay->egl->HasExt(EGLExt::NativeFenceSync)) {
            EnableFeature(dawn::native::Feature::SharedFenceSyncFD);
        }
        EnableFeature(dawn::native::Feature::SharedFenceEGLSync);
    }

    // Non-zero baseInstance requires at least desktop OpenGL 4.2, and it is not supported in
    // OpenGL ES OpenGL:
    // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDrawElementsIndirect.xhtml
    // OpenGL ES:
    // https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glDrawElementsIndirect.xhtml
    if (mFunctions.IsAtLeastGL(4, 2)) {
        EnableFeature(Feature::IndirectFirstInstance);
    }

    // ShaderF16
    // Int16 required to support buffer_view conversions
    if (mFunctions.IsGLExtensionSupported("GL_AMD_gpu_shader_half_float") &&
        mFunctions.IsGLExtensionSupported("GL_AMD_gpu_shader_int16")) {
        EnableFeature(Feature::ShaderF16);
    }

    // DualSourceBlending
    if (mFunctions.IsGLExtensionSupported("GL_EXT_blend_func_extended") ||
        mFunctions.IsAtLeastGL(3, 3)) {
        EnableFeature(Feature::DualSourceBlending);
    }

    // Unorm16TextureFormats
    if (mFunctions.IsGLExtensionSupported("GL_EXT_texture_norm16")) {
        EnableFeature(Feature::Unorm16TextureFormats);
        EnableFeature(Feature::Unorm16Filterable);
        EnableFeature(Feature::Unorm16FormatsForExternalTexture);
    }

    // Float32Blendable
    if (mFunctions.IsGLExtensionSupported("GL_EXT_float_blend")) {
        EnableFeature(Feature::Float32Blendable);
    }

    // TextureComponentSwizzle
    if (SupportTextureComponentSwizzle()) {
        EnableFeature(Feature::TextureComponentSwizzle);
    }
}

namespace {
ResultOrError<GLint> Get(const OpenGLFunctions& gl, GLenum pname) {
    GLint value;
    DAWN_GL_TRY(gl, GetIntegerv(pname, &value));
    return value;
}

ResultOrError<GLint> GetIndexed(const OpenGLFunctions& gl, GLenum pname, GLuint index) {
    GLint value;
    DAWN_GL_TRY(gl, GetIntegeri_v(pname, index, &value));
    return value;
}
}  // namespace

MaybeError PhysicalDevice::InitializeSupportedLimitsImpl(CombinedLimits* limits) {
    const OpenGLFunctions& gl = mFunctions;
    GetDefaultLimitsForSupportedFeatureLevel(limits);

    DAWN_TRY_ASSIGN(limits->v1.maxTextureDimension2D, Get(gl, GL_MAX_TEXTURE_SIZE));
    limits->v1.maxTextureDimension1D = limits->v1.maxTextureDimension2D;
    DAWN_TRY_ASSIGN(limits->v1.maxTextureDimension3D, Get(gl, GL_MAX_3D_TEXTURE_SIZE));
    DAWN_TRY_ASSIGN(limits->v1.maxTextureArrayLayers, Get(gl, GL_MAX_ARRAY_TEXTURE_LAYERS));

    // Since we flatten bindings, leave maxBindGroups and maxBindingsPerBindGroup at the default.

    DAWN_TRY_ASSIGN(limits->v1.maxDynamicUniformBuffersPerPipelineLayout,
                    Get(gl, GL_MAX_UNIFORM_BUFFER_BINDINGS));
    DAWN_TRY_ASSIGN(limits->v1.maxDynamicStorageBuffersPerPipelineLayout,
                    Get(gl, GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS));

    GLuint maxTextureImageUnits = 0, maxVertexTextureImageUnits = 0;
    DAWN_TRY_ASSIGN(maxTextureImageUnits, Get(gl, GL_MAX_TEXTURE_IMAGE_UNITS));
    DAWN_TRY_ASSIGN(maxVertexTextureImageUnits, Get(gl, GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS));
    limits->v1.maxSampledTexturesPerShaderStage =
        std::min(maxTextureImageUnits, maxVertexTextureImageUnits);

    DAWN_TRY_ASSIGN(limits->v1.maxSamplersPerShaderStage, Get(gl, GL_MAX_TEXTURE_IMAGE_UNITS));
    DAWN_TRY_ASSIGN(limits->v1.maxStorageBuffersPerShaderStage,
                    Get(gl, GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS));
    // TODO(crbug.com/dawn/1834): Note that OpenGLES allows an implementation to have zero vertex
    // image uniforms, so this isn't technically correct for vertex shaders.
    DAWN_TRY_ASSIGN(limits->v1.maxStorageTexturesPerShaderStage,
                    Get(gl, GL_MAX_COMPUTE_IMAGE_UNIFORMS));
    DAWN_TRY_ASSIGN(limits->compat.maxStorageTexturesInFragmentStage,
                    Get(gl, GL_MAX_FRAGMENT_IMAGE_UNIFORMS));

    DAWN_TRY_ASSIGN(limits->compat.maxStorageBuffersInFragmentStage,
                    Get(gl, GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS));
    DAWN_TRY_ASSIGN(limits->compat.maxStorageTexturesInVertexStage,
                    Get(gl, GL_MAX_VERTEX_IMAGE_UNIFORMS));
    DAWN_TRY_ASSIGN(limits->compat.maxStorageBuffersInVertexStage,
                    Get(gl, GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS));

    DAWN_TRY_ASSIGN(limits->v1.maxUniformBuffersPerShaderStage,
                    Get(gl, GL_MAX_UNIFORM_BUFFER_BINDINGS));
    DAWN_TRY_ASSIGN(limits->v1.maxUniformBufferBindingSize, Get(gl, GL_MAX_UNIFORM_BLOCK_SIZE));
    DAWN_TRY_ASSIGN(limits->v1.maxStorageBufferBindingSize,
                    Get(gl, GL_MAX_SHADER_STORAGE_BLOCK_SIZE));

    DAWN_TRY_ASSIGN(limits->v1.minUniformBufferOffsetAlignment,
                    Get(gl, GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT));
    DAWN_TRY_ASSIGN(limits->v1.minStorageBufferOffsetAlignment,
                    Get(gl, GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT));
    DAWN_TRY_ASSIGN(limits->v1.maxVertexBuffers, Get(gl, GL_MAX_VERTEX_ATTRIB_BINDINGS));
    limits->v1.maxBufferSize = kAssumedMaxBufferSize;
    // The code that handles adding the index buffer offset to first_index
    // used in drawIndexedIndirect can not handle a max buffer size larger than 4gig.
    // See IndirectDrawValidationEncoder.cpp
    static_assert(kAssumedMaxBufferSize < 0x100000000u);

    DAWN_TRY_ASSIGN(limits->v1.maxVertexAttributes, Get(gl, GL_MAX_VERTEX_ATTRIBS));
    DAWN_TRY_ASSIGN(limits->v1.maxVertexBufferArrayStride, Get(gl, GL_MAX_VERTEX_ATTRIB_STRIDE));
    DAWN_TRY_ASSIGN(limits->v1.maxInterStageShaderVariables, Get(gl, GL_MAX_VARYING_VECTORS));
    // TODO(dawn:685, dawn:1448): Support higher values as ANGLE compiler always generates
    // additional shader varyings (gl_PointSize and dx_Position) on ANGLE D3D backends.
    limits->v1.maxInterStageShaderVariables =
        std::min(limits->v1.maxInterStageShaderVariables, kMaxInterStageShaderVariables);

    GLuint maxColorAttachments = 0, maxDrawBuffers = 0;
    DAWN_TRY_ASSIGN(maxColorAttachments, Get(gl, GL_MAX_COLOR_ATTACHMENTS));
    DAWN_TRY_ASSIGN(maxDrawBuffers, Get(gl, GL_MAX_DRAW_BUFFERS));
    limits->v1.maxColorAttachments = std::min(maxColorAttachments, maxDrawBuffers);

    // OpenGL has no such limit; set it to 16 (the byte width of the largest format) multiplied by
    // the maximum number of attachments in order to make it a no-op.
    limits->v1.maxColorAttachmentBytesPerSample = 16 * maxColorAttachments;

    DAWN_TRY_ASSIGN(limits->v1.maxComputeWorkgroupStorageSize,
                    Get(gl, GL_MAX_COMPUTE_SHARED_MEMORY_SIZE));
    DAWN_TRY_ASSIGN(limits->v1.maxComputeInvocationsPerWorkgroup,
                    Get(gl, GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS));
    DAWN_TRY_ASSIGN(limits->v1.maxComputeWorkgroupSizeX,
                    GetIndexed(gl, GL_MAX_COMPUTE_WORK_GROUP_SIZE, 0));
    DAWN_TRY_ASSIGN(limits->v1.maxComputeWorkgroupSizeY,
                    GetIndexed(gl, GL_MAX_COMPUTE_WORK_GROUP_SIZE, 1));
    DAWN_TRY_ASSIGN(limits->v1.maxComputeWorkgroupSizeZ,
                    GetIndexed(gl, GL_MAX_COMPUTE_WORK_GROUP_SIZE, 2));
    GLint v[3];
    DAWN_TRY_ASSIGN(v[0], GetIndexed(gl, GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0));
    DAWN_TRY_ASSIGN(v[1], GetIndexed(gl, GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1));
    DAWN_TRY_ASSIGN(v[2], GetIndexed(gl, GL_MAX_COMPUTE_WORK_GROUP_COUNT, 2));
    limits->v1.maxComputeWorkgroupsPerDimension = std::min({v[0], v[1], v[2]});
    limits->v1.maxImmediateSize = kMaxImmediateDataBytes;
    return {};
}

void PhysicalDevice::SetupBackendAdapterToggles(dawn::platform::Platform* platform,
                                                TogglesState* adapterToggles) const {
    adapterToggles->Default(
        Toggle::DecomposeUniformBuffers,
        platform->IsFeatureEnabled(platform::Features::kWebGPUDecomposeUniformBuffers));
}

void PhysicalDevice::SetupBackendDeviceToggles(dawn::platform::Platform* platform,
                                               TogglesState* deviceToggles) const {
    const OpenGLFunctions& gl = mFunctions;

    // TODO(crbug.com/dawn/582): Use OES_draw_buffers_indexed where available.
    bool supportsIndexedDrawBuffers = gl.IsAtLeastGLES(3, 2) || gl.IsAtLeastGL(3, 0);

    bool supportsSnormRead =
        gl.IsAtLeastGL(4, 4) || gl.IsGLExtensionSupported("GL_EXT_render_snorm");

    // Desktop GL supports BGRA textures via swizzling in the driver; ES requires an extension.
    bool supportsBGRARead =
        gl.GetVersion().IsDesktop() || gl.IsGLExtensionSupported("GL_EXT_read_format_bgra");

    bool supportsSampleVariables = gl.IsAtLeastGL(4, 0) || gl.IsAtLeastGLES(3, 2) ||
                                   gl.IsGLExtensionSupported("GL_OES_sample_variables");

    // Decide whether glTexSubImage2D/3D accepts GL_STENCIL_INDEX or not.
    bool supportsStencilWriteTexture =
        gl.GetVersion().IsDesktop() || gl.IsGLExtensionSupported("GL_OES_texture_stencil8");

    bool isFloat32Renderable = gl.GetVersion().IsDesktop() || gl.IsAtLeastGLES(3, 2) ||
                               gl.IsGLExtensionSupported("GL_EXT_color_buffer_float");
    bool isFloat16Renderable =
        isFloat32Renderable || gl.IsGLExtensionSupported("GL_EXT_color_buffer_half_float");
    bool isRG11B10UfloatRenderable = isFloat32Renderable;

    // TODO(crbug.com/dawn/343): Investigate emulation.
    deviceToggles->Default(Toggle::DisableIndexedDrawBuffers, !supportsIndexedDrawBuffers);
    deviceToggles->Default(Toggle::DisableSampleVariables, !supportsSampleVariables);
    deviceToggles->Default(Toggle::FlushBeforeClientWaitSync, gl.GetVersion().IsES());
    // For OpenGL ES, we must use a placeholder fragment shader for vertex-only render pipeline.
    deviceToggles->Default(Toggle::UsePlaceholderFragmentInVertexOnlyPipeline,
                           gl.GetVersion().IsES());
    // For OpenGL/OpenGL ES, use compute shader blit to emulate depth16unorm texture to buffer
    // copies.
    deviceToggles->Default(Toggle::UseBlitForDepth16UnormTextureToBufferCopy, true);

    // For OpenGL ES, use compute shader blit to emulate depth32float texture to buffer copies.
    deviceToggles->Default(Toggle::UseBlitForDepth32FloatTextureToBufferCopy,
                           gl.GetVersion().IsES());

    // For OpenGL ES, use compute shader blit to emulate stencil texture to buffer copies.
    deviceToggles->Default(Toggle::UseBlitForStencilTextureToBufferCopy, gl.GetVersion().IsES());

    // For OpenGL ES, use compute shader blit to emulate snorm texture to buffer copies.
    deviceToggles->Default(Toggle::UseBlitForSnormTextureToBufferCopy,
                           gl.GetVersion().IsES() || !supportsSnormRead);

    // For OpenGL ES, use compute shader blit to emulate bgra8unorm texture to buffer copies.
    deviceToggles->Default(Toggle::UseBlitForBGRA8UnormTextureToBufferCopy, !supportsBGRARead);

    // For OpenGL ES, use compute shader blit to emulate rgb9e5ufloat texture to buffer copies.
    deviceToggles->Default(Toggle::UseBlitForRGB9E5UfloatTextureCopy, gl.GetVersion().IsES());

    // Use compute shader blit to emulate rg11b10ufloat texture to buffer copies if not color
    // renderable.
    deviceToggles->Default(Toggle::UseBlitForRG11B10UfloatTextureCopy, !isRG11B10UfloatRenderable);

    // Use compute shader blit to emulate float16 texture to buffer copies if not color renderable.
    deviceToggles->Default(Toggle::UseBlitForFloat16TextureCopy, !isFloat16Renderable);

    // Use compute shader blit to emulate float32 texture to buffer copies if not color renderable.
    deviceToggles->Default(Toggle::UseBlitForFloat32TextureCopy, !isFloat32Renderable);

    // Use a blit to emulate stencil-only buffer-to-texture copies.
    deviceToggles->Default(Toggle::UseBlitForBufferToStencilTextureCopy, true);

    // Use a blit to emulate write to stencil textures.
    deviceToggles->Default(Toggle::UseBlitForStencilTextureWrite, !supportsStencilWriteTexture);

    // Use T2B and B2T copies to emulate a T2T copy between sRGB and non-sRGB textures.
    deviceToggles->Default(Toggle::UseT2B2TForSRGBTextureCopy, true);

    // Scale depth bias value by * 0.5 on certain GL drivers.
    deviceToggles->Default(Toggle::GLDepthBiasModifier, gl.GetVersion().IsDesktop() ||
                                                            IsANGLEDesktopGL(mName) ||
                                                            IsSwiftShader(mName));

    // (crbug.com/379805731): PowerVR GE8300 GLES 3.1 driver cannot compile .length() on SSBO
    // dynamic array.
    // (crbug.com/42240914): Nividia GLES driver returns wrong value for .length() on
    // SSBO dynamic array.
    deviceToggles->Default(
        Toggle::GLUseArrayLengthFromUniform,
        mVendorId == gpu_info::kVendorID_ImgTec || mVendorId == gpu_info::kVendorID_Nvidia);

    // Enable the integer range analysis for shader robustness by default if the corresponding
    // platform feature is enabled.
    deviceToggles->Default(
        Toggle::EnableIntegerRangeAnalysisInRobustness,
        platform->IsFeatureEnabled(platform::Features::kWebGPUEnableRangeAnalysisForRobustness));
}

ResultOrError<Ref<DeviceBase>> PhysicalDevice::CreateDeviceImpl(
    AdapterBase* adapter,
    const UnpackedPtr<DeviceDescriptor>& descriptor,
    const TogglesState& deviceToggles,
    Ref<DeviceBase::DeviceLostEvent>&& lostEvent) {
    bool useANGLETextureSharing = false;
    for (size_t i = 0; i < descriptor->requiredFeatureCount; ++i) {
        if (DAWN_UNSAFE_TODO(descriptor->requiredFeatures[i]) ==
            wgpu::FeatureName::ANGLETextureSharing) {
            useANGLETextureSharing = true;
        }
    }

    bool useRobustness = !deviceToggles.IsEnabled(Toggle::DisableRobustness);

    // Workaround: Imagination EGL 1.5 drivers do not support the new robustness enum.
    // Use the pre-1.5 extension enum instead.
    bool disableEGL15Robustness = mVendorId == gpu_info::kVendorID_ImgTec;
    bool forceES31AndMinExtensions = deviceToggles.IsEnabled(Toggle::GLForceES31AndNoExtensions);
    bool bindContextOnlyDuringUse = deviceToggles.IsEnabled(Toggle::GLAllowContextOnMultiThreads) ||
                                    deviceToggles.IsEnabled(Toggle::GLDefer);

    std::unique_ptr<ContextEGL> context;
    DAWN_TRY_ASSIGN(context, ContextEGL::Create(mDisplay, GetBackendType(), useRobustness,
                                                disableEGL15Robustness, useANGLETextureSharing,
                                                forceES31AndMinExtensions, bindContextOnlyDuringUse,
                                                mAngleVirtualizationGroup));

    return Device::Create(adapter, descriptor, mFunctions, std::move(context), deviceToggles,
                          std::move(lostEvent));
}

bool PhysicalDevice::SupportsFeatureLevel(wgpu::FeatureLevel featureLevel,
                                          InstanceBase* instance) const {
    return featureLevel == wgpu::FeatureLevel::Compatibility;
}

ResultOrError<PhysicalDeviceSurfaceCapabilities> PhysicalDevice::GetSurfaceCapabilities(
    InstanceBase*,
    const Surface*) const {
    PhysicalDeviceSurfaceCapabilities capabilities;

    capabilities.usages = wgpu::TextureUsage::RenderAttachment |
                          wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::TextureBinding |
                          wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;

    for (wgpu::TextureFormat format : mDisplay->GetPotentialSurfaceFormats()) {
        if (mDisplay->ChooseConfig(EGL_WINDOW_BIT, format) != kNoConfig) {
            capabilities.formats.push_back(format);
        }
    }

    capabilities.presentModes = {
        wgpu::PresentMode::Fifo,
        wgpu::PresentMode::Immediate,
        wgpu::PresentMode::Mailbox,
    };

    capabilities.alphaModes = {
        wgpu::CompositeAlphaMode::Opaque,
    };

    return capabilities;
}

FeatureValidationResult PhysicalDevice::ValidateFeatureSupportedWithTogglesImpl(
    wgpu::FeatureName feature,
    const TogglesState& toggles) const {
    return {};
}

void PhysicalDevice::PopulateBackendProperties(UnpackedPtr<AdapterInfo>& info,
                                               const TogglesState&) const {}

bool PhysicalDevice::SupportTextureComponentSwizzle() const {
    // Texture component swizzle requires GLES 3.0 / GL 3.3+ and is known to be incomplete or
    // unsupported on the ANGLE D3D11 backend (as D3D11 lacks native support).
    return (mFunctions.IsAtLeastGLES(3, 0) || mFunctions.IsAtLeastGL(3, 3));
}

}  // namespace dawn::native::opengl
