зеркало из https://github.com/mozilla/moz-skia.git
Add perspective support to the gpu aa hairline renderer.
Review URL: http://codereview.appspot.com/4969071/ git-svn-id: http://skia.googlecode.com/svn/trunk@2249 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
Родитель
27c9b6d276
Коммит
dbeeac3332
|
@ -3,6 +3,7 @@
|
|||
#include "GrContext.h"
|
||||
#include "GrGpu.h"
|
||||
#include "GrIndexBuffer.h"
|
||||
#include "GrPathUtils.h"
|
||||
#include "SkGeometry.h"
|
||||
#include "SkTemplates.h"
|
||||
|
||||
|
@ -116,9 +117,7 @@ bool GrAAHairLinePathRenderer::supportsAA(GrDrawTarget* target,
|
|||
bool GrAAHairLinePathRenderer::canDrawPath(const GrDrawTarget* target,
|
||||
const SkPath& path,
|
||||
GrPathFill fill) const {
|
||||
// TODO: support perspective
|
||||
return kHairLine_PathFill == fill &&
|
||||
!target->getViewMatrix().hasPerspective();
|
||||
return kHairLine_PathFill == fill;
|
||||
}
|
||||
|
||||
void GrAAHairLinePathRenderer::pathWillClear() {
|
||||
|
@ -145,6 +144,7 @@ typedef GrTArray<int, true> IntArray;
|
|||
* We convert cubics to quadratics (for now).
|
||||
*/
|
||||
void convert_noninflect_cubic_to_quads(const SkPoint p[4],
|
||||
SkScalar tolScale,
|
||||
PtArray* quads,
|
||||
int sublevel = 0) {
|
||||
SkVector ab = p[1];
|
||||
|
@ -153,8 +153,9 @@ void convert_noninflect_cubic_to_quads(const SkPoint p[4],
|
|||
dc -= p[3];
|
||||
|
||||
static const SkScalar gLengthScale = 3 * SK_Scalar1 / 2;
|
||||
static const SkScalar gDistanceSqdTol = 2 * SK_Scalar1;
|
||||
static const int kMaxSubdivs = 30;
|
||||
// base tolerance is 2 pixels in dev coords.
|
||||
const SkScalar distanceSqdTol = SkScalarMul(tolScale, 2 * SK_Scalar1);
|
||||
static const int kMaxSubdivs = 10;
|
||||
|
||||
ab.scale(gLengthScale);
|
||||
dc.scale(gLengthScale);
|
||||
|
@ -165,7 +166,7 @@ void convert_noninflect_cubic_to_quads(const SkPoint p[4],
|
|||
c1 += dc;
|
||||
|
||||
SkScalar dSqd = c0.distanceToSqd(c1);
|
||||
if (sublevel > kMaxSubdivs || dSqd <= gDistanceSqdTol) {
|
||||
if (sublevel > kMaxSubdivs || dSqd <= distanceSqdTol) {
|
||||
SkPoint cAvg = c0;
|
||||
cAvg += c1;
|
||||
cAvg.scale(SK_ScalarHalf);
|
||||
|
@ -180,18 +181,22 @@ void convert_noninflect_cubic_to_quads(const SkPoint p[4],
|
|||
} else {
|
||||
SkPoint choppedPts[7];
|
||||
SkChopCubicAtHalf(p, choppedPts);
|
||||
convert_noninflect_cubic_to_quads(choppedPts + 0, quads, sublevel + 1);
|
||||
convert_noninflect_cubic_to_quads(choppedPts + 3, quads, sublevel + 1);
|
||||
convert_noninflect_cubic_to_quads(choppedPts + 0, tolScale,
|
||||
quads, sublevel + 1);
|
||||
convert_noninflect_cubic_to_quads(choppedPts + 3, tolScale,
|
||||
quads, sublevel + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void convert_cubic_to_quads(const SkPoint p[4], PtArray* quads) {
|
||||
void convert_cubic_to_quads(const SkPoint p[4],
|
||||
SkScalar tolScale,
|
||||
PtArray* quads) {
|
||||
SkPoint chopped[13];
|
||||
int count = SkChopCubicAtInflections(p, chopped);
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
SkPoint* cubic = chopped + 3*i;
|
||||
convert_noninflect_cubic_to_quads(cubic, quads);
|
||||
convert_noninflect_cubic_to_quads(cubic, tolScale, quads);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,74 +272,117 @@ int num_quad_subdivs(const SkPoint p[3]) {
|
|||
}
|
||||
}
|
||||
|
||||
int get_lines_and_quads(const SkPath& path, const SkMatrix& m, GrIRect clip,
|
||||
PtArray* lines, PtArray* quads,
|
||||
IntArray* quadSubdivCnts) {
|
||||
/**
|
||||
* Generates the lines and quads to be rendered. Lines are always recorded in
|
||||
* device space. We will do a device space bloat to account for the 1pixel
|
||||
* thickness.
|
||||
* Quads are recorded in device space unless m contains
|
||||
* perspective, then in they are in src space. We do this because we will
|
||||
* subdivide large quads to reduce over-fill. This subdivision has to be
|
||||
* performed before applying the perspective matrix.
|
||||
*/
|
||||
int generate_lines_and_quads(const SkPath& path,
|
||||
const SkMatrix& m,
|
||||
const SkVector& translate,
|
||||
GrIRect clip,
|
||||
PtArray* lines,
|
||||
PtArray* quads,
|
||||
IntArray* quadSubdivCnts) {
|
||||
SkPath::Iter iter(path, false);
|
||||
|
||||
int totalQuadCount = 0;
|
||||
GrRect bounds;
|
||||
GrIRect ibounds;
|
||||
|
||||
bool persp = m.hasPerspective();
|
||||
|
||||
for (;;) {
|
||||
GrPoint pts[4];
|
||||
GrPoint devPts[4];
|
||||
GrPathCmd cmd = (GrPathCmd)iter.next(pts);
|
||||
switch (cmd) {
|
||||
case kMove_PathCmd:
|
||||
break;
|
||||
case kLine_PathCmd:
|
||||
m.mapPoints(pts,2);
|
||||
bounds.setBounds(pts, 2);
|
||||
SkPoint::Offset(pts, 2, translate);
|
||||
m.mapPoints(devPts, pts, 2);
|
||||
bounds.setBounds(devPts, 2);
|
||||
bounds.outset(SK_Scalar1, SK_Scalar1);
|
||||
bounds.roundOut(&ibounds);
|
||||
if (SkIRect::Intersects(clip, ibounds)) {
|
||||
lines->push_back() = pts[0];
|
||||
lines->push_back() = pts[1];
|
||||
lines->push_back() = devPts[0];
|
||||
lines->push_back() = devPts[1];
|
||||
}
|
||||
break;
|
||||
case kQuadratic_PathCmd: {
|
||||
bounds.setBounds(pts, 3);
|
||||
case kQuadratic_PathCmd:
|
||||
SkPoint::Offset(pts, 3, translate);
|
||||
m.mapPoints(devPts, pts, 3);
|
||||
bounds.setBounds(devPts, 3);
|
||||
bounds.outset(SK_Scalar1, SK_Scalar1);
|
||||
bounds.roundOut(&ibounds);
|
||||
if (SkIRect::Intersects(clip, ibounds)) {
|
||||
m.mapPoints(pts, 3);
|
||||
int subdiv = num_quad_subdivs(pts);
|
||||
int subdiv = num_quad_subdivs(devPts);
|
||||
GrAssert(subdiv >= -1);
|
||||
if (-1 == subdiv) {
|
||||
lines->push_back() = pts[0];
|
||||
lines->push_back() = pts[1];
|
||||
lines->push_back() = pts[1];
|
||||
lines->push_back() = pts[2];
|
||||
lines->push_back() = devPts[0];
|
||||
lines->push_back() = devPts[1];
|
||||
lines->push_back() = devPts[1];
|
||||
lines->push_back() = devPts[2];
|
||||
} else {
|
||||
quads->push_back() = pts[0];
|
||||
quads->push_back() = pts[1];
|
||||
quads->push_back() = pts[2];
|
||||
// when in perspective keep quads in src space
|
||||
SkPoint* qPts = persp ? pts : devPts;
|
||||
quads->push_back() = qPts[0];
|
||||
quads->push_back() = qPts[1];
|
||||
quads->push_back() = qPts[2];
|
||||
quadSubdivCnts->push_back() = subdiv;
|
||||
totalQuadCount += 1 << subdiv;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case kCubic_PathCmd: {
|
||||
bounds.setBounds(pts, 4);
|
||||
break;
|
||||
case kCubic_PathCmd:
|
||||
SkPoint::Offset(pts, 4, translate);
|
||||
m.mapPoints(devPts, pts, 4);
|
||||
bounds.setBounds(devPts, 4);
|
||||
bounds.outset(SK_Scalar1, SK_Scalar1);
|
||||
bounds.roundOut(&ibounds);
|
||||
if (SkIRect::Intersects(clip, ibounds)) {
|
||||
m.mapPoints(pts, 4);
|
||||
SkPoint stackStorage[32];
|
||||
PtArray q((void*)stackStorage, 32);
|
||||
convert_cubic_to_quads(pts, &q);
|
||||
// in perspective have to do conversion in src space
|
||||
if (persp) {
|
||||
SkScalar tolScale =
|
||||
GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m,
|
||||
path.getBounds());
|
||||
convert_cubic_to_quads(pts, tolScale, &q);
|
||||
} else {
|
||||
convert_cubic_to_quads(devPts, SK_Scalar1, &q);
|
||||
}
|
||||
for (int i = 0; i < q.count(); i += 3) {
|
||||
bounds.setBounds(&q[i], 3);
|
||||
SkPoint* qInDevSpace;
|
||||
// bounds has to be calculated in device space, but q is
|
||||
// in src space when there is perspective.
|
||||
if (persp) {
|
||||
m.mapPoints(devPts, &q[i], 3);
|
||||
bounds.setBounds(devPts, 3);
|
||||
qInDevSpace = devPts;
|
||||
} else {
|
||||
bounds.setBounds(&q[i], 3);
|
||||
qInDevSpace = &q[i];
|
||||
}
|
||||
bounds.outset(SK_Scalar1, SK_Scalar1);
|
||||
bounds.roundOut(&ibounds);
|
||||
if (SkIRect::Intersects(clip, ibounds)) {
|
||||
int subdiv = num_quad_subdivs(&q[i]);
|
||||
int subdiv = num_quad_subdivs(qInDevSpace);
|
||||
GrAssert(subdiv >= -1);
|
||||
if (-1 == subdiv) {
|
||||
lines->push_back() = q[0 + i];
|
||||
lines->push_back() = q[1 + i];
|
||||
lines->push_back() = q[1 + i];
|
||||
lines->push_back() = q[2 + i];
|
||||
// lines should always be in device coords
|
||||
lines->push_back() = qInDevSpace[0];
|
||||
lines->push_back() = qInDevSpace[1];
|
||||
lines->push_back() = qInDevSpace[1];
|
||||
lines->push_back() = qInDevSpace[2];
|
||||
} else {
|
||||
// q is already in src space when there is no
|
||||
// perspective and dev coords otherwise.
|
||||
quads->push_back() = q[0 + i];
|
||||
quads->push_back() = q[1 + i];
|
||||
quads->push_back() = q[2 + i];
|
||||
|
@ -344,7 +392,7 @@ int get_lines_and_quads(const SkPath& path, const SkMatrix& m, GrIRect clip,
|
|||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
break;
|
||||
case kClose_PathCmd:
|
||||
break;
|
||||
case kEnd_PathCmd:
|
||||
|
@ -387,11 +435,38 @@ void intersect_lines(const SkPoint& ptA, const SkVector& normA,
|
|||
result->fY = SkScalarMul(result->fY, wInv);
|
||||
}
|
||||
|
||||
void bloat_quad(const SkPoint qpts[3], Vertex verts[kVertsPerQuad]) {
|
||||
void bloat_quad(const SkPoint qpts[3], const GrMatrix* toDevice,
|
||||
const GrMatrix* toSrc, Vertex verts[kVertsPerQuad]) {
|
||||
GrAssert(!toDevice == !toSrc);
|
||||
// original quad is specified by tri a,b,c
|
||||
const SkPoint& a = qpts[0];
|
||||
const SkPoint& b = qpts[1];
|
||||
const SkPoint& c = qpts[2];
|
||||
SkPoint a = qpts[0];
|
||||
SkPoint b = qpts[1];
|
||||
SkPoint c = qpts[2];
|
||||
|
||||
// compute a matrix that goes from device coords to U,V quad params
|
||||
// this should be in the src space, not dev coords, when we have perspective
|
||||
SkMatrix DevToUV;
|
||||
DevToUV.setAll(a.fX, b.fX, c.fX,
|
||||
a.fY, b.fY, c.fY,
|
||||
SK_Scalar1, SK_Scalar1, SK_Scalar1);
|
||||
DevToUV.invert(&DevToUV);
|
||||
// can't make this static, no cons :(
|
||||
SkMatrix UVpts;
|
||||
UVpts.setAll(0, SK_ScalarHalf, SK_Scalar1,
|
||||
0, 0, SK_Scalar1,
|
||||
SK_Scalar1, SK_Scalar1, SK_Scalar1);
|
||||
DevToUV.postConcat(UVpts);
|
||||
|
||||
// We really want to avoid perspective matrix muls.
|
||||
// These may wind up really close to zero
|
||||
DevToUV.setPerspX(0);
|
||||
DevToUV.setPerspY(0);
|
||||
|
||||
if (toDevice) {
|
||||
toDevice->mapPoints(&a, 1);
|
||||
toDevice->mapPoints(&b, 1);
|
||||
toDevice->mapPoints(&c, 1);
|
||||
}
|
||||
// make a new poly where we replace a and c by a 1-pixel wide edges orthog
|
||||
// to edges ab and bc:
|
||||
//
|
||||
|
@ -410,24 +485,6 @@ void bloat_quad(const SkPoint qpts[3], Vertex verts[kVertsPerQuad]) {
|
|||
Vertex& c0 = verts[3];
|
||||
Vertex& c1 = verts[4];
|
||||
|
||||
// compute a matrix that goes from device coords to U,V quad params
|
||||
SkMatrix DevToUV;
|
||||
DevToUV.setAll(a.fX, b.fX, c.fX,
|
||||
a.fY, b.fY, c.fY,
|
||||
SK_Scalar1, SK_Scalar1, SK_Scalar1);
|
||||
DevToUV.invert(&DevToUV);
|
||||
// can't make this static, no cons :(
|
||||
SkMatrix UVpts;
|
||||
UVpts.setAll(0, SK_ScalarHalf, SK_Scalar1,
|
||||
0, 0, SK_Scalar1,
|
||||
SK_Scalar1, SK_Scalar1, SK_Scalar1);
|
||||
DevToUV.postConcat(UVpts);
|
||||
|
||||
// We really want to avoid perspective matrix muls.
|
||||
// These may wind up really close to zero
|
||||
DevToUV.setPerspX(0);
|
||||
DevToUV.setPerspY(0);
|
||||
|
||||
SkVector ab = b;
|
||||
ab -= a;
|
||||
SkVector ac = c;
|
||||
|
@ -464,27 +521,33 @@ void bloat_quad(const SkPoint qpts[3], Vertex verts[kVertsPerQuad]) {
|
|||
|
||||
intersect_lines(a0.fPos, abN, c0.fPos, cbN, &b0.fPos);
|
||||
|
||||
if (toSrc) {
|
||||
toSrc->mapPointsWithStride(&verts[0].fPos, sizeof(Vertex), kVertsPerQuad);
|
||||
}
|
||||
DevToUV.mapPointsWithStride(&verts[0].fQuadCoord,
|
||||
&verts[0].fPos, sizeof(Vertex), kVertsPerQuad);
|
||||
}
|
||||
|
||||
void add_quads(const SkPoint p[3],
|
||||
int subdiv,
|
||||
const GrMatrix* toDevice,
|
||||
const GrMatrix* toSrc,
|
||||
Vertex** vert) {
|
||||
GrAssert(subdiv >= 0);
|
||||
if (subdiv) {
|
||||
SkPoint newP[5];
|
||||
SkChopQuadAtHalf(p, newP);
|
||||
add_quads(newP + 0, subdiv-1, vert);
|
||||
add_quads(newP + 2, subdiv-1, vert);
|
||||
add_quads(newP + 0, subdiv-1, toDevice, toSrc, vert);
|
||||
add_quads(newP + 2, subdiv-1, toDevice, toSrc, vert);
|
||||
} else {
|
||||
bloat_quad(p, *vert);
|
||||
bloat_quad(p, toDevice, toSrc, *vert);
|
||||
*vert += kVertsPerQuad;
|
||||
}
|
||||
}
|
||||
|
||||
void add_line(const SkPoint p[2],
|
||||
int rtHeight,
|
||||
const SkMatrix* toSrc,
|
||||
Vertex** vert) {
|
||||
const SkPoint& a = p[0];
|
||||
const SkPoint& b = p[1];
|
||||
|
@ -516,6 +579,11 @@ void add_line(const SkPoint p[2],
|
|||
(*vert)[i].fLine.fB = normal.fY;
|
||||
(*vert)[i].fLine.fC = lineC;
|
||||
}
|
||||
if (NULL != toSrc) {
|
||||
toSrc->mapPointsWithStride(&(*vert)->fPos,
|
||||
sizeof(Vertex),
|
||||
kVertsPerLineSeg);
|
||||
}
|
||||
} else {
|
||||
// just make it degenerate and likely offscreen
|
||||
(*vert)[0].fPos.set(SK_ScalarMax, SK_ScalarMax);
|
||||
|
@ -541,8 +609,12 @@ bool GrAAHairLinePathRenderer::createGeom(GrDrawTarget::StageBitfield stages) {
|
|||
clip.setLargest();
|
||||
}
|
||||
|
||||
// If none of the inputs that affect generation of path geometry have
|
||||
// have changed since last previous path draw then we can reuse the
|
||||
// previous geoemtry.
|
||||
if (stages == fPreviousStages &&
|
||||
fPreviousViewMatrix == fTarget->getViewMatrix() &&
|
||||
fPreviousTranslate == fTranslate &&
|
||||
rtHeight == fPreviousRTHeight &&
|
||||
fClipRect == clip) {
|
||||
return true;
|
||||
|
@ -556,15 +628,14 @@ bool GrAAHairLinePathRenderer::createGeom(GrDrawTarget::StageBitfield stages) {
|
|||
}
|
||||
|
||||
GrMatrix viewM = fTarget->getViewMatrix();
|
||||
viewM.preTranslate(fTranslate.fX, fTranslate.fY);
|
||||
|
||||
GrAlignedSTStorage<128, GrPoint> lineStorage;
|
||||
GrAlignedSTStorage<128, GrPoint> quadStorage;
|
||||
PtArray lines(&lineStorage);
|
||||
PtArray quads(&quadStorage);
|
||||
IntArray qSubdivs;
|
||||
fQuadCnt = get_lines_and_quads(*fPath, viewM, clip,
|
||||
&lines, &quads, &qSubdivs);
|
||||
fQuadCnt = generate_lines_and_quads(*fPath, viewM, fTranslate, clip,
|
||||
&lines, &quads, &qSubdivs);
|
||||
|
||||
fLineSegmentCnt = lines.count() / 2;
|
||||
int vertCnt = kVertsPerLineSeg * fLineSegmentCnt + kVertsPerQuad * fQuadCnt;
|
||||
|
@ -575,38 +646,53 @@ bool GrAAHairLinePathRenderer::createGeom(GrDrawTarget::StageBitfield stages) {
|
|||
if (!fTarget->reserveVertexSpace(layout, vertCnt, (void**)&verts)) {
|
||||
return false;
|
||||
}
|
||||
Vertex* base = verts;
|
||||
|
||||
const GrMatrix* toDevice = NULL;
|
||||
const GrMatrix* toSrc = NULL;
|
||||
GrMatrix ivm;
|
||||
|
||||
if (viewM.hasPerspective()) {
|
||||
if (viewM.invert(&ivm)) {
|
||||
toDevice = &viewM;
|
||||
toSrc = &ivm;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < fLineSegmentCnt; ++i) {
|
||||
add_line(&lines[2*i], rtHeight, &verts);
|
||||
add_line(&lines[2*i], rtHeight, toSrc, &verts);
|
||||
}
|
||||
|
||||
int unsubdivQuadCnt = quads.count() / 3;
|
||||
for (int i = 0; i < unsubdivQuadCnt; ++i) {
|
||||
GrAssert(qSubdivs[i] >= 0);
|
||||
add_quads(&quads[3*i], qSubdivs[i], &verts);
|
||||
add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts);
|
||||
}
|
||||
|
||||
fPreviousStages = stages;
|
||||
fPreviousViewMatrix = fTarget->getViewMatrix();
|
||||
fPreviousRTHeight = rtHeight;
|
||||
fClipRect = clip;
|
||||
fPreviousTranslate = fTranslate;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GrAAHairLinePathRenderer::drawPath(GrDrawTarget::StageBitfield stages) {
|
||||
GrDrawTarget::AutoStateRestore asr(fTarget);
|
||||
|
||||
GrMatrix ivm;
|
||||
|
||||
if (!this->createGeom(stages)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fTarget->getViewInverse(&ivm)) {
|
||||
fTarget->preConcatSamplerMatrices(stages, ivm);
|
||||
GrDrawTarget::AutoStateRestore asr;
|
||||
if (!fTarget->getViewMatrix().hasPerspective()) {
|
||||
asr.set(fTarget);
|
||||
GrMatrix ivm;
|
||||
if (fTarget->getViewInverse(&ivm)) {
|
||||
fTarget->preConcatSamplerMatrices(stages, ivm);
|
||||
}
|
||||
fTarget->setViewMatrix(GrMatrix::I());
|
||||
}
|
||||
|
||||
fTarget->setViewMatrix(GrMatrix::I());
|
||||
|
||||
// TODO: See whether rendering lines as degenerate quads improves perf
|
||||
// when we have a mix
|
||||
fTarget->setIndexSourceToBuffer(fLinesIndexBuffer);
|
||||
|
|
|
@ -47,6 +47,7 @@ private:
|
|||
// have to recreate geometry if stages in use changes :(
|
||||
GrDrawTarget::StageBitfield fPreviousStages;
|
||||
int fPreviousRTHeight;
|
||||
SkVector fPreviousTranslate;
|
||||
GrIRect fClipRect;
|
||||
|
||||
// this path renderer draws everything in device coordinates
|
||||
|
|
|
@ -147,6 +147,7 @@ int SkFindCubicInflections(const SkPoint src[4], SkScalar tValues[2]);
|
|||
|
||||
/** Return 1 for no chop, 2 for having chopped the cubic at a single
|
||||
inflection point, 3 for having chopped at 2 inflection points.
|
||||
dst will hold the resulting 1, 2, or 3 cubics.
|
||||
*/
|
||||
int SkChopCubicAtInflections(const SkPoint src[4], SkPoint dst[10]);
|
||||
|
||||
|
|
|
@ -189,6 +189,16 @@ struct SK_API SkPoint {
|
|||
}
|
||||
void setRectFan(SkScalar l, SkScalar t, SkScalar r, SkScalar b, size_t stride);
|
||||
|
||||
static void Offset(SkPoint points[], int count, const SkPoint& offset) {
|
||||
Offset(points, count, offset.fX, offset.fY);
|
||||
}
|
||||
|
||||
static void Offset(SkPoint points[], int count, SkScalar dx, SkScalar dy) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
points[i].offset(dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
void offset(SkScalar dx, SkScalar dy) {
|
||||
fX += dx;
|
||||
fY += dy;
|
||||
|
|
Загрузка…
Ссылка в новой задаче