<commit_before>/*
 * Copyright 2020 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/gpu/tessellate/GrStrokeIndirectOp.h"

#include "src/core/SkGeometry.h"
#include "src/core/SkPathPriv.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrVx.h"
#include "src/gpu/geometry/GrPathUtils.h"
#include "src/gpu/tessellate/GrStrokeIterator.h"
#include "src/gpu/tessellate/GrStrokeTessellateShader.h"
#include "src/gpu/tessellate/GrWangsFormula.h"

void GrStrokeIndirectOp::onPrePrepare(GrRecordingContext* context,
                                      const GrSurfaceProxyView& writeView, GrAppliedClip* clip,
                                      const GrXferProcessor::DstProxyView& dstProxyView,
                                      GrXferBarrierFlags renderPassXferBarriers,
                                      GrLoadOp colorLoadOp) {
    auto* arena = context->priv().recordTimeAllocator();
    this->prePrepareResolveLevels(arena);
    SkASSERT(fResolveLevels);
    if (!fTotalInstanceCount) {
        return;
    }
    this->prePreparePrograms(GrStrokeTessellateShader::Mode::kIndirect, arena, writeView,
                             (clip) ? std::move(*clip) : GrAppliedClip::Disabled(), dstProxyView,
                             renderPassXferBarriers, colorLoadOp, *context->priv().caps());
    if (fFillProgram) {
        context->priv().recordProgramInfo(fFillProgram);
    }
    if (fStencilProgram) {
        context->priv().recordProgramInfo(fStencilProgram);
    }
}

// Helpers for GrStrokeIndirectOp::prePrepareResolveLevels.
namespace {

#ifndef SKNX_NO_SIMD
using grvx::vec;
using grvx::ivec;
using grvx::uvec;

// Muxes between "N" (Nx2/2) 2d vectors in SIMD based on the provided conditions. This is equivalent
// to returning the following at each point:
//
//   (conds.lo[i] & conds.hi[i])) ? {t[i].lo, t[i].hi} : {e[i].lo, e[i].hi}.
//
template<int Nx2>
static SK_ALWAYS_INLINE vec<Nx2> if_both_then_else(ivec<Nx2> conds, vec<Nx2> t, vec<Nx2> e) {
    auto both = conds.lo & conds.hi;
    return skvx::if_then_else(skvx::join(both, both), t, e);
}

// Returns the lengths squared of "N" (Nx2/2) 2d vectors in SIMD. The x values are in "v.lo" and
// the y values are in "v.hi".
template<int Nx2> static SK_ALWAYS_INLINE vec<Nx2/2> length_pow2(vec<Nx2> v) {
    auto vv = v*v;
    return vv.lo + vv.hi;
}

// Interpolates between "a" and "b" by a factor of T. T must be <1 and >= 0.
//
// NOTE: This does not return b when T==1. It's implemented as-is because it otherwise seems to get
// better precision than "a*(1 - T) + b*T" for things like chopping cubics on exact cusp points.
// The responsibility falls on the caller to check that T != 1 before calling.
template<int N> SK_ALWAYS_INLINE vec<N> unchecked_mix(vec<N> a, vec<N> b, vec<N> T) {
    return grvx::fast_madd(b - a, T, a);
}
#endif

// Computes and writes out the resolveLevels for individual strokes. Maintains a counter of the
// number of instances at each resolveLevel. If SIMD is available, then these calculations are done
// in batches.
class ResolveLevelCounter {
public:
    ResolveLevelCounter(const SkStrokeRec& stroke, GrStrokeTessellateShader::Tolerances tolerances,
                        int* resolveLevelCounts)
            : fIsRoundJoin(stroke.getJoin() == SkPaint::kRound_Join)
            , fTolerances(tolerances)
#ifndef SKNX_NO_SIMD
            , fWangsTermQuadratic(
                    GrWangsFormula::length_term<2>(fTolerances.fParametricIntolerance))
            , fWangsTermCubic(GrWangsFormula::length_term<3>(fTolerances.fParametricIntolerance))
#endif
            , fResolveLevelCounts(resolveLevelCounts) {
    }

    bool isRoundJoin() const { return fIsRoundJoin; }

#ifdef SKNX_NO_SIMD
    bool SK_WARN_UNUSED_RESULT countLine(const SkPoint pts[2], SkPoint lastControlPoint,
                                         int8_t* resolveLevelPtr) {
        if (!fIsRoundJoin) {
            // There is no resolve level to track. It's always zero.
            ++fResolveLevelCounts[0];
            return false;
        }
        float rotation = SkMeasureAngleBetweenVectors(pts[0] - lastControlPoint, pts[1] - pts[0]);
        this->writeResolveLevel(0, rotation, resolveLevelPtr);
        return true;
    }

    void countQuad(const SkPoint pts[3], SkPoint lastControlPoint, int8_t* resolveLevelPtr) {
        float numParametricSegments =
                GrWangsFormula::quadratic(fTolerances.fParametricIntolerance, pts);
        float rotation = SkMeasureQuadRotation(pts);
        if (fIsRoundJoin) {
            SkVector nextTan = ((pts[0] == pts[1]) ? pts[2] : pts[1]) - pts[0];
            rotation += SkMeasureAngleBetweenVectors(pts[0] - lastControlPoint, nextTan);
        }
        this->writeResolveLevel(numParametricSegments, rotation, resolveLevelPtr);
    }

    void countCubic(const SkPoint pts[4], SkPoint lastControlPoint, int8_t* resolveLevelPtr) {
        float numParametricSegments =
                GrWangsFormula::cubic(fTolerances.fParametricIntolerance, pts);
        SkVector tan0 = ((pts[0] == pts[1]) ? pts[2] : pts[1]) - pts[0];
        SkVector tan1 = pts[3] - ((pts[3] == pts[2]) ? pts[1] : pts[2]);
        float rotation = SkMeasureAngleBetweenVectors(tan0, tan1);
        if (fIsRoundJoin && pts[0] != lastControlPoint) {
            SkVector nextTan = (tan0.isZero()) ? tan1 : tan0;
            rotation += SkMeasureAngleBetweenVectors(pts[0] - lastControlPoint, nextTan);
        }
        this->writeResolveLevel(numParametricSegments, rotation, resolveLevelPtr);
    }

    void countChoppedCubic(const SkPoint pts[4], const float chopT, SkPoint lastControlPoint,
                           int8_t* resolveLevelPtr) {
        SkPoint chops[7];
        SkChopCubicAt(pts, chops, chopT);
        this->countCubic(chops, lastControlPoint, resolveLevelPtr);
        this->countCubic(chops + 3, chops[3], resolveLevelPtr + 1);
    }

    void flush() {}

private:
    void writeResolveLevel(float numParametricSegments, float rotation,
                           int8_t* resolveLevelPtr) const {
        float numCombinedSegments =
                fTolerances.fNumRadialSegmentsPerRadian * rotation + numParametricSegments;
        int8_t resolveLevel = sk_float_nextlog2(numCombinedSegments);
        resolveLevel = std::min(resolveLevel, GrStrokeIndirectOp::kMaxResolveLevel);
        ++fResolveLevelCounts[(*resolveLevelPtr = resolveLevel)];
    }

#else // !defined(SKNX_NO_SIMD)
    ~ResolveLevelCounter() {
        // Always call flush() when finished.
        SkASSERT(fLineQueue.fCount == 0);
        SkASSERT(fQuadQueue.fCount == 0);
        SkASSERT(fCubicQueue.fCount == 0);
        SkASSERT(fChoppedCubicQueue.fCount == 0);
    }

    bool SK_WARN_UNUSED_RESULT countLine(const SkPoint pts[2], SkPoint lastControlPoint,
                                         int8_t* resolveLevelPtr) {
        if (!fIsRoundJoin) {
            // There is no resolve level to track. It's always zero.
            ++fResolveLevelCounts[0];
            return false;
        }
        if (fLineQueue.push(pts, fIsRoundJoin, lastControlPoint, resolveLevelPtr) == 3) {
            this->flushLines<4>();
        }
        return true;
    }

    void countQuad(const SkPoint pts[3], SkPoint lastControlPoint, int8_t* resolveLevelPtr) {
        if (fQuadQueue.push(pts, fIsRoundJoin, lastControlPoint, resolveLevelPtr) == 3) {
            this->flushQuads<4>();
        }
    }

    void countCubic(const SkPoint pts[4], SkPoint lastControlPoint, int8_t* resolveLevelPtr) {
        if (fCubicQueue.push(pts, fIsRoundJoin, lastControlPoint, resolveLevelPtr) == 3) {
            this->flushCubics<4>();
        }
    }

    void countChoppedCubic(const SkPoint pts[4], const float chopT, SkPoint lastControlPoint,
                           int8_t* resolveLevelPtr) {
        int i = fChoppedCubicQueue.push(pts, fIsRoundJoin, lastControlPoint, resolveLevelPtr);
        fCubicChopTs[i] = chopT;
        if (i == 3) {
            this->flushChoppedCubics<4>();
        }
    }

    void flush() {
        // Flush each queue, crunching either 2 curves in SIMD or 4. We do 2 when the queue is low
        // because it allows us to expand two points into a single float4: [x0,x1,y0,y1].
        if (fLineQueue.fCount) {
            SkASSERT(fIsRoundJoin);
            if (fLineQueue.fCount <= 2) {
                this->flushLines<2>();
            } else {
                this->flushLines<4>();
            }
        }
        if (fQuadQueue.fCount) {
            if (fQuadQueue.fCount <= 2) {
                this->flushQuads<2>();
            } else {
                this->flushQuads<4>();
            }
        }
        if (fCubicQueue.fCount) {
            if (fCubicQueue.fCount <= 2) {
                this->flushCubics<2>();
            } else {
                this->flushCubics<4>();
            }
        }
        if (fChoppedCubicQueue.fCount) {
            if (fChoppedCubicQueue.fCount <= 2) {
                this->flushChoppedCubics<2>();
            } else {
                this->flushChoppedCubics<4>();
            }
        }
    }

private:
    // This struct stores deferred resolveLevel calculations for performing in SIMD batches.
    template<int NumPts> struct SIMDQueue {
        // Enqueues a stroke.
        SK_ALWAYS_INLINE int push(const SkPoint pts[NumPts], bool pushRoundJoin,
                                  SkPoint lastControlPoint, int8_t* resolveLevelPtr) {
            SkASSERT(0 <= fCount && fCount < 4);
            if constexpr (NumPts == 4) {
                // Store the points transposed in 4-point queues. The caller can use a strided load.
                vec<4> x, y;
                grvx::strided_load2(&pts[0].fX, x, y);
                x.store(fXs[fCount].data());
                y.store(fYs[fCount].data());
            } else {
                for (int i = 0; i < NumPts; ++i) {
                    fXs[i][fCount] = pts[i].fX;
                    fYs[i][fCount] = pts[i].fY;
                }
            }
            if (pushRoundJoin) {
                fLastControlPointsX[fCount] = lastControlPoint.fX;
                fLastControlPointsY[fCount] = lastControlPoint.fY;
            }
            fResolveLevelPtrs[fCount] = resolveLevelPtr;
            return fCount++;
        }

        // Loads pts[idx] in SIMD for all 4 strokes, with the x values in the "vec.lo" and the y
        // values in "vec.hi".
        template<int N> SK_ALWAYS_INLINE vec<N*2> loadPoint(int idx) const {
            SkASSERT(0 <= idx && idx < NumPts);
            static_assert(NumPts != 4, "4-point queues store transposed. Use grvx::strided_load4.");
            return skvx::join(vec<N>::Load(fXs[idx].data()), vec<N>::Load(fYs[idx].data()));
        }

        // Loads all 4 lastControlPoints in SIMD, with the x values in "vec.lo" and the y values in
        // "vec.hi".
        template<int N> SK_ALWAYS_INLINE vec<N*2> loadLastControlPoint() const {
            return skvx::join(vec<N>::Load(fLastControlPointsX), vec<N>::Load(fLastControlPointsY));
        }

        std::array<float,4> fXs[NumPts];
        std::array<float,4> fYs[NumPts];
        float fLastControlPointsX[4];
        float fLastControlPointsY[4];
        int8_t* fResolveLevelPtrs[4];
        int fCount = 0;
    };

    template<int N> void flushLines() {
        SkASSERT(fLineQueue.fCount > 0);

        // Find the angle of rotation in the preceding round join.
        auto a = fLineQueue.loadLastControlPoint<N>();
        auto b = fLineQueue.loadPoint<N>(0);
        auto c = fLineQueue.loadPoint<N>(1);
        auto rotation = grvx::approx_angle_between_vectors(b - a, c - b);

        this->writeResolveLevels<N>(0, rotation, fLineQueue.fCount, fLineQueue.fResolveLevelPtrs);
        fLineQueue.fCount = 0;
    }

    template<int N> void flushQuads() {
        SkASSERT(fQuadQueue.fCount > 0);
        auto p0 = fQuadQueue.loadPoint<N>(0);
        auto p1 = fQuadQueue.loadPoint<N>(1);
        auto p2 = fQuadQueue.loadPoint<N>(2);

        // Execute Wang's formula to determine how many parametric segments the curve needs to be
        // divided into. (See GrWangsFormula::quadratic().)
        auto l = length_pow2(grvx::fast_madd<N*2>(-2, p1, p2) + p0);
        auto numParametricSegments = skvx::sqrt(fWangsTermQuadratic * skvx::sqrt(l));

        // Find the curve's rotation. Since quads cannot inflect or rotate more than 180 degrees,
        // this is equal to the angle between the beginning and ending tangents.
        // NOTE: If p0==p1 or p1==p2, this will give rotation=0.
        auto tan0 = p1 - p0;
        auto tan1 = p2 - p1;
        auto rotation = grvx::approx_angle_between_vectors(tan0, tan1);
        if (fIsRoundJoin) {
            // Add rotation for the preceding round join.
            auto lastControlPoint = fQuadQueue.loadLastControlPoint<N>();
            auto nextTan = if_both_then_else((tan0 == 0), tan1, tan0);
            rotation += grvx::approx_angle_between_vectors(p0 - lastControlPoint, nextTan);
        }

        this->writeResolveLevels<N>(numParametricSegments, rotation, fQuadQueue.fCount,
                                    fQuadQueue.fResolveLevelPtrs);
        fQuadQueue.fCount = 0;
    }

    template<int N> void flushCubics() {
        SkASSERT(fCubicQueue.fCount > 0);
        vec<N*2> p0, p1, p2, p3;
        grvx::strided_load4(fCubicQueue.fXs[0].data(), p0.lo, p1.lo, p2.lo, p3.lo);
        grvx::strided_load4(fCubicQueue.fYs[0].data(), p0.hi, p1.hi, p2.hi, p3.hi);
        this->flushCubics<N>(fCubicQueue, p0, p1, p2, p3, fIsRoundJoin, 0);
        fCubicQueue.fCount = 0;
    }

    template<int N> void flushChoppedCubics() {
        SkASSERT(fChoppedCubicQueue.fCount > 0);
        vec<N*2> p0, p1, p2, p3;
        grvx::strided_load4(fChoppedCubicQueue.fXs[0].data(), p0.lo, p1.lo, p2.lo, p3.lo);
        grvx::strided_load4(fChoppedCubicQueue.fYs[0].data(), p0.hi, p1.hi, p2.hi, p3.hi);
        // Chop the cubic at its chopT and find the resolve level for each half.
        auto T = skvx::join(vec<N>::Load(fCubicChopTs), vec<N>::Load(fCubicChopTs));
        auto ab = unchecked_mix(p0, p1, T);
        auto bc = unchecked_mix(p1, p2, T);
        auto cd = unchecked_mix(p2, p3, T);
        auto abc = unchecked_mix(ab, bc, T);
        auto bcd = unchecked_mix(bc, cd, T);
        auto abcd = unchecked_mix(abc, bcd, T);
        this->flushCubics<N>(fChoppedCubicQueue, p0, ab, abc, abcd, fIsRoundJoin, 0);
        this->flushCubics<N>(fChoppedCubicQueue, abcd, bcd, cd, p3, false/*countRoundJoin*/, 1);
        fChoppedCubicQueue.fCount = 0;
    }

    template<int N> SK_ALWAYS_INLINE void flushCubics(const SIMDQueue<4>& queue, vec<N*2> p0,
                                                      vec<N*2> p1, vec<N*2> p2, vec<N*2> p3,
                                                      bool countRoundJoin, int resultOffset) const {
        // Execute Wang's formula to determine how many parametric segments the curve needs to be
        // divided into. (See GrWangsFormula::cubic().)
        auto l0 = length_pow2(grvx::fast_madd<N*2>(-2, p1, p2) + p0);
        auto l1 = length_pow2(grvx::fast_madd<N*2>(-2, p2, p3) + p1);
        auto numParametricSegments = skvx::sqrt(fWangsTermCubic * skvx::sqrt(skvx::max(l0, l1)));

        // Find the starting tangent (or zero if p0==p1==p2).
        auto tan0 = p1 - p0;
        tan0 = if_both_then_else((tan0 == 0), p2 - p0, tan0);

        // Find the ending tangent (or zero if p1==p2==p3).
        auto tan1 = p3 - p2;
        tan1 = if_both_then_else((tan1 == 0), p3 - p1, tan1);

        // Find the curve's rotation. Since it cannot inflect or rotate more than 180 degrees at
        // this point, this is equal to the angle between the beginning and ending tangents.
        auto rotation = grvx::approx_angle_between_vectors(tan0, tan1);
        if (countRoundJoin) {
            // Add rotation for the preceding round join.
            auto lastControlPoint = queue.loadLastControlPoint<N>();
            auto nextTan = if_both_then_else((tan0 == 0), tan1, tan0);
            rotation += grvx::approx_angle_between_vectors(p0 - lastControlPoint, nextTan);
        }

        this->writeResolveLevels<N>(numParametricSegments, rotation, queue.fCount,
                                    queue.fResolveLevelPtrs, resultOffset);
    }

    template<int N> SK_ALWAYS_INLINE void writeResolveLevels(
            vec<N> numParametricSegments, vec<N> rotation, int count,
            int8_t* const* resolveLevelPtrs, int offset = 0) const {
        auto numCombinedSegments = grvx::fast_madd<N>(
                fTolerances.fNumRadialSegmentsPerRadian, rotation, numParametricSegments);

        // Find ceil(log2(numCombinedSegments)) by twiddling the exponents. See sk_float_nextlog2().
        auto bits = skvx::bit_pun<uvec<N>>(numCombinedSegments);
        bits += (1u << 23) - 1u;  // Increment the exponent for non-powers-of-2.
        // This will make negative values, denorms, and negative exponents all < 0.
        auto exp = (skvx::bit_pun<ivec<N>>(bits) >> 23) - 127;
        auto level = skvx::pin<N,int>(exp, 0, GrStrokeIndirectOp::kMaxResolveLevel);

        switch (count) {
            default: SkUNREACHABLE;
            case 4: ++fResolveLevelCounts[resolveLevelPtrs[3][offset] = level[3]]; [[fallthrough]];
            case 3: ++fResolveLevelCounts[resolveLevelPtrs[2][offset] = level[2]]; [[fallthrough]];
            case 2: ++fResolveLevelCounts[resolveLevelPtrs[1][offset] = level[1]]; [[fallthrough]];
            case 1: ++fResolveLevelCounts[resolveLevelPtrs[0][offset] = level[0]]; break;
        }
    }

#endif
    const bool fIsRoundJoin;
    GrStrokeTessellateShader::Tolerances fTolerances;

#ifndef SKNX_NO_SIMD
    const float fWangsTermQuadratic;
    const float fWangsTermCubic;

    SIMDQueue<2> fLineQueue;
    SIMDQueue<3> fQuadQueue;
    SIMDQueue<4> fCubicQueue;
    SIMDQueue<4> fChoppedCubicQueue;
    float fCubicChopTs[4];

#endif
    int* const fResolveLevelCounts;
};

}  // namespace

void GrStrokeIndirectOp::prePrepareResolveLevels(SkArenaAlloc* alloc) {
    SkASSERT(!fTotalInstanceCount);
    SkASSERT(!fResolveLevels);
    SkASSERT(!fResolveLevelArrayCount);

    // The maximum potential number of values we will need in fResolveLevels is:
    //
    //   * 3 segments per verb (from two chops)
    //   * Plus 1 extra resolveLevel per verb that says how many chops it needs
    //   * Plus 2 final resolveLevels for square caps at the very end not initiated by a "kMoveTo".
    //
    int resolveLevelAllocCount = fTotalCombinedVerbCnt * (3 + 1) + 2;<commit_msg>Clean up GrStrokeIndirectOp #ifs for using SIMD<commit_after>/*
 * Copyright 2020 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/gpu/tessellate/GrStrokeIndirectOp.h"

#include "src/core/SkGeometry.h"
#include "src/core/SkPathPriv.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrVx.h"
#include "src/gpu/geometry/GrPathUtils.h"
#include "src/gpu/tessellate/GrStrokeIterator.h"
#include "src/gpu/tessellate/GrStrokeTessellateShader.h"
#include "src/gpu/tessellate/GrWangsFormula.h"

void GrStrokeIndirectOp::onPrePrepare(GrRecordingContext* context,
                                      const GrSurfaceProxyView& writeView, GrAppliedClip* clip,
                                      const GrXferProcessor::DstProxyView& dstProxyView,
                                      GrXferBarrierFlags renderPassXferBarriers,
                                      GrLoadOp colorLoadOp) {
    auto* arena = context->priv().recordTimeAllocator();
    this->prePrepareResolveLevels(arena);
    SkASSERT(fResolveLevels);
    if (!fTotalInstanceCount) {
        return;
    }
    this->prePreparePrograms(GrStrokeTessellateShader::Mode::kIndirect, arena, writeView,
                             (clip) ? std::move(*clip) : GrAppliedClip::Disabled(), dstProxyView,
                             renderPassXferBarriers, colorLoadOp, *context->priv().caps());
    if (fFillProgram) {
        context->priv().recordProgramInfo(fFillProgram);
    }
    if (fStencilProgram) {
        context->priv().recordProgramInfo(fStencilProgram);
    }
}

// Helpers for GrStrokeIndirectOp::prePrepareResolveLevels.
namespace {

// Only use SIMD if SkVx will use a built-in compiler extensions for vectors.
#if !defined(SKNX_NO_SIMD) && (defined(__clang__) || defined(__GNUC__))
#define USE_SIMD 1
#else
#define USE_SIMD 0
#endif

#if USE_SIMD
using grvx::vec;
using grvx::ivec;
using grvx::uvec;

// Muxes between "N" (Nx2/2) 2d vectors in SIMD based on the provided conditions. This is equivalent
// to returning the following at each point:
//
//   (conds.lo[i] & conds.hi[i])) ? {t[i].lo, t[i].hi} : {e[i].lo, e[i].hi}.
//
template<int Nx2>
static SK_ALWAYS_INLINE vec<Nx2> if_both_then_else(ivec<Nx2> conds, vec<Nx2> t, vec<Nx2> e) {
    auto both = conds.lo & conds.hi;
    return skvx::if_then_else(skvx::join(both, both), t, e);
}

// Returns the lengths squared of "N" (Nx2/2) 2d vectors in SIMD. The x values are in "v.lo" and
// the y values are in "v.hi".
template<int Nx2> static SK_ALWAYS_INLINE vec<Nx2/2> length_pow2(vec<Nx2> v) {
    auto vv = v*v;
    return vv.lo + vv.hi;
}

// Interpolates between "a" and "b" by a factor of T. T must be <1 and >= 0.
//
// NOTE: This does not return b when T==1. It's implemented as-is because it otherwise seems to get
// better precision than "a*(1 - T) + b*T" for things like chopping cubics on exact cusp points.
// The responsibility falls on the caller to check that T != 1 before calling.
template<int N> SK_ALWAYS_INLINE vec<N> unchecked_mix(vec<N> a, vec<N> b, vec<N> T) {
    return grvx::fast_madd(b - a, T, a);
}
#endif

// Computes and writes out the resolveLevels for individual strokes. Maintains a counter of the
// number of instances at each resolveLevel. If SIMD is available, then these calculations are done
// in batches.
class ResolveLevelCounter {
public:
    ResolveLevelCounter(const SkStrokeRec& stroke, GrStrokeTessellateShader::Tolerances tolerances,
                        int* resolveLevelCounts)
            : fIsRoundJoin(stroke.getJoin() == SkPaint::kRound_Join)
            , fTolerances(tolerances)
#if USE_SIMD
            , fWangsTermQuadratic(
                    GrWangsFormula::length_term<2>(fTolerances.fParametricIntolerance))
            , fWangsTermCubic(GrWangsFormula::length_term<3>(fTolerances.fParametricIntolerance))
#endif
            , fResolveLevelCounts(resolveLevelCounts) {
    }

    bool isRoundJoin() const { return fIsRoundJoin; }

#if !USE_SIMD
    bool SK_WARN_UNUSED_RESULT countLine(const SkPoint pts[2], SkPoint lastControlPoint,
                                         int8_t* resolveLevelPtr) {
        if (!fIsRoundJoin) {
            // There is no resolve level to track. It's always zero.
            ++fResolveLevelCounts[0];
            return false;
        }
        float rotation = SkMeasureAngleBetweenVectors(pts[0] - lastControlPoint, pts[1] - pts[0]);
        this->writeResolveLevel(0, rotation, resolveLevelPtr);
        return true;
    }

    void countQuad(const SkPoint pts[3], SkPoint lastControlPoint, int8_t* resolveLevelPtr) {
        float numParametricSegments =
                GrWangsFormula::quadratic(fTolerances.fParametricIntolerance, pts);
        float rotation = SkMeasureQuadRotation(pts);
        if (fIsRoundJoin) {
            SkVector nextTan = ((pts[0] == pts[1]) ? pts[2] : pts[1]) - pts[0];
            rotation += SkMeasureAngleBetweenVectors(pts[0] - lastControlPoint, nextTan);
        }
        this->writeResolveLevel(numParametricSegments, rotation, resolveLevelPtr);
    }

    void countCubic(const SkPoint pts[4], SkPoint lastControlPoint, int8_t* resolveLevelPtr) {
        float numParametricSegments =
                GrWangsFormula::cubic(fTolerances.fParametricIntolerance, pts);
        SkVector tan0 = ((pts[0] == pts[1]) ? pts[2] : pts[1]) - pts[0];
        SkVector tan1 = pts[3] - ((pts[3] == pts[2]) ? pts[1] : pts[2]);
        float rotation = SkMeasureAngleBetweenVectors(tan0, tan1);
        if (fIsRoundJoin && pts[0] != lastControlPoint) {
            SkVector nextTan = (tan0.isZero()) ? tan1 : tan0;
            rotation += SkMeasureAngleBetweenVectors(pts[0] - lastControlPoint, nextTan);
        }
        this->writeResolveLevel(numParametricSegments, rotation, resolveLevelPtr);
    }

    void countChoppedCubic(const SkPoint pts[4], const float chopT, SkPoint lastControlPoint,
                           int8_t* resolveLevelPtr) {
        SkPoint chops[7];
        SkChopCubicAt(pts, chops, chopT);
        this->countCubic(chops, lastControlPoint, resolveLevelPtr);
        this->countCubic(chops + 3, chops[3], resolveLevelPtr + 1);
    }

    void flush() {}

private:
    void writeResolveLevel(float numParametricSegments, float rotation,
                           int8_t* resolveLevelPtr) const {
        float numCombinedSegments =
                fTolerances.fNumRadialSegmentsPerRadian * rotation + numParametricSegments;
        int8_t resolveLevel = sk_float_nextlog2(numCombinedSegments);
        resolveLevel = std::min(resolveLevel, GrStrokeIndirectOp::kMaxResolveLevel);
        ++fResolveLevelCounts[(*resolveLevelPtr = resolveLevel)];
    }

#else  // USE_SIMD
    ~ResolveLevelCounter() {
        // Always call flush() when finished.
        SkASSERT(fLineQueue.fCount == 0);
        SkASSERT(fQuadQueue.fCount == 0);
        SkASSERT(fCubicQueue.fCount == 0);
        SkASSERT(fChoppedCubicQueue.fCount == 0);
    }

    bool SK_WARN_UNUSED_RESULT countLine(const SkPoint pts[2], SkPoint lastControlPoint,
                                         int8_t* resolveLevelPtr) {
        if (!fIsRoundJoin) {
            // There is no resolve level to track. It's always zero.
            ++fResolveLevelCounts[0];
            return false;
        }
        if (fLineQueue.push(pts, fIsRoundJoin, lastControlPoint, resolveLevelPtr) == 3) {
            this->flushLines<4>();
        }
        return true;
    }

    void countQuad(const SkPoint pts[3], SkPoint lastControlPoint, int8_t* resolveLevelPtr) {
        if (fQuadQueue.push(pts, fIsRoundJoin, lastControlPoint, resolveLevelPtr) == 3) {
            this->flushQuads<4>();
        }
    }

    void countCubic(const SkPoint pts[4], SkPoint lastControlPoint, int8_t* resolveLevelPtr) {
        if (fCubicQueue.push(pts, fIsRoundJoin, lastControlPoint, resolveLevelPtr) == 3) {
            this->flushCubics<4>();
        }
    }

    void countChoppedCubic(const SkPoint pts[4], const float chopT, SkPoint lastControlPoint,
                           int8_t* resolveLevelPtr) {
        int i = fChoppedCubicQueue.push(pts, fIsRoundJoin, lastControlPoint, resolveLevelPtr);
        fCubicChopTs[i] = chopT;
        if (i == 3) {
            this->flushChoppedCubics<4>();
        }
    }

    void flush() {
        // Flush each queue, crunching either 2 curves in SIMD or 4. We do 2 when the queue is low
        // because it allows us to expand two points into a single float4: [x0,x1,y0,y1].
        if (fLineQueue.fCount) {
            SkASSERT(fIsRoundJoin);
            if (fLineQueue.fCount <= 2) {
                this->flushLines<2>();
            } else {
                this->flushLines<4>();
            }
        }
        if (fQuadQueue.fCount) {
            if (fQuadQueue.fCount <= 2) {
                this->flushQuads<2>();
            } else {
                this->flushQuads<4>();
            }
        }
        if (fCubicQueue.fCount) {
            if (fCubicQueue.fCount <= 2) {
                this->flushCubics<2>();
            } else {
                this->flushCubics<4>();
            }
        }
        if (fChoppedCubicQueue.fCount) {
            if (fChoppedCubicQueue.fCount <= 2) {
                this->flushChoppedCubics<2>();
            } else {
                this->flushChoppedCubics<4>();
            }
        }
    }

private:
    // This struct stores deferred resolveLevel calculations for performing in SIMD batches.
    template<int NumPts> struct SIMDQueue {
        // Enqueues a stroke.
        SK_ALWAYS_INLINE int push(const SkPoint pts[NumPts], bool pushRoundJoin,
                                  SkPoint lastControlPoint, int8_t* resolveLevelPtr) {
            SkASSERT(0 <= fCount && fCount < 4);
            if constexpr (NumPts == 4) {
                // Store the points transposed in 4-point queues. The caller can use a strided load.
                vec<4> x, y;
                grvx::strided_load2(&pts[0].fX, x, y);
                x.store(fXs[fCount].data());
                y.store(fYs[fCount].data());
            } else {
                for (int i = 0; i < NumPts; ++i) {
                    fXs[i][fCount] = pts[i].fX;
                    fYs[i][fCount] = pts[i].fY;
                }
            }
            if (pushRoundJoin) {
                fLastControlPointsX[fCount] = lastControlPoint.fX;
                fLastControlPointsY[fCount] = lastControlPoint.fY;
            }
            fResolveLevelPtrs[fCount] = resolveLevelPtr;
            return fCount++;
        }

        // Loads pts[idx] in SIMD for all 4 strokes, with the x values in the "vec.lo" and the y
        // values in "vec.hi".
        template<int N> SK_ALWAYS_INLINE vec<N*2> loadPoint(int idx) const {
            SkASSERT(0 <= idx && idx < NumPts);
            static_assert(NumPts != 4, "4-point queues store transposed. Use grvx::strided_load4.");
            return skvx::join(vec<N>::Load(fXs[idx].data()), vec<N>::Load(fYs[idx].data()));
        }

        // Loads all 4 lastControlPoints in SIMD, with the x values in "vec.lo" and the y values in
        // "vec.hi".
        template<int N> SK_ALWAYS_INLINE vec<N*2> loadLastControlPoint() const {
            return skvx::join(vec<N>::Load(fLastControlPointsX), vec<N>::Load(fLastControlPointsY));
        }

        std::array<float,4> fXs[NumPts];
        std::array<float,4> fYs[NumPts];
        float fLastControlPointsX[4];
        float fLastControlPointsY[4];
        int8_t* fResolveLevelPtrs[4];
        int fCount = 0;
    };

    template<int N> void flushLines() {
        SkASSERT(fLineQueue.fCount > 0);

        // Find the angle of rotation in the preceding round join.
        auto a = fLineQueue.loadLastControlPoint<N>();
        auto b = fLineQueue.loadPoint<N>(0);
        auto c = fLineQueue.loadPoint<N>(1);
        auto rotation = grvx::approx_angle_between_vectors(b - a, c - b);

        this->writeResolveLevels<N>(0, rotation, fLineQueue.fCount, fLineQueue.fResolveLevelPtrs);
        fLineQueue.fCount = 0;
    }

    template<int N> void flushQuads() {
        SkASSERT(fQuadQueue.fCount > 0);
        auto p0 = fQuadQueue.loadPoint<N>(0);
        auto p1 = fQuadQueue.loadPoint<N>(1);
        auto p2 = fQuadQueue.loadPoint<N>(2);

        // Execute Wang's formula to determine how many parametric segments the curve needs to be
        // divided into. (See GrWangsFormula::quadratic().)
        auto l = length_pow2(grvx::fast_madd<N*2>(-2, p1, p2) + p0);
        auto numParametricSegments = skvx::sqrt(fWangsTermQuadratic * skvx::sqrt(l));

        // Find the curve's rotation. Since quads cannot inflect or rotate more than 180 degrees,
        // this is equal to the angle between the beginning and ending tangents.
        // NOTE: If p0==p1 or p1==p2, this will give rotation=0.
        auto tan0 = p1 - p0;
        auto tan1 = p2 - p1;
        auto rotation = grvx::approx_angle_between_vectors(tan0, tan1);
        if (fIsRoundJoin) {
            // Add rotation for the preceding round join.
            auto lastControlPoint = fQuadQueue.loadLastControlPoint<N>();
            auto nextTan = if_both_then_else((tan0 == 0), tan1, tan0);
            rotation += grvx::approx_angle_between_vectors(p0 - lastControlPoint, nextTan);
        }

        this->writeResolveLevels<N>(numParametricSegments, rotation, fQuadQueue.fCount,
                                    fQuadQueue.fResolveLevelPtrs);
        fQuadQueue.fCount = 0;
    }

    template<int N> void flushCubics() {
        SkASSERT(fCubicQueue.fCount > 0);
        vec<N*2> p0, p1, p2, p3;
        grvx::strided_load4(fCubicQueue.fXs[0].data(), p0.lo, p1.lo, p2.lo, p3.lo);
        grvx::strided_load4(fCubicQueue.fYs[0].data(), p0.hi, p1.hi, p2.hi, p3.hi);
        this->flushCubics<N>(fCubicQueue, p0, p1, p2, p3, fIsRoundJoin, 0);
        fCubicQueue.fCount = 0;
    }

    template<int N> void flushChoppedCubics() {
        SkASSERT(fChoppedCubicQueue.fCount > 0);
        vec<N*2> p0, p1, p2, p3;
        grvx::strided_load4(fChoppedCubicQueue.fXs[0].data(), p0.lo, p1.lo, p2.lo, p3.lo);
        grvx::strided_load4(fChoppedCubicQueue.fYs[0].data(), p0.hi, p1.hi, p2.hi, p3.hi);
        // Chop the cubic at its chopT and find the resolve level for each half.
        auto T = skvx::join(vec<N>::Load(fCubicChopTs), vec<N>::Load(fCubicChopTs));
        auto ab = unchecked_mix(p0, p1, T);
        auto bc = unchecked_mix(p1, p2, T);
        auto cd = unchecked_mix(p2, p3, T);
        auto abc = unchecked_mix(ab, bc, T);
        auto bcd = unchecked_mix(bc, cd, T);
        auto abcd = unchecked_mix(abc, bcd, T);
        this->flushCubics<N>(fChoppedCubicQueue, p0, ab, abc, abcd, fIsRoundJoin, 0);
        this->flushCubics<N>(fChoppedCubicQueue, abcd, bcd, cd, p3, false/*countRoundJoin*/, 1);
        fChoppedCubicQueue.fCount = 0;
    }

    template<int N> SK_ALWAYS_INLINE void flushCubics(const SIMDQueue<4>& queue, vec<N*2> p0,
                                                      vec<N*2> p1, vec<N*2> p2, vec<N*2> p3,
                                                      bool countRoundJoin, int resultOffset) const {
        // Execute Wang's formula to determine how many parametric segments the curve needs to be
        // divided into. (See GrWangsFormula::cubic().)
        auto l0 = length_pow2(grvx::fast_madd<N*2>(-2, p1, p2) + p0);
        auto l1 = length_pow2(grvx::fast_madd<N*2>(-2, p2, p3) + p1);
        auto numParametricSegments = skvx::sqrt(fWangsTermCubic * skvx::sqrt(skvx::max(l0, l1)));

        // Find the starting tangent (or zero if p0==p1==p2).
        auto tan0 = p1 - p0;
        tan0 = if_both_then_else((tan0 == 0), p2 - p0, tan0);

        // Find the ending tangent (or zero if p1==p2==p3).
        auto tan1 = p3 - p2;
        tan1 = if_both_then_else((tan1 == 0), p3 - p1, tan1);

        // Find the curve's rotation. Since it cannot inflect or rotate more than 180 degrees at
        // this point, this is equal to the angle between the beginning and ending tangents.
        auto rotation = grvx::approx_angle_between_vectors(tan0, tan1);
        if (countRoundJoin) {
            // Add rotation for the preceding round join.
            auto lastControlPoint = queue.loadLastControlPoint<N>();
            auto nextTan = if_both_then_else((tan0 == 0), tan1, tan0);
            rotation += grvx::approx_angle_between_vectors(p0 - lastControlPoint, nextTan);
        }

        this->writeResolveLevels<N>(numParametricSegments, rotation, queue.fCount,
                                    queue.fResolveLevelPtrs, resultOffset);
    }

    template<int N> SK_ALWAYS_INLINE void writeResolveLevels(
            vec<N> numParametricSegments, vec<N> rotation, int count,
            int8_t* const* resolveLevelPtrs, int offset = 0) const {
        auto numCombinedSegments = grvx::fast_madd<N>(
                fTolerances.fNumRadialSegmentsPerRadian, rotation, numParametricSegments);

        // Find ceil(log2(numCombinedSegments)) by twiddling the exponents. See sk_float_nextlog2().
        auto bits = skvx::bit_pun<uvec<N>>(numCombinedSegments);
        bits += (1u << 23) - 1u;  // Increment the exponent for non-powers-of-2.
        // This will make negative values, denorms, and negative exponents all < 0.
        auto exp = (skvx::bit_pun<ivec<N>>(bits) >> 23) - 127;
        auto level = skvx::pin<N,int>(exp, 0, GrStrokeIndirectOp::kMaxResolveLevel);

        switch (count) {
            default: SkUNREACHABLE;
            case 4: ++fResolveLevelCounts[resolveLevelPtrs[3][offset] = level[3]]; [[fallthrough]];
            case 3: ++fResolveLevelCounts[resolveLevelPtrs[2][offset] = level[2]]; [[fallthrough]];
            case 2: ++fResolveLevelCounts[resolveLevelPtrs[1][offset] = level[1]]; [[fallthrough]];
            case 1: ++fResolveLevelCounts[resolveLevelPtrs[0][offset] = level[0]]; break;
        }
    }

#endif
    const bool fIsRoundJoin;
    GrStrokeTessellateShader::Tolerances fTolerances;

#if USE_SIMD
    const float fWangsTermQuadratic;
    const float fWangsTermCubic;

    SIMDQueue<2> fLineQueue;
    SIMDQueue<3> fQuadQueue;
    SIMDQueue<4> fCubicQueue;
    SIMDQueue<4> fChoppedCubicQueue;
    float fCubicChopTs[4];

#endif
    int* const fResolveLevelCounts;
};

}  // namespace

void GrStrokeIndirectOp::prePrepareResolveLevels(SkArenaAlloc* alloc) {
    SkASSERT(!fTotalInstanceCount);
    SkASSERT(!fResolveLevels);
    SkASSERT(!fResolveLevelArrayCount);

    // The maximum potential number of values we will need in fResolveLevels is:
    //
    //   * 3 segments per verb (from two chops)
    //   * Plus 1 extra resolveLevel per verb that says how many chops it needs
    //   * Plus 2 final resolveLevels for square caps at the very end not initiated by a "kMoveTo".
    //
    int resolveLevelAllocCount = fTotalCombinedVerbCnt * (3 + 1) + 2;
{'fraction_non_alphanumeric': 0.11508747710197255, 'fraction_numerical': 0.01746887195688534, 'mean_word_length': 2.4958700696055685, 'pattern_counts': {'":': 0, '<': 195, '<?xml version=': 0, '>': 241, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 32, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2127612', 'n_tokens_mistral': 12808, 'n_tokens_neox': 12265, 'n_words': 2848}