diff --git a/.builds/alpine.yml b/.builds/alpine.yml index 68679403a..5b304a7d3 100644 --- a/.builds/alpine.yml +++ b/.builds/alpine.yml @@ -3,6 +3,7 @@ packages: - eudev-dev - ffmpeg-dev - glslang + - lcms2-dev - libinput-dev - libxkbcommon-dev - mesa-dev diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index 2d62d0d1f..dd5dc3a4d 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -2,6 +2,7 @@ image: archlinux packages: - clang - ffmpeg + - lcms2 - libinput - libxkbcommon - mesa diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index a9650d642..afa96b192 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -6,6 +6,7 @@ packages: - devel/meson # implies ninja - devel/pkgconf - graphics/glslang + - graphics/lcms2 - graphics/libdrm - graphics/mesa-libs - graphics/png diff --git a/include/render/gles2.h b/include/render/gles2.h index f54fc835b..7978f124d 100644 --- a/include/render/gles2.h +++ b/include/render/gles2.h @@ -22,6 +22,13 @@ struct wlr_gles2_pixel_format { bool has_alpha; }; +struct wlr_gles2_quad_shader { + GLuint program; + GLint proj; + GLint color; + GLint pos_attrib; +}; + struct wlr_gles2_tex_shader { GLuint program; GLint proj; @@ -57,15 +64,11 @@ struct wlr_gles2_renderer { PFNGLPUSHDEBUGGROUPKHRPROC glPushDebugGroupKHR; PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES; PFNGLGETGRAPHICSRESETSTATUSKHRPROC glGetGraphicsResetStatusKHR; + PFNGLTEXIMAGE3DOESPROC glTexImage3DOES; } procs; struct { - struct { - GLuint program; - GLint proj; - GLint color; - GLint pos_attrib; - } quad; + struct wlr_gles2_quad_shader quad; struct wlr_gles2_tex_shader tex_rgba; struct wlr_gles2_tex_shader tex_rgbx; struct wlr_gles2_tex_shader tex_ext; @@ -112,6 +115,22 @@ struct wlr_gles2_texture { struct wlr_addon buffer_addon; }; +enum wlr_gles2_shader_source { + WLR_GLES2_SHADER_SOURCE_SINGLE_COLOR = 1, + WLR_GLES2_SHADER_SOURCE_TEXTURE_RGBA = 2, + WLR_GLES2_SHADER_SOURCE_TEXTURE_RGBX = 3, + WLR_GLES2_SHADER_SOURCE_TEXTURE_EXTERNAL = 4, +}; + +enum wlr_gles2_shader_color_transform { + WLR_GLES2_SHADER_COLOR_TRANSFORM_IDENTITY = 0, + WLR_GLES2_SHADER_COLOR_TRANSFORM_LUT_3D = 1, +}; + +struct wlr_gles2_shader_params { + enum wlr_gles2_shader_source source; + enum wlr_gles2_shader_color_transform color_transform; +}; bool is_gles2_pixel_format_supported(const struct wlr_gles2_renderer *renderer, const struct wlr_gles2_pixel_format *format); diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h new file mode 100644 index 000000000..5cc1b9b52 --- /dev/null +++ b/include/wlr/render/color.h @@ -0,0 +1,44 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_COLOR_H +#define WLR_RENDER_COLOR_H + +#include +#include + +/** + * A color transformation formula. + * + * The formula is approximated via a 3D look-up table. A 3D LUT is a + * three-dimensional array where each element is an RGB triplet. The flat lut_3d + * array has a length of dim_len³. + * + * Color channel values in the range [0.0, 1.0] are mapped linearly to + * 3D LUT indices such that 0.0 maps exactly to the first element and 1.0 maps + * exactly to the last element in each dimension. + * + * The offset of the RGB triplet given red, green and blue indices r_index, + * g_index and b_index is: + * + * offset = 3 * (r_index + dim_len * g_index + dim_len * dim_len * b_index) + */ +struct wlr_color_transform { + float *lut_3d; + size_t dim_len; +}; + +/** + * Initialize a color transformation to convert sRGB to an ICC profile. + */ +bool wlr_color_transform_init_srgb_to_icc(struct wlr_color_transform *tr, + const void *data, size_t size); + +void wlr_color_transform_finish(struct wlr_color_transform *tr); + +#endif diff --git a/meson_options.txt b/meson_options.txt index 6977643ca..0c9a1a8ce 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -7,3 +7,4 @@ option('backends', type: 'array', choices: ['auto', 'drm', 'libinput', 'x11'], v option('allocators', type: 'array', choices: ['auto', 'gbm'], value: ['auto'], description: 'Select built-in allocators') option('session', type: 'feature', value: 'auto', description: 'Enable session support') +option('color-management', type: 'feature', value: 'auto', description: 'Enable support for color management') diff --git a/render/color.c b/render/color.c new file mode 100644 index 000000000..9b7ed7f51 --- /dev/null +++ b/render/color.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include + +static void handle_lcms_error(cmsContext ctx, cmsUInt32Number code, const char *text) { + wlr_log(WLR_ERROR, "[lcms] %s", text); +} + +bool wlr_color_transform_init_srgb_to_icc(struct wlr_color_transform *tr, + const void *data, size_t size) { + bool ok = false; + + cmsContext ctx = cmsCreateContext(NULL, NULL); + if (ctx == NULL) { + wlr_log(WLR_ERROR, "cmsCreateContext failed"); + return false; + } + + cmsSetLogErrorHandlerTHR(ctx, handle_lcms_error); + + cmsHPROFILE icc_profile = cmsOpenProfileFromMemTHR(ctx, data, size); + if (icc_profile == NULL) { + wlr_log(WLR_ERROR, "cmsOpenProfileFromMemTHR failed"); + goto out_ctx; + } + + if (cmsGetDeviceClass(icc_profile) != cmsSigDisplayClass) { + wlr_log(WLR_ERROR, "ICC profile must have the Display device class"); + goto out_icc_profile; + } + + cmsHPROFILE srgb_profile = cmsCreate_sRGBProfile(); + if (srgb_profile == NULL) { + wlr_log(WLR_ERROR, "cmsCreate_sRGBProfile failed"); + goto out_icc_profile; + } + + cmsHTRANSFORM lcms_tr = cmsCreateTransformTHR(ctx, + srgb_profile, TYPE_RGB_FLT, icc_profile, TYPE_RGB_FLT, + INTENT_RELATIVE_COLORIMETRIC, 0); + if (lcms_tr == NULL) { + wlr_log(WLR_ERROR, "cmsCreateTransformTHR failed"); + goto out_srgb_profile; + } + + size_t dim_len = 33; + float *lut_3d = calloc(3 * dim_len * dim_len * dim_len, sizeof(float)); + if (lut_3d == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto out_lcms_tr; + } + + float factor = 1.0f / (dim_len - 1); + for (size_t b_index = 0; b_index < dim_len; b_index++) { + for (size_t g_index = 0; g_index < dim_len; g_index++) { + for (size_t r_index = 0; r_index < dim_len; r_index++) { + float rgb_in[3] = { + r_index * factor, + g_index * factor, + b_index * factor, + }; + float rgb_out[3]; + cmsDoTransform(lcms_tr, rgb_in, rgb_out, 1); + + size_t offset = 3 * (r_index + dim_len * g_index + dim_len * dim_len * b_index); + // TODO: maybe clamp values to [0.0, 1.0] here? + lut_3d[offset] = rgb_out[0]; + lut_3d[offset + 1] = rgb_out[1]; + lut_3d[offset + 2] = rgb_out[2]; + } + } + } + + ok = true; + *tr = (struct wlr_color_transform){ + .lut_3d = lut_3d, + .dim_len = dim_len, + }; + +out_lcms_tr: + cmsDeleteTransform(lcms_tr); +out_srgb_profile: + cmsCloseProfile(srgb_profile); +out_icc_profile: + cmsCloseProfile(icc_profile); +out_ctx: + cmsDeleteContext(ctx); + return ok; +} + +void wlr_color_transform_finish(struct wlr_color_transform *tr) { + free(tr->lut_3d); +} diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c index 47c58b194..7f47816b2 100644 --- a/render/gles2/renderer.c +++ b/render/gles2/renderer.c @@ -20,10 +20,7 @@ #include "types/wlr_matrix.h" #include "common_vert_src.h" -#include "quad_frag_src.h" -#include "tex_rgba_frag_src.h" -#include "tex_rgbx_frag_src.h" -#include "tex_external_frag_src.h" +#include "common_frag_src.h" static const GLfloat verts[] = { 1, 0, // top right @@ -610,11 +607,11 @@ static void gles2_log(GLenum src, GLenum type, GLuint id, GLenum severity, } static GLuint compile_shader(struct wlr_gles2_renderer *renderer, - GLuint type, const GLchar *src) { + GLuint type, const GLchar **srcs, size_t srcs_len) { push_gles2_debug(renderer); GLuint shader = glCreateShader(type); - glShaderSource(shader, 1, &src, NULL); + glShaderSource(shader, srcs_len, srcs, NULL); glCompileShader(shader); GLint ok; @@ -630,15 +627,24 @@ static GLuint compile_shader(struct wlr_gles2_renderer *renderer, } static GLuint link_program(struct wlr_gles2_renderer *renderer, - const GLchar *vert_src, const GLchar *frag_src) { + const struct wlr_gles2_shader_params *params) { + static char frag_preamble[1024]; + snprintf(frag_preamble, sizeof(frag_preamble), + "#define SOURCE %d\n" + "#define COLOR_TRANSFORM %d\n", + params->source, params->color_transform); + push_gles2_debug(renderer); - GLuint vert = compile_shader(renderer, GL_VERTEX_SHADER, vert_src); + const GLchar *vert_src = common_vert_src; + GLuint vert = compile_shader(renderer, GL_VERTEX_SHADER, &vert_src, 1); if (!vert) { goto error; } - GLuint frag = compile_shader(renderer, GL_FRAGMENT_SHADER, frag_src); + const GLchar *frag_srcs[2] = { frag_preamble, common_frag_src }; + GLuint frag = + compile_shader(renderer, GL_FRAGMENT_SHADER, frag_srcs, 2); if (!frag) { glDeleteShader(vert); goto error; @@ -670,6 +676,23 @@ error: return 0; } +static bool link_tex_program(struct wlr_gles2_renderer *renderer, + struct wlr_gles2_tex_shader *shader, + const struct wlr_gles2_shader_params *params) { + shader->program = link_program(renderer, params); + if (!shader->program) { + return false; + } + + shader->proj = glGetUniformLocation(shader->program, "proj"); + shader->tex = glGetUniformLocation(shader->program, "tex"); + shader->alpha = glGetUniformLocation(shader->program, "alpha"); + shader->pos_attrib = glGetAttribLocation(shader->program, "pos"); + shader->tex_attrib = glGetAttribLocation(shader->program, "texcoord"); + + return true; +} + static bool check_gl_ext(const char *exts, const char *ext) { size_t extlen = strlen(ext); const char *end = exts + strlen(exts); @@ -808,6 +831,10 @@ struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl) { } } + if (check_gl_ext(exts_str, "GL_OES_texture_3D")) { + load_gl_proc(&renderer->procs.glTexImage3DOES, "glTexImage3DOES"); + } + if (renderer->exts.KHR_debug) { glEnable(GL_DEBUG_OUTPUT_KHR); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR); @@ -822,9 +849,18 @@ struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl) { push_gles2_debug(renderer); + enum wlr_gles2_shader_color_transform color_transform = + WLR_GLES2_SHADER_COLOR_TRANSFORM_IDENTITY; + if (renderer->procs.glTexImage3DOES != NULL) { + color_transform = WLR_GLES2_SHADER_COLOR_TRANSFORM_LUT_3D; + } + GLuint prog; renderer->shaders.quad.program = prog = - link_program(renderer, common_vert_src, quad_frag_src); + link_program(renderer, &(struct wlr_gles2_shader_params){ + .source = WLR_GLES2_SHADER_SOURCE_SINGLE_COLOR, + .color_transform = color_transform, + }); if (!renderer->shaders.quad.program) { goto error; } @@ -832,39 +868,27 @@ struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl) { renderer->shaders.quad.color = glGetUniformLocation(prog, "color"); renderer->shaders.quad.pos_attrib = glGetAttribLocation(prog, "pos"); - renderer->shaders.tex_rgba.program = prog = - link_program(renderer, common_vert_src, tex_rgba_frag_src); - if (!renderer->shaders.tex_rgba.program) { + if (!link_tex_program(renderer, &renderer->shaders.tex_rgba, + &(struct wlr_gles2_shader_params){ + .source = WLR_GLES2_SHADER_SOURCE_TEXTURE_RGBA, + .color_transform = color_transform, + })) { goto error; } - renderer->shaders.tex_rgba.proj = glGetUniformLocation(prog, "proj"); - renderer->shaders.tex_rgba.tex = glGetUniformLocation(prog, "tex"); - renderer->shaders.tex_rgba.alpha = glGetUniformLocation(prog, "alpha"); - renderer->shaders.tex_rgba.pos_attrib = glGetAttribLocation(prog, "pos"); - renderer->shaders.tex_rgba.tex_attrib = glGetAttribLocation(prog, "texcoord"); - - renderer->shaders.tex_rgbx.program = prog = - link_program(renderer, common_vert_src, tex_rgbx_frag_src); - if (!renderer->shaders.tex_rgbx.program) { + if (!link_tex_program(renderer, &renderer->shaders.tex_rgbx, + &(struct wlr_gles2_shader_params){ + .source = WLR_GLES2_SHADER_SOURCE_TEXTURE_RGBX, + .color_transform = color_transform, + })) { goto error; } - renderer->shaders.tex_rgbx.proj = glGetUniformLocation(prog, "proj"); - renderer->shaders.tex_rgbx.tex = glGetUniformLocation(prog, "tex"); - renderer->shaders.tex_rgbx.alpha = glGetUniformLocation(prog, "alpha"); - renderer->shaders.tex_rgbx.pos_attrib = glGetAttribLocation(prog, "pos"); - renderer->shaders.tex_rgbx.tex_attrib = glGetAttribLocation(prog, "texcoord"); - - if (renderer->exts.OES_egl_image_external) { - renderer->shaders.tex_ext.program = prog = - link_program(renderer, common_vert_src, tex_external_frag_src); - if (!renderer->shaders.tex_ext.program) { - goto error; - } - renderer->shaders.tex_ext.proj = glGetUniformLocation(prog, "proj"); - renderer->shaders.tex_ext.tex = glGetUniformLocation(prog, "tex"); - renderer->shaders.tex_ext.alpha = glGetUniformLocation(prog, "alpha"); - renderer->shaders.tex_ext.pos_attrib = glGetAttribLocation(prog, "pos"); - renderer->shaders.tex_ext.tex_attrib = glGetAttribLocation(prog, "texcoord"); + if (renderer->exts.OES_egl_image_external && + !link_tex_program(renderer, &renderer->shaders.tex_ext, + &(struct wlr_gles2_shader_params){ + .source = WLR_GLES2_SHADER_SOURCE_TEXTURE_EXTERNAL, + .color_transform = color_transform, + })) { + goto error; } pop_gles2_debug(renderer); diff --git a/render/gles2/shaders/common.frag b/render/gles2/shaders/common.frag new file mode 100644 index 000000000..989c530a6 --- /dev/null +++ b/render/gles2/shaders/common.frag @@ -0,0 +1,83 @@ +/* enum wlr_gles2_shader_source */ +#define SOURCE_SINGLE_COLOR 1 +#define SOURCE_TEXTURE_RGBA 2 +#define SOURCE_TEXTURE_RGBX 3 +#define SOURCE_TEXTURE_EXTERNAL 4 + +/* enum wlr_gles2_color_transform */ +#define COLOR_TRANSFORM_IDENTITY 0 +#define COLOR_TRANSFORM_LUT_3D 1 + +#if !defined(SOURCE) || !defined(COLOR_TRANSFORM) +#error "Missing shader preamble" +#endif + +#if SOURCE == SOURCE_TEXTURE_EXTERNAL +#extension GL_OES_EGL_image_external : require +#endif + +#if COLOR_TRANSFORM == COLOR_TRANSFORM_3DLUT +#extension GL_OES_texture_3D : require +#endif + +precision mediump float; + +varying vec2 v_texcoord; + +#if SOURCE == SOURCE_TEXTURE_EXTERNAL +uniform samplerExternalOES tex; +#elif SOURCE == SOURCE_TEXTURE_RGBA || SOURCE == SOURCE_TEXTURE_RGBX +uniform sampler2D tex; +#elif SOURCE == SOURCE_SINGLE_COLOR +uniform vec4 color; +#endif + +#if SOURCE != SOURCE_SINGLE_COLOR +uniform float alpha; +#else +const float alpha = 1.0; +#endif + +#if COLOR_TRANSFORM == COLOR_TRANSFORM_LUT_3D +uniform mediump sampler3D lut_3d; +uniform float lut_3d_offset; +uniform float lut_3d_scale; +#endif + +vec4 sample_texture() { +#if SOURCE == SOURCE_TEXTURE_RGBA || SOURCE == SOURCE_TEXTURE_EXTERNAL + return texture2D(tex, v_texcoord); +#elif SOURCE == SOURCE_TEXTURE_RGBX + return vec4(texture2D(tex, v_texcoord).rgb, 1.0); +#elif SOURCE == SOURCE_SINGLE_COLOR + return color; +#endif +} + +vec4 transform_color(vec4 color) { +#if COLOR_TRANSFORM == COLOR_TRANSFORM_IDENTITY + return color; +#elif COLOR_TRANSFORM == COLOR_TRANSFORM_LUT_3D + vec3 pos = lut_3d_offset + color * lut_3d_scale; + return texture3D(lut_3d, pos).rgb; +#endif +} + +void main() { + vec4 color = sample_texture() * alpha; + +#if COLOR_TRANSFORM != COLOR_TRANSFORM_IDENTITY + // Convert from pre-multiplied alpha to straight alpha + if (color.a == 0.0) + color.rgb = vec3(0.0, 0.0, 0.0); + else + color.rgb /= color.a; + + color = transform_color(color); + + // Convert from straight alpha to pre-multiplied alpha + color.rgb *= color.a; +#endif + + gl_FragColor = color; +} diff --git a/render/gles2/shaders/meson.build b/render/gles2/shaders/meson.build index 79454d9aa..62fbe8ce2 100644 --- a/render/gles2/shaders/meson.build +++ b/render/gles2/shaders/meson.build @@ -2,10 +2,7 @@ embed = find_program('./embed.sh', native: true) shaders = [ 'common.vert', - 'quad.frag', - 'tex_rgba.frag', - 'tex_rgbx.frag', - 'tex_external.frag', + 'common.frag', ] foreach name : shaders diff --git a/render/gles2/shaders/quad.frag b/render/gles2/shaders/quad.frag deleted file mode 100644 index f97127971..000000000 --- a/render/gles2/shaders/quad.frag +++ /dev/null @@ -1,8 +0,0 @@ -precision mediump float; -varying vec4 v_color; -varying vec2 v_texcoord; -uniform vec4 color; - -void main() { - gl_FragColor = color; -} diff --git a/render/gles2/shaders/tex_external.frag b/render/gles2/shaders/tex_external.frag deleted file mode 100644 index 05eac5032..000000000 --- a/render/gles2/shaders/tex_external.frag +++ /dev/null @@ -1,10 +0,0 @@ -#extension GL_OES_EGL_image_external : require - -precision mediump float; -varying vec2 v_texcoord; -uniform samplerExternalOES texture0; -uniform float alpha; - -void main() { - gl_FragColor = texture2D(texture0, v_texcoord) * alpha; -} diff --git a/render/gles2/shaders/tex_rgba.frag b/render/gles2/shaders/tex_rgba.frag deleted file mode 100644 index c2e17a90f..000000000 --- a/render/gles2/shaders/tex_rgba.frag +++ /dev/null @@ -1,8 +0,0 @@ -precision mediump float; -varying vec2 v_texcoord; -uniform sampler2D tex; -uniform float alpha; - -void main() { - gl_FragColor = texture2D(tex, v_texcoord) * alpha; -} diff --git a/render/gles2/shaders/tex_rgbx.frag b/render/gles2/shaders/tex_rgbx.frag deleted file mode 100644 index 42ddf92ff..000000000 --- a/render/gles2/shaders/tex_rgbx.frag +++ /dev/null @@ -1,8 +0,0 @@ -precision mediump float; -varying vec2 v_texcoord; -uniform sampler2D tex; -uniform float alpha; - -void main() { - gl_FragColor = vec4(texture2D(tex, v_texcoord).rgb, 1.0) * alpha; -} diff --git a/render/meson.build b/render/meson.build index 4c3d4522a..a687a7556 100644 --- a/render/meson.build +++ b/render/meson.build @@ -38,3 +38,9 @@ endif subdir('pixman') subdir('allocator') + +lcms2 = dependency('lcms2', required: get_option('color-management')) +if lcms2.found() + wlr_deps += lcms2 + wlr_files += files('color.c') +endif