Implement SkiaRecordingCanvas, RenderNodeDrawable and other drawables.
Implement SkiaRecordingCanvas, RenderNodeDrawable, GLFunctorDrawable,
LayerDrawable, StartReorderBarrierDrawable, EndReorderBarrierDrawable.
Move AnimatedRoundRect and AnimatedCircle in a separate file.
All Skia pipeline files are moved in hwui/pipeline/skia folder.
Add unit tests for RenderNodeDrawable, StartReorderBarrierDrawable,
EndReorderBarrierDrawable and SkiaRecordingCanvas.
Test: I tested manually on 6P devices and did run the unit tests.
Change-Id: If2a347bd1fc4689953822294ce5bf98c7f3f57c7
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 4fe866f..06eb829 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -18,6 +18,12 @@
hwui/MinikinUtils.cpp \
hwui/PaintImpl.cpp \
hwui/Typeface.cpp \
+ pipeline/skia/GLFunctorDrawable.cpp \
+ pipeline/skia/LayerDrawable.cpp \
+ pipeline/skia/RenderNodeDrawable.cpp \
+ pipeline/skia/ReorderBarrierDrawables.cpp \
+ pipeline/skia/SkiaDisplayList.cpp \
+ pipeline/skia/SkiaRecordingCanvas.cpp \
renderstate/Blend.cpp \
renderstate/MeshState.cpp \
renderstate/OffscreenBufferPool.cpp \
@@ -94,7 +100,6 @@
ShadowTessellator.cpp \
SkiaCanvas.cpp \
SkiaCanvasProxy.cpp \
- SkiaDisplayList.cpp \
SkiaShader.cpp \
Snapshot.cpp \
SpotShadow.cpp \
@@ -169,6 +174,7 @@
hwui_c_includes += \
external/skia/include/private \
external/skia/src/core \
+ external/skia/src/effects \
external/harfbuzz_ng/src \
external/freetype/include
@@ -284,6 +290,7 @@
tests/unit/MeshStateTests.cpp \
tests/unit/OffscreenBufferPoolTests.cpp \
tests/unit/OpDumperTests.cpp \
+ tests/unit/RenderNodeDrawableTests.cpp \
tests/unit/RecordingCanvasTests.cpp \
tests/unit/RenderNodeTests.cpp \
tests/unit/RenderPropertiesTests.cpp \
diff --git a/libs/hwui/NinePatchUtils.h b/libs/hwui/NinePatchUtils.h
index 7a271b7..e989a46 100644
--- a/libs/hwui/NinePatchUtils.h
+++ b/libs/hwui/NinePatchUtils.h
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#pragma once
+
namespace android {
namespace NinePatchUtils {
@@ -34,5 +36,61 @@
}
}
+static inline int NumDistinctRects(const SkCanvas::Lattice& lattice) {
+ int xRects;
+ if (lattice.fXCount > 0) {
+ xRects = (0 == lattice.fXDivs[0]) ? lattice.fXCount : lattice.fXCount + 1;
+ } else {
+ xRects = 1;
+ }
+
+ int yRects;
+ if (lattice.fYCount > 0) {
+ yRects = (0 == lattice.fYDivs[0]) ? lattice.fYCount : lattice.fYCount + 1;
+ } else {
+ yRects = 1;
+ }
+ return xRects * yRects;
+}
+
+static inline void SetLatticeFlags(SkCanvas::Lattice* lattice, SkCanvas::Lattice::Flags* flags,
+ int numFlags, const Res_png_9patch& chunk) {
+ lattice->fFlags = flags;
+ sk_bzero(flags, numFlags * sizeof(SkCanvas::Lattice::Flags));
+
+ bool needPadRow = lattice->fYCount > 0 && 0 == lattice->fYDivs[0];
+ bool needPadCol = lattice->fXCount > 0 && 0 == lattice->fXDivs[0];
+
+ int yCount = lattice->fYCount;
+ if (needPadRow) {
+ // Skip flags for the degenerate first row of rects.
+ flags += lattice->fXCount + 1;
+ yCount--;
+ }
+
+ int i = 0;
+ bool setFlags = false;
+ for (int y = 0; y < yCount + 1; y++) {
+ for (int x = 0; x < lattice->fXCount + 1; x++) {
+ if (0 == x && needPadCol) {
+ // First rect of each column is degenerate, skip the flag.
+ flags++;
+ continue;
+ }
+
+ if (0 == chunk.getColors()[i++]) {
+ *flags = SkCanvas::Lattice::kTransparent_Flags;
+ setFlags = true;
+ }
+
+ flags++;
+ }
+ }
+
+ if (!setFlags) {
+ lattice->fFlags = nullptr;
+ }
+}
+
}; // namespace NinePatchUtils
}; // namespace android
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index a05c744..3819c5e 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -32,7 +32,7 @@
#include "DisplayList.h"
#include "Matrix.h"
#include "RenderProperties.h"
-#include "SkiaDisplayList.h"
+#include "pipeline/skia/SkiaDisplayList.h"
#include <vector>
@@ -50,7 +50,6 @@
class FrameBuilder;
class OffscreenBuffer;
class Rect;
-class SkiaDisplayList;
class SkiaShader;
struct RenderNodeOp;
@@ -61,6 +60,10 @@
class RenderNode;
}
+namespace skiapipeline {
+ class SkiaDisplayList;
+}
+
/**
* Primary class for storing recorded canvas commands, as well as per-View/ViewGroup display properties.
*
@@ -294,14 +297,14 @@
* Detach and transfer ownership of an already allocated displayList for use
* in recording updated content for this renderNode
*/
- std::unique_ptr<SkiaDisplayList> detachAvailableList() {
+ std::unique_ptr<skiapipeline::SkiaDisplayList> detachAvailableList() {
return std::move(mAvailableDisplayList);
}
/**
* Attach unused displayList to this node for potential future reuse.
*/
- void attachAvailableList(SkiaDisplayList* skiaDisplayList) {
+ void attachAvailableList(skiapipeline::SkiaDisplayList* skiaDisplayList) {
mAvailableDisplayList.reset(skiaDisplayList);
}
@@ -337,7 +340,7 @@
* 2) It is replaced with the displayList from the next completed frame
* 3) It is detached and used to to record a new displayList for a later frame
*/
- std::unique_ptr<SkiaDisplayList> mAvailableDisplayList;
+ std::unique_ptr<skiapipeline::SkiaDisplayList> mAvailableDisplayList;
/**
* An offscreen rendering target used to contain the contents this RenderNode
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index c48b4dc..b6ac48f 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -21,6 +21,7 @@
#include "VectorDrawable.h"
#include "hwui/Bitmap.h"
#include "hwui/MinikinUtils.h"
+#include "pipeline/skia/AnimatedDrawables.h"
#include <SkDrawable.h>
#include <SkDevice.h>
@@ -57,6 +58,8 @@
mCanvas.reset(new SkCanvas(bitmap));
}
+SkiaCanvas::~SkiaCanvas() {}
+
void SkiaCanvas::reset(SkCanvas* skiaCanvas) {
mCanvas.reset(SkRef(skiaCanvas));
mSaveStack.reset(nullptr);
@@ -671,62 +674,6 @@
indexCount, tmpPaint);
}
-static inline int num_distinct_rects(const SkCanvas::Lattice& lattice) {
- int xRects;
- if (lattice.fXCount > 0) {
- xRects = (0 == lattice.fXDivs[0]) ? lattice.fXCount : lattice.fXCount + 1;
- } else {
- xRects = 1;
- }
-
- int yRects;
- if (lattice.fYCount > 0) {
- yRects = (0 == lattice.fYDivs[0]) ? lattice.fYCount : lattice.fYCount + 1;
- } else {
- yRects = 1;
- }
- return xRects * yRects;
-}
-
-static inline void set_lattice_flags(SkCanvas::Lattice* lattice, SkCanvas::Lattice::Flags* flags,
- int numFlags, const Res_png_9patch& chunk) {
- lattice->fFlags = flags;
- sk_bzero(flags, numFlags * sizeof(SkCanvas::Lattice::Flags));
-
- bool needPadRow = lattice->fYCount > 0 && 0 == lattice->fYDivs[0];
- bool needPadCol = lattice->fXCount > 0 && 0 == lattice->fXDivs[0];
-
- int yCount = lattice->fYCount;
- if (needPadRow) {
- // Skip flags for the degenerate first row of rects.
- flags += lattice->fXCount + 1;
- yCount--;
- }
-
- int i = 0;
- bool setFlags = false;
- for (int y = 0; y < yCount + 1; y++) {
- for (int x = 0; x < lattice->fXCount + 1; x++) {
- if (0 == x && needPadCol) {
- // First rect of each column is degenerate, skip the flag.
- flags++;
- continue;
- }
-
- if (0 == chunk.getColors()[i++]) {
- *flags = SkCanvas::Lattice::kTransparent_Flags;
- setFlags = true;
- }
-
- flags++;
- }
- }
-
- if (!setFlags) {
- lattice->fFlags = nullptr;
- }
-}
-
void SkiaCanvas::drawNinePatch(Bitmap& hwuiBitmap, const Res_png_9patch& chunk,
float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) {
@@ -738,7 +685,7 @@
lattice.fFlags = nullptr;
int numFlags = 0;
- if (chunk.numColors > 0 && chunk.numColors == num_distinct_rects(lattice)) {
+ if (chunk.numColors > 0 && chunk.numColors == NinePatchUtils::NumDistinctRects(lattice)) {
// We can expect the framework to give us a color for every distinct rect.
// Skia requires a flag for every rect.
numFlags = (lattice.fXCount + 1) * (lattice.fYCount + 1);
@@ -746,7 +693,7 @@
SkAutoSTMalloc<25, SkCanvas::Lattice::Flags> flags(numFlags);
if (numFlags > 0) {
- set_lattice_flags(&lattice, flags.get(), numFlags, chunk);
+ NinePatchUtils::SetLatticeFlags(&lattice, flags.get(), numFlags, chunk);
}
lattice.fBounds = nullptr;
@@ -820,69 +767,18 @@
// Canvas draw operations: Animations
// ----------------------------------------------------------------------------
-class AnimatedRoundRect : public SkDrawable {
- public:
- AnimatedRoundRect(uirenderer::CanvasPropertyPrimitive* left,
- uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right,
- uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx,
- uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* p) :
- mLeft(left), mTop(top), mRight(right), mBottom(bottom), mRx(rx), mRy(ry), mPaint(p) {}
-
- protected:
- virtual SkRect onGetBounds() override {
- return SkRect::MakeLTRB(mLeft->value, mTop->value, mRight->value, mBottom->value);
- }
- virtual void onDraw(SkCanvas* canvas) override {
- SkRect rect = SkRect::MakeLTRB(mLeft->value, mTop->value, mRight->value, mBottom->value);
- canvas->drawRoundRect(rect, mRx->value, mRy->value, mPaint->value);
- }
-
- private:
- sp<uirenderer::CanvasPropertyPrimitive> mLeft;
- sp<uirenderer::CanvasPropertyPrimitive> mTop;
- sp<uirenderer::CanvasPropertyPrimitive> mRight;
- sp<uirenderer::CanvasPropertyPrimitive> mBottom;
- sp<uirenderer::CanvasPropertyPrimitive> mRx;
- sp<uirenderer::CanvasPropertyPrimitive> mRy;
- sp<uirenderer::CanvasPropertyPaint> mPaint;
-};
-
-class AnimatedCircle : public SkDrawable {
- public:
- AnimatedCircle(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* y,
- uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint) :
- mX(x), mY(y), mRadius(radius), mPaint(paint) {}
-
- protected:
- virtual SkRect onGetBounds() override {
- const float x = mX->value;
- const float y = mY->value;
- const float radius = mRadius->value;
- return SkRect::MakeLTRB(x - radius, y - radius, x + radius, y + radius);
- }
- virtual void onDraw(SkCanvas* canvas) override {
- canvas->drawCircle(mX->value, mY->value, mRadius->value, mPaint->value);
- }
-
- private:
- sp<uirenderer::CanvasPropertyPrimitive> mX;
- sp<uirenderer::CanvasPropertyPrimitive> mY;
- sp<uirenderer::CanvasPropertyPrimitive> mRadius;
- sp<uirenderer::CanvasPropertyPaint> mPaint;
-};
-
void SkiaCanvas::drawRoundRect(uirenderer::CanvasPropertyPrimitive* left,
uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right,
uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx,
uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* paint) {
- sk_sp<AnimatedRoundRect> drawable(
- new AnimatedRoundRect(left, top, right, bottom, rx, ry, paint));
+ sk_sp<uirenderer::skiapipeline::AnimatedRoundRect> drawable(
+ new uirenderer::skiapipeline::AnimatedRoundRect(left, top, right, bottom, rx, ry, paint));
mCanvas->drawDrawable(drawable.get());
}
void SkiaCanvas::drawCircle(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* y,
uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint) {
- sk_sp<AnimatedCircle> drawable(new AnimatedCircle(x, y, radius, paint));
+ sk_sp<uirenderer::skiapipeline::AnimatedCircle> drawable(new uirenderer::skiapipeline::AnimatedCircle(x, y, radius, paint));
mCanvas->drawDrawable(drawable.get());
}
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index d1edff9..a0cdfcb 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -40,6 +40,8 @@
*/
explicit SkiaCanvas(SkCanvas* canvas);
+ virtual ~SkiaCanvas();
+
virtual SkCanvas* asSkCanvas() override {
return mCanvas.get();
}
diff --git a/libs/hwui/SkiaDrawables.h b/libs/hwui/SkiaDrawables.h
deleted file mode 100644
index a1ceeaa..0000000
--- a/libs/hwui/SkiaDrawables.h
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2016 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 "Layer.h"
-#include "RenderNode.h"
-
-#include <SkCanvas.h>
-#include <SkDrawable.h>
-#include <SkMatrix.h>
-
-#include <utils/RefBase.h>
-#include <utils/FatVector.h>
-#include <utils/Functor.h>
-
-namespace android {
-
-class Functor;
-
-namespace uirenderer {
-
-
-class RenderProperties;
-class OffscreenBuffer;
-class GlFunctorLifecycleListener;
-class SkiaDisplayList;
-
-/**
- * This drawable wraps a RenderNode and enables it to be recorded into a list
- * of Skia drawing commands.
- */
-class RenderNodeDrawable : public SkDrawable {
-public:
- explicit RenderNodeDrawable(RenderNode* node, SkCanvas* canvas)
- : mRenderNode(node)
- , mRecordedTransform(canvas->getTotalMatrix()) {}
-
- /**
- * The renderNode (and its properties) that is to be drawn
- */
- RenderNode* getRenderNode() const { return mRenderNode.get(); }
-
- /**
- * Returns the transform on the canvas at time of recording and is used for
- * computing total transform without rerunning DL contents.
- */
- const SkMatrix& getRecordedMatrix() const { return mRecordedTransform; }
-
-protected:
- virtual SkRect onGetBounds() override {
- // We don't want to enable a record time quick reject because the properties
- // of the RenderNode may be updated on subsequent frames.
- return SkRect::MakeLargest();
- }
- virtual void onDraw(SkCanvas* canvas) override { /* TODO */ }
-
-private:
- sp<RenderNode> mRenderNode;
- const SkMatrix mRecordedTransform;
-};
-
-/**
- * This drawable wraps a OpenGL functor enabling it to be recorded into a list
- * of Skia drawing commands.
- */
-class GLFunctorDrawable : public SkDrawable {
-public:
- GLFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, SkCanvas* canvas)
- : mFunctor(functor)
- , mListener(listener) {
- canvas->getClipBounds(&mBounds);
- }
- virtual ~GLFunctorDrawable() {}
-
- void syncFunctor() const { (*mFunctor)(DrawGlInfo::kModeSync, nullptr); }
-
- protected:
- virtual SkRect onGetBounds() override { return mBounds; }
- virtual void onDraw(SkCanvas* canvas) override { /* TODO */ }
-
- private:
- Functor* mFunctor;
- sp<GlFunctorLifecycleListener> mListener;
- SkRect mBounds;
-};
-
-}; // namespace uirenderer
-}; // namespace android
diff --git a/libs/hwui/pipeline/skia/AnimatedDrawables.h b/libs/hwui/pipeline/skia/AnimatedDrawables.h
new file mode 100644
index 0000000..44c494f
--- /dev/null
+++ b/libs/hwui/pipeline/skia/AnimatedDrawables.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 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 "CanvasProperty.h"
+#include <utils/RefBase.h>
+#include <SkCanvas.h>
+#include <SkDrawable.h>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class AnimatedRoundRect : public SkDrawable {
+public:
+ AnimatedRoundRect(uirenderer::CanvasPropertyPrimitive* left,
+ uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right,
+ uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx,
+ uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* p)
+ : mLeft(left)
+ , mTop(top)
+ , mRight(right)
+ , mBottom(bottom)
+ , mRx(rx)
+ , mRy(ry)
+ , mPaint(p) {}
+
+protected:
+ virtual SkRect onGetBounds() override {
+ return SkRect::MakeLTRB(mLeft->value, mTop->value, mRight->value, mBottom->value);
+ }
+ virtual void onDraw(SkCanvas* canvas) override {
+ SkRect rect = SkRect::MakeLTRB(mLeft->value, mTop->value, mRight->value, mBottom->value);
+ canvas->drawRoundRect(rect, mRx->value, mRy->value, mPaint->value);
+ }
+
+private:
+ sp<uirenderer::CanvasPropertyPrimitive> mLeft;
+ sp<uirenderer::CanvasPropertyPrimitive> mTop;
+ sp<uirenderer::CanvasPropertyPrimitive> mRight;
+ sp<uirenderer::CanvasPropertyPrimitive> mBottom;
+ sp<uirenderer::CanvasPropertyPrimitive> mRx;
+ sp<uirenderer::CanvasPropertyPrimitive> mRy;
+ sp<uirenderer::CanvasPropertyPaint> mPaint;
+};
+
+class AnimatedCircle : public SkDrawable {
+public:
+ AnimatedCircle(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* y,
+ uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint)
+ : mX(x)
+ , mY(y)
+ , mRadius(radius)
+ , mPaint(paint) {}
+
+protected:
+ virtual SkRect onGetBounds() override {
+ const float x = mX->value;
+ const float y = mY->value;
+ const float radius = mRadius->value;
+ return SkRect::MakeLTRB(x - radius, y - radius, x + radius, y + radius);
+ }
+ virtual void onDraw(SkCanvas* canvas) override {
+ canvas->drawCircle(mX->value, mY->value, mRadius->value, mPaint->value);
+ }
+
+private:
+ sp<uirenderer::CanvasPropertyPrimitive> mX;
+ sp<uirenderer::CanvasPropertyPrimitive> mY;
+ sp<uirenderer::CanvasPropertyPrimitive> mRadius;
+ sp<uirenderer::CanvasPropertyPaint> mPaint;
+};
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
new file mode 100644
index 0000000..fb2134c
--- /dev/null
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 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 "GLFunctorDrawable.h"
+#include "GlFunctorLifecycleListener.h"
+#include "RenderNode.h"
+#include "SkClipStack.h"
+#include <private/hwui/DrawGlInfo.h>
+#include <SkPath.h>
+#include <GrContext.h>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+GLFunctorDrawable::~GLFunctorDrawable() {
+ if(mListener.get() != nullptr) {
+ mListener->onGlFunctorReleased(mFunctor);
+ }
+}
+
+void GLFunctorDrawable::syncFunctor() const {
+ (*mFunctor)(DrawGlInfo::kModeSync, nullptr);
+}
+
+static void setScissor(int viewportHeight, const SkIRect& clip) {
+ SkASSERT(!clip.isEmpty());
+ // transform to Y-flipped GL space, and prevent negatives
+ GLint y = viewportHeight - clip.fBottom;
+ GLint height = (viewportHeight - clip.fTop) - y;
+ glScissor(clip.fLeft, y, clip.width(), height);
+}
+
+void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
+ if (canvas->getGrContext() == nullptr) {
+ SkDEBUGF(("Attempting to draw GLFunctor into an unsupported surface"));
+ return;
+ }
+
+ canvas->flush();
+
+ SkImageInfo canvasInfo = canvas->imageInfo();
+ SkMatrix44 mat4(canvas->getTotalMatrix());
+
+ SkIRect ibounds;
+ canvas->getClipDeviceBounds(&ibounds);
+
+ DrawGlInfo info;
+ info.clipLeft = ibounds.fLeft;
+ info.clipTop = ibounds.fTop;
+ info.clipRight = ibounds.fRight;
+ info.clipBottom = ibounds.fBottom;
+ // info.isLayer = hasLayer();
+ info.isLayer = false;
+ info.width = canvasInfo.width();
+ info.height = canvasInfo.height();
+ mat4.asColMajorf(&info.transform[0]);
+
+ //apply a simple clip with a scissor or a complex clip with a stencil
+ SkRegion clipRegion;
+ SkPath path;
+ canvas->getClipStack()->asPath(&path);
+ clipRegion.setPath(path, SkRegion(ibounds));
+ if (CC_UNLIKELY(clipRegion.isComplex())) {
+ //It is only a temporary solution to use a scissor to draw the stencil.
+ //There is a bug 31489986 to implement efficiently non-rectangular clips.
+ glDisable(GL_SCISSOR_TEST);
+ glDisable(GL_STENCIL_TEST);
+ glStencilMask(0xff);
+ glClearStencil(0);
+ glClear(GL_STENCIL_BUFFER_BIT);
+ glEnable(GL_SCISSOR_TEST);
+ SkRegion::Cliperator it(clipRegion, ibounds);
+ while (!it.done()) {
+ setScissor(info.height, it.rect());
+ glClearStencil(0x1);
+ glClear(GL_STENCIL_BUFFER_BIT);
+ it.next();
+ }
+ glDisable(GL_SCISSOR_TEST);
+ glStencilFunc(GL_EQUAL, 0x1, 0xff);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ glEnable(GL_STENCIL_TEST);
+ } else if (clipRegion.isEmpty()) {
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_SCISSOR_TEST);
+ } else {
+ glDisable(GL_STENCIL_TEST);
+ glEnable(GL_SCISSOR_TEST);
+ setScissor(info.height, clipRegion.getBounds());
+ }
+
+ (*mFunctor)(DrawGlInfo::kModeDraw, &info);
+
+ canvas->getGrContext()->resetContext();
+ }
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.h b/libs/hwui/pipeline/skia/GLFunctorDrawable.h
new file mode 100644
index 0000000..bf39dad
--- /dev/null
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 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 <SkCanvas.h>
+#include <SkDrawable.h>
+
+#include <utils/RefBase.h>
+#include <utils/Functor.h>
+
+namespace android {
+namespace uirenderer {
+
+class GlFunctorLifecycleListener;
+
+namespace skiapipeline {
+
+/**
+ * This drawable wraps a OpenGL functor enabling it to be recorded into a list
+ * of Skia drawing commands.
+ */
+class GLFunctorDrawable : public SkDrawable {
+public:
+ GLFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, SkCanvas* canvas)
+ : mFunctor(functor)
+ , mListener(listener) {
+ canvas->getClipBounds(&mBounds);
+ }
+ virtual ~GLFunctorDrawable();
+
+ void syncFunctor() const;
+
+ protected:
+ virtual SkRect onGetBounds() override { return mBounds; }
+ virtual void onDraw(SkCanvas* canvas) override;
+
+ private:
+ Functor* mFunctor;
+ sp<GlFunctorLifecycleListener> mListener;
+ SkRect mBounds;
+};
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
new file mode 100644
index 0000000..f8a181f
--- /dev/null
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 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 "LayerDrawable.h"
+#include "gl/GrGLTypes.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+void LayerDrawable::onDraw(SkCanvas* canvas) {
+ // transform the matrix based on the layer
+ int saveCount = -1;
+ if (!mLayer->getTransform().isIdentity()) {
+ saveCount = canvas->save();
+ SkMatrix transform;
+ mLayer->getTransform().copyTo(transform);
+ canvas->concat(transform);
+ }
+ GrGLTextureInfo externalTexture;
+ externalTexture.fTarget = mLayer->getRenderTarget();
+ externalTexture.fID = mLayer->getTextureId();
+ GrContext* context = canvas->getGrContext();
+ GrBackendTextureDesc textureDescription;
+ textureDescription.fWidth = mLayer->getWidth();
+ textureDescription.fHeight = mLayer->getHeight();
+ textureDescription.fConfig = kRGBA_8888_GrPixelConfig;
+ textureDescription.fOrigin = kTopLeft_GrSurfaceOrigin;
+ textureDescription.fTextureHandle = reinterpret_cast<GrBackendObject>(&externalTexture);
+ sk_sp<SkImage> layerImage(SkImage::NewFromTexture(context, textureDescription));
+ if (layerImage) {
+ SkPaint paint;
+ paint.setAlpha(mLayer->getAlpha());
+ paint.setBlendMode(mLayer->getMode());
+ paint.setColorFilter(mLayer->getColorFilter());
+ canvas->drawImage(layerImage, 0, 0, &paint);
+ }
+ // restore the original matrix
+ if (saveCount >= 0) {
+ canvas->restoreToCount(saveCount);
+ }
+}
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.h b/libs/hwui/pipeline/skia/LayerDrawable.h
new file mode 100644
index 0000000..91e2744
--- /dev/null
+++ b/libs/hwui/pipeline/skia/LayerDrawable.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 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 "Layer.h"
+
+#include <SkCanvas.h>
+#include <SkDrawable.h>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+/*
+ * Draws a layer backed by an OpenGL texture into a SkCanvas.
+ */
+class LayerDrawable : public SkDrawable {
+ public:
+ explicit LayerDrawable(Layer* layer)
+ : mLayer(layer) {}
+
+ protected:
+ virtual SkRect onGetBounds() override {
+ return SkRect::MakeWH(mLayer->getWidth(), mLayer->getHeight());
+ }
+ virtual void onDraw(SkCanvas* canvas) override;
+
+private:
+ sp<Layer> mLayer;
+};
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
new file mode 100644
index 0000000..cefa893
--- /dev/null
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2016 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 "RenderNodeDrawable.h"
+#include "RenderNode.h"
+#include "SkiaDisplayList.h"
+#include "SkiaFrameRenderer.h"
+#include "utils/TraceUtils.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+static void clipOutline(const Outline& outline, SkCanvas* canvas, const SkRect* pendingClip) {
+ SkASSERT(outline.willClip());
+ Rect possibleRect;
+ float radius;
+ LOG_ALWAYS_FATAL_IF(!outline.getAsRoundRect(&possibleRect, &radius),
+ "clipping outlines should be at most roundedRects");
+ SkRect rect = possibleRect.toSkRect();
+ if (radius != 0.0f) {
+ if (pendingClip && !pendingClip->contains(rect)) {
+ canvas->clipRect(*pendingClip);
+ }
+ canvas->clipRRect(SkRRect::MakeRectXY(rect, radius, radius), SkRegion::kIntersect_Op, true);
+ } else {
+ if (pendingClip) {
+ (void)rect.intersect(*pendingClip);
+ }
+ canvas->clipRect(rect);
+ }
+}
+
+const RenderProperties& RenderNodeDrawable::getNodeProperties() const {
+ return mRenderNode->properties();
+}
+
+void RenderNodeDrawable::onDraw(SkCanvas* canvas) {
+ //negative and positive Z order are drawn out of order
+ if (MathUtils::isZero(mRenderNode->properties().getZ())) {
+ this->forceDraw(canvas);
+ }
+}
+
+void RenderNodeDrawable::forceDraw(SkCanvas* canvas) {
+ RenderNode* renderNode = mRenderNode.get();
+ if (SkiaFrameRenderer::skpCaptureEnabled()) {
+ SkRect dimensions = SkRect::MakeWH(renderNode->getWidth(), renderNode->getHeight());
+ canvas->drawAnnotation(dimensions, renderNode->getName(), nullptr);
+ }
+
+ // We only respect the nothingToDraw check when we are composing a layer. This
+ // ensures that we paint the layer even if it is not currently visible in the
+ // event that the properties change and it becomes visible.
+ if (!renderNode->isRenderable() || (renderNode->nothingToDraw() && mComposeLayer)) {
+ return;
+ }
+
+ SkASSERT(renderNode->getDisplayList()->isSkiaDL());
+ SkiaDisplayList* displayList = (SkiaDisplayList*)renderNode->getDisplayList();
+
+ SkAutoCanvasRestore acr(canvas, true);
+
+ const RenderProperties& properties = this->getNodeProperties();
+ if (displayList->mIsProjectionReceiver) {
+ // this node is a projection receiver. We will gather the projected nodes as we draw our
+ // children, and then draw them on top of this node's content.
+ std::vector<ProjectedChild> newList;
+ for (auto& child : displayList->mChildNodes) {
+ // our direct children are not supposed to project into us (nodes project to, at the
+ // nearest, their grandparents). So we "delay" the list's activation one level by
+ // passing it into mNextProjectedChildrenTarget rather than mProjectedChildrenTarget.
+ child.mProjectedChildrenTarget = mNextProjectedChildrenTarget;
+ child.mNextProjectedChildrenTarget = &newList;
+ }
+ // draw ourselves and our children. As a side effect, this will add projected nodes to
+ // newList.
+ this->drawContent(canvas);
+ bool willClip = properties.getOutline().willClip();
+ if (willClip) {
+ canvas->save();
+ clipOutline(properties.getOutline(), canvas, nullptr);
+ }
+ // draw the collected projected nodes
+ for (auto& projectedChild : newList) {
+ canvas->setMatrix(projectedChild.matrix);
+ projectedChild.node->drawContent(canvas);
+ }
+ if (willClip) {
+ canvas->restore();
+ }
+ } else {
+ if (properties.getProjectBackwards() && mProjectedChildrenTarget) {
+ // We are supposed to project this node, so add it to the list and do not actually draw
+ // yet. It will be drawn by its projection receiver.
+ mProjectedChildrenTarget->push_back({ this, canvas->getTotalMatrix() });
+ return;
+ }
+ for (auto& child : displayList->mChildNodes) {
+ // storing these values in the nodes themselves is a bit ugly; they should "really" be
+ // function parameters, but we have to go through the preexisting draw() method and
+ // therefore cannot add additional parameters to it
+ child.mProjectedChildrenTarget = mNextProjectedChildrenTarget;
+ child.mNextProjectedChildrenTarget = mNextProjectedChildrenTarget;
+ }
+ this->drawContent(canvas);
+ }
+ mProjectedChildrenTarget = nullptr;
+ mNextProjectedChildrenTarget = nullptr;
+}
+
+static bool layerNeedsPaint(const LayerProperties& properties,
+ float alphaMultiplier, SkPaint* paint) {
+ if (alphaMultiplier < 1.0f
+ || properties.alpha() < 255
+ || properties.xferMode() != SkBlendMode::kSrcOver
+ || properties.colorFilter() != nullptr) {
+ paint->setAlpha(properties.alpha() * alphaMultiplier);
+ paint->setBlendMode(properties.xferMode());
+ paint->setColorFilter(properties.colorFilter());
+ return true;
+ }
+ return false;
+}
+
+void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
+ RenderNode* renderNode = mRenderNode.get();
+ float alphaMultiplier = 1.0f;
+ const RenderProperties& properties = renderNode->properties();
+
+ // If we are drawing the contents of layer, we don't want to apply any of
+ // the RenderNode's properties during this pass. Those will all be applied
+ // when the layer is composited.
+ if (mComposeLayer) {
+ setViewProperties(properties, canvas, &alphaMultiplier);
+ }
+
+ //TODO should we let the bound of the drawable do this for us?
+ const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
+ bool quickRejected = properties.getClipToBounds() && canvas->quickReject(bounds);
+ if (!quickRejected) {
+ SkiaDisplayList* displayList = (SkiaDisplayList*)renderNode->getDisplayList();
+ const LayerProperties& layerProperties = properties.layerProperties();
+ // composing a hardware layer
+ if (renderNode->getLayerSurface() && mComposeLayer) {
+ SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer);
+ SkPaint* paint = nullptr;
+ SkPaint tmpPaint;
+ if (layerNeedsPaint(layerProperties, alphaMultiplier, &tmpPaint)) {
+ paint = &tmpPaint;
+ }
+ renderNode->getLayerSurface()->draw(canvas, 0, 0, paint);
+ // composing a software layer with alpha
+ } else if (properties.effectiveLayerType() == LayerType::Software) {
+ SkPaint paint;
+ bool needsLayer = layerNeedsPaint(layerProperties, alphaMultiplier, &paint);
+ if (needsLayer) {
+ canvas->saveLayer(bounds, &paint);
+ }
+ canvas->drawDrawable(displayList->mDrawable.get());
+ if (needsLayer) {
+ canvas->restore();
+ }
+ } else {
+ canvas->drawDrawable(displayList->mDrawable.get());
+ }
+ }
+}
+
+void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, SkCanvas* canvas,
+ float* alphaMultiplier) {
+ if (properties.getLeft() != 0 || properties.getTop() != 0) {
+ canvas->translate(properties.getLeft(), properties.getTop());
+ }
+ if (properties.getStaticMatrix()) {
+ canvas->concat(*properties.getStaticMatrix());
+ } else if (properties.getAnimationMatrix()) {
+ canvas->concat(*properties.getAnimationMatrix());
+ }
+ if (properties.hasTransformMatrix()) {
+ if (properties.isTransformTranslateOnly()) {
+ canvas->translate(properties.getTranslationX(), properties.getTranslationY());
+ } else {
+ canvas->concat(*properties.getTransformMatrix());
+ }
+ }
+ const bool isLayer = properties.effectiveLayerType() != LayerType::None;
+ int clipFlags = properties.getClippingFlags();
+ if (properties.getAlpha() < 1) {
+ if (isLayer) {
+ clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer
+ }
+ if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) {
+ *alphaMultiplier = properties.getAlpha();
+ } else {
+ // savelayer needed to create an offscreen buffer
+ Rect layerBounds(0, 0, properties.getWidth(), properties.getHeight());
+ if (clipFlags) {
+ properties.getClippingRectForFlags(clipFlags, &layerBounds);
+ clipFlags = 0; // all clipping done by savelayer
+ }
+ SkRect bounds = SkRect::MakeLTRB(layerBounds.left, layerBounds.top,
+ layerBounds.right, layerBounds.bottom);
+ canvas->saveLayerAlpha(&bounds, (int) (properties.getAlpha() * 255));
+ }
+
+ if (CC_UNLIKELY(ATRACE_ENABLED() && properties.promotedToLayer())) {
+ // pretend alpha always causes savelayer to warn about
+ // performance problem affecting old versions
+ ATRACE_FORMAT("alpha caused saveLayer %dx%d", properties.getWidth(),
+ properties.getHeight());
+ }
+ }
+
+ const SkRect* pendingClip = nullptr;
+ SkRect clipRect;
+
+ if (clipFlags) {
+ Rect tmpRect;
+ properties.getClippingRectForFlags(clipFlags, &tmpRect);
+ clipRect = tmpRect.toSkRect();
+ pendingClip = &clipRect;
+ }
+
+ if (properties.getRevealClip().willClip()) {
+ canvas->clipPath(*properties.getRevealClip().getPath(), SkRegion::kIntersect_Op, true);
+ } else if (properties.getOutline().willClip()) {
+ clipOutline(properties.getOutline(), canvas, pendingClip);
+ pendingClip = nullptr;
+ }
+
+ if (pendingClip) {
+ canvas->clipRect(*pendingClip);
+ }
+}
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
new file mode 100644
index 0000000..0762f37
--- /dev/null
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2016 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 <SkCanvas.h>
+#include <SkDrawable.h>
+#include <SkMatrix.h>
+#include <utils/RefBase.h>
+
+namespace android {
+namespace uirenderer {
+
+class RenderNode;
+class RenderProperties;
+
+namespace skiapipeline {
+
+/**
+ * This drawable wraps a RenderNode and enables it to be recorded into a list
+ * of Skia drawing commands.
+ */
+class RenderNodeDrawable : public SkDrawable {
+public:
+ /**
+ * This struct contains a pointer to a node that is to be
+ * projected into the drawing order of its closest ancestor
+ * (excluding its parent) that is marked as a projection
+ * receiver. The matrix is used to ensure that the node is
+ * drawn with same matrix as it would have prior to projection.
+ */
+ struct ProjectedChild {
+ const RenderNodeDrawable* node;
+ const SkMatrix matrix;
+ };
+
+ /**
+ * Creates a new RenderNodeDrawable backed by a render node.
+ *
+ * @param node that has to be drawn
+ * @param canvas is a recording canvas used to extract its matrix
+ * @param composeLayer if the node's layer type is RenderLayer this flag determines whether
+ * we should draw into the contents of the layer or compose the existing contents of the
+ * layer into the canvas.
+ */
+ explicit RenderNodeDrawable(RenderNode* node, SkCanvas* canvas, bool composeLayer = true)
+ : mRenderNode(node)
+ , mRecordedTransform(canvas->getTotalMatrix())
+ , mComposeLayer(composeLayer) {}
+
+ /**
+ * Draws into the canvas this render node and its children. If the node is marked as a
+ * projection receiver then all projected children (excluding direct children) will be drawn
+ * last. Any projected node not matching those requirements will not be drawn by this function.
+ */
+ void forceDraw(SkCanvas* canvas);
+
+ /**
+ * Returns readonly render properties for this render node.
+ */
+ const RenderProperties& getNodeProperties() const;
+
+ /**
+ * The renderNode (and its properties) that is to be drawn
+ */
+ RenderNode* getRenderNode() const { return mRenderNode.get(); }
+
+ /**
+ * Returns the transform on the canvas at time of recording and is used for
+ * computing total transform without rerunning DL contents.
+ */
+ const SkMatrix& getRecordedMatrix() const { return mRecordedTransform; }
+
+protected:
+ /*
+ * Return the (conservative) bounds of what the drawable will draw.
+ */
+ virtual SkRect onGetBounds() override {
+ // We don't want to enable a record time quick reject because the properties
+ // of the RenderNode may be updated on subsequent frames.
+ return SkRect::MakeLargest();
+ }
+ /**
+ * This function draws into a canvas as forceDraw, but does nothing if the render node has a
+ * non-zero elevation.
+ */
+ virtual void onDraw(SkCanvas* canvas) override;
+
+private:
+ /*
+ * Render node that is wrapped by this class.
+ */
+ sp<RenderNode> mRenderNode;
+
+ /**
+ * Applies the rendering properties of a view onto a SkCanvas.
+ */
+ static void setViewProperties(const RenderProperties& properties, SkCanvas* canvas,
+ float* alphaMultiplier);
+
+ /**
+ * Stores transform on the canvas at time of recording and is used for
+ * computing total transform without rerunning DL contents.
+ */
+ const SkMatrix mRecordedTransform;
+
+ /**
+ * If mRenderNode's layer type is RenderLayer this flag determines whether we
+ * should draw into the contents of the layer or compose the existing contents
+ * of the layer into the canvas.
+ */
+ const bool mComposeLayer;
+
+ /**
+ * List to which we will add any projected children we encounter while walking our descendents.
+ * This pointer is valid only while the node (including its children) is actively being drawn.
+ */
+ std::vector<ProjectedChild>* mProjectedChildrenTarget = nullptr;
+
+ /**
+ * The value to which we should set our children's mProjectedChildrenTarget. We use two pointers
+ * (mProjectedChildrenTarget and mNextProjectedChildrenTarget) because we need to skip over our
+ * parent when looking for a projection receiver.
+ */
+ std::vector<ProjectedChild>* mNextProjectedChildrenTarget = nullptr;
+
+ /*
+ * Draw the content into a canvas, depending on the render node layer type and mComposeLayer.
+ */
+ void drawContent(SkCanvas* canvas) const;
+};
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
new file mode 100644
index 0000000..8d77938
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
@@ -0,0 +1,704 @@
+/*
+ * Copyright (C) 2016 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 "ReorderBarrierDrawables.h"
+#include "RenderNode.h"
+#include "SkiaDisplayList.h"
+#include "SkiaFrameRenderer.h"
+
+#include <SkBlurMask.h>
+#include <SkBlurMaskFilter.h>
+#include <SkGaussianEdgeShader.h>
+#include <SkPathOps.h>
+#include <SkRRectsGaussianEdgeShader.h>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+StartReorderBarrierDrawable::StartReorderBarrierDrawable(SkiaDisplayList* data)
+ : mEndChildIndex(0)
+ , mBeginChildIndex(data->mChildNodes.size())
+ , mDisplayList(data) {
+}
+
+void StartReorderBarrierDrawable::onDraw(SkCanvas* canvas) {
+ if (mChildren.empty()) {
+ //mChildren is allocated and initialized only the first time onDraw is called and cached for
+ //subsequent calls
+ mChildren.reserve(mEndChildIndex - mBeginChildIndex + 1);
+ for (unsigned int i = mBeginChildIndex; i <= mEndChildIndex; i++) {
+ mChildren.push_back(const_cast<RenderNodeDrawable*>(&mDisplayList->mChildNodes[i]));
+ }
+ }
+ std::stable_sort(mChildren.begin(), mChildren.end(),
+ [](RenderNodeDrawable* a, RenderNodeDrawable* b) {
+ const float aZValue = a->getNodeProperties().getZ();
+ const float bZValue = b->getNodeProperties().getZ();
+ return aZValue < bZValue;
+ });
+
+ SkASSERT(!mChildren.empty());
+
+ size_t drawIndex = 0;
+ const size_t endIndex = mChildren.size();
+ while (drawIndex < endIndex) {
+ RenderNodeDrawable* childNode = mChildren[drawIndex];
+ SkASSERT(childNode);
+ const float casterZ = childNode->getNodeProperties().getZ();
+ if (casterZ >= -NON_ZERO_EPSILON) { //draw only children with negative Z
+ return;
+ }
+ childNode->forceDraw(canvas);
+ drawIndex++;
+ }
+}
+
+EndReorderBarrierDrawable::EndReorderBarrierDrawable(StartReorderBarrierDrawable* startBarrier)
+ : mStartBarrier(startBarrier) {
+ mStartBarrier->mEndChildIndex = mStartBarrier->mDisplayList->mChildNodes.size() - 1;
+}
+
+#define SHADOW_DELTA 0.1f
+
+void EndReorderBarrierDrawable::onDraw(SkCanvas* canvas) {
+ auto& zChildren = mStartBarrier->mChildren;
+ SkASSERT(!zChildren.empty());
+
+ /**
+ * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters
+ * with very similar Z heights to draw together.
+ *
+ * This way, if Views A & B have the same Z height and are both casting shadows, the shadows are
+ * underneath both, and neither's shadow is drawn on top of the other.
+ */
+ size_t drawIndex = 0;
+
+ const size_t endIndex = zChildren.size();
+ while (drawIndex < endIndex //draw only children with positive Z
+ && zChildren[drawIndex]->getNodeProperties().getZ() <= NON_ZERO_EPSILON) drawIndex++;
+ size_t shadowIndex = drawIndex;
+
+ float lastCasterZ = 0.0f;
+ while (shadowIndex < endIndex || drawIndex < endIndex) {
+ if (shadowIndex < endIndex) {
+ const float casterZ = zChildren[shadowIndex]->getNodeProperties().getZ();
+
+ // attempt to render the shadow if the caster about to be drawn is its caster,
+ // OR if its caster's Z value is similar to the previous potential caster
+ if (shadowIndex == drawIndex || casterZ - lastCasterZ < SHADOW_DELTA) {
+ this->drawShadow(canvas, zChildren[shadowIndex]);
+ lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
+ shadowIndex++;
+ continue;
+ }
+ }
+
+ RenderNodeDrawable* childNode = zChildren[drawIndex];
+ SkASSERT(childNode);
+ childNode->forceDraw(canvas);
+
+ drawIndex++;
+ }
+}
+
+/**
+ * @param canvas the destination for the shadow draws
+ * @param shape the shape casting the shadow
+ * @param casterZValue the Z value of the caster RRect
+ * @param ambientAlpha the maximum alpha value to use when drawing the ambient shadow
+ * @param draw the function used to draw 'shape'
+ */
+template <typename Shape, typename F>
+static void DrawAmbientShadowGeneral(SkCanvas* canvas, const Shape& shape, float casterZValue,
+ float ambientAlpha, F&& draw) {
+ if (ambientAlpha <= 0) {
+ return;
+ }
+
+ const float kHeightFactor = 1.f/128.f;
+ const float kGeomFactor = 64;
+
+ float umbraAlpha = 1 / (1 + SkMaxScalar(casterZValue*kHeightFactor, 0));
+ float radius = casterZValue*kHeightFactor*kGeomFactor;
+
+ sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
+ SkBlurMask::ConvertRadiusToSigma(radius), SkBlurMaskFilter::kNone_BlurFlag);
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setMaskFilter(std::move(mf));
+ paint.setARGB(ambientAlpha*umbraAlpha, 0, 0, 0);
+
+ draw(shape, paint);
+}
+
+/**
+ * @param canvas the destination for the shadow draws
+ * @param shape the shape casting the shadow
+ * @param casterZValue the Z value of the caster RRect
+ * @param lightPos the position of the light casting the shadow
+ * @param lightWidth
+ * @param spotAlpha the maximum alpha value to use when drawing the spot shadow
+ * @param draw the function used to draw 'shape'
+ */
+template <typename Shape, typename F>
+static void DrawSpotShadowGeneral(SkCanvas* canvas, const Shape& shape, float casterZValue,
+ float spotAlpha, F&& draw) {
+ if (spotAlpha <= 0) {
+ return;
+ }
+
+ const Vector3 lightPos = SkiaFrameRenderer::getLightCenter();
+ float zRatio = casterZValue / (lightPos.z - casterZValue);
+ // clamp
+ if (zRatio < 0.0f) {
+ zRatio = 0.0f;
+ } else if (zRatio > 0.95f) {
+ zRatio = 0.95f;
+ }
+
+ float blurRadius = SkiaFrameRenderer::getLightRadius()*zRatio;
+
+ SkAutoCanvasRestore acr(canvas, true);
+
+ sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
+ SkBlurMask::ConvertRadiusToSigma(blurRadius), SkBlurMaskFilter::kNone_BlurFlag);
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setMaskFilter(std::move(mf));
+ paint.setARGB(spotAlpha, 0, 0, 0);
+
+ // approximate projection by translating and scaling projected offset of bounds center
+ // TODO: compute the actual 2D projection
+ SkScalar scale = lightPos.z / (lightPos.z - casterZValue);
+ canvas->scale(scale, scale);
+ SkPoint center = SkPoint::Make(shape.getBounds().centerX(), shape.getBounds().centerY());
+ SkMatrix ctmInverse;
+ if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
+ ALOGW("Matrix is degenerate. Will not render shadow!");
+ return;
+ }
+ SkPoint lightPos2D = SkPoint::Make(lightPos.x, lightPos.y);
+ ctmInverse.mapPoints(&lightPos2D, 1);
+ canvas->translate(zRatio*(center.fX - lightPos2D.fX), zRatio*(center.fY - lightPos2D.fY));
+
+ draw(shape, paint);
+}
+
+#define MAX_BLUR_RADIUS 16383.75f
+#define MAX_PAD 64
+
+/**
+ * @param casterRect the rectangle bounds of the RRect casting the shadow
+ * @param casterCornerRadius the x&y radius for all the corners of the RRect casting the shadow
+ * @param ambientAlpha the maximum alpha value to use when drawing the ambient shadow
+ * @param spotAlpha the maximum alpha value to use when drawing the spot shadow
+ * @param casterAlpha the alpha value of the RRect casting the shadow (0.0-1.0 range)
+ * @param casterZValue the Z value of the caster RRect
+ * @param scaleFactor the scale needed to map from src-space to device-space
+ * @param canvas the destination for the shadow draws
+ */
+static void DrawRRectShadows(const SkRect& casterRect, SkScalar casterCornerRadius,
+ SkScalar ambientAlpha, SkScalar spotAlpha, SkScalar casterAlpha, SkScalar casterZValue,
+ SkScalar scaleFactor, SkCanvas* canvas) {
+ SkASSERT(cornerRadius >= 0.0f);
+
+ // For all of these, we need to ensure we have a rrect with radius >= 0.5f in device space
+ const SkScalar minRadius = 0.5f / scaleFactor;
+
+ const bool isOval = casterCornerRadius >= std::max(SkScalarHalf(casterRect.width()),
+ SkScalarHalf(casterRect.height()));
+ const bool isRect = casterCornerRadius <= minRadius;
+
+ sk_sp<SkShader> edgeShader(SkGaussianEdgeShader::Make());
+
+ if (ambientAlpha > 0.0f) {
+ static const float kHeightFactor = 1.0f / 128.0f;
+ static const float kGeomFactor = 64.0f;
+
+ SkScalar srcSpaceAmbientRadius = casterZValue * kHeightFactor * kGeomFactor;
+ // the device-space radius sent to the blur shader must fit in 14.2 fixed point
+ if (srcSpaceAmbientRadius*scaleFactor > MAX_BLUR_RADIUS) {
+ srcSpaceAmbientRadius = MAX_BLUR_RADIUS/scaleFactor;
+ }
+ const float umbraAlpha = 1.0f / (1.0f + std::max(casterZValue * kHeightFactor, 0.0f));
+ const SkScalar ambientOffset = srcSpaceAmbientRadius * umbraAlpha;
+
+ // For the ambient rrect, we inset the offset rect by half the srcSpaceAmbientRadius
+ // to get our stroke shape.
+ SkScalar ambientPathOutset = std::max(ambientOffset - srcSpaceAmbientRadius * 0.5f,
+ minRadius);
+
+ SkRRect ambientRRect;
+ const SkRect temp = casterRect.makeOutset(ambientPathOutset, ambientPathOutset);
+ if (isOval) {
+ ambientRRect = SkRRect::MakeOval(temp);
+ } else if (isRect) {
+ ambientRRect = SkRRect::MakeRectXY(temp, ambientPathOutset, ambientPathOutset);
+ } else {
+ ambientRRect = SkRRect::MakeRectXY(temp, casterCornerRadius + ambientPathOutset,
+ casterCornerRadius + ambientPathOutset);
+ }
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setStyle(SkPaint::kStroke_Style);
+ // we outset the stroke a little to cover up AA on the interior edge
+ float pad = 0.5f;
+ paint.setStrokeWidth(srcSpaceAmbientRadius + 2.0f * pad);
+ // handle scale of radius and pad due to CTM
+ pad *= scaleFactor;
+ const SkScalar devSpaceAmbientRadius = srcSpaceAmbientRadius * scaleFactor;
+ SkASSERT(devSpaceAmbientRadius <= MAX_BLUR_RADIUS);
+ SkASSERT(pad < MAX_PAD);
+ // convert devSpaceAmbientRadius to 14.2 fixed point and place in the R & G components
+ // convert pad to 6.2 fixed point and place in the B component
+ uint16_t iDevSpaceAmbientRadius = (uint16_t)(4.0f * devSpaceAmbientRadius);
+ paint.setColor(SkColorSetARGB((unsigned char) ambientAlpha, iDevSpaceAmbientRadius >> 8,
+ iDevSpaceAmbientRadius & 0xff, (unsigned char)(4.0f * pad)));
+
+ paint.setShader(edgeShader);
+ canvas->drawRRect(ambientRRect, paint);
+ }
+
+ if (spotAlpha > 0.0f) {
+ const Vector3 lightPos = SkiaFrameRenderer::getLightCenter();
+ float zRatio = casterZValue / (lightPos.z - casterZValue);
+ // clamp
+ if (zRatio < 0.0f) {
+ zRatio = 0.0f;
+ } else if (zRatio > 0.95f) {
+ zRatio = 0.95f;
+ }
+
+ const SkScalar lightWidth = SkiaFrameRenderer::getLightRadius();
+ SkScalar srcSpaceSpotRadius = 2.0f * lightWidth * zRatio;
+ // the device-space radius sent to the blur shader must fit in 14.2 fixed point
+ if (srcSpaceSpotRadius*scaleFactor > MAX_BLUR_RADIUS) {
+ srcSpaceSpotRadius = MAX_BLUR_RADIUS/scaleFactor;
+ }
+
+ SkRRect spotRRect;
+ if (isOval) {
+ spotRRect = SkRRect::MakeOval(casterRect);
+ } else if (isRect) {
+ spotRRect = SkRRect::MakeRectXY(casterRect, minRadius, minRadius);
+ } else {
+ spotRRect = SkRRect::MakeRectXY(casterRect, casterCornerRadius, casterCornerRadius);
+ }
+
+ SkRRect spotShadowRRect;
+ // Compute the scale and translation for the spot shadow.
+ const SkScalar scale = lightPos.z / (lightPos.z - casterZValue);
+ spotRRect.transform(SkMatrix::MakeScale(scale, scale), &spotShadowRRect);
+
+ SkPoint center = SkPoint::Make(spotShadowRRect.rect().centerX(),
+ spotShadowRRect.rect().centerY());
+ SkMatrix ctmInverse;
+ if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
+ ALOGW("Matrix is degenerate. Will not render spot shadow!");
+ return;
+ }
+ SkPoint lightPos2D = SkPoint::Make(lightPos.x, lightPos.y);
+ ctmInverse.mapPoints(&lightPos2D, 1);
+ const SkPoint spotOffset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
+ zRatio*(center.fY - lightPos2D.fY));
+
+ SkAutoCanvasRestore acr(canvas, true);
+
+ // We want to extend the stroked area in so that it meets up with the caster
+ // geometry. The stroked geometry will, by definition already be inset half the
+ // stroke width but we also have to account for the scaling.
+ // We also add 1/2 to cover up AA on the interior edge.
+ SkScalar scaleOffset = (scale - 1.0f) * SkTMax(SkTMax(SkTAbs(casterRect.fLeft),
+ SkTAbs(casterRect.fRight)), SkTMax(SkTAbs(casterRect.fTop),
+ SkTAbs(casterRect.fBottom)));
+ SkScalar insetAmount = spotOffset.length() - (0.5f * srcSpaceSpotRadius) +
+ scaleOffset + 0.5f;
+
+ // Compute area
+ SkScalar strokeWidth = srcSpaceSpotRadius + insetAmount;
+ SkScalar strokedArea = 2.0f*strokeWidth * (spotShadowRRect.width()
+ + spotShadowRRect.height());
+ SkScalar filledArea = (spotShadowRRect.height() + srcSpaceSpotRadius)
+ * (spotShadowRRect.width() + srcSpaceSpotRadius);
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+
+ // If the area of the stroked geometry is larger than the fill geometry, just fill it.
+ if (strokedArea > filledArea || casterAlpha < 1.0f) {
+ paint.setStyle(SkPaint::kStrokeAndFill_Style);
+ paint.setStrokeWidth(srcSpaceSpotRadius);
+ } else {
+ // Since we can't have unequal strokes, inset the shadow rect so the inner
+ // and outer edges of the stroke will land where we want.
+ SkRect insetRect = spotShadowRRect.rect().makeInset(insetAmount/2.0f, insetAmount/2.0f);
+ SkScalar insetRad = SkTMax(spotShadowRRect.getSimpleRadii().fX - insetAmount/2.0f,
+ minRadius);
+ spotShadowRRect = SkRRect::MakeRectXY(insetRect, insetRad, insetRad);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(strokeWidth);
+ }
+
+ // handle scale of radius and pad due to CTM
+ const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor;
+ SkASSERT(devSpaceSpotRadius <= MAX_BLUR_RADIUS);
+
+ const SkScalar devSpaceSpotPad = 0;
+ SkASSERT(devSpaceSpotPad < MAX_PAD);
+
+ // convert devSpaceSpotRadius to 14.2 fixed point and place in the R & G
+ // components convert devSpaceSpotPad to 6.2 fixed point and place in the B component
+ uint16_t iDevSpaceSpotRadius = (uint16_t)(4.0f * devSpaceSpotRadius);
+ paint.setColor(SkColorSetARGB((unsigned char) spotAlpha, iDevSpaceSpotRadius >> 8,
+ iDevSpaceSpotRadius & 0xff, (unsigned char)(4.0f * devSpaceSpotPad)));
+ paint.setShader(edgeShader);
+
+ canvas->translate(spotOffset.fX, spotOffset.fY);
+ canvas->drawRRect(spotShadowRRect, paint);
+ }
+}
+
+/**
+ * @param casterRect the rectangle bounds of the RRect casting the shadow
+ * @param casterCornerRadius the x&y radius for all the corners of the RRect casting the shadow
+ * @param ambientAlpha the maximum alpha value to use when drawing the ambient shadow
+ * @param spotAlpha the maximum alpha value to use when drawing the spot shadow
+ * @param casterZValue the Z value of the caster RRect
+ * @param scaleFactor the scale needed to map from src-space to device-space
+ * @param clipRR the oval or rect with which the drawn roundrect must be intersected
+ * @param canvas the destination for the shadow draws
+ */
+static void DrawRRectShadowsWithClip(const SkRect& casterRect, SkScalar casterCornerRadius,
+ SkScalar ambientAlpha, SkScalar spotAlpha, SkScalar casterZValue, SkScalar scaleFactor,
+ const SkRRect& clipRR, SkCanvas* canvas) {
+ SkASSERT(cornerRadius >= 0.0f);
+
+ const bool isOval = casterCornerRadius >= std::max(SkScalarHalf(casterRect.width()),
+ SkScalarHalf(casterRect.height()));
+
+ if (ambientAlpha > 0.0f) {
+ static const float kHeightFactor = 1.0f / 128.0f;
+ static const float kGeomFactor = 64.0f;
+
+ const SkScalar srcSpaceAmbientRadius = casterZValue * kHeightFactor * kGeomFactor;
+ const SkScalar devSpaceAmbientRadius = srcSpaceAmbientRadius * scaleFactor;
+
+ const float umbraAlpha = 1.0f / (1.0f + std::max(casterZValue * kHeightFactor, 0.0f));
+ const SkScalar ambientOffset = srcSpaceAmbientRadius * umbraAlpha;
+
+ const SkRect srcSpaceAmbientRect = casterRect.makeOutset(ambientOffset, ambientOffset);
+ SkRect devSpaceAmbientRect;
+ canvas->getTotalMatrix().mapRect(&devSpaceAmbientRect, srcSpaceAmbientRect);
+
+ SkRRect devSpaceAmbientRRect;
+ if (isOval) {
+ devSpaceAmbientRRect = SkRRect::MakeOval(devSpaceAmbientRect);
+ } else {
+ const SkScalar devSpaceCornerRadius = scaleFactor * (casterCornerRadius + ambientOffset);
+ devSpaceAmbientRRect = SkRRect::MakeRectXY(devSpaceAmbientRect, devSpaceCornerRadius,
+ devSpaceCornerRadius);
+ }
+
+ const SkRect srcSpaceAmbClipRect = clipRR.rect().makeOutset(ambientOffset, ambientOffset);
+ SkRect devSpaceAmbClipRect;
+ canvas->getTotalMatrix().mapRect(&devSpaceAmbClipRect, srcSpaceAmbClipRect);
+ SkRRect devSpaceAmbientClipRR;
+ if (clipRR.isOval()) {
+ devSpaceAmbientClipRR = SkRRect::MakeOval(devSpaceAmbClipRect);
+ } else {
+ SkASSERT(clipRR.isRect());
+ devSpaceAmbientClipRR = SkRRect::MakeRect(devSpaceAmbClipRect);
+ }
+
+ SkRect cover = srcSpaceAmbClipRect;
+ if (!cover.intersect(srcSpaceAmbientRect)) {
+ return;
+ }
+
+ SkPaint paint;
+ paint.setColor(SkColorSetARGB((unsigned char) ambientAlpha, 0, 0, 0));
+ paint.setShader(SkRRectsGaussianEdgeShader::Make(devSpaceAmbientRRect,
+ devSpaceAmbientClipRR, devSpaceAmbientRadius));
+ canvas->drawRect(cover, paint);
+ }
+
+ if (spotAlpha > 0.0f) {
+ const Vector3 lightPos = SkiaFrameRenderer::getLightCenter();
+ float zRatio = casterZValue / (lightPos.z - casterZValue);
+ // clamp
+ if (zRatio < 0.0f) {
+ zRatio = 0.0f;
+ } else if (zRatio > 0.95f) {
+ zRatio = 0.95f;
+ }
+
+ const SkScalar lightWidth = SkiaFrameRenderer::getLightRadius();
+ const SkScalar srcSpaceSpotRadius = 2.0f * lightWidth * zRatio;
+ const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor;
+
+ // Compute the scale and translation for the spot shadow.
+ const SkScalar scale = lightPos.z / (lightPos.z - casterZValue);
+ const SkMatrix spotMatrix = SkMatrix::MakeScale(scale, scale);
+
+ SkRect srcSpaceScaledRect = casterRect;
+ spotMatrix.mapRect(&srcSpaceScaledRect);
+ srcSpaceScaledRect.outset(SkScalarHalf(srcSpaceSpotRadius),
+ SkScalarHalf(srcSpaceSpotRadius));
+
+ SkRRect srcSpaceSpotRRect;
+ if (isOval) {
+ srcSpaceSpotRRect = SkRRect::MakeOval(srcSpaceScaledRect);
+ } else {
+ srcSpaceSpotRRect = SkRRect::MakeRectXY(srcSpaceScaledRect, casterCornerRadius * scale,
+ casterCornerRadius * scale);
+ }
+
+ SkPoint center = SkPoint::Make(srcSpaceSpotRRect.rect().centerX(),
+ srcSpaceSpotRRect.rect().centerY());
+ SkMatrix ctmInverse;
+ if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
+ ALOGW("Matrix is degenerate. Will not render spot shadow!");
+ return;
+ }
+ SkPoint lightPos2D = SkPoint::Make(lightPos.x, lightPos.y);
+ ctmInverse.mapPoints(&lightPos2D, 1);
+ const SkPoint spotOffset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
+ zRatio*(center.fY - lightPos2D.fY));
+
+ SkAutoCanvasRestore acr(canvas, true);
+ canvas->translate(spotOffset.fX, spotOffset.fY);
+
+ SkRect devSpaceScaledRect;
+ canvas->getTotalMatrix().mapRect(&devSpaceScaledRect, srcSpaceScaledRect);
+
+ SkRRect devSpaceSpotRRect;
+ if (isOval) {
+ devSpaceSpotRRect = SkRRect::MakeOval(devSpaceScaledRect);
+ } else {
+ const SkScalar devSpaceScaledCornerRadius = casterCornerRadius * scale * scaleFactor;
+ devSpaceSpotRRect = SkRRect::MakeRectXY(devSpaceScaledRect, devSpaceScaledCornerRadius,
+ devSpaceScaledCornerRadius);
+ }
+
+ SkPaint paint;
+ paint.setColor(SkColorSetARGB((unsigned char) spotAlpha, 0, 0, 0));
+
+ SkRect srcSpaceScaledClipRect = clipRR.rect();
+ spotMatrix.mapRect(&srcSpaceScaledClipRect);
+ srcSpaceScaledClipRect.outset(SkScalarHalf(srcSpaceSpotRadius),
+ SkScalarHalf(srcSpaceSpotRadius));
+
+ SkRect devSpaceScaledClipRect;
+ canvas->getTotalMatrix().mapRect(&devSpaceScaledClipRect, srcSpaceScaledClipRect);
+ SkRRect devSpaceSpotClipRR;
+ if (clipRR.isOval()) {
+ devSpaceSpotClipRR = SkRRect::MakeOval(devSpaceScaledClipRect);
+ } else {
+ SkASSERT(clipRR.isRect());
+ devSpaceSpotClipRR = SkRRect::MakeRect(devSpaceScaledClipRect);
+ }
+
+ paint.setShader(SkRRectsGaussianEdgeShader::Make(devSpaceSpotRRect, devSpaceSpotClipRR,
+ devSpaceSpotRadius));
+
+ SkRect cover = srcSpaceScaledClipRect;
+ if (!cover.intersect(srcSpaceSpotRRect.rect())) {
+ return;
+ }
+
+ canvas->drawRect(cover, paint);
+ }
+}
+
+/**
+ * @param casterRect the rectangle bounds of the RRect casting the shadow
+ * @param casterCornerRadius the x&y radius for all the corners of the RRect casting the shadow
+ * @param casterClipRect a rectangular clip that must be intersected with the
+ * shadow-casting RRect prior to casting the shadow
+ * @param revealClip a circular clip that must be interested with the castClipRect
+ * and the shadow-casting rect prior to casting the shadow
+ * @param ambientAlpha the maximum alpha value to use when drawing the ambient shadow
+ * @param spotAlpha the maximum alpha value to use when drawing the spot shadow
+ * @param casterAlpha the alpha value of the RRect casting the shadow (0.0-1.0 range)
+ * @param casterZValue the Z value of the caster RRect
+ * @param canvas the destination for the shadow draws
+ *
+ * We have special cases for 4 round rect shadow draws:
+ * 1) a RRect clipped by a reveal animation
+ * 2) a RRect clipped by a rectangle
+ * 3) an unclipped RRect with non-uniform scale
+ * 4) an unclipped RRect with uniform scale
+ * 1,2 and 4 require that the scale is uniform.
+ * 1 and 2 require that rects stay rects.
+ */
+static bool DrawShadowsAsRRects(const SkRect& casterRect, SkScalar casterCornerRadius,
+ const SkRect& casterClipRect, const RevealClip& revealClip, SkScalar ambientAlpha,
+ SkScalar spotAlpha, SkScalar casterAlpha, SkScalar casterZValue, SkCanvas* canvas) {
+ SkScalar scaleFactors[2];
+ if (!canvas->getTotalMatrix().getMinMaxScales(scaleFactors)) {
+ ALOGW("Matrix is degenerate. Will not render shadow!");
+ return false;
+ }
+
+ // The casterClipRect will contain the casterRect when bounds clipping is disabled
+ bool casterIsClippedByRect = !casterClipRect.contains(casterRect);
+ bool uniformScale = scaleFactors[0] == scaleFactors[1];
+
+ if (revealClip.willClip()) {
+ if (casterIsClippedByRect || !uniformScale || !canvas->getTotalMatrix().rectStaysRect()) {
+ return false; // Fall back to the slow path since PathOps are required
+ }
+
+ const float revealRadius = revealClip.getRadius();
+ SkRect revealClipRect = SkRect::MakeLTRB(revealClip.getX()-revealRadius,
+ revealClip.getY()-revealRadius, revealClip.getX()+revealRadius,
+ revealClip.getY()+revealRadius);
+ SkRRect revealClipRR = SkRRect::MakeOval(revealClipRect);
+
+ DrawRRectShadowsWithClip(casterRect, casterCornerRadius, ambientAlpha, spotAlpha,
+ casterZValue, scaleFactors[0], revealClipRR, canvas);
+ return true;
+ }
+
+ if (casterIsClippedByRect) {
+ if (!uniformScale || !canvas->getTotalMatrix().rectStaysRect()) {
+ return false; // Fall back to the slow path since PathOps are required
+ }
+
+ SkRRect casterClipRR = SkRRect::MakeRect(casterClipRect);
+
+ DrawRRectShadowsWithClip(casterRect, casterCornerRadius, ambientAlpha, spotAlpha,
+ casterZValue, scaleFactors[0], casterClipRR, canvas);
+ return true;
+ }
+
+ // The fast path needs uniform scale
+ if (!uniformScale) {
+ SkRRect casterRR = SkRRect::MakeRectXY(casterRect, casterCornerRadius, casterCornerRadius);
+ DrawAmbientShadowGeneral(canvas, casterRR, casterZValue, ambientAlpha,
+ [&](const SkRRect& rrect, const SkPaint& paint) {
+ canvas->drawRRect(rrect, paint);
+ });
+ DrawSpotShadowGeneral(canvas, casterRR, casterZValue, spotAlpha,
+ [&](const SkRRect& rrect, const SkPaint& paint) {
+ canvas->drawRRect(rrect, paint);
+ });
+ return true;
+ }
+
+ DrawRRectShadows(casterRect, casterCornerRadius, ambientAlpha, spotAlpha, casterAlpha,
+ casterZValue, scaleFactors[0], canvas);
+ return true;
+}
+
+// copied from FrameBuilder::deferShadow
+void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable* caster) {
+ const RenderProperties& casterProperties = caster->getNodeProperties();
+
+ if (casterProperties.getAlpha() <= 0.0f
+ || casterProperties.getOutline().getAlpha() <= 0.0f
+ || !casterProperties.getOutline().getPath()
+ || casterProperties.getScaleX() == 0
+ || casterProperties.getScaleY() == 0) {
+ // no shadow to draw
+ return;
+ }
+
+ const SkScalar casterAlpha = casterProperties.getAlpha()
+ * casterProperties.getOutline().getAlpha();
+ if (casterAlpha <= 0.0f) {
+ return;
+ }
+
+ float ambientAlpha = SkiaFrameRenderer::getAmbientShadowAlpha()*casterAlpha;
+ float spotAlpha = SkiaFrameRenderer::getSpotShadowAlpha()*casterAlpha;
+ const float casterZValue = casterProperties.getZ();
+
+ const RevealClip& revealClip = casterProperties.getRevealClip();
+ const SkPath* revealClipPath = revealClip.getPath();
+ if (revealClipPath && revealClipPath->isEmpty()) {
+ // An empty reveal clip means nothing is drawn
+ return;
+ }
+
+ bool clippedToBounds = casterProperties.getClippingFlags() & CLIP_TO_CLIP_BOUNDS;
+
+ SkRect casterClipRect = SkRect::MakeLargest();
+ if (clippedToBounds) {
+ Rect clipBounds;
+ casterProperties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds);
+ casterClipRect = clipBounds.toSkRect();
+ }
+
+ SkAutoCanvasRestore acr(canvas, true);
+
+ SkMatrix shadowMatrix;
+ mat4 hwuiMatrix(caster->getRecordedMatrix());
+ // TODO we don't pass the optional boolean to treat it as a 4x4 matrix
+ caster->getRenderNode()->applyViewPropertyTransforms(hwuiMatrix);
+ hwuiMatrix.copyTo(shadowMatrix);
+ canvas->concat(shadowMatrix);
+
+ const Outline& casterOutline = casterProperties.getOutline();
+ Rect possibleRect;
+ float radius;
+ if (casterOutline.getAsRoundRect(&possibleRect, &radius)) {
+ if (DrawShadowsAsRRects(possibleRect.toSkRect(), radius, casterClipRect, revealClip,
+ ambientAlpha, spotAlpha, casterAlpha, casterZValue, canvas)) {
+ return;
+ }
+ }
+
+ // Hard cases and calls to general shadow code
+ const SkPath* casterOutlinePath = casterProperties.getOutline().getPath();
+
+ // holds temporary SkPath to store the result of intersections
+ SkPath tmpPath;
+ const SkPath* casterPath = casterOutlinePath;
+
+ // TODO: In to following course of code that calculates the final shape, is there an optimal
+ // of doing the Op calculations?
+ // intersect the shadow-casting path with the reveal, if present
+ if (revealClipPath) {
+ Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, &tmpPath);
+ casterPath = &tmpPath;
+ }
+
+ // intersect the shadow-casting path with the clipBounds, if present
+ if (clippedToBounds) {
+ SkPath clipBoundsPath;
+ clipBoundsPath.addRect(casterClipRect);
+ Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, &tmpPath);
+ casterPath = &tmpPath;
+ }
+
+ DrawAmbientShadowGeneral(canvas, *casterPath, casterZValue, ambientAlpha,
+ [&](const SkPath& path, const SkPaint& paint) {
+ canvas->drawPath(path, paint);
+ });
+
+ DrawSpotShadowGeneral(canvas, *casterPath, casterZValue, spotAlpha,
+ [&](const SkPath& path, const SkPaint& paint) {
+ canvas->drawPath(path, paint);
+ });
+}
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h
new file mode 100644
index 0000000..298a732
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 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 "RenderNodeDrawable.h"
+
+#include <SkCanvas.h>
+#include <SkDrawable.h>
+#include <utils/FatVector.h>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaDisplayList;
+class EndReorderBarrierDrawable;
+
+/**
+ * StartReorderBarrierDrawable and EndReorderBarrierDrawable work together to define
+ * a sub-list in a display list that need to be drawn out-of-order sorted instead by render
+ * node Z index.
+ * StartReorderBarrierDrawable will sort the entire range and it will draw
+ * render nodes in the range with negative Z index.
+ */
+class StartReorderBarrierDrawable : public SkDrawable {
+public:
+ explicit StartReorderBarrierDrawable(SkiaDisplayList* data);
+
+protected:
+ virtual SkRect onGetBounds() override {
+ return SkRect::MakeLargest();
+ }
+ virtual void onDraw(SkCanvas* canvas) override;
+
+private:
+ size_t mEndChildIndex;
+ size_t mBeginChildIndex;
+ FatVector<RenderNodeDrawable*, 16> mChildren;
+ SkiaDisplayList* mDisplayList;
+
+ friend class EndReorderBarrierDrawable;
+};
+
+/**
+ * See StartReorderBarrierDrawable.
+ * EndReorderBarrierDrawable relies on StartReorderBarrierDrawable to host and sort the render
+ * nodes by Z index. When EndReorderBarrierDrawable is drawn it will draw all render nodes in the
+ * range with positive Z index. It is also responsible for drawing shadows for the nodes
+ * corresponding to their z-index.
+ */
+class EndReorderBarrierDrawable : public SkDrawable {
+public:
+ explicit EndReorderBarrierDrawable(StartReorderBarrierDrawable* startBarrier);
+protected:
+ virtual SkRect onGetBounds() override {
+ return SkRect::MakeLargest();
+ }
+ virtual void onDraw(SkCanvas* canvas) override;
+private:
+ void drawShadow(SkCanvas* canvas, RenderNodeDrawable* caster);
+ StartReorderBarrierDrawable* mStartBarrier;
+};
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
similarity index 98%
rename from libs/hwui/SkiaDisplayList.cpp
rename to libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index d10f306..c734097e 100644
--- a/libs/hwui/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -24,6 +24,7 @@
namespace android {
namespace uirenderer {
+namespace skiapipeline {
SkiaDisplayList::SkiaDisplayList(SkRect bounds) : mDrawable(SkLiteDL::New(bounds)) {
SkASSERT(projectionReceiveIndex == -1);
@@ -130,5 +131,6 @@
new (&allocator) LinearAllocator();
}
+}; // namespace skiapipeline
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
similarity index 97%
rename from libs/hwui/SkiaDisplayList.h
rename to libs/hwui/pipeline/skia/SkiaDisplayList.h
index c8a82bd..734aae4a 100644
--- a/libs/hwui/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -17,7 +17,8 @@
#pragma once
#include "DisplayList.h"
-#include "SkiaDrawables.h"
+#include "GLFunctorDrawable.h"
+#include "RenderNodeDrawable.h"
#include <deque>
#include <SkLiteDL.h>
@@ -25,6 +26,7 @@
namespace android {
namespace uirenderer {
+namespace skiapipeline {
/**
* This class is intended to be self contained, but still subclasses from
@@ -148,5 +150,6 @@
bool mPinnedImages = false;
};
+}; // namespace skiapipeline
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaFrameRenderer.h b/libs/hwui/pipeline/skia/SkiaFrameRenderer.h
new file mode 100644
index 0000000..70207c1
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaFrameRenderer.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 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
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+/**
+ * TODO: this is a stub that will be added in a subsquent CL
+ */
+class SkiaFrameRenderer {
+public:
+
+ static bool skpCaptureEnabled() { return false; }
+
+ // TODO avoids unused compile error but we need to pass this to the reorder drawables!
+ static float getLightRadius() {
+ return 1.0f;
+ }
+
+ static uint8_t getAmbientShadowAlpha() {
+ return 1;
+ }
+
+ static uint8_t getSpotShadowAlpha() {
+ return 1;
+ }
+
+ static Vector3 getLightCenter() {
+ Vector3 result;
+ result.x = result.y = result.z = 1.0f;
+ return result;
+ }
+
+};
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
new file mode 100644
index 0000000..8a42983
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2016 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 "SkiaRecordingCanvas.h"
+
+#include "Layer.h"
+#include "RenderNode.h"
+#include "LayerDrawable.h"
+#include "NinePatchUtils.h"
+#include "pipeline/skia/AnimatedDrawables.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+// ----------------------------------------------------------------------------
+// Recording Canvas Setup
+// ----------------------------------------------------------------------------
+
+void SkiaRecordingCanvas::initDisplayList(uirenderer::RenderNode* renderNode, int width,
+ int height) {
+ mBarrierPending = false;
+ mCurrentBarrier = nullptr;
+ SkASSERT(mDisplayList.get() == nullptr);
+
+ if (renderNode) {
+ mDisplayList = renderNode->detachAvailableList();
+ }
+ SkRect bounds = SkRect::MakeWH(width, height);
+ if (mDisplayList) {
+ mDisplayList->reset(nullptr, bounds);
+ } else {
+ mDisplayList.reset(new SkiaDisplayList(bounds));
+ }
+
+ mRecorder.reset(mDisplayList->mDrawable.get());
+ SkiaCanvas::reset(&mRecorder);
+}
+
+uirenderer::DisplayList* SkiaRecordingCanvas::finishRecording() {
+ // close any existing chunks if necessary
+ insertReorderBarrier(false);
+ mRecorder.restoreToCount(1);
+ return mDisplayList.release();
+}
+
+// ----------------------------------------------------------------------------
+// Recording Canvas draw operations: View System
+// ----------------------------------------------------------------------------
+
+void SkiaRecordingCanvas::drawRoundRect(uirenderer::CanvasPropertyPrimitive* left,
+ uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right,
+ uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx,
+ uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* paint) {
+ drawDrawable(mDisplayList->allocateDrawable<AnimatedRoundRect>(left, top, right, bottom,
+ rx, ry, paint));
+}
+
+void SkiaRecordingCanvas::drawCircle(uirenderer::CanvasPropertyPrimitive* x,
+ uirenderer::CanvasPropertyPrimitive* y, uirenderer::CanvasPropertyPrimitive* radius,
+ uirenderer::CanvasPropertyPaint* paint) {
+ drawDrawable(mDisplayList->allocateDrawable<AnimatedCircle>(x, y, radius, paint));
+}
+
+void SkiaRecordingCanvas::insertReorderBarrier(bool enableReorder) {
+ mBarrierPending = enableReorder;
+
+ if (nullptr != mCurrentBarrier) {
+ // finish off the existing chunk
+ SkDrawable* drawable =
+ mDisplayList->allocateDrawable<EndReorderBarrierDrawable>(
+ mCurrentBarrier);
+ mCurrentBarrier = nullptr;
+ drawDrawable(drawable);
+ }
+}
+
+void SkiaRecordingCanvas::drawLayer(uirenderer::DeferredLayerUpdater* layerUpdater) {
+ if (layerUpdater != nullptr && layerUpdater->backingLayer() != nullptr) {
+ uirenderer::Layer* layer = layerUpdater->backingLayer();
+ sk_sp<SkDrawable> drawable(new LayerDrawable(layer));
+ drawDrawable(drawable.get());
+ }
+}
+
+void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) {
+ // lazily create the chunk if needed
+ if (mBarrierPending) {
+ mCurrentBarrier = (StartReorderBarrierDrawable*)
+ mDisplayList->allocateDrawable<StartReorderBarrierDrawable>(
+ mDisplayList.get());
+ drawDrawable(mCurrentBarrier);
+ mBarrierPending = false;
+ }
+
+ // record the child node
+ mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas());
+ drawDrawable(&mDisplayList->mChildNodes.back());
+
+ // use staging property, since recording on UI thread
+ if (renderNode->stagingProperties().isProjectionReceiver()) {
+ mDisplayList->mIsProjectionReceiver = true;
+ // set projectionReceiveIndex so that RenderNode.hasProjectionReceiver returns true
+ mDisplayList->projectionReceiveIndex = mDisplayList->mChildNodes.size() - 1;
+ }
+}
+
+void SkiaRecordingCanvas::callDrawGLFunction(Functor* functor,
+ uirenderer::GlFunctorLifecycleListener* listener) {
+ mDisplayList->mChildFunctors.emplace_back(functor, listener, asSkCanvas());
+ drawDrawable(&mDisplayList->mChildFunctors.back());
+}
+
+class VectorDrawable : public SkDrawable {
+ public:
+ VectorDrawable(VectorDrawableRoot* tree) : mRoot(tree) {}
+
+ protected:
+ virtual SkRect onGetBounds() override {
+ return SkRect::MakeLargest();
+ }
+ virtual void onDraw(SkCanvas* canvas) override {
+ Bitmap& hwuiBitmap = mRoot->getBitmapUpdateIfDirty();
+ SkBitmap bitmap;
+ hwuiBitmap.getSkBitmap(&bitmap);
+ SkPaint* paint = mRoot->getPaint();
+ canvas->drawBitmapRect(bitmap, mRoot->mutateProperties()->getBounds(), paint);
+ /*
+ * TODO we can draw this directly but need to address the following...
+ *
+ * 1) Add drawDirect(SkCanvas*) to VectorDrawableRoot
+ * 2) fix VectorDrawable.cpp's Path::draw to not make a temporary path
+ * so that we don't break caching
+ * 3) figure out how to set path's as volatile during animation
+ * 4) if mRoot->getPaint() != null either promote to layer (during
+ * animation) or cache in SkSurface (for static content)
+ *
+ */
+ }
+
+ private:
+ sp<VectorDrawableRoot> mRoot;
+};
+
+void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
+ drawDrawable(mDisplayList->allocateDrawable<VectorDrawable>(tree));
+ mDisplayList->mVectorDrawables.push_back(tree);
+}
+
+// ----------------------------------------------------------------------------
+// Recording Canvas draw operations: Bitmaps
+// ----------------------------------------------------------------------------
+
+inline static const SkPaint* nonAAPaint(const SkPaint* origPaint, SkPaint* tmpPaint) {
+ if (origPaint && origPaint->isAntiAlias()) {
+ *tmpPaint = *origPaint;
+ tmpPaint->setAntiAlias(false);
+ return tmpPaint;
+ } else {
+ return origPaint;
+ }
+}
+
+void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) {
+ SkBitmap skBitmap;
+ bitmap.getSkBitmap(&skBitmap);
+
+ sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode);
+ if (!skBitmap.isImmutable()) {
+ mDisplayList->mMutableImages.push_back(image.get());
+ }
+ SkPaint tmpPaint;
+ mRecorder.drawImage(image, left, top, nonAAPaint(paint, &tmpPaint));
+}
+
+void SkiaRecordingCanvas::drawBitmap(Bitmap& hwuiBitmap, const SkMatrix& matrix,
+ const SkPaint* paint) {
+ SkBitmap bitmap;
+ hwuiBitmap.getSkBitmap(&bitmap);
+ SkAutoCanvasRestore acr(&mRecorder, true);
+ concat(matrix);
+ sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
+ if (!bitmap.isImmutable()) {
+ mDisplayList->mMutableImages.push_back(image.get());
+ }
+ SkPaint tmpPaint;
+ mRecorder.drawImage(image, 0, 0, nonAAPaint(paint, &tmpPaint));
+}
+
+void SkiaRecordingCanvas::drawBitmap(Bitmap& hwuiBitmap, float srcLeft, float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight,
+ float dstBottom, const SkPaint* paint) {
+ SkBitmap bitmap;
+ hwuiBitmap.getSkBitmap(&bitmap);
+ SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
+ SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
+ sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
+ if (!bitmap.isImmutable()) {
+ mDisplayList->mMutableImages.push_back(image.get());
+ }
+ SkPaint tmpPaint;
+ mRecorder.drawImageRect(image, srcRect, dstRect, nonAAPaint(paint, &tmpPaint));
+}
+
+void SkiaRecordingCanvas::drawNinePatch(Bitmap& hwuiBitmap, const Res_png_9patch& chunk,
+ float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) {
+ SkBitmap bitmap;
+ hwuiBitmap.getSkBitmap(&bitmap);
+
+ SkCanvas::Lattice lattice;
+ NinePatchUtils::SetLatticeDivs(&lattice, chunk, bitmap.width(), bitmap.height());
+
+ lattice.fFlags = nullptr;
+ int numFlags = 0;
+ if (chunk.numColors > 0 && chunk.numColors == NinePatchUtils::NumDistinctRects(lattice)) {
+ // We can expect the framework to give us a color for every distinct rect.
+ // Skia requires placeholder flags for degenerate rects.
+ numFlags = (lattice.fXCount + 1) * (lattice.fYCount + 1);
+ }
+
+ SkAutoSTMalloc<25, SkCanvas::Lattice::Flags> flags(numFlags);
+ if (numFlags > 0) {
+ NinePatchUtils::SetLatticeFlags(&lattice, flags.get(), numFlags, chunk);
+ }
+
+ lattice.fBounds = nullptr;
+ SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
+ sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
+ if (!bitmap.isImmutable()) {
+ mDisplayList->mMutableImages.push_back(image.get());
+ }
+
+ SkPaint tmpPaint;
+ mRecorder.drawImageLattice(image.get(), lattice, dst, nonAAPaint(paint, &tmpPaint));
+}
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
new file mode 100644
index 0000000..8aef97f
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2016 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 "SkiaCanvas.h"
+#include "SkiaDisplayList.h"
+#include "ReorderBarrierDrawables.h"
+#include <SkLiteRecorder.h>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+/**
+ * A SkiaCanvas implementation that records drawing operations for deferred rendering backed by a
+ * SkLiteRecorder and a SkiaDisplayList.
+ */
+class SkiaRecordingCanvas : public SkiaCanvas {
+ public:
+ explicit SkiaRecordingCanvas(uirenderer::RenderNode* renderNode, int width, int height) {
+ initDisplayList(renderNode, width, height);
+ }
+
+ virtual void setBitmap(const SkBitmap& bitmap) override {
+ LOG_ALWAYS_FATAL("DisplayListCanvas is not backed by a bitmap.");
+ }
+
+ virtual void resetRecording(int width, int height,
+ uirenderer::RenderNode* renderNode) override {
+ initDisplayList(renderNode, width, height);
+ }
+
+ virtual uirenderer::DisplayList* finishRecording() override;
+
+ virtual void drawBitmap(Bitmap& bitmap, float left, float top,
+ const SkPaint* paint) override;
+ virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix,
+ const SkPaint* paint) override;
+ virtual void drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop,
+ float dstRight, float dstBottom, const SkPaint* paint) override;
+ virtual void drawNinePatch(Bitmap& hwuiBitmap, const android::Res_png_9patch& chunk,
+ float dstLeft, float dstTop, float dstRight, float dstBottom,
+ const SkPaint* paint) override;
+
+ virtual void drawRoundRect(uirenderer::CanvasPropertyPrimitive* left,
+ uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right,
+ uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx,
+ uirenderer::CanvasPropertyPrimitive* ry,
+ uirenderer::CanvasPropertyPaint* paint) override;
+ virtual void drawCircle(uirenderer::CanvasPropertyPrimitive* x,
+ uirenderer::CanvasPropertyPrimitive* y, uirenderer::CanvasPropertyPrimitive* radius,
+ uirenderer::CanvasPropertyPaint* paint) override;
+
+ virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
+
+ virtual void insertReorderBarrier(bool enableReorder) override;
+ virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override;
+ virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override;
+ virtual void callDrawGLFunction(Functor* functor,
+ uirenderer::GlFunctorLifecycleListener* listener) override;
+
+private:
+ SkLiteRecorder mRecorder;
+ std::unique_ptr<SkiaDisplayList> mDisplayList;
+ bool mBarrierPending;
+ StartReorderBarrierDrawable* mCurrentBarrier;
+
+ /**
+ * A new SkiaDisplayList is created or recycled if available.
+ *
+ * @param renderNode is optional and used to recycle an old display list.
+ * @param width used to calculate recording bounds.
+ * @param height used to calculate recording bounds.
+ */
+ void initDisplayList(uirenderer::RenderNode* renderNode, int width, int height);
+};
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 5b9b003..9530c79 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -125,5 +125,44 @@
return utf16;
}
+SkColor TestUtils::getColor(const sk_sp<SkSurface>& surface, int x, int y) {
+ SkPixmap pixmap;
+ if (!surface->peekPixels(&pixmap)) {
+ return 0;
+ }
+ switch (pixmap.colorType()) {
+ case kGray_8_SkColorType: {
+ const uint8_t* addr = pixmap.addr8(x, y);
+ return SkColorSetRGB(*addr, *addr, *addr);
+ }
+ case kAlpha_8_SkColorType: {
+ const uint8_t* addr = pixmap.addr8(x, y);
+ return SkColorSetA(0, addr[0]);
+ }
+ case kRGB_565_SkColorType: {
+ const uint16_t* addr = pixmap.addr16(x, y);
+ return SkPixel16ToColor(addr[0]);
+ }
+ case kARGB_4444_SkColorType: {
+ const uint16_t* addr = pixmap.addr16(x, y);
+ SkPMColor c = SkPixel4444ToPixel32(addr[0]);
+ return SkUnPreMultiply::PMColorToColor(c);
+ }
+ case kBGRA_8888_SkColorType: {
+ const uint32_t* addr = pixmap.addr32(x, y);
+ SkPMColor c = SkSwizzle_BGRA_to_PMColor(addr[0]);
+ return SkUnPreMultiply::PMColorToColor(c);
+ }
+ case kRGBA_8888_SkColorType: {
+ const uint32_t* addr = pixmap.addr32(x, y);
+ SkPMColor c = SkSwizzle_RGBA_to_PMColor(addr[0]);
+ return SkUnPreMultiply::PMColorToColor(c);
+ }
+ default:
+ return 0;
+ }
+ return 0;
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index f6fe7d2..0be5a3b 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -260,6 +260,8 @@
int mLastMode = -1;
};
+ static SkColor getColor(const sk_sp<SkSurface>& surface, int x, int y);
+
private:
static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
node->syncProperties();
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
new file mode 100644
index 0000000..19c311c
--- /dev/null
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2016 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 <VectorDrawable.h>
+
+#include "AnimationContext.h"
+#include "DamageAccumulator.h"
+#include "IContextFactory.h"
+#include "pipeline/skia/SkiaDisplayList.h"
+#include "pipeline/skia/SkiaRecordingCanvas.h"
+#include "renderthread/CanvasContext.h"
+#include "tests/common/TestUtils.h"
+#include "SkiaCanvas.h"
+#include <SkLiteRecorder.h>
+#include <string.h>
+
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+using namespace android::uirenderer::skiapipeline;
+
+static sp<RenderNode> createSkiaNode(int left, int top, int right, int bottom,
+ std::function<void(RenderProperties& props, SkiaRecordingCanvas& canvas)> setup,
+ const char* name = nullptr, SkiaDisplayList* displayList = nullptr) {
+#if HWUI_NULL_GPU
+ // if RenderNodes are being sync'd/used, device info will be needed, since
+ // DeviceInfo::maxTextureSize() affects layer property
+ DeviceInfo::initialize();
+#endif
+ sp<RenderNode> node = new RenderNode();
+ if (name) {
+ node->setName(name);
+ }
+ RenderProperties& props = node->mutateStagingProperties();
+ props.setLeftTopRightBottom(left, top, right, bottom);
+ if (displayList) {
+ node->setStagingDisplayList(displayList, nullptr);
+ }
+ if (setup) {
+ std::unique_ptr<SkiaRecordingCanvas> canvas(new SkiaRecordingCanvas(nullptr,
+ props.getWidth(), props.getHeight()));
+ setup(props, *canvas.get());
+ node->setStagingDisplayList(canvas->finishRecording(), nullptr);
+ }
+ node->setPropertyFieldsDirty(0xFFFFFFFF);
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+ return node;
+}
+
+TEST(RenderNodeDrawable, create) {
+ auto rootNode = TestUtils::createNode(0, 0, 200, 400,
+ [](RenderProperties& props, Canvas& canvas) {
+ canvas.drawColor(Color::Red_500, SkBlendMode::kSrcOver);
+ });
+
+ auto skLiteDL = SkLiteDL::New(SkRect::MakeWH(1, 1));
+ SkLiteRecorder canvas;
+ canvas.reset(skLiteDL.get());
+ canvas.translate(100, 100);
+ RenderNodeDrawable drawable(rootNode.get(), &canvas);
+
+ ASSERT_EQ(drawable.getRenderNode(), rootNode.get());
+ ASSERT_EQ(&drawable.getNodeProperties(), &rootNode->properties());
+ ASSERT_EQ(drawable.getRecordedMatrix(), canvas.getTotalMatrix());
+}
+
+TEST(RenderNodeDrawable, drawContent) {
+ auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ SkCanvas& canvas = *surface->getCanvas();
+ canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
+
+ //create a RenderNodeDrawable backed by a RenderNode backed by a SkLiteRecorder
+ auto rootNode = createSkiaNode(0, 0, 1, 1,
+ [](RenderProperties& props, SkiaRecordingCanvas& recorder) {
+ recorder.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
+ });
+ RenderNodeDrawable drawable(rootNode.get(), &canvas, false);
+
+ //negative and positive Z order are drawn out of order
+ rootNode->animatorProperties().setElevation(10.0f);
+ canvas.drawDrawable(&drawable);
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
+ rootNode->animatorProperties().setElevation(-10.0f);
+ canvas.drawDrawable(&drawable);
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
+
+ //zero Z are drawn immediately
+ rootNode->animatorProperties().setElevation(0.0f);
+ canvas.drawDrawable(&drawable);
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
+}
+
+//TODO: another test that verifies equal z values are drawn in order, and barriers prevent Z
+//intermixing (model after FrameBuilder zReorder)
+TEST(RenderNodeDrawable, drawAndReorder) {
+ //this test exercises StartReorderBarrierDrawable, EndReorderBarrierDrawable and
+ //SkiaRecordingCanvas
+ auto surface = SkSurface::MakeRasterN32Premul(4, 4);
+ SkCanvas& canvas = *surface->getCanvas();
+
+ canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
+
+ //-z draws to all 4 pixels (RED)
+ auto redNode = createSkiaNode(0, 0, 4, 4,
+ [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
+ redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
+ props.setElevation(-10.0f);
+ }, "redNode");
+
+ //0z draws to bottom 2 pixels (GREEN)
+ auto bottomHalfGreenNode = createSkiaNode(0, 0, 4, 4,
+ [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
+ SkPaint greenPaint;
+ greenPaint.setColor(SK_ColorGREEN);
+ greenPaint.setStyle(SkPaint::kFill_Style);
+ bottomHalfGreenCanvas.drawRect(0, 2, 4, 4, greenPaint);
+ props.setElevation(0.0f);
+ }, "bottomHalfGreenNode");
+
+ //+z draws to right 2 pixels (BLUE)
+ auto rightHalfBlueNode = createSkiaNode(0, 0, 4, 4,
+ [](RenderProperties& props, SkiaRecordingCanvas& rightHalfBlueCanvas) {
+ SkPaint bluePaint;
+ bluePaint.setColor(SK_ColorBLUE);
+ bluePaint.setStyle(SkPaint::kFill_Style);
+ rightHalfBlueCanvas.drawRect(2, 0, 4, 4, bluePaint);
+ props.setElevation(10.0f);
+ }, "rightHalfBlueNode");
+
+ auto rootNode = createSkiaNode(0, 0, 4, 4,
+ [&](RenderProperties& props, SkiaRecordingCanvas& rootRecorder) {
+ rootRecorder.insertReorderBarrier(true);
+ //draw in reverse Z order, so Z alters draw order
+ rootRecorder.drawRenderNode(rightHalfBlueNode.get());
+ rootRecorder.drawRenderNode(bottomHalfGreenNode.get());
+ rootRecorder.drawRenderNode(redNode.get());
+ }, "rootNode");
+
+ RenderNodeDrawable drawable3(rootNode.get(), &canvas, false);
+ canvas.drawDrawable(&drawable3);
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 3), SK_ColorGREEN);
+ ASSERT_EQ(TestUtils::getColor(surface, 3, 3), SK_ColorBLUE);
+}
+
+TEST(RenderNodeDrawable, composeOnLayer)
+{
+ auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ SkCanvas& canvas = *surface->getCanvas();
+ canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
+
+ auto rootNode = createSkiaNode(0, 0, 1, 1,
+ [](RenderProperties& props, SkiaRecordingCanvas& recorder) {
+ recorder.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
+ });
+
+ //attach a layer to the render node
+ auto surfaceLayer = SkSurface::MakeRasterN32Premul(1, 1);
+ auto canvas2 = surfaceLayer->getCanvas();
+ canvas2->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
+ rootNode->setLayerSurface( surfaceLayer );
+
+ RenderNodeDrawable drawable1(rootNode.get(), &canvas, false);
+ canvas.drawDrawable(&drawable1);
+ ASSERT_EQ(SK_ColorRED, TestUtils::getColor(surface, 0, 0));
+
+ RenderNodeDrawable drawable2(rootNode.get(), &canvas, true);
+ canvas.drawDrawable(&drawable2);
+ ASSERT_EQ(SK_ColorWHITE, TestUtils::getColor(surface, 0, 0));
+
+ RenderNodeDrawable drawable3(rootNode.get(), &canvas, false);
+ canvas.drawDrawable(&drawable3);
+ ASSERT_EQ(SK_ColorRED, TestUtils::getColor(surface, 0, 0));
+
+ rootNode->setLayerSurface( sk_sp<SkSurface>() );
+}
+
+//TODO: refactor to cover test cases from FrameBuilderTests_projectionReorder
+//validate with bounds and projection path mask.
+//TODO: research if we could hook in and mock/validate different aspects of the drawing,
+//instead of validating pixels
+TEST(RenderNodeDrawable, projectDraw) {
+ auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ SkCanvas& canvas = *surface->getCanvas();
+ canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
+
+ auto redNode = createSkiaNode(0, 0, 1, 1,
+ [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
+ redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
+ }, "redNode");
+
+ auto greenNodeWithRedChild = createSkiaNode(0, 0, 1, 1,
+ [&](RenderProperties& props, SkiaRecordingCanvas& greenCanvasWithRedChild) {
+ greenCanvasWithRedChild.drawRenderNode(redNode.get());
+ greenCanvasWithRedChild.drawColor(SK_ColorGREEN, SkBlendMode::kSrcOver);
+ }, "greenNodeWithRedChild");
+
+ auto rootNode = createSkiaNode(0, 0, 1, 1,
+ [&](RenderProperties& props, SkiaRecordingCanvas& rootCanvas) {
+ rootCanvas.drawRenderNode(greenNodeWithRedChild.get());
+ }, "rootNode");
+ SkiaDisplayList* rootDisplayList = static_cast<SkiaDisplayList*>(
+ (const_cast<DisplayList*>(rootNode->getDisplayList())));
+
+ RenderNodeDrawable rootDrawable(rootNode.get(), &canvas, false);
+ canvas.drawDrawable(&rootDrawable);
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorGREEN);
+
+ //project redNode on rootNode, which will change the test outcome,
+ //because redNode will draw after greenNodeWithRedChild
+ rootDisplayList->mIsProjectionReceiver = true;
+ redNode->animatorProperties().setProjectBackwards(true);
+ canvas.drawDrawable(&rootDrawable);
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
+}
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 72029b9..fe6cea6 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -20,14 +20,14 @@
#include "AnimationContext.h"
#include "DamageAccumulator.h"
#include "IContextFactory.h"
-#include "SkiaDisplayList.h"
+#include "pipeline/skia/SkiaDisplayList.h"
#include "renderthread/CanvasContext.h"
#include "tests/common/TestUtils.h"
-
using namespace android;
using namespace android::uirenderer;
using namespace android::uirenderer::renderthread;
+using namespace android::uirenderer::skiapipeline;
TEST(SkiaDisplayList, create) {
SkRect bounds = SkRect::MakeWH(200, 200);