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
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 8011b59..6f548bc 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -2,6 +2,8 @@
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 @@
-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 @@
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)
# ------------------------
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
new file mode 100644
index 0000000..4d9f9b4
--- /dev/null
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -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
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
new file mode 100644
index 0000000..b8b4426
--- /dev/null
+++ b/libs/hwui/BakedOpRenderer.h
@@ -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
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
new file mode 100644
index 0000000..e2201ca
--- /dev/null
+++ b/libs/hwui/BakedOpState.h
@@ -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
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index 0c29a9e..a1825c5 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -528,7 +528,7 @@
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);
diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h
index 7873fbd..2d5979f 100644
--- a/libs/hwui/DeferredDisplayList.h
+++ b/libs/hwui/DeferredDisplayList.h
@@ -49,7 +49,7 @@
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 @@
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;
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index ee51da2..2337299 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -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 @@
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;
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 0bdb816..8ba9ac3 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -55,11 +55,12 @@
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 @@
*/
class DisplayListData {
friend class DisplayListCanvas;
+ friend class RecordingCanvas;
public:
struct Chunk {
// range of included ops in DLD::displayListOps
@@ -139,11 +141,21 @@
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 @@
}
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;
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index 77bde86..8d0ab03 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -558,16 +558,18 @@
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;
}
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index ddfc533f..1f6282d 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1397,25 +1397,26 @@
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 @@
}
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
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index fa166ae..d2da851 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -461,12 +461,12 @@
// 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 @@
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 */
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index 8d05570..6f5802e 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -71,9 +71,9 @@
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 @@
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,
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index ed517ac..c017638 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -126,6 +126,9 @@
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;
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
new file mode 100644
index 0000000..1d8be2b
--- /dev/null
+++ b/libs/hwui/OpReorderer.cpp
@@ -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
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
new file mode 100644
index 0000000..b99172c
--- /dev/null
+++ b/libs/hwui/OpReorderer.h
@@ -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
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
new file mode 100644
index 0000000..a69f030
--- /dev/null
+++ b/libs/hwui/RecordedOp.h
@@ -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
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
new file mode 100644
index 0000000..c4debd6
--- /dev/null
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -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
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
new file mode 100644
index 0000000..ed299e5
--- /dev/null
+++ b/libs/hwui/RecordingCanvas.h
@@ -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
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index bf1b4d0..d122a55 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+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 @@
* 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 @@
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 @@
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 @@
child->computeOrderingImpl(childOp, projectionChildren, projectionTransform);
}
}
+#endif
}
class DeferOperationHandler {
@@ -701,11 +748,12 @@
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 @@
// 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 @@
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 @@
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
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 88fc608..ff673ba 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -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 @@
namespace android {
namespace uirenderer {
+class CanvasState;
class DisplayListOp;
class DisplayListCanvas;
class OpenGLRenderer;
@@ -74,6 +75,7 @@
* attached.
*/
class RenderNode : public VirtualLightRefBase {
+friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties
public:
enum DirtyPropertyMask {
GENERIC = 1 << 1,
@@ -176,8 +178,25 @@
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 @@
const char* mText;
};
+
+ void syncProperties();
+ void syncDisplayList();
+
void prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer);
void pushStagingPropertiesChanges(TreeInfo& info);
void pushStagingDisplayListChanges(TreeInfo& info);
diff --git a/libs/hwui/microbench/DisplayListCanvasBench.cpp b/libs/hwui/microbench/DisplayListCanvasBench.cpp
index 7b6be7a..fd42adb 100644
--- a/libs/hwui/microbench/DisplayListCanvasBench.cpp
+++ b/libs/hwui/microbench/DisplayListCanvasBench.cpp
@@ -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 @@
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 @@
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 @@
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();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 38f6e53..e1d8abd 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -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 @@
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::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::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 @@
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 @@
// 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))) {
diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp
index a59261c..891af91 100644
--- a/libs/hwui/tests/TreeContentAnimation.cpp
+++ b/libs/hwui/tests/TreeContentAnimation.cpp
@@ -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::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 @@
}
};
-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 @@
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 @@
android::uirenderer::Rect DUMMY;
- DisplayListCanvas* renderer = startRecording(rootNode);
+ TestCanvas* renderer = startRecording(rootNode);
animation.createContent(width, height, renderer);
endRecording(renderer, rootNode);
@@ -132,7 +140,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
"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>
+});
diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp
new file mode 100644
index 0000000..82aebea
--- /dev/null
+++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp
@@ -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;
+};
+
+}
+}
diff --git a/libs/hwui/unit_tests/ClipAreaTests.cpp b/libs/hwui/unit_tests/ClipAreaTests.cpp
index 0c5e5e7..d6192df 100644
--- a/libs/hwui/unit_tests/ClipAreaTests.cpp
+++ b/libs/hwui/unit_tests/ClipAreaTests.cpp
@@ -101,10 +101,9 @@
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());
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
new file mode 100644
index 0000000..fcaea1e
--- /dev/null
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -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);
+}
+
+}
+}
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
new file mode 100644
index 0000000..c813833
--- /dev/null
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -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
+}
+
+}
+}
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
new file mode 100644
index 0000000..257dd28
--- /dev/null
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -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 */
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index d00236e..db53713 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -20,6 +20,7 @@
#include <SkColorFilter.h>
#include <SkDrawLooper.h>
+#include <SkShader.h>
#include <SkXfermode.h>
namespace android {