Flutter Engine
The Flutter Engine
M44Test.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2020 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
10#include "include/core/SkPath.h"
11#include "include/core/SkRect.h"
14#include "src/base/SkRandom.h"
16#include "tests/Test.h"
17
18static bool eq(const SkM44& a, const SkM44& b, float tol) {
19 float fa[16], fb[16];
20 a.getColMajor(fa);
21 b.getColMajor(fb);
22 for (int i = 0; i < 16; ++i) {
23 if (!SkScalarNearlyEqual(fa[i], fb[i], tol)) {
24 return false;
25 }
26 }
27 return true;
28}
29
31 SkM44 m, im;
32
33 REPORTER_ASSERT(reporter, SkM44(1, 0, 0, 0,
34 0, 1, 0, 0,
35 0, 0, 1, 0,
36 0, 0, 0, 1) == m);
38 REPORTER_ASSERT(reporter, m.invert(&im));
40
41 m.setTranslate(3, 4, 2);
42 REPORTER_ASSERT(reporter, SkM44(1, 0, 0, 3,
43 0, 1, 0, 4,
44 0, 0, 1, 2,
45 0, 0, 0, 1) == m);
46
47 const float f[] = { 1, 0, 0, 2, 3, 1, 2, 5, 0, 5, 3, 0, 0, 1, 0, 2 };
49 REPORTER_ASSERT(reporter, SkM44(f[0], f[4], f[ 8], f[12],
50 f[1], f[5], f[ 9], f[13],
51 f[2], f[6], f[10], f[14],
52 f[3], f[7], f[11], f[15]) == m);
53
54 {
55 SkM44 t = m.transpose();
57 REPORTER_ASSERT(reporter, t.rc(1,0) == m.rc(0,1));
58 SkM44 tt = t.transpose();
60 }
61
63 REPORTER_ASSERT(reporter, SkM44(f[ 0], f[ 1], f[ 2], f[ 3],
64 f[ 4], f[ 5], f[ 6], f[ 7],
65 f[ 8], f[ 9], f[10], f[14],
66 f[12], f[13], f[14], f[15]) == m);
67
68 REPORTER_ASSERT(reporter, m.invert(&im));
69
70 m = m * im;
71 // m should be identity now, but our calc is not perfect...
72 REPORTER_ASSERT(reporter, eq(SkM44(), m, 0.0000005f));
74}
75
77 SkV3 a = {1, 2, 3},
78 b = {1, 2, 2};
79
80 REPORTER_ASSERT(reporter, a.lengthSquared() == 1 + 4 + 9);
81 REPORTER_ASSERT(reporter, b.length() == 3);
82 REPORTER_ASSERT(reporter, a.dot(b) == 1 + 4 + 6);
83 REPORTER_ASSERT(reporter, b.dot(a) == 1 + 4 + 6);
84 REPORTER_ASSERT(reporter, (a.cross(b) == SkV3{-2, 1, 0}));
85 REPORTER_ASSERT(reporter, (b.cross(a) == SkV3{ 2, -1, 0}));
86
87 SkM44 m = {
88 2, 0, 0, 3,
89 0, 1, 0, 5,
90 0, 0, 3, 1,
91 0, 0, 0, 1
92 };
93
94 SkV3 c = m * a;
95 REPORTER_ASSERT(reporter, (c == SkV3{2, 2, 9}));
96 SkV4 d = m.map(4, 3, 2, 1);
97 REPORTER_ASSERT(reporter, (d == SkV4{11, 8, 7, 1}));
98}
99
101 SkM44 m( 1, 2, 3, 4,
102 5, 6, 7, 8,
103 9, 10, 11, 12,
104 13, 14, 15, 16);
105
106 SkV4 r0 = m.row(0),
107 r1 = m.row(1),
108 r2 = m.row(2),
109 r3 = m.row(3);
110
111 REPORTER_ASSERT(reporter, (r0 == SkV4{ 1, 2, 3, 4}));
112 REPORTER_ASSERT(reporter, (r1 == SkV4{ 5, 6, 7, 8}));
113 REPORTER_ASSERT(reporter, (r2 == SkV4{ 9, 10, 11, 12}));
114 REPORTER_ASSERT(reporter, (r3 == SkV4{13, 14, 15, 16}));
115
116 REPORTER_ASSERT(reporter, SkM44::Rows(r0, r1, r2, r3) == m);
117
118 SkV4 c0 = m.col(0),
119 c1 = m.col(1),
120 c2 = m.col(2),
121 c3 = m.col(3);
122
123 REPORTER_ASSERT(reporter, (c0 == SkV4{1, 5, 9, 13}));
124 REPORTER_ASSERT(reporter, (c1 == SkV4{2, 6, 10, 14}));
125 REPORTER_ASSERT(reporter, (c2 == SkV4{3, 7, 11, 15}));
126 REPORTER_ASSERT(reporter, (c3 == SkV4{4, 8, 12, 16}));
127
128 REPORTER_ASSERT(reporter, SkM44::Cols(c0, c1, c2, c3) == m);
129
130 // implement matrix * vector using column vectors
131 SkV4 v = {1, 2, 3, 4};
132 SkV4 v1 = m * v;
133 SkV4 v2 = c0 * v.x + c1 * v.y + c2 * v.z + c3 * v.w;
135
136 REPORTER_ASSERT(reporter, (c0 + r0 == SkV4{c0.x+r0.x, c0.y+r0.y, c0.z+r0.z, c0.w+r0.w}));
137 REPORTER_ASSERT(reporter, (c0 - r0 == SkV4{c0.x-r0.x, c0.y-r0.y, c0.z-r0.z, c0.w-r0.w}));
138 REPORTER_ASSERT(reporter, (c0 * r0 == SkV4{c0.x*r0.x, c0.y*r0.y, c0.z*r0.z, c0.w*r0.w}));
139}
140
141DEF_TEST(M44_rotate, reporter) {
142 const SkV3 x = {1, 0, 0},
143 y = {0, 1, 0},
144 z = {0, 0, 1};
145
146 // We have radians version of setRotateAbout methods, but even with our best approx
147 // for PI, sin(SK_ScalarPI) != 0, so to make the comparisons in the unittest clear,
148 // I'm using the variants that explicitly take the sin,cos values.
149
150 struct {
151 SkScalar sinAngle, cosAngle;
152 SkV3 aboutAxis;
153 SkV3 expectedX, expectedY, expectedZ;
154 } recs[] = {
155 { 0, 1, x, x, y, z}, // angle = 0
156 { 0, 1, y, x, y, z}, // angle = 0
157 { 0, 1, z, x, y, z}, // angle = 0
158
159 { 0,-1, x, x,-y,-z}, // angle = 180
160 { 0,-1, y, -x, y,-z}, // angle = 180
161 { 0,-1, z, -x,-y, z}, // angle = 180
162
163 // Skia coordinate system is right-handed
164
165 { 1, 0, x, x, z,-y}, // angle = 90
166 { 1, 0, y, -z, y, x}, // angle = 90
167 { 1, 0, z, y,-x, z}, // angle = 90
168
169 {-1, 0, x, x,-z, y}, // angle = -90
170 {-1, 0, y, z, y,-x}, // angle = -90
171 {-1, 0, z, -y, x, z}, // angle = -90
172 };
173
174 for (const auto& r : recs) {
176 m.setRotateUnitSinCos(r.aboutAxis, r.sinAngle, r.cosAngle);
177
178 auto mx = m * x;
179 auto my = m * y;
180 auto mz = m * z;
181 REPORTER_ASSERT(reporter, mx == r.expectedX);
182 REPORTER_ASSERT(reporter, my == r.expectedY);
183 REPORTER_ASSERT(reporter, mz == r.expectedZ);
184
185 // flipping the axis-of-rotation should flip the results
186 mx = m * -x;
187 my = m * -y;
188 mz = m * -z;
189 REPORTER_ASSERT(reporter, mx == -r.expectedX);
190 REPORTER_ASSERT(reporter, my == -r.expectedY);
191 REPORTER_ASSERT(reporter, mz == -r.expectedZ);
192 }
193}
194
195DEF_TEST(M44_rectToRect, reporter) {
196 SkV2 dstScales[] = {
197 {1.f, 1.f}, // no aspect ratio change, nor up/down scaling
198 {0.25f, 0.5f}, // aspect ratio narrows, downscale x and y
199 {0.5f, 0.25f}, // aspect ratio widens, downscale x and y
200 {0.5f, 0.5f}, // no aspect ratio change, downscale x and y
201 {2.f, 3.f}, // aspect ratio narrows, upscale x and y
202 {3.f, 2.f}, // aspect ratio widens, upscale x and y
203 {2.f, 2.f}, // no aspect ratio change, upscale x and y
204 {0.5f, 2.f}, // aspect ratio narrows, downscale x and upscale y
205 {2.f, 0.5f} // aspect ratio widens, upscale x and downscale y
206 };
207
208 auto map2d = [&](const SkM44& m, SkV2 p) {
209 SkV4 mapped = m.map(p.x, p.y, 0.f, 1.f);
210 REPORTER_ASSERT(reporter, mapped.z == 0.f);
211 REPORTER_ASSERT(reporter, mapped.w == 1.f);
212 return SkV2{mapped.x, mapped.y};
213 };
214 auto assertNearlyEqual = [&](float actual, float expected) {
216 "Expected %g == %g", actual, expected);
217 };
218 auto assertEdges = [&](float actualLow, float actualHigh,
219 float expectedLow, float expectedHigh) {
220 SkASSERT(expectedLow < expectedHigh);
221 REPORTER_ASSERT(reporter, actualLow < actualHigh,
222 "Expected %g < %g", actualLow, actualHigh);
223
224 assertNearlyEqual(actualLow, expectedLow);
225 assertNearlyEqual(actualHigh, expectedHigh);
226 };
227
228 SkRandom rand;
229 for (const auto& r : dstScales) {
230 SkRect src = SkRect::MakeXYWH(rand.nextRangeF(-10.f, 10.f),
231 rand.nextRangeF(-10.f, 10.f),
232 rand.nextRangeF(1.f, 10.f),
233 rand.nextRangeF(1.f, 10.f));
234 SkRect dst = SkRect::MakeXYWH(rand.nextRangeF(-10.f, 10.f),
235 rand.nextRangeF(-10.f, 10.f),
236 r.x * src.width(),
237 r.y * src.height());
238
240
241 // Regardless of the factory, center of src maps to center of dst
242 SkV2 center = map2d(m, {src.centerX(), src.centerY()});
243 assertNearlyEqual(center.x, dst.centerX());
244 assertNearlyEqual(center.y, dst.centerY());
245
246 // Map the four corners of src and validate against expected edge mapping
247 SkV2 tl = map2d(m, {src.fLeft, src.fTop});
248 SkV2 tr = map2d(m, {src.fRight, src.fTop});
249 SkV2 br = map2d(m, {src.fRight, src.fBottom});
250 SkV2 bl = map2d(m, {src.fLeft, src.fBottom});
251
252 assertEdges(tl.x, tr.x, dst.fLeft, dst.fRight);
253 assertEdges(bl.x, br.x, dst.fLeft, dst.fRight);
254 assertEdges(tl.y, bl.y, dst.fTop, dst.fBottom);
255 assertEdges(tr.y, br.y, dst.fTop, dst.fBottom);
256 }
257}
258
259DEF_TEST(M44_mapRect, reporter) {
260 auto assertRectsNearlyEqual = [&](const SkRect& actual, const SkRect& expected,
261 const SkRect& e) {
262 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(actual.fLeft, expected.fLeft, e.fLeft),
263 "Expected %g == %g", actual.fLeft, expected.fLeft);
264 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(actual.fTop, expected.fTop, e.fTop),
265 "Expected %g == %g", actual.fTop, expected.fTop);
266 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(actual.fRight, expected.fRight, e.fRight),
267 "Expected %g == %g", actual.fRight, expected.fRight);
268 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(actual.fBottom, expected.fBottom, e.fBottom),
269 "Expected %g == %g", actual.fBottom, expected.fBottom);
270 };
271 auto assertMapRect = [&](const SkM44& m, const SkRect& src, const SkRect* expected) {
272 SkRect epsilon = {1e-5f, 1e-5f, 1e-5f, 1e-5f};
273
275 REPORTER_ASSERT(reporter, !actual.isEmpty());
276
277 if (expected) {
278 assertRectsNearlyEqual(actual, *expected, epsilon);
279 }
280
281 SkV4 corners[4] = {{src.fLeft, src.fTop, 0.f, 1.f},
282 {src.fRight, src.fTop, 0.f, 1.f},
283 {src.fRight, src.fBottom, 0.f, 1.f},
284 {src.fLeft, src.fBottom, 0.f, 1.f}};
285 bool leftFound = false;
286 bool topFound = false;
287 bool rightFound = false;
288 bool bottomFound = false;
289 bool clipped = false;
290 for (int i = 0; i < 4; ++i) {
291 SkV4 mapped = m * corners[i];
292 if (mapped.w > 0.f) {
293 // Should be contained in actual and might be on one or two of actual's edges
294 float x = mapped.x / mapped.w;
295 float y = mapped.y / mapped.w;
296
297 // Can't use SkRect::contains() since it treats right and bottom edges as exclusive
298 REPORTER_ASSERT(reporter, actual.fLeft <= x && x <= actual.fRight,
299 "Expected %g contained in [%g, %g]",
300 x, actual.fLeft, actual.fRight);
301 REPORTER_ASSERT(reporter, actual.fTop <= y && y <= actual.fBottom,
302 "Expected %g contained in [%g, %g]",
303 y, actual.fTop, actual.fBottom);
304
305 leftFound |= SkScalarNearlyEqual(x, actual.fLeft);
306 topFound |= SkScalarNearlyEqual(y, actual.fTop);
307 rightFound |= SkScalarNearlyEqual(x, actual.fRight);
308 bottomFound |= SkScalarNearlyEqual(y, actual.fBottom);
309 } else {
310 // The mapped point would be clipped so the clipped mapped bounds don't necessarily
311 // contain it
312 clipped = true;
313 }
314 }
315
316 if (clipped) {
317 // At least one of the mapped corners should have contributed to the rect
318 REPORTER_ASSERT(reporter, leftFound || topFound || rightFound || bottomFound);
319 // For any edge that came from a clipped corner, increase its error tolerance relative
320 // to what SkPath::ApplyPerspectiveClip calculates.
321 // TODO(michaelludwig): skbug.com/12335 required updating the w epsilon distance which
322 // greatly increased noise for coords projecting to infinity. They aren't "wrong", since
323 // the intent was clearly to pick a big number that's definitely offscreen, but
324 // MapRect should have a more robust solution than a fixed w > epsilon and when it does,
325 // these expectations for clipped points should be more accurate.
326 if (!leftFound) { epsilon.fLeft = .01f * actual.fLeft; }
327 if (!topFound) { epsilon.fTop = .01f * actual.fTop; }
328 if (!rightFound) { epsilon.fRight = .01f * actual.fRight; }
329 if (!bottomFound) { epsilon.fBottom = .01f * actual.fBottom; }
330 } else {
331 // The mapped corners should have contributed to all four edges of the returned rect
332 REPORTER_ASSERT(reporter, leftFound && topFound && rightFound && bottomFound);
333 }
334
336 path.transform(m.asM33(), SkApplyPerspectiveClip::kYes);
337 assertRectsNearlyEqual(actual, path.getBounds(), epsilon);
338 };
339
340 // src chosen arbitrarily
341 const SkRect src = SkRect::MakeLTRB(4.83f, -0.48f, 5.53f, 30.68f);
342
343 // Identity maps src to src
344 assertMapRect(SkM44(), src, &src);
345 // Scale+Translate just offsets src
346 SkRect st = SkRect::MakeLTRB(10.f + 2.f * src.fLeft, 8.f + 4.f * src.fTop,
347 10.f + 2.f * src.fRight, 8.f + 4.f * src.fBottom);
348 assertMapRect(SkM44::Scale(2.f, 4.f).postTranslate(10.f, 8.f), src, &st);
349 // Rotate 45 degrees about center
350 assertMapRect(SkM44::Rotate({0.f, 0.f, 1.f}, SK_ScalarPI / 4.f)
351 .preTranslate(-src.centerX(), -src.centerY())
352 .postTranslate(src.centerX(), src.centerY()),
353 src, nullptr);
354
355 // Perspective matrix where src does not need to be clipped w > 0
356 SkM44 p = SkM44::Perspective(0.01f, 10.f, SK_ScalarPI / 3.f);
357 p.preTranslate(0.f, 5.f, -0.1f);
358 p.preConcat(SkM44::Rotate({0.f, 1.f, 0.f}, 0.008f /* radians */));
359 assertMapRect(p, src, nullptr);
360
361 // Perspective matrix where src *does* need to be clipped w > 0
362 p.setIdentity();
363 p.setRow(3, {-.2f, -.6f, 0.f, 8.f});
364 assertMapRect(p, src, nullptr);
365}
366
367DEF_TEST(M44_mapRect_skbug12335, r) {
368 // Stripped down test case from skbug.com/12335. Essentially, the corners of this rect would
369 // map to homogoneous coords with very small w's (below the old value of kW0PlaneDistance) and
370 // so they would be clipped "behind" the plane, resulting in an empty mapped rect. Coordinates
371 // with positive that wouldn't overflow when divided by w should still be included in the mapped
372 // rectangle.
373 SkRect rect = SkRect::MakeLTRB(0, 0, 319, 620);
374 SkM44 m(SkMatrix::MakeAll( 0.000152695269f, 0.00000000f, -6.53848401e-05f,
375 -1.75697533e-05f, 0.000157153074f, -1.10847975e-06f,
376 -6.00415362e-08f, 0.00000000f, 0.000169880834f));
378 REPORTER_ASSERT(r, !out.isEmpty());
379}
reporter
Definition: FontMgrTest.cpp:39
static bool eq(const SkM44 &a, const SkM44 &b, float tol)
Definition: M44Test.cpp:18
DEF_TEST(M44, reporter)
Definition: M44Test.cpp:30
#define SkASSERT(cond)
Definition: SkAssert.h:116
@ kYes
Do pre-clip the geometry before applying the (perspective) matrix.
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition: SkScalar.h:107
#define SK_ScalarPI
Definition: SkScalar.h:21
#define REPORTER_ASSERT(r, cond,...)
Definition: Test.h:286
static SkScalar center(float pos0, float pos1)
Vec2Value v2
Definition: SkM44.h:150
static SkM44 Rows(const SkV4 &r0, const SkV4 &r1, const SkV4 &r2, const SkV4 &r3)
Definition: SkM44.h:195
static SkM44 RowMajor(const SkScalar r[16])
Definition: SkM44.h:212
SkM44 & postTranslate(SkScalar x, SkScalar y, SkScalar z=0)
Definition: SkM44.cpp:100
static SkM44 ColMajor(const SkScalar c[16])
Definition: SkM44.h:218
SkScalar rc(int r, int c) const
Definition: SkM44.h:261
static SkM44 Rotate(SkV3 axis, SkScalar radians)
Definition: SkM44.h:239
SkM44 transpose() const
Definition: SkM44.cpp:256
static SkM44 RectToRect(const SkRect &src, const SkRect &dst)
Definition: SkM44.cpp:304
@ kNaN_Constructor
Definition: SkM44.h:172
static SkM44 Perspective(float near, float far, float angle)
Definition: SkM44.cpp:343
static SkM44 Scale(SkScalar x, SkScalar y, SkScalar z=1)
Definition: SkM44.h:232
static SkM44 Cols(const SkV4 &c0, const SkV4 &c1, const SkV4 &c2, const SkV4 &c3)
Definition: SkM44.h:203
static SkRect MapRect(const SkM44 &m, const SkRect &r)
Definition: SkM44.cpp:216
static SkMatrix MakeAll(SkScalar scaleX, SkScalar skewX, SkScalar transX, SkScalar skewY, SkScalar scaleY, SkScalar transY, SkScalar pers0, SkScalar pers1, SkScalar pers2)
Definition: SkMatrix.h:179
Definition: SkPath.h:59
static SkPath Rect(const SkRect &, SkPathDirection=SkPathDirection::kCW, unsigned startIndex=0)
Definition: SkPath.cpp:3586
float nextRangeF(float min, float max)
Definition: SkRandom.h:64
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition: main.cc:19
float SkScalar
Definition: extension.cpp:12
static bool b
struct MyStruct a[10]
double y
double x
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
dst
Definition: cp.py:12
SkScalar fBottom
larger y-axis bounds
Definition: extension.cpp:17
SkScalar fLeft
smaller x-axis bounds
Definition: extension.cpp:14
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition: SkRect.h:659
SkScalar fRight
larger x-axis bounds
Definition: extension.cpp:16
bool isEmpty() const
Definition: SkRect.h:693
static constexpr SkRect MakeLTRB(float l, float t, float r, float b)
Definition: SkRect.h:646
SkScalar fTop
smaller y-axis bounds
Definition: extension.cpp:15
Definition: SkM44.h:19
float x
Definition: SkM44.h:20
float y
Definition: SkM44.h:20
Definition: SkM44.h:56
Definition: SkM44.h:98
float w
Definition: SkM44.h:99
float y
Definition: SkM44.h:99
float x
Definition: SkM44.h:99
float z
Definition: SkM44.h:99