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
This commit is contained in:
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -63,6 +63,7 @@ public:
|
||||
mat4 mMatrix;
|
||||
float mAlpha;
|
||||
const RoundRectClipState* mRoundRectClipState;
|
||||
const ProjectionPathMask* mProjectionPathMask;
|
||||
};
|
||||
|
||||
class OpStatePair {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
} 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,
|
||||
@ -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) {
|
||||
|
@ -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(); }
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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" />
|
||||
|
Reference in New Issue
Block a user