Flutter Linux Embedder
fl_compositor_opengl.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "fl_compositor_opengl.h"
6 
7 #include <epoxy/egl.h>
8 #include <epoxy/gl.h>
9 
10 #include "flutter/common/constants.h"
11 #include "flutter/shell/platform/embedder/embedder.h"
14 
15 // Vertex shader to draw Flutter window contents.
16 static const char* vertex_shader_src =
17  "attribute vec2 position;\n"
18  "attribute vec2 in_texcoord;\n"
19  "uniform vec2 offset;\n"
20  "uniform vec2 scale;\n"
21  "varying vec2 texcoord;\n"
22  "\n"
23  "void main() {\n"
24  " gl_Position = vec4(offset + position * scale, 0, 1);\n"
25  " texcoord = in_texcoord;\n"
26  "}\n";
27 
28 // Fragment shader to draw Flutter window contents.
29 static const char* fragment_shader_src =
30  "#ifdef GL_ES\n"
31  "precision mediump float;\n"
32  "#endif\n"
33  "\n"
34  "uniform sampler2D texture;\n"
35  "varying vec2 texcoord;\n"
36  "\n"
37  "void main() {\n"
38  " gl_FragColor = texture2D(texture, texcoord);\n"
39  "}\n";
40 
42  FlCompositor parent_instance;
43 
44  // Engine we are rendering.
45  GWeakRef engine;
46 
47  // Target OpenGL context;
48  GdkGLContext* context;
49 
50  // Flutter OpenGL contexts.
51  FlOpenGLManager* opengl_manager;
52 
53  // target dimension for resizing
56 
57  // whether the renderer waits for frame render
59 
60  // true if frame was completed; resizing is not synchronized until first frame
61  // was rendered
63 
64  // True if we can use glBlitFramebuffer.
66 
67  // Shader program.
68  GLuint program;
69 
70  // Location of layer offset in [program].
72 
73  // Location of layer scale in [program].
75 
76  // Verticies for the uniform square.
77  GLuint vertex_buffer;
78 
79  // Framebuffers to render.
80  GPtrArray* framebuffers;
81 
82  // Mutex used when blocking the raster thread until a task is completed on
83  // platform thread.
84  GMutex present_mutex;
85 
86  // Condition to unblock the raster thread after task is completed on platform
87  // thread.
89 };
90 
91 G_DEFINE_TYPE(FlCompositorOpenGL,
92  fl_compositor_opengl,
93  fl_compositor_get_type())
94 
95 // Check if running on driver supporting blit.
96 static gboolean driver_supports_blit() {
97  const gchar* vendor = reinterpret_cast<const gchar*>(glGetString(GL_VENDOR));
98 
99  // Note: List of unsupported vendors due to issue
100  // https://github.com/flutter/flutter/issues/152099
101  const char* unsupported_vendors_exact[] = {"Vivante Corporation", "ARM"};
102  const char* unsupported_vendors_fuzzy[] = {"NVIDIA"};
103 
104  for (const char* unsupported : unsupported_vendors_fuzzy) {
105  if (strstr(vendor, unsupported) != nullptr) {
106  return FALSE;
107  }
108  }
109  for (const char* unsupported : unsupported_vendors_exact) {
110  if (strcmp(vendor, unsupported) == 0) {
111  return FALSE;
112  }
113  }
114  return TRUE;
115 }
116 
117 // Returns the log for the given OpenGL shader. Must be freed by the caller.
118 static gchar* get_shader_log(GLuint shader) {
119  GLint log_length;
120  gchar* log;
121 
122  glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
123 
124  log = static_cast<gchar*>(g_malloc(log_length + 1));
125  glGetShaderInfoLog(shader, log_length, nullptr, log);
126 
127  return log;
128 }
129 
130 // Returns the log for the given OpenGL program. Must be freed by the caller.
131 static gchar* get_program_log(GLuint program) {
132  GLint log_length;
133  gchar* log;
134 
135  glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
136 
137  log = static_cast<gchar*>(g_malloc(log_length + 1));
138  glGetProgramInfoLog(program, log_length, nullptr, log);
139 
140  return log;
141 }
142 
143 static void fl_compositor_opengl_unblock_main_thread(FlCompositorOpenGL* self) {
144  if (self->blocking_main_thread) {
145  self->blocking_main_thread = false;
146 
147  g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine));
148  if (engine != nullptr) {
150  }
151  }
152 }
153 
154 static void setup_shader(FlCompositorOpenGL* self) {
155  GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
156  glShaderSource(vertex_shader, 1, &vertex_shader_src, nullptr);
157  glCompileShader(vertex_shader);
158  GLint vertex_compile_status;
159  glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &vertex_compile_status);
160  if (vertex_compile_status == GL_FALSE) {
161  g_autofree gchar* shader_log = get_shader_log(vertex_shader);
162  g_warning("Failed to compile vertex shader: %s", shader_log);
163  }
164 
165  GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
166  glShaderSource(fragment_shader, 1, &fragment_shader_src, nullptr);
167  glCompileShader(fragment_shader);
168  GLint fragment_compile_status;
169  glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &fragment_compile_status);
170  if (fragment_compile_status == GL_FALSE) {
171  g_autofree gchar* shader_log = get_shader_log(fragment_shader);
172  g_warning("Failed to compile fragment shader: %s", shader_log);
173  }
174 
175  self->program = glCreateProgram();
176  glAttachShader(self->program, vertex_shader);
177  glAttachShader(self->program, fragment_shader);
178  glLinkProgram(self->program);
179 
180  GLint link_status;
181  glGetProgramiv(self->program, GL_LINK_STATUS, &link_status);
182  if (link_status == GL_FALSE) {
183  g_autofree gchar* program_log = get_program_log(self->program);
184  g_warning("Failed to link program: %s", program_log);
185  }
186 
187  self->offset_location = glGetUniformLocation(self->program, "offset");
188  self->scale_location = glGetUniformLocation(self->program, "scale");
189 
190  glDeleteShader(vertex_shader);
191  glDeleteShader(fragment_shader);
192 
193  // The uniform square abcd in two triangles cba + cdb
194  // a--b
195  // | |
196  // c--d
197  GLfloat vertex_data[] = {-1, -1, 0, 0, 1, 1, 1, 1, -1, 1, 0, 1,
198  -1, -1, 0, 0, 1, -1, 1, 0, 1, 1, 1, 1};
199 
200  glGenBuffers(1, &self->vertex_buffer);
201  glBindBuffer(GL_ARRAY_BUFFER, self->vertex_buffer);
202  glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data,
203  GL_STATIC_DRAW);
204 }
205 
206 static void render_with_blit(FlCompositorOpenGL* self,
207  GPtrArray* framebuffers) {
208  // Disable the scissor test as it can affect blit operations.
209  // Prevents regressions like: https://github.com/flutter/flutter/issues/140828
210  // See OpenGL specification version 4.6, section 18.3.1.
211  glDisable(GL_SCISSOR_TEST);
212 
213  for (guint i = 0; i < framebuffers->len; i++) {
214  FlFramebuffer* framebuffer =
215  FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, i));
216 
217  GLuint framebuffer_id = fl_framebuffer_get_id(framebuffer);
218  glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_id);
219  size_t width = fl_framebuffer_get_width(framebuffer);
220  size_t height = fl_framebuffer_get_height(framebuffer);
221  glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
222  GL_COLOR_BUFFER_BIT, GL_NEAREST);
223  }
224  glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
225 }
226 
227 static void render_with_textures(FlCompositorOpenGL* self,
228  GPtrArray* framebuffers,
229  int width,
230  int height) {
231  // Save bindings that are set by this function. All bindings must be restored
232  // to their original values because Skia expects that its bindings have not
233  // been altered.
234  GLint saved_texture_binding;
235  glGetIntegerv(GL_TEXTURE_BINDING_2D, &saved_texture_binding);
236  GLint saved_vao_binding;
237  glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &saved_vao_binding);
238  GLint saved_array_buffer_binding;
239  glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &saved_array_buffer_binding);
240 
241  // FIXME(robert-ancell): The vertex array is the same for all views, but
242  // cannot be shared in OpenGL. Find a way to not generate this every time.
243  GLuint vao;
244  glGenVertexArrays(1, &vao);
245  glBindVertexArray(vao);
246  glBindBuffer(GL_ARRAY_BUFFER, self->vertex_buffer);
247  GLint position_location = glGetAttribLocation(self->program, "position");
248  glEnableVertexAttribArray(position_location);
249  glVertexAttribPointer(position_location, 2, GL_FLOAT, GL_FALSE,
250  sizeof(GLfloat) * 4, 0);
251  GLint texcoord_location = glGetAttribLocation(self->program, "in_texcoord");
252  glEnableVertexAttribArray(texcoord_location);
253  glVertexAttribPointer(texcoord_location, 2, GL_FLOAT, GL_FALSE,
254  sizeof(GLfloat) * 4,
255  reinterpret_cast<void*>(sizeof(GLfloat) * 2));
256 
257  glEnable(GL_BLEND);
258  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
259 
260  glUseProgram(self->program);
261 
262  for (guint i = 0; i < framebuffers->len; i++) {
263  FlFramebuffer* framebuffer =
264  FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, i));
265 
266  // FIXME(robert-ancell): The offset from present_layers() is not here, needs
267  // to be updated.
268  size_t texture_width = fl_framebuffer_get_width(framebuffer);
269  size_t texture_height = fl_framebuffer_get_height(framebuffer);
270  glUniform2f(self->offset_location, 0, 0);
271  glUniform2f(self->scale_location, texture_width / width,
272  texture_height / height);
273 
274  GLuint texture_id = fl_framebuffer_get_texture_id(framebuffer);
275  glBindTexture(GL_TEXTURE_2D, texture_id);
276 
277  glDrawArrays(GL_TRIANGLES, 0, 6);
278  }
279 
280  glDeleteVertexArrays(1, &vao);
281 
282  glDisable(GL_BLEND);
283 
284  glBindTexture(GL_TEXTURE_2D, saved_texture_binding);
285  glBindVertexArray(saved_vao_binding);
286  glBindBuffer(GL_ARRAY_BUFFER, saved_array_buffer_binding);
287 }
288 
289 static void render(FlCompositorOpenGL* self,
290  GPtrArray* framebuffers,
291  int width,
292  int height) {
293  if (self->has_gl_framebuffer_blit) {
294  render_with_blit(self, framebuffers);
295  } else {
296  render_with_textures(self, framebuffers, width, height);
297  }
298 }
299 
300 static gboolean present_layers(FlCompositorOpenGL* self,
301  const FlutterLayer** layers,
302  size_t layers_count) {
303  g_return_val_if_fail(FL_IS_COMPOSITOR_OPENGL(self), FALSE);
304 
305  // ignore incoming frame with wrong dimensions in trivial case with just one
306  // layer
307  if (self->blocking_main_thread && layers_count == 1 &&
308  layers[0]->offset.x == 0 && layers[0]->offset.y == 0 &&
309  (layers[0]->size.width != self->target_width ||
310  layers[0]->size.height != self->target_height)) {
311  return TRUE;
312  }
313 
314  self->had_first_frame = true;
315 
317 
318  GLint general_format = GL_RGBA;
319  if (epoxy_has_gl_extension("GL_EXT_texture_format_BGRA8888")) {
320  general_format = GL_BGRA_EXT;
321  }
322 
323  g_autoptr(GPtrArray) framebuffers =
324  g_ptr_array_new_with_free_func(g_object_unref);
325  for (size_t i = 0; i < layers_count; ++i) {
326  const FlutterLayer* layer = layers[i];
327  switch (layer->type) {
328  case kFlutterLayerContentTypeBackingStore: {
329  const FlutterBackingStore* backing_store = layer->backing_store;
330  FlFramebuffer* framebuffer =
331  FL_FRAMEBUFFER(backing_store->open_gl.framebuffer.user_data);
332  g_ptr_array_add(framebuffers, g_object_ref(framebuffer));
333  } break;
334  case kFlutterLayerContentTypePlatformView: {
335  // TODO(robert-ancell) Not implemented -
336  // https://github.com/flutter/flutter/issues/41724
337  } break;
338  }
339  }
340 
341  if (self->context == nullptr) {
342  // Store for rendering later
343  g_ptr_array_unref(self->framebuffers);
344  self->framebuffers = g_ptr_array_ref(framebuffers);
345  } else {
346  // Composite into a single framebuffer.
347  if (framebuffers->len > 1) {
348  size_t width = 0, height = 0;
349 
350  for (guint i = 0; i < framebuffers->len; i++) {
351  FlFramebuffer* framebuffer =
352  FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, i));
353 
354  size_t w = fl_framebuffer_get_width(framebuffer);
355  size_t h = fl_framebuffer_get_height(framebuffer);
356  if (w > width) {
357  width = w;
358  }
359  if (h > height) {
360  height = h;
361  }
362  }
363 
364  FlFramebuffer* view_framebuffer =
365  fl_framebuffer_new(general_format, width, height);
366  glBindFramebuffer(GL_DRAW_FRAMEBUFFER,
367  fl_framebuffer_get_id(view_framebuffer));
368  render(self, framebuffers, width, height);
369  g_ptr_array_set_size(framebuffers, 0);
370  g_ptr_array_add(framebuffers, view_framebuffer);
371  }
372 
373  // Read back pixel values.
374  FlFramebuffer* framebuffer =
375  FL_FRAMEBUFFER(g_ptr_array_index(framebuffers, 0));
376  size_t width = fl_framebuffer_get_width(framebuffer);
377  size_t height = fl_framebuffer_get_height(framebuffer);
378  size_t data_length = width * height * 4;
379  g_autofree uint8_t* data = static_cast<uint8_t*>(malloc(data_length));
380  glBindFramebuffer(GL_READ_FRAMEBUFFER, fl_framebuffer_get_id(framebuffer));
381  glReadPixels(0, 0, width, height, general_format, GL_UNSIGNED_BYTE, data);
382 
383  // Write into a texture in the views context.
384  gdk_gl_context_make_current(self->context);
385  g_autoptr(FlFramebuffer) view_framebuffer =
386  fl_framebuffer_new(general_format, width, height);
387  glBindFramebuffer(GL_DRAW_FRAMEBUFFER,
388  fl_framebuffer_get_id(view_framebuffer));
389  glBindTexture(GL_TEXTURE_2D,
390  fl_framebuffer_get_texture_id(view_framebuffer));
391  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
392  GL_UNSIGNED_BYTE, data);
393 
394  g_autoptr(GPtrArray) secondary_framebuffers =
395  g_ptr_array_new_with_free_func(g_object_unref);
396  g_ptr_array_add(secondary_framebuffers, g_object_ref(view_framebuffer));
397  g_ptr_array_unref(self->framebuffers);
398  self->framebuffers = g_ptr_array_ref(secondary_framebuffers);
399  }
400 
401  return TRUE;
402 }
403 
404 typedef struct {
405  FlCompositorOpenGL* self;
406 
407  const FlutterLayer** layers;
408  size_t layers_count;
409 
410  gboolean result;
411 
412  gboolean finished;
414 
415 // Perform the present on the main thread.
416 static void present_layers_task_cb(gpointer user_data) {
417  PresentLayersData* data = static_cast<PresentLayersData*>(user_data);
418  FlCompositorOpenGL* self = data->self;
419 
420  // Perform the present.
421  fl_opengl_manager_make_current(self->opengl_manager);
422  data->result = present_layers(self, data->layers, data->layers_count);
423  fl_opengl_manager_clear_current(self->opengl_manager);
424 
425  // Complete fl_compositor_opengl_present_layers().
426  g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->present_mutex);
427  data->finished = TRUE;
428  g_cond_signal(&self->present_condition);
429 }
430 
431 static FlutterRendererType fl_compositor_opengl_get_renderer_type(
432  FlCompositor* compositor) {
433  return kOpenGL;
434 }
435 
436 static void fl_compositor_opengl_wait_for_frame(FlCompositor* compositor,
437  int target_width,
438  int target_height) {
439  FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(compositor);
440 
441  self->target_width = target_width;
442  self->target_height = target_height;
443 
444  if (self->had_first_frame && !self->blocking_main_thread) {
445  self->blocking_main_thread = true;
446  g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine));
447  if (engine != nullptr) {
449  }
450  }
451 }
452 
453 static gboolean fl_compositor_opengl_present_layers(FlCompositor* compositor,
454  const FlutterLayer** layers,
455  size_t layers_count) {
456  FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(compositor);
457 
458  // Detach the context from raster thread. Needed because blitting
459  // will be done on the main thread, which will make the context current.
460  fl_opengl_manager_clear_current(self->opengl_manager);
461 
462  g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine));
463 
464  // Schedule the present to run on the main thread.
465  FlTaskRunner* task_runner = fl_engine_get_task_runner(engine);
466  PresentLayersData data = {
467  .self = self,
468  .layers = layers,
469  .layers_count = layers_count,
470  .result = FALSE,
471  .finished = FALSE,
472  };
474 
475  // Block until present completes.
476  g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->present_mutex);
477  while (!data.finished) {
478  g_cond_wait(&self->present_condition, &self->present_mutex);
479  }
480 
481  // Restore the context to the raster thread in case the engine needs it
482  // to do some cleanup.
483  fl_opengl_manager_make_current(self->opengl_manager);
484 
485  return data.result;
486 }
487 
488 static void fl_compositor_opengl_dispose(GObject* object) {
489  FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(object);
490 
492 
493  g_weak_ref_clear(&self->engine);
494  g_clear_object(&self->context);
495  g_clear_object(&self->opengl_manager);
496  g_clear_pointer(&self->framebuffers, g_ptr_array_unref);
497  g_mutex_clear(&self->present_mutex);
498  g_cond_clear(&self->present_condition);
499 
500  G_OBJECT_CLASS(fl_compositor_opengl_parent_class)->dispose(object);
501 }
502 
503 static void fl_compositor_opengl_class_init(FlCompositorOpenGLClass* klass) {
504  FL_COMPOSITOR_CLASS(klass)->get_renderer_type =
506  FL_COMPOSITOR_CLASS(klass)->wait_for_frame =
508  FL_COMPOSITOR_CLASS(klass)->present_layers =
510 
511  G_OBJECT_CLASS(klass)->dispose = fl_compositor_opengl_dispose;
512 }
513 
514 static void fl_compositor_opengl_init(FlCompositorOpenGL* self) {
515  self->framebuffers = g_ptr_array_new();
516  g_mutex_init(&self->present_mutex);
517  g_cond_init(&self->present_condition);
518 }
519 
520 FlCompositorOpenGL* fl_compositor_opengl_new(FlEngine* engine,
521  GdkGLContext* context) {
522  FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(
523  g_object_new(fl_compositor_opengl_get_type(), nullptr));
524 
525  g_weak_ref_init(&self->engine, engine);
526  self->context =
527  context != nullptr ? GDK_GL_CONTEXT(g_object_ref(context)) : nullptr;
528  self->opengl_manager =
529  FL_OPENGL_MANAGER(g_object_ref(fl_engine_get_opengl_manager(engine)));
530 
531  fl_opengl_manager_make_current(self->opengl_manager);
532 
533  self->has_gl_framebuffer_blit =
534  driver_supports_blit() &&
535  (epoxy_gl_version() >= 30 ||
536  epoxy_has_gl_extension("GL_EXT_framebuffer_blit"));
537 
538  if (!self->has_gl_framebuffer_blit) {
539  setup_shader(self);
540  }
541 
542  return self;
543 }
544 
545 void fl_compositor_opengl_render(FlCompositorOpenGL* self,
546  int width,
547  int height) {
548  g_return_if_fail(FL_IS_COMPOSITOR_OPENGL(self));
549 
550  glClearColor(0.0, 0.0, 0.0, 0.0);
551  glClear(GL_COLOR_BUFFER_BIT);
552 
553  render(self, self->framebuffers, width, height);
554 
555  glFlush();
556 }
557 
558 void fl_compositor_opengl_cleanup(FlCompositorOpenGL* self) {
559  g_return_if_fail(FL_IS_COMPOSITOR_OPENGL(self));
560 
561  if (self->program != 0) {
562  glDeleteProgram(self->program);
563  }
564  if (self->vertex_buffer != 0) {
565  glDeleteBuffers(1, &self->vertex_buffer);
566  }
567 }
static void fl_compositor_opengl_dispose(GObject *object)
static void fl_compositor_opengl_wait_for_frame(FlCompositor *compositor, int target_width, int target_height)
static void render_with_textures(FlCompositorOpenGL *self, GPtrArray *framebuffers, int width, int height)
static gchar * get_shader_log(GLuint shader)
static gboolean present_layers(FlCompositorOpenGL *self, const FlutterLayer **layers, size_t layers_count)
static void fl_compositor_opengl_unblock_main_thread(FlCompositorOpenGL *self)
static gboolean fl_compositor_opengl_present_layers(FlCompositor *compositor, const FlutterLayer **layers, size_t layers_count)
static const char * fragment_shader_src
static void fl_compositor_opengl_init(FlCompositorOpenGL *self)
static gchar * get_program_log(GLuint program)
static void setup_shader(FlCompositorOpenGL *self)
static FlutterRendererType fl_compositor_opengl_get_renderer_type(FlCompositor *compositor)
static void render(FlCompositorOpenGL *self, GPtrArray *framebuffers, int width, int height)
static void present_layers_task_cb(gpointer user_data)
FlCompositorOpenGL * fl_compositor_opengl_new(FlEngine *engine, GdkGLContext *context)
static void render_with_blit(FlCompositorOpenGL *self, GPtrArray *framebuffers)
void fl_compositor_opengl_render(FlCompositorOpenGL *self, int width, int height)
static void fl_compositor_opengl_class_init(FlCompositorOpenGLClass *klass)
G_DEFINE_TYPE(FlCompositorOpenGL, fl_compositor_opengl, fl_compositor_get_type()) static gboolean driver_supports_blit()
void fl_compositor_opengl_cleanup(FlCompositorOpenGL *self)
static const char * vertex_shader_src
G_BEGIN_DECLS GdkGLContext * context
FlOpenGLManager * fl_engine_get_opengl_manager(FlEngine *self)
Definition: fl_engine.cc:710
FlTaskRunner * fl_engine_get_task_runner(FlEngine *self)
Definition: fl_engine.cc:1419
G_BEGIN_DECLS G_MODULE_EXPORT FlValue gpointer user_data
size_t fl_framebuffer_get_height(FlFramebuffer *self)
GLuint fl_framebuffer_get_id(FlFramebuffer *self)
size_t fl_framebuffer_get_width(FlFramebuffer *self)
GLuint fl_framebuffer_get_texture_id(FlFramebuffer *self)
FlFramebuffer * fl_framebuffer_new(GLint format, size_t width, size_t height)
void fl_opengl_manager_clear_current(FlOpenGLManager *self)
void fl_opengl_manager_make_current(FlOpenGLManager *self)
const uint8_t uint32_t uint32_t * height
const uint8_t uint32_t * width
void fl_task_runner_post_callback(FlTaskRunner *self, void(*callback)(gpointer data), gpointer data)
void fl_task_runner_release_main_thread(FlTaskRunner *self)
void fl_task_runner_block_main_thread(FlTaskRunner *self)
FlOpenGLManager * opengl_manager
FlCompositorOpenGL * self
const FlutterLayer ** layers
int64_t texture_id