/*
 * Copyright 2022 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "src/gpu/graphite/ClipStack.h"

#include "include/core/SkBlendMode.h"
#include "include/core/SkClipOp.h"
#include "include/core/SkM44.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRect.h"
#include "include/core/SkScalar.h"
#include "include/core/SkShader.h"
#include "include/core/SkSpan.h"
#include "include/core/SkStrokeRec.h"
#include "include/gpu/graphite/Recorder.h"
#include "include/private/base/SkDebug.h"
#include "include/private/base/SkFloatingPoint.h"
#include "src/base/SkEnumBitMask.h"
#include "src/base/SkVx.h"
#include "src/core/SkPathPriv.h"
#include "src/core/SkRRectPriv.h"
#include "src/core/SkRectPriv.h"
#include "src/gpu/graphite/AtlasProvider.h"
#include "src/gpu/graphite/ClipAtlasManager.h"
#include "src/gpu/graphite/Device.h"
#include "src/gpu/graphite/DrawParams.h"
#include "src/gpu/graphite/RecorderPriv.h"
#include "src/gpu/graphite/TextureProxy.h"
#include "src/gpu/graphite/geom/BoundsManager.h"
#include "src/gpu/graphite/geom/EdgeAAQuad.h"
#include "src/gpu/graphite/geom/Geometry.h"
#include "src/gpu/graphite/geom/NonMSAAClip.h"

#include <algorithm>
#include <atomic>
#include <utility>

namespace skgpu::graphite {

namespace {

Rect subtract(const Rect& a, const Rect& b, bool exact) {
    SkRect diff;
    if (SkRectPriv::Subtract(a.asSkRect(), b.asSkRect(), &diff) || !exact) {
        // Either A-B is exactly the rectangle stored in diff, or we don't need an exact answer
        // and can settle for the subrect of A excluded from B (which is also 'diff')
        return Rect{diff};
    } else {
        // For our purposes, we want the original A when A-B cannot be exactly represented
        return a;
    }
}

static constexpr uint32_t kInvalidGenID  = 0;
static constexpr uint32_t kEmptyGenID    = 1;
static constexpr uint32_t kWideOpenGenID = 2;

uint32_t next_gen_id() {
    // 0-2 are reserved for invalid, empty & wide-open
    static const uint32_t kFirstUnreservedGenID = 3;
    static std::atomic<uint32_t> nextID{kFirstUnreservedGenID};

    uint32_t id;
    do {
        id = nextID.fetch_add(1, std::memory_order_relaxed);
    } while (id < kFirstUnreservedGenID);
    return id;
}

bool oriented_bbox_intersection(const Rect& a, const Transform& aXform,
                                const Rect& b, const Transform& bXform) {
    // NOTE: We intentionally exclude projected bounds for two reasons:
    //   1. We can skip the division by w and worring about clipping to w = 0.
    //   2. W/o the projective case, the separating axes are simpler to compute (see below).
    SkASSERT(aXform.type() != Transform::Type::kPerspective &&
             bXform.type() != Transform::Type::kPerspective);
    SkV4 quadA[4], quadB[4];

    aXform.mapPoints(a, quadA);
    bXform.mapPoints(b, quadB);

    // There are 4 separating axes, defined by the two normals from quadA and from quadB, but
    // since they were produced by transforming a rectangle by an affine transform, we know the
    // normals are orthoganal to the basis vectors of upper 2x2 of their two transforms.
    auto axesX = skvx::float4(-aXform.matrix().rc(1,0), -aXform.matrix().rc(1,1),
                              -bXform.matrix().rc(1,0), -bXform.matrix().rc(1,1));
    auto axesY = skvx::float4(aXform.matrix().rc(0,0), aXform.matrix().rc(0,1),
                              bXform.matrix().rc(0,0), bXform.matrix().rc(0,1));

    // Projections of the 4 corners of each quadrilateral vs. the 4 axes. For orthonormal
    // transforms, the projections of a quad's corners to its own normal axes should work out
    // to the original dimensions of the rectangle, but this code handles skew and scale factors
    // without branching.
    auto aProj0 = quadA[0].x * axesX + quadA[0].y * axesY;
    auto aProj1 = quadA[1].x * axesX + quadA[1].y * axesY;
    auto aProj2 = quadA[2].x * axesX + quadA[2].y * axesY;
    auto aProj3 = quadA[3].x * axesX + quadA[3].y * axesY;

    auto bProj0 = quadB[0].x * axesX + quadB[0].y * axesY;
    auto bProj1 = quadB[1].x * axesX + quadB[1].y * axesY;
    auto bProj2 = quadB[2].x * axesX + quadB[2].y * axesY;
    auto bProj3 = quadB[3].x * axesX + quadB[3].y * axesY;

    // Minimum and maximum projected values against the 4 axes, for both quadA and quadB, which
    // gives us four pairs of intervals to test for separation.
    auto minA = min(min(aProj0, aProj1), min(aProj2, aProj3));
    auto maxA = max(max(aProj0, aProj1), max(aProj2, aProj3));
    auto minB = min(min(bProj0, bProj1), min(bProj2, bProj3));
    auto maxB = max(max(bProj0, bProj1), max(bProj2, bProj3));

    auto overlaps = (minB <= maxA) & (minA <= maxB);
    return all(overlaps); // any non-overlapping interval would imply no intersection
}

// LTRB are set in returned bitmask if other's LTRB edge is coincident or inside `shape`'s edge.
SkEnumBitMask<EdgeAAQuad::Flags> clipped_edges(const Rect& shape, const Rect& other) {
    // Since RB are stored negated in vals(), this works out to
    //     [other.LT >= shape.LT, other.RB <= shape.RB]
    auto insideMask = other.vals() >= shape.vals();
    return (insideMask[0] ? EdgeAAQuad::Flags::kLeft   : EdgeAAQuad::Flags::kNone) |
           (insideMask[1] ? EdgeAAQuad::Flags::kTop    : EdgeAAQuad::Flags::kNone) |
           (insideMask[2] ? EdgeAAQuad::Flags::kRight  : EdgeAAQuad::Flags::kNone) |
           (insideMask[3] ? EdgeAAQuad::Flags::kBottom : EdgeAAQuad::Flags::kNone);
}

// Tries to intersect `otherShape` transformed by `otherToDevice` directly into `shape` assuming
// that `shape` is transformed by localToDevice. If possible (true), `shape` represents the exact
// intersection of the two original shapes. Returns true if `shape` is modified, false otherwise.
bool intersect_shape(const Transform& otherToDevice, const Shape& otherShape,
                     const Transform& localToDevice, Shape* shape,
                     SkEnumBitMask<EdgeAAQuad::Flags>* edgeFlags) {
    // There are only a subset of shape types that we can analytically intersect with each other,
    // assuming a simple fill style (always the case for clip shapes):
    //
    //  rects, rrects, flood-fills (empty+inverse-fill)
    //
    // Flood-fills only appear as part of a draw, so it's only checked for `shape` and not
    // `otherShape`. In theory, per-edge AA quads could also be included but they do not appear as
    // clip shapes.
    //
    // Paths and arcs have complex intersection logic, so are skipped under the assumption that
    // simple cases have already been mapped to a rect or rrect. Lines are only ever stroked, so
    // are incompatible with this function.
    //
    // EdgeAAQuads that are rectangular can be intersected by being treated as a rect shape and
    // adjusting edge flags as non-AA edges are clipped out.
    bool shapeIntersectable = shape->isRect() ||
                              shape->isRRect() ||
                              shape->isFloodFill();
    bool otherIntersectable = otherShape.isRect() || otherShape.isRRect();
    // Only clip shapes are used for `otherShape`, so we shouldn't see any flood fills here
    SkASSERT(!otherShape.isFloodFill());
    // Only rects should have edge flags other than kAll
    SkASSERT(*edgeFlags == EdgeAAQuad::Flags::kAll || shape->isRect());

    if (!shapeIntersectable || !otherIntersectable) {
        // Technically if shapeIntersectable was true for empty+inverse, we could turn the flood
        // fill into `otherShape` regardless of its type, but those other types are more expensive
        // to render and in the situation where many draws fill against a clip path, we'd want to
        // draw the clip a single time vs. drawing the path multiple times.
        return false;
    }

    // In order to combine, otherShape must be able to map into `localToDevice` without changing
    // shape class (e.g. to a path when rotated) in order for shading to apply in the same
    // coordinate space. This is possible if the relative transform between otherToDevice and
    // localToDevice is rectStaysRect.
    Transform storage{SkM44::kUninitialized_Constructor};

    // We track `local` to `other` and use the `inverseMapRect` functions to map the `otherShape`
    // into local space when possible. Using `localToOther` instead of `otherToLocal` allows the
    // common case of a device-space clip (otherToDevice == I) and an axis-aligned draw to
    // simply use `localToDevice` as `localToOther`.
    const Transform* localToOther;

    if (otherToDevice == localToDevice) {
        // No coordinate space conversion, so set to null to signal identity mapping is skippable.
        // NOTE: This case arises in clip-clip combinations when both were axis-aligned and pre-
        // transformed to device space.
        localToOther = nullptr;
    } else if (otherToDevice.type() == Transform::Type::kIdentity &&
               localToDevice.type() <= Transform::Type::kRectStaysRect) {
        // Relative transform is (otherToDevice)^-1*localToDevice = localToDevice
        localToOther = &localToDevice;
    } else if (otherToDevice.type() <= Transform::Type::kRectStaysRect &&
               localToDevice.type() == Transform::Type::kIdentity) {
        // Relative transform is otherToDevice^-1*localToDevice = otherToDevice^-1
        // (which may not occur in a common scenario but is harmless). Inverse() is mostly
        // shuffling bytes around, not recomputing the inverse.
        storage = Transform::Inverse(otherToDevice);
        localToOther = &storage;
    } else {
        // Calculate (otherToDevice)^-1*localToDevice and see if the relative transform is
        // of the right type.
        storage = Transform(otherToDevice.inverse() * localToDevice);
        if (storage.type() <= Transform::Type::kRectStaysRect) {
            localToOther = &storage;
        } else {
            // `otherShape` can't be trivially mapped to the local coordinate space
            return false;
        }
    }

    // Since `otherShape` is either a rect or a round rect, bounds() is tight to the linear edges.
    Rect localOtherRect = otherShape.bounds();
    if (localToOther) {
        // In this block, `localOtherRect` is defined in the other coord space and `mapped` is in
        // the local coord space. At the end of the block, `localOtherRect` is set to `mapped` so
        // that afterwards it is always defined in local space.
        Rect mapped = localToOther->inverseMapRect(localOtherRect);
        // If we don't have enough precision, the other shape might not map back to the geometry.
        // Allow up to 1/1000th of a pixel in tolerance when mapping between coordinate spaces,
        // otherwise we'll have to clip the shapes independently.
        const float otherTol = 0.001f * otherToDevice.localAARadius(localOtherRect);
        if (localOtherRect.isEmptyNegativeOrNaN() ||
            !localToOther->mapRect(mapped).nearlyEquals(localOtherRect, otherTol)) {
            return false;
        }
        localOtherRect = mapped;
    }
    // Remember the edges that get clipped by the intersection
    SkEnumBitMask<EdgeAAQuad::Flags> clippedEdges = clipped_edges(shape->bounds(), localOtherRect);
    if (!shape->isFloodFill()) {
        // And now it's tight to the intersection with `shape`, sans any corner rounding
        localOtherRect.intersect(shape->bounds());
    }
    // Make sure that the intersected shape does not become subpixel in size, since drawing a
    // subpixel/hairline shape produces a different result than something that's clipped.
    float localAARadius = localToDevice.localAARadius(localOtherRect);
    if (!std::isfinite(localAARadius) || any(localOtherRect.size() <= localAARadius)) {
        return false;
    }

    SkRRect localOtherRRect;
    if (otherShape.isRect()) {
        if (shape->isRect() || shape->isFloodFill()) {
            SkASSERT(*edgeFlags == EdgeAAQuad::Flags::kAll || !shape->isFloodFill());
            // Assuming that non-AA edges seam with non-AA edges other quads to create a uniform
            // coverage field, we turn on the AA edge flag when coincident or clipped. This will
            // create a nice AA edge from this draw while the other non-AA quad is discarded.
            *edgeFlags |= clippedEdges; // This is a no-op if shape was a flood fill
            shape->setRect(localOtherRect);
            return true;
        } else {
            // Fall back to rrect+rrect intersection
            localOtherRRect = SkRRect::MakeRect(localOtherRect.asSkRect());
        }
    } else {
        SkASSERT(otherShape.isRRect());
        if (localToOther) {
            if (auto rr = otherShape.rrect().transform(localToOther->inverse().asM33())) {
                localOtherRRect = *rr;
            } else {
                // Transformation produced invalid geometry
                return false;
            }
        } else {
            localOtherRRect = otherShape.rrect();
        }

        if (shape->isRect() && *edgeFlags != EdgeAAQuad::Flags::kAll) {
            // When combining a mixed edge AA quad with a rounded rectangle, we require that all
            // non-AA edges be clipped out entirely.
            if ((clippedEdges | *edgeFlags) != EdgeAAQuad::Flags::kAll) {
                // The intersection shows AA'ed round corners and non-AA'ed edges, which can't be
                // represented by just Geometry or Shape.
                return false;
            }
        } else if (shape->isFloodFill()) {
            SkASSERT(*edgeFlags == EdgeAAQuad::Flags::kAll);
            shape->setRRect(localOtherRRect);
            return true;
        } // Else continue with rrect+rrect intersection
    }

    // `shape` can only be rect or rrect at this point, flood fill should already have returned.
    // If we've made it this far, we've also determined that the edge flags should be set to kAll
    // on a successful rrect+rrect intersection.
    SkASSERT(shape->isRect() || shape->isRRect());

    SkRRect localRRect = SkRRectPriv::ConservativeIntersect(
            localOtherRRect,
            shape->isRect() ? SkRRect::MakeRect(shape->rect().asSkRect()) : shape->rrect());
    if (localRRect.isRect()) {
        // Valid shape that can be simplified to rect
        shape->setRect(localRRect.rect());
        *edgeFlags = EdgeAAQuad::Flags::kAll;
        return true;
    } else if (!localRRect.isEmpty()) {
        // Intersection is representable as a rrect still
        shape->setRRect(localRRect);
        *edgeFlags = EdgeAAQuad::Flags::kAll;
        return true;
    } else {
        // Intersection is complex, leave edge flags unmodified
        return false;
    }
}

Rect snap_scissor(const Rect& a, const Rect& deviceBounds) {
    // Snapping to 4 pixel boundaries seems to give a good tradeoff between rasterizing slightly
    // more (but being clipped by the depth test), vs. setting a tight scissor that forces a state
    // change.
    // NOTE: This rounds out to the *next* multiple of 4, so that if the input rectangle happens to
    // land on a multiple of 4 we still create some padding to avoid scissoring just AA outsets.
    static constexpr int kRes = 4;
    Rect snapped = a.makeOutset(kRes - 1.f);
    snapped = Rect::FromVals(snapped.vals() * (1.f / kRes)).makeRoundOut();
    return Rect::FromVals(snapped.vals() * kRes).makeIntersect(deviceBounds);
}

} // anonymous namespace

///////////////////////////////////////////////////////////////////////////////
// ClipStack::TransformedShape

// A flyweight object describing geometry, subject to a local-to-device transform.
// This can be used by SaveRecords, Elements, and draws to determine how two shape operations
// interact with each other, without needing to share a base class, friend each other, or have a
// template for each combination of two types.
struct ClipStack::TransformedShape {
    const Transform& fLocalToDevice;
    const Shape&     fShape;
    const Rect&      fOuterBounds;
    const Rect&      fInnerBounds;

    SkClipOp         fOp;

    // contains() performs a fair amount of work to be as accurate as possible since it can mean
    // greatly simplifying the clip stack. However, in some contexts this isn't worth doing because
    // the actual shape is only an approximation (save records), or there's no current way to take
    // advantage of knowing this shape contains another (draws containing a clip hypothetically
    // could replace their geometry to draw the clip directly, but that isn't implemented now).
    bool fContainsChecksOnlyBounds = false;

    bool intersects(const TransformedShape&) const;
    bool contains(const TransformedShape&) const;
};

bool ClipStack::TransformedShape::intersects(const TransformedShape& o) const {
    if (!fOuterBounds.intersects(o.fOuterBounds)) {
        return false;
    }

    if (fLocalToDevice.type() <= Transform::Type::kRectStaysRect &&
        o.fLocalToDevice.type() <= Transform::Type::kRectStaysRect) {
        // The two shape's coordinate spaces are different but both rect-stays-rect or simpler.
        // This means, though, that their outer bounds approximations are tight to their transormed
        // shape bounds. There's no point to do further tests given that and that we already found
        // that these outer bounds *do* intersect.
        return true;
    } else if (fLocalToDevice == o.fLocalToDevice) {
        // Since the two shape's local coordinate spaces are the same, we can compare shape
        // bounds directly for a more accurate intersection test. We intentionally do not go
        // further and do shape-specific intersection tests since these could have unknown
        // complexity (for paths) and limited utility (e.g. two round rects that are disjoint
        // solely from their corner curves).
        return fShape.bounds().intersects(o.fShape.bounds());
    } else if (fLocalToDevice.type() != Transform::Type::kPerspective &&
               o.fLocalToDevice.type() != Transform::Type::kPerspective) {
        // The shapes don't share the same coordinate system, and their approximate 'outer'
        // bounds in device space could have substantial outsetting to contain the transformed
        // shape (e.g. 45 degree rotation). Perform a more detailed check on their oriented
        // bounding boxes.
        return oriented_bbox_intersection(fShape.bounds(), fLocalToDevice,
                                          o.fShape.bounds(), o.fLocalToDevice);
    }
    // Else multiple perspective transforms are involved, so assume intersection and allow the
    // rasterizer to handle perspective clipping.
    return true;
}

bool ClipStack::TransformedShape::contains(const TransformedShape& o) const {
    if (fInnerBounds.contains(o.fOuterBounds)) {
        return true;
    }
    // Skip more expensive contains() checks if configured not to, or if the extent of 'o' exceeds
    // this shape's outer bounds. When that happens there must be some part of 'o' that cannot be
    // contained in this shape.
    if (fContainsChecksOnlyBounds || !fOuterBounds.contains(o.fOuterBounds)) {
        return false;
    }

    if (fContainsChecksOnlyBounds) {
        return false; // don't do any more work
    }

    if (fLocalToDevice == o.fLocalToDevice) {
        // Test the shapes directly against each other, with a special check for a rrect+rrect
        // containment (a intersect b == a implies b contains a) and paths (same gen ID, or same
        // path for small paths means they contain each other).
        static constexpr int kMaxPathComparePoints = 16;
        if (fShape.isRRect() && o.fShape.isRRect()) {
            return SkRRectPriv::ConservativeIntersect(fShape.rrect(), o.fShape.rrect())
                    == o.fShape.rrect();
        } else if (fShape.isPath() && o.fShape.isPath()) {
            // TODO: Is this worth doing still if clips only cost as much as a single draw?
            return (fShape.path().getGenerationID() == o.fShape.path().getGenerationID()) ||
                    (fShape.path().countPoints() <= kMaxPathComparePoints &&
                    fShape.path() == o.fShape.path());
        } else {
            return fShape.conservativeContains(o.fShape.bounds());
        }
    } else if (fLocalToDevice.type() <= Transform::Type::kRectStaysRect &&
               o.fLocalToDevice.type() <= Transform::Type::kRectStaysRect) {
        // Optimize the common case where o's bounds can be mapped tightly into this coordinate
        // space and then tested against our shape.
        Rect localBounds = fLocalToDevice.inverseMapRect(
                o.fLocalToDevice.mapRect(o.fShape.bounds()));
        return fShape.conservativeContains(localBounds);
    } else if (fShape.convex()) {
        // Since this shape is convex, if all four corners of o's bounding box are inside it
        // then the entirety of o is also guaranteed to be inside it.
        SkV4 deviceQuad[4];
        o.fLocalToDevice.mapPoints(o.fShape.bounds(), deviceQuad);
        SkV4 localQuad[4];
        fLocalToDevice.inverseMapPoints(deviceQuad, localQuad, 4);
        for (int i = 0; i < 4; ++i) {
            // TODO: Would be nice to make this consistent with how the GPU clips NDC w.
            if (deviceQuad[i].w < SkPathPriv::kW0PlaneDistance ||
                localQuad[i].w < SkPathPriv::kW0PlaneDistance) {
                // Something in O actually projects behind the W = 0 plane and would be clipped
                // to infinity, so it's extremely unlikely that this contains O.
                return false;
            }
            if (!fShape.conservativeContains(skvx::float2::Load(localQuad + i) / localQuad[i].w)) {
                return false;
            }
        }
        return true;
    }

    // Else not an easily comparable pair of shapes so assume this doesn't contain O
    return false;
}

ClipStack::SimplifyResult ClipStack::Simplify(const TransformedShape& a,
                                              const TransformedShape& b) {
    enum class ClipCombo {
        kDD = 0b00,
        kDI = 0b01,
        kID = 0b10,
        kII = 0b11
    };

    switch(static_cast<ClipCombo>(((int) a.fOp << 1) | (int) b.fOp)) {
        case ClipCombo::kII:
            // Intersect (A) + Intersect (B)
            if (!a.intersects(b)) {
                // Regions with non-zero coverage are disjoint, so intersection = empty
                return SimplifyResult::kEmpty;
            } else if (b.contains(a)) {
                // B's full coverage region contains entirety of A, so intersection = A
                return SimplifyResult::kAOnly;
            } else if (a.contains(b)) {
                // A's full coverage region contains entirety of B, so intersection = B
                return SimplifyResult::kBOnly;
            } else {
                // The shapes intersect in some non-trivial manner
                return SimplifyResult::kBoth;
            }
        case ClipCombo::kID:
            // Intersect (A) + Difference (B)
            if (!a.intersects(b)) {
                // A only intersects B's full coverage region, so intersection = A
                return SimplifyResult::kAOnly;
            } else if (b.contains(a)) {
                // B's zero coverage region completely contains A, so intersection = empty
                return SimplifyResult::kEmpty;
            } else {
                // Intersection cannot be simplified. Note that the combination of a intersect
                // and difference op in this order cannot produce kBOnly
                return SimplifyResult::kBoth;
            }
        case ClipCombo::kDI:
            // Difference (A) + Intersect (B) - the mirror of Intersect(A) + Difference(B),
            // but combining is commutative so this is equivalent barring naming.
            if (!b.intersects(a)) {
                // B only intersects A's full coverage region, so intersection = B
                return SimplifyResult::kBOnly;
            } else if (a.contains(b)) {
                // A's zero coverage region completely contains B, so intersection = empty
                return SimplifyResult::kEmpty;
            } else {
                // Cannot be simplified
                return SimplifyResult::kBoth;
            }
        case ClipCombo::kDD:
            // Difference (A) + Difference (B)
            if (a.contains(b)) {
                // A's zero coverage region contains B, so B doesn't remove any extra
                // coverage from their intersection.
                return SimplifyResult::kAOnly;
            } else if (b.contains(a)) {
                // Mirror of the above case, intersection = B instead
                return SimplifyResult::kBOnly;
            } else {
                // Intersection of the two differences cannot be simplified. Note that for
                // this op combination it is not possible to produce kEmpty.
                return SimplifyResult::kBoth;
            }
    }
    SkUNREACHABLE;
}

ClipStack::DrawInfluence ClipStack::SimplifyForDraw(const TransformedShape& clip,
                                                    const TransformedShape& draw) {
    // Given the asserts below, we can just recast the SimplifyResult returned from
    // Simplify(A=clip, B=draw):
    //
    // If the result is kEmpty, the draw is clipped out.
    static_assert((int) SimplifyResult::kEmpty == (int) DrawInfluence::kClipsOutDraw);
    // If the result is kAOnly, only the clip's shape provides coverage and the draw could be
    // replaced with something that just covers the clip bounds.
    static_assert((int) SimplifyResult::kAOnly == (int) DrawInfluence::kReplacesDraw);
    // If the result is kBOnly, the clip's shape doesn't impact the draw's coverage at all.
    static_assert((int) SimplifyResult::kBOnly == (int) DrawInfluence::kNone);
    // If the result is kBoth, the clip and the draw combine in a complex manner
    static_assert((int) SimplifyResult::kBoth == (int) DrawInfluence::kComplexInteraction);

    SimplifyResult result = Simplify(clip, draw);
    return static_cast<DrawInfluence>(result);
}

///////////////////////////////////////////////////////////////////////////////
// ClipStack::Element

ClipStack::RawElement::RawElement(const Rect& deviceBounds,
                                  const Transform& localToDevice,
                                  const Shape& shape,
                                  SkClipOp op,
                                  PixelSnapping snapping)
        : Element{shape, localToDevice, op}
        , fUsageBounds{Rect::InfiniteInverted()}
        , fOrder(DrawOrder::kNoIntersection)
        , fMaxZ(DrawOrder::kClearDepth)
        , fInvalidatedByIndex(-1) {
    // Discard shapes that don't have any area (including when a transform can't be inverted, since
    // it means the two dimensions are collapsed to 0 or 1 dimension in device space).
    if (fShape.isLine() || !localToDevice.valid()) {
        fShape.reset();
    }
    // Make sure the shape is not inverted. An inverted shape is equivalent to a non-inverted shape
    // with the clip op toggled.
    if (fShape.inverted()) {
        fOp = (fOp == SkClipOp::kIntersect) ? SkClipOp::kDifference : SkClipOp::kIntersect;
    }

    fOuterBounds = fLocalToDevice.mapRect(fShape.bounds()).makeIntersect(deviceBounds);
    fInnerBounds = Rect::InfiniteInverted();

    // Apply rect-stays-rect transforms to rects and round rects to reduce the number of unique
    // local coordinate systems that are in play.
    if (!fOuterBounds.isEmptyNegativeOrNaN() &&
        fLocalToDevice.type() <= Transform::Type::kRectStaysRect) {
        if (fShape.isRect()) {
            // The actual geometry can be updated to the device-intersected bounds and we know the
            // inner bounds are equal to the outer.
            if (snapping == PixelSnapping::kYes) {
                fOuterBounds.round();
            }
            fShape.setRect(fOuterBounds);
            fLocalToDevice = Transform::Identity();
            fInnerBounds = fOuterBounds;
        } else if (fShape.isRRect()) {
            // Can't transform in place and must still check transform result since some very
            // ill-formed scale+translate matrices can cause invalid rrect radii.
            if (auto xformed = fShape.rrect().transform(fLocalToDevice)) {
                if (snapping == PixelSnapping::kYes) {
                    // The rounded corners will still be anti-aliased, but snap the horizontal and
                    // vertical edges to pixel values.
                    xformed->setRectRadii(SkRect::Make(xformed->rect().round()),
                                          xformed->radii().data());
                }
                fShape.setRRect(*xformed);
                fLocalToDevice = Transform::Identity();
                // Refresh outer bounds to match the transformed round rect in case
                // SkRRect::transform produces slightly different results from Transform::mapRect.
                fOuterBounds = fShape.bounds().makeIntersect(deviceBounds);
                fInnerBounds = Rect{SkRRectPriv::InnerBounds(*xformed)}.makeIntersect(fOuterBounds);
            }
        }
    }

    if (fOuterBounds.isEmptyNegativeOrNaN()) {
        // Either was already an empty shape or a non-empty shape is offscreen, so treat it as such.
        fShape.reset();
        fInnerBounds = Rect::InfiniteInverted();
    }

    // Now that fOp and fShape are canonical, set the shape's fill type to match how it needs to be
    // drawn as a depth-only shape everywhere that is clipped out (intersect is thus inverse-filled)
    fShape.setInverted(fOp == SkClipOp::kIntersect);

    // Post-conditions on inner and outer bounds
    SkASSERT(fShape.isEmpty() || deviceBounds.contains(fOuterBounds));
    this->validate();
}

ClipStack::RawElement::operator ClipStack::TransformedShape() const {
    return {fLocalToDevice, fShape, fOuterBounds, fInnerBounds, fOp};
}

void ClipStack::RawElement::drawClip(Device* device) {
    this->validate();

    // Skip elements that have not affected any draws
    if (!this->hasPendingDraw()) {
        SkASSERT(fUsageBounds.isEmptyNegativeOrNaN());
        return;
    }

    SkASSERT(!fUsageBounds.isEmptyNegativeOrNaN());
    // For clip draws, the usage bounds is the scissor.
    const Rect deviceBounds = Rect::WH(device->width(), device->height());
    Rect scissor = fUsageBounds; // all joined usage bounds are pre-snapped

    // snappedOuterBounds was the rectangle used in updateForDraw() to query the Z order the clip's
    // draw will be inserted at. The scissor must enforce that rendering doesn't happen outside of
    // those bounds.
    Rect snappedOuterBounds = snap_scissor(fOuterBounds, deviceBounds);
    scissor.intersect(snappedOuterBounds);
    // But if the overlap is sufficiently large, just rasterize out to the snapped bounds instead of
    // adding a tight scissor. A factor of 1/2 is used because that corresponds to the area
    // change caused by a 45-degree rotation.
    if (0.5f * snappedOuterBounds.area() < scissor.area()) {
        scissor = snappedOuterBounds;
    }

    Rect drawBounds = fOp == SkClipOp::kIntersect ? scissor : fOuterBounds.makeIntersect(scissor);
    if (!drawBounds.isEmptyNegativeOrNaN()) {
        // Although we are recording this clip draw after all the draws it affects, 'fOrder' was
        // determined at the first usage, so after sorting by DrawOrder the clip draw will be in the
        // right place. Unlike regular draws that use their own "Z", by writing (1 + max Z this clip
        // affects), it will cause those draws to fail either GREATER and GEQUAL depth tests where
        // they need to be clipped.
        DrawOrder order{fMaxZ.next(), fOrder};
        // An element's clip op is encoded in the shape's fill type. Inverse fills are intersect ops
        // and regular fills are difference ops. This means fShape is already in the right state to
        // draw directly.
        SkASSERT((fOp == SkClipOp::kDifference && !fShape.inverted()) ||
                 (fOp == SkClipOp::kIntersect && fShape.inverted()));

        // NOTE: We use fOuterBounds as the transformed shape bounds as that hasn't been clipped by
        // the scissor. It has been clipped by the device bounds, but that shouldn't impact any
        // decisions at this point. If that becomes not the case, we can either recompute the
        // shape's device-space bounds (fLocalToDevice.mapRect(fShape.bounds())) or store a fully
        // unclipped shape bounds on the RawElement.
        device->drawClipShape(fLocalToDevice,
                              fShape,
                              Clip{drawBounds, fOuterBounds, scissor.asSkIRect(),
                                   /* nonMSAAClip= */ {}, /* shader= */ nullptr},
                              order);
    }

    // After the clip shape is drawn, reset its state. If the clip element is being popped off the
    // stack or overwritten because a new clip invalidated it, this won't matter. But if the clips
    // were drawn because the Device had to flush pending work while the clip stack was not empty,
    // subsequent draws will still need to be clipped to the elements. In this case, the usage
    // accumulation process will begin again and automatically use the Device's post-flush Z values
    // and BoundsManager state.
    fUsageBounds = Rect::InfiniteInverted();
    fOrder = DrawOrder::kNoIntersection;
    fMaxZ = DrawOrder::kClearDepth;
}

void ClipStack::RawElement::validate() const {
    // If the shape type isn't empty, the outer bounds shouldn't be empty; if the inner bounds are
    // not empty, they must be contained in outer.
    SkASSERT((fShape.isEmpty() || !fOuterBounds.isEmptyNegativeOrNaN()) &&
             (fInnerBounds.isEmptyNegativeOrNaN() || fOuterBounds.contains(fInnerBounds)));
    SkASSERT((fOp == SkClipOp::kDifference && !fShape.inverted()) ||
             (fOp == SkClipOp::kIntersect && fShape.inverted()));
    SkASSERT(!this->hasPendingDraw() || !fUsageBounds.isEmptyNegativeOrNaN());
}

void ClipStack::RawElement::markInvalid(const SaveRecord& current) {
    SkASSERT(!this->isInvalid());
    fInvalidatedByIndex = current.firstActiveElementIndex();
    // NOTE: We don't draw the accumulated clip usage when the element is marked invalid. Some
    // invalidated elements are part of earlier save records so can become re-active after a restore
    // in which case they should continue to accumulate. Invalidated elements that are part of the
    // active save record are removed at the end of the stack modification, which is when they are
    // explicitly drawn.
}

void ClipStack::RawElement::restoreValid(const SaveRecord& current) {
    if (current.firstActiveElementIndex() < fInvalidatedByIndex) {
        fInvalidatedByIndex = -1;
    }
}

bool ClipStack::RawElement::combine(const RawElement& other, const SaveRecord& current) {
    // Don't combine elements that have collected draw usage, since that changes their geometry.
    if (this->hasPendingDraw() || other.hasPendingDraw()) {
        return false;
    }
    // To reduce the number of possibilities, only consider intersect+intersect. Difference and
    // mixed op cases could be analyzed to simplify one of the shapes, but that is a rare
    // occurrence and the math is much more complicated.
    if (other.fOp != SkClipOp::kIntersect || fOp != SkClipOp::kIntersect) {
        return false;
    }

    // NOTE: intersect_shape operates on the underlying geometry and ignores the fill rule, which
    // because these are intersect clip ops, is the inverse fill. If the shape is updated, the
    // resulting geometry is set to a regular fill so it must be re-inverted to represent the
    // pixels rasterized for a depth-only clip draw.
    SkEnumBitMask<EdgeAAQuad::Flags> edgeFlags = EdgeAAQuad::Flags::kAll;
    const bool shapeUpdated = intersect_shape(other.fLocalToDevice, other.fShape,
                                              fLocalToDevice, &fShape, &edgeFlags);
    SkASSERT(edgeFlags == EdgeAAQuad::Flags::kAll);

    if (shapeUpdated) {
        // This logic works under the assumption that both combined elements were intersect.
        SkASSERT(fOp == SkClipOp::kIntersect && other.fOp == SkClipOp::kIntersect);
        fOuterBounds.intersect(other.fOuterBounds);
        fInnerBounds.intersect(other.fInnerBounds);
        // Inner bounds can become empty, but outer bounds should not be able to.
        SkASSERT(!fOuterBounds.isEmptyNegativeOrNaN());
        fShape.setInverted(true); // Undo intersect_shape setting it to non-inverse
        this->validate();
        return true;
    } else {
        return false;
    }
}

void ClipStack::RawElement::updateForElement(RawElement* added, const SaveRecord& current) {
    if (this->isInvalid()) {
        // Already doesn't do anything, so skip this element
        return;
    }

    // 'A' refers to this element, 'B' refers to 'added'.
    switch (Simplify(*this, *added)) {
        case SimplifyResult::kEmpty:
            // Mark both elements as invalid to signal that the clip is fully empty
            this->markInvalid(current);
            added->markInvalid(current);
            break;

        case SimplifyResult::kAOnly:
            // This element already clips more than 'added', so mark 'added' is invalid to skip it
            added->markInvalid(current);
            break;

        case SimplifyResult::kBOnly:
            // 'added' clips more than this element, so mark this as invalid
            this->markInvalid(current);
            break;

        case SimplifyResult::kBoth:
            // Else the bounds checks think we need to keep both, but depending on the combination
            // of the ops and shape kinds, we may be able to do better.
            if (added->combine(*this, current)) {
                // 'added' now fully represents the combination of the two elements
                this->markInvalid(current);
            }
            break;
    }
}

ClipStack::DrawInfluence ClipStack::RawElement::testForDraw(const TransformedShape& draw) const {
    if (this->isInvalid()) {
        // Cannot affect the draw
        return DrawInfluence::kNone;
    }

    return SimplifyForDraw(*this, draw);
}

CompressedPaintersOrder ClipStack::RawElement::updateForDraw(const BoundsManager* boundsManager,
                                                             const Rect& deviceBounds,
                                                             const Rect& drawBounds,
                                                             PaintersDepth drawZ) {
    SkASSERT(!this->isInvalid());
    SkASSERT(!drawBounds.isEmptyNegativeOrNaN());

    // Always record snapped draw bounds to avoid scissor thrashing since these bounds will be used
    // to determine the scissor applied to the depth-only draw for the clip element.
    Rect snappedDrawBounds = snap_scissor(drawBounds, deviceBounds);

    if (!this->hasPendingDraw()) {
        // No usage yet so we need an order that we will use when drawing to just the depth
        // attachment. It is sufficient to use the next CompressedPaintersOrder after the
        // most recent draw under this clip's outer bounds. It is necessary to use the
        // entire clip's outer bounds because the order has to be determined before the
        // final usage bounds are known and a subsequent draw could require a completely
        // different portion of the clip than this triggering draw.
        //
        // Lazily determining the order has several benefits to computing it when the clip
        // element was first created:
        //  - Elements that are invalidated by nested clips before draws are made do not
        //    waste time in the BoundsManager.
        //  - Elements that never actually modify a draw (e.g. a defensive clip) do not
        //    waste time in the BoundsManager.
        //  - A draw that triggers clip usage on multiple elements will more likely assign
        //    the same order to those elements, meaning their depth-only draws are more
        //    likely to batch in the final DrawPass.
        //
        // However, it does mean that clip elements can have the same order as each other,
        // or as later draws (e.g. after the clip has been popped off the stack). Any
        // overlap between clips or draws is addressed when the clip is drawn by selecting
        // an appropriate DisjointStencilIndex value. Stencil-aside, this order assignment
        // logic, max Z tracking, and the depth test during rasterization are able to
        // resolve everything correctly even if clips have the same order value.
        // See go/clip-stack-order for a detailed analysis of why this works.
        Rect snappedOuterBounds = snap_scissor(fOuterBounds, deviceBounds);
        fOrder = boundsManager->getMostRecentDraw(snappedOuterBounds).next();
        fUsageBounds = snappedDrawBounds;
        fMaxZ = drawZ;
    } else {
        // Earlier draws have already used this element so we cannot change where the
        // depth-only draw will be sorted to, but we need to ensure we cover the new draw's
        // bounds and use a Z value that will clip out its pixels as appropriate.
        fUsageBounds.join(snappedDrawBounds);
        if (drawZ > fMaxZ) {
            fMaxZ = drawZ;
        }
    }

    return fOrder;
}

ClipStack::ClipState ClipStack::RawElement::clipType() const {
    // Map from the internal shape kind to the clip state enum
    switch (fShape.type()) {
        case Shape::Type::kEmpty:
            return ClipState::kEmpty;

        case Shape::Type::kRect:
            return fOp == SkClipOp::kIntersect &&
                   fLocalToDevice.type() == Transform::Type::kIdentity
                        ? ClipState::kDeviceRect : ClipState::kComplex;

        case Shape::Type::kRRect:
            return fOp == SkClipOp::kIntersect &&
                   fLocalToDevice.type() == Transform::Type::kIdentity
                        ? ClipState::kDeviceRRect : ClipState::kComplex;

        case Shape::Type::kArc:
        case Shape::Type::kLine:
            // These types should never become RawElements, but call them kComplex in release builds
            SkASSERT(false);
            [[fallthrough]];

        case Shape::Type::kPath:
            return ClipState::kComplex;
    }
    SkUNREACHABLE;
}

///////////////////////////////////////////////////////////////////////////////
// ClipStack::SaveRecord

ClipStack::SaveRecord::SaveRecord(const Rect& deviceBounds)
        : fInnerBounds(deviceBounds)
        , fOuterBounds(deviceBounds)
        , fShader(nullptr)
        , fStartingElementIndex(0)
        , fOldestValidIndex(0)
        , fDeferredSaveCount(0)
        , fStackOp(SkClipOp::kIntersect)
        , fState(ClipState::kWideOpen)
        , fGenID(kInvalidGenID) {}

ClipStack::SaveRecord::SaveRecord(const SaveRecord& prior,
                                  int startingElementIndex)
        : fInnerBounds(prior.fInnerBounds)
        , fOuterBounds(prior.fOuterBounds)
        , fShader(prior.fShader)
        , fStartingElementIndex(startingElementIndex)
        , fOldestValidIndex(prior.fOldestValidIndex)
        , fDeferredSaveCount(0)
        , fStackOp(prior.fStackOp)
        , fState(prior.fState)
        , fGenID(kInvalidGenID) {
    // If the prior record added an element, this one will insert into the same index
    // (that's okay since we'll remove it when this record is popped off the stack).
    SkASSERT(startingElementIndex >= prior.fStartingElementIndex);
}

uint32_t ClipStack::SaveRecord::genID() const {
    if (fState == ClipState::kEmpty) {
        return kEmptyGenID;
    } else if (fState == ClipState::kWideOpen) {
        return kWideOpenGenID;
    } else {
        // The gen ID shouldn't be empty or wide open, since they are reserved for the above
        // if-cases. It may be kInvalid if the record hasn't had any elements added to it yet.
        SkASSERT(fGenID != kEmptyGenID && fGenID != kWideOpenGenID);
        return fGenID;
    }
}

ClipStack::ClipState ClipStack::SaveRecord::state() const {
    if (fShader && fState != ClipState::kEmpty) {
        return ClipState::kComplex;
    } else {
        return fState;
    }
}

ClipStack::DrawInfluence ClipStack::SaveRecord::testForDraw(const TransformedShape& draw) const {
    Transform identity = Transform::Identity();
    Shape outerSaveBounds{fOuterBounds};
    TransformedShape save{identity, outerSaveBounds, fOuterBounds, fInnerBounds, fStackOp,
                          /*containsChecksOnlyBounds=*/true};

    return SimplifyForDraw(save, draw);
}

void ClipStack::SaveRecord::removeElements(RawElement::Stack* elements, Device* device) {
    while (elements->count() > fStartingElementIndex) {
        // Since the element is being deleted now, it won't be in the ClipStack when the Device
        // calls recordDeferredClipDraws(). Record the clip's draw now (if it needs it).
        elements->back().drawClip(device);
        elements->pop_back();
    }
}

void ClipStack::SaveRecord::restoreElements(RawElement::Stack* elements) {
    // Presumably this SaveRecord is the new top of the stack, and so it owns the elements
    // from its starting index to restoreCount - 1. Elements from the old save record have
    // been destroyed already, so their indices would have been >= restoreCount, and any
    // still-present element can be un-invalidated based on that.
    int i = elements->count() - 1;
    for (RawElement& e : elements->ritems()) {
        if (i < fOldestValidIndex) {
            break;
        }
        e.restoreValid(*this);
        --i;
    }
}

void ClipStack::SaveRecord::addShader(sk_sp<SkShader> shader) {
    SkASSERT(shader);
    SkASSERT(this->canBeUpdated());
    if (!fShader) {
        fShader = std::move(shader);
    } else {
        // The total coverage is computed by multiplying the coverage from each element (shape or
        // shader), but since multiplication is associative, we can use kSrcIn blending to make
        // a new shader that represents 'shader' * 'fShader'
        fShader = SkShaders::Blend(SkBlendMode::kSrcIn, std::move(shader), fShader);
    }
}

bool ClipStack::SaveRecord::addElement(RawElement&& toAdd,
                                       RawElement::Stack* elements,
                                       Device* device) {
    // Validity check the element's state first
    toAdd.validate();

    // And we shouldn't be adding an element if we have a deferred save
    SkASSERT(this->canBeUpdated());

    if (fState == ClipState::kEmpty) {
        // The clip is already empty, and we only shrink, so there's no need to record this element.
        return false;
    } else if (toAdd.shape().isEmpty()) {
        // An empty difference op should have been detected earlier, since it's a no-op
        SkASSERT(toAdd.op() == SkClipOp::kIntersect);
        fState = ClipState::kEmpty;
        this->removeElements(elements, device);
        return true;
    }

    // Here we treat the SaveRecord as a "TransformedShape" with the identity transform, and a shape
    // equal to its outer bounds. This lets us get accurate intersection tests against the new
    // element, but we pass true to skip more detailed contains checks because the SaveRecord's
    // shape is potentially very different from its aggregate outer bounds.
    Shape outerSaveBounds{fOuterBounds};
    Transform identity = Transform::Identity();
    TransformedShape save{identity, outerSaveBounds, fOuterBounds, fInnerBounds, fStackOp,
                          /*containsChecksOnlyBounds=*/true};

    // In this invocation, 'A' refers to the existing stack's bounds and 'B' refers to the new
    // element.
    switch (Simplify(save, toAdd)) {
        case SimplifyResult::kEmpty:
            // The combination results in an empty clip
            fState = ClipState::kEmpty;
            this->removeElements(elements, device);
            return true;

        case SimplifyResult::kAOnly:
            // The combination would not be any different than the existing clip
            return false;

        case SimplifyResult::kBOnly:
            // The combination would invalidate the entire existing stack and can be replaced with
            // just the new element.
            this->replaceWithElement(std::move(toAdd), elements, device);
            return true;

        case SimplifyResult::kBoth:
            // The new element combines in a complex manner, so update the stack's bounds based on
            // the combination of its and the new element's ops (handled below)
            break;
    }

    if (fState == ClipState::kWideOpen) {
        // When the stack was wide open and the clip effect was kBoth, the "complex" manner is
        // simply to keep the element and update the stack bounds to be the element's intersected
        // with the device.
        this->replaceWithElement(std::move(toAdd), elements, device);
        return true;
    }

    // Some form of actual clip element(s) to combine with.
    if (fStackOp == SkClipOp::kIntersect) {
        if (toAdd.op() == SkClipOp::kIntersect) {
            // Intersect (stack) + Intersect (toAdd)
            //  - Bounds updates is simply the paired intersections of outer and inner.
            fOuterBounds.intersect(toAdd.outerBounds());
            fInnerBounds.intersect(toAdd.innerBounds());
            // Outer should not have become empty, but is allowed to if there's no intersection.
            SkASSERT(!fOuterBounds.isEmptyNegativeOrNaN());
        } else {
            // Intersect (stack) + Difference (toAdd)
            //  - Shrink the stack's outer bounds if the difference op's inner bounds completely
            //    cuts off an edge.
            //  - Shrink the stack's inner bounds to completely exclude the op's outer bounds.
            fOuterBounds = subtract(fOuterBounds, toAdd.innerBounds(), /* exact */ true);
            fInnerBounds = subtract(fInnerBounds, toAdd.outerBounds(), /* exact */ false);
        }
    } else {
        if (toAdd.op() == SkClipOp::kIntersect) {
            // Difference (stack) + Intersect (toAdd)
            //  - Bounds updates are just the mirror of Intersect(stack) + Difference(toAdd)
            Rect oldOuter = fOuterBounds;
            fOuterBounds = subtract(toAdd.outerBounds(), fInnerBounds, /* exact */ true);
            fInnerBounds = subtract(toAdd.innerBounds(), oldOuter,     /* exact */ false);
        } else {
            // Difference (stack) + Difference (toAdd)
            //  - The updated outer bounds is the union of outer bounds and the inner becomes the
            //    largest of the two possible inner bounds
            fOuterBounds.join(toAdd.outerBounds());
            if (toAdd.innerBounds().area() > fInnerBounds.area()) {
                fInnerBounds = toAdd.innerBounds();
            }
        }
    }

    // If we get here, we're keeping the new element and the stack's bounds have been updated.
    // We ought to have caught the cases where the stack bounds resemble an empty or wide open
    // clip, so assert that's the case.
    SkASSERT(!fOuterBounds.isEmptyNegativeOrNaN() &&
             (fInnerBounds.isEmptyNegativeOrNaN() || fOuterBounds.contains(fInnerBounds)));

    return this->appendElement(std::move(toAdd), elements, device);
}

bool ClipStack::SaveRecord::appendElement(RawElement&& toAdd,
                                          RawElement::Stack* elements,
                                          Device* device) {
    // Update past elements to account for the new element
    int i = elements->count() - 1;

    // After the loop, elements between [max(youngestValid, startingIndex)+1, count-1] can be
    // removed from the stack (these are the active elements that have been invalidated by the
    // newest element; since it's the active part of the stack, no restore() can bring them back).
    int youngestValid = fStartingElementIndex - 1;
    // After the loop, elements between [0, oldestValid-1] are all invalid. The value of oldestValid
    // becomes the save record's new fLastValidIndex value.
    int oldestValid = elements->count();
    // After the loop, this is the earliest active element that was invalidated. It may be
    // older in the stack than earliestValid, so cannot be popped off, but can be used to store
    // the new element instead of allocating more.
    RawElement* oldestActiveInvalid = nullptr;
    int oldestActiveInvalidIndex = elements->count();

    for (RawElement& existing : elements->ritems()) {
        if (i < fOldestValidIndex) {
            break;
        }
        // We don't need to pass the actual index that toAdd will be saved to; just the minimum
        // index of this save record, since that will result in the same restoration behavior later.
        existing.updateForElement(&toAdd, *this);

        if (toAdd.isInvalid()) {
            if (existing.isInvalid()) {
                // Both new and old invalid implies the entire clip becomes empty
                fState = ClipState::kEmpty;
                return true;
            } else {
                // The new element doesn't change the clip beyond what the old element already does
                return false;
            }
        } else if (existing.isInvalid()) {
            // The new element cancels out the old element. The new element may have been modified
            // to account for the old element's geometry.
            if (i >= fStartingElementIndex) {
                // Still active, so the invalidated index could be used to store the new element
                oldestActiveInvalid = &existing;
                oldestActiveInvalidIndex = i;
            }
        } else {
            // Keep both new and old elements
            oldestValid = i;
            if (i > youngestValid) {
                youngestValid = i;
            }
        }

        --i;
    }

    // Post-iteration validity check
    SkASSERT(oldestValid == elements->count() ||
             (oldestValid >= fOldestValidIndex && oldestValid < elements->count()));
    SkASSERT(youngestValid == fStartingElementIndex - 1 ||
             (youngestValid >= fStartingElementIndex && youngestValid < elements->count()));
    SkASSERT((oldestActiveInvalid && oldestActiveInvalidIndex >= fStartingElementIndex &&
              oldestActiveInvalidIndex < elements->count()) || !oldestActiveInvalid);

    // Update final state
    SkASSERT(oldestValid >= fOldestValidIndex);
    fOldestValidIndex = std::min(oldestValid, oldestActiveInvalidIndex);
    fState = oldestValid == elements->count() ? toAdd.clipType() : ClipState::kComplex;
    if (fStackOp == SkClipOp::kDifference && toAdd.op() == SkClipOp::kIntersect) {
        // The stack remains in difference mode only as long as all elements are difference
        fStackOp = SkClipOp::kIntersect;
    }

    int targetCount = youngestValid + 1;
    if (!oldestActiveInvalid || oldestActiveInvalidIndex >= targetCount) {
        // toAdd will be stored right after youngestValid
        targetCount++;
        oldestActiveInvalid = nullptr;
    }
    while (elements->count() > targetCount) {
        SkASSERT(oldestActiveInvalid != &elements->back()); // shouldn't delete what we'll reuse
        elements->back().drawClip(device);
        elements->pop_back();
    }
    if (oldestActiveInvalid) {
        oldestActiveInvalid->drawClip(device);
        *oldestActiveInvalid = std::move(toAdd);
    } else if (elements->count() < targetCount) {
        elements->push_back(std::move(toAdd));
    } else {
        elements->back().drawClip(device);
        elements->back() = std::move(toAdd);
    }

    // Changing this will prompt ClipStack to invalidate any masks associated with this record.
    fGenID = next_gen_id();
    return true;
}

void ClipStack::SaveRecord::replaceWithElement(RawElement&& toAdd,
                                               RawElement::Stack* elements,
                                               Device* device) {
    // The aggregate state of the save record mirrors the element
    fInnerBounds = toAdd.innerBounds();
    fOuterBounds = toAdd.outerBounds();
    fStackOp = toAdd.op();
    fState = toAdd.clipType();

    // All prior active element can be removed from the stack: [startingIndex, count - 1]
    int targetCount = fStartingElementIndex + 1;
    while (elements->count() > targetCount) {
        elements->back().drawClip(device);
        elements->pop_back();
    }
    if (elements->count() < targetCount) {
        elements->push_back(std::move(toAdd));
    } else {
        elements->back().drawClip(device);
        elements->back() = std::move(toAdd);
    }

    SkASSERT(elements->count() == fStartingElementIndex + 1);

    // This invalidates all older elements that are owned by save records lower in the clip stack.
    fOldestValidIndex = fStartingElementIndex;
    fGenID = next_gen_id();
}

///////////////////////////////////////////////////////////////////////////////
// ClipStack::DrawShape

/**
 * DrawShape represents the approximate shape that is being drawn in order to compare it against
 * the clip stack's RawElements. It is able to map to a `TransformedShape` to be simplified with
 * either the SaveRecord or each element. For non-Shape geometries and stroked shapes, it
 * represents the oriented bounding box. For filled Shapes, it preserves the original shape for
 * more accurate contains/intersect checks and geometrically combining RawElements into the
 * shape.
 */
class ClipStack::DrawShape {
public:
    DrawShape(const Transform& localToDevice, const Geometry& geometry)
            : fLocalToDevice(&localToDevice)
            , fEdgeFlags(EdgeAAQuad::Flags::kAll)
            , fScissor(Rect::Infinite())
            , fShapeWasModified(false) {
        if (geometry.isShape()) {
            fShape = geometry.shape();
            fShapeMatchesGeometry = true;
        } else {
            // The geometry is something special like text or vertices, in which case it's
            // definitely not a shape that could simplify cleanly with the clip stack, so just track
            // its bounds. The exception is EdgeAA quads that are rectangular, in which case we can
            // clip its edges and adjust edge flags.
            fShape.setRect(geometry.bounds());
            if (geometry.isEdgeAAQuad()) {
                fEdgeFlags = geometry.edgeAAQuad().edgeFlags();
                fShapeMatchesGeometry = geometry.edgeAAQuad().isRect();
            } else {
                fShapeMatchesGeometry = false;
            }
            // If geometry is not a shape, it is not inverted.
            SkASSERT(!fShape.inverted());
        }

        fShapeCompatibleWithIntersectShape = fShape.isFloodFill() ||
                                            (!fShape.inverted() && (fShape.isRect() ||
                                                                    fShape.isRRect()));
    }

    operator TransformedShape() const {
        // A regular draw is a transformed shape that "intersects" the clip. An inverse-filled draw
        // is equivalent to "difference". For simple convex shapes we provide an inner bounds
        // because we can geometrically intersect clip elements with the draw geometry and not
        // really impact the choice of Renderer (given the family of renderers used for simple
        // shapes). In theory any convex shape could provide an inner bounds and/or use the detailed
        // contains check, but that would cause path rendering draws to potentially change in hard
        // to predict ways.
        SkClipOp op = fShape.inverted() ? SkClipOp::kDifference : SkClipOp::kIntersect;
        return TransformedShape{*fLocalToDevice, fShape, fOuterBounds, fInnerBounds, op,
                                /*containsChecksOnlyBounds=*/!this->shapeCanBeModified()};
    }

    bool shapeCanBeModified() const {
        return fShapeCompatibleWithIntersectShape && fShapeMatchesGeometry;
    }

    bool applyStyle(const SkStrokeRec& style, const Rect& deviceBounds);
    void applyScissor(const Rect& scissor);

    void resetToFloodFill();
    bool intersectClipElement(const RawElement& clip);

    // Sync any modifications back to `geometry` and return a Clip object encapsulating the
    // tracked bounds of the now-clipped draw.
    Clip toClip(Geometry* geometry, const NonMSAAClip& analyticClip, const SkShader* clipShader);

private:
    const Transform* fLocalToDevice;

    // When 'style' isn't fill, the original geometry describes the pre-stroked shape, so 'fShape'
    // is updated to include the bounds post-stroking. `fShape` may also include local AA outsets
    // under certain circumstances:
    //  1. If it's a hairline, the AA outset can be added in local space to preserve a tighter
    //     oriented bbox compared to device bounds outset by 1px.
    //  2. If it's subpixel, the rendered geometry is often treated as a hairline with an adjusted
    //     coverage ramp.
    // Notably, the local AA outset is not included in `styledShape` for other cases to maximize the
    // cases where a draw is contained in a clip, or can be clipped geometrically. This assumes that
    // rendering an AA'ed non-hairline/subpixel edge produces a 1px feathered edge that's not
    // qualitatively different from the 1px feathered edge a clip would enforce.
    Shape fShape;
    SkEnumBitMask<EdgeAAQuad::Flags> fEdgeFlags;

    // Not valid until after applyStyle() is called, although applyScissor() can shrink the inner
    // and outer bounds.
    Rect fTransformedShapeBounds;
    Rect fOuterBounds;
    Rect fInnerBounds;

    Rect fScissor;

    // Whether or not the shape matches the original geometry to draw (with style)
    bool fShapeMatchesGeometry;
    // Whether or not the clip stack can modify this shape in place (and if it has already done so).
    bool fShapeCompatibleWithIntersectShape;
    bool fShapeWasModified;
};

bool ClipStack::DrawShape::applyStyle(const SkStrokeRec& style, const Rect& deviceBounds) {
    // For overriding fLocalToDevice when the shape is only tracking device-space bounds
    static const Transform kIdentity = Transform::Identity();

    fTransformedShapeBounds = fShape.bounds(); // not scissor'ed, regular fill rule bounds
    auto origSize = fTransformedShapeBounds.size();
    if (!SkIsFinite(origSize.x(), origSize.y())) {
        // Discard all non-finite geometry as if it were clipped out
        return false;
    }

    // Discard fills and strokes that cannot produce any coverage: an empty fill, or a
    // zero-length stroke that has butt caps. Otherwise the stroke style applies to a vertical
    // or horizontal line (making it non-empty), or it's a zero-length path segment that
    // must produce round or square caps (making it non-empty):
    //     https://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
    if (!fShape.inverted() && (fShape.isLine() || any(origSize == 0.f))) {
        if (style.isFillStyle() || (style.getCap() == SkPaint::kButt_Cap && all(origSize == 0.f))) {
            return false;
        }
    }

    // Anti-aliasing makes shapes larger than their original coordinates, but we only care about
    // that for local clip checks in certain cases (see above).
    // NOTE: After this if-else block, `transformedShapeBounds` will be in device space.
    float localAAOutset = fLocalToDevice->localAARadius(fTransformedShapeBounds);
    if (!SkIsFinite(localAAOutset)) SK_UNLIKELY {
        // We cannot calculate an accurate local shape bounds, and transformedShapeBounds is meant
        // to be unclipped. This is to maximize atlas reuse for mostly unclipped draws and to detect
        // when a scissor state change is required. Setting transformedShapeBounds to deviceBounds
        // is harmless in this case as these benefits are unlikely to apply for this transform.
        fTransformedShapeBounds = deviceBounds;
        fShape.setRect(deviceBounds);
        fLocalToDevice = &kIdentity;
        fShapeMatchesGeometry = false;
    } else {
        // SkStrokeRec::GetInflationRadius() returns a device-space inflation for hairlines.
        float localOutset = 0.f;
        if (!style.isFillStyle() && !style.isHairlineStyle()) {
            // Rectangles, rounded rectangles, and lines do not produce miters so don't count the
            // pessimistic limit against their draw bounds.
            const float effectiveMiterLimit = fShape.isPath() ? style.getMiter() : 1.f;
            // Rectangles and rounded rectangles don't have caps, so don't count that against their
            // draw bounds (if we could efficiently know a path was a closed contour, it could
            // be included here too).
            const SkPaint::Cap effectiveCap = fShape.isRect() || fShape.isRRect()
                    ? SkPaint::kButt_Cap : style.getCap();
            localOutset = SkStrokeRec::GetInflationRadius(style.getJoin(),
                                                          effectiveMiterLimit,
                                                          effectiveCap,
                                                          style.getWidth());
        }

        if (style.isHairlineStyle() ||
            (!style.isFillStyle() && style.getWidth() < localAAOutset) ||
            (style.isFillStyle() && !fShape.inverted() && any(origSize < localAAOutset))) {
            // The geometry is a hairline or projects to a subpixel shape, so rendering will not
            // follow the typical 1/2px outset anti-aliasing that is compatible with clipping.
            // In this case, apply the local AA radius to the shape to have a conservative clip
            // query while preserving the oriented bounding box.
            localOutset += localAAOutset;
        }

        if (localOutset > 0.f) {
            // Propagate style and AA outset into styledShape so clip queries reflect style.
            fTransformedShapeBounds.outset(localOutset);

            bool inverted = fShape.inverted();
            if (fShape.isRRect()) {
                // Try to preserve the rounded corners, which can reduce the chance of clipping
                // stroked rounded rects that are clipped to a round rect matching their outer edge.
                fShape.rrect().outset(localOutset, localOutset, &fShape.rrect());
            } else
            {
                fShape.setRect(fTransformedShapeBounds); // it's still local at this point
            }
            fShape.setInverted(inverted);  // preserve original inversion state
            fShapeMatchesGeometry = false;
        }

        fTransformedShapeBounds = fLocalToDevice->mapRect(fTransformedShapeBounds);
    }

    fOuterBounds = fTransformedShapeBounds;
    fInnerBounds = Rect::InfiniteInverted();

    if (this->shapeCanBeModified() && fLocalToDevice->type() <= Transform::Type::kRectStaysRect) {
        if (fShape.isRect()) {
            fInnerBounds = fOuterBounds;
        } else if (fShape.isRRect()) {
            SkRect rrectInnerBounds = SkRRectPriv::InnerBounds(fShape.rrect());
            if (!rrectInnerBounds.isEmpty()) {
                fInnerBounds = fLocalToDevice->mapRect(rrectInnerBounds);
            }
        }
        // Otherwise it's a flood fill, but should have empty bounds anyways
    }
    // Otherwise we either don't need the inner bounds, or the inner bounds can't be computed
    // for a non-axis-aligned transform

     return true; // Something can be drawn based on style (might still be clipped out)
}

void ClipStack::DrawShape::applyScissor(const Rect& scissor) {
    // Apply the scissor to the outer bounds because it restricts rasterization and will allow
    // the SaveRecord::testForDraw() case to detect no clip influence if only the scissor is
    // needed.
    SkASSERT(scissor == Rect(scissor.asSkIRect())); // `scissor` must be integer valued
    fScissor.intersect(scissor); // For first call, fScissor is infinite so this is assignment
    fOuterBounds.intersect(scissor);
    fInnerBounds.intersect(scissor);
}

Clip ClipStack::DrawShape::toClip(Geometry* geometry,
                                  const NonMSAAClip& analyticClip,
                                  const SkShader* clipShader) {
    if (fShapeWasModified) {
        // Sync back to the geometry that will be drawn
        SkASSERT(this->shapeCanBeModified());
        if (geometry->isEdgeAAQuad() && fShape.isRect()) {
            // Preserve the EdgeAAQuad geometry type and sync updated edge flags
            SkASSERT(geometry->edgeAAQuad().isRect());
            geometry->setEdgeAAQuad(EdgeAAQuad(fShape.rect(), fEdgeFlags));
        } else {
            SkASSERT(fEdgeFlags == EdgeAAQuad::Flags::kAll);
            geometry->setShape(fShape);
        }
        // Reconstruct new transformedShapeBounds and outer bounds
        fTransformedShapeBounds = fLocalToDevice->mapRect(fShape.bounds());
        fOuterBounds = fTransformedShapeBounds.makeIntersect(fScissor);
    }

    Rect drawBounds = fShape.inverted() ? fScissor : fOuterBounds;
    // If the draw isn't clipped out (empty drawBounds), it should be in the scissor rect
    SkASSERT(drawBounds.isEmptyNegativeOrNaN() || fScissor.contains(drawBounds));
    // If the scissor is empty, the draw bounds must also be empty
    SkASSERT(!fScissor.isEmptyNegativeOrNaN() || drawBounds.isEmptyNegativeOrNaN());
    // fScissor.asSkIRect() must be equivalent
    SkASSERT(fScissor == Rect(fScissor.asSkIRect()));
    return Clip(drawBounds, fTransformedShapeBounds,
                fScissor.asSkIRect(), analyticClip, clipShader);
}

void ClipStack::DrawShape::resetToFloodFill() {
    if (this->shapeCanBeModified() && !fShape.isFloodFill()) {
        fShape.reset();
        fShape.setInverted(true);
        fEdgeFlags = EdgeAAQuad::Flags::kAll;
        fOuterBounds = fInnerBounds = Rect::InfiniteInverted();
        fShapeWasModified = true;
    }
}

bool ClipStack::DrawShape::intersectClipElement(const RawElement& clip) {
    SkASSERT(clip.op() == SkClipOp::kIntersect);
    if (this->shapeCanBeModified() &&
        intersect_shape(clip.localToDevice(), clip.shape(),
                        *fLocalToDevice, &fShape, &fEdgeFlags)) {
        SkASSERT(!fShape.inverted());
        if (fOuterBounds.isEmptyNegativeOrNaN()) {
            // Changing from a flood fill to the clip's shape
            fOuterBounds = clip.outerBounds();
            fInnerBounds = clip.innerBounds();
        } else {
            // Restricting the shape's geometry by the clip
            fOuterBounds.intersect(clip.outerBounds());
            fInnerBounds.intersect(clip.innerBounds());
            SkASSERT(!fOuterBounds.isEmptyNegativeOrNaN()); // Should have been caught earlier
        }

        fShapeWasModified = true;
        return true;
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////
// ClipStack

// NOTE: Based on draw calls in all GMs, SKPs, and SVGs as of 08/20, 98% use a clip stack with
// one Element and up to two SaveRecords, thus the inline size for RawElement::Stack and
// SaveRecord::Stack (this conveniently keeps the size of ClipStack manageable). The max
// encountered element stack depth was 5 and the max save depth was 6. Using an increment of 8 for
// these stacks means that clip management will incur a single allocation for the remaining 2%
// of the draws, with extra head room for more complex clips encountered in the wild.
static constexpr int kElementStackIncrement = 8;
static constexpr int kSaveStackIncrement = 8;

ClipStack::ClipStack(Device* owningDevice)
        : fElements(kElementStackIncrement)
        , fSaves(kSaveStackIncrement)
        , fDevice(owningDevice) {
    // Start with a save record that is wide open
    fSaves.emplace_back(this->deviceBounds());
}

ClipStack::~ClipStack() = default;

void ClipStack::save() {
    SkASSERT(!fSaves.empty());
    fSaves.back().pushSave();
}

void ClipStack::restore() {
    SkASSERT(!fSaves.empty());
    SaveRecord& current = fSaves.back();
    if (current.popSave()) {
        // This was just a deferred save being undone, so the record doesn't need to be removed yet
        return;
    }

    // When we remove a save record, we delete all elements >= its starting index and any masks
    // that were rasterized for it.
    current.removeElements(&fElements, fDevice);

    fSaves.pop_back();
    // Restore any remaining elements that were only invalidated by the now-removed save record.
    fSaves.back().restoreElements(&fElements);
}

Rect ClipStack::deviceBounds() const {
    return Rect::WH(fDevice->width(), fDevice->height());
}

Rect ClipStack::conservativeBounds() const {
    const SaveRecord& current = this->currentSaveRecord();
    if (current.state() == ClipState::kEmpty) {
        return Rect::InfiniteInverted();
    } else if (current.state() == ClipState::kWideOpen) {
        return this->deviceBounds();
    } else {
        if (current.op() == SkClipOp::kDifference) {
            // The outer/inner bounds represent what's cut out, so full bounds remains the device
            // bounds, minus any fully clipped content that spans the device edge.
            return subtract(this->deviceBounds(), current.innerBounds(), /* exact */ true);
        } else {
            SkASSERT(this->deviceBounds().contains(current.outerBounds()));
            return current.outerBounds();
        }
    }
}

ClipStack::SaveRecord& ClipStack::writableSaveRecord(bool* wasDeferred) {
    SaveRecord& current = fSaves.back();
    if (current.canBeUpdated()) {
        // Current record is still open, so it can be modified directly
        *wasDeferred = false;
        return current;
    } else {
        // Must undefer the save to get a new record.
        SkAssertResult(current.popSave());
        *wasDeferred = true;
        return fSaves.emplace_back(current, fElements.count());
    }
}

void ClipStack::clipShader(sk_sp<SkShader> shader) {
    // Shaders can't bring additional coverage
    if (this->currentSaveRecord().state() == ClipState::kEmpty) {
        return;
    }

    bool wasDeferred;
    this->writableSaveRecord(&wasDeferred).addShader(std::move(shader));
    // Geometry elements are not invalidated by updating the clip shader
    // TODO(b/238763003): Integrating clipShader into graphite needs more thought, particularly how
    // to handle the shader explosion and where to put the effects in the GraphicsPipelineDesc.
    // One idea is to use sample locations and draw the clipShader into the depth buffer.
    // Another is resolve the clip shader into an alpha mask image that is sampled by the draw.
}

void ClipStack::clipShape(const Transform& localToDevice,
                          const Shape& shape,
                          SkClipOp op,
                          PixelSnapping snapping) {
    if (this->currentSaveRecord().state() == ClipState::kEmpty) {
        return;
    }

    // This will apply the transform if it's shape-type preserving, and clip the element's bounds
    // to the device bounds (NOT the conservative clip bounds, since those are based on the net
    // effect of all elements while device bounds clipping happens implicitly. During addElement,
    // we may still be able to invalidate some older elements).
    // NOTE: Does not try to simplify the shape type by inspecting the SkPath.
    RawElement element{this->deviceBounds(), localToDevice, shape, op, snapping};

    // An empty op means do nothing (for difference), or close the save record, so we try and detect
    // that early before doing additional unnecessary save record allocation.
    if (element.shape().isEmpty()) {
        if (element.op() == SkClipOp::kDifference) {
            // If the shape is empty and we're subtracting, this has no effect on the clip
            return;
        }
        // else we will make the clip empty, but we need a new save record to record that change
        // in the clip state; fall through to below and updateForElement() will handle it.
    }

    bool wasDeferred;
    SaveRecord& save = this->writableSaveRecord(&wasDeferred);
    SkDEBUGCODE(int elementCount = fElements.count();)
    if (!save.addElement(std::move(element), &fElements, fDevice)) {
        if (wasDeferred) {
            // We made a new save record, but ended up not adding an element to the stack.
            // So instead of keeping an empty save record around, pop it off and restore the counter
            SkASSERT(elementCount == fElements.count());
            fSaves.pop_back();
            fSaves.back().pushSave();
        }
    }
}

// Decide whether we can use this shape to do analytic clipping. Only rects and certain
// rrects are supported. We assume these have been pre-transformed by the RawElement
// constructor, so only identity transforms are allowed.
namespace {
AnalyticClip can_apply_analytic_clip(const Shape& shape,
                                     const Transform& localToDevice) {
    if (localToDevice.type() != Transform::Type::kIdentity) {
        return {};
    }

    // The circular rrect clip only handles rrect radii >= kRadiusMin.
    static constexpr SkScalar kRadiusMin = SK_ScalarHalf;

    // Can handle Rect directly.
    if (shape.isRect()) {
        return {shape.rect(), kRadiusMin, AnalyticClip::kNone_EdgeFlag, shape.inverted()};
    }

    // Otherwise we only handle certain kinds of RRects.
    if (!shape.isRRect()) {
        return {};
    }

    const SkRRect& rrect = shape.rrect();
    if (rrect.isOval() || rrect.isSimple()) {
        SkVector radii = SkRRectPriv::GetSimpleRadii(rrect);
        if (radii.fX < kRadiusMin || radii.fY < kRadiusMin) {
            // In this case the corners are extremely close to rectangular and we collapse the
            // clip to a rectangular clip.
            return {rrect.rect(), kRadiusMin, AnalyticClip::kNone_EdgeFlag, shape.inverted()};
        }
        if (SkScalarNearlyEqual(radii.fX, radii.fY)) {
            return {rrect.rect(), radii.fX, AnalyticClip::kAll_EdgeFlag, shape.inverted()};
        } else {
            return {};
        }
    }

    if (rrect.isComplex() || rrect.isNinePatch()) {
        // Check for the "tab" cases - two adjacent circular corners and two square corners.
        constexpr uint32_t kCornerFlags[4] = {
            AnalyticClip::kTop_EdgeFlag | AnalyticClip::kLeft_EdgeFlag,
            AnalyticClip::kTop_EdgeFlag | AnalyticClip::kRight_EdgeFlag,
            AnalyticClip::kBottom_EdgeFlag | AnalyticClip::kRight_EdgeFlag,
            AnalyticClip::kBottom_EdgeFlag | AnalyticClip::kLeft_EdgeFlag,
        };
        SkScalar circularRadius = 0;
        uint32_t edgeFlags = 0;
        for (int corner = 0; corner < 4; ++corner) {
            SkVector radii = rrect.radii((SkRRect::Corner)corner);
            // Can only handle circular radii.
            // Also applies to corners with both zero and non-zero radii.
            if (!SkScalarNearlyEqual(radii.fX, radii.fY)) {
                return {};
            }
            if (radii.fX < kRadiusMin || radii.fY < kRadiusMin) {
                // The corner is square, so no need to flag as circular.
                continue;
            }
            // First circular corner seen
            if (!edgeFlags) {
                circularRadius = radii.fX;
            } else if (!SkScalarNearlyEqual(radii.fX, circularRadius)) {
                // Radius doesn't match previously seen circular radius
                return {};
            }
            edgeFlags |= kCornerFlags[corner];
        }

        if (edgeFlags == AnalyticClip::kNone_EdgeFlag) {
            // It's a rect
            return {rrect.rect(), kRadiusMin, edgeFlags, shape.inverted()};
        } else {
            // If any rounded corner pairs are non-adjacent or if there are three rounded
            // corners all edge flags will be set, which is not valid.
            if (edgeFlags == AnalyticClip::kAll_EdgeFlag) {
                return {};
            // At least one corner is rounded, or two adjacent corners are rounded.
            } else {
                return {rrect.rect(), circularRadius, edgeFlags, shape.inverted()};
            }
        }
    }

    return {};
}
}  // anonymous namespace

Clip ClipStack::visitClipStackForDraw(const Transform& localToDevice,
                                      Geometry* geometry,
                                      const SkStrokeRec& style,
                                      bool msaaSupported,
                                      ClipStack::ElementList* outEffectiveElements) const {
    static const Clip kClippedOut = {
            Rect::InfiniteInverted(), Rect::InfiniteInverted(), SkIRect::MakeEmpty(),
            /* nonMSAAClip= */ {}, /* shader= */ nullptr};

    const SaveRecord& cs = this->currentSaveRecord();
    if (cs.state() == ClipState::kEmpty) {
        // We know the draw is clipped out so don't bother computing the base draw bounds.
        return kClippedOut;
    }
    // Compute draw bounds, clipped only to our device bounds since we need to return that even if
    // the clip stack is known to be wide-open.
    const Rect deviceBounds = this->deviceBounds();

    DrawShape draw{localToDevice, *geometry};
    if (!draw.applyStyle(style, deviceBounds)) {
        return kClippedOut;
    }

    // For intersect clips, the scissor rectangle is snapped outer bounds (to loosely restrict
    // rasterization if absolutely necessary). Cases where the draw is fully inside the scissor are
    // automatically handled during GPU command generation.
    //
    // For difference clips, a tight scissor could be `subtract(drawBounds, cs.innerBounds())`
    // but this is only useful when the clip spans across an axis of the draw and can otherwise
    // lead to scissor state thrashing since it's connected to the draw's bounds as well. So just
    // use the device bounds for simplicity.
    draw.applyScissor(cs.op() == SkClipOp::kIntersect ? snap_scissor(cs.outerBounds(), deviceBounds)
                                                      : deviceBounds);

    switch (cs.testForDraw(draw)) {
        case DrawInfluence::kClipsOutDraw:
            // The draw is offscreen or clipped out, so there is no need to visit the clip elements.
            return kClippedOut;

        case DrawInfluence::kNone:
            // The draw is unaffected by the clip stack (except possibly `scissor`), and there's no
            // need to visit each clip element.
            return draw.toClip(geometry, {}, cs.shader());

        case DrawInfluence::kReplacesDraw:
            // The draw covers the clip entirely. Replace the shape with a flood fill, which can
            // intersect with shapes efficiently.
            draw.resetToFloodFill();
            [[fallthrough]];

        case DrawInfluence::kComplexInteraction:
            // Check each element's influence on the draw below
            break;
    }

    SkASSERT(outEffectiveElements);
    SkASSERT(outEffectiveElements->empty());
    int i = fElements.count();
    NonMSAAClip nonMSAAClip;
    for (const RawElement& e : fElements.ritems()) {
        --i;
        if (i < cs.oldestElementIndex()) {
            // All earlier elements have been invalidated by elements already processed so the draw
            // can't be affected by them and cannot contribute to their usage bounds.
            break;
        }

        switch (e.testForDraw(draw)) {
            case DrawInfluence::kClipsOutDraw:
                // Per-element check was able to completely reject the draw.
                outEffectiveElements->clear();
                return kClippedOut;

            case DrawInfluence::kNone:
                // This element does not interact, so continue to the next
                continue;

            case DrawInfluence::kReplacesDraw:
                // This element is covered entirely by the draw, so the draw's geometry can be
                // replaced assuming the coordinate spaces are compatible. To facilitate this, we
                // switch the drawn geometry to a flood fill and then fall through to intersection.
                // Even if the coordinate spaces aren't in alignment, this eliminates the draw's
                // source of analytic coverage.
                draw.resetToFloodFill();

                [[fallthrough]];

            case DrawInfluence::kComplexInteraction:
                // First try to handle the clip geometrically
                if (e.op() == SkClipOp::kIntersect && draw.intersectClipElement(e)) {
                    continue;
                }
                // Second try to tighten the scissor, which is lighter weight than adding an
                // analytic clip pipeline variation or triggering MSAA.
                if (e.clipType() == ClipState::kDeviceRect) {
                    Rect scissor = e.shape().rect().makeRound();
                    if (e.shape().rect().nearlyEquals(scissor)) {
                        // Pass in `scissor` since these need to be integral values while
                        // nearlyEquals allows the original rect coordinates to be slightly
                        // different (causing problems later with asSkIRect()).
                        draw.applyScissor(scissor);
                        continue;
                    }
                }
                // // Third try to handle the clip analytically in the shader
                if (nonMSAAClip.fAnalyticClip.isEmpty()) {
                    nonMSAAClip.fAnalyticClip = can_apply_analytic_clip(e.shape(),
                                                                        e.localToDevice());
                    if (!nonMSAAClip.fAnalyticClip.isEmpty()) {
                        continue;
                    }
                }
                // Fourth, remember the element for later, either to be a depth-only draw or to be
                // flattened into a clip mask.
                // Otherwise, accumulate it for later. Depending on how many elements are collected
                // we may use the scissor, analytic clip, or MSAA/atlas.
                outEffectiveElements->push_back(&e);
                break;
        }
    }

#if !defined(SK_DISABLE_GRAPHITE_CLIP_ATLAS)
    // If there is no MSAA supported, rasterize any remaining elements by flattening them
    // into a single mask and storing in an atlas. Otherwise these will be handled by
    // Device::drawClip().
    AtlasProvider* atlasProvider = fDevice->recorder()->priv().atlasProvider();
    if (!msaaSupported && !outEffectiveElements->empty()) {
        ClipAtlasManager* clipAtlas = atlasProvider->getClipAtlasManager();
        SkASSERT(clipAtlas);
        AtlasClip* atlasClip = &nonMSAAClip.fAtlasClip;

        SkIRect iMaskBounds = cs.outerBounds().makeRoundOut().asSkIRect();
        sk_sp<TextureProxy> proxy = clipAtlas->findOrCreateEntry(cs.genID(),
                                                                 outEffectiveElements,
                                                                 iMaskBounds,
                                                                 &atlasClip->fOutPos);
        if (proxy) {
            // Add to Clip
            atlasClip->fMaskBounds = iMaskBounds;
            atlasClip->fAtlasTexture = std::move(proxy);

            // Elements are represented in the clip atlas, discard.
            outEffectiveElements->clear();
        }
    }
#endif

    return draw.toClip(geometry, nonMSAAClip, cs.shader());
}

CompressedPaintersOrder ClipStack::updateClipStateForDraw(const Clip& clip,
                                                          const ElementList& effectiveElements,
                                                          const BoundsManager* boundsManager,
                                                          PaintersDepth z) {
    if (clip.isClippedOut()) {
        return DrawOrder::kNoIntersection;
    }

    SkDEBUGCODE(const SaveRecord& cs = this->currentSaveRecord();)
    SkASSERT(cs.state() != ClipState::kEmpty);

    Rect deviceBounds = this->deviceBounds();
    CompressedPaintersOrder maxClipOrder = DrawOrder::kNoIntersection;
    for (int i = 0; i < effectiveElements.size(); ++i) {
        // ClipStack owns the elements in the `clipState` so it's OK to downcast and cast away
        // const.
        // TODO: Enforce the ownership? In debug builds we could invalidate a `ClipStateForDraw` if
        // its element pointers become dangling and assert validity here.
        const RawElement* e = static_cast<const RawElement*>(effectiveElements[i]);
        CompressedPaintersOrder order =  const_cast<RawElement*>(e)->updateForDraw(
                boundsManager, deviceBounds, clip.drawBounds(), z);
        maxClipOrder = std::max(order, maxClipOrder);
    }

    return maxClipOrder;
}

void ClipStack::recordDeferredClipDraws() {
    for (auto& e : fElements.items()) {
        // When a Device requires all clip elements to be recorded, we have to iterate all elements,
        // and will draw clip shapes for elements that are still marked as invalid from the clip
        // stack, including those that are older than the current save record's oldest valid index,
        // because they could have accumulated draw usage prior to being invalidated, but weren't
        // flushed when they were invalidated because of an intervening save.
        e.drawClip(fDevice);
    }
}

}  // namespace skgpu::graphite
