blob: 595c31513472f40434cf90a785ce8aa738ca8b17 [file] [log] [blame]
// 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);
}
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);
}