diff options
-rw-r--r-- | libs/hwui/DeviceInfo.cpp | 2 | ||||
-rw-r--r-- | libs/hwui/OpReorderer.cpp | 183 | ||||
-rw-r--r-- | libs/hwui/OpReorderer.h | 7 | ||||
-rw-r--r-- | libs/hwui/Rect.h | 2 | ||||
-rw-r--r-- | libs/hwui/RenderNode.cpp | 70 | ||||
-rw-r--r-- | libs/hwui/RenderNode.h | 3 | ||||
-rw-r--r-- | libs/hwui/RenderProperties.h | 1 | ||||
-rw-r--r-- | libs/hwui/unit_tests/OpReordererTests.cpp | 145 |
8 files changed, 281 insertions, 132 deletions
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp index 39b7ecb9a914..4cfbb2a43198 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -18,6 +18,7 @@ #include "Extensions.h" #include <GLES2/gl2.h> +#include <log/log.h> #include <thread> #include <mutex> @@ -29,6 +30,7 @@ static DeviceInfo* sDeviceInfo = nullptr; static std::once_flag sInitializedFlag; const DeviceInfo* DeviceInfo::get() { + LOG_ALWAYS_FATAL_IF(!sDeviceInfo, "DeviceInfo not yet initialized."); return sDeviceInfo; } diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp index b04f16fe4788..96cac7eedaf0 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/OpReorderer.cpp @@ -21,10 +21,10 @@ #include "renderstate/OffscreenBufferPool.h" #include "utils/FatVector.h" #include "utils/PaintUtils.h" +#include "utils/TraceUtils.h" #include <SkCanvas.h> #include <SkPathOps.h> -#include <utils/Trace.h> #include <utils/TypeHelpers.h> namespace android { @@ -331,13 +331,15 @@ OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip, RenderNode* layerNode = layers.entries()[i].renderNode; const Rect& layerDamage = layers.entries()[i].damage; - saveForLayer(layerNode->getWidth(), layerNode->getHeight(), - layerDamage, nullptr, layerNode); - mCanvasState.writableSnapshot()->setClip( - layerDamage.left, layerDamage.top, layerDamage.right, layerDamage.bottom); + // map current light center into RenderNode's coordinate space + Vector3 lightCenter = mCanvasState.currentSnapshot()->getRelativeLightCenter(); + layerNode->getLayer()->inverseTransformInWindow.mapPoint3d(lightCenter); + + saveForLayer(layerNode->getWidth(), layerNode->getHeight(), 0, 0, + layerDamage, lightCenter, nullptr, layerNode); if (layerNode->getDisplayList()) { - deferImpl(*(layerNode->getDisplayList())); + deferDisplayList(*(layerNode->getDisplayList())); } restoreForLayer(); } @@ -363,7 +365,7 @@ OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayLis mCanvasState.initializeSaveStack(viewportWidth, viewportHeight, 0, 0, viewportWidth, viewportHeight, lightCenter); - deferImpl(displayList); + deferDisplayList(displayList); } void OpReorderer::onViewportInitialized() {} @@ -371,18 +373,99 @@ void OpReorderer::onViewportInitialized() {} void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} void OpReorderer::deferNodePropsAndOps(RenderNode& node) { - if (node.applyViewProperties(mCanvasState, mAllocator)) { - // not rejected so render + const RenderProperties& properties = node.properties(); + const Outline& outline = properties.getOutline(); + if (properties.getAlpha() <= 0 + || (outline.getShouldClip() && outline.isEmpty()) + || properties.getScaleX() == 0 + || properties.getScaleY() == 0) { + return; // rejected + } + + if (properties.getLeft() != 0 || properties.getTop() != 0) { + mCanvasState.translate(properties.getLeft(), properties.getTop()); + } + if (properties.getStaticMatrix()) { + mCanvasState.concatMatrix(*properties.getStaticMatrix()); + } else if (properties.getAnimationMatrix()) { + mCanvasState.concatMatrix(*properties.getAnimationMatrix()); + } + if (properties.hasTransformMatrix()) { + if (properties.isTransformTranslateOnly()) { + mCanvasState.translate(properties.getTranslationX(), properties.getTranslationY()); + } else { + mCanvasState.concatMatrix(*properties.getTransformMatrix()); + } + } + + const int width = properties.getWidth(); + const int height = properties.getHeight(); + + Rect saveLayerBounds; // will be set to non-empty if saveLayer needed + const bool isLayer = properties.effectiveLayerType() != LayerType::None; + int clipFlags = properties.getClippingFlags(); + if (properties.getAlpha() < 1) { + if (isLayer) { + clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer + } + if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) { + // simply scale rendering content's alpha + mCanvasState.scaleAlpha(properties.getAlpha()); + } else { + // schedule saveLayer by initializing saveLayerBounds + saveLayerBounds.set(0, 0, width, height); + if (clipFlags) { + properties.getClippingRectForFlags(clipFlags, &saveLayerBounds); + clipFlags = 0; // all clipping done by savelayer + } + } + + if (CC_UNLIKELY(ATRACE_ENABLED() && properties.promotedToLayer())) { + // pretend alpha always causes savelayer to warn about + // performance problem affecting old versions + ATRACE_FORMAT("%s alpha caused saveLayer %dx%d", node.getName(), width, height); + } + } + if (clipFlags) { + Rect clipRect; + properties.getClippingRectForFlags(clipFlags, &clipRect); + mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, + SkRegion::kIntersect_Op); + } + + if (properties.getRevealClip().willClip()) { + Rect bounds; + properties.getRevealClip().getBounds(&bounds); + mCanvasState.setClippingRoundRect(mAllocator, + bounds, properties.getRevealClip().getRadius()); + } else if (properties.getOutline().willClip()) { + mCanvasState.setClippingOutline(mAllocator, &(properties.getOutline())); + } + + if (!mCanvasState.quickRejectConservative(0, 0, width, height)) { + // not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer) 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) + // Node's layer already deferred, schedule it to render into parent layer currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap); } + } else if (CC_UNLIKELY(!saveLayerBounds.isEmpty())) { + // draw DisplayList contents within temporary, since persisted layer could not be used. + // (temp layers are clipped to viewport, since they don't persist offscreen content) + SkPaint saveLayerPaint; + saveLayerPaint.setAlpha(properties.getAlpha()); + onBeginLayerOp(*new (mAllocator) BeginLayerOp( + saveLayerBounds, + Matrix4::identity(), + saveLayerBounds, + &saveLayerPaint)); + deferDisplayList(*(node.getDisplayList())); + onEndLayerOp(*new (mAllocator) EndLayerOp()); } else { - deferImpl(*(node.getDisplayList())); + deferDisplayList(*(node.getDisplayList())); } } } @@ -535,7 +618,7 @@ void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) { */ #define OP_RECEIVER(Type) \ [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); }, -void OpReorderer::deferImpl(const DisplayList& displayList) { +void OpReorderer::deferDisplayList(const DisplayList& displayList) { static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = { MAP_OPS(OP_RECEIVER) }; @@ -600,34 +683,21 @@ void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) { currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices); } -void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, const Rect& repaintRect, +void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, + float contentTranslateX, float contentTranslateY, + const Rect& repaintRect, + const Vector3& lightCenter, const BeginLayerOp* beginLayerOp, RenderNode* renderNode) { - - auto previous = mCanvasState.currentSnapshot(); mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); - mCanvasState.writableSnapshot()->transform->loadIdentity(); mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight); mCanvasState.writableSnapshot()->roundRectClipState = nullptr; - - Vector3 lightCenter = previous->getRelativeLightCenter(); - if (renderNode) { - Matrix4& inverse = renderNode->getLayer()->inverseTransformInWindow; - inverse.mapPoint3d(lightCenter); - } else { - // Combine all transforms used to present saveLayer content: - // parent content transform * canvas transform * bounds offset - Matrix4 contentTransform(*previous->transform); - contentTransform.multiply(beginLayerOp->localMatrix); - contentTransform.translate(beginLayerOp->unmappedBounds.left, beginLayerOp->unmappedBounds.top); - - // inverse the total transform, to map light center into layer-relative space - Matrix4 inverse; - inverse.loadInverse(contentTransform); - inverse.mapPoint3d(lightCenter); - } mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter); + mCanvasState.writableSnapshot()->transform->loadTranslate( + contentTranslateX, contentTranslateY, 0); + mCanvasState.writableSnapshot()->setClip( + repaintRect.left, repaintRect.top, repaintRect.right, repaintRect.bottom); - // create a new layer, and push its index on the stack + // create a new layer repaint, and push its index on the stack mLayerStack.push_back(mLayerReorderers.size()); mLayerReorderers.emplace_back(layerWidth, layerHeight, repaintRect, beginLayerOp, renderNode); } @@ -640,9 +710,48 @@ void OpReorderer::restoreForLayer() { // 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, Rect(layerWidth, layerHeight), &op, nullptr); + uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth(); + uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight(); + + auto previous = mCanvasState.currentSnapshot(); + Vector3 lightCenter = previous->getRelativeLightCenter(); + + // Combine all transforms used to present saveLayer content: + // parent content transform * canvas transform * bounds offset + Matrix4 contentTransform(*previous->transform); + contentTransform.multiply(op.localMatrix); + contentTransform.translate(op.unmappedBounds.left, op.unmappedBounds.top); + + Matrix4 inverseContentTransform; + inverseContentTransform.loadInverse(contentTransform); + + // map the light center into layer-relative space + inverseContentTransform.mapPoint3d(lightCenter); + + // Clip bounds of temporary layer to parent's clip rect, so: + Rect saveLayerBounds(layerWidth, layerHeight); + // 1) transform Rect(width, height) into parent's space + // note: left/top offsets put in contentTransform above + contentTransform.mapRect(saveLayerBounds); + // 2) intersect with parent's clip + saveLayerBounds.doIntersect(previous->getRenderTargetClip()); + // 3) and transform back + inverseContentTransform.mapRect(saveLayerBounds); + saveLayerBounds.doIntersect(Rect(layerWidth, layerHeight)); + saveLayerBounds.roundOut(); + + // if bounds are reduced, will clip the layer's area by reducing required bounds... + layerWidth = saveLayerBounds.getWidth(); + layerHeight = saveLayerBounds.getHeight(); + // ...and shifting drawing content to account for left/top side clipping + float contentTranslateX = -saveLayerBounds.left; + float contentTranslateY = -saveLayerBounds.top; + + saveForLayer(layerWidth, layerHeight, + contentTranslateX, contentTranslateY, + Rect(layerWidth, layerHeight), + lightCenter, + &op, nullptr); } void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) { diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h index 09d5cbcf7559..976f41323efa 100644 --- a/libs/hwui/OpReorderer.h +++ b/libs/hwui/OpReorderer.h @@ -190,7 +190,10 @@ private: Positive }; void saveForLayer(uint32_t layerWidth, uint32_t layerHeight, - const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode); + float contentTranslateX, float contentTranslateY, + const Rect& repaintRect, + const Vector3& lightCenter, + const BeginLayerOp* beginLayerOp, RenderNode* renderNode); void restoreForLayer(); LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; } @@ -204,7 +207,7 @@ private: void deferShadow(const RenderNodeOp& casterOp); - void deferImpl(const DisplayList& displayList); + void deferDisplayList(const DisplayList& displayList); template <typename V> void defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes); diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h index 50199db75640..0736a109e572 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -276,7 +276,7 @@ public: } void dump(const char* label = nullptr) const { - ALOGD("%s[l=%f t=%f r=%f b=%f]", label ? label : "Rect", left, top, right, bottom); + ALOGD("%s[l=%.2f t=%.2f r=%.2f b=%.2f]", label ? label : "Rect", left, top, right, bottom); } }; // class Rect diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 2713f46ab33b..716d5360c25c 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -524,76 +524,6 @@ void RenderNode::decParentRefCount() { } } -bool RenderNode::applyViewProperties(CanvasState& canvasState, LinearAllocator& allocator) 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()); - } - } - - const bool isLayer = properties().effectiveLayerType() != LayerType::None; - int clipFlags = properties().getClippingFlags(); - if (properties().getAlpha() < 1) { - if (isLayer) { - clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer - } - if (CC_LIKELY(isLayer || !properties().getHasOverlappingRendering())) { - // simply scale rendering content's alpha - canvasState.scaleAlpha(properties().getAlpha()); - } else { - // savelayer needed to create an offscreen buffer - Rect layerBounds(0, 0, getWidth(), getHeight()); - if (clipFlags) { - properties().getClippingRectForFlags(clipFlags, &layerBounds); - clipFlags = 0; // all clipping done by savelayer - } - LOG_ALWAYS_FATAL("TODO: savelayer"); - } - - if (CC_UNLIKELY(ATRACE_ENABLED() && properties().promotedToLayer())) { - // pretend alpha always causes savelayer to warn about - // performance problem affecting old versions - ATRACE_FORMAT("%s alpha caused saveLayer %dx%d", getName(), getWidth(), getHeight()); - } - } - if (clipFlags) { - Rect clipRect; - properties().getClippingRectForFlags(clipFlags, &clipRect); - canvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, - SkRegion::kIntersect_Op); - } - - // TODO: support nesting round rect clips - if (mProperties.getRevealClip().willClip()) { - Rect bounds; - mProperties.getRevealClip().getBounds(&bounds); - canvasState.setClippingRoundRect(allocator, - bounds, mProperties.getRevealClip().getRadius()); - } else if (mProperties.getOutline().willClip()) { - canvasState.setClippingOutline(allocator, &(mProperties.getOutline())); - } - 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 diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index bae5ebe3f754..83d1b5888b64 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -187,9 +187,6 @@ public: AnimatorManager& animators() { return mAnimatorManager; } - // Returns false if the properties dictate the subtree contained in this RenderNode won't render - bool applyViewProperties(CanvasState& canvasState, LinearAllocator& allocator) const; - void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false) const; bool nothingToDraw() const { diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index 0bd5b65f86aa..395279806adb 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -610,7 +610,6 @@ public: bool fitsOnLayer() const { const DeviceInfo* deviceInfo = DeviceInfo::get(); - LOG_ALWAYS_FATAL_IF(!deviceInfo, "DeviceInfo uninitialized"); return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize() && mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize(); } diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp index a8c9bba32e64..07a1855490d4 100644 --- a/libs/hwui/unit_tests/OpReordererTests.cpp +++ b/libs/hwui/unit_tests/OpReordererTests.cpp @@ -17,10 +17,10 @@ #include <gtest/gtest.h> #include <BakedOpState.h> +#include <LayerUpdateQueue.h> #include <OpReorderer.h> #include <RecordedOp.h> #include <RecordingCanvas.h> -#include <renderthread/CanvasContext.h> // todo: remove #include <unit_tests/TestUtils.h> #include <unordered_map> @@ -28,8 +28,8 @@ namespace android { namespace uirenderer { -LayerUpdateQueue sEmptyLayerUpdateQueue; -Vector3 sLightCenter = {100, 100, 100}; +const LayerUpdateQueue sEmptyLayerUpdateQueue; +const Vector3 sLightCenter = {100, 100, 100}; static std::vector<sp<RenderNode>> createSyncedNodeList(sp<RenderNode>& node) { TestUtils::syncHierarchyPropertiesAndDisplayList(node); @@ -79,7 +79,7 @@ protected: /** * Dispatches all static methods to similar formed methods on renderer, which fail by default but - * are overriden by subclasses per test. + * are overridden by subclasses per test. */ class TestDispatcher { public: @@ -117,7 +117,6 @@ TEST(OpReorderer, simple) { canvas.drawBitmap(bitmap, 10, 10, nullptr); }); OpReorderer reorderer(100, 200, *dl, sLightCenter); - SimpleTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end @@ -163,7 +162,6 @@ TEST(OpReorderer, simpleBatching) { }); OpReorderer reorderer(200, 200, *dl, sLightCenter); - SimpleBatchingTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, renderer.getIndex()); // 2 x loops ops, because no merging (TODO: force no merging) @@ -208,7 +206,6 @@ TEST(OpReorderer, renderNode) { OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(parent), sLightCenter); - RenderNodeTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); } @@ -232,7 +229,6 @@ TEST(OpReorderer, clipped) { OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver 200, 200, createSyncedNodeList(node), sLightCenter); - ClippedTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); } @@ -274,7 +270,6 @@ TEST(OpReorderer, saveLayerSimple) { }); OpReorderer reorderer(200, 200, *dl, sLightCenter); - SaveLayerSimpleTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(4, renderer.getIndex()); @@ -345,7 +340,6 @@ TEST(OpReorderer, saveLayerNested) { }); OpReorderer reorderer(800, 800, *dl, sLightCenter); - SaveLayerNestedTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(10, renderer.getIndex()); @@ -520,7 +514,6 @@ RENDERTHREAD_TEST(OpReorderer, hwLayerComplex) { OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, syncedList, sLightCenter); - HwLayerComplexTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(13, renderer.getIndex()); @@ -570,7 +563,6 @@ TEST(OpReorderer, zReorder) { }); OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, createSyncedNodeList(parent), sLightCenter); - ZReorderTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(10, renderer.getIndex()); @@ -616,7 +608,6 @@ TEST(OpReorderer, shadow) { OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(parent), sLightCenter); - ShadowTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(2, renderer.getIndex()); @@ -658,7 +649,6 @@ TEST(OpReorderer, shadowSaveLayer) { OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(parent), (Vector3) { 100, 100, 100 }); - ShadowSaveLayerTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(5, renderer.getIndex()); @@ -708,7 +698,6 @@ RENDERTHREAD_TEST(OpReorderer, shadowHwLayer) { layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100)); OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, syncedList, (Vector3) { 100, 100, 100 }); - ShadowHwLayerTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(5, renderer.getIndex()); @@ -738,13 +727,11 @@ TEST(OpReorderer, shadowLayering) { OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, createSyncedNodeList(parent), sLightCenter); - ShadowLayeringTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(4, renderer.getIndex()); } - static void testProperty(TestUtils::PropSetupCallback propSetupCallback, std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) { class PropertyTestRenderer : public TestRendererBase { @@ -766,7 +753,6 @@ static void testProperty(TestUtils::PropSetupCallback propSetupCallback, OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200, createSyncedNodeList(node), sLightCenter); - PropertyTestRenderer renderer(opValidateCallback); reorderer.replayBakedOps<TestDispatcher>(renderer); EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op"; @@ -854,5 +840,128 @@ TEST(OpReorderer, renderPropTransform) { }); } +struct SaveLayerAlphaData { + uint32_t layerWidth = 0; + uint32_t layerHeight = 0; + Rect rectClippedBounds; + Matrix4 rectMatrix; +}; +/** + * Constructs a view to hit the temporary layer alpha property implementation: + * a) 0 < alpha < 1 + * b) too big for layer (larger than maxTextureSize) + * c) overlapping rendering content + * returning observed data about layer size and content clip/transform. + * + * Used to validate clipping behavior of temporary layer, where requested layer size is reduced + * (for efficiency, and to fit in layer size constraints) based on parent clip. + */ +void testSaveLayerAlphaClip(SaveLayerAlphaData* outObservedData, + TestUtils::PropSetupCallback propSetupCallback) { + class SaveLayerAlphaClipTestRenderer : public TestRendererBase { + public: + SaveLayerAlphaClipTestRenderer(SaveLayerAlphaData* outData) + : mOutData(outData) {} + + OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { + EXPECT_EQ(0, mIndex++); + mOutData->layerWidth = width; + mOutData->layerHeight = height; + return nullptr; + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + + mOutData->rectClippedBounds = state.computedState.clippedBounds; + mOutData->rectMatrix = state.computedState.transform; + } + void endLayer() override { + EXPECT_EQ(2, mIndex++); + } + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(3, mIndex++); + } + private: + SaveLayerAlphaData* mOutData; + }; + + ASSERT_GT(10000, DeviceInfo::get()->maxTextureSize()) + << "Node must be bigger than max texture size to exercise saveLayer codepath"; + auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 10000, 10000, [](RecordingCanvas& canvas) { + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 10000, 10000, paint); + }, [&propSetupCallback](RenderProperties& properties) { + properties.setHasOverlappingRendering(true); + properties.setAlpha(0.5f); // force saveLayer, since too big for HW layer + + // apply other properties + int flags = propSetupCallback(properties); + return RenderNode::GENERIC | RenderNode::ALPHA | flags; + }); + auto nodes = createSyncedNodeList(node); // sync before querying height + + OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes, sLightCenter); + SaveLayerAlphaClipTestRenderer renderer(outObservedData); + reorderer.replayBakedOps<TestDispatcher>(renderer); + + // assert, since output won't be valid if we haven't seen a save layer triggered + ASSERT_EQ(4, renderer.getIndex()) << "Test must trigger saveLayer alpha behavior."; +} + +TEST(OpReorderer, renderPropSaveLayerAlphaClipBig) { + SaveLayerAlphaData observedData; + testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { + properties.setTranslationX(10); // offset rendering content + properties.setTranslationY(-2000); // offset rendering content + return RenderNode::TRANSLATION_X | RenderNode::TRANSLATION_Y; + }); + EXPECT_EQ(190u, observedData.layerWidth); + EXPECT_EQ(200u, observedData.layerHeight); + EXPECT_EQ(Rect(0, 0, 190, 200), observedData.rectClippedBounds) + << "expect content to be clipped to screen area"; + Matrix4 expected; + expected.loadTranslate(0, -2000, 0); + EXPECT_MATRIX_APPROX_EQ(expected, observedData.rectMatrix) + << "expect content to be translated as part of being clipped"; +} + +TEST(OpReorderer, renderPropSaveLayerAlphaRotate) { + SaveLayerAlphaData observedData; + testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { + // Translate and rotate the view so that the only visible part is the top left corner of + // the view. It will form an isoceles right triangle with a long side length of 200 at the + // bottom of the viewport. + properties.setTranslationX(100); + properties.setTranslationY(100); + properties.setPivotX(0); + properties.setPivotY(0); + properties.setRotation(45); + return RenderNode::GENERIC + | RenderNode::TRANSLATION_X | RenderNode::TRANSLATION_Y + | RenderNode::ROTATION; + }); + // ceil(sqrt(2) / 2 * 200) = 142 + EXPECT_EQ(142u, observedData.layerWidth); + EXPECT_EQ(142u, observedData.layerHeight); + EXPECT_EQ(Rect(0, 0, 142, 142), observedData.rectClippedBounds); + EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix); +} + +TEST(OpReorderer, renderPropSaveLayerAlphaScale) { + SaveLayerAlphaData observedData; + testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { + properties.setPivotX(0); + properties.setPivotY(0); + properties.setScaleX(2); + properties.setScaleY(0.5f); + return RenderNode::GENERIC | RenderNode::SCALE_X | RenderNode::SCALE_Y; + }); + EXPECT_EQ(100u, observedData.layerWidth); + EXPECT_EQ(400u, observedData.layerHeight); + EXPECT_EQ(Rect(0, 0, 100, 400), observedData.rectClippedBounds); + EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix); +} + } // namespace uirenderer } // namespace android |