summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/res/blend.glsl
blob: 2deed011435756abdbc30e33f666bebee453a901 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#define COMPONENT_TRANSFER_IDENTITY 0
#define COMPONENT_TRANSFER_TABLE 1
#define COMPONENT_TRANSFER_DISCRETE 2
#define COMPONENT_TRANSFER_LINEAR 3
#define COMPONENT_TRANSFER_GAMMA 4

// Must be kept in sync with `Filter::as_int` in internal_types.rs
// Not all filters are defined here because some filter use different shaders.
#define FILTER_CONTRAST            0
#define FILTER_GRAYSCALE           1
#define FILTER_HUE_ROTATE          2
#define FILTER_INVERT              3
#define FILTER_SATURATE            4
#define FILTER_SEPIA               5
#define FILTER_BRIGHTNESS          6
#define FILTER_COLOR_MATRIX        7
#define FILTER_SRGB_TO_LINEAR      8
#define FILTER_LINEAR_TO_SRGB      9
#define FILTER_FLOOD               10
#define FILTER_COMPONENT_TRANSFER  11

#ifdef WR_VERTEX_SHADER
void SetupFilterParams(
    int op,
    float amount,
    int gpu_data_address,
    out vec4 color_offset,
    out mat4 color_mat,
    out highp int table_address
) {
    float lumR = 0.2126;
    float lumG = 0.7152;
    float lumB = 0.0722;
    float oneMinusLumR = 1.0 - lumR;
    float oneMinusLumG = 1.0 - lumG;
    float oneMinusLumB = 1.0 - lumB;
    float invAmount = 1.0 - amount;

    if (op == FILTER_GRAYSCALE) {
        color_mat = mat4(
            vec4(lumR + oneMinusLumR * invAmount, lumR - lumR * invAmount, lumR - lumR * invAmount, 0.0),
            vec4(lumG - lumG * invAmount, lumG + oneMinusLumG * invAmount, lumG - lumG * invAmount, 0.0),
            vec4(lumB - lumB * invAmount, lumB - lumB * invAmount, lumB + oneMinusLumB * invAmount, 0.0),
            vec4(0.0, 0.0, 0.0, 1.0)
        );
        color_offset = vec4(0.0);
    } else if (op ==  FILTER_HUE_ROTATE) {
        float c = cos(amount);
        float s = sin(amount);
        color_mat = mat4(
            vec4(lumR + oneMinusLumR * c - lumR * s, lumR - lumR * c + 0.143 * s, lumR - lumR * c - oneMinusLumR * s, 0.0),
            vec4(lumG - lumG * c - lumG * s, lumG + oneMinusLumG * c + 0.140 * s, lumG - lumG * c + lumG * s, 0.0),
            vec4(lumB - lumB * c + oneMinusLumB * s, lumB - lumB * c - 0.283 * s, lumB + oneMinusLumB * c + lumB * s, 0.0),
            vec4(0.0, 0.0, 0.0, 1.0)
        );
        color_offset = vec4(0.0);
    } else if (op ==   FILTER_SATURATE) {
        color_mat = mat4(
            vec4(invAmount * lumR + amount, invAmount * lumR, invAmount * lumR, 0.0),
            vec4(invAmount * lumG, invAmount * lumG + amount, invAmount * lumG, 0.0),
            vec4(invAmount * lumB, invAmount * lumB, invAmount * lumB + amount, 0.0),
            vec4(0.0, 0.0, 0.0, 1.0)
        );
        color_offset = vec4(0.0);
    } else if (op == FILTER_SEPIA) {
        color_mat = mat4(
            vec4(0.393 + 0.607 * invAmount, 0.349 - 0.349 * invAmount, 0.272 - 0.272 * invAmount, 0.0),
            vec4(0.769 - 0.769 * invAmount, 0.686 + 0.314 * invAmount, 0.534 - 0.534 * invAmount, 0.0),
            vec4(0.189 - 0.189 * invAmount, 0.168 - 0.168 * invAmount, 0.131 + 0.869 * invAmount, 0.0),
            vec4(0.0, 0.0, 0.0, 1.0)
        );
        color_offset = vec4(0.0);
    } else if (op == FILTER_COLOR_MATRIX) {
        vec4 mat_data[4] = fetch_from_gpu_cache_4(gpu_data_address);
        vec4 offset_data = fetch_from_gpu_cache_1(gpu_data_address + 4);
        color_mat = mat4(mat_data[0], mat_data[1], mat_data[2], mat_data[3]);
        color_offset = offset_data;
    } else if (op == FILTER_COMPONENT_TRANSFER) {
        table_address = gpu_data_address;
    } else if (op == FILTER_FLOOD) {
        color_offset = fetch_from_gpu_cache_1(gpu_data_address);
    }
}
#endif

#ifdef WR_FRAGMENT_SHADER
vec3 Contrast(vec3 Cs, float amount) {
    return clamp(Cs.rgb * amount - 0.5 * amount + 0.5, 0.0, 1.0);
}

vec3 Invert(vec3 Cs, float amount) {
    return mix(Cs.rgb, vec3(1.0) - Cs.rgb, amount);
}

vec3 Brightness(vec3 Cs, float amount) {
    // Apply the brightness factor.
    // Resulting color needs to be clamped to output range
    // since we are pre-multiplying alpha in the shader.
    return clamp(Cs.rgb * amount, vec3(0.0), vec3(1.0));
}

// Based on the Gecko's implementation in
// https://hg.mozilla.org/mozilla-central/file/91b4c3687d75/gfx/src/FilterSupport.cpp#l24
// These could be made faster by sampling a lookup table stored in a float texture
// with linear interpolation.

vec3 SrgbToLinear(vec3 color) {
    vec3 c1 = color / 12.92;
    vec3 c2 = pow(color / 1.055 + vec3(0.055 / 1.055), vec3(2.4));
    return if_then_else(lessThanEqual(color, vec3(0.04045)), c1, c2);
}

vec3 LinearToSrgb(vec3 color) {
    vec3 c1 = color * 12.92;
    vec3 c2 = vec3(1.055) * pow(color, vec3(1.0 / 2.4)) - vec3(0.055);
    return if_then_else(lessThanEqual(color, vec3(0.0031308)), c1, c2);
}

// This function has to be factored out due to the following issue:
// https://github.com/servo/webrender/wiki/Driver-issues#bug-1532245---switch-statement-inside-control-flow-inside-switch-statement-fails-to-compile-on-some-android-phones
// (and now the words "default: default:" so angle_shader_validation.rs passes)
vec4 ComponentTransfer(vec4 colora, vec4 vfuncs, highp int table_address) {
    // We push a different amount of data to the gpu cache depending on the
    // function type.
    // Identity => 0 blocks
    // Table/Discrete => 64 blocks (256 values)
    // Linear => 1 block (2 values)
    // Gamma => 1 block (3 values)
    // We loop through the color components and increment the offset (for the
    // next color component) into the gpu cache based on how many blocks that
    // function type put into the gpu cache.
    // Table/Discrete use a 256 entry look up table.
    // Linear/Gamma are a simple calculation.

    // Both offset and k must be marked as highp due to a Adreno 3xx bug likely
    // to do with converting between precisions (as they would otherwise be
    // promoted when adding to table_address).
    highp int offset = 0;
    highp int k;

    vec4 texel;

    // Dynamically indexing a vector is buggy on some platforms, so use a temporary array
    int[4] funcs = int[4](int(vfuncs.r), int(vfuncs.g), int(vfuncs.b), int(vfuncs.a));
    for (int i = 0; i < 4; i++) {
        switch (funcs[i]) {
            case COMPONENT_TRANSFER_IDENTITY:
                break;
            case COMPONENT_TRANSFER_TABLE:
            case COMPONENT_TRANSFER_DISCRETE: {
                // fetch value from lookup table
                k = int(floor(colora[i]*255.0 + 0.5));
                texel = fetch_from_gpu_cache_1(table_address + offset + k/4);
                colora[i] = clamp(texel[k % 4], 0.0, 1.0);
                // offset plus 256/4 blocks
                offset = offset + 64;
                break;
            }
            case COMPONENT_TRANSFER_LINEAR: {
                // fetch the two values for use in the linear equation
                texel = fetch_from_gpu_cache_1(table_address + offset);
                colora[i] = clamp(texel[0] * colora[i] + texel[1], 0.0, 1.0);
                // offset plus 1 block
                offset = offset + 1;
                break;
            }
            case COMPONENT_TRANSFER_GAMMA: {
                // fetch the three values for use in the gamma equation
                texel = fetch_from_gpu_cache_1(table_address + offset);
                colora[i] = clamp(texel[0] * pow(colora[i], texel[1]) + texel[2], 0.0, 1.0);
                // offset plus 1 block
                offset = offset + 1;
                break;
            }
            default:
                // shouldn't happen
                break;
        }
    }
    return colora;
}

void CalculateFilter(
    vec4 Cs,
    int op,
    float amount,
    highp int table_address,
    vec4 color_offset,
    mat4 color_mat,
    vec4 v_funcs,
    out vec3 color,
    out float alpha
) {
    // Un-premultiply the input.
    alpha = Cs.a;
    color = alpha != 0.0 ? Cs.rgb / alpha : Cs.rgb;

    switch (op) {
        case FILTER_CONTRAST:
            color = Contrast(color, amount);
            break;
        case FILTER_INVERT:
            color = Invert(color, amount);
            break;
        case FILTER_BRIGHTNESS:
            color = Brightness(color, amount);
            break;
        case FILTER_SRGB_TO_LINEAR:
            color = SrgbToLinear(color);
            break;
        case FILTER_LINEAR_TO_SRGB:
            color = LinearToSrgb(color);
            break;
        case FILTER_COMPONENT_TRANSFER: {
            // Get the unpremultiplied color with alpha.
            vec4 colora = vec4(color, alpha);
            colora = ComponentTransfer(colora, v_funcs, table_address);
            color = colora.rgb;
            alpha = colora.a;
            break;
        }
        case FILTER_FLOOD:
            color = color_offset.rgb;
            alpha = color_offset.a;
            break;
        default:
            // Color matrix type filters (sepia, hue-rotate, etc...)
            vec4 result = color_mat * vec4(color, alpha) + color_offset;
            result = clamp(result, vec4(0.0), vec4(1.0));
            color = result.rgb;
            alpha = result.a;
    }
}
#endif