1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5#include shared,ellipse
6
7// For edges, the colors are the same. For corners, these
8// are the colors of each edge making up the corner.
9flat varying vec4 vColor00;
10flat varying vec4 vColor01;
11flat varying vec4 vColor10;
12flat varying vec4 vColor11;
13
14// A point + tangent defining the line where the edge
15// transition occurs. Used for corners only.
16flat varying vec4 vColorLine;
17
18// x = segment, y = styles, z = edge axes, w = clip mode
19// Since by default in GLES the vertex shader uses highp
20// and the fragment shader uses mediump, we explicitely
21// use mediump precision so we align with the default
22// mediump precision in the fragment shader.
23flat varying mediump ivec4 vConfig;
24
25// xy = Local space position of the clip center.
26// zw = Scale the rect origin by this to get the outer
27// corner from the segment rectangle.
28flat varying vec4 vClipCenter_Sign;
29
30// An outer and inner elliptical radii for border
31// corner clipping.
32flat varying vec4 vClipRadii;
33
34// Reference point for determine edge clip lines.
35flat varying vec4 vEdgeReference;
36
37// Stores widths/2 and widths/3 to save doing this in FS.
38flat varying vec4 vPartialWidths;
39
40// Clipping parameters for dot or dash.
41flat varying vec4 vClipParams1;
42flat varying vec4 vClipParams2;
43
44// Local space position
45varying vec2 vPos;
46
47#define SEGMENT_TOP_LEFT        0
48#define SEGMENT_TOP_RIGHT       1
49#define SEGMENT_BOTTOM_RIGHT    2
50#define SEGMENT_BOTTOM_LEFT     3
51#define SEGMENT_LEFT            4
52#define SEGMENT_TOP             5
53#define SEGMENT_RIGHT           6
54#define SEGMENT_BOTTOM          7
55
56// Border styles as defined in webrender_api/types.rs
57#define BORDER_STYLE_NONE         0
58#define BORDER_STYLE_SOLID        1
59#define BORDER_STYLE_DOUBLE       2
60#define BORDER_STYLE_DOTTED       3
61#define BORDER_STYLE_DASHED       4
62#define BORDER_STYLE_HIDDEN       5
63#define BORDER_STYLE_GROOVE       6
64#define BORDER_STYLE_RIDGE        7
65#define BORDER_STYLE_INSET        8
66#define BORDER_STYLE_OUTSET       9
67
68#define CLIP_NONE        0
69#define CLIP_DASH_CORNER 1
70#define CLIP_DASH_EDGE   2
71#define CLIP_DOT         3
72
73#ifdef WR_VERTEX_SHADER
74
75PER_INSTANCE in vec2 aTaskOrigin;
76PER_INSTANCE in vec4 aRect;
77PER_INSTANCE in vec4 aColor0;
78PER_INSTANCE in vec4 aColor1;
79PER_INSTANCE in int aFlags;
80PER_INSTANCE in vec2 aWidths;
81PER_INSTANCE in vec2 aRadii;
82PER_INSTANCE in vec4 aClipParams1;
83PER_INSTANCE in vec4 aClipParams2;
84
85vec2 get_outer_corner_scale(int segment) {
86    vec2 p;
87
88    switch (segment) {
89        case SEGMENT_TOP_LEFT:
90            p = vec2(0.0, 0.0);
91            break;
92        case SEGMENT_TOP_RIGHT:
93            p = vec2(1.0, 0.0);
94            break;
95        case SEGMENT_BOTTOM_RIGHT:
96            p = vec2(1.0, 1.0);
97            break;
98        case SEGMENT_BOTTOM_LEFT:
99            p = vec2(0.0, 1.0);
100            break;
101        default:
102            // The result is only used for non-default segment cases
103            p = vec2(0.0);
104            break;
105    }
106
107    return p;
108}
109
110// NOTE(emilio): If you change this algorithm, do the same change
111// in border.rs
112vec4 mod_color(vec4 color, bool is_black, bool lighter) {
113    const float light_black = 0.7;
114    const float dark_black = 0.3;
115
116    const float dark_scale = 0.66666666;
117    const float light_scale = 1.0;
118
119    if (is_black) {
120        if (lighter) {
121            return vec4(vec3(light_black), color.a);
122        }
123        return vec4(vec3(dark_black), color.a);
124    }
125
126    if (lighter) {
127        return vec4(color.rgb * light_scale, color.a);
128    }
129    return vec4(color.rgb * dark_scale, color.a);
130}
131
132vec4[2] get_colors_for_side(vec4 color, int style) {
133    vec4 result[2];
134
135    bool is_black = color.rgb == vec3(0.0, 0.0, 0.0);
136
137    switch (style) {
138        case BORDER_STYLE_GROOVE:
139            result[0] = mod_color(color, is_black, true);
140            result[1] = mod_color(color, is_black, false);
141            break;
142        case BORDER_STYLE_RIDGE:
143            result[0] = mod_color(color, is_black, false);
144            result[1] = mod_color(color, is_black, true);
145            break;
146        default:
147            result[0] = color;
148            result[1] = color;
149            break;
150    }
151
152    return result;
153}
154
155void main(void) {
156    int segment = aFlags & 0xff;
157    int style0 = (aFlags >> 8) & 0xff;
158    int style1 = (aFlags >> 16) & 0xff;
159    int clip_mode = (aFlags >> 24) & 0x0f;
160
161    vec2 outer_scale = get_outer_corner_scale(segment);
162    vec2 outer = outer_scale * aRect.zw;
163    vec2 clip_sign = 1.0 - 2.0 * outer_scale;
164
165    // Set some flags used by the FS to determine the
166    // orientation of the two edges in this corner.
167    ivec2 edge_axis = ivec2(0, 0);
168    // Derive the positions for the edge clips, which must be handled
169    // differently between corners and edges.
170    vec2 edge_reference = vec2(0.0);
171    switch (segment) {
172        case SEGMENT_TOP_LEFT:
173            edge_axis = ivec2(0, 1);
174            edge_reference = outer;
175            break;
176        case SEGMENT_TOP_RIGHT:
177            edge_axis = ivec2(1, 0);
178            edge_reference = vec2(outer.x - aWidths.x, outer.y);
179            break;
180        case SEGMENT_BOTTOM_RIGHT:
181            edge_axis = ivec2(0, 1);
182            edge_reference = outer - aWidths;
183            break;
184        case SEGMENT_BOTTOM_LEFT:
185            edge_axis = ivec2(1, 0);
186            edge_reference = vec2(outer.x, outer.y - aWidths.y);
187            break;
188        case SEGMENT_TOP:
189        case SEGMENT_BOTTOM:
190            edge_axis = ivec2(1, 1);
191            break;
192        case SEGMENT_LEFT:
193        case SEGMENT_RIGHT:
194        default:
195            break;
196    }
197
198    vConfig = ivec4(
199        segment,
200        style0 | (style1 << 8),
201        edge_axis.x | (edge_axis.y << 8),
202        clip_mode
203    );
204    vPartialWidths = vec4(aWidths / 3.0, aWidths / 2.0);
205    vPos = aRect.zw * aPosition.xy;
206
207    vec4[2] color0 = get_colors_for_side(aColor0, style0);
208    vColor00 = color0[0];
209    vColor01 = color0[1];
210    vec4[2] color1 = get_colors_for_side(aColor1, style1);
211    vColor10 = color1[0];
212    vColor11 = color1[1];
213    vClipCenter_Sign = vec4(outer + clip_sign * aRadii, clip_sign);
214    vClipRadii = vec4(aRadii, max(aRadii - aWidths, 0.0));
215    vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x);
216    vEdgeReference = vec4(edge_reference, edge_reference + aWidths);
217    vClipParams1 = aClipParams1;
218    vClipParams2 = aClipParams2;
219
220    // For the case of dot and dash clips, optimize the number of pixels that
221    // are hit to just include the dot itself.
222    if (clip_mode == CLIP_DOT) {
223        float radius = aClipParams1.z;
224
225        // Expand by a small amount to allow room for AA around
226        // the dot if it's big enough.
227        if (radius > 0.5)
228            radius += 2.0;
229
230        vPos = vClipParams1.xy + radius * (2.0 * aPosition.xy - 1.0);
231        vPos = clamp(vPos, vec2(0.0), aRect.zw);
232    } else if (clip_mode == CLIP_DASH_CORNER) {
233        vec2 center = (aClipParams1.xy + aClipParams2.xy) * 0.5;
234        // This is a gross approximation which works out because dashes don't have
235        // a strong curvature and we will overshoot by inflating the geometry by
236        // this amount on each side (sqrt(2) * length(dash) would be enough and we
237        // compute 2 * approx_length(dash)).
238        float dash_length = length(aClipParams1.xy - aClipParams2.xy);
239        float width = max(aWidths.x, aWidths.y);
240        // expand by a small amout for AA just like we do for dots.
241        vec2 r = vec2(max(dash_length, width)) + 2.0;
242        vPos = clamp(vPos, center - r, center + r);
243    }
244
245    gl_Position = uTransform * vec4(aTaskOrigin + aRect.xy + vPos, 0.0, 1.0);
246}
247#endif
248
249#ifdef WR_FRAGMENT_SHADER
250vec4 evaluate_color_for_style_in_corner(
251    vec2 clip_relative_pos,
252    int style,
253    vec4 color0,
254    vec4 color1,
255    vec4 clip_radii,
256    float mix_factor,
257    int segment,
258    float aa_range
259) {
260    switch (style) {
261        case BORDER_STYLE_DOUBLE: {
262            // Get the distances from 0.33 of the radii, and
263            // also 0.67 of the radii. Use these to form a
264            // SDF subtraction which will clip out the inside
265            // third of the rounded edge.
266            float d_radii_a = distance_to_ellipse(
267                clip_relative_pos,
268                clip_radii.xy - vPartialWidths.xy,
269                aa_range
270            );
271            float d_radii_b = distance_to_ellipse(
272                clip_relative_pos,
273                clip_radii.xy - 2.0 * vPartialWidths.xy,
274                aa_range
275            );
276            float d = min(-d_radii_a, d_radii_b);
277            color0 *= distance_aa(aa_range, d);
278            break;
279        }
280        case BORDER_STYLE_GROOVE:
281        case BORDER_STYLE_RIDGE: {
282            float d = distance_to_ellipse(
283                clip_relative_pos,
284                clip_radii.xy - vPartialWidths.zw,
285                aa_range
286            );
287            float alpha = distance_aa(aa_range, d);
288            float swizzled_factor;
289            switch (segment) {
290                case SEGMENT_TOP_LEFT: swizzled_factor = 0.0; break;
291                case SEGMENT_TOP_RIGHT: swizzled_factor = mix_factor; break;
292                case SEGMENT_BOTTOM_RIGHT: swizzled_factor = 1.0; break;
293                case SEGMENT_BOTTOM_LEFT: swizzled_factor = 1.0 - mix_factor; break;
294                default: swizzled_factor = 0.0; break;
295            };
296            vec4 c0 = mix(color1, color0, swizzled_factor);
297            vec4 c1 = mix(color0, color1, swizzled_factor);
298            color0 = mix(c0, c1, alpha);
299            break;
300        }
301        default:
302            break;
303    }
304
305    return color0;
306}
307
308vec4 evaluate_color_for_style_in_edge(
309    vec2 pos_vec,
310    int style,
311    vec4 color0,
312    vec4 color1,
313    float aa_range,
314    int edge_axis_id
315) {
316    vec2 edge_axis = edge_axis_id != 0 ? vec2(0.0, 1.0) : vec2(1.0, 0.0);
317    float pos = dot(pos_vec, edge_axis);
318    switch (style) {
319        case BORDER_STYLE_DOUBLE: {
320            float d = -1.0;
321            float partial_width = dot(vPartialWidths.xy, edge_axis);
322            if (partial_width >= 1.0) {
323                vec2 ref = vec2(
324                    dot(vEdgeReference.xy, edge_axis) + partial_width,
325                    dot(vEdgeReference.zw, edge_axis) - partial_width
326                );
327                d = min(pos - ref.x, ref.y - pos);
328            }
329            color0 *= distance_aa(aa_range, d);
330            break;
331        }
332        case BORDER_STYLE_GROOVE:
333        case BORDER_STYLE_RIDGE: {
334            float ref = dot(vEdgeReference.xy + vPartialWidths.zw, edge_axis);
335            float d = pos - ref;
336            float alpha = distance_aa(aa_range, d);
337            color0 = mix(color0, color1, alpha);
338            break;
339        }
340        default:
341            break;
342    }
343
344    return color0;
345}
346
347void main(void) {
348    float aa_range = compute_aa_range(vPos);
349    vec4 color0, color1;
350
351    int segment = vConfig.x;
352    ivec2 style = ivec2(vConfig.y & 0xff, vConfig.y >> 8);
353    ivec2 edge_axis = ivec2(vConfig.z & 0xff, vConfig.z >> 8);
354    int clip_mode = vConfig.w;
355
356    float mix_factor = 0.0;
357    if (edge_axis.x != edge_axis.y) {
358        float d_line = distance_to_line(vColorLine.xy, vColorLine.zw, vPos);
359        mix_factor = distance_aa(aa_range, -d_line);
360    }
361
362    // Check if inside corner clip-region
363    vec2 clip_relative_pos = vPos - vClipCenter_Sign.xy;
364    bool in_clip_region = all(lessThan(vClipCenter_Sign.zw * clip_relative_pos, vec2(0.0)));
365    float d = -1.0;
366
367    switch (clip_mode) {
368        case CLIP_DOT: {
369            // Set clip distance based or dot position and radius.
370            d = distance(vClipParams1.xy, vPos) - vClipParams1.z;
371            break;
372        }
373        case CLIP_DASH_EDGE: {
374            bool is_vertical = vClipParams1.x == 0.;
375            float half_dash = is_vertical ? vClipParams1.y : vClipParams1.x;
376            // We want to draw something like:
377            // +---+---+---+---+
378            // |xxx|   |   |xxx|
379            // +---+---+---+---+
380            float pos = is_vertical ? vPos.y : vPos.x;
381            bool in_dash = pos < half_dash || pos > 3.0 * half_dash;
382            if (!in_dash) {
383                d = 1.;
384            }
385            break;
386        }
387        case CLIP_DASH_CORNER: {
388            // Get SDF for the two line/tangent clip lines,
389            // do SDF subtract to get clip distance.
390            float d0 = distance_to_line(vClipParams1.xy,
391                                        vClipParams1.zw,
392                                        vPos);
393            float d1 = distance_to_line(vClipParams2.xy,
394                                        vClipParams2.zw,
395                                        vPos);
396            d = max(d0, -d1);
397            break;
398        }
399        case CLIP_NONE:
400        default:
401            break;
402    }
403
404    if (in_clip_region) {
405        float d_radii_a = distance_to_ellipse(clip_relative_pos, vClipRadii.xy, aa_range);
406        float d_radii_b = distance_to_ellipse(clip_relative_pos, vClipRadii.zw, aa_range);
407        float d_radii = max(d_radii_a, -d_radii_b);
408        d = max(d, d_radii);
409
410        color0 = evaluate_color_for_style_in_corner(
411            clip_relative_pos,
412            style.x,
413            vColor00,
414            vColor01,
415            vClipRadii,
416            mix_factor,
417            segment,
418            aa_range
419        );
420        color1 = evaluate_color_for_style_in_corner(
421            clip_relative_pos,
422            style.y,
423            vColor10,
424            vColor11,
425            vClipRadii,
426            mix_factor,
427            segment,
428            aa_range
429        );
430    } else {
431        color0 = evaluate_color_for_style_in_edge(
432            vPos,
433            style.x,
434            vColor00,
435            vColor01,
436            aa_range,
437            edge_axis.x
438        );
439        color1 = evaluate_color_for_style_in_edge(
440            vPos,
441            style.y,
442            vColor10,
443            vColor11,
444            aa_range,
445            edge_axis.y
446        );
447    }
448
449    float alpha = distance_aa(aa_range, d);
450    vec4 color = mix(color0, color1, mix_factor);
451    oFragColor = color * alpha;
452}
453#endif
454