Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9fe7e163 authored by Romain Guy's avatar Romain Guy
Browse files

Gradients are now an absurd Chimera

As of O, gradients are interpolated in linear space. This unfortunately
affects applications that were expecting a certain behavior for the
alpha ramp. This change attempts to get the best of both world: better
color interpolation (in linear space) and the old alpha interpolation
(in gamma space). This is achieved by applying the electro-optical
transfer function to the alpha channel; an idea so wrong it would
make any graphics programmer worth his salt weep in disgust.

As abhorrent this idea might be to me, it also acts as a faint
beacon of hope admist the unfathomable darkness that is Android's
color management.

And if you allow me another misguided metaphor, this change
represents the flotsam I can cling onto in the hope to one day
reach the bountiful shores of linear blending and accurate color
management. Would this change not fix the distress caused by its
predecessors, I will have no choice but bow my head in shame until
the day I can finally devise an infallible plan.

Bug: 33010587
Test: CtsUiRenderingTestCases
Change-Id: I5397fefd7944413f2c820e613a5cba50579d4dd5
parent 7b6bcb60
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -205,6 +205,10 @@ void GradientCache::mixFloats(const FloatColor& start, const FloatColor& end,
    *d++ = a * (start.g * oppAmount + end.g * amount);
    *d++ = a * (start.b * oppAmount + end.b * amount);
#else
    // What we're doing to the alpha channel here is technically incorrect
    // but reproduces Android's old behavior when the alpha was pre-multiplied
    // with gamma-encoded colors
    a = EOCF_sRGB(a);
    *d++ = a * OECF_sRGB(start.r * oppAmount + end.r * amount);
    *d++ = a * OECF_sRGB(start.g * oppAmount + end.g * amount);
    *d++ = a * OECF_sRGB(start.b * oppAmount + end.b * amount);
+34 −17
Original line number Diff line number Diff line
@@ -164,24 +164,39 @@ const char* gFS_Uniforms_HasRoundRectClip =
// Dithering must be done in the quantization space
// When we are writing to an sRGB framebuffer, we must do the following:
//     EOCF(OECF(color) + dither)
// We approximate the transfer functions with gamma 2.0 to avoid branches and pow()
// The dithering pattern is generated with a triangle noise generator in the range [-0.0,1.0]
// TODO: Handle linear fp16 render targets
const char* gFS_Gradient_Functions =
        "\nfloat triangleNoise(const highp vec2 n) {\n"
        "    highp vec2 p = fract(n * vec2(5.3987, 5.4421));\n"
        "    p += dot(p.yx, p.xy + vec2(21.5351, 14.3137));\n"
        "    highp float xy = p.x * p.y;\n"
        "    return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;\n"
        "}\n";
const char* gFS_Gradient_Functions = R"__SHADER__(
        float triangleNoise(const highp vec2 n) {
            highp vec2 p = fract(n * vec2(5.3987, 5.4421));
            p += dot(p.yx, p.xy + vec2(21.5351, 14.3137));
            highp float xy = p.x * p.y;
            return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
        }

        float OECF_sRGB(const float linear) {
            // IEC 61966-2-1:1999
            return linear <= 0.0031308 ? linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;
        }

        vec3 OECF_sRGB(const vec3 linear) {
            return vec3(OECF_sRGB(linear.r), OECF_sRGB(linear.g), OECF_sRGB(linear.b));
        }

        float EOCF_sRGB(float srgb) {
            // IEC 61966-2-1:1999
            return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
        }
)__SHADER__";
const char* gFS_Gradient_Preamble[2] = {
        // Linear framebuffer
        "\nvec4 dither(const vec4 color) {\n"
        "    return vec4(color.rgb + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0), color.a);\n"
        "}\n"
        "\nvec4 gammaMix(const vec4 a, const vec4 b, float v) {\n"
        "    vec4 c = pow(mix(a, b, v), vec4(vec3(1.0 / 2.2), 1.0));\n"
        "    return vec4(c.rgb * c.a, c.a);\n"
        "    vec4 c = mix(a, b, v);\n"
        "    c.a = EOCF_sRGB(c.a);\n" // This is technically incorrect but preserves compatibility
        "    return vec4(OECF_sRGB(c.rgb) * c.a, c.a);\n"
        "}\n",
        // sRGB framebuffer
        "\nvec4 dither(const vec4 color) {\n"
@@ -200,13 +215,15 @@ const char* gFS_Gradient_Preamble[2] = {
// The gamma coefficient is chosen to thicken or thin the text accordingly
// The dot product used to compute the luminance could be approximated with
// a simple max(color.r, color.g, color.b)
const char* gFS_Gamma_Preamble =
        "\n#define GAMMA (%.2f)\n"
        "#define GAMMA_INV (%.2f)\n"
        "\nfloat gamma(float a, const vec3 color) {\n"
        "    float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));\n"
        "    return pow(a, luminance < 0.5 ? GAMMA_INV : GAMMA);\n"
        "}\n";
const char* gFS_Gamma_Preamble = R"__SHADER__(
        #define GAMMA (%.2f)
        #define GAMMA_INV (%.2f)

        float gamma(float a, const vec3 color) {
            float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));
            return pow(a, luminance < 0.5 ? GAMMA_INV : GAMMA);
        }
)__SHADER__";

const char* gFS_Main =
        "\nvoid main(void) {\n"
+1 −1
Original line number Diff line number Diff line
@@ -44,7 +44,7 @@ static int bytesPerPixel(GLint glFormat) {
    case GL_RGBA16F:
        return 8;
    default:
        LOG_ALWAYS_FATAL("UNKNOWN FORMAT %d", glFormat);
        LOG_ALWAYS_FATAL("UNKNOWN FORMAT 0x%x", glFormat);
    }
}