blob: 4e02ba7f2ef3f9d30db857b1777609526fb3f0da [file] [log] [blame]
/*
* 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/PathWedgeTessellator.h"
#include "src/core/SkPathPriv.h"
#include "src/gpu/tessellate/AffineMatrix.h"
#include "src/gpu/tessellate/PatchWriter.h"
#include "src/gpu/tessellate/PathCurveTessellator.h"
#include "src/gpu/tessellate/WangsFormula.h"
#if SK_GPU_V1
#include "src/gpu/GrMeshDrawTarget.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrResourceProvider.h"
#endif
namespace skgpu {
namespace {
// Parses out each contour in a path and tracks the midpoint. Example usage:
//
// SkTPathContourParser parser;
// while (parser.parseNextContour()) {
// SkPoint midpoint = parser.currentMidpoint();
// for (auto [verb, pts] : parser.currentContour()) {
// ...
// }
// }
//
class MidpointContourParser {
public:
MidpointContourParser(const SkPath& path)
: fPath(path)
, fVerbs(SkPathPriv::VerbData(fPath))
, fNumRemainingVerbs(fPath.countVerbs())
, fPoints(SkPathPriv::PointData(fPath))
, fWeights(SkPathPriv::ConicWeightData(fPath)) {}
// Advances the internal state to the next contour in the path. Returns false if there are no
// more contours.
bool parseNextContour() {
bool hasGeometry = false;
for (; fVerbsIdx < fNumRemainingVerbs; ++fVerbsIdx) {
switch (fVerbs[fVerbsIdx]) {
case SkPath::kMove_Verb:
if (!hasGeometry) {
fMidpoint = {0,0};
fMidpointWeight = 0;
this->advance(); // Resets fPtsIdx to 0 and advances fPoints.
fPtsIdx = 1; // Increment fPtsIdx past the kMove.
continue;
}
if (fPoints[0] != fPoints[fPtsIdx - 1]) {
// There's an implicit close at the end. Add the start point to our mean.
fMidpoint += fPoints[0];
++fMidpointWeight;
}
return true;
default:
continue;
case SkPath::kLine_Verb:
++fPtsIdx;
break;
case SkPath::kConic_Verb:
++fWtsIdx;
[[fallthrough]];
case SkPath::kQuad_Verb:
fPtsIdx += 2;
break;
case SkPath::kCubic_Verb:
fPtsIdx += 3;
break;
}
fMidpoint += fPoints[fPtsIdx - 1];
++fMidpointWeight;
hasGeometry = true;
}
if (hasGeometry && fPoints[0] != fPoints[fPtsIdx - 1]) {
// There's an implicit close at the end. Add the start point to our mean.
fMidpoint += fPoints[0];
++fMidpointWeight;
}
return hasGeometry;
}
// Allows for iterating the current contour using a range-for loop.
SkPathPriv::Iterate currentContour() {
return SkPathPriv::Iterate(fVerbs, fVerbs + fVerbsIdx, fPoints, fWeights);
}
SkPoint currentMidpoint() { return fMidpoint * (1.f / fMidpointWeight); }
private:
void advance() {
fVerbs += fVerbsIdx;
fNumRemainingVerbs -= fVerbsIdx;
fVerbsIdx = 0;
fPoints += fPtsIdx;
fPtsIdx = 0;
fWeights += fWtsIdx;
fWtsIdx = 0;
}
const SkPath& fPath;
const uint8_t* fVerbs;
int fNumRemainingVerbs = 0;
int fVerbsIdx = 0;
const SkPoint* fPoints;
int fPtsIdx = 0;
const float* fWeights;
int fWtsIdx = 0;
SkPoint fMidpoint;
int fMidpointWeight;
};
} // namespace
int PathWedgeTessellator::patchPreallocCount(int totalCombinedPathVerbCnt) const {
// Over-allocate enough wedges for 1 in 4 to chop.
int maxWedges = MaxCombinedFanEdgesInPathDrawList(totalCombinedPathVerbCnt);
return (maxWedges * 5 + 3) / 4; // i.e., ceil(maxWedges * 5/4)
}
void PathWedgeTessellator::writePatches(PatchWriter& patchWriter,
const SkMatrix& shaderMatrix,
const PathDrawList& pathDrawList) {
wangs_formula::VectorXform shaderXform(shaderMatrix);
for (auto [pathMatrix, path, color] : pathDrawList) {
AffineMatrix m(pathMatrix);
if (fAttribs & PatchAttribs::kColor) {
patchWriter.updateColorAttrib(color);
}
MidpointContourParser parser(path);
while (parser.parseNextContour()) {
patchWriter.updateFanPointAttrib(m.mapPoint(parser.currentMidpoint()));
SkPoint lastPoint = {0, 0};
SkPoint startPoint = {0, 0};
for (auto [verb, pts, w] : parser.currentContour()) {
switch (verb) {
case SkPathVerb::kMove: {
startPoint = lastPoint = pts[0];
break;
}
case SkPathVerb::kLine: {
// Explicitly convert the line to an equivalent cubic w/ four distinct
// control points because it fans better and avoids double-hitting pixels.
patchWriter.writeLine(m.map2Points(pts));
lastPoint = pts[1];
break;
}
case SkPathVerb::kQuad: {
auto [p0, p1] = m.map2Points(pts);
auto p2 = m.map1Point(pts+2);
patchWriter.writeQuadratic(p0, p1, p2, shaderXform);
lastPoint = pts[2];
break;
}
case SkPathVerb::kConic: {
auto [p0, p1] = m.map2Points(pts);
auto p2 = m.map1Point(pts+2);
patchWriter.writeConic(p0, p1, p2, *w, shaderXform);
lastPoint = pts[2];
break;
}
case SkPathVerb::kCubic: {
auto [p0, p1] = m.map2Points(pts);
auto [p2, p3] = m.map2Points(pts+2);
patchWriter.writeCubic(p0, p1, p2, p3, shaderXform);
lastPoint = pts[3];
break;
}
case SkPathVerb::kClose: {
break; // Ignore. We can assume an implicit close at the end.
}
}
}
if (lastPoint != startPoint) {
SkPoint pts[2] = {lastPoint, startPoint};
patchWriter.writeLine(m.map2Points(pts));
}
}
}
// We already chopped curves to make sure none needed a higher resolveLevel than
// kMaxFixedResolveLevel.
fFixedResolveLevel = SkTPin(patchWriter.requiredResolveLevel(),
fFixedResolveLevel,
int(kMaxFixedResolveLevel));
}
void PathWedgeTessellator::WriteFixedVertexBuffer(VertexWriter vertexWriter, size_t bufferSize) {
SkASSERT(bufferSize >= sizeof(SkPoint));
// Start out with the fan point. A negative resolve level indicates the fan point.
vertexWriter << -1.f/*resolveLevel*/ << -1.f/*idx*/;
// The rest is the same as for curves.
PathCurveTessellator::WriteFixedVertexBuffer(std::move(vertexWriter),
bufferSize - sizeof(SkPoint));
}
void PathWedgeTessellator::WriteFixedIndexBuffer(VertexWriter vertexWriter, size_t bufferSize) {
SkASSERT(bufferSize >= sizeof(uint16_t) * 3);
// Start out with the fan triangle.
vertexWriter << (uint16_t)0 << (uint16_t)1 << (uint16_t)2;
// The rest is the same as for curves, with a baseIndex of 1.
PathCurveTessellator::WriteFixedIndexBufferBaseIndex(std::move(vertexWriter),
bufferSize - sizeof(uint16_t) * 3,
1);
}
#if SK_GPU_V1
SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedVertexBufferKey);
SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedIndexBufferKey);
void PathWedgeTessellator::prepareFixedCountBuffers(GrMeshDrawTarget* target) {
GrResourceProvider* rp = target->resourceProvider();
SKGPU_DEFINE_STATIC_UNIQUE_KEY(gFixedVertexBufferKey);
fFixedVertexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kVertex,
FixedVertexBufferSize(kMaxFixedResolveLevel),
gFixedVertexBufferKey,
WriteFixedVertexBuffer);
SKGPU_DEFINE_STATIC_UNIQUE_KEY(gFixedIndexBufferKey);
fFixedIndexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kIndex,
FixedIndexBufferSize(kMaxFixedResolveLevel),
gFixedIndexBufferKey,
WriteFixedIndexBuffer);
}
void PathWedgeTessellator::drawTessellated(GrOpFlushState* flushState) const {
for (const GrVertexChunk& chunk : fVertexChunkArray) {
flushState->bindBuffers(nullptr, nullptr, chunk.fBuffer);
flushState->draw(chunk.fCount * 5, chunk.fBase * 5);
}
}
void PathWedgeTessellator::drawFixedCount(GrOpFlushState* flushState) const {
if (!fFixedVertexBuffer || !fFixedIndexBuffer) {
return;
}
// Emit 3 vertices per curve triangle, plus 3 more for the fan triangle.
int fixedIndexCount = (NumCurveTrianglesAtResolveLevel(fFixedResolveLevel) + 1) * 3;
for (const GrVertexChunk& chunk : fVertexChunkArray) {
flushState->bindBuffers(fFixedIndexBuffer, chunk.fBuffer, fFixedVertexBuffer);
flushState->drawIndexedInstanced(fixedIndexCount, 0, chunk.fCount, chunk.fBase, 0);
}
}
#endif
} // namespace skgpu