Delete all low-level rendering from ccpr
This converts ccpr to just a poorly named atlas manager. The atlas gets rendered by normal calls on its MSAA draw context: for (;;) { surfaceDrawContext->stencilPath(); // Stencil. } surfaceDrawContext->stencilRect(atlasBounds); // Cover. Bug: chromium:1158093 Change-Id: I758ffd372b2ed5bb8b370156b6f80f6204146700 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/381618 Reviewed-by: Brian Osman <brianosman@google.com> Commit-Queue: Chris Dalton <csmartdalton@google.com>
This commit is contained in:
parent
20c09f9290
commit
7d592cda58
17
gn/gpu.gni
17
gn/gpu.gni
@ -627,34 +627,17 @@ skia_gl_gpu_sources = [
|
||||
|
||||
skia_ccpr_sources = [
|
||||
# coverage counting path renderer
|
||||
"$_src/gpu/ccpr/GrAutoMapVertexBuffer.h",
|
||||
"$_src/gpu/ccpr/GrCCAtlas.cpp",
|
||||
"$_src/gpu/ccpr/GrCCAtlas.h",
|
||||
"$_src/gpu/ccpr/GrCCClipPath.cpp",
|
||||
"$_src/gpu/ccpr/GrCCClipPath.h",
|
||||
"$_src/gpu/ccpr/GrCCClipProcessor.cpp",
|
||||
"$_src/gpu/ccpr/GrCCClipProcessor.h",
|
||||
"$_src/gpu/ccpr/GrCCConicShader.cpp",
|
||||
"$_src/gpu/ccpr/GrCCConicShader.h",
|
||||
"$_src/gpu/ccpr/GrCCCoverageProcessor.cpp",
|
||||
"$_src/gpu/ccpr/GrCCCoverageProcessor.h",
|
||||
"$_src/gpu/ccpr/GrCCCubicShader.cpp",
|
||||
"$_src/gpu/ccpr/GrCCCubicShader.h",
|
||||
"$_src/gpu/ccpr/GrCCFillGeometry.cpp",
|
||||
"$_src/gpu/ccpr/GrCCFillGeometry.h",
|
||||
"$_src/gpu/ccpr/GrCCFiller.cpp",
|
||||
"$_src/gpu/ccpr/GrCCFiller.h",
|
||||
"$_src/gpu/ccpr/GrCCPerFlushResources.cpp",
|
||||
"$_src/gpu/ccpr/GrCCPerFlushResources.h",
|
||||
"$_src/gpu/ccpr/GrCCPerOpsTaskPaths.h",
|
||||
"$_src/gpu/ccpr/GrCCQuadraticShader.cpp",
|
||||
"$_src/gpu/ccpr/GrCCQuadraticShader.h",
|
||||
"$_src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp",
|
||||
"$_src/gpu/ccpr/GrCoverageCountingPathRenderer.h",
|
||||
"$_src/gpu/ccpr/GrSampleMaskProcessor.cpp",
|
||||
"$_src/gpu/ccpr/GrSampleMaskProcessor.h",
|
||||
"$_src/gpu/ccpr/GrStencilAtlasOp.cpp",
|
||||
"$_src/gpu/ccpr/GrStencilAtlasOp.h",
|
||||
]
|
||||
|
||||
skia_nvpr_sources = [
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "src/gpu/GrClipStack.h"
|
||||
|
||||
#include "include/core/SkMatrix.h"
|
||||
#include "src/core/SkPathPriv.h"
|
||||
#include "src/core/SkRRectPriv.h"
|
||||
#include "src/core/SkRectPriv.h"
|
||||
#include "src/core/SkTaskGroup.h"
|
||||
|
@ -746,6 +746,43 @@ void GrSurfaceDrawContext::internalStencilClear(const SkIRect* scissor, bool ins
|
||||
}
|
||||
}
|
||||
|
||||
bool GrSurfaceDrawContext::stencilPath(const GrHardClip* clip,
|
||||
GrAA doStencilMSAA,
|
||||
const SkMatrix& viewMatrix,
|
||||
const SkPath& path) {
|
||||
SkIRect clipBounds = clip ? clip->getConservativeBounds()
|
||||
: SkIRect::MakeSize(this->dimensions());
|
||||
GrStyledShape shape(path, GrStyledShape::DoSimplify::kNo);
|
||||
|
||||
GrPathRenderer::CanDrawPathArgs canDrawArgs;
|
||||
canDrawArgs.fCaps = fContext->priv().caps();
|
||||
canDrawArgs.fProxy = this->asRenderTargetProxy();
|
||||
canDrawArgs.fClipConservativeBounds = &clipBounds;
|
||||
canDrawArgs.fViewMatrix = &viewMatrix;
|
||||
canDrawArgs.fShape = &shape;
|
||||
canDrawArgs.fPaint = nullptr;
|
||||
canDrawArgs.fAAType = (doStencilMSAA == GrAA::kYes) ? GrAAType::kMSAA : GrAAType::kNone;
|
||||
canDrawArgs.fHasUserStencilSettings = false;
|
||||
canDrawArgs.fTargetIsWrappedVkSecondaryCB = this->wrapsVkSecondaryCB();
|
||||
GrPathRenderer* pr = this->drawingManager()->getPathRenderer(
|
||||
canDrawArgs, false, GrPathRendererChain::DrawType::kStencil);
|
||||
if (!pr) {
|
||||
SkDebugf("WARNING: No path renderer to stencil path.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
GrPathRenderer::StencilPathArgs args;
|
||||
args.fContext = fContext;
|
||||
args.fRenderTargetContext = this;
|
||||
args.fClip = clip;
|
||||
args.fClipConservativeBounds = &clipBounds;
|
||||
args.fViewMatrix = &viewMatrix;
|
||||
args.fShape = &shape;
|
||||
args.fDoStencilMSAA = doStencilMSAA;
|
||||
pr->stencilPath(args);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GrSurfaceDrawContext::stencilPath(const GrHardClip* clip,
|
||||
GrAA doStencilMSAA,
|
||||
const SkMatrix& viewMatrix,
|
||||
|
@ -583,6 +583,15 @@ public:
|
||||
this->drawFilledQuad(clip, std::move(paint), doStencilMSAA, &quad, ss);
|
||||
}
|
||||
|
||||
// Fills the user stencil bits with a non-zero value at every sample inside the path. This will
|
||||
// likely be implemented with a Redbook algorithm, but it is not guaranteed. The samples being
|
||||
// rendered to must be zero initially.
|
||||
bool stencilPath(const GrHardClip*,
|
||||
GrAA doStencilMSAA,
|
||||
const SkMatrix& viewMatrix,
|
||||
const SkPath&);
|
||||
|
||||
// Same as for stencilPath, but for an NVPR path object.
|
||||
void stencilPath(const GrHardClip*,
|
||||
GrAA doStencilMSAA,
|
||||
const SkMatrix& viewMatrix,
|
||||
|
@ -1477,39 +1477,3 @@ int GrTriangulator::polysToTriangles(Poly* polys, GrEagerVertexAllocator* vertex
|
||||
vertexAllocator->unlock(actualCount);
|
||||
return actualCount;
|
||||
}
|
||||
|
||||
int GrTriangulator::PathToVertices(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
|
||||
WindingVertex** verts) {
|
||||
SkArenaAlloc alloc(kArenaDefaultChunkSize);
|
||||
GrTriangulator triangulator(path, &alloc);
|
||||
bool isLinear;
|
||||
Poly* polys = triangulator.pathToPolys(tolerance, clipBounds, &isLinear);
|
||||
int64_t count64 = CountPoints(polys, path.getFillType());
|
||||
if (0 == count64 || count64 > SK_MaxS32) {
|
||||
*verts = nullptr;
|
||||
return 0;
|
||||
}
|
||||
int count = count64;
|
||||
|
||||
*verts = new WindingVertex[count];
|
||||
WindingVertex* vertsEnd = *verts;
|
||||
SkPoint* points = new SkPoint[count];
|
||||
SkPoint* pointsEnd = points;
|
||||
for (Poly* poly = polys; poly; poly = poly->fNext) {
|
||||
if (apply_fill_type(path.getFillType(), poly)) {
|
||||
SkPoint* start = pointsEnd;
|
||||
pointsEnd = static_cast<SkPoint*>(triangulator.emitPoly(poly, pointsEnd));
|
||||
while (start != pointsEnd) {
|
||||
vertsEnd->fPos = *start;
|
||||
vertsEnd->fWinding = poly->fWinding;
|
||||
++start;
|
||||
++vertsEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
int actualCount = static_cast<int>(vertsEnd - *verts);
|
||||
SkASSERT(actualCount <= count);
|
||||
SkASSERT(pointsEnd - points == actualCount);
|
||||
delete[] points;
|
||||
return actualCount;
|
||||
}
|
||||
|
@ -36,21 +36,6 @@ public:
|
||||
return count;
|
||||
}
|
||||
|
||||
struct WindingVertex {
|
||||
SkPoint fPos;
|
||||
int fWinding;
|
||||
};
|
||||
|
||||
// *DEPRECATED*: Once CCPR is removed this method will go away.
|
||||
//
|
||||
// Triangulates a path to an array of vertices. Each triangle is represented as a set of three
|
||||
// WindingVertex entries, each of which contains the position and winding count (which is the
|
||||
// same for all three vertices of a triangle). The 'verts' out parameter is set to point to the
|
||||
// resultant vertex array. CALLER IS RESPONSIBLE for deleting this buffer to avoid a memory
|
||||
// leak!
|
||||
static int PathToVertices(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
|
||||
WindingVertex** verts);
|
||||
|
||||
// Enums used by GrTriangulator internals.
|
||||
typedef enum { kLeft_Side, kRight_Side } Side;
|
||||
enum class EdgeType { kInner, kOuter, kConnector };
|
||||
|
@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef GrAutoMapVertexBuffer_DEFINED
|
||||
#define GrAutoMapVertexBuffer_DEFINED
|
||||
|
||||
#include "include/private/SkNoncopyable.h"
|
||||
#include "src/gpu/GrGpuBuffer.h"
|
||||
#include "src/gpu/GrOnFlushResourceProvider.h"
|
||||
|
||||
// This class automatically allocates and maps a GPU vertex buffer, and polyfills the mapping
|
||||
// functionality with a mirror buffer on CPU if it is not supported.
|
||||
class GrAutoMapVertexBuffer : SkNoncopyable {
|
||||
public:
|
||||
~GrAutoMapVertexBuffer() {
|
||||
if (this->isMapped()) {
|
||||
this->unmapBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
bool hasGpuBuffer() const { return SkToBool(fGpuBuffer.get()); }
|
||||
sk_sp<const GrGpuBuffer> gpuBuffer() const { return fGpuBuffer; }
|
||||
bool isMapped() const { return SkToBool(fData); }
|
||||
void* data() const { SkASSERT(this->isMapped()); return fData; }
|
||||
|
||||
void resetAndMapBuffer(GrOnFlushResourceProvider* onFlushRP, size_t sizeInBytes) {
|
||||
if (this->isMapped()) {
|
||||
this->unmapBuffer();
|
||||
}
|
||||
fGpuBuffer = onFlushRP->makeBuffer(GrGpuBufferType::kVertex, sizeInBytes);
|
||||
if (!fGpuBuffer) {
|
||||
fSizeInBytes = 0;
|
||||
fData = nullptr;
|
||||
return;
|
||||
}
|
||||
fSizeInBytes = sizeInBytes;
|
||||
fData = fGpuBuffer->map();
|
||||
if (!fData) {
|
||||
// Mapping failed. Allocate a mirror buffer on CPU.
|
||||
fData = sk_malloc_throw(fSizeInBytes);
|
||||
}
|
||||
}
|
||||
|
||||
void unmapBuffer() {
|
||||
SkASSERT(this->isMapped());
|
||||
if (fGpuBuffer->isMapped()) {
|
||||
fGpuBuffer->unmap();
|
||||
} else {
|
||||
// fData is a mirror buffer on CPU.
|
||||
fGpuBuffer->updateData(fData, fSizeInBytes);
|
||||
sk_free(fData);
|
||||
}
|
||||
fData = nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
sk_sp<GrGpuBuffer> fGpuBuffer;
|
||||
size_t fSizeInBytes = 0;
|
||||
void* fData = nullptr;
|
||||
};
|
||||
|
||||
template<typename T> class GrTAutoMapVertexBuffer : public GrAutoMapVertexBuffer {
|
||||
public:
|
||||
T& operator[](int idx) {
|
||||
SkASSERT(this->isMapped());
|
||||
SkASSERT(idx >= 0 && (size_t)idx < fSizeInBytes / sizeof(T));
|
||||
return ((T*)fData)[idx];
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
@ -47,15 +47,12 @@ void GrCCClipPath::init(const SkPath& deviceSpacePath, const SkIRect& accessRect
|
||||
fAccessRect = accessRect;
|
||||
}
|
||||
|
||||
void GrCCClipPath::accountForOwnPath(GrCCPerFlushResourceSpecs* specs) const {
|
||||
void GrCCClipPath::accountForOwnPath(GrCCAtlas::Specs* specs) const {
|
||||
SkASSERT(this->isInitialized());
|
||||
|
||||
++specs->fNumClipPaths;
|
||||
specs->fRenderedPathStats.statPath(fDeviceSpacePath);
|
||||
|
||||
SkIRect ibounds;
|
||||
if (ibounds.intersect(fAccessRect, fPathDevIBounds)) {
|
||||
specs->fRenderedAtlasSpecs.accountForSpace(ibounds.width(), ibounds.height());
|
||||
specs->accountForSpace(ibounds.width(), ibounds.height());
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +61,7 @@ void GrCCClipPath::renderPathInAtlas(GrCCPerFlushResources* resources,
|
||||
SkASSERT(this->isInitialized());
|
||||
SkASSERT(!fHasAtlas);
|
||||
fAtlas = resources->renderDeviceSpacePathInAtlas(
|
||||
fAccessRect, fDeviceSpacePath, fPathDevIBounds, GrFillRuleForSkPath(fDeviceSpacePath),
|
||||
&fDevToAtlasOffset);
|
||||
onFlushRP, fAccessRect, fDeviceSpacePath, fPathDevIBounds,
|
||||
GrFillRuleForSkPath(fDeviceSpacePath), &fDevToAtlasOffset);
|
||||
SkDEBUGCODE(fHasAtlas = true);
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ public:
|
||||
return fPathDevIBounds;
|
||||
}
|
||||
|
||||
void accountForOwnPath(GrCCPerFlushResourceSpecs*) const;
|
||||
void accountForOwnPath(GrCCAtlas::Specs*) const;
|
||||
void renderPathInAtlas(GrCCPerFlushResources*, GrOnFlushResourceProvider*);
|
||||
|
||||
const SkIVector& atlasTranslate() const {
|
||||
|
@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "src/gpu/ccpr/GrCCConicShader.h"
|
||||
|
||||
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
|
||||
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
|
||||
|
||||
void GrCCConicShader::emitSetupCode(
|
||||
GrGLSLVertexGeoBuilder* s, const char* pts, const char** outHull4) const {
|
||||
// K is distance from the line P2 -> P0. L is distance from the line P0 -> P1, scaled by 2w.
|
||||
// M is distance from the line P1 -> P2, scaled by 2w. We do this in a space where P1=0.
|
||||
s->declareGlobal(fKLMMatrix);
|
||||
s->codeAppendf("float x0 = %s[0].x - %s[1].x, x2 = %s[2].x - %s[1].x;", pts, pts, pts, pts);
|
||||
s->codeAppendf("float y0 = %s[0].y - %s[1].y, y2 = %s[2].y - %s[1].y;", pts, pts, pts, pts);
|
||||
s->codeAppendf("float w = %s[3].x;", pts);
|
||||
s->codeAppendf("%s = float3x3(y2 - y0, x0 - x2, x2*y0 - x0*y2, "
|
||||
"2*w * float2(+y0, -x0), 0, "
|
||||
"2*w * float2(-y2, +x2), 0);", fKLMMatrix.c_str());
|
||||
|
||||
s->declareGlobal(fControlPoint);
|
||||
s->codeAppendf("%s = %s[1];", fControlPoint.c_str(), pts);
|
||||
|
||||
// Scale KLM by the inverse Manhattan width of K, and make sure K is positive. This allows K to
|
||||
// double as the flat opposite edge AA. kwidth will not be 0 because we cull degenerate conics
|
||||
// on the CPU.
|
||||
s->codeAppendf("float kwidth = 2*bloat * (abs(%s[0].x) + abs(%s[0].y)) * sign(%s[0].z);",
|
||||
fKLMMatrix.c_str(), fKLMMatrix.c_str(), fKLMMatrix.c_str());
|
||||
s->codeAppendf("%s *= 1/kwidth;", fKLMMatrix.c_str());
|
||||
|
||||
if (outHull4) {
|
||||
// Clip the conic triangle by the tangent line at maximum height. Conics have the nice
|
||||
// property that maximum height always occurs at T=.5. This is a simple application for
|
||||
// De Casteljau's algorithm.
|
||||
s->codeAppendf("float2 p1w = %s[1]*w;", pts);
|
||||
s->codeAppend ("float r = 1 / (1 + w);");
|
||||
s->codeAppend ("float2 conic_hull[4];");
|
||||
s->codeAppendf("conic_hull[0] = %s[0];", pts);
|
||||
s->codeAppendf("conic_hull[1] = (%s[0] + p1w) * r;", pts);
|
||||
s->codeAppendf("conic_hull[2] = (p1w + %s[2]) * r;", pts);
|
||||
s->codeAppendf("conic_hull[3] = %s[2];", pts);
|
||||
*outHull4 = "conic_hull";
|
||||
}
|
||||
}
|
||||
|
||||
void GrCCConicShader::onEmitVaryings(
|
||||
GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code,
|
||||
const char* position, const char* coverage, const char* cornerCoverage, const char* wind) {
|
||||
code->appendf("float3 klm = float3(%s - %s, 1) * %s;",
|
||||
position, fControlPoint.c_str(), fKLMMatrix.c_str());
|
||||
fKLM_fWind.reset(kFloat3_GrSLType, scope);
|
||||
varyingHandler->addVarying("klm", &fKLM_fWind);
|
||||
code->appendf("%s.xyz = klm;", OutName(fKLM_fWind));
|
||||
|
||||
fGrad_fCorner.reset(cornerCoverage ? kFloat4_GrSLType : kFloat2_GrSLType, scope);
|
||||
varyingHandler->addVarying((cornerCoverage) ? "grad_and_corner" : "grad", &fGrad_fCorner);
|
||||
code->appendf("%s.xy = 2*bloat * (float3x2(%s) * float3(2*klm[0], -klm[2], -klm[1]));",
|
||||
OutName(fGrad_fCorner), fKLMMatrix.c_str());
|
||||
|
||||
}
|
||||
|
||||
void GrCCConicShader::emitSampleMaskCode(GrGLSLFPFragmentBuilder* f) const {
|
||||
f->codeAppendf("float k = %s.x, l = %s.y, m = %s.z;",
|
||||
fKLM_fWind.fsIn(), fKLM_fWind.fsIn(), fKLM_fWind.fsIn());
|
||||
f->codeAppendf("float f = k*k - l*m;");
|
||||
f->codeAppendf("float2 grad = %s;", fGrad_fCorner.fsIn());
|
||||
f->applyFnToMultisampleMask("f", "grad", GrGLSLFPFragmentBuilder::ScopeFlags::kTopLevel);
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef GrCCConicShader_DEFINED
|
||||
#define GrCCConicShader_DEFINED
|
||||
|
||||
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
|
||||
|
||||
/**
|
||||
* This class renders the coverage of closed conic curves using the techniques outlined in
|
||||
* "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and
|
||||
* Jim Blinn:
|
||||
*
|
||||
* https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
|
||||
*
|
||||
* The provided curves must be monotonic with respect to the vector of their closing edge [P2 - P0].
|
||||
* (Use GrCCGeometry::conicTo().)
|
||||
*/
|
||||
class GrCCConicShader : public GrCCCoverageProcessor::Shader {
|
||||
public:
|
||||
bool calculatesOwnEdgeCoverage() const override { return true; }
|
||||
|
||||
void emitSetupCode(
|
||||
GrGLSLVertexGeoBuilder*, const char* pts, const char** outHull4) const override;
|
||||
|
||||
void onEmitVaryings(
|
||||
GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code, const char* position,
|
||||
const char* coverage, const char* cornerCoverage, const char* wind) override;
|
||||
|
||||
void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const override;
|
||||
|
||||
private:
|
||||
const GrShaderVar fKLMMatrix{"klm_matrix", kFloat3x3_GrSLType};
|
||||
const GrShaderVar fControlPoint{"control_point", kFloat2_GrSLType};
|
||||
GrGLSLVarying fKLM_fWind;
|
||||
GrGLSLVarying fGrad_fCorner;
|
||||
};
|
||||
|
||||
#endif
|
@ -1,189 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
|
||||
|
||||
#include "src/gpu/GrOpFlushState.h"
|
||||
#include "src/gpu/GrOpsRenderPass.h"
|
||||
#include "src/gpu/GrProgramInfo.h"
|
||||
#include "src/gpu/ccpr/GrCCConicShader.h"
|
||||
#include "src/gpu/ccpr/GrCCCubicShader.h"
|
||||
#include "src/gpu/ccpr/GrCCQuadraticShader.h"
|
||||
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
|
||||
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
|
||||
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
|
||||
|
||||
class GrCCCoverageProcessor::TriangleShader : public GrCCCoverageProcessor::Shader {
|
||||
void onEmitVaryings(
|
||||
GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code,
|
||||
const char* position, const char* coverage, const char* cornerCoverage,
|
||||
const char* /*wind*/) override {
|
||||
fCoverages.reset(kHalf_GrSLType, scope);
|
||||
varyingHandler->addVarying("coverage", &fCoverages);
|
||||
code->appendf("%s = %s;", OutName(fCoverages), coverage);
|
||||
}
|
||||
|
||||
void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const override { return; }
|
||||
|
||||
GrGLSLVarying fCoverages;
|
||||
};
|
||||
|
||||
void GrCCCoverageProcessor::Shader::CalcWind(const GrCCCoverageProcessor& proc,
|
||||
GrGLSLVertexGeoBuilder* s, const char* pts,
|
||||
const char* outputWind) {
|
||||
if (3 == proc.numInputPoints()) {
|
||||
s->codeAppendf("float2 a = %s[0] - %s[1], "
|
||||
"b = %s[0] - %s[2];", pts, pts, pts, pts);
|
||||
} else {
|
||||
// All inputs are convex, so it's sufficient to just average the middle two input points.
|
||||
SkASSERT(4 == proc.numInputPoints());
|
||||
s->codeAppendf("float2 p12 = (%s[1] + %s[2]) * .5;", pts, pts);
|
||||
s->codeAppendf("float2 a = %s[0] - p12, "
|
||||
"b = %s[0] - %s[3];", pts, pts, pts);
|
||||
}
|
||||
|
||||
s->codeAppend ("float area_x2 = determinant(float2x2(a, b));");
|
||||
if (proc.isTriangles()) {
|
||||
// We cull extremely thin triangles by zeroing wind. When a triangle gets too thin it's
|
||||
// possible for FP round-off error to actually give us the wrong winding direction, causing
|
||||
// rendering artifacts. The criteria we choose is "height <~ 1/1024". So we drop a triangle
|
||||
// if the max effect it can have on any single pixel is <~ 1/1024, or 1/4 of a bit in 8888.
|
||||
s->codeAppend ("float2 bbox_size = max(abs(a), abs(b));");
|
||||
s->codeAppend ("float basewidth = max(bbox_size.x + bbox_size.y, 1);");
|
||||
s->codeAppendf("%s = (abs(area_x2 * 1024) > basewidth) ? sign(half(area_x2)) : 0;",
|
||||
outputWind);
|
||||
} else {
|
||||
// We already converted nearly-flat curves to lines on the CPU, so no need to worry about
|
||||
// thin curve hulls at this point.
|
||||
s->codeAppendf("%s = sign(half(area_x2));", outputWind);
|
||||
}
|
||||
}
|
||||
|
||||
void GrCCCoverageProcessor::Shader::CalcEdgeCoverageAtBloatVertex(GrGLSLVertexGeoBuilder* s,
|
||||
const char* leftPt,
|
||||
const char* rightPt,
|
||||
const char* rasterVertexDir,
|
||||
const char* outputCoverage) {
|
||||
// Here we find an edge's coverage at one corner of a conservative raster bloat box whose center
|
||||
// falls on the edge in question. (A bloat box is axis-aligned and the size of one pixel.) We
|
||||
// always set up coverage so it is -1 at the outermost corner, 0 at the innermost, and -.5 at
|
||||
// the center. Interpolated, these coverage values convert jagged conservative raster edges into
|
||||
// smooth antialiased edges.
|
||||
//
|
||||
// d1 == (P + sign(n) * bloat) dot n (Distance at the bloat box vertex whose
|
||||
// == P dot n + (abs(n.x) + abs(n.y)) * bloatSize coverage=-1, where the bloat box is
|
||||
// centered on P.)
|
||||
//
|
||||
// d0 == (P - sign(n) * bloat) dot n (Distance at the bloat box vertex whose
|
||||
// == P dot n - (abs(n.x) + abs(n.y)) * bloatSize coverage=0, where the bloat box is
|
||||
// centered on P.)
|
||||
//
|
||||
// d == (P + rasterVertexDir * bloatSize) dot n (Distance at the bloat box vertex whose
|
||||
// == P dot n + (rasterVertexDir dot n) * bloatSize coverage we wish to calculate.)
|
||||
//
|
||||
// coverage == -(d - d0) / (d1 - d0) (coverage=-1 at d=d1; coverage=0 at d=d0)
|
||||
//
|
||||
// == (rasterVertexDir dot n) / (abs(n.x) + abs(n.y)) * -.5 - .5
|
||||
//
|
||||
s->codeAppendf("float2 n = float2(%s.y - %s.y, %s.x - %s.x);",
|
||||
rightPt, leftPt, leftPt, rightPt);
|
||||
s->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
|
||||
s->codeAppendf("float t = dot(%s, n);", rasterVertexDir);
|
||||
// The below conditional guarantees we get exactly 1 on the divide when nwidth=t (in case the
|
||||
// GPU divides by multiplying by the reciprocal?) It also guards against NaN when nwidth=0.
|
||||
s->codeAppendf("%s = half(abs(t) != nwidth ? t / nwidth : sign(t)) * -.5 - .5;",
|
||||
outputCoverage);
|
||||
}
|
||||
|
||||
void GrCCCoverageProcessor::Shader::CalcEdgeCoveragesAtBloatVertices(GrGLSLVertexGeoBuilder* s,
|
||||
const char* leftPt,
|
||||
const char* rightPt,
|
||||
const char* bloatDir1,
|
||||
const char* bloatDir2,
|
||||
const char* outputCoverages) {
|
||||
// See comments in CalcEdgeCoverageAtBloatVertex.
|
||||
s->codeAppendf("float2 n = float2(%s.y - %s.y, %s.x - %s.x);",
|
||||
rightPt, leftPt, leftPt, rightPt);
|
||||
s->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
|
||||
s->codeAppendf("float2 t = n * float2x2(%s, %s);", bloatDir1, bloatDir2);
|
||||
s->codeAppendf("for (int i = 0; i < 2; ++i) {");
|
||||
s->codeAppendf( "%s[i] = half(abs(t[i]) != nwidth ? t[i] / nwidth : sign(t[i])) * -.5 - .5;",
|
||||
outputCoverages);
|
||||
s->codeAppendf("}");
|
||||
}
|
||||
|
||||
void GrCCCoverageProcessor::Shader::CalcCornerAttenuation(GrGLSLVertexGeoBuilder* s,
|
||||
const char* leftDir, const char* rightDir,
|
||||
const char* outputAttenuation) {
|
||||
// obtuseness = cos(corner_angle) if corner_angle > 90 degrees
|
||||
// 0 if corner_angle <= 90 degrees
|
||||
//
|
||||
// NOTE: leftDir and rightDir are normalized and point in the same direction the path was
|
||||
// defined with, i.e., leftDir points into the corner and rightDir points away from the corner.
|
||||
s->codeAppendf("half obtuseness = max(half(dot(%s, %s)), 0);", leftDir, rightDir);
|
||||
|
||||
// axis_alignedness = 1 - tan(angle_to_nearest_axis_from_corner_bisector)
|
||||
// (i.e., 1 when the corner bisector is aligned with the x- or y-axis
|
||||
// 0 when the corner bisector falls on a 45 degree angle
|
||||
// 0..1 when the corner bisector falls somewhere in between
|
||||
s->codeAppendf("half2 abs_bisect_maybe_transpose = abs((0 == obtuseness) ? half2(%s - %s) : "
|
||||
"half2(%s + %s));",
|
||||
leftDir, rightDir, leftDir, rightDir);
|
||||
s->codeAppend ("half axis_alignedness = "
|
||||
"1 - min(abs_bisect_maybe_transpose.y, abs_bisect_maybe_transpose.x) / "
|
||||
"max(abs_bisect_maybe_transpose.x, abs_bisect_maybe_transpose.y);");
|
||||
|
||||
// ninety_degreesness = sin^2(corner_angle)
|
||||
// sin^2 just because... it's always positive and the results looked better than plain sine... ?
|
||||
s->codeAppendf("half ninety_degreesness = determinant(half2x2(%s, %s));", leftDir, rightDir);
|
||||
s->codeAppend ("ninety_degreesness = ninety_degreesness * ninety_degreesness;");
|
||||
|
||||
// The below formula is not smart. It was just arrived at by considering the following
|
||||
// observations:
|
||||
//
|
||||
// 1. 90-degree, axis-aligned corners have full attenuation along the bisector.
|
||||
// (i.e. coverage = 1 - distance_to_corner^2)
|
||||
// (i.e. outputAttenuation = 0)
|
||||
//
|
||||
// 2. 180-degree corners always have zero attenuation.
|
||||
// (i.e. coverage = 1 - distance_to_corner)
|
||||
// (i.e. outputAttenuation = 1)
|
||||
//
|
||||
// 3. 90-degree corners whose bisector falls on a 45 degree angle also do not attenuate.
|
||||
// (i.e. outputAttenuation = 1)
|
||||
s->codeAppendf("%s = max(obtuseness, axis_alignedness * ninety_degreesness);",
|
||||
outputAttenuation);
|
||||
}
|
||||
|
||||
GrGLSLPrimitiveProcessor* GrCCCoverageProcessor::createGLSLInstance(const GrShaderCaps&) const {
|
||||
std::unique_ptr<Shader> shader;
|
||||
switch (fPrimitiveType) {
|
||||
case PrimitiveType::kTriangles:
|
||||
case PrimitiveType::kWeightedTriangles:
|
||||
shader = std::make_unique<TriangleShader>();
|
||||
break;
|
||||
case PrimitiveType::kQuadratics:
|
||||
shader = std::make_unique<GrCCQuadraticShader>();
|
||||
break;
|
||||
case PrimitiveType::kCubics:
|
||||
shader = std::make_unique<GrCCCubicShader>();
|
||||
break;
|
||||
case PrimitiveType::kConics:
|
||||
shader = std::make_unique<GrCCConicShader>();
|
||||
break;
|
||||
}
|
||||
return this->onCreateGLSLInstance(std::move(shader));
|
||||
}
|
||||
|
||||
void GrCCCoverageProcessor::bindPipeline(GrOpFlushState* flushState, const GrPipeline& pipeline,
|
||||
const SkRect& drawBounds,
|
||||
const GrUserStencilSettings* stencil) const {
|
||||
GrProgramInfo programInfo(flushState->writeView(), &pipeline, stencil, this,
|
||||
this->primType(), 0, flushState->renderPassBarriers(),
|
||||
flushState->colorLoadOp());
|
||||
flushState->bindPipeline(programInfo, drawBounds);
|
||||
}
|
@ -1,289 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef GrCCCoverageProcessor_DEFINED
|
||||
#define GrCCCoverageProcessor_DEFINED
|
||||
|
||||
#include "include/private/SkNx.h"
|
||||
#include "src/gpu/GrCaps.h"
|
||||
#include "src/gpu/GrGeometryProcessor.h"
|
||||
#include "src/gpu/GrPipeline.h"
|
||||
#include "src/gpu/GrShaderCaps.h"
|
||||
#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
|
||||
#include "src/gpu/glsl/GrGLSLShaderBuilder.h"
|
||||
#include "src/gpu/glsl/GrGLSLVarying.h"
|
||||
|
||||
class GrGLSLFPFragmentBuilder;
|
||||
class GrGLSLVertexGeoBuilder;
|
||||
class GrOpFlushState;
|
||||
|
||||
/**
|
||||
* This is the geometry processor for the simple convex primitive shapes (triangles and closed,
|
||||
* convex bezier curves) from which ccpr paths are composed. The output is a single-channel alpha
|
||||
* value, positive for clockwise shapes and negative for counter-clockwise, that indicates coverage.
|
||||
*
|
||||
* The caller is responsible to draw all primitives as produced by GrCCGeometry into a cleared,
|
||||
* floating point, alpha-only render target using SkBlendMode::kPlus. Once all of a path's
|
||||
* primitives have been drawn, the render target contains a composite coverage count that can then
|
||||
* be used to draw the path (see GrCCPathProcessor).
|
||||
*
|
||||
* To draw primitives, use appendMesh() and draw() (defined below).
|
||||
*/
|
||||
class GrCCCoverageProcessor : public GrGeometryProcessor {
|
||||
public:
|
||||
enum class PrimitiveType {
|
||||
kTriangles,
|
||||
kWeightedTriangles, // Triangles (from the tessellator) whose winding magnitude > 1.
|
||||
kQuadratics,
|
||||
kCubics,
|
||||
kConics
|
||||
};
|
||||
static const char* PrimitiveTypeName(PrimitiveType);
|
||||
|
||||
// Defines a single primitive shape with 3 input points (i.e. Triangles and Quadratics).
|
||||
// X,Y point values are transposed.
|
||||
struct TriPointInstance {
|
||||
float fValues[6];
|
||||
|
||||
enum class Ordering : bool {
|
||||
kXYTransposed,
|
||||
kXYInterleaved,
|
||||
};
|
||||
|
||||
void set(const SkPoint[3], const Sk2f& translate, Ordering);
|
||||
void set(const SkPoint&, const SkPoint&, const SkPoint&, const Sk2f& translate, Ordering);
|
||||
void set(const Sk2f& P0, const Sk2f& P1, const Sk2f& P2, const Sk2f& translate, Ordering);
|
||||
};
|
||||
|
||||
// Defines a single primitive shape with 4 input points, or 3 input points plus a "weight"
|
||||
// parameter duplicated in both lanes of the 4th input (i.e. Cubics, Conics, and Triangles with
|
||||
// a weighted winding number). X,Y point values are transposed.
|
||||
struct QuadPointInstance {
|
||||
float fX[4];
|
||||
float fY[4];
|
||||
|
||||
void set(const SkPoint[4], float dx, float dy);
|
||||
void setW(const SkPoint[3], const Sk2f& trans, float w);
|
||||
void setW(const SkPoint&, const SkPoint&, const SkPoint&, const Sk2f& trans, float w);
|
||||
void setW(const Sk2f& P0, const Sk2f& P1, const Sk2f& P2, const Sk2f& trans, float w);
|
||||
};
|
||||
|
||||
PrimitiveType primitiveType() const { return fPrimitiveType; }
|
||||
|
||||
// Number of bezier points for curves, or 3 for triangles.
|
||||
int numInputPoints() const { return PrimitiveType::kCubics == fPrimitiveType ? 4 : 3; }
|
||||
|
||||
bool isTriangles() const {
|
||||
return PrimitiveType::kTriangles == fPrimitiveType ||
|
||||
PrimitiveType::kWeightedTriangles == fPrimitiveType;
|
||||
}
|
||||
|
||||
int hasInputWeight() const {
|
||||
return PrimitiveType::kWeightedTriangles == fPrimitiveType ||
|
||||
PrimitiveType::kConics == fPrimitiveType;
|
||||
}
|
||||
|
||||
// GrPrimitiveProcessor overrides.
|
||||
const char* name() const override { return PrimitiveTypeName(fPrimitiveType); }
|
||||
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
|
||||
SkDEBUGCODE(this->getDebugBloatKey(b));
|
||||
b->add32((int)fPrimitiveType);
|
||||
}
|
||||
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
|
||||
|
||||
#ifdef SK_DEBUG
|
||||
// Increases the 1/2 pixel AA bloat by a factor of debugBloat.
|
||||
void enableDebugBloat(float debugBloat) { fDebugBloat = debugBloat; }
|
||||
bool debugBloatEnabled() const { return fDebugBloat > 0; }
|
||||
float debugBloat() const { SkASSERT(this->debugBloatEnabled()); return fDebugBloat; }
|
||||
void getDebugBloatKey(GrProcessorKeyBuilder* b) const {
|
||||
uint32_t bloatBits;
|
||||
memcpy(&bloatBits, &fDebugBloat, 4);
|
||||
b->add32(bloatBits);
|
||||
}
|
||||
#endif
|
||||
|
||||
// The caller uses these methods to actualy draw the coverage PrimitiveTypes. For each
|
||||
// subpassIdx of each PrimitiveType, it calls reset/bind*/drawInstances.
|
||||
virtual int numSubpasses() const = 0;
|
||||
virtual void reset(PrimitiveType, int subpassIdx, GrResourceProvider*) = 0;
|
||||
void bindPipeline(GrOpFlushState*, const GrPipeline&, const SkRect& drawBounds,
|
||||
const GrUserStencilSettings* = &GrUserStencilSettings::kUnused) const;
|
||||
virtual void bindBuffers(GrOpsRenderPass*, sk_sp<const GrBuffer> instanceBuffer) const = 0;
|
||||
virtual void drawInstances(GrOpsRenderPass*, int instanceCount, int baseInstance) const = 0;
|
||||
|
||||
// The Shader provides code to calculate each pixel's coverage in a RenderPass. It also
|
||||
// provides details about shape-specific geometry.
|
||||
class Shader {
|
||||
public:
|
||||
// Returns true if the Impl should not calculate the coverage argument for emitVaryings().
|
||||
// If true, then "coverage" will have a signed magnitude of 1.
|
||||
virtual bool calculatesOwnEdgeCoverage() const { return false; }
|
||||
|
||||
// Called before generating geometry. Subclasses may set up internal member variables during
|
||||
// this time that will be needed during onEmitVaryings (e.g. transformation matrices).
|
||||
//
|
||||
// If the 'outHull4' parameter is provided, and there are not 4 input points, the subclass
|
||||
// is required to fill it with the name of a 4-point hull around which the Impl can generate
|
||||
// its geometry. If it is left unchanged, the Impl will use the regular input points.
|
||||
virtual void emitSetupCode(
|
||||
GrGLSLVertexGeoBuilder*, const char* pts, const char** outHull4 = nullptr) const {
|
||||
SkASSERT(!outHull4);
|
||||
}
|
||||
|
||||
void emitVaryings(
|
||||
GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code,
|
||||
const char* position, const char* coverage, const char* cornerCoverage,
|
||||
const char* wind) {
|
||||
SkASSERT(GrGLSLVarying::Scope::kVertToGeo != scope);
|
||||
this->onEmitVaryings(
|
||||
varyingHandler, scope, code, position, coverage, cornerCoverage, wind);
|
||||
}
|
||||
|
||||
// Assigns the built-in sample mask at the current pixel.
|
||||
virtual void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const = 0;
|
||||
|
||||
// Calculates the winding direction of the input points (+1, -1, or 0). Wind for extremely
|
||||
// thin triangles gets rounded to zero.
|
||||
static void CalcWind(const GrCCCoverageProcessor&, GrGLSLVertexGeoBuilder*, const char* pts,
|
||||
const char* outputWind);
|
||||
|
||||
// Calculates an edge's coverage at a conservative raster vertex. The edge is defined by two
|
||||
// clockwise-ordered points, 'leftPt' and 'rightPt'. 'rasterVertexDir' is a pair of +/-1
|
||||
// values that point in the direction of conservative raster bloat, starting from an
|
||||
// endpoint.
|
||||
//
|
||||
// Coverage values ramp from -1 (completely outside the edge) to 0 (completely inside).
|
||||
static void CalcEdgeCoverageAtBloatVertex(GrGLSLVertexGeoBuilder*, const char* leftPt,
|
||||
const char* rightPt, const char* rasterVertexDir,
|
||||
const char* outputCoverage);
|
||||
|
||||
// Calculates an edge's coverage at two conservative raster vertices.
|
||||
// (See CalcEdgeCoverageAtBloatVertex).
|
||||
static void CalcEdgeCoveragesAtBloatVertices(GrGLSLVertexGeoBuilder*, const char* leftPt,
|
||||
const char* rightPt, const char* bloatDir1,
|
||||
const char* bloatDir2,
|
||||
const char* outputCoverages);
|
||||
|
||||
// Corner boxes require an additional "attenuation" varying that is multiplied by the
|
||||
// regular (linearly-interpolated) coverage. This function calculates the attenuation value
|
||||
// to use in the single, outermost vertex. The remaining three vertices of the corner box
|
||||
// all use an attenuation value of 1.
|
||||
static void CalcCornerAttenuation(GrGLSLVertexGeoBuilder*, const char* leftDir,
|
||||
const char* rightDir, const char* outputAttenuation);
|
||||
|
||||
virtual ~Shader() {}
|
||||
|
||||
protected:
|
||||
// Here the subclass adds its internal varyings to the handler and produces code to
|
||||
// initialize those varyings from a given position and coverage values.
|
||||
//
|
||||
// NOTE: the coverage values are signed appropriately for wind.
|
||||
// 'coverage' will only be +1 or -1 on curves.
|
||||
virtual void onEmitVaryings(
|
||||
GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code, const char* position,
|
||||
const char* coverage, const char* cornerCoverage, const char* wind) = 0;
|
||||
|
||||
// Returns the name of a Shader's internal varying at the point where where its value is
|
||||
// assigned. This is intended to work whether called for a vertex or a geometry shader.
|
||||
const char* OutName(const GrGLSLVarying& varying) const {
|
||||
using Scope = GrGLSLVarying::Scope;
|
||||
SkASSERT(Scope::kVertToGeo != varying.scope());
|
||||
return Scope::kGeoToFrag == varying.scope() ? varying.gsOut() : varying.vsOut();
|
||||
}
|
||||
|
||||
// Our friendship with GrGLSLShaderBuilder does not propagate to subclasses.
|
||||
inline static SkString& AccessCodeString(GrGLSLShaderBuilder* s) { return s->code(); }
|
||||
};
|
||||
|
||||
protected:
|
||||
// Slightly undershoot a bloat radius of 0.5 so vertices that fall on integer boundaries don't
|
||||
// accidentally bleed into neighbor pixels.
|
||||
static constexpr float kAABloatRadius = 0.491111f;
|
||||
|
||||
GrCCCoverageProcessor(ClassID classID) : INHERITED(classID) {}
|
||||
|
||||
virtual GrPrimitiveType primType() const = 0;
|
||||
|
||||
virtual GrGLSLPrimitiveProcessor* onCreateGLSLInstance(std::unique_ptr<Shader>) const = 0;
|
||||
|
||||
// Our friendship with GrGLSLShaderBuilder does not propagate to subclasses.
|
||||
inline static SkString& AccessCodeString(GrGLSLShaderBuilder* s) { return s->code(); }
|
||||
|
||||
PrimitiveType fPrimitiveType;
|
||||
SkDEBUGCODE(float fDebugBloat = 0);
|
||||
|
||||
class TriangleShader;
|
||||
|
||||
using INHERITED = GrGeometryProcessor;
|
||||
};
|
||||
|
||||
inline const char* GrCCCoverageProcessor::PrimitiveTypeName(PrimitiveType type) {
|
||||
switch (type) {
|
||||
case PrimitiveType::kTriangles: return "kTriangles";
|
||||
case PrimitiveType::kWeightedTriangles: return "kWeightedTriangles";
|
||||
case PrimitiveType::kQuadratics: return "kQuadratics";
|
||||
case PrimitiveType::kCubics: return "kCubics";
|
||||
case PrimitiveType::kConics: return "kConics";
|
||||
}
|
||||
SK_ABORT("Invalid PrimitiveType");
|
||||
}
|
||||
|
||||
inline void GrCCCoverageProcessor::TriPointInstance::set(
|
||||
const SkPoint p[3], const Sk2f& translate, Ordering ordering) {
|
||||
this->set(p[0], p[1], p[2], translate, ordering);
|
||||
}
|
||||
|
||||
inline void GrCCCoverageProcessor::TriPointInstance::set(
|
||||
const SkPoint& p0, const SkPoint& p1, const SkPoint& p2, const Sk2f& translate,
|
||||
Ordering ordering) {
|
||||
Sk2f P0 = Sk2f::Load(&p0);
|
||||
Sk2f P1 = Sk2f::Load(&p1);
|
||||
Sk2f P2 = Sk2f::Load(&p2);
|
||||
this->set(P0, P1, P2, translate, ordering);
|
||||
}
|
||||
|
||||
inline void GrCCCoverageProcessor::TriPointInstance::set(
|
||||
const Sk2f& P0, const Sk2f& P1, const Sk2f& P2, const Sk2f& translate, Ordering ordering) {
|
||||
if (Ordering::kXYTransposed == ordering) {
|
||||
Sk2f::Store3(fValues, P0 + translate, P1 + translate, P2 + translate);
|
||||
} else {
|
||||
(P0 + translate).store(fValues);
|
||||
(P1 + translate).store(fValues + 2);
|
||||
(P2 + translate).store(fValues + 4);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GrCCCoverageProcessor::QuadPointInstance::set(const SkPoint p[4], float dx, float dy) {
|
||||
Sk4f X,Y;
|
||||
Sk4f::Load2(p, &X, &Y);
|
||||
(X + dx).store(&fX);
|
||||
(Y + dy).store(&fY);
|
||||
}
|
||||
|
||||
inline void GrCCCoverageProcessor::QuadPointInstance::setW(const SkPoint p[3], const Sk2f& trans,
|
||||
float w) {
|
||||
this->setW(p[0], p[1], p[2], trans, w);
|
||||
}
|
||||
|
||||
inline void GrCCCoverageProcessor::QuadPointInstance::setW(const SkPoint& p0, const SkPoint& p1,
|
||||
const SkPoint& p2, const Sk2f& trans,
|
||||
float w) {
|
||||
Sk2f P0 = Sk2f::Load(&p0);
|
||||
Sk2f P1 = Sk2f::Load(&p1);
|
||||
Sk2f P2 = Sk2f::Load(&p2);
|
||||
this->setW(P0, P1, P2, trans, w);
|
||||
}
|
||||
|
||||
inline void GrCCCoverageProcessor::QuadPointInstance::setW(const Sk2f& P0, const Sk2f& P1,
|
||||
const Sk2f& P2, const Sk2f& trans,
|
||||
float w) {
|
||||
Sk2f W = Sk2f(w);
|
||||
Sk2f::Store4(this, P0 + trans, P1 + trans, P2 + trans, W);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "src/gpu/ccpr/GrCCCubicShader.h"
|
||||
|
||||
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
|
||||
#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
|
||||
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
|
||||
|
||||
using Shader = GrCCCoverageProcessor::Shader;
|
||||
|
||||
void GrCCCubicShader::emitSetupCode(
|
||||
GrGLSLVertexGeoBuilder* s, const char* pts, const char** /*outHull4*/) const {
|
||||
// Find the cubic's power basis coefficients.
|
||||
s->codeAppendf("float2x4 C = float4x4(-1, 3, -3, 1, "
|
||||
" 3, -6, 3, 0, "
|
||||
"-3, 3, 0, 0, "
|
||||
" 1, 0, 0, 0) * transpose(%s);", pts);
|
||||
|
||||
// Find the cubic's inflection function.
|
||||
s->codeAppend ("float D3 = +determinant(float2x2(C[0].yz, C[1].yz));");
|
||||
s->codeAppend ("float D2 = -determinant(float2x2(C[0].xz, C[1].xz));");
|
||||
s->codeAppend ("float D1 = +determinant(float2x2(C));");
|
||||
|
||||
// Shift the exponents in D so the largest magnitude falls somewhere in 1..2. This protects us
|
||||
// from overflow while solving for roots and KLM functionals.
|
||||
s->codeAppend ("float Dmax = max(max(abs(D1), abs(D2)), abs(D3));");
|
||||
s->codeAppend ("float norm;");
|
||||
if (s->getProgramBuilder()->shaderCaps()->fpManipulationSupport()) {
|
||||
s->codeAppend ("int exp;");
|
||||
s->codeAppend ("frexp(Dmax, exp);");
|
||||
s->codeAppend ("norm = ldexp(1, 1 - exp);");
|
||||
} else {
|
||||
s->codeAppend ("norm = 1/Dmax;"); // Dmax will not be 0 because we cull line cubics on CPU.
|
||||
}
|
||||
s->codeAppend ("D3 *= norm;");
|
||||
s->codeAppend ("D2 *= norm;");
|
||||
s->codeAppend ("D1 *= norm;");
|
||||
|
||||
// Calculate the KLM matrix.
|
||||
s->declareGlobal(fKLMMatrix);
|
||||
s->codeAppend ("float discr = 3*D2*D2 - 4*D1*D3;");
|
||||
s->codeAppend ("float x = discr >= 0 ? 3 : 1;");
|
||||
s->codeAppend ("float q = sqrt(x * abs(discr));");
|
||||
s->codeAppend ("q = x*D2 + (D2 >= 0 ? q : -q);");
|
||||
|
||||
s->codeAppend ("float2 l, m;");
|
||||
s->codeAppend ("l.ts = float2(q, 2*x * D1);");
|
||||
s->codeAppend ("m.ts = float2(2, q) * (discr >= 0 ? float2(D3, 1) "
|
||||
": float2(D2*D2 - D3*D1, D1));");
|
||||
|
||||
s->codeAppend ("float4 K;");
|
||||
s->codeAppend ("float4 lm = l.sstt * m.stst;");
|
||||
s->codeAppend ("K = float4(0, lm.x, -lm.y - lm.z, lm.w);");
|
||||
|
||||
s->codeAppend ("float4 L, M;");
|
||||
s->codeAppend ("lm.yz += 2*lm.zy;");
|
||||
s->codeAppend ("L = float4(-1,x,-x,1) * l.sstt * (discr >= 0 ? l.ssst * l.sttt : lm);");
|
||||
s->codeAppend ("M = float4(-1,x,-x,1) * m.sstt * (discr >= 0 ? m.ssst * m.sttt : lm.xzyw);");
|
||||
|
||||
s->codeAppend ("int middlerow = abs(D2) > abs(D1) ? 2 : 1;");
|
||||
s->codeAppend ("float3x3 CI = inverse(float3x3(C[0][0], C[0][middlerow], C[0][3], "
|
||||
"C[1][0], C[1][middlerow], C[1][3], "
|
||||
" 0, 0, 1));");
|
||||
s->codeAppendf("%s = CI * float3x3(K[0], K[middlerow], K[3], "
|
||||
"L[0], L[middlerow], L[3], "
|
||||
"M[0], M[middlerow], M[3]);", fKLMMatrix.c_str());
|
||||
|
||||
// Evaluate the cubic at T=.5 for a mid-ish point.
|
||||
s->codeAppendf("float2 midpoint = %s * float4(.125, .375, .375, .125);", pts);
|
||||
|
||||
// Orient the KLM matrix so L & M are both positive on the side of the curve we wish to fill.
|
||||
s->codeAppendf("float2 orientation = sign(float3(midpoint, 1) * float2x3(%s[1], %s[2]));",
|
||||
fKLMMatrix.c_str(), fKLMMatrix.c_str());
|
||||
s->codeAppendf("%s *= float3x3(orientation[0] * orientation[1], 0, 0, "
|
||||
"0, orientation[0], 0, "
|
||||
"0, 0, orientation[1]);", fKLMMatrix.c_str());
|
||||
}
|
||||
|
||||
void GrCCCubicShader::onEmitVaryings(
|
||||
GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code,
|
||||
const char* position, const char* coverage, const char* cornerCoverage, const char* wind) {
|
||||
code->appendf("float3 klm = float3(%s, 1) * %s;", position, fKLMMatrix.c_str());
|
||||
fKLM_fEdge.reset(kFloat3_GrSLType, scope);
|
||||
varyingHandler->addVarying("klm", &fKLM_fEdge);
|
||||
code->appendf("%s = klm;", OutName(fKLM_fEdge));
|
||||
|
||||
fGradMatrix.reset(kFloat4_GrSLType, scope);
|
||||
varyingHandler->addVarying("grad_matrix", &fGradMatrix);
|
||||
code->appendf("%s.xy = 2*bloat * 3 * klm[0] * %s[0].xy;",
|
||||
OutName(fGradMatrix), fKLMMatrix.c_str());
|
||||
code->appendf("%s.zw = -2*bloat * (klm[1] * %s[2].xy + klm[2] * %s[1].xy);",
|
||||
OutName(fGradMatrix), fKLMMatrix.c_str(), fKLMMatrix.c_str());
|
||||
}
|
||||
|
||||
void GrCCCubicShader::emitSampleMaskCode(GrGLSLFPFragmentBuilder* f) const {
|
||||
f->codeAppendf("float k = %s.x, l = %s.y, m = %s.z;",
|
||||
fKLM_fEdge.fsIn(), fKLM_fEdge.fsIn(), fKLM_fEdge.fsIn());
|
||||
f->codeAppendf("float f = k*k*k - l*m;");
|
||||
f->codeAppendf("float2x2 grad_matrix = float2x2(%s);", fGradMatrix.fsIn());
|
||||
f->codeAppendf("float2 grad = grad_matrix * float2(k, 1);");
|
||||
f->applyFnToMultisampleMask("f", "grad", GrGLSLFPFragmentBuilder::ScopeFlags::kTopLevel);
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef GrCCCubicShader_DEFINED
|
||||
#define GrCCCubicShader_DEFINED
|
||||
|
||||
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
|
||||
|
||||
/**
|
||||
* This class renders the coverage of convex closed cubic segments using the techniques outlined in
|
||||
* "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and
|
||||
* Jim Blinn:
|
||||
*
|
||||
* https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
|
||||
*
|
||||
* The provided curve segments must be convex, monotonic with respect to the vector of their closing
|
||||
* edge [P3 - P0], and must not contain or be near any inflection points or loop intersections.
|
||||
* (Use GrCCGeometry::cubicTo().)
|
||||
*/
|
||||
class GrCCCubicShader : public GrCCCoverageProcessor::Shader {
|
||||
public:
|
||||
void emitSetupCode(
|
||||
GrGLSLVertexGeoBuilder*, const char* pts, const char** outHull4) const override;
|
||||
|
||||
void onEmitVaryings(
|
||||
GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code, const char* position,
|
||||
const char* coverage, const char* cornerCoverage, const char* wind) override;
|
||||
|
||||
void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const override;
|
||||
|
||||
private:
|
||||
const GrShaderVar fKLMMatrix{"klm_matrix", kFloat3x3_GrSLType};
|
||||
GrGLSLVarying fKLM_fEdge;
|
||||
GrGLSLVarying fGradMatrix;
|
||||
GrGLSLVarying fCornerCoverage;
|
||||
};
|
||||
|
||||
#endif
|
@ -1,802 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "src/gpu/ccpr/GrCCFillGeometry.h"
|
||||
|
||||
#include "include/gpu/GrTypes.h"
|
||||
#include "src/core/SkGeometry.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
static constexpr float kFlatnessThreshold = 1/16.f; // 1/16 of a pixel.
|
||||
|
||||
void GrCCFillGeometry::beginPath() {
|
||||
SkASSERT(!fBuildingContour);
|
||||
fVerbs.push_back(Verb::kBeginPath);
|
||||
}
|
||||
|
||||
void GrCCFillGeometry::beginContour(const SkPoint& pt) {
|
||||
SkASSERT(!fBuildingContour);
|
||||
// Store the current verb count in the fTriangles field for now. When we close the contour we
|
||||
// will use this value to calculate the actual number of triangles in its fan.
|
||||
fCurrContourTallies = {fVerbs.count(), 0, 0, 0, 0};
|
||||
|
||||
fPoints.push_back(pt);
|
||||
fVerbs.push_back(Verb::kBeginContour);
|
||||
fCurrAnchorPoint = pt;
|
||||
|
||||
SkDEBUGCODE(fBuildingContour = true);
|
||||
}
|
||||
|
||||
void GrCCFillGeometry::lineTo(const SkPoint P[2]) {
|
||||
SkASSERT(fBuildingContour);
|
||||
SkASSERT(P[0] == fPoints.back());
|
||||
Sk2f p0 = Sk2f::Load(P);
|
||||
Sk2f p1 = Sk2f::Load(P+1);
|
||||
this->appendLine(p0, p1);
|
||||
}
|
||||
|
||||
inline void GrCCFillGeometry::appendLine(const Sk2f& p0, const Sk2f& p1) {
|
||||
SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
|
||||
if ((p0 == p1).allTrue()) {
|
||||
return;
|
||||
}
|
||||
p1.store(&fPoints.push_back());
|
||||
fVerbs.push_back(Verb::kLineTo);
|
||||
}
|
||||
|
||||
static inline Sk2f normalize(const Sk2f& n) {
|
||||
Sk2f nn = n*n;
|
||||
return n * (nn + SkNx_shuffle<1,0>(nn)).rsqrt();
|
||||
}
|
||||
|
||||
static inline float dot(const Sk2f& a, const Sk2f& b) {
|
||||
float product[2];
|
||||
(a * b).store(product);
|
||||
return product[0] + product[1];
|
||||
}
|
||||
|
||||
static inline bool are_collinear(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
|
||||
float tolerance = kFlatnessThreshold) {
|
||||
Sk2f l = p2 - p0; // Line from p0 -> p2.
|
||||
|
||||
// lwidth = Manhattan width of l.
|
||||
Sk2f labs = l.abs();
|
||||
float lwidth = labs[0] + labs[1];
|
||||
|
||||
// d = |p1 - p0| dot | l.y|
|
||||
// |-l.x| = distance from p1 to l.
|
||||
Sk2f dd = (p1 - p0) * SkNx_shuffle<1,0>(l);
|
||||
float d = dd[0] - dd[1];
|
||||
|
||||
// We are collinear if a box with radius "tolerance", centered on p1, touches the line l.
|
||||
// To decide this, we check if the distance from p1 to the line is less than the distance from
|
||||
// p1 to the far corner of this imaginary box, along that same normal vector.
|
||||
// The far corner of the box can be found at "p1 + sign(n) * tolerance", where n is normal to l:
|
||||
//
|
||||
// abs(dot(p1 - p0, n)) <= dot(sign(n) * tolerance, n)
|
||||
//
|
||||
// Which reduces to:
|
||||
//
|
||||
// abs(d) <= (n.x * sign(n.x) + n.y * sign(n.y)) * tolerance
|
||||
// abs(d) <= (abs(n.x) + abs(n.y)) * tolerance
|
||||
//
|
||||
// Use "<=" in case l == 0.
|
||||
return std::abs(d) <= lwidth * tolerance;
|
||||
}
|
||||
|
||||
static inline bool are_collinear(const SkPoint P[4], float tolerance = kFlatnessThreshold) {
|
||||
Sk4f Px, Py; // |Px Py| |p0 - p3|
|
||||
Sk4f::Load2(P, &Px, &Py); // |. . | = |p1 - p3|
|
||||
Px -= Px[3]; // |. . | |p2 - p3|
|
||||
Py -= Py[3]; // |. . | | 0 |
|
||||
|
||||
// Find [lx, ly] = the line from p3 to the furthest-away point from p3.
|
||||
Sk4f Pwidth = Px.abs() + Py.abs(); // Pwidth = Manhattan width of each point.
|
||||
int lidx = Pwidth[0] > Pwidth[1] ? 0 : 1;
|
||||
lidx = Pwidth[lidx] > Pwidth[2] ? lidx : 2;
|
||||
float lx = Px[lidx], ly = Py[lidx];
|
||||
float lwidth = Pwidth[lidx]; // lwidth = Manhattan width of [lx, ly].
|
||||
|
||||
// |Px Py|
|
||||
// d = |. . | * | ly| = distances from each point to l (two of the distances will be zero).
|
||||
// |. . | |-lx|
|
||||
// |. . |
|
||||
Sk4f d = Px*ly - Py*lx;
|
||||
|
||||
// We are collinear if boxes with radius "tolerance", centered on all 4 points all touch line l.
|
||||
// (See the rationale for this formula in the above, 3-point version of this function.)
|
||||
// Use "<=" in case l == 0.
|
||||
return (d.abs() <= lwidth * tolerance).allTrue();
|
||||
}
|
||||
|
||||
// Returns whether the (convex) curve segment is monotonic with respect to [endPt - startPt].
|
||||
static inline bool is_convex_curve_monotonic(const Sk2f& startPt, const Sk2f& tan0,
|
||||
const Sk2f& endPt, const Sk2f& tan1) {
|
||||
Sk2f v = endPt - startPt;
|
||||
float dot0 = dot(tan0, v);
|
||||
float dot1 = dot(tan1, v);
|
||||
|
||||
// A small, negative tolerance handles floating-point error in the case when one tangent
|
||||
// approaches 0 length, meaning the (convex) curve segment is effectively a flat line.
|
||||
float tolerance = -std::max(std::abs(dot0), std::abs(dot1)) * SK_ScalarNearlyZero;
|
||||
return dot0 >= tolerance && dot1 >= tolerance;
|
||||
}
|
||||
|
||||
template<int N> static inline SkNx<N,float> lerp(const SkNx<N,float>& a, const SkNx<N,float>& b,
|
||||
const SkNx<N,float>& t) {
|
||||
return SkNx_fma(t, b - a, a);
|
||||
}
|
||||
|
||||
void GrCCFillGeometry::quadraticTo(const SkPoint P[3]) {
|
||||
SkASSERT(fBuildingContour);
|
||||
SkASSERT(P[0] == fPoints.back());
|
||||
Sk2f p0 = Sk2f::Load(P);
|
||||
Sk2f p1 = Sk2f::Load(P+1);
|
||||
Sk2f p2 = Sk2f::Load(P+2);
|
||||
|
||||
// Don't crunch on the curve if it is nearly flat (or just very small). Flat curves can break
|
||||
// The monotonic chopping math.
|
||||
if (are_collinear(p0, p1, p2)) {
|
||||
this->appendLine(p0, p2);
|
||||
return;
|
||||
}
|
||||
|
||||
this->appendQuadratics(p0, p1, p2);
|
||||
}
|
||||
|
||||
inline void GrCCFillGeometry::appendQuadratics(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) {
|
||||
Sk2f tan0 = p1 - p0;
|
||||
Sk2f tan1 = p2 - p1;
|
||||
|
||||
// This should almost always be this case for well-behaved curves in the real world.
|
||||
if (is_convex_curve_monotonic(p0, tan0, p2, tan1)) {
|
||||
this->appendMonotonicQuadratic(p0, p1, p2);
|
||||
return;
|
||||
}
|
||||
|
||||
// Chop the curve into two segments with equal curvature. To do this we find the T value whose
|
||||
// tangent angle is halfway between tan0 and tan1.
|
||||
Sk2f n = normalize(tan0) - normalize(tan1);
|
||||
|
||||
// The midtangent can be found where (dQ(t) dot n) = 0:
|
||||
//
|
||||
// 0 = (dQ(t) dot n) = | 2*t 1 | * | p0 - 2*p1 + p2 | * | n |
|
||||
// | -2*p0 + 2*p1 | | . |
|
||||
//
|
||||
// = | 2*t 1 | * | tan1 - tan0 | * | n |
|
||||
// | 2*tan0 | | . |
|
||||
//
|
||||
// = 2*t * ((tan1 - tan0) dot n) + (2*tan0 dot n)
|
||||
//
|
||||
// t = (tan0 dot n) / ((tan0 - tan1) dot n)
|
||||
Sk2f dQ1n = (tan0 - tan1) * n;
|
||||
Sk2f dQ0n = tan0 * n;
|
||||
Sk2f t = (dQ0n + SkNx_shuffle<1,0>(dQ0n)) / (dQ1n + SkNx_shuffle<1,0>(dQ1n));
|
||||
t = Sk2f::Min(Sk2f::Max(t, 0), 1); // Clamp for FP error.
|
||||
|
||||
Sk2f p01 = SkNx_fma(t, tan0, p0);
|
||||
Sk2f p12 = SkNx_fma(t, tan1, p1);
|
||||
Sk2f p012 = lerp(p01, p12, t);
|
||||
|
||||
this->appendMonotonicQuadratic(p0, p01, p012);
|
||||
this->appendMonotonicQuadratic(p012, p12, p2);
|
||||
}
|
||||
|
||||
inline void GrCCFillGeometry::appendMonotonicQuadratic(const Sk2f& p0, const Sk2f& p1,
|
||||
const Sk2f& p2) {
|
||||
// Don't send curves to the GPU if we know they are nearly flat (or just very small).
|
||||
if (are_collinear(p0, p1, p2)) {
|
||||
this->appendLine(p0, p2);
|
||||
return;
|
||||
}
|
||||
|
||||
SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
|
||||
SkASSERT((p0 != p2).anyTrue());
|
||||
p1.store(&fPoints.push_back());
|
||||
p2.store(&fPoints.push_back());
|
||||
fVerbs.push_back(Verb::kMonotonicQuadraticTo);
|
||||
++fCurrContourTallies.fQuadratics;
|
||||
}
|
||||
|
||||
static inline Sk2f first_unless_nearly_zero(const Sk2f& a, const Sk2f& b) {
|
||||
Sk2f aa = a*a;
|
||||
aa += SkNx_shuffle<1,0>(aa);
|
||||
SkASSERT(aa[0] == aa[1]);
|
||||
|
||||
Sk2f bb = b*b;
|
||||
bb += SkNx_shuffle<1,0>(bb);
|
||||
SkASSERT(bb[0] == bb[1]);
|
||||
|
||||
return (aa > bb * SK_ScalarNearlyZero).thenElse(a, b);
|
||||
}
|
||||
|
||||
static inline void get_cubic_tangents(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
|
||||
const Sk2f& p3, Sk2f* tan0, Sk2f* tan1) {
|
||||
*tan0 = first_unless_nearly_zero(p1 - p0, p2 - p0);
|
||||
*tan1 = first_unless_nearly_zero(p3 - p2, p3 - p1);
|
||||
}
|
||||
|
||||
static inline bool is_cubic_nearly_quadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
|
||||
const Sk2f& p3, const Sk2f& tan0, const Sk2f& tan1,
|
||||
Sk2f* c) {
|
||||
Sk2f c1 = SkNx_fma(Sk2f(1.5f), tan0, p0);
|
||||
Sk2f c2 = SkNx_fma(Sk2f(-1.5f), tan1, p3);
|
||||
*c = (c1 + c2) * .5f; // Hopefully optimized out if not used?
|
||||
return ((c1 - c2).abs() <= 1).allTrue();
|
||||
}
|
||||
|
||||
enum class ExcludedTerm : bool {
|
||||
kQuadraticTerm,
|
||||
kLinearTerm
|
||||
};
|
||||
|
||||
// Finds where to chop a non-loop around its inflection points. The resulting cubic segments will be
|
||||
// chopped such that a box of radius 'padRadius', centered at any point along the curve segment, is
|
||||
// guaranteed to not cross the tangent lines at the inflection points (a.k.a lines L & M).
|
||||
//
|
||||
// 'chops' will be filled with 0, 2, or 4 T values. The segments between T0..T1 and T2..T3 must be
|
||||
// drawn with flat lines instead of cubics.
|
||||
//
|
||||
// A serpentine cubic has two inflection points, so this method takes Sk2f and computes the padding
|
||||
// for both in SIMD.
|
||||
static inline void find_chops_around_inflection_points(float padRadius, Sk2f tl, Sk2f sl,
|
||||
const Sk2f& C0, const Sk2f& C1,
|
||||
ExcludedTerm skipTerm, float Cdet,
|
||||
SkSTArray<4, float>* chops) {
|
||||
SkASSERT(chops->empty());
|
||||
SkASSERT(padRadius >= 0);
|
||||
|
||||
padRadius /= std::abs(Cdet); // Scale this single value rather than all of C^-1 later on.
|
||||
|
||||
// The homogeneous parametric functions for distance from lines L & M are:
|
||||
//
|
||||
// l(t,s) = (t*sl - s*tl)^3
|
||||
// m(t,s) = (t*sm - s*tm)^3
|
||||
//
|
||||
// See "Resolution Independent Curve Rendering using Programmable Graphics Hardware",
|
||||
// 4.3 Finding klmn:
|
||||
//
|
||||
// https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
|
||||
//
|
||||
// From here on we use Sk2f with "L" names, but the second lane will be for line M.
|
||||
tl = (sl > 0).thenElse(tl, -tl); // Tl=tl/sl is the triple root of l(t,s). Normalize so s >= 0.
|
||||
sl = sl.abs();
|
||||
|
||||
// Convert l(t,s), m(t,s) to power-basis form:
|
||||
//
|
||||
// | l3 m3 |
|
||||
// |l(t,s) m(t,s)| = |t^3 t^2*s t*s^2 s^3| * | l2 m2 |
|
||||
// | l1 m1 |
|
||||
// | l0 m0 |
|
||||
//
|
||||
Sk2f l3 = sl*sl*sl;
|
||||
Sk2f l2or1 = (ExcludedTerm::kLinearTerm == skipTerm) ? sl*sl*tl*-3 : sl*tl*tl*3;
|
||||
|
||||
// The equation for line L can be found as follows:
|
||||
//
|
||||
// L = C^-1 * (l excluding skipTerm)
|
||||
//
|
||||
// (See comments for GrPathUtils::calcCubicInverseTransposePowerBasisMatrix.)
|
||||
// We are only interested in the normal to L, so only need the upper 2x2 of C^-1. And rather
|
||||
// than divide by determinant(C) here, we have already performed this divide on padRadius.
|
||||
Sk2f Lx = C1[1]*l3 - C0[1]*l2or1;
|
||||
Sk2f Ly = -C1[0]*l3 + C0[0]*l2or1;
|
||||
|
||||
// A box of radius "padRadius" is touching line L if "center dot L" is less than the Manhattan
|
||||
// with of L. (See rationale in are_collinear.)
|
||||
Sk2f Lwidth = Lx.abs() + Ly.abs();
|
||||
Sk2f pad = Lwidth * padRadius;
|
||||
|
||||
// Will T=(t + cbrt(pad))/s be greater than 0? No need to solve roots outside T=0..1.
|
||||
Sk2f insideLeftPad = pad + tl*tl*tl;
|
||||
|
||||
// Will T=(t - cbrt(pad))/s be less than 1? No need to solve roots outside T=0..1.
|
||||
Sk2f tms = tl - sl;
|
||||
Sk2f insideRightPad = pad - tms*tms*tms;
|
||||
|
||||
// Solve for the T values where abs(l(T)) = pad.
|
||||
if (insideLeftPad[0] > 0 && insideRightPad[0] > 0) {
|
||||
float padT = cbrtf(pad[0]);
|
||||
Sk2f pts = (tl[0] + Sk2f(-padT, +padT)) / sl[0];
|
||||
pts.store(chops->push_back_n(2));
|
||||
}
|
||||
|
||||
// Solve for the T values where abs(m(T)) = pad.
|
||||
if (insideLeftPad[1] > 0 && insideRightPad[1] > 0) {
|
||||
float padT = cbrtf(pad[1]);
|
||||
Sk2f pts = (tl[1] + Sk2f(-padT, +padT)) / sl[1];
|
||||
pts.store(chops->push_back_n(2));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void swap_if_greater(float& a, float& b) {
|
||||
if (a > b) {
|
||||
std::swap(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
// Finds where to chop a non-loop around its intersection point. The resulting cubic segments will
|
||||
// be chopped such that a box of radius 'padRadius', centered at any point along the curve segment,
|
||||
// is guaranteed to not cross the tangent lines at the intersection point (a.k.a lines L & M).
|
||||
//
|
||||
// 'chops' will be filled with 0, 2, or 4 T values. The segments between T0..T1 and T2..T3 must be
|
||||
// drawn with quadratic splines instead of cubics.
|
||||
//
|
||||
// A loop intersection falls at two different T values, so this method takes Sk2f and computes the
|
||||
// padding for both in SIMD.
|
||||
static inline void find_chops_around_loop_intersection(float padRadius, Sk2f t2, Sk2f s2,
|
||||
const Sk2f& C0, const Sk2f& C1,
|
||||
ExcludedTerm skipTerm, float Cdet,
|
||||
SkSTArray<4, float>* chops) {
|
||||
SkASSERT(chops->empty());
|
||||
SkASSERT(padRadius >= 0);
|
||||
|
||||
padRadius /= std::abs(Cdet); // Scale this single value rather than all of C^-1 later on.
|
||||
|
||||
// The parametric functions for distance from lines L & M are:
|
||||
//
|
||||
// l(T) = (T - Td)^2 * (T - Te)
|
||||
// m(T) = (T - Td) * (T - Te)^2
|
||||
//
|
||||
// See "Resolution Independent Curve Rendering using Programmable Graphics Hardware",
|
||||
// 4.3 Finding klmn:
|
||||
//
|
||||
// https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
|
||||
Sk2f T2 = t2/s2; // T2 is the double root of l(T).
|
||||
Sk2f T1 = SkNx_shuffle<1,0>(T2); // T1 is the other root of l(T).
|
||||
|
||||
// Convert l(T), m(T) to power-basis form:
|
||||
//
|
||||
// | 1 1 |
|
||||
// |l(T) m(T)| = |T^3 T^2 T 1| * | l2 m2 |
|
||||
// | l1 m1 |
|
||||
// | l0 m0 |
|
||||
//
|
||||
// From here on we use Sk2f with "L" names, but the second lane will be for line M.
|
||||
Sk2f l2 = SkNx_fma(Sk2f(-2), T2, -T1);
|
||||
Sk2f l1 = T2 * SkNx_fma(Sk2f(2), T1, T2);
|
||||
Sk2f l0 = -T2*T2*T1;
|
||||
|
||||
// The equation for line L can be found as follows:
|
||||
//
|
||||
// L = C^-1 * (l excluding skipTerm)
|
||||
//
|
||||
// (See comments for GrPathUtils::calcCubicInverseTransposePowerBasisMatrix.)
|
||||
// We are only interested in the normal to L, so only need the upper 2x2 of C^-1. And rather
|
||||
// than divide by determinant(C) here, we have already performed this divide on padRadius.
|
||||
Sk2f l2or1 = (ExcludedTerm::kLinearTerm == skipTerm) ? l2 : l1;
|
||||
Sk2f Lx = -C0[1]*l2or1 + C1[1]; // l3 is always 1.
|
||||
Sk2f Ly = C0[0]*l2or1 - C1[0];
|
||||
|
||||
// A box of radius "padRadius" is touching line L if "center dot L" is less than the Manhattan
|
||||
// with of L. (See rationale in are_collinear.)
|
||||
Sk2f Lwidth = Lx.abs() + Ly.abs();
|
||||
Sk2f pad = Lwidth * padRadius;
|
||||
|
||||
// Is l(T=0) outside the padding around line L?
|
||||
Sk2f lT0 = l0; // l(T=0) = |0 0 0 1| dot |1 l2 l1 l0| = l0
|
||||
Sk2f outsideT0 = lT0.abs() - pad;
|
||||
|
||||
// Is l(T=1) outside the padding around line L?
|
||||
Sk2f lT1 = (Sk2f(1) + l2 + l1 + l0).abs(); // l(T=1) = |1 1 1 1| dot |1 l2 l1 l0|
|
||||
Sk2f outsideT1 = lT1.abs() - pad;
|
||||
|
||||
// Values for solving the cubic.
|
||||
Sk2f p, q, qqq, discr, numRoots, D;
|
||||
bool hasDiscr = false;
|
||||
|
||||
// Values for calculating one root (rarely needed).
|
||||
Sk2f R, QQ;
|
||||
bool hasOneRootVals = false;
|
||||
|
||||
// Values for calculating three roots.
|
||||
Sk2f P, cosTheta3;
|
||||
bool hasThreeRootVals = false;
|
||||
|
||||
// Solve for the T values where l(T) = +pad and m(T) = -pad.
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
float T = T2[i]; // T is the point we are chopping around.
|
||||
if ((T < 0 && outsideT0[i] >= 0) || (T > 1 && outsideT1[i] >= 0)) {
|
||||
// The padding around T is completely out of range. No point solving for it.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hasDiscr) {
|
||||
p = Sk2f(+.5f, -.5f) * pad;
|
||||
q = (1.f/3) * (T2 - T1);
|
||||
qqq = q*q*q;
|
||||
discr = qqq*p*2 + p*p;
|
||||
numRoots = (discr < 0).thenElse(3, 1);
|
||||
D = T2 - q;
|
||||
hasDiscr = true;
|
||||
}
|
||||
|
||||
if (1 == numRoots[i]) {
|
||||
if (!hasOneRootVals) {
|
||||
Sk2f r = qqq + p;
|
||||
Sk2f s = r.abs() + discr.sqrt();
|
||||
R = (r > 0).thenElse(-s, s);
|
||||
QQ = q*q;
|
||||
hasOneRootVals = true;
|
||||
}
|
||||
|
||||
float A = cbrtf(R[i]);
|
||||
float B = A != 0 ? QQ[i]/A : 0;
|
||||
// When there is only one root, ine L chops from root..1, line M chops from 0..root.
|
||||
if (1 == i) {
|
||||
chops->push_back(0);
|
||||
}
|
||||
chops->push_back(A + B + D[i]);
|
||||
if (0 == i) {
|
||||
chops->push_back(1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hasThreeRootVals) {
|
||||
P = q.abs() * -2;
|
||||
cosTheta3 = (q >= 0).thenElse(1, -1) + p / qqq.abs();
|
||||
hasThreeRootVals = true;
|
||||
}
|
||||
|
||||
static constexpr float k2PiOver3 = 2 * SK_ScalarPI / 3;
|
||||
float theta = std::acos(cosTheta3[i]) * (1.f/3);
|
||||
float roots[3] = {P[i] * std::cos(theta) + D[i],
|
||||
P[i] * std::cos(theta + k2PiOver3) + D[i],
|
||||
P[i] * std::cos(theta - k2PiOver3) + D[i]};
|
||||
|
||||
// Sort the three roots.
|
||||
swap_if_greater(roots[0], roots[1]);
|
||||
swap_if_greater(roots[1], roots[2]);
|
||||
swap_if_greater(roots[0], roots[1]);
|
||||
|
||||
// Line L chops around the first 2 roots, line M chops around the second 2.
|
||||
chops->push_back_n(2, &roots[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void GrCCFillGeometry::cubicTo(const SkPoint P[4], float inflectPad, float loopIntersectPad) {
|
||||
SkASSERT(fBuildingContour);
|
||||
SkASSERT(P[0] == fPoints.back());
|
||||
|
||||
// Don't crunch on the curve or inflate geometry if it is nearly flat (or just very small).
|
||||
// Flat curves can break the math below.
|
||||
if (are_collinear(P)) {
|
||||
Sk2f p0 = Sk2f::Load(P);
|
||||
Sk2f p3 = Sk2f::Load(P+3);
|
||||
this->appendLine(p0, p3);
|
||||
return;
|
||||
}
|
||||
|
||||
Sk2f p0 = Sk2f::Load(P);
|
||||
Sk2f p1 = Sk2f::Load(P+1);
|
||||
Sk2f p2 = Sk2f::Load(P+2);
|
||||
Sk2f p3 = Sk2f::Load(P+3);
|
||||
|
||||
// Also detect near-quadratics ahead of time.
|
||||
Sk2f tan0, tan1, c;
|
||||
get_cubic_tangents(p0, p1, p2, p3, &tan0, &tan1);
|
||||
if (is_cubic_nearly_quadratic(p0, p1, p2, p3, tan0, tan1, &c)) {
|
||||
this->appendQuadratics(p0, c, p3);
|
||||
return;
|
||||
}
|
||||
|
||||
double tt[2], ss[2], D[4];
|
||||
fCurrCubicType = SkClassifyCubic(P, tt, ss, D);
|
||||
SkASSERT(!SkCubicIsDegenerate(fCurrCubicType));
|
||||
Sk2f t = Sk2f(static_cast<float>(tt[0]), static_cast<float>(tt[1]));
|
||||
Sk2f s = Sk2f(static_cast<float>(ss[0]), static_cast<float>(ss[1]));
|
||||
|
||||
ExcludedTerm skipTerm = (std::abs(D[2]) > std::abs(D[1]))
|
||||
? ExcludedTerm::kQuadraticTerm
|
||||
: ExcludedTerm::kLinearTerm;
|
||||
Sk2f C0 = SkNx_fma(Sk2f(3), p1 - p2, p3 - p0);
|
||||
Sk2f C1 = (ExcludedTerm::kLinearTerm == skipTerm
|
||||
? SkNx_fma(Sk2f(-2), p1, p0 + p2)
|
||||
: p1 - p0) * 3;
|
||||
Sk2f C0x1 = C0 * SkNx_shuffle<1,0>(C1);
|
||||
float Cdet = C0x1[0] - C0x1[1];
|
||||
|
||||
SkSTArray<4, float> chops;
|
||||
if (SkCubicType::kLoop != fCurrCubicType) {
|
||||
find_chops_around_inflection_points(inflectPad, t, s, C0, C1, skipTerm, Cdet, &chops);
|
||||
} else {
|
||||
find_chops_around_loop_intersection(loopIntersectPad, t, s, C0, C1, skipTerm, Cdet, &chops);
|
||||
}
|
||||
if (4 == chops.count() && chops[1] >= chops[2]) {
|
||||
// This just the means the KLM roots are so close that their paddings overlap. We will
|
||||
// approximate the entire middle section, but still have it chopped midway. For loops this
|
||||
// chop guarantees the append code only sees convex segments. Otherwise, it means we are (at
|
||||
// least almost) a cusp and the chop makes sure we get a sharp point.
|
||||
Sk2f ts = t * SkNx_shuffle<1,0>(s);
|
||||
chops[1] = chops[2] = (ts[0] + ts[1]) / (2*s[0]*s[1]);
|
||||
}
|
||||
|
||||
#ifdef SK_DEBUG
|
||||
for (int i = 1; i < chops.count(); ++i) {
|
||||
SkASSERT(chops[i] >= chops[i - 1]);
|
||||
}
|
||||
#endif
|
||||
this->appendCubics(AppendCubicMode::kLiteral, p0, p1, p2, p3, chops.begin(), chops.count());
|
||||
}
|
||||
|
||||
static inline void chop_cubic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, const Sk2f& p3,
|
||||
float T, Sk2f* ab, Sk2f* abc, Sk2f* abcd, Sk2f* bcd, Sk2f* cd) {
|
||||
Sk2f TT = T;
|
||||
*ab = lerp(p0, p1, TT);
|
||||
Sk2f bc = lerp(p1, p2, TT);
|
||||
*cd = lerp(p2, p3, TT);
|
||||
*abc = lerp(*ab, bc, TT);
|
||||
*bcd = lerp(bc, *cd, TT);
|
||||
*abcd = lerp(*abc, *bcd, TT);
|
||||
}
|
||||
|
||||
void GrCCFillGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f& p1,
|
||||
const Sk2f& p2, const Sk2f& p3, const float chops[],
|
||||
int numChops, float localT0, float localT1) {
|
||||
if (numChops) {
|
||||
SkASSERT(numChops > 0);
|
||||
int midChopIdx = numChops/2;
|
||||
float T = chops[midChopIdx];
|
||||
// Chops alternate between literal and approximate mode.
|
||||
AppendCubicMode rightMode = (AppendCubicMode)((bool)mode ^ (midChopIdx & 1) ^ 1);
|
||||
|
||||
if (T <= localT0) {
|
||||
// T is outside 0..1. Append the right side only.
|
||||
this->appendCubics(rightMode, p0, p1, p2, p3, &chops[midChopIdx + 1],
|
||||
numChops - midChopIdx - 1, localT0, localT1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (T >= localT1) {
|
||||
// T is outside 0..1. Append the left side only.
|
||||
this->appendCubics(mode, p0, p1, p2, p3, chops, midChopIdx, localT0, localT1);
|
||||
return;
|
||||
}
|
||||
|
||||
float localT = (T - localT0) / (localT1 - localT0);
|
||||
Sk2f p01, p02, pT, p11, p12;
|
||||
chop_cubic(p0, p1, p2, p3, localT, &p01, &p02, &pT, &p11, &p12);
|
||||
this->appendCubics(mode, p0, p01, p02, pT, chops, midChopIdx, localT0, T);
|
||||
this->appendCubics(rightMode, pT, p11, p12, p3, &chops[midChopIdx + 1],
|
||||
numChops - midChopIdx - 1, T, localT1);
|
||||
return;
|
||||
}
|
||||
|
||||
this->appendCubics(mode, p0, p1, p2, p3);
|
||||
}
|
||||
|
||||
void GrCCFillGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f& p1,
|
||||
const Sk2f& p2, const Sk2f& p3, int maxSubdivisions) {
|
||||
if (SkCubicType::kLoop != fCurrCubicType) {
|
||||
// Serpentines and cusps are always monotonic after chopping around inflection points.
|
||||
SkASSERT(!SkCubicIsDegenerate(fCurrCubicType));
|
||||
|
||||
if (AppendCubicMode::kApproximate == mode) {
|
||||
// This section passes through an inflection point, so we can get away with a flat line.
|
||||
// This can cause some curves to feel slightly more flat when inspected rigorously back
|
||||
// and forth against another renderer, but for now this seems acceptable given the
|
||||
// simplicity.
|
||||
this->appendLine(p0, p3);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Sk2f tan0, tan1;
|
||||
get_cubic_tangents(p0, p1, p2, p3, &tan0, &tan1);
|
||||
|
||||
if (maxSubdivisions && !is_convex_curve_monotonic(p0, tan0, p3, tan1)) {
|
||||
this->chopAndAppendCubicAtMidTangent(mode, p0, p1, p2, p3, tan0, tan1,
|
||||
maxSubdivisions - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (AppendCubicMode::kApproximate == mode) {
|
||||
Sk2f c;
|
||||
if (!is_cubic_nearly_quadratic(p0, p1, p2, p3, tan0, tan1, &c) && maxSubdivisions) {
|
||||
this->chopAndAppendCubicAtMidTangent(mode, p0, p1, p2, p3, tan0, tan1,
|
||||
maxSubdivisions - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
this->appendMonotonicQuadratic(p0, c, p3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't send curves to the GPU if we know they are nearly flat (or just very small).
|
||||
// Since the cubic segment is known to be convex at this point, our flatness check is simple.
|
||||
if (are_collinear(p0, (p1 + p2) * .5f, p3)) {
|
||||
this->appendLine(p0, p3);
|
||||
return;
|
||||
}
|
||||
|
||||
SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
|
||||
SkASSERT((p0 != p3).anyTrue());
|
||||
p1.store(&fPoints.push_back());
|
||||
p2.store(&fPoints.push_back());
|
||||
p3.store(&fPoints.push_back());
|
||||
fVerbs.push_back(Verb::kMonotonicCubicTo);
|
||||
++fCurrContourTallies.fCubics;
|
||||
}
|
||||
|
||||
// Given a convex curve segment with the following order-2 tangent function:
|
||||
//
|
||||
// |C2x C2y|
|
||||
// tan = some_scale * |dx/dt dy/dt| = |t^2 t 1| * |C1x C1y|
|
||||
// |C0x C0y|
|
||||
//
|
||||
// This function finds the T value whose tangent angle is halfway between the tangents at T=0 and
|
||||
// T=1 (tan0 and tan1).
|
||||
static inline float find_midtangent(const Sk2f& tan0, const Sk2f& tan1,
|
||||
const Sk2f& C2, const Sk2f& C1, const Sk2f& C0) {
|
||||
// Tangents point in the direction of increasing T, so tan0 and -tan1 both point toward the
|
||||
// midtangent. 'n' will therefore bisect tan0 and -tan1, giving us the normal to the midtangent.
|
||||
//
|
||||
// n dot midtangent = 0
|
||||
//
|
||||
Sk2f n = normalize(tan0) - normalize(tan1);
|
||||
|
||||
// Find the T value at the midtangent. This is a simple quadratic equation:
|
||||
//
|
||||
// midtangent dot n = 0
|
||||
//
|
||||
// (|t^2 t 1| * C) dot n = 0
|
||||
//
|
||||
// |t^2 t 1| dot C*n = 0
|
||||
//
|
||||
// First find coeffs = C*n.
|
||||
Sk4f C[2];
|
||||
Sk2f::Store4(C, C2, C1, C0, 0);
|
||||
Sk4f coeffs = C[0]*n[0] + C[1]*n[1];
|
||||
|
||||
// Now solve the quadratic.
|
||||
float a = coeffs[0], b = coeffs[1], c = coeffs[2];
|
||||
float discr = b*b - 4*a*c;
|
||||
if (discr < 0) {
|
||||
return 0; // This will only happen if the curve is a line.
|
||||
}
|
||||
|
||||
// The roots are q/a and c/q. Pick the one closer to T=.5.
|
||||
float q = -.5f * (b + copysignf(std::sqrt(discr), b));
|
||||
float r = .5f*q*a;
|
||||
return std::abs(q*q - r) < std::abs(a*c - r) ? q/a : c/q;
|
||||
}
|
||||
|
||||
inline void GrCCFillGeometry::chopAndAppendCubicAtMidTangent(AppendCubicMode mode, const Sk2f& p0,
|
||||
const Sk2f& p1, const Sk2f& p2,
|
||||
const Sk2f& p3, const Sk2f& tan0,
|
||||
const Sk2f& tan1,
|
||||
int maxFutureSubdivisions) {
|
||||
float midT = find_midtangent(tan0, tan1, p3 + (p1 - p2)*3 - p0,
|
||||
(p0 - p1*2 + p2)*2,
|
||||
p1 - p0);
|
||||
// Use positive logic since NaN fails comparisons. (However midT should not be NaN since we cull
|
||||
// near-flat cubics in cubicTo().)
|
||||
if (!(midT > 0 && midT < 1)) {
|
||||
// The cubic is flat. Otherwise there would be a real midtangent inside T=0..1.
|
||||
this->appendLine(p0, p3);
|
||||
return;
|
||||
}
|
||||
|
||||
Sk2f p01, p02, pT, p11, p12;
|
||||
chop_cubic(p0, p1, p2, p3, midT, &p01, &p02, &pT, &p11, &p12);
|
||||
this->appendCubics(mode, p0, p01, p02, pT, maxFutureSubdivisions);
|
||||
this->appendCubics(mode, pT, p11, p12, p3, maxFutureSubdivisions);
|
||||
}
|
||||
|
||||
void GrCCFillGeometry::conicTo(const SkPoint P[3], float w) {
|
||||
SkASSERT(fBuildingContour);
|
||||
SkASSERT(P[0] == fPoints.back());
|
||||
Sk2f p0 = Sk2f::Load(P);
|
||||
Sk2f p1 = Sk2f::Load(P+1);
|
||||
Sk2f p2 = Sk2f::Load(P+2);
|
||||
|
||||
Sk2f tan0 = p1 - p0;
|
||||
Sk2f tan1 = p2 - p1;
|
||||
|
||||
if (!is_convex_curve_monotonic(p0, tan0, p2, tan1)) {
|
||||
// The derivative of a conic has a cumbersome order-4 denominator. However, this isn't
|
||||
// necessary if we are only interested in a vector in the same *direction* as a given
|
||||
// tangent line. Since the denominator scales dx and dy uniformly, we can throw it out
|
||||
// completely after evaluating the derivative with the standard quotient rule. This leaves
|
||||
// us with a simpler quadratic function that we use to find the midtangent.
|
||||
float midT = find_midtangent(tan0, tan1, (w - 1) * (p2 - p0),
|
||||
(p2 - p0) - 2*w*(p1 - p0),
|
||||
w*(p1 - p0));
|
||||
// Use positive logic since NaN fails comparisons. (However midT should not be NaN since we
|
||||
// cull near-linear conics above. And while w=0 is flat, it's not a line and has valid
|
||||
// midtangents.)
|
||||
if (!(midT > 0 && midT < 1)) {
|
||||
// The conic is flat. Otherwise there would be a real midtangent inside T=0..1.
|
||||
this->appendLine(p0, p2);
|
||||
return;
|
||||
}
|
||||
|
||||
// Chop the conic at midtangent to produce two monotonic segments.
|
||||
Sk4f p3d0 = Sk4f(p0[0], p0[1], 1, 0);
|
||||
Sk4f p3d1 = Sk4f(p1[0], p1[1], 1, 0) * w;
|
||||
Sk4f p3d2 = Sk4f(p2[0], p2[1], 1, 0);
|
||||
Sk4f midT4 = midT;
|
||||
|
||||
Sk4f p3d01 = lerp(p3d0, p3d1, midT4);
|
||||
Sk4f p3d12 = lerp(p3d1, p3d2, midT4);
|
||||
Sk4f p3d012 = lerp(p3d01, p3d12, midT4);
|
||||
|
||||
Sk2f midpoint = Sk2f(p3d012[0], p3d012[1]) / p3d012[2];
|
||||
Sk2f ww = Sk2f(p3d01[2], p3d12[2]) * Sk2f(p3d012[2]).rsqrt();
|
||||
|
||||
this->appendMonotonicConic(p0, Sk2f(p3d01[0], p3d01[1]) / p3d01[2], midpoint, ww[0]);
|
||||
this->appendMonotonicConic(midpoint, Sk2f(p3d12[0], p3d12[1]) / p3d12[2], p2, ww[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
this->appendMonotonicConic(p0, p1, p2, w);
|
||||
}
|
||||
|
||||
void GrCCFillGeometry::appendMonotonicConic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
|
||||
float w) {
|
||||
SkASSERT(w >= 0);
|
||||
|
||||
Sk2f base = p2 - p0;
|
||||
Sk2f baseAbs = base.abs();
|
||||
float baseWidth = baseAbs[0] + baseAbs[1];
|
||||
|
||||
// Find the height of the curve. Max height always occurs at T=.5 for conics.
|
||||
Sk2f d = (p1 - p0) * SkNx_shuffle<1,0>(base);
|
||||
float h1 = std::abs(d[1] - d[0]); // Height of p1 above the base.
|
||||
float ht = h1*w, hs = 1 + w; // Height of the conic = ht/hs.
|
||||
|
||||
// i.e. (ht/hs <= baseWidth * kFlatnessThreshold). Use "<=" in case base == 0.
|
||||
if (ht <= (baseWidth*hs) * kFlatnessThreshold) {
|
||||
// We are flat. (See rationale in are_collinear.)
|
||||
this->appendLine(p0, p2);
|
||||
return;
|
||||
}
|
||||
|
||||
// i.e. (w > 1 && h1 - ht/hs < baseWidth).
|
||||
if (w > 1 && h1*hs - ht < baseWidth*hs) {
|
||||
// If we get within 1px of p1 when w > 1, we will pick up artifacts from the implicit
|
||||
// function's reflection. Chop at max height (T=.5) and draw a triangle instead.
|
||||
Sk2f p1w = p1*w;
|
||||
Sk2f ab = p0 + p1w;
|
||||
Sk2f bc = p1w + p2;
|
||||
Sk2f highpoint = (ab + bc) / (2*(1 + w));
|
||||
this->appendLine(p0, highpoint);
|
||||
this->appendLine(highpoint, p2);
|
||||
return;
|
||||
}
|
||||
|
||||
SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
|
||||
SkASSERT((p0 != p2).anyTrue());
|
||||
p1.store(&fPoints.push_back());
|
||||
p2.store(&fPoints.push_back());
|
||||
fConicWeights.push_back(w);
|
||||
fVerbs.push_back(Verb::kMonotonicConicTo);
|
||||
++fCurrContourTallies.fConics;
|
||||
}
|
||||
|
||||
GrCCFillGeometry::PrimitiveTallies GrCCFillGeometry::endContour() {
|
||||
SkASSERT(fBuildingContour);
|
||||
SkASSERT(fVerbs.count() >= fCurrContourTallies.fTriangles);
|
||||
|
||||
// The fTriangles field currently contains this contour's starting verb index. We can now
|
||||
// use it to calculate the size of the contour's fan.
|
||||
int fanSize = fVerbs.count() - fCurrContourTallies.fTriangles;
|
||||
if (fPoints.back() == fCurrAnchorPoint) {
|
||||
--fanSize;
|
||||
fVerbs.push_back(Verb::kEndClosedContour);
|
||||
} else {
|
||||
fVerbs.push_back(Verb::kEndOpenContour);
|
||||
}
|
||||
|
||||
fCurrContourTallies.fTriangles = std::max(fanSize - 2, 0);
|
||||
|
||||
SkDEBUGCODE(fBuildingContour = false);
|
||||
return fCurrContourTallies;
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef GrGrCCFillGeometry_DEFINED
|
||||
#define GrGrCCFillGeometry_DEFINED
|
||||
|
||||
#include "include/core/SkPoint.h"
|
||||
#include "include/private/SkNx.h"
|
||||
#include "include/private/SkTArray.h"
|
||||
#include "src/core/SkGeometry.h"
|
||||
|
||||
/**
|
||||
* This class chops device-space contours up into a series of segments that CCPR knows how to
|
||||
* fill. (See GrCCFillGeometry::Verb.)
|
||||
*
|
||||
* NOTE: This must be done in device space, since an affine transformation can change whether a
|
||||
* curve is monotonic.
|
||||
*/
|
||||
class GrCCFillGeometry {
|
||||
public:
|
||||
// These are the verbs that CCPR knows how to fill. If a path has any segments that don't map to
|
||||
// this list, then they are chopped into smaller ones that do. A list of these comprise a
|
||||
// compact representation of what can later be expanded into GPU instance data.
|
||||
enum class Verb : uint8_t {
|
||||
kBeginPath, // Included only for caller convenience.
|
||||
kBeginContour,
|
||||
kLineTo,
|
||||
kMonotonicQuadraticTo, // Monotonic relative to the vector between its endpoints [P2 - P0].
|
||||
kMonotonicCubicTo,
|
||||
kMonotonicConicTo,
|
||||
kEndClosedContour, // endPt == startPt.
|
||||
kEndOpenContour // endPt != startPt.
|
||||
};
|
||||
|
||||
// These tallies track numbers of CCPR primitives that are required to draw a contour.
|
||||
struct PrimitiveTallies {
|
||||
int fTriangles; // Number of triangles in the contour's fan.
|
||||
int fWeightedTriangles; // Triangles (from the tessellator) whose winding magnitude > 1.
|
||||
int fQuadratics;
|
||||
int fCubics;
|
||||
int fConics;
|
||||
|
||||
void operator+=(const PrimitiveTallies&);
|
||||
PrimitiveTallies operator-(const PrimitiveTallies&) const;
|
||||
bool operator==(const PrimitiveTallies&);
|
||||
};
|
||||
|
||||
GrCCFillGeometry(int numSkPoints = 0, int numSkVerbs = 0, int numConicWeights = 0)
|
||||
: fPoints(numSkPoints * 3) // Reserve for a 3x expansion in points and verbs.
|
||||
, fVerbs(numSkVerbs * 3)
|
||||
, fConicWeights(numConicWeights * 3/2) {}
|
||||
|
||||
const SkTArray<SkPoint, true>& points() const { SkASSERT(!fBuildingContour); return fPoints; }
|
||||
const SkTArray<Verb, true>& verbs() const { SkASSERT(!fBuildingContour); return fVerbs; }
|
||||
float getConicWeight(int idx) const { SkASSERT(!fBuildingContour); return fConicWeights[idx]; }
|
||||
|
||||
void reset() {
|
||||
SkASSERT(!fBuildingContour);
|
||||
fPoints.reset();
|
||||
fVerbs.reset();
|
||||
}
|
||||
|
||||
void beginPath();
|
||||
void beginContour(const SkPoint&);
|
||||
void lineTo(const SkPoint P[2]);
|
||||
void quadraticTo(const SkPoint[3]);
|
||||
|
||||
// We pass through inflection points and loop intersections using a line and quadratic(s)
|
||||
// respectively. 'inflectPad' and 'loopIntersectPad' specify how close (in pixels) cubic
|
||||
// segments are allowed to get to these points. For normal rendering you will want to use the
|
||||
// default values, but these can be overridden for testing purposes.
|
||||
//
|
||||
// NOTE: loops do appear to require two full pixels of padding around the intersection point.
|
||||
// With just one pixel-width of pad, we start to see bad pixels. Ultimately this has a
|
||||
// minimal effect on the total amount of segments produced. Most sections that pass
|
||||
// through the loop intersection can be approximated with a single quadratic anyway,
|
||||
// regardless of whether we are use one pixel of pad or two (1.622 avg. quads per loop
|
||||
// intersection vs. 1.489 on the tiger).
|
||||
void cubicTo(const SkPoint[4], float inflectPad = 0.55f, float loopIntersectPad = 2);
|
||||
|
||||
void conicTo(const SkPoint[3], float w);
|
||||
|
||||
PrimitiveTallies endContour(); // Returns the numbers of primitives needed to draw the contour.
|
||||
|
||||
private:
|
||||
inline void appendLine(const Sk2f& p0, const Sk2f& p1);
|
||||
|
||||
inline void appendQuadratics(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2);
|
||||
inline void appendMonotonicQuadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2);
|
||||
|
||||
enum class AppendCubicMode : bool {
|
||||
kLiteral,
|
||||
kApproximate
|
||||
};
|
||||
void appendCubics(AppendCubicMode, const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
|
||||
const Sk2f& p3, const float chops[], int numChops, float localT0 = 0,
|
||||
float localT1 = 1);
|
||||
void appendCubics(AppendCubicMode, const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
|
||||
const Sk2f& p3, int maxSubdivisions = 2);
|
||||
void chopAndAppendCubicAtMidTangent(AppendCubicMode, const Sk2f& p0, const Sk2f& p1,
|
||||
const Sk2f& p2, const Sk2f& p3, const Sk2f& tan0,
|
||||
const Sk2f& tan1, int maxFutureSubdivisions);
|
||||
|
||||
void appendMonotonicConic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, float w);
|
||||
|
||||
// Transient state used while building a contour.
|
||||
SkPoint fCurrAnchorPoint;
|
||||
PrimitiveTallies fCurrContourTallies;
|
||||
SkCubicType fCurrCubicType;
|
||||
SkDEBUGCODE(bool fBuildingContour = false);
|
||||
|
||||
SkSTArray<128, SkPoint, true> fPoints;
|
||||
SkSTArray<128, Verb, true> fVerbs;
|
||||
SkSTArray<32, float, true> fConicWeights;
|
||||
};
|
||||
|
||||
inline void GrCCFillGeometry::PrimitiveTallies::operator+=(const PrimitiveTallies& b) {
|
||||
fTriangles += b.fTriangles;
|
||||
fWeightedTriangles += b.fWeightedTriangles;
|
||||
fQuadratics += b.fQuadratics;
|
||||
fCubics += b.fCubics;
|
||||
fConics += b.fConics;
|
||||
}
|
||||
|
||||
GrCCFillGeometry::PrimitiveTallies
|
||||
inline GrCCFillGeometry::PrimitiveTallies::operator-(const PrimitiveTallies& b) const {
|
||||
return {fTriangles - b.fTriangles,
|
||||
fWeightedTriangles - b.fWeightedTriangles,
|
||||
fQuadratics - b.fQuadratics,
|
||||
fCubics - b.fCubics,
|
||||
fConics - b.fConics};
|
||||
}
|
||||
|
||||
inline bool GrCCFillGeometry::PrimitiveTallies::operator==(const PrimitiveTallies& b) {
|
||||
return fTriangles == b.fTriangles && fWeightedTriangles == b.fWeightedTriangles &&
|
||||
fQuadratics == b.fQuadratics && fCubics == b.fCubics && fConics == b.fConics;
|
||||
}
|
||||
|
||||
#endif
|
@ -1,555 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "src/gpu/ccpr/GrCCFiller.h"
|
||||
|
||||
#include "include/core/SkPath.h"
|
||||
#include "include/core/SkPoint.h"
|
||||
#include "src/core/SkMathPriv.h"
|
||||
#include "src/core/SkPathPriv.h"
|
||||
#include "src/gpu/GrCaps.h"
|
||||
#include "src/gpu/GrOnFlushResourceProvider.h"
|
||||
#include "src/gpu/GrOpFlushState.h"
|
||||
#include "src/gpu/GrProgramInfo.h"
|
||||
#include <cstdlib>
|
||||
|
||||
using TriPointInstance = GrCCCoverageProcessor::TriPointInstance;
|
||||
using QuadPointInstance = GrCCCoverageProcessor::QuadPointInstance;
|
||||
|
||||
GrCCFiller::GrCCFiller(Algorithm algorithm, int numPaths, int numSkPoints, int numSkVerbs,
|
||||
int numConicWeights)
|
||||
: fAlgorithm(algorithm)
|
||||
, fGeometry(numSkPoints, numSkVerbs, numConicWeights)
|
||||
, fPathInfos(numPaths)
|
||||
, fScissorSubBatches(numPaths)
|
||||
, fTotalPrimitiveCounts{PrimitiveTallies(), PrimitiveTallies()} {
|
||||
// Batches decide what to draw by looking where the previous one ended. Define initial batches
|
||||
// that "end" at the beginning of the data. These will not be drawn, but will only be be read by
|
||||
// the first actual batch.
|
||||
fScissorSubBatches.push_back() = {PrimitiveTallies(), SkIRect::MakeEmpty()};
|
||||
fBatches.push_back() = {PrimitiveTallies(), fScissorSubBatches.count(), PrimitiveTallies()};
|
||||
}
|
||||
|
||||
void GrCCFiller::parseDeviceSpaceFill(const SkPath& path, const SkPoint* deviceSpacePts,
|
||||
GrScissorTest scissorTest, const SkIRect& clippedDevIBounds,
|
||||
const SkIVector& devToAtlasOffset) {
|
||||
SkASSERT(!fInstanceBuffer.hasGpuBuffer()); // Can't call after prepareToDraw().
|
||||
SkASSERT(!path.isEmpty());
|
||||
|
||||
int currPathPointsIdx = fGeometry.points().count();
|
||||
int currPathVerbsIdx = fGeometry.verbs().count();
|
||||
PrimitiveTallies currPathPrimitiveCounts = PrimitiveTallies();
|
||||
|
||||
fGeometry.beginPath();
|
||||
|
||||
const float* conicWeights = SkPathPriv::ConicWeightData(path);
|
||||
int ptsIdx = 0;
|
||||
int conicWeightsIdx = 0;
|
||||
bool insideContour = false;
|
||||
|
||||
for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
|
||||
switch (verb) {
|
||||
case SkPath::kMove_Verb:
|
||||
if (insideContour) {
|
||||
currPathPrimitiveCounts += fGeometry.endContour();
|
||||
}
|
||||
fGeometry.beginContour(deviceSpacePts[ptsIdx]);
|
||||
++ptsIdx;
|
||||
insideContour = true;
|
||||
continue;
|
||||
case SkPath::kClose_Verb:
|
||||
if (insideContour) {
|
||||
currPathPrimitiveCounts += fGeometry.endContour();
|
||||
}
|
||||
insideContour = false;
|
||||
continue;
|
||||
case SkPath::kLine_Verb:
|
||||
fGeometry.lineTo(&deviceSpacePts[ptsIdx - 1]);
|
||||
++ptsIdx;
|
||||
continue;
|
||||
case SkPath::kQuad_Verb:
|
||||
fGeometry.quadraticTo(&deviceSpacePts[ptsIdx - 1]);
|
||||
ptsIdx += 2;
|
||||
continue;
|
||||
case SkPath::kCubic_Verb:
|
||||
fGeometry.cubicTo(&deviceSpacePts[ptsIdx - 1]);
|
||||
ptsIdx += 3;
|
||||
continue;
|
||||
case SkPath::kConic_Verb:
|
||||
fGeometry.conicTo(&deviceSpacePts[ptsIdx - 1], conicWeights[conicWeightsIdx]);
|
||||
ptsIdx += 2;
|
||||
++conicWeightsIdx;
|
||||
continue;
|
||||
default:
|
||||
SK_ABORT("Unexpected path verb.");
|
||||
}
|
||||
}
|
||||
SkASSERT(ptsIdx == path.countPoints());
|
||||
SkASSERT(conicWeightsIdx == SkPathPriv::ConicWeightCnt(path));
|
||||
|
||||
if (insideContour) {
|
||||
currPathPrimitiveCounts += fGeometry.endContour();
|
||||
}
|
||||
|
||||
fPathInfos.emplace_back(scissorTest, devToAtlasOffset);
|
||||
|
||||
// Tessellate fans from very large and/or simple paths, in order to reduce overdraw.
|
||||
int numVerbs = fGeometry.verbs().count() - currPathVerbsIdx - 1;
|
||||
int64_t tessellationWork = (int64_t)numVerbs * (32 - SkCLZ(numVerbs)); // N log N.
|
||||
int64_t fanningWork = (int64_t)clippedDevIBounds.height() * clippedDevIBounds.width();
|
||||
if (tessellationWork * (50*50) + (100*100) < fanningWork) { // Don't tessellate under 100x100.
|
||||
fPathInfos.back().tessellateFan(
|
||||
fAlgorithm, path, fGeometry, currPathVerbsIdx, currPathPointsIdx, clippedDevIBounds,
|
||||
&currPathPrimitiveCounts);
|
||||
}
|
||||
|
||||
fTotalPrimitiveCounts[(int)scissorTest] += currPathPrimitiveCounts;
|
||||
|
||||
if (GrScissorTest::kEnabled == scissorTest) {
|
||||
fScissorSubBatches.push_back() = {fTotalPrimitiveCounts[(int)GrScissorTest::kEnabled],
|
||||
clippedDevIBounds.makeOffset(devToAtlasOffset)};
|
||||
}
|
||||
}
|
||||
|
||||
void GrCCFiller::PathInfo::tessellateFan(
|
||||
Algorithm algorithm, const SkPath& originalPath, const GrCCFillGeometry& geometry,
|
||||
int verbsIdx, int ptsIdx, const SkIRect& clippedDevIBounds,
|
||||
PrimitiveTallies* newTriangleCounts) {
|
||||
using Verb = GrCCFillGeometry::Verb;
|
||||
SkASSERT(-1 == fFanTessellationCount);
|
||||
SkASSERT(!fFanTessellation);
|
||||
|
||||
const SkTArray<Verb, true>& verbs = geometry.verbs();
|
||||
const SkTArray<SkPoint, true>& pts = geometry.points();
|
||||
|
||||
newTriangleCounts->fTriangles =
|
||||
newTriangleCounts->fWeightedTriangles = 0;
|
||||
|
||||
// Build an SkPath of the Redbook fan.
|
||||
SkPath fan;
|
||||
// When counting winding numbers in the stencil buffer, it works to use even/odd for the fan
|
||||
// tessellation (where applicable). But we need to strip out inverse fill info because
|
||||
// inverse-ness gets accounted for later on.
|
||||
fan.setFillType(SkPathFillType_ConvertToNonInverse(originalPath.getFillType()));
|
||||
SkASSERT(Verb::kBeginPath == verbs[verbsIdx]);
|
||||
for (int i = verbsIdx + 1; i < verbs.count(); ++i) {
|
||||
switch (verbs[i]) {
|
||||
case Verb::kBeginPath:
|
||||
SK_ABORT("Invalid GrCCFillGeometry");
|
||||
continue;
|
||||
|
||||
case Verb::kBeginContour:
|
||||
fan.moveTo(pts[ptsIdx++]);
|
||||
continue;
|
||||
|
||||
case Verb::kLineTo:
|
||||
fan.lineTo(pts[ptsIdx++]);
|
||||
continue;
|
||||
|
||||
case Verb::kMonotonicQuadraticTo:
|
||||
case Verb::kMonotonicConicTo:
|
||||
fan.lineTo(pts[ptsIdx + 1]);
|
||||
ptsIdx += 2;
|
||||
continue;
|
||||
|
||||
case Verb::kMonotonicCubicTo:
|
||||
fan.lineTo(pts[ptsIdx + 2]);
|
||||
ptsIdx += 3;
|
||||
continue;
|
||||
|
||||
case Verb::kEndClosedContour:
|
||||
case Verb::kEndOpenContour:
|
||||
fan.close();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
GrTriangulator::WindingVertex* vertices = nullptr;
|
||||
SkASSERT(!fan.isInverseFillType());
|
||||
fFanTessellationCount = GrTriangulator::PathToVertices(
|
||||
fan, std::numeric_limits<float>::infinity(), SkRect::Make(clippedDevIBounds),
|
||||
&vertices);
|
||||
if (fFanTessellationCount <= 0) {
|
||||
SkASSERT(0 == fFanTessellationCount);
|
||||
SkASSERT(nullptr == vertices);
|
||||
return;
|
||||
}
|
||||
|
||||
SkASSERT(0 == fFanTessellationCount % 3);
|
||||
for (int i = 0; i < fFanTessellationCount; i += 3) {
|
||||
int weight = abs(vertices[i].fWinding);
|
||||
if (SkPathFillType::kEvenOdd == fan.getFillType()) {
|
||||
// The tessellator doesn't wrap weights modulo 2 when we request even/odd fill type.
|
||||
SkASSERT(weight & 1);
|
||||
weight = 1;
|
||||
}
|
||||
newTriangleCounts->fTriangles += weight;
|
||||
vertices[i].fWinding = weight;
|
||||
}
|
||||
|
||||
fFanTessellation.reset(vertices);
|
||||
}
|
||||
|
||||
GrCCFiller::BatchID GrCCFiller::closeCurrentBatch() {
|
||||
SkASSERT(!fInstanceBuffer.hasGpuBuffer());
|
||||
SkASSERT(!fBatches.empty());
|
||||
|
||||
const auto& lastBatch = fBatches.back();
|
||||
int maxMeshes = 1 + fScissorSubBatches.count() - lastBatch.fEndScissorSubBatchIdx;
|
||||
fMaxMeshesPerDraw = std::max(fMaxMeshesPerDraw, maxMeshes);
|
||||
|
||||
const auto& lastScissorSubBatch = fScissorSubBatches[lastBatch.fEndScissorSubBatchIdx - 1];
|
||||
PrimitiveTallies batchTotalCounts = fTotalPrimitiveCounts[(int)GrScissorTest::kDisabled] -
|
||||
lastBatch.fEndNonScissorIndices;
|
||||
batchTotalCounts += fTotalPrimitiveCounts[(int)GrScissorTest::kEnabled] -
|
||||
lastScissorSubBatch.fEndPrimitiveIndices;
|
||||
|
||||
// This will invalidate lastBatch.
|
||||
fBatches.push_back() = {
|
||||
fTotalPrimitiveCounts[(int)GrScissorTest::kDisabled],
|
||||
fScissorSubBatches.count(),
|
||||
batchTotalCounts
|
||||
};
|
||||
return fBatches.count() - 1;
|
||||
}
|
||||
|
||||
// Emits a contour's triangle fan.
|
||||
//
|
||||
// Classic Redbook fanning would be the triangles: [0 1 2], [0 2 3], ..., [0 n-2 n-1].
|
||||
//
|
||||
// This function emits the triangle: [0 n/3 n*2/3], and then recurses on all three sides. The
|
||||
// advantage to this approach is that for a convex-ish contour, it generates larger triangles.
|
||||
// Classic fanning tends to generate long, skinny triangles, which are expensive to draw since they
|
||||
// have a longer perimeter to rasterize and antialias.
|
||||
//
|
||||
// The indices array indexes the fan's points (think: glDrawElements), and must have at least log3
|
||||
// elements past the end for this method to use as scratch space.
|
||||
//
|
||||
// Returns the next triangle instance after the final one emitted.
|
||||
static TriPointInstance* emit_recursive_fan(
|
||||
const SkTArray<SkPoint, true>& pts, SkTArray<int32_t, true>& indices, int firstIndex,
|
||||
int indexCount, const Sk2f& devToAtlasOffset, TriPointInstance::Ordering ordering,
|
||||
TriPointInstance out[]) {
|
||||
if (indexCount < 3) {
|
||||
return out;
|
||||
}
|
||||
|
||||
int32_t oneThirdCount = indexCount / 3;
|
||||
int32_t twoThirdsCount = (2 * indexCount) / 3;
|
||||
out++->set(pts[indices[firstIndex]], pts[indices[firstIndex + oneThirdCount]],
|
||||
pts[indices[firstIndex + twoThirdsCount]], devToAtlasOffset, ordering);
|
||||
|
||||
out = emit_recursive_fan(
|
||||
pts, indices, firstIndex, oneThirdCount + 1, devToAtlasOffset, ordering, out);
|
||||
out = emit_recursive_fan(
|
||||
pts, indices, firstIndex + oneThirdCount, twoThirdsCount - oneThirdCount + 1,
|
||||
devToAtlasOffset, ordering, out);
|
||||
|
||||
int endIndex = firstIndex + indexCount;
|
||||
int32_t oldValue = indices[endIndex];
|
||||
indices[endIndex] = indices[firstIndex];
|
||||
out = emit_recursive_fan(
|
||||
pts, indices, firstIndex + twoThirdsCount, indexCount - twoThirdsCount + 1,
|
||||
devToAtlasOffset, ordering, out);
|
||||
indices[endIndex] = oldValue;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void GrCCFiller::emitTessellatedFan(
|
||||
const GrTriangulator::WindingVertex* vertices, int numVertices,
|
||||
const Sk2f& devToAtlasOffset, TriPointInstance::Ordering ordering,
|
||||
TriPointInstance* triPointInstanceData, QuadPointInstance* quadPointInstanceData,
|
||||
GrCCFillGeometry::PrimitiveTallies* indices) {
|
||||
for (int i = 0; i < numVertices; i += 3) {
|
||||
int weight = vertices[i].fWinding;
|
||||
SkASSERT(weight >= 1);
|
||||
for (int j = 0; j < weight; ++j) {
|
||||
// Unfortunately, there is not a way to increment stencil values by an amount larger
|
||||
// than 1. Instead we draw the triangle 'weight' times.
|
||||
triPointInstanceData[indices->fTriangles++].set(
|
||||
vertices[i].fPos, vertices[i + 1].fPos, vertices[i + 2].fPos, devToAtlasOffset,
|
||||
ordering);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GrCCFiller::prepareToDraw(GrOnFlushResourceProvider* onFlushRP) {
|
||||
using Verb = GrCCFillGeometry::Verb;
|
||||
SkASSERT(!fInstanceBuffer.hasGpuBuffer());
|
||||
SkASSERT(fBatches.back().fEndNonScissorIndices == // Call closeCurrentBatch().
|
||||
fTotalPrimitiveCounts[(int)GrScissorTest::kDisabled]);
|
||||
SkASSERT(fBatches.back().fEndScissorSubBatchIdx == fScissorSubBatches.count());
|
||||
|
||||
auto triangleOrdering = TriPointInstance::Ordering::kXYInterleaved;
|
||||
|
||||
// Here we build a single instance buffer to share with every internal batch.
|
||||
//
|
||||
// CCPR processs 3 different types of primitives: triangles, quadratics, cubics. Each primitive
|
||||
// type is further divided into instances that require a scissor and those that don't. This
|
||||
// leaves us with 3*2 = 6 independent instance arrays to build for the GPU.
|
||||
//
|
||||
// Rather than place each instance array in its own GPU buffer, we allocate a single
|
||||
// megabuffer and lay them all out side-by-side. We can offset the "baseInstance" parameter in
|
||||
// our draw calls to direct the GPU to the applicable elements within a given array.
|
||||
//
|
||||
// We already know how big to make each of the 6 arrays from fTotalPrimitiveCounts, so layout is
|
||||
// straightforward. Start with triangles and quadratics. They both view the instance buffer as
|
||||
// an array of TriPointInstance[], so we can begin at zero and lay them out one after the other.
|
||||
fBaseInstances[0].fTriangles = 0;
|
||||
fBaseInstances[1].fTriangles = fBaseInstances[0].fTriangles +
|
||||
fTotalPrimitiveCounts[0].fTriangles;
|
||||
fBaseInstances[0].fQuadratics = fBaseInstances[1].fTriangles +
|
||||
fTotalPrimitiveCounts[1].fTriangles;
|
||||
fBaseInstances[1].fQuadratics = fBaseInstances[0].fQuadratics +
|
||||
fTotalPrimitiveCounts[0].fQuadratics;
|
||||
int triEndIdx = fBaseInstances[1].fQuadratics + fTotalPrimitiveCounts[1].fQuadratics;
|
||||
|
||||
// Wound triangles and cubics both view the same instance buffer as an array of
|
||||
// QuadPointInstance[]. So, reinterpreting the instance data as QuadPointInstance[], we start
|
||||
// them on the first index that will not overwrite previous TriPointInstance data.
|
||||
int quadBaseIdx =
|
||||
GrSizeDivRoundUp(triEndIdx * sizeof(TriPointInstance), sizeof(QuadPointInstance));
|
||||
fBaseInstances[0].fWeightedTriangles = quadBaseIdx;
|
||||
fBaseInstances[1].fWeightedTriangles = fBaseInstances[0].fWeightedTriangles +
|
||||
fTotalPrimitiveCounts[0].fWeightedTriangles;
|
||||
fBaseInstances[0].fCubics = fBaseInstances[1].fWeightedTriangles +
|
||||
fTotalPrimitiveCounts[1].fWeightedTriangles;
|
||||
fBaseInstances[1].fCubics = fBaseInstances[0].fCubics + fTotalPrimitiveCounts[0].fCubics;
|
||||
fBaseInstances[0].fConics = fBaseInstances[1].fCubics + fTotalPrimitiveCounts[1].fCubics;
|
||||
fBaseInstances[1].fConics = fBaseInstances[0].fConics + fTotalPrimitiveCounts[0].fConics;
|
||||
int quadEndIdx = fBaseInstances[1].fConics + fTotalPrimitiveCounts[1].fConics;
|
||||
|
||||
fInstanceBuffer.resetAndMapBuffer(onFlushRP, quadEndIdx * sizeof(QuadPointInstance));
|
||||
if (!fInstanceBuffer.hasGpuBuffer()) {
|
||||
SkDebugf("WARNING: failed to allocate CCPR fill instance buffer.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto triPointInstanceData = reinterpret_cast<TriPointInstance*>(fInstanceBuffer.data());
|
||||
auto quadPointInstanceData = reinterpret_cast<QuadPointInstance*>(fInstanceBuffer.data());
|
||||
SkASSERT(quadPointInstanceData);
|
||||
|
||||
PathInfo* nextPathInfo = fPathInfos.begin();
|
||||
Sk2f devToAtlasOffset;
|
||||
PrimitiveTallies instanceIndices[2] = {fBaseInstances[0], fBaseInstances[1]};
|
||||
PrimitiveTallies* currIndices = nullptr;
|
||||
SkSTArray<256, int32_t, true> currFan;
|
||||
bool currFanIsTessellated = false;
|
||||
|
||||
const SkTArray<SkPoint, true>& pts = fGeometry.points();
|
||||
int ptsIdx = -1;
|
||||
int nextConicWeightIdx = 0;
|
||||
|
||||
// Expand the ccpr verbs into GPU instance buffers.
|
||||
for (Verb verb : fGeometry.verbs()) {
|
||||
switch (verb) {
|
||||
case Verb::kBeginPath:
|
||||
SkASSERT(currFan.empty());
|
||||
currIndices = &instanceIndices[(int)nextPathInfo->scissorTest()];
|
||||
devToAtlasOffset = Sk2f(static_cast<float>(nextPathInfo->devToAtlasOffset().fX),
|
||||
static_cast<float>(nextPathInfo->devToAtlasOffset().fY));
|
||||
currFanIsTessellated = nextPathInfo->hasFanTessellation();
|
||||
if (currFanIsTessellated) {
|
||||
this->emitTessellatedFan(
|
||||
nextPathInfo->fanTessellation(), nextPathInfo->fanTessellationCount(),
|
||||
devToAtlasOffset, triangleOrdering, triPointInstanceData,
|
||||
quadPointInstanceData, currIndices);
|
||||
}
|
||||
++nextPathInfo;
|
||||
continue;
|
||||
|
||||
case Verb::kBeginContour:
|
||||
SkASSERT(currFan.empty());
|
||||
++ptsIdx;
|
||||
if (!currFanIsTessellated) {
|
||||
currFan.push_back(ptsIdx);
|
||||
}
|
||||
continue;
|
||||
|
||||
case Verb::kLineTo:
|
||||
++ptsIdx;
|
||||
if (!currFanIsTessellated) {
|
||||
SkASSERT(!currFan.empty());
|
||||
currFan.push_back(ptsIdx);
|
||||
}
|
||||
continue;
|
||||
|
||||
case Verb::kMonotonicQuadraticTo:
|
||||
triPointInstanceData[currIndices->fQuadratics++].set(
|
||||
&pts[ptsIdx], devToAtlasOffset, TriPointInstance::Ordering::kXYTransposed);
|
||||
ptsIdx += 2;
|
||||
if (!currFanIsTessellated) {
|
||||
SkASSERT(!currFan.empty());
|
||||
currFan.push_back(ptsIdx);
|
||||
}
|
||||
continue;
|
||||
|
||||
case Verb::kMonotonicCubicTo:
|
||||
quadPointInstanceData[currIndices->fCubics++].set(
|
||||
&pts[ptsIdx], devToAtlasOffset[0], devToAtlasOffset[1]);
|
||||
ptsIdx += 3;
|
||||
if (!currFanIsTessellated) {
|
||||
SkASSERT(!currFan.empty());
|
||||
currFan.push_back(ptsIdx);
|
||||
}
|
||||
continue;
|
||||
|
||||
case Verb::kMonotonicConicTo:
|
||||
quadPointInstanceData[currIndices->fConics++].setW(
|
||||
&pts[ptsIdx], devToAtlasOffset,
|
||||
fGeometry.getConicWeight(nextConicWeightIdx));
|
||||
ptsIdx += 2;
|
||||
++nextConicWeightIdx;
|
||||
if (!currFanIsTessellated) {
|
||||
SkASSERT(!currFan.empty());
|
||||
currFan.push_back(ptsIdx);
|
||||
}
|
||||
continue;
|
||||
|
||||
case Verb::kEndClosedContour: // endPt == startPt.
|
||||
if (!currFanIsTessellated) {
|
||||
SkASSERT(!currFan.empty());
|
||||
currFan.pop_back();
|
||||
}
|
||||
[[fallthrough]];
|
||||
case Verb::kEndOpenContour: // endPt != startPt.
|
||||
SkASSERT(!currFanIsTessellated || currFan.empty());
|
||||
if (!currFanIsTessellated && currFan.count() >= 3) {
|
||||
int fanSize = currFan.count();
|
||||
// Reserve space for emit_recursive_fan. Technically this can grow to
|
||||
// fanSize + log3(fanSize), but we approximate with log2.
|
||||
currFan.push_back_n(SkNextLog2(fanSize));
|
||||
SkDEBUGCODE(TriPointInstance* end =) emit_recursive_fan(
|
||||
pts, currFan, 0, fanSize, devToAtlasOffset, triangleOrdering,
|
||||
triPointInstanceData + currIndices->fTriangles);
|
||||
currIndices->fTriangles += fanSize - 2;
|
||||
SkASSERT(triPointInstanceData + currIndices->fTriangles == end);
|
||||
}
|
||||
currFan.reset();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
fInstanceBuffer.unmapBuffer();
|
||||
|
||||
SkASSERT(nextPathInfo == fPathInfos.end());
|
||||
SkASSERT(ptsIdx == pts.count() - 1);
|
||||
SkASSERT(instanceIndices[0].fTriangles == fBaseInstances[1].fTriangles);
|
||||
SkASSERT(instanceIndices[1].fTriangles == fBaseInstances[0].fQuadratics);
|
||||
SkASSERT(instanceIndices[0].fQuadratics == fBaseInstances[1].fQuadratics);
|
||||
SkASSERT(instanceIndices[1].fQuadratics == triEndIdx);
|
||||
SkASSERT(instanceIndices[0].fWeightedTriangles == fBaseInstances[1].fWeightedTriangles);
|
||||
SkASSERT(instanceIndices[1].fWeightedTriangles == fBaseInstances[0].fCubics);
|
||||
SkASSERT(instanceIndices[0].fCubics == fBaseInstances[1].fCubics);
|
||||
SkASSERT(instanceIndices[1].fCubics == fBaseInstances[0].fConics);
|
||||
SkASSERT(instanceIndices[0].fConics == fBaseInstances[1].fConics);
|
||||
SkASSERT(instanceIndices[1].fConics == quadEndIdx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GrCCFiller::drawFills(
|
||||
GrOpFlushState* flushState, GrCCCoverageProcessor* proc, const GrPipeline& pipeline,
|
||||
BatchID batchID, const SkIRect& drawBounds, const GrUserStencilSettings* stencil) const {
|
||||
using PrimitiveType = GrCCCoverageProcessor::PrimitiveType;
|
||||
|
||||
SkASSERT(fInstanceBuffer.hasGpuBuffer());
|
||||
|
||||
GrResourceProvider* rp = flushState->resourceProvider();
|
||||
const PrimitiveTallies& batchTotalCounts = fBatches[batchID].fTotalPrimitiveCounts;
|
||||
|
||||
int numSubpasses = proc->numSubpasses();
|
||||
|
||||
if (batchTotalCounts.fTriangles) {
|
||||
for (int i = 0; i < numSubpasses; ++i) {
|
||||
proc->reset(PrimitiveType::kTriangles, i, rp);
|
||||
this->drawPrimitives(flushState, *proc, pipeline, stencil, batchID,
|
||||
&PrimitiveTallies::fTriangles, drawBounds);
|
||||
}
|
||||
}
|
||||
|
||||
if (batchTotalCounts.fWeightedTriangles) {
|
||||
SkASSERT(Algorithm::kStencilWindingCount != fAlgorithm);
|
||||
for (int i = 0; i < numSubpasses; ++i) {
|
||||
proc->reset(PrimitiveType::kWeightedTriangles, i, rp);
|
||||
this->drawPrimitives(flushState, *proc, pipeline, stencil, batchID,
|
||||
&PrimitiveTallies::fWeightedTriangles, drawBounds);
|
||||
}
|
||||
}
|
||||
|
||||
if (batchTotalCounts.fQuadratics) {
|
||||
for (int i = 0; i < numSubpasses; ++i) {
|
||||
proc->reset(PrimitiveType::kQuadratics, i, rp);
|
||||
this->drawPrimitives(flushState, *proc, pipeline, stencil, batchID,
|
||||
&PrimitiveTallies::fQuadratics, drawBounds);
|
||||
}
|
||||
}
|
||||
|
||||
if (batchTotalCounts.fCubics) {
|
||||
for (int i = 0; i < numSubpasses; ++i) {
|
||||
proc->reset(PrimitiveType::kCubics, i, rp);
|
||||
this->drawPrimitives(flushState, *proc, pipeline, stencil, batchID,
|
||||
&PrimitiveTallies::fCubics, drawBounds);
|
||||
}
|
||||
}
|
||||
|
||||
if (batchTotalCounts.fConics) {
|
||||
for (int i = 0; i < numSubpasses; ++i) {
|
||||
proc->reset(PrimitiveType::kConics, i, rp);
|
||||
this->drawPrimitives(flushState, *proc, pipeline, stencil, batchID,
|
||||
&PrimitiveTallies::fConics, drawBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GrCCFiller::drawPrimitives(
|
||||
GrOpFlushState* flushState, const GrCCCoverageProcessor& proc, const GrPipeline& pipeline,
|
||||
const GrUserStencilSettings* stencil, BatchID batchID, int PrimitiveTallies::*instanceType,
|
||||
const SkIRect& drawBounds) const {
|
||||
SkASSERT(pipeline.isScissorTestEnabled());
|
||||
|
||||
GrOpsRenderPass* renderPass = flushState->opsRenderPass();
|
||||
proc.bindPipeline(flushState, pipeline, SkRect::Make(drawBounds), stencil);
|
||||
proc.bindBuffers(renderPass, fInstanceBuffer.gpuBuffer());
|
||||
|
||||
SkASSERT(batchID > 0);
|
||||
SkASSERT(batchID < fBatches.count());
|
||||
const Batch& previousBatch = fBatches[batchID - 1];
|
||||
const Batch& batch = fBatches[batchID];
|
||||
SkDEBUGCODE(int totalInstanceCount = 0);
|
||||
|
||||
if (int instanceCount = batch.fEndNonScissorIndices.*instanceType -
|
||||
previousBatch.fEndNonScissorIndices.*instanceType) {
|
||||
SkASSERT(instanceCount > 0);
|
||||
int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].*instanceType +
|
||||
previousBatch.fEndNonScissorIndices.*instanceType;
|
||||
renderPass->setScissorRect(SkIRect::MakeXYWH(0, 0, drawBounds.width(),
|
||||
drawBounds.height()));
|
||||
proc.drawInstances(renderPass, instanceCount, baseInstance);
|
||||
SkDEBUGCODE(totalInstanceCount += instanceCount);
|
||||
}
|
||||
|
||||
SkASSERT(previousBatch.fEndScissorSubBatchIdx > 0);
|
||||
SkASSERT(batch.fEndScissorSubBatchIdx <= fScissorSubBatches.count());
|
||||
int baseScissorInstance = fBaseInstances[(int)GrScissorTest::kEnabled].*instanceType;
|
||||
for (int i = previousBatch.fEndScissorSubBatchIdx; i < batch.fEndScissorSubBatchIdx; ++i) {
|
||||
const ScissorSubBatch& previousSubBatch = fScissorSubBatches[i - 1];
|
||||
const ScissorSubBatch& scissorSubBatch = fScissorSubBatches[i];
|
||||
int startIndex = previousSubBatch.fEndPrimitiveIndices.*instanceType;
|
||||
int instanceCount = scissorSubBatch.fEndPrimitiveIndices.*instanceType - startIndex;
|
||||
if (!instanceCount) {
|
||||
continue;
|
||||
}
|
||||
SkASSERT(instanceCount > 0);
|
||||
renderPass->setScissorRect(scissorSubBatch.fScissor);
|
||||
proc.drawInstances(renderPass, instanceCount, baseScissorInstance + startIndex);
|
||||
SkDEBUGCODE(totalInstanceCount += instanceCount);
|
||||
}
|
||||
|
||||
SkASSERT(totalInstanceCount == batch.fTotalPrimitiveCounts.*instanceType);
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef GrCCPathParser_DEFINED
|
||||
#define GrCCPathParser_DEFINED
|
||||
|
||||
#include "include/core/SkRect.h"
|
||||
#include "include/core/SkRefCnt.h"
|
||||
#include "src/core/SkPathPriv.h"
|
||||
#include "src/gpu/GrTriangulator.h"
|
||||
#include "src/gpu/ccpr/GrAutoMapVertexBuffer.h"
|
||||
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
|
||||
#include "src/gpu/ccpr/GrCCFillGeometry.h"
|
||||
#include "src/gpu/ops/GrDrawOp.h"
|
||||
|
||||
class GrOnFlushResourceProvider;
|
||||
class SkMatrix;
|
||||
class SkPath;
|
||||
|
||||
/**
|
||||
* This class parses SkPaths into CCPR primitives in GPU buffers, then issues calls to draw their
|
||||
* coverage counts.
|
||||
*/
|
||||
class GrCCFiller {
|
||||
public:
|
||||
enum class Algorithm : bool {
|
||||
kStencilWindingCount
|
||||
};
|
||||
|
||||
GrCCFiller(Algorithm, int numPaths, int numSkPoints, int numSkVerbs, int numConicWeights);
|
||||
|
||||
// Parses a device-space SkPath into the current batch, using the SkPath's original verbs and
|
||||
// 'deviceSpacePts'. Accepts an optional post-device-space translate for placement in an atlas.
|
||||
void parseDeviceSpaceFill(const SkPath&, const SkPoint* deviceSpacePts, GrScissorTest,
|
||||
const SkIRect& clippedDevIBounds, const SkIVector& devToAtlasOffset);
|
||||
|
||||
using BatchID = int;
|
||||
|
||||
// Compiles the outstanding parsed paths into a batch, and returns an ID that can be used to
|
||||
// draw their fills in the future.
|
||||
BatchID closeCurrentBatch();
|
||||
|
||||
// Builds internal GPU buffers and prepares for calls to drawFills(). Caller must close the
|
||||
// current batch before calling this method, and cannot parse new paths afer.
|
||||
bool prepareToDraw(GrOnFlushResourceProvider*);
|
||||
|
||||
// Called after prepareToDraw(). Draws the given batch of path fills.
|
||||
void drawFills(GrOpFlushState*, GrCCCoverageProcessor*, const GrPipeline&, BatchID,
|
||||
const SkIRect& drawBounds,
|
||||
const GrUserStencilSettings* = &GrUserStencilSettings::kUnused) const;
|
||||
|
||||
private:
|
||||
static constexpr int kNumScissorModes = 2;
|
||||
using PrimitiveTallies = GrCCFillGeometry::PrimitiveTallies;
|
||||
|
||||
// Every kBeginPath verb has a corresponding PathInfo entry.
|
||||
class PathInfo {
|
||||
public:
|
||||
PathInfo(GrScissorTest scissorTest, const SkIVector& devToAtlasOffset)
|
||||
: fScissorTest(scissorTest), fDevToAtlasOffset(devToAtlasOffset) {}
|
||||
|
||||
GrScissorTest scissorTest() const { return fScissorTest; }
|
||||
const SkIVector& devToAtlasOffset() const { return fDevToAtlasOffset; }
|
||||
|
||||
// An empty tessellation fan is also valid; we use negative count to denote not tessellated.
|
||||
bool hasFanTessellation() const { return fFanTessellationCount >= 0; }
|
||||
int fanTessellationCount() const {
|
||||
SkASSERT(this->hasFanTessellation());
|
||||
return fFanTessellationCount;
|
||||
}
|
||||
const GrTriangulator::WindingVertex* fanTessellation() const {
|
||||
SkASSERT(this->hasFanTessellation());
|
||||
return fFanTessellation.get();
|
||||
}
|
||||
void tessellateFan(
|
||||
Algorithm, const SkPath& originalPath, const GrCCFillGeometry&, int verbsIdx,
|
||||
int ptsIdx, const SkIRect& clippedDevIBounds, PrimitiveTallies* newTriangleCounts);
|
||||
|
||||
private:
|
||||
GrScissorTest fScissorTest;
|
||||
SkIVector fDevToAtlasOffset; // Translation from device space to location in atlas.
|
||||
int fFanTessellationCount = -1;
|
||||
std::unique_ptr<const GrTriangulator::WindingVertex[]> fFanTessellation;
|
||||
};
|
||||
|
||||
// Defines a batch of CCPR primitives. Start indices are deduced by looking at the previous
|
||||
// Batch in the list.
|
||||
struct Batch {
|
||||
PrimitiveTallies fEndNonScissorIndices;
|
||||
int fEndScissorSubBatchIdx;
|
||||
PrimitiveTallies fTotalPrimitiveCounts;
|
||||
};
|
||||
|
||||
// Defines a sub-batch that will be drawn with the given scissor rect. Start indices are deduced
|
||||
// by looking at the previous ScissorSubBatch in the list.
|
||||
struct ScissorSubBatch {
|
||||
PrimitiveTallies fEndPrimitiveIndices;
|
||||
SkIRect fScissor;
|
||||
};
|
||||
|
||||
void emitTessellatedFan(
|
||||
const GrTriangulator::WindingVertex*, int numVertices, const Sk2f& devToAtlasOffset,
|
||||
GrCCCoverageProcessor::TriPointInstance::Ordering,
|
||||
GrCCCoverageProcessor::TriPointInstance*, GrCCCoverageProcessor::QuadPointInstance*,
|
||||
GrCCFillGeometry::PrimitiveTallies*);
|
||||
void drawPrimitives(GrOpFlushState*, const GrCCCoverageProcessor&, const GrPipeline&,
|
||||
const GrUserStencilSettings*, BatchID,
|
||||
int PrimitiveTallies::*instanceType, const SkIRect& drawBounds) const;
|
||||
|
||||
const Algorithm fAlgorithm;
|
||||
GrCCFillGeometry fGeometry;
|
||||
SkSTArray<32, PathInfo, true> fPathInfos;
|
||||
SkSTArray<32, Batch, true> fBatches;
|
||||
SkSTArray<32, ScissorSubBatch, true> fScissorSubBatches;
|
||||
PrimitiveTallies fTotalPrimitiveCounts[kNumScissorModes];
|
||||
int fMaxMeshesPerDraw = 0;
|
||||
|
||||
GrAutoMapVertexBuffer fInstanceBuffer;
|
||||
PrimitiveTallies fBaseInstances[kNumScissorModes];
|
||||
};
|
||||
|
||||
#endif
|
@ -8,118 +8,23 @@
|
||||
#include "src/gpu/ccpr/GrCCPerFlushResources.h"
|
||||
|
||||
#include "include/gpu/GrRecordingContext.h"
|
||||
#include "src/gpu/GrFixedClip.h"
|
||||
#include "src/gpu/GrMemoryPool.h"
|
||||
#include "src/gpu/GrOnFlushResourceProvider.h"
|
||||
#include "src/gpu/GrRecordingContextPriv.h"
|
||||
#include "src/gpu/GrSurfaceDrawContext.h"
|
||||
#include "src/gpu/ccpr/GrSampleMaskProcessor.h"
|
||||
#include "src/gpu/geometry/GrStyledShape.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using FillBatchID = GrCCFiller::BatchID;
|
||||
|
||||
namespace {
|
||||
|
||||
// Base class for an Op that renders a CCPR atlas.
|
||||
class AtlasOp : public GrDrawOp {
|
||||
public:
|
||||
FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
|
||||
GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*,
|
||||
bool hasMixedSampledCoverage, GrClampType) override {
|
||||
return GrProcessorSet::EmptySetAnalysis();
|
||||
}
|
||||
CombineResult onCombineIfPossible(GrOp* other, SkArenaAlloc*, const GrCaps&) override {
|
||||
// We will only make multiple copy ops if they have different source proxies.
|
||||
// TODO: make use of texture chaining.
|
||||
return CombineResult::kCannotCombine;
|
||||
}
|
||||
|
||||
protected:
|
||||
AtlasOp(uint32_t classID, sk_sp<const GrCCPerFlushResources> resources,
|
||||
const SkISize& drawBounds)
|
||||
: GrDrawOp(classID)
|
||||
, fResources(std::move(resources)) {
|
||||
this->setBounds(SkRect::MakeIWH(drawBounds.width(), drawBounds.height()),
|
||||
GrOp::HasAABloat::kNo, GrOp::IsHairline::kNo);
|
||||
}
|
||||
|
||||
const sk_sp<const GrCCPerFlushResources> fResources;
|
||||
|
||||
private:
|
||||
void onPrePrepare(GrRecordingContext*,
|
||||
const GrSurfaceProxyView& writeView,
|
||||
GrAppliedClip*,
|
||||
const GrXferProcessor::DstProxyView&,
|
||||
GrXferBarrierFlags renderPassXferBarriers,
|
||||
GrLoadOp colorLoadOp) final {}
|
||||
void onPrepare(GrOpFlushState*) final {}
|
||||
};
|
||||
|
||||
// Renders coverage counts to a CCPR atlas using the resources' pre-filled GrCCPathParser.
|
||||
template<typename ProcessorType> class RenderAtlasOp : public AtlasOp {
|
||||
public:
|
||||
DEFINE_OP_CLASS_ID
|
||||
|
||||
static GrOp::Owner Make(
|
||||
GrRecordingContext* context, sk_sp<const GrCCPerFlushResources> resources,
|
||||
FillBatchID fillBatchID, const SkISize& drawBounds) {
|
||||
return GrOp::Make<RenderAtlasOp>(context,
|
||||
std::move(resources), fillBatchID, drawBounds);
|
||||
}
|
||||
|
||||
// GrDrawOp interface.
|
||||
const char* name() const override { return "RenderAtlasOp (CCPR)"; }
|
||||
|
||||
void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
|
||||
ProcessorType proc;
|
||||
GrPipeline pipeline(GrScissorTest::kEnabled, SkBlendMode::kPlus,
|
||||
flushState->drawOpArgs().writeView().swizzle());
|
||||
fResources->filler().drawFills(flushState, &proc, pipeline, fFillBatchID, fDrawBounds);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ::GrOp; // for ctor
|
||||
|
||||
RenderAtlasOp(sk_sp<const GrCCPerFlushResources> resources, FillBatchID fillBatchID,
|
||||
const SkISize& drawBounds)
|
||||
: AtlasOp(ClassID(), std::move(resources), drawBounds)
|
||||
, fFillBatchID(fillBatchID)
|
||||
, fDrawBounds(SkIRect::MakeWH(drawBounds.width(), drawBounds.height())) {
|
||||
}
|
||||
|
||||
const FillBatchID fFillBatchID;
|
||||
const SkIRect fDrawBounds;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
#include "src/gpu/ops/GrFillRectOp.h"
|
||||
|
||||
GrCCPerFlushResources::GrCCPerFlushResources(GrOnFlushResourceProvider* onFlushRP,
|
||||
const GrCCPerFlushResourceSpecs& specs)
|
||||
: fFiller(GrCCFiller::Algorithm::kStencilWindingCount,
|
||||
specs.fNumClipPaths,
|
||||
specs.fRenderedPathStats.fNumTotalSkPoints,
|
||||
specs.fRenderedPathStats.fNumTotalSkVerbs,
|
||||
specs.fRenderedPathStats.fNumTotalConicWeights)
|
||||
, fRenderedAtlasStack(specs.fRenderedAtlasSpecs, onFlushRP->caps()) {
|
||||
int numRenderedPaths = specs.fNumClipPaths;
|
||||
fStencilResolveBuffer.resetAndMapBuffer(
|
||||
onFlushRP, numRenderedPaths * sizeof(GrStencilAtlasOp::ResolveRectInstance));
|
||||
if (!fStencilResolveBuffer.hasGpuBuffer()) {
|
||||
SkDebugf("WARNING: failed to allocate CCPR stencil resolve buffer. "
|
||||
"No paths will be drawn.\n");
|
||||
return;
|
||||
}
|
||||
SkDEBUGCODE(fEndStencilResolveInstance = numRenderedPaths);
|
||||
const GrCCAtlas::Specs& specs)
|
||||
: fRenderedAtlasStack(specs, onFlushRP->caps()) {
|
||||
}
|
||||
|
||||
const GrCCAtlas* GrCCPerFlushResources::renderDeviceSpacePathInAtlas(
|
||||
const SkIRect& clipIBounds, const SkPath& devPath, const SkIRect& devPathIBounds,
|
||||
GrFillRule fillRule, SkIVector* devToAtlasOffset) {
|
||||
SkASSERT(this->isMapped());
|
||||
|
||||
GrOnFlushResourceProvider* onFlushRP, const SkIRect& clipIBounds, const SkPath& devPath,
|
||||
const SkIRect& devPathIBounds, GrFillRule fillRule, SkIVector* devToAtlasOffset) {
|
||||
if (devPath.isEmpty()) {
|
||||
SkDEBUGCODE(--fEndStencilResolveInstance);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -132,87 +37,155 @@ const GrCCAtlas* GrCCPerFlushResources::renderDeviceSpacePathInAtlas(
|
||||
enableScissorInAtlas = GrScissorTest::kEnabled;
|
||||
} else {
|
||||
// The clip and path bounds do not intersect. Draw nothing.
|
||||
SkDEBUGCODE(--fEndStencilResolveInstance);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
this->placeRenderedPathInAtlas(clippedPathIBounds, enableScissorInAtlas, devToAtlasOffset);
|
||||
fFiller.parseDeviceSpaceFill(devPath, SkPathPriv::PointData(devPath), enableScissorInAtlas,
|
||||
clippedPathIBounds, *devToAtlasOffset);
|
||||
this->placeRenderedPathInAtlas(onFlushRP, clippedPathIBounds, enableScissorInAtlas,
|
||||
devToAtlasOffset);
|
||||
|
||||
// In MSAA mode we also record an internal draw instance that will be used to resolve stencil
|
||||
// winding values to coverage when the atlas is generated.
|
||||
this->recordStencilResolveInstance(clippedPathIBounds, *devToAtlasOffset, fillRule);
|
||||
SkMatrix atlasMatrix = SkMatrix::Translate(devToAtlasOffset->fX, devToAtlasOffset->fY);
|
||||
this->enqueueRenderedPath(devPath, fillRule, clippedPathIBounds, atlasMatrix,
|
||||
enableScissorInAtlas, *devToAtlasOffset);
|
||||
|
||||
return &fRenderedAtlasStack.current();
|
||||
}
|
||||
|
||||
void GrCCPerFlushResources::placeRenderedPathInAtlas(
|
||||
const SkIRect& clippedPathIBounds, GrScissorTest scissorTest, SkIVector* devToAtlasOffset) {
|
||||
GrOnFlushResourceProvider* onFlushRP, const SkIRect& clippedPathIBounds,
|
||||
GrScissorTest scissorTest, SkIVector* devToAtlasOffset) {
|
||||
if (GrCCAtlas* retiredAtlas =
|
||||
fRenderedAtlasStack.addRect(clippedPathIBounds, devToAtlasOffset)) {
|
||||
// We did not fit in the previous coverage count atlas and it was retired. Close the path
|
||||
// parser's current batch (which does not yet include the path we just parsed). We will
|
||||
// render this batch into the retired atlas during finalize().
|
||||
retiredAtlas->setFillBatchID(fFiller.closeCurrentBatch());
|
||||
retiredAtlas->setEndStencilResolveInstance(fNextStencilResolveInstanceIdx);
|
||||
// We did not fit in the previous coverage count atlas and it was retired. Render the
|
||||
// retired atlas.
|
||||
this->flushRenderedPaths(onFlushRP, retiredAtlas);
|
||||
}
|
||||
}
|
||||
|
||||
void GrCCPerFlushResources::recordStencilResolveInstance(
|
||||
const SkIRect& clippedPathIBounds, const SkIVector& devToAtlasOffset, GrFillRule fillRule) {
|
||||
SkASSERT(fNextStencilResolveInstanceIdx < fEndStencilResolveInstance);
|
||||
|
||||
SkIRect atlasIBounds = clippedPathIBounds.makeOffset(devToAtlasOffset);
|
||||
if (GrFillRule::kEvenOdd == fillRule) {
|
||||
// Make even/odd fills counterclockwise. The resolve draw uses two-sided stencil, with
|
||||
// "nonzero" settings in front and "even/odd" settings in back.
|
||||
std::swap(atlasIBounds.fLeft, atlasIBounds.fRight);
|
||||
void GrCCPerFlushResources::enqueueRenderedPath(const SkPath& path, GrFillRule fillRule,
|
||||
const SkIRect& clippedDevIBounds,
|
||||
const SkMatrix& pathToAtlasMatrix,
|
||||
GrScissorTest enableScissorInAtlas,
|
||||
SkIVector devToAtlasOffset) {
|
||||
SkPath* atlasPath;
|
||||
if (enableScissorInAtlas == GrScissorTest::kDisabled) {
|
||||
atlasPath = &fAtlasPaths[(int)fillRule].fUberPath;
|
||||
} else {
|
||||
auto& [scissoredPath, scissor] = fAtlasPaths[(int)fillRule].fScissoredPaths.push_back();
|
||||
scissor = clippedDevIBounds.makeOffset(devToAtlasOffset);
|
||||
atlasPath = &scissoredPath;
|
||||
}
|
||||
auto origin = clippedDevIBounds.topLeft() + devToAtlasOffset;
|
||||
atlasPath->moveTo(origin.fX, origin.fY); // Implicit moveTo(0,0).
|
||||
atlasPath->addPath(path, pathToAtlasMatrix);
|
||||
}
|
||||
|
||||
static void draw_stencil_to_coverage(GrOnFlushResourceProvider* onFlushRP,
|
||||
GrSurfaceDrawContext* surfaceDrawContext, SkRect&& rect) {
|
||||
auto aaType = GrAAType::kMSAA;
|
||||
auto fillRectFlags = GrSimpleMeshDrawOpHelper::InputFlags::kNone;
|
||||
|
||||
// This will be the final op in the surfaceDrawContext. So if Ganesh is planning to discard the
|
||||
// stencil values anyway, then we might not actually need to reset the stencil values back to 0.
|
||||
bool mustResetStencil = !onFlushRP->caps()->discardStencilValuesAfterRenderPass();
|
||||
|
||||
if (surfaceDrawContext->numSamples() == 1) {
|
||||
// We are mixed sampled. We need to either enable conservative raster (preferred) or disable
|
||||
// MSAA in order to avoid double blend artifacts. (Even if we disable MSAA for the cover
|
||||
// geometry, the stencil test is still multisampled and will still produce smooth results.)
|
||||
if (onFlushRP->caps()->conservativeRasterSupport()) {
|
||||
fillRectFlags |= GrSimpleMeshDrawOpHelper::InputFlags::kConservativeRaster;
|
||||
} else {
|
||||
aaType = GrAAType::kNone;
|
||||
}
|
||||
mustResetStencil = true;
|
||||
}
|
||||
|
||||
const GrUserStencilSettings* stencil;
|
||||
if (mustResetStencil) {
|
||||
constexpr static GrUserStencilSettings kTestAndResetStencil(
|
||||
GrUserStencilSettings::StaticInit<
|
||||
0x0000,
|
||||
GrUserStencilTest::kNotEqual,
|
||||
0xffff,
|
||||
GrUserStencilOp::kZero,
|
||||
GrUserStencilOp::kKeep,
|
||||
0xffff>());
|
||||
|
||||
// Outset the cover rect in case there are T-junctions in the path bounds.
|
||||
rect.outset(1, 1);
|
||||
stencil = &kTestAndResetStencil;
|
||||
} else {
|
||||
constexpr static GrUserStencilSettings kTestStencil(
|
||||
GrUserStencilSettings::StaticInit<
|
||||
0x0000,
|
||||
GrUserStencilTest::kNotEqual,
|
||||
0xffff,
|
||||
GrUserStencilOp::kKeep,
|
||||
GrUserStencilOp::kKeep,
|
||||
0xffff>());
|
||||
|
||||
stencil = &kTestStencil;
|
||||
}
|
||||
|
||||
GrPaint paint;
|
||||
paint.setColor4f(SK_PMColor4fWHITE);
|
||||
GrQuad coverQuad(rect);
|
||||
DrawQuad drawQuad{coverQuad, coverQuad, GrQuadAAFlags::kAll};
|
||||
auto coverOp = GrFillRectOp::Make(surfaceDrawContext->recordingContext(), std::move(paint),
|
||||
aaType, &drawQuad, stencil, fillRectFlags);
|
||||
surfaceDrawContext->addDrawOp(nullptr, std::move(coverOp));
|
||||
}
|
||||
|
||||
void GrCCPerFlushResources::flushRenderedPaths(GrOnFlushResourceProvider* onFlushRP,
|
||||
GrCCAtlas* atlas) {
|
||||
auto surfaceDrawContext = atlas->instantiate(onFlushRP);
|
||||
if (!surfaceDrawContext) {
|
||||
for (int i = 0; i < (int)SK_ARRAY_COUNT(fAtlasPaths); ++i) {
|
||||
fAtlasPaths[i].fUberPath.reset();
|
||||
fAtlasPaths[i].fScissoredPaths.reset();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)SK_ARRAY_COUNT(fAtlasPaths); ++i) {
|
||||
SkPathFillType fillType = (i == (int)GrFillRule::kNonzero) ? SkPathFillType::kWinding
|
||||
: SkPathFillType::kEvenOdd;
|
||||
SkPath& uberPath = fAtlasPaths[i].fUberPath;
|
||||
if (!uberPath.isEmpty()) {
|
||||
uberPath.setIsVolatile(true);
|
||||
uberPath.setFillType(fillType);
|
||||
surfaceDrawContext->stencilPath(nullptr, GrAA::kYes, SkMatrix::I(), uberPath);
|
||||
uberPath.reset();
|
||||
}
|
||||
for (auto& [scissoredPath, scissor] : fAtlasPaths[i].fScissoredPaths) {
|
||||
GrFixedClip fixedClip(
|
||||
surfaceDrawContext->asRenderTargetProxy()->backingStoreDimensions(), scissor);
|
||||
scissoredPath.setIsVolatile(true);
|
||||
scissoredPath.setFillType(fillType);
|
||||
surfaceDrawContext->stencilPath(&fixedClip, GrAA::kYes, SkMatrix::I(), scissoredPath);
|
||||
}
|
||||
fAtlasPaths[i].fScissoredPaths.reset();
|
||||
}
|
||||
|
||||
draw_stencil_to_coverage(onFlushRP, surfaceDrawContext.get(),
|
||||
SkRect::MakeSize(SkSize::Make(atlas->drawBounds())));
|
||||
|
||||
if (surfaceDrawContext->asSurfaceProxy()->requiresManualMSAAResolve()) {
|
||||
onFlushRP->addTextureResolveTask(sk_ref_sp(surfaceDrawContext->asTextureProxy()),
|
||||
GrSurfaceProxy::ResolveFlags::kMSAA);
|
||||
}
|
||||
fStencilResolveBuffer[fNextStencilResolveInstanceIdx++] = {
|
||||
(int16_t)atlasIBounds.left(), (int16_t)atlasIBounds.top(),
|
||||
(int16_t)atlasIBounds.right(), (int16_t)atlasIBounds.bottom()};
|
||||
}
|
||||
|
||||
bool GrCCPerFlushResources::finalize(GrOnFlushResourceProvider* onFlushRP) {
|
||||
SkASSERT(this->isMapped());
|
||||
SkASSERT(fNextStencilResolveInstanceIdx == fEndStencilResolveInstance);
|
||||
|
||||
if (fStencilResolveBuffer.hasGpuBuffer()) {
|
||||
fStencilResolveBuffer.unmapBuffer();
|
||||
}
|
||||
|
||||
if (!fRenderedAtlasStack.empty()) {
|
||||
fRenderedAtlasStack.current().setFillBatchID(fFiller.closeCurrentBatch());
|
||||
fRenderedAtlasStack.current().setEndStencilResolveInstance(fNextStencilResolveInstanceIdx);
|
||||
this->flushRenderedPaths(onFlushRP, &fRenderedAtlasStack.current());
|
||||
}
|
||||
|
||||
// Build the GPU buffers to render path coverage counts. (This must not happen until after the
|
||||
// final calls to fFiller/fStroker.closeCurrentBatch().)
|
||||
if (!fFiller.prepareToDraw(onFlushRP)) {
|
||||
return false;
|
||||
#ifdef SK_DEBUG
|
||||
// These paths should have been rendered and reset to empty by this point.
|
||||
for (const auto& [uberPath, scissoredPaths] : fAtlasPaths) {
|
||||
SkASSERT(uberPath.isEmpty());
|
||||
SkASSERT(scissoredPaths.empty());
|
||||
}
|
||||
|
||||
// Render the coverage count atlas(es).
|
||||
int baseStencilResolveInstance = 0;
|
||||
for (GrCCAtlas& atlas : fRenderedAtlasStack.atlases()) {
|
||||
if (auto rtc = atlas.instantiate(onFlushRP)) {
|
||||
GrOp::Owner op;
|
||||
op = GrStencilAtlasOp::Make(
|
||||
rtc->recordingContext(), sk_ref_sp(this), atlas.getFillBatchID(),
|
||||
baseStencilResolveInstance,
|
||||
atlas.getEndStencilResolveInstance(), atlas.drawBounds());
|
||||
rtc->addDrawOp(nullptr, std::move(op));
|
||||
if (rtc->asSurfaceProxy()->requiresManualMSAAResolve()) {
|
||||
onFlushRP->addTextureResolveTask(sk_ref_sp(rtc->asTextureProxy()),
|
||||
GrSurfaceProxy::ResolveFlags::kMSAA);
|
||||
}
|
||||
}
|
||||
|
||||
SkASSERT(atlas.getEndStencilResolveInstance() >= baseStencilResolveInstance);
|
||||
baseStencilResolveInstance = atlas.getEndStencilResolveInstance();
|
||||
}
|
||||
SkASSERT(baseStencilResolveInstance == fEndStencilResolveInstance);
|
||||
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
@ -9,42 +9,9 @@
|
||||
#define GrCCPerFlushResources_DEFINED
|
||||
|
||||
#include "src/gpu/GrNonAtomicRef.h"
|
||||
#include "src/gpu/ccpr/GrAutoMapVertexBuffer.h"
|
||||
#include "src/gpu/ccpr/GrCCAtlas.h"
|
||||
#include "src/gpu/ccpr/GrCCFiller.h"
|
||||
#include "src/gpu/ccpr/GrStencilAtlasOp.h"
|
||||
|
||||
class GrCCPathCache;
|
||||
class GrCCPathCacheEntry;
|
||||
class GrOctoBounds;
|
||||
class GrOnFlushResourceProvider;
|
||||
class GrStyledShape;
|
||||
|
||||
/**
|
||||
* This struct counts values that help us preallocate buffers for rendered path geometry.
|
||||
*/
|
||||
struct GrCCRenderedPathStats {
|
||||
int fMaxPointsPerPath = 0;
|
||||
int fNumTotalSkPoints = 0;
|
||||
int fNumTotalSkVerbs = 0;
|
||||
int fNumTotalConicWeights = 0;
|
||||
|
||||
void statPath(const SkPath&);
|
||||
};
|
||||
|
||||
/**
|
||||
* This struct encapsulates the minimum and desired requirements for the GPU resources required by
|
||||
* CCPR in a given flush.
|
||||
*/
|
||||
struct GrCCPerFlushResourceSpecs {
|
||||
int fNumClipPaths = 0;
|
||||
GrCCRenderedPathStats fRenderedPathStats;
|
||||
GrCCAtlas::Specs fRenderedAtlasSpecs;
|
||||
|
||||
bool isEmpty() const {
|
||||
return 0 == fNumClipPaths;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This class wraps all the GPU resources that CCPR builds at flush time. It is allocated in CCPR's
|
||||
@ -53,57 +20,39 @@ struct GrCCPerFlushResourceSpecs {
|
||||
*/
|
||||
class GrCCPerFlushResources : public GrNonAtomicRef<GrCCPerFlushResources> {
|
||||
public:
|
||||
GrCCPerFlushResources(
|
||||
GrOnFlushResourceProvider*,const GrCCPerFlushResourceSpecs&);
|
||||
|
||||
bool isMapped() const { return fStencilResolveBuffer.isMapped(); }
|
||||
GrCCPerFlushResources(GrOnFlushResourceProvider*, const GrCCAtlas::Specs&);
|
||||
|
||||
// Renders a path into an atlas.
|
||||
const GrCCAtlas* renderDeviceSpacePathInAtlas(
|
||||
const SkIRect& clipIBounds, const SkPath& devPath, const SkIRect& devPathIBounds,
|
||||
GrFillRule fillRule, SkIVector* devToAtlasOffset);
|
||||
GrOnFlushResourceProvider*, const SkIRect& clipIBounds, const SkPath& devPath,
|
||||
const SkIRect& devPathIBounds, GrFillRule fillRule, SkIVector* devToAtlasOffset);
|
||||
|
||||
// Finishes off the GPU buffers and renders the atlas(es).
|
||||
bool finalize(GrOnFlushResourceProvider*);
|
||||
|
||||
// Accessors used by draw calls, once the resources have been finalized.
|
||||
const GrCCFiller& filler() const { SkASSERT(!this->isMapped()); return fFiller; }
|
||||
sk_sp<const GrGpuBuffer> stencilResolveBuffer() const {
|
||||
SkASSERT(!this->isMapped());
|
||||
return fStencilResolveBuffer.gpuBuffer();
|
||||
}
|
||||
|
||||
private:
|
||||
void placeRenderedPathInAtlas(
|
||||
const SkIRect& clippedPathIBounds, GrScissorTest, SkIVector* devToAtlasOffset);
|
||||
GrOnFlushResourceProvider*, const SkIRect& clippedPathIBounds, GrScissorTest,
|
||||
SkIVector* devToAtlasOffset);
|
||||
|
||||
// In MSAA mode we record an additional instance per path that draws a rectangle on top of its
|
||||
// corresponding path in the atlas and resolves stencil winding values to coverage.
|
||||
void recordStencilResolveInstance(
|
||||
const SkIRect& clippedPathIBounds, const SkIVector& devToAtlasOffset, GrFillRule);
|
||||
// Enqueues the given path to be rendered during the next call to flushRenderedPaths().
|
||||
void enqueueRenderedPath(const SkPath&, GrFillRule, const SkIRect& clippedDevIBounds,
|
||||
const SkMatrix& pathToAtlasMatrix, GrScissorTest enableScissorInAtlas,
|
||||
SkIVector devToAtlasOffset);
|
||||
|
||||
// Renders all enqueued paths into the given atlas and clears our path queue.
|
||||
void flushRenderedPaths(GrOnFlushResourceProvider*, GrCCAtlas*);
|
||||
|
||||
GrCCFiller fFiller;
|
||||
GrCCAtlasStack fRenderedAtlasStack;
|
||||
|
||||
// Used in MSAA mode make an intermediate draw that resolves stencil winding values to coverage.
|
||||
GrTAutoMapVertexBuffer<GrStencilAtlasOp::ResolveRectInstance> fStencilResolveBuffer;
|
||||
int fNextStencilResolveInstanceIdx = 0;
|
||||
SkDEBUGCODE(int fEndStencilResolveInstance);
|
||||
|
||||
public:
|
||||
#ifdef SK_DEBUG
|
||||
void debugOnly_didReuseRenderedPath() {
|
||||
--fEndStencilResolveInstance;
|
||||
}
|
||||
#endif
|
||||
const GrTexture* testingOnly_frontRenderedAtlasTexture() const;
|
||||
// Paths to be rendered in the atlas we are currently building.
|
||||
struct AtlasPaths {
|
||||
SkPath fUberPath; // Contains all contours from all non-scissored paths.
|
||||
SkSTArray<32, std::tuple<SkPath, SkIRect>> fScissoredPaths;
|
||||
};
|
||||
static_assert((int)GrFillRule::kNonzero == 0);
|
||||
static_assert((int)GrFillRule::kEvenOdd == 1);
|
||||
AtlasPaths fAtlasPaths[2]; // One for "nonzero" fill rule and one for "even-odd".
|
||||
};
|
||||
|
||||
inline void GrCCRenderedPathStats::statPath(const SkPath& path) {
|
||||
fMaxPointsPerPath = std::max(fMaxPointsPerPath, path.countPoints());
|
||||
fNumTotalSkPoints += path.countPoints();
|
||||
fNumTotalSkVerbs += path.countVerbs();
|
||||
fNumTotalConicWeights += SkPathPriv::ConicWeightCnt(path);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -23,8 +23,6 @@ class GrCCPerFlushResources;
|
||||
// DDL TODO: given the usage pattern in DDL mode, this could probably be non-atomic refcounting.
|
||||
struct GrCCPerOpsTaskPaths : public SkRefCnt {
|
||||
std::map<uint32_t, GrCCClipPath> fClipPaths;
|
||||
SkSTArenaAlloc<10 * 1024> fAllocator{10 * 1024 * 2};
|
||||
sk_sp<const GrCCPerFlushResources> fFlushResources;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "src/gpu/ccpr/GrCCQuadraticShader.h"
|
||||
|
||||
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
|
||||
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
|
||||
|
||||
void GrCCQuadraticShader::emitSetupCode(
|
||||
GrGLSLVertexGeoBuilder* s, const char* pts, const char** outHull4) const {
|
||||
s->declareGlobal(fQCoordMatrix);
|
||||
s->codeAppendf("%s = float2x2(1, 1, .5, 0) * inverse(float2x2(%s[2] - %s[0], %s[1] - %s[0]));",
|
||||
fQCoordMatrix.c_str(), pts, pts, pts, pts);
|
||||
|
||||
s->declareGlobal(fQCoord0);
|
||||
s->codeAppendf("%s = %s[0];", fQCoord0.c_str(), pts);
|
||||
|
||||
if (outHull4) {
|
||||
// Clip the bezier triangle by the tangent line at maximum height. Quadratics have the nice
|
||||
// property that maximum height always occurs at T=.5. This is a simple application for
|
||||
// De Casteljau's algorithm.
|
||||
s->codeAppend ("float2 quadratic_hull[4];");
|
||||
s->codeAppendf("quadratic_hull[0] = %s[0];", pts);
|
||||
s->codeAppendf("quadratic_hull[1] = (%s[0] + %s[1]) * .5;", pts, pts);
|
||||
s->codeAppendf("quadratic_hull[2] = (%s[1] + %s[2]) * .5;", pts, pts);
|
||||
s->codeAppendf("quadratic_hull[3] = %s[2];", pts);
|
||||
*outHull4 = "quadratic_hull";
|
||||
}
|
||||
}
|
||||
|
||||
void GrCCQuadraticShader::onEmitVaryings(
|
||||
GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope, SkString* code,
|
||||
const char* position, const char* coverage, const char* cornerCoverage, const char* wind) {
|
||||
fCoord_fGrad.reset(kFloat4_GrSLType, scope);
|
||||
varyingHandler->addVarying("coord_and_grad", &fCoord_fGrad);
|
||||
code->appendf("%s.xy = %s * (%s - %s);", // Quadratic coords.
|
||||
OutName(fCoord_fGrad), fQCoordMatrix.c_str(), position, fQCoord0.c_str());
|
||||
code->appendf("%s.zw = 2*bloat * float2(2 * %s.x, -1) * %s;", // Gradient.
|
||||
OutName(fCoord_fGrad), OutName(fCoord_fGrad), fQCoordMatrix.c_str());
|
||||
}
|
||||
|
||||
void GrCCQuadraticShader::emitSampleMaskCode(GrGLSLFPFragmentBuilder* f) const {
|
||||
f->codeAppendf("float x = %s.x, y = %s.y;", fCoord_fGrad.fsIn(), fCoord_fGrad.fsIn());
|
||||
f->codeAppendf("float f = x*x - y;");
|
||||
f->codeAppendf("float2 grad = %s.zw;", fCoord_fGrad.fsIn());
|
||||
f->applyFnToMultisampleMask("f", "grad", GrGLSLFPFragmentBuilder::ScopeFlags::kTopLevel);
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef GrCCQuadraticShader_DEFINED
|
||||
#define GrCCQuadraticShader_DEFINED
|
||||
|
||||
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
|
||||
|
||||
/**
|
||||
* This class renders the coverage of closed quadratic curves using the techniques outlined in
|
||||
* "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and
|
||||
* Jim Blinn:
|
||||
*
|
||||
* https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
|
||||
*
|
||||
* The provided curves must be monotonic with respect to the vector of their closing edge [P2 - P0].
|
||||
* (Use GrCCGeometry::quadraticTo().)
|
||||
*/
|
||||
class GrCCQuadraticShader : public GrCCCoverageProcessor::Shader {
|
||||
public:
|
||||
void emitSetupCode(
|
||||
GrGLSLVertexGeoBuilder*, const char* pts, const char** outHull4) const override;
|
||||
|
||||
void onEmitVaryings(
|
||||
GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code, const char* position,
|
||||
const char* coverage, const char* cornerCoverage, const char* wind) override;
|
||||
|
||||
void emitSampleMaskCode(GrGLSLFPFragmentBuilder*) const override;
|
||||
|
||||
private:
|
||||
const GrShaderVar fQCoordMatrix{"qcoord_matrix", kFloat2x2_GrSLType};
|
||||
const GrShaderVar fQCoord0{"qcoord0", kFloat2_GrSLType};
|
||||
GrGLSLVarying fCoord_fGrad;
|
||||
GrGLSLVarying fEdge_fWind_fCorner;
|
||||
};
|
||||
|
||||
#endif
|
@ -101,10 +101,10 @@ void GrCoverageCountingPathRenderer::preFlush(
|
||||
return; // Nothing to draw.
|
||||
}
|
||||
|
||||
GrCCPerFlushResourceSpecs specs;
|
||||
GrCCAtlas::Specs specs;
|
||||
int maxPreferredRTSize = onFlushRP->caps()->maxPreferredRenderTargetSize();
|
||||
specs.fRenderedAtlasSpecs.fMaxPreferredTextureSize = maxPreferredRTSize;
|
||||
specs.fRenderedAtlasSpecs.fMinTextureSize = std::min(512, maxPreferredRTSize);
|
||||
specs.fMaxPreferredTextureSize = maxPreferredRTSize;
|
||||
specs.fMinTextureSize = std::min(512, maxPreferredRTSize);
|
||||
|
||||
// Move the per-opsTask paths that are about to be flushed from fPendingPaths to fFlushingPaths,
|
||||
// and count them up so we can preallocate buffers.
|
||||
@ -123,45 +123,26 @@ void GrCoverageCountingPathRenderer::preFlush(
|
||||
}
|
||||
}
|
||||
|
||||
if (specs.isEmpty()) {
|
||||
return; // Nothing to draw.
|
||||
}
|
||||
fPerFlushResources = std::make_unique<GrCCPerFlushResources>(onFlushRP, specs);
|
||||
|
||||
auto resources = sk_make_sp<GrCCPerFlushResources>(onFlushRP, specs);
|
||||
if (!resources->isMapped()) {
|
||||
return; // Some allocation failed.
|
||||
}
|
||||
|
||||
// Layout the atlas(es) and parse paths.
|
||||
// Layout the atlas(es) and render paths.
|
||||
for (const auto& flushingPaths : fFlushingPaths) {
|
||||
for (auto& clipsIter : flushingPaths->fClipPaths) {
|
||||
clipsIter.second.renderPathInAtlas(resources.get(), onFlushRP);
|
||||
clipsIter.second.renderPathInAtlas(fPerFlushResources.get(), onFlushRP);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate resources and then render the atlas(es).
|
||||
if (!resources->finalize(onFlushRP)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Commit flushing paths to the resources once they are successfully completed.
|
||||
for (auto& flushingPaths : fFlushingPaths) {
|
||||
SkASSERT(!flushingPaths->fFlushResources);
|
||||
flushingPaths->fFlushResources = resources;
|
||||
}
|
||||
fPerFlushResources->finalize(onFlushRP);
|
||||
}
|
||||
|
||||
void GrCoverageCountingPathRenderer::postFlush(GrDeferredUploadToken,
|
||||
SkSpan<const uint32_t> /* taskIDs */) {
|
||||
SkASSERT(fFlushing);
|
||||
|
||||
if (!fFlushingPaths.empty()) {
|
||||
// In DDL mode these aren't guaranteed to be deleted so we must clear out the perFlush
|
||||
// resources manually.
|
||||
for (auto& flushingPaths : fFlushingPaths) {
|
||||
flushingPaths->fFlushResources = nullptr;
|
||||
}
|
||||
fPerFlushResources.reset();
|
||||
|
||||
if (!fFlushingPaths.empty()) {
|
||||
// We wait to erase these until after flush, once Ops and FPs are done accessing their data.
|
||||
fFlushingPaths.reset();
|
||||
}
|
||||
|
@ -83,10 +83,9 @@ private:
|
||||
// (It will only contain elements when fFlushing is true.)
|
||||
SkSTArray<4, sk_sp<GrCCPerOpsTaskPaths>> fFlushingPaths;
|
||||
|
||||
SkDEBUGCODE(bool fFlushing = false);
|
||||
std::unique_ptr<GrCCPerFlushResources> fPerFlushResources;
|
||||
|
||||
public:
|
||||
const GrCCPerFlushResources* testingOnly_getCurrentFlushResources();
|
||||
SkDEBUGCODE(bool fFlushing = false);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -1,151 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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/ccpr/GrSampleMaskProcessor.h"
|
||||
|
||||
#include "src/gpu/GrOpsRenderPass.h"
|
||||
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
|
||||
|
||||
class GrSampleMaskProcessor::Impl : public GrGLSLGeometryProcessor {
|
||||
public:
|
||||
Impl(std::unique_ptr<Shader> shader) : fShader(std::move(shader)) {}
|
||||
|
||||
private:
|
||||
void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&) override {}
|
||||
|
||||
void onEmitCode(EmitArgs&, GrGPArgs*) override;
|
||||
|
||||
const std::unique_ptr<Shader> fShader;
|
||||
};
|
||||
|
||||
void GrSampleMaskProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
|
||||
const GrSampleMaskProcessor& proc = args.fGP.cast<GrSampleMaskProcessor>();
|
||||
GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
|
||||
GrGLSLVertexBuilder* v = args.fVertBuilder;
|
||||
int numInputPoints = proc.numInputPoints();
|
||||
int inputWidth = (4 == numInputPoints || proc.hasInputWeight()) ? 4 : 3;
|
||||
|
||||
varyingHandler->emitAttributes(proc);
|
||||
SkASSERT(!*args.fFPCoordTransformHandler);
|
||||
|
||||
if (PrimitiveType::kTriangles == proc.fPrimitiveType) {
|
||||
SkASSERT(!proc.hasInstanceAttributes()); // Triangles are drawn with vertex arrays.
|
||||
gpArgs->fPositionVar = proc.fInputAttribs.front().asShaderVar();
|
||||
} else {
|
||||
SkASSERT(!proc.hasVertexAttributes()); // Curves are drawn with instanced rendering.
|
||||
|
||||
// Shaders expect a global "bloat" variable when calculating gradients.
|
||||
v->defineConstant("half", "bloat", ".5");
|
||||
|
||||
const char* swizzle = (4 == numInputPoints || proc.hasInputWeight()) ? "xyzw" : "xyz";
|
||||
v->codeAppendf("float%ix2 pts = transpose(float2x%i(X.%s, Y.%s));",
|
||||
inputWidth, inputWidth, swizzle, swizzle);
|
||||
|
||||
const char* hullPts = "pts";
|
||||
fShader->emitSetupCode(v, "pts", &hullPts);
|
||||
v->codeAppendf("float2 vertexpos = %s[sk_VertexID ^ (sk_VertexID >> 1)];", hullPts);
|
||||
gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
|
||||
|
||||
fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kVertToFrag,
|
||||
&AccessCodeString(v), "vertexpos", nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
// Fragment shader.
|
||||
fShader->emitSampleMaskCode(args.fFragBuilder);
|
||||
}
|
||||
|
||||
void GrSampleMaskProcessor::reset(PrimitiveType primitiveType, int subpassIdx,
|
||||
GrResourceProvider* rp) {
|
||||
SkASSERT(subpassIdx == 0);
|
||||
fPrimitiveType = primitiveType; // This will affect the return values for numInputPoints, etc.
|
||||
SkASSERT(PrimitiveType::kWeightedTriangles != fPrimitiveType);
|
||||
|
||||
this->resetCustomFeatures();
|
||||
fInputAttribs.reset();
|
||||
|
||||
switch (fPrimitiveType) {
|
||||
case PrimitiveType::kTriangles:
|
||||
case PrimitiveType::kWeightedTriangles:
|
||||
fInputAttribs.emplace_back("point", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
|
||||
this->setVertexAttributes(fInputAttribs.begin(), 1);
|
||||
this->setInstanceAttributes(nullptr, 0);
|
||||
break;
|
||||
case PrimitiveType::kQuadratics:
|
||||
case PrimitiveType::kCubics:
|
||||
case PrimitiveType::kConics: {
|
||||
auto instanceAttribType = (PrimitiveType::kQuadratics == fPrimitiveType)
|
||||
? kFloat3_GrVertexAttribType : kFloat4_GrVertexAttribType;
|
||||
auto shaderVarType = (PrimitiveType::kQuadratics == fPrimitiveType)
|
||||
? kFloat3_GrSLType : kFloat4_GrSLType;
|
||||
fInputAttribs.emplace_back("X", instanceAttribType, shaderVarType);
|
||||
fInputAttribs.emplace_back("Y", instanceAttribType, shaderVarType);
|
||||
this->setVertexAttributes(nullptr, 0);
|
||||
this->setInstanceAttributes(fInputAttribs.begin(), fInputAttribs.count());
|
||||
this->setWillUseCustomFeature(CustomFeatures::kSampleLocations);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GrPrimitiveType GrSampleMaskProcessor::primType() const {
|
||||
SkASSERT(PrimitiveType::kWeightedTriangles != fPrimitiveType);
|
||||
|
||||
switch (fPrimitiveType) {
|
||||
case PrimitiveType::kTriangles:
|
||||
case PrimitiveType::kWeightedTriangles:
|
||||
return GrPrimitiveType::kTriangles;
|
||||
case PrimitiveType::kQuadratics:
|
||||
case PrimitiveType::kCubics:
|
||||
case PrimitiveType::kConics:
|
||||
return GrPrimitiveType::kTriangleStrip;
|
||||
default:
|
||||
return GrPrimitiveType::kTriangleStrip;
|
||||
}
|
||||
}
|
||||
|
||||
void GrSampleMaskProcessor::bindBuffers(GrOpsRenderPass* renderPass,
|
||||
sk_sp<const GrBuffer> instanceBuffer) const {
|
||||
SkASSERT(PrimitiveType::kWeightedTriangles != fPrimitiveType);
|
||||
|
||||
switch (fPrimitiveType) {
|
||||
case PrimitiveType::kTriangles:
|
||||
case PrimitiveType::kWeightedTriangles: {
|
||||
renderPass->bindBuffers(nullptr, nullptr, std::move(instanceBuffer));
|
||||
break;
|
||||
}
|
||||
case PrimitiveType::kQuadratics:
|
||||
case PrimitiveType::kCubics:
|
||||
case PrimitiveType::kConics: {
|
||||
renderPass->bindBuffers(nullptr, std::move(instanceBuffer), nullptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GrSampleMaskProcessor::drawInstances(GrOpsRenderPass* renderPass, int instanceCount,
|
||||
int baseInstance) const {
|
||||
SkASSERT(PrimitiveType::kWeightedTriangles != fPrimitiveType);
|
||||
|
||||
switch (fPrimitiveType) {
|
||||
case PrimitiveType::kTriangles:
|
||||
case PrimitiveType::kWeightedTriangles: {
|
||||
renderPass->draw(instanceCount * 3, baseInstance * 3);
|
||||
break;
|
||||
}
|
||||
case PrimitiveType::kQuadratics:
|
||||
case PrimitiveType::kCubics:
|
||||
case PrimitiveType::kConics: {
|
||||
renderPass->drawInstanced(instanceCount, baseInstance, 4, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GrGLSLPrimitiveProcessor* GrSampleMaskProcessor::onCreateGLSLInstance(
|
||||
std::unique_ptr<Shader> shader) const {
|
||||
return new Impl(std::move(shader));
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Google LLC.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef GrSampleMaskProcessor_DEFINED
|
||||
#define GrSampleMaskProcessor_DEFINED
|
||||
|
||||
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
|
||||
|
||||
/**
|
||||
* This class implements GrCCCoverageProcessor with MSAA using the sample mask.
|
||||
*/
|
||||
class GrSampleMaskProcessor : public GrCCCoverageProcessor {
|
||||
public:
|
||||
GrSampleMaskProcessor() : GrCCCoverageProcessor(kGrSampleMaskProcessor_ClassID) {}
|
||||
|
||||
private:
|
||||
GrPrimitiveType primType() const final;
|
||||
int numSubpasses() const override { return 1; }
|
||||
void reset(PrimitiveType, int subpassIdx, GrResourceProvider*) override;
|
||||
void bindBuffers(GrOpsRenderPass*, sk_sp<const GrBuffer> instanceBuffer) const override;
|
||||
void drawInstances(GrOpsRenderPass*, int instanceCount, int baseInstance) const override;
|
||||
|
||||
GrGLSLPrimitiveProcessor* onCreateGLSLInstance(std::unique_ptr<Shader>) const override;
|
||||
|
||||
SkSTArray<2, Attribute> fInputAttribs;
|
||||
|
||||
class Impl;
|
||||
};
|
||||
|
||||
#endif
|
@ -1,191 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "src/gpu/ccpr/GrStencilAtlasOp.h"
|
||||
|
||||
#include "include/gpu/GrRecordingContext.h"
|
||||
#include "src/gpu/GrOpFlushState.h"
|
||||
#include "src/gpu/GrOpsRenderPass.h"
|
||||
#include "src/gpu/GrProgramInfo.h"
|
||||
#include "src/gpu/GrRecordingContextPriv.h"
|
||||
#include "src/gpu/ccpr/GrCCPerFlushResources.h"
|
||||
#include "src/gpu/ccpr/GrSampleMaskProcessor.h"
|
||||
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
|
||||
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class StencilResolveProcessor : public GrGeometryProcessor {
|
||||
public:
|
||||
StencilResolveProcessor() : INHERITED(kStencilResolveProcessor_ClassID) {
|
||||
static constexpr Attribute kIBounds = {
|
||||
"ibounds", kShort4_GrVertexAttribType, kShort4_GrSLType};
|
||||
this->setInstanceAttributes(&kIBounds, 1);
|
||||
SkASSERT(this->instanceStride() == sizeof(GrStencilAtlasOp::ResolveRectInstance));
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name() const final { return "StencilResolveProcessor"; }
|
||||
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
|
||||
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
|
||||
class Impl;
|
||||
|
||||
using INHERITED = GrGeometryProcessor;
|
||||
};
|
||||
|
||||
// This processor draws pixel-aligned rectangles directly on top of every path in the atlas.
|
||||
// The caller should have set up the instance data such that "Nonzero" paths get clockwise
|
||||
// rectangles (l < r) and "even/odd" paths get counter-clockwise (r < l). Its purpose
|
||||
// is to convert winding counts in the stencil buffer to A8 coverage in the color buffer.
|
||||
class StencilResolveProcessor::Impl : public GrGLSLGeometryProcessor {
|
||||
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
|
||||
args.fVaryingHandler->emitAttributes(args.fGP.cast<StencilResolveProcessor>());
|
||||
|
||||
GrGLSLVertexBuilder* v = args.fVertBuilder;
|
||||
v->codeAppendf("short2 devcoord;");
|
||||
v->codeAppendf("devcoord.x = (0 == (sk_VertexID & 1)) ? ibounds.x : ibounds.z;");
|
||||
v->codeAppendf("devcoord.y = (sk_VertexID < 2) ? ibounds.y : ibounds.w;");
|
||||
|
||||
v->codeAppendf("float2 atlascoord = float2(devcoord);");
|
||||
gpArgs->fPositionVar.set(kFloat2_GrSLType, "atlascoord");
|
||||
|
||||
// Just output "1" for coverage. This will be modulated by the MSAA stencil test.
|
||||
GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
|
||||
f->codeAppendf("const half4 %s = half4(1), %s = half4(1);",
|
||||
args.fOutputColor, args.fOutputCoverage);
|
||||
}
|
||||
|
||||
void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&) override {}
|
||||
};
|
||||
|
||||
GrGLSLPrimitiveProcessor* StencilResolveProcessor::createGLSLInstance(const GrShaderCaps&) const {
|
||||
return new Impl();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GrOp::Owner GrStencilAtlasOp::Make(
|
||||
GrRecordingContext* context, sk_sp<const GrCCPerFlushResources> resources,
|
||||
FillBatchID fillBatchID, int baseStencilResolveInstance,
|
||||
int endStencilResolveInstance, const SkISize& drawBounds) {
|
||||
|
||||
return GrOp::Make<GrStencilAtlasOp>(
|
||||
context, std::move(resources), fillBatchID, baseStencilResolveInstance,
|
||||
endStencilResolveInstance, drawBounds);
|
||||
}
|
||||
|
||||
// Increments clockwise triangles and decrements counterclockwise. We use the same incr/decr
|
||||
// settings regardless of fill rule; fill rule is accounted for during the resolve step.
|
||||
static constexpr GrUserStencilSettings kIncrDecrStencil(
|
||||
GrUserStencilSettings::StaticInitSeparate<
|
||||
0x0000, 0x0000,
|
||||
GrUserStencilTest::kNever, GrUserStencilTest::kNever,
|
||||
0xffff, 0xffff,
|
||||
GrUserStencilOp::kIncWrap, GrUserStencilOp::kDecWrap,
|
||||
GrUserStencilOp::kIncWrap, GrUserStencilOp::kDecWrap,
|
||||
0xffff, 0xffff>()
|
||||
);
|
||||
|
||||
// Resolves stencil winding counts to A8 coverage. Leaves stencil values untouched.
|
||||
// NOTE: For the CCW face we intentionally use "1 == (stencil & 1)" because the contrapositive logic
|
||||
// (i.e. 0 != ...) causes bugs on Adreno Vulkan. http://skbug.com/9643
|
||||
static constexpr GrUserStencilSettings kResolveStencilCoverage(
|
||||
GrUserStencilSettings::StaticInitSeparate<
|
||||
0x0000, 0x0001,
|
||||
GrUserStencilTest::kNotEqual, GrUserStencilTest::kEqual,
|
||||
0xffff, 0x0001,
|
||||
GrUserStencilOp::kKeep, GrUserStencilOp::kKeep,
|
||||
GrUserStencilOp::kKeep, GrUserStencilOp::kKeep,
|
||||
0xffff, 0xffff>()
|
||||
);
|
||||
|
||||
// Same as above, but also resets stencil values to zero. This is better for non-tilers
|
||||
// where we prefer to not clear the stencil buffer at the beginning of every render pass.
|
||||
static constexpr GrUserStencilSettings kResolveStencilCoverageAndReset(
|
||||
GrUserStencilSettings::StaticInitSeparate<
|
||||
0x0000, 0x0000,
|
||||
GrUserStencilTest::kNotEqual, GrUserStencilTest::kNotEqual,
|
||||
0xffff, 0x0001,
|
||||
GrUserStencilOp::kZero, GrUserStencilOp::kZero,
|
||||
GrUserStencilOp::kKeep, GrUserStencilOp::kZero,
|
||||
0xffff, 0xffff>()
|
||||
);
|
||||
|
||||
// Same as above, but done in two passes for D3D, which doesn't support mismatched refs or masks on
|
||||
// dual sided stencil settings.
|
||||
static constexpr GrUserStencilSettings kResolveWindingCoverageAndReset(
|
||||
GrUserStencilSettings::StaticInitSeparate<
|
||||
0x0000, 0x0000,
|
||||
GrUserStencilTest::kNotEqual, GrUserStencilTest::kNever,
|
||||
0xffff, 0xffff,
|
||||
GrUserStencilOp::kZero, GrUserStencilOp::kKeep,
|
||||
GrUserStencilOp::kKeep, GrUserStencilOp::kKeep,
|
||||
0xffff, 0xffff>()
|
||||
);
|
||||
static constexpr GrUserStencilSettings kResolveEvenOddCoverageAndReset(
|
||||
GrUserStencilSettings::StaticInitSeparate<
|
||||
0x0000, 0x0000,
|
||||
GrUserStencilTest::kNever, GrUserStencilTest::kNotEqual,
|
||||
0x0001, 0x0001,
|
||||
GrUserStencilOp::kKeep, GrUserStencilOp::kZero,
|
||||
GrUserStencilOp::kKeep, GrUserStencilOp::kZero,
|
||||
0xffff, 0xffff>()
|
||||
);
|
||||
|
||||
|
||||
void GrStencilAtlasOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
|
||||
SkIRect drawBoundsRect = SkIRect::MakeWH(fDrawBounds.width(), fDrawBounds.height());
|
||||
|
||||
GrPipeline pipeline(GrScissorTest::kEnabled, GrDisableColorXPFactory::MakeXferProcessor(),
|
||||
flushState->drawOpArgs().writeView().swizzle(),
|
||||
GrPipeline::InputFlags::kHWAntialias);
|
||||
|
||||
GrSampleMaskProcessor sampleMaskProc;
|
||||
|
||||
fResources->filler().drawFills(
|
||||
flushState, &sampleMaskProc, pipeline, fFillBatchID, drawBoundsRect, &kIncrDecrStencil);
|
||||
|
||||
// We resolve the stencil coverage to alpha by drawing pixel-aligned boxes. Fine raster is
|
||||
// not necessary, and will even cause artifacts if using mixed samples.
|
||||
constexpr auto noHWAA = GrPipeline::InputFlags::kNone;
|
||||
|
||||
GrPipeline resolvePipeline(GrScissorTest::kEnabled, SkBlendMode::kSrc,
|
||||
flushState->drawOpArgs().writeView().swizzle(), noHWAA);
|
||||
StencilResolveProcessor primProc;
|
||||
|
||||
if (!flushState->caps().twoSidedStencilRefsAndMasksMustMatch()) {
|
||||
const GrUserStencilSettings* stencil =
|
||||
(flushState->caps().discardStencilValuesAfterRenderPass()) ?
|
||||
&kResolveStencilCoverage : &kResolveStencilCoverageAndReset;
|
||||
this->drawResolve(flushState, resolvePipeline, stencil, primProc, drawBoundsRect);
|
||||
return;
|
||||
}
|
||||
|
||||
// If this ever becomes true then we should add new per-fill-type stencil settings that also
|
||||
// don't reset back to zero.
|
||||
SkASSERT(!flushState->caps().discardStencilValuesAfterRenderPass());
|
||||
|
||||
this->drawResolve(flushState, resolvePipeline, &kResolveWindingCoverageAndReset, primProc,
|
||||
drawBoundsRect);
|
||||
this->drawResolve(flushState, resolvePipeline, &kResolveEvenOddCoverageAndReset, primProc,
|
||||
drawBoundsRect);
|
||||
}
|
||||
|
||||
void GrStencilAtlasOp::drawResolve(GrOpFlushState* flushState, const GrPipeline& resolvePipeline,
|
||||
const GrUserStencilSettings* stencil,
|
||||
const GrPrimitiveProcessor& primProc,
|
||||
const SkIRect& drawBounds) const {
|
||||
GrProgramInfo programInfo(flushState->writeView(), &resolvePipeline, stencil,
|
||||
&primProc, GrPrimitiveType::kTriangleStrip, 0,
|
||||
flushState->renderPassBarriers(),
|
||||
flushState->colorLoadOp());
|
||||
flushState->bindPipeline(programInfo, SkRect::Make(drawBounds));
|
||||
flushState->setScissorRect(drawBounds);
|
||||
flushState->bindBuffers(nullptr, fResources->stencilResolveBuffer(), nullptr);
|
||||
flushState->drawInstanced(fEndStencilResolveInstance - fBaseStencilResolveInstance,
|
||||
fBaseStencilResolveInstance, 4, 0);
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Google LLC.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef GrStencilAtlasOp_DEFINED
|
||||
#define GrStencilAtlasOp_DEFINED
|
||||
|
||||
#include "src/gpu/GrMemoryPool.h"
|
||||
#include "src/gpu/ccpr/GrCCFiller.h"
|
||||
#include "src/gpu/ops/GrDrawOp.h"
|
||||
|
||||
class GrCCPerFlushResources;
|
||||
|
||||
// Renders literal A8 coverage to a CCPR atlas using an intermediate MSAA stencil buffer.
|
||||
class GrStencilAtlasOp : public GrDrawOp {
|
||||
public:
|
||||
DEFINE_OP_CLASS_ID
|
||||
|
||||
using FillBatchID = GrCCFiller::BatchID;
|
||||
|
||||
// Once all the paths in an atlas have been drawn to the stencil buffer, we make a final pass
|
||||
// where we draw "resolve" rects over each path whose purpose is to convert winding counts to A8
|
||||
// coverage.
|
||||
struct ResolveRectInstance {
|
||||
int16_t l, t, r, b;
|
||||
};
|
||||
|
||||
// GrDrawOp interface.
|
||||
const char* name() const override { return "StencilAtlasOp (CCPR)"; }
|
||||
FixedFunctionFlags fixedFunctionFlags() const override {
|
||||
return FixedFunctionFlags::kUsesHWAA | FixedFunctionFlags::kUsesStencil;
|
||||
}
|
||||
|
||||
GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*,
|
||||
bool hasMixedSampledCoverage, GrClampType) override {
|
||||
return GrProcessorSet::EmptySetAnalysis();
|
||||
}
|
||||
CombineResult onCombineIfPossible(GrOp* other, SkArenaAlloc*, const GrCaps&) override {
|
||||
// We will only make multiple copy ops if they have different source proxies.
|
||||
// TODO: make use of texture chaining.
|
||||
return CombineResult::kCannotCombine;
|
||||
}
|
||||
|
||||
static GrOp::Owner Make(
|
||||
GrRecordingContext*, sk_sp<const GrCCPerFlushResources>, FillBatchID,
|
||||
int baseStencilResolveInstance, int endStencilResolveInstance,
|
||||
const SkISize& drawBounds);
|
||||
|
||||
private:
|
||||
void onPrePrepare(GrRecordingContext*,
|
||||
const GrSurfaceProxyView& writeView,
|
||||
GrAppliedClip*,
|
||||
const GrXferProcessor::DstProxyView&,
|
||||
GrXferBarrierFlags renderPassXferBarriers,
|
||||
GrLoadOp colorLoadOp) override {}
|
||||
void onPrepare(GrOpFlushState*) override {}
|
||||
void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
|
||||
void drawResolve(GrOpFlushState*, const GrPipeline&, const GrUserStencilSettings*,
|
||||
const GrPrimitiveProcessor&, const SkIRect& drawBounds) const;
|
||||
|
||||
friend class ::GrOp; // for ctor
|
||||
|
||||
GrStencilAtlasOp(sk_sp<const GrCCPerFlushResources> resources, FillBatchID fillBatchID,
|
||||
int baseStencilResolveInstance,
|
||||
int endStencilResolveInstance, const SkISize& drawBounds)
|
||||
: GrDrawOp(ClassID())
|
||||
, fResources(std::move(resources))
|
||||
, fFillBatchID(fillBatchID)
|
||||
, fBaseStencilResolveInstance(baseStencilResolveInstance)
|
||||
, fEndStencilResolveInstance(endStencilResolveInstance)
|
||||
, fDrawBounds(drawBounds) {
|
||||
this->setBounds(SkRect::MakeIWH(fDrawBounds.width(), fDrawBounds.height()),
|
||||
GrOp::HasAABloat::kNo, GrOp::IsHairline::kNo);
|
||||
}
|
||||
|
||||
const sk_sp<const GrCCPerFlushResources> fResources;
|
||||
const FillBatchID fFillBatchID;
|
||||
const int fBaseStencilResolveInstance;
|
||||
const int fEndStencilResolveInstance;
|
||||
const SkISize fDrawBounds;
|
||||
int fResolveBaseVertex;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
@ -51,32 +51,6 @@ int GrResourceCache::countUniqueKeysWithTag(const char* tag) const {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const GrCCPerFlushResources*
|
||||
GrCoverageCountingPathRenderer::testingOnly_getCurrentFlushResources() {
|
||||
SkASSERT(fFlushing);
|
||||
if (fFlushingPaths.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
// All pending paths should share the same resources.
|
||||
const GrCCPerFlushResources* resources = fFlushingPaths.front()->fFlushResources.get();
|
||||
#ifdef SK_DEBUG
|
||||
for (const auto& flushingPaths : fFlushingPaths) {
|
||||
SkASSERT(flushingPaths->fFlushResources.get() == resources);
|
||||
}
|
||||
#endif
|
||||
return resources;
|
||||
}
|
||||
|
||||
const GrTexture* GrCCPerFlushResources::testingOnly_frontRenderedAtlasTexture() const {
|
||||
if (fRenderedAtlasStack.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
const GrTextureProxy* proxy = fRenderedAtlasStack.front().textureProxy();
|
||||
return (proxy) ? proxy->peekTexture() : nullptr;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define DRAW_OP_TEST_EXTERN(Op) \
|
||||
extern GrOp::Owner Op##__Test(GrPaint&&, SkRandom*, \
|
||||
GrRecordingContext*, int numSamples)
|
||||
|
Loading…
Reference in New Issue
Block a user