tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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