[PATCH weston 5/8] gl-renderer: Add support for blending in linear space.
John Kåre Alsaker
john.kare.alsaker at gmail.com
Thu Feb 21 10:19:31 PST 2013
This makes compositing gamma correct by assuming all input surfaces are in
the sRGB color space. It can be enabled by setting color-managed to true
in the compositor section of weston.ini.
It's implemented by converting from sRGB gamma using the new CONVERSION_FROM_SRGB
shader attribute and drawing the surfaces into a temporary buffer (the
indirect rendering introduced in the previous patch). That buffer is then
drawed to the framebuffer using the OUTPUT_TO_SRGB shader attribute which
converts back into sRGB gamma.
Both the temporary buffer and sRGB decode LUT needs atleast 12-bits per
channel for flawless results with 8-bit input/output. This is not provided
by OpenGL ES 2 by default so desktop OpenGL is required for usable results.
It also adds a check to ensure we have enough texture units for the planes
and the LUT.
---
src/compositor.c | 7 ++-
src/compositor.h | 2 +
src/gl-internal.h | 14 ++++-
src/gl-renderer.c | 47 ++++++++++++---
src/gl-shaders.c | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++---
5 files changed, 222 insertions(+), 17 deletions(-)
diff --git a/src/compositor.c b/src/compositor.c
index 9198b3b..22bd3ea 100644
--- a/src/compositor.c
+++ b/src/compositor.c
@@ -2963,10 +2963,15 @@ weston_compositor_init(struct weston_compositor *ec,
{ "keymap_variant", CONFIG_KEY_STRING, &xkb_names.variant },
{ "keymap_options", CONFIG_KEY_STRING, &xkb_names.options },
};
+ const struct config_key compositor_config_keys[] = {
+ { "color-managed", CONFIG_KEY_BOOLEAN, &ec->color_managed },
+ };
const struct config_section cs[] = {
{ "keyboard",
keyboard_config_keys, ARRAY_LENGTH(keyboard_config_keys) },
- };
+ { "compositor",
+ compositor_config_keys, ARRAY_LENGTH(compositor_config_keys) },
+ };
memset(&xkb_names, 0, sizeof(xkb_names));
parse_config_file(config_file, cs, ARRAY_LENGTH(cs), ec);
diff --git a/src/compositor.h b/src/compositor.h
index 8842372..88a3cdd 100644
--- a/src/compositor.h
+++ b/src/compositor.h
@@ -338,6 +338,8 @@ struct weston_compositor {
struct weston_plane primary_plane;
int fan_debug;
+ int color_managed;
+
uint32_t focus;
struct weston_renderer *renderer;
diff --git a/src/gl-internal.h b/src/gl-internal.h
index 205c2d9..44b1c75 100644
--- a/src/gl-internal.h
+++ b/src/gl-internal.h
@@ -47,11 +47,13 @@ enum gl_shader_attribute {
enum gl_conversion_attribute {
CONVERSION_NONE,
+ CONVERSION_FROM_SRGB,
CONVERSION_COUNT
};
enum gl_output_attribute {
OUTPUT_BLEND,
+ OUTPUT_TO_SRGB,
OUTPUT_COUNT
};
@@ -87,6 +89,7 @@ struct gl_output_state {
struct gl_surface_state {
GLfloat color[4];
enum gl_input_attribute input;
+ enum gl_conversion_attribute conversion;
GLuint textures[MAX_PLANES];
int num_textures;
@@ -114,7 +117,12 @@ struct gl_renderer {
int32_t width, height;
} border;
+ GLuint srgb_decode_lut;
+ GLuint srgb_encode_lut;
+
GLenum bgra_internal_format, bgra_format;
+ GLenum rgba16_internal_format;
+ GLenum l16_internal_format;
GLenum short_type;
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d;
@@ -158,6 +166,9 @@ get_renderer(struct weston_compositor *ec)
int
gl_init_shaders(struct gl_renderer *gr);
+int
+gl_compile_shaders(struct gl_renderer *gr);
+
void
gl_destroy_shaders(struct gl_renderer *gr);
@@ -172,7 +183,8 @@ gl_use_shader(struct gl_renderer *gr,
struct gl_shader *
gl_select_shader(struct gl_renderer *gr,
enum gl_input_attribute input,
- enum gl_output_attribute output);
+ enum gl_output_attribute output,
+ enum gl_conversion_attribute conversion);
void
gl_shader_setup(struct gl_shader *shader,
diff --git a/src/gl-renderer.c b/src/gl-renderer.c
index ac8696f..25f5f84 100644
--- a/src/gl-renderer.c
+++ b/src/gl-renderer.c
@@ -700,7 +700,10 @@ repaint_surfaces_start(struct weston_output *output, pixman_region32_t *damage)
{
struct gl_output_state *go = get_output_state(output);
- go->indirect_drawing = 0 && !go->indirect_disable;
+ go->indirect_drawing = output->compositor->color_managed;
+
+ if (go->indirect_disable)
+ go->indirect_drawing = 0;
if (go->indirect_drawing) {
glBindFramebuffer(GL_FRAMEBUFFER, go->indirect_fbo);
@@ -720,11 +723,16 @@ repaint_surfaces_finish(struct weston_output *output, pixman_region32_t *damage)
if (go->indirect_drawing) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
- shader = gl_select_shader(gr, INPUT_RGBX, OUTPUT_BLEND);
+ shader = gl_select_shader(gr,
+ INPUT_RGBX,
+ OUTPUT_TO_SRGB,
+ CONVERSION_NONE);
gl_use_shader(gr, shader);
gl_shader_set_output(shader, output);
- glUniform1f(shader->alpha_uniform, 1.0);
+
+ glActiveTexture(GL_TEXTURE0 + MAX_PLANES);
+ glBindTexture(GL_TEXTURE_2D, gr->srgb_encode_lut);
glDisable(GL_BLEND);
@@ -770,7 +778,7 @@ draw_surface(struct weston_surface *es, struct weston_output *output,
gl_shader_setup(gr->solid_shader, es, output);
}
- shader = gl_select_shader(gr, gs->input, OUTPUT_BLEND);
+ shader = gl_select_shader(gr, gs->input, OUTPUT_BLEND, gs->conversion);
gl_use_shader(gr, shader);
gl_shader_setup(shader, es, output);
@@ -800,7 +808,10 @@ draw_surface(struct weston_surface *es, struct weston_output *output,
* Xwayland surfaces need this.
*/
- struct gl_shader *rgbx_shader = gl_select_shader(gr, INPUT_RGBX, OUTPUT_BLEND);
+ struct gl_shader *rgbx_shader = gl_select_shader(gr,
+ INPUT_RGBX,
+ OUTPUT_BLEND,
+ gs->conversion);
gl_use_shader(gr, rgbx_shader);
gl_shader_setup(rgbx_shader, es, output);
}
@@ -926,7 +937,8 @@ draw_border(struct weston_output *output)
GLfloat *v;
int n;
- shader = gl_select_shader(gr, INPUT_RGBA, OUTPUT_BLEND);
+ shader = gl_select_shader(gr, INPUT_RGBA, OUTPUT_BLEND,
+ CONVERSION_NONE);
glDisable(GL_BLEND);
gl_use_shader(gr, shader);
@@ -1232,6 +1244,11 @@ gl_renderer_attach(struct weston_surface *es, struct wl_buffer *buffer)
weston_log("unhandled buffer type!\n");
weston_buffer_reference(&gs->buffer_ref, NULL);
}
+
+ if (ec->color_managed)
+ gs->conversion = CONVERSION_FROM_SRGB;
+ else
+ gs->conversion = CONVERSION_NONE;
}
static void
@@ -1246,6 +1263,7 @@ gl_renderer_surface_set_color(struct weston_surface *surface,
gs->color[3] = alpha;
gs->input = INPUT_SOLID;
+ gs->conversion = CONVERSION_NONE;
}
static int
@@ -1624,7 +1642,7 @@ fragment_debug_binding(struct wl_seat *seat, uint32_t time, uint32_t key,
gr->fragment_shader_debug ^= 1;
- gl_init_shaders(gr);
+ gl_compile_shaders(gr);
weston_compositor_damage_all(ec);
}
@@ -1635,6 +1653,7 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface)
struct gl_renderer *gr = get_renderer(ec);
const char *extensions;
EGLBoolean ret;
+ GLint param;
#ifdef BUILD_OPENGL
static const EGLint context_attribs[] = {
@@ -1646,6 +1665,8 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface)
gr->bgra_internal_format = GL_RGBA;
gr->bgra_format = GL_BGRA;
gr->short_type = GL_UNSIGNED_SHORT;
+ gr->rgba16_internal_format = GL_RGBA16;
+ gr->l16_internal_format = GL_LUMINANCE16;
#else
static const EGLint context_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
@@ -1655,6 +1676,8 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface)
gr->bgra_internal_format = GL_BGRA_EXT;
gr->bgra_format = GL_BGRA_EXT;
gr->short_type = GL_UNSIGNED_BYTE;
+ gr->rgba16_internal_format = GL_RGBA;
+ gr->l16_internal_format = GL_LUMINANCE;
#endif
if (!eglBindAPI(OPENGL_ES_VER ? EGL_OPENGL_ES_API : EGL_OPENGL_API)) {
@@ -1720,6 +1743,16 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface)
return -1;
}
+ glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, ¶m);
+
+ if (ec->color_managed)
+ param--;
+
+ if (param < MAX_PLANES) {
+ weston_log("Too few OpenGL texture units available\n");
+ return -1;
+ }
+
if (strstr(extensions, "GL_EXT_read_format_bgra"))
ec->read_format = PIXMAN_a8r8g8b8;
else
diff --git a/src/gl-shaders.c b/src/gl-shaders.c
index 0f6438e..b3c81cd 100644
--- a/src/gl-shaders.c
+++ b/src/gl-shaders.c
@@ -150,13 +150,52 @@ append(struct shader_string_list *list, const char *string)
*data = str;
}
+static void
+add_conversion(struct shader_builder *sb)
+{
+ int alpha = sb->desc->transparent;
+
+ if (sb->attributes[ATTRIBUTE_CONVERSION] != CONVERSION_FROM_SRGB)
+ return;
+
+ if (alpha)
+ append(&sb->body,
+ "gl_FragColor.rgb *= gl_FragColor.a > 0.0 ? " \
+ "1.0 / gl_FragColor.a : 0.0;\n");
+
+ append(&sb->global, "uniform sampler2D srgb_lut;\n");
+ append(&sb->body,
+ "gl_FragColor.rgb = gl_FragColor.rgb * 0.9473684210526316 + " \
+ "0.02631578947368421;\n" \
+ "gl_FragColor.rgb = vec3(" \
+ "texture2D(srgb_lut, vec2(gl_FragColor.r, 0.5)).x," \
+ "texture2D(srgb_lut, vec2(gl_FragColor.g, 0.5)).x," \
+ "texture2D(srgb_lut, vec2(gl_FragColor.b, 0.5)).x);\n");
+
+ if (alpha)
+ append(&sb->body, "gl_FragColor.rgb *= gl_FragColor.a;\n");
+}
+
+static void
+add_conversion_uniforms(struct shader_builder *builder,
+ struct gl_shader *shader)
+{
+ if (builder->attributes[ATTRIBUTE_CONVERSION] != CONVERSION_FROM_SRGB)
+ return;
+
+ glUniform1i(glGetUniformLocation(shader->program, "srgb_lut"),
+ MAX_PLANES);
+}
+
static int
shader_rgbx_constructor(struct shader_builder *sb)
{
append(&sb->global, "uniform sampler2D texture;\n");
append(&sb->body,
- "gl_FragColor.rgb = texture2D(texture, texture_coord).rgb;\n" \
- "gl_FragColor.a = 1.0;\n");
+ "gl_FragColor.rgb = texture2D(texture, texture_coord).rgb;\n");
+
+ if (sb->attributes[ATTRIBUTE_OUTPUT] != OUTPUT_TO_SRGB)
+ append(&sb->body, "gl_FragColor.a = 1.0;\n");
return 1;
}
@@ -168,7 +207,6 @@ shader_rgba_constructor(struct shader_builder *sb)
append(&sb->body,
"gl_FragColor = texture2D(texture, texture_coord);\n");
-
return 1;
}
@@ -252,6 +290,9 @@ shader_yuv_uniforms(struct shader_builder *sb, struct gl_shader *shader)
static int
shader_solid_constructor(struct shader_builder *sb)
{
+ if (sb->attributes[ATTRIBUTE_CONVERSION])
+ return 0;
+
append(&sb->global, "uniform vec4 color;\n");
append(&sb->body, "gl_FragColor = color;\n");
@@ -288,6 +329,20 @@ static struct gl_input_type_desc input_type_descs[INPUT_COUNT] = {
};
static void
+add_to_srgb_conversion(struct shader_builder *sb)
+{
+ append(&sb->global, "uniform sampler2D srgb_lut;\n");
+ append(&sb->body,
+ "gl_FragColor.rgb = gl_FragColor.rgb * 0.9946236559139785 + " \
+ "0.002688172043010753;\n" \
+ "gl_FragColor.rgb = vec3(" \
+ "texture2D(srgb_lut, vec2(gl_FragColor.r, 0.5)).x," \
+ "texture2D(srgb_lut, vec2(gl_FragColor.g, 0.5)).x," \
+ "texture2D(srgb_lut, vec2(gl_FragColor.b, 0.5)).x);\n");
+
+}
+
+static void
attributes_from_permutation(size_t permutation, size_t *attributes)
{
int i;
@@ -421,6 +476,16 @@ create_shader_permutation(struct gl_renderer *renderer,
sb.renderer = renderer;
sb.desc = &input_type_descs[sb.attributes[ATTRIBUTE_INPUT]];
+ if (sb.attributes[ATTRIBUTE_OUTPUT] == OUTPUT_TO_SRGB) {
+ /* transparent inputs must be blended first */
+ if (sb.desc->transparent)
+ return 0;
+
+ /* useless conversion from and to sRGB */
+ if (sb.attributes[ATTRIBUTE_CONVERSION] == CONVERSION_FROM_SRGB)
+ return 0;
+ }
+
shader_builder_init(&sb);
if (OPENGL_ES_VER)
@@ -435,9 +500,21 @@ create_shader_permutation(struct gl_renderer *renderer,
return 0;
}
- append(&sb.body, "gl_FragColor *= alpha;\n");
+ add_conversion(&sb);
+
+ switch (sb.attributes[ATTRIBUTE_OUTPUT]) {
+ case OUTPUT_BLEND:
+ append(&sb.body, "gl_FragColor *= alpha;\n");
+ break;
+ case OUTPUT_TO_SRGB:
+ add_to_srgb_conversion(&sb);
+ break;
+ default:
+ break;
+ }
- if (renderer->fragment_shader_debug)
+ if (renderer->fragment_shader_debug &&
+ sb.attributes[ATTRIBUTE_OUTPUT] != OUTPUT_TO_SRGB)
append(&sb.body, "gl_FragColor = vec4(0.0, 0.3, 0.0, 0.2) + " \
"gl_FragColor * 0.8;\n");
@@ -457,6 +534,12 @@ create_shader_permutation(struct gl_renderer *renderer,
sb.desc->setup_uniforms(&sb, *shader);
+ add_conversion_uniforms(&sb, *shader);
+
+ if (sb.attributes[ATTRIBUTE_OUTPUT] == OUTPUT_TO_SRGB)
+ glUniform1i(glGetUniformLocation((*shader)->program, "srgb_lut"),
+ MAX_PLANES);
+
shader_builder_release(&sb);
return 0;
@@ -513,13 +596,14 @@ error:
struct gl_shader *
gl_select_shader(struct gl_renderer *gr,
enum gl_input_attribute input,
- enum gl_output_attribute output)
+ enum gl_output_attribute output,
+ enum gl_conversion_attribute conversion)
{
struct gl_shader *shader;
size_t attributes[ATTRIBUTE_COUNT] = {
input,
output,
- CONVERSION_NONE
+ conversion
};
shader = gr->shaders[permutation_from_attributes(attributes)];
@@ -559,6 +643,7 @@ gl_shader_setup(struct gl_shader *shader,
struct weston_surface *surface,
struct weston_output *output)
{
+ struct gl_renderer *gr = get_renderer(output->compositor);
struct gl_surface_state *gs = get_surface_state(surface);
gl_shader_set_output(shader, output);
@@ -566,12 +651,77 @@ gl_shader_setup(struct gl_shader *shader,
if (gs->input == INPUT_SOLID)
glUniform4fv(shader->color_uniform, 1, gs->color);
+ if (gs->conversion == CONVERSION_FROM_SRGB) {
+ glActiveTexture(GL_TEXTURE0 + MAX_PLANES);
+ glBindTexture(GL_TEXTURE_2D, gr->srgb_decode_lut);
+ }
+
glUniform1f(shader->alpha_uniform, surface->alpha);
}
+static void
+setup_lut(GLuint *texture, const void *data,
+ GLsizei entries, GLenum internal_format, GLenum type)
+{
+ glGenTextures(1, texture);
+
+ glBindTexture(GL_TEXTURE_2D, *texture);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glTexImage2D(GL_TEXTURE_2D, 0, internal_format, entries, 1, 0,
+ GL_LUMINANCE, type, data);
+}
+
+static const uint16_t srgb_decode_lut[] = {
+ 0, 281, 751, 1519, 2618, 4073, 5919, 8166, 10847, 13984, 17589, 21690,
+ 26301, 31424, 37095, 43321, 50125, 57488, 65535
+};
+
+static const uint8_t srgb_encode_lut[] = {
+ 0, 17, 27, 34, 40, 46, 50, 55, 59, 62, 66, 69, 72, 75, 78, 80, 83, 85,
+ 88, 90, 92, 95, 97, 99, 101, 103, 105, 107, 108, 110, 112, 114, 115,
+ 117, 119, 120, 122, 124, 125, 127, 128, 130, 131, 132, 134, 135, 137,
+ 138, 139, 141, 142, 143, 145, 146, 147, 148, 149, 151, 152, 153, 154,
+ 156, 156, 158, 159, 160, 161, 162, 163, 165, 165, 166, 168, 168, 170,
+ 170, 172, 172, 174, 174, 176, 176, 178, 178, 180, 181, 181, 183, 183,
+ 185, 185, 186, 187, 188, 189, 190, 190, 192, 192, 194, 194, 195, 196,
+ 196, 198, 198, 200, 200, 201, 202, 203, 203, 204, 205, 206, 207, 207,
+ 208, 209, 210, 211, 211, 212, 213, 214, 214, 215, 216, 217, 217, 218,
+ 219, 221, 218, 222, 222, 223, 223, 224, 225, 226, 226, 227, 228, 228,
+ 229, 231, 229, 231, 232, 232, 233, 234, 235, 235, 236, 237, 237, 238,
+ 239, 239, 240, 241, 241, 242, 242, 243, 245, 242, 245, 247, 245, 247,
+ 248, 248, 249, 249, 250, 252, 250, 252, 253, 253, 255, 252, 255
+};
+
+static void
+setup_luts(struct gl_renderer *gr)
+{
+ setup_lut(&gr->srgb_decode_lut, srgb_decode_lut,
+ ARRAY_LENGTH(srgb_decode_lut),
+ gr->l16_internal_format, GL_UNSIGNED_SHORT);
+
+ setup_lut(&gr->srgb_encode_lut, srgb_encode_lut,
+ ARRAY_LENGTH(srgb_encode_lut), GL_LUMINANCE, GL_UNSIGNED_BYTE);
+}
+
int
gl_init_shaders(struct gl_renderer *gr)
{
+ if (gl_compile_shaders(gr) < 0)
+ return -1;
+
+ setup_luts(gr);
+
+ return 0;
+}
+
+int
+gl_compile_shaders(struct gl_renderer *gr)
+{
struct gl_shader **shaders = create_shader_permutations(gr);
if (!shaders)
@@ -581,7 +731,10 @@ gl_init_shaders(struct gl_renderer *gr)
gl_destroy_shaders(gr);
gr->shaders = shaders;
- gr->solid_shader = gl_select_shader(gr, INPUT_SOLID, OUTPUT_BLEND);
+ gr->solid_shader = gl_select_shader(gr,
+ INPUT_SOLID,
+ OUTPUT_BLEND,
+ CONVERSION_NONE);
/* Force use_shader() to call glUseProgram(), since we need to use
* the recompiled version of the shader. */
--
1.8.1.3
More information about the wayland-devel
mailing list