cs_clip_rectangle.glsl (23180B)
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,ellipse 6 7 varying highp vec4 vLocalPos; 8 #ifdef WR_FEATURE_FAST_PATH 9 flat varying highp vec4 v_clip_radii; 10 flat varying highp vec2 v_clip_size; 11 #else 12 flat varying highp vec4 vClipCenter_Radius_TL; 13 flat varying highp vec4 vClipCenter_Radius_TR; 14 flat varying highp vec4 vClipCenter_Radius_BL; 15 flat varying highp vec4 vClipCenter_Radius_BR; 16 flat varying highp vec3 vClipPlane_TL; 17 flat varying highp vec3 vClipPlane_TR; 18 flat varying highp vec3 vClipPlane_BL; 19 flat varying highp vec3 vClipPlane_BR; 20 #endif 21 // Clip mode. Packed in to a vector to work around bug 1630356. 22 flat varying mediump vec2 vClipMode; 23 24 #ifdef WR_VERTEX_SHADER 25 26 PER_INSTANCE in vec2 aClipLocalPos; 27 PER_INSTANCE in vec4 aClipLocalRect; 28 PER_INSTANCE in float aClipMode; 29 PER_INSTANCE in vec4 aClipRect_TL; 30 PER_INSTANCE in vec4 aClipRadii_TL; 31 PER_INSTANCE in vec4 aClipRect_TR; 32 PER_INSTANCE in vec4 aClipRadii_TR; 33 PER_INSTANCE in vec4 aClipRect_BL; 34 PER_INSTANCE in vec4 aClipRadii_BL; 35 PER_INSTANCE in vec4 aClipRect_BR; 36 PER_INSTANCE in vec4 aClipRadii_BR; 37 38 struct ClipMaskInstanceRect { 39 ClipMaskInstanceCommon base; 40 vec2 local_pos; 41 }; 42 43 ClipMaskInstanceRect fetch_clip_item() { 44 ClipMaskInstanceRect cmi; 45 46 cmi.base = fetch_clip_item_common(); 47 cmi.local_pos = aClipLocalPos; 48 49 return cmi; 50 } 51 52 struct ClipRect { 53 RectWithEndpoint rect; 54 float mode; 55 }; 56 57 struct ClipCorner { 58 RectWithEndpoint rect; 59 vec4 outer_inner_radius; 60 }; 61 62 struct ClipData { 63 ClipRect rect; 64 ClipCorner top_left; 65 ClipCorner top_right; 66 ClipCorner bottom_left; 67 ClipCorner bottom_right; 68 }; 69 70 ClipData fetch_clip() { 71 ClipData clip; 72 73 clip.rect = ClipRect(RectWithEndpoint(aClipLocalRect.xy, aClipLocalRect.zw), aClipMode); 74 clip.top_left = ClipCorner(RectWithEndpoint(aClipRect_TL.xy, aClipRect_TL.zw), aClipRadii_TL); 75 clip.top_right = ClipCorner(RectWithEndpoint(aClipRect_TR.xy, aClipRect_TR.zw), aClipRadii_TR); 76 clip.bottom_left = ClipCorner(RectWithEndpoint(aClipRect_BL.xy, aClipRect_BL.zw), aClipRadii_BL); 77 clip.bottom_right = ClipCorner(RectWithEndpoint(aClipRect_BR.xy, aClipRect_BR.zw), aClipRadii_BR); 78 79 return clip; 80 } 81 82 void main(void) { 83 ClipMaskInstanceRect cmi = fetch_clip_item(); 84 Transform clip_transform = fetch_transform(cmi.base.clip_transform_id); 85 Transform prim_transform = fetch_transform(cmi.base.prim_transform_id); 86 ClipData clip = fetch_clip(); 87 88 RectWithEndpoint local_rect = clip.rect.rect; 89 vec2 diff = cmi.local_pos - local_rect.p0; 90 local_rect.p0 = cmi.local_pos; 91 local_rect.p1 += diff; 92 93 ClipVertexInfo vi = write_clip_tile_vertex( 94 local_rect, 95 prim_transform, 96 clip_transform, 97 cmi.base.sub_rect, 98 cmi.base.task_origin, 99 cmi.base.screen_origin, 100 cmi.base.device_pixel_scale 101 ); 102 103 vClipMode.x = clip.rect.mode; 104 vLocalPos = vi.local_pos; 105 106 #ifdef WR_FEATURE_FAST_PATH 107 // If the radii are all uniform, we can use a much simpler 2d 108 // signed distance function to get a rounded rect clip. 109 vec2 half_size = 0.5 * rect_size(local_rect); 110 vLocalPos.xy -= (half_size + cmi.local_pos) * vi.local_pos.w; 111 v_clip_size = half_size; 112 v_clip_radii = vec4( 113 clip.bottom_right.outer_inner_radius.x, 114 clip.top_right.outer_inner_radius.x, 115 clip.bottom_left.outer_inner_radius.x, 116 clip.top_left.outer_inner_radius.x 117 ); 118 #else 119 RectWithEndpoint clip_rect = local_rect; 120 121 vec2 r_tl = clip.top_left.outer_inner_radius.xy; 122 vec2 r_tr = clip.top_right.outer_inner_radius.xy; 123 vec2 r_br = clip.bottom_right.outer_inner_radius.xy; 124 vec2 r_bl = clip.bottom_left.outer_inner_radius.xy; 125 126 vClipCenter_Radius_TL = vec4(clip_rect.p0 + r_tl, 127 inverse_radii_squared(r_tl)); 128 129 vClipCenter_Radius_TR = vec4(clip_rect.p1.x - r_tr.x, 130 clip_rect.p0.y + r_tr.y, 131 inverse_radii_squared(r_tr)); 132 133 vClipCenter_Radius_BR = vec4(clip_rect.p1 - r_br, 134 inverse_radii_squared(r_br)); 135 136 vClipCenter_Radius_BL = vec4(clip_rect.p0.x + r_bl.x, 137 clip_rect.p1.y - r_bl.y, 138 inverse_radii_squared(r_bl)); 139 140 // We need to know the half-spaces of the corners separate from the center 141 // and radius. We compute a point that falls on the diagonal (which is just 142 // an inner vertex pushed out along one axis, but not on both) to get the 143 // plane offset of the half-space. We also compute the direction vector of 144 // the half-space, which is a perpendicular vertex (-y,x) of the vector of 145 // the diagonal. We leave the scales of the vectors unchanged. 146 vec2 n_tl = -r_tl.yx; 147 vec2 n_tr = vec2(r_tr.y, -r_tr.x); 148 vec2 n_br = r_br.yx; 149 vec2 n_bl = vec2(-r_bl.y, r_bl.x); 150 vClipPlane_TL = vec3(n_tl, 151 dot(n_tl, vec2(clip_rect.p0.x, clip_rect.p0.y + r_tl.y))); 152 vClipPlane_TR = vec3(n_tr, 153 dot(n_tr, vec2(clip_rect.p1.x - r_tr.x, clip_rect.p0.y))); 154 vClipPlane_BR = vec3(n_br, 155 dot(n_br, vec2(clip_rect.p1.x, clip_rect.p1.y - r_br.y))); 156 vClipPlane_BL = vec3(n_bl, 157 dot(n_bl, vec2(clip_rect.p0.x + r_bl.x, clip_rect.p1.y))); 158 #endif 159 } 160 #endif 161 162 #ifdef WR_FRAGMENT_SHADER 163 164 #ifdef WR_FEATURE_FAST_PATH 165 // See https://www.shadertoy.com/view/4llXD7 166 // Notes: 167 // * pos is centered in the origin (so 0,0 is the center of the box). 168 // * The border radii must not be larger than half_box_size. 169 float sd_round_box(in vec2 pos, in vec2 half_box_size, in vec4 radii) { 170 radii.xy = (pos.x > 0.0) ? radii.xy : radii.zw; 171 radii.x = (pos.y > 0.0) ? radii.x : radii.y; 172 vec2 q = abs(pos) - half_box_size + radii.x; 173 return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radii.x; 174 } 175 #endif 176 177 void main(void) { 178 vec2 local_pos = vLocalPos.xy / vLocalPos.w; 179 float aa_range = compute_aa_range(local_pos); 180 181 #ifdef WR_FEATURE_FAST_PATH 182 float dist = sd_round_box(local_pos, v_clip_size, v_clip_radii); 183 #else 184 float dist = distance_to_rounded_rect( 185 local_pos, 186 vClipPlane_TL, 187 vClipCenter_Radius_TL, 188 vClipPlane_TR, 189 vClipCenter_Radius_TR, 190 vClipPlane_BR, 191 vClipCenter_Radius_BR, 192 vClipPlane_BL, 193 vClipCenter_Radius_BL, 194 vTransformBounds 195 ); 196 #endif 197 198 // Compute AA for the given dist and range. 199 float alpha = distance_aa(aa_range, dist); 200 201 // Select alpha or inverse alpha depending on clip in/out. 202 float final_alpha = mix(alpha, 1.0 - alpha, vClipMode.x); 203 204 float final_final_alpha = vLocalPos.w > 0.0 ? final_alpha : 0.0; 205 oFragColor = vec4(final_final_alpha, 0.0, 0.0, 1.0); 206 } 207 208 #ifdef SWGL_DRAW_SPAN 209 // Currently the cs_clip_rectangle shader is slow because it always evaluates 210 // the corner ellipse segments and the rectangle AA for every fragment the 211 // shader is run on. To alleviate this for now with SWGL, this essentially 212 // implements a rounded-rectangle span rasterizer inside the span shader. The 213 // motivation is that we can separate out the parts of the span which are fully 214 // opaque and fully transparent, outputting runs of fixed color in those areas, 215 // while only evaluating the ellipse segments and AA in the smaller outlying 216 // parts of the span that actually need it. 217 // The shader conceptually represents a rounded rectangle as an inner octagon 218 // (8 half-spaces) bounding the opaque region and an outer octagon bounding the 219 // curve and AA parts. Everything outside is transparent. The line of the span 220 // is intersected with half-spaces, looking for interior spans that minimally 221 // intersect the half-spaces (start max, end min). In the ideal case we hit a 222 // start corner ellipse segment and an end corner ellipse segment, rendering 223 // the two curves on the ends with an opaque run in between, outputting clear 224 // for any transparent runs before and after the start and end curves. 225 // This is slightly complicated by the fact that the results here must agree 226 // with the main results of the fragment shader, in case SWGL has to fall back 227 // to the main fragment shader for any reason. So, we make an effort to handle 228 // both ways of operating - the uniform radius fast-path and the varying radius 229 // slow-path. 230 void swgl_drawSpanR8() { 231 // Perspective is not supported. 232 if (swgl_interpStep(vLocalPos).w != 0.0) { 233 return; 234 } 235 236 // If the span is completely outside the Z-range and clipped out, just 237 // output clear so we don't need to consider invalid W in the rest of the 238 // shader. 239 float w = swgl_forceScalar(vLocalPos.w); 240 if (w <= 0.0) { 241 swgl_commitSolidR8(0.0); 242 return; 243 } 244 245 // To start, we evaluate the rounded-rectangle in local space relative to 246 // the local-space position. This will be interpolated across the span to 247 // track whether we intersect any half-spaces. 248 w = 1.0 / w; 249 vec2 local_pos = vLocalPos.xy * w; 250 vec2 local_pos0 = swgl_forceScalar(local_pos); 251 vec2 local_step = swgl_interpStep(vLocalPos).xy * w; 252 float step_scale = max(dot(local_step, local_step), 1.0e-6); 253 254 // Get the local-space AA range. This range represents 1/fwidth(local_pos), 255 // essentially the scale of how much local-space maps to an AA pixel. We 256 // need to know the inverse, how much local-space we traverse per AA pixel 257 // pixel step. We then scale this to represent the amount of span steps 258 // traversed per AA pixel step. 259 float aa_range = compute_aa_range(local_pos); 260 float aa_margin = inversesqrt(aa_range * aa_range * step_scale); 261 262 // We need to know the bounds of the aligned rectangle portion of the rrect 263 // in local-space. If we're using the fast-path, this is specified as the 264 // half-width of the rrect in v_clip_size, which we map to the outer 265 // bounding-box. For the general case, we have already stored the outer 266 // bounding box in vTransformBounds. 267 #ifdef WR_FEATURE_FAST_PATH 268 vec4 clip_rect = vec4(-v_clip_size, v_clip_size); 269 #else 270 vec4 clip_rect = vTransformBounds; 271 #endif 272 273 // We need to compute the local-space distance to the bounding box and then 274 // figure out how many processing steps that maps to. If we are stepping in 275 // a negative direction on an axis, we need to swap the sides of the box 276 // which we consider as the start or end. If there is no local-space step 277 // on an axis (i.e. constant Y), we need to take care to force the steps to 278 // either the start or end of the span depending on if we are inside or 279 // outside of the bounding box. 280 vec4 clip_dist = 281 mix(clip_rect, clip_rect.zwxy, lessThan(local_step, vec2(0.0)).xyxy) 282 - local_pos0.xyxy; 283 clip_dist = 284 mix(1.0e6 * step(0.0, clip_dist), 285 clip_dist * recip(local_step).xyxy, 286 notEqual(local_step, vec2(0.0)).xyxy); 287 288 // Initially, the opaque region is bounded by the further start intersect 289 // with the bounding box and the nearest end intersect with the bounding 290 // box. 291 float opaque_start = max(clip_dist.x, clip_dist.y); 292 float opaque_end = min(clip_dist.z, clip_dist.w); 293 float aa_start = opaque_start; 294 float aa_end = opaque_end; 295 296 // Here we actually intersect with the half-space of the corner. We get the 297 // plane distance of the local-space position from the diagonal bounding 298 // ellipse segment from the opaque region. The half-space is defined by the 299 // direction vector of the plane and an offset point that falls on the 300 // dividing line (which is a vertex on the corner box, which is actually on 301 // the outer radius of the bounding box, but not a corner vertex). This 302 // distance is positive if on the curve side and negative if on the inner 303 // opaque region. If we are on the curve side, we need to verify we are 304 // traveling in direction towards the opaque region so that we will 305 // eventually intersect the diagonal so we can calculate when the start 306 // corner segment will end, otherwise we are going away from the rrect. 307 // If we are inside the opaque interior, we need to verify we are traveling 308 // in direction towards the curve, so that we can calculate when the end 309 // corner segment will start. Further, if we intersect, we calculate the 310 // offset of the outer octagon where AA starts from the inner octagon of 311 // where the opaque region starts using the apex vector (which is transpose 312 // of the half-space's direction). 313 // 314 // We need to intersect the corner ellipse segments. Significantly, we need 315 // to know where the apex of the ellipse segment is and how far to push the 316 // outer diagonal of the octagon from the inner diagonal. The position of 317 // the inner diagonal simply runs diagonal across the corner box and has a 318 // constant offset from vertex on the inner bounding box. The apex also has 319 // a constant offset along the opposite diagonal relative to the diagonal 320 // intersect which is 1/sqrt(2) - 0.5 assuming unit length for the diagonal. 321 // We then need to project the vector to the apex onto the local-space step 322 // scale, but we do this with reference to the normal vector of the diagonal 323 // using dot(normal, apex) / dot(normal, local_step), where the apex vector 324 // is (0.7071 - 0.5) * abs(normal).yx * sign(normal). 325 vec3 start_plane = vec3(1.0e6); 326 vec3 end_plane = vec3(1.0e6); 327 328 // plane is assumed to be a vec3 with normal in (X, Y) and offset in Z. 329 #define CLIP_CORNER(plane, info) do { \ 330 float dist = dot(local_pos0, plane.xy) - plane.z; \ 331 float scale = -dot(local_step, plane.xy); \ 332 if (scale >= 0.0) { \ 333 if (dist > opaque_start * scale) { \ 334 SET_CORNER(start_corner, info); \ 335 start_plane = plane; \ 336 float inv_scale = recip(max(scale, 1.0e-6)); \ 337 opaque_start = dist * inv_scale; \ 338 float apex = (0.7071 - 0.5) * 2.0 * abs(plane.x * plane.y); \ 339 aa_start = opaque_start - apex * inv_scale; \ 340 } \ 341 } else if (dist > opaque_end * scale) { \ 342 SET_CORNER(end_corner, info); \ 343 end_plane = plane; \ 344 float inv_scale = recip(min(scale, -1.0e-6)); \ 345 opaque_end = dist * inv_scale; \ 346 float apex = (0.7071 - 0.5) * 2.0 * abs(plane.x * plane.y); \ 347 aa_end = opaque_end - apex * inv_scale; \ 348 } \ 349 } while (false) 350 351 #ifdef WR_FEATURE_FAST_PATH 352 // For the fast-path, we only have the half-width of the outer bounding 353 // box. We need to map this to points that fall on the diagonal of the 354 // half-space for each corner. To do this we just need to push out the 355 // vertex in the right direction on a single axis, leaving the other 356 // unchanged. 357 // However, since the corner radii are symmetric, and since the local 358 // origin of each ellipse is assumed to be at (0, 0), the plane offset 359 // of the half-space is similar for each case. 360 // So for a given corner radii of z, given a corner offset (x, y - z) 361 // and a vector of (z, z), the dot product becomes: 362 // x * z + (y-z)*z == x*z + y*z - z*z 363 // The direction vector of the corner half-space has constant length, 364 // but just needs an appropriate direction set. 365 #define OFFSET_FOR(radii) \ 366 (v_clip_size.x + v_clip_size.y - radii) * radii 367 vec3 plane_br = vec3(v_clip_radii.xx, OFFSET_FOR(v_clip_radii.x)); 368 vec3 plane_tr = vec3(v_clip_radii.y, -v_clip_radii.y, OFFSET_FOR(v_clip_radii.y)); 369 vec3 plane_bl = vec3(-v_clip_radii.z, v_clip_radii.z, OFFSET_FOR(v_clip_radii.z)); 370 vec3 plane_tl = vec3(-v_clip_radii.ww, OFFSET_FOR(v_clip_radii.w)); 371 372 #define SET_CORNER(corner, info) 373 374 // Clip against the corner half-spaces. 375 CLIP_CORNER(plane_tl, ); 376 CLIP_CORNER(plane_tr, ); 377 CLIP_CORNER(plane_br, ); 378 CLIP_CORNER(plane_bl, ); 379 380 // Later we need to calculate distance AA for both corners and the 381 // outer bounding rect. For the fast-path, this is all done inside 382 // sd_round_box. 383 #define AA_RECT(local_pos) \ 384 sd_round_box(local_pos, v_clip_size, v_clip_radii) 385 #else 386 // For the general case, we need to remember which of the actual start 387 // and end corners we intersect, so that we can evaluate the curve AA 388 // against only those corners rather than having to try against all 4 389 // corners for both sides of the span. Initialize these values so that 390 // if no corner is intersected, they will just zero the AA. 391 vec4 start_corner = vec4(vec2(1.0e6), vec2(1.0)); 392 vec4 end_corner = vec4(vec2(1.0e6), vec2(1.0)); 393 394 #define SET_CORNER(corner, info) corner = info 395 396 // Clip against the corner half-spaces. We have already computed the 397 // corner half-spaces in the vertex shader. 398 CLIP_CORNER(vClipPlane_TL, vClipCenter_Radius_TL); 399 CLIP_CORNER(vClipPlane_TR, vClipCenter_Radius_TR); 400 CLIP_CORNER(vClipPlane_BR, vClipCenter_Radius_BR); 401 CLIP_CORNER(vClipPlane_BL, vClipCenter_Radius_BL); 402 403 // Later we need to calculate distance AA for both corners and the 404 // outer bounding rect. For the general case, we need to explicitly 405 // evaluate either the ellipse segment distance or the rect distance. 406 #define AA_RECT(local_pos) \ 407 signed_distance_rect(local_pos, vTransformBounds.xy, vTransformBounds.zw) 408 #define AA_CORNER(local_pos, corner) \ 409 distance_to_ellipse_approx(local_pos - corner.xy, corner.zw, 1.0) 410 #endif 411 412 // Pad the AA region by a margin, as the intersections take place assuming 413 // pixel centers, but AA actually starts half a pixel away from the center. 414 // If the AA region narrows to nothing, be careful not to inflate so much 415 // that we start processing AA for fragments that don't need it. 416 aa_margin = max(aa_margin - max(aa_start - aa_end, 0.0), 0.0); 417 aa_start -= aa_margin; 418 aa_end += aa_margin; 419 420 // Compute the thresholds at which we need to transition between various 421 // segments of the span, from fully transparent outside to the start of 422 // the outer octagon where AA starts, from there to where the inner opaque 423 // octagon starts, from there to where the opaque inner octagon ends and 424 // AA starts again, to finally where the outer octagon/AA ends and we're 425 // back to fully transparent. These thresholds are just flipped offsets 426 // from the start of the span so we can compare against the remaining 427 // span length which automatically deducts as we commit fragments. 428 ivec4 steps = ivec4(clamp( 429 swgl_SpanLength - 430 swgl_StepSize * 431 vec4(floor(aa_start), ceil(opaque_start), floor(opaque_end), ceil(aa_end)), 432 0.0, swgl_SpanLength)); 433 int aa_start_len = steps.x; 434 int opaque_start_len = steps.y; 435 int opaque_end_len = steps.z; 436 int aa_end_len = steps.w; 437 438 // Output fully clear while we're outside the AA region. 439 if (swgl_SpanLength > aa_start_len) { 440 int num_aa = swgl_SpanLength - aa_start_len; 441 swgl_commitPartialSolidR8(num_aa, vClipMode.x); 442 local_pos += float(num_aa / swgl_StepSize) * local_step; 443 } 444 #ifdef AA_CORNER 445 if (start_plane.x < 1.0e5) { 446 // We're now in the outer octagon which requires AA. Evaluate the corner 447 // distance of the start corner here and output AA for it. Before we hit 448 // the actual opaque inner octagon, we have a transitional step where the 449 // diagonal might intersect mid-way through the step. We have consider 450 // either the corner or rect distance depending on which side we're on. 451 while (swgl_SpanLength > opaque_start_len) { 452 float alpha = distance_aa(aa_range, 453 dot(local_pos, start_plane.xy) > start_plane.z 454 ? AA_CORNER(local_pos, start_corner) 455 : AA_RECT(local_pos)); 456 swgl_commitColorR8(mix(alpha, 1.0 - alpha, vClipMode.x)); 457 local_pos += local_step; 458 } 459 } 460 #endif 461 // If there's no start corner, just do rect AA until opaque. 462 while (swgl_SpanLength > opaque_start_len) { 463 float alpha = distance_aa(aa_range, AA_RECT(local_pos)); 464 swgl_commitColorR8(mix(alpha, 1.0 - alpha, vClipMode.x)); 465 local_pos += local_step; 466 } 467 // Now we're finally in the opaque inner octagon part of the span. Just 468 // output a solid run. 469 if (swgl_SpanLength > opaque_end_len) { 470 int num_opaque = swgl_SpanLength - opaque_end_len; 471 swgl_commitPartialSolidR8(num_opaque, 1.0 - vClipMode.x); 472 local_pos += float(num_opaque / swgl_StepSize) * local_step; 473 } 474 #ifdef AA_CORNER 475 if (end_plane.x < 1.0e5) { 476 // Finally we're in the AA region on the other side, inside the outer 477 // octagon again. Just evaluate the distance to the end corner and 478 // compute AA for it. We're leaving the opaque inner octagon, but like 479 // before, we have to be careful we're not dealing with a step partially 480 // intersected by the end corner's diagonal. Check which side we are on 481 // and use either the corner or rect distance as appropriate. 482 while (swgl_SpanLength > aa_end_len) { 483 float alpha = distance_aa(aa_range, 484 dot(local_pos, end_plane.xy) > end_plane.z 485 ? AA_CORNER(local_pos, end_corner) 486 : AA_RECT(local_pos)); 487 swgl_commitColorR8(mix(alpha, 1.0 - alpha, vClipMode.x)); 488 local_pos += local_step; 489 } 490 } 491 #endif 492 // If there's no end corner, just do rect AA until clear. 493 while (swgl_SpanLength > aa_end_len) { 494 float alpha = distance_aa(aa_range, AA_RECT(local_pos)); 495 swgl_commitColorR8(mix(alpha, 1.0 - alpha, vClipMode.x)); 496 local_pos += local_step; 497 } 498 // We're now outside the outer AA octagon on the other side. Just output 499 // fully clear. 500 if (swgl_SpanLength > 0) { 501 swgl_commitPartialSolidR8(swgl_SpanLength, vClipMode.x); 502 } 503 } 504 #endif 505 506 #endif