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,clip_shared
6
7varying vec4 vLocalPos;
8varying vec2 vUv;
9flat varying vec4 vUvBounds;
10flat varying vec4 vEdge;
11flat varying vec4 vUvBounds_NoClamp;
12#if defined(PLATFORM_ANDROID) && !defined(SWGL)
13// Work around Adreno 3xx driver bug. See the v_perspective comment in
14// brush_image or bug 1630356 for details.
15flat varying vec2 vClipModeVec;
16#define vClipMode vClipModeVec.x
17#else
18flat varying float vClipMode;
19#endif
20
21#define MODE_STRETCH        0
22#define MODE_SIMPLE         1
23
24#ifdef WR_VERTEX_SHADER
25
26PER_INSTANCE in ivec2 aClipDataResourceAddress;
27PER_INSTANCE in vec2 aClipSrcRectSize;
28PER_INSTANCE in int aClipMode;
29PER_INSTANCE in ivec2 aStretchMode;
30PER_INSTANCE in vec4 aClipDestRect;
31
32struct ClipMaskInstanceBoxShadow {
33    ClipMaskInstanceCommon base;
34    ivec2 resource_address;
35};
36
37ClipMaskInstanceBoxShadow fetch_clip_item() {
38    ClipMaskInstanceBoxShadow cmi;
39
40    cmi.base = fetch_clip_item_common();
41    cmi.resource_address = aClipDataResourceAddress;
42
43    return cmi;
44}
45
46struct BoxShadowData {
47    vec2 src_rect_size;
48    int clip_mode;
49    int stretch_mode_x;
50    int stretch_mode_y;
51    RectWithEndpoint dest_rect;
52};
53
54BoxShadowData fetch_data() {
55    BoxShadowData bs_data = BoxShadowData(
56        aClipSrcRectSize,
57        aClipMode,
58        aStretchMode.x,
59        aStretchMode.y,
60        RectWithEndpoint(aClipDestRect.xy, aClipDestRect.zw)
61    );
62    return bs_data;
63}
64
65void main(void) {
66    ClipMaskInstanceBoxShadow cmi = fetch_clip_item();
67    Transform clip_transform = fetch_transform(cmi.base.clip_transform_id);
68    Transform prim_transform = fetch_transform(cmi.base.prim_transform_id);
69    BoxShadowData bs_data = fetch_data();
70    ImageSource res = fetch_image_source_direct(cmi.resource_address);
71
72    RectWithEndpoint dest_rect = bs_data.dest_rect;
73
74    ClipVertexInfo vi = write_clip_tile_vertex(
75        dest_rect,
76        prim_transform,
77        clip_transform,
78        cmi.base.sub_rect,
79        cmi.base.task_origin,
80        cmi.base.screen_origin,
81        cmi.base.device_pixel_scale
82    );
83    vClipMode = float(bs_data.clip_mode);
84
85    vec2 texture_size = vec2(TEX_SIZE(sColor0));
86    vec2 local_pos = vi.local_pos.xy / vi.local_pos.w;
87    vLocalPos = vi.local_pos;
88    vec2 dest_rect_size = rect_size(dest_rect);
89
90    switch (bs_data.stretch_mode_x) {
91        case MODE_STRETCH: {
92            vEdge.x = 0.5;
93            vEdge.z = (dest_rect_size.x / bs_data.src_rect_size.x) - 0.5;
94            vUv.x = (local_pos.x - dest_rect.p0.x) / bs_data.src_rect_size.x;
95            break;
96        }
97        case MODE_SIMPLE:
98        default: {
99            vEdge.xz = vec2(1.0);
100            vUv.x = (local_pos.x - dest_rect.p0.x) / dest_rect_size.x;
101            break;
102        }
103    }
104
105    switch (bs_data.stretch_mode_y) {
106        case MODE_STRETCH: {
107            vEdge.y = 0.5;
108            vEdge.w = (dest_rect_size.y / bs_data.src_rect_size.y) - 0.5;
109            vUv.y = (local_pos.y - dest_rect.p0.y) / bs_data.src_rect_size.y;
110            break;
111        }
112        case MODE_SIMPLE:
113        default: {
114            vEdge.yw = vec2(1.0);
115            vUv.y = (local_pos.y - dest_rect.p0.y) / dest_rect_size.y;
116            break;
117        }
118    }
119
120    vUv *= vi.local_pos.w;
121    vec2 uv0 = res.uv_rect.p0;
122    vec2 uv1 = res.uv_rect.p1;
123    vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
124    vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy;
125}
126#endif
127
128#ifdef WR_FRAGMENT_SHADER
129void main(void) {
130    vec2 uv_linear = vUv / vLocalPos.w;
131    vec2 uv = clamp(uv_linear, vec2(0.0), vEdge.xy);
132    uv += max(vec2(0.0), uv_linear - vEdge.zw);
133    uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
134    uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
135
136    float in_shadow_rect = init_transform_rough_fs(vLocalPos.xy / vLocalPos.w);
137
138    float texel = TEX_SAMPLE(sColor0, uv).r;
139
140    float alpha = mix(texel, 1.0 - texel, vClipMode);
141    float result = vLocalPos.w > 0.0 ? mix(vClipMode, alpha, in_shadow_rect) : 0.0;
142
143    oFragColor = vec4(result);
144}
145
146#ifdef SWGL_DRAW_SPAN
147// As with cs_clip_rectangle, this shader spends a lot of time doing clipping and
148// combining for every fragment, even if outside of the primitive to initialize
149// the clip tile, or inside the inner bounds of the primitive, where the shadow
150// is unnecessary. To alleviate this, the span shader attempts to first intersect
151// the the local clip bounds, outside of which we can just use a solid fill
152// to initialize those clip tile fragments. Once inside the primitive bounds,
153// we further intersect with the inner region where no shadow is necessary either
154// so that we can commit entire spans of texture within this nine-patch region
155// instead of having to do the work of mapping per fragment.
156void swgl_drawSpanR8() {
157    // If the span is completely outside the Z-range and clipped out, just
158    // output clear so we don't need to consider invalid W in the rest of the
159    // shader.
160    float w = swgl_forceScalar(vLocalPos.w);
161    if (w <= 0.0) {
162        swgl_commitSolidR8(0.0);
163        return;
164    }
165
166    // To start, we evaluate the box shadow in both UV and local space relative
167    // to the local-space position. This will be interpolated across the span to
168    // track whether we intersect the nine-patch.
169    w = 1.0 / w;
170    vec2 uv_linear = vUv * w;
171    vec2 uv_linear0 = swgl_forceScalar(uv_linear);
172    vec2 uv_linear_step = swgl_interpStep(vUv).xy * w;
173    vec2 local_pos = vLocalPos.xy * w;
174    vec2 local_pos0 = swgl_forceScalar(local_pos);
175    vec2 local_step = swgl_interpStep(vLocalPos).xy * w;
176
177    // We need to compute the local-space distance to the bounding box and then
178    // figure out how many processing steps that maps to. If we are stepping in
179    // a negative direction on an axis, we need to swap the sides of the box
180    // which we consider as the start or end. If there is no local-space step
181    // on an axis (i.e. constant Y), we need to take care to force the steps to
182    // either the start or end of the span depending on if we are inside or
183    // outside of the bounding box.
184    vec4 clip_dist =
185        mix(vTransformBounds, vTransformBounds.zwxy, lessThan(local_step, vec2(0.0)).xyxy)
186            - local_pos0.xyxy;
187    clip_dist =
188        mix(1.0e6 * step(0.0, clip_dist),
189            clip_dist * recip(local_step).xyxy,
190            notEqual(local_step, vec2(0.0)).xyxy);
191
192    // Find the start and end of the shadowed region on this span.
193    float shadow_start = max(clip_dist.x, clip_dist.y);
194    float shadow_end = min(clip_dist.z, clip_dist.w);
195
196    // Flip the offsets from the start of the span so we can compare against the
197    // remaining span length which automatically deducts as we commit fragments.
198    ivec2 shadow_steps = ivec2(clamp(
199        swgl_SpanLength - swgl_StepSize * vec2(floor(shadow_start), ceil(shadow_end)),
200        0.0, swgl_SpanLength));
201    int shadow_start_len = shadow_steps.x;
202    int shadow_end_len = shadow_steps.y;
203
204    // Likewise, once inside the primitive bounds, we also need to track which
205    // sector of the nine-patch we are in which requires intersecting against
206    // the inner box instead of the outer box.
207    vec4 opaque_dist =
208        mix(vEdge, vEdge.zwxy, lessThan(uv_linear_step, vec2(0.0)).xyxy)
209            - uv_linear0.xyxy;
210    opaque_dist =
211        mix(1.0e6 * step(0.0, opaque_dist),
212            opaque_dist * recip(uv_linear_step).xyxy,
213            notEqual(uv_linear_step, vec2(0.0)).xyxy);
214
215    // Unlike for the shadow clipping bounds, here we need to rather find the floor of all
216    // the offsets so that we don't accidentally process any chunks in the transitional areas
217    // between sectors of the nine-patch.
218    ivec4 opaque_steps = ivec4(clamp(
219        swgl_SpanLength -
220            swgl_StepSize *
221                vec4(floor(opaque_dist.x), floor(opaque_dist.y), floor(opaque_dist.z), floor(opaque_dist.w)),
222        shadow_end_len, swgl_SpanLength));
223
224    // Fill any initial sections of the span that are clipped out based on clip mode.
225    if (swgl_SpanLength > shadow_start_len) {
226        int num_before = swgl_SpanLength - shadow_start_len;
227        swgl_commitPartialSolidR8(num_before, vClipMode);
228        float steps_before = float(num_before / swgl_StepSize);
229        uv_linear += steps_before * uv_linear_step;
230        local_pos += steps_before * local_step;
231    }
232
233    // This loop tries to repeatedly process entire spans of the nine-patch that map
234    // to a contiguous spans of texture in the source box shadow. First, we process
235    // a chunk with per-fragment clipping and mapping in case we're starting on a
236    // transitional region between sectors of the nine-patch which may need to map
237    // to different spans of texture per-fragment. After, we find the largest span
238    // within the current sector before we hit the next transitional region, and
239    // attempt to commit an entire span of texture therein.
240    while (swgl_SpanLength > 0) {
241        // Here we might be in a transitional chunk, so do everything per-fragment.
242        {
243            vec2 uv = clamp(uv_linear, vec2(0.0), vEdge.xy);
244            uv += max(vec2(0.0), uv_linear - vEdge.zw);
245            uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
246            uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
247
248            float in_shadow_rect = init_transform_rough_fs(local_pos);
249
250            float texel = TEX_SAMPLE(sColor0, uv).r;
251
252            float alpha = mix(texel, 1.0 - texel, vClipMode);
253            float result = mix(vClipMode, alpha, in_shadow_rect);
254            swgl_commitColorR8(result);
255
256            uv_linear += uv_linear_step;
257            local_pos += local_step;
258        }
259        // If we now hit the end of the clip bounds, just bail out since there is
260        // no more shadow to map.
261        if (swgl_SpanLength <= shadow_end_len) {
262            break;
263        }
264        // By here we've determined to be still inside the nine-patch. We need to
265        // compare against the inner rectangle thresholds to see which sector of
266        // the nine-patch to use and thus how to map the box shadow texture. Stop
267        // at least one step before the end of the shadow region to properly clip
268        // on the boundary.
269        int num_inside = swgl_SpanLength - swgl_StepSize - shadow_end_len;
270        vec4 uv_bounds = vUvBounds;
271        if (swgl_SpanLength >= opaque_steps.y) {
272            // We're in the top Y band of the nine-patch.
273            num_inside = min(num_inside, swgl_SpanLength - opaque_steps.y);
274        } else if (swgl_SpanLength >= opaque_steps.w) {
275            // We're in the middle Y band of the nine-patch. Set the UV clamp bounds
276            // to the vertical center texel of the box shadow.
277            num_inside = min(num_inside, swgl_SpanLength - opaque_steps.w);
278            uv_bounds.yw = vec2(clamp(mix(vUvBounds_NoClamp.y, vUvBounds_NoClamp.w, vEdge.y),
279                                      vUvBounds.y, vUvBounds.w));
280        }
281        if (swgl_SpanLength >= opaque_steps.x) {
282            // We're in the left X column of the nine-patch.
283            num_inside = min(num_inside, swgl_SpanLength - opaque_steps.x);
284        } else if (swgl_SpanLength >= opaque_steps.z) {
285            // We're in the middle X band of the nine-patch. Set the UV clamp bounds
286            // to the horizontal center texel of the box shadow.
287            num_inside = min(num_inside, swgl_SpanLength - opaque_steps.z);
288            uv_bounds.xz = vec2(clamp(mix(vUvBounds_NoClamp.x, vUvBounds_NoClamp.z, vEdge.x),
289                                      vUvBounds.x, vUvBounds.z));
290        }
291        if (num_inside > 0) {
292            // We have a non-zero span of fragments within the sector. Map to the UV
293            // start offset of the sector and the UV offset within the sector.
294            vec2 uv = clamp(uv_linear, vec2(0.0), vEdge.xy);
295            uv += max(vec2(0.0), uv_linear - vEdge.zw);
296            uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
297            // If we're in the center sector of the nine-patch, then we only need to
298            // sample from a single texel of the box shadow. Just sample that single
299            // texel once and output it for the entire span. Otherwise, we just need
300            // to commit an actual span of texture from the box shadow. Depending on
301            // if we are in clip-out mode, we may need to invert the source texture.
302            if (uv_bounds.xy == uv_bounds.zw) {
303                uv = clamp(uv, uv_bounds.xy, uv_bounds.zw);
304                float texel = TEX_SAMPLE(sColor0, uv).r;
305                float alpha = mix(texel, 1.0 - texel, vClipMode);
306                swgl_commitPartialSolidR8(num_inside, alpha);
307            } else if (vClipMode != 0.0) {
308                swgl_commitPartialTextureLinearInvertR8(num_inside, sColor0, uv, uv_bounds);
309            } else {
310                swgl_commitPartialTextureLinearR8(num_inside, sColor0, uv, uv_bounds);
311            }
312            float steps_inside = float(num_inside / swgl_StepSize);
313            uv_linear += steps_inside * uv_linear_step;
314            local_pos += steps_inside * local_step;
315        }
316        // By here we're probably in a transitional chunk of the nine-patch that
317        // requires per-fragment processing, so loop around again to the handler
318        // for that case.
319    }
320
321    // Fill any remaining sections of the span that are clipped out.
322    if (swgl_SpanLength > 0) {
323        swgl_commitPartialSolidR8(swgl_SpanLength, vClipMode);
324    }
325}
326#endif
327
328#endif
329