summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chris Craik <ccraik@google.com> 2015-04-28 11:45:59 -0700
committer Chris Craik <ccraik@google.com> 2015-05-26 17:53:16 -0700
commitfca52b7583d1e5f5ff8ed06554875d2a30ef56fa (patch)
treee383a2db169421a722fa9c559dd01904e83fa504
parentaa1cd25db72297f13539928e8aa45ba992f2f230 (diff)
Use path intersection instead of saveLayer+mesh to mask projected ripples
bug:14297149 SaveLayer's performance cost is high, and proportional to the surface being projected onto. Since ripples (even unbounded ones) are now always projected to the arbitrary background content behind them, this cost is especially important to avoid. This removes the last semi-secret, saveLayer from the projected ripple implementation. Also fixes the HW test app to correctly demonstrate this projection masking behavior. Additionaly, alters PathTessellator to gracefully handle counter-clockwise paths, and simplifies the work done by ShadowTessellator to ensure all of its paths are counterclockwise. Change-Id: Ibe9e12812bd10a774e20b1d444a140c368cbba8c
-rw-r--r--libs/hwui/CanvasState.cpp3
-rw-r--r--libs/hwui/CanvasState.h1
-rw-r--r--libs/hwui/DeferredDisplayList.cpp1
-rw-r--r--libs/hwui/DeferredDisplayList.h1
-rw-r--r--libs/hwui/Matrix.h6
-rw-r--r--libs/hwui/OpenGLRenderer.cpp46
-rwxr-xr-xlibs/hwui/OpenGLRenderer.h1
-rw-r--r--libs/hwui/PathTessellator.cpp53
-rw-r--r--libs/hwui/PathTessellator.h2
-rw-r--r--libs/hwui/RenderNode.cpp34
-rw-r--r--libs/hwui/ShadowTessellator.cpp65
-rw-r--r--libs/hwui/ShadowTessellator.h17
-rw-r--r--libs/hwui/Snapshot.cpp42
-rw-r--r--libs/hwui/Snapshot.h27
-rw-r--r--libs/hwui/TessellationCache.cpp17
-rw-r--r--tests/HwAccelerationTest/AndroidManifest.xml18
16 files changed, 197 insertions, 137 deletions
diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp
index e88e9f601bbf..e22b0d3084ab 100644
--- a/libs/hwui/CanvasState.cpp
+++ b/libs/hwui/CanvasState.cpp
@@ -189,6 +189,9 @@ void CanvasState::setClippingRoundRect(LinearAllocator& allocator,
mSnapshot->setClippingRoundRect(allocator, rect, radius, highPriority);
}
+void CanvasState::setProjectionPathMask(LinearAllocator& allocator, const SkPath* path) {
+ mSnapshot->setProjectionPathMask(allocator, path);
+}
///////////////////////////////////////////////////////////////////////////////
// Quick Rejection
diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h
index 8e4a4d3548a7..9354e94a94a3 100644
--- a/libs/hwui/CanvasState.h
+++ b/libs/hwui/CanvasState.h
@@ -130,6 +130,7 @@ public:
void setClippingOutline(LinearAllocator& allocator, const Outline* outline);
void setClippingRoundRect(LinearAllocator& allocator,
const Rect& rect, float radius, bool highPriority = true);
+ void setProjectionPathMask(LinearAllocator& allocator, const SkPath* path);
/**
* Returns true if drawing in the rectangle (left, top, right, bottom)
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index 6fcf95860ffc..b077a850c5b9 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -195,6 +195,7 @@ public:
// 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
if (lhs->mRoundRectClipState != rhs->mRoundRectClipState) return false;
+ if (lhs->mProjectionPathMask != rhs->mProjectionPathMask) return false;
/* Clipping compatibility check
*
diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h
index 3d0ca6d42d0e..160c1ad2d1f6 100644
--- a/libs/hwui/DeferredDisplayList.h
+++ b/libs/hwui/DeferredDisplayList.h
@@ -63,6 +63,7 @@ public:
mat4 mMatrix;
float mAlpha;
const RoundRectClipState* mRoundRectClipState;
+ const ProjectionPathMask* mProjectionPathMask;
};
class OpStatePair {
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index a76013503d5f..c1527893ee2b 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -134,6 +134,12 @@ public:
uint8_t getType() const;
+ void multiplyInverse(const Matrix4& v) {
+ Matrix4 inv;
+ inv.loadInverse(v);
+ multiply(inv);
+ }
+
void multiply(const Matrix4& v) {
Matrix4 u;
u.loadMultiply(*this, v);
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 54bcd7e43d1d..d87a3e63ceac 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -40,6 +40,7 @@
#include <SkCanvas.h>
#include <SkColor.h>
+#include <SkPathOps.h>
#include <SkShader.h>
#include <SkTypeface.h>
@@ -1193,8 +1194,9 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef
state.mMatrix.load(*currentMatrix);
state.mAlpha = currentSnapshot()->alpha;
- // always store/restore, since it's just a pointer
+ // always store/restore, since these are just pointers
state.mRoundRectClipState = currentSnapshot()->roundRectClipState;
+ state.mProjectionPathMask = currentSnapshot()->projectionPathMask;
return false;
}
@@ -1202,6 +1204,7 @@ void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state, bool
setMatrix(state.mMatrix);
writableSnapshot()->alpha = state.mAlpha;
writableSnapshot()->roundRectClipState = state.mRoundRectClipState;
+ writableSnapshot()->projectionPathMask = state.mProjectionPathMask;
if (state.mClipValid && !skipClipRestore) {
writableSnapshot()->setClip(state.mClip.left, state.mClip.top,
@@ -1755,6 +1758,7 @@ void OpenGLRenderer::drawVertexBuffer(float translateX, float translateY,
void OpenGLRenderer::drawConvexPath(const SkPath& path, const SkPaint* paint) {
VertexBuffer vertexBuffer;
// TODO: try clipping large paths to viewport
+
PathTessellator::tessellatePath(path, paint, *currentTransform(), vertexBuffer);
drawVertexBuffer(vertexBuffer, paint);
}
@@ -1861,19 +1865,41 @@ void OpenGLRenderer::drawCircle(float x, float y, float radius, const SkPaint* p
|| PaintUtils::paintWillNotDraw(*p)) {
return;
}
+
if (p->getPathEffect() != nullptr) {
mCaches.textureState().activateTexture(0);
PathTexture* texture = mCaches.pathCache.getCircle(radius, p);
drawShape(x - radius, y - radius, texture, p);
+ return;
+ }
+
+ SkPath path;
+ if (p->getStyle() == SkPaint::kStrokeAndFill_Style) {
+ path.addCircle(x, y, radius + p->getStrokeWidth() / 2);
} else {
- SkPath path;
- if (p->getStyle() == SkPaint::kStrokeAndFill_Style) {
- path.addCircle(x, y, radius + p->getStrokeWidth() / 2);
- } else {
- path.addCircle(x, y, radius);
- }
- drawConvexPath(path, p);
+ path.addCircle(x, y, radius);
+ }
+
+ if (CC_UNLIKELY(currentSnapshot()->projectionPathMask != nullptr)) {
+ // mask ripples with projection mask
+ SkPath maskPath = *(currentSnapshot()->projectionPathMask->projectionMask);
+
+ Matrix4 screenSpaceTransform;
+ currentSnapshot()->buildScreenSpaceTransform(&screenSpaceTransform);
+
+ Matrix4 totalTransform;
+ totalTransform.loadInverse(screenSpaceTransform);
+ totalTransform.multiply(currentSnapshot()->projectionPathMask->projectionMaskTransform);
+
+ SkMatrix skTotalTransform;
+ totalTransform.copyTo(skTotalTransform);
+ maskPath.transform(skTotalTransform);
+
+ // Mask the ripple path by the projection mask, now that it's
+ // in local space. Note that this can create CCW paths.
+ Op(path, maskPath, kIntersect_PathOp, &path);
}
+ drawConvexPath(path, p);
}
void OpenGLRenderer::drawOval(float left, float top, float right, float bottom,
@@ -2146,6 +2172,10 @@ void OpenGLRenderer::setClippingRoundRect(LinearAllocator& allocator,
mState.setClippingRoundRect(allocator, rect, radius, highPriority);
}
+void OpenGLRenderer::setProjectionPathMask(LinearAllocator& allocator, const SkPath* path) {
+ mState.setProjectionPathMask(allocator, path);
+}
+
void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float x, float y,
const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds,
DrawOpMode drawOpMode) {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 218818d8e41d..8dae82c5b114 100755
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -399,6 +399,7 @@ public:
void setClippingOutline(LinearAllocator& allocator, const Outline* outline);
void setClippingRoundRect(LinearAllocator& allocator,
const Rect& rect, float radius, bool highPriority = true);
+ void setProjectionPathMask(LinearAllocator& allocator, const SkPath* path);
inline bool hasRectToRectTransform() const { return mState.hasRectToRectTransform(); }
inline const mat4* currentTransform() const { return mState.currentTransform(); }
diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp
index c1f61d696b00..e7c6c05af139 100644
--- a/libs/hwui/PathTessellator.cpp
+++ b/libs/hwui/PathTessellator.cpp
@@ -37,6 +37,7 @@
#include <SkPath.h>
#include <SkPaint.h>
+#include <SkPoint.h>
#include <SkGeometry.h> // WARNING: Internal Skia Header
#include <stdlib.h>
@@ -912,6 +913,39 @@ void pushToVector(Vector<Vertex>& vertices, float x, float y) {
Vertex::set(newVertex, x, y);
}
+class ClockwiseEnforcer {
+public:
+ void addPoint(const SkPoint& point) {
+ double x = point.x();
+ double y = point.y();
+
+ if (initialized) {
+ sum += (x + lastX) * (y - lastY);
+ } else {
+ initialized = true;
+ }
+
+ lastX = x;
+ lastY = y;
+ }
+ void reverseVectorIfNotClockwise(Vector<Vertex>& vertices) {
+ if (sum < 0) {
+ // negative sum implies CounterClockwise
+ const int size = vertices.size();
+ for (int i = 0; i < size / 2; i++) {
+ Vertex tmp = vertices[i];
+ int k = size - 1 - i;
+ vertices.replaceAt(vertices[k], i);
+ vertices.replaceAt(tmp, k);
+ }
+ }
+ }
+private:
+ bool initialized = false;
+ double lastX, lastY;
+ double sum = 0;
+};
+
bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool forceClose,
float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared,
Vector<Vertex>& outputVertices) {
@@ -922,18 +956,22 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo
SkPath::Iter iter(path, forceClose);
SkPoint pts[4];
SkPath::Verb v;
+ ClockwiseEnforcer clockwiseEnforcer;
while (SkPath::kDone_Verb != (v = iter.next(pts))) {
switch (v) {
case SkPath::kMove_Verb:
pushToVector(outputVertices, pts[0].x(), pts[0].y());
ALOGV("Move to pos %f %f", pts[0].x(), pts[0].y());
+ clockwiseEnforcer.addPoint(pts[0]);
break;
case SkPath::kClose_Verb:
ALOGV("Close at pos %f %f", pts[0].x(), pts[0].y());
+ clockwiseEnforcer.addPoint(pts[0]);
break;
case SkPath::kLine_Verb:
ALOGV("kLine_Verb %f %f -> %f %f", pts[0].x(), pts[0].y(), pts[1].x(), pts[1].y());
pushToVector(outputVertices, pts[1].x(), pts[1].y());
+ clockwiseEnforcer.addPoint(pts[1]);
break;
case SkPath::kQuad_Verb:
ALOGV("kQuad_Verb");
@@ -942,6 +980,8 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo
pts[2].x(), pts[2].y(),
pts[1].x(), pts[1].y(),
sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices);
+ clockwiseEnforcer.addPoint(pts[1]);
+ clockwiseEnforcer.addPoint(pts[2]);
break;
case SkPath::kCubic_Verb:
ALOGV("kCubic_Verb");
@@ -951,6 +991,9 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo
pts[3].x(), pts[3].y(),
pts[2].x(), pts[2].y(),
sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices);
+ clockwiseEnforcer.addPoint(pts[1]);
+ clockwiseEnforcer.addPoint(pts[2]);
+ clockwiseEnforcer.addPoint(pts[3]);
break;
case SkPath::kConic_Verb: {
ALOGV("kConic_Verb");
@@ -965,6 +1008,8 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo
quads[offset+1].x(), quads[offset+1].y(),
sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices);
}
+ clockwiseEnforcer.addPoint(pts[1]);
+ clockwiseEnforcer.addPoint(pts[2]);
break;
}
default:
@@ -972,13 +1017,17 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo
}
}
+ bool wasClosed = false;
int size = outputVertices.size();
if (size >= 2 && outputVertices[0].x == outputVertices[size - 1].x &&
outputVertices[0].y == outputVertices[size - 1].y) {
outputVertices.pop();
- return true;
+ wasClosed = true;
}
- return false;
+
+ // ensure output vector is clockwise
+ clockwiseEnforcer.reverseVectorIfNotClockwise(outputVertices);
+ return wasClosed;
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/PathTessellator.h b/libs/hwui/PathTessellator.h
index 8ac9a3b2562e..ccae65b1aaf5 100644
--- a/libs/hwui/PathTessellator.h
+++ b/libs/hwui/PathTessellator.h
@@ -82,7 +82,7 @@ public:
const mat4& transform, VertexBuffer& vertexBuffer);
/**
- * Approximates a convex, CW outline into a Vector of 2d vertices.
+ * Approximates a convex outline into a clockwise Vector of 2d vertices.
*
* @param path The outline to be approximated
* @param thresholdSquared The threshold of acceptable error (in pixels) when approximating
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 9146b6831a9b..c2f72341b1c3 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -768,31 +768,9 @@ void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T&
const RenderProperties& backgroundProps = backgroundOp->mRenderNode->properties();
renderer.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
- // If the projection reciever has an outline, we mask each of the projected rendernodes to it
- // Either with clipRect, or special saveLayer masking
- if (projectionReceiverOutline != nullptr) {
- const SkRect& outlineBounds = projectionReceiverOutline->getBounds();
- if (projectionReceiverOutline->isRect(nullptr)) {
- // mask to the rect outline simply with clipRect
- ClipRectOp* clipOp = new (alloc) ClipRectOp(
- outlineBounds.left(), outlineBounds.top(),
- outlineBounds.right(), outlineBounds.bottom(), SkRegion::kIntersect_Op);
- handler(clipOp, PROPERTY_SAVECOUNT, properties().getClipToBounds());
- } else {
- // wrap the projected RenderNodes with a SaveLayer that will mask to the outline
- SaveLayerOp* op = new (alloc) SaveLayerOp(
- outlineBounds.left(), outlineBounds.top(),
- outlineBounds.right(), outlineBounds.bottom(),
- 255, SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag | SkCanvas::kARGB_ClipLayer_SaveFlag);
- op->setMask(projectionReceiverOutline);
- handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds());
-
- /* TODO: add optimizations here to take advantage of placement/size of projected
- * children (which may shrink saveLayer area significantly). This is dependent on
- * passing actual drawing/dirtying bounds of projected content down to native.
- */
- }
- }
+ // If the projection reciever has an outline, we mask projected content to it
+ // (which we know, apriori, are all tessellated paths)
+ renderer.setProjectionPathMask(alloc, projectionReceiverOutline);
// draw projected nodes
for (size_t i = 0; i < mProjectedNodes.size(); i++) {
@@ -807,10 +785,8 @@ void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T&
renderer.restoreToCount(restoreTo);
}
- if (projectionReceiverOutline != nullptr) {
- handler(new (alloc) RestoreToCountOp(restoreTo),
- PROPERTY_SAVECOUNT, properties().getClipToBounds());
- }
+ handler(new (alloc) RestoreToCountOp(restoreTo),
+ PROPERTY_SAVECOUNT, properties().getClipToBounds());
}
/**
diff --git a/libs/hwui/ShadowTessellator.cpp b/libs/hwui/ShadowTessellator.cpp
index fb285312a6e7..024ff10a52b9 100644
--- a/libs/hwui/ShadowTessellator.cpp
+++ b/libs/hwui/ShadowTessellator.cpp
@@ -158,71 +158,6 @@ Vector2 ShadowTessellator::calculateNormal(const Vector2& p1, const Vector2& p2)
}
return result;
}
-/**
- * Test whether the polygon is order in clockwise.
- *
- * @param polygon the polygon as a Vector2 array
- * @param len the number of points of the polygon
- */
-bool ShadowTessellator::isClockwise(const Vector2* polygon, int len) {
- if (len < 2 || polygon == nullptr) {
- return true;
- }
- double sum = 0;
- double p1x = polygon[len - 1].x;
- double p1y = polygon[len - 1].y;
- for (int i = 0; i < len; i++) {
-
- double p2x = polygon[i].x;
- double p2y = polygon[i].y;
- sum += p1x * p2y - p2x * p1y;
- p1x = p2x;
- p1y = p2y;
- }
- return sum < 0;
-}
-
-bool ShadowTessellator::isClockwisePath(const SkPath& path) {
- SkPath::Iter iter(path, false);
- SkPoint pts[4];
- SkPath::Verb v;
-
- Vector<Vector2> arrayForDirection;
- while (SkPath::kDone_Verb != (v = iter.next(pts))) {
- switch (v) {
- case SkPath::kMove_Verb:
- arrayForDirection.add((Vector2){pts[0].x(), pts[0].y()});
- break;
- case SkPath::kLine_Verb:
- arrayForDirection.add((Vector2){pts[1].x(), pts[1].y()});
- break;
- case SkPath::kConic_Verb:
- case SkPath::kQuad_Verb:
- arrayForDirection.add((Vector2){pts[1].x(), pts[1].y()});
- arrayForDirection.add((Vector2){pts[2].x(), pts[2].y()});
- break;
- case SkPath::kCubic_Verb:
- arrayForDirection.add((Vector2){pts[1].x(), pts[1].y()});
- arrayForDirection.add((Vector2){pts[2].x(), pts[2].y()});
- arrayForDirection.add((Vector2){pts[3].x(), pts[3].y()});
- break;
- default:
- break;
- }
- }
-
- return isClockwise(arrayForDirection.array(), arrayForDirection.size());
-}
-
-void ShadowTessellator::reverseVertexArray(Vertex* polygon, int len) {
- int n = len / 2;
- for (int i = 0; i < n; i++) {
- Vertex tmp = polygon[i];
- int k = len - 1 - i;
- polygon[i] = polygon[k];
- polygon[k] = tmp;
- }
-}
int ShadowTessellator::getExtraVertexNumber(const Vector2& vector1,
const Vector2& vector2, float divisor) {
diff --git a/libs/hwui/ShadowTessellator.h b/libs/hwui/ShadowTessellator.h
index c04d8ef1f481..5f4c9c53a9d8 100644
--- a/libs/hwui/ShadowTessellator.h
+++ b/libs/hwui/ShadowTessellator.h
@@ -83,23 +83,6 @@ public:
static bool isClockwise(const Vector2* polygon, int len);
static Vector2 calculateNormal(const Vector2& p1, const Vector2& p2);
- /**
- * Determine whether the path is clockwise, using the control points.
- *
- * TODO: Given the skia is using inverted Y coordinate, shadow system needs
- * to convert to the same coordinate to avoid the extra reverse.
- *
- * @param path The path to be examined.
- */
- static bool isClockwisePath(const SkPath &path);
-
- /**
- * Reverse the vertex array.
- *
- * @param polygon The vertex array to be reversed.
- * @param len The length of the vertex array.
- */
- static void reverseVertexArray(Vertex* polygon, int len);
static int getExtraVertexNumber(const Vector2& vector1, const Vector2& vector2,
float divisor);
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index 9e7faee69fba..beb2e1d0481c 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -36,6 +36,7 @@ Snapshot::Snapshot()
, empty(false)
, alpha(1.0f)
, roundRectClipState(nullptr)
+ , projectionPathMask(nullptr)
, mClipArea(&mClipAreaRoot) {
transform = &mTransformRoot;
region = nullptr;
@@ -54,6 +55,7 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags)
, empty(false)
, alpha(s->alpha)
, roundRectClipState(s->roundRectClipState)
+ , projectionPathMask(s->projectionPathMask)
, mClipArea(nullptr)
, mViewportData(s->mViewportData)
, mRelativeLightCenter(s->mRelativeLightCenter) {
@@ -141,6 +143,34 @@ void Snapshot::resetTransform(float x, float y, float z) {
transform->loadTranslate(x, y, z);
}
+void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const {
+ // build (reverse ordered) list of the stack of snapshots, terminated with a NULL
+ Vector<const Snapshot*> snapshotList;
+ snapshotList.push(nullptr);
+ const Snapshot* current = this;
+ do {
+ snapshotList.push(current);
+ current = current->previous.get();
+ } while (current);
+
+ // traverse the list, adding in each transform that contributes to the total transform
+ outTransform->loadIdentity();
+ for (size_t i = snapshotList.size() - 1; i > 0; i--) {
+ // iterate down the stack
+ const Snapshot* current = snapshotList[i];
+ const Snapshot* next = snapshotList[i - 1];
+ if (current->flags & kFlagIsFboLayer) {
+ // if we've hit a layer, translate by the layer's draw offset
+ outTransform->translate(current->layer->layer.left, current->layer->layer.top);
+ }
+ if (!next || (next->flags & kFlagIsFboLayer)) {
+ // if this snapshot is last, or if this snapshot is last before an
+ // FBO layer (which reset the transform), apply it
+ outTransform->multiply(*(current->transform));
+ }
+ }
+}
+
///////////////////////////////////////////////////////////////////////////////
// Clipping round rect
///////////////////////////////////////////////////////////////////////////////
@@ -191,6 +221,18 @@ void Snapshot::setClippingRoundRect(LinearAllocator& allocator, const Rect& boun
roundRectClipState = state;
}
+void Snapshot::setProjectionPathMask(LinearAllocator& allocator, const SkPath* path) {
+ if (path) {
+ ProjectionPathMask* mask = new (allocator) ProjectionPathMask;
+ mask->projectionMask = path;
+ buildScreenSpaceTransform(&(mask->projectionMaskTransform));
+
+ projectionPathMask = mask;
+ } else {
+ projectionPathMask = nullptr;
+ }
+}
+
///////////////////////////////////////////////////////////////////////////////
// Queries
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 4d704abe205a..af6ad727da85 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -63,6 +63,17 @@ public:
float radius;
};
+class ProjectionPathMask {
+public:
+ /** static void* operator new(size_t size); PURPOSELY OMITTED, allocator only **/
+ static void* operator new(size_t size, LinearAllocator& allocator) {
+ return allocator.alloc(size);
+ }
+
+ const SkPath* projectionMask;
+ Matrix4 projectionMaskTransform;
+};
+
/**
* A snapshot holds information about the current state of the rendering
* surface. A snapshot is usually created whenever the user calls save()
@@ -190,6 +201,11 @@ public:
float radius, bool highPriority);
/**
+ * Sets (and replaces) the current projection mask
+ */
+ void setProjectionPathMask(LinearAllocator& allocator, const SkPath* path);
+
+ /**
* Indicates whether this snapshot should be ignored. A snapshot
* is typically ignored if its layer is invisible or empty.
*/
@@ -201,6 +217,12 @@ public:
bool hasPerspectiveTransform() const;
/**
+ * Fills outTransform with the current, total transform to screen space,
+ * across layer boundaries.
+ */
+ void buildScreenSpaceTransform(Matrix4* outTransform) const;
+
+ /**
* Dirty flags.
*/
int flags;
@@ -272,6 +294,11 @@ public:
*/
const RoundRectClipState* roundRectClipState;
+ /**
+ * Current projection masking path - used exclusively to mask tessellated circles.
+ */
+ const ProjectionPathMask* projectionPathMask;
+
void dump() const;
private:
diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp
index fc173f7fb9c1..704a691128d4 100644
--- a/libs/hwui/TessellationCache.cpp
+++ b/libs/hwui/TessellationCache.cpp
@@ -207,6 +207,16 @@ static void mapPointFakeZ(Vector3& point, const mat4* transformXY, const mat4* t
transformXY->mapPoint(point.x, point.y);
}
+static void reverseVertexArray(Vertex* polygon, int len) {
+ int n = len / 2;
+ for (int i = 0; i < n; i++) {
+ Vertex tmp = polygon[i];
+ int k = len - 1 - i;
+ polygon[i] = polygon[k];
+ polygon[k] = tmp;
+ }
+}
+
static void tessellateShadows(
const Matrix4* drawTransform, const Rect* localClip,
bool isCasterOpaque, const SkPath* casterPerimeter,
@@ -219,10 +229,9 @@ static void tessellateShadows(
const float casterRefinementThresholdSquared = 4.0f;
PathTessellator::approximatePathOutlineVertices(*casterPerimeter,
casterRefinementThresholdSquared, casterVertices2d);
- if (!ShadowTessellator::isClockwisePath(*casterPerimeter)) {
- ShadowTessellator::reverseVertexArray(casterVertices2d.editArray(),
- casterVertices2d.size());
- }
+
+ // Shadow requires CCW for now. TODO: remove potential double-reverse
+ reverseVertexArray(casterVertices2d.editArray(), casterVertices2d.size());
if (casterVertices2d.size() == 0) return;
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 8531944c4b8b..10cf5c1b2f8b 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -24,11 +24,11 @@
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
- <uses-sdk android:minSdkVersion="11" />
-
+ <uses-sdk android:minSdkVersion="21" />
+
<application
android:label="HwUi"
- android:hardwareAccelerated="true">
+ android:theme="@android:style/Theme.Material.Light">
<activity
android:name="HwTests"
@@ -42,8 +42,7 @@
<activity
android:name="PathOpsActivity"
- android:label="Path/Ops"
- android:theme="@android:style/Theme.Holo.Light">
+ android:label="Path/Ops">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.android.test.hwui.TEST" />
@@ -52,8 +51,7 @@
<activity
android:name="AssetsAtlasActivity"
- android:label="Atlas/Framework"
- android:theme="@android:style/Theme.Holo.Light">
+ android:label="Atlas/Framework">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.android.test.hwui.TEST" />
@@ -62,8 +60,7 @@
<activity
android:name="ScaledTextActivity"
- android:label="Text/Scaled"
- android:theme="@android:style/Theme.Holo.Light">
+ android:label="Text/Scaled">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.android.test.hwui.TEST" />
@@ -72,8 +69,7 @@
<activity
android:name="Rotate3dTextActivity"
- android:label="Text/3D Rotation"
- android:theme="@android:style/Theme.Holo.Light">
+ android:label="Text/3D Rotation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.android.test.hwui.TEST" />