blob: 80bdbea2db06a4bfe885afa78fe00207f52410c3 [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_engine_private.h"
#include "flutter/shell/platform/linux/fl_framebuffer.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 {
// Engine we are rendering.
GWeakRef engine;
// Views being rendered.
GHashTable* views;
// 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;
// True if we can use glBlitFramebuffer.
bool has_gl_framebuffer_blit;
// Shader program.
GLuint program;
// Framebuffers to render.
GPtrArray* framebuffers;
} FlRendererPrivate;
G_DEFINE_TYPE_WITH_PRIVATE(FlRenderer, fl_renderer, G_TYPE_OBJECT)
// Check if running on an NVIDIA driver.
static gboolean is_nvidia() {
const gchar* vendor = reinterpret_cast<const gchar*>(glGetString(GL_VENDOR));
return strstr(vendor, "NVIDIA") != nullptr;
}
// Returns the log for the given OpenGL shader. Must be freed by the caller.
static gchar* get_shader_log(GLuint shader) {
GLint 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) {
GLint 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;
g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&priv->engine));
if (engine != nullptr) {
fl_task_runner_release_main_thread(fl_engine_get_task_runner(engine));
}
}
}
static void setup_shader(FlRenderer* self) {
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
fl_renderer_get_instance_private(self));
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &vertex_shader_src, nullptr);
glCompileShader(vertex_shader);
GLint 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);
GLint 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);
GLint 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);
}
static void render_with_blit(FlRenderer* self) {
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
fl_renderer_get_instance_private(self));
// Disable the scissor test as it can affect blit operations.
// Prevents regressions like: https://github.com/flutter/flutter/issues/140828
// See OpenGL specification version 4.6, section 18.3.1.
glDisable(GL_SCISSOR_TEST);
for (guint i = 0; i < priv->framebuffers->len; i++) {
FlFramebuffer* framebuffer =
FL_FRAMEBUFFER(g_ptr_array_index(priv->framebuffers, i));
GLuint framebuffer_id = fl_framebuffer_get_id(framebuffer);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_id);
size_t width = fl_framebuffer_get_width(framebuffer);
size_t height = fl_framebuffer_get_height(framebuffer);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}
static void render_with_textures(FlRenderer* self, int width, int height) {
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
fl_renderer_get_instance_private(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);
glUseProgram(priv->program);
for (guint i = 0; i < priv->framebuffers->len; i++) {
FlFramebuffer* framebuffer =
FL_FRAMEBUFFER(g_ptr_array_index(priv->framebuffers, i));
GLuint texture_id = fl_framebuffer_get_texture_id(framebuffer);
glBindTexture(GL_TEXTURE_2D, texture_id);
// Translate into OpenGL co-ordinates
size_t texture_width = fl_framebuffer_get_width(framebuffer);
size_t texture_height = fl_framebuffer_get_height(framebuffer);
GLfloat x0 = pixels_to_gl_coords(0, width);
GLfloat y0 = pixels_to_gl_coords(height - texture_height, height);
GLfloat x1 = pixels_to_gl_coords(texture_width, width);
GLfloat y1 = pixels_to_gl_coords(height, 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);
}
glBindTexture(GL_TEXTURE_2D, saved_texture_binding);
glBindVertexArray(saved_vao_binding);
glBindBuffer(GL_ARRAY_BUFFER, saved_array_buffer_binding);
}
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_weak_ref_clear(&priv->engine);
g_clear_pointer(&priv->views, g_hash_table_unref);
g_clear_pointer(&priv->framebuffers, 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->views =
g_hash_table_new_full(g_direct_hash, g_direct_equal, nullptr, nullptr);
priv->framebuffers = g_ptr_array_new_with_free_func(g_object_unref);
}
void fl_renderer_set_engine(FlRenderer* self, FlEngine* engine) {
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
fl_renderer_get_instance_private(self));
g_return_if_fail(FL_IS_RENDERER(self));
g_weak_ref_init(&priv->engine, engine);
}
void fl_renderer_add_view(FlRenderer* self,
FlutterViewId view_id,
FlView* view) {
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
fl_renderer_get_instance_private(self));
g_return_if_fail(FL_IS_RENDERER(self));
g_hash_table_insert(priv->views, GINT_TO_POINTER(view_id), view);
}
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);
FlFramebuffer* framebuffer =
fl_framebuffer_new(config->size.width, config->size.height);
if (!framebuffer) {
g_warning("Failed to create backing store");
return FALSE;
}
backing_store_out->type = kFlutterBackingStoreTypeOpenGL;
backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer;
backing_store_out->open_gl.framebuffer.user_data = framebuffer;
backing_store_out->open_gl.framebuffer.name =
fl_framebuffer_get_id(framebuffer);
backing_store_out->open_gl.framebuffer.target =
fl_framebuffer_get_format(framebuffer);
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 #FlFramebuffer.
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;
g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&priv->engine));
if (engine != nullptr) {
fl_task_runner_block_main_thread(fl_engine_get_task_runner(engine));
}
}
}
gboolean fl_renderer_present_layers(FlRenderer* self,
FlutterViewId view_id,
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);
g_ptr_array_set_size(priv->framebuffers, 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;
FlFramebuffer* framebuffer =
FL_FRAMEBUFFER(backing_store->open_gl.framebuffer.user_data);
g_ptr_array_add(priv->framebuffers, g_object_ref(framebuffer));
} break;
case kFlutterLayerContentTypePlatformView: {
// TODO(robert-ancell) Not implemented -
// https://github.com/flutter/flutter/issues/41724
} break;
}
}
FlView* view =
FL_VIEW(g_hash_table_lookup(priv->views, GINT_TO_POINTER(view_id)));
if (view != nullptr) {
fl_view_redraw(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));
// Note: NVIDIA is temporarily disabled due to
// https://github.com/flutter/flutter/issues/152099
priv->has_gl_framebuffer_blit =
!is_nvidia() && (epoxy_gl_version() >= 30 ||
epoxy_has_gl_extension("GL_EXT_framebuffer_blit"));
if (!priv->has_gl_framebuffer_blit) {
setup_shader(self);
}
}
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));
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
if (priv->has_gl_framebuffer_blit) {
render_with_blit(self);
} else {
render_with_textures(self, width, height);
}
glFlush();
}
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));
if (priv->program != 0) {
glDeleteProgram(priv->program);
}
}