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:
parent
59b2a92c96
commit
e2067645ef
@ -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(); )
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user