summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/hwui/DeviceInfo.cpp2
-rw-r--r--libs/hwui/OpReorderer.cpp183
-rw-r--r--libs/hwui/OpReorderer.h7
-rw-r--r--libs/hwui/Rect.h2
-rw-r--r--libs/hwui/RenderNode.cpp70
-rw-r--r--libs/hwui/RenderNode.h3
-rw-r--r--libs/hwui/RenderProperties.h1
-rw-r--r--libs/hwui/unit_tests/OpReordererTests.cpp145
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