blob: b9568d1774809ebf307a562b0009eaa82d753827 [file] [log] [blame]
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrWangsFormula_DEFINED
#define GrWangsFormula_DEFINED
#include "include/core/SkPoint.h"
#include "include/private/SkFloatingPoint.h"
#include "src/gpu/GrVx.h"
#include "src/gpu/tessellate/GrVectorXform.h"
// Wang's formula gives the minimum number of evenly spaced (in the parametric sense) line segments
// that a bezier curve must be chopped into in order to guarantee all lines stay within a distance
// of "1/intolerance" pixels from the true curve. Its definition for a bezier curve of degree "n" is
// as follows:
//
// maxLength = max([length(p[i+2] - 2p[i+1] + p[i]) for (0 <= i <= n-2)])
// numParametricSegments = sqrt(maxLength * intolerance * n*(n - 1)/8)
//
// (Goldman, Ron. (2003). 5.6.3 Wang's Formula. "Pyramid Algorithms: A Dynamic Programming Approach
// to Curves and Surfaces for Geometric Modeling". Morgan Kaufmann Publishers.)
namespace GrWangsFormula {
// Returns the value by which to multiply length in Wang's formula. (See above.)
template<int Degree> constexpr float length_term(float intolerance) {
return (Degree * (Degree - 1) / 8.f) * intolerance;
}
template<int Degree> constexpr float length_term_pow2(float intolerance) {
return ((Degree * Degree) * ((Degree - 1) * (Degree - 1)) / 64.f) * (intolerance * intolerance);
}
SK_ALWAYS_INLINE static float root4(float x) {
return sqrtf(sqrtf(x));
}
// Returns nextlog2(sqrt(sqrt(x))):
//
// log2(sqrt(sqrt(x))) == log2(x^(1/4)) == log2(x)/4 == log2(x)/log2(16) == log16(x)
//
SK_ALWAYS_INLINE static int nextlog16(float x) {
return (sk_float_nextlog2(x) + 3) >> 2;
}
// Returns Wang's formula, raised to the 4th power, specialized for a quadratic curve.
SK_ALWAYS_INLINE static float quadratic_pow4(float intolerance, const SkPoint pts[],
const GrVectorXform& vectorXform = GrVectorXform()) {
using grvx::float2, skvx::bit_pun;
float2 p0 = bit_pun<float2>(pts[0]);
float2 p1 = bit_pun<float2>(pts[1]);
float2 p2 = bit_pun<float2>(pts[2]);
float2 v = grvx::fast_madd<2>(-2, p1, p0) + p2;
v = vectorXform(v);
float2 vv = v*v;
return (vv[0] + vv[1]) * length_term_pow2<2>(intolerance);
}
// Returns Wang's formula specialized for a quadratic curve.
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 specialized for a quadratic curve, rounded up to the
// next int.
SK_ALWAYS_INLINE static int quadratic_log2(float intolerance, const SkPoint pts[],
const GrVectorXform& vectorXform = GrVectorXform()) {
// nextlog16(x) == ceil(log2(sqrt(sqrt(x))))
return nextlog16(quadratic_pow4(intolerance, pts, vectorXform));
}
// Returns Wang's formula, raised to the 4th power, specialized for a cubic curve.
SK_ALWAYS_INLINE static float cubic_pow4(float intolerance, const SkPoint pts[],
const GrVectorXform& vectorXform = GrVectorXform()) {
using grvx::float4;
float4 p01 = float4::Load(pts);
float4 p12 = float4::Load(pts + 1);
float4 p23 = float4::Load(pts + 2);
float4 v = grvx::fast_madd<4>(-2, p12, p01) + p23;
v = vectorXform(v);
float4 vv = v*v;
return std::max(vv[0] + vv[1], vv[2] + vv[3]) * length_term_pow2<3>(intolerance);
}
// Returns Wang's formula specialized for a cubic 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 specialized for a cubic curve, rounded up to the next
// int.
SK_ALWAYS_INLINE static int cubic_log2(float intolerance, const SkPoint pts[],
const GrVectorXform& vectorXform = GrVectorXform()) {
// nextlog16(x) == ceil(log2(sqrt(sqrt(x))))
return nextlog16(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 = length_term<3>(intolerance);
return sqrtf(2*k * SkVector::Length(devWidth, devHeight));
}
// Returns the maximum log2 number of line segments a cubic with the given device-space bounding box
// size would ever need to be divided into.
SK_ALWAYS_INLINE static int worst_case_cubic_log2(float intolerance, float devWidth,
float devHeight) {
float kk = length_term_pow2<3>(intolerance);
// nextlog16(x) == ceil(log2(sqrt(sqrt(x))))
return nextlog16(4*kk * (devWidth * devWidth + devHeight * devHeight));
}
} // namespace GrWangsFormula
#endif