Merge "Use path intersection instead of saveLayer+mesh to mask projected ripples" into mnc-dev

This commit is contained in:
Chris Craik
2015-05-28 22:03:06 +00:00
committed by Android (Google) Code Review
16 changed files with 198 additions and 138 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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
*

View File

@@ -63,6 +63,7 @@ public:
mat4 mMatrix;
float mAlpha;
const RoundRectClipState* mRoundRectClipState;
const ProjectionPathMask* mProjectionPathMask;
};
class OpStatePair {

View File

@@ -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);

View File

@@ -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,
@@ -1758,6 +1761,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);
}
@@ -1864,19 +1868,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);
} 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);
return;
}
SkPath path;
if (p->getStyle() == SkPaint::kStrokeAndFill_Style) {
path.addCircle(x, y, radius + p->getStrokeWidth() / 2);
} else {
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,
@@ -2149,6 +2175,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) {

View File

@@ -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(); }

View File

@@ -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;
}
///////////////////////////////////////////////////////////////////////////////

View File

@@ -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

View File

@@ -769,31 +769,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++) {
@@ -808,10 +786,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());
}
/**

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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
///////////////////////////////////////////////////////////////////////////////

View File

@@ -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()
@@ -189,6 +200,11 @@ public:
void setClippingRoundRect(LinearAllocator& allocator, const Rect& bounds,
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.
@@ -200,6 +216,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.
*/
@@ -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:

View File

@@ -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;

View File

@@ -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" />