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"
15 
17  FlCompositor parent_instance;
18 
19  // Task runner to wait for frames on.
20  FlTaskRunner* task_runner;
21 
22  // TRUE if can share framebuffers between contexts.
23  gboolean shareable;
24 
25  // Flutter OpenGL contexts.
26  FlOpenGLManager* opengl_manager;
27 
28  // Last rendered frame.
29  FlFramebuffer* framebuffer;
30 
31  // Last rendered frame pixels (only set if shareable is FALSE).
32  uint8_t* pixels;
33 
34  // Shader program used to composite layers.
35  FlCompositorOpenGLShader* shader;
36 
37  // Ensure Flutter and GTK can access the frame data (framebuffer or pixels).
38  GMutex frame_mutex;
39 };
40 
41 G_DEFINE_TYPE(FlCompositorOpenGL,
42  fl_compositor_opengl,
43  fl_compositor_get_type())
44 
45 static void composite_layer(FlCompositorOpenGL* self,
46  FlFramebuffer* framebuffer,
47  double x,
48  double y,
49  int width,
50  int height) {
51  size_t texture_width = fl_framebuffer_get_width(framebuffer);
53  fl_compositor_opengl_shader_set_offset(self->shader, (2 * x / width) - 1.0,
54  (2 * y / height) - 1.0);
55  fl_compositor_opengl_shader_set_scale(self->shader, texture_width / width,
57 
59  glBindTexture(GL_TEXTURE_2D, texture_id);
60 
61  glDrawArrays(GL_TRIANGLES, 0, 6);
62 }
63 
64 static gboolean fl_compositor_opengl_present_layers(FlCompositor* compositor,
65  const FlutterLayer** layers,
66  size_t layers_count) {
67  FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(compositor);
68 
69  g_mutex_lock(&self->frame_mutex);
70  if (layers_count == 0) {
71  g_mutex_unlock(&self->frame_mutex);
72  return TRUE;
73  }
74 
75  GLint general_format = GL_RGBA;
76  if (epoxy_has_gl_extension("GL_EXT_texture_format_BGRA8888")) {
77  general_format = GL_BGRA_EXT;
78  }
79 
80  // Save bindings that are set by this function. All bindings must be restored
81  // to their original values because Skia expects that its bindings have not
82  // been altered.
83  GLint saved_texture_binding;
84  glGetIntegerv(GL_TEXTURE_BINDING_2D, &saved_texture_binding);
85  GLint saved_vao_binding;
86  glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &saved_vao_binding);
87  GLint saved_array_buffer_binding;
88  glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &saved_array_buffer_binding);
89  GLint saved_draw_framebuffer_binding;
90  glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &saved_draw_framebuffer_binding);
91  GLint saved_read_framebuffer_binding;
92  glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &saved_read_framebuffer_binding);
93  GLint saved_current_program;
94  glGetIntegerv(GL_CURRENT_PROGRAM, &saved_current_program);
95  GLboolean saved_scissor_test = glIsEnabled(GL_SCISSOR_TEST);
96  GLboolean saved_blend = glIsEnabled(GL_BLEND);
97  GLint saved_src_rgb;
98  glGetIntegerv(GL_BLEND_SRC_RGB, &saved_src_rgb);
99  GLint saved_src_alpha;
100  glGetIntegerv(GL_BLEND_SRC_ALPHA, &saved_src_alpha);
101  GLint saved_dst_rgb;
102  glGetIntegerv(GL_BLEND_DST_RGB, &saved_dst_rgb);
103  GLint saved_dst_alpha;
104  glGetIntegerv(GL_BLEND_DST_ALPHA, &saved_dst_alpha);
105 
106  // Update framebuffer to write into.
107  size_t width = layers[0]->size.width;
108  size_t height = layers[0]->size.height;
109  if (self->framebuffer == nullptr ||
110  fl_framebuffer_get_width(self->framebuffer) != width ||
111  fl_framebuffer_get_height(self->framebuffer) != height) {
112  g_clear_object(&self->framebuffer);
113  self->framebuffer =
114  fl_framebuffer_new(general_format, width, height, self->shareable);
115 
116  // If not shareable make buffer to copy frame pixels into.
117  if (!self->shareable) {
118  size_t data_length = width * height * 4;
119  self->pixels =
120  static_cast<uint8_t*>(g_realloc(self->pixels, data_length));
121  }
122  }
123 
124  // FIXME(robert-ancell): The vertex array is the same for all views, but
125  // cannot be shared in OpenGL. Find a way to not generate this every time.
126  GLuint vao;
127  glGenVertexArrays(1, &vao);
128  glBindVertexArray(vao);
129 
130  glEnable(GL_BLEND);
131  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
132 
133  fl_compositor_opengl_shader_use(self->shader);
134 
135  // Disable the scissor test as it can affect blit operations.
136  // Prevents regressions like: https://github.com/flutter/flutter/issues/140828
137  // See OpenGL specification version 4.6, section 18.3.1.
138  glDisable(GL_SCISSOR_TEST);
139 
140  glBindFramebuffer(GL_DRAW_FRAMEBUFFER,
141  fl_framebuffer_get_id(self->framebuffer));
142  gboolean first_layer = TRUE;
143  for (size_t i = 0; i < layers_count; ++i) {
144  const FlutterLayer* layer = layers[i];
145  switch (layer->type) {
146  case kFlutterLayerContentTypeBackingStore: {
147  const FlutterBackingStore* backing_store = layer->backing_store;
148  FlFramebuffer* framebuffer =
149  FL_FRAMEBUFFER(backing_store->open_gl.framebuffer.user_data);
150  glBindFramebuffer(GL_READ_FRAMEBUFFER,
152  // The first layer can be blitted, and following layers composited with
153  // this.
154  if (first_layer) {
155  glBlitFramebuffer(layer->offset.x, layer->offset.y, layer->size.width,
156  layer->size.height, layer->offset.x,
157  layer->offset.y, layer->size.width,
158  layer->size.height, GL_COLOR_BUFFER_BIT,
159  GL_NEAREST);
160  first_layer = FALSE;
161  } else {
162  composite_layer(self, framebuffer, layer->offset.x, layer->offset.y,
163  width, height);
164  }
165  } break;
166  case kFlutterLayerContentTypePlatformView: {
167  // TODO(robert-ancell) Not implemented -
168  // https://github.com/flutter/flutter/issues/41724
169  } break;
170  }
171  }
172  glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
173  glFlush();
174 
175  glDeleteVertexArrays(1, &vao);
176 
177  if (saved_blend) {
178  glEnable(GL_BLEND);
179  } else {
180  glDisable(GL_BLEND);
181  }
182  if (saved_scissor_test) {
183  glEnable(GL_SCISSOR_TEST);
184  } else {
185  glDisable(GL_SCISSOR_TEST);
186  }
187 
188  glBindTexture(GL_TEXTURE_2D, saved_texture_binding);
189  glBindVertexArray(saved_vao_binding);
190  glBindBuffer(GL_ARRAY_BUFFER, saved_array_buffer_binding);
191  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, saved_draw_framebuffer_binding);
192  glUseProgram(saved_current_program);
193  glBlendFuncSeparate(saved_src_rgb, saved_dst_rgb, saved_src_alpha,
194  saved_dst_alpha);
195 
196  if (!self->shareable) {
197  glBindFramebuffer(GL_READ_FRAMEBUFFER,
198  fl_framebuffer_get_id(self->framebuffer));
199  glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, self->pixels);
200  glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
201  }
202  glBindFramebuffer(GL_READ_FRAMEBUFFER, saved_read_framebuffer_binding);
203 
204  g_mutex_unlock(&self->frame_mutex);
205 
206  fl_task_runner_stop_wait(self->task_runner);
207 
208  return TRUE;
209 }
210 
211 static void fl_compositor_opengl_get_frame_size(FlCompositor* compositor,
212  size_t* width,
213  size_t* height) {
214  FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(compositor);
215 
216  g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&self->frame_mutex);
217 
218  if (width != nullptr) {
219  *width = self->framebuffer != nullptr
220  ? fl_framebuffer_get_width(self->framebuffer)
221  : 0;
222  }
223  if (height != nullptr) {
224  *height = self->framebuffer != nullptr
225  ? fl_framebuffer_get_height(self->framebuffer)
226  : 0;
227  }
228 }
229 
230 static gboolean fl_compositor_opengl_render(FlCompositor* compositor,
231  cairo_t* cr,
232  GdkWindow* window,
233  gboolean wait_for_frame) {
234  FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(compositor);
235 
236  g_mutex_lock(&self->frame_mutex);
237  if (self->framebuffer == nullptr) {
238  g_mutex_unlock(&self->frame_mutex);
239  return FALSE;
240  }
241 
242  // If frame not ready, then wait for it.
243  gint scale_factor = gdk_window_get_scale_factor(window);
244  size_t width, height;
245  gint64 expiry_time =
246  g_get_monotonic_time() + kCompositorRenderTimeoutMicroseconds;
247  while (true) {
248  width = gdk_window_get_width(window) * scale_factor;
249  height = gdk_window_get_height(window) * scale_factor;
250  if (!wait_for_frame) {
251  break;
252  }
253 
254  size_t framebuffer_width = fl_framebuffer_get_width(self->framebuffer);
255  size_t framebuffer_height = fl_framebuffer_get_height(self->framebuffer);
256  if (framebuffer_width == width && framebuffer_height == height) {
257  break;
258  }
259 
260  if (g_get_monotonic_time() > expiry_time) {
261  g_warning(
262  "Timed out waiting for OpenGL frame of size %zdx%zd (have %zdx%zd)",
263  width, height, framebuffer_width, framebuffer_height);
264  break;
265  }
266 
267  g_mutex_unlock(&self->frame_mutex);
268  fl_task_runner_wait(self->task_runner, expiry_time);
269  g_mutex_lock(&self->frame_mutex);
270  }
271 
272  if (fl_framebuffer_get_shareable(self->framebuffer)) {
273  g_autoptr(FlFramebuffer) sibling =
274  fl_framebuffer_create_sibling(self->framebuffer);
275  gdk_cairo_draw_from_gl(cr, window, fl_framebuffer_get_texture_id(sibling),
276  GL_TEXTURE, scale_factor, 0, 0, width, height);
277  } else {
278  GLint saved_texture_binding;
279  glGetIntegerv(GL_TEXTURE_BINDING_2D, &saved_texture_binding);
280 
281  GLuint texture_id;
282  glGenTextures(1, &texture_id);
283  glBindTexture(GL_TEXTURE_2D, texture_id);
284  GLsizei fb_width = 0;
285  GLsizei fb_height = 0;
286  if (self->framebuffer != nullptr) {
287  fb_width =
288  static_cast<GLsizei>(fl_framebuffer_get_width(self->framebuffer));
289  fb_height =
290  static_cast<GLsizei>(fl_framebuffer_get_height(self->framebuffer));
291  }
292  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fb_width, fb_height, 0, GL_RGBA,
293  GL_UNSIGNED_BYTE, self->pixels);
294 
295  gdk_cairo_draw_from_gl(cr, window, texture_id, GL_TEXTURE, scale_factor, 0,
296  0, width, height);
297 
298  glDeleteTextures(1, &texture_id);
299 
300  glBindTexture(GL_TEXTURE_2D, saved_texture_binding);
301  }
302 
303  glFlush();
304 
305  g_mutex_unlock(&self->frame_mutex);
306 
307  return TRUE;
308 }
309 
310 static void fl_compositor_opengl_dispose(GObject* object) {
311  FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(object);
312 
313  g_clear_object(&self->shader);
314 
315  g_clear_object(&self->task_runner);
316  g_clear_object(&self->opengl_manager);
317  g_clear_object(&self->framebuffer);
318  g_clear_pointer(&self->pixels, g_free);
319  g_mutex_clear(&self->frame_mutex);
320 
321  G_OBJECT_CLASS(fl_compositor_opengl_parent_class)->dispose(object);
322 }
323 
324 static void fl_compositor_opengl_class_init(FlCompositorOpenGLClass* klass) {
325  FL_COMPOSITOR_CLASS(klass)->present_layers =
327  FL_COMPOSITOR_CLASS(klass)->get_frame_size =
329  FL_COMPOSITOR_CLASS(klass)->render = fl_compositor_opengl_render;
330 
331  G_OBJECT_CLASS(klass)->dispose = fl_compositor_opengl_dispose;
332 }
333 
334 static void fl_compositor_opengl_init(FlCompositorOpenGL* self) {
335  g_mutex_init(&self->frame_mutex);
336 }
337 
338 FlCompositorOpenGL* fl_compositor_opengl_new(FlTaskRunner* task_runner,
339  FlOpenGLManager* opengl_manager,
340  gboolean shareable) {
341  FlCompositorOpenGL* self = FL_COMPOSITOR_OPENGL(
342  g_object_new(fl_compositor_opengl_get_type(), nullptr));
343 
344  self->task_runner = FL_TASK_RUNNER(g_object_ref(task_runner));
345  self->shareable = shareable;
346  self->opengl_manager = FL_OPENGL_MANAGER(g_object_ref(opengl_manager));
348 
349  return self;
350 }
g_autoptr(FlEngine) engine
constexpr G_BEGIN_DECLS gint64 kCompositorRenderTimeoutMicroseconds
Definition: fl_compositor.h:16
static void fl_compositor_opengl_dispose(GObject *object)
GLuint texture_id
FlCompositorOpenGL * fl_compositor_opengl_new(FlTaskRunner *task_runner, FlOpenGLManager *opengl_manager, gboolean shareable)
G_DEFINE_TYPE(FlCompositorOpenGL, fl_compositor_opengl, fl_compositor_get_type()) static void composite_layer(FlCompositorOpenGL *self
FlFramebuffer * framebuffer
static gboolean fl_compositor_opengl_present_layers(FlCompositor *compositor, const FlutterLayer **layers, size_t layers_count)
FlFramebuffer double double int int height
FlFramebuffer double x
static void fl_compositor_opengl_init(FlCompositorOpenGL *self)
size_t texture_height
FlFramebuffer double double int width
glDrawArrays(GL_TRIANGLES, 0, 6)
fl_compositor_opengl_shader_set_scale(self->shader, texture_width/width, texture_height/height)
static void fl_compositor_opengl_class_init(FlCompositorOpenGLClass *klass)
glBindTexture(GL_TEXTURE_2D, texture_id)
static gboolean fl_compositor_opengl_render(FlCompositor *compositor, cairo_t *cr, GdkWindow *window, gboolean wait_for_frame)
static void fl_compositor_opengl_get_frame_size(FlCompositor *compositor, size_t *width, size_t *height)
fl_compositor_opengl_shader_set_offset(self->shader,(2 *x/width) - 1.0,(2 *y/height) - 1.0)
FlFramebuffer double double y
G_BEGIN_DECLS FlOpenGLManager gboolean shareable
G_BEGIN_DECLS FlOpenGLManager * opengl_manager
FlCompositorOpenGLShader * fl_compositor_opengl_shader_new(FlOpenGLManager *opengl_manager)
void fl_compositor_opengl_shader_use(FlCompositorOpenGLShader *self)
const FlutterLayer size_t layers_count
const FlutterLayer ** layers
return TRUE
fl_task_runner_stop_wait(self->task_runner)
size_t fl_framebuffer_get_height(FlFramebuffer *self)
GLuint fl_framebuffer_get_id(FlFramebuffer *self)
gboolean fl_framebuffer_get_shareable(FlFramebuffer *self)
size_t fl_framebuffer_get_width(FlFramebuffer *self)
GLuint fl_framebuffer_get_texture_id(FlFramebuffer *self)
FlFramebuffer * fl_framebuffer_create_sibling(FlFramebuffer *self)
FlFramebuffer * fl_framebuffer_new(GLint format, size_t width, size_t height, gboolean shareable)
void fl_task_runner_wait(FlTaskRunner *self, gint64 expiry_time)
FlCompositorOpenGLShader * shader
FlOpenGLManager * opengl_manager
FlFramebuffer * framebuffer