Optimize GrWangsFormula

Adds various optimizations to GrWangsFormula as well as "pow4" variants
of the formula that are quicker than the standard and/or log2 versions.
Uses the pow4 variants in GrStrokePatchBuilder.

Bug: skia:10419
Change-Id: I8478582df5296b088d25808bcaeb93107ff20797
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/318954
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
This commit is contained in:
Chris Dalton 2020-09-23 11:07:20 -06:00 committed by Skia Commit-Bot
parent 59b2a92c96
commit e2067645ef
5 changed files with 163 additions and 102 deletions

View File

@ -13,6 +13,7 @@
#include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
#include "src/gpu/tessellate/GrPathTessellateOp.h"
#include "src/gpu/tessellate/GrResolveLevelCounter.h"
#include "src/gpu/tessellate/GrStrokePatchBuilder.h"
#include "src/gpu/tessellate/GrWangsFormula.h"
#include "tools/ToolUtils.h"
@ -63,12 +64,24 @@ public:
int* startVertex) override {
if (vertexSize * vertexCount > sizeof(fStaticVertexData)) {
SK_ABORT("FATAL: wanted %zu bytes of static vertex data; only have %zu.\n",
vertexSize * vertexCount, SK_ARRAY_COUNT(fStaticVertexData));
vertexSize * vertexCount, sizeof(fStaticVertexData));
}
*startVertex = 0;
return fStaticVertexData;
}
void* makeVertexSpaceAtLeast(size_t vertexSize, int minVertexCount, int fallbackVertexCount,
sk_sp<const GrBuffer>*, int* startVertex,
int* actualVertexCount) override {
if (vertexSize * minVertexCount > sizeof(fStaticVertexData)) {
SK_ABORT("FATAL: wanted %zu bytes of static vertex data; only have %zu.\n",
vertexSize * minVertexCount, sizeof(fStaticVertexData));
}
*startVertex = 0;
*actualVertexCount = sizeof(fStaticVertexData) / vertexSize;
return fStaticVertexData;
}
GrDrawIndexedIndirectCommand* makeDrawIndexedIndirectSpace(
int drawCount, sk_sp<const GrBuffer>* buffer, size_t* offsetInBytes) override {
int staticBufferCount = (int)SK_ARRAY_COUNT(fStaticDrawIndexedIndirectData);
@ -83,7 +96,6 @@ public:
UNIMPL(void recordDraw(const GrGeometryProcessor*, const GrSimpleMesh[], int,
const GrSurfaceProxy* const[], GrPrimitiveType))
UNIMPL(uint16_t* makeIndexSpace(int, sk_sp<const GrBuffer>*, int*))
UNIMPL(void* makeVertexSpaceAtLeast(size_t, int, int, sk_sp<const GrBuffer>*, int*, int*))
UNIMPL(uint16_t* makeIndexSpaceAtLeast(int, int, sk_sp<const GrBuffer>*, int*, int*))
UNIMPL(GrDrawIndirectCommand* makeDrawIndirectSpace(int, sk_sp<const GrBuffer>*, size_t*))
UNIMPL(void putBackIndices(int))
@ -151,7 +163,7 @@ private:
SkString fName;
};
#define DEF_TESS_BENCH(NAME, PATH, MATRIX, TARGET, OP) \
#define DEF_PATH_TESS_BENCH(NAME, PATH, MATRIX, TARGET, OP) \
class GrPathTessellateOp::TestingOnly_Benchmark::NAME \
: public GrPathTessellateOp::TestingOnly_Benchmark { \
public: \
@ -162,27 +174,27 @@ private:
void GrPathTessellateOp::TestingOnly_Benchmark::NAME::runBench( \
GrMeshDrawOp::Target* TARGET, GrPathTessellateOp* op)
DEF_TESS_BENCH(prepareMiddleOutStencilGeometry, make_cubic_path(), SkMatrix::I(), target, op) {
DEF_PATH_TESS_BENCH(prepareMiddleOutStencilGeometry, make_cubic_path(), SkMatrix::I(), target, op) {
op->prepareMiddleOutTrianglesAndCubics(target);
}
DEF_TESS_BENCH(prepareMiddleOutStencilGeometry_indirect, make_cubic_path(), SkMatrix::I(), target,
op) {
DEF_PATH_TESS_BENCH(prepareMiddleOutStencilGeometry_indirect, make_cubic_path(), SkMatrix::I(),
target, op) {
GrResolveLevelCounter resolveLevelCounter;
op->prepareMiddleOutTrianglesAndCubics(target, &resolveLevelCounter, true);
}
DEF_TESS_BENCH(prepareIndirectOuterCubics, make_cubic_path(), SkMatrix::I(), target, op) {
DEF_PATH_TESS_BENCH(prepareIndirectOuterCubics, make_cubic_path(), SkMatrix::I(), target, op) {
GrResolveLevelCounter resolveLevelCounter;
resolveLevelCounter.reset(op->fPath, SkMatrix::I(), 4);
op->prepareIndirectOuterCubics(target, resolveLevelCounter);
}
DEF_TESS_BENCH(prepareTessellatedOuterCubics, make_cubic_path(), SkMatrix::I(), target, op) {
DEF_PATH_TESS_BENCH(prepareTessellatedOuterCubics, make_cubic_path(), SkMatrix::I(), target, op) {
op->prepareTessellatedOuterCubics(target, kNumCubicsInChalkboard);
}
DEF_TESS_BENCH(prepareTessellatedCubicWedges, make_cubic_path(), SkMatrix::I(), target, op) {
DEF_PATH_TESS_BENCH(prepareTessellatedCubicWedges, make_cubic_path(), SkMatrix::I(), target, op) {
op->prepareTessellatedCubicWedges(target);
}
@ -200,23 +212,23 @@ static void benchmark_wangs_formula_cubic_log2(const SkMatrix& matrix, const SkP
}
}
DEF_TESS_BENCH(wangs_formula_cubic_log2, make_cubic_path(), SkMatrix::I(), target, op) {
DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2, make_cubic_path(), SkMatrix::I(), target, op) {
benchmark_wangs_formula_cubic_log2(op->fViewMatrix, op->fPath);
}
DEF_TESS_BENCH(wangs_formula_cubic_log2_scale, make_cubic_path(), SkMatrix::Scale(1.1f, 0.9f),
target, op) {
DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2_scale, make_cubic_path(), SkMatrix::Scale(1.1f, 0.9f),
target, op) {
benchmark_wangs_formula_cubic_log2(op->fViewMatrix, op->fPath);
}
DEF_TESS_BENCH(wangs_formula_cubic_log2_affine, make_cubic_path(),
SkMatrix::MakeAll(.9f,0.9f,0, 1.1f,1.1f,0, 0,0,1), target, op) {
DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2_affine, make_cubic_path(),
SkMatrix::MakeAll(.9f,0.9f,0, 1.1f,1.1f,0, 0,0,1), target, op) {
benchmark_wangs_formula_cubic_log2(op->fViewMatrix, op->fPath);
}
DEF_TESS_BENCH(middle_out_triangulation,
ToolUtils::make_star(SkRect::MakeWH(500, 500), kNumCubicsInChalkboard),
SkMatrix::I(), target, op) {
DEF_PATH_TESS_BENCH(middle_out_triangulation,
ToolUtils::make_star(SkRect::MakeWH(500, 500), kNumCubicsInChalkboard),
SkMatrix::I(), target, op) {
int baseVertex;
auto vertexData = static_cast<SkPoint*>(target->makeVertexSpace(
sizeof(SkPoint), kNumCubicsInChalkboard, nullptr, &baseVertex));
@ -240,3 +252,38 @@ DEF_TESS_BENCH(middle_out_triangulation,
middleOut.closeAndMove(pts[0]);
}
}
class StrokePatchBuilderBench : public Benchmark {
const char* onGetName() override { return "tessellate_StrokePatchBuilder"; }
bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; }
void onDelayedSetup() override {
fPath.reset().moveTo(0, 0);
for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) {
fPath.cubicTo(100, 0, 0, 100, 100, 100);
fPath.cubicTo(100, 0, 0, 100, 0, 0);
}
fStrokeRec.setStrokeStyle(8);
fStrokeRec.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kMiter_Join, 4);
}
void onDraw(int loops, SkCanvas*) final {
if (!fTarget.mockContext()) {
SkDebugf("ERROR: could not create mock context.");
return;
}
for (int i = 0; i < loops; ++i) {
fPatchChunks.reset();
GrStrokePatchBuilder builder(&fTarget, &fPatchChunks, 1, fStrokeRec,
fPath.countVerbs());
builder.addPath(fPath);
}
}
BenchmarkTarget fTarget;
SkPath fPath;
SkStrokeRec fStrokeRec = SkStrokeRec(SkStrokeRec::kFill_InitStyle);
SkSTArray<8, GrStrokePatchBuilder::PatchChunk> fPatchChunks;
};
DEF_BENCH( return new StrokePatchBuilderBench(); )

View File

@ -46,7 +46,12 @@ static float num_combined_segments(float numParametricSegments, float numRadialS
static float num_parametric_segments(float numCombinedSegments, float numRadialSegments) {
// numCombinedSegments = numParametricSegments + numRadialSegments - 1.
// (See num_combined_segments()).
return numCombinedSegments + 1 - numRadialSegments;
return std::max(numCombinedSegments + 1 - numRadialSegments, 0.f);
}
static float pow4(float x) {
float xx = x*x;
return xx*xx;
}
GrStrokePatchBuilder::GrStrokePatchBuilder(GrMeshDrawOp::Target* target,
@ -74,12 +79,15 @@ GrStrokePatchBuilder::GrStrokePatchBuilder(GrMeshDrawOp::Target* target,
// send almost all curves directly to the hardware without having to chop.
float numRadialSegments180 = std::max(std::ceil(
SK_ScalarPI * fNumRadialSegmentsPerRadian), 1.f);
fMaxParametricSegments180 = num_parametric_segments(fMaxTessellationSegments,
numRadialSegments180);
float maxParametricSegments180 = num_parametric_segments(fMaxTessellationSegments,
numRadialSegments180);
fMaxParametricSegments180_pow4 = pow4(maxParametricSegments180);
float numRadialSegments360 = std::max(std::ceil(
2*SK_ScalarPI * fNumRadialSegmentsPerRadian), 1.f);
fMaxParametricSegments360 = num_parametric_segments(fMaxTessellationSegments,
numRadialSegments360);
float maxParametricSegments360 = num_parametric_segments(fMaxTessellationSegments,
numRadialSegments360);
fMaxParametricSegments360_pow4 = pow4(maxParametricSegments360);
// Now calculate the worst-case numbers of parametric segments if we are to integrate a join
// into the same patch as the curve.
@ -98,8 +106,10 @@ GrStrokePatchBuilder::GrStrokePatchBuilder(GrMeshDrawOp::Target* target,
}
// Subtract an extra 1 off the end because when we integrate a join, the tessellator has to add
// a redundant edge between the join and curve.
fMaxParametricSegments180_withJoin = fMaxParametricSegments180 - maxNumSegmentsInJoin - 1;
fMaxParametricSegments360_withJoin = fMaxParametricSegments360 - maxNumSegmentsInJoin - 1;
fMaxParametricSegments180_pow4_withJoin = pow4(std::max(
maxParametricSegments180 - maxNumSegmentsInJoin - 1, 0.f));
fMaxParametricSegments360_pow4_withJoin = pow4(std::max(
maxParametricSegments360 - maxNumSegmentsInJoin - 1, 0.f));
fMaxCombinedSegments_withJoin = fMaxTessellationSegments - maxNumSegmentsInJoin - 1;
fSoloRoundJoinAlwaysFitsInPatch = (numRadialSegments180 <= fMaxTessellationSegments);
@ -215,15 +225,15 @@ void GrStrokePatchBuilder::quadraticTo(const SkPoint p[3], JoinType prevJoinType
//
// An informal survey of skottie animations and gms revealed that even with a bare minimum of 64
// tessellation segments, 99.9%+ of quadratics take this early out.
float numParametricSegments = GrWangsFormula::quadratic(fLinearizationIntolerance, p);
if (numParametricSegments <= fMaxParametricSegments180_withJoin &&
float numParametricSegments_pow4 = GrWangsFormula::quadratic_pow4(fLinearizationIntolerance, p);
if (numParametricSegments_pow4 <= fMaxParametricSegments180_pow4_withJoin &&
prevJoinType != JoinType::kCusp) {
this->cubicToRaw(prevJoinType, asCubic);
return;
}
if (numParametricSegments <= fMaxParametricSegments180 || maxDepth == 0) {
if (numParametricSegments > fMaxParametricSegments180_withJoin ||
if (numParametricSegments_pow4 <= fMaxParametricSegments180_pow4 || maxDepth == 0) {
if (numParametricSegments_pow4 > fMaxParametricSegments180_pow4_withJoin ||
prevJoinType == JoinType::kCusp) {
// Either there aren't enough guaranteed segments to include the join in the quadratic's
// patch, or we need a cusp. Emit a standalone patch for the join.
@ -238,6 +248,7 @@ void GrStrokePatchBuilder::quadraticTo(const SkPoint p[3], JoinType prevJoinType
// actual rotation.
float numRadialSegments = SkMeasureQuadRotation(p) * fNumRadialSegmentsPerRadian;
numRadialSegments = std::max(std::ceil(numRadialSegments), 1.f);
float numParametricSegments = GrWangsFormula::root4(numParametricSegments_pow4);
numParametricSegments = std::max(std::ceil(numParametricSegments), 1.f);
float numCombinedSegments = num_combined_segments(numParametricSegments, numRadialSegments);
if (numCombinedSegments > fMaxTessellationSegments) {
@ -300,19 +311,19 @@ void GrStrokePatchBuilder::cubicTo(const SkPoint p[4], JoinType prevJoinType,
//
// An informal survey of skottie animations revealed that with a bare minimum of 64 tessellation
// segments, 95% of cubics take this early out.
float numParametricSegments = GrWangsFormula::cubic(fLinearizationIntolerance, p);
if (numParametricSegments <= fMaxParametricSegments360_withJoin &&
float numParametricSegments_pow4 = GrWangsFormula::cubic_pow4(fLinearizationIntolerance, p);
if (numParametricSegments_pow4 <= fMaxParametricSegments360_pow4_withJoin &&
prevJoinType != JoinType::kCusp) {
this->cubicToRaw(prevJoinType, p);
return;
}
float maxParametricSegments = (convex180Status == Convex180Status::kYes) ?
fMaxParametricSegments180 : fMaxParametricSegments360;
if (numParametricSegments <= maxParametricSegments || maxDepth == 0) {
float maxParametricSegments_withJoin = (convex180Status == Convex180Status::kYes) ?
fMaxParametricSegments180_withJoin : fMaxParametricSegments360_withJoin;
if (numParametricSegments > maxParametricSegments_withJoin ||
float maxParametricSegments_pow4 = (convex180Status == Convex180Status::kYes) ?
fMaxParametricSegments180_pow4 : fMaxParametricSegments360_pow4;
if (numParametricSegments_pow4 <= maxParametricSegments_pow4 || maxDepth == 0) {
float maxParametricSegments_pow4_withJoin = (convex180Status == Convex180Status::kYes) ?
fMaxParametricSegments180_pow4_withJoin : fMaxParametricSegments360_pow4_withJoin;
if (numParametricSegments_pow4 > maxParametricSegments_pow4_withJoin ||
prevJoinType == JoinType::kCusp) {
// Either there aren't enough guaranteed segments to include the join in the cubic's
// patch, or we need a cusp. Emit a standalone patch for the join.
@ -352,6 +363,7 @@ void GrStrokePatchBuilder::cubicTo(const SkPoint p[4], JoinType prevJoinType,
// its actual rotation.
float numRadialSegments = SkMeasureNonInflectCubicRotation(p) * fNumRadialSegmentsPerRadian;
numRadialSegments = std::max(std::ceil(numRadialSegments), 1.f);
float numParametricSegments = GrWangsFormula::root4(numParametricSegments_pow4);
numParametricSegments = std::max(std::ceil(numParametricSegments), 1.f);
float numCombinedSegments = num_combined_segments(numParametricSegments, numRadialSegments);
if (numCombinedSegments > fMaxTessellationSegments) {

View File

@ -103,14 +103,15 @@ private:
// Variables related to the stroke parameters.
const SkStrokeRec fStroke;
float fNumRadialSegmentsPerRadian;
// These values contain worst-case numbers of parametric segments our hardware can support for
// the current stroke radius, in the event that there are also enough radial segments to rotate
// 180 and 360 degrees respectively. These are used for "quick accepts" that allow us to send
// almost all curves directly to the hardware without having to chop or think any further.
float fMaxParametricSegments180;
float fMaxParametricSegments360;
float fMaxParametricSegments180_withJoin;
float fMaxParametricSegments360_withJoin;
// These values contain worst-case numbers of parametric segments, raised to the 4th power, that
// our hardware can support for the current stroke radius. They assume curve rotations of 180
// and 360 degrees respectively. These are used for "quick accepts" that allow us to send almost
// all curves directly to the hardware without having to chop. We raise to the 4th power because
// the "pow4" variants of Wang's formula are the quickest to evaluate.
float fMaxParametricSegments180_pow4;
float fMaxParametricSegments360_pow4;
float fMaxParametricSegments180_pow4_withJoin;
float fMaxParametricSegments360_pow4_withJoin;
float fMaxCombinedSegments_withJoin;
bool fSoloRoundJoinAlwaysFitsInPatch;

View File

@ -18,9 +18,13 @@
// lines stay within a distance of "1/intolerance" pixels from the true curve.
namespace GrWangsFormula {
SK_ALWAYS_INLINE static float length(const Sk2f& n) {
Sk2f nn = n*n;
return std::sqrt(nn[0] + nn[1]);
SK_ALWAYS_INLINE static float root4(float x) {
// rsqrt() is quicker than sqrt(), and 1/sqrt(1/sqrt(x)) == sqrt(sqrt(x)).
return (x != 0) ? sk_float_rsqrt(sk_float_rsqrt(x)) : 0;
}
SK_ALWAYS_INLINE static int ceil_log2_sqrt_sqrt(float x) {
return (sk_float_nextlog2(x) + 3) >> 2; // i.e., "ceil(log2(sqrt(sqrt(f))))
}
// Constant term for the quatratic formula.
@ -28,52 +32,11 @@ constexpr float quadratic_k(float intolerance) {
return .25f * intolerance;
}
// Returns the minimum number of evenly spaced (in the parametric sense) line segments that the
// quadratic must be chopped into in order to guarantee all lines stay within a distance of
// "1/intolerance" pixels from the true curve.
SK_ALWAYS_INLINE static float quadratic(float intolerance, const SkPoint pts[]) {
Sk2f p0 = Sk2f::Load(pts);
Sk2f p1 = Sk2f::Load(pts + 1);
Sk2f p2 = Sk2f::Load(pts + 2);
float k = quadratic_k(intolerance);
return SkScalarSqrt(k * length(p0 - p1*2 + p2));
}
// Constant term for the cubic formula.
constexpr float cubic_k(float intolerance) {
return .75f * intolerance;
}
// Returns the minimum number of evenly spaced (in the parametric sense) line segments that the
// cubic must be chopped into in order to guarantee all lines stay within a distance of
// "1/intolerance" pixels from the true curve.
SK_ALWAYS_INLINE static float cubic(float intolerance, const SkPoint pts[]) {
Sk2f p0 = Sk2f::Load(pts);
Sk2f p1 = Sk2f::Load(pts + 1);
Sk2f p2 = Sk2f::Load(pts + 2);
Sk2f p3 = Sk2f::Load(pts + 3);
float k = cubic_k(intolerance);
return SkScalarSqrt(k * length(Sk2f::Max((p0 - p1*2 + p2).abs(),
(p1 - p2*2 + p3).abs())));
}
// Returns the maximum number of line segments a cubic with the given device-space bounding box size
// would ever need to be divided into. This is simply a special case of the cubic formula where we
// maximize its value by placing control points on specific corners of the bounding box.
SK_ALWAYS_INLINE static float worst_case_cubic(float intolerance, float devWidth, float devHeight) {
float k = cubic_k(intolerance);
return SkScalarSqrt(2*k * SkVector::Length(devWidth, devHeight));
}
SK_ALWAYS_INLINE static int ceil_log2_sqrt_sqrt(float f) {
return (sk_float_nextlog2(f) + 3) >> 2; // i.e., "ceil(log2(sqrt(sqrt(f))))
}
// Returns the minimum log2 number of evenly spaced (in the parametric sense) line segments that the
// transformed quadratic must be chopped into in order to guarantee all lines stay within a distance
// of "1/intolerance" pixels from the true curve.
SK_ALWAYS_INLINE static int quadratic_log2(float intolerance, const SkPoint pts[],
const GrVectorXform& vectorXform = GrVectorXform()) {
// Returns Wang's formula for the given quadratic, raised to the 4th power. (Refer to
// GrWangsFormula::quadratic for more comments on the formula.)
// The math is quickest when we calculate this value raised to the 4th power.
SK_ALWAYS_INLINE static float quadratic_pow4(float intolerance, const SkPoint pts[],
const GrVectorXform& vectorXform = GrVectorXform()) {
Sk2f p0 = Sk2f::Load(pts);
Sk2f p1 = Sk2f::Load(pts + 1);
Sk2f p2 = Sk2f::Load(pts + 2);
@ -81,15 +44,33 @@ SK_ALWAYS_INLINE static int quadratic_log2(float intolerance, const SkPoint pts[
v = vectorXform(v);
Sk2f vv = v*v;
float k = quadratic_k(intolerance);
float f = k*k * (vv[0] + vv[1]);
return ceil_log2_sqrt_sqrt(f);
return k*k * (vv[0] + vv[1]);
}
// Returns the minimum log2 number of evenly spaced (in the parametric sense) line segments that the
// transformed cubic must be chopped into in order to guarantee all lines stay within a distance of
// Returns the minimum number of evenly spaced (in the parametric sense) line segments that the
// quadratic must be chopped into in order to guarantee all lines stay within a distance of
// "1/intolerance" pixels from the true curve.
SK_ALWAYS_INLINE static int cubic_log2(float intolerance, const SkPoint pts[],
const GrVectorXform& vectorXform = GrVectorXform()) {
SK_ALWAYS_INLINE static float quadratic(float intolerance, const SkPoint pts[],
const GrVectorXform& vectorXform = GrVectorXform()) {
return root4(quadratic_pow4(intolerance, pts, vectorXform));
}
// Returns the log2 value of Wang's formula for the given quadratic, rounded up to the next int.
SK_ALWAYS_INLINE static int quadratic_log2(float intolerance, const SkPoint pts[],
const GrVectorXform& vectorXform = GrVectorXform()) {
return ceil_log2_sqrt_sqrt(quadratic_pow4(intolerance, pts, vectorXform));
}
// Constant term for the cubic formula.
constexpr float cubic_k(float intolerance) {
return .75f * intolerance;
}
// Returns Wang's formula for the given cubic, raised to the 4th power. (Refer to
// GrWangsFormula::cubic for more comments on the formula.)
// The math is quickest when we calculate this value raised to the 4th power.
SK_ALWAYS_INLINE static float cubic_pow4(float intolerance, const SkPoint pts[],
const GrVectorXform& vectorXform = GrVectorXform()) {
Sk4f p01 = Sk4f::Load(pts);
Sk4f p12 = Sk4f::Load(pts + 1);
Sk4f p23 = Sk4f::Load(pts + 2);
@ -98,8 +79,29 @@ SK_ALWAYS_INLINE static int cubic_log2(float intolerance, const SkPoint pts[],
Sk4f vv = v*v;
vv = Sk4f::Max(vv, SkNx_shuffle<2,3,0,1>(vv));
float k = cubic_k(intolerance);
float f = k*k * (vv[0] + vv[1]);
return ceil_log2_sqrt_sqrt(f);
return k*k * (vv[0] + vv[1]);
}
// Returns the minimum number of evenly spaced (in the parametric sense) line segments that the
// cubic must be chopped into in order to guarantee all lines stay within a distance of
// "1/intolerance" pixels from the true curve.
SK_ALWAYS_INLINE static float cubic(float intolerance, const SkPoint pts[],
const GrVectorXform& vectorXform = GrVectorXform()) {
return root4(cubic_pow4(intolerance, pts, vectorXform));
}
// Returns the log2 value of Wang's formula for the given cubic, rounded up to the next int.
SK_ALWAYS_INLINE static int cubic_log2(float intolerance, const SkPoint pts[],
const GrVectorXform& vectorXform = GrVectorXform()) {
return ceil_log2_sqrt_sqrt(cubic_pow4(intolerance, pts, vectorXform));
}
// Returns the maximum number of line segments a cubic with the given device-space bounding box size
// would ever need to be divided into. This is simply a special case of the cubic formula where we
// maximize its value by placing control points on specific corners of the bounding box.
SK_ALWAYS_INLINE static float worst_case_cubic(float intolerance, float devWidth, float devHeight) {
float k = cubic_k(intolerance);
return root4(4*k*k * (devWidth * devWidth + devHeight * devHeight));
}
// Returns the maximum log2 number of line segments a cubic with the given device-space bounding box

View File

@ -258,7 +258,6 @@ DEF_TEST(WangsFormula_worst_case_cubic, r) {
float actual = GrWangsFormula::cubic(kIntolerance, pts);
REPORTER_ASSERT(r, worst >= actual);
REPORTER_ASSERT(r, std::ceil(std::log2(std::max(1.f, worst))) == worst_log2);
SkASSERT(std::ceil(std::log2(std::max(1.f, worst))) == worst_log2);
};
SkRandom rand;
for (int i = 0; i < 100; ++i) {