summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/res/brush_blend.glsl
blob: 67d422a81b42dc5e55fa282d4b02cd71b7be7b89 (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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
/* 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 VECS_PER_SPECIFIC_BRUSH 3
#define WR_FEATURE_TEXTURE_2D

#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

#include shared,prim_shared,brush

// Interpolated UV coordinates to sample.
varying vec2 v_uv;
varying vec2 v_local_pos;

// Normalized bounds of the source image in the texture, adjusted to avoid
// sampling artifacts.
flat varying vec4 v_uv_sample_bounds;

flat varying vec4 v_color_offset;

// Flag to allow perspective interpolation of UV.
flat varying float v_perspective;

flat varying float v_amount;

flat varying int v_op;
flat varying int v_table_address;

flat varying mat4 vColorMat;

flat varying int vFuncs[4];

#ifdef WR_VERTEX_SHADER

void brush_vs(
    VertexInfo vi,
    int prim_address,
    RectWithSize local_rect,
    RectWithSize segment_rect,
    ivec4 prim_user_data,
    int specific_resource_address,
    mat4 transform,
    PictureTask pic_task,
    int brush_flags,
    vec4 unused
) {
    ImageResource res = fetch_image_resource(prim_user_data.x);
    vec2 uv0 = res.uv_rect.p0;
    vec2 uv1 = res.uv_rect.p1;

    vec2 inv_texture_size = vec2(1.0) / vec2(textureSize(sColor0, 0).xy);
    vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
    f = get_image_quad_uv(prim_user_data.x, f);
    vec2 uv = mix(uv0, uv1, f);
    float perspective_interpolate = (brush_flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0 ? 1.0 : 0.0;

    v_uv = uv * inv_texture_size * mix(vi.world_pos.w, 1.0, perspective_interpolate);
    v_perspective = perspective_interpolate;

    v_uv_sample_bounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) * inv_texture_size.xyxy;

    v_local_pos = vi.local_pos;

    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 amount = float(prim_user_data.z) / 65536.0;
    float invAmount = 1.0 - amount;

    v_op = prim_user_data.y & 0xffff;
    v_amount = amount;

    // This assignment is only used for component transfer filters but this
    // assignment has to be done here and not in the component transfer case
    // below because it doesn't get executed on Windows because of a suspected
    // miscompile of this shader on Windows. See
    // https://github.com/servo/webrender/wiki/Driver-issues#bug-1505871---assignment-to-varying-flat-arrays-inside-switch-statement-of-vertex-shader-suspected-miscompile-on-windows
    // default: just to satisfy angle_shader_validation.rs which needs one
    // default: for every switch, even in comments.
    vFuncs[0] = (prim_user_data.y >> 28) & 0xf; // R
    vFuncs[1] = (prim_user_data.y >> 24) & 0xf; // G
    vFuncs[2] = (prim_user_data.y >> 20) & 0xf; // B
    vFuncs[3] = (prim_user_data.y >> 16) & 0xf; // A

    if (v_op == FILTER_GRAYSCALE) {
        vColorMat = 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)
        );
        v_color_offset = vec4(0.0);
    } else if (v_op ==  FILTER_HUE_ROTATE) {
        float c = cos(amount);
        float s = sin(amount);
        vColorMat = 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)
        );
        v_color_offset = vec4(0.0);
    } else if (v_op ==   FILTER_SATURATE) {
        vColorMat = 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)
        );
        v_color_offset = vec4(0.0);
    } else if (v_op == FILTER_SEPIA) {
        vColorMat = 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)
        );
        v_color_offset = vec4(0.0);
    } else if (v_op == FILTER_COLOR_MATRIX) {
        vec4 mat_data[4] = fetch_from_gpu_cache_4(prim_user_data.z);
        vec4 offset_data = fetch_from_gpu_cache_1(prim_user_data.z + 4);
        vColorMat = mat4(mat_data[0], mat_data[1], mat_data[2], mat_data[3]);
        v_color_offset = offset_data;
    } else if (v_op == FILTER_COMPONENT_TRANSFER) {
        v_table_address = prim_user_data.z;
    } else if (v_op == FILTER_FLOOD) {
        v_color_offset = fetch_from_gpu_cache_1(prim_user_data.z);
    }
}
#endif

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

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) {
    // 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.
    int offset = 0;
    vec4 texel;
    int k;

    for (int i = 0; i < 4; i++) {
        switch (vFuncs[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));
                texel = fetch_from_gpu_cache_1(v_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(v_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(v_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;
}

Fragment brush_fs() {
    float perspective_divisor = mix(gl_FragCoord.w, 1.0, v_perspective);
    vec2 uv = v_uv * perspective_divisor;
    // Clamp the uvs to avoid sampling artifacts.
    uv = clamp(uv, v_uv_sample_bounds.xy, v_uv_sample_bounds.zw);

    vec4 Cs = texture(sColor0, uv);

    // Un-premultiply the input.
    float alpha = Cs.a;
    vec3 color = alpha != 0.0 ? Cs.rgb / alpha : Cs.rgb;

    switch (v_op) {
        case FILTER_CONTRAST:
            color = Contrast(color, v_amount);
            break;
        case FILTER_INVERT:
            color = Invert(color, v_amount);
            break;
        case FILTER_BRIGHTNESS:
            color = Brightness(color, v_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);
            color = colora.rgb;
            alpha = colora.a;
            break;
        }
        case FILTER_FLOOD:
            color = v_color_offset.rgb;
            alpha = v_color_offset.a;
            break;
        default:
            // Color matrix type filters (sepia, hue-rotate, etc...)
            vec4 result = vColorMat * vec4(color, alpha) + v_color_offset;
            result = clamp(result, vec4(0.0), vec4(1.0));
            color = result.rgb;
            alpha = result.a;
    }

    #ifdef WR_FEATURE_ALPHA_PASS
        alpha *= init_transform_fs(v_local_pos);
        // Pre-multiply the alpha into the output value.
    #endif

    return Fragment(alpha * vec4(color, 1.0));
}
#endif