Add an isinf() polyfill for tessellation shaders

Tessellation uses infinity to flag conics, and also as a conic weight
that converts them into triangles. The es2 shading language doesn't
support infinity. This CL adds a "portable infinity" mode, where we
pretend inputs >= 2^126 are infinity. The rationale for doing this is
that those numbers are so large, they will overflow if we try to do any
bezier math with then anyway.

Bug: chromium:1220246
Change-Id: I3ad29ebb6fbbad5d23cfdba7a2717605009f8180
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/423697
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
This commit is contained in:
Chris Dalton 2021-07-01 11:17:53 -06:00 committed by Skia Commit-Bot
parent ebf89a0eed
commit 6904303a66
29 changed files with 210 additions and 158 deletions

View File

@ -202,8 +202,7 @@ DEF_PATH_TESS_BENCH(middle_out_triangulation,
int baseVertex;
GrVertexWriter vertexWriter = static_cast<SkPoint*>(fTarget->makeVertexSpace(
sizeof(SkPoint), kNumCubicsInChalkboard, &buffer, &baseVertex));
GrMiddleOutPolygonTriangulator::WritePathInnerFan(
&vertexWriter, GrMiddleOutPolygonTriangulator::OutputType::kTriangles, fPath);
GrMiddleOutPolygonTriangulator::WritePathInnerFan(&vertexWriter, 0, 0, fPath);
}
using PathStrokeList = GrStrokeTessellator::PathStrokeList;
@ -216,7 +215,7 @@ static std::unique_ptr<GrStrokeTessellator> make_hw_tessellator(
ShaderFlags shaderFlags, const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix,
PathStrokeList* pathStrokeList, std::array<float, 2> matrixMinMaxScales,
const SkRect& strokeCullBounds) {
return std::make_unique<GrStrokeHardwareTessellator>(shaderFlags, shaderCaps, viewMatrix,
return std::make_unique<GrStrokeHardwareTessellator>(shaderCaps, shaderFlags, viewMatrix,
pathStrokeList, matrixMinMaxScales,
strokeCullBounds);
}
@ -225,9 +224,9 @@ static std::unique_ptr<GrStrokeTessellator> make_fixed_count_tessellator(
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,
shaderCaps);
return std::make_unique<GrStrokeFixedCountTessellator>(shaderCaps, shaderFlags, viewMatrix,
pathStrokeList, matrixMinMaxScales,
strokeCullBounds);
}
using MakePathStrokesFn = std::vector<PathStrokeList>(*)();

View File

@ -53,6 +53,7 @@ GrShaderCaps::GrShaderCaps(const GrContextOptions& options) {
fSampleMaskSupport = false;
fExternalTextureSupport = false;
fVertexIDSupport = false;
fInfinitySupport = false;
fBitManipulationSupport = false;
fFloatIs32Bits = true;
fHalfIs32Bits = false;
@ -140,6 +141,7 @@ void GrShaderCaps::dumpJSON(SkJSONWriter* writer) const {
writer->appendBool("Sample mask support", fSampleMaskSupport);
writer->appendBool("External texture support", fExternalTextureSupport);
writer->appendBool("sk_VertexID support", fVertexIDSupport);
writer->appendBool("Infinity support", fInfinitySupport);
writer->appendBool("Bit manipulation support", fBitManipulationSupport);
writer->appendBool("float == fp32", fFloatIs32Bits);
writer->appendBool("half == fp32", fHalfIs32Bits);

View File

@ -76,7 +76,10 @@ public:
bool vertexIDSupport() const { return fVertexIDSupport; }
// frexp, ldexp, findMSB, findLSB.
// isinf() is defined, and floating point infinities are handled according to IEEE standards.
bool infinitySupport() const { return fInfinitySupport; }
// frexp(), ldexp(), findMSB(), findLSB().
bool bitManipulationSupport() const { return fBitManipulationSupport; }
bool floatIs32Bits() const { return fFloatIs32Bits; }
@ -297,6 +300,7 @@ private:
bool fSampleMaskSupport : 1;
bool fExternalTextureSupport : 1;
bool fVertexIDSupport : 1;
bool fInfinitySupport : 1;
bool fBitManipulationSupport : 1;
bool fFloatIs32Bits : 1;
bool fHalfIs32Bits : 1;

View File

@ -23,8 +23,6 @@
* thereof.
*/
struct GrVertexWriter {
constexpr static uint32_t kIEEE_32_infinity = 0x7f800000;
void* fPtr;
GrVertexWriter() = default;

View File

@ -243,6 +243,7 @@ void GrD3DCaps::initShaderCaps(int vendorID, const D3D12_FEATURE_DATA_D3D12_OPTI
shaderCaps->fIntegerSupport = true;
shaderCaps->fVertexIDSupport = true;
shaderCaps->fInfinitySupport = true;
shaderCaps->fBitManipulationSupport = true;
shaderCaps->fFloatIs32Bits = true;

View File

@ -971,6 +971,13 @@ void GrGLCaps::initGLSL(const GrGLContextInfo& ctxInfo, const GrGLInterface* gli
shaderCaps->fVertexIDSupport = ctxInfo.glslGeneration() >= k330_GrGLSLGeneration;
}
if (GR_IS_GR_GL(standard)) {
shaderCaps->fInfinitySupport = true;
} else if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) {
// Desktop GLSL 3.30 == ES GLSL 3.00.
shaderCaps->fInfinitySupport = ctxInfo.glslGeneration() >= k330_GrGLSLGeneration;
}
if (GR_IS_GR_GL(standard)) {
shaderCaps->fBitManipulationSupport = ctxInfo.glslGeneration() >= k400_GrGLSLGeneration;
} else if (GR_IS_GR_GL_ES(standard) || GR_IS_GR_WEBGL(standard)) {

View File

@ -476,6 +476,7 @@ void GrMtlCaps::initShaderCaps() {
shaderCaps->fIntegerSupport = true;
shaderCaps->fNonsquareMatrixSupport = true;
shaderCaps->fVertexIDSupport = false;
shaderCaps->fInfinitySupport = true;
// Metal uses IEEE float and half floats so assuming those values here.
shaderCaps->fFloatIs32Bits = true;

View File

@ -43,15 +43,14 @@
// recursion, we manipulate an O(log N) stack to determine the correct middle-out triangulation.
class GrMiddleOutPolygonTriangulator {
public:
enum class OutputType : bool {
kTriangles, // Output 3-vertex triangles.
kConicsWithInfiniteWeight // Output 4-vertex conics with w=Inf.
};
GrMiddleOutPolygonTriangulator(GrVertexWriter* vertexWriter, OutputType outputType,
int maxPushVertexCalls)
: fVertexWriter(vertexWriter)
, fOutputType(outputType) {
// Writes out 3 SkPoints per triangle to "vertexWriter". Additionally writes out "pad32Count"
// repetitions of "pad32Value" after each triangle. Set pad32Count to 0 if the triangles are
// to be tightly packed.
GrMiddleOutPolygonTriangulator(GrVertexWriter* vertexWriter, int pad32Count,
uint32_t pad32Value, int maxPushVertexCalls)
: fPad32Count(pad32Count)
, fPad32Value(pad32Value)
, fVertexWriter(vertexWriter) {
// Determine the deepest our stack can ever go.
int maxStackDepth = SkNextLog2(maxPushVertexCalls) + 1;
if (maxStackDepth > kStackPreallocCount) {
@ -123,9 +122,10 @@ public:
SkASSERT(fTop->fVertexIdxDelta == 0); // Ensure we are in the initial stack state.
}
static int WritePathInnerFan(GrVertexWriter* vertexWriter, OutputType outputType,
static int WritePathInnerFan(GrVertexWriter* vertexWriter, int pad32Count, uint32_t pad32Value,
const SkPath& path) {
GrMiddleOutPolygonTriangulator middleOut(vertexWriter, outputType, path.countVerbs());
GrMiddleOutPolygonTriangulator middleOut(vertexWriter, pad32Count, pad32Value,
path.countVerbs());
for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
switch (verb) {
case SkPathVerb::kMove:
@ -169,19 +169,20 @@ private:
SkASSERT(fTop > fVertexStack); // We should never pop the starting point.
--fTop;
fVertexWriter->write(fTop[0].fPoint, fTop[1].fPoint, lastPt);
if (fOutputType == OutputType::kConicsWithInfiniteWeight) {
if (fPad32Count) {
// Output a 4-point conic with w=Inf.
fVertexWriter->fill(GrVertexWriter::kIEEE_32_infinity, 2);
fVertexWriter->fill(fPad32Value, fPad32Count);
}
}
constexpr static int kStackPreallocCount = 32;
const int fPad32Count;
const uint32_t fPad32Value;
SkAutoSTMalloc<kStackPreallocCount, StackVertex> fVertexStack;
SkDEBUGCODE(int fStackAllocCount;)
StackVertex* fTop;
GrVertexWriter* fVertexWriter;
int fTotalClosedTriangleCount = 0;
OutputType fOutputType;
};
#endif

View File

@ -22,8 +22,10 @@ constexpr static float kPrecision = GrTessellationShader::kLinearizationPrecisio
// supported by the hardware.
class CurveWriter {
public:
CurveWriter(const SkRect& cullBounds, const SkMatrix& viewMatrix, int maxSegments)
: fCullTest(cullBounds, viewMatrix)
CurveWriter(const GrShaderCaps& shaderCaps, const SkRect& cullBounds,
const SkMatrix& viewMatrix, int maxSegments)
: fShaderCaps(shaderCaps)
, fCullTest(cullBounds, viewMatrix)
, fVectorXform(viewMatrix)
, fMaxSegments_pow2(maxSegments * maxSegments)
, fMaxSegments_pow4(fMaxSegments_pow2 * fMaxSegments_pow2) {
@ -51,7 +53,7 @@ public:
}
if (numSegments_pow2 > 1) {
if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
GrTessellationShader::WriteConicPatch(p, w, &vertexWriter);
GrTessellationShader::WriteConicPatch(fShaderCaps, p, w, &vertexWriter);
}
fNumFixedSegments_pow4 = std::max(numSegments_pow2 * numSegments_pow2,
fNumFixedSegments_pow4);
@ -119,10 +121,11 @@ private:
if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
vertexWriter.write(p0, p1, p2);
// Mark this instance as a triangle by setting it to a conic with w=Inf.
vertexWriter.fill(GrVertexWriter::kIEEE_32_infinity, 2);
vertexWriter.fill(GrTessellationShader::PortableInfinityBits32(fShaderCaps), 2);
}
}
const GrShaderCaps& fShaderCaps;
GrCullTest fCullTest;
GrVectorXform fVectorXform;
const float fMaxSegments_pow2;
@ -166,6 +169,8 @@ void GrPathCurveTessellator::prepare(GrMeshDrawTarget* target, const SkRect& cul
const BreadcrumbTriangleList* breadcrumbTriangleList) {
SkASSERT(fVertexChunkArray.empty());
const GrShaderCaps& shaderCaps = *target->caps().shaderCaps();
// Determine how many triangles to allocate.
int maxTriangles = 0;
if (fDrawInnerFan) {
@ -193,9 +198,10 @@ void GrPathCurveTessellator::prepare(GrMeshDrawTarget* target, const SkRect& cul
}
int numRemainingTriangles = maxTriangles;
if (fDrawInnerFan) {
// Pad the triangles with 2 infinities. This produces conic patches with w=Inf.
int numWritten = GrMiddleOutPolygonTriangulator::WritePathInnerFan(
&vertexWriter,
GrMiddleOutPolygonTriangulator::OutputType::kConicsWithInfiniteWeight, path);
&vertexWriter, 2, GrPathTessellationShader::PortableInfinityBits32(shaderCaps),
path);
numRemainingTriangles -= numWritten;
}
if (breadcrumbTriangleList) {
@ -216,7 +222,7 @@ void GrPathCurveTessellator::prepare(GrMeshDrawTarget* target, const SkRect& cul
}
vertexWriter.writeArray(tri->fPts, 3);
// Mark this instance as a triangle by setting it to a conic with w=Inf.
vertexWriter.fill(GrVertexWriter::kIEEE_32_infinity, 2);
vertexWriter.fill(GrTessellationShader::PortableInfinityBits32(shaderCaps), 2);
++numWritten;
}
SkASSERT(count == breadcrumbTriangleList->count());
@ -230,12 +236,12 @@ void GrPathCurveTessellator::prepare(GrMeshDrawTarget* target, const SkRect& cul
// The curve shader tessellates T=0..(1/2) on the first side of the canonical triangle and
// T=(1/2)..1 on the second side. This means we get double the max tessellation segments
// for the range T=0..1.
maxSegments = target->caps().shaderCaps()->maxTessellationSegments() * 2;
maxSegments = shaderCaps.maxTessellationSegments() * 2;
} else {
maxSegments = GrPathTessellationShader::kMaxFixedCountSegments;
}
CurveWriter curveWriter(cullBounds, fShader->viewMatrix(), maxSegments);
CurveWriter curveWriter(shaderCaps, cullBounds, fShader->viewMatrix(), maxSegments);
for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
switch (verb) {
case SkPathVerb::kQuad:

View File

@ -13,7 +13,6 @@
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrResourceProvider.h"
#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/tessellate/GrPathCurveTessellator.h"
#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
@ -49,13 +48,14 @@ private:
GrGLSLGeometryProcessor* HullShader::createGLSLInstance(const GrShaderCaps&) const {
class Impl : public GrPathTessellationShader::Impl {
void emitVertexCode(const GrPathTessellationShader&, GrGLSLVertexBuilder* v,
GrGPArgs* gpArgs) override {
void emitVertexCode(const GrShaderCaps& shaderCaps, const GrPathTessellationShader&,
GrGLSLVertexBuilder* v, GrGPArgs* gpArgs) override {
v->insertFunction(SkSLPortable_isinf(shaderCaps));
v->codeAppend(R"(
float4x2 P = float4x2(input_points_0_1, input_points_2_3);
if (isinf(P[3].y)) { // Is the curve a conic?
if (isinf_portable(P[3].y)) { // Is the curve a conic?
float w = P[3].x;
if (isinf(w)) {
if (isinf_portable(w)) {
// A conic with w=Inf is an exact triangle.
P = float4x2(P[0], P[1], P[2], P[2]);
} else {
@ -84,7 +84,7 @@ GrGLSLGeometryProcessor* HullShader::createGLSLInstance(const GrShaderCaps&) con
}
})");
if (v->getProgramBuilder()->caps()->shaderCaps()->vertexIDSupport()) {
if (shaderCaps.vertexIDSupport()) {
// If we don't have sk_VertexID support then "vertexidx" already came in as a
// vertex attrib.
v->codeAppend(R"(

View File

@ -12,7 +12,6 @@
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrResourceProvider.h"
#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
#include "src/gpu/tessellate/GrPathCurveTessellator.h"
@ -50,9 +49,9 @@ private:
GrGLSLGeometryProcessor* BoundingBoxShader::createGLSLInstance(const GrShaderCaps&) const {
class Impl : public GrPathTessellationShader::Impl {
void emitVertexCode(const GrPathTessellationShader&, GrGLSLVertexBuilder* v,
GrGPArgs* gpArgs) override {
if (v->getProgramBuilder()->caps()->shaderCaps()->vertexIDSupport()) {
void emitVertexCode(const GrShaderCaps& shaderCaps, const GrPathTessellationShader&,
GrGLSLVertexBuilder* v, GrGPArgs* gpArgs) override {
if (shaderCaps.vertexIDSupport()) {
// If we don't have sk_VertexID support then "unitCoord" already came in as a vertex
// attrib.
v->codeAppendf(R"(
@ -185,9 +184,8 @@ void GrPathStencilCoverOp::onPrepare(GrOpFlushState* flushState) {
GrEagerDynamicVertexAllocator vertexAlloc(flushState, &fFanBuffer, &fFanBaseVertex);
int maxFanTriangles = fPath.countVerbs() - 2; // n - 2 triangles make an n-gon.
GrVertexWriter triangleVertexWriter = vertexAlloc.lock<SkPoint>(maxFanTriangles * 3);
fFanVertexCount = GrMiddleOutPolygonTriangulator::WritePathInnerFan(
&triangleVertexWriter, GrMiddleOutPolygonTriangulator::OutputType::kTriangles,
fPath) * 3;
fFanVertexCount = 3 * GrMiddleOutPolygonTriangulator::WritePathInnerFan(
&triangleVertexWriter, 0, 0, fPath);
SkASSERT(fFanVertexCount <= maxFanTriangles * 3);
vertexAlloc.unlock(fFanVertexCount);
}

View File

@ -139,15 +139,16 @@ public:
fNumFixedSegments_pow4 = std::max(numSegments_pow4, fNumFixedSegments_pow4);
}
SK_ALWAYS_INLINE void writeConicWedge(GrVertexChunkBuilder* chunker, const SkPoint p[3],
SK_ALWAYS_INLINE void writeConicWedge(const GrShaderCaps& shaderCaps,
GrVertexChunkBuilder* chunker, const SkPoint p[3],
float w, SkPoint midpoint) {
float numSegments_pow2 = GrWangsFormula::conic_pow2(kPrecision, p, w, fVectorXform);
if (GrWangsFormula::conic_pow2(kPrecision, p, w, fVectorXform) > fMaxSegments_pow2) {
this->chopAndWriteConicWedges(chunker, {p, w}, midpoint);
this->chopAndWriteConicWedges(shaderCaps, chunker, {p, w}, midpoint);
return;
}
if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
GrTessellationShader::WriteConicPatch(p, w, &vertexWriter);
GrTessellationShader::WriteConicPatch(shaderCaps, p, w, &vertexWriter);
vertexWriter.write(midpoint);
}
fNumFixedSegments_pow4 = std::max(numSegments_pow2 * numSegments_pow2,
@ -185,15 +186,15 @@ private:
}
}
void chopAndWriteConicWedges(GrVertexChunkBuilder* chunker, const SkConic& conic,
SkPoint midpoint) {
void chopAndWriteConicWedges(const GrShaderCaps& shaderCaps, GrVertexChunkBuilder* chunker,
const SkConic& conic, SkPoint midpoint) {
SkConic chops[2];
if (!conic.chopAt(.5, chops)) {
return;
}
for (int i = 0; i < 2; ++i) {
if (fCullTest.areVisible3(chops[i].fPts)) {
this->writeConicWedge(chunker, chops[i].fPts, chops[i].fW, midpoint);
this->writeConicWedge(shaderCaps, chunker, chops[i].fPts, chops[i].fW, midpoint);
} else {
this->writeFlatWedge(chunker, chops[i].fPts[0], chops[i].fPts[2], midpoint);
}
@ -269,6 +270,7 @@ void GrPathWedgeTessellator::prepare(GrMeshDrawTarget* target, const SkRect& cul
maxSegments = GrPathTessellationShader::kMaxFixedCountSegments;
}
const GrShaderCaps& shaderCaps = *target->caps().shaderCaps();
WedgeWriter wedgeWriter(cullBounds, fShader->viewMatrix(), maxSegments);
MidpointContourParser parser(path);
while (parser.parseNextContour()) {
@ -291,7 +293,7 @@ void GrPathWedgeTessellator::prepare(GrMeshDrawTarget* target, const SkRect& cul
lastPoint = pts[2];
break;
case SkPathVerb::kConic:
wedgeWriter.writeConicWedge(&chunker, pts, *w, midpoint);
wedgeWriter.writeConicWedge(shaderCaps, &chunker, pts, *w, midpoint);
lastPoint = pts[2];
break;
case SkPathVerb::kCubic:

View File

@ -77,15 +77,15 @@ public:
fMaxParametricSegments_pow4);
}
SK_ALWAYS_INLINE void conicTo(const SkPoint p[3], float w) {
SK_ALWAYS_INLINE void conicTo(const GrShaderCaps& shaderCaps, const SkPoint p[3], float w) {
float n = GrWangsFormula::conic_pow2(fParametricPrecision, p, w);
float numParametricSegments_pow4 = n*n;
if (numParametricSegments_pow4 > kMaxParametricSegments_pow4) {
this->chopConicTo({p, w});
this->chopConicTo(shaderCaps, {p, w});
return;
}
SkPoint conic[4];
GrTessellationShader::WriteConicPatch(p, w, conic);
GrTessellationShader::WriteConicPatch(shaderCaps, p, w, conic);
SkPoint endControlPoint = conic[1];
this->writeStroke(conic, endControlPoint);
fMaxParametricSegments_pow4 = std::max(numParametricSegments_pow4,
@ -147,14 +147,14 @@ private:
}
}
void chopConicTo(const SkConic& conic) {
void chopConicTo(const GrShaderCaps& shaderCaps, const SkConic& conic) {
SkConic chops[2];
if (!conic.chopAt(.5f, chops)) {
return;
}
for (int i = 0; i < 2; ++i) {
if (fCullTest.areVisible3(chops[i].fPts)) {
this->conicTo(chops[i].fPts, chops[i].fW);
this->conicTo(shaderCaps, chops[i].fPts, chops[i].fW);
} else {
this->discardStroke(chops[i].fPts, 3);
}
@ -235,21 +235,22 @@ static int worst_case_edges_in_join(SkPaint::Join joinType, float numRadialSegme
} // namespace
GrStrokeFixedCountTessellator::GrStrokeFixedCountTessellator(ShaderFlags shaderFlags,
GrStrokeFixedCountTessellator::GrStrokeFixedCountTessellator(const GrShaderCaps& shaderCaps,
ShaderFlags shaderFlags,
const SkMatrix& viewMatrix,
PathStrokeList* pathStrokeList,
std::array<float,2> matrixMinMaxScales,
const SkRect& strokeCullBounds,
const GrShaderCaps& shaderCaps)
: GrStrokeTessellator(GrStrokeTessellationShader::Mode::kFixedCount, shaderFlags,
kMaxParametricSegments_log2, viewMatrix, pathStrokeList,
matrixMinMaxScales, strokeCullBounds, shaderCaps) {
PathStrokeList* pathStrokeList,std::array<float,
2> matrixMinMaxScales,
const SkRect& strokeCullBounds)
: GrStrokeTessellator(shaderCaps, GrStrokeTessellationShader::Mode::kFixedCount,
shaderFlags, kMaxParametricSegments_log2, viewMatrix,
pathStrokeList, matrixMinMaxScales, strokeCullBounds) {
}
GR_DECLARE_STATIC_UNIQUE_KEY(gVertexIDFallbackBufferKey);
void GrStrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target,
int totalCombinedVerbCnt) {
const GrShaderCaps& shaderCaps = *target->caps().shaderCaps();
int maxEdgesInJoin = 0;
float maxRadialSegmentsPerRadian = 0;
@ -335,7 +336,7 @@ void GrStrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target,
instanceWriter.lineTo(p[0], cusp);
instanceWriter.lineTo(cusp, p[2]);
} else {
instanceWriter.conicTo(p, strokeIter.w());
instanceWriter.conicTo(shaderCaps, p, strokeIter.w());
}
break;
case Verb::kCubic:

View File

@ -15,9 +15,9 @@
// instance are emitted as degenerate triangles.
class GrStrokeFixedCountTessellator : public GrStrokeTessellator {
public:
GrStrokeFixedCountTessellator(ShaderFlags, const SkMatrix&, PathStrokeList*,
std::array<float,2> matrixMinMaxScales,
const SkRect& strokeCullBounds, const GrShaderCaps&);
GrStrokeFixedCountTessellator(const GrShaderCaps&, ShaderFlags, const SkMatrix&,
PathStrokeList*, std::array<float, 2> matrixMinMaxScales,
const SkRect& strokeCullBounds);
void prepare(GrMeshDrawTarget*, int totalCombinedVerbCnt) override;
void draw(GrOpFlushState*) const override;

View File

@ -167,8 +167,8 @@ public:
// Recursively chops the given conic and its previous join until the segments fit in
// tessellation patches.
void writeConicPatchesTo(const SkPoint p[3], float w) {
this->internalConicPatchesTo(fStrokeJoinType, p, w);
void writeConicPatchesTo(const GrShaderCaps& shaderCaps, const SkPoint p[3], float w) {
this->internalConicPatchesTo(shaderCaps, fStrokeJoinType, p, w);
}
// Chops the given cubic at points of inflection and 180-degree rotation, and then recursively
@ -350,8 +350,8 @@ private:
// Recursively chops the given conic and its previous join until the segments fit in
// tessellation patches.
void internalConicPatchesTo(JoinType prevJoinType, const SkPoint p[3], float w,
int maxDepth = -1) {
void internalConicPatchesTo(const GrShaderCaps& shaderCaps, JoinType prevJoinType,
const SkPoint p[3], float w, int maxDepth = -1) {
if (!fCullTest.areVisible3(p)) {
// The stroke is out of view. Discard it.
this->discardStroke(p, 3);
@ -370,7 +370,7 @@ private:
if (w == 1) {
GrPathUtils::convertQuadToCubic(p, asPatch);
} else {
GrTessellationShader::WriteConicPatch(p, w, asPatch);
GrTessellationShader::WriteConicPatch(shaderCaps, p, w, asPatch);
}
float numParametricSegments_pow4;
@ -411,18 +411,19 @@ private:
} else {
SkChopQuadAtMidTangent(p, chops);
}
this->internalConicPatchesTo(prevJoinType, chops, 1, maxDepth - 1);
this->internalConicPatchesTo(JoinType::kBowtie, chops + 2, 1, maxDepth - 1);
this->internalConicPatchesTo(shaderCaps, prevJoinType, chops, 1, maxDepth - 1);
this->internalConicPatchesTo(shaderCaps, JoinType::kBowtie, chops + 2, 1,
maxDepth - 1);
} else {
SkConic conic(p, w);
float chopT = (numParametricSegments >= numRadialSegments) ? .5f
: conic.findMidTangent();
SkConic chops[2];
if (conic.chopAt(chopT, chops)) {
this->internalConicPatchesTo(prevJoinType, chops[0].fPts, chops[0].fW,
maxDepth - 1);
this->internalConicPatchesTo(JoinType::kBowtie, chops[1].fPts, chops[1].fW,
maxDepth - 1);
this->internalConicPatchesTo(shaderCaps, prevJoinType, chops[0].fPts,
chops[0].fW, maxDepth - 1);
this->internalConicPatchesTo(shaderCaps, JoinType::kBowtie, chops[1].fPts,
chops[1].fW, maxDepth - 1);
}
}
return;
@ -703,20 +704,22 @@ SK_ALWAYS_INLINE static bool cubic_has_cusp(const SkPoint p[4]) {
} // namespace
GrStrokeHardwareTessellator::GrStrokeHardwareTessellator(ShaderFlags shaderFlags,
const GrShaderCaps& shaderCaps,
GrStrokeHardwareTessellator::GrStrokeHardwareTessellator(const GrShaderCaps& shaderCaps,
ShaderFlags shaderFlags,
const SkMatrix& viewMatrix,
PathStrokeList* pathStrokeList,
std::array<float,2> matrixMinMaxScales,
const SkRect& strokeCullBounds)
: GrStrokeTessellator(GrStrokeTessellationShader::Mode::kHardwareTessellation, shaderFlags,
SkNextLog2(shaderCaps.maxTessellationSegments()), viewMatrix,
pathStrokeList, matrixMinMaxScales, strokeCullBounds, shaderCaps) {
: GrStrokeTessellator(shaderCaps, GrStrokeTessellationShader::Mode::kHardwareTessellation,
shaderFlags, SkNextLog2(shaderCaps.maxTessellationSegments()),
viewMatrix, pathStrokeList, matrixMinMaxScales, strokeCullBounds) {
}
void GrStrokeHardwareTessellator::prepare(GrMeshDrawTarget* target, int totalCombinedVerbCnt) {
using JoinType = PatchWriter::JoinType;
const GrShaderCaps& shaderCaps = *target->caps().shaderCaps();
// Over-allocate enough patches for 1 in 4 strokes to chop and for 8 extra caps.
int strokePreallocCount = totalCombinedVerbCnt * 5/4;
int capPreallocCount = 8;
@ -807,7 +810,7 @@ void GrStrokeHardwareTessellator::prepare(GrMeshDrawTarget* target, int totalCom
if (!patchWriter.stroke180FitsInPatch(numParametricSegments_pow4)) {
// The curve requires more tessellation segments than the hardware can
// support. This is rare. Recursively chop until each sub-curve fits.
patchWriter.writeConicPatchesTo(p, 1);
patchWriter.writeConicPatchesTo(shaderCaps, p, 1);
continue;
}
// The curve fits in a single tessellation patch. This is the most common case.
@ -846,14 +849,14 @@ void GrStrokeHardwareTessellator::prepare(GrMeshDrawTarget* target, int totalCom
if (!patchWriter.stroke180FitsInPatch(numParametricSegments_pow4)) {
// The curve requires more tessellation segments than the hardware can
// support. This is rare. Recursively chop until each sub-curve fits.
patchWriter.writeConicPatchesTo(p, *w);
patchWriter.writeConicPatchesTo(shaderCaps, p, *w);
continue;
}
// The curve fits in a single tessellation patch. This is the most common
// case. Write it out directly.
prevJoinFitsInPatch = patchWriter.stroke180FitsInPatch_withJoin(
numParametricSegments_pow4);
GrTessellationShader::WriteConicPatch(p, *w, scratchPts);
GrTessellationShader::WriteConicPatch(shaderCaps, p, *w, scratchPts);
patchPts = scratchPts;
endControlPoint = p[1];
break;

View File

@ -16,7 +16,7 @@
// MSAA if antialiasing is desired.
class GrStrokeHardwareTessellator : public GrStrokeTessellator {
public:
GrStrokeHardwareTessellator(ShaderFlags shaderFlags, const GrShaderCaps& shaderCaps,
GrStrokeHardwareTessellator(const GrShaderCaps& shaderCaps, ShaderFlags shaderFlags,
const SkMatrix& viewMatrix, PathStrokeList* pathStrokeList,
std::array<float,2> matrixMinMaxScales,
const SkRect& strokeCullBounds);

View File

@ -190,16 +190,15 @@ void GrStrokeTessellateOp::prePrepareTessellator(GrTessellationShader::ProgramAr
if (can_use_hardware_tessellation(fTotalCombinedVerbCnt, *pipeline, caps)) {
// Only use hardware tessellation if we're drawing a somewhat large number of verbs.
// Otherwise we seem to be better off using instanced draws.
fTessellator = arena->make<GrStrokeHardwareTessellator>(fShaderFlags, *caps.shaderCaps(),
fTessellator = arena->make<GrStrokeHardwareTessellator>(*caps.shaderCaps(), fShaderFlags,
fViewMatrix, &fPathStrokeList,
matrixMinMaxScales,
strokeCullBounds);
} else {
fTessellator = arena->make<GrStrokeFixedCountTessellator>(fShaderFlags, fViewMatrix,
&fPathStrokeList,
fTessellator = arena->make<GrStrokeFixedCountTessellator>(*caps.shaderCaps(), fShaderFlags,
fViewMatrix, &fPathStrokeList,
matrixMinMaxScales,
strokeCullBounds,
*caps.shaderCaps());
strokeCullBounds);
}
auto fillStencil = &GrUserStencilSettings::kUnused;

View File

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

View File

@ -39,6 +39,7 @@ bool GrTessellationPathRenderer::IsSupported(const GrCaps& caps) {
return !caps.avoidStencilBuffers() &&
caps.drawInstancedSupport() &&
caps.shaderCaps()->integerSupport() &&
GrTessellationShader::SupportsPortableInfinity(*caps.shaderCaps()) &&
!caps.disableTessellationPathRenderer();
}

View File

@ -31,8 +31,8 @@ private:
GrGLSLGeometryProcessor* SimpleTriangleShader::createGLSLInstance(const GrShaderCaps&) const {
class Impl : public GrPathTessellationShader::Impl {
void emitVertexCode(const GrPathTessellationShader&, GrGLSLVertexBuilder* v,
GrGPArgs* gpArgs) override {
void emitVertexCode(const GrShaderCaps&, const GrPathTessellationShader&,
GrGLSLVertexBuilder* v, GrGPArgs* gpArgs) override {
v->codeAppend(R"(
float2 localcoord = inputPoint;
float2 vertexpos = AFFINE_MATRIX * localcoord + TRANSLATE;)");
@ -50,19 +50,6 @@ GrPathTessellationShader* GrPathTessellationShader::MakeSimpleTriangleShader(
return arena->make<SimpleTriangleShader>(viewMatrix, color);
}
// Converts a 4-point input patch into the rational cubic it intended to represent.
const char* GrPathTessellationShader::Impl::kUnpackRationalCubicFn = R"(
float4x3 unpack_rational_cubic(float2 p0, float2 p1, float2 p2, float2 p3) {
float4x3 P = float4x3(p0,1, p1,1, p2,1, p3,1);
if (isinf(P[3].y)) {
// This patch is actually a conic. Convert to a rational cubic.
float w = P[3].x;
float3 c = P[1] * ((2.0/3.0) * w);
P = float4x3(P[0], fma(P[0], float3(1.0/3.0), c), fma(P[2], float3(1.0/3.0), c), P[2]);
}
return P;
})";
// Evaluate our point of interest using numerically stable linear interpolations. We add our own
// "safe_mix" method to guarantee we get exactly "b" when T=1. The builtin mix() function seems
// spec'd to behave this way, but empirical results results have shown it does not always.
@ -94,7 +81,7 @@ void GrPathTessellationShader::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs
kFloat2_GrSLType, "translate", &translate);
args.fVertBuilder->codeAppendf("float2x2 AFFINE_MATRIX = float2x2(%s);", affineMatrix);
args.fVertBuilder->codeAppendf("float2 TRANSLATE = %s;", translate);
this->emitVertexCode(shader, args.fVertBuilder, gpArgs);
this->emitVertexCode(*args.fShaderCaps, shader, args.fVertBuilder, gpArgs);
// Fragment shader.
const char* color;

View File

@ -187,11 +187,6 @@ protected:
const GrGeometryProcessor&) override;
protected:
// float2 eval_rational_cubic(float4x3 P, float T) { ...
//
// Converts a 4-point input patch into the rational cubic it intended to represent.
static const char* kUnpackRationalCubicFn;
// float4x3 unpack_rational_cubic(float2 p0, float2 p1, float2 p2, float2 p3) { ...
//
// Evaluate our point of interest using numerically stable linear interpolations. We add our
@ -200,8 +195,8 @@ protected:
// does not always.
static const char* kEvalRationalCubicFn;
virtual void emitVertexCode(const GrPathTessellationShader&, GrGLSLVertexBuilder*,
GrGPArgs*) = 0;
virtual void emitVertexCode(const GrShaderCaps&, const GrPathTessellationShader&,
GrGLSLVertexBuilder*, GrGPArgs*) = 0;
GrGLSLUniformHandler::UniformHandle fAffineMatrixUniform;
GrGLSLUniformHandler::UniformHandle fTranslateUniform;

View File

@ -46,12 +46,14 @@ private:
GrGLSLGeometryProcessor* HardwareWedgeShader::createGLSLInstance(const GrShaderCaps&) const {
class Impl : public GrPathTessellationShader::Impl {
void emitVertexCode(const GrPathTessellationShader&, GrGLSLVertexBuilder* v,
GrGPArgs*) override {
void emitVertexCode(const GrShaderCaps& shaderCaps, const GrPathTessellationShader&,
GrGLSLVertexBuilder* v, GrGPArgs*) override {
v->declareGlobal(GrShaderVar("vsPt", kFloat2_GrSLType, GrShaderVar::TypeModifier::Out));
v->insertFunction(SkSLPortable_isinf(shaderCaps));
v->codeAppend(R"(
// If y is infinity then x is a conic weight. Don't transform.
vsPt = (isinf(inputPoint.y)) ? inputPoint : AFFINE_MATRIX * inputPoint + TRANSLATE;)");
vsPt = (isinf_portable(inputPoint.y)) ? inputPoint
: AFFINE_MATRIX * inputPoint + TRANSLATE;)");
}
SkString getTessControlShaderGLSL(const GrGeometryProcessor&,
const char* versionAndExtensionDecls,
@ -64,7 +66,7 @@ GrGLSLGeometryProcessor* HardwareWedgeShader::createGLSLInstance(const GrShaderC
#define PRECISION %f)", GrTessellationShader::kLinearizationPrecision);
code.append(kSkSLTypeDefs);
code.append(GrWangsFormula::as_sksl());
code.append(kUnpackRationalCubicFn);
code.append(SkSLPortable_isinf(shaderCaps));
code.append(R"(
layout(vertices = 1) out;
@ -76,7 +78,7 @@ GrGLSLGeometryProcessor* HardwareWedgeShader::createGLSLInstance(const GrShaderC
void main() {
mat4x2 P = mat4x2(vsPt[0], vsPt[1], vsPt[2], vsPt[3]);
float numSegments;
if (isinf(P[3].y)) {
if (isinf_portable(P[3].y)) {
// This is a conic.
float w = P[3].x;
numSegments = wangs_formula_conic(PRECISION, mat3x2(P), w);
@ -174,12 +176,14 @@ private:
GrGLSLGeometryProcessor* HardwareCurveShader::createGLSLInstance(const GrShaderCaps&) const {
class Impl : public GrPathTessellationShader::Impl {
void emitVertexCode(const GrPathTessellationShader&, GrGLSLVertexBuilder* v,
GrGPArgs*) override {
void emitVertexCode(const GrShaderCaps& shaderCaps, const GrPathTessellationShader&,
GrGLSLVertexBuilder* v, GrGPArgs*) override {
v->declareGlobal(GrShaderVar("P", kFloat2_GrSLType, GrShaderVar::TypeModifier::Out));
v->insertFunction(SkSLPortable_isinf(shaderCaps));
v->codeAppend(R"(
// If y is infinity then x is a conic weight. Don't transform.
P = (isinf(inputPoint.y)) ? inputPoint : AFFINE_MATRIX * inputPoint + TRANSLATE;)");
P = (isinf_portable(inputPoint.y)) ? inputPoint
: AFFINE_MATRIX * inputPoint + TRANSLATE;)");
}
SkString getTessControlShaderGLSL(const GrGeometryProcessor&,
const char* versionAndExtensionDecls,
@ -192,7 +196,7 @@ GrGLSLGeometryProcessor* HardwareCurveShader::createGLSLInstance(const GrShaderC
#define PRECISION %f)", GrTessellationShader::kLinearizationPrecision);
code.append(kSkSLTypeDefs);
code.append(GrWangsFormula::as_sksl());
code.append(kUnpackRationalCubicFn);
code.append(SkSLPortable_isinf(shaderCaps));
code.append(R"(
layout(vertices = 1) out;
@ -203,7 +207,7 @@ GrGLSLGeometryProcessor* HardwareCurveShader::createGLSLInstance(const GrShaderC
void main() {
float w = -1; // w<0 means a cubic.
vec2 p1w = P[1];
if (isinf(P[3].y)) {
if (isinf_portable(P[3].y)) {
// This patch is actually a conic. Project to homogeneous space.
w = P[3].x;
p1w *= w;
@ -218,7 +222,7 @@ GrGLSLGeometryProcessor* HardwareCurveShader::createGLSLInstance(const GrShaderC
vec2 abcd = (abc + bcd) * .5;
float n0, n1;
if (w < 0 || isinf(w)) {
if (w < 0 || isinf_portable(w)) {
if (w < 0) {
// The patch is a cubic. Calculate how many segments are required to
// linearize each half of the curve.

View File

@ -9,7 +9,6 @@
#include "src/core/SkMathPriv.h"
#include "src/gpu/geometry/GrWangsFormula.h"
#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
namespace {
@ -61,13 +60,14 @@ private:
GrGLSLGeometryProcessor* MiddleOutShader::createGLSLInstance(const GrShaderCaps&) const {
class Impl : public GrPathTessellationShader::Impl {
void emitVertexCode(const GrPathTessellationShader& shader, GrGLSLVertexBuilder* v,
GrGPArgs* gpArgs) override {
void emitVertexCode(const GrShaderCaps& shaderCaps, const GrPathTessellationShader& shader,
GrGLSLVertexBuilder* v, GrGPArgs* gpArgs) override {
v->defineConstant("PRECISION", GrTessellationShader::kLinearizationPrecision);
v->defineConstant("MAX_FIXED_RESOLVE_LEVEL", (float)kMaxFixedCountResolveLevel);
v->defineConstant("MAX_FIXED_SEGMENTS", (float)kMaxFixedCountSegments);
v->insertFunction(GrWangsFormula::as_sksl().c_str());
if (v->getProgramBuilder()->shaderCaps()->bitManipulationSupport()) {
v->insertFunction(SkSLPortable_isinf(shaderCaps));
if (shaderCaps.bitManipulationSupport()) {
v->insertFunction(R"(
float ldexp_portable(float x, float p) {
return ldexp(x, int(p));
@ -90,7 +90,7 @@ GrGLSLGeometryProcessor* MiddleOutShader::createGLSLInstance(const GrShaderCaps&
} else)"); // Fall through to next if ().
}
v->codeAppend(R"(
if (isinf(inputPoints_2_3.z)) {
if (isinf_portable(inputPoints_2_3.z)) {
// A conic with w=Inf is an exact triangle.
localcoord = (resolveLevel != 0) ? inputPoints_0_1.zw
: (idxInResolveLevel != 0) ? inputPoints_2_3.xy
@ -99,7 +99,7 @@ GrGLSLGeometryProcessor* MiddleOutShader::createGLSLInstance(const GrShaderCaps&
float w = -1; // w < 0 tells us to treat the instance as an integral cubic.
float4x2 P = float4x2(inputPoints_0_1, inputPoints_2_3);
float maxResolveLevel;
if (isinf(P[3].y)) {
if (isinf_portable(P[3].y)) {
// The patch is a conic.
w = P[3].x;
maxResolveLevel =

View File

@ -13,11 +13,11 @@
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/tessellate/GrStrokeTessellator.h"
GrStrokeTessellationShader::GrStrokeTessellationShader(Mode mode, ShaderFlags shaderFlags,
GrStrokeTessellationShader::GrStrokeTessellationShader(const GrShaderCaps& shaderCaps, Mode mode,
ShaderFlags shaderFlags,
const SkMatrix& viewMatrix,
const SkStrokeRec& stroke, SkPMColor4f color,
int8_t maxParametricSegments_log2,
const GrShaderCaps& shaderCaps)
int8_t maxParametricSegments_log2)
: GrTessellationShader(kTessellate_GrStrokeTessellationShader_ClassID,
(mode == Mode::kHardwareTessellation)
? GrPrimitiveType::kPatches

View File

@ -98,8 +98,8 @@ public:
};
// 'viewMatrix' is applied to the geometry post tessellation. It cannot have perspective.
GrStrokeTessellationShader(Mode, ShaderFlags, const SkMatrix& viewMatrix, const SkStrokeRec&,
SkPMColor4f, int8_t maxParametricSegments_log2, const GrShaderCaps&);
GrStrokeTessellationShader(const GrShaderCaps&, Mode, ShaderFlags, const SkMatrix& viewMatrix,
const SkStrokeRec&, SkPMColor4f, int8_t maxParametricSegments_log2);
Mode mode() const { return fMode; }
ShaderFlags flags() const { return fShaderFlags; }

View File

@ -57,6 +57,7 @@ void GrStrokeTessellationShader::HardwareImpl::onEmitCode(EmitArgs& args, GrGPAr
v->insertFunction(kCosineBetweenVectorsFn);
v->insertFunction(kMiterExtentFn);
v->insertFunction(kUncheckedMixFn);
v->insertFunction(SkSLPortable_isinf(*args.fShaderCaps));
if (shader.hasDynamicStroke()) {
v->insertFunction(kNumRadialSegmentsPerRadianFn);
}
@ -113,7 +114,7 @@ void GrStrokeTessellationShader::HardwareImpl::onEmitCode(EmitArgs& args, GrGPAr
// translate until the end; we just need to perform the scale and skew right now.
v->codeAppend(R"(
P = AFFINE_MATRIX * P;
if (isinf(pts23Attr.w)) {
if (isinf_portable(pts23Attr.w)) {
// If y3 is infinity then x3 is a conic weight. Don't transform.
P[3] = pts23Attr.zw;
}
@ -125,7 +126,7 @@ void GrStrokeTessellationShader::HardwareImpl::onEmitCode(EmitArgs& args, GrGPAr
// (pre-chopping) input points or else the seams might crack.
float2 prevJoinTangent = P[0] - prevControlPoint;
float2 tan0 = ((P[1] == P[0]) ? P[2] : P[1]) - P[0];
float2 tan1 = (P[3] == P[2] || isinf(P[3].y)) ? P[2] - P[1] : P[3] - P[2];
float2 tan1 = (P[3] == P[2] || isinf_portable(P[3].y)) ? P[2] - P[1] : P[3] - P[2];
if (tan0 == float2(0)) {
// [p0, p0, p0, p3] is a reserved pattern that means this patch is a "bowtie".
@ -266,7 +267,7 @@ void GrStrokeTessellationShader::HardwareImpl::onEmitCode(EmitArgs& args, GrGPAr
}
// If the curve is a straight line, point, or conic, don't chop it into sections after all.
if ((P[0] == P[1] && P[2] == P[3]) || isinf(P[3].y)) {
if ((P[0] == P[1] && P[2] == P[3]) || isinf_portable(P[3].y)) {
chopT = float2(0);
innerTangents = float2x2(tan0, tan0);
}
@ -274,7 +275,8 @@ void GrStrokeTessellationShader::HardwareImpl::onEmitCode(EmitArgs& args, GrGPAr
// Chop the curve at chopT[0] and chopT[1].
float4 ab = unchecked_mix(P[0].xyxy, P[1].xyxy, chopT.sstt);
float4 bc = unchecked_mix(P[1].xyxy, P[2].xyxy, chopT.sstt);
float4 cd = isinf(P[3].y) ? P[2].xyxy : unchecked_mix(P[2].xyxy, P[3].xyxy, chopT.sstt);
float4 cd = isinf_portable(P[3].y) ? P[2].xyxy
: unchecked_mix(P[2].xyxy, P[3].xyxy, chopT.sstt);
float4 abc = unchecked_mix(ab, bc, chopT.sstt);
float4 bcd = unchecked_mix(bc, cd, chopT.sstt);
float4 abcd = unchecked_mix(abc, bcd, chopT.sstt);
@ -353,6 +355,7 @@ SkString GrStrokeTessellationShader::HardwareImpl::getTessControlShaderGLSL(
code.append(kAtan2Fn);
code.append(kCosineBetweenVectorsFn);
code.append(kMiterExtentFn);
code.append(SkSLPortable_isinf(shaderCaps));
code.append(R"(
float cross2d(vec2 a, vec2 b) {
return determinant(mat2(a,b));
@ -428,7 +431,7 @@ SkString GrStrokeTessellationShader::HardwareImpl::getTessControlShaderGLSL(
// Calculate the number of parametric segments. The final tessellated strip will be a
// composition of these parametric segments as well as radial segments.
float w = isinf(P[3].y) ? P[3].x : -1.0; // w<0 means the curve is an integral cubic.
float w = isinf_portable(P[3].y) ? P[3].x : -1.0; // w<0 means integral cubic.
float numParametricSegments;
if (w < 0.0) {
numParametricSegments = wangs_formula_cubic(PARAMETRIC_PRECISION, P, mat2(1));
@ -452,7 +455,7 @@ SkString GrStrokeTessellationShader::HardwareImpl::getTessControlShaderGLSL(
// Adjust sign of rotation to match the direction the curve turns.
// 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 = isinf(P[3].y) ? cross2d(P[1] - P[0], P[2] - P[1])
float turn = isinf_portable(P[3].y) ? cross2d(P[1] - P[0], P[2] - P[1])
: cross2d(P[2] - P[0], P[3] - P[1]);
if (turn == 0.0) { // This is the case for joins and cusps where points are co-located.
turn = determinant(tangents);
@ -561,6 +564,8 @@ SkString GrStrokeTessellationShader::HardwareImpl::getTessEvaluationShaderGLSL(
code.appendf("uniform vec4 %s;\n", affineMatrixName);
code.appendf("#define AFFINE_MATRIX mat2(%s)\n", affineMatrixName);
code.append(SkSLPortable_isinf(shaderCaps));
code.append(R"(
in vec4 tcsPts01[];
in vec4 tcsPt2Tan0[];
@ -632,7 +637,7 @@ SkString GrStrokeTessellationShader::HardwareImpl::getTessEvaluationShaderGLSL(
float2 tan1 = tcsEndPtEndTan.zw;
bool isFinalEdge = (gl_TessCoord.x == 1);
float w = -1.0; // w<0 means the curve is an integral cubic.
if (isinf(P[3].y)) {
if (isinf_portable(P[3].y)) {
w = P[3].x; // The curve is actually a conic.
P[3] = P[2]; // Setting p3 equal to p2 works for the remaining rotational logic.
})");

View File

@ -28,6 +28,7 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
args.fVertBuilder->insertFunction(kMiterExtentFn);
args.fVertBuilder->insertFunction(kUncheckedMixFn);
args.fVertBuilder->insertFunction(GrWangsFormula::as_sksl().c_str());
args.fVertBuilder->insertFunction(SkSLPortable_isinf(*args.fShaderCaps));
// Tessellation control uniforms and/or dynamic attributes.
if (!shader.hasDynamicStroke()) {
@ -90,7 +91,7 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
float4x2 P = float4x2(pts01Attr, pts23Attr);
float2 lastControlPoint = argsAttr.xy;
float w = -1; // w<0 means the curve is an integral cubic.
if (isinf(P[3].y)) {
if (isinf_portable(P[3].y)) {
w = P[3].x; // The curve is actually a conic.
P[3] = P[2]; // Setting p3 equal to p2 works for the remaining rotational logic.
})");

View File

@ -39,16 +39,52 @@ public:
const SkMatrix& viewMatrix() const { return fViewMatrix; }
const SkPMColor4f& color() const { return fColor;}
static bool SupportsPortableInfinity(const GrShaderCaps& shaderCaps) {
// If we don't have native infinity then we need full 32-bit floats in order to emulate it.
return shaderCaps.infinitySupport() || shaderCaps.floatIs32Bits();
}
static uint32_t PortableInfinityBits32(const GrShaderCaps& shaderCaps) {
SkASSERT(SupportsPortableInfinity(shaderCaps));
// Tessellation shaders use infinity to flag conics, and also as a weight that turns conics
// into triangles.
constexpr static uint32_t kIEEE_32_infinity = 0xff << 23;
// On hardware that doesn't support infinity, we pretend that inputs >= 2^126 are infinity.
// The rationale for doing this is that these number are so large, they will overflow if we
// try to do any bezier math with them anyway.
constexpr static uint32_t kIEEE_32_pseudo_infinity =
(0xfd << 23) | (1 << 22); // 1.5 * 2^126.
return shaderCaps.infinitySupport() ? kIEEE_32_infinity : kIEEE_32_pseudo_infinity;
}
static const char* SkSLPortable_isinf(const GrShaderCaps& shaderCaps) {
SkASSERT(SupportsPortableInfinity(shaderCaps));
if (shaderCaps.infinitySupport()) {
return R"(
bool isinf_portable(float x) {
return isinf(x);
})";
} else {
SkASSERT(shaderCaps.floatIs32Bits());
return R"(
bool isinf_portable(float x) {
return abs(x) >= exp2(126);
})";
}
}
// Fills in a 4-point patch in such a way that the shader will recognize it as a conic.
static void WriteConicPatch(const SkPoint pts[3], float w, GrVertexWriter* writer) {
static void WriteConicPatch(const GrShaderCaps& shaderCaps, const SkPoint pts[3], float w,
GrVertexWriter* writer) {
// Write out the 3 conic points to patch[0..2], the weight to patch[3].x, and then set
// patch[3].y as NaN to flag this patch as a conic.
writer->writeArray(pts, 3);
writer->write(w, GrVertexWriter::kIEEE_32_infinity);
writer->write(w, PortableInfinityBits32(shaderCaps));
}
static void WriteConicPatch(const SkPoint pts[3], float w, SkPoint patch[4]) {
static void WriteConicPatch(const GrShaderCaps& shaderCaps, const SkPoint pts[3], float w,
SkPoint patch[4]) {
GrVertexWriter writer(patch);
WriteConicPatch(pts, w, &writer);
WriteConicPatch(shaderCaps, pts, w, &writer);
}
struct ProgramArgs {

View File

@ -716,6 +716,7 @@ void GrVkCaps::initShaderCaps(const VkPhysicalDeviceProperties& properties,
shaderCaps->fIntegerSupport = true;
shaderCaps->fNonsquareMatrixSupport = true;
shaderCaps->fVertexIDSupport = true;
shaderCaps->fInfinitySupport = true;
shaderCaps->fBitManipulationSupport = true;
// Assume the minimum precisions mandated by the SPIR-V spec.