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

Commit 636afc18 authored by Romain Guy's avatar Romain Guy
Browse files

Apply transfer function when rendering with linear textures

RGBA16F bitmaps are always encoded in linear space, which means we must
apply the opto-electronic transfer function before we can render them
in the framebuffer.

Since our linear bitmaps are assumed to be scRGB, values can be negative.
The OETF is a slightly modified sRGB OETF:

sign(x) * OETF_sRGB(abs(x))

This effectively mirrors the OETF over the negative domain.

This CL also removes the "optimized" shader generation path. With
current compilers, the optimized path doesn't do anything of value
and makes ProgramCache difficult to maintain. Shader compilers inline
everything and are really good at folding expressions and removing
unused code.

Bug: 32984164
Test: CtsUiRenderingTestCases
Change-Id: Ieb458ad53574e3a8959aa6bccbbd2d1fe203cbc5
parent 9fe7e163
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -605,6 +605,7 @@ void GlopBuilder::build() {
        } else {
            mDescription.hasExternalTexture = true;
        }
        mDescription.hasLinearTexture = mOutGlop->fill.texture.texture->isLinear();
    }

    mDescription.hasColors = mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::Color;
+6 −0
Original line number Diff line number Diff line
@@ -87,6 +87,7 @@ namespace uirenderer {
#define PROGRAM_HAS_ROUND_RECT_CLIP 43

#define PROGRAM_HAS_GAMMA_CORRECTION 44
#define PROGRAM_HAS_LINEAR_TEXTURE 45

///////////////////////////////////////////////////////////////////////////////
// Types
@@ -162,7 +163,10 @@ struct ProgramDescription {
    bool hasDebugHighlight;
    bool hasRoundRectClip;

    // Extra gamma correction used for text
    bool hasGammaCorrection;
    // Set when sampling an image in linear space
    bool hasLinearTexture;

    /**
     * Resets this description. All fields are reset back to the default
@@ -205,6 +209,7 @@ struct ProgramDescription {
        hasRoundRectClip = false;

        hasGammaCorrection = false;
        hasLinearTexture = false;
    }

    /**
@@ -275,6 +280,7 @@ struct ProgramDescription {
        if (hasDebugHighlight) key |= programid(0x1) << PROGRAM_HAS_DEBUG_HIGHLIGHT;
        if (hasRoundRectClip) key |= programid(0x1) << PROGRAM_HAS_ROUND_RECT_CLIP;
        if (hasGammaCorrection) key |= programid(0x1) << PROGRAM_HAS_GAMMA_CORRECTION;
        if (hasLinearTexture) key |= programid(0x1) << PROGRAM_HAS_LINEAR_TEXTURE;
        return key;
    }

+41 −133
Original line number Diff line number Diff line
@@ -161,9 +161,35 @@ const char* gFS_Uniforms_HasRoundRectClip =
        "uniform vec4 roundRectInnerRectLTRB;\n"
        "uniform float roundRectRadius;\n";

const char* gFS_OETF[2] = {
         "\nvec4 OETF(const vec4 linear) {\n"
         "    return linear;\n"
         "}\n",
          // We expect linear data to be scRGB so we mirror the gamma function
         "\nvec4 OETF(const vec4 linear) {"
          "    return vec4(sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)), linear.a);\n"
          "}\n",
};

const char* gFS_Transfer_Functions = R"__SHADER__(
        float OETF_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 OETF_sRGB(const vec3 linear) {
            return vec3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
        }

        float EOTF_sRGB(float srgb) {
            // IEC 61966-2-1:1999
            return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
        }
)__SHADER__";

// 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)
//     EOTF(OETF(color) + dither)
// 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 = R"__SHADER__(
@@ -173,20 +199,6 @@ const char* gFS_Gradient_Functions = R"__SHADER__(
            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
@@ -195,8 +207,8 @@ const char* gFS_Gradient_Preamble[2] = {
        "}\n"
        "\nvec4 gammaMix(const vec4 a, const vec4 b, float v) {\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"
        "    c.a = EOTF_sRGB(c.a);\n" // This is technically incorrect but preserves compatibility
        "    return vec4(OETF_sRGB(c.rgb) * c.a, c.a);\n"
        "}\n",
        // sRGB framebuffer
        "\nvec4 dither(const vec4 color) {\n"
@@ -232,52 +244,6 @@ const char* gFS_Main =
const char* gFS_Main_AddDither =
        "    fragColor = dither(fragColor);\n";

// Fast cases
const char* gFS_Fast_SingleColor =
        "\nvoid main(void) {\n"
        "    gl_FragColor = color;\n"
        "}\n\n";
const char* gFS_Fast_SingleTexture =
        "\nvoid main(void) {\n"
        "    gl_FragColor = texture2D(baseSampler, outTexCoords);\n"
        "}\n\n";
const char* gFS_Fast_SingleModulateTexture =
        "\nvoid main(void) {\n"
        "    gl_FragColor = color.a * texture2D(baseSampler, outTexCoords);\n"
        "}\n\n";
const char* gFS_Fast_SingleA8Texture =
        "\nvoid main(void) {\n"
        "    gl_FragColor = texture2D(baseSampler, outTexCoords);\n"
        "}\n\n";
const char* gFS_Fast_SingleA8Texture_ApplyGamma =
        "\nvoid main(void) {\n"
        "    gl_FragColor = vec4(0.0, 0.0, 0.0, pow(texture2D(baseSampler, outTexCoords).a, GAMMA));\n"
        "}\n\n";
const char* gFS_Fast_SingleModulateA8Texture =
        "\nvoid main(void) {\n"
        "    gl_FragColor = color * texture2D(baseSampler, outTexCoords).a;\n"
        "}\n\n";
const char* gFS_Fast_SingleModulateA8Texture_ApplyGamma =
        "\nvoid main(void) {\n"
        "    gl_FragColor = color * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n"
        "}\n\n";
const char* gFS_Fast_SingleGradient[2] = {
        "\nvoid main(void) {\n"
        "    gl_FragColor = dither(texture2D(gradientSampler, linear));\n"
        "}\n\n",
        "\nvoid main(void) {\n"
        "    gl_FragColor = dither(gammaMix(startColor, endColor, clamp(linear, 0.0, 1.0)));\n"
        "}\n\n",
};
const char* gFS_Fast_SingleModulateGradient[2] = {
        "\nvoid main(void) {\n"
        "    gl_FragColor = dither(color.a * texture2D(gradientSampler, linear));\n"
        "}\n\n",
        "\nvoid main(void) {\n"
        "    gl_FragColor = dither(color.a * gammaMix(startColor, endColor, clamp(linear, 0.0, 1.0)));\n"
        "}\n\n"
};

// General case
const char* gFS_Main_FetchColor =
        "    fragColor = color;\n";
@@ -290,7 +256,7 @@ const char* gFS_Main_ApplyVertexAlphaShadowInterp =
        "    fragColor *= texture2D(baseSampler, vec2(alpha, 0.5)).a;\n";
const char* gFS_Main_FetchTexture[2] = {
        // Don't modulate
        "    fragColor = texture2D(baseSampler, outTexCoords);\n",
        "    fragColor = OETF(texture2D(baseSampler, outTexCoords));\n",
        // Modulate
        "    fragColor = color * texture2D(baseSampler, outTexCoords);\n"
};
@@ -321,9 +287,9 @@ const char* gFS_Main_FetchGradient[6] = {
        "    vec4 gradientColor = gammaMix(startColor, endColor, clamp(index - floor(index), 0.0, 1.0));\n"
};
const char* gFS_Main_FetchBitmap =
        "    vec4 bitmapColor = texture2D(bitmapSampler, outBitmapTexCoords);\n";
        "    vec4 bitmapColor = OETF(texture2D(bitmapSampler, outBitmapTexCoords));\n";
const char* gFS_Main_FetchBitmapNpot =
        "    vec4 bitmapColor = texture2D(bitmapSampler, wrap(outBitmapTexCoords));\n";
        "    vec4 bitmapColor = OETF(texture2D(bitmapSampler, wrap(outBitmapTexCoords)));\n";
const char* gFS_Main_BlendShadersBG =
        "    fragColor = blendShaders(gradientColor, bitmapColor)";
const char* gFS_Main_BlendShadersGB =
@@ -649,71 +615,6 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
        shader.appendFormat(gFS_Gamma_Preamble, Properties::textGamma, 1.0f / Properties::textGamma);
    }

    // Optimization for common cases
    if (!description.hasVertexAlpha
            && !blendFramebuffer
            && !description.hasColors
            && description.colorOp == ProgramDescription::ColorFilterMode::None
            && !description.hasDebugHighlight
            && !description.hasRoundRectClip) {
        bool fast = false;

        const bool noShader = !description.hasGradient && !description.hasBitmap;
        const bool singleTexture = (description.hasTexture || description.hasExternalTexture) &&
                !description.hasAlpha8Texture && noShader;
        const bool singleA8Texture = description.hasTexture &&
                description.hasAlpha8Texture && noShader;
        const bool singleGradient = !description.hasTexture && !description.hasExternalTexture &&
                description.hasGradient && !description.hasBitmap &&
                description.gradientType == ProgramDescription::kGradientLinear;

        if (singleColor) {
            shader.append(gFS_Fast_SingleColor);
            fast = true;
        } else if (singleTexture) {
            if (!description.modulate) {
                shader.append(gFS_Fast_SingleTexture);
            } else {
                shader.append(gFS_Fast_SingleModulateTexture);
            }
            fast = true;
        } else if (singleA8Texture) {
            if (!description.modulate) {
                if (description.hasGammaCorrection) {
                    shader.append(gFS_Fast_SingleA8Texture_ApplyGamma);
                } else {
                    shader.append(gFS_Fast_SingleA8Texture);
                }
            } else {
                if (description.hasGammaCorrection) {
                    shader.append(gFS_Fast_SingleModulateA8Texture_ApplyGamma);
                } else {
                    shader.append(gFS_Fast_SingleModulateA8Texture);
                }
            }
            fast = true;
        } else if (singleGradient) {
            shader.append(gFS_Gradient_Functions);
            shader.append(gFS_Gradient_Preamble[mHasSRGB]);
            if (!description.modulate) {
                shader.append(gFS_Fast_SingleGradient[description.isSimpleGradient]);
            } else {
                shader.append(gFS_Fast_SingleModulateGradient[description.isSimpleGradient]);
            }
            fast = true;
        }

        if (fast) {
#if DEBUG_PROGRAMS
                PROGRAM_LOGD("*** Fast case:\n");
                PROGRAM_LOGD("*** Generated fragment shader:\n\n");
                printLongString(shader);
#endif

            return shader;
        }
    }

    if (description.hasBitmap) {
        if (description.isShaderBitmapExternal) {
            shader.append(gFS_Uniforms_BitmapExternalSampler);
@@ -736,6 +637,13 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
    if (description.useShaderBasedWrap) {
        generateTextureWrap(shader, description.bitmapWrapS, description.bitmapWrapT);
    }
    if (description.hasGradient || description.hasLinearTexture) {
        shader.append(gFS_Transfer_Functions);
    }
    if (description.hasBitmap || ((description.hasTexture || description.hasExternalTexture) &&
            !description.hasAlpha8Texture)) {
        shader.append(gFS_OETF[description.hasLinearTexture && !mHasSRGB]);
    }
    if (description.hasGradient) {
        shader.append(gFS_Gradient_Functions);
        shader.append(gFS_Gradient_Preamble[mHasSRGB]);
@@ -827,10 +735,10 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
    // End the shader
    shader.append(gFS_Footer);

#if DEBUG_PROGRAMS
//#if DEBUG_PROGRAMS
        PROGRAM_LOGD("*** Generated fragment shader:\n\n");
        printLongString(shader);
#endif
//#endif

    return shader;
}
+4 −0
Original line number Diff line number Diff line
@@ -48,6 +48,10 @@ static int bytesPerPixel(GLint glFormat) {
    }
}

bool Texture::isLinear() const {
    return mInternalFormat == GL_RGBA16F;
}

void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force) {

    if (force || wrapS != mWrapS || wrapT != mWrapT) {
+5 −0
Original line number Diff line number Diff line
@@ -127,6 +127,11 @@ public:
        return mTarget;
    }

    /**
     * Returns true if this texture uses a linear encoding format.
     */
    bool isLinear() const;

    /**
     * Generation of the backing bitmap,
     */