Skip to content

Commit d4322a8

Browse files
reed-at-googleSkia Commit-Bot
authored andcommitted
Change cubicmap to eval directly (no table)
- gives us zero setup cost - perfect accuracy (at least better than our 16-polyline approx) - slower at runtime, but for skottie seems still way under the radar Bug: skia: Change-Id: Ic01f76184c359c65d6af9a3bedeac34a2ad7d3a6 Reviewed-on: https://skia-review.googlesource.com/146961 Reviewed-by: Mike Klein <mtklein@google.com> Reviewed-by: Florin Malita <fmalita@chromium.org> Commit-Queue: Mike Reed <reed@google.com>
1 parent 1b95ef9 commit d4322a8

4 files changed

Lines changed: 175 additions & 117 deletions

File tree

gm/aaclip.cpp

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,52 +11,6 @@
1111
#include "SkPath.h"
1212
#include "SkMakeUnique.h"
1313

14-
15-
#include "SkCubicMap.h"
16-
17-
static void test_cubic(SkCanvas* canvas) {
18-
const SkPoint pts[] = {
19-
{ 0.333333f, 0.333333f }, { 0.666666f, 0.666666f },
20-
{ 1, 0 }, { 0, 1 },
21-
{ 0, 1 }, { 1, 0 },
22-
{ 0, 0 }, { 1, 1 },
23-
{ 1, 1 }, { 0, 0 },
24-
{ 0, 1 }, { 0, 1 },
25-
{ 1, 0 }, { 1, 0 },
26-
};
27-
28-
SkPaint paint0, paint1;
29-
paint0.setAntiAlias(true); paint0.setStrokeWidth(3/256.0f); paint0.setColor(SK_ColorRED);
30-
paint1.setAntiAlias(true);
31-
32-
SkCubicMap cmap;
33-
34-
canvas->translate(10, 266);
35-
canvas->scale(256, -256);
36-
for (size_t i = 0; i < SK_ARRAY_COUNT(pts); i += 2) {
37-
cmap.setPts(pts[i], pts[i+1]);
38-
39-
const int N = 128;
40-
SkPoint tmp0[N+1], tmp1[N+1], tmp2[N+1];
41-
for (int j = 0; j <= N; ++j) {
42-
float p = j * 1.0f / N;
43-
tmp0[j] = cmap.computeFromT(p);
44-
tmp1[j].set(p, cmap.computeYFromX(p));
45-
tmp2[j].set(p, cmap.hackYFromX(p));
46-
}
47-
48-
canvas->save();
49-
canvas->drawPoints(SkCanvas::kPolygon_PointMode, N+1, tmp0, paint0);
50-
canvas->drawPoints(SkCanvas::kPolygon_PointMode, N+1, tmp1, paint1);
51-
canvas->translate(0, -1.2f);
52-
canvas->drawPoints(SkCanvas::kPolygon_PointMode, N+1, tmp0, paint0);
53-
canvas->drawPoints(SkCanvas::kPolygon_PointMode, N+1, tmp2, paint1);
54-
canvas->restore();
55-
56-
canvas->translate(1.1f, 0);
57-
}
58-
}
59-
6014
static void do_draw(SkCanvas* canvas, const SkRect& r) {
6115
SkPaint paint;
6216
paint.setBlendMode(SkBlendMode::kSrc);
@@ -187,8 +141,6 @@ class AAClipGM : public skiagm::GM {
187141
}
188142

189143
void onDraw(SkCanvas* canvas) override {
190-
if (0) { test_cubic(canvas); return; }
191-
192144
// Initial pixel-boundary-aligned draw
193145
draw_rect_tests(canvas);
194146

gm/stringart.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,75 @@ class StringArtGM : public skiagm::GM {
8383
};
8484

8585
DEF_GM( return new StringArtGM; )
86+
87+
/////////////////////////////////////////////////////////////////////////////////////////////////
88+
89+
#if 0
90+
#include "Skottie.h"
91+
92+
class SkottieGM : public skiagm::GM {
93+
enum {
94+
kWidth = 800,
95+
kHeight = 600,
96+
};
97+
98+
enum {
99+
N = 100,
100+
};
101+
skottie::Animation* fAnims[N];
102+
SkRect fRects[N];
103+
SkScalar fDur;
104+
105+
public:
106+
SkottieGM() {
107+
sk_bzero(fAnims, sizeof(fAnims));
108+
}
109+
~SkottieGM() override {
110+
for (auto anim : fAnims) {
111+
SkSafeUnref(anim);
112+
}
113+
}
114+
115+
protected:
116+
117+
SkString onShortName() override { return SkString("skottie"); }
118+
119+
SkISize onISize() override { return SkISize::Make(kWidth, kHeight); }
120+
121+
void init() {
122+
SkRandom rand;
123+
auto data = SkData::MakeFromFileName("/Users/reed/Downloads/maps_pinlet.json");
124+
// for (;;) skottie::Animation::Make((const char*)data->data(), data->size());
125+
for (int i = 0; i < N; ++i) {
126+
fAnims[i] = skottie::Animation::Make((const char*)data->data(), data->size()).release();
127+
SkScalar x = rand.nextF() * kWidth;
128+
SkScalar y = rand.nextF() * kHeight;
129+
fRects[i].setXYWH(x, y, 400, 400);
130+
}
131+
fDur = fAnims[0]->duration();
132+
}
133+
134+
void onDraw(SkCanvas* canvas) override {
135+
if (!fAnims[0]) {
136+
this->init();
137+
}
138+
canvas->drawColor(0xFFBBBBBB);
139+
for (int i = 0; i < N; ++i) {
140+
fAnims[0]->render(canvas, &fRects[i]);
141+
}
142+
}
143+
144+
bool onAnimate(const SkAnimTimer& timer) override {
145+
SkScalar time = (float)(fmod(timer.secs(), fDur) / fDur);
146+
for (auto anim : fAnims) {
147+
anim->seek(time);
148+
}
149+
return true;
150+
}
151+
152+
private:
153+
typedef GM INHERITED;
154+
};
155+
DEF_GM( return new SkottieGM; )
156+
#endif
157+

src/core/SkCubicMap.cpp

Lines changed: 86 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,75 +9,111 @@
99
#include "SkNx.h"
1010
#include "../../src/pathops/SkPathOpsCubic.h"
1111

12-
void SkCubicMap::setPts(SkPoint p1, SkPoint p2) {
13-
Sk2s s1 = Sk2s::Load(&p1) * 3;
14-
Sk2s s2 = Sk2s::Load(&p2) * 3;
15-
16-
s1 = Sk2s::Min(Sk2s::Max(s1, 0), 3);
17-
s2 = Sk2s::Min(Sk2s::Max(s2, 0), 3);
18-
19-
(Sk2s(1) + s1 - s2).store(&fCoeff[0]);
20-
(s2 - s1 - s1).store(&fCoeff[1]);
21-
s1.store(&fCoeff[2]);
12+
static float eval_poly3(float a, float b, float c, float d, float t) {
13+
return ((a * t + b) * t + c) * t + d;
14+
}
2215

23-
this->buildXTable();
16+
static float eval_poly2(float a, float b, float c, float t) {
17+
return (a * t + b) * t + c;
2418
}
2519

26-
SkPoint SkCubicMap::computeFromT(float t) const {
27-
Sk2s a = Sk2s::Load(&fCoeff[0]);
28-
Sk2s b = Sk2s::Load(&fCoeff[1]);
29-
Sk2s c = Sk2s::Load(&fCoeff[2]);
20+
static float eval_poly1(float a, float b, float t) {
21+
return a * t + b;
22+
}
3023

31-
SkPoint result;
32-
(((a * t + b) * t + c) * t).store(&result);
33-
return result;
24+
static float guess_nice_cubic_root(float A, float B, float C, float D) {
25+
return -D;
3426
}
3527

36-
float SkCubicMap::computeYFromX(float x) const {
37-
x = SkTPin<float>(x, 0, 0.99999f) * kTableCount;
38-
float ix = sk_float_floor(x);
39-
int index = (int)ix;
40-
SkASSERT((unsigned)index < SK_ARRAY_COUNT(fXTable));
41-
return this->computeFromT(fXTable[index].fT0 + fXTable[index].fDT * (x - ix)).fY;
28+
#ifdef SK_DEBUG
29+
static bool valid(float r) {
30+
return r >= 0 && r <= 1;
4231
}
32+
#endif
4333

44-
float SkCubicMap::hackYFromX(float x) const {
45-
x = SkTPin<float>(x, 0, 0.99999f) * kTableCount;
46-
float ix = sk_float_floor(x);
47-
int index = (int)ix;
48-
SkASSERT((unsigned)index < SK_ARRAY_COUNT(fXTable));
49-
return fXTable[index].fY0 + fXTable[index].fDY * (x - ix);
34+
/*
35+
* TODO: will this be faster if we algebraically compute the polynomials for the numer and denom
36+
* rather than compute them in parts?
37+
*
38+
* TODO: investigate Householder's method, to see if we can get away with even fewer
39+
* iterations (divides)
40+
*/
41+
static float solve_nice_cubic_halley(float A, float B, float C, float D) {
42+
const int MAX_ITERS = 3; // 3 is accurate to 0.0000112 (anecdotally)
43+
// 2 is accurate to 0.0188965 (anecdotally)
44+
const float A3 = 3 * A;
45+
const float B2 = B + B;
46+
47+
float t = guess_nice_cubic_root(A, B, C, D);
48+
for (int iters = 0; iters < MAX_ITERS; ++iters) {
49+
float f = eval_poly3(A, B, C, D, t); // f = At^3 + Bt^2 + Ct + D
50+
float fp = eval_poly2(A3, B2, C, t); // f' = 3At^2 + 2Bt + C
51+
float fpp = eval_poly1(A3 + A3, B2, t); // f'' = 6At + 2B
52+
53+
float numer = 2 * fp * f;
54+
float denom = 2 * fp * fp - f * fpp;
55+
float delta = numer / denom;
56+
float new_t = t - delta;
57+
SkASSERT(valid(new_t));
58+
t = new_t;
59+
}
60+
SkASSERT(valid(t));
61+
return t;
5062
}
5163

52-
static float compute_t_from_x(float A, float B, float C, float x) {
64+
#ifdef SK_DEBUG
65+
static float compute_slow(float A, float B, float C, float x) {
5366
double roots[3];
5467
SkDEBUGCODE(int count =) SkDCubic::RootsValidT(A, B, C, -x, roots);
5568
SkASSERT(count == 1);
5669
return (float)roots[0];
5770
}
5871

59-
void SkCubicMap::buildXTable() {
60-
float prevT = 0;
72+
static float max_err;
73+
#endif
6174

62-
const float dx = 1.0f / kTableCount;
63-
float x = dx;
75+
static float compute_t_from_x(float A, float B, float C, float x) {
76+
#ifdef SK_DEBUG
77+
float answer = compute_slow(A, B, C, x);
78+
#endif
79+
float answer2 = solve_nice_cubic_halley(A, B, C, -x);
80+
#ifdef SK_DEBUG
81+
float err = sk_float_abs(answer - answer2);
82+
if (err > max_err) {
83+
max_err = err;
84+
SkDebugf("max error %g\n", max_err);
85+
}
86+
#endif
87+
return answer2;
88+
}
6489

65-
fXTable[0].fT0 = 0;
66-
fXTable[0].fY0 = 0;
67-
for (int i = 1; i < kTableCount; ++i) {
68-
float t = compute_t_from_x(fCoeff[0].fX, fCoeff[1].fX, fCoeff[2].fX, x);
69-
SkASSERT(t > prevT);
90+
float SkCubicMap::computeYFromX(float x) const {
91+
SkASSERT(valid(x));
92+
float t = compute_t_from_x(fCoeff[0].fX, fCoeff[1].fX, fCoeff[2].fX, x);
93+
float a = fCoeff[0].fY;
94+
float b = fCoeff[1].fY;
95+
float c = fCoeff[2].fY;
96+
return ((a * t + b) * t + c) * t;
97+
}
7098

71-
fXTable[i - 1].fDT = t - prevT;
72-
fXTable[i].fT0 = t;
99+
void SkCubicMap::setPts(SkPoint p1, SkPoint p2) {
100+
Sk2s s1 = Sk2s::Load(&p1) * 3;
101+
Sk2s s2 = Sk2s::Load(&p2) * 3;
73102

74-
SkPoint p = this->computeFromT(t);
75-
fXTable[i - 1].fDY = p.fY - fXTable[i - 1].fY0;
76-
fXTable[i].fY0 = p.fY;
103+
s1 = Sk2s::Min(Sk2s::Max(s1, 0), 3);
104+
s2 = Sk2s::Min(Sk2s::Max(s2, 0), 3);
77105

78-
prevT = t;
79-
x += dx;
80-
}
81-
fXTable[kTableCount - 1].fDT = 1 - prevT;
82-
fXTable[kTableCount - 1].fDY = 1 - fXTable[kTableCount - 1].fY0;
106+
(Sk2s(1) + s1 - s2).store(&fCoeff[0]);
107+
(s2 - s1 - s1).store(&fCoeff[1]);
108+
s1.store(&fCoeff[2]);
109+
}
110+
111+
SkPoint SkCubicMap::computeFromT(float t) const {
112+
Sk2s a = Sk2s::Load(&fCoeff[0]);
113+
Sk2s b = Sk2s::Load(&fCoeff[1]);
114+
Sk2s c = Sk2s::Load(&fCoeff[2]);
115+
116+
SkPoint result;
117+
(((a * t + b) * t + c) * t).store(&result);
118+
return result;
83119
}

src/core/SkCubicMap.h

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,31 @@
1010

1111
#include "SkPoint.h"
1212

13+
/**
14+
* Fast evaluation of a cubic ease-in / ease-out curve. This is defined as a parametric cubic
15+
* curve inside the unit square.
16+
*
17+
* pt[0] is implicitly { 0, 0 }
18+
* pt[3] is implicitly { 1, 1 }
19+
* pts[1,2] are inside the unit square
20+
*/
1321
class SkCubicMap {
1422
public:
15-
void setPts(SkPoint p1, SkPoint p2);
16-
void setPts(float x1, float y1, float x2, float y2) {
17-
this->setPts({x1, y1}, {x2, y2});
23+
SkCubicMap() {} // must call setPts() before using
24+
25+
SkCubicMap(SkPoint p1, SkPoint p2) {
26+
this->setPts(p1, p2);
1827
}
28+
void setPts(SkPoint p1, SkPoint p2);
1929

20-
SkPoint computeFromT(float t) const;
2130
float computeYFromX(float x) const;
2231

23-
// experimental
24-
float hackYFromX(float x) const;
32+
// is this needed?
33+
SkPoint computeFromT(float t) const;
2534

2635
private:
27-
SkPoint fCoeff[4];
28-
// x->t lookup
29-
enum { kTableCount = 16 };
30-
struct Rec {
31-
float fT0;
32-
float fDT;
33-
34-
float fY0;
35-
float fDY;
36-
};
37-
Rec fXTable[kTableCount];
38-
39-
void buildXTable();
36+
SkPoint fCoeff[3];
4037
};
38+
4139
#endif
4240

0 commit comments

Comments
 (0)