diff options
author | 2016-10-17 16:26:15 -0400 | |
---|---|---|
committer | 2016-10-31 14:27:02 -0400 | |
commit | 021693b967a2c5556dddd183eb0247df4079e1ad (patch) | |
tree | 162c1da3b5fad315aa0591f16e3f66b899e1b6cc /libs | |
parent | 99449eea6cfe174eba269b3cfff06e6533d6314e (diff) |
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
Diffstat (limited to 'libs')
24 files changed, 2320 insertions, 223 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 4fe866f2b5aa..06eb829e22e3 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -18,6 +18,12 @@ hwui_src_files := \ 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 @@ hwui_src_files := \ ShadowTessellator.cpp \ SkiaCanvas.cpp \ SkiaCanvasProxy.cpp \ - SkiaDisplayList.cpp \ SkiaShader.cpp \ Snapshot.cpp \ SpotShadow.cpp \ @@ -169,6 +174,7 @@ endef 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 @@ LOCAL_SRC_FILES += \ 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 7a271b7ce03a..e989a4680a60 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 void SetLatticeDivs(SkCanvas::Lattice* lattice, const Res_png_9pat } } +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 a05c744c4a56..3819c5e9187d 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 DisplayListOp; class FrameBuilder; class OffscreenBuffer; class Rect; -class SkiaDisplayList; class SkiaShader; struct RenderNodeOp; @@ -61,6 +60,10 @@ namespace proto { 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 @@ public: * 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 @@ private: * 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 c48b4dcc83e5..b6ac48f2edc5 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 @@ SkiaCanvas::SkiaCanvas(const SkBitmap& bitmap) { mCanvas.reset(new SkCanvas(bitmap)); } +SkiaCanvas::~SkiaCanvas() {} + void SkiaCanvas::reset(SkCanvas* skiaCanvas) { mCanvas.reset(SkRef(skiaCanvas)); mSaveStack.reset(nullptr); @@ -671,62 +674,6 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& hwuiBitmap, int meshWidth, int meshHeigh 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 @@ void SkiaCanvas::drawNinePatch(Bitmap& hwuiBitmap, const Res_png_9patch& chunk, 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 @@ void SkiaCanvas::drawNinePatch(Bitmap& hwuiBitmap, const Res_png_9patch& chunk, 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 @@ void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, // 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 d1edff98eac6..a0cdfcbfeab7 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -40,6 +40,8 @@ public: */ 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 a1ceeaa0afb4..000000000000 --- 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 000000000000..44c494f77231 --- /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 000000000000..fb2134c51e22 --- /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 000000000000..bf39dadbfcc5 --- /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 000000000000..f8a181fa385e --- /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 000000000000..91e274475b34 --- /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 000000000000..cefa893f2864 --- /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 000000000000..0762f37ff591 --- /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 000000000000..8d77938ad1b9 --- /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 000000000000..298a7320df64 --- /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 index d10f306ad6d1..c734097e12c6 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 @@ void SkiaDisplayList::reset(GrContext* context, SkRect bounds) { new (&allocator) LinearAllocator(); } +}; // namespace skiapipeline }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h index c8a82bd7f57c..734aae4a968e 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 @@ private: 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 000000000000..70207c1280ab --- /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 000000000000..8a429839377c --- /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 000000000000..8aef97f615ce --- /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 5b9b003b4715..9530c793e4e4 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -125,5 +125,44 @@ std::unique_ptr<uint16_t[]> TestUtils::asciiToUtf16(const char* str) { 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 f6fe7d267354..0be5a3b9d439 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -260,6 +260,8 @@ public: 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 000000000000..19c311c7e182 --- /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 72029b9ccfc7..fe6cea6dfb4b 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); |