diff --git a/CMakeLists.txt b/CMakeLists.txt index 734aba51..3d4fbbe4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -207,6 +207,7 @@ if (ENABLE_OPENGL) if (EGL_FOUND AND GLES2_FOUND) target_sources(flutterpi_module PRIVATE src/egl_gbm_render_surface.c + src/egl_offscreen_render_surface.c src/gl_renderer.c ) target_link_libraries(flutterpi_module PUBLIC diff --git a/src/egl_gbm_render_surface.c b/src/egl_gbm_render_surface.c index 30484b45..99eb2099 100644 --- a/src/egl_gbm_render_surface.c +++ b/src/egl_gbm_render_surface.c @@ -180,7 +180,7 @@ static int egl_gbm_render_surface_init( /// TODO: Think about allowing different tilings / modifiers here if (egl_config == EGL_NO_CONFIG_KHR) { // choose a config - egl_config = gl_renderer_choose_config_direct(renderer, pixel_format); + egl_config = gl_renderer_choose_gbm_window_config(renderer, pixel_format); if (egl_config == EGL_NO_CONFIG_KHR) { LOG_ERROR( "EGL doesn't supported the specified pixel format %s. Try a different one (ARGB8888 should always work).\n", diff --git a/src/egl_offscreen_render_surface.c b/src/egl_offscreen_render_surface.c new file mode 100644 index 00000000..299a7109 --- /dev/null +++ b/src/egl_offscreen_render_surface.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: MIT +/* + * GBM Surface Backing Stores + * + * - implements EGL/GL render surfaces using a gbm surface + * - ideal way to create render surfaces right now + * + * Copyright (c) 2022, Hannes Winkler + */ + +#include "egl_offscreen_render_surface.h" + +#include +#include + +#include "egl.h" +#include "gl_renderer.h" +#include "gles.h" +#include "modesetting.h" +#include "pixel_format.h" +#include "render_surface.h" +#include "render_surface_private.h" +#include "surface.h" +#include "tracer.h" +#include "util/collection.h" +#include "util/logging.h" +#include "util/refcounting.h" + +struct egl_offscreen_render_surface; + +struct egl_offscreen_render_surface { + union { + struct surface surface; + struct render_surface render_surface; + }; + +#ifdef DEBUG + uuid_t uuid; +#endif + + EGLDisplay egl_display; + EGLSurface egl_surface; + EGLConfig egl_config; + struct gl_renderer *renderer; +}; + +COMPILE_ASSERT(offsetof(struct egl_offscreen_render_surface, surface) == 0); +COMPILE_ASSERT(offsetof(struct egl_offscreen_render_surface, render_surface.surface) == 0); + +#ifdef DEBUG +static const uuid_t uuid = CONST_UUID(0xf9, 0xab, 0x5d, 0xad, 0x2e, 0x3b, 0x4e, 0x2c, 0x9d, 0x26, 0x64, 0x70, 0xfa, 0x9a, 0x25, 0xab); +#endif + +#define CAST_THIS(ptr) CAST_EGL_OFFSCREEN_RENDER_SURFACE(ptr) +#define CAST_THIS_UNCHECKED(ptr) CAST_EGL_OFFSCREEN_RENDER_SURFACE_UNCHECKED(ptr) + +#ifdef DEBUG +ATTR_PURE struct egl_offscreen_render_surface *__checked_cast_egl_offscreen_render_surface(void *ptr) { + struct egl_offscreen_render_surface *s; + + s = CAST_EGL_OFFSCREEN_RENDER_SURFACE_UNCHECKED(ptr); + assert(uuid_equals(s->uuid, uuid)); + return s; +} +#endif + +void egl_offscreen_render_surface_deinit(struct surface *s); +static int egl_offscreen_render_surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); +static int +egl_offscreen_render_surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); +static int egl_offscreen_render_surface_fill(struct render_surface *s, FlutterBackingStore *fl_store); +static int egl_offscreen_render_surface_queue_present(struct render_surface *s, const FlutterBackingStore *fl_store); + +static int egl_offscreen_render_surface_init( + struct egl_offscreen_render_surface *s, + struct tracer *tracer, + struct vec2i size, + struct gl_renderer *renderer +) { + EGLDisplay egl_display; + EGLSurface egl_surface; + EGLBoolean egl_ok; + EGLConfig egl_config; + int ok; + + ASSERT_NOT_NULL(renderer); + egl_display = gl_renderer_get_egl_display(renderer); + ASSERT_NOT_NULL(egl_display); + + /// TODO: Think about allowing different tilings / modifiers here + // choose a config + egl_config = gl_renderer_choose_pbuffer_config(renderer, 8, 8, 8, 8); + if (egl_config == EGL_NO_CONFIG_KHR) { + LOG_ERROR( + "EGL doesn't supported the hardcoded software rendering pixel format ARGB8888.\n" + ); + return EINVAL; + } + +// EGLAttribKHR is defined by EGL_KHR_cl_event2. +#ifndef EGL_KHR_cl_event2 + #error "EGL header definitions for extension EGL_KHR_cl_event2 are required." +#endif + + static const EGLAttribKHR surface_attribs[] = { + /* EGL_GL_COLORSPACE, GL_LINEAR / GL_SRGB */ + /* EGL_RENDER_BUFFER, EGL_BACK_BUFFER / EGL_SINGLE_BUFFER */ + /* EGL_VG_ALPHA_FORMAT, EGL_VG_ALPHA_FORMAT_NONPRE / EGL_VG_ALPHA_FORMAT_PRE */ + /* EGL_VG_COLORSPACE, EGL_VG_COLORSPACE_sRGB / EGL_VG_COLORSPACE_LINEAR */ + EGL_NONE, + }; + + (void) surface_attribs; + + egl_ok = eglBindAPI(EGL_OPENGL_ES_API); + if (egl_ok == EGL_FALSE) { + LOG_EGL_ERROR(eglGetError(), "Couldn't bind OpenGL ES API to EGL. eglBindAPI"); + return EIO; + } + + egl_surface = gl_renderer_create_pbuffer_surface(renderer, egl_config, NULL, NULL); + if (egl_surface == EGL_NO_SURFACE) { + return EIO; + } + + /// TODO: Implement + ok = render_surface_init(CAST_RENDER_SURFACE_UNCHECKED(s), tracer, size); + if (ok != 0) { + goto fail_destroy_egl_surface; + } + + s->surface.present_kms = egl_offscreen_render_surface_present_kms; + s->surface.present_fbdev = egl_offscreen_render_surface_present_fbdev; + s->surface.deinit = egl_offscreen_render_surface_deinit; + s->render_surface.fill = egl_offscreen_render_surface_fill; + s->render_surface.queue_present = egl_offscreen_render_surface_queue_present; +#ifdef DEBUG + uuid_copy(&s->uuid, uuid); +#endif + s->egl_display = egl_display; + s->egl_surface = egl_surface; + s->egl_config = egl_config; + s->renderer = gl_renderer_ref(renderer); + return 0; + +fail_destroy_egl_surface: + eglDestroySurface(egl_display, egl_surface); + + return ok; +} + +/** + * @brief Create a new gbm_surface based render surface, with an explicit EGL Config for the created EGLSurface. + * + * @param compositor The compositor that this surface will be registered to when calling surface_register. + * @param size The size of the surface. + * @param renderer The EGL/OpenGL used to create any GL surfaces. + * @return struct egl_offscreen_render_surface* + */ +struct egl_offscreen_render_surface *egl_offscreen_render_surface_new( + struct tracer *tracer, + struct vec2i size, + struct gl_renderer *renderer +) { + struct egl_offscreen_render_surface *surface; + int ok; + + surface = malloc(sizeof *surface); + if (surface == NULL) { + goto fail_return_null; + } + + ok = egl_offscreen_render_surface_init(surface, tracer, size, renderer); + if (ok != 0) { + goto fail_free_surface; + } + + return surface; + +fail_free_surface: + free(surface); + +fail_return_null: + return NULL; +} + +void egl_offscreen_render_surface_deinit(struct surface *s) { + struct egl_offscreen_render_surface *egl_surface; + + egl_surface = CAST_THIS(s); + + gl_renderer_unref(egl_surface->renderer); + render_surface_deinit(s); +} + +static int egl_offscreen_render_surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder) { + (void) s; + (void) props; + (void) builder; + + UNIMPLEMENTED(); + + return 0; +} + +static int +egl_offscreen_render_surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder) { + struct egl_offscreen_render_surface *egl_surface; + + /// TODO: Implement by mmapping the current front bo, copy it into the fbdev + /// TODO: Print a warning here if we're not using explicit linear tiling and use glReadPixels instead of gbm_bo_map in that case + + egl_surface = CAST_THIS(s); + (void) egl_surface; + (void) props; + (void) builder; + + UNIMPLEMENTED(); + + return 0; +} + +static int egl_offscreen_render_surface_fill(struct render_surface *s, FlutterBackingStore *fl_store) { + fl_store->type = kFlutterBackingStoreTypeOpenGL; + fl_store->open_gl = (FlutterOpenGLBackingStore) { + .type = kFlutterOpenGLTargetTypeFramebuffer, + .framebuffer = { + /* for some reason flutter wants this to be GL_BGRA8_EXT, contrary to what the docs say */ + .target = GL_BGRA8_EXT, + + /* 0 refers to the window surface, instead of to an FBO */ + .name = 0, + + /* + * even though the compositor will call surface_ref too to fill the FlutterBackingStore.user_data, + * we need to ref two times because flutter will call both this destruction callback and the + * compositor collect callback + */ + .user_data = surface_ref(CAST_SURFACE_UNCHECKED(s)), + .destruction_callback = surface_unref_void, + }, + }; + return 0; +} + +static int egl_offscreen_render_surface_queue_present(struct render_surface *s, const FlutterBackingStore *fl_store) { + (void) s; + (void) fl_store; + + // nothing to do here + + return 0; +} + +/** + * @brief Get the EGL Surface for rendering into this render surface. + * + * Flutter doesn't really support backing stores to be EGL Surfaces, so we have to hack around this, kinda. + * + * @param s + * @return EGLSurface The EGLSurface associated with this render surface. Only valid for the lifetime of this egl_offscreen_render_surface. + */ +ATTR_PURE EGLSurface egl_offscreen_render_surface_get_egl_surface(struct egl_offscreen_render_surface *s) { + return s->egl_surface; +} + +/** + * @brief Get the EGLConfig that was used to create the EGLSurface for this render surface. + * + * If the display doesn't support EGL_KHR_no_config_context, we need to create the EGL rendering context with + * the same EGLConfig as every EGLSurface we want to bind to it. So we can just let egl_offscreen_render_surface choose a config + * and let flutter-pi query that config when creating the rendering contexts in that case. + * + * @param s + * @return EGLConfig The chosen EGLConfig. Valid forever. + */ +ATTR_PURE EGLConfig egl_offscreen_render_surface_get_egl_config(struct egl_offscreen_render_surface *s) { + return s->egl_config; +} diff --git a/src/egl_offscreen_render_surface.h b/src/egl_offscreen_render_surface.h new file mode 100644 index 00000000..0ebe45dd --- /dev/null +++ b/src/egl_offscreen_render_surface.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +/* + * Offscreen (MESA surfaceless) backing store + * + * Copyright (c) 2022, Hannes Winkler + */ + +#ifndef _FLUTTERPI_SRC_EGL_OFFSCREEN_RENDER_SURFACE_H +#define _FLUTTERPI_SRC_EGL_OFFSCREEN_RENDER_SURFACE_H + +#include "compositor_ng.h" +#include "pixel_format.h" +#include "util/collection.h" + +struct render_surface; +struct gbm_device; +struct egl_offscreen_render_surface; + +#define CAST_EGL_OFFSCREEN_RENDER_SURFACE_UNCHECKED(ptr) ((struct egl_offscreen_render_surface *) (ptr)) +#ifdef DEBUG + #define CAST_EGL_OFFSCREEN_RENDER_SURFACE(ptr) __checked_cast_egl_offscreen_render_surface(ptr) +ATTR_PURE struct egl_offscreen_render_surface *__checked_cast_egl_offscreen_render_surface(void *ptr); +#else + #define CAST_EGL_OFFSCREEN_RENDER_SURFACE(ptr) CAST_EGL_OFFSCREEN_RENDER_SURFACE_UNCHECKED(ptr) +#endif + +struct egl_offscreen_render_surface *egl_offscreen_render_surface_new( + struct tracer *tracer, + struct vec2i size, + struct gl_renderer *renderer +); + +ATTR_PURE EGLSurface egl_offscreen_render_surface_get_egl_surface(struct egl_offscreen_render_surface *s); + +ATTR_PURE EGLConfig egl_offscreen_render_surface_get_egl_config(struct egl_offscreen_render_surface *s); + +#endif // _FLUTTERPI_SRC_EGL_GBM_RENDER_SURFACE_H diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 31b8c1ce..35db03b3 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -2165,14 +2165,14 @@ static struct drmdev *find_drmdev(struct libseat *libseat) { return NULL; } -static struct gbm_device *open_rendernode_as_gbm_device() { +UNUSED static struct gbm_device *try_open_rendernode_as_gbm_device() { struct gbm_device *gbm; drmDevicePtr devices[64]; int ok, n_devices; ok = drmGetDevices2(0, devices, sizeof(devices) / sizeof(*devices)); if (ok < 0) { - LOG_ERROR("Could not query DRM device list: %s\n", strerror(-ok)); + LOG_DEBUG("Could not find a usable GPU, couldn't query GPU device list: drmGetDevices2: %s\n", strerror(-ok)); return NULL; } @@ -2209,10 +2209,7 @@ static struct gbm_device *open_rendernode_as_gbm_device() { drmFreeDevices(devices, n_devices); if (gbm == NULL) { - LOG_ERROR( - "flutter-pi couldn't find a usable render device.\n" - "Please make sure you have a GPU connected.\n" - ); + LOG_DEBUG("Could not find a usable GPU.\n"); return NULL; } @@ -2442,10 +2439,7 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { // render node as a GBM device. // There's other ways to get an offscreen EGL display, but we need the gbm_device for other things // (e.g. buffer allocating for the video player, so we just do this.) - gbm_device = open_rendernode_as_gbm_device(); - if (gbm_device == NULL) { - goto fail_destroy_locales; - } + gbm_device = try_open_rendernode_as_gbm_device(); } else { drmdev = find_drmdev(libseat); if (drmdev == NULL) { @@ -2486,11 +2480,21 @@ struct flutterpi *flutterpi_new_from_args(int argc, char **argv) { } else if (renderer_type == kOpenGL_RendererType) { #ifdef HAVE_EGL_GLES2 vk_renderer = NULL; - gl_renderer = gl_renderer_new_from_gbm_device(tracer, gbm_device, cmd_args.has_pixel_format, cmd_args.pixel_format); - if (gl_renderer == NULL) { - LOG_ERROR("Couldn't create EGL/OpenGL renderer.\n"); - ok = EIO; - goto fail_unref_scheduler; + + if (gbm_device != NULL) { + gl_renderer = gl_renderer_new_from_gbm_device(tracer, gbm_device); + if (gl_renderer == NULL) { + LOG_ERROR("Couldn't create EGL/OpenGL renderer.\n"); + ok = EIO; + goto fail_unref_scheduler; + } + } else { + gl_renderer = gl_renderer_new_surfaceless(tracer); + if (gl_renderer == NULL) { + LOG_ERROR("Couldn't create EGL/OpenGL renderer.\n"); + ok = EIO; + goto fail_unref_scheduler; + } } // it seems that after some Raspbian update, regular users are sometimes no longer allowed diff --git a/src/gl_renderer.c b/src/gl_renderer.c index 58b25ea2..961244ab 100644 --- a/src/gl_renderer.c +++ b/src/gl_renderer.c @@ -32,18 +32,6 @@ struct gl_renderer { EGLContext root_context, flutter_rendering_context, flutter_resource_uploading_context, flutter_setup_context; pthread_mutex_t root_context_lock; - bool has_forced_pixel_format; - enum pixfmt pixel_format; - - /** - * @brief If EGL doesn't support EGL_KHR_no_config_context, we need to specify an EGLConfig (basically the framebuffer format) - * for the context. And since all shared contexts need to have the same EGL Config, we basically have to choose a single global config - * for the display. - * - * If this field is not EGL_NO_CONFIG_KHR, all contexts we create need to have exactly this EGL Config. - */ - EGLConfig forced_egl_config; - EGLint major, minor; const char *egl_client_exts; const char *egl_display_exts; @@ -90,15 +78,18 @@ static void *get_proc_address(const char *name) { return address; } -static ATTR_PURE EGLConfig choose_config_with_pixel_format(EGLDisplay display, const EGLint *attrib_list, enum pixfmt pixel_format) { +static ATTR_PURE EGLConfig choose_config( + EGLDisplay display, + const EGLint *attrib_list, + bool has_native_visual_id, EGLint native_visual_id, + int red_size, int green_size, int blue_size, int alpha_size +) { EGLConfig *matching; EGLBoolean egl_ok; EGLint value, n_matched; assert(display != EGL_NO_DISPLAY); - LOG_DEBUG("Choosing EGL config with pixel format %s...\n", get_pixfmt_info(pixel_format)->name); - n_matched = 0; egl_ok = eglChooseConfig(display, attrib_list, NULL, 0, &n_matched); if (egl_ok != EGL_TRUE) { @@ -115,14 +106,69 @@ static ATTR_PURE EGLConfig choose_config_with_pixel_format(EGLDisplay display, c } for (int i = 0; i < n_matched; i++) { - egl_ok = eglGetConfigAttrib(display, matching[i], EGL_NATIVE_VISUAL_ID, &value); - if (egl_ok != EGL_TRUE) { - LOG_EGL_ERROR(eglGetError(), "Could not query pixel format of EGL framebuffer config. eglGetConfigAttrib"); - return EGL_NO_CONFIG_KHR; - } + if (has_native_visual_id) { + egl_ok = eglGetConfigAttrib(display, matching[i], EGL_NATIVE_VISUAL_ID, &value); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not query pixel format of EGL framebuffer config. eglGetConfigAttrib"); + return EGL_NO_CONFIG_KHR; + } + + if (value == native_visual_id) { + // found a config with matching pixel format. + return matching[i]; + } + } else { + egl_ok = eglGetConfigAttrib(display, matching[i], EGL_COLOR_BUFFER_TYPE, &value); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not query buffer type of EGL framebuffer config. eglGetConfigAttrib"); + return EGL_NO_CONFIG_KHR; + } + + if (value != EGL_RGB_BUFFER) { + continue; + } + + egl_ok = eglGetConfigAttrib(display, matching[i], EGL_RED_SIZE, &value); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not query pixel format of EGL framebuffer config. eglGetConfigAttrib"); + return EGL_NO_CONFIG_KHR; + } + + /// TODO: This implicitly depends on fbdev + if (value != red_size) { + continue; + } + + egl_ok = eglGetConfigAttrib(display, matching[i], EGL_GREEN_SIZE, &value); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not query pixel format of EGL framebuffer config. eglGetConfigAttrib"); + return EGL_NO_CONFIG_KHR; + } + + if (value != green_size) { + continue; + } + + egl_ok = eglGetConfigAttrib(display, matching[i], EGL_BLUE_SIZE, &value); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not query pixel format of EGL framebuffer config. eglGetConfigAttrib"); + return EGL_NO_CONFIG_KHR; + } + + if (value != blue_size) { + continue; + } + + egl_ok = eglGetConfigAttrib(display, matching[i], EGL_ALPHA_SIZE, &value); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not query pixel format of EGL framebuffer config. eglGetConfigAttrib"); + return EGL_NO_CONFIG_KHR; + } + + if (value != alpha_size) { + continue; + } - if (int32_to_uint32(value) == get_pixfmt_info(pixel_format)->gbm_format) { - // found a config with matching pixel format. return matching[i]; } } @@ -134,9 +180,7 @@ static const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NON struct gl_renderer *gl_renderer_new_from_gbm_device( struct tracer *tracer, - struct gbm_device *gbm_device, - bool has_forced_pixel_format, - enum pixfmt pixel_format + struct gbm_device *gbm_device ) { struct gl_renderer *renderer; const char *egl_client_exts, *egl_display_exts; @@ -144,7 +188,6 @@ struct gl_renderer *gl_renderer_new_from_gbm_device( EGLContext root_context, flutter_render_context, flutter_resource_uploading_context, flutter_setup_context; EGLDisplay egl_display; EGLBoolean egl_ok; - EGLConfig forced_egl_config; EGLint major, minor; bool supports_egl_ext_platform_base; int ok; @@ -282,48 +325,317 @@ struct gl_renderer *gl_renderer_new_from_gbm_device( goto fail_terminate_display; } - if (check_egl_extension(egl_client_exts, egl_display_exts, "EGL_KHR_no_config_context")) { - // EGL supports creating contexts without an EGLConfig, which is nice. - // Just create a context without selecting a config and let the backing stores (when they're created) select - // the framebuffer config instead. - forced_egl_config = EGL_NO_CONFIG_KHR; + if (!check_egl_extension(egl_client_exts, egl_display_exts, "EGL_KHR_no_config_context")) { + LOG_ERROR("EGL does not support EGL_no_config_context, which is required for flutter-pi.\n"); + goto fail_terminate_display; + } + + root_context = eglCreateContext(egl_display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, context_attribs); + if (root_context == EGL_NO_CONTEXT) { + LOG_EGL_ERROR(eglGetError(), "Could not create EGL context for OpenGL ES. eglCreateContext"); + goto fail_terminate_display; + } + + flutter_render_context = eglCreateContext(egl_display, EGL_NO_CONFIG_KHR, root_context, context_attribs); + if (flutter_render_context == EGL_NO_CONTEXT) { + LOG_EGL_ERROR(eglGetError(), "Could not create EGL OpenGL ES context for flutter rendering. eglCreateContext"); + goto fail_destroy_root_context; + } + + flutter_resource_uploading_context = eglCreateContext(egl_display, EGL_NO_CONFIG_KHR, root_context, context_attribs); + if (flutter_resource_uploading_context == EGL_NO_CONTEXT) { + LOG_EGL_ERROR(eglGetError(), "Could not create EGL OpenGL ES context for flutter resource uploads. eglCreateContext"); + goto fail_destroy_flutter_render_context; + } + + flutter_setup_context = eglCreateContext(egl_display, EGL_NO_CONFIG_KHR, root_context, context_attribs); + if (flutter_setup_context == EGL_NO_CONTEXT) { + LOG_EGL_ERROR(eglGetError(), "Could not create EGL OpenGL ES context for flutter initialization. eglCreateContext"); + goto fail_destroy_flutter_resource_uploading_context; + } + + egl_ok = eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, root_context); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not make EGL OpenGL ES root context current to query OpenGL information. eglMakeCurrent"); + goto fail_destroy_flutter_setup_context; + } + + gl_renderer = (const char *) glGetString(GL_RENDERER); + if (gl_renderer == NULL) { + LOG_ERROR("Couldn't query OpenGL ES renderer information.\n"); + goto fail_clear_current; + } + + gl_exts = (const char *) glGetString(GL_EXTENSIONS); + if (gl_exts == NULL) { + LOG_ERROR("Couldn't query supported OpenGL ES extensions.\n"); + goto fail_clear_current; + } + + LOG_DEBUG_UNPREFIXED( + "===================================\n" + "EGL information:\n" + " version: %s\n" + " vendor: %s\n" + " client extensions: %s\n" + " display extensions: %s\n" + "===================================\n", + eglQueryString(egl_display, EGL_VERSION), + eglQueryString(egl_display, EGL_VENDOR), + egl_client_exts, + egl_display_exts + ); + + // this needs to be here because the EGL context needs to be current. + LOG_DEBUG_UNPREFIXED( + "===================================\n" + "OpenGL ES information:\n" + " version: \"%s\"\n" + " shading language version: \"%s\"\n" + " vendor: \"%s\"\n" + " renderer: \"%s\"\n" + " extensions: \"%s\"\n" + "===================================\n", + glGetString(GL_VERSION), + glGetString(GL_SHADING_LANGUAGE_VERSION), + glGetString(GL_VENDOR), + gl_renderer, + gl_exts + ); + + egl_ok = eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Could not clear EGL OpenGL ES context. eglMakeCurrent"); + goto fail_destroy_flutter_resource_uploading_context; + } + + ok = pthread_mutex_init(&renderer->root_context_lock, NULL); + if (ok < 0) { + goto fail_destroy_flutter_resource_uploading_context; + } + + renderer->n_refs = REFCOUNT_INIT_1; + renderer->tracer = tracer_ref(tracer); + renderer->gbm_device = gbm_device; + renderer->egl_display = egl_display; + renderer->root_context = root_context; + renderer->flutter_rendering_context = flutter_render_context; + renderer->flutter_resource_uploading_context = flutter_resource_uploading_context; + renderer->flutter_setup_context = flutter_setup_context; + renderer->major = major; + renderer->minor = minor; + renderer->egl_client_exts = egl_client_exts; + renderer->egl_display_exts = egl_display_exts; + renderer->gl_renderer = gl_renderer; + renderer->gl_exts = gl_exts; + renderer->supports_egl_ext_platform_base = supports_egl_ext_platform_base; +#ifdef EGL_EXT_platform_base + renderer->egl_get_platform_display_ext = egl_get_platform_display_ext; + renderer->egl_create_platform_window_surface_ext = egl_create_platform_window_surface_ext; +#endif +#ifdef EGL_VERSION_1_5 + renderer->egl_get_platform_display = egl_get_platform_display; + renderer->egl_create_platform_window_surface = egl_create_platform_window_surface; +#endif + return renderer; + +fail_clear_current: + eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + +fail_destroy_flutter_setup_context: + eglDestroyContext(egl_display, flutter_setup_context); + +fail_destroy_flutter_resource_uploading_context: + eglDestroyContext(egl_display, flutter_resource_uploading_context); + +fail_destroy_flutter_render_context: + eglDestroyContext(egl_display, flutter_render_context); + +fail_destroy_root_context: + eglDestroyContext(egl_display, root_context); + +fail_terminate_display: + eglTerminate(egl_display); + +fail_free_renderer: + free(renderer); + +fail_return_null: + return NULL; +} + +struct gl_renderer *gl_renderer_new_surfaceless(struct tracer *tracer) { + struct gl_renderer *renderer; + const char *egl_client_exts, *egl_display_exts; + const char *gl_renderer, *gl_exts; + EGLContext root_context, flutter_render_context, flutter_resource_uploading_context, flutter_setup_context; + EGLDisplay egl_display; + EGLBoolean egl_ok; + EGLint major, minor; + bool supports_egl_ext_platform_base; + int ok; + + renderer = malloc(sizeof *renderer); + if (renderer == NULL) { + goto fail_return_null; + } + + egl_client_exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (egl_client_exts == NULL) { + LOG_EGL_ERROR(eglGetError(), "Couldn't query EGL client extensions. eglQueryString"); + goto fail_free_renderer; + } + + if (!check_egl_extension(egl_client_exts, NULL, "EGL_MESA_platform_surfaceless")) { + LOG_ERROR("EGL does not support surfaceless platform.\n"); + goto fail_free_renderer; + } + + if (check_egl_extension(egl_client_exts, NULL, "EGL_EXT_platform_base")) { +#ifdef EGL_EXT_platform_base + supports_egl_ext_platform_base = true; +#else + LOG_ERROR( + "EGL supports EGL_EXT_platform_base, but EGL headers didn't contain definitions for EGL_EXT_platform_base." + "eglGetPlatformDisplayEXT and eglCreatePlatformWindowSurfaceEXT will not be used to create an EGL display.\n" + ); + supports_egl_ext_platform_base = false; +#endif } else { - // choose a config - const EGLint config_attribs[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SAMPLES, 0, EGL_NONE, - }; - - if (has_forced_pixel_format == false) { - has_forced_pixel_format = true; - pixel_format = PIXFMT_ARGB8888; + supports_egl_ext_platform_base = false; + } + +// PFNEGLGETPLATFORMDISPLAYEXTPROC, PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC +// are defined by EGL_EXT_platform_base. +#ifdef EGL_EXT_platform_base + PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_ext; + PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC egl_create_platform_window_surface_ext; +#endif + + if (supports_egl_ext_platform_base) { +#ifdef EGL_EXT_platform_base + egl_get_platform_display_ext = try_get_proc_address("eglGetPlatformDisplayEXT"); + if (egl_get_platform_display_ext == NULL) { + LOG_ERROR("Couldn't resolve \"eglGetPlatformDisplayEXT\" even though \"EGL_EXT_platform_base\" was listed as supported.\n"); + supports_egl_ext_platform_base = false; + } +#else + UNREACHABLE(); +#endif + } + + if (supports_egl_ext_platform_base) { +#ifdef EGL_EXT_platform_base + egl_create_platform_window_surface_ext = try_get_proc_address("eglCreatePlatformWindowSurfaceEXT"); + if (egl_create_platform_window_surface_ext == NULL) { + LOG_ERROR( + "Couldn't resolve \"eglCreatePlatformWindowSurfaceEXT\" even though \"EGL_EXT_platform_base\" was listed as supported.\n" + ); + egl_get_platform_display_ext = NULL; + supports_egl_ext_platform_base = false; + } +#else + UNREACHABLE(); +#endif + } + +// EGL_PLATFORM_GBM_KHR is defined by EGL_KHR_platform_gbm. +#ifndef EGL_KHR_platform_gbm + #error "EGL extension EGL_KHR_platform_gbm is required." +#endif + + egl_display = EGL_NO_DISPLAY; + bool failed_before = false; + +#ifdef EGL_VERSION_1_5 + PFNEGLGETPLATFORMDISPLAYPROC egl_get_platform_display = try_get_proc_address("eglGetPlatformDisplay"); + + if (egl_display == EGL_NO_DISPLAY && egl_get_platform_display != NULL) { + egl_display = egl_get_platform_display(EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, NULL); + if (egl_display == EGL_NO_DISPLAY) { + LOG_EGL_ERROR(eglGetError(), "Could not get surfaceless EGL display. eglGetPlatformDisplay"); + failed_before = true; + } + } +#endif + +#ifdef EGL_EXT_platform_base + if (egl_display == EGL_NO_DISPLAY && egl_get_platform_display_ext != NULL) { + if (failed_before) { + LOG_DEBUG("Attempting eglGetPlatformDisplayEXT...\n"); + } + + egl_display = egl_get_platform_display_ext(EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, NULL); + if (egl_display == EGL_NO_DISPLAY) { + LOG_EGL_ERROR(eglGetError(), "Could not get EGL display from GBM device. eglGetPlatformDisplayEXT"); + failed_before = true; + } + } +#endif + + if (egl_display == EGL_NO_DISPLAY) { + if (failed_before) { + LOG_DEBUG("Attempting eglGetDisplay...\n"); } - forced_egl_config = choose_config_with_pixel_format(egl_display, config_attribs, pixel_format); - if (forced_egl_config == EGL_NO_CONFIG_KHR) { - LOG_ERROR("No fitting EGL framebuffer configuration found.\n"); - goto fail_terminate_display; + egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (egl_display == EGL_NO_DISPLAY) { + LOG_EGL_ERROR(eglGetError(), "Could not get surfaceless EGL display. eglGetDisplay"); } } - root_context = eglCreateContext(egl_display, forced_egl_config, EGL_NO_CONTEXT, context_attribs); + if (egl_display == EGL_NO_DISPLAY) { + LOG_ERROR("Could not get EGL Display from any function.\n"); + goto fail_free_renderer; + } + + egl_ok = eglInitialize(egl_display, &major, &minor); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Failed to initialize EGL! eglInitialize:"); + goto fail_free_renderer; + } + + egl_display_exts = eglQueryString(egl_display, EGL_EXTENSIONS); + if (egl_display_exts == NULL) { + LOG_EGL_ERROR(eglGetError(), "Couldn't query EGL display extensions. eglQueryString"); + goto fail_terminate_display; + } + + if (!check_egl_extension(egl_client_exts, egl_display_exts, "EGL_KHR_surfaceless_context")) { + LOG_ERROR("EGL doesn't support the EGL_KHR_surfaceless_context extension, which is required by flutter-pi.\n"); + goto fail_terminate_display; + } + + egl_ok = eglBindAPI(EGL_OPENGL_ES_API); + if (egl_ok != EGL_TRUE) { + LOG_EGL_ERROR(eglGetError(), "Couldn't bind OpenGL ES API to EGL. eglBindAPI"); + goto fail_terminate_display; + } + + if (!check_egl_extension(egl_client_exts, egl_display_exts, "EGL_KHR_no_config_context")) { + LOG_ERROR("EGL does not support EGL_no_config_context, which is required for flutter-pi.\n"); + goto fail_terminate_display; + } + + root_context = eglCreateContext(egl_display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, context_attribs); if (root_context == EGL_NO_CONTEXT) { LOG_EGL_ERROR(eglGetError(), "Could not create EGL context for OpenGL ES. eglCreateContext"); goto fail_terminate_display; } - flutter_render_context = eglCreateContext(egl_display, forced_egl_config, root_context, context_attribs); + flutter_render_context = eglCreateContext(egl_display, EGL_NO_CONFIG_KHR, root_context, context_attribs); if (flutter_render_context == EGL_NO_CONTEXT) { LOG_EGL_ERROR(eglGetError(), "Could not create EGL OpenGL ES context for flutter rendering. eglCreateContext"); goto fail_destroy_root_context; } - flutter_resource_uploading_context = eglCreateContext(egl_display, forced_egl_config, root_context, context_attribs); + flutter_resource_uploading_context = eglCreateContext(egl_display, EGL_NO_CONFIG_KHR, root_context, context_attribs); if (flutter_resource_uploading_context == EGL_NO_CONTEXT) { LOG_EGL_ERROR(eglGetError(), "Could not create EGL OpenGL ES context for flutter resource uploads. eglCreateContext"); goto fail_destroy_flutter_render_context; } - flutter_setup_context = eglCreateContext(egl_display, forced_egl_config, root_context, context_attribs); + flutter_setup_context = eglCreateContext(egl_display, EGL_NO_CONFIG_KHR, root_context, context_attribs); if (flutter_setup_context == EGL_NO_CONTEXT) { LOG_EGL_ERROR(eglGetError(), "Could not create EGL OpenGL ES context for flutter initialization. eglCreateContext"); goto fail_destroy_flutter_resource_uploading_context; @@ -391,15 +703,12 @@ struct gl_renderer *gl_renderer_new_from_gbm_device( renderer->n_refs = REFCOUNT_INIT_1; renderer->tracer = tracer_ref(tracer); - renderer->gbm_device = gbm_device; + renderer->gbm_device = NULL; renderer->egl_display = egl_display; renderer->root_context = root_context; renderer->flutter_rendering_context = flutter_render_context; renderer->flutter_resource_uploading_context = flutter_resource_uploading_context; renderer->flutter_setup_context = flutter_setup_context; - renderer->has_forced_pixel_format = has_forced_pixel_format; - renderer->pixel_format = pixel_format; - renderer->forced_egl_config = forced_egl_config; renderer->major = major; renderer->minor = minor; renderer->egl_client_exts = egl_client_exts; @@ -413,7 +722,7 @@ struct gl_renderer *gl_renderer_new_from_gbm_device( #endif #ifdef EGL_VERSION_1_5 renderer->egl_get_platform_display = egl_get_platform_display; - renderer->egl_create_platform_window_surface = egl_create_platform_window_surface; + renderer->egl_create_platform_window_surface = NULL; #endif return renderer; @@ -442,6 +751,7 @@ struct gl_renderer *gl_renderer_new_from_gbm_device( return NULL; } + void gl_renderer_destroy(struct gl_renderer *renderer) { ASSERT_NOT_NULL(renderer); tracer_unref(renderer->tracer); @@ -455,27 +765,6 @@ void gl_renderer_destroy(struct gl_renderer *renderer) { DEFINE_REF_OPS(gl_renderer, n_refs) -bool gl_renderer_has_forced_pixel_format(struct gl_renderer *renderer) { - ASSERT_NOT_NULL(renderer); - return renderer->has_forced_pixel_format; -} - -enum pixfmt gl_renderer_get_forced_pixel_format(struct gl_renderer *renderer) { - ASSERT_NOT_NULL(renderer); - assert(renderer->has_forced_pixel_format); - return renderer->pixel_format; -} - -bool gl_renderer_has_forced_egl_config(struct gl_renderer *renderer) { - ASSERT_NOT_NULL(renderer); - return renderer->forced_egl_config != EGL_NO_CONFIG_KHR; -} - -EGLConfig gl_renderer_get_forced_egl_config(struct gl_renderer *renderer) { - ASSERT_NOT_NULL(renderer); - return renderer->forced_egl_config; -} - struct gbm_device *gl_renderer_get_gbm_device(struct gl_renderer *renderer) { return renderer->gbm_device; } @@ -567,7 +856,7 @@ EGLContext gl_renderer_create_context(struct gl_renderer *renderer) { ASSERT_NOT_NULL(renderer); pthread_mutex_lock(&renderer->root_context_lock); - context = eglCreateContext(renderer->egl_display, renderer->forced_egl_config, renderer->root_context, context_attribs); + context = eglCreateContext(renderer->egl_display, EGL_NO_CONFIG_KHR, renderer->root_context, context_attribs); pthread_mutex_unlock(&renderer->root_context_lock); if (context == EGL_NO_CONTEXT) { @@ -607,7 +896,7 @@ int gl_renderer_make_this_a_render_thread(struct gl_renderer *renderer) { assert(is_render_thread == false); pthread_mutex_lock(&renderer->root_context_lock); - context = eglCreateContext(renderer->egl_display, renderer->forced_egl_config, renderer->root_context, context_attribs); + context = eglCreateContext(renderer->egl_display, EGL_NO_CONFIG_KHR, renderer->root_context, context_attribs); pthread_mutex_unlock(&renderer->root_context_lock); if (context == EGL_NO_CONTEXT) { @@ -654,34 +943,52 @@ void gl_renderer_cleanup_this_render_thread() { } } -ATTR_PURE EGLConfig -gl_renderer_choose_config(struct gl_renderer *renderer, bool has_desired_pixel_format, enum pixfmt desired_pixel_format) { +ATTR_PURE EGLConfig gl_renderer_choose_gbm_window_config( + struct gl_renderer *renderer, + enum pixfmt pixel_format +) { ASSERT_NOT_NULL(renderer); + ASSUME_PIXFMT_VALID(pixel_format); - if (renderer->forced_egl_config != EGL_NO_CONFIG_KHR) { - return renderer->forced_egl_config; - } - - const EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SAMPLES, 0, EGL_NONE }; + const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_SAMPLES, 0, + EGL_NONE, + }; - return choose_config_with_pixel_format( + return choose_config( renderer->egl_display, config_attribs, - renderer->has_forced_pixel_format ? renderer->pixel_format : - has_desired_pixel_format ? desired_pixel_format : - PIXFMT_ARGB8888 + true, + get_pixfmt_info(pixel_format)->gbm_format, + -1, -1, -1, -1 ); } -ATTR_PURE EGLConfig gl_renderer_choose_config_direct(struct gl_renderer *renderer, enum pixfmt pixel_format) { +ATTR_PURE EGLConfig gl_renderer_choose_pbuffer_config( + struct gl_renderer *renderer, + int red_size, + int green_size, + int blue_size, + int alpha_size +) { ASSERT_NOT_NULL(renderer); - ASSUME_PIXFMT_VALID(pixel_format); const EGLint config_attribs[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SAMPLES, 0, EGL_NONE, + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_SAMPLES, 0, + EGL_NONE, }; - return choose_config_with_pixel_format(renderer->egl_display, config_attribs, pixel_format); + return choose_config( + renderer->egl_display, + config_attribs, + false, + EGL_NONE, + red_size, green_size, blue_size, alpha_size + ); } EGLSurface gl_renderer_create_gbm_window_surface( @@ -735,3 +1042,21 @@ EGLSurface gl_renderer_create_gbm_window_surface( return surface; } + +EGLSurface gl_renderer_create_pbuffer_surface( + struct gl_renderer *renderer, + EGLConfig config, + const EGLAttribKHR *attrib_list, + const EGLint *int_attrib_list +) { + EGLSurface surface; + + (void) attrib_list; + + surface = eglCreatePbufferSurface(renderer->egl_display, config, int_attrib_list); + if (surface == EGL_NO_SURFACE) { + LOG_EGL_ERROR(eglGetError(), "Couldn't create pixelbuffer surface. eglCreatePbufferSurface"); + } + + return surface; +} diff --git a/src/gl_renderer.h b/src/gl_renderer.h index d6c8160a..4febf69f 100644 --- a/src/gl_renderer.h +++ b/src/gl_renderer.h @@ -30,11 +30,11 @@ struct tracer; struct gl_renderer *gl_renderer_new_from_gbm_device( struct tracer *tracer, - struct gbm_device *gbm_device, - bool has_forced_pixel_format, - enum pixfmt pixel_format + struct gbm_device *gbm_device ); +struct gl_renderer *gl_renderer_new_surfaceless(struct tracer *tracer); + void gl_renderer_destroy(struct gl_renderer *renderer); DECLARE_REF_OPS(gl_renderer) @@ -73,9 +73,18 @@ int gl_renderer_make_this_a_render_thread(struct gl_renderer *renderer); void gl_renderer_cleanup_this_render_thread(); -ATTR_PURE EGLConfig gl_renderer_choose_config(struct gl_renderer *renderer, bool has_desired_pixel_format, enum pixfmt desired_pixel_format); +ATTR_PURE EGLConfig gl_renderer_choose_gbm_window_config( + struct gl_renderer *renderer, + enum pixfmt pixel_format +); -ATTR_PURE EGLConfig gl_renderer_choose_config_direct(struct gl_renderer *renderer, enum pixfmt pixel_format); +ATTR_PURE EGLConfig gl_renderer_choose_pbuffer_config( + struct gl_renderer *renderer, + int red_size, + int green_size, + int blue_size, + int alpha_size +); EGLSurface gl_renderer_create_gbm_window_surface( struct gl_renderer *renderer, @@ -85,4 +94,11 @@ EGLSurface gl_renderer_create_gbm_window_surface( const EGLint *int_attrib_list ); +EGLSurface gl_renderer_create_pbuffer_surface( + struct gl_renderer *renderer, + EGLConfig config, + const EGLAttribKHR *attrib_list, + const EGLint *int_attrib_list +); + #endif // _FLUTTERPI_SRC_GL_RENDERER_H diff --git a/src/window.c b/src/window.c index 687a747f..b83dffec 100644 --- a/src/window.c +++ b/src/window.c @@ -33,6 +33,7 @@ #ifdef HAVE_EGL_GLES2 #include "egl_gbm_render_surface.h" + #include "egl_offscreen_render_surface.h" #include "gl_renderer.h" #endif @@ -1694,15 +1695,10 @@ static struct render_surface *dummy_window_get_render_surface_internal(struct wi #error "EGL header definitions for extension EGL_KHR_no_config_context are required." #endif - struct egl_gbm_render_surface *egl_surface = egl_gbm_render_surface_new_with_egl_config( + struct egl_offscreen_render_surface *egl_surface = egl_offscreen_render_surface_new( window->tracer, size, - gl_renderer_get_gbm_device(window->gl_renderer), - window->gl_renderer, - window->has_forced_pixel_format ? window->forced_pixel_format : PIXFMT_ARGB8888, - EGL_NO_CONFIG_KHR, - NULL, - 0 + window->gl_renderer ); if (egl_surface == NULL) { LOG_ERROR("Couldn't create EGL GBM rendering surface.\n"); @@ -1750,7 +1746,7 @@ static EGLSurface dummy_window_get_egl_surface(struct window *window) { if (window->renderer_type == kOpenGL_RendererType) { struct render_surface *render_surface = dummy_window_get_render_surface_internal(window, false, VEC2I(0, 0)); - return egl_gbm_render_surface_get_egl_surface(CAST_EGL_GBM_RENDER_SURFACE(render_surface)); + return egl_offscreen_render_surface_get_egl_surface(CAST_EGL_OFFSCREEN_RENDER_SURFACE(render_surface)); } else { return EGL_NO_SURFACE; }