#define AL_LOG_SECTION "egl" #include #include #ifdef STELA_API_OPENGL #include "egl_common.h" #define STELA_GL_API EGL_OPENGL_API #define STELA_GLES_API EGL_OPENGL_ES_API #define STELA_GL_BIT EGL_OPENGL_BIT #define STELA_GLES2_BIT EGL_OPENGL_ES2_BIT #define STELA_GLES3_BIT EGL_OPENGL_ES3_BIT #define STELA_GL_CORE_PROFILE_BIT EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT #include "gl_versions.h" PASTE_GL_VERSIONS() #endif bool stl_maybe_load_egl(void) { #ifdef STELA_USE_GLAD s32 egl_version = gladLoaderLoadEGL(NULL); if (!egl_version) { log_error("Failed to pre-load EGL (glad)."); return false; } #endif return true; } bool stl_load_egl(struct stl_egl_global *global) { EGLint major, minor; if (!eglInitialize(global->display, &major, &minor)) { log_error("Failed to initialize."); return false; } log_debug("eglInitialize()'d version %d.%d.", major, minor); #ifdef STELA_USE_GLAD s32 egl_version = gladLoaderLoadEGL(global->display); if (!egl_version) { log_error("Failed to load EGL (glad)."); return false; } log_debug("Using EGL (glad) version %s.", eglQueryString(global->display, EGL_VERSION)); #else log_debug("Using EGL version %s.", eglQueryString(global->display, EGL_VERSION)); #endif return true; } void stl_terminate_egl(struct stl_egl_global *global) { if (global->display != EGL_NO_DISPLAY) { if (!eglTerminate(global->display)) { log_warn("Failed to eglTerminate()."); } } } // https://gitlab.freedesktop.org/mesa/kmscube/-/blob/master/common.c#L162 static EGLint match_config_to_visual(EGLDisplay display, EGLint visual_id, EGLConfig *configs, EGLint count) { EGLint id; for (EGLint i = 0; i < count; i++) { if (!eglGetConfigAttrib(display, configs[i], EGL_NATIVE_VISUAL_ID, &id)) { continue; } if (id == visual_id) return i; } return -1; } static bool egl_choose_config(EGLDisplay display, const EGLint *attribs, EGLint visual_id, EGLConfig *config) { EGLint count = 0; if (!eglGetConfigs(display, NULL, 0, &count) || count <= 0) { log_error("No configs to choose from."); return false; } EGLint matched = 0; EGLConfig *configs = (EGLConfig *)al_malloc(count * sizeof(EGLConfig)); if (!eglChooseConfig(display, attribs, configs, count, &matched) || !matched) { log_error("No configs with appropriate attributes."); al_free(configs); return false; } EGLint index = visual_id ? match_config_to_visual(display, visual_id, config, matched) : 0; if (index != -1) *config = configs[index]; al_free(configs); return index != -1; } bool stl_init_egl_context(struct stl_egl *egl, struct stl_egl_global *global, EGLint visual_id, EGLConfig *config) { egl->display = global->display; egl->context = EGL_NO_CONTEXT; for (u32 i = 0; i < ARRAY_SIZE(stl_gl_vers); i++) { const EGLint context_attr_gles[] = { EGL_CONTEXT_CLIENT_VERSION, stl_gl_vers[i].major, EGL_NONE }; const EGLint context_attr_gl[] = { #ifdef AL_DEBUG EGL_CONTEXT_OPENGL_DEBUG, EGL_TRUE, #endif EGL_CONTEXT_MAJOR_VERSION, stl_gl_vers[i].major, EGL_CONTEXT_MINOR_VERSION, stl_gl_vers[i].minor, EGL_CONTEXT_OPENGL_PROFILE_MASK, stl_gl_vers[i].profile, EGL_NONE }; STELA_LOG_GL_CONTEXT_CREATION(stl_gl_vers[i]); const EGLint *context_attr = stl_gl_vers[i].api == STELA_GL_API ? context_attr_gl : context_attr_gles; eglBindAPI(stl_gl_vers[i].api); #define STELA_INCLUDE_DEPTH_BUFFER const EGLint window_attr[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, #ifdef STELA_INCLUDE_DEPTH_BUFFER EGL_DEPTH_SIZE, 24, EGL_STENCIL_SIZE, 8, #else EGL_DEPTH_SIZE, 0, EGL_STENCIL_SIZE, 0, #endif EGL_RENDERABLE_TYPE, stl_gl_vers[i].type, EGL_SAMPLES, 0, EGL_NONE }; if (!egl_choose_config(egl->display, window_attr, visual_id, config)) { log_error("Failed to choose a display config."); continue; } egl->context = eglCreateContext(egl->display, *config, EGL_NO_CONTEXT, context_attr); if (egl->context == EGL_NO_CONTEXT) { continue; } else { break; } } if (egl->context == EGL_NO_CONTEXT) { log_error("Failed to create context."); return false; } egl->current = EGL_NO_CONTEXT; egl->surface = EGL_NO_SURFACE; return true; } bool stl_egl_make_current(struct stl_egl *egl) { if (egl->context != egl->current) { al_assert(egl->current == EGL_NO_CONTEXT); egl->current = egl->context; return eglMakeCurrent(egl->display, egl->surface, egl->surface, egl->context); } return true; } void stl_egl_release_current(struct stl_egl *egl) { // https://gitlab.freedesktop.org/mesa/mesa/-/issues/6547 // libplacebo calls release_current() after swapping buffers. That locks in the // current backbuffer size. So, if all that happens for the next frame is: // 1. wl_egl_window_resize() // 2. resize libplacebo swapchain // 3. start frame // 4. submit frame // 5. swap buffers // The frame that is presented will still have the dimensions of the previous backbuffer. // We only support 1 context at a time, so this shouldn't be an issue. al_assert(egl->context == egl->current); #if 0 if (egl->current != EGL_NO_CONTEXT) { eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); egl->current = EGL_NO_CONTEXT; } #endif } void stl_egl_swap_interval(struct stl_egl *egl, EGLint interval) { eglSwapInterval(egl->display, interval); } void stl_egl_swap_buffers(struct stl_egl *egl) { eglSwapBuffers(egl->display, egl->surface); } bool stl_egl_is_ready(struct stl_egl *egl) { return egl->context != EGL_NO_CONTEXT; } void stl_egl_destroy_surface(struct stl_egl *egl) { eglDestroySurface(egl->display, egl->surface); } void stl_egl_destroy_context(struct stl_egl *egl) { eglDestroyContext(egl->display, egl->context); } #define ERROR_CASE_STR(value) case value: return #value; static const char *eglGetErrorString(EGLint error) { switch (error) { ERROR_CASE_STR(EGL_SUCCESS) ERROR_CASE_STR(EGL_NOT_INITIALIZED) ERROR_CASE_STR(EGL_BAD_ACCESS) ERROR_CASE_STR(EGL_BAD_ALLOC) ERROR_CASE_STR(EGL_BAD_ATTRIBUTE) ERROR_CASE_STR(EGL_BAD_CONTEXT) ERROR_CASE_STR(EGL_BAD_CONFIG) ERROR_CASE_STR(EGL_BAD_CURRENT_SURFACE) ERROR_CASE_STR(EGL_BAD_DISPLAY) ERROR_CASE_STR(EGL_BAD_SURFACE) ERROR_CASE_STR(EGL_BAD_MATCH) ERROR_CASE_STR(EGL_BAD_PARAMETER) ERROR_CASE_STR(EGL_BAD_NATIVE_PIXMAP) ERROR_CASE_STR(EGL_BAD_NATIVE_WINDOW) ERROR_CASE_STR(EGL_CONTEXT_LOST) default: return "Unknown"; } } #undef ERROR_CASE_STR void stl_egl_check_error() { EGLint error; while ((error = eglGetError()) != EGL_SUCCESS) { log_error("EGL error %s, %d.", eglGetErrorString(error), error); } }