// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/gl/gl_display.h"

#include <string>
#include <type_traits>
#include <vector>

#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/debug/crash_logging.h"
#include "base/export_template.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/system/sys_info.h"
#include "build/build_config.h"
#include "ui/gl/angle_platform_impl.h"
#include "ui/gl/egl_util.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_context_egl.h"
#include "ui/gl/gl_display_egl_util.h"
#include "ui/gl/gl_features.h"
#include "ui/gl/gl_implementation.h"
#include "ui/gl/gl_surface.h"

#if BUILDFLAG(IS_OZONE)
#include "ui/ozone/buildflags.h"
#endif  // BUILDFLAG(IS_OZONE)

#if BUILDFLAG(IS_ANDROID)
#include "base/android/build_info.h"
#endif

// From ANGLE's egl/eglext.h.

#ifndef EGL_ANGLE_platform_angle
#define EGL_ANGLE_platform_angle 1
#define EGL_PLATFORM_ANGLE_ANGLE 0x3202
#define EGL_PLATFORM_ANGLE_TYPE_ANGLE 0x3203
#define EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE 0x3204
#define EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE 0x3205
#define EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE 0x3206
#define EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED_ANGLE 0x3451
#define EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE 0x3209
#define EGL_PLATFORM_ANGLE_DEVICE_TYPE_EGL_ANGLE 0x348E
#define EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE 0x320A
#define EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE 0x345E
#define EGL_PLATFORM_ANGLE_DEVICE_TYPE_SWIFTSHADER_ANGLE 0x3487
#define EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE 0x348F
#endif /* EGL_ANGLE_platform_angle */

#ifndef EGL_ANGLE_platform_angle_d3d
#define EGL_ANGLE_platform_angle_d3d 1
#define EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE 0x3207
#define EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE 0x3208
#define EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_WARP_ANGLE 0x320B
#define EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_REFERENCE_ANGLE 0x320C
#endif /* EGL_ANGLE_platform_angle_d3d */

#ifndef EGL_ANGLE_platform_angle_d3d_luid
#define EGL_ANGLE_platform_angle_d3d_luid 1
#define EGL_PLATFORM_ANGLE_D3D_LUID_HIGH_ANGLE 0x34A0
#define EGL_PLATFORM_ANGLE_D3D_LUID_LOW_ANGLE 0x34A1
#endif /* EGL_ANGLE_platform_angle_d3d_luid */

#ifndef EGL_ANGLE_platform_angle_d3d11on12
#define EGL_ANGLE_platform_angle_d3d11on12 1
#define EGL_PLATFORM_ANGLE_D3D11ON12_ANGLE 0x3488
#endif /* EGL_ANGLE_platform_angle_d3d11on12 */

#ifndef EGL_ANGLE_platform_angle_opengl
#define EGL_ANGLE_platform_angle_opengl 1
#define EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE 0x320D
#define EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE 0x320E
#endif /* EGL_ANGLE_platform_angle_opengl */

#ifndef EGL_ANGLE_platform_angle_null
#define EGL_ANGLE_platform_angle_null 1
#define EGL_PLATFORM_ANGLE_TYPE_NULL_ANGLE 0x33AE
#endif /* EGL_ANGLE_platform_angle_null */

#ifndef EGL_ANGLE_platform_angle_vulkan
#define EGL_ANGLE_platform_angle_vulkan 1
#define EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE 0x3450
#define EGL_PLATFORM_VULKAN_DISPLAY_MODE_HEADLESS_ANGLE 0x34A5
#endif /* EGL_ANGLE_platform_angle_vulkan */

#ifndef EGL_ANGLE_platform_angle_metal
#define EGL_ANGLE_platform_angle_metal 1
#define EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE 0x3489
#endif /* EGL_ANGLE_platform_angle_metal */

#ifndef EGL_ANGLE_x11_visual
#define EGL_ANGLE_x11_visual 1
#define EGL_X11_VISUAL_ID_ANGLE 0x33A3
#endif /* EGL_ANGLE_x11_visual */

#ifndef EGL_ANGLE_direct_composition
#define EGL_ANGLE_direct_composition 1
#define EGL_DIRECT_COMPOSITION_ANGLE 0x33A5
#endif /* EGL_ANGLE_direct_composition */

#ifndef EGL_ANGLE_display_robust_resource_initialization
#define EGL_ANGLE_display_robust_resource_initialization 1
#define EGL_DISPLAY_ROBUST_RESOURCE_INITIALIZATION_ANGLE 0x3453
#endif /* EGL_ANGLE_display_robust_resource_initialization */

#ifndef EGL_ANGLE_display_power_preference
#define EGL_ANGLE_display_power_preference 1
#define EGL_POWER_PREFERENCE_ANGLE 0x3482
#define EGL_LOW_POWER_ANGLE 0x0001
#define EGL_HIGH_POWER_ANGLE 0x0002
#endif /* EGL_ANGLE_power_preference */

#ifndef EGL_ANGLE_platform_angle_device_id
#define EGL_ANGLE_platform_angle_device_id
#define EGL_PLATFORM_ANGLE_DEVICE_ID_HIGH_ANGLE 0x34D6
#define EGL_PLATFORM_ANGLE_DEVICE_ID_LOW_ANGLE 0x34D7
#define EGL_PLATFORM_ANGLE_DISPLAY_KEY_ANGLE 0x34DC
#endif /* EGL_ANGLE_platform_angle_device_id */

// From ANGLE's egl/eglext.h.
#ifndef EGL_ANGLE_feature_control
#define EGL_ANGLE_feature_control 1
#define EGL_FEATURE_NAME_ANGLE 0x3460
#define EGL_FEATURE_CATEGORY_ANGLE 0x3461
#define EGL_FEATURE_DESCRIPTION_ANGLE 0x3462
#define EGL_FEATURE_BUG_ANGLE 0x3463
#define EGL_FEATURE_STATUS_ANGLE 0x3464
#define EGL_FEATURE_COUNT_ANGLE 0x3465
#define EGL_FEATURE_OVERRIDES_ENABLED_ANGLE 0x3466
#define EGL_FEATURE_OVERRIDES_DISABLED_ANGLE 0x3467
#define EGL_FEATURE_ALL_DISABLED_ANGLE 0x3469
#endif /* EGL_ANGLE_feature_control */

using ui::GetLastEGLErrorString;

namespace gl {

namespace {

void AdjustAngleFeaturesFromChromeFeatures(
    std::vector<std::string>& enabled_angle_features,
    std::vector<std::string>& disabled_angle_features) {
#if BUILDFLAG(IS_MAC)
  if (base::FeatureList::IsEnabled(features::kWriteMetalShaderCacheToDisk)) {
    disabled_angle_features.push_back("enableParallelMtlLibraryCompilation");
    enabled_angle_features.push_back("compileMetalShaders");
    enabled_angle_features.push_back("disableProgramCaching");
  }
  if (base::FeatureList::IsEnabled(features::kUseBuiltInMetalShaderCache)) {
    enabled_angle_features.push_back("loadMetalShadersFromBlobCache");
  }
#endif
}

std::vector<const char*> GetAttribArrayFromStringVector(
    const std::vector<std::string>& strings) {
  std::vector<const char*> attribs;
  for (const std::string& item : strings) {
    attribs.push_back(item.c_str());
  }
  attribs.push_back(nullptr);
  return attribs;
}

std::vector<std::string> GetStringVectorFromCommandLine(
    const base::CommandLine* command_line,
    const char switch_name[]) {
  std::string command_string = command_line->GetSwitchValueASCII(switch_name);
  return base::SplitString(command_string, ", ;", base::TRIM_WHITESPACE,
                           base::SPLIT_WANT_NONEMPTY);
}

EGLDisplay GetPlatformANGLEDisplay(
    EGLNativeDisplayType display,
    EGLenum platform_type,
    const std::vector<std::string>& enabled_features,
    const std::vector<std::string>& disabled_features,
    const std::vector<EGLAttrib>& extra_display_attribs) {
  std::vector<EGLAttrib> display_attribs(extra_display_attribs);

  display_attribs.push_back(EGL_PLATFORM_ANGLE_TYPE_ANGLE);
  display_attribs.push_back(static_cast<EGLAttrib>(platform_type));

  if (platform_type == EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE) {
    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
    if (command_line->HasSwitch(switches::kUseAdapterLuid)) {
      // If the LUID is specified, the format is <high part>,<low part>. Split
      // and add them to the EGL_ANGLE_platform_angle_d3d_luid ext attributes.
      std::string luid =
          command_line->GetSwitchValueASCII(switches::kUseAdapterLuid);
      size_t comma = luid.find(',');
      if (comma != std::string::npos) {
        int32_t high;
        uint32_t low;
        if (!base::StringToInt(luid.substr(0, comma), &high) ||
            !base::StringToUint(luid.substr(comma + 1), &low))
          return EGL_NO_DISPLAY;

        display_attribs.push_back(EGL_PLATFORM_ANGLE_D3D_LUID_HIGH_ANGLE);
        display_attribs.push_back(high);

        display_attribs.push_back(EGL_PLATFORM_ANGLE_D3D_LUID_LOW_ANGLE);
        display_attribs.push_back(low);
      }
    }
  }

  GLDisplayEglUtil::GetInstance()->GetPlatformExtraDisplayAttribs(
      platform_type, &display_attribs);

  std::vector<const char*> enabled_features_attribs =
      GetAttribArrayFromStringVector(enabled_features);
  std::vector<const char*> disabled_features_attribs =
      GetAttribArrayFromStringVector(disabled_features);
  if (g_driver_egl.client_ext.b_EGL_ANGLE_feature_control) {
    if (!enabled_features_attribs.empty()) {
      display_attribs.push_back(EGL_FEATURE_OVERRIDES_ENABLED_ANGLE);
      display_attribs.push_back(
          reinterpret_cast<EGLAttrib>(enabled_features_attribs.data()));
    }
    if (!disabled_features_attribs.empty()) {
      display_attribs.push_back(EGL_FEATURE_OVERRIDES_DISABLED_ANGLE);
      display_attribs.push_back(
          reinterpret_cast<EGLAttrib>(disabled_features_attribs.data()));
    }
  }
  // TODO(dbehr) Add an attrib to Angle to pass EGL platform.

  if (g_driver_egl.client_ext.b_EGL_ANGLE_display_power_preference) {
    GpuPreference pref =
        GLSurface::AdjustGpuPreference(GpuPreference::kDefault);
    switch (pref) {
      case GpuPreference::kDefault:
        // Don't request any GPU, let ANGLE and the native driver decide.
        break;
      case GpuPreference::kLowPower:
        display_attribs.push_back(EGL_POWER_PREFERENCE_ANGLE);
        display_attribs.push_back(EGL_LOW_POWER_ANGLE);
        break;
      case GpuPreference::kHighPerformance:
        display_attribs.push_back(EGL_POWER_PREFERENCE_ANGLE);
        display_attribs.push_back(EGL_HIGH_POWER_ANGLE);
        break;
      default:
        NOTREACHED();
    }
  }

  display_attribs.push_back(EGL_NONE);

  // This is an EGL 1.5 function that we know ANGLE supports. It's used to pass
  // EGLAttribs (pointers) instead of EGLints into the display
  return eglGetPlatformDisplay(EGL_PLATFORM_ANGLE_ANGLE,
                               reinterpret_cast<void*>(display),
                               &display_attribs[0]);
}

EGLDisplay GetDisplayFromType(
    DisplayType display_type,
    EGLDisplayPlatform native_display,
    const std::vector<std::string>& enabled_angle_features,
    const std::vector<std::string>& disabled_angle_features,
    bool disable_all_angle_features,
    uint64_t system_device_id,
    DisplayKey display_key) {
  std::vector<EGLAttrib> extra_display_attribs;
  if (disable_all_angle_features) {
    extra_display_attribs.push_back(EGL_FEATURE_ALL_DISABLED_ANGLE);
    extra_display_attribs.push_back(EGL_TRUE);
  }
  if (system_device_id != 0 &&
      g_driver_egl.client_ext.b_EGL_ANGLE_platform_angle_device_id) {
    uint32_t low_part = system_device_id & 0xffffffff;
    extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_DEVICE_ID_LOW_ANGLE);
    extra_display_attribs.push_back(low_part);

    uint32_t high_part = (system_device_id >> 32) & 0xffffffff;
    extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_DEVICE_ID_HIGH_ANGLE);
    extra_display_attribs.push_back(high_part);
  }
  if (display_key != DisplayKey::kDefault) {
    extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_DISPLAY_KEY_ANGLE);
    extra_display_attribs.push_back(static_cast<EGLint>(display_key));
  }
  EGLNativeDisplayType display = native_display.GetDisplay();
  switch (display_type) {
    case DEFAULT:
    case SWIFT_SHADER: {
      if (native_display.GetPlatform() != 0) {
        return eglGetPlatformDisplay(native_display.GetPlatform(),
                                     reinterpret_cast<void*>(display), nullptr);
      }
      return eglGetDisplay(display);
    }
    case ANGLE_D3D9:
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE, enabled_angle_features,
          disabled_angle_features, extra_display_attribs);
    case ANGLE_D3D11:
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, enabled_angle_features,
          disabled_angle_features, extra_display_attribs);
    case ANGLE_D3D11_NULL:
      extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE);
      extra_display_attribs.push_back(
          EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE);
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, enabled_angle_features,
          disabled_angle_features, extra_display_attribs);
    case ANGLE_OPENGL:
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE, enabled_angle_features,
          disabled_angle_features, extra_display_attribs);
    case ANGLE_OPENGL_EGL:
      extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE);
      extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_EGL_ANGLE);
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE, enabled_angle_features,
          disabled_angle_features, extra_display_attribs);
    case ANGLE_OPENGL_NULL:
      extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE);
      extra_display_attribs.push_back(
          EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE);
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE, enabled_angle_features,
          disabled_angle_features, extra_display_attribs);
    case ANGLE_OPENGLES:
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE,
          enabled_angle_features, disabled_angle_features,
          extra_display_attribs);
    case ANGLE_OPENGLES_EGL:
      extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE);
      extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_EGL_ANGLE);
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE,
          enabled_angle_features, disabled_angle_features,
          extra_display_attribs);
    case ANGLE_OPENGLES_NULL:
      extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE);
      extra_display_attribs.push_back(
          EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE);
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE,
          enabled_angle_features, disabled_angle_features,
          extra_display_attribs);
    case ANGLE_NULL:
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_NULL_ANGLE, enabled_angle_features,
          disabled_angle_features, extra_display_attribs);
    case ANGLE_VULKAN:
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE, enabled_angle_features,
          disabled_angle_features, extra_display_attribs);
    case ANGLE_VULKAN_NULL:
      extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE);
      extra_display_attribs.push_back(
          EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE);
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE, enabled_angle_features,
          disabled_angle_features, extra_display_attribs);
    case ANGLE_D3D11on12:
      extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_D3D11ON12_ANGLE);
      extra_display_attribs.push_back(EGL_TRUE);
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, enabled_angle_features,
          disabled_angle_features, extra_display_attribs);
    case ANGLE_SWIFTSHADER:
      extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE);
      extra_display_attribs.push_back(
          EGL_PLATFORM_ANGLE_DEVICE_TYPE_SWIFTSHADER_ANGLE);
#if BUILDFLAG(IS_OZONE)
#if BUILDFLAG(IS_CHROMEOS) && BUILDFLAG(OZONE_PLATFORM_X11)
      extra_display_attribs.push_back(
          EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE);
      extra_display_attribs.push_back(
          EGL_PLATFORM_VULKAN_DISPLAY_MODE_HEADLESS_ANGLE);
#endif  // BUILDFLAG(OZONE_PLATFORM_X11)
#endif  // BUILDFLAG(IS_OZONE)
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE, enabled_angle_features,
          disabled_angle_features, extra_display_attribs);
    case ANGLE_METAL:
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE, enabled_angle_features,
          disabled_angle_features, extra_display_attribs);
    case ANGLE_METAL_NULL:
      extra_display_attribs.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE);
      extra_display_attribs.push_back(
          EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE);
      return GetPlatformANGLEDisplay(
          display, EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE, enabled_angle_features,
          disabled_angle_features, extra_display_attribs);
    default:
      NOTREACHED();
      return EGL_NO_DISPLAY;
  }
}

ANGLEImplementation GetANGLEImplementationFromDisplayType(
    DisplayType display_type) {
  switch (display_type) {
    case ANGLE_D3D9:
      return ANGLEImplementation::kD3D9;
    case ANGLE_D3D11:
    case ANGLE_D3D11_NULL:
    case ANGLE_D3D11on12:
      return ANGLEImplementation::kD3D11;
    case ANGLE_OPENGL:
    case ANGLE_OPENGL_EGL:
    case ANGLE_OPENGL_NULL:
      return ANGLEImplementation::kOpenGL;
    case ANGLE_OPENGLES:
    case ANGLE_OPENGLES_EGL:
    case ANGLE_OPENGLES_NULL:
      return ANGLEImplementation::kOpenGLES;
    case ANGLE_NULL:
      return ANGLEImplementation::kNull;
    case ANGLE_VULKAN:
    case ANGLE_VULKAN_NULL:
      return ANGLEImplementation::kVulkan;
    case ANGLE_SWIFTSHADER:
      return ANGLEImplementation::kSwiftShader;
    case ANGLE_METAL:
    case ANGLE_METAL_NULL:
      return ANGLEImplementation::kMetal;
    default:
      return ANGLEImplementation::kNone;
  }
}

const char* DisplayTypeString(DisplayType display_type) {
  switch (display_type) {
    case DEFAULT:
      return "Default";
    case SWIFT_SHADER:
      return "SwiftShader";
    case ANGLE_D3D9:
      return "D3D9";
    case ANGLE_D3D11:
      return "D3D11";
    case ANGLE_D3D11_NULL:
      return "D3D11Null";
    case ANGLE_OPENGL:
      return "OpenGL";
    case ANGLE_OPENGL_NULL:
      return "OpenGLNull";
    case ANGLE_OPENGLES:
      return "OpenGLES";
    case ANGLE_OPENGLES_NULL:
      return "OpenGLESNull";
    case ANGLE_NULL:
      return "Null";
    case ANGLE_VULKAN:
      return "Vulkan";
    case ANGLE_VULKAN_NULL:
      return "VulkanNull";
    case ANGLE_D3D11on12:
      return "D3D11on12";
    case ANGLE_SWIFTSHADER:
      return "SwANGLE";
    case ANGLE_OPENGL_EGL:
      return "OpenGLEGL";
    case ANGLE_OPENGLES_EGL:
      return "OpenGLESEGL";
    case ANGLE_METAL:
      return "Metal";
    case ANGLE_METAL_NULL:
      return "MetalNull";
    default:
      NOTREACHED();
      return "Err";
  }
}

const char* GetDebugMessageTypeString(EGLint source) {
  switch (source) {
    case EGL_DEBUG_MSG_CRITICAL_KHR:
      return "Critical";
    case EGL_DEBUG_MSG_ERROR_KHR:
      return "Error";
    case EGL_DEBUG_MSG_WARN_KHR:
      return "Warning";
    case EGL_DEBUG_MSG_INFO_KHR:
      return "Info";
    default:
      return "UNKNOWN";
  }
}

void EGLAPIENTRY LogEGLDebugMessage(EGLenum error,
                                    const char* command,
                                    EGLint message_type,
                                    EGLLabelKHR thread_label,
                                    EGLLabelKHR object_label,
                                    const char* message) {
  std::string formatted_message = std::string("EGL Driver message (") +
                                  GetDebugMessageTypeString(message_type) +
                                  ") " + command + ": " + message;

  // Assume that all labels that have been set are strings
  if (thread_label) {
    formatted_message += " thread: ";
    formatted_message += static_cast<const char*>(thread_label);
  }
  if (object_label) {
    formatted_message += " object: ";
    formatted_message += static_cast<const char*>(object_label);
  }

  if (message_type == EGL_DEBUG_MSG_CRITICAL_KHR ||
      message_type == EGL_DEBUG_MSG_ERROR_KHR) {
    LOG(ERROR) << formatted_message;
  } else {
    DVLOG(1) << formatted_message;
  }
}

void SetEglDebugMessageControl() {
  static bool egl_debug_message_control_is_set = false;
  if (!egl_debug_message_control_is_set) {
    EGLAttrib controls[] = {
        EGL_DEBUG_MSG_CRITICAL_KHR,
        EGL_TRUE,
        EGL_DEBUG_MSG_ERROR_KHR,
        EGL_TRUE,
        EGL_DEBUG_MSG_WARN_KHR,
        EGL_TRUE,
        EGL_DEBUG_MSG_INFO_KHR,
        EGL_TRUE,
        EGL_NONE,
        EGL_NONE,
    };

    eglDebugMessageControlKHR(&LogEGLDebugMessage, controls);
  }
}

}  // namespace

GLDisplay::GLDisplay(uint64_t system_device_id,
                     DisplayKey display_key,
                     DisplayPlatform type)
    : system_device_id_(system_device_id),
      display_key_(display_key),
      type_(type) {}

GLDisplay::~GLDisplay() = default;

template <typename GLDisplayPlatform>
GLDisplayPlatform* GLDisplay::GetAs() {
  bool type_checked = false;
  switch (type_) {
    case NONE:
      NOTREACHED();
      break;

    case EGL:
#if defined(USE_EGL)
      type_checked = std::is_same<GLDisplayPlatform, GLDisplayEGL>::value;
#endif  // defined(USE_EGL)
      break;
  }
  if (type_checked)
    return static_cast<GLDisplayPlatform*>(this);

  return nullptr;
}

#if defined(USE_EGL)
template EXPORT_TEMPLATE_DEFINE(GL_EXPORT)
    GLDisplayEGL* GLDisplay::GetAs<GLDisplayEGL>();
#endif  // defined(USE_EGL)

#if defined(USE_EGL)
GLDisplayEGL::EGLGpuSwitchingObserver::EGLGpuSwitchingObserver(
    EGLDisplay display)
    : display_(display) {
  DCHECK(display != EGL_NO_DISPLAY);
}

void GLDisplayEGL::EGLGpuSwitchingObserver::OnGpuSwitched(
    GpuPreference active_gpu_heuristic) {
  eglHandleGPUSwitchANGLE(display_);
}

GLDisplayEGL::GLDisplayEGL(uint64_t system_device_id, DisplayKey display_key)
    : GLDisplay(system_device_id, display_key, EGL), display_(EGL_NO_DISPLAY) {
  ext = std::make_unique<DisplayExtensionsEGL>();
}

GLDisplayEGL::~GLDisplayEGL() = default;

EGLDisplay GLDisplayEGL::GetDisplay() const {
  return display_;
}

void GLDisplayEGL::Shutdown() {
  if (display_ == EGL_NO_DISPLAY)
    return;

  if (gpu_switching_observer_.get()) {
    ui::GpuSwitchingManager::GetInstance()->RemoveObserver(
        gpu_switching_observer_.get());
    gpu_switching_observer_.reset();
  }

  angle::ResetPlatform(display_);
  DCHECK(g_driver_egl.fn.eglTerminateFn);
  eglTerminate(display_);

  display_ = EGL_NO_DISPLAY;
  egl_surfaceless_context_supported_ = false;
  egl_context_priority_supported_ = false;
  egl_android_native_fence_sync_supported_ = false;

#if BUILDFLAG(IS_APPLE)
  CleanupMetalSharedEvent();
#endif
}

bool GLDisplayEGL::IsInitialized() const {
  return display_ != EGL_NO_DISPLAY;
}

void GLDisplayEGL::SetDisplay(EGLDisplay display) {
  display_ = display;
}

EGLDisplayPlatform GLDisplayEGL::GetNativeDisplay() const {
  return native_display_;
}

DisplayType GLDisplayEGL::GetDisplayType() const {
  return display_type_;
}

// static
GLDisplayEGL* GLDisplayEGL::GetDisplayForCurrentContext() {
  GLContext* context = GLContext::GetCurrent();
  return context ? context->GetGLDisplayEGL() : nullptr;
}

bool GLDisplayEGL::IsEGLSurfacelessContextSupported() {
  return egl_surfaceless_context_supported_;
}

bool GLDisplayEGL::IsEGLContextPrioritySupported() {
  return egl_context_priority_supported_;
}

bool GLDisplayEGL::IsAndroidNativeFenceSyncSupported() {
  return egl_android_native_fence_sync_supported_;
}

bool GLDisplayEGL::IsANGLEExternalContextAndSurfaceSupported() {
  return this->ext->b_EGL_ANGLE_external_context_and_surface;
}

bool GLDisplayEGL::Initialize(bool supports_angle,
                              std::vector<DisplayType> init_displays,
                              EGLDisplayPlatform native_display) {
  if (display_ != EGL_NO_DISPLAY)
    return true;

  if (!InitializeDisplay(supports_angle, init_displays, native_display,
                         /*existing_display=*/nullptr)) {
    return false;
  }
  InitializeCommon(/*for_testing=*/false);

  return true;
}

bool GLDisplayEGL::Initialize(GLDisplay* other_display) {
  DCHECK(other_display);
  DCHECK_EQ(display_, EGL_NO_DISPLAY);
  DCHECK_NE(display_key_, other_display->display_key());
  GLDisplayEGL* other_display_egl = other_display->GetAs<GLDisplayEGL>();
  if (other_display_egl == nullptr || !other_display_egl->IsInitialized()) {
    return false;
  }

  // Only allow initialization from a display from the same device.
  if (other_display_egl->system_device_id() != system_device_id_) {
    return false;
  }

  auto gl_implementation = GetGLImplementationParts();
  bool supports_angle = (gl_implementation.gl == kGLImplementationEGLANGLE);
  std::vector<DisplayType> init_displays;
  init_displays.push_back(other_display_egl->GetDisplayType());
  if (!InitializeDisplay(supports_angle, init_displays,
                         other_display_egl->GetNativeDisplay(),
                         other_display_egl)) {
    return false;
  }

  InitializeCommon(/*for_testing=*/false);

  return true;
}

void GLDisplayEGL::InitializeForTesting() {
  display_ = eglGetCurrentDisplay();
  ext->InitializeExtensionSettings(display_);
  InitializeCommon(/*for_testing=*/true);
}

bool GLDisplayEGL::InitializeExtensionSettings() {
  if (display_ == EGL_NO_DISPLAY)
    return false;
  ext->UpdateConditionalExtensionSettings(display_);
  return true;
}

// InitializeDisplay is necessary because the static binding code
// needs a full Display init before it can query the Display extensions.
bool GLDisplayEGL::InitializeDisplay(bool supports_angle,
                                     std::vector<DisplayType> init_displays,
                                     EGLDisplayPlatform native_display,
                                     gl::GLDisplayEGL* existing_display) {
  if (display_ != EGL_NO_DISPLAY)
    return true;

  native_display_ = native_display;

  bool supports_egl_debug = g_driver_egl.client_ext.b_EGL_KHR_debug;
  if (supports_egl_debug) {
    SetEglDebugMessageControl();
  }

  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();

  std::vector<std::string> enabled_angle_features =
      GetStringVectorFromCommandLine(command_line,
                                     switches::kEnableANGLEFeatures);
  std::vector<std::string> disabled_angle_features =
      GetStringVectorFromCommandLine(command_line,
                                     switches::kDisableANGLEFeatures);

  AdjustAngleFeaturesFromChromeFeatures(enabled_angle_features,
                                        disabled_angle_features);

  bool disable_all_angle_features =
      command_line->HasSwitch(switches::kDisableGpuDriverBugWorkarounds);

  for (size_t disp_index = 0; disp_index < init_displays.size(); ++disp_index) {
    DisplayType display_type = init_displays[disp_index];
    EGLDisplay display =
        GetDisplayFromType(display_type, native_display, enabled_angle_features,
                           disabled_angle_features, disable_all_angle_features,
                           system_device_id_, display_key_);
    if (display == EGL_NO_DISPLAY) {
      // Assume this is not an error, so don't verbosely report it;
      // simply try the next display type.
      continue;
    }

    if (!existing_display) {
      // Init ANGLE platform now that we have the global display.
      if (supports_angle) {
        if (!angle::InitializePlatform(display)) {
          LOG(ERROR) << "ANGLE Platform initialization failed.";
        }

        SetANGLEImplementation(
            GetANGLEImplementationFromDisplayType(display_type));
      }

      // The platform may need to unset its platform specific display env in
      // case of vulkan if the platform doesn't support Vulkan surface.
      absl::optional<base::ScopedEnvironmentVariableOverride> unset_display;
      if (display_type == ANGLE_VULKAN) {
        unset_display = GLDisplayEglUtil::GetInstance()
                            ->MaybeGetScopedDisplayUnsetForVulkan();
      }
    }

    if (!eglInitialize(display, nullptr, nullptr)) {
      bool is_last = disp_index == init_displays.size() - 1;

      LOG(ERROR) << "eglInitialize " << DisplayTypeString(display_type)
                 << " failed with error " << GetLastEGLErrorString()
                 << (is_last ? "" : ", trying next display type");
      continue;
    }

    if (!existing_display) {
      std::ostringstream display_type_string;
      auto gl_implementation = GetGLImplementationParts();
      display_type_string << GetGLImplementationGLName(gl_implementation);
      if (gl_implementation.gl == kGLImplementationEGLANGLE) {
        display_type_string << ":" << DisplayTypeString(display_type);
      }

      static auto* egl_display_type_key = base::debug::AllocateCrashKeyString(
          "egl-display-type", base::debug::CrashKeySize::Size32);
      base::debug::SetCrashKeyString(egl_display_type_key,
                                     display_type_string.str());

      UMA_HISTOGRAM_ENUMERATION("GPU.EGLDisplayType", display_type,
                                DISPLAY_TYPE_MAX);
    }
    display_ = display;
    display_type_ = display_type;
    if (!existing_display) {
      ext->InitializeExtensionSettings(display);
    } else {
      type_ = existing_display->type();
      ext =
          std::make_unique<DisplayExtensionsEGL>(*existing_display->ext.get());
    }
    return true;
  }

  LOG(ERROR) << "Initialization of all EGL display types failed.";

  return false;
}

void GLDisplayEGL::InitializeCommon(bool for_testing) {
  // According to https://source.android.com/compatibility/android-cdd.html the
  // EGL_IMG_context_priority extension is mandatory for Virtual Reality High
  // Performance support, but due to a bug in Android Nougat the extension
  // isn't being reported even when it's present. As a fallback, check if other
  // related extensions that were added for VR support are present, and assume
  // that this implies context priority is also supported. See also:
  // https://github.com/googlevr/gvr-android-sdk/issues/330
  egl_context_priority_supported_ =
      ext->b_EGL_IMG_context_priority ||
      (ext->b_EGL_ANDROID_front_buffer_auto_refresh &&
       ext->b_EGL_ANDROID_create_native_client_buffer);

  // Check if SurfacelessEGL is supported.
  egl_surfaceless_context_supported_ = ext->b_EGL_KHR_surfaceless_context;

  // TODO(oetuaho@nvidia.com): Surfaceless is disabled on Android as a temporary
  // workaround, since code written for Android WebView takes different paths
  // based on whether GL surface objects have underlying EGL surface handles,
  // conflicting with the use of surfaceless. ANGLE can still expose surfacelss
  // because it is emulated with pbuffers if native support is not present. See
  // https://crbug.com/382349.

#if BUILDFLAG(IS_ANDROID)
  // Use the WebGL compatibility extension for detecting ANGLE. ANGLE always
  // exposes it.
  bool is_angle = ext->b_EGL_ANGLE_create_context_webgl_compatibility;
  if (!is_angle) {
    egl_surfaceless_context_supported_ = false;
  }
#endif  // BUILDFLAG(IS_ANDROID)

  if (egl_surfaceless_context_supported_) {
    // EGL_KHR_surfaceless_context is supported but ensure
    // GL_OES_surfaceless_context is also supported. We need a current context
    // to query for supported GL extensions.
    scoped_refptr<GLSurface> surface =
        new SurfacelessEGL(this, gfx::Size(1, 1));
    scoped_refptr<GLContext> context = InitializeGLContext(
        new GLContextEGL(nullptr), surface.get(), GLContextAttribs());
    if (!context || !context->MakeCurrent(surface.get()))
      egl_surfaceless_context_supported_ = false;

    // Ensure context supports GL_OES_surfaceless_context.
    if (egl_surfaceless_context_supported_) {
      egl_surfaceless_context_supported_ =
          context->HasExtension("GL_OES_surfaceless_context");
      context->ReleaseCurrent(surface.get());
    }
  }

  // The native fence sync extension is a bit complicated. It's reported as
  // present for ChromeOS, but Android currently doesn't report this extension
  // even when it's present, and older devices and Android emulator may export
  // a useless wrapper function. See crbug.com/775707 for details. In short, if
  // the symbol is present and we're on Android N or newer and we are not on
  // Android emulator, assume that it's usable even if the extension wasn't
  // reported. TODO(https://crbug.com/1086781): Once this is fixed at the
  // Android level, update the heuristic to trust the reported extension from
  // that version onward.
  egl_android_native_fence_sync_supported_ =
      ext->b_EGL_ANDROID_native_fence_sync;
#if BUILDFLAG(IS_ANDROID)
  if (!egl_android_native_fence_sync_supported_ &&
      base::android::BuildInfo::GetInstance()->sdk_int() >=
          base::android::SDK_VERSION_NOUGAT &&
      g_driver_egl.fn.eglDupNativeFenceFDANDROIDFn &&
      base::SysInfo::GetAndroidHardwareEGL() != "swiftshader" &&
      base::SysInfo::GetAndroidHardwareEGL() != "emulation") {
    egl_android_native_fence_sync_supported_ = true;
  }
#endif  // BUILDFLAG(IS_ANDROID)

  if (!for_testing) {
    if (ext->b_EGL_ANGLE_power_preference) {
      gpu_switching_observer_ =
          std::make_unique<EGLGpuSwitchingObserver>(display_);
      ui::GpuSwitchingManager::GetInstance()->AddObserver(
          gpu_switching_observer_.get());
    }
  }
}
#endif  // defined(USE_EGL)

}  // namespace gl