| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "fl_renderer.h" | |
| #include <epoxy/egl.h> | |
| #include <epoxy/gl.h> | |
| #include "flutter/shell/platform/embedder/embedder.h" | |
| #include "flutter/shell/platform/linux/fl_backing_store_provider.h" | |
| #include "flutter/shell/platform/linux/fl_engine_private.h" | |
| #include "flutter/shell/platform/linux/fl_view_private.h" | |
| // Vertex shader to draw Flutter window contents. | |
| static const char* vertex_shader_src = | |
| "attribute vec2 position;\n" | |
| "attribute vec2 in_texcoord;\n" | |
| "varying vec2 texcoord;\n" | |
| "\n" | |
| "void main() {\n" | |
| " gl_Position = vec4(position, 0, 1);\n" | |
| " texcoord = in_texcoord;\n" | |
| "}\n"; | |
| // Fragment shader to draw Flutter window contents. | |
| static const char* fragment_shader_src = | |
| "uniform sampler2D texture;\n" | |
| "varying vec2 texcoord;\n" | |
| "\n" | |
| "void main() {\n" | |
| " gl_FragColor = texture2D(texture, texcoord);\n" | |
| "}\n"; | |
| G_DEFINE_QUARK(fl_renderer_error_quark, fl_renderer_error) | |
| typedef struct { | |
| FlView* view; | |
| // target dimension for resizing | |
| int target_width; | |
| int target_height; | |
| // whether the renderer waits for frame render | |
| bool blocking_main_thread; | |
| // true if frame was completed; resizing is not synchronized until first frame | |
| // was rendered | |
| bool had_first_frame; | |
| // Shader program. | |
| GLuint program; | |
| // Textures to render. | |
| GPtrArray* textures; | |
| } FlRendererPrivate; | |
| G_DEFINE_TYPE_WITH_PRIVATE(FlRenderer, fl_renderer, G_TYPE_OBJECT) | |
| // Returns the log for the given OpenGL shader. Must be freed by the caller. | |
| static gchar* get_shader_log(GLuint shader) { | |
| int log_length; | |
| gchar* log; | |
| glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length); | |
| log = static_cast<gchar*>(g_malloc(log_length + 1)); | |
| glGetShaderInfoLog(shader, log_length, nullptr, log); | |
| return log; | |
| } | |
| // Returns the log for the given OpenGL program. Must be freed by the caller. | |
| static gchar* get_program_log(GLuint program) { | |
| int log_length; | |
| gchar* log; | |
| glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length); | |
| log = static_cast<gchar*>(g_malloc(log_length + 1)); | |
| glGetProgramInfoLog(program, log_length, nullptr, log); | |
| return log; | |
| } | |
| /// Converts a pixel co-ordinate from 0..pixels to OpenGL -1..1. | |
| static GLfloat pixels_to_gl_coords(GLfloat position, GLfloat pixels) { | |
| return (2.0 * position / pixels) - 1.0; | |
| } | |
| static void fl_renderer_unblock_main_thread(FlRenderer* self) { | |
| FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>( | |
| fl_renderer_get_instance_private(self)); | |
| if (priv->blocking_main_thread) { | |
| priv->blocking_main_thread = false; | |
| FlTaskRunner* runner = | |
| fl_engine_get_task_runner(fl_view_get_engine(priv->view)); | |
| fl_task_runner_release_main_thread(runner); | |
| } | |
| } | |
| static void fl_renderer_dispose(GObject* object) { | |
| FlRenderer* self = FL_RENDERER(object); | |
| FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>( | |
| fl_renderer_get_instance_private(self)); | |
| fl_renderer_unblock_main_thread(self); | |
| g_clear_pointer(&priv->textures, g_ptr_array_unref); | |
| G_OBJECT_CLASS(fl_renderer_parent_class)->dispose(object); | |
| } | |
| static void fl_renderer_class_init(FlRendererClass* klass) { | |
| G_OBJECT_CLASS(klass)->dispose = fl_renderer_dispose; | |
| } | |
| static void fl_renderer_init(FlRenderer* self) { | |
| FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>( | |
| fl_renderer_get_instance_private(self)); | |
| priv->textures = g_ptr_array_new_with_free_func(g_object_unref); | |
| } | |
| gboolean fl_renderer_start(FlRenderer* self, FlView* view) { | |
| FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>( | |
| fl_renderer_get_instance_private(self)); | |
| g_return_val_if_fail(FL_IS_RENDERER(self), FALSE); | |
| priv->view = view; | |
| return TRUE; | |
| } | |
| void* fl_renderer_get_proc_address(FlRenderer* self, const char* name) { | |
| g_return_val_if_fail(FL_IS_RENDERER(self), NULL); | |
| return reinterpret_cast<void*>(eglGetProcAddress(name)); | |
| } | |
| void fl_renderer_make_current(FlRenderer* self) { | |
| g_return_if_fail(FL_IS_RENDERER(self)); | |
| FL_RENDERER_GET_CLASS(self)->make_current(self); | |
| } | |
| void fl_renderer_make_resource_current(FlRenderer* self) { | |
| g_return_if_fail(FL_IS_RENDERER(self)); | |
| FL_RENDERER_GET_CLASS(self)->make_resource_current(self); | |
| } | |
| void fl_renderer_clear_current(FlRenderer* self) { | |
| g_return_if_fail(FL_IS_RENDERER(self)); | |
| FL_RENDERER_GET_CLASS(self)->clear_current(self); | |
| } | |
| gdouble fl_renderer_get_refresh_rate(FlRenderer* self) { | |
| g_return_val_if_fail(FL_IS_RENDERER(self), -1.0); | |
| return FL_RENDERER_GET_CLASS(self)->get_refresh_rate(self); | |
| } | |
| guint32 fl_renderer_get_fbo(FlRenderer* self) { | |
| g_return_val_if_fail(FL_IS_RENDERER(self), 0); | |
| // There is only one frame buffer object - always return that. | |
| return 0; | |
| } | |
| gboolean fl_renderer_create_backing_store( | |
| FlRenderer* renderer, | |
| const FlutterBackingStoreConfig* config, | |
| FlutterBackingStore* backing_store_out) { | |
| fl_renderer_make_current(renderer); | |
| FlBackingStoreProvider* provider = | |
| fl_backing_store_provider_new(config->size.width, config->size.height); | |
| if (!provider) { | |
| g_warning("Failed to create backing store"); | |
| return FALSE; | |
| } | |
| uint32_t name = fl_backing_store_provider_get_gl_framebuffer_id(provider); | |
| uint32_t format = fl_backing_store_provider_get_gl_format(provider); | |
| backing_store_out->type = kFlutterBackingStoreTypeOpenGL; | |
| backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; | |
| backing_store_out->open_gl.framebuffer.user_data = provider; | |
| backing_store_out->open_gl.framebuffer.name = name; | |
| backing_store_out->open_gl.framebuffer.target = format; | |
| backing_store_out->open_gl.framebuffer.destruction_callback = [](void* p) { | |
| // Backing store destroyed in fl_renderer_collect_backing_store(), set | |
| // on FlutterCompositor.collect_backing_store_callback during engine start. | |
| }; | |
| return TRUE; | |
| } | |
| gboolean fl_renderer_collect_backing_store( | |
| FlRenderer* self, | |
| const FlutterBackingStore* backing_store) { | |
| fl_renderer_make_current(self); | |
| // OpenGL context is required when destroying #FlBackingStoreProvider. | |
| g_object_unref(backing_store->open_gl.framebuffer.user_data); | |
| return TRUE; | |
| } | |
| void fl_renderer_wait_for_frame(FlRenderer* self, | |
| int target_width, | |
| int target_height) { | |
| FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>( | |
| fl_renderer_get_instance_private(self)); | |
| g_return_if_fail(FL_IS_RENDERER(self)); | |
| priv->target_width = target_width; | |
| priv->target_height = target_height; | |
| if (priv->had_first_frame && !priv->blocking_main_thread) { | |
| priv->blocking_main_thread = true; | |
| FlTaskRunner* runner = | |
| fl_engine_get_task_runner(fl_view_get_engine(priv->view)); | |
| fl_task_runner_block_main_thread(runner); | |
| } | |
| } | |
| gboolean fl_renderer_present_layers(FlRenderer* self, | |
| const FlutterLayer** layers, | |
| size_t layers_count) { | |
| FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>( | |
| fl_renderer_get_instance_private(self)); | |
| g_return_val_if_fail(FL_IS_RENDERER(self), FALSE); | |
| // ignore incoming frame with wrong dimensions in trivial case with just one | |
| // layer | |
| if (priv->blocking_main_thread && layers_count == 1 && | |
| layers[0]->offset.x == 0 && layers[0]->offset.y == 0 && | |
| (layers[0]->size.width != priv->target_width || | |
| layers[0]->size.height != priv->target_height)) { | |
| return true; | |
| } | |
| priv->had_first_frame = true; | |
| fl_renderer_unblock_main_thread(self); | |
| if (!priv->view) { | |
| return FALSE; | |
| } | |
| g_ptr_array_set_size(priv->textures, 0); | |
| for (size_t i = 0; i < layers_count; ++i) { | |
| const FlutterLayer* layer = layers[i]; | |
| switch (layer->type) { | |
| case kFlutterLayerContentTypeBackingStore: { | |
| const FlutterBackingStore* backing_store = layer->backing_store; | |
| auto framebuffer = &backing_store->open_gl.framebuffer; | |
| FlBackingStoreProvider* provider = | |
| FL_BACKING_STORE_PROVIDER(framebuffer->user_data); | |
| g_ptr_array_add(priv->textures, g_object_ref(provider)); | |
| } break; | |
| case kFlutterLayerContentTypePlatformView: { | |
| // TODO(robert-ancell) Not implemented - | |
| // https://github.com/flutter/flutter/issues/41724 | |
| } break; | |
| } | |
| } | |
| fl_view_redraw(priv->view); | |
| return TRUE; | |
| } | |
| void fl_renderer_setup(FlRenderer* self) { | |
| FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>( | |
| fl_renderer_get_instance_private(self)); | |
| g_return_if_fail(FL_IS_RENDERER(self)); | |
| GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); | |
| glShaderSource(vertex_shader, 1, &vertex_shader_src, nullptr); | |
| glCompileShader(vertex_shader); | |
| int vertex_compile_status; | |
| glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &vertex_compile_status); | |
| if (vertex_compile_status == GL_FALSE) { | |
| g_autofree gchar* shader_log = get_shader_log(vertex_shader); | |
| g_warning("Failed to compile vertex shader: %s", shader_log); | |
| } | |
| GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); | |
| glShaderSource(fragment_shader, 1, &fragment_shader_src, nullptr); | |
| glCompileShader(fragment_shader); | |
| int fragment_compile_status; | |
| glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &fragment_compile_status); | |
| if (fragment_compile_status == GL_FALSE) { | |
| g_autofree gchar* shader_log = get_shader_log(fragment_shader); | |
| g_warning("Failed to compile fragment shader: %s", shader_log); | |
| } | |
| priv->program = glCreateProgram(); | |
| glAttachShader(priv->program, vertex_shader); | |
| glAttachShader(priv->program, fragment_shader); | |
| glLinkProgram(priv->program); | |
| int link_status; | |
| glGetProgramiv(priv->program, GL_LINK_STATUS, &link_status); | |
| if (link_status == GL_FALSE) { | |
| g_autofree gchar* program_log = get_program_log(priv->program); | |
| g_warning("Failed to link program: %s", program_log); | |
| } | |
| glDeleteShader(vertex_shader); | |
| glDeleteShader(fragment_shader); | |
| } | |
| void fl_renderer_render(FlRenderer* self, int width, int height) { | |
| FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>( | |
| fl_renderer_get_instance_private(self)); | |
| g_return_if_fail(FL_IS_RENDERER(self)); | |
| // Save bindings that are set by this function. All bindings must be restored | |
| // to their original values because Skia expects that its bindings have not | |
| // been altered. | |
| GLint saved_texture_binding; | |
| glGetIntegerv(GL_TEXTURE_BINDING_2D, &saved_texture_binding); | |
| GLint saved_vao_binding; | |
| glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &saved_vao_binding); | |
| GLint saved_array_buffer_binding; | |
| glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &saved_array_buffer_binding); | |
| glClearColor(0.0, 0.0, 0.0, 1.0); | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| glUseProgram(priv->program); | |
| for (guint i = 0; i < priv->textures->len; i++) { | |
| FlBackingStoreProvider* texture = | |
| FL_BACKING_STORE_PROVIDER(g_ptr_array_index(priv->textures, i)); | |
| uint32_t texture_id = fl_backing_store_provider_get_gl_texture_id(texture); | |
| glBindTexture(GL_TEXTURE_2D, texture_id); | |
| // Translate into OpenGL co-ordinates | |
| GdkRectangle texture_geometry = | |
| fl_backing_store_provider_get_geometry(texture); | |
| GLfloat texture_x = texture_geometry.x; | |
| GLfloat texture_y = texture_geometry.y; | |
| GLfloat texture_width = texture_geometry.width; | |
| GLfloat texture_height = texture_geometry.height; | |
| GLfloat x0 = pixels_to_gl_coords(texture_x, width); | |
| GLfloat y0 = | |
| pixels_to_gl_coords(height - (texture_y + texture_height), height); | |
| GLfloat x1 = pixels_to_gl_coords(texture_x + texture_width, width); | |
| GLfloat y1 = pixels_to_gl_coords(height - texture_y, height); | |
| GLfloat vertex_data[] = {x0, y0, 0, 0, x1, y1, 1, 1, x0, y1, 0, 1, | |
| x0, y0, 0, 0, x1, y0, 1, 0, x1, y1, 1, 1}; | |
| GLuint vao, vertex_buffer; | |
| glGenVertexArrays(1, &vao); | |
| glBindVertexArray(vao); | |
| glGenBuffers(1, &vertex_buffer); | |
| glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); | |
| glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, | |
| GL_STATIC_DRAW); | |
| GLint position_index = glGetAttribLocation(priv->program, "position"); | |
| glEnableVertexAttribArray(position_index); | |
| glVertexAttribPointer(position_index, 2, GL_FLOAT, GL_FALSE, | |
| sizeof(GLfloat) * 4, 0); | |
| GLint texcoord_index = glGetAttribLocation(priv->program, "in_texcoord"); | |
| glEnableVertexAttribArray(texcoord_index); | |
| glVertexAttribPointer(texcoord_index, 2, GL_FLOAT, GL_FALSE, | |
| sizeof(GLfloat) * 4, (void*)(sizeof(GLfloat) * 2)); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| glDeleteVertexArrays(1, &vao); | |
| glDeleteBuffers(1, &vertex_buffer); | |
| } | |
| glFlush(); | |
| glBindTexture(GL_TEXTURE_2D, saved_texture_binding); | |
| glBindVertexArray(saved_vao_binding); | |
| glBindBuffer(GL_ARRAY_BUFFER, saved_array_buffer_binding); | |
| } | |
| void fl_renderer_cleanup(FlRenderer* self) { | |
| FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>( | |
| fl_renderer_get_instance_private(self)); | |
| g_return_if_fail(FL_IS_RENDERER(self)); | |
| glDeleteProgram(priv->program); | |
| } |