backend: gl: use libepoxy

There is actually no specification what symbols are exported from a
libGL implementation. The (extremely outdated) OpenGL ABI specification
says only GL 1.2 functions are guaranteed. Don't know how relevant that
is now, but different libGL implementations do export different set of
symbols. On Linux we are most likely to be linked with libglvnd, which
has everything we need. But on other platforms this is not necessarily
the case, for example on OpenBSD we are missing glGetQueryObjectui64v.

Use libepoxy so we can outsource this problem and never worry about it
ever again. Plus it also saves us from calling GetProcAddress ourselves.

Changes other than trivial build fixes I have to make:

1. Can't use eglCreatePlatformWindowSurface/eglGetPlatformDisplay.
   libepoxy checks for EGL 1.5 when resolving these functions. But
   without a current context, libepoxy assumes we only have EGL 1.4.
   This creates a chicken and egg problem - we need a display to call
   eglGetPlatformDisplay. We have to use the *EXT version instead.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2024-02-10 20:36:02 +00:00
parent fcd51e7373
commit eb723eee29
No known key found for this signature in database
GPG Key ID: D3A4405BE6CC17F4
10 changed files with 32 additions and 163 deletions

View File

@ -36,12 +36,6 @@ struct egl_data {
EGLContext ctx; EGLContext ctx;
}; };
static PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC glEGLImageTargetTexStorage = NULL;
static PFNEGLCREATEIMAGEKHRPROC eglCreateImageProc = NULL;
static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageProc = NULL;
static PFNEGLGETPLATFORMDISPLAYPROC eglGetPlatformDisplayProc = NULL;
static PFNEGLCREATEPLATFORMWINDOWSURFACEPROC eglCreatePlatformWindowSurfaceProc = NULL;
const char *eglGetErrorString(EGLint error) { const char *eglGetErrorString(EGLint error) {
#define CASE_STR(value) \ #define CASE_STR(value) \
case value: return #value; case value: return #value;
@ -74,7 +68,7 @@ static void egl_release_image(backend_t *base, struct gl_texture *tex) {
struct egl_pixmap *p = tex->user_data; struct egl_pixmap *p = tex->user_data;
// Release binding // Release binding
if (p->image != EGL_NO_IMAGE) { if (p->image != EGL_NO_IMAGE) {
eglDestroyImageProc(gd->display, p->image); eglDestroyImage(gd->display, p->image);
p->image = EGL_NO_IMAGE; p->image = EGL_NO_IMAGE;
} }
@ -134,18 +128,6 @@ static backend_t *egl_init(session_t *ps, xcb_window_t target) {
bool success = false; bool success = false;
struct egl_data *gd = NULL; struct egl_data *gd = NULL;
#define get_proc(name, type) \
name##Proc = (type)eglGetProcAddress(#name); \
if (!name##Proc) { \
log_error("Failed to get EGL function " #name); \
goto end; \
}
get_proc(eglCreateImage, PFNEGLCREATEIMAGEKHRPROC);
get_proc(eglDestroyImage, PFNEGLDESTROYIMAGEKHRPROC);
get_proc(eglGetPlatformDisplay, PFNEGLGETPLATFORMDISPLAYPROC);
get_proc(eglCreatePlatformWindowSurface, PFNEGLCREATEPLATFORMWINDOWSURFACEPROC);
#undef get_proc
// Check if we have the X11 platform // Check if we have the X11 platform
const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (strstr(exts, "EGL_EXT_platform_x11") == NULL) { if (strstr(exts, "EGL_EXT_platform_x11") == NULL) {
@ -154,8 +136,8 @@ static backend_t *egl_init(session_t *ps, xcb_window_t target) {
} }
gd = ccalloc(1, struct egl_data); gd = ccalloc(1, struct egl_data);
gd->display = eglGetPlatformDisplayProc(EGL_PLATFORM_X11_EXT, ps->c.dpy, gd->display = eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT, ps->c.dpy,
(EGLAttrib[]){ (EGLint[]){
EGL_PLATFORM_X11_SCREEN_EXT, EGL_PLATFORM_X11_SCREEN_EXT,
ps->c.screen, ps->c.screen,
EGL_NONE, EGL_NONE,
@ -212,7 +194,7 @@ static backend_t *egl_init(session_t *ps, xcb_window_t target) {
// clang-format on // clang-format on
gd->target_win = gd->target_win =
eglCreatePlatformWindowSurfaceProc(gd->display, config, &target, NULL); eglCreatePlatformWindowSurfaceEXT(gd->display, config, &target, NULL);
if (gd->target_win == EGL_NO_SURFACE) { if (gd->target_win == EGL_NO_SURFACE) {
log_error("Failed to create EGL surface."); log_error("Failed to create EGL surface.");
goto end; goto end;
@ -243,14 +225,6 @@ static backend_t *egl_init(session_t *ps, xcb_window_t target) {
goto end; goto end;
} }
glEGLImageTargetTexStorage =
(PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC)eglGetProcAddress("glEGLImageTargetTexS"
"torageEXT");
if (glEGLImageTargetTexStorage == NULL) {
log_error("Failed to get glEGLImageTargetTexStorageEXT.");
goto end;
}
gd->gl.decouple_texture_user_data = egl_decouple_user_data; gd->gl.decouple_texture_user_data = egl_decouple_user_data;
gd->gl.release_user_data = egl_release_image; gd->gl.release_user_data = egl_release_image;
@ -302,8 +276,7 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
eglpixmap = cmalloc(struct egl_pixmap); eglpixmap = cmalloc(struct egl_pixmap);
eglpixmap->pixmap = pixmap; eglpixmap->pixmap = pixmap;
eglpixmap->image = eglpixmap->image = eglCreateImage(gd->display, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR,
eglCreateImageProc(gd->display, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR,
(EGLClientBuffer)(uintptr_t)pixmap, NULL); (EGLClientBuffer)(uintptr_t)pixmap, NULL);
eglpixmap->owned = owned; eglpixmap->owned = owned;
@ -324,14 +297,14 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
wd->dim = 0; wd->dim = 0;
wd->inner->refcount = 1; wd->inner->refcount = 1;
glBindTexture(GL_TEXTURE_2D, inner->texture); glBindTexture(GL_TEXTURE_2D, inner->texture);
glEGLImageTargetTexStorage(GL_TEXTURE_2D, eglpixmap->image, NULL); glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, eglpixmap->image, NULL);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
gl_check_err(); gl_check_err();
return wd; return wd;
err: err:
if (eglpixmap && eglpixmap->image) { if (eglpixmap && eglpixmap->image) {
eglDestroyImageProc(gd->display, eglpixmap->image); eglDestroyImage(gd->display, eglpixmap->image);
} }
free(eglpixmap); free(eglpixmap);
@ -422,7 +395,6 @@ struct backend_operations egl_ops = {
.max_buffer_age = 5, // Why? .max_buffer_age = 5, // Why?
}; };
PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName;
/** /**
* Check if a EGL extension exists. * Check if a EGL extension exists.
*/ */
@ -472,16 +444,4 @@ void eglext_init(EGLDisplay dpy) {
check_ext(EGL_MESA_query_driver); check_ext(EGL_MESA_query_driver);
#endif #endif
#undef check_ext #undef check_ext
// Checking if the returned function pointer is NULL is not really necessary,
// or maybe not even useful, since eglGetProcAddress might always return
// something. We are doing it just for completeness' sake.
#ifdef EGL_MESA_query_driver
eglGetDisplayDriverName =
(PFNEGLGETDISPLAYDRIVERNAMEPROC)eglGetProcAddress("eglGetDisplayDriverName");
if (!eglGetDisplayDriverName) {
eglext.has_EGL_MESA_query_driver = false;
}
#endif
} }

View File

@ -1,10 +1,8 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com> // Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once #pragma once
#include <EGL/egl.h> #include <epoxy/egl.h>
#include <EGL/eglext.h> #include <epoxy/gl.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <stdbool.h> #include <stdbool.h>
#include <xcb/render.h> #include <xcb/render.h>
#include <xcb/xcb.h> #include <xcb/xcb.h>
@ -24,8 +22,4 @@ struct eglext_info {
extern struct eglext_info eglext; extern struct eglext_info eglext;
#ifdef EGL_MESA_query_driver
extern PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName;
#endif
void eglext_init(EGLDisplay); void eglext_init(EGLDisplay);

View File

@ -1,7 +1,6 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com> // Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#include <GL/gl.h> #include <epoxy/gl.h>
#include <GL/glext.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>

View File

@ -1,8 +1,7 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com> // Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once #pragma once
#include <GL/gl.h> #include <epoxy/gl.h>
#include <GL/glext.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>

View File

@ -580,16 +580,6 @@ static inline bool glx_has_extension(Display *dpy, int screen, const char *ext)
} }
struct glxext_info glxext = {0}; struct glxext_info glxext = {0};
PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML;
PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA;
PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT;
PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT;
PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
#ifdef GLX_MESA_query_renderer #ifdef GLX_MESA_query_renderer
PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA; PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA;
@ -614,36 +604,4 @@ void glxext_init(Display *dpy, int screen) {
check_ext(GLX_MESA_query_renderer); check_ext(GLX_MESA_query_renderer);
#endif #endif
#undef check_ext #undef check_ext
#define lookup(name) ((name) = (__typeof__(name))glXGetProcAddress((GLubyte *)#name))
// Checking if the returned function pointer is NULL is not really necessary,
// or maybe not even useful, since glXGetProcAddress might always return
// something. We are doing it just for completeness' sake.
if (!lookup(glXGetVideoSyncSGI) || !lookup(glXWaitVideoSyncSGI)) {
glxext.has_GLX_SGI_video_sync = false;
}
if (!lookup(glXSwapIntervalEXT)) {
glxext.has_GLX_EXT_swap_control = false;
}
if (!lookup(glXSwapIntervalMESA)) {
glxext.has_GLX_MESA_swap_control = false;
}
if (!lookup(glXSwapIntervalSGI)) {
glxext.has_GLX_SGI_swap_control = false;
}
if (!lookup(glXWaitForMscOML) || !lookup(glXGetSyncValuesOML)) {
glxext.has_GLX_OML_sync_control = false;
}
if (!lookup(glXBindTexImageEXT) || !lookup(glXReleaseTexImageEXT)) {
glxext.has_GLX_EXT_texture_from_pixmap = false;
}
if (!lookup(glXCreateContextAttribsARB)) {
glxext.has_GLX_ARB_create_context = false;
}
#ifdef GLX_MESA_query_renderer
if (!lookup(glXQueryCurrentRendererIntegerMESA)) {
glxext.has_GLX_MESA_query_renderer = false;
}
#endif
#undef lookup
} }

View File

@ -1,17 +1,9 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com> // Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once #pragma once
#include <stdbool.h>
// Older version of glx.h defines function prototypes for these extensions...
// Rename them to avoid conflicts
#define glXSwapIntervalMESA glXSwapIntervalMESA_
#define glXBindTexImageEXT glXBindTexImageEXT_
#define glXReleaseTexImageEXT glXReleaseTexImageEXT
#include <GL/glx.h>
#undef glXSwapIntervalMESA
#undef glXBindTexImageEXT
#undef glXReleaseTexImageEXT
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <epoxy/glx.h>
#include <stdbool.h>
#include <xcb/render.h> #include <xcb/render.h>
#include <xcb/xcb.h> #include <xcb/xcb.h>
@ -59,19 +51,4 @@ struct glxext_info {
extern struct glxext_info glxext; extern struct glxext_info glxext;
extern PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
extern PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
extern PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
extern PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML;
extern PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
extern PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
extern PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA;
extern PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT;
extern PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT;
extern PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
#ifdef GLX_MESA_query_renderer
extern PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA;
#endif
void glxext_init(Display *, int screen); void glxext_init(Display *, int screen);

View File

@ -9,7 +9,7 @@
#include <unistd.h> #include <unistd.h>
#ifdef CONFIG_OPENGL #ifdef CONFIG_OPENGL
#include <GL/gl.h> #include <epoxy/gl.h>
#include "backend/gl/gl_common.h" #include "backend/gl/gl_common.h"
#include "backend/gl/glx.h" #include "backend/gl/glx.h"
#endif #endif
@ -338,21 +338,14 @@ struct log_target *stderr_logger_new(void) {
} }
#ifdef CONFIG_OPENGL #ifdef CONFIG_OPENGL
/// An opengl logger that can be used for logging into opengl debugging tools,
/// such as apitrace
struct gl_string_marker_logger {
struct log_target tgt;
PFNGLSTRINGMARKERGREMEDYPROC gl_string_marker;
};
static void static void gl_string_marker_logger_write(struct log_target *tgt attr_unused,
gl_string_marker_logger_write(struct log_target *tgt, const char *str, size_t len) { const char *str, size_t len) {
auto g = (struct gl_string_marker_logger *)tgt;
// strip newlines at the end of the string // strip newlines at the end of the string
while (len > 0 && str[len - 1] == '\n') { while (len > 0 && str[len - 1] == '\n') {
len--; len--;
} }
g->gl_string_marker((GLsizei)len, str); glStringMarkerGREMEDY((GLsizei)len, str);
} }
static const struct log_ops gl_string_marker_logger_ops = { static const struct log_ops gl_string_marker_logger_ops = {
@ -361,20 +354,16 @@ static const struct log_ops gl_string_marker_logger_ops = {
.destroy = logger_trivial_destroy, .destroy = logger_trivial_destroy,
}; };
/// Create an opengl logger that can be used for logging into opengl debugging tools,
/// such as apitrace
struct log_target *gl_string_marker_logger_new(void) { struct log_target *gl_string_marker_logger_new(void) {
if (!gl_has_extension("GL_GREMEDY_string_marker")) { if (!gl_has_extension("GL_GREMEDY_string_marker")) {
return NULL; return NULL;
} }
void *fnptr = glXGetProcAddress((GLubyte *)"glStringMarkerGREMEDY"); auto ret = cmalloc(struct log_target);
if (!fnptr) { ret->ops = &gl_string_marker_logger_ops;
return NULL; return ret;
}
auto ret = cmalloc(struct gl_string_marker_logger);
ret->tgt.ops = &gl_string_marker_logger_ops;
ret->gl_string_marker = fnptr;
return &ret->tgt;
} }
#else #else

View File

@ -58,8 +58,8 @@ if get_option('vsync_drm')
endif endif
if get_option('opengl') if get_option('opengl')
cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES'] cflags += ['-DCONFIG_OPENGL']
deps += [dependency('gl', required: true), dependency('egl', required: true)] deps += [dependency('epoxy', required: true)]
srcs += [ 'opengl.c' ] srcs += [ 'opengl.c' ]
endif endif

View File

@ -18,9 +18,9 @@
#include "render.h" #include "render.h"
#include "win.h" #include "win.h"
#include <GL/gl.h>
#include <GL/glx.h>
#include <ctype.h> #include <ctype.h>
#include <epoxy/gl.h>
#include <epoxy/glx.h>
#include <locale.h> #include <locale.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>

View File

@ -11,11 +11,11 @@
#ifdef CONFIG_OPENGL #ifdef CONFIG_OPENGL
// Enable sgi_video_sync_vblank_scheduler // Enable sgi_video_sync_vblank_scheduler
#include <GL/glx.h>
#include <X11/X.h> #include <X11/X.h>
#include <X11/Xlib-xcb.h> #include <X11/Xlib-xcb.h>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/Xutil.h> #include <X11/Xutil.h>
#include <epoxy/glx.h>
#include <pthread.h> #include <pthread.h>
#endif #endif
@ -96,8 +96,6 @@ struct sgi_video_sync_thread_args {
pthread_cond_t start_cnd; pthread_cond_t start_cnd;
}; };
static PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
static bool check_sgi_video_sync_extension(Display *dpy, int screen) { static bool check_sgi_video_sync_extension(Display *dpy, int screen) {
const char *glx_ext = glXQueryExtensionsString(dpy, screen); const char *glx_ext = glXQueryExtensionsString(dpy, screen);
const char *needle = "GLX_SGI_video_sync"; const char *needle = "GLX_SGI_video_sync";
@ -112,11 +110,6 @@ static bool check_sgi_video_sync_extension(Display *dpy, int screen) {
return false; return false;
} }
glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)(void *)glXGetProcAddress(
(const GLubyte *)"glXWaitVideoSyncSGI");
if (!glXWaitVideoSyncSGI) {
return false;
}
return true; return true;
} }