Merge "Implement SkiaRecordingCanvas, RenderNodeDrawable and other drawables."

This commit is contained in:
TreeHugger Robot
2016-10-31 20:37:26 +00:00
committed by Android (Google) Code Review
24 changed files with 2320 additions and 223 deletions

View File

@@ -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 \

View File

@@ -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

View File

@@ -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

View File

@@ -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());
}

View File

@@ -40,6 +40,8 @@ public:
*/
explicit SkiaCanvas(SkCanvas* canvas);
virtual ~SkiaCanvas();
virtual SkCanvas* asSkCanvas() override {
return mCanvas.get();
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 */

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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);