From 35cae567d61e3a3e5c57249c74c3e825203e8d53 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 16 Apr 2021 17:42:40 +0300 Subject: [PATCH] tests/alpha-blending: add sRGB linear light case Now that GL-renderer and color manager implement linear light blending for sRGB EOTF, add a test case to verify the result is expected. As noted in test comments, this new tests is quite powerful in ensuring the whole linear light pipeline is working correctly with 1D LUTs in GL-renderer. This test will even catch smashing source_lut.scale = 1.0f and source_lut.offset = 0.0f which would result in wrong texture sample positions for LUT data. As the assumption is that by default content and outputs are in sRGB color space, this test should not need fix-ups or become stale when more color management features are implemented. The sRGB EOTF can be found in: http://www.color.org/sRGB.pdf (beware, typos) https://www.w3.org/Graphics/Color/srgb https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#TRANSFER_SRGB Note on AMD Polaris 11 error threshold: this is quite likely due to using fp16 format shadow framebuffer and GCN fp32 to fp16 conversion instruction rounding mode. When using fp32 shadow framebuffer, the error glitch is not present and the threshold could be significantly lower. Signed-off-by: Pekka Paalanen --- tests/alpha-blending-test.c | 123 +++++++++++++++++++++++++++-- tests/reference/alpha_blend-01.png | Bin 0 -> 474 bytes 2 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 tests/reference/alpha_blend-01.png diff --git a/tests/alpha-blending-test.c b/tests/alpha-blending-test.c index aaf77e54..8aa2fcdd 100644 --- a/tests/alpha-blending-test.c +++ b/tests/alpha-blending-test.c @@ -33,6 +33,7 @@ struct setup_args { struct fixture_metadata meta; enum renderer_type renderer; + bool color_management; }; static const int ALPHA_STEPS = 256; @@ -41,12 +42,19 @@ static const int BLOCK_WIDTH = 3; static const struct setup_args my_setup_args[] = { { .renderer = RENDERER_PIXMAN, + .color_management = false, .meta.name = "pixman" }, { .renderer = RENDERER_GL, + .color_management = false, .meta.name = "GL" }, + { + .renderer = RENDERER_GL, + .color_management = true, + .meta.name = "GL sRGB EOTF" + }, }; static enum test_result_code @@ -60,6 +68,12 @@ fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg) setup.height = 16; setup.shell = SHELL_TEST_DESKTOP; + if (arg->color_management) { + weston_ini_setup(&setup, + cfgln("[core]"), + cfgln("color-management=true")); + } + return weston_test_harness_execute_as_client(harness, &setup); } DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args, meta); @@ -154,6 +168,46 @@ unpremult_float(struct color_float *cf) } } +static float +sRGB_EOTF(float e) +{ + assert(e >= 0.0f); + assert(e <= 1.0f); + + if (e <= 0.04045) + return e / 12.92; + else + return pow((e + 0.055) / 1.055, 2.4); +} + +static void +sRGB_linearize(struct color_float *cf) +{ + cf->r = sRGB_EOTF(cf->r); + cf->g = sRGB_EOTF(cf->g); + cf->b = sRGB_EOTF(cf->b); +} + +static float +sRGB_EOTF_inv(float o) +{ + assert(o >= 0.0f); + assert(o <= 1.0f); + + if (o <= 0.04045 / 12.92) + return o * 12.92; + else + return pow(o, 1.0 / 2.4) * 1.055 - 0.055; +} + +static void +sRGB_delinearize(struct color_float *cf) +{ + cf->r = sRGB_EOTF_inv(cf->r); + cf->g = sRGB_EOTF_inv(cf->g); + cf->b = sRGB_EOTF_inv(cf->b); +} + static bool compare_float(float ref, float dst, int x, const char *chan, float *max_diff) { @@ -188,7 +242,18 @@ compare_float(float ref, float dst, int x, const char *chan, float *max_diff) if (diff > *max_diff) *max_diff = diff; - if (diff < 0.5f / 255.f) + /* + * Allow for +/- 1.5 code points of error in non-linear 8-bit channel + * value. This is necessary for the BLEND_LINEAR case. + * + * With llvmpipe, we could go as low as +/- 0.65 code points of error + * and still pass. + * + * AMD Polaris 11 would be ok with +/- 1.0 code points error threshold + * if not for one particular case of blending (a=254, r=0) into r=255, + * which results in error of 1.29 code points. + */ + if (diff < 1.5f / 255.f) return true; testlog("x=%d %s: ref %f != dst %f, delta %f\n", @@ -197,9 +262,15 @@ compare_float(float ref, float dst, int x, const char *chan, float *max_diff) return false; } +enum blend_space { + BLEND_NONLINEAR, + BLEND_LINEAR, +}; + static bool verify_sRGB_blend_a8r8g8b8(uint32_t bg32, uint32_t fg32, uint32_t dst32, - int x, struct color_float *max_diff) + int x, struct color_float *max_diff, + enum blend_space space) { struct color_float bg = a8r8g8b8_to_float(bg32); struct color_float fg = a8r8g8b8_to_float(fg32); @@ -211,10 +282,18 @@ verify_sRGB_blend_a8r8g8b8(uint32_t bg32, uint32_t fg32, uint32_t dst32, unpremult_float(&fg); unpremult_float(&dst); + if (space == BLEND_LINEAR) { + sRGB_linearize(&bg); + sRGB_linearize(&fg); + } + ref.r = (1.0f - fg.a) * bg.r + fg.a * fg.r; ref.g = (1.0f - fg.a) * bg.g + fg.a * fg.g; ref.b = (1.0f - fg.a) * bg.b + fg.a * fg.b; + if (space == BLEND_LINEAR) + sRGB_delinearize(&ref); + ok = compare_float(ref.r, dst.r, x, "r", &max_diff->r) && ok; ok = compare_float(ref.g, dst.g, x, "g", &max_diff->g) && ok; ok = compare_float(ref.b, dst.b, x, "b", &max_diff->b) && ok; @@ -268,7 +347,8 @@ get_middle_row(struct buffer *buf) } static bool -check_blend_pattern(struct buffer *bg, struct buffer *fg, struct buffer *shot) +check_blend_pattern(struct buffer *bg, struct buffer *fg, struct buffer *shot, + enum blend_space space) { uint32_t *bg_row = get_middle_row(bg); uint32_t *fg_row = get_middle_row(fg); @@ -282,7 +362,8 @@ check_blend_pattern(struct buffer *bg, struct buffer *fg, struct buffer *shot) ret = false; if (!verify_sRGB_blend_a8r8g8b8(bg_row[x], fg_row[x], - shot_row[x], x, &max_diff)) + shot_row[x], x, &max_diff, + space)) ret = false; } @@ -309,6 +390,24 @@ check_blend_pattern(struct buffer *bg, struct buffer *fg, struct buffer *shot) * - red goes from 1.0 to 0.0, monotonic * - green is not monotonic * - blue goes from 0.0 to 1.0, monotonic + * + * This test has two modes: BLEND_NONLINEAR and BLEND_LINEAR. + * + * BLEND_NONLINEAR does blending with pixel values as is, which are non-linear, + * and therefore result in "physically incorrect" blending result. Yet, people + * have accustomed to seeing this effect. This mode hits pipeline_premult() + * in fragment.glsl. + * + * BLEND_LINEAR has sRGB encoded pixels (non-linear). These are converted to + * linear light (optical) values, blended, and converted back to non-linear + * (electrical) values. This results in "physically more correct" blending + * result for some value of "physical". This mode hits pipeline_straight() + * in fragment.glsl, and tests even more things: + * - gl-renderer implementation of 1D LUT is correct + * - color-lcms instantiates the correct sRGB EOTF and inverse LUTs + * - color space conversions do not happen when both content and output are + * using their default color spaces + * - blending through gl-renderer shadow framebuffer */ TEST(alpha_blend) { @@ -320,6 +419,7 @@ TEST(alpha_blend) .blue = 0x0000, .alpha = 0xffff }; + const struct setup_args *args; struct client *client; struct buffer *bg; struct buffer *fg; @@ -328,6 +428,17 @@ TEST(alpha_blend) struct wl_subsurface *sub; struct buffer *shot; bool match; + int seq_no; + enum blend_space space; + + args = &my_setup_args[get_test_fixture_index()]; + if (args->color_management) { + seq_no = 1; + space = BLEND_LINEAR; + } else { + seq_no = 0; + space = BLEND_NONLINEAR; + } client = create_client(); subco = bind_to_singleton_global(client, &wl_subcompositor_interface, 1); @@ -361,10 +472,10 @@ TEST(alpha_blend) shot = capture_screenshot_of_output(client); assert(shot); - match = verify_image(shot, "alpha_blend", 0, NULL, 0); + match = verify_image(shot, "alpha_blend", seq_no, NULL, seq_no); + assert(check_blend_pattern(bg, fg, shot, space)); assert(match); - assert(check_blend_pattern(bg, fg, shot)); buffer_destroy(shot); wl_subsurface_destroy(sub); diff --git a/tests/reference/alpha_blend-01.png b/tests/reference/alpha_blend-01.png new file mode 100644 index 0000000000000000000000000000000000000000..fd93bd4ba5b2f038d9972a5ea81dcc2608b098a7 GIT binary patch literal 474 zcmeAS@N?(olHy`uVBq!ia0y~yUi(0|R5f zr;B4q#hkY{9Q~LLd0G>fubmK4Ah7OmVMJkC*n%=4v7XCD`ANQ-THOEdvweAF;peEd zXp4EV|NZz`wpE`D)KmW*J9Th!^L3A)Z}Z-Wci!}yrn1uZrf09Z*_w^lrk~nT5trpF z{cBxa#$~(5v+mveqH{BK-j7SJ>2o*el+S!~X?J$|<;s5=oN9YE7Qfyuc`y66gxQ)- zsaqQ&`d=KqXcMH&yEtj*xl_lJK3!a^Gj)FaV&yOHm2npz)^B}nv+CciFTUI7ZG9uG zle;THT26{r-@Nx2M_0I=;ob$;Smr+cV&^|~iTRYJtJPvwt}MSJdZkC${?(&*(pJ}X z=N>oxIx99XNcH2))$wcCcK>CdNM_l<#}dPnp>BQTI(eqf-KtYMUkb5fGuTy+y@ OEQ6=3pUXO@geCw