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

Commit 42e1e0d4 authored by Romain Guy's avatar Romain Guy
Browse files

Improve gradients

Avoid using textures for common gradients (two stops from 0.0 to 1.0)

Change-Id: Iff55d21b126c8cfc4cfb701669f2339c8f6b131a
parent 8ab8fbbf
Loading
Loading
Loading
Loading
+38 −8
Original line number Diff line number Diff line
@@ -150,19 +150,35 @@ static SkiaShader* LinearGradient_postCreate1(JNIEnv* env, jobject o, SkShader*
    jfloat* storedBounds = new jfloat[4];
    storedBounds[0] = x0; storedBounds[1] = y0;
    storedBounds[2] = x1; storedBounds[3] = y1;
    jfloat* storedPositions = new jfloat[count];
    uint32_t* storedColors = new uint32_t[count];
    for (size_t i = 0; i < count; i++) {
        storedColors[i] = static_cast<uint32_t>(colorValues[i]);
    }
    
    bool missFirst = false;
    bool missLast = false;
    size_t stopCount = count;

    jfloat* storedPositions = NULL;
    if (posArray) {
        AutoJavaFloatArray autoPos(env, posArray, count);
        const float* posValues = autoPos.ptr();
        for (size_t i = 0; i < count; i++) {
            storedPositions[i] = posValues[i];

        missFirst = posValues[0] != 0.0f;
        missLast = posValues[count - 1] != 1.0f;

        stopCount += missFirst + missLast;
        storedPositions = new jfloat[stopCount];

        if (missFirst) {
            storedPositions[0] = 0.0f;
        }

        for (size_t i = missFirst; i < count + missFirst; i++) {
            storedPositions[i] = posValues[i - missFirst];
        }

        if (missLast) {
            storedPositions[stopCount - 1] = 1.0f;
        }
    } else {
        storedPositions = new jfloat[count];
        storedPositions[0] = 0.0f;
        const jfloat step = 1.0f / (count - 1);
        for (size_t i = 1; i < count - 1; i++) {
@@ -171,8 +187,22 @@ static SkiaShader* LinearGradient_postCreate1(JNIEnv* env, jobject o, SkShader*
        storedPositions[count - 1] = 1.0f;
    }

    uint32_t* storedColors = new uint32_t[stopCount];

    if (missFirst) {
        storedColors[0] = static_cast<uint32_t>(colorValues[0]);
    }

    for (size_t i = missFirst; i < count + missFirst; i++) {
        storedColors[i] = static_cast<uint32_t>(colorValues[i - missFirst]);
    }

    if (missLast) {
        storedColors[stopCount - 1] = static_cast<uint32_t>(colorValues[count - 1]);
    }

    SkiaShader* skiaShader = new SkiaLinearGradientShader(storedBounds, storedColors,
            storedPositions, count, shader, static_cast<SkShader::TileMode>(tileMode), NULL,
            storedPositions, stopCount, shader, static_cast<SkShader::TileMode>(tileMode), NULL,
            (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);

    env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT);
+98 −42
Original line number Diff line number Diff line
@@ -16,9 +16,6 @@

#define LOG_TAG "OpenGLRenderer"

#include <SkCanvas.h>
#include <SkGradientShader.h>

#include <utils/threads.h>

#include "Debug.h"
@@ -28,6 +25,22 @@
namespace android {
namespace uirenderer {

///////////////////////////////////////////////////////////////////////////////
// Defines
///////////////////////////////////////////////////////////////////////////////

#define GRADIENT_TEXTURE_HEIGHT 2
#define GRADIENT_BYTES_PER_PIXEL 4

///////////////////////////////////////////////////////////////////////////////
// Functions
///////////////////////////////////////////////////////////////////////////////

template<typename T>
static inline T min(T a, T b) {
    return a < b ? a : b;
}

///////////////////////////////////////////////////////////////////////////////
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
@@ -83,7 +96,7 @@ void GradientCache::setMaxSize(uint32_t maxSize) {

void GradientCache::operator()(GradientCacheEntry& shader, Texture*& texture) {
    if (texture) {
        const uint32_t size = texture->width * texture->height * 4;
        const uint32_t size = texture->width * texture->height * GRADIENT_BYTES_PER_PIXEL;
        mSize -= size;
    }

@@ -97,14 +110,13 @@ void GradientCache::operator()(GradientCacheEntry& shader, Texture*& texture) {
// Caching
///////////////////////////////////////////////////////////////////////////////

Texture* GradientCache::get(uint32_t* colors, float* positions,
        int count, SkShader::TileMode tileMode) {
Texture* GradientCache::get(uint32_t* colors, float* positions, int count) {

    GradientCacheEntry gradient(colors, positions, count, tileMode);
    GradientCacheEntry gradient(colors, positions, count);
    Texture* texture = mCache.get(gradient);

    if (!texture) {
        texture = addLinearGradient(gradient, colors, positions, count, tileMode);
        texture = addLinearGradient(gradient, colors, positions, count);
    }

    return texture;
@@ -114,39 +126,41 @@ void GradientCache::clear() {
    mCache.clear();
}

Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient,
        uint32_t* colors, float* positions, int count, SkShader::TileMode tileMode) {
    int width = 256 * (count - 1);
    width = width < mMaxTextureSize ? width : mMaxTextureSize;

    SkBitmap bitmap;
    bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, 4);
    bitmap.allocPixels();
    bitmap.eraseColor(0);
void GradientCache::getGradientInfo(const uint32_t* colors, const int count,
        GradientInfo& info) {
    uint32_t width = 1 << (31 - __builtin_clz(256 * (count - 1)));
    bool hasAlpha = false;

    SkCanvas canvas(bitmap);
    for (int i = 0; i < count; i++) {
        if (((colors[i] >> 24) & 0xff) < 255) {
            hasAlpha = true;
            break;
        }
    }

    SkPoint points[2];
    points[0].set(0.0f, 0.0f);
    points[1].set(bitmap.width(), 0.0f);
    info.width = min(width, uint32_t(mMaxTextureSize));
    info.hasAlpha = hasAlpha;
}

    SkShader* localShader = SkGradientShader::CreateLinear(points,
            reinterpret_cast<const SkColor*>(colors), positions, count, tileMode);
Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient,
        uint32_t* colors, float* positions, int count) {

    SkPaint p;
    p.setStyle(SkPaint::kStrokeAndFill_Style);
    p.setShader(localShader)->unref();
    GradientInfo info;
    getGradientInfo(colors, count, info);

    canvas.drawRectCoords(0.0f, 0.0f, bitmap.width(), 4.0f, p);
    Texture* texture = new Texture;
    texture->width = info.width;
    texture->height = GRADIENT_TEXTURE_HEIGHT;
    texture->blend = info.hasAlpha;
    texture->generation = 1;

    // Asume the cache is always big enough
    const uint32_t size = bitmap.rowBytes() * bitmap.height();
    const uint32_t size = texture->width * texture->height * GRADIENT_BYTES_PER_PIXEL;
    while (mSize + size > mMaxSize) {
        mCache.removeOldest();
    }

    Texture* texture = new Texture;
    generateTexture(&bitmap, texture);
    generateTexture(colors, positions, count, texture);

    mSize += size;
    mCache.put(gradient, texture);
@@ -154,25 +168,67 @@ Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient,
    return texture;
}

void GradientCache::generateTexture(SkBitmap* bitmap, Texture* texture) {
    SkAutoLockPixels autoLock(*bitmap);
    if (!bitmap->readyToDraw()) {
        ALOGE("Cannot generate texture from shader");
        return;
void GradientCache::generateTexture(uint32_t* colors, float* positions,
        int count, Texture* texture) {

    const uint32_t width = texture->width;
    const GLsizei rowBytes = width * GRADIENT_BYTES_PER_PIXEL;
    uint32_t pixels[width * texture->height];

    int currentPos = 1;

    float startA = (colors[0] >> 24) & 0xff;
    float startR = (colors[0] >> 16) & 0xff;
    float startG = (colors[0] >>  8) & 0xff;
    float startB = (colors[0] >>  0) & 0xff;

    float endA = (colors[1] >> 24) & 0xff;
    float endR = (colors[1] >> 16) & 0xff;
    float endG = (colors[1] >>  8) & 0xff;
    float endB = (colors[1] >>  0) & 0xff;

    float start = positions[0];
    float distance = positions[1] - start;

    uint8_t* p = (uint8_t*) pixels;
    for (uint32_t x = 0; x < width; x++) {
        float pos = x / float(width - 1);
        if (pos > positions[currentPos]) {
            startA = endA;
            startR = endR;
            startG = endG;
            startB = endB;
            start = positions[currentPos];

            currentPos++;

            endA = (colors[currentPos] >> 24) & 0xff;
            endR = (colors[currentPos] >> 16) & 0xff;
            endG = (colors[currentPos] >>  8) & 0xff;
            endB = (colors[currentPos] >>  0) & 0xff;
            distance = positions[currentPos] - start;
        }

    texture->generation = bitmap->getGenerationID();
    texture->width = bitmap->width();
    texture->height = bitmap->height();
        float amount = (pos - start) / distance;
        float oppAmount = 1.0f - amount;

        *p++ = uint8_t(startR * oppAmount + endR * amount);
        *p++ = uint8_t(startG * oppAmount + endG * amount);
        *p++ = uint8_t(startB * oppAmount + endB * amount);
        *p++ = uint8_t(startA * oppAmount + endA * amount);
    }

    for (int i = 1; i < GRADIENT_TEXTURE_HEIGHT; i++) {
        memcpy(pixels + width * i, pixels, rowBytes);
    }

    glGenTextures(1, &texture->id);

    glBindTexture(GL_TEXTURE_2D, texture->id);
    glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
    glPixelStorei(GL_UNPACK_ALIGNMENT, GRADIENT_BYTES_PER_PIXEL);

    texture->blend = !bitmap->isOpaque();
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap->rowBytesAsPixels(), texture->height, 0,
            GL_RGBA, GL_UNSIGNED_BYTE, bitmap->getPixels());
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, texture->height, 0,
            GL_RGBA, GL_UNSIGNED_BYTE, pixels);

    texture->setFilter(GL_LINEAR);
    texture->setWrap(GL_CLAMP_TO_EDGE);
+21 −20
Original line number Diff line number Diff line
@@ -36,16 +36,14 @@ struct GradientCacheEntry {
        count = 0;
        colors = NULL;
        positions = NULL;
        tileMode = SkShader::kClamp_TileMode;
    }

    GradientCacheEntry(uint32_t* colors, float* positions, int count,
            SkShader::TileMode tileMode) {
        copy(colors, positions, count, tileMode);
    GradientCacheEntry(uint32_t* colors, float* positions, int count) {
        copy(colors, positions, count);
    }

    GradientCacheEntry(const GradientCacheEntry& entry) {
        copy(entry.colors, entry.positions, entry.count, entry.tileMode);
        copy(entry.colors, entry.positions, entry.count);
    }

    ~GradientCacheEntry() {
@@ -58,7 +56,7 @@ struct GradientCacheEntry {
            delete[] colors;
            delete[] positions;

            copy(entry.colors, entry.positions, entry.count, entry.tileMode);
            copy(entry.colors, entry.positions, entry.count);
        }

        return *this;
@@ -67,7 +65,6 @@ struct GradientCacheEntry {
    bool operator<(const GradientCacheEntry& r) const {
        const GradientCacheEntry& rhs = (const GradientCacheEntry&) r;
        LTE_INT(count) {
            LTE_INT(tileMode) {
            int result = memcmp(colors, rhs.colors, count * sizeof(uint32_t));
            if (result< 0) return true;
            else if (result == 0) {
@@ -75,7 +72,6 @@ struct GradientCacheEntry {
                if (result < 0) return true;
            }
        }
        }
        return false;
    }

@@ -86,11 +82,10 @@ struct GradientCacheEntry {

private:

    void copy(uint32_t* colors, float* positions, int count, SkShader::TileMode tileMode) {
    void copy(uint32_t* colors, float* positions, int count) {
        this->count = count;
        this->colors = new uint32_t[count];
        this->positions = new float[count];
        this->tileMode = tileMode;

        memcpy(this->colors, colors, count * sizeof(uint32_t));
        memcpy(this->positions, positions, count * sizeof(float));
@@ -118,8 +113,8 @@ public:
    /**
     * Returns the texture associated with the specified shader.
     */
    Texture* get(uint32_t* colors, float* positions,
            int count, SkShader::TileMode tileMode = SkShader::kClamp_TileMode);
    Texture* get(uint32_t* colors, float* positions, int count);

    /**
     * Clears the cache. This causes all textures to be deleted.
     */
@@ -144,10 +139,16 @@ private:
     * returned.
     */
    Texture* addLinearGradient(GradientCacheEntry& gradient,
            uint32_t* colors, float* positions, int count,
            SkShader::TileMode tileMode = SkShader::kClamp_TileMode);
            uint32_t* colors, float* positions, int count);

    void generateTexture(uint32_t* colors, float* positions, int count, Texture* texture);

    struct GradientInfo {
        uint32_t width;
        bool hasAlpha;
    };

    void generateTexture(SkBitmap* bitmap, Texture* texture);
    void getGradientInfo(const uint32_t* colors, const int count, GradientInfo& info);

    GenerationCache<GradientCacheEntry, Texture*> mCache;

+7 −2
Original line number Diff line number Diff line
@@ -79,6 +79,8 @@ namespace uirenderer {

#define PROGRAM_HAS_GAMMA_CORRECTION 40

#define PROGRAM_IS_SIMPLE_GRADIENT 41

///////////////////////////////////////////////////////////////////////////////
// Types
///////////////////////////////////////////////////////////////////////////////
@@ -96,14 +98,14 @@ typedef uint64_t programid;
 */
struct ProgramDescription {
    enum ColorModifier {
        kColorNone,
        kColorNone = 0,
        kColorMatrix,
        kColorLighting,
        kColorBlend
    };

    enum Gradient {
        kGradientLinear,
        kGradientLinear = 0,
        kGradientCircular,
        kGradientSweep
    };
@@ -129,6 +131,7 @@ struct ProgramDescription {

    bool hasGradient;
    Gradient gradientType;
    bool isSimpleGradient;

    SkXfermode::Mode shadersMode;

@@ -170,6 +173,7 @@ struct ProgramDescription {

        hasGradient = false;
        gradientType = kGradientLinear;
        isSimpleGradient = false;

        shadersMode = SkXfermode::kClear_Mode;

@@ -255,6 +259,7 @@ struct ProgramDescription {
        if (hasExternalTexture) key |= programid(0x1) << PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT;
        if (hasTextureTransform) key |= programid(0x1) << PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT;
        if (hasGammaCorrection) key |= programid(0x1) << PROGRAM_HAS_GAMMA_CORRECTION;
        if (isSimpleGradient) key |= programid(0x1) << PROGRAM_IS_SIMPLE_GRADIENT;
        return key;
    }

+49 −23
Original line number Diff line number Diff line
@@ -71,13 +71,16 @@ const char* gVS_Header_Varyings_PointHasBitmap =
        "varying highp vec2 outPointBitmapTexCoords;\n";
// TODO: These values are used to sample from textures,
//       they may need to be highp
const char* gVS_Header_Varyings_HasGradient[3] = {
const char* gVS_Header_Varyings_HasGradient[6] = {
        // Linear
        "varying highp vec2 linear;\n",
        "varying highp float linear;\n",
        // Circular
        "varying highp vec2 circular;\n",
        "varying highp vec2 circular;\n",
        // Sweep
        "varying highp vec2 sweep;\n"
        "varying highp vec2 sweep;\n",
        "varying highp vec2 sweep;\n",
};
const char* gVS_Main =
        "\nvoid main(void) {\n";
@@ -85,13 +88,16 @@ const char* gVS_Main_OutTexCoords =
        "    outTexCoords = texCoords;\n";
const char* gVS_Main_OutTransformedTexCoords =
        "    outTexCoords = (mainTextureTransform * vec4(texCoords, 0.0, 1.0)).xy;\n";
const char* gVS_Main_OutGradient[3] = {
const char* gVS_Main_OutGradient[6] = {
        // Linear
        "    linear = vec2((screenSpace * position).x, 0.5);\n",
        "    linear = (screenSpace * position).x;\n",
        // Circular
        "    circular = (screenSpace * position).xy;\n",
        "    circular = (screenSpace * position).xy;\n",
        // Sweep
        "    sweep = (screenSpace * position).xy;\n"
        "    sweep = (screenSpace * position).xy;\n",
        "    sweep = (screenSpace * position).xy;\n",
};
const char* gVS_Main_OutBitmapTexCoords =
        "    outBitmapTexCoords = (textureTransform * position).xy * textureDimension;\n";
@@ -131,13 +137,19 @@ const char* gFS_Uniforms_TextureSampler =
        "uniform sampler2D sampler;\n";
const char* gFS_Uniforms_ExternalTextureSampler =
        "uniform samplerExternalOES sampler;\n";
const char* gFS_Uniforms_GradientSampler[3] = {
const char* gFS_Uniforms_GradientSampler[6] = {
        // Linear
        "uniform sampler2D gradientSampler;\n",
        "uniform vec4 startColor;\n"
        "uniform vec4 endColor;\n",
        // Circular
        "uniform sampler2D gradientSampler;\n",
        "uniform vec4 startColor;\n"
        "uniform vec4 endColor;\n",
        // Sweep
        "uniform sampler2D gradientSampler;\n"
        "uniform sampler2D gradientSampler;\n",
        "uniform vec4 startColor;\n"
        "uniform vec4 endColor;\n",
};
const char* gFS_Uniforms_BitmapSampler =
        "uniform sampler2D bitmapSampler;\n";
@@ -193,14 +205,22 @@ const char* gFS_Fast_SingleModulateA8Texture_ApplyGamma =
        "\nvoid main(void) {\n"
        "    gl_FragColor = color * pow(texture2D(sampler, outTexCoords).a, gamma);\n"
        "}\n\n";
const char* gFS_Fast_SingleGradient =
const char* gFS_Fast_SingleGradient[2] = {
        "\nvoid main(void) {\n"
        "    gl_FragColor = texture2D(gradientSampler, linear);\n"
        "}\n\n";
const char* gFS_Fast_SingleModulateGradient =
        "}\n\n",
        "\nvoid main(void) {\n"
        "    gl_FragColor = mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n"
        "}\n\n"
};
const char* gFS_Fast_SingleModulateGradient[2] = {
        "\nvoid main(void) {\n"
        "    gl_FragColor = color.a * texture2D(gradientSampler, linear);\n"
        "}\n\n";
        "}\n\n",
        "\nvoid main(void) {\n"
        "    gl_FragColor = color.a * mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n"
        "}\n\n"
};

// General case
const char* gFS_Main_FetchColor =
@@ -232,15 +252,18 @@ const char* gFS_Main_FetchA8Texture[2] = {
        // Modulate
        "    fragColor = color * texture2D(sampler, outTexCoords).a;\n"
};
const char* gFS_Main_FetchGradient[3] = {
const char* gFS_Main_FetchGradient[6] = {
        // Linear
        "    vec4 gradientColor = texture2D(gradientSampler, linear);\n",
        "    vec4 gradientColor = mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n",
        // Circular
        "    highp float index = length(circular);\n"
        "    vec4 gradientColor = texture2D(gradientSampler, vec2(index, 0.5));\n",
        "    vec4 gradientColor = texture2D(gradientSampler, vec2(length(circular), 0.5));\n",
        "    vec4 gradientColor = mix(startColor, endColor, clamp(length(circular), 0.0, 1.0));\n",
        // Sweep
        "    highp float index = atan(sweep.y, sweep.x) * 0.15915494309; // inv(2 * PI)\n"
        "    vec4 gradientColor = texture2D(gradientSampler, vec2(index - floor(index), 0.5));\n"
        "    vec4 gradientColor = texture2D(gradientSampler, vec2(index - floor(index), 0.5));\n",
        "    highp float index = atan(sweep.y, sweep.x) * 0.15915494309; // inv(2 * PI)\n"
        "    vec4 gradientColor = mix(startColor, endColor, clamp(index - floor(index), 0.0, 1.0));\n"
};
const char* gFS_Main_FetchBitmap =
        "    vec4 bitmapColor = texture2D(bitmapSampler, outBitmapTexCoords);\n";
@@ -395,8 +418,11 @@ Program* ProgramCache::generateProgram(const ProgramDescription& description, pr
    String8 vertexShader = generateVertexShader(description);
    String8 fragmentShader = generateFragmentShader(description);

    Program* program = new Program(description, vertexShader.string(), fragmentShader.string());
    return program;
    return new Program(description, vertexShader.string(), fragmentShader.string());
}

static inline size_t gradientIndex(const ProgramDescription& description) {
    return description.gradientType * 2 + description.isSimpleGradient;
}

String8 ProgramCache::generateVertexShader(const ProgramDescription& description) {
@@ -430,7 +456,7 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
        shader.append(gVS_Header_Varyings_IsAA);
    }
    if (description.hasGradient) {
        shader.append(gVS_Header_Varyings_HasGradient[description.gradientType]);
        shader.append(gVS_Header_Varyings_HasGradient[gradientIndex(description)]);
    }
    if (description.hasBitmap) {
        shader.append(description.isPoint ?
@@ -449,7 +475,7 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
            shader.append(gVS_Main_AA);
        }
        if (description.hasGradient) {
            shader.append(gVS_Main_OutGradient[description.gradientType]);
            shader.append(gVS_Main_OutGradient[gradientIndex(description)]);
        }
        if (description.hasBitmap) {
            shader.append(description.isPoint ?
@@ -491,7 +517,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
        shader.append(gVS_Header_Varyings_IsAA);
    }
    if (description.hasGradient) {
        shader.append(gVS_Header_Varyings_HasGradient[description.gradientType]);
        shader.append(gVS_Header_Varyings_HasGradient[gradientIndex(description)]);
    }
    if (description.hasBitmap) {
        shader.append(description.isPoint ?
@@ -517,7 +543,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
        shader.append(gFS_Uniforms_AA);
    }
    if (description.hasGradient) {
        shader.append(gFS_Uniforms_GradientSampler[description.gradientType]);
        shader.append(gFS_Uniforms_GradientSampler[gradientIndex(description)]);
    }
    if (description.hasBitmap && description.isPoint) {
        shader.append(gFS_Header_Uniforms_PointHasBitmap);
@@ -567,9 +593,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
            fast = true;
        } else if (singleGradient) {
            if (!description.modulate) {
                shader.append(gFS_Fast_SingleGradient);
                shader.append(gFS_Fast_SingleGradient[description.isSimpleGradient]);
            } else {
                shader.append(gFS_Fast_SingleModulateGradient);
                shader.append(gFS_Fast_SingleModulateGradient[description.isSimpleGradient]);
            }
            fast = true;
        }
@@ -624,7 +650,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
            shader.append(gFS_Main_AccountForAA);
        }
        if (description.hasGradient) {
            shader.append(gFS_Main_FetchGradient[description.gradientType]);
            shader.append(gFS_Main_FetchGradient[gradientIndex(description)]);
        }
        if (description.hasBitmap) {
            if (description.isPoint) {
Loading