EGL and OpenGL ES on Mac


#1

Hello,
Just want to share my experience and consult about proper fix submission. I have compiled libangle and tried to make this code work (LWJGL/Kotlin):

import org.lwjgl.egl.EGL
import org.lwjgl.egl.EGL10.eglGetError
import org.lwjgl.egl.EGL10.eglInitialize
import org.lwjgl.egl.EGLCapabilities
import org.lwjgl.glfw.Callbacks.glfwFreeCallbacks
import org.lwjgl.glfw.GLFW.*
import org.lwjgl.glfw.GLFWErrorCallback
import org.lwjgl.glfw.GLFWNativeEGL.glfwGetEGLDisplay
import org.lwjgl.opengles.GLES
import org.lwjgl.opengles.GLES20.*
import org.lwjgl.opengles.GLESCapabilities
import org.lwjgl.system.Configuration
import org.lwjgl.system.MemoryStack.stackPush
import org.lwjgl.system.MemoryUtil.NULL


fun main()
{
    Configuration.DEBUG.set(true)
    Configuration.DEBUG_LOADER.set(true)
    Configuration.DEBUG_FUNCTIONS.set(true)

    GLFWErrorCallback.createPrint().set()
    if (!glfwInit()) {
        throw IllegalStateException("Unable to initialize glfw")
    }

    glfwDefaultWindowHints()
    glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE)
    glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE)

    // GLFW setup for EGL & OpenGL ES
    glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API)
    glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API)
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3)
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0)

    val WIDTH = 300
    val HEIGHT = 300

    val window = glfwCreateWindow(WIDTH, HEIGHT, "GLFW EGL/OpenGL ES Demo", NULL, NULL)
    if (window == NULL) {
        throw RuntimeException("Failed to create the GLFW window")
    }

    glfwSetKeyCallback(window) { windowHnd, key, scancode, action, mods ->
        if (action == GLFW_RELEASE && key == GLFW_KEY_ESCAPE) {
            glfwSetWindowShouldClose(windowHnd, true)
        }
    }

    // EGL capabilities
    val dpy = glfwGetEGLDisplay()

    val egl: EGLCapabilities = stackPush().use { stack ->
        val major = stack.mallocInt(1)
        val minor = stack.mallocInt(1)

        if (!eglInitialize(dpy, major, minor)) {
            throw IllegalStateException(String.format("Failed to initialize EGL [0x%X]", eglGetError()))
        }

        return@use EGL.createDisplayCapabilities(dpy, major.get(0), minor.get(0))
    }

    try {
        println("EGL Capabilities:")
        for (f in EGLCapabilities::class.java.fields) {
            if (f.type === Boolean::class.javaPrimitiveType) {
                if (f.get(egl).equals(java.lang.Boolean.TRUE)) {
                    System.out.println("\t" + f.name)
                }
            }
        }
    } catch (e: IllegalAccessException) {
        e.printStackTrace()
    }

    // OpenGL ES capabilities
    glfwMakeContextCurrent(window)
    val gles = GLES.createCapabilities()

    try {
        println("OpenGL ES Capabilities:")
        for (f in GLESCapabilities::class.java.fields) {
            if (f.type === Boolean::class.javaPrimitiveType) {
                if (f.get(gles).equals(java.lang.Boolean.TRUE)) {
                    System.out.println("\t" + f.name)
                }
            }
        }
    } catch (e: IllegalAccessException) {
        e.printStackTrace()
    }

    System.out.println("GL_VENDOR: " + glGetString(GL_VENDOR))
    System.out.println("GL_VERSION: " + glGetString(GL_VERSION))
    System.out.println("GL_RENDERER: " + glGetString(GL_RENDERER))

    // Render with OpenGL ES
    glfwShowWindow(window)

    glClearColor(0.0f, 0.0f, 1.0f, 1.0f)
    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents()

        glClear(GL_COLOR_BUFFER_BIT)
        glfwSwapBuffers(window)
    }

    GLES.setCapabilities(null)

    glfwFreeCallbacks(window)
    glfwTerminate()
}

It failed with EGL_BAD_NATIVE_WINDOW in glfwCreateWindow(). After some debugging I applied the following patch:

diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h
index cf7ead59..cc09c31f 100644
--- a/src/cocoa_platform.h
+++ b/src/cocoa_platform.h
@@ -56,7 +56,7 @@ typedef VkResult (APIENTRY *PFN_vkCreateMacOSSurfaceMVK)(VkInstance,const VkMacO
 #define _glfw_dlclose(handle) dlclose(handle)
 #define _glfw_dlsym(handle, name) dlsym(handle, name)
 
-#define _GLFW_EGL_NATIVE_WINDOW  ((EGLNativeWindowType) window->ns.view)
+#define _GLFW_EGL_NATIVE_WINDOW  ((EGLNativeWindowType) window->ns.layer)
 #define _GLFW_EGL_NATIVE_DISPLAY EGL_DEFAULT_DISPLAY
 
 #define _GLFW_PLATFORM_WINDOW_STATE         _GLFWwindowNS  ns
diff --git a/src/cocoa_window.m b/src/cocoa_window.m
index 9bea2f27..3773c6c9 100644
--- a/src/cocoa_window.m
+++ b/src/cocoa_window.m
@@ -891,6 +891,10 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
 
     _glfwPlatformGetWindowSize(window, &window->ns.width, &window->ns.height);
     _glfwPlatformGetFramebufferSize(window, &window->ns.fbWidth, &window->ns.fbHeight);
+    
+    // layer required for EGL window surface creation
+    [window->ns.view setWantsLayer:YES];
+    window->ns.layer = [window->ns.view layer];
 
     return GLFW_TRUE;
 }

and now it works. Libangle expects native window type to be layer, not view (that is what the first fix is about). Secondly, the layer should be created, which was not done in createNativeWindow(), it is done in _glfwPlatformCreateWindowSurface() which however is not going to be called when EGL context initialisation is requested. So the second fix is layer creation just with the window. I am not sure if these fixes will not cause regression for other modes on MacOS (most probably they will). But at least I can confirm that making GLFW to work with angle and EGL/OpenGL ES on Mac is not difficult. Hope this support in proper form will be integrated one day into the upstream.


#2

This looks like the following issue: