summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/res/cs_clip_box_shadow.glsl
blob: 37a983c759e7a4d13be3f533259a5a5e122133f9 (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
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/* 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/. */

#include shared,clip_shared

varying highp vec4 vLocalPos;
varying highp vec2 vUv;
flat varying highp vec4 vUvBounds;
flat varying mediump vec4 vEdge;
flat varying highp vec4 vUvBounds_NoClamp;
// Clip mode. Packed in to a vector to avoid bug 1630356.
flat varying mediump vec2 vClipMode;

#define MODE_STRETCH        0
#define MODE_SIMPLE         1

#ifdef WR_VERTEX_SHADER

PER_INSTANCE in ivec2 aClipDataResourceAddress;
PER_INSTANCE in vec2 aClipSrcRectSize;
PER_INSTANCE in int aClipMode;
PER_INSTANCE in ivec2 aStretchMode;
PER_INSTANCE in vec4 aClipDestRect;

struct ClipMaskInstanceBoxShadow {
    ClipMaskInstanceCommon base;
    ivec2 resource_address;
};

ClipMaskInstanceBoxShadow fetch_clip_item() {
    ClipMaskInstanceBoxShadow cmi;

    cmi.base = fetch_clip_item_common();
    cmi.resource_address = aClipDataResourceAddress;

    return cmi;
}

struct BoxShadowData {
    vec2 src_rect_size;
    int clip_mode;
    int stretch_mode_x;
    int stretch_mode_y;
    RectWithEndpoint dest_rect;
};

BoxShadowData fetch_data() {
    BoxShadowData bs_data = BoxShadowData(
        aClipSrcRectSize,
        aClipMode,
        aStretchMode.x,
        aStretchMode.y,
        RectWithEndpoint(aClipDestRect.xy, aClipDestRect.zw)
    );
    return bs_data;
}

void main(void) {
    ClipMaskInstanceBoxShadow cmi = fetch_clip_item();
    Transform clip_transform = fetch_transform(cmi.base.clip_transform_id);
    Transform prim_transform = fetch_transform(cmi.base.prim_transform_id);
    BoxShadowData bs_data = fetch_data();
    ImageSource res = fetch_image_source_direct(cmi.resource_address);

    RectWithEndpoint dest_rect = bs_data.dest_rect;

    ClipVertexInfo vi = write_clip_tile_vertex(
        dest_rect,
        prim_transform,
        clip_transform,
        cmi.base.sub_rect,
        cmi.base.task_origin,
        cmi.base.screen_origin,
        cmi.base.device_pixel_scale
    );
    vClipMode.x = float(bs_data.clip_mode);

    vec2 texture_size = vec2(TEX_SIZE(sColor0));
    vec2 local_pos = vi.local_pos.xy / vi.local_pos.w;
    vLocalPos = vi.local_pos;
    vec2 dest_rect_size = rect_size(dest_rect);

    switch (bs_data.stretch_mode_x) {
        case MODE_STRETCH: {
            vEdge.x = 0.5;
            vEdge.z = (dest_rect_size.x / bs_data.src_rect_size.x) - 0.5;
            vUv.x = (local_pos.x - dest_rect.p0.x) / bs_data.src_rect_size.x;
            break;
        }
        case MODE_SIMPLE:
        default: {
            vEdge.xz = vec2(1.0);
            vUv.x = (local_pos.x - dest_rect.p0.x) / dest_rect_size.x;
            break;
        }
    }

    switch (bs_data.stretch_mode_y) {
        case MODE_STRETCH: {
            vEdge.y = 0.5;
            vEdge.w = (dest_rect_size.y / bs_data.src_rect_size.y) - 0.5;
            vUv.y = (local_pos.y - dest_rect.p0.y) / bs_data.src_rect_size.y;
            break;
        }
        case MODE_SIMPLE:
        default: {
            vEdge.yw = vec2(1.0);
            vUv.y = (local_pos.y - dest_rect.p0.y) / dest_rect_size.y;
            break;
        }
    }

    vUv *= vi.local_pos.w;
    vec2 uv0 = res.uv_rect.p0;
    vec2 uv1 = res.uv_rect.p1;
    vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
    vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy;
}
#endif

#ifdef WR_FRAGMENT_SHADER
void main(void) {
    vec2 uv_linear = vUv / vLocalPos.w;
    vec2 uv = clamp(uv_linear, vec2(0.0), vEdge.xy);
    uv += max(vec2(0.0), uv_linear - vEdge.zw);
    uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
    uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);

    float in_shadow_rect = init_transform_rough_fs(vLocalPos.xy / vLocalPos.w);

    float texel = TEX_SAMPLE(sColor0, uv).r;

    float alpha = mix(texel, 1.0 - texel, vClipMode.x);
    float result = vLocalPos.w > 0.0 ? mix(vClipMode.x, alpha, in_shadow_rect) : 0.0;

    oFragColor = vec4(result);
}

#ifdef SWGL_DRAW_SPAN
// As with cs_clip_rectangle, this shader spends a lot of time doing clipping and
// combining for every fragment, even if outside of the primitive to initialize
// the clip tile, or inside the inner bounds of the primitive, where the shadow
// is unnecessary. To alleviate this, the span shader attempts to first intersect
// the the local clip bounds, outside of which we can just use a solid fill
// to initialize those clip tile fragments. Once inside the primitive bounds,
// we further intersect with the inner region where no shadow is necessary either
// so that we can commit entire spans of texture within this nine-patch region
// instead of having to do the work of mapping per fragment.
void swgl_drawSpanR8() {
    // Perspective is not supported.
    if (swgl_interpStep(vLocalPos).w != 0.0) {
        return;
    }

    // If the span is completely outside the Z-range and clipped out, just
    // output clear so we don't need to consider invalid W in the rest of the
    // shader.
    float w = swgl_forceScalar(vLocalPos.w);
    if (w <= 0.0) {
        swgl_commitSolidR8(0.0);
        return;
    }

    // To start, we evaluate the box shadow in both UV and local space relative
    // to the local-space position. This will be interpolated across the span to
    // track whether we intersect the nine-patch.
    w = 1.0 / w;
    vec2 uv_linear = vUv * w;
    vec2 uv_linear0 = swgl_forceScalar(uv_linear);
    vec2 uv_linear_step = swgl_interpStep(vUv).xy * w;
    vec2 local_pos = vLocalPos.xy * w;
    vec2 local_pos0 = swgl_forceScalar(local_pos);
    vec2 local_step = swgl_interpStep(vLocalPos).xy * w;

    // We need to compute the local-space distance to the bounding box and then
    // figure out how many processing steps that maps to. If we are stepping in
    // a negative direction on an axis, we need to swap the sides of the box
    // which we consider as the start or end. If there is no local-space step
    // on an axis (i.e. constant Y), we need to take care to force the steps to
    // either the start or end of the span depending on if we are inside or
    // outside of the bounding box.
    vec4 clip_dist =
        mix(vTransformBounds, vTransformBounds.zwxy, lessThan(local_step, vec2(0.0)).xyxy)
            - local_pos0.xyxy;
    clip_dist =
        mix(1.0e6 * step(0.0, clip_dist),
            clip_dist * recip(local_step).xyxy,
            notEqual(local_step, vec2(0.0)).xyxy);

    // Find the start and end of the shadowed region on this span.
    float shadow_start = max(clip_dist.x, clip_dist.y);
    float shadow_end = min(clip_dist.z, clip_dist.w);

    // Flip the offsets from the start of the span so we can compare against the
    // remaining span length which automatically deducts as we commit fragments.
    ivec2 shadow_steps = ivec2(clamp(
        swgl_SpanLength - swgl_StepSize * vec2(floor(shadow_start), ceil(shadow_end)),
        0.0, swgl_SpanLength));
    int shadow_start_len = shadow_steps.x;
    int shadow_end_len = shadow_steps.y;

    // Likewise, once inside the primitive bounds, we also need to track which
    // sector of the nine-patch we are in which requires intersecting against
    // the inner box instead of the outer box.
    vec4 opaque_dist =
        mix(vEdge, vEdge.zwxy, lessThan(uv_linear_step, vec2(0.0)).xyxy)
            - uv_linear0.xyxy;
    opaque_dist =
        mix(1.0e6 * step(0.0, opaque_dist),
            opaque_dist * recip(uv_linear_step).xyxy,
            notEqual(uv_linear_step, vec2(0.0)).xyxy);

    // Unlike for the shadow clipping bounds, here we need to rather find the floor of all
    // the offsets so that we don't accidentally process any chunks in the transitional areas
    // between sectors of the nine-patch.
    ivec4 opaque_steps = ivec4(clamp(
        swgl_SpanLength -
            swgl_StepSize *
                vec4(floor(opaque_dist.x), floor(opaque_dist.y), floor(opaque_dist.z), floor(opaque_dist.w)),
        shadow_end_len, swgl_SpanLength));

    // Fill any initial sections of the span that are clipped out based on clip mode.
    if (swgl_SpanLength > shadow_start_len) {
        int num_before = swgl_SpanLength - shadow_start_len;
        swgl_commitPartialSolidR8(num_before, vClipMode.x);
        float steps_before = float(num_before / swgl_StepSize);
        uv_linear += steps_before * uv_linear_step;
        local_pos += steps_before * local_step;
    }

    // This loop tries to repeatedly process entire spans of the nine-patch that map
    // to a contiguous spans of texture in the source box shadow. First, we process
    // a chunk with per-fragment clipping and mapping in case we're starting on a
    // transitional region between sectors of the nine-patch which may need to map
    // to different spans of texture per-fragment. After, we find the largest span
    // within the current sector before we hit the next transitional region, and
    // attempt to commit an entire span of texture therein.
    while (swgl_SpanLength > 0) {
        // Here we might be in a transitional chunk, so do everything per-fragment.
        {
            vec2 uv = clamp(uv_linear, vec2(0.0), vEdge.xy);
            uv += max(vec2(0.0), uv_linear - vEdge.zw);
            uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
            uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);

            float in_shadow_rect = init_transform_rough_fs(local_pos);

            float texel = TEX_SAMPLE(sColor0, uv).r;

            float alpha = mix(texel, 1.0 - texel, vClipMode.x);
            float result = mix(vClipMode.x, alpha, in_shadow_rect);
            swgl_commitColorR8(result);

            uv_linear += uv_linear_step;
            local_pos += local_step;
        }
        // If we now hit the end of the clip bounds, just bail out since there is
        // no more shadow to map.
        if (swgl_SpanLength <= shadow_end_len) {
            break;
        }
        // By here we've determined to be still inside the nine-patch. We need to
        // compare against the inner rectangle thresholds to see which sector of
        // the nine-patch to use and thus how to map the box shadow texture. Stop
        // at least one step before the end of the shadow region to properly clip
        // on the boundary.
        int num_inside = swgl_SpanLength - swgl_StepSize - shadow_end_len;
        vec4 uv_bounds = vUvBounds;
        if (swgl_SpanLength >= opaque_steps.y) {
            // We're in the top Y band of the nine-patch.
            num_inside = min(num_inside, swgl_SpanLength - opaque_steps.y);
        } else if (swgl_SpanLength >= opaque_steps.w) {
            // We're in the middle Y band of the nine-patch. Set the UV clamp bounds
            // to the vertical center texel of the box shadow.
            num_inside = min(num_inside, swgl_SpanLength - opaque_steps.w);
            uv_bounds.yw = vec2(clamp(mix(vUvBounds_NoClamp.y, vUvBounds_NoClamp.w, vEdge.y),
                                      vUvBounds.y, vUvBounds.w));
        }
        if (swgl_SpanLength >= opaque_steps.x) {
            // We're in the left X column of the nine-patch.
            num_inside = min(num_inside, swgl_SpanLength - opaque_steps.x);
        } else if (swgl_SpanLength >= opaque_steps.z) {
            // We're in the middle X band of the nine-patch. Set the UV clamp bounds
            // to the horizontal center texel of the box shadow.
            num_inside = min(num_inside, swgl_SpanLength - opaque_steps.z);
            uv_bounds.xz = vec2(clamp(mix(vUvBounds_NoClamp.x, vUvBounds_NoClamp.z, vEdge.x),
                                      vUvBounds.x, vUvBounds.z));
        }
        if (num_inside > 0) {
            // We have a non-zero span of fragments within the sector. Map to the UV
            // start offset of the sector and the UV offset within the sector.
            vec2 uv = clamp(uv_linear, vec2(0.0), vEdge.xy);
            uv += max(vec2(0.0), uv_linear - vEdge.zw);
            uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
            // If we're in the center sector of the nine-patch, then we only need to
            // sample from a single texel of the box shadow. Just sample that single
            // texel once and output it for the entire span. Otherwise, we just need
            // to commit an actual span of texture from the box shadow. Depending on
            // if we are in clip-out mode, we may need to invert the source texture.
            if (uv_bounds.xy == uv_bounds.zw) {
                uv = clamp(uv, uv_bounds.xy, uv_bounds.zw);
                float texel = TEX_SAMPLE(sColor0, uv).r;
                float alpha = mix(texel, 1.0 - texel, vClipMode.x);
                swgl_commitPartialSolidR8(num_inside, alpha);
            } else if (vClipMode.x != 0.0) {
                swgl_commitPartialTextureLinearInvertR8(num_inside, sColor0, uv, uv_bounds);
            } else {
                swgl_commitPartialTextureLinearR8(num_inside, sColor0, uv, uv_bounds);
            }
            float steps_inside = float(num_inside / swgl_StepSize);
            uv_linear += steps_inside * uv_linear_step;
            local_pos += steps_inside * local_step;
        }
        // By here we're probably in a transitional chunk of the nine-patch that
        // requires per-fragment processing, so loop around again to the handler
        // for that case.
    }

    // Fill any remaining sections of the span that are clipped out.
    if (swgl_SpanLength > 0) {
        swgl_commitPartialSolidR8(swgl_SpanLength, vClipMode.x);
    }
}
#endif

#endif