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