blob: 2c8520b28aeb9764ad2d603fb417af8ca9487ae6 [file] [log] [blame]
/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrTessellatingPathRenderer.h"
#include "GrBatchFlushState.h"
#include "GrBatchTest.h"
#include "GrDefaultGeoProcFactory.h"
#include "GrMesh.h"
#include "GrPathUtils.h"
#include "GrResourceCache.h"
#include "GrResourceProvider.h"
#include "GrTessellator.h"
#include "SkGeometry.h"
#include "batches/GrVertexBatch.h"
#include <stdio.h>
/*
* This path renderer tessellates the path into triangles using GrTessellator, uploads the triangles
* to a vertex buffer, and renders them with a single draw call. It does not currently do
* antialiasing, so it must be used in conjunction with multisampling.
*/
namespace {
struct TessInfo {
SkScalar fTolerance;
int fCount;
};
// When the SkPathRef genID changes, invalidate a corresponding GrResource described by key.
class PathInvalidator : public SkPathRef::GenIDChangeListener {
public:
explicit PathInvalidator(const GrUniqueKey& key) : fMsg(key) {}
private:
GrUniqueKeyInvalidatedMessage fMsg;
void onChange() override {
SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(fMsg);
}
};
bool cache_match(GrBuffer* vertexBuffer, SkScalar tol, int* actualCount) {
if (!vertexBuffer) {
return false;
}
const SkData* data = vertexBuffer->getUniqueKey().getCustomData();
SkASSERT(data);
const TessInfo* info = static_cast<const TessInfo*>(data->data());
if (info->fTolerance == 0 || info->fTolerance < 3.0f * tol) {
*actualCount = info->fCount;
return true;
}
return false;
}
class StaticVertexAllocator : public GrTessellator::VertexAllocator {
public:
StaticVertexAllocator(GrResourceProvider* resourceProvider, bool canMapVB)
: fResourceProvider(resourceProvider)
, fCanMapVB(canMapVB)
, fVertices(nullptr) {
}
SkPoint* lock(int vertexCount) override {
size_t size = vertexCount * sizeof(SkPoint);
fVertexBuffer.reset(fResourceProvider->createBuffer(
size, kVertex_GrBufferType, kStatic_GrAccessPattern, 0));
if (!fVertexBuffer.get()) {
return nullptr;
}
if (fCanMapVB) {
fVertices = static_cast<SkPoint*>(fVertexBuffer->map());
} else {
fVertices = new SkPoint[vertexCount];
}
return fVertices;
}
void unlock(int actualCount) override {
if (fCanMapVB) {
fVertexBuffer->unmap();
} else {
fVertexBuffer->updateData(fVertices, actualCount * sizeof(SkPoint));
delete[] fVertices;
}
fVertices = nullptr;
}
GrBuffer* vertexBuffer() { return fVertexBuffer.get(); }
private:
SkAutoTUnref<GrBuffer> fVertexBuffer;
GrResourceProvider* fResourceProvider;
bool fCanMapVB;
SkPoint* fVertices;
};
} // namespace
GrTessellatingPathRenderer::GrTessellatingPathRenderer() {
}
bool GrTessellatingPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
// This path renderer can draw all fill styles, all stroke styles except hairlines, but does
// not do antialiasing. It can do convex and concave paths, but we'll leave the convex ones to
// simpler algorithms.
return !IsStrokeHairlineOrEquivalent(*args.fStroke, *args.fViewMatrix, nullptr) &&
!args.fAntiAlias && !args.fPath->isConvex();
}
class TessellatingPathBatch : public GrVertexBatch {
public:
DEFINE_BATCH_CLASS_ID
static GrDrawBatch* Create(const GrColor& color,
const SkPath& path,
const GrStrokeInfo& stroke,
const SkMatrix& viewMatrix,
SkRect clipBounds) {
return new TessellatingPathBatch(color, path, stroke, viewMatrix, clipBounds);
}
const char* name() const override { return "TessellatingPathBatch"; }
void computePipelineOptimizations(GrInitInvariantOutput* color,
GrInitInvariantOutput* coverage,
GrBatchToXPOverrides* overrides) const override {
color->setKnownFourComponents(fColor);
coverage->setUnknownSingleComponent();
}
private:
void initBatchTracker(const GrXPOverridesForBatch& overrides) override {
// Handle any color overrides
if (!overrides.readsColor()) {
fColor = GrColor_ILLEGAL;
}
overrides.getOverrideColorIfSet(&fColor);
fPipelineInfo = overrides;
}
void draw(Target* target, const GrGeometryProcessor* gp) const {
// construct a cache key from the path's genID and the view matrix
static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
GrUniqueKey key;
int clipBoundsSize32 =
fPath.isInverseFillType() ? sizeof(fClipBounds) / sizeof(uint32_t) : 0;
int strokeDataSize32 = fStroke.computeUniqueKeyFragmentData32Cnt();
GrUniqueKey::Builder builder(&key, kDomain, 2 + clipBoundsSize32 + strokeDataSize32);
builder[0] = fPath.getGenerationID();
builder[1] = fPath.getFillType();
// For inverse fills, the tessellation is dependent on clip bounds.
if (fPath.isInverseFillType()) {
memcpy(&builder[2], &fClipBounds, sizeof(fClipBounds));
}
fStroke.asUniqueKeyFragment(&builder[2 + clipBoundsSize32]);
builder.finish();
GrResourceProvider* rp = target->resourceProvider();
SkAutoTUnref<GrBuffer> cachedVertexBuffer(rp->findAndRefTByUniqueKey<GrBuffer>(key));
int actualCount;
SkScalar screenSpaceTol = GrPathUtils::kDefaultTolerance;
SkScalar tol = GrPathUtils::scaleToleranceToSrc(
screenSpaceTol, fViewMatrix, fPath.getBounds());
if (cache_match(cachedVertexBuffer.get(), tol, &actualCount)) {
this->drawVertices(target, gp, cachedVertexBuffer.get(), 0, actualCount);
return;
}
SkPath path;
GrStrokeInfo stroke(fStroke);
if (stroke.isDashed()) {
if (!stroke.applyDashToPath(&path, &stroke, fPath)) {
return;
}
} else {
path = fPath;
}
if (!stroke.isFillStyle()) {
stroke.setResScale(SkScalarAbs(fViewMatrix.getMaxScale()));
if (!stroke.applyToPath(&path, path)) {
return;
}
stroke.setFillStyle();
}
bool isLinear;
bool canMapVB = GrCaps::kNone_MapFlags != target->caps().mapBufferFlags();
StaticVertexAllocator allocator(rp, canMapVB);
int count = GrTessellator::PathToTriangles(path, tol, fClipBounds, &allocator, &isLinear);
if (count == 0) {
return;
}
this->drawVertices(target, gp, allocator.vertexBuffer(), 0, count);
if (!fPath.isVolatile()) {
TessInfo info;
info.fTolerance = isLinear ? 0 : tol;
info.fCount = count;
SkAutoTUnref<SkData> data(SkData::NewWithCopy(&info, sizeof(info)));
key.setCustomData(data.get());
rp->assignUniqueKeyToResource(key, allocator.vertexBuffer());
SkPathPriv::AddGenIDChangeListener(fPath, new PathInvalidator(key));
}
}
void onPrepareDraws(Target* target) const override {
SkAutoTUnref<const GrGeometryProcessor> gp;
{
using namespace GrDefaultGeoProcFactory;
Color color(fColor);
LocalCoords localCoords(fPipelineInfo.readsLocalCoords() ?
LocalCoords::kUsePosition_Type :
LocalCoords::kUnused_Type);
Coverage::Type coverageType;
if (fPipelineInfo.readsCoverage()) {
coverageType = Coverage::kSolid_Type;
} else {
coverageType = Coverage::kNone_Type;
}
Coverage coverage(coverageType);
gp.reset(GrDefaultGeoProcFactory::Create(color, coverage, localCoords,
fViewMatrix));
}
this->draw(target, gp.get());
}
void drawVertices(Target* target, const GrGeometryProcessor* gp, const GrBuffer* vb,
int firstVertex, int count) const {
SkASSERT(gp->getVertexStride() == sizeof(SkPoint));
GrPrimitiveType primitiveType = TESSELLATOR_WIREFRAME ? kLines_GrPrimitiveType
: kTriangles_GrPrimitiveType;
GrMesh mesh;
mesh.init(primitiveType, vb, firstVertex, count);
target->draw(gp, mesh);
}
bool onCombineIfPossible(GrBatch*, const GrCaps&) override { return false; }
TessellatingPathBatch(const GrColor& color,
const SkPath& path,
const GrStrokeInfo& stroke,
const SkMatrix& viewMatrix,
const SkRect& clipBounds)
: INHERITED(ClassID())
, fColor(color)
, fPath(path)
, fStroke(stroke)
, fViewMatrix(viewMatrix) {
const SkRect& pathBounds = path.getBounds();
fClipBounds = clipBounds;
// Because the clip bounds are used to add a contour for inverse fills, they must also
// include the path bounds.
fClipBounds.join(pathBounds);
if (path.isInverseFillType()) {
fBounds = fClipBounds;
} else {
fBounds = path.getBounds();
}
SkScalar radius = stroke.getInflationRadius();
fBounds.outset(radius, radius);
viewMatrix.mapRect(&fBounds);
}
GrColor fColor;
SkPath fPath;
GrStrokeInfo fStroke;
SkMatrix fViewMatrix;
SkRect fClipBounds; // in source space
GrXPOverridesForBatch fPipelineInfo;
typedef GrVertexBatch INHERITED;
};
bool GrTessellatingPathRenderer::onDrawPath(const DrawPathArgs& args) {
GR_AUDIT_TRAIL_AUTO_FRAME(args.fTarget->getAuditTrail(),
"GrTessellatingPathRenderer::onDrawPath");
SkASSERT(!args.fAntiAlias);
const GrRenderTarget* rt = args.fPipelineBuilder->getRenderTarget();
if (nullptr == rt) {
return false;
}
SkIRect clipBoundsI;
args.fPipelineBuilder->clip().getConservativeBounds(rt->width(), rt->height(), &clipBoundsI);
SkRect clipBounds = SkRect::Make(clipBoundsI);
SkMatrix vmi;
if (!args.fViewMatrix->invert(&vmi)) {
return false;
}
vmi.mapRect(&clipBounds);
SkAutoTUnref<GrDrawBatch> batch(TessellatingPathBatch::Create(args.fColor, *args.fPath,
*args.fStroke, *args.fViewMatrix,
clipBounds));
args.fTarget->drawBatch(*args.fPipelineBuilder, batch);
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef GR_TEST_UTILS
DRAW_BATCH_TEST_DEFINE(TesselatingPathBatch) {
GrColor color = GrRandomColor(random);
SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
SkPath path = GrTest::TestPath(random);
SkRect clipBounds = GrTest::TestRect(random);
SkMatrix vmi;
bool result = viewMatrix.invert(&vmi);
if (!result) {
SkFAIL("Cannot invert matrix\n");
}
vmi.mapRect(&clipBounds);
GrStrokeInfo strokeInfo = GrTest::TestStrokeInfo(random);
return TessellatingPathBatch::Create(color, path, strokeInfo, viewMatrix, clipBounds);
}
#endif