Add an sk_VertexID workaround for tessellated stroking

Tessellation should now be completely free of its dependence on
sk_VertexID.

Bug: chromium:1220246
Change-Id: I4027099392b92e45aee7d8417945335352e3416e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/423496
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
This commit is contained in:
Chris Dalton 2021-07-01 10:58:55 -06:00 committed by Skia Commit-Bot
parent 024668cf7f
commit bb995e60a3
10 changed files with 150 additions and 88 deletions

View File

@ -222,11 +222,12 @@ static std::unique_ptr<GrStrokeTessellator> make_hw_tessellator(
}
static std::unique_ptr<GrStrokeTessellator> make_fixed_count_tessellator(
ShaderFlags shaderFlags, const GrShaderCaps&, const SkMatrix& viewMatrix,
ShaderFlags shaderFlags, const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix,
PathStrokeList* pathStrokeList, std::array<float, 2> matrixMinMaxScales,
const SkRect& strokeCullBounds) {
return std::make_unique<GrStrokeFixedCountTessellator>(shaderFlags, viewMatrix, pathStrokeList,
matrixMinMaxScales, strokeCullBounds);
matrixMinMaxScales, strokeCullBounds,
shaderCaps);
}
using MakePathStrokesFn = std::vector<PathStrokeList>(*)();

View File

@ -8,6 +8,7 @@
#include "src/gpu/tessellate/GrStrokeFixedCountTessellator.h"
#include "src/core/SkGeometry.h"
#include "src/gpu/GrResourceProvider.h"
#include "src/gpu/geometry/GrPathUtils.h"
#include "src/gpu/geometry/GrWangsFormula.h"
#include "src/gpu/tessellate/GrCullTest.h"
@ -238,12 +239,15 @@ GrStrokeFixedCountTessellator::GrStrokeFixedCountTessellator(ShaderFlags shaderF
const SkMatrix& viewMatrix,
PathStrokeList* pathStrokeList,
std::array<float,2> matrixMinMaxScales,
const SkRect& strokeCullBounds)
const SkRect& strokeCullBounds,
const GrShaderCaps& shaderCaps)
: GrStrokeTessellator(GrStrokeTessellationShader::Mode::kFixedCount, shaderFlags,
kMaxParametricSegments_log2, viewMatrix, pathStrokeList,
matrixMinMaxScales, strokeCullBounds) {
matrixMinMaxScales, strokeCullBounds, shaderCaps) {
}
GR_DECLARE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey);
void GrStrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target,
int totalCombinedVerbCnt) {
int maxEdgesInJoin = 0;
@ -404,6 +408,26 @@ void GrStrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target,
// emit both because the join's edge is half-width and the stroke's is full-width.
int fixedEdgeCount = maxEdgesInJoin + maxEdgesInStroke;
// Don't draw more vertices than can be indexed by a signed short. We just have to draw the line
// somewhere and this seems reasonable enough. (There are two vertices per edge, so 2^14 edges
// make 2^15 vertices.)
fixedEdgeCount = std::min(fixedEdgeCount, (1 << 14) - 1);
if (!target->caps().shaderCaps()->vertexIDSupport()) {
// Our shader won't be able to use sk_VertexID. Bind a fallback vertex buffer with the IDs
// in it instead.
constexpr static int kMaxVerticesInFallbackBuffer = 2048;
fixedEdgeCount = std::min(fixedEdgeCount, kMaxVerticesInFallbackBuffer/2);
GR_DEFINE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey);
fVertexBufferIfNoIDSupport = target->resourceProvider()->findOrMakeStaticBuffer(
GrGpuBufferType::kVertex,
kMaxVerticesInFallbackBuffer * sizeof(float),
gVertexIDFallbackBufferKey,
GrStrokeTessellationShader::InitializeVertexIDFallbackBuffer);
}
fShader.setFixedCountNumTotalEdges(fixedEdgeCount);
fFixedVertexCount = fixedEdgeCount * 2;
}
@ -413,7 +437,7 @@ void GrStrokeFixedCountTessellator::draw(GrOpFlushState* flushState) const {
return;
}
for (const auto& instanceChunk : fInstanceChunks) {
flushState->bindBuffers(nullptr, instanceChunk.fBuffer, nullptr);
flushState->bindBuffers(nullptr, instanceChunk.fBuffer, fVertexBufferIfNoIDSupport);
flushState->drawInstanced(instanceChunk.fCount, instanceChunk.fBase, fFixedVertexCount, 0);
}
}

View File

@ -17,7 +17,7 @@ class GrStrokeFixedCountTessellator : public GrStrokeTessellator {
public:
GrStrokeFixedCountTessellator(ShaderFlags, const SkMatrix&, PathStrokeList*,
std::array<float,2> matrixMinMaxScales,
const SkRect& strokeCullBounds);
const SkRect& strokeCullBounds, const GrShaderCaps&);
void prepare(GrMeshDrawTarget*, int totalCombinedVerbCnt) override;
void draw(GrOpFlushState*) const override;
@ -25,6 +25,9 @@ public:
private:
GrVertexChunkArray fInstanceChunks;
int fFixedVertexCount = 0;
// Only used if sk_VertexID is not supported.
sk_sp<const GrGpuBuffer> fVertexBufferIfNoIDSupport;
};
#endif

View File

@ -709,10 +709,9 @@ GrStrokeHardwareTessellator::GrStrokeHardwareTessellator(ShaderFlags shaderFlags
PathStrokeList* pathStrokeList,
std::array<float,2> matrixMinMaxScales,
const SkRect& strokeCullBounds)
: GrStrokeTessellator(GrStrokeTessellationShader::Mode::kHardwareTessellation,
shaderFlags, SkNextLog2(shaderCaps.maxTessellationSegments()),
viewMatrix, pathStrokeList, matrixMinMaxScales,
strokeCullBounds) {
: GrStrokeTessellator(GrStrokeTessellationShader::Mode::kHardwareTessellation, shaderFlags,
SkNextLog2(shaderCaps.maxTessellationSegments()), viewMatrix,
pathStrokeList, matrixMinMaxScales, strokeCullBounds, shaderCaps) {
}
void GrStrokeHardwareTessellator::prepare(GrMeshDrawTarget* target, int totalCombinedVerbCnt) {

View File

@ -198,7 +198,8 @@ void GrStrokeTessellateOp::prePrepareTessellator(GrTessellationShader::ProgramAr
fTessellator = arena->make<GrStrokeFixedCountTessellator>(fShaderFlags, fViewMatrix,
&fPathStrokeList,
matrixMinMaxScales,
strokeCullBounds);
strokeCullBounds,
*caps.shaderCaps());
}
auto fillStencil = &GrUserStencilSettings::kUnused;

View File

@ -28,9 +28,9 @@ public:
GrStrokeTessellator(GrStrokeTessellationShader::Mode shaderMode, ShaderFlags shaderFlags,
int8_t maxParametricSegments_log2, const SkMatrix& viewMatrix,
PathStrokeList* pathStrokeList, std::array<float,2> matrixMinMaxScales,
const SkRect& strokeCullBounds)
const SkRect& strokeCullBounds, const GrShaderCaps& shaderCaps)
: fShader(shaderMode, shaderFlags, viewMatrix, pathStrokeList->fStroke,
pathStrokeList->fColor, maxParametricSegments_log2)
pathStrokeList->fColor, maxParametricSegments_log2, shaderCaps)
, fPathStrokeList(pathStrokeList)
, fMatrixMinMaxScales(matrixMinMaxScales)
, fStrokeCullBounds(strokeCullBounds) {

View File

@ -78,8 +78,7 @@ GrPathRenderer::CanDrawPath GrTessellationPathRenderer::onCanDrawPath(
return CanDrawPath::kNo;
}
if (!shape.style().isSimpleFill()) {
if (shape.inverseFilled() ||
!args.fCaps->shaderCaps()->vertexIDSupport()) {
if (shape.inverseFilled()) {
return CanDrawPath::kNo;
}
}

View File

@ -13,6 +13,83 @@
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/tessellate/GrStrokeTessellator.h"
GrStrokeTessellationShader::GrStrokeTessellationShader(Mode mode, ShaderFlags shaderFlags,
const SkMatrix& viewMatrix,
const SkStrokeRec& stroke, SkPMColor4f color,
int8_t maxParametricSegments_log2,
const GrShaderCaps& shaderCaps)
: GrTessellationShader(kTessellate_GrStrokeTessellationShader_ClassID,
(mode == Mode::kHardwareTessellation)
? GrPrimitiveType::kPatches
: GrPrimitiveType::kTriangleStrip,
(mode == Mode::kHardwareTessellation) ? 1 : 0, viewMatrix, color)
, fMode(mode)
, fShaderFlags(shaderFlags)
, fStroke(stroke)
, fMaxParametricSegments_log2(maxParametricSegments_log2) {
if (fMode == Mode::kHardwareTessellation) {
// A join calculates its starting angle using prevCtrlPtAttr.
fAttribs.emplace_back("prevCtrlPtAttr", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
// pts 0..3 define the stroke as a cubic bezier. If p3.y is infinity, then it's a conic
// with w=p3.x.
//
// If p0 == prevCtrlPtAttr, then no join is emitted.
//
// pts=[p0, p3, p3, p3] is a reserved pattern that means this patch is a join only,
// whose start and end tangents are (p0 - inputPrevCtrlPt) and (p3 - p0).
//
// pts=[p0, p0, p0, p3] is a reserved pattern that means this patch is a "bowtie", or
// double-sided round join, anchored on p0 and rotating from (p0 - prevCtrlPtAttr) to
// (p3 - p0).
fAttribs.emplace_back("pts01Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
fAttribs.emplace_back("pts23Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
} else {
// pts 0..3 define the stroke as a cubic bezier. If p3.y is infinity, then it's a conic
// with w=p3.x.
//
// An empty stroke (p0==p1==p2==p3) is a special case that denotes a circle, or
// 180-degree point stroke.
fAttribs.emplace_back("pts01Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
fAttribs.emplace_back("pts23Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
if (fMode == Mode::kLog2Indirect) {
// argsAttr.xy contains the lastControlPoint for setting up the join.
//
// "argsAttr.z=numTotalEdges" tells the shader the literal number of edges in the
// triangle strip being rendered (i.e., it should be vertexCount/2). If
// numTotalEdges is negative and the join type is "kRound", it also instructs the
// shader to only allocate one segment the preceding round join.
fAttribs.emplace_back("argsAttr", kFloat3_GrVertexAttribType, kFloat3_GrSLType);
} else {
SkASSERT(fMode == Mode::kFixedCount);
// argsAttr contains the lastControlPoint for setting up the join.
fAttribs.emplace_back("argsAttr", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
}
}
if (fShaderFlags & ShaderFlags::kDynamicStroke) {
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::kHardwareTessellation) {
this->setVertexAttributes(fAttribs.data(), fAttribs.count());
} else {
this->setInstanceAttributes(fAttribs.data(), fAttribs.count());
if (!shaderCaps.vertexIDSupport()) {
constexpr static Attribute kVertexAttrib("edgeID", kFloat_GrVertexAttribType,
kFloat_GrSLType);
this->setVertexAttributes(&kVertexAttrib, 1);
}
}
SkASSERT(fAttribs.count() <= kMaxAttribCount);
}
// The built-in atan() is undefined when x==0. This method relieves that restriction, but also can
// return values larger than 2*PI. This shouldn't matter for our purposes.
const char* GrStrokeTessellationShader::Impl::kAtan2Fn = R"(

View File

@ -98,74 +98,8 @@ public:
};
// 'viewMatrix' is applied to the geometry post tessellation. It cannot have perspective.
GrStrokeTessellationShader(Mode mode, ShaderFlags shaderFlags, const SkMatrix& viewMatrix,
const SkStrokeRec& stroke, SkPMColor4f color,
int8_t maxParametricSegments_log2)
: GrTessellationShader(kTessellate_GrStrokeTessellationShader_ClassID,
(mode == Mode::kHardwareTessellation)
? GrPrimitiveType::kPatches
: GrPrimitiveType::kTriangleStrip,
(mode == Mode::kHardwareTessellation) ? 1 : 0, viewMatrix, color)
, fMode(mode)
, fShaderFlags(shaderFlags)
, fStroke(stroke)
, fMaxParametricSegments_log2(maxParametricSegments_log2) {
if (fMode == Mode::kHardwareTessellation) {
// A join calculates its starting angle using prevCtrlPtAttr.
fAttribs.emplace_back("prevCtrlPtAttr", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
// pts 0..3 define the stroke as a cubic bezier. If p3.y is infinity, then it's a conic
// with w=p3.x.
//
// If p0 == prevCtrlPtAttr, then no join is emitted.
//
// pts=[p0, p3, p3, p3] is a reserved pattern that means this patch is a join only,
// whose start and end tangents are (p0 - inputPrevCtrlPt) and (p3 - p0).
//
// pts=[p0, p0, p0, p3] is a reserved pattern that means this patch is a "bowtie", or
// double-sided round join, anchored on p0 and rotating from (p0 - prevCtrlPtAttr) to
// (p3 - p0).
fAttribs.emplace_back("pts01Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
fAttribs.emplace_back("pts23Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
} else {
// pts 0..3 define the stroke as a cubic bezier. If p3.y is infinity, then it's a conic
// with w=p3.x.
//
// An empty stroke (p0==p1==p2==p3) is a special case that denotes a circle, or
// 180-degree point stroke.
fAttribs.emplace_back("pts01Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
fAttribs.emplace_back("pts23Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
if (fMode == Mode::kLog2Indirect) {
// argsAttr.xy contains the lastControlPoint for setting up the join.
//
// "argsAttr.z=numTotalEdges" tells the shader the literal number of edges in the
// triangle strip being rendered (i.e., it should be vertexCount/2). If
// numTotalEdges is negative and the join type is "kRound", it also instructs the
// shader to only allocate one segment the preceding round join.
fAttribs.emplace_back("argsAttr", kFloat3_GrVertexAttribType, kFloat3_GrSLType);
} else {
SkASSERT(fMode == Mode::kFixedCount);
// argsAttr contains the lastControlPoint for setting up the join.
fAttribs.emplace_back("argsAttr", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
}
}
if (fShaderFlags & ShaderFlags::kDynamicStroke) {
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::kHardwareTessellation) {
this->setVertexAttributes(fAttribs.data(), fAttribs.count());
} else {
this->setInstanceAttributes(fAttribs.data(), fAttribs.count());
}
SkASSERT(fAttribs.count() <= kMaxAttribCount);
}
GrStrokeTessellationShader(Mode, ShaderFlags, const SkMatrix& viewMatrix, const SkStrokeRec&,
SkPMColor4f, int8_t maxParametricSegments_log2, const GrShaderCaps&);
Mode mode() const { return fMode; }
ShaderFlags flags() const { return fShaderFlags; }
@ -182,6 +116,12 @@ public:
fFixedCountNumTotalEdges = value;
}
// Initializes the fallback vertex buffer that should be bound when drawing in Mode::kFixedCount
// and sk_VertexID is not supported. Each vertex is a single float and each edge is composed of
// two vertices, so the desired edge count in the buffer is presumed to be
// "bufferSize / (sizeof(float) * 2)". The caller cannot draw more vertices than edgeCount * 2.
static void InitializeVertexIDFallbackBuffer(GrVertexWriter vertexWriter, size_t bufferSize);
private:
const char* name() const override {
switch (fMode) {

View File

@ -11,7 +11,6 @@
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/tessellate/GrStrokeTessellator.h"
void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
const auto& shader = args.fGeomProc.cast<GrStrokeTessellationShader>();
@ -126,6 +125,15 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
tan1 = float2(-1,0);
})");
if (args.fShaderCaps->vertexIDSupport()) {
// If we don't have sk_VertexID support then "edgeID" already came in as a vertex attrib.
args.fVertBuilder->codeAppend(R"(
float edgeID = float(sk_VertexID >> 1);
if ((sk_VertexID & 1) != 0) {
edgeID = -edgeID;
})");
}
// Potential optimization: (shader.hasDynamicStroke() && shader.hasRoundJoins())?
if (shader.stroke().getJoin() == SkPaint::kRound_Join || shader.hasDynamicStroke()) {
args.fVertBuilder->codeAppend(R"(
@ -167,7 +175,7 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
// NOTE: Since the curve is not allowed to inflect, we can just check F'(.5) x F''(.5).
// NOTE: F'(.5) x F''(.5) has the same sign as (P2 - P0) x (P3 - P1)
float turn = cross(P[2] - P[0], P[3] - P[1]);
float combinedEdgeID = float(sk_VertexID >> 1) - numEdgesInJoin;
float combinedEdgeID = abs(edgeID) - numEdgesInJoin;
if (combinedEdgeID < 0) {
tan1 = tan0;
// Don't let tan0 become zero. The code as-is isn't built to handle that case. tan0=0
@ -188,7 +196,7 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
}
float numRadialSegments;
float strokeOutset = ((sk_VertexID & 1) == 0) ? +1 : -1;
float strokeOutset = sign(edgeID);
if (combinedEdgeID < 0) {
// We belong to the preceding join. The first and final edges get duplicated, so we only
// have "numEdgesInJoin - 2" segments.
@ -235,8 +243,8 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
if (joinType == SkPaint::kMiter_Join || shader.hasDynamicStroke()) {
args.fVertBuilder->codeAppendf(R"(
// Vertices #4 and #5 belong to the edge of the join that extends to the miter point.
if ((sk_VertexID | 1) == (4 | 5) && %s) {
// Edge #2 extends to the miter point.
if (abs(edgeID) == 2 && %s) {
strokeOutset *= miter_extent(cosTheta, JOIN_TYPE/*miterLimit*/);
})", shader.hasDynamicStroke() ? "JOIN_TYPE > 0/*Is the join a miter type?*/" : "true");
}
@ -245,3 +253,13 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
this->emitFragmentCode(shader, args);
}
void GrStrokeTessellationShader::InitializeVertexIDFallbackBuffer(GrVertexWriter vertexWriter,
size_t bufferSize) {
SkASSERT(bufferSize % (sizeof(float) * 2) == 0);
int edgeCount = bufferSize / (sizeof(float) * 2);
for (int i = 0; i < edgeCount; ++i) {
vertexWriter.write<float>(i);
vertexWriter.write<float>(-i);
}
}