Initial commit of new Canvas operation recording / replay

Done:
- drawRect, drawBitmap, drawColor, drawPaint, drawRenderNode, drawRegion
- Recording with new DisplayList format
- batching & reordering
- Stateless op reorder
- Stateless op rendering
- Frame lifecycle (clear, geterror, cleanup)

Not done:
- SaveLayer (clipped and unclipped)
- HW layers
- Complex clipping
- Ripple projection
- Z reordering
- Z shadows
- onDefer prefetching (text + task kickoff)
- round rect clip
- linear allocation for std collections
- AssetAtlas support

Change-Id: Iaf98c1a3aeab5fa47cc8f9c6d964420abc0e7691
This commit is contained in:
Chris Craik
2015-10-05 13:00:52 -07:00
parent b08949151f
commit b565df13a9
29 changed files with 2482 additions and 78 deletions

View File

@ -2,6 +2,8 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
HWUI_NEW_OPS := false
hwui_src_files := \
font/CacheTexture.cpp \
font/Font.cpp \
@ -85,6 +87,17 @@ hwui_cflags := \
-Wall -Wno-unused-parameter -Wunreachable-code \
-ffast-math -O3 -Werror
ifeq (true, $(HWUI_NEW_OPS))
hwui_src_files += \
BakedOpRenderer.cpp \
OpReorderer.cpp \
RecordingCanvas.cpp
hwui_cflags += -DHWUI_NEW_OPS
endif
ifndef HWUI_COMPILE_SYMBOLS
hwui_cflags += -fvisibility=hidden
endif
@ -172,6 +185,13 @@ LOCAL_SRC_FILES += \
unit_tests/LinearAllocatorTests.cpp \
unit_tests/StringUtilsTests.cpp
ifeq (true, $(HWUI_NEW_OPS))
LOCAL_SRC_FILES += \
unit_tests/BakedOpStateTests.cpp \
unit_tests/RecordingCanvasTests.cpp \
unit_tests/OpReordererTests.cpp
endif
include $(BUILD_NATIVE_TEST)
# ------------------------

View File

@ -0,0 +1,126 @@
/*
* Copyright (C) 2015 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 "BakedOpRenderer.h"
#include "Caches.h"
#include "Glop.h"
#include "GlopBuilder.h"
#include "renderstate/RenderState.h"
#include "utils/GLUtils.h"
namespace android {
namespace uirenderer {
Texture* BakedOpRenderer::Info::getTexture(const SkBitmap* bitmap) {
Texture* texture = renderState.assetAtlas().getEntryTexture(bitmap);
if (!texture) {
return caches.textureCache.get(bitmap);
}
return texture;
}
void BakedOpRenderer::Info::renderGlop(const BakedOpState& state, const Glop& glop) {
bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None;
renderState.scissor().setEnabled(useScissor);
if (useScissor) {
const Rect& clip = state.computedState.clipRect;
renderState.scissor().set(clip.left, viewportHeight - clip.bottom,
clip.getWidth(), clip.getHeight());
}
renderState.render(glop, orthoMatrix);
didDraw = true;
}
void BakedOpRenderer::startFrame(Info& info) {
info.renderState.setViewport(info.viewportWidth, info.viewportHeight);
info.renderState.blend().syncEnabled();
Caches::getInstance().clearGarbage();
if (!info.opaque) {
// TODO: partial invalidate!
info.renderState.scissor().setEnabled(false);
glClear(GL_COLOR_BUFFER_BIT);
info.didDraw = true;
}
}
void BakedOpRenderer::endFrame(Info& info) {
info.caches.pathCache.trim();
info.caches.tessellationCache.trim();
#if DEBUG_OPENGL
GLUtils::dumpGLErrors();
#endif
#if DEBUG_MEMORY_USAGE
info.caches.dumpMemoryUsage();
#else
if (Properties::debugLevel & kDebugMemory) {
info.caches.dumpMemoryUsage();
}
#endif
}
void BakedOpRenderer::onRenderNodeOp(Info*, const RenderNodeOp&, const BakedOpState&) {
LOG_ALWAYS_FATAL("unsupported operation");
}
void BakedOpRenderer::onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
info->caches.textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
Texture* texture = info->getTexture(op.bitmap);
if (!texture) return;
const AutoTexture autoCleanup(texture);
const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
Glop glop;
GlopBuilder(info->renderState, info->caches, &glop)
.setRoundRectClipState(state.roundRectClipState)
.setMeshTexturedUnitQuad(texture->uvMapper)
.setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
.setTransform(state.computedState.transform, TransformFlags::None)
.setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height))
.build();
info->renderGlop(state, glop);
}
void BakedOpRenderer::onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
Glop glop;
GlopBuilder(info->renderState, info->caches, &glop)
.setRoundRectClipState(state.roundRectClipState)
.setMeshUnitQuad()
.setFillPaint(*op.paint, state.alpha)
.setTransform(state.computedState.transform, TransformFlags::None)
.setModelViewMapUnitToRect(op.unmappedBounds)
.build();
info->renderGlop(state, glop);
}
void BakedOpRenderer::onSimpleRectsOp(Info* info, const SimpleRectsOp& op, const BakedOpState& state) {
Glop glop;
GlopBuilder(info->renderState, info->caches, &glop)
.setRoundRectClipState(state.roundRectClipState)
.setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4)
.setFillPaint(*op.paint, state.alpha)
.setTransform(state.computedState.transform, TransformFlags::None)
.setModelViewOffsetRect(0, 0, op.unmappedBounds)
.build();
info->renderGlop(state, glop);
}
} // namespace uirenderer
} // namespace android

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2015 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.
*/
#ifndef ANDROID_HWUI_BAKED_OP_RENDERER_H
#define ANDROID_HWUI_BAKED_OP_RENDERER_H
#include "BakedOpState.h"
#include "Matrix.h"
namespace android {
namespace uirenderer {
class Caches;
struct Glop;
class RenderState;
class BakedOpRenderer {
public:
class Info {
public:
Info(Caches& caches, RenderState& renderState, int viewportWidth, int viewportHeight, bool opaque)
: renderState(renderState)
, caches(caches)
, opaque(opaque)
, viewportWidth(viewportWidth)
, viewportHeight(viewportHeight) {
orthoMatrix.loadOrtho(viewportWidth, viewportHeight);
}
Texture* getTexture(const SkBitmap* bitmap);
void renderGlop(const BakedOpState& state, const Glop& glop);
RenderState& renderState;
Caches& caches;
bool didDraw = false;
bool opaque;
// where should these live? layer state object?
int viewportWidth;
int viewportHeight;
Matrix4 orthoMatrix;
};
static void startFrame(Info& info);
static void endFrame(Info& info);
/**
* Declare all "onBitmapOp(...)" style function for every op type.
*
* These functions will perform the actual rendering of the individual operations in OpenGL,
* given the transform/clip and other state built into the BakedOpState object passed in.
*/
#define BAKED_OP_RENDERER_METHOD(Type) static void on##Type(Info* info, const Type& op, const BakedOpState& state);
MAP_OPS(BAKED_OP_RENDERER_METHOD);
};
}; // namespace uirenderer
}; // namespace android
#endif // ANDROID_HWUI_BAKED_OP_RENDERER_H

142
libs/hwui/BakedOpState.h Normal file
View File

@ -0,0 +1,142 @@
/*
* Copyright (C) 2015 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.
*/
#ifndef ANDROID_HWUI_BAKED_OP_STATE_H
#define ANDROID_HWUI_BAKED_OP_STATE_H
#include "Matrix.h"
#include "RecordedOp.h"
#include "Rect.h"
#include "Snapshot.h"
namespace android {
namespace uirenderer {
namespace OpClipSideFlags {
enum {
None = 0x0,
Left = 0x1,
Top = 0x2,
Right = 0x4,
Bottom = 0x8,
Full = 0xF,
// ConservativeFull = 0x1F needed?
};
}
/**
* Holds the resolved clip, transform, and bounds of a recordedOp, when replayed with a snapshot
*/
class ResolvedRenderState {
public:
// TODO: remove the mapRects/matrix multiply when snapshot & recorded transforms are translates
ResolvedRenderState(const Snapshot& snapshot, const RecordedOp& recordedOp) {
/* TODO: benchmark a fast path for translate-only matrices, such as:
if (CC_LIKELY(snapshot.transform->getType() == Matrix4::kTypeTranslate
&& recordedOp.localMatrix.getType() == Matrix4::kTypeTranslate)) {
float translateX = snapshot.transform->getTranslateX() + recordedOp.localMatrix.getTranslateX();
float translateY = snapshot.transform->getTranslateY() + recordedOp.localMatrix.getTranslateY();
transform.loadTranslate(translateX, translateY, 0);
// resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
clipRect = recordedOp.localClipRect;
clipRect.translate(translateX, translateY);
clipRect.doIntersect(snapshot.getClipRect());
clipRect.snapToPixelBoundaries();
// resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
clippedBounds = recordedOp.unmappedBounds;
clippedBounds.translate(translateX, translateY);
} ... */
// resolvedMatrix = parentMatrix * localMatrix
transform.loadMultiply(*snapshot.transform, recordedOp.localMatrix);
// resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
clipRect = recordedOp.localClipRect;
snapshot.transform->mapRect(clipRect);
clipRect.doIntersect(snapshot.getClipRect());
clipRect.snapToPixelBoundaries();
// resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
clippedBounds = recordedOp.unmappedBounds;
transform.mapRect(clippedBounds);
if (clipRect.left > clippedBounds.left) clipSideFlags |= OpClipSideFlags::Left;
if (clipRect.top > clippedBounds.top) clipSideFlags |= OpClipSideFlags::Top;
if (clipRect.right < clippedBounds.right) clipSideFlags |= OpClipSideFlags::Right;
if (clipRect.bottom < clippedBounds.bottom) clipSideFlags |= OpClipSideFlags::Bottom;
clippedBounds.doIntersect(clipRect);
/**
* TODO: once we support complex clips, we may want to reject to avoid that work where
* possible. Should we:
* 1 - quickreject based on clippedBounds, quick early (duplicating logic in resolvedOp)
* 2 - merge stuff into tryConstruct factory method, so it can handle quickRejection
* and early return null in one place.
*/
}
Matrix4 transform;
Rect clipRect;
int clipSideFlags = 0;
Rect clippedBounds;
};
/**
* Self-contained op wrapper, containing all resolved state required to draw the op.
*
* Stashed pointers within all point to longer lived objects, with no ownership implied.
*/
class BakedOpState {
public:
static BakedOpState* tryConstruct(LinearAllocator& allocator,
const Snapshot& snapshot, const RecordedOp& recordedOp) {
BakedOpState* bakedOp = new (allocator) BakedOpState(
snapshot, recordedOp);
if (bakedOp->computedState.clippedBounds.isEmpty()) {
// bounds are empty, so op is rejected
allocator.rewindIfLastAlloc(bakedOp);
return nullptr;
}
return bakedOp;
}
static void* operator new(size_t size, LinearAllocator& allocator) {
return allocator.alloc(size);
}
// computed state:
const ResolvedRenderState computedState;
// simple state (straight pointer/value storage):
const float alpha;
const RoundRectClipState* roundRectClipState;
const ProjectionPathMask* projectionPathMask;
const RecordedOp* op;
private:
BakedOpState(const Snapshot& snapshot, const RecordedOp& recordedOp)
: computedState(snapshot, recordedOp)
, alpha(snapshot.alpha)
, roundRectClipState(snapshot.roundRectClipState)
, projectionPathMask(snapshot.projectionPathMask)
, op(&recordedOp) {}
};
}; // namespace uirenderer
}; // namespace android
#endif // ANDROID_HWUI_BAKED_OP_STATE_H

View File

@ -528,7 +528,7 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
int insertBatchIndex = mBatches.size();
if (!mBatches.empty()) {
if (state->mBounds.isEmpty()) {
// don't know the bounds for op, so add to last batch and start from scratch on next op
// don't know the bounds for op, so create new batch and start from scratch on next op
DrawBatch* b = new DrawBatch(deferInfo);
b->add(op, state, deferInfo.opaqueOverBounds);
mBatches.push_back(b);

View File

@ -49,7 +49,7 @@ typedef const void* mergeid_t;
class DeferredDisplayState {
public:
/** static void* operator new(size_t size); PURPOSELY OMITTED **/
static void* operator new(size_t size) = delete;
static void* operator new(size_t size, LinearAllocator& allocator) {
return allocator.alloc(size);
}
@ -61,7 +61,6 @@ public:
bool mClipValid;
Rect mClip;
int mClipSideFlags; // specifies which sides of the bounds are clipped, unclipped if cleared
bool mClipped;
mat4 mMatrix;
float mAlpha;
const RoundRectClipState* mRoundRectClipState;

View File

@ -21,7 +21,13 @@
#include "Debug.h"
#include "DisplayList.h"
#include "RenderNode.h"
#if HWUI_NEW_OPS
#include "RecordedOp.h"
#else
#include "DisplayListOp.h"
#endif
namespace android {
namespace uirenderer {
@ -61,8 +67,13 @@ void DisplayListData::cleanupResources() {
regions.clear();
}
#if HWUI_NEW_OPS
size_t DisplayListData::addChild(RenderNodeOp* op) {
mReferenceHolders.push_back(op->renderNode);
#else
size_t DisplayListData::addChild(DrawRenderNodeOp* op) {
mReferenceHolders.push_back(op->renderNode());
mReferenceHolders.push_back(op->renderNode);
#endif
size_t index = mChildren.size();
mChildren.push_back(op);
return index;

View File

@ -55,11 +55,12 @@ class OpenGLRenderer;
class Rect;
class Layer;
class ClipRectOp;
class SaveLayerOp;
class SaveOp;
class RestoreToCountOp;
#if HWUI_NEW_OPS
struct RecordedOp;
struct RenderNodeOp;
#else
class DrawRenderNodeOp;
#endif
/**
* Holds data used in the playback a tree of DisplayLists.
@ -107,6 +108,7 @@ struct ReplayStateStruct : public PlaybackStateStruct {
*/
class DisplayListData {
friend class DisplayListCanvas;
friend class RecordingCanvas;
public:
struct Chunk {
// range of included ops in DLD::displayListOps
@ -139,11 +141,21 @@ public:
Vector<Functor*> functors;
const std::vector<Chunk>& getChunks() const {
return chunks;
return chunks;
}
#if HWUI_NEW_OPS
const std::vector<RecordedOp*>& getOps() const {
return ops;
}
#endif
#if HWUI_NEW_OPS
size_t addChild(RenderNodeOp* childOp);
const std::vector<RenderNodeOp*>& children() { return mChildren; }
#else
size_t addChild(DrawRenderNodeOp* childOp);
const std::vector<DrawRenderNodeOp*>& children() { return mChildren; }
#endif
void ref(VirtualLightRefBase* prop) {
mReferenceHolders.push_back(prop);
@ -157,10 +169,18 @@ public:
}
private:
#if HWUI_NEW_OPS
std::vector<RecordedOp*> ops;
#endif
std::vector< sp<VirtualLightRefBase> > mReferenceHolders;
#if HWUI_NEW_OPS
std::vector<RenderNodeOp*> mChildren;
#else
// list of children display lists for quick, non-drawing traversal
std::vector<DrawRenderNodeOp*> mChildren;
#endif
std::vector<Chunk> chunks;

View File

@ -558,16 +558,18 @@ size_t DisplayListCanvas::addDrawOp(DrawOp* op) {
size_t DisplayListCanvas::addRenderNodeOp(DrawRenderNodeOp* op) {
int opIndex = addDrawOp(op);
#if !HWUI_NEW_OPS
int childIndex = mDisplayListData->addChild(op);
// update the chunk's child indices
DisplayListData::Chunk& chunk = mDisplayListData->chunks.back();
chunk.endChildIndex = childIndex + 1;
if (op->renderNode()->stagingProperties().isProjectionReceiver()) {
if (op->renderNode->stagingProperties().isProjectionReceiver()) {
// use staging property, since recording on UI thread
mDisplayListData->projectionReceiveIndex = opIndex;
}
#endif
return opIndex;
}

View File

@ -1397,25 +1397,26 @@ private:
class DrawRenderNodeOp : public DrawBoundedOp {
friend class RenderNode; // grant RenderNode access to info of child
friend class DisplayListData; // grant DisplayListData access to info of child
friend class DisplayListCanvas;
public:
DrawRenderNodeOp(RenderNode* renderNode, const mat4& transformFromParent, bool clipIsSimple)
: DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), nullptr)
, mRenderNode(renderNode)
, renderNode(renderNode)
, mRecordedWithPotentialStencilClip(!clipIsSimple || !transformFromParent.isSimple())
, mTransformFromParent(transformFromParent)
, mSkipInOrderDraw(false) {}
virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
bool useQuickReject) override {
if (mRenderNode->isRenderable() && !mSkipInOrderDraw) {
mRenderNode->defer(deferStruct, level + 1);
if (renderNode->isRenderable() && !mSkipInOrderDraw) {
renderNode->defer(deferStruct, level + 1);
}
}
virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level,
bool useQuickReject) override {
if (mRenderNode->isRenderable() && !mSkipInOrderDraw) {
mRenderNode->replay(replayStruct, level + 1);
if (renderNode->isRenderable() && !mSkipInOrderDraw) {
renderNode->replay(replayStruct, level + 1);
}
}
@ -1424,18 +1425,16 @@ public:
}
virtual void output(int level, uint32_t logFlags) const override {
OP_LOG("Draw RenderNode %p %s", mRenderNode, mRenderNode->getName());
if (mRenderNode && (logFlags & kOpLogFlag_Recurse)) {
mRenderNode->output(level + 1);
OP_LOG("Draw RenderNode %p %s", renderNode, renderNode->getName());
if (renderNode && (logFlags & kOpLogFlag_Recurse)) {
renderNode->output(level + 1);
}
}
virtual const char* name() override { return "DrawRenderNode"; }
RenderNode* renderNode() { return mRenderNode; }
private:
RenderNode* mRenderNode;
RenderNode* renderNode;
/**
* This RenderNode was drawn into a DisplayList with the canvas in a state that will likely

View File

@ -461,12 +461,12 @@ GlopBuilder& GlopBuilder::setFillTextureLayer(Layer& layer, float alpha) {
// Transform
////////////////////////////////////////////////////////////////////////////////
void GlopBuilder::setTransform(const Matrix4& canvas,
const int transformFlags) {
GlopBuilder& GlopBuilder::setTransform(const Matrix4& canvas, const int transformFlags) {
TRIGGER_STAGE(kTransformStage);
mOutGlop->transform.canvas = canvas;
mOutGlop->transform.transformFlags = transformFlags;
return *this;
}
////////////////////////////////////////////////////////////////////////////////
@ -632,5 +632,42 @@ void GlopBuilder::build() {
mOutGlop->transform.meshTransform().mapRect(mOutGlop->bounds);
}
void GlopBuilder::dump(const Glop& glop) {
ALOGD("Glop Mesh");
const Glop::Mesh& mesh = glop.mesh;
ALOGD(" primitive mode: %d", mesh.primitiveMode);
ALOGD(" indices: buffer obj %x, indices %p", mesh.indices.bufferObject, mesh.indices.indices);
const Glop::Mesh::Vertices& vertices = glop.mesh.vertices;
ALOGD(" vertices: buffer obj %x, flags %x, pos %p, tex %p, clr %p, stride %d",
vertices.bufferObject, vertices.attribFlags,
vertices.position, vertices.texCoord, vertices.color, vertices.stride);
ALOGD(" element count: %d", mesh.elementCount);
ALOGD("Glop Fill");
const Glop::Fill& fill = glop.fill;
ALOGD(" program %p", fill.program);
if (fill.texture.texture) {
ALOGD(" texture %p, target %d, filter %d, clamp %d",
fill.texture.texture, fill.texture.target, fill.texture.filter, fill.texture.clamp);
if (fill.texture.textureTransform) {
fill.texture.textureTransform->dump("texture transform");
}
}
ALOGD_IF(fill.colorEnabled, " color (argb) %.2f %.2f %.2f %.2f",
fill.color.a, fill.color.r, fill.color.g, fill.color.b);
ALOGD_IF(fill.filterMode != ProgramDescription::ColorFilterMode::None,
" filterMode %d", (int)fill.filterMode);
ALOGD_IF(fill.skiaShaderData.skiaShaderType, " shader type %d",
fill.skiaShaderData.skiaShaderType);
ALOGD("Glop transform");
glop.transform.modelView.dump("model view");
glop.transform.canvas.dump("canvas");
ALOGD("Glop blend %d %d", glop.blend.src, glop.blend.dst);
ALOGD("Glop bounds " RECT_STRING, RECT_ARGS(glop.bounds));
}
} /* namespace uirenderer */
} /* namespace android */

View File

@ -71,9 +71,9 @@ public:
GlopBuilder& setFillTextureLayer(Layer& layer, float alpha);
GlopBuilder& setTransform(const Snapshot& snapshot, const int transformFlags) {
setTransform(*snapshot.transform, transformFlags);
return *this;
return setTransform(*snapshot.transform, transformFlags);
}
GlopBuilder& setTransform(const Matrix4& canvas, const int transformFlags);
GlopBuilder& setModelViewMapUnitToRect(const Rect destination);
GlopBuilder& setModelViewMapUnitToRectSnap(const Rect destination);
@ -98,11 +98,12 @@ public:
GlopBuilder& setRoundRectClipState(const RoundRectClipState* roundRectClipState);
void build();
static void dump(const Glop& glop);
private:
void setFill(int color, float alphaScale,
SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage,
const SkShader* shader, const SkColorFilter* colorFilter);
void setTransform(const Matrix4& canvas, const int transformFlags);
enum StageFlags {
kInitialStage = 0,

View File

@ -126,6 +126,9 @@ public:
void loadMultiply(const Matrix4& u, const Matrix4& v);
void loadOrtho(float left, float right, float bottom, float top, float near, float far);
void loadOrtho(int width, int height) {
loadOrtho(0, width, height, 0, -1, 1);
}
uint8_t getType() const;

404
libs/hwui/OpReorderer.cpp Normal file
View File

@ -0,0 +1,404 @@
/*
* Copyright (C) 2015 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 "OpReorderer.h"
#include "utils/PaintUtils.h"
#include "RenderNode.h"
#include "SkCanvas.h"
#include "utils/Trace.h"
namespace android {
namespace uirenderer {
class BatchBase {
public:
BatchBase(batchid_t batchId, BakedOpState* op, bool merging)
: mBatchId(batchId)
, mMerging(merging) {
mBounds = op->computedState.clippedBounds;
mOps.push_back(op);
}
bool intersects(const Rect& rect) const {
if (!rect.intersects(mBounds)) return false;
for (const BakedOpState* op : mOps) {
if (rect.intersects(op->computedState.clippedBounds)) {
return true;
}
}
return false;
}
batchid_t getBatchId() const { return mBatchId; }
bool isMerging() const { return mMerging; }
const std::vector<BakedOpState*>& getOps() const { return mOps; }
void dump() const {
ALOGD(" Batch %p, merging %d, bounds " RECT_STRING, this, mMerging, RECT_ARGS(mBounds));
}
protected:
batchid_t mBatchId;
Rect mBounds;
std::vector<BakedOpState*> mOps;
bool mMerging;
};
class OpBatch : public BatchBase {
public:
static void* operator new(size_t size, LinearAllocator& allocator) {
return allocator.alloc(size);
}
OpBatch(batchid_t batchId, BakedOpState* op)
: BatchBase(batchId, op, false) {
}
void batchOp(BakedOpState* op) {
mBounds.unionWith(op->computedState.clippedBounds);
mOps.push_back(op);
}
};
class MergingOpBatch : public BatchBase {
public:
static void* operator new(size_t size, LinearAllocator& allocator) {
return allocator.alloc(size);
}
MergingOpBatch(batchid_t batchId, BakedOpState* op)
: BatchBase(batchId, op, true) {
}
/*
* Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds
* and clip side flags. Positive bounds delta means new bounds fit in old.
*/
static inline bool checkSide(const int currentFlags, const int newFlags, const int side,
float boundsDelta) {
bool currentClipExists = currentFlags & side;
bool newClipExists = newFlags & side;
// if current is clipped, we must be able to fit new bounds in current
if (boundsDelta > 0 && currentClipExists) return false;
// if new is clipped, we must be able to fit current bounds in new
if (boundsDelta < 0 && newClipExists) return false;
return true;
}
static bool paintIsDefault(const SkPaint& paint) {
return paint.getAlpha() == 255
&& paint.getColorFilter() == nullptr
&& paint.getShader() == nullptr;
}
static bool paintsAreEquivalent(const SkPaint& a, const SkPaint& b) {
return a.getAlpha() == b.getAlpha()
&& a.getColorFilter() == b.getColorFilter()
&& a.getShader() == b.getShader();
}
/*
* Checks if a (mergeable) op can be merged into this batch
*
* If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is
* important to consider all paint attributes used in the draw calls in deciding both a) if an
* op tries to merge at all, and b) if the op can merge with another set of ops
*
* False positives can lead to information from the paints of subsequent merged operations being
* dropped, so we make simplifying qualifications on the ops that can merge, per op type.
*/
bool canMergeWith(BakedOpState* op) const {
bool isTextBatch = getBatchId() == OpBatchType::Text
|| getBatchId() == OpBatchType::ColorText;
// Overlapping other operations is only allowed for text without shadow. For other ops,
// multiDraw isn't guaranteed to overdraw correctly
if (!isTextBatch || PaintUtils::hasTextShadow(op->op->paint)) {
if (intersects(op->computedState.clippedBounds)) return false;
}
const BakedOpState* lhs = op;
const BakedOpState* rhs = mOps[0];
if (!MathUtils::areEqual(lhs->alpha, rhs->alpha)) return false;
// Identical round rect clip state means both ops will clip in the same way, or not at all.
// As the state objects are const, we can compare their pointers to determine mergeability
if (lhs->roundRectClipState != rhs->roundRectClipState) return false;
if (lhs->projectionPathMask != rhs->projectionPathMask) return false;
/* Clipping compatibility check
*
* Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its
* clip for that side.
*/
const int currentFlags = mClipSideFlags;
const int newFlags = op->computedState.clipSideFlags;
if (currentFlags != OpClipSideFlags::None || newFlags != OpClipSideFlags::None) {
const Rect& opBounds = op->computedState.clippedBounds;
float boundsDelta = mBounds.left - opBounds.left;
if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Left, boundsDelta)) return false;
boundsDelta = mBounds.top - opBounds.top;
if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Top, boundsDelta)) return false;
// right and bottom delta calculation reversed to account for direction
boundsDelta = opBounds.right - mBounds.right;
if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Right, boundsDelta)) return false;
boundsDelta = opBounds.bottom - mBounds.bottom;
if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Bottom, boundsDelta)) return false;
}
const SkPaint* newPaint = op->op->paint;
const SkPaint* oldPaint = mOps[0]->op->paint;
if (newPaint == oldPaint) {
// if paints are equal, then modifiers + paint attribs don't need to be compared
return true;
} else if (newPaint && !oldPaint) {
return paintIsDefault(*newPaint);
} else if (!newPaint && oldPaint) {
return paintIsDefault(*oldPaint);
}
return paintsAreEquivalent(*newPaint, *oldPaint);
}
void mergeOp(BakedOpState* op) {
mBounds.unionWith(op->computedState.clippedBounds);
mOps.push_back(op);
const int newClipSideFlags = op->computedState.clipSideFlags;
mClipSideFlags |= newClipSideFlags;
const Rect& opClip = op->computedState.clipRect;
if (newClipSideFlags & OpClipSideFlags::Left) mClipRect.left = opClip.left;
if (newClipSideFlags & OpClipSideFlags::Top) mClipRect.top = opClip.top;
if (newClipSideFlags & OpClipSideFlags::Right) mClipRect.right = opClip.right;
if (newClipSideFlags & OpClipSideFlags::Bottom) mClipRect.bottom = opClip.bottom;
}
private:
int mClipSideFlags = 0;
Rect mClipRect;
};
class NullClient: public CanvasStateClient {
void onViewportInitialized() override {}
void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
GLuint getTargetFbo() const override { return 0; }
};
static NullClient sNullClient;
OpReorderer::OpReorderer()
: mCanvasState(sNullClient) {
}
void OpReorderer::defer(int viewportWidth, int viewportHeight,
const std::vector< sp<RenderNode> >& nodes) {
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
0, 0, viewportWidth, viewportHeight, Vector3());
for (const sp<RenderNode>& node : nodes) {
if (node->nothingToDraw()) continue;
// TODO: dedupe this code with onRenderNode()
mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
if (node->applyViewProperties(mCanvasState)) {
// not rejected do ops...
const DisplayListData& data = node->getDisplayListData();
deferImpl(data.getChunks(), data.getOps());
}
mCanvasState.restore();
}
}
void OpReorderer::defer(int viewportWidth, int viewportHeight,
const std::vector<DisplayListData::Chunk>& chunks, const std::vector<RecordedOp*>& ops) {
ATRACE_NAME("prepare drawing commands");
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
0, 0, viewportWidth, viewportHeight, Vector3());
deferImpl(chunks, ops);
}
/**
* Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
*
* This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. E.g. a
* BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
*/
#define OP_RECIEVER(Type) \
[](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
void OpReorderer::deferImpl(const std::vector<DisplayListData::Chunk>& chunks,
const std::vector<RecordedOp*>& ops) {
static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = {
MAP_OPS(OP_RECIEVER)
};
for (const DisplayListData::Chunk& chunk : chunks) {
for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
const RecordedOp* op = ops[opIndex];
receivers[op->opId](*this, *op);
}
}
}
void OpReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) {
ATRACE_NAME("flush drawing commands");
for (const BatchBase* batch : mBatches) {
// TODO: different behavior based on batch->isMerging()
for (const BakedOpState* op : batch->getOps()) {
receivers[op->op->opId](arg, *op->op, *op);
}
}
}
BakedOpState* OpReorderer::bakeOpState(const RecordedOp& recordedOp) {
return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
}
void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
if (op.renderNode->nothingToDraw()) {
return;
}
mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
// apply state from RecordedOp
mCanvasState.concatMatrix(op.localMatrix);
mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top,
op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op);
// apply RenderProperties state
if (op.renderNode->applyViewProperties(mCanvasState)) {
// not rejected do ops...
const DisplayListData& data = op.renderNode->getDisplayListData();
deferImpl(data.getChunks(), data.getOps());
}
mCanvasState.restore();
}
static batchid_t tessellatedBatchId(const SkPaint& paint) {
return paint.getPathEffect()
? OpBatchType::AlphaMaskTexture
: (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices);
}
void OpReorderer::onBitmapOp(const BitmapOp& op) {
BakedOpState* bakedStateOp = bakeOpState(op);
if (!bakedStateOp) return; // quick rejected
mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
// TODO: AssetAtlas
deferMergeableOp(bakedStateOp, OpBatchType::Bitmap, mergeId);
}
void OpReorderer::onRectOp(const RectOp& op) {
BakedOpState* bakedStateOp = bakeOpState(op);
if (!bakedStateOp) return; // quick rejected
deferUnmergeableOp(bakedStateOp, tessellatedBatchId(*op.paint));
}
void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
BakedOpState* bakedStateOp = bakeOpState(op);
if (!bakedStateOp) return; // quick rejected
deferUnmergeableOp(bakedStateOp, OpBatchType::Vertices);
}
// iterate back toward target to see if anything drawn since should overlap the new op
// if no target, merging ops still interate to find similar batch to insert after
void OpReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
BatchBase** targetBatch, size_t* insertBatchIndex) const {
for (size_t i = mBatches.size() - 1; i >= mEarliestBatchIndex; i--) {
BatchBase* overBatch = mBatches[i];
if (overBatch == *targetBatch) break;
// TODO: also consider shader shared between batch types
if (batchId == overBatch->getBatchId()) {
*insertBatchIndex = i + 1;
if (!*targetBatch) break; // found insert position, quit
}
if (overBatch->intersects(clippedBounds)) {
// NOTE: it may be possible to optimize for special cases where two operations
// of the same batch/paint could swap order, such as with a non-mergeable
// (clipped) and a mergeable text operation
*targetBatch = nullptr;
break;
}
}
}
void OpReorderer::deferUnmergeableOp(BakedOpState* op, batchid_t batchId) {
OpBatch* targetBatch = mBatchLookup[batchId];
size_t insertBatchIndex = mBatches.size();
if (targetBatch) {
locateInsertIndex(batchId, op->computedState.clippedBounds,
(BatchBase**)(&targetBatch), &insertBatchIndex);
}
if (targetBatch) {
targetBatch->batchOp(op);
} else {
// new non-merging batch
targetBatch = new (mAllocator) OpBatch(batchId, op);
mBatchLookup[batchId] = targetBatch;
mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
}
}
// insertion point of a new batch, will hopefully be immediately after similar batch
// (generally, should be similar shader)
void OpReorderer::deferMergeableOp(BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
MergingOpBatch* targetBatch = nullptr;
// Try to merge with any existing batch with same mergeId
auto getResult = mMergingBatches[batchId].find(mergeId);
if (getResult != mMergingBatches[batchId].end()) {
targetBatch = getResult->second;
if (!targetBatch->canMergeWith(op)) {
targetBatch = nullptr;
}
}
size_t insertBatchIndex = mBatches.size();
locateInsertIndex(batchId, op->computedState.clippedBounds,
(BatchBase**)(&targetBatch), &insertBatchIndex);
if (targetBatch) {
targetBatch->mergeOp(op);
} else {
// new merging batch
targetBatch = new (mAllocator) MergingOpBatch(batchId, op);
mMergingBatches[batchId].insert(std::make_pair(mergeId, targetBatch));
mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
}
}
void OpReorderer::dump() {
for (const BatchBase* batch : mBatches) {
batch->dump();
}
}
} // namespace uirenderer
} // namespace android

142
libs/hwui/OpReorderer.h Normal file
View File

@ -0,0 +1,142 @@
/*
* Copyright (C) 2015 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.
*/
#ifndef ANDROID_HWUI_OP_REORDERER_H
#define ANDROID_HWUI_OP_REORDERER_H
#include "BakedOpState.h"
#include "CanvasState.h"
#include "DisplayList.h"
#include "RecordedOp.h"
#include <vector>
#include <unordered_map>
namespace android {
namespace uirenderer {
class BakedOpState;
class BatchBase;
class MergingOpBatch;
class OpBatch;
class Rect;
typedef int batchid_t;
typedef const void* mergeid_t;
namespace OpBatchType {
enum {
None = 0, // Don't batch
Bitmap,
Patch,
AlphaVertices,
Vertices,
AlphaMaskTexture,
Text,
ColorText,
Count // must be last
};
}
class OpReorderer {
public:
OpReorderer();
// TODO: not final, just presented this way for simplicity. Layers too?
void defer(int viewportWidth, int viewportHeight, const std::vector< sp<RenderNode> >& nodes);
void defer(int viewportWidth, int viewportHeight,
const std::vector<DisplayListData::Chunk>& chunks, const std::vector<RecordedOp*>& ops);
typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpReceiver;
/**
* replayBakedOps() is templated based on what class will recieve ops being replayed.
*
* It constructs a lookup array of lambdas, which allows a recorded BakeOpState to use
* state->op->opId to lookup a receiver that will be called when the op is replayed.
*
* For example a BitmapOp would resolve, via the lambda lookup, to calling:
*
* StaticReceiver::onBitmapOp(Arg* arg, const BitmapOp& op, const BakedOpState& state);
*/
#define BAKED_OP_RECEIVER(Type) \
[](void* internalArg, const RecordedOp& op, const BakedOpState& state) { \
StaticReceiver::on##Type(static_cast<Arg*>(internalArg), static_cast<const Type&>(op), state); \
},
template <typename StaticReceiver, typename Arg>
void replayBakedOps(Arg* arg) {
static BakedOpReceiver receivers[] = {
MAP_OPS(BAKED_OP_RECEIVER)
};
StaticReceiver::startFrame(*arg);
replayBakedOpsImpl((void*)arg, receivers);
StaticReceiver::endFrame(*arg);
}
private:
BakedOpState* bakeOpState(const RecordedOp& recordedOp);
void deferImpl(const std::vector<DisplayListData::Chunk>& chunks,
const std::vector<RecordedOp*>& ops);
void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers);
/**
* Declares all OpReorderer::onXXXXOp() methods for every RecordedOp type.
*
* These private methods are called from within deferImpl to defer each individual op
* type differently.
*/
#define INTERNAL_OP_HANDLER(Type) \
void on##Type(const Type& op);
MAP_OPS(INTERNAL_OP_HANDLER)
// iterate back toward target to see if anything drawn since should overlap the new op
// if no target, merging ops still iterate to find similar batch to insert after
void locateInsertIndex(int batchId, const Rect& clippedBounds,
BatchBase** targetBatch, size_t* insertBatchIndex) const;
void deferUnmergeableOp(BakedOpState* op, batchid_t batchId);
// insertion point of a new batch, will hopefully be immediately after similar batch
// (generally, should be similar shader)
void deferMergeableOp(BakedOpState* op, batchid_t batchId, mergeid_t mergeId);
void dump();
std::vector<BatchBase*> mBatches;
/**
* Maps the mergeid_t returned by an op's getMergeId() to the most recently seen
* MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
* collide, which avoids the need to resolve mergeid collisions.
*/
std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatches[OpBatchType::Count];
// Maps batch ids to the most recent *non-merging* batch of that id
OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
CanvasState mCanvasState;
// contains ResolvedOps and Batches
LinearAllocator mAllocator;
size_t mEarliestBatchIndex = 0;
};
}; // namespace uirenderer
}; // namespace android
#endif // ANDROID_HWUI_OP_REORDERER_H

118
libs/hwui/RecordedOp.h Normal file
View File

@ -0,0 +1,118 @@
/*
* Copyright (C) 2015 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.
*/
#ifndef ANDROID_HWUI_RECORDED_OP_H
#define ANDROID_HWUI_RECORDED_OP_H
#include "utils/LinearAllocator.h"
#include "Rect.h"
#include "Matrix.h"
#include "SkXfermode.h"
class SkBitmap;
class SkPaint;
namespace android {
namespace uirenderer {
class RenderNode;
struct Vertex;
/**
* The provided macro is executed for each op type in order, with the results separated by commas.
*
* This serves as the authoritative list of ops, used for generating ID enum, and ID based LUTs.
*/
#define MAP_OPS(OP_FN) \
OP_FN(BitmapOp) \
OP_FN(RectOp) \
OP_FN(RenderNodeOp) \
OP_FN(SimpleRectsOp)
// Generate OpId enum
#define IDENTITY_FN(Type) Type,
namespace RecordedOpId {
enum {
MAP_OPS(IDENTITY_FN)
Count,
};
}
static_assert(RecordedOpId::BitmapOp == 0,
"First index must be zero for LUTs to work");
#define BASE_PARAMS const Rect& unmappedBounds, const Matrix4& localMatrix, const Rect& localClipRect, const SkPaint* paint
#define BASE_PARAMS_PAINTLESS const Rect& unmappedBounds, const Matrix4& localMatrix, const Rect& localClipRect
#define SUPER(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClipRect, paint)
#define SUPER_PAINTLESS(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClipRect, nullptr)
struct RecordedOp {
/* ID from RecordedOpId - generally used for jumping into function tables */
const int opId;
/* bounds in *local* space, without accounting for DisplayList transformation */
const Rect unmappedBounds;
/* transform in recording space (vs DisplayList origin) */
const Matrix4 localMatrix;
/* clip in recording space */
const Rect localClipRect;
/* optional paint, stored in base object to simplify merging logic */
const SkPaint* paint;
protected:
RecordedOp(unsigned int opId, BASE_PARAMS)
: opId(opId)
, unmappedBounds(unmappedBounds)
, localMatrix(localMatrix)
, localClipRect(localClipRect)
, paint(paint) {}
};
struct RenderNodeOp : RecordedOp {
RenderNodeOp(BASE_PARAMS_PAINTLESS, RenderNode* renderNode)
: SUPER_PAINTLESS(RenderNodeOp)
, renderNode(renderNode) {}
RenderNode * renderNode; // not const, since drawing modifies it (somehow...)
};
struct BitmapOp : RecordedOp {
BitmapOp(BASE_PARAMS, const SkBitmap* bitmap)
: SUPER(BitmapOp)
, bitmap(bitmap) {}
const SkBitmap* bitmap;
// TODO: asset atlas/texture id lookup?
};
struct RectOp : RecordedOp {
RectOp(BASE_PARAMS)
: SUPER(RectOp) {}
};
struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?)
SimpleRectsOp(BASE_PARAMS, Vertex* vertices, size_t vertexCount)
: SUPER(SimpleRectsOp)
, vertices(vertices)
, vertexCount(vertexCount) {}
Vertex* vertices;
const size_t vertexCount;
};
}; // namespace uirenderer
}; // namespace android
#endif // ANDROID_HWUI_RECORDED_OP_H

View File

@ -0,0 +1,402 @@
/*
* Copyright (C) 2015 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 "RecordingCanvas.h"
#include "RecordedOp.h"
#include "RenderNode.h"
namespace android {
namespace uirenderer {
RecordingCanvas::RecordingCanvas(size_t width, size_t height)
: mState(*this)
, mResourceCache(ResourceCache::getInstance()) {
reset(width, height);
}
RecordingCanvas::~RecordingCanvas() {
LOG_ALWAYS_FATAL_IF(mDisplayListData,
"Destroyed a RecordingCanvas during a record!");
}
void RecordingCanvas::reset(int width, int height) {
LOG_ALWAYS_FATAL_IF(mDisplayListData,
"prepareDirty called a second time during a recording!");
mDisplayListData = new DisplayListData();
mState.initializeSaveStack(width, height, 0, 0, width, height, Vector3());
mDeferredBarrierType = kBarrier_InOrder;
mState.setDirtyClip(false);
mRestoreSaveCount = -1;
}
DisplayListData* RecordingCanvas::finishRecording() {
mPaintMap.clear();
mRegionMap.clear();
mPathMap.clear();
DisplayListData* data = mDisplayListData;
mDisplayListData = nullptr;
mSkiaCanvasProxy.reset(nullptr);
return data;
}
SkCanvas* RecordingCanvas::asSkCanvas() {
LOG_ALWAYS_FATAL_IF(!mDisplayListData,
"attempting to get an SkCanvas when we are not recording!");
if (!mSkiaCanvasProxy) {
mSkiaCanvasProxy.reset(new SkiaCanvasProxy(this));
}
// SkCanvas instances default to identity transform, but should inherit
// the state of this Canvas; if this code was in the SkiaCanvasProxy
// constructor, we couldn't cache mSkiaCanvasProxy.
SkMatrix parentTransform;
getMatrix(&parentTransform);
mSkiaCanvasProxy.get()->setMatrix(parentTransform);
return mSkiaCanvasProxy.get();
}
// ----------------------------------------------------------------------------
// android/graphics/Canvas state operations
// ----------------------------------------------------------------------------
// Save (layer)
int RecordingCanvas::save(SkCanvas::SaveFlags flags) {
return mState.save((int) flags);
}
void RecordingCanvas::RecordingCanvas::restore() {
if (mRestoreSaveCount < 0) {
restoreToCount(getSaveCount() - 1);
return;
}
mRestoreSaveCount--;
mState.restore();
}
void RecordingCanvas::restoreToCount(int saveCount) {
mRestoreSaveCount = saveCount;
mState.restoreToCount(saveCount);
}
int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
SkCanvas::SaveFlags flags) {
LOG_ALWAYS_FATAL("TODO");
return 0;
}
// Matrix
void RecordingCanvas::rotate(float degrees) {
if (degrees == 0) return;
mState.rotate(degrees);
}
void RecordingCanvas::scale(float sx, float sy) {
if (sx == 1 && sy == 1) return;
mState.scale(sx, sy);
}
void RecordingCanvas::skew(float sx, float sy) {
mState.skew(sx, sy);
}
void RecordingCanvas::translate(float dx, float dy) {
if (dx == 0 && dy == 0) return;
mState.translate(dx, dy, 0);
}
// Clip
bool RecordingCanvas::getClipBounds(SkRect* outRect) const {
Rect bounds = mState.getLocalClipBounds();
*outRect = SkRect::MakeLTRB(bounds.left, bounds.top, bounds.right, bounds.bottom);
return !(outRect->isEmpty());
}
bool RecordingCanvas::quickRejectRect(float left, float top, float right, float bottom) const {
return mState.quickRejectConservative(left, top, right, bottom);
}
bool RecordingCanvas::quickRejectPath(const SkPath& path) const {
SkRect bounds = path.getBounds();
return mState.quickRejectConservative(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
}
bool RecordingCanvas::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
return mState.clipRect(left, top, right, bottom, op);
}
bool RecordingCanvas::clipPath(const SkPath* path, SkRegion::Op op) {
return mState.clipPath(path, op);
}
bool RecordingCanvas::clipRegion(const SkRegion* region, SkRegion::Op op) {
return mState.clipRegion(region, op);
}
// ----------------------------------------------------------------------------
// android/graphics/Canvas draw operations
// ----------------------------------------------------------------------------
void RecordingCanvas::drawColor(int color, SkXfermode::Mode mode) {
SkPaint paint;
paint.setColor(color);
paint.setXfermodeMode(mode);
drawPaint(paint);
}
void RecordingCanvas::drawPaint(const SkPaint& paint) {
// TODO: more efficient recording?
Matrix4 identity;
identity.loadIdentity();
addOp(new (alloc()) RectOp(
mState.getRenderTargetClipBounds(),
identity,
mState.getRenderTargetClipBounds(),
refPaint(&paint)));
}
// Geometry
void RecordingCanvas::drawPoints(const float* points, int count, const SkPaint& paint) {
LOG_ALWAYS_FATAL("TODO!");
}
void RecordingCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
LOG_ALWAYS_FATAL("TODO!");
}
void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
addOp(new (alloc()) RectOp(
Rect(left, top, right, bottom),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
refPaint(&paint)));
}
void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) {
if (rects == nullptr) return;
Vertex* rectData = (Vertex*) mDisplayListData->allocator.alloc(vertexCount * sizeof(Vertex));
Vertex* vertex = rectData;
float left = FLT_MAX;
float top = FLT_MAX;
float right = FLT_MIN;
float bottom = FLT_MIN;
for (int index = 0; index < vertexCount; index += 4) {
float l = rects[index + 0];
float t = rects[index + 1];
float r = rects[index + 2];
float b = rects[index + 3];
Vertex::set(vertex++, l, t);
Vertex::set(vertex++, r, t);
Vertex::set(vertex++, l, b);
Vertex::set(vertex++, r, b);
left = std::min(left, l);
top = std::min(top, t);
right = std::max(right, r);
bottom = std::max(bottom, b);
}
addOp(new (alloc()) SimpleRectsOp(
Rect(left, top, right, bottom),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
refPaint(paint), rectData, vertexCount));
}
void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
if (paint.getStyle() == SkPaint::kFill_Style
&& (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) {
int count = 0;
Vector<float> rects;
SkRegion::Iterator it(region);
while (!it.done()) {
const SkIRect& r = it.rect();
rects.push(r.fLeft);
rects.push(r.fTop);
rects.push(r.fRight);
rects.push(r.fBottom);
count += 4;
it.next();
}
drawSimpleRects(rects.array(), count, &paint);
} else {
SkRegion::Iterator it(region);
while (!it.done()) {
const SkIRect& r = it.rect();
drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint);
it.next();
}
}
}
void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, const SkPaint& paint) {
LOG_ALWAYS_FATAL("TODO!");
}
void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
LOG_ALWAYS_FATAL("TODO!");
}
void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
LOG_ALWAYS_FATAL("TODO!");
}
void RecordingCanvas::drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
LOG_ALWAYS_FATAL("TODO!");
}
void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
LOG_ALWAYS_FATAL("TODO!");
}
// Bitmap-based
void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) {
save(SkCanvas::kMatrix_SaveFlag);
translate(left, top);
drawBitmap(&bitmap, paint);
restore();
}
void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
const SkPaint* paint) {
if (matrix.isIdentity()) {
drawBitmap(&bitmap, paint);
} else if (!(matrix.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask))
&& MathUtils::isPositive(matrix.getScaleX())
&& MathUtils::isPositive(matrix.getScaleY())) {
// SkMatrix::isScaleTranslate() not available in L
SkRect src;
SkRect dst;
bitmap.getBounds(&src);
matrix.mapRect(&dst, src);
drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint);
} else {
save(SkCanvas::kMatrix_SaveFlag);
concat(matrix);
drawBitmap(&bitmap, paint);
restore();
}
}
void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
float srcRight, float srcBottom, float dstLeft, float dstTop,
float dstRight, float dstBottom, const SkPaint* paint) {
if (srcLeft == 0 && srcTop == 0
&& srcRight == bitmap.width()
&& srcBottom == bitmap.height()
&& (srcBottom - srcTop == dstBottom - dstTop)
&& (srcRight - srcLeft == dstRight - dstLeft)) {
// transform simple rect to rect drawing case into position bitmap ops, since they merge
save(SkCanvas::kMatrix_SaveFlag);
translate(dstLeft, dstTop);
drawBitmap(&bitmap, paint);
restore();
} else {
LOG_ALWAYS_FATAL("TODO!");
}
}
void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
const float* vertices, const int* colors, const SkPaint* paint) {
LOG_ALWAYS_FATAL("TODO!");
}
void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
float dstLeft, float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) {
LOG_ALWAYS_FATAL("TODO!");
}
// Text
void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int count,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
float boundsRight, float boundsBottom, float totalAdvance) {
LOG_ALWAYS_FATAL("TODO!");
}
void RecordingCanvas::drawPosText(const uint16_t* text, const float* positions, int count,
int posCount, const SkPaint& paint) {
LOG_ALWAYS_FATAL("TODO!");
}
void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) {
LOG_ALWAYS_FATAL("TODO!");
}
void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
addOp(new (alloc()) BitmapOp(
Rect(0, 0, bitmap->width(), bitmap->height()),
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
refPaint(paint), refBitmap(*bitmap)));
}
void RecordingCanvas::drawRenderNode(RenderNode* renderNode) {
RenderNodeOp* op = new (alloc()) RenderNodeOp(
Rect(0, 0, renderNode->getWidth(), renderNode->getHeight()), // are these safe? they're theoretically dynamic
*(mState.currentSnapshot()->transform),
mState.getRenderTargetClipBounds(),
renderNode);
int opIndex = addOp(op);
int childIndex = mDisplayListData->addChild(op);
// update the chunk's child indices
DisplayListData::Chunk& chunk = mDisplayListData->chunks.back();
chunk.endChildIndex = childIndex + 1;
if (renderNode->stagingProperties().isProjectionReceiver()) {
// use staging property, since recording on UI thread
mDisplayListData->projectionReceiveIndex = opIndex;
}
}
size_t RecordingCanvas::addOp(RecordedOp* op) {
// TODO: validate if "addDrawOp" quickrejection logic is useful before adding
int insertIndex = mDisplayListData->ops.size();
mDisplayListData->ops.push_back(op);
if (mDeferredBarrierType != kBarrier_None) {
// op is first in new chunk
mDisplayListData->chunks.emplace_back();
DisplayListData::Chunk& newChunk = mDisplayListData->chunks.back();
newChunk.beginOpIndex = insertIndex;
newChunk.endOpIndex = insertIndex + 1;
newChunk.reorderChildren = (mDeferredBarrierType == kBarrier_OutOfOrder);
int nextChildIndex = mDisplayListData->children().size();
newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
mDeferredBarrierType = kBarrier_None;
} else {
// standard case - append to existing chunk
mDisplayListData->chunks.back().endOpIndex = insertIndex + 1;
}
return insertIndex;
}
void RecordingCanvas::refBitmapsInShader(const SkShader* shader) {
if (!shader) return;
// If this paint has an SkShader that has an SkBitmap add
// it to the bitmap pile
SkBitmap bitmap;
SkShader::TileMode xy[2];
if (shader->asABitmap(&bitmap, nullptr, xy) == SkShader::kDefault_BitmapType) {
refBitmap(bitmap);
return;
}
SkShader::ComposeRec rec;
if (shader->asACompose(&rec)) {
refBitmapsInShader(rec.fShaderA);
refBitmapsInShader(rec.fShaderB);
return;
}
}
}; // namespace uirenderer
}; // namespace android

300
libs/hwui/RecordingCanvas.h Normal file
View File

@ -0,0 +1,300 @@
/*
* Copyright (C) 2015 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.
*/
#ifndef ANDROID_HWUI_RECORDING_CANVAS_H
#define ANDROID_HWUI_RECORDING_CANVAS_H
#include "Canvas.h"
#include "CanvasState.h"
#include "DisplayList.h"
#include "utils/LinearAllocator.h"
#include "utils/NinePatch.h"
#include "ResourceCache.h"
#include "SkiaCanvasProxy.h"
#include "Snapshot.h"
#include "SkDrawFilter.h"
#include <vector>
namespace android {
namespace uirenderer {
class OpReceiver;
struct RecordedOp;
class RecordingCanvas: public Canvas, public CanvasStateClient {
public:
RecordingCanvas(size_t width, size_t height);
virtual ~RecordingCanvas();
void reset(int width, int height);
DisplayListData* finishRecording();
// ----------------------------------------------------------------------------
// MISC HWUI OPERATIONS - TODO: CATEGORIZE
// ----------------------------------------------------------------------------
void insertReorderBarrier(bool enableReorder) {}
void drawRenderNode(RenderNode* renderNode);
// ----------------------------------------------------------------------------
// CanvasStateClient interface
// ----------------------------------------------------------------------------
virtual void onViewportInitialized() override {}
virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override {}
virtual GLuint getTargetFbo() const override { return -1; }
// ----------------------------------------------------------------------------
// android/graphics/Canvas interface
// ----------------------------------------------------------------------------
virtual SkCanvas* asSkCanvas() override;
virtual void setBitmap(const SkBitmap& bitmap) override {
LOG_ALWAYS_FATAL("RecordingCanvas is not backed by a bitmap.");
}
virtual bool isOpaque() override { return false; }
virtual int width() override { return mState.getWidth(); }
virtual int height() override { return mState.getHeight(); }
virtual void setHighContrastText(bool highContrastText) override {
mHighContrastText = highContrastText;
}
virtual bool isHighContrastText() override { return mHighContrastText; }
// ----------------------------------------------------------------------------
// android/graphics/Canvas state operations
// ----------------------------------------------------------------------------
// Save (layer)
virtual int getSaveCount() const override { return mState.getSaveCount(); }
virtual int save(SkCanvas::SaveFlags flags) override;
virtual void restore() override;
virtual void restoreToCount(int saveCount) override;
virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
SkCanvas::SaveFlags flags) override;
virtual int saveLayerAlpha(float left, float top, float right, float bottom,
int alpha, SkCanvas::SaveFlags flags) override {
SkPaint paint;
paint.setAlpha(alpha);
return saveLayer(left, top, right, bottom, &paint, flags);
}
// Matrix
virtual void getMatrix(SkMatrix* outMatrix) const override { mState.getMatrix(outMatrix); }
virtual void setMatrix(const SkMatrix& matrix) override { mState.setMatrix(matrix); }
virtual void concat(const SkMatrix& matrix) override { mState.concatMatrix(matrix); }
virtual void rotate(float degrees) override;
virtual void scale(float sx, float sy) override;
virtual void skew(float sx, float sy) override;
virtual void translate(float dx, float dy) override;
// Clip
virtual bool getClipBounds(SkRect* outRect) const override;
virtual bool quickRejectRect(float left, float top, float right, float bottom) const override;
virtual bool quickRejectPath(const SkPath& path) const override;
virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op) override;
virtual bool clipPath(const SkPath* path, SkRegion::Op op) override;
virtual bool clipRegion(const SkRegion* region, SkRegion::Op op) override;
// Misc
virtual SkDrawFilter* getDrawFilter() override { return mDrawFilter.get(); }
virtual void setDrawFilter(SkDrawFilter* filter) override {
mDrawFilter.reset(SkSafeRef(filter));
}
// ----------------------------------------------------------------------------
// android/graphics/Canvas draw operations
// ----------------------------------------------------------------------------
virtual void drawColor(int color, SkXfermode::Mode mode) override;
virtual void drawPaint(const SkPaint& paint) override;
// Geometry
virtual void drawPoint(float x, float y, const SkPaint& paint) override {
float points[2] = { x, y };
drawPoints(points, 2, paint);
}
virtual void drawPoints(const float* points, int count, const SkPaint& paint) override;
virtual void drawLine(float startX, float startY, float stopX, float stopY,
const SkPaint& paint) override {
float points[4] = { startX, startY, stopX, stopY };
drawLines(points, 4, paint);
}
virtual void drawLines(const float* points, int count, const SkPaint& paint) override;
virtual void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override;
virtual void drawRegion(const SkRegion& region, const SkPaint& paint) override;
virtual void drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, const SkPaint& paint) override;
virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) override;
virtual void drawOval(float left, float top, float right, float bottom, const SkPaint& paint) override;
virtual void drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) override;
virtual void drawPath(const SkPath& path, const SkPaint& paint) override;
virtual void drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
const float* verts, const float* tex, const int* colors,
const uint16_t* indices, int indexCount, const SkPaint& paint) override
{ /* RecordingCanvas does not support drawVertices(); ignore */ }
// Bitmap-based
virtual void drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) override;
virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
const SkPaint* paint) override;
virtual void drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
float srcRight, float srcBottom, float dstLeft, float dstTop,
float dstRight, float dstBottom, const SkPaint* paint) override;
virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
const float* vertices, const int* colors, const SkPaint* paint) override;
virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
float dstLeft, float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
// Text
virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
float boundsRight, float boundsBottom, float totalAdvance) override;
virtual void drawPosText(const uint16_t* text, const float* positions, int count,
int posCount, const SkPaint& paint) override;
virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) override;
virtual bool drawTextAbsolutePos() const override { return false; }
private:
enum DeferredBarrierType {
kBarrier_None,
kBarrier_InOrder,
kBarrier_OutOfOrder,
};
void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint);
size_t addOp(RecordedOp* op);
// ----------------------------------------------------------------------------
// lazy object copy
// ----------------------------------------------------------------------------
LinearAllocator& alloc() { return mDisplayListData->allocator; }
void refBitmapsInShader(const SkShader* shader);
template<class T>
inline const T* refBuffer(const T* srcBuffer, int32_t count) {
if (!srcBuffer) return nullptr;
T* dstBuffer = (T*) mDisplayListData->allocator.alloc(count * sizeof(T));
memcpy(dstBuffer, srcBuffer, count * sizeof(T));
return dstBuffer;
}
inline char* refText(const char* text, size_t byteLength) {
return (char*) refBuffer<uint8_t>((uint8_t*)text, byteLength);
}
inline const SkPath* refPath(const SkPath* path) {
if (!path) return nullptr;
// The points/verbs within the path are refcounted so this copy operation
// is inexpensive and maintains the generationID of the original path.
const SkPath* cachedPath = new SkPath(*path);
mDisplayListData->pathResources.push_back(cachedPath);
return cachedPath;
}
inline const SkPaint* refPaint(const SkPaint* paint) {
if (!paint) return nullptr;
// If there is a draw filter apply it here and store the modified paint
// so that we don't need to modify the paint every time we access it.
SkTLazy<SkPaint> filteredPaint;
if (mDrawFilter.get()) {
filteredPaint.set(*paint);
mDrawFilter->filter(filteredPaint.get(), SkDrawFilter::kPaint_Type);
paint = filteredPaint.get();
}
// compute the hash key for the paint and check the cache.
const uint32_t key = paint->getHash();
const SkPaint* cachedPaint = mPaintMap.valueFor(key);
// In the unlikely event that 2 unique paints have the same hash we do a
// object equality check to ensure we don't erroneously dedup them.
if (cachedPaint == nullptr || *cachedPaint != *paint) {
cachedPaint = new SkPaint(*paint);
std::unique_ptr<const SkPaint> copy(cachedPaint);
mDisplayListData->paints.push_back(std::move(copy));
// replaceValueFor() performs an add if the entry doesn't exist
mPaintMap.replaceValueFor(key, cachedPaint);
refBitmapsInShader(cachedPaint->getShader());
}
return cachedPaint;
}
inline const SkRegion* refRegion(const SkRegion* region) {
if (!region) {
return region;
}
const SkRegion* cachedRegion = mRegionMap.valueFor(region);
// TODO: Add generation ID to SkRegion
if (cachedRegion == nullptr) {
std::unique_ptr<const SkRegion> copy(new SkRegion(*region));
cachedRegion = copy.get();
mDisplayListData->regions.push_back(std::move(copy));
// replaceValueFor() performs an add if the entry doesn't exist
mRegionMap.replaceValueFor(region, cachedRegion);
}
return cachedRegion;
}
inline const SkBitmap* refBitmap(const SkBitmap& bitmap) {
// Note that this assumes the bitmap is immutable. There are cases this won't handle
// correctly, such as creating the bitmap from scratch, drawing with it, changing its
// contents, and drawing again. The only fix would be to always copy it the first time,
// which doesn't seem worth the extra cycles for this unlikely case.
SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap);
alloc().autoDestroy(localBitmap);
mDisplayListData->bitmapResources.push_back(localBitmap);
return localBitmap;
}
inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) {
mDisplayListData->patchResources.push_back(patch);
mResourceCache.incrementRefcount(patch);
return patch;
}
DefaultKeyedVector<uint32_t, const SkPaint*> mPaintMap;
DefaultKeyedVector<const SkPath*, const SkPath*> mPathMap;
DefaultKeyedVector<const SkRegion*, const SkRegion*> mRegionMap;
CanvasState mState;
std::unique_ptr<SkiaCanvasProxy> mSkiaCanvasProxy;
ResourceCache& mResourceCache;
DeferredBarrierType mDeferredBarrierType = kBarrier_None;
DisplayListData* mDisplayListData = nullptr;
bool mHighContrastText = false;
SkAutoTUnref<SkDrawFilter> mDrawFilter;
int mRestoreSaveCount = -1;
}; // class RecordingCanvas
}; // namespace uirenderer
}; // namespace android
#endif // ANDROID_HWUI_RECORDING_CANVAS_H

View File

@ -25,6 +25,9 @@
#include "DamageAccumulator.h"
#include "Debug.h"
#if HWUI_NEW_OPS
#include "RecordedOp.h"
#endif
#include "DisplayListOp.h"
#include "LayerRenderer.h"
#include "OpenGLRenderer.h"
@ -47,7 +50,7 @@ void RenderNode::debugDumpLayers(const char* prefix) {
}
if (mDisplayListData) {
for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
mDisplayListData->children()[i]->mRenderNode->debugDumpLayers(prefix);
mDisplayListData->children()[i]->renderNode->debugDumpLayers(prefix);
}
}
}
@ -172,7 +175,7 @@ void RenderNode::copyTo(proto::RenderNode *pnode) {
pnode->clear_children();
if (mDisplayListData) {
for (auto&& child : mDisplayListData->children()) {
child->mRenderNode->copyTo(pnode->add_children());
child->renderNode->copyTo(pnode->add_children());
}
}
}
@ -332,6 +335,10 @@ void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) {
info.damageAccumulator->popTransform();
}
void RenderNode::syncProperties() {
mProperties = mStagingProperties;
}
void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
// Push the animators first so that setupStartValueIfNecessary() is called
// before properties() is trampled by stagingProperties(), as they are
@ -343,7 +350,7 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
mDirtyPropertyFields = 0;
damageSelf(info);
info.damageAccumulator->popTransform();
mProperties = mStagingProperties;
syncProperties();
applyLayerPropertiesToLayer(info);
// We could try to be clever and only re-damage if the matrix changed.
// However, we don't need to worry about that. The cost of over-damaging
@ -364,35 +371,39 @@ void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) {
mLayer->setBlend(props.needsBlending());
}
void RenderNode::syncDisplayList() {
// Make sure we inc first so that we don't fluctuate between 0 and 1,
// which would thrash the layer cache
if (mStagingDisplayListData) {
for (auto&& child : mStagingDisplayListData->children()) {
child->renderNode->incParentRefCount();
}
}
deleteDisplayListData();
mDisplayListData = mStagingDisplayListData;
mStagingDisplayListData = nullptr;
if (mDisplayListData) {
for (size_t i = 0; i < mDisplayListData->functors.size(); i++) {
(*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, nullptr);
}
}
}
void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) {
if (mNeedsDisplayListDataSync) {
mNeedsDisplayListDataSync = false;
// Make sure we inc first so that we don't fluctuate between 0 and 1,
// which would thrash the layer cache
if (mStagingDisplayListData) {
for (size_t i = 0; i < mStagingDisplayListData->children().size(); i++) {
mStagingDisplayListData->children()[i]->mRenderNode->incParentRefCount();
}
}
// Damage with the old display list first then the new one to catch any
// changes in isRenderable or, in the future, bounds
damageSelf(info);
deleteDisplayListData();
mDisplayListData = mStagingDisplayListData;
mStagingDisplayListData = nullptr;
if (mDisplayListData) {
for (size_t i = 0; i < mDisplayListData->functors.size(); i++) {
(*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, nullptr);
}
}
syncDisplayList();
damageSelf(info);
}
}
void RenderNode::deleteDisplayListData() {
if (mDisplayListData) {
for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
mDisplayListData->children()[i]->mRenderNode->decParentRefCount();
for (auto&& child : mDisplayListData->children()) {
child->renderNode->decParentRefCount();
}
}
delete mDisplayListData;
@ -407,13 +418,17 @@ void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayL
info.prepareTextures = cache.prefetchAndMarkInUse(
info.canvasContext, subtree->bitmapResources[i]);
}
for (size_t i = 0; i < subtree->children().size(); i++) {
DrawRenderNodeOp* op = subtree->children()[i];
RenderNode* childNode = op->mRenderNode;
for (auto&& op : subtree->children()) {
RenderNode* childNode = op->renderNode;
#if HWUI_NEW_OPS
info.damageAccumulator->pushTransform(&op->localMatrix);
bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip;
#else
info.damageAccumulator->pushTransform(&op->mTransformFromParent);
bool childFunctorsNeedLayer = functorsNeedLayer
// Recorded with non-rect clip, or canvas-rotated by parent
|| op->mRecordedWithPotentialStencilClip;
#endif
childNode->prepareTreeImpl(info, childFunctorsNeedLayer);
info.damageAccumulator->popTransform();
}
@ -426,8 +441,8 @@ void RenderNode::destroyHardwareResources() {
mLayer = nullptr;
}
if (mDisplayListData) {
for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
mDisplayListData->children()[i]->mRenderNode->destroyHardwareResources();
for (auto&& child : mDisplayListData->children()) {
child->renderNode->destroyHardwareResources();
}
if (mNeedsDisplayListDataSync) {
// Next prepare tree we are going to push a new display list, so we can
@ -449,6 +464,34 @@ void RenderNode::decParentRefCount() {
}
}
bool RenderNode::applyViewProperties(CanvasState& canvasState) const {
const Outline& outline = properties().getOutline();
if (properties().getAlpha() <= 0
|| (outline.getShouldClip() && outline.isEmpty())
|| properties().getScaleX() == 0
|| properties().getScaleY() == 0) {
return false; // rejected
}
if (properties().getLeft() != 0 || properties().getTop() != 0) {
canvasState.translate(properties().getLeft(), properties().getTop());
}
if (properties().getStaticMatrix()) {
canvasState.concatMatrix(*properties().getStaticMatrix());
} else if (properties().getAnimationMatrix()) {
canvasState.concatMatrix(*properties().getAnimationMatrix());
}
if (properties().hasTransformMatrix()) {
if (properties().isTransformTranslateOnly()) {
canvasState.translate(properties().getTranslationX(), properties().getTranslationY());
} else {
canvasState.concatMatrix(*properties().getTransformMatrix());
}
}
return !canvasState.quickRejectConservative(
0, 0, properties().getWidth(), properties().getHeight());
}
/*
* For property operations, we pass a savecount of 0, since the operations aren't part of the
* displaylist, and thus don't have to compensate for the record-time/playback-time discrepancy in
@ -580,6 +623,7 @@ void RenderNode::applyViewPropertyTransforms(mat4& matrix, bool true3dTransform)
* which are flagged to not draw in the standard draw loop.
*/
void RenderNode::computeOrdering() {
#if !HWUI_NEW_OPS
ATRACE_CALL();
mProjectedNodes.clear();
@ -588,14 +632,16 @@ void RenderNode::computeOrdering() {
if (mDisplayListData == nullptr) return;
for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
childOp->mRenderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity());
childOp->renderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity());
}
#endif
}
void RenderNode::computeOrderingImpl(
DrawRenderNodeOp* opState,
std::vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
const mat4* transformFromProjectionSurface) {
#if !HWUI_NEW_OPS
mProjectedNodes.clear();
if (mDisplayListData == nullptr || mDisplayListData->isEmpty()) return;
@ -619,7 +665,7 @@ void RenderNode::computeOrderingImpl(
bool haveAppliedPropertiesToProjection = false;
for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
RenderNode* child = childOp->mRenderNode;
RenderNode* child = childOp->renderNode;
std::vector<DrawRenderNodeOp*>* projectionChildren = nullptr;
const mat4* projectionTransform = nullptr;
@ -642,6 +688,7 @@ void RenderNode::computeOrderingImpl(
child->computeOrderingImpl(childOp, projectionChildren, projectionTransform);
}
}
#endif
}
class DeferOperationHandler {
@ -701,11 +748,12 @@ void RenderNode::replay(ReplayStateStruct& replayStruct, const int level) {
void RenderNode::buildZSortedChildList(const DisplayListData::Chunk& chunk,
std::vector<ZDrawRenderNodeOpPair>& zTranslatedNodes) {
#if !HWUI_NEW_OPS
if (chunk.beginChildIndex == chunk.endChildIndex) return;
for (unsigned int i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) {
DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
RenderNode* child = childOp->mRenderNode;
RenderNode* child = childOp->renderNode;
float childZ = child->properties().getZ();
if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
@ -719,6 +767,7 @@ void RenderNode::buildZSortedChildList(const DisplayListData::Chunk& chunk,
// Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order)
std::stable_sort(zTranslatedNodes.begin(), zTranslatedNodes.end());
#endif
}
template <class T>
@ -824,7 +873,7 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode,
while (shadowIndex < endIndex || drawIndex < endIndex) {
if (shadowIndex < endIndex) {
DrawRenderNodeOp* casterOp = zTranslatedNodes[shadowIndex].value;
RenderNode* caster = casterOp->mRenderNode;
RenderNode* caster = casterOp->renderNode;
const float casterZ = zTranslatedNodes[shadowIndex].key;
// 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
@ -869,7 +918,7 @@ void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T&
const DisplayListOp* op =
(mDisplayListData->displayListOps[mDisplayListData->projectionReceiveIndex]);
const DrawRenderNodeOp* backgroundOp = reinterpret_cast<const DrawRenderNodeOp*>(op);
const RenderProperties& backgroundProps = backgroundOp->mRenderNode->properties();
const RenderProperties& backgroundProps = backgroundOp->renderNode->properties();
renderer.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
// If the projection reciever has an outline, we mask projected content to it

View File

@ -29,8 +29,8 @@
#include "AnimatorManager.h"
#include "Debug.h"
#include "Matrix.h"
#include "DisplayList.h"
#include "Matrix.h"
#include "RenderProperties.h"
#include <vector>
@ -43,6 +43,7 @@ class SkRegion;
namespace android {
namespace uirenderer {
class CanvasState;
class DisplayListOp;
class DisplayListCanvas;
class OpenGLRenderer;
@ -74,6 +75,7 @@ class RenderNode;
* attached.
*/
class RenderNode : public VirtualLightRefBase {
friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties
public:
enum DirtyPropertyMask {
GENERIC = 1 << 1,
@ -176,8 +178,25 @@ public:
AnimatorManager& animators() { return mAnimatorManager; }
// Returns false if the properties dictate the subtree contained in this RenderNode won't render
bool applyViewProperties(CanvasState& canvasState) const;
void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false) const;
bool nothingToDraw() const {
const Outline& outline = properties().getOutline();
return mDisplayListData == nullptr
|| properties().getAlpha() <= 0
|| (outline.getShouldClip() && outline.isEmpty())
|| properties().getScaleX() == 0
|| properties().getScaleY() == 0;
}
// Only call if RenderNode has DisplayListData...
const DisplayListData& getDisplayListData() const {
return *mDisplayListData;
}
private:
typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
@ -235,6 +254,10 @@ private:
const char* mText;
};
void syncProperties();
void syncDisplayList();
void prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer);
void pushStagingPropertiesChanges(TreeInfo& info);
void pushStagingDisplayListChanges(TreeInfo& info);

View File

@ -18,12 +18,22 @@
#include <utils/Singleton.h>
#include "DisplayList.h"
#if HWUI_NEW_OPS
#include "RecordingCanvas.h"
#else
#include "DisplayListCanvas.h"
#endif
#include "microbench/MicroBench.h"
using namespace android;
using namespace android::uirenderer;
#if HWUI_NEW_OPS
typedef RecordingCanvas TestCanvas;
#else
typedef DisplayListCanvas TestCanvas;
#endif
BENCHMARK_NO_ARG(BM_DisplayListData_alloc);
void BM_DisplayListData_alloc::Run(int iters) {
StartBenchmarkTiming();
@ -48,7 +58,7 @@ void BM_DisplayListData_alloc_theoretical::Run(int iters) {
BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_empty);
void BM_DisplayListCanvas_record_empty::Run(int iters) {
DisplayListCanvas canvas(100, 100);
TestCanvas canvas(100, 100);
canvas.finishRecording();
StartBenchmarkTiming();
@ -62,7 +72,7 @@ void BM_DisplayListCanvas_record_empty::Run(int iters) {
BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_saverestore);
void BM_DisplayListCanvas_record_saverestore::Run(int iters) {
DisplayListCanvas canvas(100, 100);
TestCanvas canvas(100, 100);
canvas.finishRecording();
StartBenchmarkTiming();
@ -80,7 +90,7 @@ void BM_DisplayListCanvas_record_saverestore::Run(int iters) {
BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_translate);
void BM_DisplayListCanvas_record_translate::Run(int iters) {
DisplayListCanvas canvas(100, 100);
TestCanvas canvas(100, 100);
canvas.finishRecording();
StartBenchmarkTiming();

View File

@ -28,6 +28,11 @@
#include "renderstate/Stencil.h"
#include "protos/hwui.pb.h"
#if HWUI_NEW_OPS
#include "BakedOpRenderer.h"
#include "OpReorderer.h"
#endif
#include <cutils/properties.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <private/hwui/DrawGlInfo.h>
@ -121,9 +126,11 @@ void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) {
bool CanvasContext::initialize(ANativeWindow* window) {
setSurface(window);
#if !HWUI_NEW_OPS
if (mCanvas) return false;
mCanvas = new OpenGLRenderer(mRenderThread.renderState());
mCanvas->initProperties();
#endif
return true;
}
@ -162,11 +169,13 @@ void CanvasContext::makeCurrent() {
}
void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
#if !HWUI_NEW_OPS
bool success = layerUpdater->apply();
LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!");
if (layerUpdater->backingLayer()->deferredUpdateScheduled) {
mCanvas->pushLayerUpdate(layerUpdater->backingLayer());
}
#endif
}
static bool wasSkipped(FrameInfo* info) {
@ -239,8 +248,10 @@ void CanvasContext::notifyFramePending() {
}
void CanvasContext::draw() {
#if !HWUI_NEW_OPS
LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
"drawRenderNode called on a context with no canvas or surface!");
#endif
SkRect dirty;
mDamageAccumulator.finish(&dirty);
@ -254,6 +265,8 @@ void CanvasContext::draw() {
mCurrentFrameInfo->markIssueDrawCommandsStart();
Frame frame = mEglManager.beginFrame(mEglSurface);
#if !HWUI_NEW_OPS
if (frame.width() != mCanvas->getViewportWidth()
|| frame.height() != mCanvas->getViewportHeight()) {
// can't rely on prior content of window if viewport size changes
@ -417,6 +430,16 @@ void CanvasContext::draw() {
// Even if we decided to cancel the frame, from the perspective of jank
// metrics the frame was swapped at this point
mCurrentFrameInfo->markSwapBuffers();
#else
OpReorderer reorderer;
reorderer.defer(frame.width(), frame.height(), mRenderNodes);
BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(),
frame.width(), frame.height(), mOpaque);
reorderer.replayBakedOps<BakedOpRenderer>(&info);
bool drew = info.didDraw;
SkRect screenDirty = SkRect::MakeWH(frame.width(), frame.height());
#endif
if (drew) {
if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) {

View File

@ -20,6 +20,7 @@
#include <AnimationContext.h>
#include <DisplayListCanvas.h>
#include <RecordingCanvas.h>
#include <RenderNode.h>
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
@ -39,6 +40,13 @@ using namespace android::uirenderer;
using namespace android::uirenderer::renderthread;
using namespace android::uirenderer::test;
#if HWUI_NEW_OPS
typedef RecordingCanvas TestCanvas;
#else
typedef DisplayListCanvas TestCanvas;
#endif
class ContextFactory : public IContextFactory {
public:
virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
@ -46,13 +54,13 @@ public:
}
};
static DisplayListCanvas* startRecording(RenderNode* node) {
DisplayListCanvas* renderer = new DisplayListCanvas(
static TestCanvas* startRecording(RenderNode* node) {
TestCanvas* renderer = new TestCanvas(
node->stagingProperties().getWidth(), node->stagingProperties().getHeight());
return renderer;
}
static void endRecording(DisplayListCanvas* renderer, RenderNode* node) {
static void endRecording(TestCanvas* renderer, RenderNode* node) {
node->setStagingDisplayList(renderer->finishRecording());
delete renderer;
}
@ -67,7 +75,7 @@ public:
frameCount = fc;
}
}
virtual void createContent(int width, int height, DisplayListCanvas* renderer) = 0;
virtual void createContent(int width, int height, TestCanvas* renderer) = 0;
virtual void doFrame(int frameNr) = 0;
template <class T>
@ -102,7 +110,7 @@ public:
android::uirenderer::Rect DUMMY;
DisplayListCanvas* renderer = startRecording(rootNode);
TestCanvas* renderer = startRecording(rootNode);
animation.createContent(width, height, renderer);
endRecording(renderer, rootNode);
@ -132,7 +140,7 @@ public:
class ShadowGridAnimation : public TreeContentAnimation {
public:
std::vector< sp<RenderNode> > cards;
void createContent(int width, int height, DisplayListCanvas* renderer) override {
void createContent(int width, int height, TestCanvas* renderer) override {
renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
renderer->insertReorderBarrier(true);
@ -163,7 +171,7 @@ private:
node->mutateStagingProperties().mutableOutline().setShouldClip(true);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
DisplayListCanvas* renderer = startRecording(node.get());
TestCanvas* renderer = startRecording(node.get());
renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
endRecording(renderer, node.get());
return node;
@ -179,7 +187,7 @@ static Benchmark _ShadowGrid(BenchmarkInfo{
class ShadowGrid2Animation : public TreeContentAnimation {
public:
std::vector< sp<RenderNode> > cards;
void createContent(int width, int height, DisplayListCanvas* renderer) override {
void createContent(int width, int height, TestCanvas* renderer) override {
renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
renderer->insertReorderBarrier(true);
@ -210,7 +218,7 @@ private:
node->mutateStagingProperties().mutableOutline().setShouldClip(true);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
DisplayListCanvas* renderer = startRecording(node.get());
TestCanvas* renderer = startRecording(node.get());
renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
endRecording(renderer, node.get());
return node;
@ -226,7 +234,7 @@ static Benchmark _ShadowGrid2(BenchmarkInfo{
class RectGridAnimation : public TreeContentAnimation {
public:
sp<RenderNode> card;
void createContent(int width, int height, DisplayListCanvas* renderer) override {
void createContent(int width, int height, TestCanvas* renderer) override {
renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
renderer->insertReorderBarrier(true);
@ -247,7 +255,7 @@ private:
node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
DisplayListCanvas* renderer = startRecording(node.get());
TestCanvas* renderer = startRecording(node.get());
renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
SkRegion region;
@ -275,7 +283,7 @@ static Benchmark _RectGrid(BenchmarkInfo{
class OvalAnimation : public TreeContentAnimation {
public:
sp<RenderNode> card;
void createContent(int width, int height, DisplayListCanvas* renderer) override {
void createContent(int width, int height, TestCanvas* renderer) override {
renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
renderer->insertReorderBarrier(true);
@ -297,7 +305,7 @@ private:
node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
DisplayListCanvas* renderer = startRecording(node.get());
TestCanvas* renderer = startRecording(node.get());
SkPaint paint;
paint.setAntiAlias(true);
@ -317,7 +325,7 @@ static Benchmark _Oval(BenchmarkInfo{
class PartialDamageTest : public TreeContentAnimation {
public:
std::vector< sp<RenderNode> > cards;
void createContent(int width, int height, DisplayListCanvas* renderer) override {
void createContent(int width, int height, TestCanvas* renderer) override {
static SkColor COLORS[] = {
0xFFF44336,
0xFF9C27B0,
@ -342,7 +350,7 @@ public:
cards[0]->mutateStagingProperties().setTranslationY(curFrame);
cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
DisplayListCanvas* renderer = startRecording(cards[0].get());
TestCanvas* renderer = startRecording(cards[0].get());
renderer->drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0),
SkXfermode::kSrcOver_Mode);
endRecording(renderer, cards[0].get());
@ -370,7 +378,7 @@ private:
node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
DisplayListCanvas* renderer = startRecording(node.get());
TestCanvas* renderer = startRecording(node.get());
renderer->drawColor(color, SkXfermode::kSrcOver_Mode);
endRecording(renderer, node.get());
return node;
@ -383,3 +391,43 @@ static Benchmark _PartialDamage(BenchmarkInfo{
"EGL_KHR_partial_update is supported by the device & are enabled in hwui.",
TreeContentAnimation::run<PartialDamageTest>
});
class SimpleRectGridAnimation : public TreeContentAnimation {
public:
sp<RenderNode> card;
void createContent(int width, int height, TestCanvas* renderer) override {
SkPaint paint;
paint.setColor(0xFF00FFFF);
renderer->drawRect(0, 0, width, height, paint);
card = createCard(40, 40, 200, 200);
renderer->drawRenderNode(card.get());
}
void doFrame(int frameNr) override {
int curFrame = frameNr % 150;
card->mutateStagingProperties().setTranslationX(curFrame);
card->mutateStagingProperties().setTranslationY(curFrame);
card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
}
private:
sp<RenderNode> createCard(int x, int y, int width, int height) {
sp<RenderNode> node = new RenderNode();
node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
TestCanvas* renderer = startRecording(node.get());
SkPaint paint;
paint.setColor(0xFFFF00FF);
renderer->drawRect(0, 0, width, height, paint);
endRecording(renderer, node.get());
return node;
}
};
static Benchmark _SimpleRectGrid(BenchmarkInfo{
"simplerectgrid",
"A simple collection of rects. "
"Low CPU/GPU load.",
TreeContentAnimation::run<SimpleRectGridAnimation>
});

View File

@ -0,0 +1,97 @@
/*
* Copyright (C) 2015 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 <BakedOpState.h>
#include <RecordedOp.h>
#include <unit_tests/TestUtils.h>
namespace android {
namespace uirenderer {
TEST(ResolvedRenderState, resolution) {
Matrix4 identity;
identity.loadIdentity();
Matrix4 translate10x20;
translate10x20.loadTranslate(10, 20, 0);
SkPaint paint;
RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, Rect(0, 0, 100, 200), &paint);
{
// recorded with transform, no parent transform
auto parentSnapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
ResolvedRenderState state(*parentSnapshot, recordedOp);
EXPECT_MATRIX_APPROX_EQ(state.transform, translate10x20);
EXPECT_EQ(state.clipRect, Rect(0, 0, 100, 200));
EXPECT_EQ(state.clippedBounds, Rect(40, 60, 100, 200)); // translated and also clipped
}
{
// recorded with transform and parent transform
auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(0, 0, 100, 200));
ResolvedRenderState state(*parentSnapshot, recordedOp);
Matrix4 expectedTranslate;
expectedTranslate.loadTranslate(20, 40, 0);
EXPECT_MATRIX_APPROX_EQ(state.transform, expectedTranslate);
// intersection of parent & transformed child clip
EXPECT_EQ(state.clipRect, Rect(10, 20, 100, 200));
// translated and also clipped
EXPECT_EQ(state.clippedBounds, Rect(50, 80, 100, 200));
}
}
TEST(BakedOpState, constructAndReject) {
LinearAllocator allocator;
Matrix4 identity;
identity.loadIdentity();
Matrix4 translate100x0;
translate100x0.loadTranslate(100, 0, 0);
SkPaint paint;
{
RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, Rect(0, 0, 100, 200), &paint);
auto snapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, rejectOp);
EXPECT_EQ(bakedOp, nullptr); // rejected by clip, so not constructed
EXPECT_LE(allocator.usedSize(), 8u); // no significant allocation space used for rejected op
}
{
RectOp successOp(Rect(30, 40, 100, 200), identity, Rect(0, 0, 100, 200), &paint);
auto snapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200));
BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, successOp);
EXPECT_NE(bakedOp, nullptr); // NOT rejected by clip, so will be constructed
EXPECT_GT(allocator.usedSize(), 64u); // relatively large alloc for non-rejected op
}
}
#define UNSUPPORTED_OP(Info, Type) \
static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); }
class Info {
public:
int index = 0;
};
}
}

View File

@ -101,10 +101,9 @@ TEST(ClipArea, paths) {
EXPECT_FALSE(area.isEmpty());
EXPECT_FALSE(area.isSimple());
EXPECT_FALSE(area.isRectangleList());
Rect clipRect(area.getClipRect());
clipRect.dump("clipRect");
Rect expected(0, 0, r * 2, r * 2);
expected.dump("expected");
EXPECT_EQ(expected, clipRect);
SkRegion clipRegion(area.getClipRegion());
auto skRect(clipRegion.getBounds());

View File

@ -0,0 +1,161 @@
/*
* Copyright (C) 2015 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 <BakedOpState.h>
#include <OpReorderer.h>
#include <RecordedOp.h>
#include <RecordingCanvas.h>
#include <unit_tests/TestUtils.h>
#include <unordered_map>
namespace android {
namespace uirenderer {
#define UNSUPPORTED_OP(Info, Type) \
static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); }
class Info {
public:
int index = 0;
};
class SimpleReceiver {
public:
static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
EXPECT_EQ(1, info->index++);
}
static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
EXPECT_EQ(0, info->index++);
}
UNSUPPORTED_OP(Info, RenderNodeOp)
UNSUPPORTED_OP(Info, SimpleRectsOp)
static void startFrame(Info& info) {}
static void endFrame(Info& info) {}
};
TEST(OpReorderer, simple) {
auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
SkBitmap bitmap;
bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25));
canvas.drawRect(0, 0, 100, 200, SkPaint());
canvas.drawBitmap(bitmap, 10, 10, nullptr);
});
OpReorderer reorderer;
reorderer.defer(200, 200, dld->getChunks(), dld->getOps());
Info info;
reorderer.replayBakedOps<SimpleReceiver>(&info);
}
static int SIMPLE_BATCHING_LOOPS = 5;
class SimpleBatchingReceiver {
public:
static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
EXPECT_TRUE(info->index++ >= SIMPLE_BATCHING_LOOPS);
}
static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
EXPECT_TRUE(info->index++ < SIMPLE_BATCHING_LOOPS);
}
UNSUPPORTED_OP(Info, RenderNodeOp)
UNSUPPORTED_OP(Info, SimpleRectsOp)
static void startFrame(Info& info) {}
static void endFrame(Info& info) {}
};
TEST(OpReorderer, simpleBatching) {
auto dld = TestUtils::createDLD<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
SkBitmap bitmap;
bitmap.setInfo(SkImageInfo::MakeUnknown(10, 10));
// Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
// Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
for (int i = 0; i < SIMPLE_BATCHING_LOOPS; i++) {
canvas.translate(0, 10);
canvas.drawRect(0, 0, 10, 10, SkPaint());
canvas.drawBitmap(bitmap, 5, 0, nullptr);
}
canvas.restore();
});
OpReorderer reorderer;
reorderer.defer(200, 200, dld->getChunks(), dld->getOps());
Info info;
reorderer.replayBakedOps<SimpleBatchingReceiver>(&info);
EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, info.index); // 2 x loops ops, because no merging (TODO: force no merging)
}
class RenderNodeReceiver {
public:
UNSUPPORTED_OP(Info, BitmapOp)
static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
switch(info->index++) {
case 0:
EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clippedBounds);
EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
break;
case 1:
EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds);
EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
break;
default:
FAIL();
}
}
UNSUPPORTED_OP(Info, RenderNodeOp)
UNSUPPORTED_OP(Info, SimpleRectsOp)
static void startFrame(Info& info) {}
static void endFrame(Info& info) {}
};
TEST(OpReorderer, renderNode) {
sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
});
RenderNode* childPtr = child.get();
sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorDKGRAY);
canvas.drawRect(0, 0, 200, 200, paint);
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.translate(40, 40);
canvas.drawRenderNode(childPtr);
canvas.restore();
});
TestUtils::syncNodePropertiesAndDisplayList(child);
TestUtils::syncNodePropertiesAndDisplayList(parent);
std::vector< sp<RenderNode> > nodes;
nodes.push_back(parent.get());
OpReorderer reorderer;
reorderer.defer(200, 200, nodes);
Info info;
reorderer.replayBakedOps<RenderNodeReceiver>(&info);
}
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright (C) 2015 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 <RecordedOp.h>
#include <RecordingCanvas.h>
#include <unit_tests/TestUtils.h>
namespace android {
namespace uirenderer {
static void playbackOps(const std::vector<DisplayListData::Chunk>& chunks,
const std::vector<RecordedOp*>& ops, std::function<void(const RecordedOp&)> opReciever) {
for (const DisplayListData::Chunk& chunk : chunks) {
for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
opReciever(*ops[opIndex]);
}
}
}
TEST(RecordingCanvas, emptyPlayback) {
auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.restore();
});
playbackOps(dld->getChunks(), dld->getOps(), [](const RecordedOp& op) { FAIL(); });
}
TEST(RecordingCanvas, testSimpleRectRecord) {
auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
canvas.drawRect(10, 20, 90, 180, SkPaint());
});
int count = 0;
playbackOps(dld->getChunks(), dld->getOps(), [&count](const RecordedOp& op) {
count++;
ASSERT_EQ(RecordedOpId::RectOp, op.opId);
ASSERT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
ASSERT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
});
ASSERT_EQ(1, count); // only one observed
}
TEST(RecordingCanvas, backgroundAndImage) {
auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
SkBitmap bitmap;
bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25));
SkPaint paint;
paint.setColor(SK_ColorBLUE);
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
{
// a background!
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.drawRect(0, 0, 100, 200, paint);
canvas.restore();
}
{
// an image!
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.translate(25, 25);
canvas.scale(2, 2);
canvas.drawBitmap(bitmap, 0, 0, nullptr);
canvas.restore();
}
canvas.restore();
});
int count = 0;
playbackOps(dld->getChunks(), dld->getOps(), [&count](const RecordedOp& op) {
if (count == 0) {
ASSERT_EQ(RecordedOpId::RectOp, op.opId);
ASSERT_NE(nullptr, op.paint);
EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
EXPECT_EQ(Rect(0, 0, 100, 200), op.unmappedBounds);
EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
Matrix4 expectedMatrix;
expectedMatrix.loadIdentity();
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
} else {
ASSERT_EQ(RecordedOpId::BitmapOp, op.opId);
EXPECT_EQ(nullptr, op.paint);
EXPECT_EQ(Rect(0, 0, 25, 25), op.unmappedBounds);
EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
Matrix4 expectedMatrix;
expectedMatrix.loadTranslate(25, 25, 0);
expectedMatrix.scale(2, 2, 1);
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
}
count++;
});
ASSERT_EQ(2, count); // two draws observed
}
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 2015 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.
*/
#ifndef TEST_UTILS_H
#define TEST_UTILS_H
#include <Matrix.h>
#include <Snapshot.h>
#include <RenderNode.h>
#include <memory>
namespace android {
namespace uirenderer {
#define EXPECT_MATRIX_APPROX_EQ(a, b) \
EXPECT_TRUE(TestUtils::matricesAreApproxEqual(a, b))
class TestUtils {
public:
static bool matricesAreApproxEqual(const Matrix4& a, const Matrix4& b) {
for (int i = 0; i < 16; i++) {
if (!MathUtils::areEqual(a[i], b[i])) {
return false;
}
}
return true;
}
static std::unique_ptr<Snapshot> makeSnapshot(const Matrix4& transform, const Rect& clip) {
std::unique_ptr<Snapshot> snapshot(new Snapshot());
snapshot->clip(clip.left, clip.top, clip.right, clip.bottom, SkRegion::kReplace_Op);
*(snapshot->transform) = transform;
return snapshot;
}
template<class CanvasType>
static std::unique_ptr<DisplayListData> createDLD(int width, int height,
std::function<void(CanvasType& canvas)> canvasCallback) {
CanvasType canvas(width, height);
canvasCallback(canvas);
return std::unique_ptr<DisplayListData>(canvas.finishRecording());
}
template<class CanvasType>
static sp<RenderNode> createNode(int left, int top, int right, int bottom,
std::function<void(CanvasType& canvas)> canvasCallback) {
sp<RenderNode> node = new RenderNode();
node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
CanvasType canvas(
node->stagingProperties().getWidth(), node->stagingProperties().getHeight());
canvasCallback(canvas);
node->setStagingDisplayList(canvas.finishRecording());
return node;
}
static void syncNodePropertiesAndDisplayList(sp<RenderNode>& node) {
node->syncProperties();
node->syncDisplayList();
}
}; // class TestUtils
} /* namespace uirenderer */
} /* namespace android */
#endif /* TEST_UTILS_H */

View File

@ -20,6 +20,7 @@
#include <SkColorFilter.h>
#include <SkDrawLooper.h>
#include <SkShader.h>
#include <SkXfermode.h>
namespace android {