/**
 * @file egl.c
 * @brief EGL OpenGL extension module
 */
/*****************************************************************************
 * Copyright © 2010-2014 Rémi Denis-Courmont
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdlib.h>
#include <assert.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>

#include <vlc_common.h>
#include <vlc_threads.h>
#include <vlc_plugin.h>
#include <vlc_opengl.h>
#include <vlc_window.h>
#ifdef USE_PLATFORM_X11
# include <vlc_xlib.h>
#endif
#ifdef USE_PLATFORM_XCB
# include <xcb/xcb.h>
# include "../xcb/events.h"
#endif
#ifdef USE_PLATFORM_WAYLAND
# include <wayland-egl.h>
#endif
#if defined (USE_PLATFORM_ANDROID)
# include "../android/utils.h"
#endif

static const char *clientExts;
#ifdef EGL_EXT_platform_base
static PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplayEXT;
static PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC createPlatformWindowSurfaceEXT;
#endif

typedef struct vlc_gl_sys_t
{
    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;
#if defined (USE_PLATFORM_X11)
    Display *x11;
    Window x11_win;
#endif
#if defined (USE_PLATFORM_XCB)
    xcb_connection_t *conn;
    xcb_window_t xcb_win;
#endif
#if defined (USE_PLATFORM_WAYLAND)
    struct wl_egl_window *window;
#endif
    bool is_current;
} vlc_gl_sys_t;

static bool CheckAPI(EGLDisplay dpy, const char *api)
{
    const char *apis = eglQueryString(dpy, EGL_CLIENT_APIS);
    return vlc_gl_StrHasToken(apis, api);
}

static bool CheckClientExt(const char *name)
{
    return vlc_gl_StrHasToken(clientExts, name);
}

struct gl_api
{
   const char name[10];
   EGLenum    api;
   EGLint     min_minor;
   EGLint     render_bit;
   EGLint     attr[3];
};

static int MakeCurrent (vlc_gl_t *gl)
{
    vlc_gl_sys_t *sys = gl->sys;

    if (eglMakeCurrent (sys->display, sys->surface, sys->surface,
                        sys->context) != EGL_TRUE)
        return VLC_EGENERIC;
    sys->is_current = true;
    return VLC_SUCCESS;
}

static void ReleaseCurrent (vlc_gl_t *gl)
{
    vlc_gl_sys_t *sys = gl->sys;

    eglMakeCurrent (sys->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
                    EGL_NO_CONTEXT);
    sys->is_current = false;
}

#ifdef USE_PLATFORM_WAYLAND
static void Resize (vlc_gl_t *gl, unsigned width, unsigned height)
{
    vlc_gl_sys_t *sys = gl->sys;

    wl_egl_window_resize(sys->window, width, height, 0, 0);
}

static void DestroySurface(vlc_gl_t *gl)
{
    vlc_gl_sys_t *sys = gl->sys;

    wl_egl_window_destroy(sys->window);
}

static EGLSurface CreateSurface(vlc_gl_t *gl, EGLDisplay dpy, EGLConfig config,
                                unsigned int width, unsigned int height)
{
    vlc_gl_sys_t *sys = gl->sys;
    EGLSurface surface;

    sys->window = wl_egl_window_create(gl->surface->handle.wl, width, height);
    if (sys->window == NULL)
        return EGL_NO_SURFACE;

# if defined(EGL_VERSION_1_5)
    assert(CheckClientExt("EGL_KHR_platform_wayland"));
    surface = eglCreatePlatformWindowSurface(dpy, config, sys->window, NULL);

# elif defined(EGL_EXT_platform_base)
    assert(CheckClientExt("EGL_EXT_platform_wayland"));
    surface = createPlatformWindowSurfaceEXT(dpy, config, sys->window, NULL);

# else
    surface = EGL_NO_SURFACE;
# endif

    if (surface == EGL_NO_SURFACE)
        wl_egl_window_destroy(sys->window);

    return surface;
}

static void ReleaseDisplay(vlc_gl_t *gl)
{
    (void) gl;
}

static EGLDisplay OpenDisplay(vlc_gl_t *gl)
{
    vlc_window_t *surface = gl->surface;
    EGLint ref_attr = EGL_NONE;

# ifdef EGL_KHR_display_reference
    if (CheckClientExt("EGL_KHR_display_reference"))
        ref_attr = EGL_TRACK_REFERENCES_KHR;
# endif

    if (surface->type != VLC_WINDOW_TYPE_WAYLAND)
        return EGL_NO_DISPLAY;

# if defined(EGL_VERSION_1_5)
#  ifdef EGL_KHR_platform_wayland
    if (CheckClientExt("EGL_KHR_platform_wayland")) {
        const EGLAttrib attrs[] = { ref_attr, EGL_TRUE, EGL_NONE };

        return eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR,
                                     surface->display.wl, attrs);
    }
#  endif

# elif defined(EGL_EXT_platform_wayland)
    if (CheckClientExt("EGL_EXT_platform_wayland")) {
        const EGLint attrs[] = { ref_attr, EGL_TRUE, EGL_NONE };

        return getPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT,
                                     surface->display.wl, attrs);
    }

# endif
    return EGL_NO_DISPLAY;
}

#elif defined(USE_PLATFORM_X11)
static void Resize (vlc_gl_t *gl, unsigned width, unsigned height)
{
    vlc_gl_sys_t *sys = gl->sys;
    EGLint val;

    EGLDisplay old_display = eglGetCurrentDisplay();
    EGLSurface old_draw = eglGetCurrentSurface(EGL_DRAW);
    EGLSurface old_read = eglGetCurrentSurface(EGL_READ);
    EGLContext old_ctx = eglGetCurrentContext();

    bool made_current = eglMakeCurrent(sys->display, sys->surface, sys->surface, sys->context);
    if (made_current)
        eglWaitClient();

    unsigned long init_serial = LastKnownRequestProcessed(sys->x11);
    unsigned long resize_serial = NextRequest(sys->x11);
    XResizeWindow(sys->x11, sys->x11_win, width, height);

    if (made_current)
    {
        eglQuerySurface(sys->display, sys->surface, EGL_HEIGHT, &val); /* force Mesa to see new size in time for next draw */
        eglWaitNative(EGL_CORE_NATIVE_ENGINE);
        if (old_display != EGL_NO_DISPLAY)
            eglMakeCurrent(old_display, old_draw, old_read, old_ctx);
        else
            eglMakeCurrent(sys->display, NULL, NULL, NULL);
    }
    if (LastKnownRequestProcessed(sys->x11) - init_serial < resize_serial - init_serial)
        XSync(sys->x11, False);
}

static void DestroySurface(vlc_gl_t *gl)
{
    vlc_gl_sys_t *sys = gl->sys;

    XUnmapWindow(sys->x11, sys->x11_win);
}

static EGLSurface CreateSurface(vlc_gl_t *gl, EGLDisplay dpy, EGLConfig config,
                                unsigned int width, unsigned int height)
{
    vlc_gl_sys_t *sys = gl->sys;
    Window win = sys->x11_win;

    XResizeWindow(sys->x11, win, width, height);
    XMapWindow(sys->x11, win);

# if defined(EGL_VERSION_1_5)
    if (CheckClientExt("EGL_KHR_platform_x11"))
        return eglCreatePlatformWindowSurface(dpy, config, &win, NULL);

# elif defined(EGL_EXT_platform_base)
    if (CheckClientExt("EGL_EXT_platform_x11"))
        return createPlatformWindowSurfaceEXT(dpy, config, &win, NULL);

# endif
    return eglCreateWindowSurface(dpy, config, win, NULL);
}

static void ReleaseDisplay(vlc_gl_t *gl)
{
    vlc_gl_sys_t *sys = gl->sys;

    XCloseDisplay(sys->x11);
}

static EGLDisplay OpenDisplay(vlc_gl_t *gl)
{
    vlc_window_t *surface = gl->surface;
    vlc_gl_sys_t *sys = gl->sys;

    if (surface->type != VLC_WINDOW_TYPE_XID || !vlc_xlib_init(VLC_OBJECT(gl)))
        return EGL_NO_DISPLAY;

    Display *x11 = XOpenDisplay(surface->display.x11);
    if (x11 == NULL)
        return EGL_NO_DISPLAY;

    XWindowAttributes wa;

    if (!XGetWindowAttributes(x11, surface->handle.xid, &wa))
        goto error;

    EGLDisplay display;
    int snum = XScreenNumberOfScreen(wa.screen);

# if defined(EGL_VERSION_1_5)
#  ifdef EGL_KHR_paltform_x11
    if (CheckClientExt("EGL_KHR_platform_x11")) {
        const EGLAttrib attrs[] = {
            EGL_PLATFORM_X11_SCREEN_KHR, snum,
            EGL_NONE
        };

        display = getPlatformDisplayEXT(EGL_PLATFORM_X11_KHR, x11, attrs);
    } else
#  endif
# elif defined(EGL_EXT_platform_x11)
    if (CheckClientExt("EGL_EXT_platform_x11")) {
        const EGLint attrs[] = {
            EGL_PLATFORM_X11_SCREEN_EXT, snum,
            EGL_NONE
        };

        display = getPlatformDisplayEXT(EGL_PLATFORM_X11_EXT, x11, attrs);
    } else
# endif
# ifdef __unix__
    if (snum == XDefaultScreen(x11))
        display = eglGetDisplay(x11);
    else
# endif
        display = EGL_NO_DISPLAY;

    if (display == EGL_NO_DISPLAY)
        goto error;

    unsigned long mask =
        CWBackPixel | CWBorderPixel | CWBitGravity | CWColormap;
    XSetWindowAttributes swa = {
        .background_pixel = BlackPixelOfScreen(wa.screen),
        .border_pixel = BlackPixelOfScreen(wa.screen),
        .bit_gravity = NorthWestGravity,
        .colormap = DefaultColormapOfScreen(wa.screen),
    };

    sys->x11 = x11;
    sys->x11_win = XCreateWindow(x11, surface->handle.xid, 0, 0, wa.width,
                                 wa.height, 0, DefaultDepthOfScreen(wa.screen),
                                 InputOutput, DefaultVisualOfScreen(wa.screen),
                                 mask, &swa);
    return display;
error:
    XCloseDisplay(x11);
    return EGL_NO_DISPLAY;
}

#elif defined(USE_PLATFORM_XCB)
static int GetScreenNum(xcb_connection_t *conn, const xcb_screen_t *scr)
{
    xcb_screen_iterator_t i = xcb_setup_roots_iterator(xcb_get_setup(conn));
    int n = 0;

    while (scr != i.data) {
         xcb_screen_next(&i);
         n++;
    }
    return n;
}

static void Resize (vlc_gl_t *gl, unsigned width, unsigned height)
{
    vlc_gl_sys_t *sys = gl->sys;
    EGLint val;

    EGLDisplay old_display = eglGetCurrentDisplay();
    EGLSurface old_draw = eglGetCurrentSurface(EGL_DRAW);
    EGLSurface old_read = eglGetCurrentSurface(EGL_READ);
    EGLContext old_ctx = eglGetCurrentContext();

    bool made_current = eglMakeCurrent(sys->display, sys->surface, sys->surface, sys->context);
    if (made_current)
        eglWaitClient();

    uint16_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
    const uint32_t values[] = { width, height };
    xcb_void_cookie_t cookie = xcb_configure_window_checked(sys->conn, sys->xcb_win, mask, values);

    if (made_current)
    {
        eglQuerySurface(sys->display, sys->surface, EGL_HEIGHT, &val); /* force Mesa to see new size in time for next draw */
        eglWaitNative(EGL_CORE_NATIVE_ENGINE);
        if (old_display != EGL_NO_DISPLAY)
            eglMakeCurrent(old_display, old_draw, old_read, old_ctx);
        else
            eglMakeCurrent(sys->display, NULL, NULL, NULL);
    }
    free(xcb_request_check(sys->conn, cookie));
}

static void DestroySurface(vlc_gl_t *gl)
{
    vlc_gl_sys_t *sys = gl->sys;

    xcb_unmap_window(sys->conn, sys->xcb_win);
}

static EGLSurface CreateSurface(vlc_gl_t *gl, EGLDisplay dpy, EGLConfig config,
                                unsigned int width, unsigned int height)
{
# ifdef EGL_EXT_platform_base
    vlc_gl_sys_t *sys = gl->sys;
    xcb_connection_t *conn = sys->conn;
    xcb_window_t win = sys->xcb_win;
    uint32_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
    const uint32_t values[] = { width, height, };

    xcb_configure_window(conn, win, mask, values);
    xcb_map_window(conn, win);

    assert(CheckClientExt("EGL_EXT_platform_xcb"));
    return createPlatformWindowSurfaceEXT(dpy, config, &win, NULL);
# else
    return EGL_NO_SURFACE;
# endif
}

static void ReleaseDisplay(vlc_gl_t *gl)
{
    vlc_gl_sys_t *sys = gl->sys;

    xcb_disconnect(sys->conn);
}

static EGLDisplay OpenDisplay(vlc_gl_t *gl)
{
# ifdef EGL_EXT_platform_xcb
    vlc_window_t *surface = gl->surface;
    vlc_gl_sys_t *sys = gl->sys;
    xcb_connection_t *conn;
    const xcb_screen_t *scr;

    if (surface->type != VLC_WINDOW_TYPE_XID)
        return EGL_NO_DISPLAY;
    if (!CheckClientExt("EGL_EXT_platform_xcb"))
        return EGL_NO_DISPLAY;
    if (vlc_xcb_parent_Create(gl->obj.logger, surface, &conn,
                              &scr) != VLC_SUCCESS)
        return EGL_NO_DISPLAY;

    const EGLint attrs[] = {
        EGL_PLATFORM_XCB_SCREEN_EXT, GetScreenNum(conn, scr),
        EGL_NONE
    };

    EGLDisplay display = getPlatformDisplayEXT(EGL_PLATFORM_XCB_EXT, conn,
                                               attrs);
    if (display == EGL_NO_DISPLAY) {
        xcb_disconnect(conn);
        goto out;
    }

    xcb_window_t win = xcb_generate_id(conn);
    uint32_t mask =
        XCB_CW_BACK_PIXEL |
        XCB_CW_BORDER_PIXEL |
        XCB_CW_BIT_GRAVITY |
        XCB_CW_COLORMAP;
    const uint32_t values[] = {
        /* XCB_CW_BACK_PIXEL */
        scr->black_pixel,
        /* XCB_CW_BORDER_PIXEL */
        scr->black_pixel,
        /* XCB_CW_BIT_GRAVITY */
        XCB_GRAVITY_NORTH_WEST,
        /* XCB_CW_COLORMAP */
        scr->default_colormap,
    };

    xcb_create_window(conn, scr->root_depth, win, surface->handle.xid, 0, 0, 1,
                      1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, scr->root_visual,
                      mask, values);
    sys->conn = conn;
    sys->xcb_win = win;
out:
    return display;
# else
    return EGL_NO_DISPLAY;
# endif
}

#elif defined (USE_PLATFORM_ANDROID)
# define Resize (NULL)

static void DestroySurface(vlc_gl_t *gl)
{
    AWindowHandler_releaseANativeWindow(gl->surface->display.anativewindow,
                                        gl->surface->handle.android_id);
}

static EGLSurface CreateSurface(vlc_gl_t *gl, EGLDisplay dpy, EGLConfig config,
                                unsigned int width, unsigned int height)
{
    ANativeWindow *anw =
        AWindowHandler_getANativeWindow(gl->surface->display.anativewindow,
                                        gl->surface->handle.android_id);

    (void) width; (void) height;
    return (anw != NULL) ? eglCreateWindowSurface(dpy, config, anw, NULL)
                         : EGL_NO_SURFACE;
}

static void ReleaseDisplay(vlc_gl_t *gl)
{
    (void) gl;
}

static EGLDisplay OpenDisplay(vlc_gl_t *gl)
{
# if defined (__ANDROID__) || defined (ANDROID)
    if (gl->surface->type == VLC_WINDOW_TYPE_ANDROID_NATIVE)
        return eglGetDisplay(EGL_DEFAULT_DISPLAY);
# endif
    return EGL_NO_DISPLAY;
}

#else
# define Resize (NULL)

static void DestroySurface(vlc_gl_t *gl)
{
    (void) gl;
}

static EGLSurface CreateSurface(vlc_gl_t *gl, EGLDisplay dpy, EGLConfig config,
                                unsigned int width, unsigned int height)
{
    HWND window = gl->surface->handle.hwnd;

    (void) width; (void) height;
    return eglCreateWindowSurface(dpy, config, window, NULL);
}

static void ReleaseDisplay(vlc_gl_t *gl)
{
    (void) gl;
}

static EGLDisplay OpenDisplay(vlc_gl_t *gl)
{
# if defined (_WIN32) || defined (__VC32__) \
  && !defined (__CYGWIN__) && !defined (__SCITECH_SNAP__)
    if (gl->surface->type == VLC_WINDOW_TYPE_HWND)
        return eglGetDisplay(EGL_DEFAULT_DISPLAY);
# endif
    return EGL_NO_DISPLAY;
}
#endif

static void SwapBuffers (vlc_gl_t *gl)
{
    vlc_gl_sys_t *sys = gl->sys;

    if (!sys->is_current)
    {
        EGLSurface s_read = eglGetCurrentSurface(EGL_READ);
        EGLSurface s_draw = eglGetCurrentSurface(EGL_DRAW);
        EGLContext previous_context = eglGetCurrentContext();

        eglMakeCurrent(sys->display, sys->surface, sys->surface, sys->context);
        eglSwapBuffers (sys->display, sys->surface);
        eglMakeCurrent(sys->display, s_read, s_draw, previous_context);
    }
    else
        eglSwapBuffers (sys->display, sys->surface);
}

static void *GetSymbol(vlc_gl_t *gl, const char *procname)
{
    (void) gl;
    return (void *)eglGetProcAddress (procname);
}

static void Close(vlc_gl_t *gl)
{
    vlc_gl_sys_t *sys = gl->sys;

    if (sys->context != EGL_NO_CONTEXT)
        eglDestroyContext(sys->display, sys->context);
    if (sys->surface != EGL_NO_SURFACE) {
        eglDestroySurface(sys->display, sys->surface);
        DestroySurface(gl);
    }
    eglTerminate(sys->display);
    ReleaseDisplay(gl);
    free (sys);
}

static void InitEGL(void)
{
    static vlc_once_t once = VLC_STATIC_ONCE;

    if (unlikely(!vlc_once_begin(&once))) {
        clientExts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
        if (!clientExts)
            clientExts = "";

#ifdef EGL_EXT_platform_base
        getPlatformDisplayEXT =
            (void *) eglGetProcAddress("eglGetPlatformDisplayEXT");
        createPlatformWindowSurfaceEXT =
            (void *) eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
#endif
        vlc_once_complete(&once);
    }
}

/**
 * Probe EGL display availability
 */
static int Open(vlc_gl_t *gl, const struct gl_api *api,
                unsigned width, unsigned height,
                const struct vlc_gl_cfg *gl_cfg)
{
    InitEGL();

    int ret = VLC_EGENERIC;
    vlc_object_t *obj = VLC_OBJECT(gl);
    vlc_gl_sys_t *sys = malloc(sizeof (*sys));
    if (unlikely(sys == NULL))
        return VLC_ENOMEM;

    msg_Dbg(obj, "EGL client extensions: %s", clientExts);

    gl->sys = sys;
    sys->display = EGL_NO_DISPLAY;
    sys->surface = EGL_NO_SURFACE;
    sys->context = EGL_NO_CONTEXT;
    sys->is_current = false;

    sys->display = OpenDisplay(gl);
    if (sys->display == EGL_NO_DISPLAY) {
        free(sys);
        return VLC_ENOTSUP;
    }

    /* Initialize EGL display */
    EGLint major, minor;
    if (eglInitialize(sys->display, &major, &minor) != EGL_TRUE)
        goto error;
    msg_Dbg(obj, "EGL version %s by %s",
            eglQueryString(sys->display, EGL_VERSION),
            eglQueryString(sys->display, EGL_VENDOR));

    const char *ext = eglQueryString(sys->display, EGL_EXTENSIONS);
    if (*ext)
        msg_Dbg(obj, "EGL display extensions: %s", ext);

    if (major != 1 || minor < api->min_minor
     || !CheckAPI(sys->display, api->name))
    {
        msg_Err(obj, "cannot select %s API", api->name);
        goto error;
    }

    const EGLint conf_attr[] = {
        EGL_RED_SIZE, 5,
        EGL_GREEN_SIZE, 5,
        EGL_BLUE_SIZE, 5,
        EGL_ALPHA_SIZE, gl_cfg->need_alpha ? 5 : 0,
        EGL_RENDERABLE_TYPE, api->render_bit,
        EGL_NONE
    };
    EGLConfig cfgv[1];
    EGLint cfgc;

    if (eglChooseConfig(sys->display, conf_attr, cfgv, 1, &cfgc) != EGL_TRUE
     || cfgc == 0)
    {
        msg_Err (obj, "cannot choose EGL configuration");
        goto error;
    }

    /* Create a drawing surface */
    sys->surface = CreateSurface(gl, sys->display, cfgv[0], width, height);
    if (sys->surface == EGL_NO_SURFACE)
    {
        msg_Err (obj, "cannot create EGL window surface");
        goto error;
    }

    if (eglBindAPI (api->api) != EGL_TRUE)
    {
        msg_Err (obj, "cannot bind EGL API");
        goto error;
    }

    EGLContext ctx = eglCreateContext(sys->display, cfgv[0], EGL_NO_CONTEXT,
                                      api->attr);
    if (ctx == EGL_NO_CONTEXT)
    {
        msg_Err (obj, "cannot create EGL context");
        goto error;
    }
    sys->context = ctx;

    /* Initialize OpenGL callbacks */
    static const struct vlc_gl_operations ops =
    {
        .make_current = MakeCurrent,
        .release_current = ReleaseCurrent,
        .resize = Resize,
        .swap = SwapBuffers,
        .get_proc_address = GetSymbol,
        .close = Close,
    };
    gl->ops = &ops;

    return VLC_SUCCESS;

error:
    Close(gl);
    return ret;
}

static int OpenGLES2(vlc_gl_t *gl, unsigned width, unsigned height,
                     const struct vlc_gl_cfg *gl_cfg)
{
    static const struct gl_api api = {
        "OpenGL_ES", EGL_OPENGL_ES_API, 4, EGL_OPENGL_ES2_BIT,
        { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE },
    };
    return Open(gl, &api, width, height, gl_cfg);
}

static int OpenGL(vlc_gl_t *gl, unsigned width, unsigned height,
                  const struct vlc_gl_cfg *gl_cfg)
{
    static const struct gl_api api = {
        "OpenGL", EGL_OPENGL_API, 4, EGL_OPENGL_BIT,
        { EGL_NONE },
    };
    return Open(gl, &api, width, height, gl_cfg);
}

#ifdef USE_PLATFORM_XCB
# define VLC_PRIORITY 60
#endif
#ifndef VLC_PRIORITY
# define VLC_PRIORITY 50
#endif

vlc_module_begin ()
    set_shortname (N_("EGL"))
    set_description (N_("EGL extension for OpenGL"))
    set_subcategory (SUBCAT_VIDEO_VOUT)
    set_callback_opengl(OpenGL, VLC_PRIORITY)
    add_shortcut ("egl")

    add_submodule ()
    set_callback_opengl_es2(OpenGLES2, VLC_PRIORITY)
    add_shortcut ("egl")

vlc_module_end ()
