/*
* Copyright 2014 Google Inc.
* Copyright 2017 ARM Ltd.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrSmallPathRenderer.h"
#include "GrBuffer.h"
#include "GrContext.h"
#include "GrDistanceFieldGenFromVector.h"
#include "GrDrawOpTest.h"
#include "GrOpFlushState.h"
#include "GrPipelineBuilder.h"
#include "GrResourceProvider.h"
#include "GrSWMaskHelper.h"
#include "GrSurfacePriv.h"
#include "GrSurfaceProxyPriv.h"
#include "GrTexturePriv.h"
#include "effects/GrBitmapTextGeoProc.h"
#include "effects/GrDistanceFieldGeoProc.h"
#include "ops/GrMeshDrawOp.h"
#include "SkAutoMalloc.h"
#include "SkDistanceFieldGen.h"
#include "SkPathOps.h"
#define ATLAS_TEXTURE_WIDTH 2048
#define ATLAS_TEXTURE_HEIGHT 2048
#define PLOT_WIDTH 512
#define PLOT_HEIGHT 256
#define NUM_PLOTS_X (ATLAS_TEXTURE_WIDTH / PLOT_WIDTH)
#define NUM_PLOTS_Y (ATLAS_TEXTURE_HEIGHT / PLOT_HEIGHT)
#ifdef DF_PATH_TRACKING
static int g_NumCachedShapes = 0;
static int g_NumFreedShapes = 0;
#endif
// mip levels
static const SkScalar kIdealMinMIP = 12;
static const SkScalar kMaxMIP = 162;
static const SkScalar kMaxDim = 73;
static const SkScalar kMinSize = SK_ScalarHalf;
static const SkScalar kMaxSize = 2*kMaxMIP;
// Callback to clear out internal path cache when eviction occurs
void GrSmallPathRenderer::HandleEviction(GrDrawOpAtlas::AtlasID id, void* pr) {
GrSmallPathRenderer* dfpr = (GrSmallPathRenderer*)pr;
// remove any paths that use this plot
ShapeDataList::Iter iter;
iter.init(dfpr->fShapeList, ShapeDataList::Iter::kHead_IterStart);
ShapeData* shapeData;
while ((shapeData = iter.get())) {
iter.next();
if (id == shapeData->fID) {
dfpr->fShapeCache.remove(shapeData->fKey);
dfpr->fShapeList.remove(shapeData);
delete shapeData;
#ifdef DF_PATH_TRACKING
++g_NumFreedPaths;
#endif
}
}
}
////////////////////////////////////////////////////////////////////////////////
GrSmallPathRenderer::GrSmallPathRenderer() : fAtlas(nullptr) {}
GrSmallPathRenderer::~GrSmallPathRenderer() {
ShapeDataList::Iter iter;
iter.init(fShapeList, ShapeDataList::Iter::kHead_IterStart);
ShapeData* shapeData;
while ((shapeData = iter.get())) {
iter.next();
delete shapeData;
}
#ifdef DF_PATH_TRACKING
SkDebugf("Cached shapes: %d, freed shapes: %d\n", g_NumCachedShapes, g_NumFreedShapes);
#endif
}
////////////////////////////////////////////////////////////////////////////////
bool GrSmallPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
if (!args.fShaderCaps->shaderDerivativeSupport()) {
return false;
}
// If the shape has no key then we won't get any reuse.
if (!args.fShape->hasUnstyledKey()) {
return false;
}
// This only supports filled paths, however, the caller may apply the style to make a filled
// path and try again.
if (!args.fShape->style().isSimpleFill()) {
return false;
}
// This does non-inverse coverage-based antialiased fills.
if (GrAAType::kCoverage != args.fAAType) {
return false;
}
// TODO: Support inverse fill
if (args.fShape->inverseFilled()) {
return false;
}
// currently don't support perspective
if (args.fViewMatrix->hasPerspective()) {
return false;
}
// Only support paths with bounds within kMaxDim by kMaxDim,
// scaled to have bounds within kMaxSize by kMaxSize.
// The goal is to accelerate rendering of lots of small paths that may be scaling.
SkScalar scaleFactors[2];
if (!args.fViewMatrix->getMinMaxScales(scaleFactors)) {
return false;
}
SkRect bounds = args.fShape->styledBounds();
SkScalar minDim = SkMinScalar(bounds.width(), bounds.height());
SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
SkScalar minSize = minDim * SkScalarAbs(scaleFactors[0]);
SkScalar maxSize = maxDim * SkScalarAbs(scaleFactors[1]);
return maxDim <= kMaxDim && kMinSize <= minSize && maxSize <= kMaxSize;
}
////////////////////////////////////////////////////////////////////////////////
// padding around path bounds to allow for antialiased pixels
static const SkScalar kAntiAliasPad = 1.0f;
class SmallPathOp final : public GrMeshDrawOp {
public:
DEFINE_OP_CLASS_ID
using ShapeData = GrSmallPathRenderer::ShapeData;
using ShapeCache = SkTDynamicHash<ShapeData, ShapeData::Key>;
using ShapeDataList = GrSmallPathRenderer::ShapeDataList;
static std::unique_ptr<GrMeshDrawOp> Make(GrColor color, const GrShape& shape,
const SkMatrix& viewMatrix, GrDrawOpAtlas* atlas,
ShapeCache* shapeCache, ShapeDataList* shapeList,
bool gammaCorrect) {
return std::unique_ptr<GrMeshDrawOp>(new SmallPathOp(color, shape, viewMatrix, atlas,
shapeCache, shapeList, gammaCorrect));
}
const char* name() const override { return "SmallPathOp"; }
SkString dumpInfo() const override {
SkString string;
for (const auto& geo : fShapes) {
string.appendf("Color: 0x%08x\n", geo.fColor);
}
string.append(DumpPipelineInfo(*this->pipeline()));
string.append(INHERITED::dumpInfo());
return string;
}
private:
SmallPathOp(GrColor color, const GrShape& shape, const SkMatrix& viewMatrix,
GrDrawOpAtlas* atlas, ShapeCache* shapeCache, ShapeDataList* shapeList,
bool gammaCorrect)
: INHERITED(ClassID()) {
SkASSERT(shape.hasUnstyledKey());
// Compute bounds
this->setTransformedBounds(shape.bounds(), viewMatrix, HasAABloat::kYes, IsZeroArea::kNo);
#if defined(SK_BUILD_FOR_ANDROID) && !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK)
fUsesDistanceField = true;
#else
// only use distance fields on desktop and Android framework to save space in the atlas
fUsesDistanceField = this->bounds().width() > kMaxMIP || this->bounds().height() > kMaxMIP;
#endif
fViewMatrix = viewMatrix;
SkVector translate = SkVector::Make(0, 0);
if (!fUsesDistanceField) {
// In this case we don't apply a view matrix, so we need to remove the non-subpixel
// translation and add it back when we generate the quad for the path
SkScalar translateX = viewMatrix.getTranslateX();
SkScalar translateY = viewMatrix.getTranslateY();
translate = SkVector::Make(SkScalarFloorToScalar(translateX),
SkScalarFloorToScalar(translateY));
// Only store the fractional part of the translation in the view matrix
fViewMatrix.setTranslateX(translateX - translate.fX);
fViewMatrix.setTranslateY(translateY - translate.fY);
}
fShapes.emplace_back(Entry{color, shape, translate});
fAtlas = atlas;
fShapeCache = shapeCache;
fShapeList = shapeList;
fGammaCorrect = gammaCorrect;
}
void getFragmentProcessorAnalysisInputs(GrPipelineAnalysisColor* color,
GrPipelineAnalysisCoverage* coverage) const override {
color->setToConstant(fShapes[0].fColor);
*coverage = GrPipelineAnalysisCoverage::kSingleChannel;
}
void applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) override {
optimizations.getOverrideColorIfSet(&fShapes[0].fColor);
fUsesLocalCoords = optimizations.readsLocalCoords();
}
struct FlushInfo {
sk_sp<const GrBuffer> fVertexBuffer;
sk_sp<const GrBuffer> fIndexBuffer;
sk_sp<GrGeometryProcessor> fGeometryProcessor;
int fVertexOffset;
int fInstancesToFlush;
};
void onPrepareDraws(Target* target) const override {
int instanceCount = fShapes.count();
const SkMatrix& ctm = this->viewMatrix();
FlushInfo flushInfo;
// Setup GrGeometryProcessor
GrDrawOpAtlas* atlas = fAtlas;
if (fUsesDistanceField) {
GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kBilerp_FilterMode);
uint32_t flags = 0;
flags |= ctm.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
flags |= ctm.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
flags |= fGammaCorrect ? kGammaCorrect_DistanceFieldEffectFlag : 0;
flushInfo.fGeometryProcessor = GrDistanceFieldPathGeoProc::Make(
atlas->context()->resourceProvider(),
this->color(), this->viewMatrix(), atlas->getProxy(), params, flags,
this->usesLocalCoords());
} else {
GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kNone_FilterMode);
SkMatrix invert;
if (this->usesLocalCoords()) {
if (!this->viewMatrix().invert(&invert)) {
SkDebugf("Could not invert viewmatrix\n");
return;
}
// for local coords, we need to add the translation back in that we removed
// from the stored view matrix
invert.preTranslate(-fShapes[0].fTranslate.fX, -fShapes[0].fTranslate.fY);
}
flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(
atlas->context()->resourceProvider(),
this->color(), atlas->getProxy(), params, kA8_GrMaskFormat, invert,
this->usesLocalCoords());
}
// allocate vertices
size_t vertexStride = flushInfo.fGeometryProcessor->getVertexStride();
SkASSERT(vertexStride == sizeof(SkPoint) + sizeof(GrColor) + 2*sizeof(uint16_t));
const GrBuffer* vertexBuffer;
void* vertices = target->makeVertexSpace(vertexStride,
kVerticesPerQuad * instanceCount,
&vertexBuffer,
&flushInfo.fVertexOffset);
flushInfo.fVertexBuffer.reset(SkRef(vertexBuffer));
flushInfo.fIndexBuffer.reset(target->resourceProvider()->refQuadIndexBuffer());
if (!vertices || !flushInfo.fIndexBuffer) {
SkDebugf("Could not allocate vertices\n");
return;
}
flushInfo.fInstancesToFlush = 0;
// Pointer to the next set of vertices to write.
intptr_t offset = reinterpret_cast<intptr_t>(vertices);
for (int i = 0; i < instanceCount; i++) {
const Entry& args = fShapes[i];
ShapeData* shapeData;
SkScalar maxScale;
if (fUsesDistanceField) {
// get mip level
maxScale = SkScalarAbs(this->viewMatrix().getMaxScale());
const SkRect& bounds = args.fShape.bounds();
SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
// We try to create the DF at a 2^n scaled path resolution (1/2, 1, 2, 4, etc.)
// In the majority of cases this will yield a crisper rendering.
SkScalar mipScale = 1.0f;
// Our mipscale is the maxScale clamped to the next highest power of 2
if (maxScale <= SK_ScalarHalf) {
SkScalar log = SkScalarFloorToScalar(SkScalarLog2(SkScalarInvert(maxScale)));
mipScale = SkScalarPow(2, -log);
} else if (maxScale > SK_Scalar1) {
SkScalar log = SkScalarCeilToScalar(SkScalarLog2(maxScale));
mipScale = SkScalarPow(2, log);
}
SkASSERT(maxScale <= mipScale);
SkScalar mipSize = mipScale*SkScalarAbs(maxDim);
// For sizes less than kIdealMinMIP we want to use as large a distance field as we can
// so we can preserve as much detail as possible. However, we can't scale down more
// than a 1/4 of the size without artifacts. So the idea is that we pick the mipsize
// just bigger than the ideal, and then scale down until we are no more than 4x the
// original mipsize.
if (mipSize < kIdealMinMIP) {
SkScalar newMipSize = mipSize;
do {
newMipSize *= 2;
} while (newMipSize < kIdealMinMIP);
while (newMipSize > 4 * mipSize) {
newMipSize *= 0.25f;
}
mipSize = newMipSize;
}
SkScalar desiredDimension = SkTMin(mipSize, kMaxMIP);
// check to see if df path is cached
ShapeData::Key key(args.fShape, SkScalarCeilToInt(desiredDimension));
shapeData = fShapeCache->find(key);
if (nullptr == shapeData || !atlas->hasID(shapeData->fID)) {
// Remove the stale cache entry
if (shapeData) {
fShapeCache->remove(shapeData->fKey);
fShapeList->remove(shapeData);
delete shapeData;
}
SkScalar scale = desiredDimension / maxDim;
shapeData = new ShapeData;
if (!this->addDFPathToAtlas(target,
&flushInfo,
atlas,
shapeData,
args.fShape,
SkScalarCeilToInt(desiredDimension),
scale)) {
delete shapeData;
SkDebugf("Can't rasterize path\n");
continue;
}
}
} else {
// check to see if bitmap path is cached
ShapeData::Key key(args.fShape, this->viewMatrix());
shapeData = fShapeCache->find(key);
if (nullptr == shapeData || !atlas->hasID(shapeData->fID)) {
// Remove the stale cache entry
if (shapeData) {
fShapeCache->remove(shapeData->fKey);
fShapeList->remove(shapeData);
delete shapeData;
}
shapeData = new ShapeData;
if (!this->addBMPathToAtlas(target,
&flushInfo,
atlas,
shapeData,
args.fShape,
this->viewMatrix())) {
delete shapeData;
SkDebugf("Can't rasterize path\n");
continue;
}
}
maxScale = 1;
}
atlas->setLastUseToken(shapeData->fID, target->nextDrawToken());
this->writePathVertices(target,
atlas,
offset,
args.fColor,
vertexStride,
maxScale,
args.fTranslate,
shapeData);
offset += kVerticesPerQuad * vertexStride;
flushInfo.fInstancesToFlush++;
}
this->flush(target, &flushInfo);
}
bool addDFPathToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo, GrDrawOpAtlas* atlas,
ShapeData* shapeData, const GrShape& shape, uint32_t dimension,
SkScalar scale) const {
const SkRect& bounds = shape.bounds();
// generate bounding rect for bitmap draw
SkRect scaledBounds = bounds;
// scale to mip level size
scaledBounds.fLeft *= scale;
scaledBounds.fTop *= scale;
scaledBounds.fRight *= scale;
scaledBounds.fBottom *= scale;
// subtract out integer portion of origin
// (SDF created will be placed with fractional offset burnt in)
SkScalar dx = SkScalarFloorToScalar(scaledBounds.fLeft);
SkScalar dy = SkScalarFloorToScalar(scaledBounds.fTop);
scaledBounds.offset(-dx, -dy);
// get integer boundary
SkIRect devPathBounds;
scaledBounds.roundOut(&devPathBounds);
// pad to allow room for antialiasing
const int intPad = SkScalarCeilToInt(kAntiAliasPad);
// place devBounds at origin
int width = devPathBounds.width() + 2*intPad;
int height = devPathBounds.height() + 2*intPad;
devPathBounds = SkIRect::MakeWH(width, height);
// draw path to bitmap
SkMatrix drawMatrix;
drawMatrix.setScale(scale, scale);
drawMatrix.postTranslate(intPad - dx, intPad - dy);
SkASSERT(devPathBounds.fLeft == 0);
SkASSERT(devPathBounds.fTop == 0);
SkASSERT(devPathBounds.width() > 0);
SkASSERT(devPathBounds.height() > 0);
// setup signed distance field storage
SkIRect dfBounds = devPathBounds.makeOutset(SK_DistanceFieldPad, SK_DistanceFieldPad);
width = dfBounds.width();
height = dfBounds.height();
// TODO We should really generate this directly into the plot somehow
SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char));
SkPath path;
shape.asPath(&path);
#ifndef SK_USE_LEGACY_DISTANCE_FIELDS
// Generate signed distance field directly from SkPath
bool succeed = GrGenerateDistanceFieldFromPath((unsigned char*)dfStorage.get(),
path, drawMatrix,
width, height, width * sizeof(unsigned char));
if (!succeed) {
#endif
// setup bitmap backing
SkAutoPixmapStorage dst;
if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(),
devPathBounds.height()))) {
return false;
}
sk_bzero(dst.writable_addr(), dst.getSafeSize());
// rasterize path
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setAntiAlias(true);
SkDraw draw;
sk_bzero(&draw, sizeof(draw));
SkRasterClip rasterClip;
rasterClip.setRect(devPathBounds);
draw.fRC = &rasterClip;
draw.fMatrix = &drawMatrix;
draw.fDst = dst;
draw.drawPathCoverage(path, paint);
// Generate signed distance field
SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(),
(const unsigned char*)dst.addr(),
dst.width(), dst.height(), dst.rowBytes());
#ifndef SK_USE_LEGACY_DISTANCE_FIELDS
}
#endif
// add to atlas
SkIPoint16 atlasLocation;
GrDrawOpAtlas::AtlasID id;
if (!atlas->addToAtlas(&id, target, width, height, dfStorage.get(), &atlasLocation)) {
this->flush(target, flushInfo);
if (!atlas->addToAtlas(&id, target, width, height, dfStorage.get(), &atlasLocation)) {
return false;
}
}
// add to cache
shapeData->fKey.set(shape, dimension);
shapeData->fID = id;
// set the bounds rect to the original bounds
shapeData->fBounds = bounds;
// set up path to texture coordinate transform
shapeData->fScale = scale;
dx -= SK_DistanceFieldPad + kAntiAliasPad;
dy -= SK_DistanceFieldPad + kAntiAliasPad;
shapeData->fTranslate.fX = atlasLocation.fX - dx;
shapeData->fTranslate.fY = atlasLocation.fY - dy;
fShapeCache->add(shapeData);
fShapeList->addToTail(shapeData);
#ifdef DF_PATH_TRACKING
++g_NumCachedPaths;
#endif
return true;
}
bool addBMPathToAtlas(GrMeshDrawOp::Target* target, FlushInfo* flushInfo,
GrDrawOpAtlas* atlas, ShapeData* shapeData,
const GrShape& shape, const SkMatrix& ctm) const {
const SkRect& bounds = shape.bounds();
if (bounds.isEmpty()) {
return false;
}
SkMatrix drawMatrix(ctm);
drawMatrix.set(SkMatrix::kMTransX, SkScalarFraction(ctm.get(SkMatrix::kMTransX)));
drawMatrix.set(SkMatrix::kMTransY, SkScalarFraction(ctm.get(SkMatrix::kMTransY)));
SkRect shapeDevBounds;
drawMatrix.mapRect(&shapeDevBounds, bounds);
SkScalar dx = SkScalarFloorToScalar(shapeDevBounds.fLeft);
SkScalar dy = SkScalarFloorToScalar(shapeDevBounds.fTop);
// get integer boundary
SkIRect devPathBounds;
shapeDevBounds.roundOut(&devPathBounds);
// pad to allow room for antialiasing
const int intPad = SkScalarCeilToInt(kAntiAliasPad);
// place devBounds at origin
int width = devPathBounds.width() + 2 * intPad;
int height = devPathBounds.height() + 2 * intPad;
devPathBounds = SkIRect::MakeWH(width, height);
SkScalar translateX = intPad - dx;
SkScalar translateY = intPad - dy;
SkASSERT(devPathBounds.fLeft == 0);
SkASSERT(devPathBounds.fTop == 0);
SkASSERT(devPathBounds.width() > 0);
SkASSERT(devPathBounds.height() > 0);
SkPath path;
shape.asPath(&path);
// setup bitmap backing
SkAutoPixmapStorage dst;
if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(),
devPathBounds.height()))) {
return false;
}
sk_bzero(dst.writable_addr(), dst.getSafeSize());
// rasterize path
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setAntiAlias(true);
SkDraw draw;
sk_bzero(&draw, sizeof(draw));
SkRasterClip rasterClip;
rasterClip.setRect(devPathBounds);
draw.fRC = &rasterClip;
drawMatrix.postTranslate(translateX, translateY);
draw.fMatrix = &drawMatrix;
draw.fDst = dst;
draw.drawPathCoverage(path, paint);
// add to atlas
SkIPoint16 atlasLocation;
GrDrawOpAtlas::AtlasID id;
if (!atlas->addToAtlas(&id, target, dst.width(), dst.height(), dst.addr(),
&atlasLocation)) {
this->flush(target, flushInfo);
if (!atlas->addToAtlas(&id, target, dst.width(), dst.height(), dst.addr(),
&atlasLocation)) {
return false;
}
}
// add to cache
shapeData->fKey.set(shape, ctm);
shapeData->fID = id;
// set the bounds rect to the original bounds
shapeData->fBounds = SkRect::Make(devPathBounds);
shapeData->fBounds.offset(-translateX, -translateY);
// set up path to texture coordinate transform
shapeData->fScale = SK_Scalar1;
shapeData->fTranslate.fX = atlasLocation.fX + translateX;
shapeData->fTranslate.fY = atlasLocation.fY + translateY;
fShapeCache->add(shapeData);
fShapeList->addToTail(shapeData);
#ifdef DF_PATH_TRACKING
++g_NumCachedPaths;
#endif
return true;
}
void writePathVertices(GrDrawOp::Target* target,
GrDrawOpAtlas* atlas,
intptr_t offset,
GrColor color,
size_t vertexStride,
SkScalar maxScale,
const SkVector& preTranslate,
const ShapeData* shapeData) const {
SkPoint* positions = reinterpret_cast<SkPoint*>(offset);
SkRect bounds = shapeData->fBounds;
if (fUsesDistanceField) {
// outset bounds to include ~1 pixel of AA in device space
SkScalar outset = SkScalarInvert(maxScale);
bounds.outset(outset, outset);
}
// vertex positions
// TODO make the vertex attributes a struct
positions->setRectFan(bounds.left() + preTranslate.fX,
bounds.top() + preTranslate.fY,
bounds.right() + preTranslate.fX,
bounds.bottom() + preTranslate.fY,
vertexStride);
// colors
for (int i = 0; i < kVerticesPerQuad; i++) {
GrColor* colorPtr = (GrColor*)(offset + sizeof(SkPoint) + i * vertexStride);
*colorPtr = color;
}
// set up texture coordinates
SkScalar texLeft = bounds.fLeft;
SkScalar texTop = bounds.fTop;
SkScalar texRight = bounds.fRight;
SkScalar texBottom = bounds.fBottom;
// transform original path's bounds to texture space
SkScalar scale = shapeData->fScale;
const SkVector& translate = shapeData->fTranslate;
texLeft *= scale;
texTop *= scale;
texRight *= scale;
texBottom *= scale;
texLeft += translate.fX;
texTop += translate.fY;
texRight += translate.fX;
texBottom += translate.fY;
// convert texcoords to unsigned short format
sk_sp<GrTextureProxy> proxy = atlas->getProxy();
// The proxy must be functionally exact for this normalization to work correctly
SkASSERT(GrResourceProvider::IsFunctionallyExact(proxy.get()));
SkScalar uFactor = 65535.f / proxy->width();
SkScalar vFactor = 65535.f / proxy->height();
uint16_t l = (uint16_t)(texLeft*uFactor);
uint16_t t = (uint16_t)(texTop*vFactor);
uint16_t r = (uint16_t)(texRight*uFactor);
uint16_t b = (uint16_t)(texBottom*vFactor);
// set vertex texture coords
intptr_t textureCoordOffset = offset + sizeof(SkPoint) + sizeof(GrColor);
uint16_t* textureCoords = (uint16_t*) textureCoordOffset;
textureCoords[0] = l;
textureCoords[1] = t;
textureCoordOffset += vertexStride;
textureCoords = (uint16_t*)textureCoordOffset;
textureCoords[0] = l;
textureCoords[1] = b;
textureCoordOffset += vertexStride;
textureCoords = (uint16_t*)textureCoordOffset;
textureCoords[0] = r;
textureCoords[1] = b;
textureCoordOffset += vertexStride;
textureCoords = (uint16_t*)textureCoordOffset;
textureCoords[0] = r;
textureCoords[1] = t;
}
void flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const {
if (flushInfo->fInstancesToFlush) {
GrMesh mesh;
int maxInstancesPerDraw =
static_cast<int>(flushInfo->fIndexBuffer->gpuMemorySize() / sizeof(uint16_t) / 6);
mesh.initInstanced(kTriangles_GrPrimitiveType, flushInfo->fVertexBuffer.get(),
flushInfo->fIndexBuffer.get(), flushInfo->fVertexOffset, kVerticesPerQuad,
kIndicesPerQuad, flushInfo->fInstancesToFlush, maxInstancesPerDraw);
target->draw(flushInfo->fGeometryProcessor.get(), mesh);
flushInfo->fVertexOffset += kVerticesPerQuad * flushInfo->fInstancesToFlush;
flushInfo->fInstancesToFlush = 0;
}
}
GrColor color() const { return fShapes[0].fColor; }
const SkMatrix& viewMatrix() const { return fViewMatrix; }
bool usesLocalCoords() const { return fUsesLocalCoords; }
bool usesDistanceField() const { return fUsesDistanceField; }
bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
SmallPathOp* that = t->cast<SmallPathOp>();
if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
that->bounds(), caps)) {
return false;
}
if (this->usesDistanceField() != that->usesDistanceField()) {
return false;
}
// TODO We can position on the cpu for distance field paths
if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
return false;
}
if (!this->usesDistanceField() && this->usesLocalCoords() &&
!this->fShapes[0].fTranslate.equalsWithinTolerance(that->fShapes[0].fTranslate)) {
return false;
}
fShapes.push_back_n(that->fShapes.count(), that->fShapes.begin());
this->joinBounds(*that);
return true;
}
SkMatrix fViewMatrix;
bool fUsesLocalCoords;
bool fUsesDistanceField;
struct Entry {
GrColor fColor;
GrShape fShape;
SkVector fTranslate;
};
SkSTArray<1, Entry> fShapes;
GrDrawOpAtlas* fAtlas;
ShapeCache* fShapeCache;
ShapeDataList* fShapeList;
bool fGammaCorrect;
typedef GrMeshDrawOp INHERITED;
};
bool GrSmallPathRenderer::onDrawPath(const DrawPathArgs& args) {
GR_AUDIT_TRAIL_AUTO_FRAME(args.fRenderTargetContext->auditTrail(),
"GrSmallPathRenderer::onDrawPath");
// we've already bailed on inverse filled paths, so this is safe
SkASSERT(!args.fShape->isEmpty());
SkASSERT(args.fShape->hasUnstyledKey());
if (!fAtlas) {
fAtlas = GrDrawOpAtlas::Make(args.fContext,
kAlpha_8_GrPixelConfig,
ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT,
NUM_PLOTS_X, NUM_PLOTS_Y,
&GrSmallPathRenderer::HandleEviction,
(void*)this);
if (!fAtlas) {
return false;
}
}
std::unique_ptr<GrMeshDrawOp> op = SmallPathOp::Make(
args.fPaint.getColor(), *args.fShape, *args.fViewMatrix, fAtlas.get(), &fShapeCache,
&fShapeList, args.fGammaCorrect);
GrPipelineBuilder pipelineBuilder(std::move(args.fPaint), args.fAAType);
pipelineBuilder.setUserStencil(args.fUserStencilSettings);
args.fRenderTargetContext->addMeshDrawOp(pipelineBuilder, *args.fClip, std::move(op));
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#if GR_TEST_UTILS
struct PathTestStruct {
typedef GrSmallPathRenderer::ShapeCache ShapeCache;
typedef GrSmallPathRenderer::ShapeData ShapeData;
typedef GrSmallPathRenderer::ShapeDataList ShapeDataList;
PathTestStruct() : fContextID(SK_InvalidGenID), fAtlas(nullptr) {}
~PathTestStruct() { this->reset(); }
void reset() {
ShapeDataList::Iter iter;
iter.init(fShapeList, ShapeDataList::Iter::kHead_IterStart);
ShapeData* shapeData;
while ((shapeData = iter.get())) {
iter.next();
fShapeList.remove(shapeData);
delete shapeData;
}
fAtlas = nullptr;
fShapeCache.reset();
}
static void HandleEviction(GrDrawOpAtlas::AtlasID id, void* pr) {
PathTestStruct* dfpr = (PathTestStruct*)pr;
// remove any paths that use this plot
ShapeDataList::Iter iter;
iter.init(dfpr->fShapeList, ShapeDataList::Iter::kHead_IterStart);
ShapeData* shapeData;
while ((shapeData = iter.get())) {
iter.next();
if (id == shapeData->fID) {
dfpr->fShapeCache.remove(shapeData->fKey);
dfpr->fShapeList.remove(shapeData);
delete shapeData;
}
}
}
uint32_t fContextID;
std::unique_ptr<GrDrawOpAtlas> fAtlas;
ShapeCache fShapeCache;
ShapeDataList fShapeList;
};
DRAW_OP_TEST_DEFINE(SmallPathOp) {
static PathTestStruct gTestStruct;
if (context->uniqueID() != gTestStruct.fContextID) {
gTestStruct.fContextID = context->uniqueID();
gTestStruct.reset();
gTestStruct.fAtlas = GrDrawOpAtlas::Make(context, kAlpha_8_GrPixelConfig,
ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT,
NUM_PLOTS_X, NUM_PLOTS_Y,
&PathTestStruct::HandleEviction,
(void*)&gTestStruct);
}
SkMatrix viewMatrix = GrTest::TestMatrix(random);
GrColor color = GrRandomColor(random);
bool gammaCorrect = random->nextBool();
// This path renderer only allows fill styles.
GrShape shape(GrTest::TestPath(random), GrStyle::SimpleFill());
return SmallPathOp::Make(color, shape, viewMatrix, gTestStruct.fAtlas.get(),
&gTestStruct.fShapeCache, &gTestStruct.fShapeList, gammaCorrect);
}
#endif