From 9a6a4e7032669be727c965ca19e3e30098c892e7 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Mon, 22 Mar 2021 16:17:28 +0200 Subject: [PATCH] gl-renderer: implement SHADER_COLOR_CURVE_LUT_3x1D This adds shader support for using a three-channel one-dimensional look-up table for de/encoding input colors. This operation will be useful for applying EOTF or its inverse, in other words, gamma curves. It will also be useful in optimizing a following 3D LUT tap distribution once support for 3D LUT is added. Even though called three-channel and one-dimensional, it is actually implemented as a one-channel two-dimensional texture with four rows. Each row corresponds to a source color channel except the fourth one is unused. The reason for having the fourth row is to get texture coordinates in 1/8 steps instead of 1/6 steps. 1/6 may would not be exact in floating- or fixed-point arithmetic and might perhaps risk unintended results from bilinear texture filtering when we want linear filtering only in x but not in y texture coordinates. I may be paranoid. The LUT is applied on source colors after they have been converted to straight RGB. It cannot be applied with pre-multiplied alpha. A LUT can be used for both applying EOTF to go from source color space to blending color space, and EOTF^-1 to go from blending space to output (electrical) space. However, this type of LUT cannot do color space conversions. For now, this feature is hardcoded to off everywhere, to be enabled in following patches. Signed-off-by: Pekka Paalanen --- libweston/renderer-gl/fragment.glsl | 78 ++++++++++++++++++-- libweston/renderer-gl/gl-renderer-internal.h | 11 ++- libweston/renderer-gl/gl-renderer.c | 4 + libweston/renderer-gl/gl-shaders.c | 44 ++++++++++- 4 files changed, 129 insertions(+), 8 deletions(-) diff --git a/libweston/renderer-gl/fragment.glsl b/libweston/renderer-gl/fragment.glsl index 5d975fea..63a20cd6 100644 --- a/libweston/renderer-gl/fragment.glsl +++ b/libweston/renderer-gl/fragment.glsl @@ -42,6 +42,10 @@ #define SHADER_VARIANT_SOLID 7 #define SHADER_VARIANT_EXTERNAL 8 +/* enum gl_shader_color_curve */ +#define SHADER_COLOR_CURVE_IDENTITY 0 +#define SHADER_COLOR_CURVE_LUT_3x1D 1 + #if DEF_VARIANT == SHADER_VARIANT_EXTERNAL #extension GL_OES_EGL_image_external : require #endif @@ -61,6 +65,7 @@ precision HIGHPRECISION float; compile_const int c_variant = DEF_VARIANT; compile_const bool c_input_is_premult = DEF_INPUT_IS_PREMULT; compile_const bool c_green_tint = DEF_GREEN_TINT; +compile_const int c_color_pre_curve = DEF_COLOR_PRE_CURVE; vec4 yuva2rgba(vec4 yuva) @@ -102,6 +107,8 @@ uniform sampler2D tex1; uniform sampler2D tex2; uniform float alpha; uniform vec4 unicolor; +uniform HIGHPRECISION sampler2D color_pre_curve_lut_2d; +uniform HIGHPRECISION vec2 color_pre_curve_lut_scale_offset; vec4 sample_input_texture() @@ -147,6 +154,62 @@ sample_input_texture() return yuva2rgba(yuva); } +/* + * Texture coordinates go from 0.0 to 1.0 corresponding to texture edges. + * When we do LUT look-ups with linear filtering, the correct range to sample + * from is not from edge to edge, but center of first texel to center of last + * texel. This follows because with LUTs, you have the exact end points given, + * you never extrapolate but only interpolate. + * The scale and offset are precomputed to achieve this mapping. + */ +float +lut_texcoord(float x, vec2 scale_offset) +{ + return x * scale_offset.s + scale_offset.t; +} + +/* + * Sample a 1D LUT which is a single row of a 2D texture. The 2D texture has + * four rows so that the centers of texels have precise y-coordinates. + */ +float +sample_color_pre_curve_lut_2d(float x, compile_const int row) +{ + float tx = lut_texcoord(x, color_pre_curve_lut_scale_offset); + + return texture2D(color_pre_curve_lut_2d, + vec2(tx, (float(row) + 0.5) / 4.0)).x; +} + +vec3 +color_pre_curve(vec3 color) +{ + vec3 ret; + + if (c_color_pre_curve == SHADER_COLOR_CURVE_IDENTITY) { + return color; + } else if (c_color_pre_curve == SHADER_COLOR_CURVE_LUT_3x1D) { + ret.r = sample_color_pre_curve_lut_2d(color.r, 0); + ret.g = sample_color_pre_curve_lut_2d(color.g, 1); + ret.b = sample_color_pre_curve_lut_2d(color.b, 2); + return ret; + } else { + /* Never reached, bad c_color_pre_curve. */ + return vec3(1.0, 0.3, 1.0); + } +} + +vec4 +color_pipeline(vec4 color) +{ + /* View alpha (opacity) */ + color.a *= alpha; + + color.rgb = color_pre_curve(color.rgb); + + return color; +} + void main() { @@ -155,15 +218,18 @@ main() /* Electrical (non-linear) RGBA values, may be premult or not */ color = sample_input_texture(); - /* Ensure premultiplied alpha, apply view alpha (opacity) */ + /* Ensure straight alpha */ if (c_input_is_premult) { - color *= alpha; - } else { - color.a *= alpha; - color.rgb *= color.a; + if (color.a == 0.0) + color.rgb = vec3(0, 0, 0); + else + color.rgb *= 1.0 / color.a; } - /* color is guaranteed premult here */ + color = color_pipeline(color); + + /* pre-multiply for blending */ + color.rgb *= color.a; if (c_green_tint) color = vec4(0.0, 0.3, 0.0, 0.2) + color * 0.8; diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h index 0beb1dc6..326e5bb9 100644 --- a/libweston/renderer-gl/gl-renderer-internal.h +++ b/libweston/renderer-gl/gl-renderer-internal.h @@ -50,6 +50,12 @@ enum gl_shader_texture_variant { SHADER_VARIANT_EXTERNAL, }; +/* Keep the following in sync with fragment.glsl. */ +enum gl_shader_color_curve { + SHADER_COLOR_CURVE_IDENTITY = 0, + SHADER_COLOR_CURVE_LUT_3x1D, +}; + /** GL shader requirements key * * This structure is used as a binary blob key for building and searching @@ -64,12 +70,13 @@ struct gl_shader_requirements unsigned variant:4; /* enum gl_shader_texture_variant */ bool input_is_premult:1; bool green_tint:1; + unsigned color_pre_curve:1; /* enum gl_shader_color_curve */ /* * The total size of all bitfields plus pad_bits_ must fill up exactly * how many bytes the compiler allocates for them together. */ - unsigned pad_bits_:26; + unsigned pad_bits_:25; }; static_assert(sizeof(struct gl_shader_requirements) == 4 /* total bitfield size in bytes */, @@ -86,6 +93,8 @@ struct gl_shader_config { GLfloat unicolor[4]; GLint input_tex_filter; /* GL_NEAREST or GL_LINEAR */ GLuint input_tex[GL_SHADER_INPUT_TEX_MAX]; + GLuint color_pre_curve_lut_tex; + GLfloat color_pre_curve_lut_scale_offset[2]; }; struct gl_renderer { diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index 5f9b8c2d..fb486294 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -739,6 +739,7 @@ triangle_fan_debug(struct gl_renderer *gr, .req = { .variant = SHADER_VARIANT_SOLID, .input_is_premult = true, + .color_pre_curve = SHADER_COLOR_CURVE_IDENTITY, }, .projection = sconf->projection, .view_alpha = 1.0f, @@ -927,6 +928,7 @@ maybe_censor_override(struct gl_shader_config *sconf, .req = { .variant = SHADER_VARIANT_SOLID, .input_is_premult = true, + .color_pre_curve = SHADER_COLOR_CURVE_IDENTITY, }, .projection = sconf->projection, .view_alpha = sconf->view_alpha, @@ -1269,6 +1271,7 @@ draw_output_borders(struct weston_output *output, .req = { .variant = SHADER_VARIANT_RGBA, .input_is_premult = true, + .color_pre_curve = SHADER_COLOR_CURVE_IDENTITY, }, .view_alpha = 1.0f, }; @@ -1494,6 +1497,7 @@ blit_shadow_to_output(struct weston_output *output, .req = { .variant = SHADER_VARIANT_RGBA, .input_is_premult = true, + .color_pre_curve = SHADER_COLOR_CURVE_IDENTITY, }, .projection = { .d = { /* transpose */ diff --git a/libweston/renderer-gl/gl-shaders.c b/libweston/renderer-gl/gl-shaders.c index 0b1c8cfb..97f288c0 100644 --- a/libweston/renderer-gl/gl-shaders.c +++ b/libweston/renderer-gl/gl-shaders.c @@ -58,6 +58,8 @@ struct gl_shader { GLint tex_uniforms[3]; GLint alpha_uniform; GLint color_uniform; + GLint color_pre_curve_lut_2d_uniform; + GLint color_pre_curve_lut_scale_offset_uniform; struct wl_list link; /* gl_renderer::shader_list */ struct timespec last_used; }; @@ -82,6 +84,19 @@ gl_shader_texture_variant_to_string(enum gl_shader_texture_variant v) return "!?!?"; /* never reached */ } +static const char * +gl_shader_color_curve_to_string(enum gl_shader_color_curve kind) +{ + switch (kind) { +#define CASERET(x) case x: return #x; + CASERET(SHADER_COLOR_CURVE_IDENTITY) + CASERET(SHADER_COLOR_CURVE_LUT_3x1D) +#undef CASERET + } + + return "!?!?"; /* never reached */ +} + static void dump_program_with_line_numbers(int count, const char **sources) { @@ -147,8 +162,9 @@ create_shader_description_string(const struct gl_shader_requirements *req) int size; char *str; - size = asprintf(&str, "%s %cinput_is_premult %cgreen", + size = asprintf(&str, "%s %s %cinput_is_premult %cgreen", gl_shader_texture_variant_to_string(req->variant), + gl_shader_color_curve_to_string(req->color_pre_curve), req->input_is_premult ? '+' : '-', req->green_tint ? '+' : '-'); if (size < 0) @@ -165,9 +181,11 @@ create_shader_config_string(const struct gl_shader_requirements *req) size = asprintf(&str, "#define DEF_GREEN_TINT %s\n" "#define DEF_INPUT_IS_PREMULT %s\n" + "#define DEF_COLOR_PRE_CURVE %s\n" "#define DEF_VARIANT %s\n", req->green_tint ? "true" : "false", req->input_is_premult ? "true" : "false", + gl_shader_color_curve_to_string(req->color_pre_curve), gl_shader_texture_variant_to_string(req->variant)); if (size < 0) return NULL; @@ -245,6 +263,10 @@ gl_shader_create(struct gl_renderer *gr, shader->alpha_uniform = glGetUniformLocation(shader->program, "alpha"); shader->color_uniform = glGetUniformLocation(shader->program, "unicolor"); + shader->color_pre_curve_lut_2d_uniform = + glGetUniformLocation(shader->program, "color_pre_curve_lut_2d"); + shader->color_pre_curve_lut_scale_offset_uniform = + glGetUniformLocation(shader->program, "color_pre_curve_lut_scale_offset"); free(conf); @@ -353,6 +375,7 @@ gl_renderer_create_fallback_shader(struct gl_renderer *gr) static const struct gl_shader_requirements fallback_requirements = { .variant = SHADER_VARIANT_SOLID, .input_is_premult = true, + .color_pre_curve = SHADER_COLOR_CURVE_IDENTITY, }; struct gl_shader *shader; @@ -473,6 +496,25 @@ gl_shader_load_config(struct gl_shader *shader, glTexParameteri(in_tgt, GL_TEXTURE_MIN_FILTER, in_filter); glTexParameteri(in_tgt, GL_TEXTURE_MAG_FILTER, in_filter); } + + /* Fixed texture unit for color_pre_curve LUT */ + i = GL_SHADER_INPUT_TEX_MAX; + glActiveTexture(GL_TEXTURE0 + i); + switch (sconf->req.color_pre_curve) { + case SHADER_COLOR_CURVE_IDENTITY: + assert(sconf->color_pre_curve_lut_tex == 0); + break; + case SHADER_COLOR_CURVE_LUT_3x1D: + assert(sconf->color_pre_curve_lut_tex != 0); + assert(shader->color_pre_curve_lut_2d_uniform != -1); + assert(shader->color_pre_curve_lut_scale_offset_uniform != -1); + + glBindTexture(GL_TEXTURE_2D, sconf->color_pre_curve_lut_tex); + glUniform1i(shader->color_pre_curve_lut_2d_uniform, i); + glUniform2fv(shader->color_pre_curve_lut_scale_offset_uniform, + 1, sconf->color_pre_curve_lut_scale_offset); + break; + } } bool