[canvaskit] Handle 4x4 matrix passing ourselves.

This reduces the skm44_concat benchmark from 2us to .35us, a 5x
speedup.

SkCanvas.concat now takes a 3x2, 3x3, or 4x4 matrix and upscales
them all to 4x4. This makes concat44 redundant.

Removes redundant null checks for matrices, since freeing(0)
in WASM is fine like it is in C++.

Change-Id: I44a776ffd0babb81d8a34f9d94ae4d7831d02b55
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/281721
Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
Kevin Lubick 2020-04-06 13:52:15 -04:00
parent 16464c3232
commit c1d0898d0a
10 changed files with 187 additions and 69 deletions

View File

@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Support for DOMMatrix on all APIs that take SkMatrix (i.e. arrays or Float32Arrays of length 9).
- Support for DOMMatrix on all APIs that take SkMatrix (i.e. arrays or Float32Arrays of length 6/9/16).
### Removed
- Previously deprecated functions MakeSkDashPathEffect, MakeLinearGradientShader,
@ -22,6 +22,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
always composed of floats.
- localmatrix option for `SkShader.Lerp` and `SkShader.Blend`.
### Deprecated
- `SkCanvas.concat44` has been folded into concat (which now takes 3x2, 3x3, or 4x4 matrices). It will
be removed soon.
## [0.14.0] - 2020-03-18

View File

@ -734,7 +734,7 @@ const curves = {
function drawCubeFace(canvas, m, color) {
const trans = new CanvasKit.SkM44.translated([vSphereRadius/2, vSphereRadius/2, 0]);
canvas.concat44(CanvasKit.SkM44.multiply(trans, m, mustInvert(trans)));
canvas.concat(CanvasKit.SkM44.multiply(trans, m, mustInvert(trans)));
const znormal = front(canvas.getLocalToDevice());
if (znormal < 0) {
return; // skip faces facing backwards

View File

@ -96,33 +96,6 @@ struct OptionalMatrix : SkMatrix {
}
};
// Experimental 4x4 matrices, also represented in JS with arrays.
struct SimpleM44 {
SkScalar m0, m1, m2, m3;
SkScalar m4, m5, m6, m7;
SkScalar m8, m9, m10, m11;
SkScalar m12, m13, m14, m15;
};
SkM44 toSkM44(const SimpleM44& sm) {
SkM44 result(
sm.m0, sm.m1, sm.m2, sm.m3,
sm.m4, sm.m5, sm.m6, sm.m7,
sm.m8, sm.m9, sm.m10, sm.m11,
sm.m12, sm.m13, sm.m14, sm.m15);
return result;
}
SimpleM44 toSimpleM44(const SkM44& sm) {
SimpleM44 m {
sm.rc(0,0), sm.rc(0,1), sm.rc(0,2), sm.rc(0,3),
sm.rc(1,0), sm.rc(1,1), sm.rc(1,2), sm.rc(1,3),
sm.rc(2,0), sm.rc(2,1), sm.rc(2,2), sm.rc(2,3),
sm.rc(3,0), sm.rc(3,1), sm.rc(3,2), sm.rc(3,3),
};
return m;
}
SimpleColor4f toSimpleColor4f(const SkColor4f c) {
SimpleColor4f color {
c.fR, c.fG, c.fB, c.fA,
@ -903,8 +876,10 @@ EMSCRIPTEN_BINDINGS(Skia) {
.function("clipRect", select_overload<void (const SkRect&, SkClipOp, bool)>(&SkCanvas::clipRect))
.function("_concat", optional_override([](SkCanvas& self, uintptr_t /* SkScalar* */ mPtr) {
// See comment above for uintptr_t explanation
OptionalMatrix localMatrix(mPtr);
self.concat(localMatrix);
//TODO(skbug.com/10108): make the JS side be column major.
const SkScalar* sixteenMatrixValues = reinterpret_cast<const SkScalar*>(mPtr);
SkM44 m = SkM44::RowMajor(sixteenMatrixValues);
self.concat(m);
}))
.function("drawArc", &SkCanvas::drawArc)
// _drawAtlas takes an SkColor, unlike most private functions handling color.
@ -1043,30 +1018,47 @@ EMSCRIPTEN_BINDINGS(Skia) {
.function("_writePixels", optional_override([](SkCanvas& self, SimpleImageInfo di,
uintptr_t /* uint8_t* */ pPtr,
size_t srcRowBytes, int dstX, int dstY) {
// See comment above for uintptr_t explanation
uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
SkImageInfo dstInfo = toSkImageInfo(di);
return self.writePixels(dstInfo, pixels, srcRowBytes, dstX, dstY);
}))
// 4x4 matrix functions
.function("saveCamera", optional_override([](SkCanvas& self,
const SimpleM44& projection, const SimpleM44& camera) {
self.saveCamera(toSkM44(projection), toSkM44(camera));
.function("_saveCamera", optional_override([](SkCanvas& self,
uintptr_t /* SkScalar* */ pPtr, uintptr_t /* SkScalar* */ cPtr) {
// See comment above for uintptr_t explanation
const SkScalar* projectionMatrixValues = reinterpret_cast<const SkScalar*>(pPtr);
const SkScalar* cameraMatrixValues = reinterpret_cast<const SkScalar*>(cPtr);
SkM44 projection = SkM44::RowMajor(projectionMatrixValues);
SkM44 camera = SkM44::RowMajor(cameraMatrixValues);
self.saveCamera(projection, camera);
}))
.function("concat44", optional_override([](SkCanvas& self, const SimpleM44& m) {
self.concat(toSkM44(m));
}))
.function("getLocalToDevice", optional_override([](const SkCanvas& self)->SimpleM44 {
// Just like with getTotalMatrix, we allocate the buffer for the 16 floats to go in from
// interface.js, so it can also free them when its done.
.function("_getLocalToDevice", optional_override([](const SkCanvas& self, uintptr_t /* SkScalar* */ mPtr) {
SkScalar* sixteenMatrixValues = reinterpret_cast<SkScalar*>(mPtr);
if (!sixteenMatrixValues) {
return; // matrix cannot be null
}
SkM44 m = self.getLocalToDevice();
return toSimpleM44(m);
m.getRowMajor(sixteenMatrixValues);
}))
.function("getLocalToWorld", optional_override([](const SkCanvas& self)->SimpleM44 {
.function("_getLocalToWorld", optional_override([](const SkCanvas& self, uintptr_t /* SkScalar* */ mPtr) {
SkScalar* sixteenMatrixValues = reinterpret_cast<SkScalar*>(mPtr);
if (!sixteenMatrixValues) {
return; // matrix cannot be null
}
SkM44 m = self.experimental_getLocalToWorld();
return toSimpleM44(m);
m.getRowMajor(sixteenMatrixValues);
}))
.function("getLocalToCamera", optional_override([](const SkCanvas& self)->SimpleM44 {
.function("_getLocalToCamera", optional_override([](const SkCanvas& self, uintptr_t /* SkScalar* */ mPtr) {
SkScalar* sixteenMatrixValues = reinterpret_cast<SkScalar*>(mPtr);
if (!sixteenMatrixValues) {
return; // matrix cannot be null
}
SkM44 m = self.experimental_getLocalToCamera();
return toSimpleM44(m);
m.getRowMajor(sixteenMatrixValues);
}));
class_<SkColorFilter>("SkColorFilter")
@ -1741,12 +1733,6 @@ EMSCRIPTEN_BINDINGS(Skia) {
.field("cap", &StrokeOpts::cap)
.field("precision", &StrokeOpts::precision);
value_array<SimpleM44>("SkM44")
.element(&SimpleM44::m0).element(&SimpleM44::m1).element(&SimpleM44::m2).element(&SimpleM44::m3)
.element(&SimpleM44::m4).element(&SimpleM44::m5).element(&SimpleM44::m6).element(&SimpleM44::m7)
.element(&SimpleM44::m8).element(&SimpleM44::m9).element(&SimpleM44::m10).element(&SimpleM44::m11)
.element(&SimpleM44::m12).element(&SimpleM44::m13).element(&SimpleM44::m14).element(&SimpleM44::m15);
value_array<SimpleColor4f>("SkColor4f")
.element(&SimpleColor4f::r)
.element(&SimpleColor4f::g)

View File

@ -196,6 +196,9 @@ var CanvasKit = {
_drawAtlas: function() {},
_drawPoints: function() {},
_drawSimpleText: function() {},
_getLocalToCamera: function() {},
_getLocalToDevice: function() {},
_getLocalToWorld: function() {},
_getTotalMatrix: function() {},
_readPixels: function() {},
_writePixels: function() {},
@ -838,9 +841,13 @@ CanvasKit.SkImage.prototype.encodeToData = function() {};
CanvasKit.SkImage.prototype.makeShader = function() {};
CanvasKit.SkCanvas.prototype.concat = function() {};
CanvasKit.SkCanvas.prototype.concat44 = function() {}; // deprecated
CanvasKit.SkCanvas.prototype.drawAtlas = function() {};
CanvasKit.SkCanvas.prototype.drawPoints = function() {};
CanvasKit.SkCanvas.prototype.drawText = function() {};
CanvasKit.SkCanvas.prototype.getLocalToCamera = function() {};
CanvasKit.SkCanvas.prototype.getLocalToDevice = function() {};
CanvasKit.SkCanvas.prototype.getLocalToWorld = function() {};
CanvasKit.SkCanvas.prototype.getTotalMatrix = function() {};
/** @return {Uint8Array} */
CanvasKit.SkCanvas.prototype.readPixels = function() {};

View File

@ -284,8 +284,7 @@ function copy3x3MatrixToWasm(matr) {
CanvasKit.HEAPF32.set(defaultPerspective, 6 + mPtr / 4);
}
} else {
// Try as if it's a DOMMatrix. Reminder that DOMMatrix lists their
// column first, then row.
// Try as if it's a DOMMatrix. Reminder that DOMMatrix is column-major.
var floats = Float32Array.of(
matr.m11, matr.m21, matr.m41,
matr.m12, matr.m22, matr.m42,
@ -296,6 +295,62 @@ function copy3x3MatrixToWasm(matr) {
return mPtr;
}
function copy4x4MatrixToWasm(matr) {
if (!matr) {
return nullptr;
}
var mPtr = CanvasKit._malloc(16 * 4); // 9 matrix scalars, each at 4 bytes.
if (matr.length) {
if (matr.length !== 16 && matr.length !== 6 && matr.length !== 9) {
throw 'invalid matrix size';
}
if (matr.length === 16) {
// This should be an array or typed array.
// have to divide the pointer by 4 to "cast" it from bytes to float.
CanvasKit.HEAPF32.set(matr, mPtr / 4);
} else {
// Upscale the row-major 3x3 or 3x2 matrix into a 4x4 row-major matrix
// TODO(skbug.com/10108) This will need to change when we convert our
// JS 4x4 to be column-major.
var floats = Float32Array.of(
matr[0], matr[1], 0, matr[2],
matr[3], matr[4], 0, matr[5],
0, 0, 0, 0,
matr[6], matr[7], 0, matr[8]);
if (matr.length === 6) {
// fix perspective for the 3x2 case (from above, they will be undefined).
floats[4*3+0]=0;
floats[4*3+1]=0;
floats[4*3+3]=1;
}
CanvasKit.HEAPF32.set(floats, mPtr / 4);
}
} else {
// Try as if it's a DOMMatrix. Reminder that DOMMatrix is column-major.
// TODO(skbug.com/10108) use toFloat32Array().
var floats = Float32Array.of(
matr.m11, matr.m21, matr.m31, matr.m41,
matr.m12, matr.m22, matr.m32, matr.m42,
matr.m13, matr.m23, matr.m33, matr.m43,
matr.m14, matr.m24, matr.m34, matr.m44);
// have to divide the pointer by 4 to "cast" it from bytes to float.
CanvasKit.HEAPF32.set(floats, mPtr / 4);
}
return mPtr;
}
// copies a 4x4 matrix at the given pointer into a JS array.
function copy4x4MatrixFromWasm(matrPtr) {
// read them out into an array. TODO(kjlubick): If we change SkMatrix to be
// typedArrays, then we should return a typed array here too.
var rv = new Array(16);
for (var i = 0; i < 16; i++) {
rv[i] = CanvasKit.HEAPF32[matrPtr/4 + i]; // divide by 4 to "cast" to float.
}
CanvasKit._free(matrPtr);
return rv;
}
// Caching the Float32Arrays can save having to reallocate them
// over and over again.
var Float32ArrayCache = {};

View File

@ -231,8 +231,8 @@ CanvasKit.onRuntimeInitialized = function() {
];
}
// Functions for creating and manipulating 4x4 matrices. Accepted in place of SkM44 in canvas
// methods, for the same reasons as the 3x3 matrices above.
// Functions for creating and manipulating (row-major) 4x4 matrices. Accepted in place of
// SkM44 in canvas methods, for the same reasons as the 3x3 matrices above.
// ported from C++ code in SkM44.cpp
CanvasKit.SkM44 = {};
// Create a 4x4 identity matrix
@ -283,10 +283,10 @@ CanvasKit.onRuntimeInitialized = function() {
var m = CanvasKit.SkM44.identity();
// set each column's top three numbers
stride(s, m, 4, 0, 0);
stride(s, m, 4, 0, 0);
stride(CanvasKit.SkVector.cross(s, f), m, 4, 1, 0);
stride(CanvasKit.SkVector.mulScalar(f, -1), m, 4, 2, 0);
stride(eyeVec, m, 4, 3, 0);
stride(eyeVec, m, 4, 3, 0);
var m2 = CanvasKit.SkM44.invert(m);
if (m2 === null) {
@ -808,7 +808,7 @@ CanvasKit.onRuntimeInitialized = function() {
CanvasKit.SkImage.prototype.makeShader = function(xTileMode, yTileMode, localMatrix) {
var localMatrixPtr = copy3x3MatrixToWasm(localMatrix);
var shader = this._makeShader(xTileMode, yTileMode, localMatrixPtr);
localMatrixPtr && CanvasKit._free(localMatrixPtr);
CanvasKit._free(localMatrixPtr);
return shader;
}
@ -852,12 +852,17 @@ CanvasKit.onRuntimeInitialized = function() {
return retVal;
}
// concat takes a 3x2, a 3x3, or a 4x4 matrix and upscales it (if needed) to 4x4. This is because
// under the hood, SkCanvas uses a 4x4 matrix.
CanvasKit.SkCanvas.prototype.concat = function(matr) {
var matrPtr = copy3x3MatrixToWasm(matr);
var matrPtr = copy4x4MatrixToWasm(matr);
this._concat(matrPtr);
matrPtr && CanvasKit._free(matrPtr);
CanvasKit._free(matrPtr);
}
// Deprecated - just use concat
CanvasKit.SkCanvas.prototype.concat44 = CanvasKit.SkCanvas.prototype.concat;
// atlas is an SkImage, e.g. from CanvasKit.MakeImageFromEncoded
// srcRects and dstXforms should be CanvasKit.SkRectBuilder and CanvasKit.RSXFormBuilder
// or just arrays of floats in groups of 4.
@ -940,6 +945,31 @@ CanvasKit.onRuntimeInitialized = function() {
CanvasKit._free(ptr);
}
// getLocalToCamera returns a 4x4 matrix.
CanvasKit.SkCanvas.prototype.getLocalToCamera = function() {
var matrPtr = CanvasKit._malloc(16 * 4); // allocate space for the matrix
// _getLocalToCamera will copy the values into the pointer.
this._getLocalToCamera(matrPtr);
return copy4x4MatrixFromWasm(matrPtr);
}
// getLocalToDevice returns a 4x4 matrix.
CanvasKit.SkCanvas.prototype.getLocalToDevice = function() {
var matrPtr = CanvasKit._malloc(16 * 4); // allocate space for the matrix
// _getLocalToDevice will copy the values into the pointer.
this._getLocalToDevice(matrPtr);
return copy4x4MatrixFromWasm(matrPtr);
}
// getLocalToWorld returns a 4x4 matrix.
CanvasKit.SkCanvas.prototype.getLocalToWorld = function() {
var matrPtr = CanvasKit._malloc(16 * 4); // allocate space for the matrix
// _getLocalToWorld will copy the values into the pointer.
this._getLocalToWorld(matrPtr);
return copy4x4MatrixFromWasm(matrPtr);
}
// getTotalMatrix returns the current matrix as a 3x3 matrix.
CanvasKit.SkCanvas.prototype.getTotalMatrix = function() {
var matrPtr = CanvasKit._malloc(9 * 4); // allocate space for the matrix
// _getTotalMatrix will copy the values into the pointer.
@ -982,6 +1012,14 @@ CanvasKit.onRuntimeInitialized = function() {
return pixels;
}
CanvasKit.SkCanvas.prototype.saveCamera = function(projection, camera) {
var pPtr = copy4x4MatrixToWasm(projection);
var cPtr = copy4x4MatrixToWasm(camera);
this._saveCamera(pPtr, cPtr);
CanvasKit._free(pPtr)
CanvasKit._free(cPtr);
}
// pixels is a TypedArray. No matter the input size, it will be treated as
// a Uint8Array (essentially, a byte array).
CanvasKit.SkCanvas.prototype.writePixels = function(pixels, srcWidth, srcHeight,
@ -1025,7 +1063,7 @@ CanvasKit.onRuntimeInitialized = function() {
var matrPtr = copy3x3MatrixToWasm(matr);
var imgF = CanvasKit.SkImageFilter._MakeMatrixTransform(matrPtr, filterQuality, input);
matrPtr && CanvasKit._free(matrPtr);
CanvasKit._free(matrPtr);
return imgF;
}
@ -1099,7 +1137,7 @@ CanvasKit.onRuntimeInitialized = function() {
var lgs = CanvasKit._MakeLinearGradientShader(start, end, colorPtr, posPtr,
colors.length, mode, flags, localMatrixPtr);
localMatrixPtr && CanvasKit._free(localMatrixPtr);
CanvasKit._free(localMatrixPtr);
CanvasKit._free(colorPtr);
CanvasKit._free(posPtr);
return lgs;
@ -1114,7 +1152,7 @@ CanvasKit.onRuntimeInitialized = function() {
var rgs = CanvasKit._MakeRadialGradientShader(center, radius, colorPtr, posPtr,
colors.length, mode, flags, localMatrixPtr);
localMatrixPtr && CanvasKit._free(localMatrixPtr);
CanvasKit._free(localMatrixPtr);
CanvasKit._free(colorPtr);
CanvasKit._free(posPtr);
return rgs;
@ -1133,7 +1171,7 @@ CanvasKit.onRuntimeInitialized = function() {
startAngle, endAngle, flags,
localMatrixPtr);
localMatrixPtr && CanvasKit._free(localMatrixPtr);
CanvasKit._free(localMatrixPtr);
CanvasKit._free(colorPtr);
CanvasKit._free(posPtr);
return sgs;
@ -1150,7 +1188,7 @@ CanvasKit.onRuntimeInitialized = function() {
start, startRadius, end, endRadius,
colorPtr, posPtr, colors.length, mode, flags, localMatrixPtr);
localMatrixPtr && CanvasKit._free(localMatrixPtr);
CanvasKit._free(localMatrixPtr);
CanvasKit._free(colorPtr);
CanvasKit._free(posPtr);
return rgs;

View File

@ -137,7 +137,7 @@ describe('SkM44 (4x4 matrix)', () => {
}
function test(ctx) {
ctx.canvas.concat44(matr);
ctx.canvas.concat(matr);
}
function teardown(ctx) {

View File

@ -7,7 +7,7 @@ CanvasKit._extraInitializations.push(function() {
// Our array has 4 bytes per float, so be sure to account for that before
// sending it over the wire.
var rts = this._makeShader(fptr, floats.length * 4, !!isOpaque, localMatrixPtr);
localMatrixPtr && CanvasKit._free(localMatrixPtr);
CanvasKit._free(localMatrixPtr);
return rts;
}
@ -27,7 +27,7 @@ CanvasKit._extraInitializations.push(function() {
// sending it over the wire.
var rts = this._makeShaderWithChildren(fptr, floats.length * 4, !!isOpaque, childrenPointers,
barePointers.length, localMatrixPtr);
localMatrixPtr && CanvasKit._free(localMatrixPtr);
CanvasKit._free(localMatrixPtr);
return rts;
}
});

View File

@ -591,8 +591,8 @@ describe('Canvas Behavior', () => {
matr = canvas.getLocalToCamera();
expect(matr).toEqual(CanvasKit.SkM44.identity());
canvas.concat44(CanvasKit.SkM44.rotated([0, 1, 0], Math.PI/4));
canvas.concat44(CanvasKit.SkM44.rotated([1, 0, 1], Math.PI/8));
canvas.concat(CanvasKit.SkM44.rotated([0, 1, 0], Math.PI/4));
canvas.concat(CanvasKit.SkM44.rotated([1, 0, 1], Math.PI/8));
const expected = CanvasKit.SkM44.multiply(
CanvasKit.SkM44.rotated([0, 1, 0], Math.PI/4),
@ -621,7 +621,7 @@ describe('Canvas Behavior', () => {
CanvasKit.SkM44.rotated([0, 0, 1], Math.PI/16),
CanvasKit.SkM44.translated([-CANVAS_WIDTH/2, 0, 0]),
);
canvas.concat44(turn);
canvas.concat(turn);
// Draw some stripes to help the eye detect the turn
const stripeWidth = 10;

View File

@ -606,6 +606,35 @@ describe('Core canvas behavior', () => {
paint.delete();
shader.delete();
});
const radiansToDegrees = (rad) => {
return (rad / Math.PI) * 180;
}
// this should draw the same as concat_with4x4_canvas
gm('concat_dommatrix', (canvas) => {
const path = starPath(CanvasKit, CANVAS_WIDTH/2, CANVAS_HEIGHT/2);
const paint = new CanvasKit.SkPaint();
paint.setAntiAlias(true);
canvas.clear(CanvasKit.WHITE);
canvas.concat(new DOMMatrix().translate(CANVAS_WIDTH/2, 0, 0));
canvas.concat(new DOMMatrix().rotateAxisAngle(1, 0, 0, radiansToDegrees(Math.PI/3)));
canvas.concat(new DOMMatrix().rotateAxisAngle(0, 1, 0, radiansToDegrees(Math.PI/4)));
canvas.concat(new DOMMatrix().rotateAxisAngle(0, 0, 1, radiansToDegrees(Math.PI/16)));
canvas.concat(new DOMMatrix().translate(-CANVAS_WIDTH/2, 0, 0));
// Draw some stripes to help the eye detect the turn
const stripeWidth = 10;
paint.setColor(CanvasKit.BLACK);
for (let i = 0; i < CANVAS_WIDTH; i += 2*stripeWidth) {
canvas.drawRect(CanvasKit.LTRBRect(i, 0, i + stripeWidth, CANVAS_HEIGHT), paint);
}
paint.setColor(CanvasKit.YELLOW);
canvas.drawPath(path, paint);
paint.delete();
path.delete();
});
}); // end describe('DOMMatrix support')
});