gfx/gl/GLLibraryEGL.cpp
author Lee Salzman <lsalzman@mozilla.com>
Thu, 22 Aug 2019 15:15:08 +0000
changeset 489457 975f26e0fff1c7535a1d663130e12daf7fb12733
parent 484809 4e4751677534ad208d37a0630bbcf39cf04de8e9
child 491626 37276c20441289f74d6c245e50cfe530252533f5
permissions -rw-r--r--
Bug 1571838 - Backed out changeset a6f3fd30a0a7 (bug 1443181). r=jbonisteel Differential Revision: https://phabricator.services.mozilla.com/D43080

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "GLLibraryEGL.h"

#include "gfxConfig.h"
#include "gfxCrashReporterUtils.h"
#include "gfxUtils.h"
#include "mozilla/Preferences.h"
#include "mozilla/Assertions.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Tokenizer.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/StaticPrefs_webgl.h"
#include "mozilla/Unused.h"
#include "mozilla/webrender/RenderThread.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsIGfxInfo.h"
#include "nsPrintfCString.h"
#ifdef XP_WIN
#  include "mozilla/gfx/DeviceManagerDx.h"
#  include "nsWindowsHelpers.h"

#  include <d3d11.h>
#endif
#include "OGLShaderProgram.h"
#include "prenv.h"
#include "prsystem.h"
#include "GLContext.h"
#include "GLContextProvider.h"
#include "ScopedGLHelpers.h"
#ifdef MOZ_WIDGET_GTK
#  include <gdk/gdk.h>
#  ifdef MOZ_WAYLAND
#    include <gdk/gdkwayland.h>
#    include <dlfcn.h>
#    include "mozilla/widget/nsWaylandDisplay.h"
#  endif  // MOZ_WIDGET_GTK
#endif    // MOZ_WAYLAND

namespace mozilla {
namespace gl {

StaticMutex GLLibraryEGL::sMutex;
StaticRefPtr<GLLibraryEGL> GLLibraryEGL::sEGLLibrary;

// should match the order of EGLExtensions, and be null-terminated.
static const char* sEGLExtensionNames[] = {
    "EGL_KHR_image_base",
    "EGL_KHR_image_pixmap",
    "EGL_KHR_gl_texture_2D_image",
    "EGL_KHR_lock_surface",
    "EGL_ANGLE_surface_d3d_texture_2d_share_handle",
    "EGL_EXT_create_context_robustness",
    "EGL_KHR_image",
    "EGL_KHR_fence_sync",
    "EGL_ANDROID_native_fence_sync",
    "EGL_ANDROID_image_crop",
    "EGL_ANGLE_platform_angle",
    "EGL_ANGLE_platform_angle_d3d",
    "EGL_ANGLE_d3d_share_handle_client_buffer",
    "EGL_KHR_create_context",
    "EGL_KHR_stream",
    "EGL_KHR_stream_consumer_gltexture",
    "EGL_EXT_device_query",
    "EGL_NV_stream_consumer_gltexture_yuv",
    "EGL_ANGLE_stream_producer_d3d_texture",
    "EGL_ANGLE_device_creation",
    "EGL_ANGLE_device_creation_d3d11",
    "EGL_KHR_surfaceless_context",
    "EGL_KHR_create_context_no_error",
    "EGL_MOZ_create_context_provoking_vertex_dont_care"};

PRLibrary* LoadApitraceLibrary() {
  const char* path = nullptr;

#ifdef ANDROID
  // We only need to explicitly dlopen egltrace
  // on android as we can use LD_PRELOAD or other tricks
  // on other platforms. We look for it in /data/local
  // as that's writeable by all users.
  path = "/data/local/tmp/egltrace.so";
#endif
  if (!path) return nullptr;

  // Initialization of gfx prefs here is only needed during the unit tests...
  if (!StaticPrefs::gfx_apitrace_enabled_AtStartup()) {
    return nullptr;
  }

  static PRLibrary* sApitraceLibrary = nullptr;
  if (sApitraceLibrary) return sApitraceLibrary;

  nsAutoCString logFile;
  Preferences::GetCString("gfx.apitrace.logfile", logFile);
  if (logFile.IsEmpty()) {
    logFile = "firefox.trace";
  }

  // The firefox process can't write to /data/local, but it can write
  // to $GRE_HOME/
  nsAutoCString logPath;
  logPath.AppendPrintf("%s/%s", getenv("GRE_HOME"), logFile.get());

#ifndef XP_WIN  // Windows is missing setenv and forbids PR_LoadLibrary.
  // apitrace uses the TRACE_FILE environment variable to determine where
  // to log trace output to
  printf_stderr("Logging GL tracing output to %s", logPath.get());
  setenv("TRACE_FILE", logPath.get(), false);

  printf_stderr("Attempting load of %s\n", path);
  sApitraceLibrary = PR_LoadLibrary(path);
#endif

  return sApitraceLibrary;
}

#ifdef XP_WIN
// see the comment in GLLibraryEGL::EnsureInitialized() for the rationale here.
static PRLibrary* LoadLibraryForEGLOnWindows(const nsAString& filename) {
  nsAutoString path(gfx::gfxVars::GREDirectory());
  path.Append(PR_GetDirectorySeparator());
  path.Append(filename);

  PRLibSpec lspec;
  lspec.type = PR_LibSpec_PathnameU;
  lspec.value.pathname_u = path.get();
  return PR_LoadLibraryWithFlags(lspec, PR_LD_LAZY | PR_LD_LOCAL);
}

#endif  // XP_WIN

static EGLDisplay GetAndInitWARPDisplay(GLLibraryEGL& egl, void* displayType) {
  EGLint attrib_list[] = {LOCAL_EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE,
                          LOCAL_EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE,
                          // Requires:
                          LOCAL_EGL_PLATFORM_ANGLE_TYPE_ANGLE,
                          LOCAL_EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
                          LOCAL_EGL_NONE};
  EGLDisplay display = egl.fGetPlatformDisplayEXT(
      LOCAL_EGL_PLATFORM_ANGLE_ANGLE, displayType, attrib_list);

  if (display == EGL_NO_DISPLAY) {
    const EGLint err = egl.fGetError();
    if (err != LOCAL_EGL_SUCCESS) {
      gfxCriticalError() << "Unexpected GL error: " << gfx::hexa(err);
      MOZ_CRASH("GFX: Unexpected GL error.");
    }
    return EGL_NO_DISPLAY;
  }

  if (!egl.fInitialize(display, nullptr, nullptr)) return EGL_NO_DISPLAY;

  return display;
}

static EGLDisplay GetAndInitDisplayForWebRender(GLLibraryEGL& egl,
                                                void* displayType) {
#ifdef XP_WIN
  const EGLint attrib_list[] = {LOCAL_EGL_EXPERIMENTAL_PRESENT_PATH_ANGLE,
                                LOCAL_EGL_EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE,
                                LOCAL_EGL_NONE};
  RefPtr<ID3D11Device> d3d11Device =
      gfx::DeviceManagerDx::Get()->GetCompositorDevice();
  if (!d3d11Device) {
    gfxCriticalNote << "Failed to get compositor device for EGLDisplay";
    return EGL_NO_DISPLAY;
  }
  EGLDeviceEXT eglDevice = egl.fCreateDeviceANGLE(
      LOCAL_EGL_D3D11_DEVICE_ANGLE, reinterpret_cast<void*>(d3d11Device.get()),
      nullptr);
  if (!eglDevice) {
    gfxCriticalNote << "Failed to get EGLDeviceEXT of D3D11Device";
    return EGL_NO_DISPLAY;
  }
  // Create an EGLDisplay using the EGLDevice
  EGLDisplay display = egl.fGetPlatformDisplayEXT(LOCAL_EGL_PLATFORM_DEVICE_EXT,
                                                  eglDevice, attrib_list);
  if (!display) {
    gfxCriticalNote << "Failed to get EGLDisplay of D3D11Device";
    return EGL_NO_DISPLAY;
  }

  if (display == EGL_NO_DISPLAY) {
    const EGLint err = egl.fGetError();
    if (err != LOCAL_EGL_SUCCESS) {
      gfxCriticalError() << "Unexpected GL error: " << gfx::hexa(err);
      MOZ_CRASH("GFX: Unexpected GL error.");
    }
    return EGL_NO_DISPLAY;
  }

  if (!egl.fInitialize(display, nullptr, nullptr)) {
    const EGLint err = egl.fGetError();
    if (err != LOCAL_EGL_SUCCESS) {
      gfxCriticalError()
          << "Failed to initialize EGLDisplay for WebRender error: "
          << gfx::hexa(err);
    }
    return EGL_NO_DISPLAY;
  }
  return display;
#else
  return EGL_NO_DISPLAY;
#endif
}

static bool IsAccelAngleSupported(const nsCOMPtr<nsIGfxInfo>& gfxInfo,
                                  nsACString* const out_failureId) {
  if (wr::RenderThread::IsInRenderThread()) {
    // We can only enter here with WebRender, so assert that this is a
    // WebRender-enabled build.
    return true;
  }
  int32_t angleSupport;
  nsCString failureId;
  gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_ANGLE,
                                       failureId, &angleSupport);
  if (failureId.IsEmpty() && angleSupport != nsIGfxInfo::FEATURE_STATUS_OK) {
    // This shouldn't happen, if we see this it's because we've missed
    // some failure paths
    failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_ACCL_ANGLE_NOT_OK");
  }
  if (out_failureId->IsEmpty()) {
    *out_failureId = failureId;
  }
  return (angleSupport == nsIGfxInfo::FEATURE_STATUS_OK);
}

static EGLDisplay GetAndInitDisplay(GLLibraryEGL& egl, void* displayType) {
  EGLDisplay display = egl.fGetDisplay(displayType);
  if (display == EGL_NO_DISPLAY) return EGL_NO_DISPLAY;

  if (!egl.fInitialize(display, nullptr, nullptr)) return EGL_NO_DISPLAY;

  return display;
}

class AngleErrorReporting {
 public:
  AngleErrorReporting() : mFailureId(nullptr) {
    // No static constructor
  }

  void SetFailureId(nsACString* const aFailureId) { mFailureId = aFailureId; }

  void logError(const char* errorMessage) {
    if (!mFailureId) {
      return;
    }

    nsCString str(errorMessage);
    Tokenizer tokenizer(str);

    // Parse "ANGLE Display::initialize error " << error.getID() << ": "
    //       << error.getMessage()
    nsCString currWord;
    Tokenizer::Token intToken;
    if (tokenizer.CheckWord("ANGLE") && tokenizer.CheckWhite() &&
        tokenizer.CheckWord("Display") && tokenizer.CheckChar(':') &&
        tokenizer.CheckChar(':') && tokenizer.CheckWord("initialize") &&
        tokenizer.CheckWhite() && tokenizer.CheckWord("error") &&
        tokenizer.CheckWhite() &&
        tokenizer.Check(Tokenizer::TOKEN_INTEGER, intToken)) {
      *mFailureId = "FAILURE_ID_ANGLE_ID_";
      mFailureId->AppendPrintf("%" PRIu64, intToken.AsInteger());
    } else {
      *mFailureId = "FAILURE_ID_ANGLE_UNKNOWN";
    }
  }

 private:
  nsACString* mFailureId;
};

AngleErrorReporting gAngleErrorReporter;

static EGLDisplay GetAndInitDisplayForAccelANGLE(
    GLLibraryEGL& egl, nsACString* const out_failureId) {
  EGLDisplay ret = 0;

  if (wr::RenderThread::IsInRenderThread()) {
    return GetAndInitDisplayForWebRender(egl, EGL_DEFAULT_DISPLAY);
  }

  FeatureState& d3d11ANGLE = gfxConfig::GetFeature(Feature::D3D11_HW_ANGLE);

  if (!StaticPrefs::webgl_angle_try_d3d11()) {
    d3d11ANGLE.UserDisable("User disabled D3D11 ANGLE by pref",
                           NS_LITERAL_CSTRING("FAILURE_ID_ANGLE_PREF"));
  }
  if (StaticPrefs::webgl_angle_force_d3d11()) {
    d3d11ANGLE.UserForceEnable(
        "User force-enabled D3D11 ANGLE on disabled hardware");
  }
  gAngleErrorReporter.SetFailureId(out_failureId);

  auto guardShutdown = mozilla::MakeScopeExit([&] {
    gAngleErrorReporter.SetFailureId(nullptr);
    // NOTE: Ideally we should be calling ANGLEPlatformShutdown after the
    //       ANGLE display is destroyed. However gAngleErrorReporter
    //       will live longer than the ANGLE display so we're fine.
  });

  if (gfxConfig::IsForcedOnByUser(Feature::D3D11_HW_ANGLE)) {
    return GetAndInitDisplay(egl, LOCAL_EGL_D3D11_ONLY_DISPLAY_ANGLE);
  }

  if (d3d11ANGLE.IsEnabled()) {
    ret = GetAndInitDisplay(egl, LOCAL_EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE);
  }

  if (!ret) {
    ret = GetAndInitDisplay(egl, EGL_DEFAULT_DISPLAY);
  }

  if (!ret && out_failureId->IsEmpty()) {
    *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_ACCL_ANGLE_NO_DISP");
  }

  return ret;
}

bool GLLibraryEGL::ReadbackEGLImage(EGLImage image,
                                    gfx::DataSourceSurface* out_surface) {
  StaticMutexAutoUnlock lock(sMutex);
  if (!mReadbackGL) {
    nsCString discardFailureId;
    mReadbackGL = gl::GLContextProvider::CreateHeadless(
        gl::CreateContextFlags::NONE, &discardFailureId);
  }

  ScopedTexture destTex(mReadbackGL);
  const GLuint target = mReadbackGL->GetPreferredEGLImageTextureTarget();
  ScopedBindTexture autoTex(mReadbackGL, destTex.Texture(), target);
  mReadbackGL->fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_S,
                              LOCAL_GL_CLAMP_TO_EDGE);
  mReadbackGL->fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_T,
                              LOCAL_GL_CLAMP_TO_EDGE);
  mReadbackGL->fTexParameteri(target, LOCAL_GL_TEXTURE_MAG_FILTER,
                              LOCAL_GL_NEAREST);
  mReadbackGL->fTexParameteri(target, LOCAL_GL_TEXTURE_MIN_FILTER,
                              LOCAL_GL_NEAREST);
  mReadbackGL->fEGLImageTargetTexture2D(target, image);

  ShaderConfigOGL config =
      ShaderConfigFromTargetAndFormat(target, out_surface->GetFormat());
  int shaderConfig = config.mFeatures;
  mReadbackGL->ReadTexImageHelper()->ReadTexImage(
      out_surface, 0, target, out_surface->GetSize(), shaderConfig);

  return true;
}

// -

#if defined(XP_UNIX)
#  define GLES2_LIB "libGLESv2.so"
#  define GLES2_LIB2 "libGLESv2.so.2"
#elif defined(XP_WIN)
#  define GLES2_LIB "libGLESv2.dll"
#else
#  error "Platform not recognized"
#endif

Maybe<SymbolLoader> GLLibraryEGL::GetSymbolLoader() const {
  auto ret = SymbolLoader(mSymbols.fGetProcAddress);
  ret.mLib = mGLLibrary;
  return Some(ret);
}

// -

/* static */
bool GLLibraryEGL::EnsureInitialized(bool forceAccel,
                                     nsACString* const out_failureId) {
  if (!sEGLLibrary) {
    sEGLLibrary = new GLLibraryEGL();
  }
  return sEGLLibrary->DoEnsureInitialized(forceAccel, out_failureId);
}

bool GLLibraryEGL::DoEnsureInitialized() {
  nsCString failureId;
  return DoEnsureInitialized(false, &failureId);
}

bool GLLibraryEGL::DoEnsureInitialized(bool forceAccel,
                                       nsACString* const out_failureId) {
  if (mInitialized && !mSymbols.fTerminate) {
    *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_EGL_DESTROYED");
    MOZ_ASSERT(false);
    return false;
  }

  if (mInitialized) {
    return true;
  }

  mozilla::ScopedGfxFeatureReporter reporter("EGL");

#ifdef XP_WIN
  if (!mEGLLibrary) {
    // On Windows, the GLESv2, EGL and DXSDK libraries are shipped with libxul
    // and we should look for them there. We have to load the libs in this
    // order, because libEGL.dll depends on libGLESv2.dll which depends on the
    // DXSDK libraries. This matters especially for WebRT apps which are in a
    // different directory. See bug 760323 and bug 749459

    // Also note that we intentionally leak the libs we load.

    do {
      // Windows 8.1+ has d3dcompiler_47.dll in the system directory.
      // Try it first. Note that _46 will never be in the system
      // directory. So there is no point trying _46 in the system
      // directory.

      if (LoadLibrarySystem32(L"d3dcompiler_47.dll")) break;

#  ifdef MOZ_D3DCOMPILER_VISTA_DLL
      if (LoadLibraryForEGLOnWindows(
              NS_LITERAL_STRING(NS_STRINGIFY(MOZ_D3DCOMPILER_VISTA_DLL))))
        break;
#  endif

      MOZ_ASSERT(false, "d3dcompiler DLL loading failed.");
    } while (false);

    mGLLibrary = LoadLibraryForEGLOnWindows(NS_LITERAL_STRING("libGLESv2.dll"));

    mEGLLibrary = LoadLibraryForEGLOnWindows(NS_LITERAL_STRING("libEGL.dll"));
  }

#else  // !Windows

  // On non-Windows (Android) we use system copies of libEGL. We look for
  // the APITrace lib, libEGL.so, and libEGL.so.1 in that order.

#  if defined(ANDROID)
  if (!mEGLLibrary) mEGLLibrary = LoadApitraceLibrary();
#  endif

  if (!mEGLLibrary) {
    printf_stderr("Attempting load of libEGL.so\n");
    mEGLLibrary = PR_LoadLibrary("libEGL.so");
  }
#  if defined(XP_UNIX)
  if (!mEGLLibrary) {
    mEGLLibrary = PR_LoadLibrary("libEGL.so.1");
  }
#  endif

#  ifdef APITRACE_LIB
  if (!mGLLibrary) {
    mGLLibrary = PR_LoadLibrary(APITRACE_LIB);
  }
#  endif

  if (!mGLLibrary) {
    mGLLibrary = PR_LoadLibrary(GLES2_LIB);
  }

#  ifdef GLES2_LIB2
  if (!mGLLibrary) {
    mGLLibrary = PR_LoadLibrary(GLES2_LIB2);
  }
#  endif

#endif  // !Windows

  if (!mEGLLibrary || !mGLLibrary) {
    NS_WARNING("Couldn't load EGL LIB.");
    *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_EGL_LOAD_3");
    return false;
  }

#define SYMBOL(X)                 \
  {                               \
    (PRFuncPtr*)&mSymbols.f##X, { \
      { "egl" #X }                \
    }                             \
  }
#define END_OF_SYMBOLS \
  {                    \
    nullptr, {}        \
  }

  SymLoadStruct earlySymbols[] = {SYMBOL(GetDisplay),
                                  SYMBOL(Terminate),
                                  SYMBOL(GetCurrentSurface),
                                  SYMBOL(GetCurrentContext),
                                  SYMBOL(MakeCurrent),
                                  SYMBOL(DestroyContext),
                                  SYMBOL(CreateContext),
                                  SYMBOL(DestroySurface),
                                  SYMBOL(CreateWindowSurface),
                                  SYMBOL(CreatePbufferSurface),
                                  SYMBOL(CreatePbufferFromClientBuffer),
                                  SYMBOL(CreatePixmapSurface),
                                  SYMBOL(BindAPI),
                                  SYMBOL(Initialize),
                                  SYMBOL(ChooseConfig),
                                  SYMBOL(GetError),
                                  SYMBOL(GetConfigs),
                                  SYMBOL(GetConfigAttrib),
                                  SYMBOL(WaitNative),
                                  SYMBOL(GetProcAddress),
                                  SYMBOL(SwapBuffers),
                                  SYMBOL(CopyBuffers),
                                  SYMBOL(QueryString),
                                  SYMBOL(QueryContext),
                                  SYMBOL(BindTexImage),
                                  SYMBOL(ReleaseTexImage),
                                  SYMBOL(SwapInterval),
                                  SYMBOL(QuerySurface),
                                  END_OF_SYMBOLS};

  {
    const SymbolLoader libLoader(*mEGLLibrary);
    if (!libLoader.LoadSymbols(earlySymbols)) {
      NS_WARNING(
          "Couldn't find required entry points in EGL library (early init)");
      *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_EGL_SYM");
      return false;
    }
  }

  {
    const char internalFuncName[] =
        "_Z35eglQueryStringImplementationANDROIDPvi";
    const auto& internalFunc =
        PR_FindFunctionSymbol(mEGLLibrary, internalFuncName);
    if (internalFunc) {
      *(PRFuncPtr*)&mSymbols.fQueryString = internalFunc;
    }
  }
  const SymbolLoader pfnLoader(mSymbols.fGetProcAddress);

  InitClientExtensions();

  const auto fnLoadSymbols = [&](const SymLoadStruct* symbols) {
    if (pfnLoader.LoadSymbols(symbols)) return true;

    ClearSymbols(symbols);
    return false;
  };

  // Check the ANGLE support the system has
  nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
  mIsANGLE = IsExtensionSupported(ANGLE_platform_angle);

  // Client exts are ready. (But not display exts!)

  if (mIsANGLE) {
    MOZ_ASSERT(IsExtensionSupported(ANGLE_platform_angle_d3d));
    const SymLoadStruct angleSymbols[] = {SYMBOL(GetPlatformDisplayEXT),
                                          END_OF_SYMBOLS};
    if (!fnLoadSymbols(angleSymbols)) {
      gfxCriticalError() << "Failed to load ANGLE symbols!";
      return false;
    }
    MOZ_ASSERT(IsExtensionSupported(ANGLE_platform_angle_d3d));
    const SymLoadStruct createDeviceSymbols[] = {
        SYMBOL(CreateDeviceANGLE), SYMBOL(ReleaseDeviceANGLE), END_OF_SYMBOLS};
    if (!fnLoadSymbols(createDeviceSymbols)) {
      NS_ERROR(
          "EGL supports ANGLE_device_creation without exposing its functions!");
      MarkExtensionUnsupported(ANGLE_device_creation);
    }
  }

  mEGLDisplay = CreateDisplay(forceAccel, gfxInfo, out_failureId);
  if (!mEGLDisplay) {
    return false;
  }

  InitDisplayExtensions();

  ////////////////////////////////////
  // Alright, load display exts.

  if (IsExtensionSupported(KHR_lock_surface)) {
    const SymLoadStruct lockSymbols[] = {
        SYMBOL(LockSurfaceKHR), SYMBOL(UnlockSurfaceKHR), END_OF_SYMBOLS};
    if (!fnLoadSymbols(lockSymbols)) {
      NS_ERROR("EGL supports KHR_lock_surface without exposing its functions!");
      MarkExtensionUnsupported(KHR_lock_surface);
    }
  }

  if (IsExtensionSupported(ANGLE_surface_d3d_texture_2d_share_handle)) {
    const SymLoadStruct d3dSymbols[] = {SYMBOL(QuerySurfacePointerANGLE),
                                        END_OF_SYMBOLS};
    if (!fnLoadSymbols(d3dSymbols)) {
      NS_ERROR(
          "EGL supports ANGLE_surface_d3d_texture_2d_share_handle without "
          "exposing its functions!");
      MarkExtensionUnsupported(ANGLE_surface_d3d_texture_2d_share_handle);
    }
  }

  if (IsExtensionSupported(KHR_fence_sync)) {
    const SymLoadStruct syncSymbols[] = {
        SYMBOL(CreateSyncKHR), SYMBOL(DestroySyncKHR),
        SYMBOL(ClientWaitSyncKHR), SYMBOL(GetSyncAttribKHR), END_OF_SYMBOLS};
    if (!fnLoadSymbols(syncSymbols)) {
      NS_ERROR("EGL supports KHR_fence_sync without exposing its functions!");
      MarkExtensionUnsupported(KHR_fence_sync);
    }
  }

  if (IsExtensionSupported(KHR_image) || IsExtensionSupported(KHR_image_base)) {
    const SymLoadStruct imageSymbols[] = {
        SYMBOL(CreateImageKHR), SYMBOL(DestroyImageKHR), END_OF_SYMBOLS};
    if (!fnLoadSymbols(imageSymbols)) {
      NS_ERROR("EGL supports KHR_image(_base) without exposing its functions!");
      MarkExtensionUnsupported(KHR_image);
      MarkExtensionUnsupported(KHR_image_base);
      MarkExtensionUnsupported(KHR_image_pixmap);
    }
  } else {
    MarkExtensionUnsupported(KHR_image_pixmap);
  }

  if (IsExtensionSupported(ANDROID_native_fence_sync)) {
    const SymLoadStruct nativeFenceSymbols[] = {SYMBOL(DupNativeFenceFDANDROID),
                                                END_OF_SYMBOLS};
    if (!fnLoadSymbols(nativeFenceSymbols)) {
      NS_ERROR(
          "EGL supports ANDROID_native_fence_sync without exposing its "
          "functions!");
      MarkExtensionUnsupported(ANDROID_native_fence_sync);
    }
  }

  if (IsExtensionSupported(KHR_stream)) {
    const SymLoadStruct streamSymbols[] = {
        SYMBOL(CreateStreamKHR), SYMBOL(DestroyStreamKHR),
        SYMBOL(QueryStreamKHR), END_OF_SYMBOLS};
    if (!fnLoadSymbols(streamSymbols)) {
      NS_ERROR("EGL supports KHR_stream without exposing its functions!");
      MarkExtensionUnsupported(KHR_stream);
    }
  }

  if (IsExtensionSupported(KHR_stream_consumer_gltexture)) {
    const SymLoadStruct streamConsumerSymbols[] = {
        SYMBOL(StreamConsumerGLTextureExternalKHR),
        SYMBOL(StreamConsumerAcquireKHR), SYMBOL(StreamConsumerReleaseKHR),
        END_OF_SYMBOLS};
    if (!fnLoadSymbols(streamConsumerSymbols)) {
      NS_ERROR(
          "EGL supports KHR_stream_consumer_gltexture without exposing its "
          "functions!");
      MarkExtensionUnsupported(KHR_stream_consumer_gltexture);
    }
  }

  if (IsExtensionSupported(EXT_device_query)) {
    const SymLoadStruct queryDisplaySymbols[] = {SYMBOL(QueryDisplayAttribEXT),
                                                 SYMBOL(QueryDeviceAttribEXT),
                                                 END_OF_SYMBOLS};
    if (!fnLoadSymbols(queryDisplaySymbols)) {
      NS_ERROR("EGL supports EXT_device_query without exposing its functions!");
      MarkExtensionUnsupported(EXT_device_query);
    }
  }

  if (IsExtensionSupported(NV_stream_consumer_gltexture_yuv)) {
    const SymLoadStruct nvStreamSymbols[] = {
        SYMBOL(StreamConsumerGLTextureExternalAttribsNV), END_OF_SYMBOLS};
    if (!fnLoadSymbols(nvStreamSymbols)) {
      NS_ERROR(
          "EGL supports NV_stream_consumer_gltexture_yuv without exposing its "
          "functions!");
      MarkExtensionUnsupported(NV_stream_consumer_gltexture_yuv);
    }
  }

  if (IsExtensionSupported(ANGLE_stream_producer_d3d_texture)) {
    const SymLoadStruct nvStreamSymbols[] = {
        SYMBOL(CreateStreamProducerD3DTextureANGLE),
        SYMBOL(StreamPostD3DTextureANGLE), END_OF_SYMBOLS};
    if (!fnLoadSymbols(nvStreamSymbols)) {
      NS_ERROR(
          "EGL supports ANGLE_stream_producer_d3d_texture without exposing its "
          "functions!");
      MarkExtensionUnsupported(ANGLE_stream_producer_d3d_texture);
    }
  }

  if (IsExtensionSupported(KHR_surfaceless_context)) {
    const auto vendor = fQueryString(mEGLDisplay, LOCAL_EGL_VENDOR);

    // Bug 1464610: Mali T720 (Amazon Fire 8 HD) claims to support this
    // extension, but if you actually eglMakeCurrent() with EGL_NO_SURFACE, it
    // fails to render anything when a real surface is provided later on. We
    // only have the EGL vendor available here, so just avoid using this
    // extension on all Mali devices.
    if (strcmp((const char*)vendor, "ARM") == 0) {
      MarkExtensionUnsupported(KHR_surfaceless_context);
    }
  }

  mInitialized = true;
  reporter.SetSuccessful();
  return true;
}

#undef SYMBOL
#undef END_OF_SYMBOLS

void GLLibraryEGL::Shutdown() {
  if (this != sEGLLibrary) {
    return;
  }
  if (mEGLDisplay) {
    fTerminate(mEGLDisplay);
    mEGLDisplay = EGL_NO_DISPLAY;
  }
  mSymbols = {};
  sEGLLibrary = nullptr;
}

EGLDisplay GLLibraryEGL::CreateDisplay(bool forceAccel,
                                       const nsCOMPtr<nsIGfxInfo>& gfxInfo,
                                       nsACString* const out_failureId) {
  MOZ_ASSERT(!mInitialized);

  EGLDisplay chosenDisplay = nullptr;

  if (IsExtensionSupported(ANGLE_platform_angle_d3d)) {
    nsCString accelAngleFailureId;
    bool accelAngleSupport =
        IsAccelAngleSupported(gfxInfo, &accelAngleFailureId);
    bool shouldTryAccel = forceAccel || accelAngleSupport;
    bool shouldTryWARP = !forceAccel;  // Only if ANGLE not supported or fails

    // If WARP preferred, will override ANGLE support
    if (StaticPrefs::webgl_angle_force_warp()) {
      shouldTryWARP = true;
      shouldTryAccel = false;
      if (accelAngleFailureId.IsEmpty()) {
        accelAngleFailureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_FORCE_WARP");
      }
    }

    // Hardware accelerated ANGLE path (supported or force accel)
    if (shouldTryAccel) {
      chosenDisplay = GetAndInitDisplayForAccelANGLE(*this, out_failureId);
    }

    // Report the acceleration status to telemetry
    if (!chosenDisplay) {
      if (accelAngleFailureId.IsEmpty()) {
        Telemetry::Accumulate(
            Telemetry::CANVAS_WEBGL_ACCL_FAILURE_ID,
            NS_LITERAL_CSTRING("FEATURE_FAILURE_ACCL_ANGLE_UNKNOWN"));
      } else {
        Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_ACCL_FAILURE_ID,
                              accelAngleFailureId);
      }
    } else {
      Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_ACCL_FAILURE_ID,
                            NS_LITERAL_CSTRING("SUCCESS"));
    }

    // Fallback to a WARP display if ANGLE fails, or if WARP is forced
    if (!chosenDisplay && shouldTryWARP) {
      chosenDisplay = GetAndInitWARPDisplay(*this, EGL_DEFAULT_DISPLAY);
      if (!chosenDisplay) {
        if (out_failureId->IsEmpty()) {
          *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WARP_FALLBACK");
        }
        NS_ERROR("Fallback WARP context failed to initialize.");
        return nullptr;
      }
      mIsWARP = true;
    }
  } else {
    void* nativeDisplay = EGL_DEFAULT_DISPLAY;
#ifdef MOZ_WAYLAND
    // Some drivers doesn't support EGL_DEFAULT_DISPLAY
    GdkDisplay* gdkDisplay = gdk_display_get_default();
    if (!GDK_IS_X11_DISPLAY(gdkDisplay)) {
      nativeDisplay = WaylandDisplayGetWLDisplay(gdkDisplay);
      if (!nativeDisplay) {
        NS_WARNING("Failed to get wl_display.");
        return nullptr;
      }
    }
#endif
    chosenDisplay = GetAndInitDisplay(*this, nativeDisplay);
  }

  if (!chosenDisplay) {
    if (out_failureId->IsEmpty()) {
      *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_NO_DISPLAY");
    }
    NS_WARNING("Failed to initialize a display.");
    return nullptr;
  }
  return chosenDisplay;
}

template <size_t N>
static void MarkExtensions(const char* rawExtString, bool shouldDumpExts,
                           const char* extType, std::bitset<N>* const out) {
  MOZ_ASSERT(rawExtString);

  const nsDependentCString extString(rawExtString);

  std::vector<nsCString> extList;
  SplitByChar(extString, ' ', &extList);

  if (shouldDumpExts) {
    printf_stderr("%u EGL %s extensions: (*: recognized)\n",
                  (uint32_t)extList.size(), extType);
  }

  MarkBitfieldByStrings(extList, shouldDumpExts, sEGLExtensionNames, out);
}

void GLLibraryEGL::InitClientExtensions() {
  const bool shouldDumpExts = GLContext::ShouldDumpExts();

  const char* rawExtString = nullptr;

#ifndef ANDROID
  // Bug 1209612: Crashes on a number of android drivers.
  // Ideally we would only blocklist this there, but for now we don't need the
  // client extension list on ANDROID (we mostly need it on ANGLE), and we'd
  // rather not crash.
  rawExtString = (const char*)fQueryString(nullptr, LOCAL_EGL_EXTENSIONS);
#endif

  if (!rawExtString) {
    if (shouldDumpExts) {
      printf_stderr("No EGL client extensions.\n");
    }
    return;
  }

  MarkExtensions(rawExtString, shouldDumpExts, "client", &mAvailableExtensions);
}

void GLLibraryEGL::InitDisplayExtensions() {
  MOZ_ASSERT(mEGLDisplay);

  const bool shouldDumpExts = GLContext::ShouldDumpExts();

  const auto rawExtString =
      (const char*)fQueryString(mEGLDisplay, LOCAL_EGL_EXTENSIONS);
  if (!rawExtString) {
    NS_WARNING("Failed to query EGL display extensions!.");
    return;
  }

  MarkExtensions(rawExtString, shouldDumpExts, "display",
                 &mAvailableExtensions);
}

void GLLibraryEGL::DumpEGLConfig(EGLConfig cfg) {
  int attrval;
  int err;

#define ATTR(_x)                                                   \
  do {                                                             \
    fGetConfigAttrib(mEGLDisplay, cfg, LOCAL_EGL_##_x, &attrval);  \
    if ((err = fGetError()) != 0x3000) {                           \
      printf_stderr("  %s: ERROR (0x%04x)\n", #_x, err);           \
    } else {                                                       \
      printf_stderr("  %s: %d (0x%04x)\n", #_x, attrval, attrval); \
    }                                                              \
  } while (0)

  printf_stderr("EGL Config: %d [%p]\n", (int)(intptr_t)cfg, cfg);

  ATTR(BUFFER_SIZE);
  ATTR(ALPHA_SIZE);
  ATTR(BLUE_SIZE);
  ATTR(GREEN_SIZE);
  ATTR(RED_SIZE);
  ATTR(DEPTH_SIZE);
  ATTR(STENCIL_SIZE);
  ATTR(CONFIG_CAVEAT);
  ATTR(CONFIG_ID);
  ATTR(LEVEL);
  ATTR(MAX_PBUFFER_HEIGHT);
  ATTR(MAX_PBUFFER_PIXELS);
  ATTR(MAX_PBUFFER_WIDTH);
  ATTR(NATIVE_RENDERABLE);
  ATTR(NATIVE_VISUAL_ID);
  ATTR(NATIVE_VISUAL_TYPE);
  ATTR(PRESERVED_RESOURCES);
  ATTR(SAMPLES);
  ATTR(SAMPLE_BUFFERS);
  ATTR(SURFACE_TYPE);
  ATTR(TRANSPARENT_TYPE);
  ATTR(TRANSPARENT_RED_VALUE);
  ATTR(TRANSPARENT_GREEN_VALUE);
  ATTR(TRANSPARENT_BLUE_VALUE);
  ATTR(BIND_TO_TEXTURE_RGB);
  ATTR(BIND_TO_TEXTURE_RGBA);
  ATTR(MIN_SWAP_INTERVAL);
  ATTR(MAX_SWAP_INTERVAL);
  ATTR(LUMINANCE_SIZE);
  ATTR(ALPHA_MASK_SIZE);
  ATTR(COLOR_BUFFER_TYPE);
  ATTR(RENDERABLE_TYPE);
  ATTR(CONFORMANT);

#undef ATTR
}

void GLLibraryEGL::DumpEGLConfigs() {
  int nc = 0;
  fGetConfigs(mEGLDisplay, nullptr, 0, &nc);
  EGLConfig* ec = new EGLConfig[nc];
  fGetConfigs(mEGLDisplay, ec, nc, &nc);

  for (int i = 0; i < nc; ++i) {
    printf_stderr("========= EGL Config %d ========\n", i);
    DumpEGLConfig(ec[i]);
  }

  delete[] ec;
}

static bool ShouldTrace() {
  static bool ret = gfxEnv::GlDebugVerbose();
  return ret;
}

void BeforeEGLCall(const char* glFunction) {
  if (ShouldTrace()) {
    printf_stderr("[egl] > %s\n", glFunction);
  }
}

void AfterEGLCall(const char* glFunction) {
  if (ShouldTrace()) {
    printf_stderr("[egl] < %s\n", glFunction);
  }
}

} /* namespace gl */
} /* namespace mozilla */