Merge changes I4f72448f,I74b7233c into nyc-dev
* changes: Fix ripple positioning within scrolled node Clip projected ripples to outlines
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <SkPaintDefaults.h>
|
#include <SkPaintDefaults.h>
|
||||||
|
#include <SkPathOps.h>
|
||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
namespace uirenderer {
|
namespace uirenderer {
|
||||||
@@ -527,6 +528,12 @@ void BakedOpDispatcher::onOvalOp(BakedOpRenderer& renderer, const OvalOp& op, co
|
|||||||
SkPath path;
|
SkPath path;
|
||||||
SkRect rect = getBoundsOfFill(op);
|
SkRect rect = getBoundsOfFill(op);
|
||||||
path.addOval(rect);
|
path.addOval(rect);
|
||||||
|
|
||||||
|
if (state.computedState.localProjectionPathMask != nullptr) {
|
||||||
|
// Mask the ripple path by the local space projection mask in local space.
|
||||||
|
// Note that this can create CCW paths.
|
||||||
|
Op(path, *state.computedState.localProjectionPathMask, kIntersect_SkPathOp, &path);
|
||||||
|
}
|
||||||
renderConvexPath(renderer, state, path, *(op.paint));
|
renderConvexPath(renderer, state, path, *(op.paint));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,9 +63,22 @@ ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& s
|
|||||||
clipState = nullptr;
|
clipState = nullptr;
|
||||||
clippedBounds.setEmpty();
|
clippedBounds.setEmpty();
|
||||||
} else {
|
} else {
|
||||||
// Not rejected! compute true clippedBounds and clipSideFlags
|
// Not rejected! compute true clippedBounds, clipSideFlags, and path mask
|
||||||
clipSideFlags = computeClipSideFlags(clipRect, clippedBounds);
|
clipSideFlags = computeClipSideFlags(clipRect, clippedBounds);
|
||||||
clippedBounds.doIntersect(clipRect);
|
clippedBounds.doIntersect(clipRect);
|
||||||
|
|
||||||
|
if (CC_UNLIKELY(snapshot.projectionPathMask)) {
|
||||||
|
// map projection path mask from render target space into op space,
|
||||||
|
// so intersection with op geometry is possible
|
||||||
|
Matrix4 inverseTransform;
|
||||||
|
inverseTransform.loadInverse(transform);
|
||||||
|
SkMatrix skInverseTransform;
|
||||||
|
inverseTransform.copyTo(skInverseTransform);
|
||||||
|
|
||||||
|
auto localMask = allocator.create<SkPath>();
|
||||||
|
snapshot.projectionPathMask->transform(skInverseTransform, localMask);
|
||||||
|
localProjectionPathMask = localMask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,13 +86,15 @@ ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& s
|
|||||||
: transform(*snapshot.transform)
|
: transform(*snapshot.transform)
|
||||||
, clipState(snapshot.mutateClipArea().serializeClip(allocator))
|
, clipState(snapshot.mutateClipArea().serializeClip(allocator))
|
||||||
, clippedBounds(clipState->rect)
|
, clippedBounds(clipState->rect)
|
||||||
, clipSideFlags(OpClipSideFlags::Full) {}
|
, clipSideFlags(OpClipSideFlags::Full)
|
||||||
|
, localProjectionPathMask(nullptr) {}
|
||||||
|
|
||||||
ResolvedRenderState::ResolvedRenderState(const ClipRect* clipRect, const Rect& dstRect)
|
ResolvedRenderState::ResolvedRenderState(const ClipRect* clipRect, const Rect& dstRect)
|
||||||
: transform(Matrix4::identity())
|
: transform(Matrix4::identity())
|
||||||
, clipState(clipRect)
|
, clipState(clipRect)
|
||||||
, clippedBounds(dstRect)
|
, clippedBounds(dstRect)
|
||||||
, clipSideFlags(computeClipSideFlags(clipRect->rect, dstRect)) {
|
, clipSideFlags(computeClipSideFlags(clipRect->rect, dstRect))
|
||||||
|
, localProjectionPathMask(nullptr) {
|
||||||
clippedBounds.doIntersect(clipRect->rect);
|
clippedBounds.doIntersect(clipRect->rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ public:
|
|||||||
const ClipBase* clipState = nullptr;
|
const ClipBase* clipState = nullptr;
|
||||||
Rect clippedBounds;
|
Rect clippedBounds;
|
||||||
int clipSideFlags = 0;
|
int clipSideFlags = 0;
|
||||||
|
const SkPath* localProjectionPathMask = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,7 +155,6 @@ public:
|
|||||||
// simple state (straight pointer/value storage):
|
// simple state (straight pointer/value storage):
|
||||||
const float alpha;
|
const float alpha;
|
||||||
const RoundRectClipState* roundRectClipState;
|
const RoundRectClipState* roundRectClipState;
|
||||||
const ProjectionPathMask* projectionPathMask;
|
|
||||||
const RecordedOp* op;
|
const RecordedOp* op;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -165,21 +165,18 @@ private:
|
|||||||
: computedState(allocator, snapshot, recordedOp, expandForStroke)
|
: computedState(allocator, snapshot, recordedOp, expandForStroke)
|
||||||
, alpha(snapshot.alpha)
|
, alpha(snapshot.alpha)
|
||||||
, roundRectClipState(snapshot.roundRectClipState)
|
, roundRectClipState(snapshot.roundRectClipState)
|
||||||
, projectionPathMask(snapshot.projectionPathMask)
|
|
||||||
, op(&recordedOp) {}
|
, op(&recordedOp) {}
|
||||||
|
|
||||||
BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const ShadowOp* shadowOpPtr)
|
BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const ShadowOp* shadowOpPtr)
|
||||||
: computedState(allocator, snapshot)
|
: computedState(allocator, snapshot)
|
||||||
, alpha(snapshot.alpha)
|
, alpha(snapshot.alpha)
|
||||||
, roundRectClipState(snapshot.roundRectClipState)
|
, roundRectClipState(snapshot.roundRectClipState)
|
||||||
, projectionPathMask(snapshot.projectionPathMask)
|
|
||||||
, op(shadowOpPtr) {}
|
, op(shadowOpPtr) {}
|
||||||
|
|
||||||
BakedOpState(const ClipRect* clipRect, const Rect& dstRect, const RecordedOp& recordedOp)
|
BakedOpState(const ClipRect* clipRect, const Rect& dstRect, const RecordedOp& recordedOp)
|
||||||
: computedState(clipRect, dstRect)
|
: computedState(clipRect, dstRect)
|
||||||
, alpha(1.0f)
|
, alpha(1.0f)
|
||||||
, roundRectClipState(nullptr)
|
, roundRectClipState(nullptr)
|
||||||
, projectionPathMask(nullptr)
|
|
||||||
, op(&recordedOp) {}
|
, op(&recordedOp) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -389,34 +389,38 @@ void FrameBuilder::deferShadow(const RenderNodeOp& casterNodeOp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FrameBuilder::deferProjectedChildren(const RenderNode& renderNode) {
|
void FrameBuilder::deferProjectedChildren(const RenderNode& renderNode) {
|
||||||
const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
|
|
||||||
int count = mCanvasState.save(SaveFlags::MatrixClip);
|
int count = mCanvasState.save(SaveFlags::MatrixClip);
|
||||||
|
const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
|
||||||
|
|
||||||
// can't be null, since DL=null node rejection happens before deferNodePropsAndOps
|
SkPath transformedMaskPath; // on stack, since BakedOpState makes a deep copy
|
||||||
const DisplayList& displayList = *(renderNode.getDisplayList());
|
if (projectionReceiverOutline) {
|
||||||
|
// transform the mask for this projector into render target space
|
||||||
const RecordedOp* op = (displayList.getOps()[displayList.projectionReceiveIndex]);
|
// TODO: consider combining both transforms by stashing transform instead of applying
|
||||||
const RenderNodeOp* backgroundOp = static_cast<const RenderNodeOp*>(op);
|
SkMatrix skCurrentTransform;
|
||||||
const RenderProperties& backgroundProps = backgroundOp->renderNode->properties();
|
mCanvasState.currentTransform()->copyTo(skCurrentTransform);
|
||||||
|
projectionReceiverOutline->transform(
|
||||||
// Transform renderer to match background we're projecting onto
|
skCurrentTransform,
|
||||||
// (by offsetting canvas by translationX/Y of background rendernode, since only those are set)
|
&transformedMaskPath);
|
||||||
mCanvasState.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
|
mCanvasState.setProjectionPathMask(mAllocator, &transformedMaskPath);
|
||||||
|
|
||||||
// If the projection receiver has an outline, we mask projected content to it
|
|
||||||
// (which we know, apriori, are all tessellated paths)
|
|
||||||
mCanvasState.setProjectionPathMask(mAllocator, projectionReceiverOutline);
|
|
||||||
|
|
||||||
// draw projected nodes
|
|
||||||
for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) {
|
|
||||||
RenderNodeOp* childOp = renderNode.mProjectedNodes[i];
|
|
||||||
|
|
||||||
int restoreTo = mCanvasState.save(SaveFlags::Matrix);
|
|
||||||
mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
|
|
||||||
deferRenderNodeOpImpl(*childOp);
|
|
||||||
mCanvasState.restoreToCount(restoreTo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) {
|
||||||
|
RenderNodeOp* childOp = renderNode.mProjectedNodes[i];
|
||||||
|
RenderNode& childNode = *childOp->renderNode;
|
||||||
|
|
||||||
|
// Draw child if it has content, but ignore state in childOp - matrix already applied to
|
||||||
|
// transformFromCompositingAncestor, and record-time clip is ignored when projecting
|
||||||
|
if (!childNode.nothingToDraw()) {
|
||||||
|
int restoreTo = mCanvasState.save(SaveFlags::MatrixClip);
|
||||||
|
|
||||||
|
// Apply transform between ancestor and projected descendant
|
||||||
|
mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
|
||||||
|
|
||||||
|
deferNodePropsAndOps(childNode);
|
||||||
|
|
||||||
|
mCanvasState.restoreToCount(restoreTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
mCanvasState.restoreToCount(count);
|
mCanvasState.restoreToCount(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,10 @@ public:
|
|||||||
// Identical round rect clip state means both ops will clip in the same way, or not at all.
|
// Identical round rect clip state means both ops will clip in the same way, or not at all.
|
||||||
// As the state objects are const, we can compare their pointers to determine mergeability
|
// As the state objects are const, we can compare their pointers to determine mergeability
|
||||||
if (lhs->roundRectClipState != rhs->roundRectClipState) return false;
|
if (lhs->roundRectClipState != rhs->roundRectClipState) return false;
|
||||||
if (lhs->projectionPathMask != rhs->projectionPathMask) return false;
|
|
||||||
|
// Local masks prevent merge, since they're potentially in different coordinate spaces
|
||||||
|
if (lhs->computedState.localProjectionPathMask
|
||||||
|
|| rhs->computedState.localProjectionPathMask) return false;
|
||||||
|
|
||||||
/* Clipping compatibility check
|
/* Clipping compatibility check
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1148,7 +1148,9 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef
|
|||||||
|
|
||||||
// always store/restore, since these are just pointers
|
// always store/restore, since these are just pointers
|
||||||
state.mRoundRectClipState = currentSnapshot()->roundRectClipState;
|
state.mRoundRectClipState = currentSnapshot()->roundRectClipState;
|
||||||
|
#if !HWUI_NEW_OPS
|
||||||
state.mProjectionPathMask = currentSnapshot()->projectionPathMask;
|
state.mProjectionPathMask = currentSnapshot()->projectionPathMask;
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1156,7 +1158,9 @@ void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state, bool
|
|||||||
setGlobalMatrix(state.mMatrix);
|
setGlobalMatrix(state.mMatrix);
|
||||||
writableSnapshot()->alpha = state.mAlpha;
|
writableSnapshot()->alpha = state.mAlpha;
|
||||||
writableSnapshot()->roundRectClipState = state.mRoundRectClipState;
|
writableSnapshot()->roundRectClipState = state.mRoundRectClipState;
|
||||||
|
#if !HWUI_NEW_OPS
|
||||||
writableSnapshot()->projectionPathMask = state.mProjectionPathMask;
|
writableSnapshot()->projectionPathMask = state.mProjectionPathMask;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (state.mClipValid && !skipClipRestore) {
|
if (state.mClipValid && !skipClipRestore) {
|
||||||
writableSnapshot()->setClip(state.mClip.left, state.mClip.top,
|
writableSnapshot()->setClip(state.mClip.left, state.mClip.top,
|
||||||
@@ -1833,6 +1837,7 @@ void OpenGLRenderer::drawCircle(float x, float y, float radius, const SkPaint* p
|
|||||||
path.addCircle(x, y, radius);
|
path.addCircle(x, y, radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !HWUI_NEW_OPS
|
||||||
if (CC_UNLIKELY(currentSnapshot()->projectionPathMask != nullptr)) {
|
if (CC_UNLIKELY(currentSnapshot()->projectionPathMask != nullptr)) {
|
||||||
// mask ripples with projection mask
|
// mask ripples with projection mask
|
||||||
SkPath maskPath = *(currentSnapshot()->projectionPathMask->projectionMask);
|
SkPath maskPath = *(currentSnapshot()->projectionPathMask->projectionMask);
|
||||||
@@ -1852,6 +1857,7 @@ void OpenGLRenderer::drawCircle(float x, float y, float radius, const SkPaint* p
|
|||||||
// in local space. Note that this can create CCW paths.
|
// in local space. Note that this can create CCW paths.
|
||||||
Op(path, maskPath, kIntersect_SkPathOp, &path);
|
Op(path, maskPath, kIntersect_SkPathOp, &path);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
drawConvexPath(path, p);
|
drawConvexPath(path, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -146,6 +146,9 @@ void Snapshot::resetTransform(float x, float y, float z) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const {
|
void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const {
|
||||||
|
#if HWUI_NEW_OPS
|
||||||
|
LOG_ALWAYS_FATAL("not supported - not needed by new ops");
|
||||||
|
#else
|
||||||
// build (reverse ordered) list of the stack of snapshots, terminated with a NULL
|
// build (reverse ordered) list of the stack of snapshots, terminated with a NULL
|
||||||
Vector<const Snapshot*> snapshotList;
|
Vector<const Snapshot*> snapshotList;
|
||||||
snapshotList.push(nullptr);
|
snapshotList.push(nullptr);
|
||||||
@@ -171,6 +174,7 @@ void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const {
|
|||||||
outTransform->multiply(*(current->transform));
|
outTransform->multiply(*(current->transform));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -223,15 +227,19 @@ void Snapshot::setClippingRoundRect(LinearAllocator& allocator, const Rect& boun
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Snapshot::setProjectionPathMask(LinearAllocator& allocator, const SkPath* path) {
|
void Snapshot::setProjectionPathMask(LinearAllocator& allocator, const SkPath* path) {
|
||||||
|
#if HWUI_NEW_OPS
|
||||||
|
// TODO: remove allocator param for HWUI_NEW_OPS
|
||||||
|
projectionPathMask = path;
|
||||||
|
#else
|
||||||
if (path) {
|
if (path) {
|
||||||
ProjectionPathMask* mask = new (allocator) ProjectionPathMask;
|
ProjectionPathMask* mask = new (allocator) ProjectionPathMask;
|
||||||
mask->projectionMask = path;
|
mask->projectionMask = path;
|
||||||
buildScreenSpaceTransform(&(mask->projectionMaskTransform));
|
buildScreenSpaceTransform(&(mask->projectionMaskTransform));
|
||||||
|
|
||||||
projectionPathMask = mask;
|
projectionPathMask = mask;
|
||||||
} else {
|
} else {
|
||||||
projectionPathMask = nullptr;
|
projectionPathMask = nullptr;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ public:
|
|||||||
float radius;
|
float radius;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: remove for HWUI_NEW_OPS
|
||||||
class ProjectionPathMask {
|
class ProjectionPathMask {
|
||||||
public:
|
public:
|
||||||
static void* operator new(size_t size) = delete;
|
static void* operator new(size_t size) = delete;
|
||||||
@@ -219,6 +220,7 @@ public:
|
|||||||
* Fills outTransform with the current, total transform to screen space,
|
* Fills outTransform with the current, total transform to screen space,
|
||||||
* across layer boundaries.
|
* across layer boundaries.
|
||||||
*/
|
*/
|
||||||
|
// TODO: remove for HWUI_NEW_OPS
|
||||||
void buildScreenSpaceTransform(Matrix4* outTransform) const;
|
void buildScreenSpaceTransform(Matrix4* outTransform) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -294,9 +296,13 @@ public:
|
|||||||
const RoundRectClipState* roundRectClipState;
|
const RoundRectClipState* roundRectClipState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current projection masking path - used exclusively to mask tessellated circles.
|
* Current projection masking path - used exclusively to mask projected, tessellated circles.
|
||||||
*/
|
*/
|
||||||
|
#if HWUI_NEW_OPS
|
||||||
|
const SkPath* projectionPathMask;
|
||||||
|
#else
|
||||||
const ProjectionPathMask* projectionPathMask;
|
const ProjectionPathMask* projectionPathMask;
|
||||||
|
#endif
|
||||||
|
|
||||||
void dump() const;
|
void dump() const;
|
||||||
|
|
||||||
|
|||||||
@@ -990,21 +990,26 @@ TEST(FrameBuilder, projectionReorder) {
|
|||||||
EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
|
EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
|
||||||
EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
|
EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
|
||||||
expectedMatrix.loadIdentity();
|
expectedMatrix.loadIdentity();
|
||||||
|
EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
EXPECT_EQ(Rect(-10, -10, 60, 60), op.unmappedBounds);
|
EXPECT_EQ(Rect(-10, -10, 60, 60), op.unmappedBounds);
|
||||||
EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
|
EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
|
||||||
expectedMatrix.loadTranslate(50, 50, 0); // TODO: should scroll be respected here?
|
expectedMatrix.loadTranslate(50 - scrollX, 50 - scrollY, 0);
|
||||||
|
ASSERT_NE(nullptr, state.computedState.localProjectionPathMask);
|
||||||
|
EXPECT_EQ(Rect(-35, -30, 45, 50),
|
||||||
|
Rect(state.computedState.localProjectionPathMask->getBounds()));
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
EXPECT_EQ(Rect(100, 50), op.unmappedBounds);
|
EXPECT_EQ(Rect(100, 50), op.unmappedBounds);
|
||||||
EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
|
EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
|
||||||
expectedMatrix.loadTranslate(-scrollX, 50 - scrollY, 0);
|
expectedMatrix.loadTranslate(-scrollX, 50 - scrollY, 0);
|
||||||
|
EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ADD_FAILURE();
|
ADD_FAILURE();
|
||||||
}
|
}
|
||||||
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, state.computedState.transform);
|
EXPECT_EQ(expectedMatrix, state.computedState.transform);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1045,6 +1050,9 @@ TEST(FrameBuilder, projectionReorder) {
|
|||||||
});
|
});
|
||||||
auto parent = TestUtils::createNode(0, 0, 100, 100,
|
auto parent = TestUtils::createNode(0, 0, 100, 100,
|
||||||
[&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
|
[&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||||
|
// Set a rect outline for the projecting ripple to be masked against.
|
||||||
|
properties.mutableOutline().setRoundRect(10, 10, 90, 90, 5, 1.0f);
|
||||||
|
|
||||||
canvas.save(SaveFlags::MatrixClip);
|
canvas.save(SaveFlags::MatrixClip);
|
||||||
canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
|
canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
|
||||||
canvas.drawRenderNode(receiverBackground.get());
|
canvas.drawRenderNode(receiverBackground.get());
|
||||||
@@ -1059,6 +1067,145 @@ TEST(FrameBuilder, projectionReorder) {
|
|||||||
EXPECT_EQ(3, renderer.getIndex());
|
EXPECT_EQ(3, renderer.getIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RENDERTHREAD_TEST(FrameBuilder, projectionHwLayer) {
|
||||||
|
static const int scrollX = 5;
|
||||||
|
static const int scrollY = 10;
|
||||||
|
class ProjectionHwLayerTestRenderer : public TestRendererBase {
|
||||||
|
public:
|
||||||
|
void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
|
||||||
|
EXPECT_EQ(0, mIndex++);
|
||||||
|
}
|
||||||
|
void onArcOp(const ArcOp& op, const BakedOpState& state) override {
|
||||||
|
EXPECT_EQ(1, mIndex++);
|
||||||
|
ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
|
||||||
|
}
|
||||||
|
void endLayer() override {
|
||||||
|
EXPECT_EQ(2, mIndex++);
|
||||||
|
}
|
||||||
|
void onRectOp(const RectOp& op, const BakedOpState& state) override {
|
||||||
|
EXPECT_EQ(3, mIndex++);
|
||||||
|
ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
|
||||||
|
}
|
||||||
|
void onOvalOp(const OvalOp& op, const BakedOpState& state) override {
|
||||||
|
EXPECT_EQ(4, mIndex++);
|
||||||
|
ASSERT_NE(nullptr, state.computedState.localProjectionPathMask);
|
||||||
|
Matrix4 expected;
|
||||||
|
expected.loadTranslate(100 - scrollX, 100 - scrollY, 0);
|
||||||
|
EXPECT_EQ(expected, state.computedState.transform);
|
||||||
|
EXPECT_EQ(Rect(-85, -80, 295, 300),
|
||||||
|
Rect(state.computedState.localProjectionPathMask->getBounds()));
|
||||||
|
}
|
||||||
|
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
|
||||||
|
EXPECT_EQ(5, mIndex++);
|
||||||
|
ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
auto receiverBackground = TestUtils::createNode(0, 0, 400, 400,
|
||||||
|
[](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||||
|
properties.setProjectionReceiver(true);
|
||||||
|
// scroll doesn't apply to background, so undone via translationX/Y
|
||||||
|
// NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
|
||||||
|
properties.setTranslationX(scrollX);
|
||||||
|
properties.setTranslationY(scrollY);
|
||||||
|
|
||||||
|
canvas.drawRect(0, 0, 400, 400, SkPaint());
|
||||||
|
});
|
||||||
|
auto projectingRipple = TestUtils::createNode(0, 0, 200, 200,
|
||||||
|
[](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||||
|
properties.setProjectBackwards(true);
|
||||||
|
properties.setClipToBounds(false);
|
||||||
|
canvas.drawOval(100, 100, 300, 300, SkPaint()); // drawn mostly out of layer bounds
|
||||||
|
});
|
||||||
|
auto child = TestUtils::createNode(100, 100, 300, 300,
|
||||||
|
[&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||||
|
properties.mutateLayerProperties().setType(LayerType::RenderLayer);
|
||||||
|
canvas.drawRenderNode(projectingRipple.get());
|
||||||
|
canvas.drawArc(0, 0, 200, 200, 0.0f, 280.0f, true, SkPaint());
|
||||||
|
});
|
||||||
|
auto parent = TestUtils::createNode(0, 0, 400, 400,
|
||||||
|
[&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||||
|
// Set a rect outline for the projecting ripple to be masked against.
|
||||||
|
properties.mutableOutline().setRoundRect(10, 10, 390, 390, 0, 1.0f);
|
||||||
|
canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
|
||||||
|
canvas.drawRenderNode(receiverBackground.get());
|
||||||
|
canvas.drawRenderNode(child.get());
|
||||||
|
});
|
||||||
|
|
||||||
|
OffscreenBuffer** layerHandle = child->getLayerHandle();
|
||||||
|
|
||||||
|
// create RenderNode's layer here in same way prepareTree would, setting windowTransform
|
||||||
|
OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200, 200);
|
||||||
|
Matrix4 windowTransform;
|
||||||
|
windowTransform.loadTranslate(100, 100, 0); // total transform of layer's origin
|
||||||
|
layer.setWindowTransform(windowTransform);
|
||||||
|
*layerHandle = &layer;
|
||||||
|
|
||||||
|
auto syncedList = TestUtils::createSyncedNodeList(parent);
|
||||||
|
LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
|
||||||
|
layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(200, 200));
|
||||||
|
FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
|
||||||
|
syncedList, sLightGeometry, nullptr);
|
||||||
|
ProjectionHwLayerTestRenderer renderer;
|
||||||
|
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
|
||||||
|
EXPECT_EQ(6, renderer.getIndex());
|
||||||
|
|
||||||
|
// clean up layer pointer, so we can safely destruct RenderNode
|
||||||
|
*layerHandle = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
RENDERTHREAD_TEST(FrameBuilder, projectionChildScroll) {
|
||||||
|
static const int scrollX = 500000;
|
||||||
|
static const int scrollY = 0;
|
||||||
|
class ProjectionChildScrollTestRenderer : public TestRendererBase {
|
||||||
|
public:
|
||||||
|
void onRectOp(const RectOp& op, const BakedOpState& state) override {
|
||||||
|
EXPECT_EQ(0, mIndex++);
|
||||||
|
EXPECT_TRUE(state.computedState.transform.isIdentity());
|
||||||
|
}
|
||||||
|
void onOvalOp(const OvalOp& op, const BakedOpState& state) override {
|
||||||
|
EXPECT_EQ(1, mIndex++);
|
||||||
|
ASSERT_NE(nullptr, state.computedState.clipState);
|
||||||
|
ASSERT_EQ(ClipMode::Rectangle, state.computedState.clipState->mode);
|
||||||
|
ASSERT_EQ(Rect(400, 400), state.computedState.clipState->rect);
|
||||||
|
EXPECT_TRUE(state.computedState.transform.isIdentity());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
auto receiverBackground = TestUtils::createNode(0, 0, 400, 400,
|
||||||
|
[](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||||
|
properties.setProjectionReceiver(true);
|
||||||
|
canvas.drawRect(0, 0, 400, 400, SkPaint());
|
||||||
|
});
|
||||||
|
auto projectingRipple = TestUtils::createNode(0, 0, 200, 200,
|
||||||
|
[](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||||
|
// scroll doesn't apply to background, so undone via translationX/Y
|
||||||
|
// NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
|
||||||
|
properties.setTranslationX(scrollX);
|
||||||
|
properties.setTranslationY(scrollY);
|
||||||
|
properties.setProjectBackwards(true);
|
||||||
|
properties.setClipToBounds(false);
|
||||||
|
canvas.drawOval(0, 0, 200, 200, SkPaint());
|
||||||
|
});
|
||||||
|
auto child = TestUtils::createNode(0, 0, 400, 400,
|
||||||
|
[&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||||
|
// Record time clip will be ignored by projectee
|
||||||
|
canvas.clipRect(100, 100, 300, 300, SkRegion::kIntersect_Op);
|
||||||
|
|
||||||
|
canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
|
||||||
|
canvas.drawRenderNode(projectingRipple.get());
|
||||||
|
});
|
||||||
|
auto parent = TestUtils::createNode(0, 0, 400, 400,
|
||||||
|
[&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
|
||||||
|
canvas.drawRenderNode(receiverBackground.get());
|
||||||
|
canvas.drawRenderNode(child.get());
|
||||||
|
});
|
||||||
|
|
||||||
|
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
|
||||||
|
TestUtils::createSyncedNodeList(parent), sLightGeometry, nullptr);
|
||||||
|
ProjectionChildScrollTestRenderer renderer;
|
||||||
|
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
|
||||||
|
EXPECT_EQ(2, renderer.getIndex());
|
||||||
|
}
|
||||||
|
|
||||||
// creates a 100x100 shadow casting node with provided translationZ
|
// creates a 100x100 shadow casting node with provided translationZ
|
||||||
static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
|
static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
|
||||||
return TestUtils::createNode(0, 0, 100, 100,
|
return TestUtils::createNode(0, 0, 100, 100,
|
||||||
|
|||||||
@@ -3,24 +3,32 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
<FrameLayout
|
<ScrollView
|
||||||
|
android:orientation="vertical"
|
||||||
android:translationX="50dp"
|
android:translationX="50dp"
|
||||||
android:translationY="50dp"
|
android:translationY="50dp"
|
||||||
android:elevation="30dp"
|
android:elevation="30dp"
|
||||||
android:layout_width="200dp"
|
android:layout_width="200dp"
|
||||||
android:layout_height="200dp"
|
android:layout_height="200dp"
|
||||||
android:background="@drawable/round_rect_background">
|
android:background="@drawable/round_rect_background">
|
||||||
<View
|
<FrameLayout
|
||||||
android:id="@+id/clickable1"
|
android:layout_width="200dp"
|
||||||
android:layout_width="100dp"
|
android:layout_height="wrap_content">
|
||||||
android:layout_height="100dp"
|
<View
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless"/>
|
android:layout_width="200dp"
|
||||||
<View
|
android:layout_height="2000dp"/>
|
||||||
android:id="@+id/clickable2"
|
<View
|
||||||
android:translationX="50dp"
|
android:id="@+id/clickable1"
|
||||||
android:translationY="10dp"
|
android:layout_width="100dp"
|
||||||
android:layout_width="150dp"
|
android:layout_height="100dp"
|
||||||
android:layout_height="100dp"
|
android:background="?android:attr/selectableItemBackgroundBorderless"/>
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless"/>
|
<View
|
||||||
</FrameLayout>
|
android:id="@+id/clickable2"
|
||||||
|
android:translationX="50dp"
|
||||||
|
android:translationY="10dp"
|
||||||
|
android:layout_width="150dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"/>
|
||||||
|
</FrameLayout>
|
||||||
|
</ScrollView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user