Reland "EGL: Move initialization of EGL inside a DisplayEGL class"

This is a reland of commit 9fba7ea8e943cb9396c356c4becb6842ef0710d5

Two fixes were needed for this CL:

 - Do not call eglTerminate in Dawn as EGL may return the same display
   multiple times with eglGetDisplay, such that terminating the display
   in Dawn could terminate the display used by someone else as well.
   (This is a known design bug in EGL unfortunately).
 - Explicitly pass in the EGLDisplay in Chromium during GPU info
   collection. Previously Dawn tried to get a display using
   eglGetCurrentDisplay() but this seems brittle compared to passing the
   display explicitly.

Original change's description:
> EGL: Move initialization of EGL inside a DisplayEGL class
>
> The moves initialization and checks of EGLDisplays to be in its own
> class instead of split across opengl::Backend and
> opengl::PhysicalDevice. That class also contains data that's per EGL
> connection such as the EGLDisplay and the procs. This removes the need
> for opengl::Backend to return a single PhysicalDevice ever.
>
> The ContextEGL class now only deals with creating context and doing
> MakeCurrent, with some of its validation checks moved to classes using
> it. This will make it easier to dynamically handle EGL extension being
> present (e.g. to support webgpu.h when EGL_KHR_image_base is not present).
>
> Finally the encapsulation in these classes is step towards supporting
> more embedder APIs in the future (like GLX).
>
> Bug: 344814083
> Bug: 42241384
> Change-Id: I34d7c24c518a791da920a640a5764458073f976e
> Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/193160
> Commit-Queue: Corentin Wallez <cwallez@chromium.org>
> Reviewed-by: Austin Eng <enga@chromium.org>
> Reviewed-by: Stephen White <senorblanco@chromium.org>

Bug: 344814083
Bug: 42241384
Change-Id: I76f904be88696a6a90b21e2c55637f3451054c00
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/194780
Auto-Submit: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn
index cab023e..5f4d886 100644
--- a/src/dawn/native/BUILD.gn
+++ b/src/dawn/native/BUILD.gn
@@ -727,6 +727,8 @@
       "opengl/ContextEGL.h",
       "opengl/DeviceGL.cpp",
       "opengl/DeviceGL.h",
+      "opengl/DisplayEGL.cpp",
+      "opengl/DisplayEGL.h",
       "opengl/EGLFunctions.cpp",
       "opengl/EGLFunctions.h",
       "opengl/Forward.h",
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt
index e07f5c7..c588d71 100644
--- a/src/dawn/native/CMakeLists.txt
+++ b/src/dawn/native/CMakeLists.txt
@@ -601,6 +601,8 @@
         "opengl/ContextEGL.h"
         "opengl/DeviceGL.cpp"
         "opengl/DeviceGL.h"
+        "opengl/DisplayEGL.cpp"
+        "opengl/DisplayEGL.h"
         "opengl/EGLFunctions.cpp"
         "opengl/EGLFunctions.h"
         "opengl/Forward.h"
diff --git a/src/dawn/native/opengl/BackendGL.cpp b/src/dawn/native/opengl/BackendGL.cpp
index 3c32d8b..5e8a1b3 100644
--- a/src/dawn/native/opengl/BackendGL.cpp
+++ b/src/dawn/native/opengl/BackendGL.cpp
@@ -27,16 +27,28 @@
 
 #include "dawn/native/opengl/BackendGL.h"
 
+#include <memory>
 #include <string>
+#include <utility>
 
 #include "dawn/native/ChainUtils.h"
 #include "dawn/native/Instance.h"
 #include "dawn/native/OpenGLBackend.h"
+#include "dawn/native/opengl/DisplayEGL.h"
 #include "dawn/native/opengl/PhysicalDeviceGL.h"
 
 namespace dawn::native::opengl {
 
-// Implementation of the OpenGL backend's BackendConnection
+namespace {
+#if DAWN_PLATFORM_IS(WINDOWS)
+const char* kEGLLib = "libEGL.dll";
+#elif DAWN_PLATFORM_IS(MACOS)
+const char* kEGLLib = "libEGL.dylib";
+#else
+const char* kEGLLib = "libEGL.so";
+#endif
+
+}  // anonymous namespace
 
 Backend::Backend(InstanceBase* instance, wgpu::BackendType backendType)
     : BackendConnection(instance, backendType) {}
@@ -51,61 +63,48 @@
         return {};
     }
 
-    EGLGetProcProc getProc = nullptr;
-    EGLDisplay display = EGL_NO_DISPLAY;
+    std::vector<Ref<PhysicalDeviceBase>> devices;
+
+    // A helper function performing checks on the display we're trying to use, and adding the
+    // physical device from it to the list returned by the discovery.
+    auto AppendNewDeviceFrom =
+        [&](ResultOrError<std::unique_ptr<DisplayEGL>> maybeDisplay) -> MaybeError {
+        std::unique_ptr<DisplayEGL> display;
+        DAWN_TRY_ASSIGN(display, std::move(maybeDisplay));
+
+        if (!display->egl.HasExt(EGLExt::CreateContextRobustness)) {
+            return DAWN_VALIDATION_ERROR("EGL_EXT_create_context_robustness is required.");
+        }
+        if (!display->egl.HasExt(EGLExt::FenceSync) && !display->egl.HasExt(EGLExt::ReusableSync)) {
+            return DAWN_INTERNAL_ERROR(
+                "EGL_KHR_fence_sync or EGL_KHR_reusable_sync must be supported");
+        }
+
+        Ref<PhysicalDevice> device;
+        DAWN_TRY_ASSIGN(device, PhysicalDevice::Create(GetType(), std::move(display)));
+        devices.push_back(device);
+
+        return {};
+    };
+
+    // A helper function used to give more context before printing the EGL loading error.
+    auto SwallowDiscoveryError = [&](MaybeError maybeError) {
+        if (maybeError.IsError()) {
+            std::unique_ptr<ErrorData> error = maybeError.AcquireError();
+            error->AppendContext("trying to discover a %s adapter.", GetType());
+            GetInstance()->ConsumedErrorAndWarnOnce(std::move(error));
+        }
+    };
 
     if (auto* glGetProcOptions = options.Get<RequestAdapterOptionsGetGLProc>()) {
-        getProc = glGetProcOptions->getProc;
-        display = glGetProcOptions->display;
+        SwallowDiscoveryError(AppendNewDeviceFrom(DisplayEGL::CreateFromProcAndDisplay(
+            GetType(), glGetProcOptions->getProc, glGetProcOptions->display)));
+    } else {
+        SwallowDiscoveryError(
+            AppendNewDeviceFrom(DisplayEGL::CreateFromDynamicLoading(GetType(), kEGLLib)));
     }
 
-    if (getProc == nullptr) {
-        // getProc not passed. Try to load it from libEGL.
-
-#if DAWN_PLATFORM_IS(WINDOWS)
-        const char* eglLib = "libEGL.dll";
-#elif DAWN_PLATFORM_IS(MACOS)
-        const char* eglLib = "libEGL.dylib";
-#else
-        const char* eglLib = "libEGL.so";
-#endif
-        std::string err;
-        if (!mLibEGL.Valid() && !mLibEGL.Open(eglLib, &err)) {
-            GetInstance()->ConsumedErrorAndWarnOnce(
-                DAWN_VALIDATION_ERROR("Failed to load %s: %s", eglLib, err.c_str()));
-            return {};
-        }
-
-        getProc = reinterpret_cast<decltype(getProc)>(mLibEGL.GetProc("eglGetProcAddress"));
-        if (!getProc) {
-            GetInstance()->ConsumedErrorAndWarnOnce(
-                DAWN_VALIDATION_ERROR("eglGetProcAddress return nullptr"));
-            return {};
-        }
-    }
-
-    return DiscoverPhysicalDevicesWithProcs(getProc, display);
-}
-
-std::vector<Ref<PhysicalDeviceBase>> Backend::DiscoverPhysicalDevicesWithProcs(
-    EGLGetProcProc getProc,
-    EGLDisplay display) {
-    // TODO(cwallez@chromium.org): For now only create a single OpenGL physicalDevice because don't
-    // know how to handle MakeCurrent.
-    if (mPhysicalDevice != nullptr && (mGetProc != getProc || mDisplay != display)) {
-        GetInstance()->ConsumedErrorAndWarnOnce(
-            DAWN_VALIDATION_ERROR("The OpenGL backend can only create a single physicalDevice."));
-        return {};
-    }
-    if (mPhysicalDevice == nullptr) {
-        if (GetInstance()->ConsumedErrorAndWarnOnce(
-                PhysicalDevice::Create(GetType(), getProc, display), &mPhysicalDevice)) {
-            return {};
-        }
-        mGetProc = getProc;
-        mDisplay = display;
-    }
-    return {mPhysicalDevice};
+    return devices;
 }
 
 BackendConnection* Connect(InstanceBase* instance, wgpu::BackendType backendType) {
diff --git a/src/dawn/native/opengl/BackendGL.h b/src/dawn/native/opengl/BackendGL.h
index c8dc2dc..6cba273 100644
--- a/src/dawn/native/opengl/BackendGL.h
+++ b/src/dawn/native/opengl/BackendGL.h
@@ -30,30 +30,16 @@
 
 #include <vector>
 
-#include "dawn/common/DynamicLib.h"
 #include "dawn/native/BackendConnection.h"
-#include "dawn/native/OpenGLBackend.h"
-
-using EGLDisplay = void*;
 
 namespace dawn::native::opengl {
 
-class PhysicalDevice;
 class Backend : public BackendConnection {
   public:
     Backend(InstanceBase* instance, wgpu::BackendType backendType);
 
     std::vector<Ref<PhysicalDeviceBase>> DiscoverPhysicalDevices(
         const UnpackedPtr<RequestAdapterOptions>& options) override;
-
-  private:
-    std::vector<Ref<PhysicalDeviceBase>> DiscoverPhysicalDevicesWithProcs(EGLGetProcProc getProc,
-                                                                          EGLDisplay display);
-
-    Ref<PhysicalDevice> mPhysicalDevice = nullptr;
-    EGLGetProcProc mGetProc;
-    EGLDisplay mDisplay;
-    DynamicLib mLibEGL;
 };
 
 }  // namespace dawn::native::opengl
diff --git a/src/dawn/native/opengl/ContextEGL.cpp b/src/dawn/native/opengl/ContextEGL.cpp
index 9897de4..fb14de9 100644
--- a/src/dawn/native/opengl/ContextEGL.cpp
+++ b/src/dawn/native/opengl/ContextEGL.cpp
@@ -29,8 +29,10 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
+#include "dawn/native/opengl/DisplayEGL.h"
 #include "dawn/native/opengl/UtilsEGL.h"
 
 #ifndef EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE
@@ -39,92 +41,92 @@
 
 namespace dawn::native::opengl {
 
-ResultOrError<std::unique_ptr<ContextEGL>> ContextEGL::Create(const EGLFunctions& egl,
-                                                              EGLenum api,
-                                                              EGLDisplay display,
+// static
+ResultOrError<std::unique_ptr<ContextEGL>> ContextEGL::Create(const DisplayEGL* display,
+                                                              wgpu::BackendType backend,
+                                                              bool useRobustness,
                                                               bool useANGLETextureSharing) {
-    EGLint renderableType = api == EGL_OPENGL_ES_API ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_BIT;
+    auto context = std::make_unique<ContextEGL>(display);
+    DAWN_TRY(context->Initialize(backend, useRobustness, useANGLETextureSharing));
+    return std::move(context);
+}
 
-    // We require at least EGL 1.4.
-    DAWN_INVALID_IF(
-        egl.GetMajorVersion() < 1 || (egl.GetMajorVersion() == 1 && egl.GetMinorVersion() < 4),
-        "EGL version (%u.%u) must be at least 1.4", egl.GetMajorVersion(), egl.GetMinorVersion());
+ContextEGL::ContextEGL(const DisplayEGL* display) : mDisplay(display) {}
+
+ContextEGL::~ContextEGL() {
+    if (mContext != EGL_NO_CONTEXT) {
+        mDisplay->egl.DestroyContext(mDisplay->GetDisplay(), mContext);
+        mContext = EGL_NO_CONTEXT;
+    }
+}
+
+MaybeError ContextEGL::Initialize(wgpu::BackendType backend,
+                                  bool useRobustness,
+                                  bool useANGLETextureSharing) {
+    const EGLFunctions& egl = mDisplay->egl;
 
     // Since we're creating a surfaceless context, the only thing we really care
     // about is the RENDERABLE_TYPE.
-    EGLint config_attribs[] = {EGL_RENDERABLE_TYPE, renderableType, EGL_NONE};
+    EGLint configAttribs[] = {EGL_RENDERABLE_TYPE, mDisplay->GetAPIBit(), EGL_NONE};
 
-    EGLint num_config;
+    EGLint numConfig;
     EGLConfig config;
-    DAWN_TRY(CheckEGL(egl, egl.ChooseConfig(display, config_attribs, &config, 1, &num_config),
-                      "eglChooseConfig"));
+    DAWN_TRY(CheckEGL(
+        egl, egl.ChooseConfig(mDisplay->GetDisplay(), configAttribs, &config, 1, &numConfig),
+        "eglChooseConfig"));
 
-    DAWN_INVALID_IF(num_config == 0, "eglChooseConfig returned zero configs");
+    DAWN_INVALID_IF(numConfig == 0, "eglChooseConfig returned zero configs");
 
-    DAWN_TRY(CheckEGL(egl, egl.BindAPI(api), "eglBindAPI"));
-
-    if (!egl.HasExt(EGLExt::ImageBase)) {
-        return DAWN_INTERNAL_ERROR("EGL_KHR_image_base is required.");
-    }
-    if (!egl.HasExt(EGLExt::CreateContextRobustness)) {
-        return DAWN_INTERNAL_ERROR("EGL_EXT_create_context_robustness is required.");
-    }
-
-    if (!egl.HasExt(EGLExt::FenceSync) && !egl.HasExt(EGLExt::ReusableSync)) {
-        return DAWN_INTERNAL_ERROR("EGL_KHR_fence_sync or EGL_KHR_reusable_sync must be supported");
-    }
+    DAWN_TRY(CheckEGL(egl, egl.BindAPI(mDisplay->GetAPIEnum()), "eglBindAPI"));
 
     int major, minor;
-    if (api == EGL_OPENGL_ES_API) {
-        major = 3;
-        minor = 1;
-    } else {
-        major = 4;
-        minor = 4;
+    switch (backend) {
+        case wgpu::BackendType::OpenGLES:
+            major = 3;
+            minor = 1;
+            break;
+        case wgpu::BackendType::OpenGL:
+            major = 4;
+            minor = 4;
+            break;
+        default:
+            DAWN_UNREACHABLE();
     }
-    std::vector<EGLint> attrib_list{
+
+    std::vector<EGLint> attribs{
         EGL_CONTEXT_MAJOR_VERSION,
         major,
         EGL_CONTEXT_MINOR_VERSION,
         minor,
-        EGL_CONTEXT_OPENGL_ROBUST_ACCESS,  // Core in EGL 1.5
-        EGL_TRUE,
     };
-    if (useANGLETextureSharing) {
-        if (!egl.HasExt(EGLExt::DisplayTextureShareGroup)) {
-            return DAWN_INTERNAL_ERROR(
-                "EGL_GL_ANGLE_display_texture_share_group must be supported to use GL texture "
-                "sharing");
+
+    if (useRobustness) {
+        DAWN_ASSERT(egl.HasExt(EGLExt::CreateContextRobustness));
+        // EGL_EXT_create_context_robustness is promoted to 1.5 but with a different enum value.
+        if (egl.GetMinorVersion() >= 5) {
+            attribs.push_back(EGL_CONTEXT_OPENGL_ROBUST_ACCESS);
+        } else {
+            attribs.push_back(EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT);
         }
-        attrib_list.push_back(EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE);
-        attrib_list.push_back(EGL_TRUE);
+        attribs.push_back(EGL_TRUE);
     }
-    attrib_list.push_back(EGL_NONE);
 
-    EGLContext context = egl.CreateContext(display, config, EGL_NO_CONTEXT, attrib_list.data());
-    DAWN_TRY(CheckEGL(egl, context != EGL_NO_CONTEXT, "eglCreateContext"));
+    if (useANGLETextureSharing) {
+        DAWN_ASSERT(egl.HasExt(EGLExt::DisplayTextureShareGroup));
+        attribs.push_back(EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE);
+        attribs.push_back(EGL_TRUE);
+    }
 
-    return std::unique_ptr<ContextEGL>(new ContextEGL(egl, display, context));
+    attribs.push_back(EGL_NONE);
+
+    mContext = egl.CreateContext(mDisplay->GetDisplay(), config, EGL_NO_CONTEXT, attribs.data());
+    return CheckEGL(egl, mContext != EGL_NO_CONTEXT, "eglCreateContext");
 }
 
-ContextEGL::ContextEGL(const EGLFunctions& functions, EGLDisplay display, EGLContext context)
-    : mEgl(functions), mDisplay(display), mContext(context) {}
-
 void ContextEGL::MakeCurrent() {
-    EGLBoolean success = mEgl.MakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, mContext);
-    DAWN_ASSERT(success == EGL_TRUE);
-}
-
-EGLDisplay ContextEGL::GetEGLDisplay() const {
-    return mDisplay;
-}
-
-const EGLFunctions& ContextEGL::GetEGL() const {
-    return mEgl;
-}
-
-ContextEGL::~ContextEGL() {
-    mEgl.DestroyContext(mDisplay, mContext);
+    EGLBoolean success =
+        mDisplay->egl.MakeCurrent(mDisplay->GetDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, mContext);
+    IgnoreErrors(CheckEGL(mDisplay->egl, success == EGL_TRUE, "eglMakeCurrent"));
 }
 
 }  // namespace dawn::native::opengl
diff --git a/src/dawn/native/opengl/ContextEGL.h b/src/dawn/native/opengl/ContextEGL.h
index 44d16bd..d17288e 100644
--- a/src/dawn/native/opengl/ContextEGL.h
+++ b/src/dawn/native/opengl/ContextEGL.h
@@ -37,24 +37,27 @@
 
 namespace dawn::native::opengl {
 
+class DisplayEGL;
+
 class ContextEGL : NonMovable {
   public:
-    static ResultOrError<std::unique_ptr<ContextEGL>> Create(const EGLFunctions& functions,
-                                                             EGLenum api,
-                                                             EGLDisplay display,
+    static ResultOrError<std::unique_ptr<ContextEGL>> Create(const DisplayEGL* display,
+                                                             wgpu::BackendType backend,
+                                                             bool useRobustness,
                                                              bool useANGLETextureSharing);
+
+    explicit ContextEGL(const DisplayEGL* display);
     ~ContextEGL();
 
+    MaybeError Initialize(wgpu::BackendType backend,
+                          bool useRobustness,
+                          bool useANGLETextureSharing);
+
     void MakeCurrent();
-    EGLDisplay GetEGLDisplay() const;
-    const EGLFunctions& GetEGL() const;
 
   private:
-    ContextEGL(const EGLFunctions& functions, EGLDisplay display, EGLContext context);
-
-    const EGLFunctions mEgl;
-    EGLDisplay mDisplay;
-    EGLContext mContext;
+    const DisplayEGL* mDisplay;
+    EGLContext mContext = EGL_NO_CONTEXT;
 };
 
 }  // namespace dawn::native::opengl
diff --git a/src/dawn/native/opengl/DeviceGL.cpp b/src/dawn/native/opengl/DeviceGL.cpp
index c8b3db8..34e70bf 100644
--- a/src/dawn/native/opengl/DeviceGL.cpp
+++ b/src/dawn/native/opengl/DeviceGL.cpp
@@ -40,6 +40,8 @@
 #include "dawn/native/opengl/CommandBufferGL.h"
 #include "dawn/native/opengl/ComputePipelineGL.h"
 #include "dawn/native/opengl/ContextEGL.h"
+#include "dawn/native/opengl/DisplayEGL.h"
+#include "dawn/native/opengl/PhysicalDeviceGL.h"
 #include "dawn/native/opengl/PipelineLayoutGL.h"
 #include "dawn/native/opengl/QuerySetGL.h"
 #include "dawn/native/opengl/QueueGL.h"
@@ -314,6 +316,10 @@
     if (ConsumedError(ValidateTextureCanBeWrapped(textureDescriptor))) {
         return nullptr;
     }
+    // The EGLImage was created from outside of Dawn so it must be on the same display that was
+    // provided to create the device. The best check we can do is that we indeed have
+    // EGL_KHR_image_base.
+    DAWN_ASSERT(GetEGL(false).HasExt(EGLExt::ImageBase));
 
     GLuint tex;
     gl.GenTextures(1, &tex);
@@ -443,11 +449,11 @@
         mContext->MakeCurrent();
         ToBackend(GetQueue())->OnGLUsed();
     }
-    return mContext->GetEGL();
+    return ToBackend(GetPhysicalDevice())->GetDisplay()->egl;
 }
 
 EGLDisplay Device::GetEGLDisplay() const {
-    return mContext->GetEGLDisplay();
+    return ToBackend(GetPhysicalDevice())->GetDisplay()->GetDisplay();
 }
 
 ContextEGL* Device::GetContext() const {
diff --git a/src/dawn/native/opengl/DisplayEGL.cpp b/src/dawn/native/opengl/DisplayEGL.cpp
new file mode 100644
index 0000000..90aca12
--- /dev/null
+++ b/src/dawn/native/opengl/DisplayEGL.cpp
@@ -0,0 +1,127 @@
+// Copyright 2024 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 "dawn/native/opengl/DisplayEGL.h"
+
+#include <string>
+#include <utility>
+
+namespace dawn::native::opengl {
+
+// static
+ResultOrError<std::unique_ptr<DisplayEGL>> DisplayEGL::CreateFromDynamicLoading(
+    wgpu::BackendType backend,
+    const char* libName) {
+    auto display = std::make_unique<DisplayEGL>(backend);
+    DAWN_TRY(display->InitializeWithDynamicLoading(libName));
+    return std::move(display);
+}
+
+// static
+ResultOrError<std::unique_ptr<DisplayEGL>> DisplayEGL::CreateFromProcAndDisplay(
+    wgpu::BackendType backend,
+    EGLGetProcProc getProc,
+    EGLDisplay eglDisplay) {
+    auto display = std::make_unique<DisplayEGL>(backend);
+    DAWN_TRY(display->InitializeWithProcAndDisplay(getProc, eglDisplay));
+    return std::move(display);
+}
+
+DisplayEGL::DisplayEGL(wgpu::BackendType backend) : egl(mFunctions) {
+    switch (backend) {
+        case wgpu::BackendType::OpenGL:
+            mApiEnum = EGL_OPENGL_API;
+            mApiBit = EGL_OPENGL_BIT;
+            break;
+        case wgpu::BackendType::OpenGLES:
+            mApiEnum = EGL_OPENGL_ES_API;
+            mApiBit = EGL_OPENGL_ES3_BIT;
+            break;
+        default:
+            DAWN_UNREACHABLE();
+    }
+}
+
+DisplayEGL::~DisplayEGL() {
+    if (mDisplay != EGL_NO_DISPLAY) {
+        // Note that we don't call eglTerminate on purpose here. Calls to eglGetDisplay may return
+        // the same display multiple times in which case eglInitialize is a noop (if the display is
+        // already initialized) and eglTerminate on one of the handles would terminate the
+        // EGLDisplay for everyone.
+        mDisplay = EGL_NO_DISPLAY;
+    }
+}
+
+MaybeError DisplayEGL::InitializeWithDynamicLoading(const char* libName) {
+    std::string err;
+    if (!mLib.Valid() && !mLib.Open(libName, &err)) {
+        return DAWN_VALIDATION_ERROR("Failed to load %s: \"%s\".", libName, err.c_str());
+    }
+
+    EGLGetProcProc getProc = reinterpret_cast<EGLGetProcProc>(mLib.GetProc("eglGetProcAddress"));
+    if (!getProc) {
+        return DAWN_VALIDATION_ERROR("Couldn't get \"eglGetProcAddress\" from %s.", libName);
+    }
+
+    return InitializeWithProcAndDisplay(getProc, EGL_NO_DISPLAY);
+}
+
+MaybeError DisplayEGL::InitializeWithProcAndDisplay(EGLGetProcProc getProc, EGLDisplay display) {
+    // Load the EGL functions.
+    DAWN_TRY(mFunctions.LoadClientProcs(getProc));
+
+    mDisplay = display;
+    if (mDisplay == EGL_NO_DISPLAY) {
+        mDisplay = egl.GetDisplay(EGL_DEFAULT_DISPLAY);
+    }
+    if (mDisplay == EGL_NO_DISPLAY) {
+        return DAWN_VALIDATION_ERROR("Couldn't create the default EGL display.");
+    }
+
+    DAWN_TRY(mFunctions.LoadDisplayProcs(mDisplay));
+
+    // We require at least EGL 1.4.
+    DAWN_INVALID_IF(
+        egl.GetMajorVersion() < 1 || (egl.GetMajorVersion() == 1 && egl.GetMinorVersion() < 4),
+        "EGL version (%u.%u) must be at least 1.4", egl.GetMajorVersion(), egl.GetMinorVersion());
+
+    return {};
+}
+
+EGLDisplay DisplayEGL::GetDisplay() const {
+    return mDisplay;
+}
+
+EGLint DisplayEGL::GetAPIEnum() const {
+    return mApiEnum;
+}
+
+EGLint DisplayEGL::GetAPIBit() const {
+    return mApiBit;
+}
+
+}  // namespace dawn::native::opengl
diff --git a/src/dawn/native/opengl/DisplayEGL.h b/src/dawn/native/opengl/DisplayEGL.h
new file mode 100644
index 0000000..c511b4f
--- /dev/null
+++ b/src/dawn/native/opengl/DisplayEGL.h
@@ -0,0 +1,75 @@
+// Copyright 2024 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.
+
+#ifndef SRC_DAWN_NATIVE_OPENGL_DISPLAYEGL_H_
+#define SRC_DAWN_NATIVE_OPENGL_DISPLAYEGL_H_
+
+#include <memory>
+
+#include "dawn/common/DynamicLib.h"
+#include "dawn/common/NonMovable.h"
+#include "dawn/common/egl_platform.h"
+#include "dawn/native/opengl/EGLFunctions.h"
+
+namespace dawn::native::opengl {
+
+// Represents a connection to an EGL driver, with its EGLDisplay, its functions and other metadata
+// global to EGL.
+class DisplayEGL : NonMovable {
+  public:
+    static ResultOrError<std::unique_ptr<DisplayEGL>> CreateFromDynamicLoading(
+        wgpu::BackendType backend,
+        const char* libName);
+
+    static ResultOrError<std::unique_ptr<DisplayEGL>>
+    CreateFromProcAndDisplay(wgpu::BackendType backend, EGLGetProcProc getProc, EGLDisplay display);
+
+    explicit DisplayEGL(wgpu::BackendType backend);
+    ~DisplayEGL();
+
+    // A convenience ref to avoid having to call an accessor function every time we need to use EGL
+    const EGLFunctions& egl;
+    EGLDisplay GetDisplay() const;
+    EGLint GetAPIEnum() const;
+    EGLint GetAPIBit() const;
+
+  private:
+    MaybeError InitializeWithDynamicLoading(const char* libName);
+    MaybeError InitializeWithProcAndDisplay(EGLGetProcProc getProc, EGLDisplay display);
+
+    DynamicLib mLib;
+
+    EGLFunctions mFunctions;
+    EGLDisplay mDisplay = EGL_NO_DISPLAY;
+
+    EGLint mApiEnum;
+    EGLint mApiBit;
+};
+
+}  // namespace dawn::native::opengl
+
+#endif  // SRC_DAWN_NATIVE_OPENGL_DISPLAYEGL_H_
diff --git a/src/dawn/native/opengl/PhysicalDeviceGL.cpp b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
index 0434fad..ecfe80b 100644
--- a/src/dawn/native/opengl/PhysicalDeviceGL.cpp
+++ b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
@@ -38,6 +38,7 @@
 #include "dawn/native/Instance.h"
 #include "dawn/native/opengl/ContextEGL.h"
 #include "dawn/native/opengl/DeviceGL.h"
+#include "dawn/native/opengl/DisplayEGL.h"
 
 namespace dawn::native::opengl {
 
@@ -91,46 +92,39 @@
 
 // static
 ResultOrError<Ref<PhysicalDevice>> PhysicalDevice::Create(wgpu::BackendType backendType,
-                                                          EGLGetProcProc getProc,
-                                                          EGLDisplay display) {
-    EGLFunctions egl;
-    DAWN_TRY(egl.LoadClientProcs(getProc));
+                                                          std::unique_ptr<DisplayEGL> display) {
+    const EGLFunctions& egl = display->egl;
+    EGLDisplay eglDisplay = display->GetDisplay();
 
-    if (display == EGL_NO_DISPLAY) {
-        display = egl.GetCurrentDisplay();
-    }
-    if (display == EGL_NO_DISPLAY) {
-        display = egl.GetDisplay(EGL_DEFAULT_DISPLAY);
-    }
-
-    DAWN_TRY(egl.LoadDisplayProcs(display));
-
-    EGLenum api = backendType == wgpu::BackendType::OpenGLES ? EGL_OPENGL_ES_API : EGL_OPENGL_API;
+    // 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(egl, api, display, false));
+    DAWN_TRY_ASSIGN(context, ContextEGL::Create(display.get(), backendType, /*useRobustness*/ false,
+                                                /*useANGLETextureSharing*/ false));
 
-    EGLContext prevDrawSurface = egl.GetCurrentSurface(EGL_DRAW);
-    EGLContext prevReadSurface = egl.GetCurrentSurface(EGL_READ);
+    EGLSurface prevDrawSurface = egl.GetCurrentSurface(EGL_DRAW);
+    EGLSurface prevReadSurface = egl.GetCurrentSurface(EGL_READ);
     EGLContext prevContext = egl.GetCurrentContext();
 
     context->MakeCurrent();
 
-    Ref<PhysicalDevice> physicalDevice = AcquireRef(new PhysicalDevice(backendType, display));
-    DAWN_TRY(physicalDevice->InitializeGLFunctions(getProc));
-    DAWN_TRY(physicalDevice->Initialize());
+    Ref<PhysicalDevice> physicalDevice =
+        AcquireRef(new PhysicalDevice(backendType, std::move(display)));
+    DAWN_TRY_WITH_CLEANUP(physicalDevice->Initialize(), {
+        egl.MakeCurrent(eglDisplay, prevDrawSurface, prevReadSurface, prevContext);
+    });
 
-    egl.MakeCurrent(display, prevDrawSurface, prevReadSurface, prevContext);
+    egl.MakeCurrent(eglDisplay, prevDrawSurface, prevReadSurface, prevContext);
+
     return physicalDevice;
 }
 
-PhysicalDevice::PhysicalDevice(wgpu::BackendType backendType, EGLDisplay display)
-    : PhysicalDeviceBase(backendType), mDisplay(display) {}
+PhysicalDevice::PhysicalDevice(wgpu::BackendType backendType, std::unique_ptr<DisplayEGL> display)
+    : PhysicalDeviceBase(backendType), mDisplay(std::move(display)) {}
 
-MaybeError PhysicalDevice::InitializeGLFunctions(EGLGetProcProc getProc) {
-    // Use getProc to populate the dispatch table
-    DAWN_TRY(mEGLFunctions.LoadClientProcs(getProc));
-    DAWN_TRY(mEGLFunctions.LoadDisplayProcs(mDisplay));
-    return mFunctions.Initialize(getProc);
+DisplayEGL* PhysicalDevice::GetDisplay() const {
+    return mDisplay.get();
 }
 
 bool PhysicalDevice::SupportsExternalImages() const {
@@ -139,6 +133,8 @@
 }
 
 MaybeError PhysicalDevice::InitializeImpl() {
+    DAWN_TRY(mFunctions.Initialize(mDisplay->egl.GetProcAddress));
+
     if (mFunctions.GetVersion().IsES()) {
         DAWN_ASSERT(GetBackendType() == wgpu::BackendType::OpenGLES);
 
@@ -217,7 +213,8 @@
             EnableFeature(dawn::native::Feature::TextureCompressionBC);
         }
     }
-    if (mName.find("ANGLE") != std::string::npos) {
+
+    if (mDisplay->egl.HasExt(EGLExt::DisplayTextureShareGroup)) {
         EnableFeature(dawn::native::Feature::ANGLETextureSharing);
     }
 
@@ -406,8 +403,6 @@
     const UnpackedPtr<DeviceDescriptor>& descriptor,
     const TogglesState& deviceToggles,
     Ref<DeviceBase::DeviceLostEvent>&& lostEvent) {
-    EGLenum api =
-        GetBackendType() == wgpu::BackendType::OpenGL ? EGL_OPENGL_API : EGL_OPENGL_ES_API;
     bool useANGLETextureSharing = false;
     for (size_t i = 0; i < descriptor->requiredFeatureCount; ++i) {
         if (descriptor->requiredFeatures[i] == wgpu::FeatureName::ANGLETextureSharing) {
@@ -415,9 +410,11 @@
         }
     }
 
+    bool useRobustness = !deviceToggles.IsEnabled(Toggle::DisableRobustness);
+
     std::unique_ptr<ContextEGL> context;
-    DAWN_TRY_ASSIGN(context,
-                    ContextEGL::Create(mEGLFunctions, api, mDisplay, useANGLETextureSharing));
+    DAWN_TRY_ASSIGN(context, ContextEGL::Create(mDisplay.get(), GetBackendType(), useRobustness,
+                                                useANGLETextureSharing));
 
     return Device::Create(adapter, descriptor, mFunctions, std::move(context), deviceToggles,
                           std::move(lostEvent));
diff --git a/src/dawn/native/opengl/PhysicalDeviceGL.h b/src/dawn/native/opengl/PhysicalDeviceGL.h
index 86c689f..f078000 100644
--- a/src/dawn/native/opengl/PhysicalDeviceGL.h
+++ b/src/dawn/native/opengl/PhysicalDeviceGL.h
@@ -28,20 +28,25 @@
 #ifndef SRC_DAWN_NATIVE_OPENGL_PHYSICALDEVICEGL_H_
 #define SRC_DAWN_NATIVE_OPENGL_PHYSICALDEVICEGL_H_
 
+#include <memory>
+
 #include "dawn/native/PhysicalDevice.h"
 #include "dawn/native/opengl/EGLFunctions.h"
 #include "dawn/native/opengl/OpenGLFunctions.h"
 
 namespace dawn::native::opengl {
 
+class DisplayEGL;
+
 class PhysicalDevice : public PhysicalDeviceBase {
   public:
     static ResultOrError<Ref<PhysicalDevice>> Create(wgpu::BackendType backendType,
-                                                     EGLGetProcProc getProc,
-                                                     EGLDisplay display);
+                                                     std::unique_ptr<DisplayEGL> display);
 
     ~PhysicalDevice() override = default;
 
+    DisplayEGL* GetDisplay() const;
+
     // PhysicalDeviceBase Implementation
     bool SupportsExternalImages() const override;
     bool SupportsFeatureLevel(FeatureLevel featureLevel) const override;
@@ -50,8 +55,7 @@
         const Surface* surface) const override;
 
   private:
-    PhysicalDevice(wgpu::BackendType backendType, EGLDisplay display);
-    MaybeError InitializeGLFunctions(EGLGetProcProc getProc);
+    PhysicalDevice(wgpu::BackendType backendType, std::unique_ptr<DisplayEGL> display);
 
     MaybeError InitializeImpl() override;
     void InitializeSupportedFeaturesImpl() override;
@@ -74,8 +78,7 @@
     void PopulateBackendProperties(UnpackedPtr<AdapterProperties>& properties) const override;
 
     OpenGLFunctions mFunctions;
-    EGLDisplay mDisplay;
-    EGLFunctions mEGLFunctions;
+    std::unique_ptr<DisplayEGL> mDisplay;
 };
 
 }  // namespace dawn::native::opengl