blob: 336ffc1b4d6af312469d2473b24cba4db7143fb6 [file] [log] [blame]
/*
* Copyright 2022 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/ganesh/tessellate/StrokeTessellator.h"
#include "include/core/SkMatrix.h"
#include "include/private/base/SkAlignedStorage.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkOnce.h"
#include "include/private/base/SkPoint_impl.h"
#include "include/private/gpu/ganesh/GrTypesPriv.h"
#include "src/core/SkGeometry.h"
#include "src/gpu/ResourceKey.h"
#include "src/gpu/ganesh/GrBuffer.h"
#include "src/gpu/ganesh/GrCaps.h"
#include "src/gpu/ganesh/GrMeshDrawTarget.h"
#include "src/gpu/ganesh/GrOpFlushState.h"
#include "src/gpu/ganesh/GrResourceProvider.h"
#include "src/gpu/ganesh/GrShaderCaps.h"
#include "src/gpu/ganesh/tessellate/VertexChunkPatchAllocator.h"
#include "src/gpu/tessellate/FixedCountBufferUtils.h"
#include "src/gpu/tessellate/LinearTolerances.h"
#include "src/gpu/tessellate/PatchWriter.h"
#include "src/gpu/tessellate/StrokeIterator.h"
#include "src/gpu/tessellate/WangsFormula.h"
#include <algorithm>
#include <cmath>
#include <utility>
namespace skgpu::ganesh {
namespace {
using namespace skgpu::tess;
using StrokeWriter = PatchWriter<VertexChunkPatchAllocator,
Required<PatchAttribs::kJoinControlPoint>,
Optional<PatchAttribs::kStrokeParams>,
Optional<PatchAttribs::kColor>,
Optional<PatchAttribs::kWideColorIfEnabled>,
Optional<PatchAttribs::kExplicitCurveType>,
ReplicateLineEndPoints,
TrackJoinControlPoints>;
void write_fixed_count_patches(StrokeWriter&& patchWriter,
const SkMatrix& shaderMatrix,
StrokeTessellator::PathStrokeList* pathStrokeList) {
// The vector xform approximates how the control points are transformed by the shader to
// more accurately compute how many *parametric* segments are needed.
// getMaxScale() returns -1 if it can't compute a scale factor (e.g. perspective), taking the
// absolute value automatically converts that to an identity scale factor for our purposes.
patchWriter.setShaderTransform(wangs_formula::VectorXform{shaderMatrix},
std::abs(shaderMatrix.getMaxScale()));
if (!(patchWriter.attribs() & PatchAttribs::kStrokeParams)) {
// Strokes are static. Calculate tolerances once.
patchWriter.updateUniformStrokeParams(pathStrokeList->fStroke);
}
for (auto* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) {
const SkStrokeRec& stroke = pathStroke->fStroke;
if (patchWriter.attribs() & PatchAttribs::kStrokeParams) {
// Strokes are dynamic. Calculate tolerances every time.
patchWriter.updateStrokeParamsAttrib(stroke);
}
if (patchWriter.attribs() & PatchAttribs::kColor) {
patchWriter.updateColorAttrib(pathStroke->fColor);
}
StrokeIterator strokeIter(pathStroke->fPath, &pathStroke->fStroke, &shaderMatrix);
while (strokeIter.next()) {
using Verb = StrokeIterator::Verb;
const SkPoint* p = strokeIter.pts();
int numChops;
switch (strokeIter.verb()) {
case Verb::kContourFinished:
patchWriter.writeDeferredStrokePatch();
break;
case Verb::kCircle:
// Round cap or else an empty stroke that is specified to be drawn as a circle.
patchWriter.writeCircle(p[0]);
[[fallthrough]];
case Verb::kMoveWithinContour:
// A regular kMove invalidates the previous control point; the stroke iterator
// tells us a new value to use.
patchWriter.updateJoinControlPointAttrib(p[0]);
break;
case Verb::kLine:
patchWriter.writeLine(p[0], p[1]);
break;
case Verb::kQuad:
if (ConicHasCusp(p)) {
// The cusp is always at the midtandent.
SkPoint cusp = SkEvalQuadAt(p, SkFindQuadMidTangent(p));
patchWriter.writeCircle(cusp);
// A quad can only have a cusp if it's flat with a 180-degree turnaround.
patchWriter.writeLine(p[0], cusp);
patchWriter.writeLine(cusp, p[2]);
} else {
patchWriter.writeQuadratic(p);
}
break;
case Verb::kConic:
if (ConicHasCusp(p)) {
// The cusp is always at the midtandent.
SkConic conic(p, strokeIter.w());
SkPoint cusp = conic.evalAt(conic.findMidTangent());
patchWriter.writeCircle(cusp);
// A conic can only have a cusp if it's flat with a 180-degree turnaround.
patchWriter.writeLine(p[0], cusp);
patchWriter.writeLine(cusp, p[2]);
} else {
patchWriter.writeConic(p, strokeIter.w());
}
break;
case Verb::kCubic:
SkPoint chops[10];
float T[2];
bool areCusps;
numChops = FindCubicConvex180Chops(p, T, &areCusps);
if (numChops == 0) {
patchWriter.writeCubic(p);
} else if (numChops == 1) {
SkChopCubicAt(p, chops, T[0]);
if (areCusps) {
patchWriter.writeCircle(chops[3]);
// In a perfect world, these 3 points would be be equal after chopping
// on a cusp.
chops[2] = chops[4] = chops[3];
}
patchWriter.writeCubic(chops);
patchWriter.writeCubic(chops + 3);
} else {
SkASSERT(numChops == 2);
SkChopCubicAt(p, chops, T[0], T[1]);
if (areCusps) {
patchWriter.writeCircle(chops[3]);
patchWriter.writeCircle(chops[6]);
// Two cusps are only possible if it's a flat line with two 180-degree
// turnarounds.
patchWriter.writeLine(chops[0], chops[3]);
patchWriter.writeLine(chops[3], chops[6]);
patchWriter.writeLine(chops[6], chops[9]);
} else {
patchWriter.writeCubic(chops);
patchWriter.writeCubic(chops + 3);
patchWriter.writeCubic(chops + 6);
}
}
break;
}
}
}
}
} // namespace
SKGPU_DECLARE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey);
void StrokeTessellator::prepare(GrMeshDrawTarget* target,
const SkMatrix& shaderMatrix,
PathStrokeList* pathStrokeList,
int totalCombinedStrokeVerbCnt) {
LinearTolerances worstCase;
const int preallocCount = FixedCountStrokes::PreallocCount(totalCombinedStrokeVerbCnt);
StrokeWriter patchWriter{fAttribs, &worstCase, target, &fVertexChunkArray, preallocCount};
write_fixed_count_patches(std::move(patchWriter), shaderMatrix, pathStrokeList);
fVertexCount = FixedCountStrokes::VertexCount(worstCase);
if (!target->caps().shaderCaps()->fVertexIDSupport) {
// Our shader won't be able to use sk_VertexID. Bind a fallback vertex buffer with the IDs
// in it instead.
fVertexCount = std::min(fVertexCount, 2 * FixedCountStrokes::kMaxEdgesNoVertexIDs);
SKGPU_DEFINE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey);
fVertexBufferIfNoIDSupport = target->resourceProvider()->findOrMakeStaticBuffer(
GrGpuBufferType::kVertex,
FixedCountStrokes::VertexBufferSize(),
gVertexIDFallbackBufferKey,
FixedCountStrokes::WriteVertexBuffer);
}
}
void StrokeTessellator::draw(GrOpFlushState* flushState) const {
if (fVertexChunkArray.empty() || fVertexCount <= 0) {
return;
}
if (!flushState->caps().shaderCaps()->fVertexIDSupport &&
!fVertexBufferIfNoIDSupport) {
return;
}
for (const auto& instanceChunk : fVertexChunkArray) {
flushState->bindBuffers(nullptr, instanceChunk.fBuffer, fVertexBufferIfNoIDSupport);
flushState->drawInstanced(instanceChunk.fCount,
instanceChunk.fBase,
fVertexCount,
0);
}
}
} // namespace skgpu::ganesh