Introduce CanvasFrontend
Add a front-end to convert between "stateful" canvas and "semi-statelss" CanvasOps. Handles tracking save/restores, clips, and transforms, and injects the transform on all draw ops. Test: CanvasFrontend unit tests Change-Id: Ifbcd14600690f10717047c50954ab54b4fc27aee
This commit is contained in:
@@ -429,6 +429,7 @@ cc_defaults {
|
||||
whole_static_libs: ["libskia"],
|
||||
|
||||
srcs: [
|
||||
"canvas/CanvasFrontend.cpp",
|
||||
"canvas/CanvasOpBuffer.cpp",
|
||||
"canvas/CanvasOpRasterizer.cpp",
|
||||
"pipeline/skia/SkiaDisplayList.cpp",
|
||||
@@ -607,6 +608,7 @@ cc_test {
|
||||
"tests/unit/CacheManagerTests.cpp",
|
||||
"tests/unit/CanvasContextTests.cpp",
|
||||
"tests/unit/CanvasOpTests.cpp",
|
||||
"tests/unit/CanvasFrontendTests.cpp",
|
||||
"tests/unit/CommonPoolTests.cpp",
|
||||
"tests/unit/DamageAccumulatorTests.cpp",
|
||||
"tests/unit/DeferredLayerUpdaterTests.cpp",
|
||||
|
||||
36
libs/hwui/SaveFlags.h
Normal file
36
libs/hwui/SaveFlags.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
// TODO: Move this to an enum class
|
||||
namespace android::SaveFlags {
|
||||
|
||||
// These must match the corresponding Canvas API constants.
|
||||
enum {
|
||||
Matrix = 0x01,
|
||||
Clip = 0x02,
|
||||
HasAlphaLayer = 0x04,
|
||||
ClipToLayer = 0x10,
|
||||
|
||||
// Helper constant
|
||||
MatrixClip = Matrix | Clip,
|
||||
};
|
||||
typedef uint32_t Flags;
|
||||
|
||||
} // namespace android::SaveFlags
|
||||
117
libs/hwui/canvas/CanvasFrontend.cpp
Normal file
117
libs/hwui/canvas/CanvasFrontend.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "CanvasFrontend.h"
|
||||
#include "CanvasOps.h"
|
||||
#include "CanvasOpBuffer.h"
|
||||
|
||||
namespace android::uirenderer {
|
||||
|
||||
CanvasStateHelper::CanvasStateHelper(int width, int height) {
|
||||
mInitialBounds = SkIRect::MakeWH(width, height);
|
||||
mSaveStack.emplace_back();
|
||||
mClipStack.emplace_back().setRect(mInitialBounds);
|
||||
mTransformStack.emplace_back();
|
||||
mCurrentClipIndex = 0;
|
||||
mCurrentTransformIndex = 0;
|
||||
}
|
||||
|
||||
bool CanvasStateHelper::internalSave(SaveEntry saveEntry) {
|
||||
mSaveStack.push_back(saveEntry);
|
||||
if (saveEntry.matrix) {
|
||||
// We need to push before accessing transform() to ensure the reference doesn't move
|
||||
// across vector resizes
|
||||
mTransformStack.emplace_back() = transform();
|
||||
mCurrentTransformIndex += 1;
|
||||
}
|
||||
if (saveEntry.clip) {
|
||||
// We need to push before accessing clip() to ensure the reference doesn't move
|
||||
// across vector resizes
|
||||
mClipStack.emplace_back() = clip();
|
||||
mCurrentClipIndex += 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Assert that the cast from SkClipOp to SkRegion::Op is valid
|
||||
static_assert(static_cast<int>(SkClipOp::kDifference) == SkRegion::Op::kDifference_Op);
|
||||
static_assert(static_cast<int>(SkClipOp::kIntersect) == SkRegion::Op::kIntersect_Op);
|
||||
static_assert(static_cast<int>(SkClipOp::kUnion_deprecated) == SkRegion::Op::kUnion_Op);
|
||||
static_assert(static_cast<int>(SkClipOp::kXOR_deprecated) == SkRegion::Op::kXOR_Op);
|
||||
static_assert(static_cast<int>(SkClipOp::kReverseDifference_deprecated) == SkRegion::Op::kReverseDifference_Op);
|
||||
static_assert(static_cast<int>(SkClipOp::kReplace_deprecated) == SkRegion::Op::kReplace_Op);
|
||||
|
||||
void CanvasStateHelper::internalClipRect(const SkRect& rect, SkClipOp op) {
|
||||
clip().opRect(rect, transform(), mInitialBounds, (SkRegion::Op)op, false);
|
||||
}
|
||||
|
||||
void CanvasStateHelper::internalClipPath(const SkPath& path, SkClipOp op) {
|
||||
clip().opPath(path, transform(), mInitialBounds, (SkRegion::Op)op, true);
|
||||
}
|
||||
|
||||
bool CanvasStateHelper::internalRestore() {
|
||||
// Prevent underflows
|
||||
if (saveCount() <= 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SaveEntry entry = mSaveStack[mSaveStack.size() - 1];
|
||||
mSaveStack.pop_back();
|
||||
bool needsRestorePropagation = entry.layer;
|
||||
if (entry.matrix) {
|
||||
mTransformStack.pop_back();
|
||||
mCurrentTransformIndex -= 1;
|
||||
}
|
||||
if (entry.clip) {
|
||||
// We need to push before accessing clip() to ensure the reference doesn't move
|
||||
// across vector resizes
|
||||
mClipStack.pop_back();
|
||||
mCurrentClipIndex -= 1;
|
||||
needsRestorePropagation = true;
|
||||
}
|
||||
return needsRestorePropagation;
|
||||
}
|
||||
|
||||
SkRect CanvasStateHelper::getClipBounds() const {
|
||||
SkIRect ibounds = clip().getBounds();
|
||||
|
||||
if (ibounds.isEmpty()) {
|
||||
return SkRect::MakeEmpty();
|
||||
}
|
||||
|
||||
SkMatrix inverse;
|
||||
// if we can't invert the CTM, we can't return local clip bounds
|
||||
if (!transform().invert(&inverse)) {
|
||||
return SkRect::MakeEmpty();
|
||||
}
|
||||
|
||||
SkRect ret = SkRect::MakeEmpty();
|
||||
inverse.mapRect(&ret, SkRect::Make(ibounds));
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CanvasStateHelper::quickRejectRect(float left, float top, float right, float bottom) const {
|
||||
// TODO: Implement
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CanvasStateHelper::quickRejectPath(const SkPath& path) const {
|
||||
// TODO: Implement
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace android::uirenderer
|
||||
200
libs/hwui/canvas/CanvasFrontend.h
Normal file
200
libs/hwui/canvas/CanvasFrontend.h
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// TODO: Can we get the dependencies scoped down more?
|
||||
#include "CanvasOps.h"
|
||||
#include "CanvasOpBuffer.h"
|
||||
#include <SaveFlags.h>
|
||||
|
||||
#include <SkRasterClip.h>
|
||||
#include <ui/FatVector.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace android::uirenderer {
|
||||
|
||||
// Exists to avoid forcing all this common logic into the templated class
|
||||
class CanvasStateHelper {
|
||||
protected:
|
||||
CanvasStateHelper(int width, int height);
|
||||
~CanvasStateHelper() = default;
|
||||
|
||||
struct SaveEntry {
|
||||
bool clip : 1 = false;
|
||||
bool matrix : 1 = false;
|
||||
bool layer : 1 = false;
|
||||
};
|
||||
|
||||
constexpr SaveEntry saveEntryForLayer() {
|
||||
return {
|
||||
.clip = true,
|
||||
.matrix = true,
|
||||
.layer = true,
|
||||
};
|
||||
}
|
||||
|
||||
constexpr SaveEntry flagsToSaveEntry(SaveFlags::Flags flags) {
|
||||
return SaveEntry {
|
||||
.clip = static_cast<bool>(flags & SaveFlags::Clip),
|
||||
.matrix = static_cast<bool>(flags & SaveFlags::Matrix),
|
||||
.layer = false
|
||||
};
|
||||
}
|
||||
|
||||
bool internalSave(SaveEntry saveEntry);
|
||||
bool internalSave(SaveFlags::Flags flags) {
|
||||
return internalSave(flagsToSaveEntry(flags));
|
||||
}
|
||||
void internalSaveLayer(const SkCanvas::SaveLayerRec& layerRec) {
|
||||
internalSave({
|
||||
.clip = true,
|
||||
.matrix = true,
|
||||
.layer = true
|
||||
});
|
||||
internalClipRect(*layerRec.fBounds, SkClipOp::kIntersect);
|
||||
}
|
||||
|
||||
bool internalRestore();
|
||||
|
||||
void internalClipRect(const SkRect& rect, SkClipOp op);
|
||||
void internalClipPath(const SkPath& path, SkClipOp op);
|
||||
|
||||
SkIRect mInitialBounds;
|
||||
FatVector<SaveEntry, 6> mSaveStack;
|
||||
FatVector<SkMatrix, 6> mTransformStack;
|
||||
FatVector<SkConservativeClip, 6> mClipStack;
|
||||
|
||||
size_t mCurrentTransformIndex;
|
||||
size_t mCurrentClipIndex;
|
||||
|
||||
const SkConservativeClip& clip() const {
|
||||
return mClipStack[mCurrentClipIndex];
|
||||
}
|
||||
|
||||
SkConservativeClip& clip() {
|
||||
return mClipStack[mCurrentClipIndex];
|
||||
}
|
||||
|
||||
public:
|
||||
int saveCount() const { return mSaveStack.size(); }
|
||||
|
||||
SkRect getClipBounds() const;
|
||||
bool quickRejectRect(float left, float top, float right, float bottom) const;
|
||||
bool quickRejectPath(const SkPath& path) const;
|
||||
|
||||
const SkMatrix& transform() const {
|
||||
return mTransformStack[mCurrentTransformIndex];
|
||||
}
|
||||
|
||||
SkMatrix& transform() {
|
||||
return mTransformStack[mCurrentTransformIndex];
|
||||
}
|
||||
|
||||
// For compat with existing HWUI Canvas interface
|
||||
void getMatrix(SkMatrix* outMatrix) const {
|
||||
*outMatrix = transform();
|
||||
}
|
||||
|
||||
void setMatrix(const SkMatrix& matrix) {
|
||||
transform() = matrix;
|
||||
}
|
||||
|
||||
void concat(const SkMatrix& matrix) {
|
||||
transform().preConcat(matrix);
|
||||
}
|
||||
|
||||
void rotate(float degrees) {
|
||||
SkMatrix m;
|
||||
m.setRotate(degrees);
|
||||
concat(m);
|
||||
}
|
||||
|
||||
void scale(float sx, float sy) {
|
||||
SkMatrix m;
|
||||
m.setScale(sx, sy);
|
||||
concat(m);
|
||||
}
|
||||
|
||||
void skew(float sx, float sy) {
|
||||
SkMatrix m;
|
||||
m.setSkew(sx, sy);
|
||||
concat(m);
|
||||
}
|
||||
|
||||
void translate(float dx, float dy) {
|
||||
transform().preTranslate(dx, dy);
|
||||
}
|
||||
};
|
||||
|
||||
// Front-end canvas that handles queries, up-front state, and produces CanvasOp<> output downstream
|
||||
template <typename CanvasOpReceiver>
|
||||
class CanvasFrontend final : public CanvasStateHelper {
|
||||
public:
|
||||
template<class... Args>
|
||||
CanvasFrontend(int width, int height, Args&&... args) : CanvasStateHelper(width, height),
|
||||
mReceiver(std::forward<Args>(args)...) { }
|
||||
~CanvasFrontend() = default;
|
||||
|
||||
void save(SaveFlags::Flags flags = SaveFlags::MatrixClip) {
|
||||
if (internalSave(flagsToSaveEntry(flags))) {
|
||||
submit<CanvasOpType::Save>({});
|
||||
}
|
||||
}
|
||||
|
||||
void restore() {
|
||||
if (internalRestore()) {
|
||||
submit<CanvasOpType::Restore>({});
|
||||
}
|
||||
}
|
||||
|
||||
template <CanvasOpType T>
|
||||
void draw(CanvasOp<T>&& op) {
|
||||
// The front-end requires going through certain front-doors, which these aren't.
|
||||
static_assert(T != CanvasOpType::Save, "Must use CanvasFrontend::save() call instead");
|
||||
static_assert(T != CanvasOpType::Restore, "Must use CanvasFrontend::restore() call instead");
|
||||
|
||||
if constexpr (T == CanvasOpType::SaveLayer) {
|
||||
internalSaveLayer(op.saveLayerRec);
|
||||
}
|
||||
if constexpr (T == CanvasOpType::SaveBehind) {
|
||||
// Don't use internalSaveLayer as this doesn't apply clipping, it's a "regular" save
|
||||
// But we do want to flag it as a layer, such that restore is Definitely Required
|
||||
internalSave(saveEntryForLayer());
|
||||
}
|
||||
if constexpr (T == CanvasOpType::ClipRect) {
|
||||
internalClipRect(op.rect, op.op);
|
||||
}
|
||||
if constexpr (T == CanvasOpType::ClipPath) {
|
||||
internalClipPath(op.path, op.op);
|
||||
}
|
||||
|
||||
submit(std::move(op));
|
||||
}
|
||||
|
||||
const CanvasOpReceiver& receiver() const { return mReceiver; }
|
||||
|
||||
private:
|
||||
CanvasOpReceiver mReceiver;
|
||||
|
||||
template <CanvasOpType T>
|
||||
void submit(CanvasOp<T>&& op) {
|
||||
mReceiver.push_container(CanvasOpContainer(std::move(op), transform()));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace android::uirenderer
|
||||
22
libs/hwui/canvas/CanvasOpRecorder.cpp
Normal file
22
libs/hwui/canvas/CanvasOpRecorder.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "CanvasOpRecorder.h"
|
||||
|
||||
#include "CanvasOpBuffer.h"
|
||||
#include "CanvasOps.h"
|
||||
|
||||
namespace android::uirenderer {} // namespace android::uirenderer
|
||||
38
libs/hwui/canvas/CanvasOpRecorder.h
Normal file
38
libs/hwui/canvas/CanvasOpRecorder.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "hwui/Canvas.h"
|
||||
#include "CanvasOpBuffer.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace android::uirenderer {
|
||||
|
||||
// Interop with existing HWUI Canvas
|
||||
class CanvasOpRecorder final : /* todo: public Canvas */ {
|
||||
public:
|
||||
// Transform ops
|
||||
private:
|
||||
struct SaveEntry {
|
||||
|
||||
};
|
||||
|
||||
std::vector<SaveEntry> mSaveStack;
|
||||
};
|
||||
|
||||
} // namespace android::uirenderer
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <cutils/compiler.h>
|
||||
#include <utils/Functor.h>
|
||||
#include <SaveFlags.h>
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include "Properties.h"
|
||||
@@ -57,22 +58,6 @@ class SkiaDisplayList;
|
||||
using DisplayList = skiapipeline::SkiaDisplayList;
|
||||
}
|
||||
|
||||
namespace SaveFlags {
|
||||
|
||||
// These must match the corresponding Canvas API constants.
|
||||
enum {
|
||||
Matrix = 0x01,
|
||||
Clip = 0x02,
|
||||
HasAlphaLayer = 0x04,
|
||||
ClipToLayer = 0x10,
|
||||
|
||||
// Helper constant
|
||||
MatrixClip = Matrix | Clip,
|
||||
};
|
||||
typedef uint32_t Flags;
|
||||
|
||||
} // namespace SaveFlags
|
||||
|
||||
namespace uirenderer {
|
||||
namespace VectorDrawable {
|
||||
class Tree;
|
||||
|
||||
213
libs/hwui/tests/unit/CanvasFrontendTests.cpp
Normal file
213
libs/hwui/tests/unit/CanvasFrontendTests.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <canvas/CanvasFrontend.h>
|
||||
#include <canvas/CanvasOpBuffer.h>
|
||||
#include <canvas/CanvasOps.h>
|
||||
#include <canvas/CanvasOpRasterizer.h>
|
||||
|
||||
#include <tests/common/CallCountingCanvas.h>
|
||||
|
||||
#include "SkPictureRecorder.h"
|
||||
#include "SkColor.h"
|
||||
#include "SkLatticeIter.h"
|
||||
#include "pipeline/skia/AnimatedDrawables.h"
|
||||
#include <SkNoDrawCanvas.h>
|
||||
|
||||
using namespace android;
|
||||
using namespace android::uirenderer;
|
||||
using namespace android::uirenderer::test;
|
||||
|
||||
class CanvasOpCountingReceiver {
|
||||
public:
|
||||
template <CanvasOpType T>
|
||||
void push_container(CanvasOpContainer<T>&& op) {
|
||||
mOpCounts[static_cast<size_t>(T)] += 1;
|
||||
}
|
||||
|
||||
int operator[](CanvasOpType op) const {
|
||||
return mOpCounts[static_cast<size_t>(op)];
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<int, static_cast<size_t>(CanvasOpType::COUNT)> mOpCounts;
|
||||
};
|
||||
|
||||
TEST(CanvasFrontend, saveCount) {
|
||||
SkNoDrawCanvas skiaCanvas(100, 100);
|
||||
CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100);
|
||||
const auto& receiver = opCanvas.receiver();
|
||||
|
||||
EXPECT_EQ(1, skiaCanvas.getSaveCount());
|
||||
EXPECT_EQ(1, opCanvas.saveCount());
|
||||
|
||||
skiaCanvas.save();
|
||||
opCanvas.save(SaveFlags::MatrixClip);
|
||||
EXPECT_EQ(2, skiaCanvas.getSaveCount());
|
||||
EXPECT_EQ(2, opCanvas.saveCount());
|
||||
|
||||
skiaCanvas.restore();
|
||||
opCanvas.restore();
|
||||
EXPECT_EQ(1, skiaCanvas.getSaveCount());
|
||||
EXPECT_EQ(1, opCanvas.saveCount());
|
||||
|
||||
skiaCanvas.restore();
|
||||
opCanvas.restore();
|
||||
EXPECT_EQ(1, skiaCanvas.getSaveCount());
|
||||
EXPECT_EQ(1, opCanvas.saveCount());
|
||||
|
||||
EXPECT_EQ(1, receiver[CanvasOpType::Save]);
|
||||
EXPECT_EQ(1, receiver[CanvasOpType::Restore]);
|
||||
}
|
||||
|
||||
TEST(CanvasFrontend, transform) {
|
||||
SkNoDrawCanvas skiaCanvas(100, 100);
|
||||
CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100);
|
||||
|
||||
skiaCanvas.translate(10, 10);
|
||||
opCanvas.translate(10, 10);
|
||||
EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
|
||||
|
||||
{
|
||||
skiaCanvas.save();
|
||||
opCanvas.save(SaveFlags::Matrix);
|
||||
skiaCanvas.scale(2.0f, 1.125f);
|
||||
opCanvas.scale(2.0f, 1.125f);
|
||||
|
||||
EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
|
||||
skiaCanvas.restore();
|
||||
opCanvas.restore();
|
||||
}
|
||||
|
||||
EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
|
||||
|
||||
{
|
||||
skiaCanvas.save();
|
||||
opCanvas.save(SaveFlags::Matrix);
|
||||
skiaCanvas.rotate(90.f);
|
||||
opCanvas.rotate(90.f);
|
||||
|
||||
EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
|
||||
|
||||
{
|
||||
skiaCanvas.save();
|
||||
opCanvas.save(SaveFlags::Matrix);
|
||||
skiaCanvas.skew(5.0f, 2.25f);
|
||||
opCanvas.skew(5.0f, 2.25f);
|
||||
|
||||
EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
|
||||
skiaCanvas.restore();
|
||||
opCanvas.restore();
|
||||
}
|
||||
|
||||
skiaCanvas.restore();
|
||||
opCanvas.restore();
|
||||
}
|
||||
|
||||
EXPECT_EQ(skiaCanvas.getTotalMatrix(), opCanvas.transform());
|
||||
}
|
||||
|
||||
TEST(CanvasFrontend, drawOpTransform) {
|
||||
CanvasFrontend<CanvasOpBuffer> opCanvas(100, 100);
|
||||
const auto& receiver = opCanvas.receiver();
|
||||
|
||||
auto makeDrawRect = [] {
|
||||
return CanvasOp<CanvasOpType::DrawRect>{
|
||||
.rect = SkRect::MakeWH(50, 50),
|
||||
.paint = SkPaint(SkColors::kBlack),
|
||||
};
|
||||
};
|
||||
|
||||
opCanvas.draw(makeDrawRect());
|
||||
|
||||
opCanvas.translate(10, 10);
|
||||
opCanvas.draw(makeDrawRect());
|
||||
|
||||
opCanvas.save();
|
||||
opCanvas.scale(2.0f, 4.0f);
|
||||
opCanvas.draw(makeDrawRect());
|
||||
opCanvas.restore();
|
||||
|
||||
opCanvas.save();
|
||||
opCanvas.translate(20, 15);
|
||||
opCanvas.draw(makeDrawRect());
|
||||
opCanvas.save();
|
||||
opCanvas.rotate(90.f);
|
||||
opCanvas.draw(makeDrawRect());
|
||||
opCanvas.restore();
|
||||
opCanvas.restore();
|
||||
|
||||
// Validate the results
|
||||
std::vector<SkMatrix> transforms;
|
||||
transforms.reserve(5);
|
||||
receiver.for_each([&](auto op) {
|
||||
// Filter for the DrawRect calls; ignore the save & restores
|
||||
// (TODO: Add a filtered for_each variant to OpBuffer?)
|
||||
if (op->type() == CanvasOpType::DrawRect) {
|
||||
transforms.push_back(op->transform());
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_EQ(transforms.size(), 5);
|
||||
|
||||
{
|
||||
// First result should be identity
|
||||
const auto& result = transforms[0];
|
||||
EXPECT_EQ(SkMatrix::kIdentity_Mask, result.getType());
|
||||
EXPECT_EQ(SkMatrix::I(), result);
|
||||
}
|
||||
|
||||
{
|
||||
// Should be translate 10, 10
|
||||
const auto& result = transforms[1];
|
||||
EXPECT_EQ(SkMatrix::kTranslate_Mask, result.getType());
|
||||
SkMatrix m;
|
||||
m.setTranslate(10, 10);
|
||||
EXPECT_EQ(m, result);
|
||||
}
|
||||
|
||||
{
|
||||
// Should be translate 10, 10 + scale 2, 4
|
||||
const auto& result = transforms[2];
|
||||
EXPECT_EQ(SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask, result.getType());
|
||||
SkMatrix m;
|
||||
m.setTranslate(10, 10);
|
||||
m.preScale(2.0f, 4.0f);
|
||||
EXPECT_EQ(m, result);
|
||||
}
|
||||
|
||||
{
|
||||
// Should be translate 10, 10 + translate 20, 15
|
||||
const auto& result = transforms[3];
|
||||
EXPECT_EQ(SkMatrix::kTranslate_Mask, result.getType());
|
||||
SkMatrix m;
|
||||
m.setTranslate(30, 25);
|
||||
EXPECT_EQ(m, result);
|
||||
}
|
||||
|
||||
{
|
||||
// Should be translate 10, 10 + translate 20, 15 + rotate 90
|
||||
const auto& result = transforms[4];
|
||||
EXPECT_EQ(SkMatrix::kTranslate_Mask | SkMatrix::kAffine_Mask | SkMatrix::kScale_Mask,
|
||||
result.getType());
|
||||
SkMatrix m;
|
||||
m.setTranslate(30, 25);
|
||||
m.preRotate(90.f);
|
||||
EXPECT_EQ(m, result);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <canvas/CanvasFrontend.h>
|
||||
#include <canvas/CanvasOpBuffer.h>
|
||||
#include <canvas/CanvasOps.h>
|
||||
#include <canvas/CanvasOpRasterizer.h>
|
||||
@@ -26,6 +27,7 @@
|
||||
#include "SkColor.h"
|
||||
#include "SkLatticeIter.h"
|
||||
#include "pipeline/skia/AnimatedDrawables.h"
|
||||
#include <SkNoDrawCanvas.h>
|
||||
|
||||
using namespace android;
|
||||
using namespace android::uirenderer;
|
||||
@@ -78,6 +80,21 @@ struct MockOp<MockTypes::Lifecycle> {
|
||||
|
||||
using MockBuffer = OpBuffer<MockTypes, MockOpContainer>;
|
||||
|
||||
class CanvasOpCountingReceiver {
|
||||
public:
|
||||
template <CanvasOpType T>
|
||||
void push_container(CanvasOpContainer<T>&& op) {
|
||||
mOpCounts[static_cast<size_t>(T)] += 1;
|
||||
}
|
||||
|
||||
int operator[](CanvasOpType op) const {
|
||||
return mOpCounts[static_cast<size_t>(op)];
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<int, static_cast<size_t>(CanvasOpType::COUNT)> mOpCounts;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
static int countItems(const T& t) {
|
||||
int count = 0;
|
||||
@@ -614,4 +631,35 @@ TEST(CanvasOp, immediateRendering) {
|
||||
rasterizer.draw(op);
|
||||
EXPECT_EQ(1, canvas->drawRectCount);
|
||||
EXPECT_EQ(1, canvas->sumTotalDrawCalls());
|
||||
}
|
||||
|
||||
TEST(CanvasOp, frontendSaveCount) {
|
||||
SkNoDrawCanvas skiaCanvas(100, 100);
|
||||
CanvasFrontend<CanvasOpCountingReceiver> opCanvas(100, 100);
|
||||
const auto& receiver = opCanvas.receiver();
|
||||
|
||||
EXPECT_EQ(1, skiaCanvas.getSaveCount());
|
||||
EXPECT_EQ(1, opCanvas.saveCount());
|
||||
|
||||
skiaCanvas.save();
|
||||
opCanvas.save(SaveFlags::MatrixClip);
|
||||
EXPECT_EQ(2, skiaCanvas.getSaveCount());
|
||||
EXPECT_EQ(2, opCanvas.saveCount());
|
||||
|
||||
skiaCanvas.restore();
|
||||
opCanvas.restore();
|
||||
EXPECT_EQ(1, skiaCanvas.getSaveCount());
|
||||
EXPECT_EQ(1, opCanvas.saveCount());
|
||||
|
||||
skiaCanvas.restore();
|
||||
opCanvas.restore();
|
||||
EXPECT_EQ(1, skiaCanvas.getSaveCount());
|
||||
EXPECT_EQ(1, opCanvas.saveCount());
|
||||
|
||||
EXPECT_EQ(1, receiver[Op::Save]);
|
||||
EXPECT_EQ(1, receiver[Op::Restore]);
|
||||
}
|
||||
|
||||
TEST(CanvasOp, frontendTransform) {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user