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:
parent
1bddd42b98
commit
baae2dd7fb
@ -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) ? ©->fNext : that->fInstanceTail;
|
||||
auto* copy = alloc->make<Instance>(that->fHeadInstance);
|
||||
*fTailInstance = copy;
|
||||
fTailInstance = (!copy->fNext) ? ©->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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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"; }
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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"; }
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user