[RFCv2 6/9] gl-renderer: Add support for blending in linear space.

John Kåre Alsaker john.kare.alsaker at gmail.com
Fri Nov 16 18:23:57 PST 2012


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 |  13 ++++-
 src/gl-renderer.c |  45 ++++++++++++---
 src/gl-shaders.c  | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 5 files changed, 219 insertions(+), 17 deletions(-)

diff --git a/src/compositor.c b/src/compositor.c
index 587fded..6c6697a 100644
--- a/src/compositor.c
+++ b/src/compositor.c
@@ -2795,10 +2795,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 a5dd3c6..ec74e2f 100644
--- a/src/compositor.h
+++ b/src/compositor.h
@@ -324,6 +324,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 40f109b..2a1f0c6 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
 };
 
@@ -85,6 +87,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;
@@ -108,8 +111,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;
@@ -153,6 +160,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);
 
@@ -167,7 +177,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 b35c5a9..c5b3908 100644
--- a/src/gl-renderer.c
+++ b/src/gl-renderer.c
@@ -687,7 +687,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);
@@ -707,11 +710,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);
 
@@ -756,7 +764,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);
@@ -786,7 +794,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);
 		}
@@ -912,7 +923,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);
@@ -1204,6 +1216,11 @@ gl_renderer_attach(struct weston_surface *es, struct wl_buffer *buffer)
 	} else {
 		weston_log("unhandled buffer type!\n");
 	}
+
+	if (ec->color_managed)
+		gs->conversion = CONVERSION_FROM_SRGB;
+	else
+		gs->conversion = CONVERSION_NONE;
 }
 
 static void
@@ -1218,6 +1235,7 @@ gl_renderer_surface_set_color(struct weston_surface *surface,
 	gs->color[3] = alpha;
 
 	gs->input = INPUT_SOLID;
+	gs->conversion = CONVERSION_NONE;
 }
 
 static int
@@ -1578,7 +1596,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);
 }
 
@@ -1588,6 +1606,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[] = {
@@ -1600,6 +1619,7 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface)
 	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,
@@ -1610,6 +1630,7 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface)
 	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)) {
@@ -1675,6 +1696,16 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface)
 		return -1;
 	}
 
+	glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &param);
+
+	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 8595daf..0f49eb8 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;
 }
 
@@ -251,6 +289,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");
 
@@ -287,6 +328,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;
@@ -420,6 +475,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)
@@ -434,9 +499,21 @@ create_shader_permutation(struct gl_renderer *renderer,
 		return 0;
 	}
 
-	append(&sb.body, "gl_FragColor *= alpha;\n");
+	add_conversion(&sb);
 
-	if (renderer->fragment_shader_debug)
+	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 &&
+			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");
 
@@ -456,6 +533,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;
@@ -512,13 +595,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)];
@@ -558,6 +642,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);
@@ -565,12 +650,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)
@@ -580,7 +730,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);
 
 	return 0;
 }
-- 
1.8.0



More information about the wayland-devel mailing list