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>
diff --git a/bench/TessellateBench.cpp b/bench/TessellateBench.cpp
index b1bdbeb..6d1e6fa 100644
--- a/bench/TessellateBench.cpp
+++ b/bench/TessellateBench.cpp
@@ -231,7 +231,8 @@
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 @@
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();
}
diff --git a/src/gpu/GrColor.h b/src/gpu/GrColor.h
index 26f0937..f9bab67 100644
--- a/src/gpu/GrColor.h
+++ b/src/gpu/GrColor.h
@@ -92,13 +92,19 @@
*/
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; }
diff --git a/src/gpu/tessellate/GrStrokeHardwareTessellator.cpp b/src/gpu/tessellate/GrStrokeHardwareTessellator.cpp
index 8211e8c..31dc3b0 100644
--- a/src/gpu/tessellate/GrStrokeHardwareTessellator.cpp
+++ b/src/gpu/tessellate/GrStrokeHardwareTessellator.cpp
@@ -121,10 +121,17 @@
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 @@
if (fShaderFlags & ShaderFlags::kDynamicStroke) {
fPatchWriter.write(fDynamicStroke);
}
+ if (fShaderFlags & ShaderFlags::kDynamicColor) {
+ fPatchWriter.write(fDynamicColor);
+ }
}
bool GrStrokeHardwareTessellator::reservePatch() {
diff --git a/src/gpu/tessellate/GrStrokeHardwareTessellator.h b/src/gpu/tessellate/GrStrokeHardwareTessellator.h
index 3488bd8..20766a6 100644
--- a/src/gpu/tessellate/GrStrokeHardwareTessellator.h
+++ b/src/gpu/tessellate/GrStrokeHardwareTessellator.h
@@ -125,6 +125,7 @@
// 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.
diff --git a/src/gpu/tessellate/GrStrokeIndirectTessellator.cpp b/src/gpu/tessellate/GrStrokeIndirectTessellator.cpp
index 63fc2fb..15ed2a9 100644
--- a/src/gpu/tessellate/GrStrokeIndirectTessellator.cpp
+++ b/src/gpu/tessellate/GrStrokeIndirectTessellator.cpp
@@ -444,6 +444,8 @@
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);
diff --git a/src/gpu/tessellate/GrStrokeTessellateOp.cpp b/src/gpu/tessellate/GrStrokeTessellateOp.cpp
index 0af849e..5885e10 100644
--- a/src/gpu/tessellate/GrStrokeTessellateOp.cpp
+++ b/src/gpu/tessellate/GrStrokeTessellateOp.cpp
@@ -20,13 +20,15 @@
: 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 @@
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 @@
// 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 @@
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 @@
}
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;
diff --git a/src/gpu/tessellate/GrStrokeTessellateOp.h b/src/gpu/tessellate/GrStrokeTessellateOp.h
index 2278fbc..42b8d45 100644
--- a/src/gpu/tessellate/GrStrokeTessellateOp.h
+++ b/src/gpu/tessellate/GrStrokeTessellateOp.h
@@ -24,9 +24,11 @@
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 @@
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 @@
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.
diff --git a/src/gpu/tessellate/GrStrokeTessellateShader.cpp b/src/gpu/tessellate/GrStrokeTessellateShader.cpp
index cdc9eee..73d81d4 100644
--- a/src/gpu/tessellate/GrStrokeTessellateShader.cpp
+++ b/src/gpu/tessellate/GrStrokeTessellateShader.cpp
@@ -135,6 +135,9 @@
// [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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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();
diff --git a/src/gpu/tessellate/GrStrokeTessellateShader.h b/src/gpu/tessellate/GrStrokeTessellateShader.h
index d146917..d491d1d 100644
--- a/src/gpu/tessellate/GrStrokeTessellateShader.h
+++ b/src/gpu/tessellate/GrStrokeTessellateShader.h
@@ -34,7 +34,9 @@
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 @@
// 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 @@
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 @@
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 @@
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;
diff --git a/tests/StrokeIndirectTest.cpp b/tests/StrokeIndirectTest.cpp
index f955e24..39135a0 100644
--- a/tests/StrokeIndirectTest.cpp
+++ b/tests/StrokeIndirectTest.cpp
@@ -42,10 +42,11 @@
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);
}
}