diff options
-rw-r--r-- | libs/hwui/Android.mk | 2 | ||||
-rw-r--r-- | libs/hwui/BakedOpRenderer.cpp | 46 | ||||
-rw-r--r-- | libs/hwui/BakedOpRenderer.h | 9 | ||||
-rw-r--r-- | libs/hwui/DisplayList.h | 6 | ||||
-rw-r--r-- | libs/hwui/LayerUpdateQueue.cpp | 42 | ||||
-rw-r--r-- | libs/hwui/LayerUpdateQueue.h | 53 | ||||
-rw-r--r-- | libs/hwui/OpReorderer.cpp | 98 | ||||
-rw-r--r-- | libs/hwui/OpReorderer.h | 36 | ||||
-rw-r--r-- | libs/hwui/RecordedOp.h | 34 | ||||
-rw-r--r-- | libs/hwui/RecordingCanvas.cpp | 1 | ||||
-rw-r--r-- | libs/hwui/RecordingCanvas.h | 7 | ||||
-rw-r--r-- | libs/hwui/RenderNode.cpp | 64 | ||||
-rw-r--r-- | libs/hwui/RenderNode.h | 29 | ||||
-rw-r--r-- | libs/hwui/TreeInfo.h | 10 | ||||
-rw-r--r-- | libs/hwui/microbench/OpReordererBench.cpp | 4 | ||||
-rw-r--r-- | libs/hwui/renderthread/CanvasContext.cpp | 12 | ||||
-rw-r--r-- | libs/hwui/renderthread/CanvasContext.h | 8 | ||||
-rw-r--r-- | libs/hwui/tests/TreeContentAnimation.cpp | 25 | ||||
-rw-r--r-- | libs/hwui/unit_tests/LayerUpdateQueueTests.cpp | 82 | ||||
-rw-r--r-- | libs/hwui/unit_tests/OpReordererTests.cpp | 176 | ||||
-rw-r--r-- | libs/hwui/unit_tests/TestUtils.h | 23 |
21 files changed, 670 insertions, 97 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index d94c91d64572..ae5fa6c15c01 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -56,6 +56,7 @@ hwui_src_files := \ Layer.cpp \ LayerCache.cpp \ LayerRenderer.cpp \ + LayerUpdateQueue.cpp \ Matrix.cpp \ OpenGLRenderer.cpp \ Patch.cpp \ @@ -204,6 +205,7 @@ LOCAL_SRC_FILES += \ unit_tests/ClipAreaTests.cpp \ unit_tests/DamageAccumulatorTests.cpp \ unit_tests/FatVectorTests.cpp \ + unit_tests/LayerUpdateQueueTests.cpp \ unit_tests/LinearAllocatorTests.cpp \ unit_tests/StringUtilsTests.cpp diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp index 086885320cce..705583941067 100644 --- a/libs/hwui/BakedOpRenderer.cpp +++ b/libs/hwui/BakedOpRenderer.cpp @@ -31,7 +31,9 @@ namespace uirenderer { OffscreenBuffer::OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight, uint32_t viewportWidth, uint32_t viewportHeight) - : texture(caches) + : viewportWidth(viewportWidth) + , viewportHeight(viewportHeight) + , texture(caches) , texCoords(0, viewportHeight / float(textureHeight), viewportWidth / float(textureWidth), 0) { texture.width = textureWidth; texture.height = textureHeight; @@ -52,12 +54,29 @@ OffscreenBuffer::OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t // BakedOpRenderer //////////////////////////////////////////////////////////////////////////////// -OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) { +OffscreenBuffer* BakedOpRenderer::createOffscreenBuffer(uint32_t width, uint32_t height) { + // TODO: get from cache! + return new OffscreenBuffer(Caches::getInstance(), width, height, width, height); +} + +void BakedOpRenderer::destroyOffscreenBuffer(OffscreenBuffer* offscreenBuffer) { + // destroy and delete, since each clipped saveLayer is only drawn once. + offscreenBuffer->texture.deleteTexture(); + + // TODO: return texture/offscreenbuffer to cache! + delete offscreenBuffer; +} + +OffscreenBuffer* BakedOpRenderer::createLayer(uint32_t width, uint32_t height) { LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer..."); - // TODO: really should be caching these! - OffscreenBuffer* buffer = new OffscreenBuffer(mCaches, width, height, width, height); - mRenderTarget.offscreenBuffer = buffer; + OffscreenBuffer* buffer = createOffscreenBuffer(width, height); + startLayer(buffer); + return buffer; +} + +void BakedOpRenderer::startLayer(OffscreenBuffer* offscreenBuffer) { + mRenderTarget.offscreenBuffer = offscreenBuffer; // create and bind framebuffer mRenderTarget.frameBufferId = mRenderState.genFramebuffer(); @@ -65,7 +84,7 @@ OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) { // attach the texture to the FBO glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - buffer->texture.id, 0); + offscreenBuffer->texture.id, 0); LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED"); LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE, "framebuffer incomplete!"); @@ -75,8 +94,7 @@ OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) { glClear(GL_COLOR_BUFFER_BIT); // Change the viewport & ortho projection - setViewport(width, height); - return buffer; + setViewport(offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight); } void BakedOpRenderer::endLayer() { @@ -212,23 +230,21 @@ void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, // TODO: extend this to handle HW layers & paint properties which // reside in node.properties().layerProperties() - float layerAlpha = (op.paint->getAlpha() / 255.0f) * state.alpha; + float layerAlpha = op.alpha * state.alpha; const bool tryToSnap = state.computedState.transform.isPureTranslate(); Glop glop; GlopBuilder(renderer.renderState(), renderer.caches(), &glop) .setRoundRectClipState(state.roundRectClipState) .setMeshTexturedUvQuad(nullptr, buffer->texCoords) - .setFillLayer(buffer->texture, op.paint->getColorFilter(), layerAlpha, PaintUtils::getXfermodeDirect(op.paint), Blend::ModeOrderSwap::NoSwap) + .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap) .setTransform(state.computedState.transform, TransformFlags::None) .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds) .build(); renderer.renderGlop(state, glop); - // destroy and delete, since each clipped saveLayer is only drawn once. - buffer->texture.deleteTexture(); - - // TODO: return texture/offscreenbuffer to cache! - delete buffer; + if (op.destroy) { + BakedOpRenderer::destroyOffscreenBuffer(buffer); + } } } // namespace uirenderer diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h index 16afad44657e..722bf02ff81e 100644 --- a/libs/hwui/BakedOpRenderer.h +++ b/libs/hwui/BakedOpRenderer.h @@ -37,7 +37,8 @@ class OffscreenBuffer { public: OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight, uint32_t viewportWidth, uint32_t viewportHeight); - + uint32_t viewportWidth; + uint32_t viewportHeight; Texture texture; Rect texCoords; Region region; @@ -60,12 +61,16 @@ public: , mOpaque(opaque) { } + static OffscreenBuffer* createOffscreenBuffer(uint32_t width, uint32_t height); + static void destroyOffscreenBuffer(OffscreenBuffer*); + RenderState& renderState() { return mRenderState; } Caches& caches() { return mCaches; } void startFrame(uint32_t width, uint32_t height); void endFrame(); - OffscreenBuffer* startLayer(uint32_t width, uint32_t height); + OffscreenBuffer* createLayer(uint32_t width, uint32_t height); + void startLayer(OffscreenBuffer* offscreenBuffer); void endLayer(); Texture* getTexture(const SkBitmap* bitmap); diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index 86796c5a5e0c..00c4e2d47e4c 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -154,7 +154,11 @@ public: return allocator.usedSize(); } bool isEmpty() { +#if HWUI_NEW_OPS + return ops.empty(); +#else return !hasDrawOps; +#endif } private: @@ -179,7 +183,7 @@ private: // List of functors LsaVector<Functor*> functors; - bool hasDrawOps; + bool hasDrawOps; // only used if !HWUI_NEW_OPS void cleanupResources(); }; diff --git a/libs/hwui/LayerUpdateQueue.cpp b/libs/hwui/LayerUpdateQueue.cpp new file mode 100644 index 000000000000..db5f676d09dc --- /dev/null +++ b/libs/hwui/LayerUpdateQueue.cpp @@ -0,0 +1,42 @@ +/* + * 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 "LayerUpdateQueue.h" + +#include "RenderNode.h" + +namespace android { +namespace uirenderer { + +void LayerUpdateQueue::clear() { + mEntries.clear(); +} + +void LayerUpdateQueue::enqueueLayerWithDamage(RenderNode* renderNode, Rect damage) { + damage.doIntersect(0, 0, renderNode->getWidth(), renderNode->getHeight()); + if (!damage.isEmpty()) { + for (Entry& entry : mEntries) { + if (CC_UNLIKELY(entry.renderNode == renderNode)) { + entry.damage.unionWith(damage); + return; + } + } + mEntries.emplace_back(renderNode, damage); + } +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/LayerUpdateQueue.h b/libs/hwui/LayerUpdateQueue.h new file mode 100644 index 000000000000..be612d2a15e7 --- /dev/null +++ b/libs/hwui/LayerUpdateQueue.h @@ -0,0 +1,53 @@ +/* + * 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_LAYER_UPDATE_QUEUE_H +#define ANDROID_HWUI_LAYER_UPDATE_QUEUE_H + +#include "Rect.h" +#include "utils/Macros.h" + +#include <vector> +#include <unordered_map> + +namespace android { +namespace uirenderer { + +class RenderNode; + +class LayerUpdateQueue { + PREVENT_COPY_AND_ASSIGN(LayerUpdateQueue); +public: + struct Entry { + Entry(RenderNode* renderNode, const Rect& damage) + : renderNode(renderNode) + , damage(damage) {} + RenderNode* renderNode; + Rect damage; + }; + + LayerUpdateQueue() {} + void enqueueLayerWithDamage(RenderNode* renderNode, Rect dirty); + void clear(); + const std::vector<Entry> entries() const { return mEntries; } +private: + std::vector<Entry> mEntries; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_LAYER_UPDATE_QUEUE_H diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp index ddeb33624798..163f7cc4b1d0 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/OpReorderer.cpp @@ -18,6 +18,7 @@ #include "utils/PaintUtils.h" #include "RenderNode.h" +#include "LayerUpdateQueue.h" #include "SkCanvas.h" #include "utils/Trace.h" @@ -202,6 +203,14 @@ private: Rect mClipRect; }; +OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height, + const BeginLayerOp* beginLayerOp, RenderNode* renderNode) + : width(width) + , height(height) + , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr) + , beginLayerOp(beginLayerOp) + , renderNode(renderNode) {} + // 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 OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds, @@ -288,33 +297,48 @@ void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpDispatche } void OpReorderer::LayerReorderer::dump() const { + ALOGD("LayerReorderer %p, %ux%u buffer %p, blo %p, rn %p", + this, width, height, offscreenBuffer, beginLayerOp, renderNode); for (const BatchBase* batch : mBatches) { batch->dump(); } } -OpReorderer::OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight, +OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip, + uint32_t viewportWidth, uint32_t viewportHeight, const std::vector< sp<RenderNode> >& nodes) : mCanvasState(*this) { ATRACE_NAME("prepare drawing commands"); - mLayerReorderers.emplace_back(viewportWidth, viewportHeight); - mLayerStack.push_back(0); + mLayerStack.push_back(0); mCanvasState.initializeSaveStack(viewportWidth, viewportHeight, clip.fLeft, clip.fTop, clip.fRight, clip.fBottom, Vector3()); + + // Render all layers to be updated, in order. Defer in reverse order, so that they'll be + // updated in the order they're passed in (mLayerReorderers are issued to Renderer in reverse) + for (int i = layers.entries().size() - 1; i >= 0; i--) { + RenderNode* layerNode = layers.entries()[i].renderNode; + const Rect& layerDamage = layers.entries()[i].damage; + + saveForLayer(layerNode->getWidth(), layerNode->getHeight(), nullptr, layerNode); + mCanvasState.writableSnapshot()->setClip( + layerDamage.left, layerDamage.top, layerDamage.right, layerDamage.bottom); + + if (layerNode->getDisplayList()) { + deferImpl(*(layerNode->getDisplayList())); + } + restoreForLayer(); + } + + // Defer Fbo0 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 DisplayList& displayList = node->getDisplayList(); - deferImpl(displayList); - } - mCanvasState.restore(); + int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + deferNodePropsAndOps(*node); + mCanvasState.restoreToCount(count); } } @@ -334,6 +358,23 @@ void OpReorderer::onViewportInitialized() {} void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} +void OpReorderer::deferNodePropsAndOps(RenderNode& node) { + if (node.applyViewProperties(mCanvasState)) { + // not rejected so render + if (node.getLayer()) { + // HW layer + LayerOp* drawLayerOp = new (mAllocator) LayerOp(node); + BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp); + if (bakedOpState) { + // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack) + currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap); + } + } else { + deferImpl(*(node.getDisplayList())); + } + } +} + /** * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods. * @@ -365,11 +406,9 @@ void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) { 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)) { - // if node not rejected based on properties, do ops... - deferImpl(op.renderNode->getDisplayList()); - } + // then apply state from node properties, and defer ops + deferNodePropsAndOps(*op.renderNode); + mCanvasState.restoreToCount(count); } @@ -400,10 +439,8 @@ void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) { currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices); } -// TODO: test rejection at defer time, where the bounds become empty -void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) { - const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth(); - const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight(); +void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, + const BeginLayerOp* beginLayerOp, RenderNode* renderNode) { mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); mCanvasState.writableSnapshot()->transform->loadIdentity(); @@ -412,18 +449,27 @@ void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) { // create a new layer, and push its index on the stack mLayerStack.push_back(mLayerReorderers.size()); - mLayerReorderers.emplace_back(layerWidth, layerHeight); - mLayerReorderers.back().beginLayerOp = &op; + mLayerReorderers.emplace_back(layerWidth, layerHeight, beginLayerOp, renderNode); } -void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) { +void OpReorderer::restoreForLayer() { + // restore canvas, and pop finished layer off of the stack mCanvasState.restore(); + mLayerStack.pop_back(); +} - const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp; +// TODO: test rejection at defer time, where the bounds become empty +void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) { + const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth(); + const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight(); + saveForLayer(layerWidth, layerHeight, &op, nullptr); +} - // pop finished layer off of the stack +void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) { + const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp; int finishedLayerIndex = mLayerStack.back(); - mLayerStack.pop_back(); + + restoreForLayer(); // record the draw operation into the previous layer's list of draw commands // uses state from the associated beginLayerOp, since it has all the state needed for drawing diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h index 927ecfae3b9f..77be40292205 100644 --- a/libs/hwui/OpReorderer.h +++ b/libs/hwui/OpReorderer.h @@ -32,6 +32,7 @@ namespace uirenderer { class BakedOpState; class BatchBase; +class LayerUpdateQueue; class MergingOpBatch; class OffscreenBuffer; class OpBatch; @@ -64,9 +65,14 @@ class OpReorderer : public CanvasStateClient { */ class LayerReorderer { public: + // Create LayerReorderer for Fbo0 LayerReorderer(uint32_t width, uint32_t height) - : width(width) - , height(height) {} + : LayerReorderer(width, height, nullptr, nullptr) {}; + + // Create LayerReorderer for an offscreen layer, where beginLayerOp is present for a + // saveLayer, renderNode is present for a HW layer. + LayerReorderer(uint32_t width, uint32_t height, + const BeginLayerOp* beginLayerOp, RenderNode* renderNode); // 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 @@ -92,12 +98,12 @@ class OpReorderer : public CanvasStateClient { void dump() const; - OffscreenBuffer* offscreenBuffer = nullptr; - const BeginLayerOp* beginLayerOp = nullptr; const uint32_t width; const uint32_t height; + OffscreenBuffer* offscreenBuffer; + const BeginLayerOp* beginLayerOp; + const RenderNode* renderNode; private: - std::vector<BatchBase*> mBatches; /** @@ -112,8 +118,8 @@ class OpReorderer : public CanvasStateClient { }; public: - // TODO: not final, just presented this way for simplicity. Layers too? - OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight, + OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip, + uint32_t viewportWidth, uint32_t viewportHeight, const std::vector< sp<RenderNode> >& nodes); OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList); @@ -144,8 +150,13 @@ public: // later in the list will be drawn by earlier ones for (int i = mLayerReorderers.size() - 1; i >= 1; i--) { LayerReorderer& layer = mLayerReorderers[i]; - if (!layer.empty()) { - layer.offscreenBuffer = renderer.startLayer(layer.width, layer.height); + if (layer.renderNode) { + // cached HW layer - can't skip layer if empty + renderer.startLayer(layer.offscreenBuffer); + layer.replayBakedOpsImpl((void*)&renderer, receivers); + renderer.endLayer(); + } else if (!layer.empty()) { // save layer - skip entire layer if empty + layer.offscreenBuffer = renderer.createLayer(layer.width, layer.height); layer.replayBakedOpsImpl((void*)&renderer, receivers); renderer.endLayer(); } @@ -171,12 +182,19 @@ public: virtual GLuint getTargetFbo() const override { return 0; } private: + void saveForLayer(uint32_t layerWidth, uint32_t layerHeight, + const BeginLayerOp* beginLayerOp, RenderNode* renderNode); + void restoreForLayer(); + LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; } BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) { return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp); } + // should always be surrounded by a save/restore pair + void deferNodePropsAndOps(RenderNode& node); + void deferImpl(const DisplayList& displayList); void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers); diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h index 7874d85f249c..9ae868a9858c 100644 --- a/libs/hwui/RecordedOp.h +++ b/libs/hwui/RecordedOp.h @@ -20,6 +20,7 @@ #include "utils/LinearAllocator.h" #include "Rect.h" #include "Matrix.h" +#include "RenderNode.h" #include "SkXfermode.h" @@ -136,13 +137,42 @@ struct EndLayerOp : RecordedOp { : RecordedOp(RecordedOpId::EndLayerOp, Rect(0, 0), Matrix4::identity(), Rect(0, 0), nullptr) {} }; +/** + * Draws an OffscreenBuffer. + * + * Alpha, mode, and colorfilter are embedded, since LayerOps are always dynamically generated, + * when creating/tracking a SkPaint* during defer isn't worth the bother. + */ struct LayerOp : RecordedOp { + // Records a one-use (saveLayer) layer for drawing. Once drawn, the layer will be destroyed. LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle) - : SUPER(LayerOp) - , layerHandle(layerHandle) {} + : SUPER_PAINTLESS(LayerOp) + , layerHandle(layerHandle) + , alpha(paint->getAlpha() / 255.0f) + , mode(PaintUtils::getXfermodeDirect(paint)) + , colorFilter(paint->getColorFilter()) + , destroy(true) {} + + LayerOp(RenderNode& node) + : RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), Rect(node.getWidth(), node.getHeight()), nullptr) + , layerHandle(node.getLayerHandle()) + , alpha(node.properties().layerProperties().alpha() / 255.0f) + , mode(node.properties().layerProperties().xferMode()) + , colorFilter(node.properties().layerProperties().colorFilter()) + , destroy(false) {} + // Records a handle to the Layer object, since the Layer itself won't be // constructed until after this operation is constructed. OffscreenBuffer** layerHandle; + const float alpha; + const SkXfermode::Mode mode; + + // pointer to object owned by either LayerProperties, or a recorded Paint object in a + // BeginLayerOp. Lives longer than LayerOp in either case, so no skia ref counting is used. + SkColorFilter* colorFilter; + + // whether to destroy the layer, once rendered + const bool destroy; }; }; // namespace uirenderer diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 273af3ac5997..7c460b1bbc1a 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -77,7 +77,6 @@ SkCanvas* RecordingCanvas::asSkCanvas() { // ---------------------------------------------------------------------------- void RecordingCanvas::onViewportInitialized() { - } void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) { diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 454ee24cfc64..8a564758e7b1 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -26,9 +26,10 @@ #include "SkiaCanvasProxy.h" #include "Snapshot.h" -#include "SkDrawFilter.h" -#include "SkPaint.h" -#include "SkTLazy.h" +#include <SkDrawFilter.h> +#include <SkPaint.h> +#include <SkTLazy.h> + #include <vector> namespace android { diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 39cb8e9229b1..de02bb89ac73 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -20,6 +20,7 @@ #include "Debug.h" #if HWUI_NEW_OPS #include "RecordedOp.h" +#include "BakedOpRenderer.h" #endif #include "DisplayListOp.h" #include "LayerRenderer.h" @@ -42,11 +43,15 @@ namespace android { namespace uirenderer { void RenderNode::debugDumpLayers(const char* prefix) { +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL("TODO: dump layer"); +#else if (mLayer) { ALOGD("%sNode %p (%s) has layer %p (fbo = %u, wasBuildLayered = %s)", prefix, this, getName(), mLayer, mLayer->getFbo(), mLayer->wasBuildLayered ? "true" : "false"); } +#endif if (mDisplayList) { for (auto&& child : mDisplayList->getChildren()) { child->renderNode->debugDumpLayers(prefix); @@ -60,18 +65,21 @@ RenderNode::RenderNode() , mDisplayList(nullptr) , mStagingDisplayList(nullptr) , mAnimatorManager(*this) - , mLayer(nullptr) , mParentCount(0) { } RenderNode::~RenderNode() { deleteDisplayList(); delete mStagingDisplayList; +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL_IF(mLayer, "layer missed detachment!"); +#else if (mLayer) { ALOGW("Memory Warning: Layer %p missed its detachment, held on to for far too long!", mLayer); mLayer->postDecStrong(); mLayer = nullptr; } +#endif } void RenderNode::setStagingDisplayList(DisplayList* displayList) { @@ -240,13 +248,29 @@ void RenderNode::prepareLayer(TreeInfo& info, uint32_t dirtyMask) { } } +layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) { +#if HWUI_NEW_OPS + return BakedOpRenderer::createOffscreenBuffer(width, height); +#else + return LayerRenderer::createRenderLayer(renderState, width, height); +#endif +} + +void destroyLayer(layer_t* layer) { +#if HWUI_NEW_OPS + BakedOpRenderer::destroyOffscreenBuffer(layer); +#else + LayerRenderer::destroyLayer(layer); +#endif +} + void RenderNode::pushLayerUpdate(TreeInfo& info) { LayerType layerType = properties().effectiveLayerType(); // If we are not a layer OR we cannot be rendered (eg, view was detached) // we need to destroy any Layers we may have had previously if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable())) { if (CC_UNLIKELY(mLayer)) { - LayerRenderer::destroyLayer(mLayer); + destroyLayer(mLayer); mLayer = nullptr; } return; @@ -254,14 +278,18 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { bool transformUpdateNeeded = false; if (!mLayer) { - mLayer = LayerRenderer::createRenderLayer( - info.canvasContext.getRenderState(), getWidth(), getHeight()); - applyLayerPropertiesToLayer(info); - damageSelf(info); - transformUpdateNeeded = true; + mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight()); + damageSelf(info); + transformUpdateNeeded = true; +#if HWUI_NEW_OPS + } else if (mLayer->viewportWidth != getWidth() || mLayer->viewportHeight != getHeight()) { + // TODO: allow it to grow larger + if (getWidth() > mLayer->texture.width || getHeight() > mLayer->texture.height) { +#else } else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) { if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) { - LayerRenderer::destroyLayer(mLayer); +#endif + destroyLayer(mLayer); mLayer = nullptr; } damageSelf(info); @@ -276,7 +304,7 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { if (info.errorHandler) { std::ostringstream err; err << "Unable to create layer for " << getName(); - const int maxTextureSize = Caches::getInstance().maxTextureSize; + const uint32_t maxTextureSize = Caches::getInstance().maxTextureSize; if (getWidth() > maxTextureSize || getHeight() > maxTextureSize) { err << ", size " << getWidth() << "x" << getHeight() << " exceeds max size " << maxTextureSize; @@ -292,9 +320,16 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { // update the transform in window of the layer to reset its origin wrt light source position Matrix4 windowTransform; info.damageAccumulator->computeCurrentTransform(&windowTransform); +#if HWUI_NEW_OPS + // TODO: update layer transform (perhaps as part of enqueueLayerWithDamage) +#else mLayer->setWindowTransform(windowTransform); +#endif } +#if HWUI_NEW_OPS + info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty); +#else if (dirty.intersect(0, 0, getWidth(), getHeight())) { dirty.roundOut(&dirty); mLayer->updateDeferred(this, dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom); @@ -304,6 +339,7 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { if (info.renderer && mLayer->deferredUpdateScheduled) { info.renderer->pushLayerUpdate(mLayer); } +#endif // There might be prefetched layers that need to be accounted for. // That might be us, so tell CanvasContext that this layer is in the @@ -365,7 +401,9 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { damageSelf(info); info.damageAccumulator->popTransform(); syncProperties(); +#if !HWUI_NEW_OPS applyLayerPropertiesToLayer(info); +#endif // 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 // here is only going to be a single additional map rect of this node @@ -376,6 +414,7 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { } } +#if !HWUI_NEW_OPS void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) { if (CC_LIKELY(!mLayer)) return; @@ -384,6 +423,7 @@ void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) { mLayer->setColorFilter(props.colorFilter()); mLayer->setBlend(props.needsBlending()); } +#endif void RenderNode::syncDisplayList() { // Make sure we inc first so that we don't fluctuate between 0 and 1, @@ -451,7 +491,7 @@ void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayL void RenderNode::destroyHardwareResources() { if (mLayer) { - LayerRenderer::destroyLayer(mLayer); + destroyLayer(mLayer); mLayer = nullptr; } if (mDisplayList) { @@ -978,7 +1018,11 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { return; } +#if HWUI_NEW_OPS + const bool drawLayer = false; +#else const bool drawLayer = (mLayer && (&renderer != mLayer->renderer.get())); +#endif // If we are updating the contents of mLayer, we don't want to apply any of // the RenderNode's properties to this issueOperations pass. Those will all // be applied when the layer is drawn, aka when this is true. diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index 57e41c611547..3500cb200a51 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -44,13 +44,22 @@ namespace android { namespace uirenderer { class CanvasState; -class DisplayListOp; class DisplayListCanvas; +class DisplayListOp; class OpenGLRenderer; +class OpReorderer; class Rect; -class Layer; class SkiaShader; + +#if HWUI_NEW_OPS +class OffscreenBuffer; +typedef OffscreenBuffer layer_t; +#else +class Layer; +typedef Layer layer_t; +#endif + class ClipRectOp; class SaveLayerOp; class SaveOp; @@ -162,11 +171,11 @@ public: return mStagingProperties; } - int getWidth() { + uint32_t getWidth() { return properties().getWidth(); } - int getHeight() { + uint32_t getHeight() { return properties().getHeight(); } @@ -193,9 +202,13 @@ public: } // Only call if RenderNode has DisplayList... - const DisplayList& getDisplayList() const { - return *mDisplayList; + const DisplayList* getDisplayList() const { + return mDisplayList; } +#if HWUI_NEW_OPS + OffscreenBuffer* getLayer() const { return mLayer; } + OffscreenBuffer** getLayerHandle() { return &mLayer; } // ugh... +#endif private: typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair; @@ -262,7 +275,9 @@ private: void pushStagingPropertiesChanges(TreeInfo& info); void pushStagingDisplayListChanges(TreeInfo& info); void prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree); +#if !HWUI_NEW_OPS void applyLayerPropertiesToLayer(TreeInfo& info); +#endif void prepareLayer(TreeInfo& info, uint32_t dirtyMask); void pushLayerUpdate(TreeInfo& info); void deleteDisplayList(); @@ -287,7 +302,7 @@ private: // Owned by RT. Lifecycle is managed by prepareTree(), with the exception // being in ~RenderNode() which may happen on any thread. - Layer* mLayer; + layer_t* mLayer = nullptr; /** * Draw time state - these properties are only set and used during rendering diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h index 1c3148726b63..be25516c587a 100644 --- a/libs/hwui/TreeInfo.h +++ b/libs/hwui/TreeInfo.h @@ -16,11 +16,11 @@ #ifndef TREEINFO_H #define TREEINFO_H -#include <string> +#include "utils/Macros.h" #include <utils/Timers.h> -#include "utils/Macros.h" +#include <string> namespace android { namespace uirenderer { @@ -30,6 +30,7 @@ class CanvasContext; } class DamageAccumulator; +class LayerUpdateQueue; class OpenGLRenderer; class RenderState; @@ -75,9 +76,14 @@ public: // Must not be null during actual usage DamageAccumulator* damageAccumulator = nullptr; + +#if HWUI_NEW_OPS + LayerUpdateQueue* layerUpdateQueue = nullptr; +#else // The renderer that will be drawing the next frame. Use this to push any // layer updates or similar. May be NULL. OpenGLRenderer* renderer = nullptr; +#endif ErrorHandler* errorHandler = nullptr; struct Out { diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp index 43f170f54d41..7b8d0e542a2f 100644 --- a/libs/hwui/microbench/OpReordererBench.cpp +++ b/libs/hwui/microbench/OpReordererBench.cpp @@ -56,7 +56,9 @@ void BM_OpReorderer_defer::Run(int iters) { BENCHMARK_NO_ARG(BM_OpReorderer_deferAndRender); void BM_OpReorderer_deferAndRender::Run(int iters) { - TestUtils::runOnRenderThread([this, iters](RenderState& renderState, Caches& caches) { + TestUtils::runOnRenderThread([this, iters](renderthread::RenderThread& thread) { + RenderState& renderState = thread.renderState(); + Caches& caches = Caches::getInstance(); StartBenchmarkTiming(); for (int i = 0; i < iters; i++) { OpReorderer reorderer(200, 200, *sReorderingDisplayList); diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index fac26dc2bfa2..e9219a630a18 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -20,6 +20,7 @@ #include "Caches.h" #include "DeferredLayerUpdater.h" #include "EglManager.h" +#include "LayerUpdateQueue.h" #include "LayerRenderer.h" #include "OpenGLRenderer.h" #include "Properties.h" @@ -198,7 +199,11 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, mCurrentFrameInfo->markSyncStart(); info.damageAccumulator = &mDamageAccumulator; +#if HWUI_NEW_OPS + info.layerUpdateQueue = &mLayerUpdateQueue; +#else info.renderer = mCanvas; +#endif mAnimationContext->startFrame(info.mode); for (const sp<RenderNode>& node : mRenderNodes) { @@ -333,7 +338,8 @@ void CanvasContext::draw() { mEglManager.damageFrame(frame, dirty); #if HWUI_NEW_OPS - OpReorderer reorderer(dirty, frame.width(), frame.height(), mRenderNodes); + OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), mRenderNodes); + mLayerUpdateQueue.clear(); BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), mOpaque); // TODO: profiler().draw(mCanvas); reorderer.replayBakedOps<BakedOpDispatcher>(renderer); @@ -552,7 +558,11 @@ void CanvasContext::buildLayer(RenderNode* node) { TreeInfo info(TreeInfo::MODE_FULL, *this); info.damageAccumulator = &mDamageAccumulator; +#if HWUI_NEW_OPS + info.layerUpdateQueue = &mLayerUpdateQueue; +#else info.renderer = mCanvas; +#endif info.runAnimations = false; node->prepareTree(info); SkRect ignore; diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 30e6562526d5..d656014fdbcb 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -18,9 +18,10 @@ #define CANVASCONTEXT_H_ #include "DamageAccumulator.h" -#include "IContextFactory.h" #include "FrameInfo.h" #include "FrameInfoVisualizer.h" +#include "IContextFactory.h" +#include "LayerUpdateQueue.h" #include "RenderNode.h" #include "utils/RingBuffer.h" #include "renderthread/RenderTask.h" @@ -83,7 +84,7 @@ public: void draw(); void destroy(); - // IFrameCallback, Chroreographer-driven frame callback entry point + // IFrameCallback, Choreographer-driven frame callback entry point virtual void doFrame() override; void prepareAndDraw(RenderNode* node); @@ -118,7 +119,7 @@ public: void addRenderNode(RenderNode* node, bool placeFront) { int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size()); - mRenderNodes.emplace( mRenderNodes.begin() + pos, node); + mRenderNodes.emplace(mRenderNodes.begin() + pos, node); } void removeRenderNode(RenderNode* node) { @@ -166,6 +167,7 @@ private: OpenGLRenderer* mCanvas = nullptr; bool mHaveNewSurface = false; DamageAccumulator mDamageAccumulator; + LayerUpdateQueue mLayerUpdateQueue; std::unique_ptr<AnimationContext> mAnimationContext; std::vector< sp<RenderNode> > mRenderNodes; diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp index 2eefd37561a6..29d9803ddf9c 100644 --- a/libs/hwui/tests/TreeContentAnimation.cpp +++ b/libs/hwui/tests/TreeContentAnimation.cpp @@ -24,6 +24,7 @@ #include <RenderNode.h> #include <renderthread/RenderProxy.h> #include <renderthread/RenderTask.h> +#include <unit_tests/TestUtils.h> #include "Benchmark.h" #include "TestContext.h" @@ -401,3 +402,27 @@ static Benchmark _SaveLayer(BenchmarkInfo{ "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.", TreeContentAnimation::run<SaveLayerAnimation> }); + + +class HwLayerAnimation : public TreeContentAnimation { +public: + sp<RenderNode> card = TestUtils::createNode<TestCanvas>(0, 0, 200, 200, [] (TestCanvas& canvas) { + canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); + }, true); + void createContent(int width, int height, TestCanvas* canvas) override { + canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background + canvas->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); + } +}; +static Benchmark _HwLayer(BenchmarkInfo{ + "hwlayer", + "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. " + "Tests the hardware layer codepath.", + TreeContentAnimation::run<HwLayerAnimation> +}); diff --git a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp new file mode 100644 index 000000000000..9d625bc62696 --- /dev/null +++ b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp @@ -0,0 +1,82 @@ +/* + * 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 <LayerUpdateQueue.h> +#include <RenderNode.h> + +#include <unit_tests/TestUtils.h> + +namespace android { +namespace uirenderer { + +TEST(LayerUpdateQueue, construct) { + LayerUpdateQueue queue; + EXPECT_TRUE(queue.entries().empty()); +} + +// sync node properties, so properties() reflects correct width and height +static sp<RenderNode> createSyncedNode(uint32_t width, uint32_t height) { + sp<RenderNode> node = TestUtils::createNode(0, 0, width, height); + TestUtils::syncNodePropertiesAndDisplayList(node); + return node; +} + +TEST(LayerUpdateQueue, enqueueSimple) { + sp<RenderNode> a = createSyncedNode(100, 100); + sp<RenderNode> b = createSyncedNode(200, 200); + + LayerUpdateQueue queue; + queue.enqueueLayerWithDamage(a.get(), Rect(25, 25, 75, 75)); + queue.enqueueLayerWithDamage(b.get(), Rect(100, 100, 300, 300)); + + EXPECT_EQ(2u, queue.entries().size()); + + EXPECT_EQ(a.get(), queue.entries()[0].renderNode); + EXPECT_EQ(Rect(25, 25, 75, 75), queue.entries()[0].damage); + EXPECT_EQ(b.get(), queue.entries()[1].renderNode); + EXPECT_EQ(Rect(100, 100, 200, 200), queue.entries()[1].damage); // clipped to bounds +} + +TEST(LayerUpdateQueue, enqueueUnion) { + sp<RenderNode> a = createSyncedNode(100, 100); + + LayerUpdateQueue queue; + queue.enqueueLayerWithDamage(a.get(), Rect(10, 10, 20, 20)); + queue.enqueueLayerWithDamage(a.get(), Rect(30, 30, 40, 40)); + + EXPECT_EQ(1u, queue.entries().size()); + + EXPECT_EQ(a.get(), queue.entries()[0].renderNode); + EXPECT_EQ(Rect(10, 10, 40, 40), queue.entries()[0].damage); +} + +TEST(LayerUpdateQueue, clear) { + sp<RenderNode> a = createSyncedNode(100, 100); + + LayerUpdateQueue queue; + queue.enqueueLayerWithDamage(a.get(), Rect(100, 100)); + + EXPECT_FALSE(queue.entries().empty()); + + queue.clear(); + + EXPECT_TRUE(queue.entries().empty()); +} + +}; +}; diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp index ffb575f2ddd9..cef46f7b2afa 100644 --- a/libs/hwui/unit_tests/OpReordererTests.cpp +++ b/libs/hwui/unit_tests/OpReordererTests.cpp @@ -20,6 +20,7 @@ #include <OpReorderer.h> #include <RecordedOp.h> #include <RecordingCanvas.h> +#include <renderthread/CanvasContext.h> // todo: remove #include <unit_tests/TestUtils.h> #include <unordered_map> @@ -27,6 +28,7 @@ namespace android { namespace uirenderer { +LayerUpdateQueue sEmptyLayerUpdateQueue; /** * Virtual class implemented by each test to redirect static operation / state transitions to @@ -42,7 +44,8 @@ namespace uirenderer { class TestRendererBase { public: virtual ~TestRendererBase() {} - virtual OffscreenBuffer* startLayer(uint32_t width, uint32_t height) { ADD_FAILURE(); return nullptr; } + virtual OffscreenBuffer* createLayer(uint32_t, uint32_t) { ADD_FAILURE(); return nullptr; } + virtual void startLayer(OffscreenBuffer*) { ADD_FAILURE(); } virtual void endLayer() { ADD_FAILURE(); } virtual void startFrame(uint32_t width, uint32_t height) {} virtual void endFrame() {} @@ -192,7 +195,8 @@ TEST(OpReorderer, renderNode) { std::vector< sp<RenderNode> > nodes; nodes.push_back(parent.get()); - OpReorderer reorderer(SkRect::MakeWH(200, 200), 200, 200, nodes); + OpReorderer reorderer(sEmptyLayerUpdateQueue, + SkRect::MakeWH(200, 200), 200, 200, nodes); RenderNodeTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); @@ -216,7 +220,8 @@ TEST(OpReorderer, clipped) { std::vector< sp<RenderNode> > nodes; nodes.push_back(node.get()); - OpReorderer reorderer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver + OpReorderer reorderer(sEmptyLayerUpdateQueue, + SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver 200, 200, nodes); ClippedTestRenderer renderer; @@ -226,7 +231,7 @@ TEST(OpReorderer, clipped) { class SaveLayerSimpleTestRenderer : public TestRendererBase { public: - OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override { + OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override { EXPECT_EQ(0, mIndex++); EXPECT_EQ(180u, width); EXPECT_EQ(180u, height); @@ -268,13 +273,13 @@ TEST(OpReorderer, saveLayerSimple) { /* saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as: - * - startLayer2, rect2 endLayer2 - * - startLayer1, rect1, drawLayer2, endLayer1 + * - createLayer2, rect2 endLayer2 + * - createLayer1, rect1, drawLayer2, endLayer1 * - startFrame, layerOp1, endFrame */ class SaveLayerNestedTestRenderer : public TestRendererBase { public: - OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override { + OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override { const int index = mIndex++; if (index == 0) { EXPECT_EQ(400u, width); @@ -356,5 +361,162 @@ TEST(OpReorderer, saveLayerContentRejection) { reorderer.replayBakedOps<TestDispatcher>(renderer); } +class HwLayerSimpleTestRenderer : public TestRendererBase { +public: + void startLayer(OffscreenBuffer* offscreenBuffer) override { + EXPECT_EQ(0, mIndex++); + EXPECT_EQ(offscreenBuffer, (OffscreenBuffer*) 0x0124); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + + // verify transform is reset + EXPECT_TRUE(state.computedState.transform.isIdentity()); + + // verify damage rect is used as clip + EXPECT_EQ(state.computedState.clipRect, Rect(25, 25, 75, 75)); + } + void endLayer() override { + EXPECT_EQ(2, mIndex++); + } + void startFrame(uint32_t width, uint32_t height) override { + EXPECT_EQ(3, mIndex++); + } + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(4, mIndex++); + } + void endFrame() override { + EXPECT_EQ(5, mIndex++); + } +}; +TEST(OpReorderer, hwLayerSimple) { + sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) { + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 100, 100, paint); + }); + node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer); + node->setPropertyFieldsDirty(RenderNode::GENERIC); + OffscreenBuffer** bufferHandle = node->getLayerHandle(); + *bufferHandle = (OffscreenBuffer*) 0x0124; + + TestUtils::syncNodePropertiesAndDisplayList(node); + + std::vector< sp<RenderNode> > nodes; + nodes.push_back(node.get()); + + // only enqueue partial damage + LayerUpdateQueue layerUpdateQueue; + layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75)); + + OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes); + + HwLayerSimpleTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(6, renderer.getIndex()); + + // clean up layer pointer, so we can safely destruct RenderNode + *bufferHandle = nullptr; +} + + +/* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as: + * - startLayer(child), rect(grey), endLayer + * - createLayer, drawLayer(child), endLayer + * - startLayer(parent), rect(white), drawLayer(saveLayer), endLayer + * - startFrame, drawLayer(parent), endLayerb + */ +class HwLayerComplexTestRenderer : public TestRendererBase { +public: + OffscreenBuffer* createLayer(uint32_t width, uint32_t height) { + EXPECT_EQ(3, mIndex++); // savelayer first + return (OffscreenBuffer*)0xabcd; + } + void startLayer(OffscreenBuffer* offscreenBuffer) override { + int index = mIndex++; + if (index == 0) { + // starting inner layer + EXPECT_EQ((OffscreenBuffer*)0x4567, offscreenBuffer); + } else if (index == 6) { + // starting outer layer + EXPECT_EQ((OffscreenBuffer*)0x0123, offscreenBuffer); + } else { ADD_FAILURE(); } + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + int index = mIndex++; + if (index == 1) { + // inner layer's rect (white) + EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); + } else if (index == 7) { + // outer layer's rect (grey) + EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor()); + } else { ADD_FAILURE(); } + } + void endLayer() override { + int index = mIndex++; + EXPECT_TRUE(index == 2 || index == 5 || index == 9); + } + void startFrame(uint32_t width, uint32_t height) override { + EXPECT_EQ(10, mIndex++); + } + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + int index = mIndex++; + if (index == 4) { + EXPECT_EQ((OffscreenBuffer*)0x4567, *op.layerHandle); + } else if (index == 8) { + EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle); + } else if (index == 11) { + EXPECT_EQ((OffscreenBuffer*)0x0123, *op.layerHandle); + } else { ADD_FAILURE(); } + } + void endFrame() override { + EXPECT_EQ(12, mIndex++); + } +}; +TEST(OpReorderer, hwLayerComplex) { + sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(50, 50, 150, 150, [](RecordingCanvas& canvas) { + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 100, 100, paint); + }); + child->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer); + child->setPropertyFieldsDirty(RenderNode::GENERIC); + *(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567; + + 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.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag); + canvas.drawRenderNode(childPtr); + canvas.restore(); + }); + parent->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer); + parent->setPropertyFieldsDirty(RenderNode::GENERIC); + *(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123; + + TestUtils::syncNodePropertiesAndDisplayList(child); + TestUtils::syncNodePropertiesAndDisplayList(parent); + + std::vector< sp<RenderNode> > nodes; + nodes.push_back(parent.get()); + + LayerUpdateQueue layerUpdateQueue; + layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100)); + layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200)); + + OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes); + + HwLayerComplexTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(13, renderer.getIndex()); + + // clean up layer pointers, so we can safely destruct RenderNodes + *(child->getLayerHandle()) = nullptr; + *(parent->getLayerHandle()) = nullptr; +} + } // namespace uirenderer } // namespace android diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h index 5b09fdac4ff2..770f413352c2 100644 --- a/libs/hwui/unit_tests/TestUtils.h +++ b/libs/hwui/unit_tests/TestUtils.h @@ -89,15 +89,24 @@ public: return std::unique_ptr<DisplayList>(canvas.finishRecording()); } - template<class CanvasType> - static sp<RenderNode> createNode(int left, int top, int right, int bottom, - std::function<void(CanvasType& canvas)> canvasCallback) { + static sp<RenderNode> createNode(int left, int top, int right, int bottom, bool onLayer = false) { sp<RenderNode> node = new RenderNode(); node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + if (onLayer) { + node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer); + node->setPropertyFieldsDirty(RenderNode::GENERIC); + } + return node; + } + + template<class CanvasType> + static sp<RenderNode> createNode(int left, int top, int right, int bottom, + std::function<void(CanvasType& canvas)> canvasCallback, bool onLayer = false) { + sp<RenderNode> node = createNode(left, top, right, bottom, onLayer); - CanvasType canvas( - node->stagingProperties().getWidth(), node->stagingProperties().getHeight()); + auto&& props = node->stagingProperties(); // staging, since not sync'd yet + CanvasType canvas(props.getWidth(), props.getHeight()); canvasCallback(canvas); node->setStagingDisplayList(canvas.finishRecording()); return node; @@ -108,7 +117,7 @@ public: node->syncDisplayList(); } - typedef std::function<void(RenderState& state, Caches& caches)> RtCallback; + typedef std::function<void(renderthread::RenderThread& thread)> RtCallback; class TestTask : public renderthread::RenderTask { public: @@ -120,7 +129,7 @@ public: RenderState& renderState = renderthread::RenderThread::getInstance().renderState(); renderState.onGLContextCreated(); - rtCallback(renderState, Caches::getInstance()); + rtCallback(renderthread::RenderThread::getInstance()); renderState.onGLContextDestroyed(); }; RtCallback rtCallback; |