opengl: Add support for EGL 1.4 without EGL_KHR_surfaceless_context.

This is core in EGL 1.5 but not in EGL 1.4. Without it we need to create
a pbuffer to set as an offscreen surface when there is no window to
render to.

Bug: 42241384
Change-Id: I046bcdd878d49ec18d7b9e43a600feb653b046c5
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/195918
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Stephen White <senorblanco@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn/native/opengl/ContextEGL.cpp b/src/dawn/native/opengl/ContextEGL.cpp
index f1b7c4e..d9b6835 100644
--- a/src/dawn/native/opengl/ContextEGL.cpp
+++ b/src/dawn/native/opengl/ContextEGL.cpp
@@ -54,6 +54,10 @@
 ContextEGL::ContextEGL(const DisplayEGL* display) : mDisplay(display) {}
 
 ContextEGL::~ContextEGL() {
+    if (mOffscreenSurface != EGL_NO_SURFACE) {
+        mDisplay->egl.DestroySurface(mDisplay->GetDisplay(), mOffscreenSurface);
+        mOffscreenSurface = EGL_NO_SURFACE;
+    }
     if (mContext != EGL_NO_CONTEXT) {
         mDisplay->egl.DestroyContext(mDisplay->GetDisplay(), mContext);
         mContext = EGL_NO_CONTEXT;
@@ -68,14 +72,15 @@
     // Unless EGL_KHR_no_config is present, we need to choose an EGLConfig on context creation that
     // will lock the EGLContext to be use with a single kind of color buffer. In that case the
     // display should only list one kind of color buffer as potentially supported.
-    EGLConfig config = kNoConfig;
+    EGLConfig contextConfig = kNoConfig;
     if (!egl.HasExt(EGLExt::NoConfigContext)) {
         DAWN_ASSERT(mDisplay->GetPotentialSurfaceFormats().size() == 1);
         wgpu::TextureFormat format = mDisplay->GetPotentialSurfaceFormats()[0];
 
-        config = mDisplay->ChooseConfig(EGL_WINDOW_BIT, format);
-        if (config == kNoConfig) {
-            return DAWN_FORMAT_INTERNAL_ERROR("Couldn't find an EGLConfig for %s.", format);
+        contextConfig = mDisplay->ChooseConfig(EGL_WINDOW_BIT, format);
+        if (contextConfig == kNoConfig) {
+            return DAWN_FORMAT_INTERNAL_ERROR(
+                "Couldn't find an EGLConfig rendering to a window for %s.", format);
         }
     }
 
@@ -121,13 +126,41 @@
 
     attribs.push_back(EGL_NONE);
 
-    mContext = egl.CreateContext(mDisplay->GetDisplay(), config, EGL_NO_CONTEXT, attribs.data());
-    return CheckEGL(egl, mContext != EGL_NO_CONTEXT, "eglCreateContext");
+    mContext =
+        egl.CreateContext(mDisplay->GetDisplay(), contextConfig, EGL_NO_CONTEXT, attribs.data());
+    DAWN_TRY(CheckEGL(egl, mContext != EGL_NO_CONTEXT, "eglCreateContext"));
+
+    // When EGL_KHR_surfaceless_context is not supported, we need to create a pbuffer to act
+    // as an offscreen surface.
+    if (!egl.HasExt(EGLExt::SurfacelessContext)) {
+        // The first potential surface format is always a good choice to find the config. Either we
+        // only support this one and the context will be compatible with the pbuffer, or
+        // EGL_KHR_no_config_context is supported and the context is compatible with any config.
+        wgpu::TextureFormat format = mDisplay->GetPotentialSurfaceFormats()[0];
+
+        EGLConfig pbufferConfig = mDisplay->ChooseConfig(EGL_PBUFFER_BIT, format);
+        if (pbufferConfig == kNoConfig) {
+            return DAWN_FORMAT_INTERNAL_ERROR(
+                "Couldn't find an EGLConfig rendering to a window for %s.", format);
+        }
+
+        EGLint pbufferAttribs[] = {
+            EGL_WIDTH,  1,  //
+            EGL_HEIGHT, 1,  //
+            EGL_NONE,
+        };
+        mOffscreenSurface =
+            egl.CreatePbufferSurface(mDisplay->GetDisplay(), pbufferConfig, pbufferAttribs);
+        DAWN_TRY(
+            CheckEGL(egl, mOffscreenSurface != EGL_NO_SURFACE, "Creating the offscreen surface."));
+    }
+
+    return {};
 }
 
 void ContextEGL::MakeCurrent() {
-    EGLBoolean success =
-        mDisplay->egl.MakeCurrent(mDisplay->GetDisplay(), mSurface, mSurface, mContext);
+    EGLBoolean success = mDisplay->egl.MakeCurrent(mDisplay->GetDisplay(), mCurrentSurface,
+                                                   mCurrentSurface, mContext);
     IgnoreErrors(CheckEGL(mDisplay->egl, success == EGL_TRUE, "eglMakeCurrent"));
 }
 
@@ -141,11 +174,11 @@
 ContextEGL::ScopedMakeSurfaceCurrent::ScopedMakeSurfaceCurrent(ContextEGL* context,
                                                                EGLSurface surface)
     : mContext(context) {
-    mContext->mSurface = surface;
+    mContext->mCurrentSurface = surface;
 }
 
 ContextEGL::ScopedMakeSurfaceCurrent::~ScopedMakeSurfaceCurrent() {
-    mContext->mSurface = EGL_NO_SURFACE;
+    mContext->mCurrentSurface = mContext->mOffscreenSurface;
 }
 
 }  // namespace dawn::native::opengl
diff --git a/src/dawn/native/opengl/ContextEGL.h b/src/dawn/native/opengl/ContextEGL.h
index 281f4a2..8595440 100644
--- a/src/dawn/native/opengl/ContextEGL.h
+++ b/src/dawn/native/opengl/ContextEGL.h
@@ -71,7 +71,8 @@
   private:
     raw_ptr<const DisplayEGL> mDisplay;
     EGLContext mContext = EGL_NO_CONTEXT;
-    EGLSurface mSurface = EGL_NO_SURFACE;
+    EGLSurface mCurrentSurface = EGL_NO_SURFACE;
+    EGLSurface mOffscreenSurface = EGL_NO_SURFACE;
 };
 
 }  // namespace dawn::native::opengl