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 00000000..fd93bd4b Binary files /dev/null and b/tests/reference/alpha_blend-01.png differ