diff options
| author | 2015-10-05 13:00:52 -0700 | |
|---|---|---|
| committer | 2015-10-14 16:10:40 -0700 | |
| commit | b565df13a9e5c7b1d7d93bdfa4a793752d66d3cc (patch) | |
| tree | 4a2dadd3b2c95663c23a626eff75962a0b8d4ce2 | |
| parent | b08949151f8dfc7a72ea6696f2e6067c2b1643bb (diff) | |
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
29 files changed, 2482 insertions, 78 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 8011b5945ee2..6f548bcabedc 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -2,6 +2,8 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk +HWUI_NEW_OPS := false + hwui_src_files := \ font/CacheTexture.cpp \ font/Font.cpp \ @@ -85,6 +87,17 @@ hwui_cflags := \ -Wall -Wno-unused-parameter -Wunreachable-code \ -ffast-math -O3 -Werror + +ifeq (true, $(HWUI_NEW_OPS)) + hwui_src_files += \ + BakedOpRenderer.cpp \ + OpReorderer.cpp \ + RecordingCanvas.cpp + + hwui_cflags += -DHWUI_NEW_OPS + +endif + ifndef HWUI_COMPILE_SYMBOLS hwui_cflags += -fvisibility=hidden endif @@ -172,6 +185,13 @@ LOCAL_SRC_FILES += \ unit_tests/LinearAllocatorTests.cpp \ unit_tests/StringUtilsTests.cpp +ifeq (true, $(HWUI_NEW_OPS)) + LOCAL_SRC_FILES += \ + unit_tests/BakedOpStateTests.cpp \ + unit_tests/RecordingCanvasTests.cpp \ + unit_tests/OpReordererTests.cpp +endif + include $(BUILD_NATIVE_TEST) # ------------------------ diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp new file mode 100644 index 000000000000..4d9f9b479343 --- /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 000000000000..b8b4426412e4 --- /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 000000000000..e2201ca06a4b --- /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 0c29a9e928a2..a1825c5bc4c1 100644 --- a/libs/hwui/DeferredDisplayList.cpp +++ b/libs/hwui/DeferredDisplayList.cpp @@ -528,7 +528,7 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { int insertBatchIndex = mBatches.size(); if (!mBatches.empty()) { if (state->mBounds.isEmpty()) { - // don't know the bounds for op, so add to last batch and start from scratch on next op + // don't know the bounds for op, so create new batch and start from scratch on next op DrawBatch* b = new DrawBatch(deferInfo); b->add(op, state, deferInfo.opaqueOverBounds); mBatches.push_back(b); diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h index 7873fbdd342a..2d5979f2f1a7 100644 --- a/libs/hwui/DeferredDisplayList.h +++ b/libs/hwui/DeferredDisplayList.h @@ -49,7 +49,7 @@ typedef const void* mergeid_t; class DeferredDisplayState { public: - /** static void* operator new(size_t size); PURPOSELY OMITTED **/ + static void* operator new(size_t size) = delete; static void* operator new(size_t size, LinearAllocator& allocator) { return allocator.alloc(size); } @@ -61,7 +61,6 @@ public: bool mClipValid; Rect mClip; int mClipSideFlags; // specifies which sides of the bounds are clipped, unclipped if cleared - bool mClipped; mat4 mMatrix; float mAlpha; const RoundRectClipState* mRoundRectClipState; diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index ee51da2fb116..2337299da145 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 @@ void DisplayListData::cleanupResources() { regions.clear(); } +#if HWUI_NEW_OPS +size_t DisplayListData::addChild(RenderNodeOp* op) { + mReferenceHolders.push_back(op->renderNode); +#else size_t DisplayListData::addChild(DrawRenderNodeOp* op) { - mReferenceHolders.push_back(op->renderNode()); + mReferenceHolders.push_back(op->renderNode); +#endif size_t index = mChildren.size(); mChildren.push_back(op); return index; diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index 0bdb8169ea06..8ba9ac357414 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -55,11 +55,12 @@ class OpenGLRenderer; class Rect; class Layer; -class ClipRectOp; -class SaveLayerOp; -class SaveOp; -class RestoreToCountOp; +#if HWUI_NEW_OPS +struct RecordedOp; +struct RenderNodeOp; +#else class DrawRenderNodeOp; +#endif /** * Holds data used in the playback a tree of DisplayLists. @@ -107,6 +108,7 @@ struct ReplayStateStruct : public PlaybackStateStruct { */ class DisplayListData { friend class DisplayListCanvas; + friend class RecordingCanvas; public: struct Chunk { // range of included ops in DLD::displayListOps @@ -139,11 +141,21 @@ public: Vector<Functor*> functors; const std::vector<Chunk>& getChunks() const { - return chunks; + return chunks; } +#if HWUI_NEW_OPS + const std::vector<RecordedOp*>& getOps() const { + return ops; + } +#endif +#if HWUI_NEW_OPS + size_t addChild(RenderNodeOp* childOp); + const std::vector<RenderNodeOp*>& children() { return mChildren; } +#else size_t addChild(DrawRenderNodeOp* childOp); const std::vector<DrawRenderNodeOp*>& children() { return mChildren; } +#endif void ref(VirtualLightRefBase* prop) { mReferenceHolders.push_back(prop); @@ -157,10 +169,18 @@ public: } private: +#if HWUI_NEW_OPS + std::vector<RecordedOp*> ops; +#endif + std::vector< sp<VirtualLightRefBase> > mReferenceHolders; +#if HWUI_NEW_OPS + std::vector<RenderNodeOp*> mChildren; +#else // list of children display lists for quick, non-drawing traversal std::vector<DrawRenderNodeOp*> mChildren; +#endif std::vector<Chunk> chunks; diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp index 77bde863af51..8d0ab0322b5b 100644 --- a/libs/hwui/DisplayListCanvas.cpp +++ b/libs/hwui/DisplayListCanvas.cpp @@ -558,16 +558,18 @@ size_t DisplayListCanvas::addDrawOp(DrawOp* op) { size_t DisplayListCanvas::addRenderNodeOp(DrawRenderNodeOp* op) { int opIndex = addDrawOp(op); +#if !HWUI_NEW_OPS int childIndex = mDisplayListData->addChild(op); // update the chunk's child indices DisplayListData::Chunk& chunk = mDisplayListData->chunks.back(); chunk.endChildIndex = childIndex + 1; - if (op->renderNode()->stagingProperties().isProjectionReceiver()) { + if (op->renderNode->stagingProperties().isProjectionReceiver()) { // use staging property, since recording on UI thread mDisplayListData->projectionReceiveIndex = opIndex; } +#endif return opIndex; } diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index ddfc533f9d77..1f6282d68688 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -1397,25 +1397,26 @@ private: class DrawRenderNodeOp : public DrawBoundedOp { friend class RenderNode; // grant RenderNode access to info of child friend class DisplayListData; // grant DisplayListData access to info of child + friend class DisplayListCanvas; public: DrawRenderNodeOp(RenderNode* renderNode, const mat4& transformFromParent, bool clipIsSimple) : DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), nullptr) - , mRenderNode(renderNode) + , renderNode(renderNode) , mRecordedWithPotentialStencilClip(!clipIsSimple || !transformFromParent.isSimple()) , mTransformFromParent(transformFromParent) , mSkipInOrderDraw(false) {} virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level, bool useQuickReject) override { - if (mRenderNode->isRenderable() && !mSkipInOrderDraw) { - mRenderNode->defer(deferStruct, level + 1); + if (renderNode->isRenderable() && !mSkipInOrderDraw) { + renderNode->defer(deferStruct, level + 1); } } virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level, bool useQuickReject) override { - if (mRenderNode->isRenderable() && !mSkipInOrderDraw) { - mRenderNode->replay(replayStruct, level + 1); + if (renderNode->isRenderable() && !mSkipInOrderDraw) { + renderNode->replay(replayStruct, level + 1); } } @@ -1424,18 +1425,16 @@ public: } virtual void output(int level, uint32_t logFlags) const override { - OP_LOG("Draw RenderNode %p %s", mRenderNode, mRenderNode->getName()); - if (mRenderNode && (logFlags & kOpLogFlag_Recurse)) { - mRenderNode->output(level + 1); + OP_LOG("Draw RenderNode %p %s", renderNode, renderNode->getName()); + if (renderNode && (logFlags & kOpLogFlag_Recurse)) { + renderNode->output(level + 1); } } virtual const char* name() override { return "DrawRenderNode"; } - RenderNode* renderNode() { return mRenderNode; } - private: - RenderNode* mRenderNode; + RenderNode* renderNode; /** * This RenderNode was drawn into a DisplayList with the canvas in a state that will likely diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp index fa166ae5ca5a..d2da8513ff56 100644 --- a/libs/hwui/GlopBuilder.cpp +++ b/libs/hwui/GlopBuilder.cpp @@ -461,12 +461,12 @@ GlopBuilder& GlopBuilder::setFillTextureLayer(Layer& layer, float alpha) { // Transform //////////////////////////////////////////////////////////////////////////////// -void GlopBuilder::setTransform(const Matrix4& canvas, - const int transformFlags) { +GlopBuilder& GlopBuilder::setTransform(const Matrix4& canvas, const int transformFlags) { TRIGGER_STAGE(kTransformStage); mOutGlop->transform.canvas = canvas; mOutGlop->transform.transformFlags = transformFlags; + return *this; } //////////////////////////////////////////////////////////////////////////////// @@ -632,5 +632,42 @@ void GlopBuilder::build() { mOutGlop->transform.meshTransform().mapRect(mOutGlop->bounds); } +void GlopBuilder::dump(const Glop& glop) { + ALOGD("Glop Mesh"); + const Glop::Mesh& mesh = glop.mesh; + ALOGD(" primitive mode: %d", mesh.primitiveMode); + ALOGD(" indices: buffer obj %x, indices %p", mesh.indices.bufferObject, mesh.indices.indices); + + const Glop::Mesh::Vertices& vertices = glop.mesh.vertices; + ALOGD(" vertices: buffer obj %x, flags %x, pos %p, tex %p, clr %p, stride %d", + vertices.bufferObject, vertices.attribFlags, + vertices.position, vertices.texCoord, vertices.color, vertices.stride); + ALOGD(" element count: %d", mesh.elementCount); + + ALOGD("Glop Fill"); + const Glop::Fill& fill = glop.fill; + ALOGD(" program %p", fill.program); + if (fill.texture.texture) { + ALOGD(" texture %p, target %d, filter %d, clamp %d", + fill.texture.texture, fill.texture.target, fill.texture.filter, fill.texture.clamp); + if (fill.texture.textureTransform) { + fill.texture.textureTransform->dump("texture transform"); + } + } + ALOGD_IF(fill.colorEnabled, " color (argb) %.2f %.2f %.2f %.2f", + fill.color.a, fill.color.r, fill.color.g, fill.color.b); + ALOGD_IF(fill.filterMode != ProgramDescription::ColorFilterMode::None, + " filterMode %d", (int)fill.filterMode); + ALOGD_IF(fill.skiaShaderData.skiaShaderType, " shader type %d", + fill.skiaShaderData.skiaShaderType); + + ALOGD("Glop transform"); + glop.transform.modelView.dump("model view"); + glop.transform.canvas.dump("canvas"); + + ALOGD("Glop blend %d %d", glop.blend.src, glop.blend.dst); + ALOGD("Glop bounds " RECT_STRING, RECT_ARGS(glop.bounds)); +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h index 8d05570dd206..6f5802eedefd 100644 --- a/libs/hwui/GlopBuilder.h +++ b/libs/hwui/GlopBuilder.h @@ -71,9 +71,9 @@ public: GlopBuilder& setFillTextureLayer(Layer& layer, float alpha); GlopBuilder& setTransform(const Snapshot& snapshot, const int transformFlags) { - setTransform(*snapshot.transform, transformFlags); - return *this; + return setTransform(*snapshot.transform, transformFlags); } + GlopBuilder& setTransform(const Matrix4& canvas, const int transformFlags); GlopBuilder& setModelViewMapUnitToRect(const Rect destination); GlopBuilder& setModelViewMapUnitToRectSnap(const Rect destination); @@ -98,11 +98,12 @@ public: GlopBuilder& setRoundRectClipState(const RoundRectClipState* roundRectClipState); void build(); + + static void dump(const Glop& glop); private: void setFill(int color, float alphaScale, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage, const SkShader* shader, const SkColorFilter* colorFilter); - void setTransform(const Matrix4& canvas, const int transformFlags); enum StageFlags { kInitialStage = 0, diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h index ed517ac26295..c017638db895 100644 --- a/libs/hwui/Matrix.h +++ b/libs/hwui/Matrix.h @@ -126,6 +126,9 @@ public: void loadMultiply(const Matrix4& u, const Matrix4& v); void loadOrtho(float left, float right, float bottom, float top, float near, float far); + void loadOrtho(int width, int height) { + loadOrtho(0, width, height, 0, -1, 1); + } uint8_t getType() const; diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp new file mode 100644 index 000000000000..1d8be2b47d43 --- /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 000000000000..b99172cb673e --- /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 000000000000..a69f0308ebc5 --- /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 000000000000..c4debd60d888 --- /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 000000000000..ed299e56ce7c --- /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 bf1b4d0b0d0e..d122a55ef702 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 @@ void RenderNode::debugDumpLayers(const char* prefix) { } if (mDisplayListData) { for (size_t i = 0; i < mDisplayListData->children().size(); i++) { - mDisplayListData->children()[i]->mRenderNode->debugDumpLayers(prefix); + mDisplayListData->children()[i]->renderNode->debugDumpLayers(prefix); } } } @@ -172,7 +175,7 @@ void RenderNode::copyTo(proto::RenderNode *pnode) { pnode->clear_children(); if (mDisplayListData) { for (auto&& child : mDisplayListData->children()) { - child->mRenderNode->copyTo(pnode->add_children()); + child->renderNode->copyTo(pnode->add_children()); } } } @@ -332,6 +335,10 @@ void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) { info.damageAccumulator->popTransform(); } +void RenderNode::syncProperties() { + mProperties = mStagingProperties; +} + void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { // Push the animators first so that setupStartValueIfNecessary() is called // before properties() is trampled by stagingProperties(), as they are @@ -343,7 +350,7 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { mDirtyPropertyFields = 0; damageSelf(info); info.damageAccumulator->popTransform(); - mProperties = mStagingProperties; + syncProperties(); applyLayerPropertiesToLayer(info); // We could try to be clever and only re-damage if the matrix changed. // However, we don't need to worry about that. The cost of over-damaging @@ -364,35 +371,39 @@ void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) { mLayer->setBlend(props.needsBlending()); } +void RenderNode::syncDisplayList() { + // Make sure we inc first so that we don't fluctuate between 0 and 1, + // which would thrash the layer cache + if (mStagingDisplayListData) { + for (auto&& child : mStagingDisplayListData->children()) { + child->renderNode->incParentRefCount(); + } + } + deleteDisplayListData(); + mDisplayListData = mStagingDisplayListData; + mStagingDisplayListData = nullptr; + if (mDisplayListData) { + for (size_t i = 0; i < mDisplayListData->functors.size(); i++) { + (*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, nullptr); + } + } +} + void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) { if (mNeedsDisplayListDataSync) { mNeedsDisplayListDataSync = false; - // Make sure we inc first so that we don't fluctuate between 0 and 1, - // which would thrash the layer cache - if (mStagingDisplayListData) { - for (size_t i = 0; i < mStagingDisplayListData->children().size(); i++) { - mStagingDisplayListData->children()[i]->mRenderNode->incParentRefCount(); - } - } // Damage with the old display list first then the new one to catch any // changes in isRenderable or, in the future, bounds damageSelf(info); - deleteDisplayListData(); - mDisplayListData = mStagingDisplayListData; - mStagingDisplayListData = nullptr; - if (mDisplayListData) { - for (size_t i = 0; i < mDisplayListData->functors.size(); i++) { - (*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, nullptr); - } - } + syncDisplayList(); damageSelf(info); } } void RenderNode::deleteDisplayListData() { if (mDisplayListData) { - for (size_t i = 0; i < mDisplayListData->children().size(); i++) { - mDisplayListData->children()[i]->mRenderNode->decParentRefCount(); + for (auto&& child : mDisplayListData->children()) { + child->renderNode->decParentRefCount(); } } delete mDisplayListData; @@ -407,13 +418,17 @@ void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayL info.prepareTextures = cache.prefetchAndMarkInUse( info.canvasContext, subtree->bitmapResources[i]); } - for (size_t i = 0; i < subtree->children().size(); i++) { - DrawRenderNodeOp* op = subtree->children()[i]; - RenderNode* childNode = op->mRenderNode; + for (auto&& op : subtree->children()) { + RenderNode* childNode = op->renderNode; +#if HWUI_NEW_OPS + info.damageAccumulator->pushTransform(&op->localMatrix); + bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip; +#else info.damageAccumulator->pushTransform(&op->mTransformFromParent); bool childFunctorsNeedLayer = functorsNeedLayer // Recorded with non-rect clip, or canvas-rotated by parent || op->mRecordedWithPotentialStencilClip; +#endif childNode->prepareTreeImpl(info, childFunctorsNeedLayer); info.damageAccumulator->popTransform(); } @@ -426,8 +441,8 @@ void RenderNode::destroyHardwareResources() { mLayer = nullptr; } if (mDisplayListData) { - for (size_t i = 0; i < mDisplayListData->children().size(); i++) { - mDisplayListData->children()[i]->mRenderNode->destroyHardwareResources(); + for (auto&& child : mDisplayListData->children()) { + child->renderNode->destroyHardwareResources(); } if (mNeedsDisplayListDataSync) { // Next prepare tree we are going to push a new display list, so we can @@ -449,6 +464,34 @@ void RenderNode::decParentRefCount() { } } +bool RenderNode::applyViewProperties(CanvasState& canvasState) const { + const Outline& outline = properties().getOutline(); + if (properties().getAlpha() <= 0 + || (outline.getShouldClip() && outline.isEmpty()) + || properties().getScaleX() == 0 + || properties().getScaleY() == 0) { + return false; // rejected + } + + if (properties().getLeft() != 0 || properties().getTop() != 0) { + canvasState.translate(properties().getLeft(), properties().getTop()); + } + if (properties().getStaticMatrix()) { + canvasState.concatMatrix(*properties().getStaticMatrix()); + } else if (properties().getAnimationMatrix()) { + canvasState.concatMatrix(*properties().getAnimationMatrix()); + } + if (properties().hasTransformMatrix()) { + if (properties().isTransformTranslateOnly()) { + canvasState.translate(properties().getTranslationX(), properties().getTranslationY()); + } else { + canvasState.concatMatrix(*properties().getTransformMatrix()); + } + } + return !canvasState.quickRejectConservative( + 0, 0, properties().getWidth(), properties().getHeight()); +} + /* * For property operations, we pass a savecount of 0, since the operations aren't part of the * displaylist, and thus don't have to compensate for the record-time/playback-time discrepancy in @@ -580,6 +623,7 @@ void RenderNode::applyViewPropertyTransforms(mat4& matrix, bool true3dTransform) * which are flagged to not draw in the standard draw loop. */ void RenderNode::computeOrdering() { +#if !HWUI_NEW_OPS ATRACE_CALL(); mProjectedNodes.clear(); @@ -588,14 +632,16 @@ void RenderNode::computeOrdering() { if (mDisplayListData == nullptr) return; for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) { DrawRenderNodeOp* childOp = mDisplayListData->children()[i]; - childOp->mRenderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity()); + childOp->renderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity()); } +#endif } void RenderNode::computeOrderingImpl( DrawRenderNodeOp* opState, std::vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface, const mat4* transformFromProjectionSurface) { +#if !HWUI_NEW_OPS mProjectedNodes.clear(); if (mDisplayListData == nullptr || mDisplayListData->isEmpty()) return; @@ -619,7 +665,7 @@ void RenderNode::computeOrderingImpl( bool haveAppliedPropertiesToProjection = false; for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) { DrawRenderNodeOp* childOp = mDisplayListData->children()[i]; - RenderNode* child = childOp->mRenderNode; + RenderNode* child = childOp->renderNode; std::vector<DrawRenderNodeOp*>* projectionChildren = nullptr; const mat4* projectionTransform = nullptr; @@ -642,6 +688,7 @@ void RenderNode::computeOrderingImpl( child->computeOrderingImpl(childOp, projectionChildren, projectionTransform); } } +#endif } class DeferOperationHandler { @@ -701,11 +748,12 @@ void RenderNode::replay(ReplayStateStruct& replayStruct, const int level) { void RenderNode::buildZSortedChildList(const DisplayListData::Chunk& chunk, std::vector<ZDrawRenderNodeOpPair>& zTranslatedNodes) { +#if !HWUI_NEW_OPS if (chunk.beginChildIndex == chunk.endChildIndex) return; for (unsigned int i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) { DrawRenderNodeOp* childOp = mDisplayListData->children()[i]; - RenderNode* child = childOp->mRenderNode; + RenderNode* child = childOp->renderNode; float childZ = child->properties().getZ(); if (!MathUtils::isZero(childZ) && chunk.reorderChildren) { @@ -719,6 +767,7 @@ void RenderNode::buildZSortedChildList(const DisplayListData::Chunk& chunk, // Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order) std::stable_sort(zTranslatedNodes.begin(), zTranslatedNodes.end()); +#endif } template <class T> @@ -824,7 +873,7 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode, while (shadowIndex < endIndex || drawIndex < endIndex) { if (shadowIndex < endIndex) { DrawRenderNodeOp* casterOp = zTranslatedNodes[shadowIndex].value; - RenderNode* caster = casterOp->mRenderNode; + RenderNode* caster = casterOp->renderNode; const float casterZ = zTranslatedNodes[shadowIndex].key; // attempt to render the shadow if the caster about to be drawn is its caster, // OR if its caster's Z value is similar to the previous potential caster @@ -869,7 +918,7 @@ void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T& const DisplayListOp* op = (mDisplayListData->displayListOps[mDisplayListData->projectionReceiveIndex]); const DrawRenderNodeOp* backgroundOp = reinterpret_cast<const DrawRenderNodeOp*>(op); - const RenderProperties& backgroundProps = backgroundOp->mRenderNode->properties(); + const RenderProperties& backgroundProps = backgroundOp->renderNode->properties(); renderer.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY()); // If the projection reciever has an outline, we mask projected content to it diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index 88fc60856feb..ff673ba32343 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 @@ class SkRegion; namespace android { namespace uirenderer { +class CanvasState; class DisplayListOp; class DisplayListCanvas; class OpenGLRenderer; @@ -74,6 +75,7 @@ class RenderNode; * attached. */ class RenderNode : public VirtualLightRefBase { +friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties public: enum DirtyPropertyMask { GENERIC = 1 << 1, @@ -176,8 +178,25 @@ public: AnimatorManager& animators() { return mAnimatorManager; } + // Returns false if the properties dictate the subtree contained in this RenderNode won't render + bool applyViewProperties(CanvasState& canvasState) const; + void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false) const; + bool nothingToDraw() const { + const Outline& outline = properties().getOutline(); + return mDisplayListData == nullptr + || properties().getAlpha() <= 0 + || (outline.getShouldClip() && outline.isEmpty()) + || properties().getScaleX() == 0 + || properties().getScaleY() == 0; + } + + // Only call if RenderNode has DisplayListData... + const DisplayListData& getDisplayListData() const { + return *mDisplayListData; + } + private: typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair; @@ -235,6 +254,10 @@ private: const char* mText; }; + + void syncProperties(); + void syncDisplayList(); + void prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer); void pushStagingPropertiesChanges(TreeInfo& info); void pushStagingDisplayListChanges(TreeInfo& info); diff --git a/libs/hwui/microbench/DisplayListCanvasBench.cpp b/libs/hwui/microbench/DisplayListCanvasBench.cpp index 7b6be7a17a37..fd42adb13da2 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 @@ void BM_DisplayListData_alloc_theoretical::Run(int iters) { BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_empty); void BM_DisplayListCanvas_record_empty::Run(int iters) { - DisplayListCanvas canvas(100, 100); + TestCanvas canvas(100, 100); canvas.finishRecording(); StartBenchmarkTiming(); @@ -62,7 +72,7 @@ void BM_DisplayListCanvas_record_empty::Run(int iters) { BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_saverestore); void BM_DisplayListCanvas_record_saverestore::Run(int iters) { - DisplayListCanvas canvas(100, 100); + TestCanvas canvas(100, 100); canvas.finishRecording(); StartBenchmarkTiming(); @@ -80,7 +90,7 @@ void BM_DisplayListCanvas_record_saverestore::Run(int iters) { BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_translate); void BM_DisplayListCanvas_record_translate::Run(int iters) { - DisplayListCanvas canvas(100, 100); + TestCanvas canvas(100, 100); canvas.finishRecording(); StartBenchmarkTiming(); diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 38f6e539693e..e1d8abdd7034 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 @@ void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) { bool CanvasContext::initialize(ANativeWindow* window) { setSurface(window); +#if !HWUI_NEW_OPS if (mCanvas) return false; mCanvas = new OpenGLRenderer(mRenderThread.renderState()); mCanvas->initProperties(); +#endif return true; } @@ -162,11 +169,13 @@ void CanvasContext::makeCurrent() { } void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) { +#if !HWUI_NEW_OPS bool success = layerUpdater->apply(); LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!"); if (layerUpdater->backingLayer()->deferredUpdateScheduled) { mCanvas->pushLayerUpdate(layerUpdater->backingLayer()); } +#endif } static bool wasSkipped(FrameInfo* info) { @@ -239,8 +248,10 @@ void CanvasContext::notifyFramePending() { } void CanvasContext::draw() { +#if !HWUI_NEW_OPS LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE, "drawRenderNode called on a context with no canvas or surface!"); +#endif SkRect dirty; mDamageAccumulator.finish(&dirty); @@ -254,6 +265,8 @@ void CanvasContext::draw() { mCurrentFrameInfo->markIssueDrawCommandsStart(); Frame frame = mEglManager.beginFrame(mEglSurface); + +#if !HWUI_NEW_OPS if (frame.width() != mCanvas->getViewportWidth() || frame.height() != mCanvas->getViewportHeight()) { // can't rely on prior content of window if viewport size changes @@ -417,6 +430,16 @@ void CanvasContext::draw() { // Even if we decided to cancel the frame, from the perspective of jank // metrics the frame was swapped at this point mCurrentFrameInfo->markSwapBuffers(); +#else + OpReorderer reorderer; + reorderer.defer(frame.width(), frame.height(), mRenderNodes); + BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(), + frame.width(), frame.height(), mOpaque); + reorderer.replayBakedOps<BakedOpRenderer>(&info); + + bool drew = info.didDraw; + SkRect screenDirty = SkRect::MakeWH(frame.width(), frame.height()); +#endif if (drew) { if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) { diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp index a59261c14fc5..891af9171518 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; using namespace android::uirenderer::renderthread; using namespace android::uirenderer::test; +#if HWUI_NEW_OPS +typedef RecordingCanvas TestCanvas; +#else +typedef DisplayListCanvas TestCanvas; +#endif + + class ContextFactory : public IContextFactory { public: virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override { @@ -46,13 +54,13 @@ public: } }; -static DisplayListCanvas* startRecording(RenderNode* node) { - DisplayListCanvas* renderer = new DisplayListCanvas( +static TestCanvas* startRecording(RenderNode* node) { + TestCanvas* renderer = new TestCanvas( node->stagingProperties().getWidth(), node->stagingProperties().getHeight()); return renderer; } -static void endRecording(DisplayListCanvas* renderer, RenderNode* node) { +static void endRecording(TestCanvas* renderer, RenderNode* node) { node->setStagingDisplayList(renderer->finishRecording()); delete renderer; } @@ -67,7 +75,7 @@ public: frameCount = fc; } } - virtual void createContent(int width, int height, DisplayListCanvas* renderer) = 0; + virtual void createContent(int width, int height, TestCanvas* renderer) = 0; virtual void doFrame(int frameNr) = 0; template <class T> @@ -102,7 +110,7 @@ public: android::uirenderer::Rect DUMMY; - DisplayListCanvas* renderer = startRecording(rootNode); + TestCanvas* renderer = startRecording(rootNode); animation.createContent(width, height, renderer); endRecording(renderer, rootNode); @@ -132,7 +140,7 @@ public: class ShadowGridAnimation : public TreeContentAnimation { public: std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, DisplayListCanvas* renderer) override { + void createContent(int width, int height, TestCanvas* renderer) override { renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); renderer->insertReorderBarrier(true); @@ -163,7 +171,7 @@ private: node->mutateStagingProperties().mutableOutline().setShouldClip(true); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); - DisplayListCanvas* renderer = startRecording(node.get()); + TestCanvas* renderer = startRecording(node.get()); renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); endRecording(renderer, node.get()); return node; @@ -179,7 +187,7 @@ static Benchmark _ShadowGrid(BenchmarkInfo{ class ShadowGrid2Animation : public TreeContentAnimation { public: std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, DisplayListCanvas* renderer) override { + void createContent(int width, int height, TestCanvas* renderer) override { renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); renderer->insertReorderBarrier(true); @@ -210,7 +218,7 @@ private: node->mutateStagingProperties().mutableOutline().setShouldClip(true); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); - DisplayListCanvas* renderer = startRecording(node.get()); + TestCanvas* renderer = startRecording(node.get()); renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); endRecording(renderer, node.get()); return node; @@ -226,7 +234,7 @@ static Benchmark _ShadowGrid2(BenchmarkInfo{ class RectGridAnimation : public TreeContentAnimation { public: sp<RenderNode> card; - void createContent(int width, int height, DisplayListCanvas* renderer) override { + void createContent(int width, int height, TestCanvas* renderer) override { renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); renderer->insertReorderBarrier(true); @@ -247,7 +255,7 @@ private: node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - DisplayListCanvas* renderer = startRecording(node.get()); + TestCanvas* renderer = startRecording(node.get()); renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode); SkRegion region; @@ -275,7 +283,7 @@ static Benchmark _RectGrid(BenchmarkInfo{ class OvalAnimation : public TreeContentAnimation { public: sp<RenderNode> card; - void createContent(int width, int height, DisplayListCanvas* renderer) override { + void createContent(int width, int height, TestCanvas* renderer) override { renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); renderer->insertReorderBarrier(true); @@ -297,7 +305,7 @@ private: node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - DisplayListCanvas* renderer = startRecording(node.get()); + TestCanvas* renderer = startRecording(node.get()); SkPaint paint; paint.setAntiAlias(true); @@ -317,7 +325,7 @@ static Benchmark _Oval(BenchmarkInfo{ class PartialDamageTest : public TreeContentAnimation { public: std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, DisplayListCanvas* renderer) override { + void createContent(int width, int height, TestCanvas* renderer) override { static SkColor COLORS[] = { 0xFFF44336, 0xFF9C27B0, @@ -342,7 +350,7 @@ public: cards[0]->mutateStagingProperties().setTranslationY(curFrame); cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - DisplayListCanvas* renderer = startRecording(cards[0].get()); + TestCanvas* renderer = startRecording(cards[0].get()); renderer->drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0), SkXfermode::kSrcOver_Mode); endRecording(renderer, cards[0].get()); @@ -370,7 +378,7 @@ private: node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - DisplayListCanvas* renderer = startRecording(node.get()); + TestCanvas* renderer = startRecording(node.get()); renderer->drawColor(color, SkXfermode::kSrcOver_Mode); endRecording(renderer, node.get()); return node; @@ -383,3 +391,43 @@ static Benchmark _PartialDamage(BenchmarkInfo{ "EGL_KHR_partial_update is supported by the device & are enabled in hwui.", TreeContentAnimation::run<PartialDamageTest> }); + + +class SimpleRectGridAnimation : public TreeContentAnimation { +public: + sp<RenderNode> card; + void createContent(int width, int height, TestCanvas* renderer) override { + SkPaint paint; + paint.setColor(0xFF00FFFF); + renderer->drawRect(0, 0, width, height, paint); + + card = createCard(40, 40, 200, 200); + renderer->drawRenderNode(card.get()); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +private: + sp<RenderNode> createCard(int x, int y, int width, int height) { + sp<RenderNode> node = new RenderNode(); + node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); + node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + + TestCanvas* renderer = startRecording(node.get()); + SkPaint paint; + paint.setColor(0xFFFF00FF); + renderer->drawRect(0, 0, width, height, paint); + + endRecording(renderer, node.get()); + return node; + } +}; +static Benchmark _SimpleRectGrid(BenchmarkInfo{ + "simplerectgrid", + "A simple collection of rects. " + "Low CPU/GPU load.", + TreeContentAnimation::run<SimpleRectGridAnimation> +}); diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp new file mode 100644 index 000000000000..82aebeabe038 --- /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 0c5e5e715dea..d6192df08bc3 100644 --- a/libs/hwui/unit_tests/ClipAreaTests.cpp +++ b/libs/hwui/unit_tests/ClipAreaTests.cpp @@ -101,10 +101,9 @@ TEST(ClipArea, paths) { EXPECT_FALSE(area.isEmpty()); EXPECT_FALSE(area.isSimple()); EXPECT_FALSE(area.isRectangleList()); + Rect clipRect(area.getClipRect()); - clipRect.dump("clipRect"); Rect expected(0, 0, r * 2, r * 2); - expected.dump("expected"); EXPECT_EQ(expected, clipRect); SkRegion clipRegion(area.getClipRegion()); auto skRect(clipRegion.getBounds()); diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp new file mode 100644 index 000000000000..fcaea1e36a69 --- /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 000000000000..c8138338a20e --- /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 000000000000..257dd2806d38 --- /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 d00236ed955a..db537130e12e 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 { |