summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chris Craik <ccraik@google.com> 2015-11-16 10:27:59 -0800
committer Chris Craik <ccraik@google.com> 2015-11-17 14:50:49 -0800
commit8ecf41c61a5185207a21d64681e8ebc2502b7b2a (patch)
tree94592cbff1b686302433a68a74b01bdb3b44d487
parentb20dbf6c6a19d6f6f69791eba7492c4480e8f113 (diff)
Add temporary layer alpha fallback to OpReorderer
Also adds logic to clip temporary layers to viewport both for efficiency and to allow large ones (such as the fallback case) to fit in max texture size. Change-Id: Iee51495220f5ca1dc7e6f5fd3615db2e896efd74
-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