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:
parent
294723d690
commit
90a0d9f618
@ -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",
|
||||
|
@ -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());
|
||||
|
@ -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; }
|
||||
|
@ -129,6 +129,7 @@ public:
|
||||
kFwidthSquircleTestProcessor_ClassID,
|
||||
kSwizzleFragmentProcessor_ClassID,
|
||||
kTessellate_BoundingBoxShader_ClassID,
|
||||
kTessellate_GrModulateAtlasCoverageFP_ClassID,
|
||||
kTessellate_GrStrokeTessellationShader_ClassID,
|
||||
kTessellate_HardwareCurveShader_ClassID,
|
||||
kTessellate_HardwareWedgeShader_ClassID,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
81
src/gpu/tessellate/shaders/GrModulateAtlasCoverageFP.cpp
Normal file
81
src/gpu/tessellate/shaders/GrModulateAtlasCoverageFP.cpp
Normal 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>();
|
||||
}
|
52
src/gpu/tessellate/shaders/GrModulateAtlasCoverageFP.h
Normal file
52
src/gpu/tessellate/shaders/GrModulateAtlasCoverageFP.h
Normal 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
|
Loading…
Reference in New Issue
Block a user