Skip to content

Commit

Permalink
use all 16 matrix entries in Canvas.transform() to enable 3D matrix c…
Browse files Browse the repository at this point in the history
…oncatenation (flutter#28856)
  • Loading branch information
flar authored Sep 28, 2021
1 parent 42afe29 commit a84b2a7
Show file tree
Hide file tree
Showing 14 changed files with 576 additions and 247 deletions.
122 changes: 68 additions & 54 deletions flow/display_list.cc
Original file line number Diff line number Diff line change
Expand Up @@ -306,53 +306,50 @@ struct SkewOp final : DLOp {
};
// 4 byte header + 24 byte payload uses 28 bytes but is rounded up to 32 bytes
// (4 bytes unused)
struct Transform2x3Op final : DLOp {
static const auto kType = DisplayListOpType::kTransform2x3;

Transform2x3Op(SkScalar mxx,
SkScalar mxy,
SkScalar mxt,
SkScalar myx,
SkScalar myy,
SkScalar myt)
struct Transform2DAffineOp final : DLOp {
static const auto kType = DisplayListOpType::kTransform2DAffine;

// clang-format off
Transform2DAffineOp(SkScalar mxx, SkScalar mxy, SkScalar mxt,
SkScalar myx, SkScalar myy, SkScalar myt)
: mxx(mxx), mxy(mxy), mxt(mxt), myx(myx), myy(myy), myt(myt) {}
// clang-format on

const SkScalar mxx, mxy, mxt;
const SkScalar myx, myy, myt;

void dispatch(Dispatcher& dispatcher) const {
dispatcher.transform2x3(mxx, mxy, mxt, myx, myy, myt);
dispatcher.transform2DAffine(mxx, mxy, mxt, //
myx, myy, myt);
}
};
// 4 byte header + 36 byte payload packs evenly into 40 bytes
struct Transform3x3Op final : DLOp {
static const auto kType = DisplayListOpType::kTransform3x3;

Transform3x3Op(SkScalar mxx,
SkScalar mxy,
SkScalar mxt,
SkScalar myx,
SkScalar myy,
SkScalar myt,
SkScalar px,
SkScalar py,
SkScalar pt)
: mxx(mxx),
mxy(mxy),
mxt(mxt),
myx(myx),
myy(myy),
myt(myt),
px(px),
py(py),
pt(pt) {}

const SkScalar mxx, mxy, mxt;
const SkScalar myx, myy, myt;
const SkScalar px, py, pt;
// 4 byte header + 64 byte payload uses 68 bytes which is rounded up to 72 bytes
// (4 bytes unused)
struct TransformFullPerspectiveOp final : DLOp {
static const auto kType = DisplayListOpType::kTransformFullPerspective;

// clang-format off
TransformFullPerspectiveOp(
SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt,
SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt)
: mxx(mxx), mxy(mxy), mxz(mxz), mxt(mxt),
myx(myx), myy(myy), myz(myz), myt(myt),
mzx(mzx), mzy(mzy), mzz(mzz), mzt(mzt),
mwx(mwx), mwy(mwy), mwz(mwz), mwt(mwt) {}
// clang-format on

const SkScalar mxx, mxy, mxz, mxt;
const SkScalar myx, myy, myz, myt;
const SkScalar mzx, mzy, mzz, mzt;
const SkScalar mwx, mwy, mwz, mwt;

void dispatch(Dispatcher& dispatcher) const {
dispatcher.transform3x3(mxx, mxy, mxt, myx, myy, myt, px, py, pt);
dispatcher.transformFullPerspective(mxx, mxy, mxz, mxt, //
myx, myy, myz, myt, //
mzx, mzy, mzz, mzt, //
mwx, mwy, mwz, mwt);
}
};

Expand Down Expand Up @@ -1183,26 +1180,43 @@ void DisplayListBuilder::rotate(SkScalar degrees) {
void DisplayListBuilder::skew(SkScalar sx, SkScalar sy) {
Push<SkewOp>(0, 1, sx, sy);
}
void DisplayListBuilder::transform2x3(SkScalar mxx,
SkScalar mxy,
SkScalar mxt,
SkScalar myx,
SkScalar myy,
SkScalar myt) {
Push<Transform2x3Op>(0, 1, mxx, mxy, mxt, myx, myy, myt);

// clang-format off

// 2x3 2D affine subset of a 4x4 transform in row major order
void DisplayListBuilder::transform2DAffine(
SkScalar mxx, SkScalar mxy, SkScalar mxt,
SkScalar myx, SkScalar myy, SkScalar myt) {
if (!(mxx == 1 && mxy == 0 && mxt == 0 &&
myx == 0 && myy == 1 && myt == 0)) {
Push<Transform2DAffineOp>(0, 1,
mxx, mxy, mxt,
myx, myy, myt);
}
}
void DisplayListBuilder::transform3x3(SkScalar mxx,
SkScalar mxy,
SkScalar mxt,
SkScalar myx,
SkScalar myy,
SkScalar myt,
SkScalar px,
SkScalar py,
SkScalar pt) {
Push<Transform3x3Op>(0, 1, mxx, mxy, mxt, myx, myy, myt, px, py, pt);
// full 4x4 transform in row major order
void DisplayListBuilder::transformFullPerspective(
SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt,
SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) {
if ( mxz == 0 &&
myz == 0 &&
mzx == 0 && mzy == 0 && mzz == 1 && mzt == 0 &&
mwx == 0 && mwy == 0 && mwz == 0 && mwt == 1) {
transform2DAffine(mxx, mxy, mxt,
myx, myy, myt);
} else {
Push<TransformFullPerspectiveOp>(0, 1,
mxx, mxy, mxz, mxt,
myx, myy, myz, myt,
mzx, mzy, mzz, mzt,
mwx, mwy, mwz, mwt);
}
}

// clang-format on

void DisplayListBuilder::clipRect(const SkRect& rect,
SkClipOp clip_op,
bool is_aa) {
Expand Down
152 changes: 115 additions & 37 deletions flow/display_list.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ namespace flutter {
V(Scale) \
V(Rotate) \
V(Skew) \
V(Transform2x3) \
V(Transform3x3) \
V(Transform2DAffine) \
V(TransformFullPerspective) \
\
V(ClipIntersectRect) \
V(ClipIntersectRRect) \
Expand Down Expand Up @@ -271,26 +271,105 @@ class Dispatcher {
virtual void scale(SkScalar sx, SkScalar sy) = 0;
virtual void rotate(SkScalar degrees) = 0;
virtual void skew(SkScalar sx, SkScalar sy) = 0;
// |transform2x3| is equivalent to concatenating an SkMatrix
// with only the upper 2x3 affine 2D parameters and the rest
// of the transformation parameters set to their identity values.
virtual void transform2x3(SkScalar mxx,
SkScalar mxy,
SkScalar mxt,
SkScalar myx,
SkScalar myy,
SkScalar myt) = 0;
// |transform3x3| is equivalent to concatenating an SkMatrix
// with no promises about the non-affine-2D parameters.
virtual void transform3x3(SkScalar mxx,
SkScalar mxy,
SkScalar mxt,
SkScalar myx,
SkScalar myy,
SkScalar myt,
SkScalar px,
SkScalar py,
SkScalar pt) = 0;

// The transform methods all assume the following math for transforming
// an arbitrary 3D homogenous point (x, y, z, w).
// All coordinates in the rendering methods (and SkPoint and SkRect objects)
// represent a simplified coordinate (x, y, 0, 1).
// x' = x * mxx + y * mxy + z * mxz + w * mxt
// y' = x * myx + y * myy + z * myz + w * myt
// z' = x * mzx + y * mzy + z * mzz + w * mzt
// w' = x * mwx + y * mwy + z * mwz + w * mwt
// Note that for non-homogenous 2D coordinates, the last column in those
// equations is multiplied by 1 and is simply adding a translation and
// so is referred to with the final letter "t" here instead of "w".
//
// In 2D coordinates, z=0 and so the 3rd column always evaluates to 0.
//
// In non-perspective transforms, the 4th row has identity values
// and so w` = w. (i.e. w'=1 for 2d points transformed by a matrix
// with identity values in the last row).
//
// In affine 2D transforms, the 3rd and 4th row and 3rd column are all
// identity values and so z` = z (which is 0 for 2D coordinates) and
// the x` and y` equations don't see a contribution from a z coordinate
// and the w' ends up being the same as the w from the source coordinate
// (which is 1 for a 2D coordinate).
//
// Here is the math for transforming a 2D source coordinate and
// looking for the destination 2D coordinate (for a surface that
// does not have a Z buffer or track the Z coordinates in any way)
// Source coordinate = (x, y, 0, 1)
// x' = x * mxx + y * mxy + 0 * mxz + 1 * mxt
// y' = x * myx + y * myy + 0 * myz + 1 * myt
// z' = x * mzx + y * mzy + 0 * mzz + 1 * mzt
// w' = x * mwx + y * mwy + 0 * mwz + 1 * mwt
// Destination coordinate does not need z', so this reduces to:
// x' = x * mxx + y * mxy + mxt
// y' = x * myx + y * myy + myt
// w' = x * mwx + y * mwy + mwt
// Destination coordinate is (x' / w', y' / w', 0, 1)
// Note that these are the matrix values in SkMatrix which means that
// an SkMatrix contains enough data to transform a 2D source coordinate
// and place it on a 2D surface, but is otherwise not enough to continue
// concatenating with further matrices as its missing elements will not
// be able to model the interplay between the rows and columns that
// happens during a full 4x4 by 4x4 matrix multiplication.
//
// If the transform doesn't have any perspective parts (the last
// row is identity - 0, 0, 0, 1), then this further simplifies to:
// x' = x * mxx + y * mxy + mxt
// y' = x * myx + y * myy + myt
// w' = x * 0 + y * 0 + 1 = 1
//
// In short, while the full 4x4 set of matrix entries needs to be
// maintained for accumulating transform mutations accurately, the
// actual end work of transforming a single 2D coordinate (or, in
// the case of bounds transformations, 4 of them) can be accomplished
// with the 9 values from transform3x3 or SkMatrix.
//
// The only need for the w value here is for homogenous coordinates
// which only come up if the perspective elements (the 4th row) of
// a transform are non-identity. Otherwise the w always ends up
// being 1 in all calculations. If the matrix has perspecitve elements
// then the final transformed coordinates will have a w that is not 1
// and the actual coordinates are determined by dividing out that w
// factor resulting in a real-world point expressed as (x, y, z, 1).
//
// Because of the predominance of 2D affine transforms the
// 2x3 subset of the 4x4 transform matrix is special cased with
// its own dispatch method that omits the last 2 rows and the 3rd
// column. Even though a 3x3 subset is enough for transforming
// leaf coordinates as shown above, no method is provided for
// representing a 3x3 transform in the DisplayList since if there
// is perspective involved then a full 4x4 matrix should be provided
// for accurate concatenations. Providing a 3x3 method or record
// in the stream would encourage developers to prematurely subset
// a full perspective matrix.

// clang-format off

// |transform2DAffine| is equivalent to concatenating the internal
// 4x4 transform with the following row major transform matrix:
// [ mxx mxy 0 mxt ]
// [ myx myy 0 myt ]
// [ 0 0 1 0 ]
// [ 0 0 0 1 ]
virtual void transform2DAffine(SkScalar mxx, SkScalar mxy, SkScalar mxt,
SkScalar myx, SkScalar myy, SkScalar myt) = 0;
// |transformFullPerspective| is equivalent to concatenating the internal
// 4x4 transform with the following row major transform matrix:
// [ mxx mxy mxz mxt ]
// [ myx myy myz myt ]
// [ mzx mzy mzz mzt ]
// [ mwx mwy mwz mwt ]
virtual void transformFullPerspective(
SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt,
SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) = 0;

// clang-format on

virtual void clipRect(const SkRect& rect, SkClipOp clip_op, bool is_aa) = 0;
virtual void clipRRect(const SkRRect& rrect,
Expand Down Expand Up @@ -402,21 +481,20 @@ class DisplayListBuilder final : public virtual Dispatcher, public SkRefCnt {
void scale(SkScalar sx, SkScalar sy) override;
void rotate(SkScalar degrees) override;
void skew(SkScalar sx, SkScalar sy) override;
void transform2x3(SkScalar mxx,
SkScalar mxy,
SkScalar mxt,
SkScalar myx,
SkScalar myy,
SkScalar myt) override;
void transform3x3(SkScalar mxx,
SkScalar mxy,
SkScalar mxt,
SkScalar myx,
SkScalar myy,
SkScalar myt,
SkScalar px,
SkScalar py,
SkScalar pt) override;

// clang-format off

// 2x3 2D affine subset of a 4x4 transform in row major order
void transform2DAffine(SkScalar mxx, SkScalar mxy, SkScalar mxt,
SkScalar myx, SkScalar myy, SkScalar myt) override;
// full 4x4 transform in row major order
void transformFullPerspective(
SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt,
SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) override;

// clang-format on

void clipRect(const SkRect& rect, SkClipOp clip_op, bool isAA) override;
void clipRRect(const SkRRect& rrect, SkClipOp clip_op, bool isAA) override;
Expand Down
60 changes: 33 additions & 27 deletions flow/display_list_canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,30 @@ void DisplayListCanvasDispatcher::rotate(SkScalar degrees) {
void DisplayListCanvasDispatcher::skew(SkScalar sx, SkScalar sy) {
canvas_->skew(sx, sy);
}
void DisplayListCanvasDispatcher::transform2x3(SkScalar mxx,
SkScalar mxy,
SkScalar mxt,
SkScalar myx,
SkScalar myy,
SkScalar myt) {
canvas_->concat(SkMatrix::MakeAll(mxx, mxy, mxt, myx, myy, myt, 0, 0, 1));
}
void DisplayListCanvasDispatcher::transform3x3(SkScalar mxx,
SkScalar mxy,
SkScalar mxt,
SkScalar myx,
SkScalar myy,
SkScalar myt,
SkScalar px,
SkScalar py,
SkScalar pt) {
canvas_->concat(SkMatrix::MakeAll(mxx, mxy, mxt, myx, myy, myt, px, py, pt));
}
// clang-format off
// 2x3 2D affine subset of a 4x4 transform in row major order
void DisplayListCanvasDispatcher::transform2DAffine(
SkScalar mxx, SkScalar mxy, SkScalar mxt,
SkScalar myx, SkScalar myy, SkScalar myt) {
// Internally concat(SkMatrix) gets redirected to concat(SkM44)
// so we just jump directly to the SkM44 version
canvas_->concat(SkM44(mxx, mxy, 0, mxt,
myx, myy, 0, myt,
0, 0, 1, 0,
0, 0, 0, 1));
}
// full 4x4 transform in row major order
void DisplayListCanvasDispatcher::transformFullPerspective(
SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt,
SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) {
canvas_->concat(SkM44(mxx, mxy, mxz, mxt,
myx, myy, myz, myt,
mzx, mzy, mzz, mzt,
mwx, mwy, mwz, mwt));
}
// clang-format on

void DisplayListCanvasDispatcher::clipRect(const SkRect& rect,
SkClipOp clip_op,
Expand Down Expand Up @@ -201,15 +206,16 @@ sk_sp<DisplayList> DisplayListCanvasRecorder::Build() {
return display_list;
}

// clang-format off
void DisplayListCanvasRecorder::didConcat44(const SkM44& m44) {
SkMatrix m = m44.asM33();
if (m.hasPerspective()) {
builder_->transform3x3(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7],
m[8]);
} else {
builder_->transform2x3(m[0], m[1], m[2], m[3], m[4], m[5]);
}
}
// transform4x4 takes a full 4x4 transform in row major order
builder_->transformFullPerspective(
m44.rc(0, 0), m44.rc(0, 1), m44.rc(0, 2), m44.rc(0, 3),
m44.rc(1, 0), m44.rc(1, 1), m44.rc(1, 2), m44.rc(1, 3),
m44.rc(2, 0), m44.rc(2, 1), m44.rc(2, 2), m44.rc(2, 3),
m44.rc(3, 0), m44.rc(3, 1), m44.rc(3, 2), m44.rc(3, 3));
}
// clang-format on
void DisplayListCanvasRecorder::didTranslate(SkScalar tx, SkScalar ty) {
builder_->translate(tx, ty);
}
Expand Down
Loading

0 comments on commit a84b2a7

Please sign in to comment.