Use a custom FP for tessellation atlas clips

Something about GrTextureEffect::MakeSubset was upsetting NVIDIA
Vulkan. This is cleaner anyway though since we only have to create 2
fps instead of 3, and since we don't need to make new shaders for
inverting coverage anymore.

Bug: skia:12102
Change-Id: I5d03ed12abba5c4053e08062c75ac8d40933b422
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/419150
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
This commit is contained in:
Chris Dalton 2021-06-17 10:43:58 -06:00 committed by Skia Commit-Bot
parent 294723d690
commit 90a0d9f618
8 changed files with 170 additions and 57 deletions

View File

@ -447,6 +447,8 @@ skia_gpu_sources = [
"$_src/gpu/tessellate/GrVectorXform.h",
# tessellate/shaders
"$_src/gpu/tessellate/shaders/GrModulateAtlasCoverageFP.cpp",
"$_src/gpu/tessellate/shaders/GrModulateAtlasCoverageFP.h",
"$_src/gpu/tessellate/shaders/GrPathTessellationShader.cpp",
"$_src/gpu/tessellate/shaders/GrPathTessellationShader.h",
"$_src/gpu/tessellate/shaders/GrPathTessellationShader_Hardware.cpp",

View File

@ -109,6 +109,11 @@ GrDynamicAtlas::Node* GrDynamicAtlas::makeNode(Node* previous, int l, int t, int
return fNodeAllocator.make<Node>(previous, rectanizer, l, t);
}
GrSurfaceProxyView GrDynamicAtlas::surfaceProxyView(const GrCaps& caps) const {
return {fTextureProxy, kTextureOrigin,
caps.getReadSwizzle(fTextureProxy->backendFormat(), fColorType)};
}
bool GrDynamicAtlas::addRect(int width, int height, SkIPoint16* location) {
// This can't be called anymore once instantiate() has been called.
SkASSERT(!this->isInstantiated());

View File

@ -56,6 +56,7 @@ public:
int maxAtlasSize() const { return fMaxAtlasSize; }
GrTextureProxy* textureProxy() const { return fTextureProxy.get(); }
GrSurfaceProxyView surfaceProxyView(const GrCaps&) const;
bool isInstantiated() const { return fTextureProxy->isInstantiated(); }
int currentWidth() const { return fWidth; }
int currentHeight() const { return fHeight; }

View File

@ -129,6 +129,7 @@ public:
kFwidthSquircleTestProcessor_ClassID,
kSwizzleFragmentProcessor_ClassID,
kTessellate_BoundingBoxShader_ClassID,
kTessellate_GrModulateAtlasCoverageFP_ClassID,
kTessellate_GrStrokeTessellationShader_ClassID,
kTessellate_HardwareCurveShader_ClassID,
kTessellate_HardwareWedgeShader_ClassID,

View File

@ -25,6 +25,7 @@
#include "src/gpu/tessellate/GrPathStencilCoverOp.h"
#include "src/gpu/tessellate/GrPathTessellateOp.h"
#include "src/gpu/tessellate/GrStrokeTessellateOp.h"
#include "src/gpu/tessellate/shaders/GrModulateAtlasCoverageFP.h"
constexpr static SkISize kAtlasInitialSize{512, 512};
constexpr static int kMaxAtlasSize = 2048;
@ -210,11 +211,13 @@ void GrTessellationPathRenderer::onStencilPath(const StencilPathArgs& args) {
surfaceDrawContext->addDrawOp(args.fClip, std::move(op));
}
GrFPResult GrTessellationPathRenderer::makeAtlasClipFP(
const SkIRect& drawBounds, const SkMatrix& viewMatrix, const SkPath& path, GrAA aa,
std::unique_ptr<GrFragmentProcessor> inputCoverage, const GrCaps& caps) {
GrFPResult GrTessellationPathRenderer::makeAtlasClipFP(const SkIRect& drawBounds,
const SkMatrix& viewMatrix,
const SkPath& path, GrAA aa,
std::unique_ptr<GrFragmentProcessor> inputFP,
const GrCaps& caps) {
if (viewMatrix.hasPerspective()) {
return GrFPFailure(std::move(inputCoverage));
return GrFPFailure(std::move(inputFP));
}
SkIRect devIBounds;
SkIPoint16 locationInAtlas;
@ -224,63 +227,31 @@ GrFPResult GrTessellationPathRenderer::makeAtlasClipFP(
aa != GrAA::kNo, &devIBounds, &locationInAtlas,
&transposedInAtlas)) {
// The path is too big, or the atlas ran out of room.
return GrFPFailure(std::move(inputCoverage));
return GrFPFailure(std::move(inputFP));
}
GrSurfaceProxyView atlasView(sk_ref_sp(fAtlas.textureProxy()), GrDynamicAtlas::kTextureOrigin,
caps.getReadSwizzle(fAtlas.textureProxy()->backendFormat(),
GrColorType::kAlpha_8));
SkMatrix atlasMatrix;
SkRect atlasSubset, atlasDomain;
auto [atlasX, atlasY] = locationInAtlas;
if (!transposedInAtlas) {
auto atlasOffset = SkVector::Make(atlasX - devIBounds.left(), atlasY - devIBounds.top());
atlasMatrix = SkMatrix::Translate(atlasOffset);
atlasSubset = SkRect::Make(devIBounds).makeOffset(atlasOffset);
atlasDomain = SkRect::Make(drawBounds).makeOffset(atlasOffset);
atlasMatrix = SkMatrix::Translate(atlasX - devIBounds.left(), atlasY - devIBounds.top());
} else {
atlasMatrix.setAll(0, 1, atlasX - devIBounds.top(),
1, 0, atlasY - devIBounds.left(),
0, 0, 1);
atlasSubset = SkRect::MakeXYWH(atlasX, atlasY, devIBounds.height(), devIBounds.width());
atlasDomain = atlasMatrix.mapRect(SkRect::Make(drawBounds));
}
#ifdef SK_DEBUG
if (!path.isInverseFillType()) {
// At this point in time we expect callers to tighten the scissor for "kIntersect" clips, as
// opposed to us having to enforce the texture subset. Feel free to remove this assert if
// that ever changes.
SkASSERT(atlasDomain.isEmpty() || atlasSubset.contains(atlasDomain));
}
#endif
// Inset the domain because if it is equal to the subset, then it falls on an exact boundary
// between pixels, the "nearest" filter becomes undefined, and GrTextureEffect is forced to
// manually enforce the subset. This inset is justifiable because textures are sampled at pixel
// center, unless sample shading is enabled, in which case we assume standard sample locations
// (https://www.khronos.org/registry/vulkan/specs/1.2/html/chap25.html).
// NOTE: At MSAA16, standard sample locations begin falling on actual pixel boundaries. If this
// happens then we simply have to rely on the fact that the atlas has a 1px padding between
// entries.
constexpr static float kMinInsetOfStandardMSAA8Locations = 1/16.f;
atlasDomain.inset(kMinInsetOfStandardMSAA8Locations, kMinInsetOfStandardMSAA8Locations);
// Look up clip coverage in the atlas.
GrSamplerState samplerState(GrSamplerState::WrapMode::kClampToBorder,
GrSamplerState::Filter::kNearest);
auto fp = GrTextureEffect::MakeSubset(std::move(atlasView), kPremul_SkAlphaType, atlasMatrix,
samplerState, atlasSubset, atlasDomain, caps);
// Feed sk_FragCoord into the above texture lookup.
fp = GrDeviceSpaceEffect::Make(std::move(fp));
auto flags = GrModulateAtlasCoverageFP::Flags::kNone;
if (path.isInverseFillType()) {
// outputCoverage = inputCoverage * (1 - atlasAlpha)
fp = GrBlendFragmentProcessor::Make(
std::move(fp), std::move(inputCoverage), SkBlendMode::kDstOut,
GrBlendFragmentProcessor::BlendBehavior::kSkModeBehavior);
} else {
// outputCoverage = inputCoverage * atlasAlpha
fp = GrBlendFragmentProcessor::Make(
std::move(fp), std::move(inputCoverage), SkBlendMode::kDstIn,
GrBlendFragmentProcessor::BlendBehavior::kSkModeBehavior);
flags |= GrModulateAtlasCoverageFP::Flags::kInvertCoverage;
}
return GrFPSuccess(std::move(fp));
if (!devIBounds.contains(drawBounds)) {
flags |= GrModulateAtlasCoverageFP::Flags::kCheckBounds;
// At this point in time we expect callers to tighten the scissor for "kIntersect" clips, as
// opposed to us having to check the path bounds. Feel free to remove this assert if that
// ever changes.
SkASSERT(path.isInverseFillType());
}
return GrFPSuccess(std::make_unique<GrModulateAtlasCoverageFP>(flags, std::move(inputFP),
fAtlas.surfaceProxyView(caps),
atlasMatrix, devIBounds));
}
void GrTessellationPathRenderer::AtlasPathKey::set(const SkMatrix& m, bool antialias,

View File

@ -42,16 +42,16 @@ public:
bool onDrawPath(const DrawPathArgs&) override;
void onStencilPath(const StencilPathArgs&) override;
// Returns a fragment processor that modulates inputCoverage by the given deviceSpacePath's
// coverage, implemented using an internal atlas.
// Returns a fragment processor that modulates inputFP by the given deviceSpacePath's coverage,
// implemented using an internal atlas.
//
// Returns 'inputCoverage' wrapped in GrFPFailure() if the path was too big, or if the atlas was
// out of room. (Currently, "too big" means more than 128*128 total pixels, or larger than half
// the atlas size in either dimension.)
// Returns 'inputFP' wrapped in GrFPFailure() if the path was too big, or if the atlas was out
// of room. (Currently, "too big" means more than 128*128 total pixels, or larger than half the
// atlas size in either dimension.)
//
// Also return GrFPFailure() if the view matrix has perspective.
// Also returns GrFPFailure() if the view matrix has perspective.
GrFPResult makeAtlasClipFP(const SkIRect& drawBounds, const SkMatrix&, const SkPath&, GrAA,
std::unique_ptr<GrFragmentProcessor> inputCoverage, const GrCaps&);
std::unique_ptr<GrFragmentProcessor> inputFP, const GrCaps&);
void preFlush(GrOnFlushResourceProvider*, SkSpan<const uint32_t> taskIDs) override;

View File

@ -0,0 +1,81 @@
/*
* Copyright 2021 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/tessellate/shaders/GrModulateAtlasCoverageFP.h"
#include "src/gpu/GrDynamicAtlas.h"
#include "src/gpu/effects/GrTextureEffect.h"
GrModulateAtlasCoverageFP::GrModulateAtlasCoverageFP(Flags flags,
std::unique_ptr<GrFragmentProcessor> inputFP,
GrSurfaceProxyView atlasView,
const SkMatrix& devToAtlasMatrix,
const SkIRect& devIBounds)
: GrFragmentProcessor(kTessellate_GrModulateAtlasCoverageFP_ClassID,
kCompatibleWithCoverageAsAlpha_OptimizationFlag)
, fFlags(flags)
, fBounds((fFlags & Flags::kCheckBounds) ? devIBounds : SkIRect{0,0,0,0}) {
this->registerChild(std::move(inputFP));
this->registerChild(GrTextureEffect::Make(std::move(atlasView), kUnknown_SkAlphaType,
devToAtlasMatrix, GrSamplerState::Filter::kNearest),
SkSL::SampleUsage::Explicit());
}
GrModulateAtlasCoverageFP::GrModulateAtlasCoverageFP(const GrModulateAtlasCoverageFP& that)
: GrFragmentProcessor(kTessellate_GrModulateAtlasCoverageFP_ClassID,
kCompatibleWithCoverageAsAlpha_OptimizationFlag)
, fFlags(that.fFlags)
, fBounds(that.fBounds) {
this->cloneAndRegisterAllChildProcessors(that);
}
std::unique_ptr<GrGLSLFragmentProcessor> GrModulateAtlasCoverageFP::onMakeProgramImpl() const {
class Impl : public GrGLSLFragmentProcessor {
void emitCode(EmitArgs& args) override {
auto fp = args.fFp.cast<GrModulateAtlasCoverageFP>();
auto f = args.fFragBuilder;
auto uniHandler = args.fUniformHandler;
SkString inputColor = this->invokeChild(0, args);
f->codeAppend("half coverage = 0;");
if (fp.fFlags & Flags::kCheckBounds) {
const char* boundsName;
fBoundsUniform = uniHandler->addUniform(&fp, kFragment_GrShaderFlag,
kFloat4_GrSLType, "bounds", &boundsName);
// Are we inside the path's valid atlas bounds?
f->codeAppendf("if (all(greaterThan(sk_FragCoord.xy, %s.xy)) && "
"all(lessThan(sk_FragCoord.xy, %s.zw))) ",
boundsName, boundsName);
}
f->codeAppendf("{");
SkString atlasCoverage = this->invokeChild(1, args, "sk_FragCoord.xy");
f->codeAppendf("coverage = %s.a;", atlasCoverage.c_str());
f->codeAppendf("}");
const char* coverageInvertName;
fCoverageInvertUniform = uniHandler->addUniform(&fp, kFragment_GrShaderFlag,
kHalf2_GrSLType, "coverageInvert",
&coverageInvertName);
// Return "inputColor * coverage" or "inputColor * (1 - coverage)".
f->codeAppendf("return %s * fma(coverage, %s.x, %s.y);",
inputColor.c_str(), coverageInvertName, coverageInvertName);
}
void onSetData(const GrGLSLProgramDataManager& pdman,
const GrFragmentProcessor& processor) override {
auto fp = processor.cast<GrModulateAtlasCoverageFP>();
if (fp.fFlags & Flags::kCheckBounds) {
pdman.set4fv(fBoundsUniform, 1, SkRect::Make(fp.fBounds).asScalars());
}
if (fp.fFlags & Flags::kInvertCoverage) {
pdman.set2f(fCoverageInvertUniform, -1, 1); // coverage * -1 + 1 == 1 - coverage.
} else {
pdman.set2f(fCoverageInvertUniform, 1, 0); // coverage * 1 + 0 == coverage.
}
}
UniformHandle fBoundsUniform;
UniformHandle fCoverageInvertUniform;
};
return std::make_unique<Impl>();
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2021 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrGrModulateAtlasCoverageFP_DEFINED
#define GrGrModulateAtlasCoverageFP_DEFINED
#include "src/gpu/GrFragmentProcessor.h"
// Multiplies 'inputFP' by the coverage value in an atlas, optionally inverting or clamping to 0.
class GrModulateAtlasCoverageFP : public GrFragmentProcessor {
public:
enum class Flags {
kNone = 0,
kInvertCoverage = 1 << 0, // Return inputColor * (1 - atlasCoverage).
kCheckBounds = 1 << 1 // Clamp atlasCoverage to 0 if outside the path's valid atlas bounds.
};
GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(Flags);
GrModulateAtlasCoverageFP(Flags flags, std::unique_ptr<GrFragmentProcessor> inputFP,
GrSurfaceProxyView atlasView, const SkMatrix& devToAtlasMatrix,
const SkIRect& devIBounds);
GrModulateAtlasCoverageFP(const GrModulateAtlasCoverageFP& that);
const char* name() const override {
return "GrModulateAtlasCoverageFP";
}
void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
b->add32(fFlags & Flags::kCheckBounds);
}
std::unique_ptr<GrFragmentProcessor> clone() const override {
return std::make_unique<GrModulateAtlasCoverageFP>(*this);
}
bool onIsEqual(const GrFragmentProcessor& that) const override {
auto fp = that.cast<GrModulateAtlasCoverageFP>();
return fFlags == fp.fFlags && fBounds == fp.fBounds;
}
std::unique_ptr<GrGLSLFragmentProcessor> onMakeProgramImpl() const override;
private:
const Flags fFlags;
const SkIRect fBounds;
};
GR_MAKE_BITFIELD_CLASS_OPS(GrModulateAtlasCoverageFP::Flags);
#endif