/** * @file lv_opengles_driver.c * */ /********************* * INCLUDES *********************/ #include "../../display/lv_display.h" #include "../../misc/lv_area_private.h" #if LV_USE_OPENGLES #include "lv_opengles_debug.h" #include "lv_opengles_driver.h" /********************* * DEFINES *********************/ #define LV_OPENGLES_VERTEX_BUFFER_LEN 16 /********************** * TYPEDEFS **********************/ /********************** * STATIC PROTOTYPES **********************/ static void lv_opengles_render_internal(unsigned int texture, const lv_area_t * texture_area, lv_opa_t opa, int32_t disp_w, int32_t disp_h, const lv_area_t * texture_clip_area, bool flip, lv_color_t fill_color); static void lv_opengles_enable_blending(void); static void lv_opengles_vertex_buffer_init(const void * data, unsigned int size); static void lv_opengles_vertex_buffer_deinit(void); static void lv_opengles_vertex_buffer_bind(void); static void lv_opengles_vertex_buffer_unbind(void); static void lv_opengles_vertex_array_init(void); static void lv_opengles_vertex_array_deinit(void); static void lv_opengles_vertex_array_bind(void); static void lv_opengles_vertex_array_unbind(void); static void lv_opengles_vertex_array_add_buffer(void); static void lv_opengles_index_buffer_init(const unsigned int * data, unsigned int count); static void lv_opengles_index_buffer_deinit(void); static unsigned int lv_opengles_index_buffer_get_count(void); static void lv_opengles_index_buffer_bind(void); static void lv_opengles_index_buffer_unbind(void); static unsigned int lv_opengles_shader_compile(unsigned int type, const char * source); static unsigned int lv_opengles_shader_create(const char * vertexShader, const char * fragmentShader); static void lv_opengles_shader_init(void); static void lv_opengles_shader_deinit(void); static void lv_opengles_shader_bind(void); static void lv_opengles_shader_unbind(void); static int lv_opengles_shader_get_uniform_location(const char * name); static void lv_opengles_shader_set_uniform1i(const char * name, int value); static void lv_opengles_shader_set_uniformmatrix3fv(const char * name, int count, bool transpose, const float * values); static void lv_opengles_shader_set_uniform1f(const char * name, float value); static void lv_opengles_shader_set_uniform3f(const char * name, float value_0, float value_1, float value_2); static void lv_opengles_render_draw(void); static float lv_opengles_map_float(float x, float min_in, float max_in, float min_out, float max_out); /*********************** * GLOBAL PROTOTYPES ***********************/ /********************** * STATIC VARIABLES **********************/ static bool is_init; static unsigned int vertex_buffer_id = 0; static unsigned int vertex_array_id = 0; static unsigned int index_buffer_id = 0; static unsigned int index_buffer_count = 0; static unsigned int shader_id; static const char * shader_names[] = { "u_Texture", "u_ColorDepth", "u_VertexTransform", "u_Opa", "u_IsFill", "u_FillColor" }; static int shader_location[] = { 0, 0, 0, 0, 0, 0 }; static const char * vertex_shader = "#version 300 es\n" "\n" "in vec4 position;\n" "in vec2 texCoord;\n" "\n" "out vec2 v_TexCoord;\n" "\n" "uniform mat3 u_VertexTransform;\n" "\n" "void main()\n" "{\n" " gl_Position = vec4((u_VertexTransform * vec3(position.xy, 1)).xy, position.zw);\n" " v_TexCoord = texCoord;\n" "};\n"; static const char * fragment_shader = "#version 300 es\n" "\n" "precision lowp float;\n" "\n" "layout(location = 0) out vec4 color;\n" "\n" "in vec2 v_TexCoord;\n" "\n" "uniform sampler2D u_Texture;\n" "uniform int u_ColorDepth;\n" "uniform float u_Opa;\n" "uniform bool u_IsFill;\n" "uniform vec3 u_FillColor;\n" "\n" "void main()\n" "{\n" " vec4 texColor;\n" " if (u_IsFill) {\n" " texColor = vec4(u_FillColor, 1.0f);\n" " } else {\n" " texColor = texture(u_Texture, v_TexCoord);\n" " }\n" " if (u_ColorDepth == 8) {\n" " float gray = texColor.r;\n" " color = vec4(gray, gray, gray, u_Opa);\n" " } else {\n" " float combinedAlpha = texColor.a * u_Opa;\n" " color = vec4(texColor.rgb * combinedAlpha, combinedAlpha);\n" " }\n" "};\n"; /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ void lv_opengles_init(void) { if(is_init) return; lv_opengles_enable_blending(); unsigned int indices[] = { 0, 1, 2, 2, 3, 0 }; lv_opengles_vertex_buffer_init(NULL, sizeof(float) * LV_OPENGLES_VERTEX_BUFFER_LEN); lv_opengles_vertex_array_init(); lv_opengles_vertex_array_add_buffer(); lv_opengles_index_buffer_init(indices, 6); lv_opengles_shader_init(); lv_opengles_shader_bind(); /* unbind everything */ lv_opengles_vertex_array_unbind(); lv_opengles_vertex_buffer_unbind(); lv_opengles_index_buffer_unbind(); lv_opengles_shader_unbind(); is_init = true; } void lv_opengles_deinit(void) { if(!is_init) return; lv_opengles_shader_deinit(); lv_opengles_index_buffer_deinit(); lv_opengles_vertex_buffer_deinit(); lv_opengles_vertex_array_deinit(); is_init = false; } void lv_opengles_render_texture(unsigned int texture, const lv_area_t * texture_area, lv_opa_t opa, int32_t disp_w, int32_t disp_h, const lv_area_t * texture_clip_area, bool flip) { lv_opengles_render_internal(texture, texture_area, opa, disp_w, disp_h, texture_clip_area, flip, lv_color_black()); } void lv_opengles_render_fill(lv_color_t color, const lv_area_t * area, lv_opa_t opa, int32_t disp_w, int32_t disp_h) { lv_opengles_render_internal(0, area, opa, disp_w, disp_h, area, false, color); } void lv_opengles_render_clear(void) { GL_CALL(glClear(GL_COLOR_BUFFER_BIT)); } void lv_opengles_viewport(int32_t x, int32_t y, int32_t w, int32_t h) { glViewport(x, y, w, h); } /********************** * STATIC FUNCTIONS **********************/ static void lv_opengles_render_internal(unsigned int texture, const lv_area_t * texture_area, lv_opa_t opa, int32_t disp_w, int32_t disp_h, const lv_area_t * texture_clip_area, bool flip, lv_color_t fill_color) { lv_area_t intersection; if(!lv_area_intersect(&intersection, texture_area, texture_clip_area)) return; GL_CALL(glActiveTexture(GL_TEXTURE0)); GL_CALL(glBindTexture(GL_TEXTURE_2D, texture)); float tex_w = (float)lv_area_get_width(&intersection); float tex_h = (float)lv_area_get_height(&intersection); float hor_scale = tex_w / (float)disp_w; float ver_scale = tex_h / (float)disp_h; float hor_translate = (float)intersection.x1 / (float)disp_w * 2.0f - (1.0f - hor_scale); float ver_translate = -((float)intersection.y1 / (float)disp_h * 2.0f - (1.0f - ver_scale)); if(flip) ver_scale = -ver_scale; float matrix[9] = { hor_scale, 0.0f, hor_translate, 0.0f, ver_scale, ver_translate, 0.0f, 0.0f, 1.0f }; if(texture != 0) { float x_coef = 1.0f / (float)(2 * lv_area_get_width(texture_area)); float y_coef = 1.0f / (float)(2 * lv_area_get_height(texture_area)); float tex_clip_x1 = lv_opengles_map_float(texture_clip_area->x1, texture_area->x1, texture_area->x2, x_coef, 1.0f - x_coef); float tex_clip_x2 = lv_opengles_map_float(texture_clip_area->x2, texture_area->x1, texture_area->x2, x_coef, 1.0f - x_coef); float tex_clip_y1 = lv_opengles_map_float(texture_clip_area->y1, texture_area->y1, texture_area->y2, y_coef, 1.0f - y_coef); float tex_clip_y2 = lv_opengles_map_float(texture_clip_area->y2, texture_area->y1, texture_area->y2, y_coef, 1.0f - y_coef); float positions[LV_OPENGLES_VERTEX_BUFFER_LEN] = { -1.0f, 1.0f, tex_clip_x1, tex_clip_y2, 1.0f, 1.0f, tex_clip_x2, tex_clip_y2, 1.0f, -1.0f, tex_clip_x2, tex_clip_y1, -1.0f, -1.0f, tex_clip_x1, tex_clip_y1 }; lv_opengles_vertex_buffer_init(positions, sizeof(positions)); } lv_opengles_shader_bind(); lv_opengles_shader_set_uniform1i("u_ColorDepth", LV_COLOR_DEPTH); lv_opengles_shader_set_uniform1i("u_Texture", 0); lv_opengles_shader_set_uniformmatrix3fv("u_VertexTransform", 1, true, matrix); lv_opengles_shader_set_uniform1f("u_Opa", (float)opa / (float)LV_OPA_100); lv_opengles_shader_set_uniform1i("u_IsFill", texture == 0); lv_opengles_shader_set_uniform3f("u_FillColor", (float)fill_color.red / 255.0f, (float)fill_color.green / 255.0f, (float)fill_color.blue / 255.0f); lv_opengles_render_draw(); } static void lv_opengles_enable_blending(void) { glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } static void lv_opengles_vertex_buffer_init(const void * data, unsigned int size) { if(vertex_buffer_id == 0) GL_CALL(glGenBuffers(1, &vertex_buffer_id)); GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_id)); GL_CALL(glBufferData(GL_ARRAY_BUFFER, size, data, GL_DYNAMIC_DRAW)); } static void lv_opengles_vertex_buffer_deinit(void) { if(vertex_buffer_id == 0) return; GL_CALL(glDeleteBuffers(1, &vertex_buffer_id)); vertex_buffer_id = 0; } static void lv_opengles_vertex_buffer_bind(void) { GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_id)); } static void lv_opengles_vertex_buffer_unbind(void) { GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0)); } static void lv_opengles_vertex_array_init(void) { if(vertex_array_id == 0) GL_CALL(glGenVertexArrays(1, &vertex_array_id)); } static void lv_opengles_vertex_array_deinit(void) { if(vertex_array_id == 0) return; GL_CALL(glDeleteVertexArrays(1, &vertex_array_id)); vertex_array_id = 0; } static void lv_opengles_vertex_array_bind(void) { GL_CALL(glBindVertexArray(vertex_array_id)); } static void lv_opengles_vertex_array_unbind(void) { GL_CALL(glBindVertexArray(0)); } static void lv_opengles_vertex_array_add_buffer(void) { lv_opengles_vertex_buffer_bind(); intptr_t offset = 0; for(unsigned int i = 0; i < 2; i++) { lv_opengles_vertex_array_bind(); GL_CALL(glEnableVertexAttribArray(i)); GL_CALL(glVertexAttribPointer(i, 2, GL_FLOAT, GL_FALSE, 16, (const void *)offset)); offset += 2 * 4; } } static void lv_opengles_index_buffer_init(const unsigned int * data, unsigned int count) { index_buffer_count = count; if(index_buffer_id == 0) GL_CALL(glGenBuffers(1, &index_buffer_id)); GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_id)); GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * sizeof(GLuint), data, GL_STATIC_DRAW)); } static void lv_opengles_index_buffer_deinit(void) { if(index_buffer_id == 0) return; GL_CALL(glDeleteBuffers(1, &index_buffer_id)); index_buffer_id = 0; } static unsigned int lv_opengles_index_buffer_get_count(void) { return index_buffer_count; } static void lv_opengles_index_buffer_bind(void) { GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_id)); } static void lv_opengles_index_buffer_unbind(void) { GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); } static unsigned int lv_opengles_shader_compile(unsigned int type, const char * source) { unsigned int id; GL_CALL(id = glCreateShader(type)); const char * src = source; GL_CALL(glShaderSource(id, 1, &src, NULL)); GL_CALL(glCompileShader(id)); int result; GL_CALL(glGetShaderiv(id, GL_COMPILE_STATUS, &result)); if(result == GL_FALSE) { int length; GL_CALL(glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length)); char * message = lv_malloc_zeroed(length * sizeof(char)); GL_CALL(glGetShaderInfoLog(id, length, &length, message)); LV_LOG_ERROR("Failed to compile %s shader!", type == GL_VERTEX_SHADER ? "vertex" : "fragment"); LV_LOG_ERROR("%s", message); GL_CALL(glDeleteShader(id)); return 0; } return id; } static unsigned int lv_opengles_shader_create(const char * vertexShader, const char * fragmentShader) { unsigned int program; GL_CALL(program = glCreateProgram()); unsigned int vs = lv_opengles_shader_compile(GL_VERTEX_SHADER, vertexShader); unsigned int fs = lv_opengles_shader_compile(GL_FRAGMENT_SHADER, fragmentShader); GL_CALL(glAttachShader(program, vs)); GL_CALL(glAttachShader(program, fs)); GL_CALL(glLinkProgram(program)); GL_CALL(glValidateProgram(program)); GL_CALL(glDeleteShader(vs)); GL_CALL(glDeleteShader(fs)); return program; } static void lv_opengles_shader_init(void) { if(shader_id == 0) shader_id = lv_opengles_shader_create(vertex_shader, fragment_shader); } static void lv_opengles_shader_deinit(void) { if(shader_id == 0) return; GL_CALL(glDeleteProgram(shader_id)); shader_id = 0; } static void lv_opengles_shader_bind(void) { GL_CALL(glUseProgram(shader_id)); } static void lv_opengles_shader_unbind(void) { GL_CALL(glUseProgram(0)); } static int lv_opengles_shader_get_uniform_location(const char * name) { int id = -1; for(size_t i = 0; i < sizeof(shader_location) / sizeof(int); i++) { if(lv_strcmp(shader_names[i], name) == 0) { id = i; } } if(id == -1) { return -1; } if(shader_location[id] != 0) { return shader_location[id]; } int location; GL_CALL(location = glGetUniformLocation(shader_id, name)); if(location == -1) LV_LOG_WARN("Warning: uniform '%s' doesn't exist!", name); shader_location[id] = location; return location; } static void lv_opengles_shader_set_uniform1i(const char * name, int value) { GL_CALL(glUniform1i(lv_opengles_shader_get_uniform_location(name), value)); } static void lv_opengles_shader_set_uniformmatrix3fv(const char * name, int count, bool transpose, const float * values) { GL_CALL(glUniformMatrix3fv(lv_opengles_shader_get_uniform_location(name), count, transpose, values)); } static void lv_opengles_shader_set_uniform1f(const char * name, float value) { GL_CALL(glUniform1f(lv_opengles_shader_get_uniform_location(name), value)); } static void lv_opengles_shader_set_uniform3f(const char * name, float value_0, float value_1, float value_2) { GL_CALL(glUniform3f(lv_opengles_shader_get_uniform_location(name), value_0, value_1, value_2)); } static void lv_opengles_render_draw(void) { lv_opengles_shader_bind(); lv_opengles_vertex_array_bind(); lv_opengles_index_buffer_bind(); unsigned int count = lv_opengles_index_buffer_get_count(); GL_CALL(glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, NULL)); } /** * Copied from `lv_map` in lv_math.h to operate on floats */ static float lv_opengles_map_float(float x, float min_in, float max_in, float min_out, float max_out) { if(max_in >= min_in && x >= max_in) return max_out; if(max_in >= min_in && x <= min_in) return min_out; if(max_in <= min_in && x <= max_in) return max_out; if(max_in <= min_in && x >= min_in) return min_out; /** * The equation should be: * ((x - min_in) * delta_out) / delta in) + min_out * To avoid rounding error reorder the operations: * (x - min_in) * (delta_out / delta_min) + min_out */ float delta_in = max_in - min_in; float delta_out = max_out - min_out; return ((x - min_in) * delta_out) / delta_in + min_out; } #endif /* LV_USE_OPENGLES */