Flutter Linux Embedder
fl_compositor_opengl_test.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 <thread>
6 #include "flutter/shell/platform/linux/testing/linux_test.h"
7 #include "gtest/gtest.h"
8 
9 #include "flutter/common/constants.h"
10 #include "flutter/fml/logging.h"
11 #include "flutter/fml/synchronization/waitable_event.h"
17 #include "flutter/shell/platform/linux/testing/mock_epoxy.h"
18 #include "flutter/shell/platform/linux/testing/mock_gtk.h"
19 #include "flutter/shell/platform/linux/testing/mock_renderable.h"
20 
21 #include <epoxy/egl.h>
22 
23 class FlCompositorOpenGLTest : public flutter::testing::LinuxTest {
24  protected:
25  void SetUp() override {
28  renderable = fl_mock_renderable_new();
30  fl_engine_set_implicit_view(engine, FL_RENDERABLE(renderable));
31  }
32 
34  g_clear_object(&compositor);
35  g_clear_object(&renderable);
36  g_clear_object(&opengl_manager);
37  g_clear_object(&task_runner);
38  }
39 
40  ::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
41  FlTaskRunner* task_runner = nullptr;
42  FlOpenGLManager* opengl_manager = nullptr;
43  FlMockRenderable* renderable = nullptr;
44  FlCompositorOpenGL* compositor = nullptr;
45 };
46 
48  // Present layer from a thread.
49  constexpr size_t width = 100;
50  constexpr size_t height = 100;
51  g_autoptr(FlFramebuffer) framebuffer =
52  fl_framebuffer_new(GL_RGB, width, height, FALSE);
53  FlutterBackingStore backing_store = {
54  .type = kFlutterBackingStoreTypeOpenGL,
55  .open_gl = {.framebuffer = {.user_data = framebuffer}}};
56  FlutterLayer layer = {.type = kFlutterLayerContentTypeBackingStore,
57  .backing_store = &backing_store,
58  .offset = {0, 0},
59  .size = {width, height}};
60  const FlutterLayer* layers[1] = {&layer};
61  std::thread([&]() {
62  fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
63  }).join();
64 
65  size_t frame_width, frame_height;
66  fl_compositor_get_frame_size(FL_COMPOSITOR(compositor), &frame_width,
67  &frame_height);
68  EXPECT_EQ(frame_width, width);
69  EXPECT_EQ(frame_height, height);
70 
71  // Render presented layer.
72  int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
73  g_autofree unsigned char* image_data =
74  static_cast<unsigned char*>(malloc(height * stride));
75  cairo_surface_t* surface = cairo_image_surface_create_for_data(
76  image_data, CAIRO_FORMAT_ARGB32, width, height, stride);
77  cairo_t* cr = cairo_create(surface);
78  fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
79  cairo_surface_destroy(surface);
80  cairo_destroy(cr);
81 }
82 
84  // Present a layer that is the old size.
85  constexpr size_t width1 = 90;
86  constexpr size_t height1 = 90;
87  g_autoptr(FlFramebuffer) framebuffer1 =
88  fl_framebuffer_new(GL_RGB, width1, height1, FALSE);
89  FlutterBackingStore backing_store1 = {
90  .type = kFlutterBackingStoreTypeOpenGL,
91  .open_gl = {.framebuffer = {.user_data = framebuffer1}}};
92  FlutterLayer layer1 = {.type = kFlutterLayerContentTypeBackingStore,
93  .backing_store = &backing_store1,
94  .offset = {0, 0},
95  .size = {width1, height1}};
96  const FlutterLayer* layers1[1] = {&layer1};
97  std::thread([&]() {
98  fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers1, 1);
99  }).join();
100 
101  // Present layer in current size.
102  constexpr size_t width2 = 100;
103  constexpr size_t height2 = 100;
104  g_autoptr(FlFramebuffer) framebuffer2 =
105  fl_framebuffer_new(GL_RGB, width2, height2, FALSE);
106  FlutterBackingStore backing_store2 = {
107  .type = kFlutterBackingStoreTypeOpenGL,
108  .open_gl = {.framebuffer = {.user_data = framebuffer2}}};
109  FlutterLayer layer2 = {.type = kFlutterLayerContentTypeBackingStore,
110  .backing_store = &backing_store2,
111  .offset = {0, 0},
112  .size = {width2, height2}};
113  const FlutterLayer* layers2[1] = {&layer2};
114  fml::AutoResetWaitableEvent latch;
115  std::thread([&]() {
116  fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers2, 1);
117  latch.Signal();
118  }).detach();
119 
120  // Render, will wait for the second layer if necessary.
121  int stride2 = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width2);
122  g_autofree unsigned char* image_data =
123  static_cast<unsigned char*>(malloc(height2 * stride2));
124  cairo_surface_t* surface = cairo_image_surface_create_for_data(
125  image_data, CAIRO_FORMAT_ARGB32, width2, height2, stride2);
126  cairo_t* cr = cairo_create(surface);
127  fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
128  cairo_surface_destroy(surface);
129  cairo_destroy(cr);
130 
131  latch.Wait();
132 }
133 
134 TEST_F(FlCompositorOpenGLTest, RestoresGLState) {
135  constexpr size_t width = 100;
136  constexpr size_t height = 100;
137 
138  // OpenGL 3.0
139  ON_CALL(epoxy, glGetString(GL_VENDOR))
140  .WillByDefault(
141  ::testing::Return(reinterpret_cast<const GLubyte*>("Intel")));
142  ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true));
143  ON_CALL(epoxy, epoxy_gl_version).WillByDefault(::testing::Return(30));
144 
145  g_autoptr(FlFramebuffer) framebuffer =
146  fl_framebuffer_new(GL_RGB, width, height, FALSE);
147  FlutterBackingStore backing_store = {
148  .type = kFlutterBackingStoreTypeOpenGL,
149  .open_gl = {.framebuffer = {.user_data = framebuffer}}};
150  FlutterLayer layer = {.type = kFlutterLayerContentTypeBackingStore,
151  .backing_store = &backing_store,
152  .offset = {0, 0},
153  .size = {width, height}};
154  const FlutterLayer* layers[1] = {&layer};
155 
156  constexpr GLuint kFakeTextureName = 123;
157  glBindTexture(GL_TEXTURE_2D, kFakeTextureName);
158  glDisable(GL_BLEND);
159  glEnable(GL_SCISSOR_TEST);
160 
161  // Present layer and render.
162  std::thread([&]() {
163  fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
164  }).join();
165  int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
166  g_autofree unsigned char* image_data =
167  static_cast<unsigned char*>(malloc(height * stride));
168  cairo_surface_t* surface = cairo_image_surface_create_for_data(
169  image_data, CAIRO_FORMAT_ARGB32, width, height, stride);
170  cairo_t* cr = cairo_create(surface);
171  fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
172  cairo_surface_destroy(surface);
173  cairo_destroy(cr);
174 
175  GLuint texture_2d_binding;
176  glGetIntegerv(GL_TEXTURE_BINDING_2D,
177  reinterpret_cast<GLint*>(&texture_2d_binding));
178  EXPECT_EQ(texture_2d_binding, kFakeTextureName);
179  EXPECT_EQ(glIsEnabled(GL_BLEND), GL_FALSE);
180  EXPECT_EQ(glIsEnabled(GL_SCISSOR_TEST), GL_TRUE);
181 }
182 
183 TEST_F(FlCompositorOpenGLTest, BlitFramebuffer) {
184  constexpr size_t width = 100;
185  constexpr size_t height = 100;
186 
187  // OpenGL 3.0
188  ON_CALL(epoxy, glGetString(GL_VENDOR))
189  .WillByDefault(
190  ::testing::Return(reinterpret_cast<const GLubyte*>("Intel")));
191  ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true));
192  EXPECT_CALL(epoxy, epoxy_gl_version).WillRepeatedly(::testing::Return(30));
193 
194  EXPECT_CALL(epoxy, glBlitFramebuffer);
195 
196  g_autoptr(FlFramebuffer) framebuffer =
197  fl_framebuffer_new(GL_RGB, width, height, FALSE);
198  FlutterBackingStore backing_store = {
199  .type = kFlutterBackingStoreTypeOpenGL,
200  .open_gl = {.framebuffer = {.user_data = framebuffer}}};
201  FlutterLayer layer = {.type = kFlutterLayerContentTypeBackingStore,
202  .backing_store = &backing_store,
203  .offset = {0, 0},
204  .size = {width, height}};
205  const FlutterLayer* layers[1] = {&layer};
206 
207  // Present layer and render.
208  std::thread([&]() {
209  fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
210  }).join();
211  int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
212  g_autofree unsigned char* image_data =
213  static_cast<unsigned char*>(malloc(height * stride));
214  cairo_surface_t* surface = cairo_image_surface_create_for_data(
215  image_data, CAIRO_FORMAT_ARGB32, width, height, stride);
216  cairo_t* cr = cairo_create(surface);
217  fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
218  cairo_surface_destroy(surface);
219  cairo_destroy(cr);
220 }
221 
222 TEST_F(FlCompositorOpenGLTest, BlitFramebufferExtension) {
223  constexpr size_t width = 100;
224  constexpr size_t height = 100;
225 
226  // OpenGL 2.0 with GL_EXT_framebuffer_blit extension
227  ON_CALL(epoxy, glGetString(GL_VENDOR))
228  .WillByDefault(
229  ::testing::Return(reinterpret_cast<const GLubyte*>("Intel")));
230  ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true));
231  EXPECT_CALL(epoxy, epoxy_gl_version).WillRepeatedly(::testing::Return(20));
232  EXPECT_CALL(epoxy, epoxy_has_gl_extension(::testing::_))
233  .WillRepeatedly(::testing::Return(false));
234  EXPECT_CALL(epoxy, epoxy_has_gl_extension(
235  ::testing::StrEq("GL_EXT_framebuffer_blit")))
236  .WillRepeatedly(::testing::Return(true));
237 
238  EXPECT_CALL(epoxy, glBlitFramebuffer);
239 
240  g_autoptr(FlFramebuffer) framebuffer =
241  fl_framebuffer_new(GL_RGB, width, height, FALSE);
242  FlutterBackingStore backing_store = {
243  .type = kFlutterBackingStoreTypeOpenGL,
244  .open_gl = {.framebuffer = {.user_data = framebuffer}}};
245  FlutterLayer layer = {.type = kFlutterLayerContentTypeBackingStore,
246  .backing_store = &backing_store,
247  .offset = {0, 0},
248  .size = {width, height}};
249  const FlutterLayer* layers[1] = {&layer};
250 
251  // Present layer and render.
252  std::thread([&]() {
253  fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
254  }).join();
255  int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
256  g_autofree unsigned char* image_data =
257  static_cast<unsigned char*>(malloc(height * stride));
258  cairo_surface_t* surface = cairo_image_surface_create_for_data(
259  image_data, CAIRO_FORMAT_ARGB32, width, height, stride);
260  cairo_t* cr = cairo_create(surface);
261  fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
262  cairo_surface_destroy(surface);
263  cairo_destroy(cr);
264 }
265 
266 TEST_F(FlCompositorOpenGLTest, NoBlitFramebuffer) {
267  constexpr size_t width = 100;
268  constexpr size_t height = 100;
269 
270  // OpenGL 2.0
271  ON_CALL(epoxy, glGetString(GL_VENDOR))
272  .WillByDefault(
273  ::testing::Return(reinterpret_cast<const GLubyte*>("Intel")));
274  ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true));
275  EXPECT_CALL(epoxy, epoxy_gl_version).WillRepeatedly(::testing::Return(20));
276 
277  g_autoptr(FlFramebuffer) framebuffer =
278  fl_framebuffer_new(GL_RGB, width, height, FALSE);
279  FlutterBackingStore backing_store = {
280  .type = kFlutterBackingStoreTypeOpenGL,
281  .open_gl = {.framebuffer = {.user_data = framebuffer}}};
282  FlutterLayer layer = {.type = kFlutterLayerContentTypeBackingStore,
283  .backing_store = &backing_store,
284  .offset = {0, 0},
285  .size = {width, height}};
286  const FlutterLayer* layers[1] = {&layer};
287 
288  // Present layer and render.
289  std::thread([&]() {
290  fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
291  }).join();
292  int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
293  g_autofree unsigned char* image_data =
294  static_cast<unsigned char*>(malloc(height * stride));
295  cairo_surface_t* surface = cairo_image_surface_create_for_data(
296  image_data, CAIRO_FORMAT_ARGB32, width, height, stride);
297  cairo_t* cr = cairo_create(surface);
298  fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
299  cairo_surface_destroy(surface);
300  cairo_destroy(cr);
301 }
302 
303 TEST_F(FlCompositorOpenGLTest, BlitFramebufferNvidia) {
304  constexpr size_t width = 100;
305  constexpr size_t height = 100;
306 
307  // OpenGL 3.0, but on NVIDIA driver so temporarily disabled due to
308  // https://github.com/flutter/flutter/issues/152099
309  ON_CALL(epoxy, glGetString(GL_VENDOR))
310  .WillByDefault(
311  ::testing::Return(reinterpret_cast<const GLubyte*>("NVIDIA")));
312  ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true));
313  EXPECT_CALL(epoxy, epoxy_gl_version).WillRepeatedly(::testing::Return(30));
314 
315  g_autoptr(FlFramebuffer) framebuffer =
316  fl_framebuffer_new(GL_RGB, width, height, FALSE);
317  FlutterBackingStore backing_store = {
318  .type = kFlutterBackingStoreTypeOpenGL,
319  .open_gl = {.framebuffer = {.user_data = framebuffer}}};
320  FlutterLayer layer = {.type = kFlutterLayerContentTypeBackingStore,
321  .backing_store = &backing_store,
322  .offset = {0, 0},
323  .size = {width, height}};
324  const FlutterLayer* layers[1] = {&layer};
325 
326  // Present layer and render.
327  std::thread([&]() {
328  fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
329  }).join();
330  int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
331  g_autofree unsigned char* image_data =
332  static_cast<unsigned char*>(malloc(height * stride));
333  cairo_surface_t* surface = cairo_image_surface_create_for_data(
334  image_data, CAIRO_FORMAT_ARGB32, width, height, stride);
335  cairo_t* cr = cairo_create(surface);
336  fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, TRUE);
337  cairo_surface_destroy(surface);
338  cairo_destroy(cr);
339 }
340 
341 TEST_F(FlCompositorOpenGLTest, RenderResizeCrash) {
342  // Present layer of size 100x100.
343  constexpr size_t width = 100;
344  constexpr size_t height = 100;
345  g_autoptr(FlFramebuffer) framebuffer =
346  fl_framebuffer_new(GL_RGB, width, height, FALSE);
347  FlutterBackingStore backing_store = {
348  .type = kFlutterBackingStoreTypeOpenGL,
349  .open_gl = {.framebuffer = {.user_data = framebuffer}}};
350  FlutterLayer layer = {.type = kFlutterLayerContentTypeBackingStore,
351  .backing_store = &backing_store,
352  .offset = {0, 0},
353  .size = {width, height}};
354  const FlutterLayer* layers[1] = {&layer};
355  std::thread([&]() {
356  fl_compositor_present_layers(FL_COMPOSITOR(compositor), layers, 1);
357  }).join();
358 
359  // Mock window size to be larger (200x200).
360  flutter::testing::MockGtk mock_gtk;
361  EXPECT_CALL(mock_gtk, gdk_window_get_width(::testing::_))
362  .WillRepeatedly(::testing::Return(200));
363  EXPECT_CALL(mock_gtk, gdk_window_get_height(::testing::_))
364  .WillRepeatedly(::testing::Return(200));
365 
366  // Render with wait_for_frame = FALSE.
367  // This should not wait for a new frame, and try to render the 100x100 frame
368  // into the 200x200 window.
369  // If bug is present, it will try to read 200x200 from 100x100 buffer.
370  int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, 200);
371  g_autofree unsigned char* image_data =
372  static_cast<unsigned char*>(g_malloc(200 * stride));
373  cairo_surface_t* surface = cairo_image_surface_create_for_data(
374  image_data, CAIRO_FORMAT_ARGB32, 200, 200, stride);
375  cairo_t* cr = cairo_create(surface);
376 
377  // We expect this to not crash.
378  fl_compositor_render(FL_COMPOSITOR(compositor), cr, nullptr, FALSE);
379 
380  cairo_surface_destroy(surface);
381  cairo_destroy(cr);
382 }
::testing::NiceMock< flutter::testing::MockEpoxy > epoxy
g_autoptr(FlEngine) engine
gboolean fl_compositor_render(FlCompositor *self, cairo_t *cr, GdkWindow *window, gboolean wait_for_frame)
void fl_compositor_get_frame_size(FlCompositor *self, size_t *width, size_t *height)
gboolean fl_compositor_present_layers(FlCompositor *self, const FlutterLayer **layers, size_t layers_count)
FlCompositorOpenGL * fl_compositor_opengl_new(FlTaskRunner *task_runner, FlOpenGLManager *opengl_manager, gboolean shareable)
FlFramebuffer * framebuffer
FlFramebuffer double double int int height
FlFramebuffer double double int width
glBindTexture(GL_TEXTURE_2D, texture_id)
TEST_F(FlCompositorOpenGLTest, Render)
const FlutterLayer ** layers
return TRUE
void fl_engine_set_implicit_view(FlEngine *self, FlRenderable *renderable)
Definition: fl_engine.cc:957
FlFramebuffer * fl_framebuffer_new(GLint format, size_t width, size_t height, gboolean shareable)
FlOpenGLManager * fl_opengl_manager_new()
FlTaskRunner * fl_task_runner_new(FlEngine *engine)