Implement SkiaRecordingCanvas, RenderNodeDrawable and other drawables.
Implement SkiaRecordingCanvas, RenderNodeDrawable, GLFunctorDrawable,
LayerDrawable, StartReorderBarrierDrawable, EndReorderBarrierDrawable.
Move AnimatedRoundRect and AnimatedCircle in a separate file.
All Skia pipeline files are moved in hwui/pipeline/skia folder.
Add unit tests for RenderNodeDrawable, StartReorderBarrierDrawable,
EndReorderBarrierDrawable and SkiaRecordingCanvas.
Test: I tested manually on 6P devices and did run the unit tests.
Change-Id: If2a347bd1fc4689953822294ce5bf98c7f3f57c7
diff --git a/libs/hwui/pipeline/skia/AnimatedDrawables.h b/libs/hwui/pipeline/skia/AnimatedDrawables.h
new file mode 100644
index 0000000..44c494f
--- /dev/null
+++ b/libs/hwui/pipeline/skia/AnimatedDrawables.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "CanvasProperty.h"
+#include <utils/RefBase.h>
+#include <SkCanvas.h>
+#include <SkDrawable.h>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class AnimatedRoundRect : public SkDrawable {
+public:
+ AnimatedRoundRect(uirenderer::CanvasPropertyPrimitive* left,
+ uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right,
+ uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx,
+ uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* p)
+ : mLeft(left)
+ , mTop(top)
+ , mRight(right)
+ , mBottom(bottom)
+ , mRx(rx)
+ , mRy(ry)
+ , mPaint(p) {}
+
+protected:
+ virtual SkRect onGetBounds() override {
+ return SkRect::MakeLTRB(mLeft->value, mTop->value, mRight->value, mBottom->value);
+ }
+ virtual void onDraw(SkCanvas* canvas) override {
+ SkRect rect = SkRect::MakeLTRB(mLeft->value, mTop->value, mRight->value, mBottom->value);
+ canvas->drawRoundRect(rect, mRx->value, mRy->value, mPaint->value);
+ }
+
+private:
+ sp<uirenderer::CanvasPropertyPrimitive> mLeft;
+ sp<uirenderer::CanvasPropertyPrimitive> mTop;
+ sp<uirenderer::CanvasPropertyPrimitive> mRight;
+ sp<uirenderer::CanvasPropertyPrimitive> mBottom;
+ sp<uirenderer::CanvasPropertyPrimitive> mRx;
+ sp<uirenderer::CanvasPropertyPrimitive> mRy;
+ sp<uirenderer::CanvasPropertyPaint> mPaint;
+};
+
+class AnimatedCircle : public SkDrawable {
+public:
+ AnimatedCircle(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* y,
+ uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint)
+ : mX(x)
+ , mY(y)
+ , mRadius(radius)
+ , mPaint(paint) {}
+
+protected:
+ virtual SkRect onGetBounds() override {
+ const float x = mX->value;
+ const float y = mY->value;
+ const float radius = mRadius->value;
+ return SkRect::MakeLTRB(x - radius, y - radius, x + radius, y + radius);
+ }
+ virtual void onDraw(SkCanvas* canvas) override {
+ canvas->drawCircle(mX->value, mY->value, mRadius->value, mPaint->value);
+ }
+
+private:
+ sp<uirenderer::CanvasPropertyPrimitive> mX;
+ sp<uirenderer::CanvasPropertyPrimitive> mY;
+ sp<uirenderer::CanvasPropertyPrimitive> mRadius;
+ sp<uirenderer::CanvasPropertyPaint> mPaint;
+};
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
new file mode 100644
index 0000000..fb2134c
--- /dev/null
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GLFunctorDrawable.h"
+#include "GlFunctorLifecycleListener.h"
+#include "RenderNode.h"
+#include "SkClipStack.h"
+#include <private/hwui/DrawGlInfo.h>
+#include <SkPath.h>
+#include <GrContext.h>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+GLFunctorDrawable::~GLFunctorDrawable() {
+ if(mListener.get() != nullptr) {
+ mListener->onGlFunctorReleased(mFunctor);
+ }
+}
+
+void GLFunctorDrawable::syncFunctor() const {
+ (*mFunctor)(DrawGlInfo::kModeSync, nullptr);
+}
+
+static void setScissor(int viewportHeight, const SkIRect& clip) {
+ SkASSERT(!clip.isEmpty());
+ // transform to Y-flipped GL space, and prevent negatives
+ GLint y = viewportHeight - clip.fBottom;
+ GLint height = (viewportHeight - clip.fTop) - y;
+ glScissor(clip.fLeft, y, clip.width(), height);
+}
+
+void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
+ if (canvas->getGrContext() == nullptr) {
+ SkDEBUGF(("Attempting to draw GLFunctor into an unsupported surface"));
+ return;
+ }
+
+ canvas->flush();
+
+ SkImageInfo canvasInfo = canvas->imageInfo();
+ SkMatrix44 mat4(canvas->getTotalMatrix());
+
+ SkIRect ibounds;
+ canvas->getClipDeviceBounds(&ibounds);
+
+ DrawGlInfo info;
+ info.clipLeft = ibounds.fLeft;
+ info.clipTop = ibounds.fTop;
+ info.clipRight = ibounds.fRight;
+ info.clipBottom = ibounds.fBottom;
+ // info.isLayer = hasLayer();
+ info.isLayer = false;
+ info.width = canvasInfo.width();
+ info.height = canvasInfo.height();
+ mat4.asColMajorf(&info.transform[0]);
+
+ //apply a simple clip with a scissor or a complex clip with a stencil
+ SkRegion clipRegion;
+ SkPath path;
+ canvas->getClipStack()->asPath(&path);
+ clipRegion.setPath(path, SkRegion(ibounds));
+ if (CC_UNLIKELY(clipRegion.isComplex())) {
+ //It is only a temporary solution to use a scissor to draw the stencil.
+ //There is a bug 31489986 to implement efficiently non-rectangular clips.
+ glDisable(GL_SCISSOR_TEST);
+ glDisable(GL_STENCIL_TEST);
+ glStencilMask(0xff);
+ glClearStencil(0);
+ glClear(GL_STENCIL_BUFFER_BIT);
+ glEnable(GL_SCISSOR_TEST);
+ SkRegion::Cliperator it(clipRegion, ibounds);
+ while (!it.done()) {
+ setScissor(info.height, it.rect());
+ glClearStencil(0x1);
+ glClear(GL_STENCIL_BUFFER_BIT);
+ it.next();
+ }
+ glDisable(GL_SCISSOR_TEST);
+ glStencilFunc(GL_EQUAL, 0x1, 0xff);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ glEnable(GL_STENCIL_TEST);
+ } else if (clipRegion.isEmpty()) {
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_SCISSOR_TEST);
+ } else {
+ glDisable(GL_STENCIL_TEST);
+ glEnable(GL_SCISSOR_TEST);
+ setScissor(info.height, clipRegion.getBounds());
+ }
+
+ (*mFunctor)(DrawGlInfo::kModeDraw, &info);
+
+ canvas->getGrContext()->resetContext();
+ }
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.h b/libs/hwui/pipeline/skia/GLFunctorDrawable.h
new file mode 100644
index 0000000..bf39dad
--- /dev/null
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <SkCanvas.h>
+#include <SkDrawable.h>
+
+#include <utils/RefBase.h>
+#include <utils/Functor.h>
+
+namespace android {
+namespace uirenderer {
+
+class GlFunctorLifecycleListener;
+
+namespace skiapipeline {
+
+/**
+ * This drawable wraps a OpenGL functor enabling it to be recorded into a list
+ * of Skia drawing commands.
+ */
+class GLFunctorDrawable : public SkDrawable {
+public:
+ GLFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, SkCanvas* canvas)
+ : mFunctor(functor)
+ , mListener(listener) {
+ canvas->getClipBounds(&mBounds);
+ }
+ virtual ~GLFunctorDrawable();
+
+ void syncFunctor() const;
+
+ protected:
+ virtual SkRect onGetBounds() override { return mBounds; }
+ virtual void onDraw(SkCanvas* canvas) override;
+
+ private:
+ Functor* mFunctor;
+ sp<GlFunctorLifecycleListener> mListener;
+ SkRect mBounds;
+};
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
new file mode 100644
index 0000000..f8a181f
--- /dev/null
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "LayerDrawable.h"
+#include "gl/GrGLTypes.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+void LayerDrawable::onDraw(SkCanvas* canvas) {
+ // transform the matrix based on the layer
+ int saveCount = -1;
+ if (!mLayer->getTransform().isIdentity()) {
+ saveCount = canvas->save();
+ SkMatrix transform;
+ mLayer->getTransform().copyTo(transform);
+ canvas->concat(transform);
+ }
+ GrGLTextureInfo externalTexture;
+ externalTexture.fTarget = mLayer->getRenderTarget();
+ externalTexture.fID = mLayer->getTextureId();
+ GrContext* context = canvas->getGrContext();
+ GrBackendTextureDesc textureDescription;
+ textureDescription.fWidth = mLayer->getWidth();
+ textureDescription.fHeight = mLayer->getHeight();
+ textureDescription.fConfig = kRGBA_8888_GrPixelConfig;
+ textureDescription.fOrigin = kTopLeft_GrSurfaceOrigin;
+ textureDescription.fTextureHandle = reinterpret_cast<GrBackendObject>(&externalTexture);
+ sk_sp<SkImage> layerImage(SkImage::NewFromTexture(context, textureDescription));
+ if (layerImage) {
+ SkPaint paint;
+ paint.setAlpha(mLayer->getAlpha());
+ paint.setBlendMode(mLayer->getMode());
+ paint.setColorFilter(mLayer->getColorFilter());
+ canvas->drawImage(layerImage, 0, 0, &paint);
+ }
+ // restore the original matrix
+ if (saveCount >= 0) {
+ canvas->restoreToCount(saveCount);
+ }
+}
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.h b/libs/hwui/pipeline/skia/LayerDrawable.h
new file mode 100644
index 0000000..91e2744
--- /dev/null
+++ b/libs/hwui/pipeline/skia/LayerDrawable.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "Layer.h"
+
+#include <SkCanvas.h>
+#include <SkDrawable.h>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+/*
+ * Draws a layer backed by an OpenGL texture into a SkCanvas.
+ */
+class LayerDrawable : public SkDrawable {
+ public:
+ explicit LayerDrawable(Layer* layer)
+ : mLayer(layer) {}
+
+ protected:
+ virtual SkRect onGetBounds() override {
+ return SkRect::MakeWH(mLayer->getWidth(), mLayer->getHeight());
+ }
+ virtual void onDraw(SkCanvas* canvas) override;
+
+private:
+ sp<Layer> mLayer;
+};
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
new file mode 100644
index 0000000..cefa893
--- /dev/null
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RenderNodeDrawable.h"
+#include "RenderNode.h"
+#include "SkiaDisplayList.h"
+#include "SkiaFrameRenderer.h"
+#include "utils/TraceUtils.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+static void clipOutline(const Outline& outline, SkCanvas* canvas, const SkRect* pendingClip) {
+ SkASSERT(outline.willClip());
+ Rect possibleRect;
+ float radius;
+ LOG_ALWAYS_FATAL_IF(!outline.getAsRoundRect(&possibleRect, &radius),
+ "clipping outlines should be at most roundedRects");
+ SkRect rect = possibleRect.toSkRect();
+ if (radius != 0.0f) {
+ if (pendingClip && !pendingClip->contains(rect)) {
+ canvas->clipRect(*pendingClip);
+ }
+ canvas->clipRRect(SkRRect::MakeRectXY(rect, radius, radius), SkRegion::kIntersect_Op, true);
+ } else {
+ if (pendingClip) {
+ (void)rect.intersect(*pendingClip);
+ }
+ canvas->clipRect(rect);
+ }
+}
+
+const RenderProperties& RenderNodeDrawable::getNodeProperties() const {
+ return mRenderNode->properties();
+}
+
+void RenderNodeDrawable::onDraw(SkCanvas* canvas) {
+ //negative and positive Z order are drawn out of order
+ if (MathUtils::isZero(mRenderNode->properties().getZ())) {
+ this->forceDraw(canvas);
+ }
+}
+
+void RenderNodeDrawable::forceDraw(SkCanvas* canvas) {
+ RenderNode* renderNode = mRenderNode.get();
+ if (SkiaFrameRenderer::skpCaptureEnabled()) {
+ SkRect dimensions = SkRect::MakeWH(renderNode->getWidth(), renderNode->getHeight());
+ canvas->drawAnnotation(dimensions, renderNode->getName(), nullptr);
+ }
+
+ // We only respect the nothingToDraw check when we are composing a layer. This
+ // ensures that we paint the layer even if it is not currently visible in the
+ // event that the properties change and it becomes visible.
+ if (!renderNode->isRenderable() || (renderNode->nothingToDraw() && mComposeLayer)) {
+ return;
+ }
+
+ SkASSERT(renderNode->getDisplayList()->isSkiaDL());
+ SkiaDisplayList* displayList = (SkiaDisplayList*)renderNode->getDisplayList();
+
+ SkAutoCanvasRestore acr(canvas, true);
+
+ const RenderProperties& properties = this->getNodeProperties();
+ if (displayList->mIsProjectionReceiver) {
+ // this node is a projection receiver. We will gather the projected nodes as we draw our
+ // children, and then draw them on top of this node's content.
+ std::vector<ProjectedChild> newList;
+ for (auto& child : displayList->mChildNodes) {
+ // our direct children are not supposed to project into us (nodes project to, at the
+ // nearest, their grandparents). So we "delay" the list's activation one level by
+ // passing it into mNextProjectedChildrenTarget rather than mProjectedChildrenTarget.
+ child.mProjectedChildrenTarget = mNextProjectedChildrenTarget;
+ child.mNextProjectedChildrenTarget = &newList;
+ }
+ // draw ourselves and our children. As a side effect, this will add projected nodes to
+ // newList.
+ this->drawContent(canvas);
+ bool willClip = properties.getOutline().willClip();
+ if (willClip) {
+ canvas->save();
+ clipOutline(properties.getOutline(), canvas, nullptr);
+ }
+ // draw the collected projected nodes
+ for (auto& projectedChild : newList) {
+ canvas->setMatrix(projectedChild.matrix);
+ projectedChild.node->drawContent(canvas);
+ }
+ if (willClip) {
+ canvas->restore();
+ }
+ } else {
+ if (properties.getProjectBackwards() && mProjectedChildrenTarget) {
+ // We are supposed to project this node, so add it to the list and do not actually draw
+ // yet. It will be drawn by its projection receiver.
+ mProjectedChildrenTarget->push_back({ this, canvas->getTotalMatrix() });
+ return;
+ }
+ for (auto& child : displayList->mChildNodes) {
+ // storing these values in the nodes themselves is a bit ugly; they should "really" be
+ // function parameters, but we have to go through the preexisting draw() method and
+ // therefore cannot add additional parameters to it
+ child.mProjectedChildrenTarget = mNextProjectedChildrenTarget;
+ child.mNextProjectedChildrenTarget = mNextProjectedChildrenTarget;
+ }
+ this->drawContent(canvas);
+ }
+ mProjectedChildrenTarget = nullptr;
+ mNextProjectedChildrenTarget = nullptr;
+}
+
+static bool layerNeedsPaint(const LayerProperties& properties,
+ float alphaMultiplier, SkPaint* paint) {
+ if (alphaMultiplier < 1.0f
+ || properties.alpha() < 255
+ || properties.xferMode() != SkBlendMode::kSrcOver
+ || properties.colorFilter() != nullptr) {
+ paint->setAlpha(properties.alpha() * alphaMultiplier);
+ paint->setBlendMode(properties.xferMode());
+ paint->setColorFilter(properties.colorFilter());
+ return true;
+ }
+ return false;
+}
+
+void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
+ RenderNode* renderNode = mRenderNode.get();
+ float alphaMultiplier = 1.0f;
+ const RenderProperties& properties = renderNode->properties();
+
+ // If we are drawing the contents of layer, we don't want to apply any of
+ // the RenderNode's properties during this pass. Those will all be applied
+ // when the layer is composited.
+ if (mComposeLayer) {
+ setViewProperties(properties, canvas, &alphaMultiplier);
+ }
+
+ //TODO should we let the bound of the drawable do this for us?
+ const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
+ bool quickRejected = properties.getClipToBounds() && canvas->quickReject(bounds);
+ if (!quickRejected) {
+ SkiaDisplayList* displayList = (SkiaDisplayList*)renderNode->getDisplayList();
+ const LayerProperties& layerProperties = properties.layerProperties();
+ // composing a hardware layer
+ if (renderNode->getLayerSurface() && mComposeLayer) {
+ SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer);
+ SkPaint* paint = nullptr;
+ SkPaint tmpPaint;
+ if (layerNeedsPaint(layerProperties, alphaMultiplier, &tmpPaint)) {
+ paint = &tmpPaint;
+ }
+ renderNode->getLayerSurface()->draw(canvas, 0, 0, paint);
+ // composing a software layer with alpha
+ } else if (properties.effectiveLayerType() == LayerType::Software) {
+ SkPaint paint;
+ bool needsLayer = layerNeedsPaint(layerProperties, alphaMultiplier, &paint);
+ if (needsLayer) {
+ canvas->saveLayer(bounds, &paint);
+ }
+ canvas->drawDrawable(displayList->mDrawable.get());
+ if (needsLayer) {
+ canvas->restore();
+ }
+ } else {
+ canvas->drawDrawable(displayList->mDrawable.get());
+ }
+ }
+}
+
+void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, SkCanvas* canvas,
+ float* alphaMultiplier) {
+ if (properties.getLeft() != 0 || properties.getTop() != 0) {
+ canvas->translate(properties.getLeft(), properties.getTop());
+ }
+ if (properties.getStaticMatrix()) {
+ canvas->concat(*properties.getStaticMatrix());
+ } else if (properties.getAnimationMatrix()) {
+ canvas->concat(*properties.getAnimationMatrix());
+ }
+ if (properties.hasTransformMatrix()) {
+ if (properties.isTransformTranslateOnly()) {
+ canvas->translate(properties.getTranslationX(), properties.getTranslationY());
+ } else {
+ canvas->concat(*properties.getTransformMatrix());
+ }
+ }
+ const bool isLayer = properties.effectiveLayerType() != LayerType::None;
+ int clipFlags = properties.getClippingFlags();
+ if (properties.getAlpha() < 1) {
+ if (isLayer) {
+ clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer
+ }
+ if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) {
+ *alphaMultiplier = properties.getAlpha();
+ } else {
+ // savelayer needed to create an offscreen buffer
+ Rect layerBounds(0, 0, properties.getWidth(), properties.getHeight());
+ if (clipFlags) {
+ properties.getClippingRectForFlags(clipFlags, &layerBounds);
+ clipFlags = 0; // all clipping done by savelayer
+ }
+ SkRect bounds = SkRect::MakeLTRB(layerBounds.left, layerBounds.top,
+ layerBounds.right, layerBounds.bottom);
+ canvas->saveLayerAlpha(&bounds, (int) (properties.getAlpha() * 255));
+ }
+
+ if (CC_UNLIKELY(ATRACE_ENABLED() && properties.promotedToLayer())) {
+ // pretend alpha always causes savelayer to warn about
+ // performance problem affecting old versions
+ ATRACE_FORMAT("alpha caused saveLayer %dx%d", properties.getWidth(),
+ properties.getHeight());
+ }
+ }
+
+ const SkRect* pendingClip = nullptr;
+ SkRect clipRect;
+
+ if (clipFlags) {
+ Rect tmpRect;
+ properties.getClippingRectForFlags(clipFlags, &tmpRect);
+ clipRect = tmpRect.toSkRect();
+ pendingClip = &clipRect;
+ }
+
+ if (properties.getRevealClip().willClip()) {
+ canvas->clipPath(*properties.getRevealClip().getPath(), SkRegion::kIntersect_Op, true);
+ } else if (properties.getOutline().willClip()) {
+ clipOutline(properties.getOutline(), canvas, pendingClip);
+ pendingClip = nullptr;
+ }
+
+ if (pendingClip) {
+ canvas->clipRect(*pendingClip);
+ }
+}
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
new file mode 100644
index 0000000..0762f37
--- /dev/null
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <SkCanvas.h>
+#include <SkDrawable.h>
+#include <SkMatrix.h>
+#include <utils/RefBase.h>
+
+namespace android {
+namespace uirenderer {
+
+class RenderNode;
+class RenderProperties;
+
+namespace skiapipeline {
+
+/**
+ * This drawable wraps a RenderNode and enables it to be recorded into a list
+ * of Skia drawing commands.
+ */
+class RenderNodeDrawable : public SkDrawable {
+public:
+ /**
+ * This struct contains a pointer to a node that is to be
+ * projected into the drawing order of its closest ancestor
+ * (excluding its parent) that is marked as a projection
+ * receiver. The matrix is used to ensure that the node is
+ * drawn with same matrix as it would have prior to projection.
+ */
+ struct ProjectedChild {
+ const RenderNodeDrawable* node;
+ const SkMatrix matrix;
+ };
+
+ /**
+ * Creates a new RenderNodeDrawable backed by a render node.
+ *
+ * @param node that has to be drawn
+ * @param canvas is a recording canvas used to extract its matrix
+ * @param composeLayer if the node's layer type is RenderLayer this flag determines whether
+ * we should draw into the contents of the layer or compose the existing contents of the
+ * layer into the canvas.
+ */
+ explicit RenderNodeDrawable(RenderNode* node, SkCanvas* canvas, bool composeLayer = true)
+ : mRenderNode(node)
+ , mRecordedTransform(canvas->getTotalMatrix())
+ , mComposeLayer(composeLayer) {}
+
+ /**
+ * Draws into the canvas this render node and its children. If the node is marked as a
+ * projection receiver then all projected children (excluding direct children) will be drawn
+ * last. Any projected node not matching those requirements will not be drawn by this function.
+ */
+ void forceDraw(SkCanvas* canvas);
+
+ /**
+ * Returns readonly render properties for this render node.
+ */
+ const RenderProperties& getNodeProperties() const;
+
+ /**
+ * The renderNode (and its properties) that is to be drawn
+ */
+ RenderNode* getRenderNode() const { return mRenderNode.get(); }
+
+ /**
+ * Returns the transform on the canvas at time of recording and is used for
+ * computing total transform without rerunning DL contents.
+ */
+ const SkMatrix& getRecordedMatrix() const { return mRecordedTransform; }
+
+protected:
+ /*
+ * Return the (conservative) bounds of what the drawable will draw.
+ */
+ virtual SkRect onGetBounds() override {
+ // We don't want to enable a record time quick reject because the properties
+ // of the RenderNode may be updated on subsequent frames.
+ return SkRect::MakeLargest();
+ }
+ /**
+ * This function draws into a canvas as forceDraw, but does nothing if the render node has a
+ * non-zero elevation.
+ */
+ virtual void onDraw(SkCanvas* canvas) override;
+
+private:
+ /*
+ * Render node that is wrapped by this class.
+ */
+ sp<RenderNode> mRenderNode;
+
+ /**
+ * Applies the rendering properties of a view onto a SkCanvas.
+ */
+ static void setViewProperties(const RenderProperties& properties, SkCanvas* canvas,
+ float* alphaMultiplier);
+
+ /**
+ * Stores transform on the canvas at time of recording and is used for
+ * computing total transform without rerunning DL contents.
+ */
+ const SkMatrix mRecordedTransform;
+
+ /**
+ * If mRenderNode's layer type is RenderLayer this flag determines whether we
+ * should draw into the contents of the layer or compose the existing contents
+ * of the layer into the canvas.
+ */
+ const bool mComposeLayer;
+
+ /**
+ * List to which we will add any projected children we encounter while walking our descendents.
+ * This pointer is valid only while the node (including its children) is actively being drawn.
+ */
+ std::vector<ProjectedChild>* mProjectedChildrenTarget = nullptr;
+
+ /**
+ * The value to which we should set our children's mProjectedChildrenTarget. We use two pointers
+ * (mProjectedChildrenTarget and mNextProjectedChildrenTarget) because we need to skip over our
+ * parent when looking for a projection receiver.
+ */
+ std::vector<ProjectedChild>* mNextProjectedChildrenTarget = nullptr;
+
+ /*
+ * Draw the content into a canvas, depending on the render node layer type and mComposeLayer.
+ */
+ void drawContent(SkCanvas* canvas) const;
+};
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
new file mode 100644
index 0000000..8d77938
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
@@ -0,0 +1,704 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ReorderBarrierDrawables.h"
+#include "RenderNode.h"
+#include "SkiaDisplayList.h"
+#include "SkiaFrameRenderer.h"
+
+#include <SkBlurMask.h>
+#include <SkBlurMaskFilter.h>
+#include <SkGaussianEdgeShader.h>
+#include <SkPathOps.h>
+#include <SkRRectsGaussianEdgeShader.h>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+StartReorderBarrierDrawable::StartReorderBarrierDrawable(SkiaDisplayList* data)
+ : mEndChildIndex(0)
+ , mBeginChildIndex(data->mChildNodes.size())
+ , mDisplayList(data) {
+}
+
+void StartReorderBarrierDrawable::onDraw(SkCanvas* canvas) {
+ if (mChildren.empty()) {
+ //mChildren is allocated and initialized only the first time onDraw is called and cached for
+ //subsequent calls
+ mChildren.reserve(mEndChildIndex - mBeginChildIndex + 1);
+ for (unsigned int i = mBeginChildIndex; i <= mEndChildIndex; i++) {
+ mChildren.push_back(const_cast<RenderNodeDrawable*>(&mDisplayList->mChildNodes[i]));
+ }
+ }
+ std::stable_sort(mChildren.begin(), mChildren.end(),
+ [](RenderNodeDrawable* a, RenderNodeDrawable* b) {
+ const float aZValue = a->getNodeProperties().getZ();
+ const float bZValue = b->getNodeProperties().getZ();
+ return aZValue < bZValue;
+ });
+
+ SkASSERT(!mChildren.empty());
+
+ size_t drawIndex = 0;
+ const size_t endIndex = mChildren.size();
+ while (drawIndex < endIndex) {
+ RenderNodeDrawable* childNode = mChildren[drawIndex];
+ SkASSERT(childNode);
+ const float casterZ = childNode->getNodeProperties().getZ();
+ if (casterZ >= -NON_ZERO_EPSILON) { //draw only children with negative Z
+ return;
+ }
+ childNode->forceDraw(canvas);
+ drawIndex++;
+ }
+}
+
+EndReorderBarrierDrawable::EndReorderBarrierDrawable(StartReorderBarrierDrawable* startBarrier)
+ : mStartBarrier(startBarrier) {
+ mStartBarrier->mEndChildIndex = mStartBarrier->mDisplayList->mChildNodes.size() - 1;
+}
+
+#define SHADOW_DELTA 0.1f
+
+void EndReorderBarrierDrawable::onDraw(SkCanvas* canvas) {
+ auto& zChildren = mStartBarrier->mChildren;
+ SkASSERT(!zChildren.empty());
+
+ /**
+ * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters
+ * with very similar Z heights to draw together.
+ *
+ * This way, if Views A & B have the same Z height and are both casting shadows, the shadows are
+ * underneath both, and neither's shadow is drawn on top of the other.
+ */
+ size_t drawIndex = 0;
+
+ const size_t endIndex = zChildren.size();
+ while (drawIndex < endIndex //draw only children with positive Z
+ && zChildren[drawIndex]->getNodeProperties().getZ() <= NON_ZERO_EPSILON) drawIndex++;
+ size_t shadowIndex = drawIndex;
+
+ float lastCasterZ = 0.0f;
+ while (shadowIndex < endIndex || drawIndex < endIndex) {
+ if (shadowIndex < endIndex) {
+ const float casterZ = zChildren[shadowIndex]->getNodeProperties().getZ();
+
+ // attempt to render the shadow if the caster about to be drawn is its caster,
+ // OR if its caster's Z value is similar to the previous potential caster
+ if (shadowIndex == drawIndex || casterZ - lastCasterZ < SHADOW_DELTA) {
+ this->drawShadow(canvas, zChildren[shadowIndex]);
+ lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
+ shadowIndex++;
+ continue;
+ }
+ }
+
+ RenderNodeDrawable* childNode = zChildren[drawIndex];
+ SkASSERT(childNode);
+ childNode->forceDraw(canvas);
+
+ drawIndex++;
+ }
+}
+
+/**
+ * @param canvas the destination for the shadow draws
+ * @param shape the shape casting the shadow
+ * @param casterZValue the Z value of the caster RRect
+ * @param ambientAlpha the maximum alpha value to use when drawing the ambient shadow
+ * @param draw the function used to draw 'shape'
+ */
+template <typename Shape, typename F>
+static void DrawAmbientShadowGeneral(SkCanvas* canvas, const Shape& shape, float casterZValue,
+ float ambientAlpha, F&& draw) {
+ if (ambientAlpha <= 0) {
+ return;
+ }
+
+ const float kHeightFactor = 1.f/128.f;
+ const float kGeomFactor = 64;
+
+ float umbraAlpha = 1 / (1 + SkMaxScalar(casterZValue*kHeightFactor, 0));
+ float radius = casterZValue*kHeightFactor*kGeomFactor;
+
+ sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
+ SkBlurMask::ConvertRadiusToSigma(radius), SkBlurMaskFilter::kNone_BlurFlag);
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setMaskFilter(std::move(mf));
+ paint.setARGB(ambientAlpha*umbraAlpha, 0, 0, 0);
+
+ draw(shape, paint);
+}
+
+/**
+ * @param canvas the destination for the shadow draws
+ * @param shape the shape casting the shadow
+ * @param casterZValue the Z value of the caster RRect
+ * @param lightPos the position of the light casting the shadow
+ * @param lightWidth
+ * @param spotAlpha the maximum alpha value to use when drawing the spot shadow
+ * @param draw the function used to draw 'shape'
+ */
+template <typename Shape, typename F>
+static void DrawSpotShadowGeneral(SkCanvas* canvas, const Shape& shape, float casterZValue,
+ float spotAlpha, F&& draw) {
+ if (spotAlpha <= 0) {
+ return;
+ }
+
+ const Vector3 lightPos = SkiaFrameRenderer::getLightCenter();
+ float zRatio = casterZValue / (lightPos.z - casterZValue);
+ // clamp
+ if (zRatio < 0.0f) {
+ zRatio = 0.0f;
+ } else if (zRatio > 0.95f) {
+ zRatio = 0.95f;
+ }
+
+ float blurRadius = SkiaFrameRenderer::getLightRadius()*zRatio;
+
+ SkAutoCanvasRestore acr(canvas, true);
+
+ sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
+ SkBlurMask::ConvertRadiusToSigma(blurRadius), SkBlurMaskFilter::kNone_BlurFlag);
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setMaskFilter(std::move(mf));
+ paint.setARGB(spotAlpha, 0, 0, 0);
+
+ // approximate projection by translating and scaling projected offset of bounds center
+ // TODO: compute the actual 2D projection
+ SkScalar scale = lightPos.z / (lightPos.z - casterZValue);
+ canvas->scale(scale, scale);
+ SkPoint center = SkPoint::Make(shape.getBounds().centerX(), shape.getBounds().centerY());
+ SkMatrix ctmInverse;
+ if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
+ ALOGW("Matrix is degenerate. Will not render shadow!");
+ return;
+ }
+ SkPoint lightPos2D = SkPoint::Make(lightPos.x, lightPos.y);
+ ctmInverse.mapPoints(&lightPos2D, 1);
+ canvas->translate(zRatio*(center.fX - lightPos2D.fX), zRatio*(center.fY - lightPos2D.fY));
+
+ draw(shape, paint);
+}
+
+#define MAX_BLUR_RADIUS 16383.75f
+#define MAX_PAD 64
+
+/**
+ * @param casterRect the rectangle bounds of the RRect casting the shadow
+ * @param casterCornerRadius the x&y radius for all the corners of the RRect casting the shadow
+ * @param ambientAlpha the maximum alpha value to use when drawing the ambient shadow
+ * @param spotAlpha the maximum alpha value to use when drawing the spot shadow
+ * @param casterAlpha the alpha value of the RRect casting the shadow (0.0-1.0 range)
+ * @param casterZValue the Z value of the caster RRect
+ * @param scaleFactor the scale needed to map from src-space to device-space
+ * @param canvas the destination for the shadow draws
+ */
+static void DrawRRectShadows(const SkRect& casterRect, SkScalar casterCornerRadius,
+ SkScalar ambientAlpha, SkScalar spotAlpha, SkScalar casterAlpha, SkScalar casterZValue,
+ SkScalar scaleFactor, SkCanvas* canvas) {
+ SkASSERT(cornerRadius >= 0.0f);
+
+ // For all of these, we need to ensure we have a rrect with radius >= 0.5f in device space
+ const SkScalar minRadius = 0.5f / scaleFactor;
+
+ const bool isOval = casterCornerRadius >= std::max(SkScalarHalf(casterRect.width()),
+ SkScalarHalf(casterRect.height()));
+ const bool isRect = casterCornerRadius <= minRadius;
+
+ sk_sp<SkShader> edgeShader(SkGaussianEdgeShader::Make());
+
+ if (ambientAlpha > 0.0f) {
+ static const float kHeightFactor = 1.0f / 128.0f;
+ static const float kGeomFactor = 64.0f;
+
+ SkScalar srcSpaceAmbientRadius = casterZValue * kHeightFactor * kGeomFactor;
+ // the device-space radius sent to the blur shader must fit in 14.2 fixed point
+ if (srcSpaceAmbientRadius*scaleFactor > MAX_BLUR_RADIUS) {
+ srcSpaceAmbientRadius = MAX_BLUR_RADIUS/scaleFactor;
+ }
+ const float umbraAlpha = 1.0f / (1.0f + std::max(casterZValue * kHeightFactor, 0.0f));
+ const SkScalar ambientOffset = srcSpaceAmbientRadius * umbraAlpha;
+
+ // For the ambient rrect, we inset the offset rect by half the srcSpaceAmbientRadius
+ // to get our stroke shape.
+ SkScalar ambientPathOutset = std::max(ambientOffset - srcSpaceAmbientRadius * 0.5f,
+ minRadius);
+
+ SkRRect ambientRRect;
+ const SkRect temp = casterRect.makeOutset(ambientPathOutset, ambientPathOutset);
+ if (isOval) {
+ ambientRRect = SkRRect::MakeOval(temp);
+ } else if (isRect) {
+ ambientRRect = SkRRect::MakeRectXY(temp, ambientPathOutset, ambientPathOutset);
+ } else {
+ ambientRRect = SkRRect::MakeRectXY(temp, casterCornerRadius + ambientPathOutset,
+ casterCornerRadius + ambientPathOutset);
+ }
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setStyle(SkPaint::kStroke_Style);
+ // we outset the stroke a little to cover up AA on the interior edge
+ float pad = 0.5f;
+ paint.setStrokeWidth(srcSpaceAmbientRadius + 2.0f * pad);
+ // handle scale of radius and pad due to CTM
+ pad *= scaleFactor;
+ const SkScalar devSpaceAmbientRadius = srcSpaceAmbientRadius * scaleFactor;
+ SkASSERT(devSpaceAmbientRadius <= MAX_BLUR_RADIUS);
+ SkASSERT(pad < MAX_PAD);
+ // convert devSpaceAmbientRadius to 14.2 fixed point and place in the R & G components
+ // convert pad to 6.2 fixed point and place in the B component
+ uint16_t iDevSpaceAmbientRadius = (uint16_t)(4.0f * devSpaceAmbientRadius);
+ paint.setColor(SkColorSetARGB((unsigned char) ambientAlpha, iDevSpaceAmbientRadius >> 8,
+ iDevSpaceAmbientRadius & 0xff, (unsigned char)(4.0f * pad)));
+
+ paint.setShader(edgeShader);
+ canvas->drawRRect(ambientRRect, paint);
+ }
+
+ if (spotAlpha > 0.0f) {
+ const Vector3 lightPos = SkiaFrameRenderer::getLightCenter();
+ float zRatio = casterZValue / (lightPos.z - casterZValue);
+ // clamp
+ if (zRatio < 0.0f) {
+ zRatio = 0.0f;
+ } else if (zRatio > 0.95f) {
+ zRatio = 0.95f;
+ }
+
+ const SkScalar lightWidth = SkiaFrameRenderer::getLightRadius();
+ SkScalar srcSpaceSpotRadius = 2.0f * lightWidth * zRatio;
+ // the device-space radius sent to the blur shader must fit in 14.2 fixed point
+ if (srcSpaceSpotRadius*scaleFactor > MAX_BLUR_RADIUS) {
+ srcSpaceSpotRadius = MAX_BLUR_RADIUS/scaleFactor;
+ }
+
+ SkRRect spotRRect;
+ if (isOval) {
+ spotRRect = SkRRect::MakeOval(casterRect);
+ } else if (isRect) {
+ spotRRect = SkRRect::MakeRectXY(casterRect, minRadius, minRadius);
+ } else {
+ spotRRect = SkRRect::MakeRectXY(casterRect, casterCornerRadius, casterCornerRadius);
+ }
+
+ SkRRect spotShadowRRect;
+ // Compute the scale and translation for the spot shadow.
+ const SkScalar scale = lightPos.z / (lightPos.z - casterZValue);
+ spotRRect.transform(SkMatrix::MakeScale(scale, scale), &spotShadowRRect);
+
+ SkPoint center = SkPoint::Make(spotShadowRRect.rect().centerX(),
+ spotShadowRRect.rect().centerY());
+ SkMatrix ctmInverse;
+ if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
+ ALOGW("Matrix is degenerate. Will not render spot shadow!");
+ return;
+ }
+ SkPoint lightPos2D = SkPoint::Make(lightPos.x, lightPos.y);
+ ctmInverse.mapPoints(&lightPos2D, 1);
+ const SkPoint spotOffset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
+ zRatio*(center.fY - lightPos2D.fY));
+
+ SkAutoCanvasRestore acr(canvas, true);
+
+ // We want to extend the stroked area in so that it meets up with the caster
+ // geometry. The stroked geometry will, by definition already be inset half the
+ // stroke width but we also have to account for the scaling.
+ // We also add 1/2 to cover up AA on the interior edge.
+ SkScalar scaleOffset = (scale - 1.0f) * SkTMax(SkTMax(SkTAbs(casterRect.fLeft),
+ SkTAbs(casterRect.fRight)), SkTMax(SkTAbs(casterRect.fTop),
+ SkTAbs(casterRect.fBottom)));
+ SkScalar insetAmount = spotOffset.length() - (0.5f * srcSpaceSpotRadius) +
+ scaleOffset + 0.5f;
+
+ // Compute area
+ SkScalar strokeWidth = srcSpaceSpotRadius + insetAmount;
+ SkScalar strokedArea = 2.0f*strokeWidth * (spotShadowRRect.width()
+ + spotShadowRRect.height());
+ SkScalar filledArea = (spotShadowRRect.height() + srcSpaceSpotRadius)
+ * (spotShadowRRect.width() + srcSpaceSpotRadius);
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+
+ // If the area of the stroked geometry is larger than the fill geometry, just fill it.
+ if (strokedArea > filledArea || casterAlpha < 1.0f) {
+ paint.setStyle(SkPaint::kStrokeAndFill_Style);
+ paint.setStrokeWidth(srcSpaceSpotRadius);
+ } else {
+ // Since we can't have unequal strokes, inset the shadow rect so the inner
+ // and outer edges of the stroke will land where we want.
+ SkRect insetRect = spotShadowRRect.rect().makeInset(insetAmount/2.0f, insetAmount/2.0f);
+ SkScalar insetRad = SkTMax(spotShadowRRect.getSimpleRadii().fX - insetAmount/2.0f,
+ minRadius);
+ spotShadowRRect = SkRRect::MakeRectXY(insetRect, insetRad, insetRad);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(strokeWidth);
+ }
+
+ // handle scale of radius and pad due to CTM
+ const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor;
+ SkASSERT(devSpaceSpotRadius <= MAX_BLUR_RADIUS);
+
+ const SkScalar devSpaceSpotPad = 0;
+ SkASSERT(devSpaceSpotPad < MAX_PAD);
+
+ // convert devSpaceSpotRadius to 14.2 fixed point and place in the R & G
+ // components convert devSpaceSpotPad to 6.2 fixed point and place in the B component
+ uint16_t iDevSpaceSpotRadius = (uint16_t)(4.0f * devSpaceSpotRadius);
+ paint.setColor(SkColorSetARGB((unsigned char) spotAlpha, iDevSpaceSpotRadius >> 8,
+ iDevSpaceSpotRadius & 0xff, (unsigned char)(4.0f * devSpaceSpotPad)));
+ paint.setShader(edgeShader);
+
+ canvas->translate(spotOffset.fX, spotOffset.fY);
+ canvas->drawRRect(spotShadowRRect, paint);
+ }
+}
+
+/**
+ * @param casterRect the rectangle bounds of the RRect casting the shadow
+ * @param casterCornerRadius the x&y radius for all the corners of the RRect casting the shadow
+ * @param ambientAlpha the maximum alpha value to use when drawing the ambient shadow
+ * @param spotAlpha the maximum alpha value to use when drawing the spot shadow
+ * @param casterZValue the Z value of the caster RRect
+ * @param scaleFactor the scale needed to map from src-space to device-space
+ * @param clipRR the oval or rect with which the drawn roundrect must be intersected
+ * @param canvas the destination for the shadow draws
+ */
+static void DrawRRectShadowsWithClip(const SkRect& casterRect, SkScalar casterCornerRadius,
+ SkScalar ambientAlpha, SkScalar spotAlpha, SkScalar casterZValue, SkScalar scaleFactor,
+ const SkRRect& clipRR, SkCanvas* canvas) {
+ SkASSERT(cornerRadius >= 0.0f);
+
+ const bool isOval = casterCornerRadius >= std::max(SkScalarHalf(casterRect.width()),
+ SkScalarHalf(casterRect.height()));
+
+ if (ambientAlpha > 0.0f) {
+ static const float kHeightFactor = 1.0f / 128.0f;
+ static const float kGeomFactor = 64.0f;
+
+ const SkScalar srcSpaceAmbientRadius = casterZValue * kHeightFactor * kGeomFactor;
+ const SkScalar devSpaceAmbientRadius = srcSpaceAmbientRadius * scaleFactor;
+
+ const float umbraAlpha = 1.0f / (1.0f + std::max(casterZValue * kHeightFactor, 0.0f));
+ const SkScalar ambientOffset = srcSpaceAmbientRadius * umbraAlpha;
+
+ const SkRect srcSpaceAmbientRect = casterRect.makeOutset(ambientOffset, ambientOffset);
+ SkRect devSpaceAmbientRect;
+ canvas->getTotalMatrix().mapRect(&devSpaceAmbientRect, srcSpaceAmbientRect);
+
+ SkRRect devSpaceAmbientRRect;
+ if (isOval) {
+ devSpaceAmbientRRect = SkRRect::MakeOval(devSpaceAmbientRect);
+ } else {
+ const SkScalar devSpaceCornerRadius = scaleFactor * (casterCornerRadius + ambientOffset);
+ devSpaceAmbientRRect = SkRRect::MakeRectXY(devSpaceAmbientRect, devSpaceCornerRadius,
+ devSpaceCornerRadius);
+ }
+
+ const SkRect srcSpaceAmbClipRect = clipRR.rect().makeOutset(ambientOffset, ambientOffset);
+ SkRect devSpaceAmbClipRect;
+ canvas->getTotalMatrix().mapRect(&devSpaceAmbClipRect, srcSpaceAmbClipRect);
+ SkRRect devSpaceAmbientClipRR;
+ if (clipRR.isOval()) {
+ devSpaceAmbientClipRR = SkRRect::MakeOval(devSpaceAmbClipRect);
+ } else {
+ SkASSERT(clipRR.isRect());
+ devSpaceAmbientClipRR = SkRRect::MakeRect(devSpaceAmbClipRect);
+ }
+
+ SkRect cover = srcSpaceAmbClipRect;
+ if (!cover.intersect(srcSpaceAmbientRect)) {
+ return;
+ }
+
+ SkPaint paint;
+ paint.setColor(SkColorSetARGB((unsigned char) ambientAlpha, 0, 0, 0));
+ paint.setShader(SkRRectsGaussianEdgeShader::Make(devSpaceAmbientRRect,
+ devSpaceAmbientClipRR, devSpaceAmbientRadius));
+ canvas->drawRect(cover, paint);
+ }
+
+ if (spotAlpha > 0.0f) {
+ const Vector3 lightPos = SkiaFrameRenderer::getLightCenter();
+ float zRatio = casterZValue / (lightPos.z - casterZValue);
+ // clamp
+ if (zRatio < 0.0f) {
+ zRatio = 0.0f;
+ } else if (zRatio > 0.95f) {
+ zRatio = 0.95f;
+ }
+
+ const SkScalar lightWidth = SkiaFrameRenderer::getLightRadius();
+ const SkScalar srcSpaceSpotRadius = 2.0f * lightWidth * zRatio;
+ const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor;
+
+ // Compute the scale and translation for the spot shadow.
+ const SkScalar scale = lightPos.z / (lightPos.z - casterZValue);
+ const SkMatrix spotMatrix = SkMatrix::MakeScale(scale, scale);
+
+ SkRect srcSpaceScaledRect = casterRect;
+ spotMatrix.mapRect(&srcSpaceScaledRect);
+ srcSpaceScaledRect.outset(SkScalarHalf(srcSpaceSpotRadius),
+ SkScalarHalf(srcSpaceSpotRadius));
+
+ SkRRect srcSpaceSpotRRect;
+ if (isOval) {
+ srcSpaceSpotRRect = SkRRect::MakeOval(srcSpaceScaledRect);
+ } else {
+ srcSpaceSpotRRect = SkRRect::MakeRectXY(srcSpaceScaledRect, casterCornerRadius * scale,
+ casterCornerRadius * scale);
+ }
+
+ SkPoint center = SkPoint::Make(srcSpaceSpotRRect.rect().centerX(),
+ srcSpaceSpotRRect.rect().centerY());
+ SkMatrix ctmInverse;
+ if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
+ ALOGW("Matrix is degenerate. Will not render spot shadow!");
+ return;
+ }
+ SkPoint lightPos2D = SkPoint::Make(lightPos.x, lightPos.y);
+ ctmInverse.mapPoints(&lightPos2D, 1);
+ const SkPoint spotOffset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
+ zRatio*(center.fY - lightPos2D.fY));
+
+ SkAutoCanvasRestore acr(canvas, true);
+ canvas->translate(spotOffset.fX, spotOffset.fY);
+
+ SkRect devSpaceScaledRect;
+ canvas->getTotalMatrix().mapRect(&devSpaceScaledRect, srcSpaceScaledRect);
+
+ SkRRect devSpaceSpotRRect;
+ if (isOval) {
+ devSpaceSpotRRect = SkRRect::MakeOval(devSpaceScaledRect);
+ } else {
+ const SkScalar devSpaceScaledCornerRadius = casterCornerRadius * scale * scaleFactor;
+ devSpaceSpotRRect = SkRRect::MakeRectXY(devSpaceScaledRect, devSpaceScaledCornerRadius,
+ devSpaceScaledCornerRadius);
+ }
+
+ SkPaint paint;
+ paint.setColor(SkColorSetARGB((unsigned char) spotAlpha, 0, 0, 0));
+
+ SkRect srcSpaceScaledClipRect = clipRR.rect();
+ spotMatrix.mapRect(&srcSpaceScaledClipRect);
+ srcSpaceScaledClipRect.outset(SkScalarHalf(srcSpaceSpotRadius),
+ SkScalarHalf(srcSpaceSpotRadius));
+
+ SkRect devSpaceScaledClipRect;
+ canvas->getTotalMatrix().mapRect(&devSpaceScaledClipRect, srcSpaceScaledClipRect);
+ SkRRect devSpaceSpotClipRR;
+ if (clipRR.isOval()) {
+ devSpaceSpotClipRR = SkRRect::MakeOval(devSpaceScaledClipRect);
+ } else {
+ SkASSERT(clipRR.isRect());
+ devSpaceSpotClipRR = SkRRect::MakeRect(devSpaceScaledClipRect);
+ }
+
+ paint.setShader(SkRRectsGaussianEdgeShader::Make(devSpaceSpotRRect, devSpaceSpotClipRR,
+ devSpaceSpotRadius));
+
+ SkRect cover = srcSpaceScaledClipRect;
+ if (!cover.intersect(srcSpaceSpotRRect.rect())) {
+ return;
+ }
+
+ canvas->drawRect(cover, paint);
+ }
+}
+
+/**
+ * @param casterRect the rectangle bounds of the RRect casting the shadow
+ * @param casterCornerRadius the x&y radius for all the corners of the RRect casting the shadow
+ * @param casterClipRect a rectangular clip that must be intersected with the
+ * shadow-casting RRect prior to casting the shadow
+ * @param revealClip a circular clip that must be interested with the castClipRect
+ * and the shadow-casting rect prior to casting the shadow
+ * @param ambientAlpha the maximum alpha value to use when drawing the ambient shadow
+ * @param spotAlpha the maximum alpha value to use when drawing the spot shadow
+ * @param casterAlpha the alpha value of the RRect casting the shadow (0.0-1.0 range)
+ * @param casterZValue the Z value of the caster RRect
+ * @param canvas the destination for the shadow draws
+ *
+ * We have special cases for 4 round rect shadow draws:
+ * 1) a RRect clipped by a reveal animation
+ * 2) a RRect clipped by a rectangle
+ * 3) an unclipped RRect with non-uniform scale
+ * 4) an unclipped RRect with uniform scale
+ * 1,2 and 4 require that the scale is uniform.
+ * 1 and 2 require that rects stay rects.
+ */
+static bool DrawShadowsAsRRects(const SkRect& casterRect, SkScalar casterCornerRadius,
+ const SkRect& casterClipRect, const RevealClip& revealClip, SkScalar ambientAlpha,
+ SkScalar spotAlpha, SkScalar casterAlpha, SkScalar casterZValue, SkCanvas* canvas) {
+ SkScalar scaleFactors[2];
+ if (!canvas->getTotalMatrix().getMinMaxScales(scaleFactors)) {
+ ALOGW("Matrix is degenerate. Will not render shadow!");
+ return false;
+ }
+
+ // The casterClipRect will contain the casterRect when bounds clipping is disabled
+ bool casterIsClippedByRect = !casterClipRect.contains(casterRect);
+ bool uniformScale = scaleFactors[0] == scaleFactors[1];
+
+ if (revealClip.willClip()) {
+ if (casterIsClippedByRect || !uniformScale || !canvas->getTotalMatrix().rectStaysRect()) {
+ return false; // Fall back to the slow path since PathOps are required
+ }
+
+ const float revealRadius = revealClip.getRadius();
+ SkRect revealClipRect = SkRect::MakeLTRB(revealClip.getX()-revealRadius,
+ revealClip.getY()-revealRadius, revealClip.getX()+revealRadius,
+ revealClip.getY()+revealRadius);
+ SkRRect revealClipRR = SkRRect::MakeOval(revealClipRect);
+
+ DrawRRectShadowsWithClip(casterRect, casterCornerRadius, ambientAlpha, spotAlpha,
+ casterZValue, scaleFactors[0], revealClipRR, canvas);
+ return true;
+ }
+
+ if (casterIsClippedByRect) {
+ if (!uniformScale || !canvas->getTotalMatrix().rectStaysRect()) {
+ return false; // Fall back to the slow path since PathOps are required
+ }
+
+ SkRRect casterClipRR = SkRRect::MakeRect(casterClipRect);
+
+ DrawRRectShadowsWithClip(casterRect, casterCornerRadius, ambientAlpha, spotAlpha,
+ casterZValue, scaleFactors[0], casterClipRR, canvas);
+ return true;
+ }
+
+ // The fast path needs uniform scale
+ if (!uniformScale) {
+ SkRRect casterRR = SkRRect::MakeRectXY(casterRect, casterCornerRadius, casterCornerRadius);
+ DrawAmbientShadowGeneral(canvas, casterRR, casterZValue, ambientAlpha,
+ [&](const SkRRect& rrect, const SkPaint& paint) {
+ canvas->drawRRect(rrect, paint);
+ });
+ DrawSpotShadowGeneral(canvas, casterRR, casterZValue, spotAlpha,
+ [&](const SkRRect& rrect, const SkPaint& paint) {
+ canvas->drawRRect(rrect, paint);
+ });
+ return true;
+ }
+
+ DrawRRectShadows(casterRect, casterCornerRadius, ambientAlpha, spotAlpha, casterAlpha,
+ casterZValue, scaleFactors[0], canvas);
+ return true;
+}
+
+// copied from FrameBuilder::deferShadow
+void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable* caster) {
+ const RenderProperties& casterProperties = caster->getNodeProperties();
+
+ if (casterProperties.getAlpha() <= 0.0f
+ || casterProperties.getOutline().getAlpha() <= 0.0f
+ || !casterProperties.getOutline().getPath()
+ || casterProperties.getScaleX() == 0
+ || casterProperties.getScaleY() == 0) {
+ // no shadow to draw
+ return;
+ }
+
+ const SkScalar casterAlpha = casterProperties.getAlpha()
+ * casterProperties.getOutline().getAlpha();
+ if (casterAlpha <= 0.0f) {
+ return;
+ }
+
+ float ambientAlpha = SkiaFrameRenderer::getAmbientShadowAlpha()*casterAlpha;
+ float spotAlpha = SkiaFrameRenderer::getSpotShadowAlpha()*casterAlpha;
+ const float casterZValue = casterProperties.getZ();
+
+ const RevealClip& revealClip = casterProperties.getRevealClip();
+ const SkPath* revealClipPath = revealClip.getPath();
+ if (revealClipPath && revealClipPath->isEmpty()) {
+ // An empty reveal clip means nothing is drawn
+ return;
+ }
+
+ bool clippedToBounds = casterProperties.getClippingFlags() & CLIP_TO_CLIP_BOUNDS;
+
+ SkRect casterClipRect = SkRect::MakeLargest();
+ if (clippedToBounds) {
+ Rect clipBounds;
+ casterProperties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds);
+ casterClipRect = clipBounds.toSkRect();
+ }
+
+ SkAutoCanvasRestore acr(canvas, true);
+
+ SkMatrix shadowMatrix;
+ mat4 hwuiMatrix(caster->getRecordedMatrix());
+ // TODO we don't pass the optional boolean to treat it as a 4x4 matrix
+ caster->getRenderNode()->applyViewPropertyTransforms(hwuiMatrix);
+ hwuiMatrix.copyTo(shadowMatrix);
+ canvas->concat(shadowMatrix);
+
+ const Outline& casterOutline = casterProperties.getOutline();
+ Rect possibleRect;
+ float radius;
+ if (casterOutline.getAsRoundRect(&possibleRect, &radius)) {
+ if (DrawShadowsAsRRects(possibleRect.toSkRect(), radius, casterClipRect, revealClip,
+ ambientAlpha, spotAlpha, casterAlpha, casterZValue, canvas)) {
+ return;
+ }
+ }
+
+ // Hard cases and calls to general shadow code
+ const SkPath* casterOutlinePath = casterProperties.getOutline().getPath();
+
+ // holds temporary SkPath to store the result of intersections
+ SkPath tmpPath;
+ const SkPath* casterPath = casterOutlinePath;
+
+ // TODO: In to following course of code that calculates the final shape, is there an optimal
+ // of doing the Op calculations?
+ // intersect the shadow-casting path with the reveal, if present
+ if (revealClipPath) {
+ Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, &tmpPath);
+ casterPath = &tmpPath;
+ }
+
+ // intersect the shadow-casting path with the clipBounds, if present
+ if (clippedToBounds) {
+ SkPath clipBoundsPath;
+ clipBoundsPath.addRect(casterClipRect);
+ Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, &tmpPath);
+ casterPath = &tmpPath;
+ }
+
+ DrawAmbientShadowGeneral(canvas, *casterPath, casterZValue, ambientAlpha,
+ [&](const SkPath& path, const SkPaint& paint) {
+ canvas->drawPath(path, paint);
+ });
+
+ DrawSpotShadowGeneral(canvas, *casterPath, casterZValue, spotAlpha,
+ [&](const SkPath& path, const SkPaint& paint) {
+ canvas->drawPath(path, paint);
+ });
+}
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h
new file mode 100644
index 0000000..298a732
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "RenderNodeDrawable.h"
+
+#include <SkCanvas.h>
+#include <SkDrawable.h>
+#include <utils/FatVector.h>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaDisplayList;
+class EndReorderBarrierDrawable;
+
+/**
+ * StartReorderBarrierDrawable and EndReorderBarrierDrawable work together to define
+ * a sub-list in a display list that need to be drawn out-of-order sorted instead by render
+ * node Z index.
+ * StartReorderBarrierDrawable will sort the entire range and it will draw
+ * render nodes in the range with negative Z index.
+ */
+class StartReorderBarrierDrawable : public SkDrawable {
+public:
+ explicit StartReorderBarrierDrawable(SkiaDisplayList* data);
+
+protected:
+ virtual SkRect onGetBounds() override {
+ return SkRect::MakeLargest();
+ }
+ virtual void onDraw(SkCanvas* canvas) override;
+
+private:
+ size_t mEndChildIndex;
+ size_t mBeginChildIndex;
+ FatVector<RenderNodeDrawable*, 16> mChildren;
+ SkiaDisplayList* mDisplayList;
+
+ friend class EndReorderBarrierDrawable;
+};
+
+/**
+ * See StartReorderBarrierDrawable.
+ * EndReorderBarrierDrawable relies on StartReorderBarrierDrawable to host and sort the render
+ * nodes by Z index. When EndReorderBarrierDrawable is drawn it will draw all render nodes in the
+ * range with positive Z index. It is also responsible for drawing shadows for the nodes
+ * corresponding to their z-index.
+ */
+class EndReorderBarrierDrawable : public SkDrawable {
+public:
+ explicit EndReorderBarrierDrawable(StartReorderBarrierDrawable* startBarrier);
+protected:
+ virtual SkRect onGetBounds() override {
+ return SkRect::MakeLargest();
+ }
+ virtual void onDraw(SkCanvas* canvas) override;
+private:
+ void drawShadow(SkCanvas* canvas, RenderNodeDrawable* caster);
+ StartReorderBarrierDrawable* mStartBarrier;
+};
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
new file mode 100644
index 0000000..c734097e
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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 "SkiaDisplayList.h"
+
+#include "renderthread/CanvasContext.h"
+#include "VectorDrawable.h"
+
+#include <SkImagePriv.h>
+#include <SkMutex.h>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+SkiaDisplayList::SkiaDisplayList(SkRect bounds) : mDrawable(SkLiteDL::New(bounds)) {
+ SkASSERT(projectionReceiveIndex == -1);
+}
+
+void SkiaDisplayList::syncContents() {
+ for (auto& functor : mChildFunctors) {
+ functor.syncFunctor();
+ }
+ for (auto& vectorDrawable : mVectorDrawables) {
+ vectorDrawable->syncProperties();
+ }
+}
+
+bool SkiaDisplayList::reuseDisplayList(RenderNode* node, renderthread::CanvasContext* context) {
+ reset(context ? context->getGrContext() : nullptr, SkRect::MakeEmpty());
+ node->attachAvailableList(this);
+ return true;
+}
+
+void SkiaDisplayList::updateChildren(std::function<void(RenderNode*)> updateFn) {
+ for (auto& child : mChildNodes) {
+ updateFn(child.getRenderNode());
+ }
+}
+
+bool SkiaDisplayList::prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
+ std::function<void(RenderNode*, TreeInfo&, bool)> childFn) {
+ // force all mutable images to be pinned in the GPU cache for the duration
+ // of this frame
+ pinImages(info.canvasContext.getGrContext());
+
+ for (auto& child : mChildNodes) {
+ RenderNode* childNode = child.getRenderNode();
+ Matrix4 mat4(child.getRecordedMatrix());
+ info.damageAccumulator->pushTransform(&mat4);
+ // TODO: a layer is needed if the canvas is rotated or has a non-rect clip
+ bool childFunctorsNeedLayer = functorsNeedLayer;
+ childFn(childNode, info, childFunctorsNeedLayer);
+ info.damageAccumulator->popTransform();
+ }
+
+ bool isDirty = false;
+ for (auto& vectorDrawable : mVectorDrawables) {
+ // If any vector drawable in the display list needs update, damage the node.
+ if (vectorDrawable->isDirty()) {
+ isDirty = true;
+ }
+ vectorDrawable->setPropertyChangeWillBeConsumed(true);
+ }
+ return isDirty;
+}
+
+static std::vector<sk_sp<SkImage>> gPinnedImages;
+static SkBaseMutex gLock;
+
+void SkiaDisplayList::pinImages(GrContext* context) {
+ if (mPinnedImages) return;
+ for (SkImage* image : mMutableImages) {
+ SkImage_pinAsTexture(image, context);
+ }
+ mPinnedImages = true;
+}
+
+void SkiaDisplayList::unpinImages(GrContext* context) {
+ if (!mPinnedImages) return;
+ if (context) {
+ for (SkImage* image : mMutableImages) {
+ SkImage_unpinAsTexture(image, context);
+ }
+ } else {
+ gLock.acquire();
+ for (SkImage* image : mMutableImages) {
+ gPinnedImages.emplace_back(sk_ref_sp(image));
+ }
+ gLock.release();
+ }
+ mPinnedImages = false;
+}
+
+void SkiaDisplayList::cleanupImages(GrContext* context) {
+ gLock.acquire();
+ for (auto& image : gPinnedImages) {
+ SkImage_unpinAsTexture(image.get(), context);
+ }
+ gPinnedImages.clear();
+ gLock.release();
+}
+
+void SkiaDisplayList::reset(GrContext* context, SkRect bounds) {
+ unpinImages(context);
+ SkASSERT(!mPinnedImages);
+ mIsProjectionReceiver = false;
+
+ mDrawable->reset(bounds);
+
+ mMutableImages.clear();
+ mVectorDrawables.clear();
+ mChildFunctors.clear();
+ mChildNodes.clear();
+
+ projectionReceiveIndex = -1;
+ allocator.~LinearAllocator();
+ new (&allocator) LinearAllocator();
+}
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
new file mode 100644
index 0000000..734aae4a
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -0,0 +1,155 @@
+/*
+ * 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 "DisplayList.h"
+#include "GLFunctorDrawable.h"
+#include "RenderNodeDrawable.h"
+
+#include <deque>
+#include <SkLiteDL.h>
+#include <SkPictureRecorder.h>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+/**
+ * This class is intended to be self contained, but still subclasses from
+ * DisplayList to make it easier to support switching between the two at
+ * runtime. The downside of this inheritance is that we pay for the overhead
+ * of the parent class construction/destruction without any real benefit.
+ */
+class SkiaDisplayList : public DisplayList {
+public:
+ SkiaDisplayList(SkRect bounds);
+ virtual ~SkiaDisplayList() {
+ /* Given that we are using a LinearStdAllocator to store some of the
+ * SkDrawable contents we must ensure that any other object that is
+ * holding a reference to those drawables is destroyed prior to their
+ * deletion.
+ */
+ mDrawable.reset();
+ }
+
+ /**
+ * This resets the DisplayList so that it behaves as if the object were newly
+ * constructed with the provided bounds. The reuse avoids any overhead
+ * associated with destroying the SkLiteDL as well as the deques and vectors.
+ */
+ void reset(GrContext* context, SkRect bounds);
+
+ /**
+ * Use the linear allocator to create any SkDrawables needed by the display
+ * list. This could be dangerous as these objects are ref-counted, so we
+ * need to monitor that they don't extend beyond the lifetime of the class
+ * that creates them.
+ */
+ template<class T, typename... Params>
+ SkDrawable* allocateDrawable(Params&&... params) {
+ return allocator.create<T>(std::forward<Params>(params)...);
+ }
+
+ bool isSkiaDL() const override { return true; }
+
+ /**
+ * Returns true if the DisplayList does not have any recorded content
+ */
+ bool isEmpty() const override { return mDrawable->empty(); }
+
+ /**
+ * Returns true if this list directly contains a GLFunctor drawing command.
+ */
+ bool hasFunctor() const override { return !mChildFunctors.empty(); }
+
+ /**
+ * Returns true if this list directly contains a VectorDrawable drawing command.
+ */
+ bool hasVectorDrawables() const override { return !mVectorDrawables.empty(); }
+
+ /**
+ * Attempts to reset and reuse this DisplayList.
+ *
+ * @return true if the displayList will be reused and therefore should not be deleted
+ */
+ bool reuseDisplayList(RenderNode* node, renderthread::CanvasContext* context) override;
+
+ /**
+ * ONLY to be called by RenderNode::syncDisplayList so that we can notify any
+ * contained VectorDrawables or GLFunctors to sync their state.
+ *
+ * NOTE: This function can be folded into RenderNode when we no longer need
+ * to subclass from DisplayList
+ */
+ void syncContents() override;
+
+ /**
+ * ONLY to be called by RenderNode::prepareTree in order to prepare this
+ * list while the UI thread is blocked. Here we can upload mutable bitmaps
+ * and notify our parent if any of our content has been invalidated and in
+ * need of a redraw. If the renderNode has any children then they are also
+ * call in order to prepare them.
+ *
+ * @return true if any content change requires the node to be invalidated
+ *
+ * NOTE: This function can be folded into RenderNode when we no longer need
+ * to subclass from DisplayList
+ */
+
+ bool prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
+ std::function<void(RenderNode*, TreeInfo&, bool)> childFn) override;
+
+ /**
+ * Calls the provided function once for each child of this DisplayList
+ */
+ void updateChildren(std::function<void(RenderNode*)> updateFn) override;
+
+ /**
+ * Pin/Unpin any mutable images to the GPU cache. A pinned images is
+ * guaranteed to be remain in the cache until it has been unpinned which
+ * we leverage to avoid making a CPU copy of the pixels.
+ */
+ void pinImages(GrContext* context);
+ void unpinImages(GrContext* context);
+
+ /**
+ * If a SkiaDisplayList is deleted on the UI thread we cache a list of any
+ * images that need unpinned from the GPU cache and call this function on
+ * a subsequent frame to perform that cleanup.
+ */
+ static void cleanupImages(GrContext* context);
+
+ /**
+ * We use std::deque here because (1) we need to iterate through these
+ * elements and (2) mDrawable holds pointers to the elements, so they cannot
+ * relocate.
+ */
+ std::deque<RenderNodeDrawable> mChildNodes;
+ std::deque<GLFunctorDrawable> mChildFunctors;
+ std::vector<SkImage*> mMutableImages;
+ std::vector<VectorDrawableRoot*> mVectorDrawables;
+ sk_sp<SkLiteDL> mDrawable;
+
+ bool mIsProjectionReceiver = false;
+
+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 0000000..70207c1
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaFrameRenderer.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+/**
+ * TODO: this is a stub that will be added in a subsquent CL
+ */
+class SkiaFrameRenderer {
+public:
+
+ static bool skpCaptureEnabled() { return false; }
+
+ // TODO avoids unused compile error but we need to pass this to the reorder drawables!
+ static float getLightRadius() {
+ return 1.0f;
+ }
+
+ static uint8_t getAmbientShadowAlpha() {
+ return 1;
+ }
+
+ static uint8_t getSpotShadowAlpha() {
+ return 1;
+ }
+
+ static Vector3 getLightCenter() {
+ Vector3 result;
+ result.x = result.y = result.z = 1.0f;
+ return result;
+ }
+
+};
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
new file mode 100644
index 0000000..8a42983
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SkiaRecordingCanvas.h"
+
+#include "Layer.h"
+#include "RenderNode.h"
+#include "LayerDrawable.h"
+#include "NinePatchUtils.h"
+#include "pipeline/skia/AnimatedDrawables.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+// ----------------------------------------------------------------------------
+// Recording Canvas Setup
+// ----------------------------------------------------------------------------
+
+void SkiaRecordingCanvas::initDisplayList(uirenderer::RenderNode* renderNode, int width,
+ int height) {
+ mBarrierPending = false;
+ mCurrentBarrier = nullptr;
+ SkASSERT(mDisplayList.get() == nullptr);
+
+ if (renderNode) {
+ mDisplayList = renderNode->detachAvailableList();
+ }
+ SkRect bounds = SkRect::MakeWH(width, height);
+ if (mDisplayList) {
+ mDisplayList->reset(nullptr, bounds);
+ } else {
+ mDisplayList.reset(new SkiaDisplayList(bounds));
+ }
+
+ mRecorder.reset(mDisplayList->mDrawable.get());
+ SkiaCanvas::reset(&mRecorder);
+}
+
+uirenderer::DisplayList* SkiaRecordingCanvas::finishRecording() {
+ // close any existing chunks if necessary
+ insertReorderBarrier(false);
+ mRecorder.restoreToCount(1);
+ return mDisplayList.release();
+}
+
+// ----------------------------------------------------------------------------
+// Recording Canvas draw operations: View System
+// ----------------------------------------------------------------------------
+
+void SkiaRecordingCanvas::drawRoundRect(uirenderer::CanvasPropertyPrimitive* left,
+ uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right,
+ uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx,
+ uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* paint) {
+ drawDrawable(mDisplayList->allocateDrawable<AnimatedRoundRect>(left, top, right, bottom,
+ rx, ry, paint));
+}
+
+void SkiaRecordingCanvas::drawCircle(uirenderer::CanvasPropertyPrimitive* x,
+ uirenderer::CanvasPropertyPrimitive* y, uirenderer::CanvasPropertyPrimitive* radius,
+ uirenderer::CanvasPropertyPaint* paint) {
+ drawDrawable(mDisplayList->allocateDrawable<AnimatedCircle>(x, y, radius, paint));
+}
+
+void SkiaRecordingCanvas::insertReorderBarrier(bool enableReorder) {
+ mBarrierPending = enableReorder;
+
+ if (nullptr != mCurrentBarrier) {
+ // finish off the existing chunk
+ SkDrawable* drawable =
+ mDisplayList->allocateDrawable<EndReorderBarrierDrawable>(
+ mCurrentBarrier);
+ mCurrentBarrier = nullptr;
+ drawDrawable(drawable);
+ }
+}
+
+void SkiaRecordingCanvas::drawLayer(uirenderer::DeferredLayerUpdater* layerUpdater) {
+ if (layerUpdater != nullptr && layerUpdater->backingLayer() != nullptr) {
+ uirenderer::Layer* layer = layerUpdater->backingLayer();
+ sk_sp<SkDrawable> drawable(new LayerDrawable(layer));
+ drawDrawable(drawable.get());
+ }
+}
+
+void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) {
+ // lazily create the chunk if needed
+ if (mBarrierPending) {
+ mCurrentBarrier = (StartReorderBarrierDrawable*)
+ mDisplayList->allocateDrawable<StartReorderBarrierDrawable>(
+ mDisplayList.get());
+ drawDrawable(mCurrentBarrier);
+ mBarrierPending = false;
+ }
+
+ // record the child node
+ mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas());
+ drawDrawable(&mDisplayList->mChildNodes.back());
+
+ // use staging property, since recording on UI thread
+ if (renderNode->stagingProperties().isProjectionReceiver()) {
+ mDisplayList->mIsProjectionReceiver = true;
+ // set projectionReceiveIndex so that RenderNode.hasProjectionReceiver returns true
+ mDisplayList->projectionReceiveIndex = mDisplayList->mChildNodes.size() - 1;
+ }
+}
+
+void SkiaRecordingCanvas::callDrawGLFunction(Functor* functor,
+ uirenderer::GlFunctorLifecycleListener* listener) {
+ mDisplayList->mChildFunctors.emplace_back(functor, listener, asSkCanvas());
+ drawDrawable(&mDisplayList->mChildFunctors.back());
+}
+
+class VectorDrawable : public SkDrawable {
+ public:
+ VectorDrawable(VectorDrawableRoot* tree) : mRoot(tree) {}
+
+ protected:
+ virtual SkRect onGetBounds() override {
+ return SkRect::MakeLargest();
+ }
+ virtual void onDraw(SkCanvas* canvas) override {
+ Bitmap& hwuiBitmap = mRoot->getBitmapUpdateIfDirty();
+ SkBitmap bitmap;
+ hwuiBitmap.getSkBitmap(&bitmap);
+ SkPaint* paint = mRoot->getPaint();
+ canvas->drawBitmapRect(bitmap, mRoot->mutateProperties()->getBounds(), paint);
+ /*
+ * TODO we can draw this directly but need to address the following...
+ *
+ * 1) Add drawDirect(SkCanvas*) to VectorDrawableRoot
+ * 2) fix VectorDrawable.cpp's Path::draw to not make a temporary path
+ * so that we don't break caching
+ * 3) figure out how to set path's as volatile during animation
+ * 4) if mRoot->getPaint() != null either promote to layer (during
+ * animation) or cache in SkSurface (for static content)
+ *
+ */
+ }
+
+ private:
+ sp<VectorDrawableRoot> mRoot;
+};
+
+void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
+ drawDrawable(mDisplayList->allocateDrawable<VectorDrawable>(tree));
+ mDisplayList->mVectorDrawables.push_back(tree);
+}
+
+// ----------------------------------------------------------------------------
+// Recording Canvas draw operations: Bitmaps
+// ----------------------------------------------------------------------------
+
+inline static const SkPaint* nonAAPaint(const SkPaint* origPaint, SkPaint* tmpPaint) {
+ if (origPaint && origPaint->isAntiAlias()) {
+ *tmpPaint = *origPaint;
+ tmpPaint->setAntiAlias(false);
+ return tmpPaint;
+ } else {
+ return origPaint;
+ }
+}
+
+void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) {
+ SkBitmap skBitmap;
+ bitmap.getSkBitmap(&skBitmap);
+
+ sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode);
+ if (!skBitmap.isImmutable()) {
+ mDisplayList->mMutableImages.push_back(image.get());
+ }
+ SkPaint tmpPaint;
+ mRecorder.drawImage(image, left, top, nonAAPaint(paint, &tmpPaint));
+}
+
+void SkiaRecordingCanvas::drawBitmap(Bitmap& hwuiBitmap, const SkMatrix& matrix,
+ const SkPaint* paint) {
+ SkBitmap bitmap;
+ hwuiBitmap.getSkBitmap(&bitmap);
+ SkAutoCanvasRestore acr(&mRecorder, true);
+ concat(matrix);
+ sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
+ if (!bitmap.isImmutable()) {
+ mDisplayList->mMutableImages.push_back(image.get());
+ }
+ SkPaint tmpPaint;
+ mRecorder.drawImage(image, 0, 0, nonAAPaint(paint, &tmpPaint));
+}
+
+void SkiaRecordingCanvas::drawBitmap(Bitmap& hwuiBitmap, float srcLeft, float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight,
+ float dstBottom, const SkPaint* paint) {
+ SkBitmap bitmap;
+ hwuiBitmap.getSkBitmap(&bitmap);
+ SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
+ SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
+ sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
+ if (!bitmap.isImmutable()) {
+ mDisplayList->mMutableImages.push_back(image.get());
+ }
+ SkPaint tmpPaint;
+ mRecorder.drawImageRect(image, srcRect, dstRect, nonAAPaint(paint, &tmpPaint));
+}
+
+void SkiaRecordingCanvas::drawNinePatch(Bitmap& hwuiBitmap, const Res_png_9patch& chunk,
+ float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) {
+ SkBitmap bitmap;
+ hwuiBitmap.getSkBitmap(&bitmap);
+
+ SkCanvas::Lattice lattice;
+ NinePatchUtils::SetLatticeDivs(&lattice, chunk, bitmap.width(), bitmap.height());
+
+ lattice.fFlags = nullptr;
+ int numFlags = 0;
+ if (chunk.numColors > 0 && chunk.numColors == NinePatchUtils::NumDistinctRects(lattice)) {
+ // We can expect the framework to give us a color for every distinct rect.
+ // Skia requires placeholder flags for degenerate rects.
+ numFlags = (lattice.fXCount + 1) * (lattice.fYCount + 1);
+ }
+
+ SkAutoSTMalloc<25, SkCanvas::Lattice::Flags> flags(numFlags);
+ if (numFlags > 0) {
+ NinePatchUtils::SetLatticeFlags(&lattice, flags.get(), numFlags, chunk);
+ }
+
+ lattice.fBounds = nullptr;
+ SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
+ sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
+ if (!bitmap.isImmutable()) {
+ mDisplayList->mMutableImages.push_back(image.get());
+ }
+
+ SkPaint tmpPaint;
+ mRecorder.drawImageLattice(image.get(), lattice, dst, nonAAPaint(paint, &tmpPaint));
+}
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
new file mode 100644
index 0000000..8aef97f
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "SkiaCanvas.h"
+#include "SkiaDisplayList.h"
+#include "ReorderBarrierDrawables.h"
+#include <SkLiteRecorder.h>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+/**
+ * A SkiaCanvas implementation that records drawing operations for deferred rendering backed by a
+ * SkLiteRecorder and a SkiaDisplayList.
+ */
+class SkiaRecordingCanvas : public SkiaCanvas {
+ public:
+ explicit SkiaRecordingCanvas(uirenderer::RenderNode* renderNode, int width, int height) {
+ initDisplayList(renderNode, width, height);
+ }
+
+ virtual void setBitmap(const SkBitmap& bitmap) override {
+ LOG_ALWAYS_FATAL("DisplayListCanvas is not backed by a bitmap.");
+ }
+
+ virtual void resetRecording(int width, int height,
+ uirenderer::RenderNode* renderNode) override {
+ initDisplayList(renderNode, width, height);
+ }
+
+ virtual uirenderer::DisplayList* finishRecording() override;
+
+ virtual void drawBitmap(Bitmap& bitmap, float left, float top,
+ const SkPaint* paint) override;
+ virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix,
+ const SkPaint* paint) override;
+ virtual void drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop,
+ float dstRight, float dstBottom, const SkPaint* paint) override;
+ virtual void drawNinePatch(Bitmap& hwuiBitmap, const android::Res_png_9patch& chunk,
+ float dstLeft, float dstTop, float dstRight, float dstBottom,
+ const SkPaint* paint) override;
+
+ virtual void drawRoundRect(uirenderer::CanvasPropertyPrimitive* left,
+ uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right,
+ uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx,
+ uirenderer::CanvasPropertyPrimitive* ry,
+ uirenderer::CanvasPropertyPaint* paint) override;
+ virtual void drawCircle(uirenderer::CanvasPropertyPrimitive* x,
+ uirenderer::CanvasPropertyPrimitive* y, uirenderer::CanvasPropertyPrimitive* radius,
+ uirenderer::CanvasPropertyPaint* paint) override;
+
+ virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
+
+ virtual void insertReorderBarrier(bool enableReorder) override;
+ virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override;
+ virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override;
+ virtual void callDrawGLFunction(Functor* functor,
+ uirenderer::GlFunctorLifecycleListener* listener) override;
+
+private:
+ SkLiteRecorder mRecorder;
+ std::unique_ptr<SkiaDisplayList> mDisplayList;
+ bool mBarrierPending;
+ StartReorderBarrierDrawable* mCurrentBarrier;
+
+ /**
+ * A new SkiaDisplayList is created or recycled if available.
+ *
+ * @param renderNode is optional and used to recycle an old display list.
+ * @param width used to calculate recording bounds.
+ * @param height used to calculate recording bounds.
+ */
+ void initDisplayList(uirenderer::RenderNode* renderNode, int width, int height);
+};
+
+}; // namespace skiapipeline
+}; // namespace uirenderer
+}; // namespace android