Use glBlitFramebuffer when rendering (flutter/engine#53080)
This is much faster than using a shader which is not required currently (we are not doing any transformations).
This commit is contained in:
parent
095683b513
commit
dde01765c9
@ -48,6 +48,9 @@ typedef struct {
|
|||||||
// was rendered
|
// was rendered
|
||||||
bool had_first_frame;
|
bool had_first_frame;
|
||||||
|
|
||||||
|
// True if we can use glBlitFramebuffer.
|
||||||
|
bool has_gl_framebuffer_blit;
|
||||||
|
|
||||||
// Shader program.
|
// Shader program.
|
||||||
GLuint program;
|
GLuint program;
|
||||||
|
|
||||||
@ -59,7 +62,7 @@ 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.
|
// Returns the log for the given OpenGL shader. Must be freed by the caller.
|
||||||
static gchar* get_shader_log(GLuint shader) {
|
static gchar* get_shader_log(GLuint shader) {
|
||||||
int log_length;
|
GLint log_length;
|
||||||
gchar* log;
|
gchar* log;
|
||||||
|
|
||||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
|
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
|
||||||
@ -72,7 +75,7 @@ static gchar* get_shader_log(GLuint shader) {
|
|||||||
|
|
||||||
// Returns the log for the given OpenGL program. Must be freed by the caller.
|
// Returns the log for the given OpenGL program. Must be freed by the caller.
|
||||||
static gchar* get_program_log(GLuint program) {
|
static gchar* get_program_log(GLuint program) {
|
||||||
int log_length;
|
GLint log_length;
|
||||||
gchar* log;
|
gchar* log;
|
||||||
|
|
||||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
|
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
|
||||||
@ -100,6 +103,136 @@ static void fl_renderer_unblock_main_thread(FlRenderer* self) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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->textures->len; i++) {
|
||||||
|
FlBackingStoreProvider* texture =
|
||||||
|
FL_BACKING_STORE_PROVIDER(g_ptr_array_index(priv->textures, i));
|
||||||
|
|
||||||
|
uint32_t framebuffer_id =
|
||||||
|
fl_backing_store_provider_get_gl_framebuffer_id(texture);
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_id);
|
||||||
|
GdkRectangle geometry = fl_backing_store_provider_get_geometry(texture);
|
||||||
|
glBlitFramebuffer(0, 0, geometry.width, geometry.height, geometry.x,
|
||||||
|
geometry.y, geometry.x + geometry.width,
|
||||||
|
geometry.y + geometry.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->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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
static void fl_renderer_dispose(GObject* object) {
|
||||||
FlRenderer* self = FL_RENDERER(object);
|
FlRenderer* self = FL_RENDERER(object);
|
||||||
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
|
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
|
||||||
@ -244,10 +377,6 @@ gboolean fl_renderer_present_layers(FlRenderer* self,
|
|||||||
|
|
||||||
fl_renderer_unblock_main_thread(self);
|
fl_renderer_unblock_main_thread(self);
|
||||||
|
|
||||||
if (!priv->view) {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_ptr_array_set_size(priv->textures, 0);
|
g_ptr_array_set_size(priv->textures, 0);
|
||||||
for (size_t i = 0; i < layers_count; ++i) {
|
for (size_t i = 0; i < layers_count; ++i) {
|
||||||
const FlutterLayer* layer = layers[i];
|
const FlutterLayer* layer = layers[i];
|
||||||
@ -266,7 +395,9 @@ gboolean fl_renderer_present_layers(FlRenderer* self,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fl_view_redraw(priv->view);
|
if (priv->view != nullptr) {
|
||||||
|
fl_view_redraw(priv->view);
|
||||||
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@ -277,40 +408,13 @@ void fl_renderer_setup(FlRenderer* self) {
|
|||||||
|
|
||||||
g_return_if_fail(FL_IS_RENDERER(self));
|
g_return_if_fail(FL_IS_RENDERER(self));
|
||||||
|
|
||||||
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
|
priv->has_gl_framebuffer_blit =
|
||||||
glShaderSource(vertex_shader, 1, &vertex_shader_src, nullptr);
|
epoxy_gl_version() >= 30 ||
|
||||||
glCompileShader(vertex_shader);
|
epoxy_has_gl_extension("GL_EXT_framebuffer_blit");
|
||||||
int vertex_compile_status;
|
|
||||||
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &vertex_compile_status);
|
if (!priv->has_gl_framebuffer_blit) {
|
||||||
if (vertex_compile_status == GL_FALSE) {
|
setup_shader(self);
|
||||||
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) {
|
void fl_renderer_render(FlRenderer* self, int width, int height) {
|
||||||
@ -319,70 +423,16 @@ void fl_renderer_render(FlRenderer* self, int width, int height) {
|
|||||||
|
|
||||||
g_return_if_fail(FL_IS_RENDERER(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);
|
glClearColor(0.0, 0.0, 0.0, 1.0);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
glUseProgram(priv->program);
|
if (priv->has_gl_framebuffer_blit) {
|
||||||
|
render_with_blit(self);
|
||||||
for (guint i = 0; i < priv->textures->len; i++) {
|
} else {
|
||||||
FlBackingStoreProvider* texture =
|
render_with_textures(self, width, height);
|
||||||
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();
|
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) {
|
void fl_renderer_cleanup(FlRenderer* self) {
|
||||||
@ -391,5 +441,7 @@ void fl_renderer_cleanup(FlRenderer* self) {
|
|||||||
|
|
||||||
g_return_if_fail(FL_IS_RENDERER(self));
|
g_return_if_fail(FL_IS_RENDERER(self));
|
||||||
|
|
||||||
glDeleteProgram(priv->program);
|
if (priv->program != 0) {
|
||||||
|
glDeleteProgram(priv->program);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,17 @@
|
|||||||
|
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
#include <epoxy/egl.h>
|
|
||||||
|
|
||||||
#include "flutter/fml/logging.h"
|
#include "flutter/fml/logging.h"
|
||||||
#include "flutter/shell/platform/linux/fl_backing_store_provider.h"
|
#include "flutter/shell/platform/linux/fl_backing_store_provider.h"
|
||||||
#include "flutter/shell/platform/linux/testing/fl_test_gtk_logs.h"
|
#include "flutter/shell/platform/linux/testing/fl_test_gtk_logs.h"
|
||||||
|
#include "flutter/shell/platform/linux/testing/mock_epoxy.h"
|
||||||
#include "flutter/shell/platform/linux/testing/mock_renderer.h"
|
#include "flutter/shell/platform/linux/testing/mock_renderer.h"
|
||||||
|
|
||||||
|
#include <epoxy/egl.h>
|
||||||
|
|
||||||
TEST(FlRendererTest, RestoresGLState) {
|
TEST(FlRendererTest, RestoresGLState) {
|
||||||
|
::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
|
||||||
|
|
||||||
constexpr int kWidth = 100;
|
constexpr int kWidth = 100;
|
||||||
constexpr int kHeight = 100;
|
constexpr int kHeight = 100;
|
||||||
|
|
||||||
@ -67,3 +70,88 @@ TEST(FlRendererTest, RefreshRate) {
|
|||||||
fl_renderer_get_refresh_rate(FL_RENDERER(renderer));
|
fl_renderer_get_refresh_rate(FL_RENDERER(renderer));
|
||||||
EXPECT_DOUBLE_EQ(result_refresh_rate, kExpectedRefreshRate);
|
EXPECT_DOUBLE_EQ(result_refresh_rate, kExpectedRefreshRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(FlRendererTest, BlitFramebuffer) {
|
||||||
|
::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
|
||||||
|
|
||||||
|
// OpenGL 3.0
|
||||||
|
ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true));
|
||||||
|
EXPECT_CALL(epoxy, epoxy_gl_version).WillRepeatedly(::testing::Return(30));
|
||||||
|
|
||||||
|
EXPECT_CALL(epoxy, glBlitFramebuffer);
|
||||||
|
|
||||||
|
g_autoptr(FlMockRenderer) renderer =
|
||||||
|
fl_mock_renderer_new(&renderer_get_refresh_rate);
|
||||||
|
fl_renderer_setup(FL_RENDERER(renderer));
|
||||||
|
fl_renderer_wait_for_frame(FL_RENDERER(renderer), 1024, 1024);
|
||||||
|
FlutterBackingStoreConfig config = {
|
||||||
|
.struct_size = sizeof(FlutterBackingStoreConfig),
|
||||||
|
.size = {.width = 1024, .height = 1024}};
|
||||||
|
FlutterBackingStore backing_store;
|
||||||
|
fl_renderer_create_backing_store(FL_RENDERER(renderer), &config,
|
||||||
|
&backing_store);
|
||||||
|
const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer),
|
||||||
|
.type = kFlutterLayerContentTypeBackingStore,
|
||||||
|
.backing_store = &backing_store,
|
||||||
|
.size = {.width = 1024, .height = 1024}};
|
||||||
|
const FlutterLayer* layers[] = {&layer0};
|
||||||
|
fl_renderer_present_layers(FL_RENDERER(renderer), layers, 1);
|
||||||
|
fl_renderer_render(FL_RENDERER(renderer), 1024, 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FlRendererTest, BlitFramebufferExtension) {
|
||||||
|
::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
|
||||||
|
|
||||||
|
// OpenGL 2.0 with GL_EXT_framebuffer_blit extension
|
||||||
|
ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true));
|
||||||
|
EXPECT_CALL(epoxy, epoxy_gl_version).WillRepeatedly(::testing::Return(20));
|
||||||
|
EXPECT_CALL(epoxy, epoxy_has_gl_extension(
|
||||||
|
::testing::StrEq("GL_EXT_framebuffer_blit")))
|
||||||
|
.WillRepeatedly(::testing::Return(true));
|
||||||
|
|
||||||
|
EXPECT_CALL(epoxy, glBlitFramebuffer);
|
||||||
|
|
||||||
|
g_autoptr(FlMockRenderer) renderer =
|
||||||
|
fl_mock_renderer_new(&renderer_get_refresh_rate);
|
||||||
|
fl_renderer_setup(FL_RENDERER(renderer));
|
||||||
|
fl_renderer_wait_for_frame(FL_RENDERER(renderer), 1024, 1024);
|
||||||
|
FlutterBackingStoreConfig config = {
|
||||||
|
.struct_size = sizeof(FlutterBackingStoreConfig),
|
||||||
|
.size = {.width = 1024, .height = 1024}};
|
||||||
|
FlutterBackingStore backing_store;
|
||||||
|
fl_renderer_create_backing_store(FL_RENDERER(renderer), &config,
|
||||||
|
&backing_store);
|
||||||
|
const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer),
|
||||||
|
.type = kFlutterLayerContentTypeBackingStore,
|
||||||
|
.backing_store = &backing_store,
|
||||||
|
.size = {.width = 1024, .height = 1024}};
|
||||||
|
const FlutterLayer* layers[] = {&layer0};
|
||||||
|
fl_renderer_present_layers(FL_RENDERER(renderer), layers, 1);
|
||||||
|
fl_renderer_render(FL_RENDERER(renderer), 1024, 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FlRendererTest, NoBlitFramebuffer) {
|
||||||
|
::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
|
||||||
|
|
||||||
|
// OpenGL 2.0
|
||||||
|
ON_CALL(epoxy, epoxy_is_desktop_gl).WillByDefault(::testing::Return(true));
|
||||||
|
EXPECT_CALL(epoxy, epoxy_gl_version).WillRepeatedly(::testing::Return(20));
|
||||||
|
|
||||||
|
g_autoptr(FlMockRenderer) renderer =
|
||||||
|
fl_mock_renderer_new(&renderer_get_refresh_rate);
|
||||||
|
fl_renderer_setup(FL_RENDERER(renderer));
|
||||||
|
fl_renderer_wait_for_frame(FL_RENDERER(renderer), 1024, 1024);
|
||||||
|
FlutterBackingStoreConfig config = {
|
||||||
|
.struct_size = sizeof(FlutterBackingStoreConfig),
|
||||||
|
.size = {.width = 1024, .height = 1024}};
|
||||||
|
FlutterBackingStore backing_store;
|
||||||
|
fl_renderer_create_backing_store(FL_RENDERER(renderer), &config,
|
||||||
|
&backing_store);
|
||||||
|
const FlutterLayer layer0 = {.struct_size = sizeof(FlutterLayer),
|
||||||
|
.type = kFlutterLayerContentTypeBackingStore,
|
||||||
|
.backing_store = &backing_store,
|
||||||
|
.size = {.width = 1024, .height = 1024}};
|
||||||
|
const FlutterLayer* layers[] = {&layer0};
|
||||||
|
fl_renderer_present_layers(FL_RENDERER(renderer), layers, 1);
|
||||||
|
fl_renderer_render(FL_RENDERER(renderer), 1024, 1024);
|
||||||
|
}
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
#include <epoxy/egl.h>
|
#include "flutter/shell/platform/linux/testing/mock_epoxy.h"
|
||||||
#include <epoxy/gl.h>
|
|
||||||
|
using namespace flutter::testing;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
EGLint config_id;
|
EGLint config_id;
|
||||||
@ -45,6 +46,7 @@ typedef struct {
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
} MockSurface;
|
} MockSurface;
|
||||||
|
|
||||||
|
static MockEpoxy* mock = nullptr;
|
||||||
static bool display_initialized = false;
|
static bool display_initialized = false;
|
||||||
static MockDisplay mock_display;
|
static MockDisplay mock_display;
|
||||||
static MockConfig mock_config;
|
static MockConfig mock_config;
|
||||||
@ -53,6 +55,10 @@ static MockSurface mock_surface;
|
|||||||
|
|
||||||
static EGLint mock_error = EGL_SUCCESS;
|
static EGLint mock_error = EGL_SUCCESS;
|
||||||
|
|
||||||
|
MockEpoxy::MockEpoxy() {
|
||||||
|
mock = this;
|
||||||
|
}
|
||||||
|
|
||||||
static bool check_display(EGLDisplay dpy) {
|
static bool check_display(EGLDisplay dpy) {
|
||||||
if (dpy == nullptr) {
|
if (dpy == nullptr) {
|
||||||
mock_error = EGL_BAD_DISPLAY;
|
mock_error = EGL_BAD_DISPLAY;
|
||||||
@ -346,6 +352,8 @@ EGLBoolean _eglSwapBuffers(EGLDisplay dpy, EGLSurface surface) {
|
|||||||
|
|
||||||
static GLuint bound_texture_2d;
|
static GLuint bound_texture_2d;
|
||||||
|
|
||||||
|
void _glAttachShader(GLuint program, GLuint shader) {}
|
||||||
|
|
||||||
static void _glBindFramebuffer(GLenum target, GLuint framebuffer) {}
|
static void _glBindFramebuffer(GLenum target, GLuint framebuffer) {}
|
||||||
|
|
||||||
static void _glBindTexture(GLenum target, GLuint texture) {
|
static void _glBindTexture(GLenum target, GLuint texture) {
|
||||||
@ -354,8 +362,34 @@ static void _glBindTexture(GLenum target, GLuint texture) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _glBlitFramebuffer(GLint srcX0,
|
||||||
|
GLint srcY0,
|
||||||
|
GLint srcX1,
|
||||||
|
GLint srcY1,
|
||||||
|
GLint dstX0,
|
||||||
|
GLint dstY0,
|
||||||
|
GLint dstX1,
|
||||||
|
GLint dstY1,
|
||||||
|
GLbitfield mask,
|
||||||
|
GLenum filter) {
|
||||||
|
mock->glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1,
|
||||||
|
dstY1, mask, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint _glCreateProgram() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _glCompileShader(GLuint shader) {}
|
||||||
|
|
||||||
|
GLuint _glCreateShader(GLenum shaderType) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void _glDeleteFramebuffers(GLsizei n, const GLuint* framebuffers) {}
|
void _glDeleteFramebuffers(GLsizei n, const GLuint* framebuffers) {}
|
||||||
|
|
||||||
|
void _glDeleteShader(GLuint shader) {}
|
||||||
|
|
||||||
void _glDeleteTextures(GLsizei n, const GLuint* textures) {}
|
void _glDeleteTextures(GLsizei n, const GLuint* textures) {}
|
||||||
|
|
||||||
static void _glFramebufferTexture2D(GLenum target,
|
static void _glFramebufferTexture2D(GLenum target,
|
||||||
@ -382,6 +416,28 @@ static void _glGetIntegerv(GLenum pname, GLint* data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _glGetProgramiv(GLuint program, GLenum pname, GLint* params) {
|
||||||
|
if (pname == GL_LINK_STATUS) {
|
||||||
|
*params = GL_TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _glGetProgramInfoLog(GLuint program,
|
||||||
|
GLsizei maxLength,
|
||||||
|
GLsizei* length,
|
||||||
|
GLchar* infoLog) {}
|
||||||
|
|
||||||
|
static void _glGetShaderiv(GLuint shader, GLenum pname, GLint* params) {
|
||||||
|
if (pname == GL_COMPILE_STATUS) {
|
||||||
|
*params = GL_TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _glGetShaderInfoLog(GLuint shader,
|
||||||
|
GLsizei maxLength,
|
||||||
|
GLsizei* length,
|
||||||
|
GLchar* infoLog) {}
|
||||||
|
|
||||||
static void _glTexParameterf(GLenum target, GLenum pname, GLfloat param) {}
|
static void _glTexParameterf(GLenum target, GLenum pname, GLfloat param) {}
|
||||||
|
|
||||||
static void _glTexParameteri(GLenum target, GLenum pname, GLint param) {}
|
static void _glTexParameteri(GLenum target, GLenum pname, GLint param) {}
|
||||||
@ -400,16 +456,23 @@ static GLenum _glGetError() {
|
|||||||
return GL_NO_ERROR;
|
return GL_NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _glLinkProgram(GLuint program) {}
|
||||||
|
|
||||||
|
void _glShaderSource(GLuint shader,
|
||||||
|
GLsizei count,
|
||||||
|
const GLchar* const* string,
|
||||||
|
const GLint* length) {}
|
||||||
|
|
||||||
bool epoxy_has_gl_extension(const char* extension) {
|
bool epoxy_has_gl_extension(const char* extension) {
|
||||||
return false;
|
return mock->epoxy_has_gl_extension(extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool epoxy_is_desktop_gl(void) {
|
bool epoxy_is_desktop_gl(void) {
|
||||||
return false;
|
return mock->epoxy_is_desktop_gl();
|
||||||
}
|
}
|
||||||
|
|
||||||
int epoxy_gl_version(void) {
|
int epoxy_gl_version(void) {
|
||||||
return 0;
|
return mock->epoxy_gl_version();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
@ -463,9 +526,24 @@ EGLBoolean (*epoxy_eglMakeCurrent)(EGLDisplay dpy,
|
|||||||
EGLContext ctx);
|
EGLContext ctx);
|
||||||
EGLBoolean (*epoxy_eglSwapBuffers)(EGLDisplay dpy, EGLSurface surface);
|
EGLBoolean (*epoxy_eglSwapBuffers)(EGLDisplay dpy, EGLSurface surface);
|
||||||
|
|
||||||
|
void (*epoxy_glAttachShader)(GLuint program, GLuint shader);
|
||||||
void (*epoxy_glBindFramebuffer)(GLenum target, GLuint framebuffer);
|
void (*epoxy_glBindFramebuffer)(GLenum target, GLuint framebuffer);
|
||||||
void (*epoxy_glBindTexture)(GLenum target, GLuint texture);
|
void (*epoxy_glBindTexture)(GLenum target, GLuint texture);
|
||||||
|
void (*epoxy_glBlitFramebuffer)(GLint srcX0,
|
||||||
|
GLint srcY0,
|
||||||
|
GLint srcX1,
|
||||||
|
GLint srcY1,
|
||||||
|
GLint dstX0,
|
||||||
|
GLint dstY0,
|
||||||
|
GLint dstX1,
|
||||||
|
GLint dstY1,
|
||||||
|
GLbitfield mask,
|
||||||
|
GLenum filter);
|
||||||
|
void (*epoxy_glCompileShader)(GLuint shader);
|
||||||
|
GLuint (*epoxy_glCreateProgram)();
|
||||||
|
GLuint (*epoxy_glCreateShader)(GLenum shaderType);
|
||||||
void (*epoxy_glDeleteFramebuffers)(GLsizei n, const GLuint* framebuffers);
|
void (*epoxy_glDeleteFramebuffers)(GLsizei n, const GLuint* framebuffers);
|
||||||
|
void (*expoxy_glDeleteShader)(GLuint shader);
|
||||||
void (*epoxy_glDeleteTextures)(GLsizei n, const GLuint* textures);
|
void (*epoxy_glDeleteTextures)(GLsizei n, const GLuint* textures);
|
||||||
void (*epoxy_glFramebufferTexture2D)(GLenum target,
|
void (*epoxy_glFramebufferTexture2D)(GLenum target,
|
||||||
GLenum attachment,
|
GLenum attachment,
|
||||||
@ -474,6 +552,11 @@ void (*epoxy_glFramebufferTexture2D)(GLenum target,
|
|||||||
GLint level);
|
GLint level);
|
||||||
void (*epoxy_glGenFramebuffers)(GLsizei n, GLuint* framebuffers);
|
void (*epoxy_glGenFramebuffers)(GLsizei n, GLuint* framebuffers);
|
||||||
void (*epoxy_glGenTextures)(GLsizei n, GLuint* textures);
|
void (*epoxy_glGenTextures)(GLsizei n, GLuint* textures);
|
||||||
|
void (*epoxy_glLinkProgram)(GLuint program);
|
||||||
|
void (*epoxy_glShaderSource)(GLuint shader,
|
||||||
|
GLsizei count,
|
||||||
|
const GLchar* const* string,
|
||||||
|
const GLint* length);
|
||||||
void (*epoxy_glTexParameterf)(GLenum target, GLenum pname, GLfloat param);
|
void (*epoxy_glTexParameterf)(GLenum target, GLenum pname, GLfloat param);
|
||||||
void (*epoxy_glTexParameteri)(GLenum target, GLenum pname, GLint param);
|
void (*epoxy_glTexParameteri)(GLenum target, GLenum pname, GLint param);
|
||||||
void (*epoxy_glTexImage2D)(GLenum target,
|
void (*epoxy_glTexImage2D)(GLenum target,
|
||||||
@ -502,14 +585,26 @@ static void library_init() {
|
|||||||
epoxy_eglQueryContext = _eglQueryContext;
|
epoxy_eglQueryContext = _eglQueryContext;
|
||||||
epoxy_eglSwapBuffers = _eglSwapBuffers;
|
epoxy_eglSwapBuffers = _eglSwapBuffers;
|
||||||
|
|
||||||
|
epoxy_glAttachShader = _glAttachShader;
|
||||||
epoxy_glBindFramebuffer = _glBindFramebuffer;
|
epoxy_glBindFramebuffer = _glBindFramebuffer;
|
||||||
epoxy_glBindTexture = _glBindTexture;
|
epoxy_glBindTexture = _glBindTexture;
|
||||||
|
epoxy_glBlitFramebuffer = _glBlitFramebuffer;
|
||||||
|
epoxy_glCompileShader = _glCompileShader;
|
||||||
|
epoxy_glCreateProgram = _glCreateProgram;
|
||||||
|
epoxy_glCreateShader = _glCreateShader;
|
||||||
epoxy_glDeleteFramebuffers = _glDeleteFramebuffers;
|
epoxy_glDeleteFramebuffers = _glDeleteFramebuffers;
|
||||||
|
epoxy_glDeleteShader = _glDeleteShader;
|
||||||
epoxy_glDeleteTextures = _glDeleteTextures;
|
epoxy_glDeleteTextures = _glDeleteTextures;
|
||||||
epoxy_glFramebufferTexture2D = _glFramebufferTexture2D;
|
epoxy_glFramebufferTexture2D = _glFramebufferTexture2D;
|
||||||
epoxy_glGenFramebuffers = _glGenFramebuffers;
|
epoxy_glGenFramebuffers = _glGenFramebuffers;
|
||||||
epoxy_glGenTextures = _glGenTextures;
|
epoxy_glGenTextures = _glGenTextures;
|
||||||
epoxy_glGetIntegerv = _glGetIntegerv;
|
epoxy_glGetIntegerv = _glGetIntegerv;
|
||||||
|
epoxy_glGetProgramiv = _glGetProgramiv;
|
||||||
|
epoxy_glGetProgramInfoLog = _glGetProgramInfoLog;
|
||||||
|
epoxy_glGetShaderiv = _glGetShaderiv;
|
||||||
|
epoxy_glGetShaderInfoLog = _glGetShaderInfoLog;
|
||||||
|
epoxy_glLinkProgram = _glLinkProgram;
|
||||||
|
epoxy_glShaderSource = _glShaderSource;
|
||||||
epoxy_glTexParameterf = _glTexParameterf;
|
epoxy_glTexParameterf = _glTexParameterf;
|
||||||
epoxy_glTexParameteri = _glTexParameteri;
|
epoxy_glTexParameteri = _glTexParameteri;
|
||||||
epoxy_glTexImage2D = _glTexImage2D;
|
epoxy_glTexImage2D = _glTexImage2D;
|
||||||
|
40
engine/src/flutter/shell/platform/linux/testing/mock_epoxy.h
Normal file
40
engine/src/flutter/shell/platform/linux/testing/mock_epoxy.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_EPOXY_H_
|
||||||
|
#define FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_EPOXY_H_
|
||||||
|
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
|
||||||
|
#include <epoxy/egl.h>
|
||||||
|
#include <epoxy/gl.h>
|
||||||
|
|
||||||
|
namespace flutter {
|
||||||
|
namespace testing {
|
||||||
|
|
||||||
|
class MockEpoxy {
|
||||||
|
public:
|
||||||
|
MockEpoxy();
|
||||||
|
|
||||||
|
MOCK_METHOD(bool, epoxy_has_gl_extension, (const char* extension));
|
||||||
|
MOCK_METHOD(bool, epoxy_is_desktop_gl, ());
|
||||||
|
MOCK_METHOD(int, epoxy_gl_version, ());
|
||||||
|
MOCK_METHOD(void,
|
||||||
|
glBlitFramebuffer,
|
||||||
|
(GLint srcX0,
|
||||||
|
GLint srcY0,
|
||||||
|
GLint srcX1,
|
||||||
|
GLint srcY1,
|
||||||
|
GLint dstX0,
|
||||||
|
GLint dstY0,
|
||||||
|
GLint dstX1,
|
||||||
|
GLint dstY1,
|
||||||
|
GLbitfield mask,
|
||||||
|
GLenum filter));
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace testing
|
||||||
|
} // namespace flutter
|
||||||
|
|
||||||
|
#endif // FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_EPOXY_H_
|
Loading…
x
Reference in New Issue
Block a user