From 7ef26886f57b2e8ec2c4c4eac1c29535a4a423a4 Mon Sep 17 00:00:00 2001 From: Harish Krupo Date: Thu, 18 Apr 2019 21:45:48 +0530 Subject: [PATCH] gl-renderer: implement intermediate framebuffer (shadow) Proper color management will need blending done with linear light pixel values, that is, EOTF applied before blending, and then inverse-EOTF applied for scanout after blending. The simplest way to set that up is to use an intemediate framebuffer a.k.a shadow buffer containing the composited image in linear light values, then blit from that to the actual framebuffer. This patch implements the shadow buffer, but the linear light blending is left for another patch. This allows GL-renderer to turn WESTON_CAP_COLOR_OPS on. Half-float is chosen as the buffer format because linear light values require more bits to encode with sufficient precision than the usual non-linear pixel values. v2: Use /* */ instead of // (Pekka) Rename fbo and tex to shadow_{fbo,tex} (Pekka) Check for OpenGLES capabilities before creating shadow_{tex,fbo} (Pekka) Signed-off-by: Harish Krupo v3: Rebased. Simplified GL version checks (Sebastian) Apply changes from "libweston: add color ops cap and bool renderer shadow buffer" Renamed supports_half_float_texture to has_gl_half_float to follow the existing naming pattern. Introduce gl_renderer_create_shadow_16f(). Undo moving of glViewport() call. Replace half_float_texture_enabled with shadow_exists(). Introduce struct gl_output_state_shadow. Assert no resizing with shadow. Fix triangle fan debug. Rename repaint_from_texture() to blit_shadow_to_output(). Rewrite commit message because linear light blending is not implemented in this patch. Fix blit_shadow_to_output() for scaled/transformed outputs and remove redundant code. Fix has_gl_half_float determination. v4: Disable blending in blit_shadow. (Daniel) Port to gl_renderer_get_program(). Make a generic fbo-texture struct with parameterized format. (Daniel) Change has_gl_half_float into gl_half_float_type. Signed-off-by: Pekka Paalanen --- libweston/renderer-gl/gl-renderer-internal.h | 2 + libweston/renderer-gl/gl-renderer.c | 227 ++++++++++++++++++- tests/yuv-buffer-test.c | 1 + 3 files changed, 223 insertions(+), 7 deletions(-) diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h index 1795a2f2..a03fbe8a 100644 --- a/libweston/renderer-gl/gl-renderer-internal.h +++ b/libweston/renderer-gl/gl-renderer-internal.h @@ -154,6 +154,8 @@ struct gl_renderer { bool has_wait_sync; PFNEGLWAITSYNCKHRPROC wait_sync; + GLint gl_half_float_type; + /** struct gl_shader::link * * List constains cached shaders built from struct gl_shader_requirements diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index e56dadda..61a354bd 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -77,6 +78,13 @@ struct gl_border_image { void *data; }; +struct gl_fbo_texture { + GLuint fbo; + GLuint tex; + int32_t width; + int32_t height; +}; + struct gl_output_state { EGLSurface egl_surface; pixman_region32_t buffer_damage[BUFFER_DAMAGE_COUNT]; @@ -92,6 +100,8 @@ struct gl_output_state { /* struct timeline_render_point::link */ struct wl_list timeline_render_point_list; + + struct gl_fbo_texture shadow; }; enum buffer_type { @@ -259,6 +269,12 @@ get_surface_state(struct weston_surface *surface) return (struct gl_surface_state *)surface->renderer_state; } +static bool +shadow_exists(const struct gl_output_state *go) +{ + return go->shadow.fbo != 0; +} + static void timeline_render_point_destroy(struct timeline_render_point *trp) { @@ -625,6 +641,69 @@ texture_region(struct weston_view *ev, pixman_region32_t *region, return nvtx; } +/** Create a texture and a framebuffer object + * + * \param fbotex To be initialized. + * \param width Texture width in pixels. + * \param height Texture heigh in pixels. + * \param internal_format See glTexImage2D. + * \param format See glTexImage2D. + * \param type See glTexImage2D. + * \return True on success, false otherwise. + */ +static bool +gl_fbo_texture_init(struct gl_fbo_texture *fbotex, + int32_t width, + int32_t height, + GLint internal_format, + GLenum format, + GLenum type) +{ + int fb_status; + GLuint shadow_fbo; + GLuint shadow_tex; + + glActiveTexture(GL_TEXTURE0); + glGenTextures(1, &shadow_tex); + glBindTexture(GL_TEXTURE_2D, shadow_tex); + glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, + format, type, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); + + glGenFramebuffers(1, &shadow_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, shadow_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, shadow_tex, 0); + + fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + if (fb_status != GL_FRAMEBUFFER_COMPLETE) { + glDeleteFramebuffers(1, &shadow_fbo); + glDeleteTextures(1, &shadow_tex); + return false; + } + + fbotex->fbo = shadow_fbo; + fbotex->tex = shadow_tex; + fbotex->width = width; + fbotex->height = height; + + return true; +} + +static void +gl_fbo_texture_fini(struct gl_fbo_texture *fbotex) +{ + glDeleteFramebuffers(1, &fbotex->fbo); + fbotex->fbo = 0; + glDeleteTextures(1, &fbotex->tex); + fbotex->tex = 0; +} + static struct gl_shader * gl_renderer_get_program(struct gl_renderer *gr, const struct gl_shader_requirements *requirements) @@ -1484,6 +1563,77 @@ pixman_region_to_egl_y_invert(struct weston_output *output, pixman_region32_fini(&transformed); } +static void +blit_shadow_to_output(struct weston_output *output, + pixman_region32_t *output_damage) +{ + const struct gl_shader_requirements blit_requirements = { + .variant = SHADER_VARIANT_RGBA, + }; + struct gl_output_state *go = get_output_state(output); + struct gl_renderer *gr = get_renderer(output->compositor); + struct gl_shader *shader; + double width = output->current_mode->width; + double height = output->current_mode->height; + pixman_box32_t *rects; + int n_rects; + int i; + pixman_region32_t translated_damage; + GLfloat verts[4 * 2]; + static const GLfloat proj[16] = { /* transpose */ + 2.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 0.0f, 1.0f + }; + + pixman_region32_init(&translated_damage); + + shader = gl_renderer_get_program(gr, &blit_requirements); + if (!gl_renderer_use_program(gr, &shader)) + return; + + glUniformMatrix4fv(shader->proj_uniform, 1, GL_FALSE, proj); + glUniform1f(shader->alpha_uniform, 1.0f); + glUniform1i(shader->tex_uniforms[0], 0); + + glDisable(GL_BLEND); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, go->shadow.tex); + + /* output_damage is in global coordinates */ + pixman_region32_intersect(&translated_damage, output_damage, + &output->region); + /* Convert to output pixel coordinates in-place */ + weston_output_region_from_global(output, &translated_damage); + + rects = pixman_region32_rectangles(&translated_damage, &n_rects); + for (i = 0; i < n_rects; i++) { + + verts[0] = rects[i].x1 / width; + verts[1] = (height - rects[i].y1) / height; + verts[2] = rects[i].x2 / width; + verts[3] = (height - rects[i].y1) / height; + + verts[4] = rects[i].x2 / width; + verts[5] = (height - rects[i].y2) / height; + verts[6] = rects[i].x1 / width; + verts[7] = (height - rects[i].y2) / height; + + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts); + glEnableVertexAttribArray(0); + + /* texcoord: */ + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, verts); + glEnableVertexAttribArray(1); + + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } + + glBindTexture(GL_TEXTURE_2D, 0); + pixman_region32_fini(&translated_damage); +} + /* NOTE: We now allow falling back to ARGB gl visuals when XRGB is * unavailable, so we're assuming the background has no transparency * and that everything with a blend, like drop shadows, will have something @@ -1528,12 +1678,6 @@ gl_renderer_repaint_output(struct weston_output *output, go->begin_render_sync = create_render_sync(gr); - /* Calculate the viewport */ - glViewport(go->borders[GL_RENDERER_BORDER_LEFT].width, - go->borders[GL_RENDERER_BORDER_BOTTOM].height, - output->current_mode->width, - output->current_mode->height); - /* Calculate the global GL matrix */ go->output_matrix = output->matrix; weston_matrix_translate(&go->output_matrix, @@ -1543,6 +1687,22 @@ gl_renderer_repaint_output(struct weston_output *output, 2.0 / output->current_mode->width, -2.0 / output->current_mode->height, 1); + /* If using shadow, redirect all drawing to it first. */ + if (shadow_exists(go)) { + /* XXX: Shadow code does not support resizing. */ + assert(output->current_mode->width == go->shadow.width); + assert(output->current_mode->height == go->shadow.height); + + glBindFramebuffer(GL_FRAMEBUFFER, go->shadow.fbo); + glViewport(0, 0, go->shadow.width, go->shadow.height); + } else { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(go->borders[GL_RENDERER_BORDER_LEFT].width, + go->borders[GL_RENDERER_BORDER_BOTTOM].height, + output->current_mode->width, + output->current_mode->height); + } + /* In fan debug mode, redraw everything to make sure that we clear any * fans left over from previous draws on this buffer. * This precludes the use of EGL_EXT_swap_buffers_with_damage and @@ -1588,7 +1748,19 @@ gl_renderer_repaint_output(struct weston_output *output, free(egl_rects); } - repaint_views(output, &total_damage); + if (shadow_exists(go)) { + /* Repaint into shadow. */ + repaint_views(output, output_damage); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(go->borders[GL_RENDERER_BORDER_LEFT].width, + go->borders[GL_RENDERER_BORDER_BOTTOM].height, + output->current_mode->width, + output->current_mode->height); + blit_shadow_to_output(output, &total_damage); + } else { + repaint_views(output, &total_damage); + } pixman_region32_fini(&total_damage); pixman_region32_fini(&previous_damage); @@ -3143,6 +3315,9 @@ gl_renderer_output_create(struct weston_output *output, EGLSurface surface) { struct gl_output_state *go; + struct gl_renderer *gr = get_renderer(output->compositor); + GLint internal_format; + bool ret; int i; go = zalloc(sizeof *go); @@ -3159,6 +3334,31 @@ gl_renderer_output_create(struct weston_output *output, go->begin_render_sync = EGL_NO_SYNC_KHR; go->end_render_sync = EGL_NO_SYNC_KHR; + if (output->use_renderer_shadow_buffer) { + assert(gr->gl_half_float_type); + + if (gr->gl_half_float_type == GL_HALF_FLOAT_OES) + internal_format = GL_RGBA; + else + internal_format = GL_RGBA16F; + + ret = gl_fbo_texture_init(&go->shadow, + output->current_mode->width, + output->current_mode->height, + internal_format, + GL_RGBA, + gr->gl_half_float_type); + if (ret) { + weston_log("Output %s uses 16F shadow.\n", + output->name); + } else { + weston_log("Output %s failed to create 16F shadow.\n", + output->name); + free(go); + return -1; + } + } + output->renderer_state = go; return 0; @@ -3255,6 +3455,9 @@ gl_renderer_output_destroy(struct weston_output *output) for (i = 0; i < 2; i++) pixman_region32_fini(&go->buffer_damage[i]); + if (shadow_exists(go)) + gl_fbo_texture_fini(&go->shadow); + eglMakeCurrent(gr->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); @@ -3488,6 +3691,9 @@ gl_renderer_display_create(struct weston_compositor *ec, goto fail_with_error; } + if (gr->gl_half_float_type != 0) + ec->capabilities |= WESTON_CAP_COLOR_OPS; + return 0; fail_with_error: @@ -3652,6 +3858,13 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) if (weston_check_egl_extension(extensions, "GL_OES_EGL_image_external")) gr->has_egl_image_external = true; + if (weston_check_egl_extension(extensions, "GL_EXT_color_buffer_half_float")) { + if (gr->gl_version >= gr_gl_version(3, 0)) + gr->gl_half_float_type = GL_HALF_FLOAT; + else if (weston_check_egl_extension(extensions, "GL_OES_texture_half_float")) + gr->gl_half_float_type = GL_HALF_FLOAT_OES; + } + glActiveTexture(GL_TEXTURE0); gr->fallback_shader = gl_renderer_create_fallback_shader(gr); diff --git a/tests/yuv-buffer-test.c b/tests/yuv-buffer-test.c index fb409427..ce34fbd1 100644 --- a/tests/yuv-buffer-test.c +++ b/tests/yuv-buffer-test.c @@ -48,6 +48,7 @@ fixture_setup(struct weston_test_harness *harness) setup.width = 324; setup.height = 264; setup.shell = SHELL_TEST_DESKTOP; + setup.logging_scopes = "log,gl-shader-generator"; return weston_test_harness_execute_as_client(harness, &setup); }