Add a dynamic color attrib to hw tessellated stroking

Only adds color to the hardware tessellator. The indirect tessellator
reorders draws with is log2 binning, so we can't have different
colors.

Bug: chromium:1172543
Bug: skia:10419
Change-Id: I2a3700cd4572e8222002bfb028af05c6ec447708
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/369976
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
This commit is contained in:
Chris Dalton 2021-02-18 15:22:54 -07:00 committed by Skia Commit-Bot
parent fac4efcabe
commit 1017a3558f
10 changed files with 165 additions and 60 deletions

View File

@ -231,7 +231,8 @@ private:
SkMatrix matrix = SkMatrix::Scale(fMatrixScale, fMatrixScale);
GrStrokeHardwareTessellator tessellator(ShaderFlags::kNone,
*fTarget->caps().shaderCaps());
tessellator.prepare(fTarget.get(), matrix, {fPath, fStrokeRec}, fPath.countVerbs());
tessellator.prepare(fTarget.get(), matrix, {fPath, fStrokeRec, SK_PMColor4fWHITE},
fPath.countVerbs());
}
}
@ -270,10 +271,10 @@ private:
for (int i = 0; i < loops; ++i) {
for (const SkPath& path : fPaths) {
GrStrokeIndirectTessellator tessellator(ShaderFlags::kNone, SkMatrix::I(),
{path, fStrokeRec}, path.countVerbs(),
fTarget->allocator());
tessellator.prepare(fTarget.get(), SkMatrix::I(), {path, fStrokeRec},
path.countVerbs());
{path, fStrokeRec, SK_PMColor4fWHITE},
path.countVerbs(), fTarget->allocator());
tessellator.prepare(fTarget.get(), SkMatrix::I(),
{path, fStrokeRec, SK_PMColor4fWHITE}, path.countVerbs());
}
fTarget->resetAllocator();
}

View File

@ -92,13 +92,19 @@ static inline uint64_t SkPMColor4f_toFP16(const SkPMColor4f& color) {
*/
class GrVertexColor {
public:
explicit GrVertexColor(const SkPMColor4f& color, bool wideColor)
: fWideColor(wideColor) {
GrVertexColor() = default;
explicit GrVertexColor(const SkPMColor4f& color, bool wideColor) {
this->set(color, wideColor);
}
void set(const SkPMColor4f& color, bool wideColor) {
if (wideColor) {
memcpy(fColor, color.vec(), sizeof(fColor));
} else {
fColor[0] = color.toBytes_RGBA();
}
fWideColor = wideColor;
}
size_t size() const { return fWideColor ? 16 : 4; }

View File

@ -121,10 +121,17 @@ void GrStrokeHardwareTessellator::prepare(GrMeshDrawOp::Target* target, const Sk
auto tolerances = Tolerances::MakePreTransform(matrixScales.data(), stroke.getWidth());
this->updateTolerances(tolerances, stroke.getJoin());
}
fStroke = &stroke;
if (fShaderFlags & ShaderFlags::kDynamicStroke) {
fDynamicStroke.set(stroke);
}
fStroke = &stroke;
if (fShaderFlags & ShaderFlags::kDynamicColor) {
bool wideColor = fShaderFlags & ShaderFlags::kWideColor;
SkASSERT(wideColor || pathStroke.fColor.fitsInBytes());
fDynamicColor.set(pathStroke.fColor, wideColor);
}
fHasLastControlPoint = false;
SkDEBUGCODE(fHasCurrentPoint = false;)
SkPathVerb previousVerb = SkPathVerb::kClose;
@ -655,6 +662,9 @@ void GrStrokeHardwareTessellator::emitDynamicAttribs() {
if (fShaderFlags & ShaderFlags::kDynamicStroke) {
fPatchWriter.write(fDynamicStroke);
}
if (fShaderFlags & ShaderFlags::kDynamicColor) {
fPatchWriter.write(fDynamicColor);
}
}
bool GrStrokeHardwareTessellator::reservePatch() {

View File

@ -125,6 +125,7 @@ private:
// Stateful values for the dynamic state (if any) that will get written out with each patch.
GrStrokeTessellateShader::DynamicStroke fDynamicStroke;
GrVertexColor fDynamicColor;
friend class GrOp; // For ctor.

View File

@ -444,6 +444,8 @@ GrStrokeIndirectTessellator::GrStrokeIndirectTessellator(
const GrSTArenaList<PathStroke>& pathStrokeList, int totalCombinedVerbCnt,
SkArenaAlloc* alloc)
: GrStrokeTessellator(shaderFlags) {
// We can't combine colors because our log2 binning draws things out of order.
SkASSERT(!(fShaderFlags & ShaderFlags::kDynamicColor));
SkASSERT(!fTotalInstanceCount);
SkASSERT(!fResolveLevels);
SkASSERT(!fResolveLevelArrayCount);

View File

@ -20,13 +20,15 @@ GrStrokeTessellateOp::GrStrokeTessellateOp(GrAAType aaType, const SkMatrix& view
: GrDrawOp(ClassID())
, fAAType(aaType)
, fViewMatrix(viewMatrix)
, fColor(paint.getColor4f())
, fProcessors(std::move(paint))
, fPathStrokeList(path, stroke)
, fTotalCombinedVerbCnt(path.countVerbs()) {
, fPathStrokeList(path, stroke, paint.getColor4f())
, fTotalCombinedVerbCnt(path.countVerbs())
, fProcessors(std::move(paint)) {
if (SkPathPriv::ConicWeightCnt(path) != 0) {
fShaderFlags |= ShaderFlags::kHasConics;
}
if (!this->headColor().fitsInBytes()) {
fShaderFlags |= ShaderFlags::kWideColor;
}
SkRect devBounds = path.getBounds();
float inflationRadius = stroke.getInflationRadius();
devBounds.outset(inflationRadius, inflationRadius);
@ -62,21 +64,21 @@ GrProcessorSet::Analysis GrStrokeTessellateOp::finalize(const GrCaps& caps,
SkASSERT(fPathStrokeList.begin().fCurr->fNext == nullptr);
SkASSERT(fAAType != GrAAType::kCoverage || hasMixedSampledCoverage);
const GrProcessorSet::Analysis& analysis = fProcessors.finalize(
fColor, GrProcessorAnalysisCoverage::kNone, clip, &GrUserStencilSettings::kUnused,
hasMixedSampledCoverage, caps, clampType, &fColor);
this->headColor(), GrProcessorAnalysisCoverage::kNone, clip,
&GrUserStencilSettings::kUnused, hasMixedSampledCoverage, caps, clampType,
&this->headColor());
fNeedsStencil = !analysis.unaffectedByDstValue();
return analysis;
}
GrOp::CombineResult GrStrokeTessellateOp::onCombineIfPossible(GrOp* grOp, SkArenaAlloc* alloc,
const GrCaps&) {
const GrCaps& caps) {
using DynamicStroke = GrStrokeTessellateShader::DynamicStroke;
SkASSERT(grOp->classID() == this->classID());
auto* op = static_cast<GrStrokeTessellateOp*>(grOp);
if (fNeedsStencil ||
op->fNeedsStencil ||
fColor != op->fColor ||
fViewMatrix != op->fViewMatrix ||
fAAType != op->fAAType ||
fProcessors != op->fProcessors ||
@ -91,14 +93,29 @@ GrOp::CombineResult GrStrokeTessellateOp::onCombineIfPossible(GrOp* grOp, SkAren
// still decide to combine them.
combinedFlags |= ShaderFlags::kDynamicStroke;
}
if (combinedFlags & ShaderFlags::kDynamicStroke) {
// Don't actually enable dynamic stroke on ops that already have lots of verbs.
if (!this->shouldUseDynamicState(ShaderFlags::kDynamicStroke) ||
!op->shouldUseDynamicState(ShaderFlags::kDynamicStroke)) {
if (!(combinedFlags & ShaderFlags::kDynamicColor) && this->headColor() != op->headColor()) {
// The paths have different colors. We will need to enable dynamic color if we still decide
// to combine them.
combinedFlags |= ShaderFlags::kDynamicColor;
}
// Don't actually enable new dynamic state on ops that already have lots of verbs.
constexpr static GrTFlagsMask<ShaderFlags> kDynamicStatesMask(ShaderFlags::kDynamicStroke |
ShaderFlags::kDynamicColor);
ShaderFlags neededDynamicStates = combinedFlags & kDynamicStatesMask;
if (neededDynamicStates != ShaderFlags::kNone) {
if (!this->shouldUseDynamicStates(neededDynamicStates) ||
!op->shouldUseDynamicStates(neededDynamicStates)) {
return CombineResult::kCannotCombine;
}
}
// The indirect tessellator can't combine colors because its log2 binning draws things out of
// order. Only enable dynamic color if we have hardware tessellation.
if ((combinedFlags & ShaderFlags::kDynamicColor) && !this->canUseHardwareTessellation(caps)) {
return CombineResult::kCannotCombine;
}
fPathStrokeList.concat(std::move(op->fPathStrokeList), alloc);
fTotalCombinedVerbCnt += op->fTotalCombinedVerbCnt;
fShaderFlags = combinedFlags;
@ -136,14 +153,11 @@ void GrStrokeTessellateOp::prePrepareTessellator(GrPathShader::ProgramArgs&& arg
const GrCaps& caps = *args.fCaps;
SkArenaAlloc* arena = args.fArena;
// Only use hardware tessellation if the path has a somewhat large number of verbs. Otherwise we
// seem to be better off using indirect draws. Our back door for HW tessellation shaders isn't
// currently capable of passing varyings to the fragment shader either, so if the processors
// have varyings we need to use indirect draws.
// Only use hardware tessellation if we need dynamic color or if the path has a somewhat large
// number of verbs. Otherwise we seem to be better off using indirect draws.
GrStrokeTessellateShader::Mode shaderMode;
if (caps.shaderCaps()->tessellationSupport() &&
fTotalCombinedVerbCnt > 50 &&
!fProcessors.usesVaryingCoords()) {
if (this->canUseHardwareTessellation(caps) &&
((fShaderFlags & ShaderFlags::kDynamicColor) || fTotalCombinedVerbCnt > 50)) {
fTessellator = arena->make<GrStrokeHardwareTessellator>(fShaderFlags, *caps.shaderCaps());
shaderMode = GrStrokeTessellateShader::Mode::kTessellation;
} else {
@ -164,7 +178,7 @@ void GrStrokeTessellateOp::prePrepareTessellator(GrPathShader::ProgramArgs&& arg
}
auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
shaderMode, fShaderFlags, fViewMatrix, this->headStroke(), fColor);
shaderMode, fShaderFlags, fViewMatrix, this->headStroke(), this->headColor());
auto* fillPipeline = GrFillPathShader::MakeFillPassPipeline(args, fAAType, std::move(clip),
std::move(fProcessors));
auto fillStencil = &GrUserStencilSettings::kUnused;

View File

@ -24,9 +24,11 @@ public:
GrStrokeTessellator(ShaderFlags shaderFlags) : fShaderFlags(shaderFlags) {}
struct PathStroke {
PathStroke(const SkPath& path, const SkStrokeRec& stroke) : fPath(path), fStroke(stroke) {}
PathStroke(const SkPath& path, const SkStrokeRec& stroke, const SkPMColor4f& color)
: fPath(path), fStroke(stroke), fColor(color) {}
SkPath fPath;
SkStrokeRec fStroke;
SkPMColor4f fColor;
};
// Called before draw(). Prepares GPU buffers containing the geometry to tessellate.
@ -55,16 +57,25 @@ private:
DEFINE_OP_CLASS_ID
SkStrokeRec& headStroke() { return fPathStrokeList.head().fStroke; }
SkPMColor4f& headColor() { return fPathStrokeList.head().fColor; }
// Returns whether it is a good tradeoff to use the given dynamic state. Dynamic state improves
// batching, but if it isn't already enabled, it comes at the cost of having to write out more
// data with each patch or instance.
bool shouldUseDynamicState(ShaderFlags dynamicState) const {
// Use the dynamic state if either (1) the state is already enabled anyway, or (2) we don't
// Returns whether it is a good tradeoff to use the dynamic states flagged in the given
// bitfield. Dynamic states improve batching, but if they aren't already enabled, they come at
// the cost of having to write out more data with each patch or instance.
bool shouldUseDynamicStates(ShaderFlags neededDynamicStates) const {
// Use the dynamic states if either (1) they are all already enabled anyway, or (2) we don't
// have many verbs.
constexpr static int kMaxVerbsToEnableDynamicState = 50;
return (fShaderFlags & dynamicState) ||
(fTotalCombinedVerbCnt <= kMaxVerbsToEnableDynamicState);
bool anyStateDisabled = (bool)(~fShaderFlags & neededDynamicStates);
bool allStatesEnabled = !anyStateDisabled;
return allStatesEnabled || (fTotalCombinedVerbCnt <= kMaxVerbsToEnableDynamicState);
}
bool canUseHardwareTessellation(const GrCaps& caps) {
SkASSERT(!fStencilProgram && !fFillProgram); // Ensure we haven't std::moved fProcessors.
// Our back door for HW tessellation shaders isn't currently capable of passing varyings to
// the fragment shader, so if the processors have varyings we need to use indirect draws.
return caps.shaderCaps()->tessellationSupport() && !fProcessors.usesVaryingCoords();
}
const char* name() const override { return "GrStrokeTessellateOp"; }
@ -87,13 +98,11 @@ private:
const GrAAType fAAType;
const SkMatrix fViewMatrix;
SkPMColor4f fColor;
bool fNeedsStencil = false;
GrProcessorSet fProcessors;
ShaderFlags fShaderFlags = ShaderFlags::kNone;
GrSTArenaList<PathStroke> fPathStrokeList;
int fTotalCombinedVerbCnt = 0;
GrProcessorSet fProcessors;
bool fNeedsStencil = false;
GrStrokeTessellator* fTessellator = nullptr;
const GrProgramInfo* fStencilProgram = nullptr; // Only used if the stroke has transparency.

View File

@ -135,6 +135,9 @@ private:
// [NUM_RADIAL_SEGMENTS_PER_RADIAN, STROKE_RADIUS]
v->declareGlobal(GrShaderVar("vsStrokeArgs", kFloat2_GrSLType, TypeModifier::Out));
}
if (shader.hasDynamicColor()) {
v->declareGlobal(GrShaderVar("vsColor", kHalf4_GrSLType, TypeModifier::Out));
}
v->insertFunction(kAtan2Fn);
v->insertFunction(kCosineBetweenVectorsFn);
@ -380,12 +383,25 @@ private:
v->codeAppend(R"(
vsStrokeArgs = float2(NUM_RADIAL_SEGMENTS_PER_RADIAN, STROKE_RADIUS);)");
}
if (shader.hasDynamicColor()) {
v->codeAppend(R"(
vsColor = dynamicColorAttr;)");
}
// The fragment shader just outputs a uniform color.
const char* colorUniformName;
fColorUniform = uniHandler->addUniform(nullptr, kFragment_GrShaderFlag, kHalf4_GrSLType,
"color", &colorUniformName);
args.fFragBuilder->codeAppendf("%s = %s;", args.fOutputColor, colorUniformName);
if (!shader.hasDynamicColor()) {
// The fragment shader just outputs a uniform color.
const char* colorUniformName;
fColorUniform = uniHandler->addUniform(nullptr, kFragment_GrShaderFlag, kHalf4_GrSLType,
"color", &colorUniformName);
args.fFragBuilder->codeAppendf("%s = %s;", args.fOutputColor, colorUniformName);
} else {
// Color gets passed in from the tess evaluation shader.
SkString flatness(args.fShaderCaps->preferFlatInterpolation() ? "flat" : "");
args.fFragBuilder->declareGlobal(GrShaderVar(SkString("tesColor"), kHalf4_GrSLType,
TypeModifier::In, 0, SkString(),
flatness));
args.fFragBuilder->codeAppendf("%s = tesColor;", args.fOutputColor);
}
args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage);
}
@ -423,7 +439,9 @@ private:
m.getScaleY());
}
pdman.set4fv(fColorUniform, 1, shader.fColor.vec());
if (!shader.hasDynamicColor()) {
pdman.set4fv(fColorUniform, 1, shader.fColor.vec());
}
}
GrGLSLUniformHandler::UniformHandle fTessArgsUniform;
@ -487,6 +505,10 @@ SkString GrStrokeTessellateShader::getTessControlShaderGLSL(
code.append(R"(
in vec2 vsStrokeArgs[];)");
}
if (this->hasDynamicColor()) {
code.append(R"(
in mediump vec4 vsColor[];)");
}
code.append(R"(
out vec4 tcsPts01[];
@ -500,6 +522,10 @@ SkString GrStrokeTessellateShader::getTessControlShaderGLSL(
code.append(R"(
patch out float tcsStrokeRadius;)");
}
if (this->hasDynamicColor()) {
code.append(R"(
patch out mediump vec4 tcsColor;)");
}
code.append(R"(
void main() {
@ -510,6 +536,10 @@ SkString GrStrokeTessellateShader::getTessControlShaderGLSL(
code.append(R"(
tcsStrokeRadius = vsStrokeArgs[0].y;)");
}
if (this->hasDynamicColor()) {
code.append(R"(
tcsColor = vsColor[0];)");
}
code.append(R"(
// Unpack the curve args from the vertex shader.
@ -831,6 +861,11 @@ SkString GrStrokeTessellateShader::getTessEvaluationShaderGLSL(
code.append(R"(
patch in float tcsStrokeRadius;)");
}
if (this->hasDynamicColor()) {
code.appendf(R"(
patch in mediump vec4 tcsColor;
%s out mediump vec4 tesColor;)", shaderCaps.preferFlatInterpolation() ? "flat" : "");
}
code.append(R"(
uniform vec4 sk_RTAdjust;)");
@ -932,7 +967,14 @@ SkString GrStrokeTessellateShader::getTessEvaluationShaderGLSL(
}
code.append(R"(
gl_Position = vec4(vertexPos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
gl_Position = vec4(vertexPos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);)");
if (this->hasDynamicColor()) {
code.append(R"(
tesColor = tcsColor;)");
}
code.append(R"(
})");
return code;
@ -1242,9 +1284,11 @@ class GrStrokeTessellateShader::IndirectImpl : public GrGLSLGeometryProcessor {
void GrStrokeTessellateShader::getGLSLProcessorKey(const GrShaderCaps&,
GrProcessorKeyBuilder* b) const {
bool keyNeedsJoin = fMode == Mode::kIndirect && !(fShaderFlags & ShaderFlags::kDynamicStroke);
bool keyNeedsJoin = (fMode == Mode::kIndirect) && !(fShaderFlags & ShaderFlags::kDynamicStroke);
SkASSERT(fStroke.getJoin() >> 2 == 0);
uint32_t key = (uint32_t)fShaderFlags;
// Attribs get worked into the key automatically during GrPrimitiveProcessor::getAttributeKey().
// When color is in a uniform, it's always wide. kWideColor doesn't need to be considered here.
uint32_t key = (uint32_t)(fShaderFlags & ~ShaderFlags::kWideColor);
key = (key << 1) | (uint32_t)fMode;
key = (key << 2) | ((keyNeedsJoin) ? fStroke.getJoin() : 0);
key = (key << 1) | (uint32_t)fStroke.isHairlineStyle();

View File

@ -34,7 +34,9 @@ public:
enum class ShaderFlags {
kNone = 0,
kHasConics = 1 << 0,
kDynamicStroke = 1 << 1 // Each patch or instance has its own stroke width and join type.
kWideColor = 1 << 1,
kDynamicStroke = 1 << 2, // Each patch or instance has its own stroke width and join type.
kDynamicColor = 1 << 3, // Each patch or instance has its own color.
};
GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(ShaderFlags);
@ -142,19 +144,23 @@ public:
// Size in bytes of a tessellation patch with the given shader flags.
static size_t PatchStride(ShaderFlags shaderFlags) {
size_t stride = sizeof(SkPoint) * 5;
if (shaderFlags & ShaderFlags::kDynamicStroke) {
stride += sizeof(DynamicStroke);
}
return stride;
return sizeof(SkPoint) * 5 + DynamicStateStride(shaderFlags);
}
// Size in bytes of an indirect draw instance with the given shader flags.
static size_t IndirectInstanceStride(ShaderFlags shaderFlags) {
size_t stride = sizeof(float) * 11;
return sizeof(float) * 11 + DynamicStateStride(shaderFlags);
}
// Combined size in bytes of the dynamic state attribs enabled in the given shader flags.
static size_t DynamicStateStride(ShaderFlags shaderFlags) {
size_t stride = 0;
if (shaderFlags & ShaderFlags::kDynamicStroke) {
stride += sizeof(DynamicStroke);
}
if (shaderFlags & ShaderFlags::kDynamicColor) {
stride += (shaderFlags & ShaderFlags::kWideColor) ? sizeof(float) * 4 : 4;
}
return stride;
}
@ -207,6 +213,13 @@ public:
fAttribs.emplace_back("dynamicStrokeAttr", kFloat2_GrVertexAttribType,
kFloat2_GrSLType);
}
if (fShaderFlags & ShaderFlags::kDynamicColor) {
fAttribs.emplace_back("dynamicColorAttr",
(fShaderFlags & ShaderFlags::kWideColor)
? kFloat4_GrVertexAttribType
: kUByte4_norm_GrVertexAttribType,
kHalf4_GrSLType);
}
if (fMode == Mode::kTessellation) {
this->setVertexAttributes(fAttribs.data(), fAttribs.count());
SkASSERT(this->vertexStride() == PatchStride(fShaderFlags));
@ -214,10 +227,12 @@ public:
this->setInstanceAttributes(fAttribs.data(), fAttribs.count());
SkASSERT(this->instanceStride() == IndirectInstanceStride(fShaderFlags));
}
SkASSERT(fAttribs.count() <= kMaxAttribCount);
}
bool hasConics() const { return fShaderFlags & ShaderFlags::kHasConics; }
bool hasDynamicStroke() const { return fShaderFlags & ShaderFlags::kDynamicStroke; }
bool hasDynamicColor() const { return fShaderFlags & ShaderFlags::kDynamicColor; }
private:
const char* name() const override { return "GrStrokeTessellateShader"; }
@ -237,7 +252,9 @@ private:
const ShaderFlags fShaderFlags;
const SkStrokeRec fStroke;
const SkPMColor4f fColor;
SkSTArray<4, Attribute> fAttribs;
constexpr static int kMaxAttribCount = 5;
SkSTArray<kMaxAttribCount, Attribute> fAttribs;
class TessellationImpl;
class IndirectImpl;

View File

@ -42,10 +42,11 @@ static void test_stroke(skiatest::Reporter* r, GrDirectContext* ctx, GrMockOpTar
float scale = ldexpf(rand.nextF() + 1, i);
auto matrix = SkMatrix::Scale(scale, scale);
GrStrokeIndirectTessellator tessellator(GrStrokeTessellateShader::ShaderFlags::kNone,
matrix, {path, stroke}, path.countVerbs(),
target->allocator());
matrix, {path, stroke, SK_PMColor4fWHITE},
path.countVerbs(), target->allocator());
tessellator.verifyResolveLevels(r, target, matrix, path, stroke);
tessellator.prepare(target, matrix, {path, stroke}, path.countVerbs());
tessellator.prepare(target, matrix, {path, stroke, SK_PMColor4fWHITE},
path.countVerbs());
tessellator.verifyBuffers(r, target, matrix, stroke);
}
}