Implement inverse fill support in GrTessellationPathRenderer

Bug: skia:10419
Bug: skia:11396
Change-Id: I489236ec5dff59b0e7c4c4e22b83b2b5ec4e9a26
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/421796
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
This commit is contained in:
Chris Dalton 2021-06-25 14:52:49 -06:00 committed by Skia Commit-Bot
parent 1bddd42b98
commit baae2dd7fb
9 changed files with 208 additions and 126 deletions

View File

@ -10,6 +10,7 @@
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrOpsRenderPass.h"
#include "src/gpu/GrProgramInfo.h"
#include "src/gpu/GrVertexWriter.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
#include "src/gpu/glsl/GrGLSLVarying.h"
@ -17,25 +18,27 @@
namespace {
constexpr static GrGeometryProcessor::Attribute kInstanceAttribs[] = {
{"dev_xywh", kInt4_GrVertexAttribType, kInt4_GrSLType},
{"atlas_xy", kInt2_GrVertexAttribType, kInt2_GrSLType},
{"color", kFloat4_GrVertexAttribType, kHalf4_GrSLType},
{"viewmatrix_scaleskew", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
{"viewmatrix_trans", kFloat2_GrVertexAttribType, kFloat2_GrSLType}};
class DrawAtlasPathShader : public GrGeometryProcessor {
public:
DrawAtlasPathShader(const GrTextureProxy* atlasProxy, GrSwizzle swizzle, bool usesLocalCoords)
DrawAtlasPathShader(const GrTextureProxy* atlasProxy, GrSwizzle swizzle, bool isInverseFill,
bool usesLocalCoords)
: GrGeometryProcessor(kDrawAtlasPathShader_ClassID)
, fAtlasAccess(GrSamplerState::Filter::kNearest, atlasProxy->backendFormat(), swizzle)
, fAtlasDimensions(atlasProxy->backingStoreDimensions())
, fIsInverseFill(isInverseFill)
, fUsesLocalCoords(usesLocalCoords) {
int numInstanceAttribs = SK_ARRAY_COUNT(kInstanceAttribs);
if (!fUsesLocalCoords) {
numInstanceAttribs -= 2;
fAttribs.emplace_back("dev_xywh", kInt4_GrVertexAttribType, kInt4_GrSLType);
fAttribs.emplace_back("atlas_xy", kInt2_GrVertexAttribType, kInt2_GrSLType);
fAttribs.emplace_back("color", kFloat4_GrVertexAttribType, kHalf4_GrSLType);
if (fIsInverseFill) {
fAttribs.emplace_back("drawbounds", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
}
this->setInstanceAttributes(kInstanceAttribs, numInstanceAttribs);
if (fUsesLocalCoords) {
fAttribs.emplace_back("viewmatrix_scaleskew", kFloat4_GrVertexAttribType,
kFloat4_GrSLType);
fAttribs.emplace_back("viewmatrix_trans", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
}
this->setInstanceAttributes(fAttribs.data(), fAttribs.count());
this->setTextureSamplerCnt(1);
}
@ -49,7 +52,9 @@ private:
const TextureSampler fAtlasAccess;
const SkISize fAtlasDimensions;
const bool fIsInverseFill;
const bool fUsesLocalCoords;
SkSTArray<6, GrGeometryProcessor::Attribute> fAttribs;
class Impl;
};
@ -65,7 +70,7 @@ class DrawAtlasPathShader::Impl : public GrGLSLGeometryProcessor {
GrGLSLVarying color(kHalf4_GrSLType);
args.fFragBuilder->codeAppendf("half4 %s;", args.fOutputColor);
args.fVaryingHandler->addPassThroughAttribute(
kInstanceAttribs[2], args.fOutputColor,
shader.fAttribs[2], args.fOutputColor,
GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
const char* atlasAdjust;
@ -73,29 +78,61 @@ class DrawAtlasPathShader::Impl : public GrGLSLGeometryProcessor {
nullptr, kVertex_GrShaderFlag, kFloat2_GrSLType, "atlas_adjust", &atlasAdjust);
args.fVertBuilder->codeAppendf(R"(
float2 T = float2(sk_VertexID & 1, sk_VertexID >> 1);
float2 devtopleft = float2(dev_xywh.xy);
float2 devcoord = abs(float2(dev_xywh.zw)) * T + devtopleft;
float2 atlascoord = devcoord - devtopleft;
if (dev_xywh.w < 0) { // Negative height indicates that the path is transposed.
atlascoord = atlascoord.yx;
}
atlascoord += float2(atlas_xy);
%s = atlascoord * %s;)",
atlasCoord.vsOut(), atlasAdjust);
float2 T = float2(sk_VertexID & 1, sk_VertexID >> 1);
float2 devtopleft = float2(dev_xywh.xy);)");
if (shader.fIsInverseFill) {
args.fVertBuilder->codeAppendf(R"(
float2 devcoord = mix(drawbounds.xy, drawbounds.zw, T);)");
} else {
args.fVertBuilder->codeAppendf(R"(
float2 devcoord = abs(float2(dev_xywh.zw)) * T + devtopleft;)");
}
args.fVertBuilder->codeAppendf(R"(
float2 atlascoord = devcoord - devtopleft;
bool transposed = dev_xywh.w < 0; // Negative height means the path is transposed.
if (transposed) {
atlascoord = atlascoord.yx;
}
atlascoord += float2(atlas_xy);
%s = atlascoord * %s;)",
atlasCoord.vsOut(), atlasAdjust);
gpArgs->fPositionVar.set(kFloat2_GrSLType, "devcoord");
if (shader.fUsesLocalCoords) {
args.fVertBuilder->codeAppendf(R"(
float2x2 M = float2x2(viewmatrix_scaleskew);
float2 localcoord = inverse(M) * (devcoord - viewmatrix_trans);)");
float2x2 M = float2x2(viewmatrix_scaleskew);
float2 localcoord = inverse(M) * (devcoord - viewmatrix_trans);)");
gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "localcoord");
}
args.fFragBuilder->codeAppendf("half4 %s = ", args.fOutputCoverage);
args.fFragBuilder->appendTextureLookup(args.fTexSamplers[0], atlasCoord.fsIn());
args.fFragBuilder->codeAppendf(".aaaa;");
if (shader.fIsInverseFill) {
GrGLSLVarying atlasBounds(kFloat4_GrSLType);
args.fVaryingHandler->addVarying("atlasbounds", &atlasBounds,
GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
args.fVertBuilder->codeAppendf(R"(
int2 atlas_wh = (transposed) ? abs(dev_xywh.wz) : dev_xywh.zw;
%s = float4(atlas_xy, atlas_xy + atlas_wh) * %s.xyxy;)", atlasBounds.vsOut(),
atlasAdjust);
args.fFragBuilder->codeAppendf(R"(
half coverage = 0;
float2 atlascoord = %s;
float4 atlasbounds = %s;
if (all(greaterThan(atlascoord, atlasbounds.xy)) &&
all(lessThan(atlascoord, atlasbounds.zw))) {
coverage = )", atlasCoord.fsIn(), atlasBounds.fsIn());
args.fFragBuilder->appendTextureLookup(args.fTexSamplers[0], "atlascoord");
args.fFragBuilder->codeAppendf(R"(.a;
}
half4 %s = half4(1 - coverage);)", args.fOutputCoverage);
} else {
args.fFragBuilder->codeAppendf("half4 %s = ", args.fOutputCoverage);
args.fFragBuilder->appendTextureLookup(args.fTexSamplers[0], atlasCoord.fsIn());
args.fFragBuilder->codeAppendf(".aaaa;");
}
}
void setData(const GrGLSLProgramDataManager& pdman,
@ -117,8 +154,8 @@ GrGLSLGeometryProcessor* DrawAtlasPathShader::createGLSLInstance(const GrShaderC
GrProcessorSet::Analysis GrDrawAtlasPathOp::finalize(const GrCaps& caps, const GrAppliedClip* clip,
GrClampType clampType) {
const GrProcessorSet::Analysis& analysis = fProcessors.finalize(
fInstanceList.fInstance.fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip,
&GrUserStencilSettings::kUnused, caps, clampType, &fInstanceList.fInstance.fColor);
fHeadInstance.fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip,
&GrUserStencilSettings::kUnused, caps, clampType, &fHeadInstance.fColor);
fUsesLocalCoords = analysis.usesLocalCoords();
return analysis;
}
@ -129,40 +166,27 @@ GrOp::CombineResult GrDrawAtlasPathOp::onCombineIfPossible(
SkASSERT(fAtlasProxy == that->fAtlasProxy);
SkASSERT(fEnableHWAA == that->fEnableHWAA);
if (fProcessors != that->fProcessors) {
if (fIsInverseFill != that->fIsInverseFill || fProcessors != that->fProcessors) {
return CombineResult::kCannotCombine;
}
SkASSERT(fUsesLocalCoords == that->fUsesLocalCoords);
auto* copy = alloc->make<InstanceList>(that->fInstanceList);
*fInstanceTail = copy;
fInstanceTail = (!copy->fNext) ? &copy->fNext : that->fInstanceTail;
auto* copy = alloc->make<Instance>(that->fHeadInstance);
*fTailInstance = copy;
fTailInstance = (!copy->fNext) ? &copy->fNext : that->fTailInstance;
fInstanceCount += that->fInstanceCount;
return CombineResult::kMerged;
}
void GrDrawAtlasPathOp::onPrePrepare(GrRecordingContext*,
const GrSurfaceProxyView& writeView,
GrAppliedClip*,
const GrDstProxyView&,
void GrDrawAtlasPathOp::onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView& writeView,
GrAppliedClip*, const GrDstProxyView&,
GrXferBarrierFlags renderPassXferBarriers,
GrLoadOp colorLoadOp) {}
void GrDrawAtlasPathOp::onPrepare(GrOpFlushState* state) {
size_t instanceStride = Instance::Stride(fUsesLocalCoords);
if (char* instanceData = (char*)state->makeVertexSpace(
instanceStride, fInstanceCount, &fInstanceBuffer, &fBaseInstance)) {
SkDEBUGCODE(char* end = instanceData + fInstanceCount * instanceStride);
for (const InstanceList* list = &fInstanceList; list; list = list->fNext) {
memcpy(instanceData, &list->fInstance, instanceStride);
instanceData += instanceStride;
}
SkASSERT(instanceData == end);
}
GrLoadOp colorLoadOp) {
SK_ABORT("DDL support not implemented for GrDrawAtlasPathOp.");
}
void GrDrawAtlasPathOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) {
SkASSERT(fAtlasProxy->isInstantiated());
void GrDrawAtlasPathOp::onPrepare(GrOpFlushState* state) {
SkArenaAlloc* arena = state->allocator();
GrPipeline::InitArgs initArgs;
if (fEnableHWAA) {
@ -171,20 +195,34 @@ void GrDrawAtlasPathOp::onExecute(GrOpFlushState* state, const SkRect& chainBoun
initArgs.fCaps = &state->caps();
initArgs.fDstProxyView = state->drawOpArgs().dstProxyView();
initArgs.fWriteSwizzle = state->drawOpArgs().writeView().swizzle();
GrPipeline pipeline(initArgs, std::move(fProcessors), state->detachAppliedClip());
auto pipeline = arena->make<GrPipeline>(initArgs, std::move(fProcessors),
state->detachAppliedClip());
GrSwizzle swizzle = state->caps().getReadSwizzle(fAtlasProxy->backendFormat(),
GrColorType::kAlpha_8);
auto shader = arena->make<DrawAtlasPathShader>(fAtlasProxy.get(), swizzle, fIsInverseFill,
fUsesLocalCoords);
fProgram = arena->make<GrProgramInfo>(state->writeView(), pipeline,
&GrUserStencilSettings::kUnused, shader,
GrPrimitiveType::kTriangleStrip, 0,
state->renderPassBarriers(), state->colorLoadOp());
DrawAtlasPathShader shader(fAtlasProxy.get(), swizzle, fUsesLocalCoords);
SkASSERT(shader.instanceStride() == Instance::Stride(fUsesLocalCoords));
if (GrVertexWriter instanceWriter = state->makeVertexSpace(
shader->instanceStride(), fInstanceCount, &fInstanceBuffer, &fBaseInstance)) {
for (const Instance* instance = &fHeadInstance; instance; instance = instance->fNext) {
instanceWriter.write(
instance->fDevXYWH,
instance->fAtlasXY,
instance->fColor,
GrVertexWriter::If(fIsInverseFill, instance->fDrawBoundsIfInverseFilled),
GrVertexWriter::If(fUsesLocalCoords, instance->fViewMatrixIfUsingLocalCoords));
}
}
}
GrProgramInfo programInfo(state->writeView(), &pipeline, &GrUserStencilSettings::kUnused,
&shader, GrPrimitiveType::kTriangleStrip, 0,
state->renderPassBarriers(), state->colorLoadOp());
state->bindPipelineAndScissorClip(programInfo, this->bounds());
state->bindTextures(shader, *fAtlasProxy, pipeline);
void GrDrawAtlasPathOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) {
SkASSERT(fAtlasProxy->isInstantiated());
state->bindPipelineAndScissorClip(*fProgram, this->bounds());
state->bindTextures(fProgram->geomProc(), *fAtlasProxy, fProgram->pipeline());
state->bindBuffers(nullptr, std::move(fInstanceBuffer), nullptr);
state->drawInstanced(fInstanceCount, fBaseInstance, 4, 0);
}

View File

@ -17,14 +17,16 @@ public:
GrDrawAtlasPathOp(int numRenderTargetSamples, sk_sp<GrTextureProxy> atlasProxy,
const SkIRect& devIBounds, const SkIPoint16& locationInAtlas,
bool transposedInAtlas, const SkMatrix& viewMatrix, GrPaint&& paint)
bool transposedInAtlas, const SkMatrix& viewMatrix,
GrPaint&& paint, const SkRect& drawBounds, bool isInverseFill)
: GrDrawOp(ClassID())
, fEnableHWAA(numRenderTargetSamples > 1)
, fIsInverseFill(isInverseFill)
, fAtlasProxy(std::move(atlasProxy))
, fInstanceList(devIBounds, locationInAtlas, transposedInAtlas, paint.getColor4f(),
viewMatrix)
, fHeadInstance(devIBounds, locationInAtlas, transposedInAtlas, paint.getColor4f(),
drawBounds, viewMatrix)
, fProcessors(std::move(paint)) {
this->setBounds(SkRect::Make(devIBounds), HasAABloat::kYes, IsHairline::kNo);
this->setBounds(drawBounds, HasAABloat::kYes, IsHairline::kNo);
}
const char* name() const override { return "GrDrawAtlasPathOp"; }
@ -49,20 +51,15 @@ private:
GrLoadOp colorLoadOp) override;
struct Instance {
constexpr static size_t Stride(bool usesLocalCoords) {
size_t stride = sizeof(Instance);
if (!usesLocalCoords) {
stride -= sizeof(Instance::fViewMatrixIfUsingLocalCoords);
}
return stride;
}
Instance(const SkIRect& devIBounds, const SkIPoint16& locationInAtlas,
bool transposedInAtlas, const SkPMColor4f& color, const SkMatrix& m)
bool transposedInAtlas, const SkPMColor4f& color, const SkRect& drawBounds,
const SkMatrix& m)
: fDevXYWH{devIBounds.left(), devIBounds.top(), devIBounds.width(),
// We use negative height to indicate that the path is transposed.
(transposedInAtlas) ? -devIBounds.height() : devIBounds.height()}
, fAtlasXY{locationInAtlas.x(), locationInAtlas.y()}
, fColor(color)
, fDrawBoundsIfInverseFilled(drawBounds)
, fViewMatrixIfUsingLocalCoords{m.getScaleX(), m.getSkewY(),
m.getSkewX(), m.getScaleY(),
m.getTranslateX(), m.getTranslateY()} {
@ -70,26 +67,22 @@ private:
std::array<int, 4> fDevXYWH;
std::array<int, 2> fAtlasXY;
SkPMColor4f fColor;
float fViewMatrixIfUsingLocalCoords[6];
};
struct InstanceList {
InstanceList(const SkIRect& devIBounds, const SkIPoint16& locationInAtlas,
bool transposedInAtlas, const SkPMColor4f& color, const SkMatrix& viewMatrix)
: fInstance(devIBounds, locationInAtlas, transposedInAtlas, color, viewMatrix) {
}
InstanceList* fNext = nullptr;
Instance fInstance;
SkRect fDrawBoundsIfInverseFilled;
std::array<float, 6> fViewMatrixIfUsingLocalCoords;
Instance* fNext = nullptr;
};
const bool fEnableHWAA;
const bool fIsInverseFill;
const sk_sp<GrTextureProxy> fAtlasProxy;
bool fUsesLocalCoords = false;
InstanceList fInstanceList;
InstanceList** fInstanceTail = &fInstanceList.fNext;
Instance fHeadInstance;
Instance** fTailInstance = &fHeadInstance.fNext;
int fInstanceCount = 1;
GrProgramInfo* fProgram = nullptr;
sk_sp<const GrBuffer> fInstanceBuffer;
int fBaseInstance;

View File

@ -195,7 +195,7 @@ void GrPathInnerTriangulateOp::prePreparePrograms(const GrTessellationShader::Pr
fPath.countVerbs(), *pipelineForStencils,
*args.fCaps);
const GrUserStencilSettings* stencilPathSettings =
GrPathTessellationShader::StencilPathSettings(fPath.getFillType());
GrPathTessellationShader::StencilPathSettings(GrFillRuleForSkPath(fPath));
fStencilCurvesProgram = GrTessellationShader::MakeProgram(args, fTessellator->shader(),
pipelineForStencils,
stencilPathSettings);
@ -207,7 +207,7 @@ void GrPathInnerTriangulateOp::prePreparePrograms(const GrTessellationShader::Pr
// Use a standard Redbook "stencil then cover" algorithm instead of bypassing the
// stencil buffer to fill the fan directly.
const GrUserStencilSettings* stencilPathSettings =
GrPathTessellationShader::StencilPathSettings(fPath.getFillType());
GrPathTessellationShader::StencilPathSettings(GrFillRuleForSkPath(fPath));
this->pushFanStencilProgram(args, pipelineForStencils, stencilPathSettings);
if (doFill) {
this->pushFanFillProgram(args,

View File

@ -29,7 +29,7 @@ private:
GrPathInnerTriangulateOp(const SkMatrix& viewMatrix, const SkPath& path, GrPaint&& paint,
GrAAType aaType, GrTessellationPathRenderer::PathFlags pathFlags,
const SkRect& devBounds)
const SkRect& drawBounds)
: GrDrawOp(ClassID())
, fPathFlags(pathFlags)
, fViewMatrix(viewMatrix)
@ -37,7 +37,8 @@ private:
, fAAType(aaType)
, fColor(paint.getColor4f())
, fProcessors(std::move(paint)) {
this->setBounds(devBounds, HasAABloat::kNo, IsHairline::kNo);
SkASSERT(!fPath.isInverseFillType());
this->setBounds(drawBounds, HasAABloat::kNo, IsHairline::kNo);
}
const char* name() const override { return "GrPathInnerTriangulateOp"; }

View File

@ -93,14 +93,10 @@ void GrPathStencilCoverOp::prePreparePrograms(const GrTessellationShader::Progra
SkASSERT(!fStencilPathProgram);
SkASSERT(!fCoverBBoxProgram);
if (fPath.countVerbs() <= 0) {
return;
}
const GrPipeline* stencilPipeline = GrPathTessellationShader::MakeStencilOnlyPipeline(
args, fAAType, fPathFlags, appliedClip.hardClip());
const GrUserStencilSettings* stencilPathSettings =
GrPathTessellationShader::StencilPathSettings(fPath.getFillType());
GrPathTessellationShader::StencilPathSettings(GrFillRuleForSkPath(fPath));
if (fPath.countVerbs() > 50 && this->bounds().height() * this->bounds().width() > 256 * 256) {
// Large complex paths do better with a dedicated triangle shader for the inner fan.
@ -130,7 +126,8 @@ void GrPathStencilCoverOp::prePreparePrograms(const GrTessellationShader::Progra
auto* bboxPipeline = GrTessellationShader::MakePipeline(args, fAAType,
std::move(appliedClip),
std::move(fProcessors));
auto* bboxStencil = GrPathTessellationShader::TestAndResetStencilSettings();
auto* bboxStencil =
GrPathTessellationShader::TestAndResetStencilSettings(fPath.isInverseFillType());
fCoverBBoxProgram = GrTessellationShader::MakeProgram(args, bboxShader, bboxPipeline,
bboxStencil);
}
@ -184,7 +181,20 @@ void GrPathStencilCoverOp::onPrepare(GrOpFlushState* flushState) {
if (fCoverBBoxProgram) {
GrVertexWriter vertexWriter = flushState->makeVertexSpace(sizeof(SkRect), 1, &fBBoxBuffer,
&fBBoxBaseInstance);
vertexWriter.write(fPath.getBounds());
if (fPath.isInverseFillType()) {
// Fill the entire backing store to make sure we clear every stencil value back to 0. If
// there is a scissor it will have already clipped the stencil draw.
auto rtBounds = flushState->writeView().asRenderTargetProxy()->backingStoreBoundsRect();
SkASSERT(rtBounds == fOriginalDrawBounds);
SkRect pathSpaceRTBounds;
if (SkMatrixPriv::InverseMapRect(fViewMatrix, &pathSpaceRTBounds, rtBounds)) {
vertexWriter.write(pathSpaceRTBounds);
} else {
vertexWriter.write(fPath.getBounds());
}
} else {
vertexWriter.write(fPath.getBounds());
}
}
}

View File

@ -21,9 +21,11 @@ class GrPathStencilCoverOp : public GrDrawOp {
private:
DEFINE_OP_CLASS_ID
// If the path is inverse filled, drawBounds must be the entire backing store dimensions of the
// render target.
GrPathStencilCoverOp(const SkMatrix& viewMatrix, const SkPath& path, GrPaint&& paint,
GrAAType aaType, GrTessellationPathRenderer::PathFlags pathFlags,
const SkRect& devBounds)
const SkRect& drawBounds)
: GrDrawOp(ClassID())
, fPathFlags(pathFlags)
, fViewMatrix(viewMatrix)
@ -31,7 +33,8 @@ private:
, fAAType(aaType)
, fColor(paint.getColor4f())
, fProcessors(std::move(paint)) {
this->setBounds(devBounds, HasAABloat::kNo, IsHairline::kNo);
this->setBounds(drawBounds, HasAABloat::kNo, IsHairline::kNo);
SkDEBUGCODE(fOriginalDrawBounds = drawBounds;)
}
const char* name() const override { return "GrPathStencilCoverOp"; }
@ -54,6 +57,7 @@ private:
const GrAAType fAAType;
SkPMColor4f fColor;
GrProcessorSet fProcessors;
SkDEBUGCODE(SkRect fOriginalDrawBounds;)
// Decided during prePreparePrograms.
GrPathTessellator* fTessellator = nullptr;

View File

@ -21,7 +21,7 @@ private:
GrPathTessellateOp(const SkMatrix& viewMatrix, const SkPath& path, GrPaint&& paint,
GrAAType aaType, const GrUserStencilSettings* stencil,
const SkRect& devBounds)
const SkRect& drawBounds)
: GrDrawOp(ClassID())
, fViewMatrix(viewMatrix)
, fPath(path)
@ -29,7 +29,8 @@ private:
, fStencil(stencil)
, fColor(paint.getColor4f())
, fProcessors(std::move(paint)) {
this->setBounds(devBounds, HasAABloat::kNo, IsHairline::kNo);
SkASSERT(!fPath.isInverseFillType());
this->setBounds(drawBounds, HasAABloat::kNo, IsHairline::kNo);
}
const char* name() const override { return "GrPathTessellateOp"; }

View File

@ -59,9 +59,9 @@ GrTessellationPathRenderer::GrTessellationPathRenderer(GrRecordingContext* rCont
GrPathRenderer::StencilSupport GrTessellationPathRenderer::onGetStencilSupport(
const GrStyledShape& shape) const {
if (!shape.style().isSimpleFill()) {
// Don't bother with stroke stencilling yet. Skia probably shouldn't support this at all
// since you can't clip by a stroke.
if (!shape.style().isSimpleFill() || shape.inverseFilled()) {
// Don't bother with stroke stencilling or inverse fills yet. The Skia API doesn't support
// clipping by a stroke, and the stencilling code already knows how to invert a fill.
return kNoSupport_StencilSupport;
}
return shape.knownToBeConvex() ? kNoRestriction_StencilSupport : kStencilOnly_StencilSupport;
@ -74,14 +74,14 @@ GrPathRenderer::CanDrawPath GrTessellationPathRenderer::onCanDrawPath(
shape.style().hasPathEffect() ||
args.fViewMatrix->hasPerspective() ||
shape.style().strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style ||
shape.inverseFilled() ||
(shape.inverseFilled() && !shape.style().isSimpleFill()) ||
!args.fProxy->canUseStencil(*args.fCaps)) {
return CanDrawPath::kNo;
}
if (args.fHasUserStencilSettings) {
// Non-convex paths and strokes use the stencil buffer internally, so they can't support
// draws with stencil settings.
if (!shape.style().isSimpleFill() || !shape.knownToBeConvex()) {
if (!shape.style().isSimpleFill() || !shape.knownToBeConvex() || shape.inverseFilled()) {
return CanDrawPath::kNo;
}
}
@ -90,28 +90,28 @@ GrPathRenderer::CanDrawPath GrTessellationPathRenderer::onCanDrawPath(
static GrOp::Owner make_non_convex_fill_op(GrRecordingContext* rContext,
GrTessellationPathRenderer::PathFlags pathFlags,
GrAAType aaType, const SkRect& pathDevBounds,
GrAAType aaType, const SkRect& drawBounds,
const SkMatrix& viewMatrix, const SkPath& path,
GrPaint&& paint) {
SkASSERT(!path.isConvex());
SkASSERT(!path.isConvex() || path.isInverseFillType());
int numVerbs = path.countVerbs();
if (numVerbs > 0) {
if (numVerbs > 0 && !path.isInverseFillType()) {
// Check if the path is large and/or simple enough that we can triangulate the inner fan
// on the CPU. This is our fastest approach. It allows us to stencil only the curves,
// and then fill the inner fan directly to the final render target, thus drawing the
// majority of pixels in a single render pass.
float gpuFragmentWork = pathDevBounds.height() * pathDevBounds.width();
float gpuFragmentWork = drawBounds.height() * drawBounds.width();
float cpuTessellationWork = numVerbs * SkNextLog2(numVerbs); // N log N.
constexpr static float kCpuWeight = 512;
constexpr static float kMinNumPixelsToTriangulate = 256 * 256;
if (cpuTessellationWork * kCpuWeight + kMinNumPixelsToTriangulate < gpuFragmentWork) {
return GrOp::Make<GrPathInnerTriangulateOp>(rContext, viewMatrix, path,
std::move(paint), aaType, pathFlags,
pathDevBounds);
drawBounds);
}
}
return GrOp::Make<GrPathStencilCoverOp>(rContext, viewMatrix, path, std::move(paint), aaType,
pathFlags, pathDevBounds);
pathFlags, drawBounds);
}
bool GrTessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
@ -122,6 +122,7 @@ bool GrTessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
// Handle strokes first.
if (!args.fShape->style().isSimpleFill()) {
SkASSERT(!path.isInverseFillType()); // See onGetStencilSupport().
SkASSERT(args.fUserStencilSettings->isUnused());
const SkStrokeRec& stroke = args.fShape->style().strokeRec();
SkASSERT(stroke.getStyle() != SkStrokeRec::kStrokeAndFill_Style);
@ -131,8 +132,15 @@ bool GrTessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
return true;
}
SkRect pathDevBounds;
args.fViewMatrix->mapRect(&pathDevBounds, args.fShape->bounds());
SkRect pathDevBounds = args.fViewMatrix->mapRect(args.fShape->bounds());
if (pathDevBounds.isEmpty()) {
// tryAddPathToAtlas() doesn't accept empty bounds.
if (path.isInverseFillType()) {
args.fSurfaceDrawContext->drawPaint(args.fClip, std::move(args.fPaint),
*args.fViewMatrix);
}
return true;
}
if (args.fUserStencilSettings->isUnused()) {
// See if the path is small and simple enough to atlas instead of drawing directly.
@ -154,10 +162,16 @@ bool GrTessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
if (this->tryAddPathToAtlas(args.fContext, *args.fViewMatrix, path, pathDevBounds,
args.fAAType != GrAAType::kNone, &devIBounds, &locationInAtlas,
&transposedInAtlas, visitProxiesUsedByDraw)) {
const SkRect& drawBounds = path.isInverseFillType()
? (args.fClip
? SkRect::Make(args.fClip->getConservativeBounds())
: args.fSurfaceDrawContext->asSurfaceProxy()->backingStoreBoundsRect())
: pathDevBounds;
auto op = GrOp::Make<GrDrawAtlasPathOp>(
args.fContext, surfaceDrawContext->numSamples(),
sk_ref_sp(fAtlasRenderTasks.back()->atlasProxy()), devIBounds, locationInAtlas,
transposedInAtlas, *args.fViewMatrix, std::move(args.fPaint));
transposedInAtlas, *args.fViewMatrix, std::move(args.fPaint), drawBounds,
path.isInverseFillType());
surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
return true;
}
@ -165,7 +179,7 @@ bool GrTessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
// Handle convex paths only if we couldn't fit them in the atlas. We give the atlas priority in
// an effort to reduce DMSAA triggers.
if (args.fShape->knownToBeConvex()) {
if (args.fShape->knownToBeConvex() && !path.isInverseFillType()) {
auto op = GrOp::Make<GrPathTessellateOp>(args.fContext, *args.fViewMatrix, path,
std::move(args.fPaint), args.fAAType,
args.fUserStencilSettings, pathDevBounds);
@ -174,7 +188,10 @@ bool GrTessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
}
SkASSERT(args.fUserStencilSettings->isUnused()); // See onGetStencilSupport().
auto op = make_non_convex_fill_op(args.fContext, PathFlags::kNone, args.fAAType, pathDevBounds,
const SkRect& drawBounds = path.isInverseFillType()
? args.fSurfaceDrawContext->asSurfaceProxy()->backingStoreBoundsRect()
: pathDevBounds;
auto op = make_non_convex_fill_op(args.fContext, PathFlags::kNone, args.fAAType, drawBounds,
*args.fViewMatrix, path, std::move(args.fPaint));
surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
return true;
@ -182,6 +199,7 @@ bool GrTessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
void GrTessellationPathRenderer::onStencilPath(const StencilPathArgs& args) {
SkASSERT(args.fShape->style().isSimpleFill()); // See onGetStencilSupport().
SkASSERT(!args.fShape->inverseFilled()); // See onGetStencilSupport().
GrSurfaceDrawContext* surfaceDrawContext = args.fSurfaceDrawContext;
GrAAType aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone;
@ -225,6 +243,12 @@ GrFPResult GrTessellationPathRenderer::makeAtlasClipFP(GrRecordingContext* rCont
if (viewMatrix.hasPerspective()) {
return GrFPFailure(std::move(inputFP));
}
SkRect pathDevBounds = viewMatrix.mapRect(path.getBounds());
if (pathDevBounds.isEmpty()) {
// tryAddPathToAtlas() doesn't accept empty bounds.
return path.isInverseFillType() ? GrFPSuccess(std::move(inputFP))
: GrFPFailure(std::move(inputFP));
}
SkIRect devIBounds;
SkIPoint16 locationInAtlas;
bool transposedInAtlas;
@ -235,9 +259,9 @@ GrFPResult GrTessellationPathRenderer::makeAtlasClipFP(GrRecordingContext* rCont
}
};
// tryAddPathToAtlas() ignores inverseness of the fill. See getAtlasUberPath().
if (!this->tryAddPathToAtlas(rContext, viewMatrix, path, viewMatrix.mapRect(path.getBounds()),
aa != GrAA::kNo, &devIBounds, &locationInAtlas,
&transposedInAtlas, visitProxiesUsedByDraw)) {
if (!this->tryAddPathToAtlas(rContext, viewMatrix, path, pathDevBounds, aa != GrAA::kNo,
&devIBounds, &locationInAtlas, &transposedInAtlas,
visitProxiesUsedByDraw)) {
// The path is too big, or the atlas ran out of room.
return GrFPFailure(std::move(inputFP));
}

View File

@ -62,7 +62,7 @@ public:
const SkPMColor4f&, PatchType);
// Returns the stencil settings to use for a standard Redbook "stencil" pass.
static const GrUserStencilSettings* StencilPathSettings(SkPathFillType fillType) {
static const GrUserStencilSettings* StencilPathSettings(GrFillRule fillRule) {
// Increments clockwise triangles and decrements counterclockwise. Used for "winding" fill.
constexpr static GrUserStencilSettings kIncrDecrStencil(
GrUserStencilSettings::StaticInitSeparate<
@ -83,14 +83,13 @@ public:
GrUserStencilOp::kKeep,
0x0001>());
SkASSERT(fillType == SkPathFillType::kWinding || fillType == SkPathFillType::kEvenOdd);
return (fillType == SkPathFillType::kWinding) ? &kIncrDecrStencil : &kInvertStencil;
return (fillRule == GrFillRule::kNonzero) ? &kIncrDecrStencil : &kInvertStencil;
}
// Returns the stencil settings to use for a standard Redbook "fill" pass. Allows non-zero
// stencil values to pass and write a color, and resets the stencil value back to zero; discards
// immediately on stencil values of zero.
static const GrUserStencilSettings* TestAndResetStencilSettings() {
static const GrUserStencilSettings* TestAndResetStencilSettings(bool isInverseFill = false) {
constexpr static GrUserStencilSettings kTestAndResetStencil(
GrUserStencilSettings::StaticInit<
0x0000,
@ -101,7 +100,19 @@ public:
GrUserStencilOp::kZero,
GrUserStencilOp::kKeep,
0xffff>());
return &kTestAndResetStencil;
constexpr static GrUserStencilSettings kTestAndResetStencilInverted(
GrUserStencilSettings::StaticInit<
0x0000,
// No need to check the clip because the previous stencil pass will have only
// written to samples already inside the clip.
GrUserStencilTest::kEqual,
0xffff,
GrUserStencilOp::kKeep,
GrUserStencilOp::kZero,
0xffff>());
return isInverseFill ? &kTestAndResetStencilInverted : &kTestAndResetStencil;
}
// Creates a pipeline that does not write to the color buffer.