|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | +/* |
| 3 | + * GBM Surface Backing Stores |
| 4 | + * |
| 5 | + * - implements EGL/GL render surfaces using a gbm surface |
| 6 | + * - ideal way to create render surfaces right now |
| 7 | + * |
| 8 | + * Copyright (c) 2022, Hannes Winkler <[email protected]> |
| 9 | + */ |
| 10 | + |
| 11 | +#include "egl_offscreen_render_surface.h" |
| 12 | + |
| 13 | +#include <errno.h> |
| 14 | +#include <stdlib.h> |
| 15 | + |
| 16 | +#include "egl.h" |
| 17 | +#include "gl_renderer.h" |
| 18 | +#include "gles.h" |
| 19 | +#include "modesetting.h" |
| 20 | +#include "pixel_format.h" |
| 21 | +#include "render_surface.h" |
| 22 | +#include "render_surface_private.h" |
| 23 | +#include "surface.h" |
| 24 | +#include "tracer.h" |
| 25 | +#include "util/collection.h" |
| 26 | +#include "util/logging.h" |
| 27 | +#include "util/refcounting.h" |
| 28 | + |
| 29 | +struct egl_offscreen_render_surface; |
| 30 | + |
| 31 | +struct egl_offscreen_render_surface { |
| 32 | + union { |
| 33 | + struct surface surface; |
| 34 | + struct render_surface render_surface; |
| 35 | + }; |
| 36 | + |
| 37 | +#ifdef DEBUG |
| 38 | + uuid_t uuid; |
| 39 | +#endif |
| 40 | + |
| 41 | + enum pixfmt pixel_format; |
| 42 | + EGLDisplay egl_display; |
| 43 | + EGLSurface egl_surface; |
| 44 | + EGLConfig egl_config; |
| 45 | + struct gl_renderer *renderer; |
| 46 | +}; |
| 47 | + |
| 48 | +COMPILE_ASSERT(offsetof(struct egl_offscreen_render_surface, surface) == 0); |
| 49 | +COMPILE_ASSERT(offsetof(struct egl_offscreen_render_surface, render_surface.surface) == 0); |
| 50 | + |
| 51 | +#ifdef DEBUG |
| 52 | +static const uuid_t uuid = CONST_UUID(0xf9, 0xab, 0x5d, 0xad, 0x2e, 0x3b, 0x4e, 0x2c, 0x9d, 0x26, 0x64, 0x70, 0xfa, 0x9a, 0x25, 0xab); |
| 53 | +#endif |
| 54 | + |
| 55 | +#define CAST_THIS(ptr) CAST_EGL_OFFSCREEN_RENDER_SURFACE(ptr) |
| 56 | +#define CAST_THIS_UNCHECKED(ptr) CAST_EGL_OFFSCREEN_RENDER_SURFACE_UNCHECKED(ptr) |
| 57 | + |
| 58 | +#ifdef DEBUG |
| 59 | +ATTR_PURE struct egl_offscreen_render_surface *__checked_cast_egl_offscreen_render_surface(void *ptr) { |
| 60 | + struct egl_offscreen_render_surface *s; |
| 61 | + |
| 62 | + s = CAST_EGL_OFFSCREEN_RENDER_SURFACE_UNCHECKED(ptr); |
| 63 | + assert(uuid_equals(s->uuid, uuid)); |
| 64 | + return s; |
| 65 | +} |
| 66 | +#endif |
| 67 | + |
| 68 | +void egl_offscreen_render_surface_deinit(struct surface *s); |
| 69 | +static int egl_offscreen_render_surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder); |
| 70 | +static int egl_offscreen_render_surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder); |
| 71 | +static int egl_offscreen_render_surface_fill(struct render_surface *s, FlutterBackingStore *fl_store); |
| 72 | +static int egl_offscreen_render_surface_queue_present(struct render_surface *s, const FlutterBackingStore *fl_store); |
| 73 | + |
| 74 | +static int egl_offscreen_render_surface_init( |
| 75 | + struct egl_offscreen_render_surface *s, |
| 76 | + struct tracer *tracer, |
| 77 | + struct vec2i size, |
| 78 | + struct gl_renderer *renderer, |
| 79 | + enum pixfmt pixel_format, |
| 80 | + EGLConfig egl_config |
| 81 | +) { |
| 82 | + EGLDisplay egl_display; |
| 83 | + EGLSurface egl_surface; |
| 84 | + EGLBoolean egl_ok; |
| 85 | + int ok; |
| 86 | + |
| 87 | + ASSERT_NOT_NULL(renderer); |
| 88 | + ASSUME_PIXFMT_VALID(pixel_format); |
| 89 | + egl_display = gl_renderer_get_egl_display(renderer); |
| 90 | + ASSERT_NOT_NULL(egl_display); |
| 91 | + |
| 92 | +#ifdef DEBUG |
| 93 | + if (egl_config != EGL_NO_CONFIG_KHR) { |
| 94 | + EGLint value = 0; |
| 95 | + |
| 96 | + egl_ok = eglGetConfigAttrib(egl_display, egl_config, EGL_NATIVE_VISUAL_ID, &value); |
| 97 | + if (egl_ok == EGL_FALSE) { |
| 98 | + LOG_EGL_ERROR(eglGetError(), "Couldn't query pixel format of EGL framebuffer config. eglGetConfigAttrib"); |
| 99 | + return EIO; |
| 100 | + } |
| 101 | + |
| 102 | + ASSERT_EQUALS_MSG( |
| 103 | + value, |
| 104 | + get_pixfmt_info(pixel_format)->gbm_format, |
| 105 | + "EGL framebuffer config pixel format doesn't match the argument pixel format." |
| 106 | + ); |
| 107 | + } |
| 108 | +#endif |
| 109 | + |
| 110 | + /// TODO: Think about allowing different tilings / modifiers here |
| 111 | + if (egl_config == EGL_NO_CONFIG_KHR) { |
| 112 | + // choose a config |
| 113 | + egl_config = gl_renderer_choose_config_direct(renderer, pixel_format); |
| 114 | + if (egl_config == EGL_NO_CONFIG_KHR) { |
| 115 | + LOG_ERROR( |
| 116 | + "EGL doesn't supported the specified pixel format %s. Try a different one (ARGB8888 should always work).\n", |
| 117 | + get_pixfmt_info(pixel_format)->name |
| 118 | + ); |
| 119 | + return EINVAL; |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | +// EGLAttribKHR is defined by EGL_KHR_cl_event2. |
| 124 | +#ifndef EGL_KHR_cl_event2 |
| 125 | + #error "EGL header definitions for extension EGL_KHR_cl_event2 are required." |
| 126 | +#endif |
| 127 | + |
| 128 | + static const EGLAttribKHR surface_attribs[] = { |
| 129 | + /* EGL_GL_COLORSPACE, GL_LINEAR / GL_SRGB */ |
| 130 | + /* EGL_RENDER_BUFFER, EGL_BACK_BUFFER / EGL_SINGLE_BUFFER */ |
| 131 | + /* EGL_VG_ALPHA_FORMAT, EGL_VG_ALPHA_FORMAT_NONPRE / EGL_VG_ALPHA_FORMAT_PRE */ |
| 132 | + /* EGL_VG_COLORSPACE, EGL_VG_COLORSPACE_sRGB / EGL_VG_COLORSPACE_LINEAR */ |
| 133 | + EGL_NONE, |
| 134 | + }; |
| 135 | + |
| 136 | + (void) surface_attribs; |
| 137 | + |
| 138 | + egl_ok = eglBindAPI(EGL_OPENGL_ES_API); |
| 139 | + if (egl_ok == EGL_FALSE) { |
| 140 | + LOG_EGL_ERROR(eglGetError(), "Couldn't bind OpenGL ES API to EGL. eglBindAPI"); |
| 141 | + return EIO; |
| 142 | + } |
| 143 | + |
| 144 | + egl_surface = gl_renderer_create_pbuffer_surface(renderer, egl_config, NULL, NULL); |
| 145 | + if (egl_surface == EGL_NO_SURFACE) { |
| 146 | + return EIO; |
| 147 | + } |
| 148 | + |
| 149 | + /// TODO: Implement |
| 150 | + ok = render_surface_init(CAST_RENDER_SURFACE_UNCHECKED(s), tracer, size); |
| 151 | + if (ok != 0) { |
| 152 | + goto fail_destroy_egl_surface; |
| 153 | + } |
| 154 | + |
| 155 | + s->surface.present_kms = egl_offscreen_render_surface_present_kms; |
| 156 | + s->surface.present_fbdev = egl_offscreen_render_surface_present_fbdev; |
| 157 | + s->surface.deinit = egl_offscreen_render_surface_deinit; |
| 158 | + s->render_surface.fill = egl_offscreen_render_surface_fill; |
| 159 | + s->render_surface.queue_present = egl_offscreen_render_surface_queue_present; |
| 160 | +#ifdef DEBUG |
| 161 | + uuid_copy(&s->uuid, uuid); |
| 162 | +#endif |
| 163 | + s->pixel_format = pixel_format; |
| 164 | + s->egl_display = egl_display; |
| 165 | + s->egl_surface = egl_surface; |
| 166 | + s->egl_config = egl_config; |
| 167 | + s->renderer = gl_renderer_ref(renderer); |
| 168 | + return 0; |
| 169 | + |
| 170 | +fail_destroy_egl_surface: |
| 171 | + eglDestroySurface(egl_display, egl_surface); |
| 172 | + |
| 173 | + return ok; |
| 174 | +} |
| 175 | + |
| 176 | +/** |
| 177 | + * @brief Create a new gbm_surface based render surface, with an explicit EGL Config for the created EGLSurface. |
| 178 | + * |
| 179 | + * @param compositor The compositor that this surface will be registered to when calling surface_register. |
| 180 | + * @param size The size of the surface. |
| 181 | + * @param renderer The EGL/OpenGL used to create any GL surfaces. |
| 182 | + * @param pixel_format The pixel format to be used by the framebuffers of the surface. |
| 183 | + * @param egl_config The EGLConfig used for creating the EGLSurface. |
| 184 | + * @param allowed_modifiers The list of modifiers that gbm_surface_create_with_modifiers can choose from. |
| 185 | + * NULL if not specified. (In that case, gbm_surface_create will be used) |
| 186 | + * @param n_allowed_modifiers The number of modifiers in @param allowed_modifiers. |
| 187 | + * @return struct egl_offscreen_render_surface* |
| 188 | + */ |
| 189 | +struct egl_offscreen_render_surface *egl_offscreen_render_surface_new_with_egl_config( |
| 190 | + struct tracer *tracer, |
| 191 | + struct vec2i size, |
| 192 | + struct gl_renderer *renderer, |
| 193 | + enum pixfmt pixel_format, |
| 194 | + EGLConfig egl_config |
| 195 | +) { |
| 196 | + struct egl_offscreen_render_surface *surface; |
| 197 | + int ok; |
| 198 | + |
| 199 | + surface = malloc(sizeof *surface); |
| 200 | + if (surface == NULL) { |
| 201 | + goto fail_return_null; |
| 202 | + } |
| 203 | + |
| 204 | + ok = egl_offscreen_render_surface_init( |
| 205 | + surface, |
| 206 | + tracer, |
| 207 | + size, |
| 208 | + renderer, |
| 209 | + pixel_format, |
| 210 | + egl_config |
| 211 | + ); |
| 212 | + if (ok != 0) { |
| 213 | + goto fail_free_surface; |
| 214 | + } |
| 215 | + |
| 216 | + return surface; |
| 217 | + |
| 218 | +fail_free_surface: |
| 219 | + free(surface); |
| 220 | + |
| 221 | +fail_return_null: |
| 222 | + return NULL; |
| 223 | +} |
| 224 | + |
| 225 | +/** |
| 226 | + * @brief Create a new gbm_surface based render surface. |
| 227 | + * |
| 228 | + * @param compositor The compositor that this surface will be registered to when calling surface_register. |
| 229 | + * @param size The size of the surface. |
| 230 | + * @param device The GBM device used to allocate the surface. |
| 231 | + * @param renderer The EGL/OpenGL used to create any GL surfaces. |
| 232 | + * @param pixel_format The pixel format to be used by the framebuffers of the surface. |
| 233 | + * @return struct egl_offscreen_render_surface* |
| 234 | + */ |
| 235 | +struct egl_offscreen_render_surface *egl_offscreen_render_surface_new( |
| 236 | + struct tracer *tracer, |
| 237 | + struct vec2i size, |
| 238 | + struct gl_renderer *renderer, |
| 239 | + enum pixfmt pixel_format |
| 240 | +) { |
| 241 | + return egl_offscreen_render_surface_new_with_egl_config(tracer, size, renderer, pixel_format, EGL_NO_CONFIG_KHR); |
| 242 | +} |
| 243 | + |
| 244 | +void egl_offscreen_render_surface_deinit(struct surface *s) { |
| 245 | + struct egl_offscreen_render_surface *egl_surface; |
| 246 | + |
| 247 | + egl_surface = CAST_THIS(s); |
| 248 | + |
| 249 | + gl_renderer_unref(egl_surface->renderer); |
| 250 | + render_surface_deinit(s); |
| 251 | +} |
| 252 | + |
| 253 | +static int egl_offscreen_render_surface_present_kms(struct surface *s, const struct fl_layer_props *props, struct kms_req_builder *builder) { |
| 254 | + (void) s; |
| 255 | + (void) props; |
| 256 | + (void) builder; |
| 257 | + |
| 258 | + UNIMPLEMENTED(); |
| 259 | + |
| 260 | + return 0; |
| 261 | +} |
| 262 | + |
| 263 | +static int |
| 264 | +egl_offscreen_render_surface_present_fbdev(struct surface *s, const struct fl_layer_props *props, struct fbdev_commit_builder *builder) { |
| 265 | + struct egl_offscreen_render_surface *egl_surface; |
| 266 | + |
| 267 | + /// TODO: Implement by mmapping the current front bo, copy it into the fbdev |
| 268 | + /// TODO: Print a warning here if we're not using explicit linear tiling and use glReadPixels instead of gbm_bo_map in that case |
| 269 | + |
| 270 | + egl_surface = CAST_THIS(s); |
| 271 | + (void) egl_surface; |
| 272 | + (void) props; |
| 273 | + (void) builder; |
| 274 | + |
| 275 | + UNIMPLEMENTED(); |
| 276 | + |
| 277 | + return 0; |
| 278 | +} |
| 279 | + |
| 280 | +static int egl_offscreen_render_surface_fill(struct render_surface *s, FlutterBackingStore *fl_store) { |
| 281 | + fl_store->type = kFlutterBackingStoreTypeOpenGL; |
| 282 | + fl_store->open_gl = (FlutterOpenGLBackingStore |
| 283 | + ){ .type = kFlutterOpenGLTargetTypeFramebuffer, |
| 284 | + .framebuffer = { /* for some reason flutter wants this to be GL_BGRA8_EXT, contrary to what the docs say */ |
| 285 | + .target = GL_BGRA8_EXT, |
| 286 | + |
| 287 | + /* 0 refers to the window surface, instead of to an FBO */ |
| 288 | + .name = 0, |
| 289 | + |
| 290 | + /* |
| 291 | + * even though the compositor will call surface_ref too to fill the FlutterBackingStore.user_data, |
| 292 | + * we need to ref two times because flutter will call both this destruction callback and the |
| 293 | + * compositor collect callback |
| 294 | + */ |
| 295 | + .user_data = surface_ref(CAST_SURFACE_UNCHECKED(s)), |
| 296 | + .destruction_callback = surface_unref_void } }; |
| 297 | + return 0; |
| 298 | +} |
| 299 | + |
| 300 | +static int egl_offscreen_render_surface_queue_present(struct render_surface *s, const FlutterBackingStore *fl_store) { |
| 301 | + (void) s; |
| 302 | + (void) fl_store; |
| 303 | + |
| 304 | + // nothing to do here |
| 305 | + |
| 306 | + return 0; |
| 307 | +} |
| 308 | + |
| 309 | +/** |
| 310 | + * @brief Get the EGL Surface for rendering into this render surface. |
| 311 | + * |
| 312 | + * Flutter doesn't really support backing stores to be EGL Surfaces, so we have to hack around this, kinda. |
| 313 | + * |
| 314 | + * @param s |
| 315 | + * @return EGLSurface The EGLSurface associated with this render surface. Only valid for the lifetime of this egl_offscreen_render_surface. |
| 316 | + */ |
| 317 | +ATTR_PURE EGLSurface egl_offscreen_render_surface_get_egl_surface(struct egl_offscreen_render_surface *s) { |
| 318 | + return s->egl_surface; |
| 319 | +} |
| 320 | + |
| 321 | +/** |
| 322 | + * @brief Get the EGLConfig that was used to create the EGLSurface for this render surface. |
| 323 | + * |
| 324 | + * If the display doesn't support EGL_KHR_no_config_context, we need to create the EGL rendering context with |
| 325 | + * the same EGLConfig as every EGLSurface we want to bind to it. So we can just let egl_offscreen_render_surface choose a config |
| 326 | + * and let flutter-pi query that config when creating the rendering contexts in that case. |
| 327 | + * |
| 328 | + * @param s |
| 329 | + * @return EGLConfig The chosen EGLConfig. Valid forever. |
| 330 | + */ |
| 331 | +ATTR_PURE EGLConfig egl_offscreen_render_surface_get_egl_config(struct egl_offscreen_render_surface *s) { |
| 332 | + return s->egl_config; |
| 333 | +} |
0 commit comments