1#version 450
2layout(location = 0) in vec4 fragColor;
3layout(location = 1) in vec4 fragClip;
4layout(location = 2) in vec4 fragBounds;
5layout(location = 3) in vec4 fragRadii;
6layout(location = 4) in vec3 fragAtlas;
7layout(location = 5) flat in float fragKind;
8layout(location = 6) flat in vec4 fragWidths;
9layout(location = 7) flat in vec4 fragSideTop;
10layout(location = 8) flat in vec4 fragSideRight;
11layout(location = 9) flat in vec4 fragSideBottom;
12layout(location = 10) flat in vec4 fragSideLeft;
13layout(location = 11) flat in vec4 fragShape;
14layout(location = 12) flat in vec4 fragShadow;
15// Set 0 holds the glyph atlas; set 1 holds the current image batch.
16layout(set = 0, binding = 0) uniform sampler2DArray atlas;
17layout(set = 1, binding = 0) uniform sampler2D imageTexture;
18layout(location = 0) out vec4 outColor;
19// Signed distance to a rounded rectangle with corner order TL/TR/BR/BL.
20float roundedDistance(vec2 p, vec4 box, vec4 radii) {
21 vec2 halfSize = box.zw * .5;
22 vec2 center = box.xy + halfSize;
23 float r = p.y < center.y ? (p.x < center.x ? radii.x : radii.y)
24 : (p.x < center.x ? radii.w : radii.z);
25 r = clamp(r, 0.0, max(0.0, min(halfSize.x, halfSize.y)));
26 vec2 d = abs(p - center) - halfSize + r;
27 return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - r;
28}
29
30float cover(float d) { return clamp(.5 - d, 0.0, 1.0); }
31
32// Gaussian half-plane integral: Abramowitz/Stegun erf approximation. This
33// signed-distance approximation also follows the curved rounded-box corners.
34float gaussianCover(float d, float sigma) {
35 if (sigma <= .01) return cover(d);
36 float x = abs(d) / (sigma * 1.41421356);
37 float t = 1.0 / (1.0 + .3275911 * x);
38 float erfValue = 1.0 - (((((1.061405429 * t - 1.453152027) * t)
39 + 1.421413741) * t - .284496736) * t + .254829592) * t * exp(-x*x);
40 return clamp(d < 0.0 ? .5 + .5*erfValue : .5 - .5*erfValue, 0.0, 1.0);
41}
42
43void main() {
44 vec2 p = gl_FragCoord.xy;
45 if (any(lessThan(p, fragClip.xy)) || any(greaterThanEqual(p, fragClip.xy + fragClip.zw))) discard;
46 if (fragKind > .5 && fragKind < 1.5) {
47 float coverage = texture(atlas, fragAtlas).r;
48 outColor = vec4(fragColor.rgb * fragColor.a * coverage, fragColor.a * coverage);
49 return;
50 }
51 float coverage = cover(roundedDistance(p, fragBounds, fragRadii));
52 if (fragKind > 5.5 && fragKind < 6.5) {
53 // Uploaded texels are straight sRGB RGBA. Match other list colors:
54 // premultiply after sampling and applying element opacity.
55 vec4 texel = texture(imageTexture, fragAtlas.xy);
56 float alpha = texel.a * fragColor.a * coverage;
57 outColor = vec4(texel.rgb * alpha, alpha);
58 return;
59 }
60 vec4 color = fragColor;
61 if (fragKind > 1.5 && fragKind < 3.5) {
62 vec4 inner = fragBounds;
63 vec4 outerRadii = fragRadii;
64 if (fragKind < 2.5) {
65 // Border radii belong to the outer edge; inner corners shrink by
66 // their adjacent widths (CSS non-elliptical radius approximation).
67 inner.xy += vec2(fragWidths.w, fragWidths.x);
68 inner.zw -= vec2(fragWidths.w + fragWidths.y, fragWidths.x + fragWidths.z);
69 } else {
70 // Outline bounds already include outline width, and layout already
71 // applied outline-offset to fragShape.
72 outerRadii += vec4(max(fragWidths.x, fragWidths.w), max(fragWidths.x, fragWidths.y),
73 max(fragWidths.z, fragWidths.y), max(fragWidths.z, fragWidths.w));
74 inner = fragShape;
75 color = fragColor;
76 coverage = cover(roundedDistance(p, fragBounds, outerRadii));
77 }
78 vec4 innerRadii = fragKind < 2.5 ? max(fragRadii - vec4(max(fragWidths.x, fragWidths.w),
79 max(fragWidths.x, fragWidths.y), max(fragWidths.z, fragWidths.y), max(fragWidths.z, fragWidths.w)), 0.0) : fragRadii;
80 float hole = inner.z > 0.0 && inner.w > 0.0 ? cover(roundedDistance(p, inner, innerRadii)) : 0.0;
81 coverage *= 1.0 - hole;
82 if (fragKind < 2.5) {
83 // Split corners using each painted side's width. A zero-width
84 // side must never contribute its default color at the corners.
85 vec2 edge = p - fragBounds.xy;
86 vec2 farEdge = fragBounds.zw - edge;
87 float top = fragWidths.x > 0.0 ? edge.y / fragWidths.x : 1e20;
88 float right = fragWidths.y > 0.0 ? farEdge.x / fragWidths.y : 1e20;
89 float bottom = fragWidths.z > 0.0 ? farEdge.y / fragWidths.z : 1e20;
90 float left = fragWidths.w > 0.0 ? edge.x / fragWidths.w : 1e20;
91 float nearest = min(min(top, bottom), min(left, right));
92 color = nearest == top ? fragSideTop : nearest == bottom ? fragSideBottom
93 : nearest == left ? fragSideLeft : fragSideRight;
94 }
95 } else if (fragKind > 3.5) {
96 vec4 box = fragShape;
97 if (fragKind < 4.5) {
98 box.xy += fragShadow.xy - vec2(fragShadow.w);
99 box.zw += vec2(2.0 * fragShadow.w);
100 coverage = box.z > 0.0 && box.w > 0.0 ? gaussianCover(roundedDistance(p, box,
101 max(fragRadii + vec4(fragShadow.w), 0.0)), max(.0, fragShadow.z * .5)) : 0.0;
102 } else {
103 // Inset shadow is the complement of a shifted, contracted box,
104 // clipped to the original rounded box.
105 box.xy += fragShadow.xy + vec2(fragShadow.w);
106 box.zw -= vec2(2.0 * fragShadow.w);
107 float hole = box.z > 0.0 && box.w > 0.0 ? gaussianCover(roundedDistance(p, box,
108 max(fragRadii - vec4(fragShadow.w), 0.0)), max(.0, fragShadow.z * .5)) : 0.0;
109 coverage = cover(roundedDistance(p, fragShape, fragRadii)) * (1.0 - hole);
110 }
111 }
112 outColor = vec4(color.rgb * color.a * coverage, color.a * coverage);
113}