aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar csmartdalton <csmartdalton@google.com>2016-07-07 08:49:11 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2016-07-07 08:49:11 -0700
commita7f29640f6ab4eb50962a9d9f12d01ac2ce8b471 (patch)
treeb2ab8bb78abede727cf680cfeca76e385bd7205b
parentd5f6e9a759891473b8211efb90f665b14a85b830 (diff)
Begin instanced rendering for simple shapes
Adds a module that performs instanced rendering and starts using it for a select subset of draws on Mac GL platforms. The instance processor can currently handle rects, ovals, round rects, and double round rects. It can generalize shapes as round rects in order to improve batching. The instance processor also employs new drawing algorithms, irrespective of instanced rendering, that improve GPU-side performance (e.g. sample mask, different triangle layouts, etc.). This change only scratches the surface of instanced rendering. The majority of draws still only have one instance. Future work may include: * Passing coord transforms through the texel buffer. * Sending FP uniforms through instanced vertex attribs. * Using instanced rendering for more draws (stencil writes, drawAtlas, etc.). * Adding more shapes to the instance processor’s repertoire. * Batching draws that have mismatched scissors (analyzing draw bounds, inserting clip planes, etc.). * Bindless textures. * Uber shaders. BUG=skia: GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2066993003 Committed: https://skia.googlesource.com/skia/+/42eafa4bc00354b132ad114d22ed6b95d8849891 Review-Url: https://codereview.chromium.org/2066993003
-rw-r--r--bench/ShapesBench.cpp6
-rw-r--r--gyp/gpu.gypi9
-rw-r--r--include/gpu/GrDrawContext.h2
-rw-r--r--include/private/GrInstancedPipelineInfo.h49
-rw-r--r--src/gpu/GrDrawContext.cpp94
-rw-r--r--src/gpu/GrDrawContextPriv.h5
-rw-r--r--src/gpu/GrDrawTarget.cpp12
-rw-r--r--src/gpu/GrDrawTarget.h33
-rw-r--r--src/gpu/GrDrawingManager.cpp14
-rw-r--r--src/gpu/GrGpu.h10
-rw-r--r--src/gpu/gl/GrGLGpu.cpp7
-rw-r--r--src/gpu/gl/GrGLGpu.h5
-rw-r--r--src/gpu/instanced/GLInstancedRendering.cpp301
-rw-r--r--src/gpu/instanced/GLInstancedRendering.h60
-rw-r--r--src/gpu/instanced/InstanceProcessor.cpp2096
-rw-r--r--src/gpu/instanced/InstanceProcessor.h63
-rw-r--r--src/gpu/instanced/InstancedRendering.cpp488
-rw-r--r--src/gpu/instanced/InstancedRendering.h187
-rw-r--r--src/gpu/instanced/InstancedRenderingTypes.h192
19 files changed, 3603 insertions, 30 deletions
diff --git a/bench/ShapesBench.cpp b/bench/ShapesBench.cpp
index a658c453d1..83f0e455b1 100644
--- a/bench/ShapesBench.cpp
+++ b/bench/ShapesBench.cpp
@@ -248,6 +248,9 @@ private:
typedef Benchmark INHERITED;
};
+#if ENABLE_COMMAND_LINE_SHAPES_BENCH
+DEF_BENCH(return new ShapesBench;)
+#else
// Small primitives (CPU bound, in theory):
DEF_BENCH(return new ShapesBench(ShapesBench::kRect_ShapesType, ShapesBench::kNone_ShapesType,
10000, SkISize::Make(32, 32), false);)
@@ -282,7 +285,4 @@ DEF_BENCH(return new ShapesBench(ShapesBench::kRect_ShapesType, ShapesBench::kRe
50, SkISize::Make(500, 500), false);)
DEF_BENCH(return new ShapesBench(ShapesBench::kRRect_ShapesType, ShapesBench::kRRect_ShapesType,
50, SkISize::Make(500, 500), false);)
-
-#if ENABLE_COMMAND_LINE_SHAPES_BENCH
-DEF_BENCH(return new ShapesBench;)
#endif
diff --git a/gyp/gpu.gypi b/gyp/gpu.gypi
index 4a8deca6bd..d533e64402 100644
--- a/gyp/gpu.gypi
+++ b/gyp/gpu.gypi
@@ -60,6 +60,7 @@
# Private includes
'<(skia_include_path)/private/GrAuditTrail.h',
+ '<(skia_include_path)/private/GrInstancedPipelineInfo.h',
'<(skia_include_path)/private/GrSingleOwner.h',
'<(skia_include_path)/private/GrRenderTargetProxy.h',
'<(skia_include_path)/private/GrSurfaceProxy.h',
@@ -308,6 +309,14 @@
'<(skia_src_path)/gpu/effects/GrYUVEffect.cpp',
'<(skia_src_path)/gpu/effects/GrYUVEffect.h',
+ '<(skia_src_path)/gpu/instanced/InstancedRendering.cpp',
+ '<(skia_src_path)/gpu/instanced/InstancedRendering.h',
+ '<(skia_src_path)/gpu/instanced/InstancedRenderingTypes.h',
+ '<(skia_src_path)/gpu/instanced/InstanceProcessor.cpp',
+ '<(skia_src_path)/gpu/instanced/InstanceProcessor.h',
+ '<(skia_src_path)/gpu/instanced/GLInstancedRendering.cpp',
+ '<(skia_src_path)/gpu/instanced/GLInstancedRendering.h',
+
# text
'<(skia_src_path)/gpu/text/GrAtlasTextBlob.cpp',
'<(skia_src_path)/gpu/text/GrAtlasTextBlob_regenInBatch.cpp',
diff --git a/include/gpu/GrDrawContext.h b/include/gpu/GrDrawContext.h
index f56570b10b..96f42693b4 100644
--- a/include/gpu/GrDrawContext.h
+++ b/include/gpu/GrDrawContext.h
@@ -14,6 +14,7 @@
#include "SkRefCnt.h"
#include "SkRegion.h"
#include "SkSurfaceProps.h"
+#include "../private/GrInstancedPipelineInfo.h"
#include "../private/GrSingleOwner.h"
class GrAtlasTextContext;
@@ -346,6 +347,7 @@ private:
GrDrawTarget* fDrawTarget;
SkAutoTDelete<GrAtlasTextContext> fAtlasTextContext;
GrContext* fContext;
+ GrInstancedPipelineInfo fInstancedPipelineInfo;
SkSurfaceProps fSurfaceProps;
GrAuditTrail* fAuditTrail;
diff --git a/include/private/GrInstancedPipelineInfo.h b/include/private/GrInstancedPipelineInfo.h
new file mode 100644
index 0000000000..f12b89fc6b
--- /dev/null
+++ b/include/private/GrInstancedPipelineInfo.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrGrInstancedPipelineInfo_DEFINED
+#define GrGrInstancedPipelineInfo_DEFINED
+
+#include "GrRenderTarget.h"
+
+/**
+ * Provides info about the pipeline that GrInstancedRendering needs in order to select appropriate
+ * drawing algorithms.
+ */
+struct GrInstancedPipelineInfo {
+ GrInstancedPipelineInfo(const GrRenderTarget* rt)
+ : fIsMultisampled(rt->isStencilBufferMultisampled()),
+ fIsMixedSampled(rt->hasMixedSamples()),
+ fIsRenderingToFloat(GrPixelConfigIsFloatingPoint(rt->desc().fConfig)),
+ fColorDisabled(false),
+ fDrawingShapeToStencil(false),
+ fCanDiscard(false) {
+ }
+
+ bool canUseCoverageAA() const {
+ return !fIsMultisampled || (fIsMixedSampled && !fDrawingShapeToStencil);
+ }
+
+ bool fIsMultisampled : 1;
+ bool fIsMixedSampled : 1;
+ bool fIsRenderingToFloat : 1;
+ bool fColorDisabled : 1;
+ /**
+ * Indicates that the instanced renderer should take extra precautions to ensure the shape gets
+ * drawn correctly to the stencil buffer (e.g. no coverage AA). NOTE: this does not mean a
+ * stencil test is or is not active.
+ */
+ bool fDrawingShapeToStencil : 1;
+ /**
+ * Indicates that the instanced renderer can use processors with discard instructions. This
+ * should not be set if the shader will use derivatives, automatic mipmap LOD, or other features
+ * that depend on neighboring pixels. Some draws will fail to create if this is not set.
+ */
+ bool fCanDiscard : 1;
+};
+
+#endif
diff --git a/src/gpu/GrDrawContext.cpp b/src/gpu/GrDrawContext.cpp
index c42661488d..430ae886f3 100644
--- a/src/gpu/GrDrawContext.cpp
+++ b/src/gpu/GrDrawContext.cpp
@@ -25,6 +25,8 @@
#include "effects/GrRRectEffect.h"
+#include "instanced/InstancedRendering.h"
+
#include "text/GrAtlasTextContext.h"
#include "text/GrStencilAndCoverTextContext.h"
@@ -41,6 +43,8 @@
#define RETURN_FALSE_IF_ABANDONED_PRIV if (fDrawContext->fDrawingManager->wasAbandoned()) { return false; }
#define RETURN_NULL_IF_ABANDONED if (fDrawingManager->wasAbandoned()) { return nullptr; }
+using gr_instanced::InstancedRendering;
+
class AutoCheckFlush {
public:
AutoCheckFlush(GrDrawingManager* drawingManager) : fDrawingManager(drawingManager) {
@@ -70,6 +74,7 @@ GrDrawContext::GrDrawContext(GrContext* context,
, fRenderTarget(std::move(rt))
, fDrawTarget(SkSafeRef(fRenderTarget->getLastDrawTarget()))
, fContext(context)
+ , fInstancedPipelineInfo(fRenderTarget.get())
, fSurfaceProps(SkSurfacePropsCopyOrDefault(surfaceProps))
, fAuditTrail(auditTrail)
#ifdef SK_DEBUG
@@ -273,23 +278,29 @@ GrDrawBatch* GrDrawContext::getFillRectBatch(const GrPaint& paint,
const SkMatrix& viewMatrix,
const SkRect& rect,
bool* useHWAA) {
+ if (InstancedRendering* ir = this->getDrawTarget()->instancedRendering()) {
+ if (GrDrawBatch* batch = ir->recordRect(rect, viewMatrix, paint.getColor(),
+ paint.isAntiAlias(), fInstancedPipelineInfo,
+ useHWAA)) {
+ return batch;
+ }
+ }
- GrDrawBatch* batch = nullptr;
if (should_apply_coverage_aa(paint, fRenderTarget.get(), useHWAA)) {
// The fill path can handle rotation but not skew.
if (view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
SkRect devBoundRect;
viewMatrix.mapRect(&devBoundRect, rect);
- batch = GrRectBatchFactory::CreateAAFill(paint.getColor(), viewMatrix,
- rect, devBoundRect);
+ return GrRectBatchFactory::CreateAAFill(paint.getColor(), viewMatrix,
+ rect, devBoundRect);
}
} else {
// filled BW rect
- batch = GrRectBatchFactory::CreateNonAAFill(paint.getColor(), viewMatrix, rect,
- nullptr, nullptr);
+ return GrRectBatchFactory::CreateNonAAFill(paint.getColor(), viewMatrix, rect,
+ nullptr, nullptr);
}
- return batch;
+ return nullptr;
}
void GrDrawContext::drawRect(const GrClip& clip,
@@ -502,9 +513,19 @@ void GrDrawContext::fillRectToRect(const GrClip& clip,
GR_AUDIT_TRAIL_AUTO_FRAME(fAuditTrail, "GrDrawContext::fillRectToRect");
AutoCheckFlush acf(fDrawingManager);
-
- bool useHWAA;
SkAutoTUnref<GrDrawBatch> batch;
+ bool useHWAA;
+
+ if (InstancedRendering* ir = this->getDrawTarget()->instancedRendering()) {
+ batch.reset(ir->recordRect(rectToDraw, viewMatrix, paint.getColor(), localRect,
+ paint.isAntiAlias(), fInstancedPipelineInfo, &useHWAA));
+ if (batch) {
+ GrPipelineBuilder pipelineBuilder(paint, useHWAA);
+ this->getDrawTarget()->drawBatch(pipelineBuilder, this, clip, batch);
+ return;
+ }
+ }
+
if (should_apply_coverage_aa(paint, fRenderTarget.get(), &useHWAA) &&
view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
batch.reset(GrAAFillRectBatch::CreateWithLocalRect(paint.getColor(), viewMatrix, rectToDraw,
@@ -531,9 +552,19 @@ void GrDrawContext::fillRectWithLocalMatrix(const GrClip& clip,
GR_AUDIT_TRAIL_AUTO_FRAME(fAuditTrail, "GrDrawContext::fillRectWithLocalMatrix");
AutoCheckFlush acf(fDrawingManager);
-
- bool useHWAA;
SkAutoTUnref<GrDrawBatch> batch;
+ bool useHWAA;
+
+ if (InstancedRendering* ir = this->getDrawTarget()->instancedRendering()) {
+ batch.reset(ir->recordRect(rectToDraw, viewMatrix, paint.getColor(), localMatrix,
+ paint.isAntiAlias(), fInstancedPipelineInfo, &useHWAA));
+ if (batch) {
+ GrPipelineBuilder pipelineBuilder(paint, useHWAA);
+ this->getDrawTarget()->drawBatch(pipelineBuilder, this, clip, batch);
+ return;
+ }
+ }
+
if (should_apply_coverage_aa(paint, fRenderTarget.get(), &useHWAA) &&
view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
batch.reset(GrAAFillRectBatch::Create(paint.getColor(), viewMatrix, localMatrix,
@@ -630,13 +661,25 @@ void GrDrawContext::drawRRect(const GrClip& clip,
}
SkASSERT(!style.pathEffect()); // this should've been devolved to a path in SkGpuDevice
- const SkStrokeRec stroke = style.strokeRec();
- AutoCheckFlush acf(fDrawingManager);
+ AutoCheckFlush acf(fDrawingManager);
+ const SkStrokeRec stroke = style.strokeRec();
bool useHWAA;
+
+ if (this->getDrawTarget()->instancedRendering() && stroke.isFillStyle()) {
+ InstancedRendering* ir = this->getDrawTarget()->instancedRendering();
+ SkAutoTUnref<GrDrawBatch> batch(ir->recordRRect(rrect, viewMatrix, paint.getColor(),
+ paint.isAntiAlias(), fInstancedPipelineInfo,
+ &useHWAA));
+ if (batch) {
+ GrPipelineBuilder pipelineBuilder(paint, useHWAA);
+ this->getDrawTarget()->drawBatch(pipelineBuilder, this, clip, batch);
+ return;
+ }
+ }
+
if (should_apply_coverage_aa(paint, fRenderTarget.get(), &useHWAA)) {
GrShaderCaps* shaderCaps = fContext->caps()->shaderCaps();
-
SkAutoTUnref<GrDrawBatch> batch(GrOvalRenderer::CreateRRectBatch(paint.getColor(),
viewMatrix,
rrect,
@@ -663,6 +706,18 @@ bool GrDrawContext::drawFilledDRRect(const GrClip& clip,
SkASSERT(!origInner.isEmpty());
SkASSERT(!origOuter.isEmpty());
+ if (InstancedRendering* ir = this->getDrawTarget()->instancedRendering()) {
+ bool useHWAA;
+ SkAutoTUnref<GrDrawBatch> batch(ir->recordDRRect(origOuter, origInner, viewMatrix,
+ paintIn.getColor(), paintIn.isAntiAlias(),
+ fInstancedPipelineInfo, &useHWAA));
+ if (batch) {
+ GrPipelineBuilder pipelineBuilder(paintIn, useHWAA);
+ this->getDrawTarget()->drawBatch(pipelineBuilder, this, clip, batch);
+ return true;
+ }
+ }
+
bool applyAA = paintIn.isAntiAlias() && !fRenderTarget->isUnifiedMultisampled();
GrPrimitiveEdgeType innerEdgeType = applyAA ? kInverseFillAA_GrProcessorEdgeType :
@@ -761,6 +816,19 @@ void GrDrawContext::drawOval(const GrClip& clip,
AutoCheckFlush acf(fDrawingManager);
const SkStrokeRec& stroke = style.strokeRec();
bool useHWAA;
+
+ if (this->getDrawTarget()->instancedRendering() && stroke.isFillStyle()) {
+ InstancedRendering* ir = this->getDrawTarget()->instancedRendering();
+ SkAutoTUnref<GrDrawBatch> batch(ir->recordOval(oval, viewMatrix, paint.getColor(),
+ paint.isAntiAlias(), fInstancedPipelineInfo,
+ &useHWAA));
+ if (batch) {
+ GrPipelineBuilder pipelineBuilder(paint, useHWAA);
+ this->getDrawTarget()->drawBatch(pipelineBuilder, this, clip, batch);
+ return;
+ }
+ }
+
if (should_apply_coverage_aa(paint, fRenderTarget.get(), &useHWAA)) {
GrShaderCaps* shaderCaps = fContext->caps()->shaderCaps();
SkAutoTUnref<GrDrawBatch> batch(GrOvalRenderer::CreateOvalBatch(paint.getColor(),
diff --git a/src/gpu/GrDrawContextPriv.h b/src/gpu/GrDrawContextPriv.h
index 8087bcf588..c77d8e488b 100644
--- a/src/gpu/GrDrawContextPriv.h
+++ b/src/gpu/GrDrawContextPriv.h
@@ -9,6 +9,7 @@
#define GrDrawContextPriv_DEFINED
#include "GrDrawContext.h"
+#include "GrDrawTarget.h"
#include "GrPathRendering.h"
class GrFixedClip;
@@ -20,6 +21,10 @@ struct GrUserStencilSettings;
data members or virtual methods. */
class GrDrawContextPriv {
public:
+ gr_instanced::InstancedRendering* accessInstancedRendering() const {
+ return fDrawContext->getDrawTarget()->instancedRendering();
+ }
+
void clearStencilClip(const SkIRect& rect, bool insideClip);
void stencilRect(const GrFixedClip& clip,
diff --git a/src/gpu/GrDrawTarget.cpp b/src/gpu/GrDrawTarget.cpp
index fc3caf5212..86e0c82f22 100644
--- a/src/gpu/GrDrawTarget.cpp
+++ b/src/gpu/GrDrawTarget.cpp
@@ -33,6 +33,8 @@
#include "batches/GrRectBatchFactory.h"
#include "batches/GrStencilPathBatch.h"
+#include "instanced/InstancedRendering.h"
+
////////////////////////////////////////////////////////////////////////////////
// Experimentally we have found that most batching occurs within the first 10 comparisons.
@@ -45,7 +47,8 @@ GrDrawTarget::GrDrawTarget(GrRenderTarget* rt, GrGpu* gpu, GrResourceProvider* r
, fResourceProvider(resourceProvider)
, fAuditTrail(auditTrail)
, fFlags(0)
- , fRenderTarget(rt) {
+ , fRenderTarget(rt)
+ , fInstancedRendering(fGpu->createInstancedRenderingIfSupported()) {
// TODO: Stop extracting the context (currently needed by GrClipMaskManager)
fContext = fGpu->getContext();
@@ -201,6 +204,10 @@ void GrDrawTarget::prepareBatches(GrBatchFlushState* flushState) {
fBatches[i]->prepare(flushState);
}
}
+
+ if (fInstancedRendering) {
+ fInstancedRendering->beginFlush(flushState->resourceProvider());
+ }
}
void GrDrawTarget::drawBatches(GrBatchFlushState* flushState) {
@@ -269,6 +276,9 @@ void GrDrawTarget::drawBatches(GrBatchFlushState* flushState) {
void GrDrawTarget::reset() {
fBatches.reset();
+ if (fInstancedRendering) {
+ fInstancedRendering->endFlush();
+ }
}
void GrDrawTarget::drawBatch(const GrPipelineBuilder& pipelineBuilder,
diff --git a/src/gpu/GrDrawTarget.h b/src/gpu/GrDrawTarget.h
index 64c8a3345c..2a5bbde1a3 100644
--- a/src/gpu/GrDrawTarget.h
+++ b/src/gpu/GrDrawTarget.h
@@ -144,6 +144,11 @@ public:
const SkIRect& srcRect,
const SkIPoint& dstPoint);
+ /**
+ * Gets the shape rendering object if it is supported on this platform.
+ */
+ gr_instanced::InstancedRendering* instancedRendering() const { return fInstancedRendering; }
+
private:
friend class GrDrawingManager; // for resetFlag & TopoSortTraits
friend class GrDrawContextPriv; // for clearStencilClip
@@ -209,24 +214,26 @@ private:
// Used only by drawContextPriv.
void clearStencilClip(const SkIRect&, bool insideClip, GrRenderTarget*);
- SkSTArray<256, SkAutoTUnref<GrBatch>, true> fBatches;
+ SkSTArray<256, SkAutoTUnref<GrBatch>, true> fBatches;
// The context is only in service of the clip mask manager, remove once CMM doesn't need this.
- GrContext* fContext;
- GrGpu* fGpu;
- GrResourceProvider* fResourceProvider;
- GrAuditTrail* fAuditTrail;
+ GrContext* fContext;
+ GrGpu* fGpu;
+ GrResourceProvider* fResourceProvider;
+ GrAuditTrail* fAuditTrail;
- SkDEBUGCODE(int fDebugID;)
- uint32_t fFlags;
+ SkDEBUGCODE(int fDebugID;)
+ uint32_t fFlags;
// 'this' drawTarget relies on the output of the drawTargets in 'fDependencies'
- SkTDArray<GrDrawTarget*> fDependencies;
- GrRenderTarget* fRenderTarget;
+ SkTDArray<GrDrawTarget*> fDependencies;
+ GrRenderTarget* fRenderTarget;
+
+ bool fClipBatchToBounds;
+ bool fDrawBatchBounds;
+ int fMaxBatchLookback;
+ int fMaxBatchLookahead;
- bool fClipBatchToBounds;
- bool fDrawBatchBounds;
- int fMaxBatchLookback;
- int fMaxBatchLookahead;
+ SkAutoTDelete<gr_instanced::InstancedRendering> fInstancedRendering;
typedef SkRefCnt INHERITED;
};
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index 458bd20571..75ee0db7c8 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -13,9 +13,13 @@
#include "GrSoftwarePathRenderer.h"
#include "SkTTopoSort.h"
+#include "instanced/InstancedRendering.h"
+
#include "text/GrAtlasTextContext.h"
#include "text/GrStencilAndCoverTextContext.h"
+using gr_instanced::InstancedRendering;
+
void GrDrawingManager::cleanup() {
for (int i = 0; i < fDrawTargets.count(); ++i) {
fDrawTargets[i]->makeClosed(); // no drawTarget should receive a new command after this
@@ -40,6 +44,11 @@ GrDrawingManager::~GrDrawingManager() {
void GrDrawingManager::abandon() {
fAbandoned = true;
+ for (int i = 0; i < fDrawTargets.count(); ++i) {
+ if (InstancedRendering* ir = fDrawTargets[i]->instancedRendering()) {
+ ir->resetGpuResources(InstancedRendering::ResetType::kAbandon);
+ }
+ }
this->cleanup();
}
@@ -48,6 +57,11 @@ void GrDrawingManager::freeGpuResources() {
delete fPathRendererChain;
fPathRendererChain = nullptr;
SkSafeSetNull(fSoftwarePathRenderer);
+ for (int i = 0; i < fDrawTargets.count(); ++i) {
+ if (InstancedRendering* ir = fDrawTargets[i]->instancedRendering()) {
+ ir->resetGpuResources(InstancedRendering::ResetType::kDestroy);
+ }
+ }
}
void GrDrawingManager::reset() {
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index e7c2196c62..be8a59a738 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -39,6 +39,8 @@ class GrStencilSettings;
class GrSurface;
class GrTexture;
+namespace gr_instanced { class InstancedRendering; }
+
class GrGpu : public SkRefCnt {
public:
/**
@@ -148,6 +150,13 @@ public:
const void* data = nullptr);
/**
+ * Creates an instanced rendering object if it is supported on this platform.
+ */
+ virtual gr_instanced::InstancedRendering* createInstancedRenderingIfSupported() {
+ return nullptr;
+ }
+
+ /**
* Resolves MSAA.
*/
void resolveRenderTarget(GrRenderTarget* target);
@@ -591,6 +600,7 @@ private:
GrContext* fContext;
friend class GrPathRendering;
+ friend class gr_instanced::InstancedRendering;
typedef SkRefCnt INHERITED;
};
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index a37d72e26b..9972690487 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -23,6 +23,7 @@
#include "glsl/GrGLSL.h"
#include "glsl/GrGLSLCaps.h"
#include "glsl/GrGLSLPLSPathRendering.h"
+#include "instanced/GLInstancedRendering.h"
#include "SkMipMap.h"
#include "SkPixmap.h"
#include "SkStrokeRec.h"
@@ -46,6 +47,8 @@
///////////////////////////////////////////////////////////////////////////////
+using gr_instanced::InstancedRendering;
+using gr_instanced::GLInstancedRendering;
static const GrGLenum gXfermodeEquation2Blend[] = {
// Basic OpenGL blend equations.
@@ -475,6 +478,10 @@ void GrGLGpu::disconnect(DisconnectType type) {
///////////////////////////////////////////////////////////////////////////////
+InstancedRendering* GrGLGpu::createInstancedRenderingIfSupported() {
+ return GLInstancedRendering::CreateIfSupported(this);
+}
+
void GrGLGpu::onResetContext(uint32_t resetBits) {
// we don't use the zb at all
if (resetBits & kMisc_GrGLBackendState) {
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index c8edbb9c28..5cc0facea6 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -28,6 +28,8 @@ class GrPipeline;
class GrNonInstancedMesh;
class GrSwizzle;
+namespace gr_instanced { class GLInstancedRendering; }
+
#ifdef SK_DEBUG
#define PROGRAM_CACHE_STATS
#endif
@@ -54,6 +56,8 @@ public:
return static_cast<GrGLPathRendering*>(pathRendering());
}
+ gr_instanced::InstancedRendering* createInstancedRenderingIfSupported() override;
+
// Used by GrGLProgram to configure OpenGL state.
void bindTexture(int unitIdx, const GrTextureParams& params, bool allowSRGBInputs,
GrGLTexture* texture);
@@ -595,6 +599,7 @@ private:
typedef GrGpu INHERITED;
friend class GrGLPathRendering; // For accessing setTextureUnit.
+ friend class gr_instanced::GLInstancedRendering; // For accessing flushGLState.
};
#endif
diff --git a/src/gpu/instanced/GLInstancedRendering.cpp b/src/gpu/instanced/GLInstancedRendering.cpp
new file mode 100644
index 0000000000..7df39f07df
--- /dev/null
+++ b/src/gpu/instanced/GLInstancedRendering.cpp
@@ -0,0 +1,301 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GLInstancedRendering.h"
+
+#include "GrResourceProvider.h"
+#include "gl/GrGLGpu.h"
+#include "instanced/InstanceProcessor.h"
+
+#define GL_CALL(X) GR_GL_CALL(this->glGpu()->glInterface(), X)
+
+namespace gr_instanced {
+
+class GLInstancedRendering::GLBatch : public InstancedRendering::Batch {
+public:
+ DEFINE_BATCH_CLASS_ID
+
+ GLBatch(GLInstancedRendering* instRendering) : INHERITED(ClassID(), instRendering) {}
+ int numGLCommands() const { return 1 + fNumChangesInGeometry; }
+
+private:
+ int fEmulatedBaseInstance;
+ int fGLDrawCmdsIdx;
+
+ friend class GLInstancedRendering;
+
+ typedef Batch INHERITED;
+};
+
+GLInstancedRendering* GLInstancedRendering::CreateIfSupported(GrGLGpu* gpu) {
+#ifndef SK_BUILD_FOR_MAC
+ // Only whitelisting on Mac for now. Once we've been able to work through the various issues on
+ // other platforms we can enable more generally.
+ return nullptr;
+#endif
+ const GrGLCaps& glCaps = gpu->glCaps();
+ AntialiasMode lastSupportedAAMode;
+ if (!glCaps.vertexArrayObjectSupport() ||
+ !glCaps.drawIndirectSupport() ||
+ !InstanceProcessor::IsSupported(*glCaps.glslCaps(), glCaps, &lastSupportedAAMode)) {
+ return nullptr;
+ }
+ return new GLInstancedRendering(gpu, lastSupportedAAMode);
+}
+
+GLInstancedRendering::GLInstancedRendering(GrGLGpu* gpu, AntialiasMode lastSupportedAAMode)
+ : INHERITED(gpu, lastSupportedAAMode, gpu->glCaps().canDrawIndirectToFloat()),
+ fVertexArrayID(0),
+ fGLDrawCmdsInfo(0),
+ fInstanceAttribsBufferUniqueId(SK_InvalidUniqueID) {
+}
+
+GLInstancedRendering::~GLInstancedRendering() {
+ if (fVertexArrayID) {
+ GL_CALL(DeleteVertexArrays(1, &fVertexArrayID));
+ this->glGpu()->notifyVertexArrayDelete(fVertexArrayID);
+ }
+}
+
+inline GrGLGpu* GLInstancedRendering::glGpu() const {
+ return static_cast<GrGLGpu*>(this->gpu());
+}
+
+InstancedRendering::Batch* GLInstancedRendering::createBatch() {
+ return new GLBatch(this);
+}
+
+void GLInstancedRendering::onBeginFlush(GrResourceProvider* rp) {
+ // Count what there is to draw.
+ BatchList::Iter iter;
+ iter.init(this->trackedBatches(), BatchList::Iter::kHead_IterStart);
+ int numGLInstances = 0;
+ int numGLDrawCmds = 0;
+ while (Batch* b = iter.get()) {
+ GLBatch* batch = static_cast<GLBatch*>(b);
+ iter.next();
+
+ numGLInstances += batch->fNumDraws;
+ numGLDrawCmds += batch->numGLCommands();
+ }
+ if (!numGLDrawCmds) {
+ return;
+ }
+ SkASSERT(numGLInstances);
+
+ // Lazily create a vertex array object.
+ if (!fVertexArrayID) {
+ GL_CALL(GenVertexArrays(1, &fVertexArrayID));
+ if (!fVertexArrayID) {
+ return;
+ }
+ this->glGpu()->bindVertexArray(fVertexArrayID);
+
+ // Attach our index buffer to the vertex array.
+ GL_CALL(BindBuffer(GR_GL_ELEMENT_ARRAY_BUFFER,
+ static_cast<const GrGLBuffer*>(this->indexBuffer())->bufferID()));
+
+ // Set up the non-instanced attribs.
+ this->glGpu()->bindBuffer(kVertex_GrBufferType,
+ static_cast<const GrGLBuffer*>(this->vertexBuffer()));
+ GL_CALL(EnableVertexAttribArray((int)Attrib::kShapeCoords));
+ GL_CALL(VertexAttribPointer((int)Attrib::kShapeCoords, 2, GR_GL_FLOAT, GR_GL_FALSE,
+ sizeof(ShapeVertex), (void*) offsetof(ShapeVertex, fX)));
+ GL_CALL(EnableVertexAttribArray((int)Attrib::kVertexAttrs));
+ GL_CALL(VertexAttribIPointer((int)Attrib::kVertexAttrs, 1, GR_GL_INT, sizeof(ShapeVertex),
+ (void*) offsetof(ShapeVertex, fAttrs)));
+
+ SkASSERT(SK_InvalidUniqueID == fInstanceAttribsBufferUniqueId);
+ }
+
+ // Create and map instance and draw-indirect buffers.
+ SkASSERT(!fInstanceBuffer);
+ fInstanceBuffer.reset(static_cast<GrGLBuffer*>(
+ rp->createBuffer(sizeof(Instance) * numGLInstances, kVertex_GrBufferType,
+ kDynamic_GrAccessPattern, GrResourceProvider::kNoPendingIO_Flag)));
+ if (!fInstanceBuffer) {
+ return;
+ }
+
+ SkASSERT(!fDrawIndirectBuffer);
+ fDrawIndirectBuffer.reset(static_cast<GrGLBuffer*>(
+ rp->createBuffer(sizeof(GrGLDrawElementsIndirectCommand) * numGLDrawCmds,
+ kDrawIndirect_GrBufferType, kDynamic_GrAccessPattern,
+ GrResourceProvider::kNoPendingIO_Flag)));
+ if (!fDrawIndirectBuffer) {
+ return;
+ }
+
+ Instance* glMappedInstances = static_cast<Instance*>(fInstanceBuffer->map());
+ int glInstancesIdx = 0;
+
+ auto* glMappedCmds = static_cast<GrGLDrawElementsIndirectCommand*>(fDrawIndirectBuffer->map());
+ int glDrawCmdsIdx = 0;
+
+ bool baseInstanceSupport = this->glGpu()->glCaps().baseInstanceSupport();
+
+ if (GR_GL_LOG_INSTANCED_BATCHES || !baseInstanceSupport) {
+ fGLDrawCmdsInfo.reset(numGLDrawCmds);
+ }
+
+ // Generate the instance and draw-indirect buffer contents based on the tracked batches.
+ iter.init(this->trackedBatches(), BatchList::Iter::kHead_IterStart);
+ while (Batch* b = iter.get()) {
+ GLBatch* batch = static_cast<GLBatch*>(b);
+ iter.next();
+
+ batch->fEmulatedBaseInstance = baseInstanceSupport ? 0 : glInstancesIdx;
+ batch->fGLDrawCmdsIdx = glDrawCmdsIdx;
+
+ const Batch::Draw* draw = batch->fHeadDraw;
+ SkASSERT(draw);
+ do {
+ int instanceCount = 0;
+ IndexRange geometry = draw->fGeometry;
+ SkASSERT(!geometry.isEmpty());
+
+ do {
+ glMappedInstances[glInstancesIdx + instanceCount++] = draw->fInstance;
+ draw = draw->fNext;
+ } while (draw && draw->fGeometry == geometry);
+
+ GrGLDrawElementsIndirectCommand& glCmd = glMappedCmds[glDrawCmdsIdx];
+ glCmd.fCount = geometry.fCount;
+ glCmd.fInstanceCount = instanceCount;
+ glCmd.fFirstIndex = geometry.fStart;
+ glCmd.fBaseVertex = 0;
+ glCmd.fBaseInstance = baseInstanceSupport ? glInstancesIdx : 0;
+
+ if (GR_GL_LOG_INSTANCED_BATCHES || !baseInstanceSupport) {
+ fGLDrawCmdsInfo[glDrawCmdsIdx].fInstanceCount = instanceCount;
+#if GR_GL_LOG_INSTANCED_BATCHES
+ fGLDrawCmdsInfo[glDrawCmdsIdx].fGeometry = geometry;
+#endif
+ }
+
+ glInstancesIdx += instanceCount;
+ ++glDrawCmdsIdx;
+ } while (draw);
+ }
+
+ SkASSERT(glDrawCmdsIdx == numGLDrawCmds);
+ fDrawIndirectBuffer->unmap();
+
+ SkASSERT(glInstancesIdx == numGLInstances);
+ fInstanceBuffer->unmap();
+}
+
+void GLInstancedRendering::onDraw(const GrPipeline& pipeline, const InstanceProcessor& instProc,
+ const Batch* baseBatch) {
+ if (!fDrawIndirectBuffer) {
+ return; // beginFlush was not successful.
+ }
+ if (!this->glGpu()->flushGLState(pipeline, instProc)) {
+ return;
+ }
+
+ this->glGpu()->bindBuffer(kDrawIndirect_GrBufferType, fDrawIndirectBuffer.get());
+
+ const GrGLCaps& glCaps = this->glGpu()->glCaps();
+ const GLBatch* batch = static_cast<const GLBatch*>(baseBatch);
+ int numCommands = batch->numGLCommands();
+
+#if GR_GL_LOG_INSTANCED_BATCHES
+ SkASSERT(fGLDrawCmdsInfo);
+ SkDebugf("Instanced batch: [");
+ for (int i = 0; i < numCommands; ++i) {
+ int glCmdIdx = batch->fGLDrawCmdsIdx + i;
+ SkDebugf("%s%i * %s", (i ? ", " : ""), fGLDrawCmdsInfo[glCmdIdx].fInstanceCount,
+ InstanceProcessor::GetNameOfIndexRange(fGLDrawCmdsInfo[glCmdIdx].fGeometry));
+ }
+ SkDebugf("]\n");
+#else
+ SkASSERT(SkToBool(fGLDrawCmdsInfo) == !glCaps.baseInstanceSupport());
+#endif
+
+ if (1 == numCommands || !glCaps.baseInstanceSupport() || !glCaps.multiDrawIndirectSupport()) {
+ int emulatedBaseInstance = batch->fEmulatedBaseInstance;
+ for (int i = 0; i < numCommands; ++i) {
+ int glCmdIdx = batch->fGLDrawCmdsIdx + i;
+ this->flushInstanceAttribs(emulatedBaseInstance);
+ GL_CALL(DrawElementsIndirect(GR_GL_TRIANGLES, GR_GL_UNSIGNED_BYTE,
+ (GrGLDrawElementsIndirectCommand*) nullptr + glCmdIdx));
+ if (!glCaps.baseInstanceSupport()) {
+ emulatedBaseInstance += fGLDrawCmdsInfo[glCmdIdx].fInstanceCount;
+ }
+ }
+ } else {
+ int glCmdsIdx = batch->fGLDrawCmdsIdx;
+ this->flushInstanceAttribs(batch->fEmulatedBaseInstance);
+ GL_CALL(MultiDrawElementsIndirect(GR_GL_TRIANGLES, GR_GL_UNSIGNED_BYTE,
+ (GrGLDrawElementsIndirectCommand*) nullptr + glCmdsIdx,
+ numCommands, 0));
+ }
+}
+
+void GLInstancedRendering::flushInstanceAttribs(int baseInstance) {
+ SkASSERT(fVertexArrayID);
+ this->glGpu()->bindVertexArray(fVertexArrayID);
+
+ SkASSERT(fInstanceBuffer);
+ if (fInstanceAttribsBufferUniqueId != fInstanceBuffer->getUniqueID() ||
+ fInstanceAttribsBaseInstance != baseInstance) {
+ Instance* offsetInBuffer = (Instance*) nullptr + baseInstance;
+
+ this->glGpu()->bindBuffer(kVertex_GrBufferType, fInstanceBuffer.get());
+
+ // Info attrib.
+ GL_CALL(EnableVertexAttribArray((int)Attrib::kInstanceInfo));
+ GL_CALL(VertexAttribIPointer((int)Attrib::kInstanceInfo, 1, GR_GL_UNSIGNED_INT,
+ sizeof(Instance), &offsetInBuffer->fInfo));
+ GL_CALL(VertexAttribDivisor((int)Attrib::kInstanceInfo, 1));
+
+ // Shape matrix attrib.
+ GL_CALL(EnableVertexAttribArray((int)Attrib::kShapeMatrixX));
+ GL_CALL(EnableVertexAttribArray((int)Attrib::kShapeMatrixY));
+ GL_CALL(VertexAttribPointer((int)Attrib::kShapeMatrixX, 3, GR_GL_FLOAT, GR_GL_FALSE,
+ sizeof(Instance), &offsetInBuffer->fShapeMatrix2x3[0]));
+ GL_CALL(VertexAttribPointer((int)Attrib::kShapeMatrixY, 3, GR_GL_FLOAT, GR_GL_FALSE,
+ sizeof(Instance), &offsetInBuffer->fShapeMatrix2x3[3]));
+ GL_CALL(VertexAttribDivisor((int)Attrib::kShapeMatrixX, 1));
+ GL_CALL(VertexAttribDivisor((int)Attrib::kShapeMatrixY, 1));
+
+ // Color attrib.
+ GL_CALL(EnableVertexAttribArray((int)Attrib::kColor));
+ GL_CALL(VertexAttribPointer((int)Attrib::kColor, 4, GR_GL_UNSIGNED_BYTE, GR_GL_TRUE,
+ sizeof(Instance), &offsetInBuffer->fColor));
+ GL_CALL(VertexAttribDivisor((int)Attrib::kColor, 1));
+
+ // Local rect attrib.
+ GL_CALL(EnableVertexAttribArray((int)Attrib::kLocalRect));
+ GL_CALL(VertexAttribPointer((int)Attrib::kLocalRect, 4, GR_GL_FLOAT, GR_GL_FALSE,
+ sizeof(Instance), &offsetInBuffer->fLocalRect));
+ GL_CALL(VertexAttribDivisor((int)Attrib::kLocalRect, 1));
+
+ fInstanceAttribsBufferUniqueId = fInstanceBuffer->getUniqueID();
+ fInstanceAttribsBaseInstance = baseInstance;
+ }
+}
+
+void GLInstancedRendering::onEndFlush() {
+ fInstanceBuffer.reset();
+ fDrawIndirectBuffer.reset();
+ fGLDrawCmdsInfo.reset(0);
+}
+
+void GLInstancedRendering::onResetGpuResources(ResetType resetType) {
+ if (fVertexArrayID && ResetType::kDestroy == resetType) {
+ GL_CALL(DeleteVertexArrays(1, &fVertexArrayID));
+ this->glGpu()->notifyVertexArrayDelete(fVertexArrayID);
+ }
+ fVertexArrayID = 0;
+ fInstanceBuffer.reset();
+ fDrawIndirectBuffer.reset();
+ fInstanceAttribsBufferUniqueId = SK_InvalidUniqueID;
+}
+
+}
diff --git a/src/gpu/instanced/GLInstancedRendering.h b/src/gpu/instanced/GLInstancedRendering.h
new file mode 100644
index 0000000000..569e6e3160
--- /dev/null
+++ b/src/gpu/instanced/GLInstancedRendering.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef gr_instanced_GLInstancedRendering_DEFINED
+#define gr_instanced_GLInstancedRendering_DEFINED
+
+#include "gl/GrGLBuffer.h"
+#include "instanced/InstancedRendering.h"
+
+class GrGLGpu;
+
+#define GR_GL_LOG_INSTANCED_BATCHES 0
+
+namespace gr_instanced {
+
+class GLInstancedRendering final : public InstancedRendering {
+public:
+ static GLInstancedRendering* CreateIfSupported(GrGLGpu*);
+ ~GLInstancedRendering() override;
+
+private:
+ GLInstancedRendering(GrGLGpu*, AntialiasMode lastSupportedAAMode);
+
+ GrGLGpu* glGpu() const;
+
+ Batch* createBatch() override;
+
+ void onBeginFlush(GrResourceProvider*) override;
+ void onDraw(const GrPipeline&, const InstanceProcessor&, const Batch*) override;
+ void onEndFlush() override;
+ void onResetGpuResources(ResetType) override;
+
+ void flushInstanceAttribs(int baseInstance);
+
+ struct GLDrawCmdInfo {
+ int fInstanceCount;
+#if GR_GL_LOG_INSTANCED_BATCHES
+ IndexRange fGeometry;
+#endif
+ };
+
+ GrGLuint fVertexArrayID;
+ SkAutoTUnref<GrGLBuffer> fInstanceBuffer;
+ SkAutoTUnref<GrGLBuffer> fDrawIndirectBuffer;
+ SkAutoSTMalloc<1024, GLDrawCmdInfo> fGLDrawCmdsInfo;
+ uint32_t fInstanceAttribsBufferUniqueId;
+ int fInstanceAttribsBaseInstance;
+
+ class GLBatch;
+
+ typedef InstancedRendering INHERITED;
+};
+
+}
+
+#endif
diff --git a/src/gpu/instanced/InstanceProcessor.cpp b/src/gpu/instanced/InstanceProcessor.cpp
new file mode 100644
index 0000000000..acad4c1e1b
--- /dev/null
+++ b/src/gpu/instanced/InstanceProcessor.cpp
@@ -0,0 +1,2096 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "InstanceProcessor.h"
+
+#include "GrContext.h"
+#include "GrRenderTargetPriv.h"
+#include "GrResourceCache.h"
+#include "GrResourceProvider.h"
+#include "glsl/GrGLSLGeometryProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "glsl/GrGLSLVarying.h"
+
+namespace gr_instanced {
+
+bool InstanceProcessor::IsSupported(const GrGLSLCaps& glslCaps, const GrCaps& caps,
+ AntialiasMode* lastSupportedAAMode) {
+ if (!glslCaps.canUseAnyFunctionInShader() ||
+ !glslCaps.flatInterpolationSupport() ||
+ !glslCaps.integerSupport() ||
+ 0 == glslCaps.maxVertexSamplers() ||
+ !caps.shaderCaps()->texelBufferSupport() ||
+ caps.maxVertexAttributes() < kNumAttribs) {
+ return false;
+ }
+ if (caps.sampleLocationsSupport() &&
+ glslCaps.sampleVariablesSupport() &&
+ glslCaps.shaderDerivativeSupport()) {
+ if (0 != caps.maxRasterSamples() &&
+ glslCaps.sampleMaskOverrideCoverageSupport()) {
+ *lastSupportedAAMode = AntialiasMode::kMixedSamples;
+ } else {
+ *lastSupportedAAMode = AntialiasMode::kMSAA;
+ }
+ } else {
+ *lastSupportedAAMode = AntialiasMode::kCoverage;
+ }
+ return true;
+}
+
+InstanceProcessor::InstanceProcessor(BatchInfo batchInfo, GrBuffer* paramsBuffer)
+ : fBatchInfo(batchInfo) {
+ this->initClassID<InstanceProcessor>();
+
+ this->addVertexAttrib(Attribute("shapeCoords", kVec2f_GrVertexAttribType, kHigh_GrSLPrecision));
+ this->addVertexAttrib(Attribute("vertexAttrs", kInt_GrVertexAttribType));
+ this->addVertexAttrib(Attribute("instanceInfo", kUint_GrVertexAttribType));
+ this->addVertexAttrib(Attribute("shapeMatrixX", kVec3f_GrVertexAttribType,
+ kHigh_GrSLPrecision));
+ this->addVertexAttrib(Attribute("shapeMatrixY", kVec3f_GrVertexAttribType,
+ kHigh_GrSLPrecision));
+ this->addVertexAttrib(Attribute("color", kVec4f_GrVertexAttribType, kLow_GrSLPrecision));
+ this->addVertexAttrib(Attribute("localRect", kVec4f_GrVertexAttribType, kHigh_GrSLPrecision));
+
+ GR_STATIC_ASSERT(0 == (int)Attrib::kShapeCoords);
+ GR_STATIC_ASSERT(1 == (int)Attrib::kVertexAttrs);
+ GR_STATIC_ASSERT(2 == (int)Attrib::kInstanceInfo);
+ GR_STATIC_ASSERT(3 == (int)Attrib::kShapeMatrixX);
+ GR_STATIC_ASSERT(4 == (int)Attrib::kShapeMatrixY);
+ GR_STATIC_ASSERT(5 == (int)Attrib::kColor);
+ GR_STATIC_ASSERT(6 == (int)Attrib::kLocalRect);
+ GR_STATIC_ASSERT(7 == kNumAttribs);
+
+ if (fBatchInfo.fHasParams) {
+ SkASSERT(paramsBuffer);
+ fParamsAccess.reset(kRGBA_float_GrPixelConfig, paramsBuffer, kVertex_GrShaderFlag);
+ this->addBufferAccess(&fParamsAccess);
+ }
+
+ if (fBatchInfo.fAntialiasMode >= AntialiasMode::kMSAA) {
+ if (!fBatchInfo.isSimpleRects() ||
+ AntialiasMode::kMixedSamples == fBatchInfo.fAntialiasMode) {
+ this->setWillUseSampleLocations();
+ }
+ }
+}
+
+class GLSLInstanceProcessor : public GrGLSLGeometryProcessor {
+public:
+ void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
+
+private:
+ void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&) override {}
+
+ class VertexInputs;
+ class Backend;
+ class BackendNonAA;
+ class BackendCoverage;
+ class BackendMultisample;
+
+ typedef GrGLSLGeometryProcessor INHERITED;
+};
+
+GrGLSLPrimitiveProcessor* InstanceProcessor::createGLSLInstance(const GrGLSLCaps&) const {
+ return new GLSLInstanceProcessor();
+}
+
+class GLSLInstanceProcessor::VertexInputs {
+public:
+ VertexInputs(const InstanceProcessor& instProc, GrGLSLVertexBuilder* vertexBuilder)
+ : fInstProc(instProc),
+ fVertexBuilder(vertexBuilder) {
+ }
+
+ void initParams(const SamplerHandle paramsBuffer) {
+ fParamsBuffer = paramsBuffer;
+ fVertexBuilder->definef("PARAMS_IDX_MASK", "0x%xu", kParamsIdx_InfoMask);
+ fVertexBuilder->appendPrecisionModifier(kHigh_GrSLPrecision);
+ fVertexBuilder->codeAppendf("int paramsIdx = int(%s & PARAMS_IDX_MASK);",
+ this->attr(Attrib::kInstanceInfo));
+ }
+
+ const char* attr(Attrib attr) const { return fInstProc.getAttrib((int)attr).fName; }
+
+ void fetchNextParam(GrSLType type = kVec4f_GrSLType) const {
+ SkASSERT(fParamsBuffer.isValid());
+ if (type != kVec4f_GrSLType) {
+ fVertexBuilder->codeAppendf("%s(", GrGLSLTypeString(type));
+ }
+ fVertexBuilder->appendTexelFetch(fParamsBuffer, "paramsIdx++");
+ if (type != kVec4f_GrSLType) {
+ fVertexBuilder->codeAppend(")");
+ }
+ }
+
+ void skipParams(unsigned n) const {
+ SkASSERT(fParamsBuffer.isValid());
+ fVertexBuilder->codeAppendf("paramsIdx += %u;", n);
+ }
+
+private:
+ const InstanceProcessor& fInstProc;
+ GrGLSLVertexBuilder* fVertexBuilder;
+ SamplerHandle fParamsBuffer;
+};
+
+class GLSLInstanceProcessor::Backend {
+public:
+ static Backend* SK_WARN_UNUSED_RESULT Create(const GrPipeline&, BatchInfo, const VertexInputs&);
+ virtual ~Backend() {}
+
+ void init(GrGLSLVaryingHandler*, GrGLSLVertexBuilder*);
+ virtual void setupRect(GrGLSLVertexBuilder*) = 0;
+ virtual void setupOval(GrGLSLVertexBuilder*) = 0;
+ void setupRRect(GrGLSLVertexBuilder*);
+
+ void initInnerShape(GrGLSLVaryingHandler*, GrGLSLVertexBuilder*);
+ virtual void setupInnerRect(GrGLSLVertexBuilder*) = 0;
+ virtual void setupInnerOval(GrGLSLVertexBuilder*) = 0;
+ void setupInnerRRect(GrGLSLVertexBuilder*);
+
+ const char* outShapeCoords() {
+ return fModifiedShapeCoords ? fModifiedShapeCoords : fInputs.attr(Attrib::kShapeCoords);
+ }
+
+ void emitCode(GrGLSLVertexBuilder*, GrGLSLPPFragmentBuilder*, const char* outCoverage,
+ const char* outColor);
+
+protected:
+ Backend(BatchInfo batchInfo, const VertexInputs& inputs)
+ : fBatchInfo(batchInfo),
+ fInputs(inputs),
+ fModifiesCoverage(false),
+ fModifiesColor(false),
+ fNeedsNeighborRadii(false),
+ fColor(kVec4f_GrSLType),
+ fTriangleIsArc(kInt_GrSLType),
+ fArcCoords(kVec2f_GrSLType),
+ fInnerShapeCoords(kVec2f_GrSLType),
+ fInnerRRect(kVec4f_GrSLType),
+ fModifiedShapeCoords(nullptr) {
+ if (fBatchInfo.fShapeTypes & kRRect_ShapesMask) {
+ fModifiedShapeCoords = "adjustedShapeCoords";
+ }
+ }
+
+ virtual void onInit(GrGLSLVaryingHandler*, GrGLSLVertexBuilder*) = 0;
+ virtual void adjustRRectVertices(GrGLSLVertexBuilder*);
+ virtual void onSetupRRect(GrGLSLVertexBuilder*) {}
+
+ virtual void onInitInnerShape(GrGLSLVaryingHandler*, GrGLSLVertexBuilder*) = 0;
+ virtual void onSetupInnerRRect(GrGLSLVertexBuilder*) = 0;
+
+ virtual void onEmitCode(GrGLSLVertexBuilder*, GrGLSLPPFragmentBuilder*,
+ const char* outCoverage, const char* outColor) = 0;
+
+ void setupSimpleRadii(GrGLSLVertexBuilder*);
+ void setupNinePatchRadii(GrGLSLVertexBuilder*);
+ void setupComplexRadii(GrGLSLVertexBuilder*);
+
+ const BatchInfo fBatchInfo;
+ const VertexInputs& fInputs;
+ bool fModifiesCoverage;
+ bool fModifiesColor;
+ bool fNeedsNeighborRadii;
+ GrGLSLVertToFrag fColor;
+ GrGLSLVertToFrag fTriangleIsArc;
+ GrGLSLVertToFrag fArcCoords;
+ GrGLSLVertToFrag fInnerShapeCoords;
+ GrGLSLVertToFrag fInnerRRect;
+ const char* fModifiedShapeCoords;
+};
+
+void GLSLInstanceProcessor::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
+ const GrPipeline& pipeline = args.fVertBuilder->getProgramBuilder()->pipeline();
+ const InstanceProcessor& ip = args.fGP.cast<InstanceProcessor>();
+ GrGLSLUniformHandler* uniHandler = args.fUniformHandler;
+ GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
+ GrGLSLVertexBuilder* v = args.fVertBuilder;
+ GrGLSLPPFragmentBuilder* f = args.fFragBuilder;
+
+ varyingHandler->emitAttributes(ip);
+
+ VertexInputs inputs(ip, v);
+ if (ip.batchInfo().fHasParams) {
+ SkASSERT(1 == ip.numBuffers());
+ inputs.initParams(args.fBufferSamplers[0]);
+ }
+
+ if (!ip.batchInfo().fHasPerspective) {
+ v->codeAppendf("mat2x3 shapeMatrix = mat2x3(%s, %s);",
+ inputs.attr(Attrib::kShapeMatrixX), inputs.attr(Attrib::kShapeMatrixY));
+ } else {
+ v->definef("PERSPECTIVE_FLAG", "0x%xu", kPerspective_InfoFlag);
+ v->codeAppendf("mat3 shapeMatrix = mat3(%s, %s, vec3(0, 0, 1));",
+ inputs.attr(Attrib::kShapeMatrixX), inputs.attr(Attrib::kShapeMatrixY));
+ v->codeAppendf("if (0u != (%s & PERSPECTIVE_FLAG)) {",
+ inputs.attr(Attrib::kInstanceInfo));
+ v->codeAppend ( "shapeMatrix[2] = ");
+ inputs.fetchNextParam(kVec3f_GrSLType);
+ v->codeAppend ( ";");
+ v->codeAppend ("}");
+ }
+
+ int usedShapeTypes = 0;
+
+ bool hasSingleShapeType = SkIsPow2(ip.batchInfo().fShapeTypes);
+ if (!hasSingleShapeType) {
+ usedShapeTypes |= ip.batchInfo().fShapeTypes;
+ v->define("SHAPE_TYPE_BIT", kShapeType_InfoBit);
+ v->codeAppendf("uint shapeType = %s >> SHAPE_TYPE_BIT;",
+ inputs.attr(Attrib::kInstanceInfo));
+ }
+
+ SkAutoTDelete<Backend> backend(Backend::Create(pipeline, ip.batchInfo(), inputs));
+ backend->init(varyingHandler, v);
+
+ if (hasSingleShapeType) {
+ if (kRect_ShapeFlag == ip.batchInfo().fShapeTypes) {
+ backend->setupRect(v);
+ } else if (kOval_ShapeFlag == ip.batchInfo().fShapeTypes) {
+ backend->setupOval(v);
+ } else {
+ backend->setupRRect(v);
+ }
+ } else {
+ v->codeAppend ("switch (shapeType) {");
+ if (ip.batchInfo().fShapeTypes & kRect_ShapeFlag) {
+ v->codeAppend ("case RECT_SHAPE_TYPE: {");
+ backend->setupRect(v);
+ v->codeAppend ("} break;");
+ }
+ if (ip.batchInfo().fShapeTypes & kOval_ShapeFlag) {
+ v->codeAppend ("case OVAL_SHAPE_TYPE: {");
+ backend->setupOval(v);
+ v->codeAppend ("} break;");
+ }
+ if (ip.batchInfo().fShapeTypes & kRRect_ShapesMask) {
+ v->codeAppend ("default: {");
+ backend->setupRRect(v);
+ v->codeAppend ("} break;");
+ }
+ v->codeAppend ("}");
+ }
+
+ if (ip.batchInfo().fInnerShapeTypes) {
+ bool hasSingleInnerShapeType = SkIsPow2(ip.batchInfo().fInnerShapeTypes);
+ if (!hasSingleInnerShapeType) {
+ usedShapeTypes |= ip.batchInfo().fInnerShapeTypes;
+ v->definef("INNER_SHAPE_TYPE_MASK", "0x%xu", kInnerShapeType_InfoMask);
+ v->define("INNER_SHAPE_TYPE_BIT", kInnerShapeType_InfoBit);
+ v->codeAppendf("uint innerShapeType = ((%s & INNER_SHAPE_TYPE_MASK) >> "
+ "INNER_SHAPE_TYPE_BIT);",
+ inputs.attr(Attrib::kInstanceInfo));
+ }
+ // Here we take advantage of the fact that outerRect == localRect in recordDRRect.
+ v->codeAppendf("vec4 outer = %s;", inputs.attr(Attrib::kLocalRect));
+ v->codeAppend ("vec4 inner = ");
+ inputs.fetchNextParam();
+ v->codeAppend (";");
+ // outer2Inner is a transform from shape coords to inner shape coords:
+ // e.g. innerShapeCoords = shapeCoords * outer2Inner.xy + outer2Inner.zw
+ v->codeAppend ("vec4 outer2Inner = vec4(outer.zw - outer.xy, "
+ "outer.xy + outer.zw - inner.xy - inner.zw) / "
+ "(inner.zw - inner.xy).xyxy;");
+ v->codeAppendf("vec2 innerShapeCoords = %s * outer2Inner.xy + outer2Inner.zw;",
+ backend->outShapeCoords());
+
+ backend->initInnerShape(varyingHandler, v);
+
+ if (hasSingleInnerShapeType) {
+ if (kRect_ShapeFlag == ip.batchInfo().fInnerShapeTypes) {
+ backend->setupInnerRect(v);
+ } else if (kOval_ShapeFlag == ip.batchInfo().fInnerShapeTypes) {
+ backend->setupInnerOval(v);
+ } else {
+ backend->setupInnerRRect(v);
+ }
+ } else {
+ v->codeAppend("switch (innerShapeType) {");
+ if (ip.batchInfo().fInnerShapeTypes & kRect_ShapeFlag) {
+ v->codeAppend("case RECT_SHAPE_TYPE: {");
+ backend->setupInnerRect(v);
+ v->codeAppend("} break;");
+ }
+ if (ip.batchInfo().fInnerShapeTypes & kOval_ShapeFlag) {
+ v->codeAppend("case OVAL_SHAPE_TYPE: {");
+ backend->setupInnerOval(v);
+ v->codeAppend("} break;");
+ }
+ if (ip.batchInfo().fInnerShapeTypes & kRRect_ShapesMask) {
+ v->codeAppend("default: {");
+ backend->setupInnerRRect(v);
+ v->codeAppend("} break;");
+ }
+ v->codeAppend("}");
+ }
+ }
+
+ if (usedShapeTypes & kRect_ShapeFlag) {
+ v->definef("RECT_SHAPE_TYPE", "%du", (int)ShapeType::kRect);
+ }
+ if (usedShapeTypes & kOval_ShapeFlag) {
+ v->definef("OVAL_SHAPE_TYPE", "%du", (int)ShapeType::kOval);
+ }
+
+ backend->emitCode(v, f, pipeline.ignoresCoverage() ? nullptr : args.fOutputCoverage,
+ args.fOutputColor);
+
+ const char* localCoords = nullptr;
+ if (ip.batchInfo().fUsesLocalCoords) {
+ localCoords = "localCoords";
+ v->codeAppendf("vec2 t = 0.5 * (%s + vec2(1));", backend->outShapeCoords());
+ v->codeAppendf("vec2 localCoords = (1.0 - t) * %s.xy + t * %s.zw;",
+ inputs.attr(Attrib::kLocalRect), inputs.attr(Attrib::kLocalRect));
+ }
+ if (ip.batchInfo().fHasLocalMatrix && ip.batchInfo().fHasParams) {
+ v->definef("LOCAL_MATRIX_FLAG", "0x%xu", kLocalMatrix_InfoFlag);
+ v->codeAppendf("if (0u != (%s & LOCAL_MATRIX_FLAG)) {",
+ inputs.attr(Attrib::kInstanceInfo));
+ if (!ip.batchInfo().fUsesLocalCoords) {
+ inputs.skipParams(2);
+ } else {
+ v->codeAppendf( "mat2x3 localMatrix;");
+ v->codeAppend ( "localMatrix[0] = ");
+ inputs.fetchNextParam(kVec3f_GrSLType);
+ v->codeAppend ( ";");
+ v->codeAppend ( "localMatrix[1] = ");
+ inputs.fetchNextParam(kVec3f_GrSLType);
+ v->codeAppend ( ";");
+ v->codeAppend ( "localCoords = (vec3(localCoords, 1) * localMatrix).xy;");
+ }
+ v->codeAppend("}");
+ }
+
+ GrSLType positionType = ip.batchInfo().fHasPerspective ? kVec3f_GrSLType : kVec2f_GrSLType;
+ v->codeAppendf("%s deviceCoords = vec3(%s, 1) * shapeMatrix;",
+ GrGLSLTypeString(positionType), backend->outShapeCoords());
+ gpArgs->fPositionVar.set(positionType, "deviceCoords");
+
+ this->emitTransforms(v, varyingHandler, uniHandler, gpArgs->fPositionVar, localCoords,
+ args.fTransformsIn, args.fTransformsOut);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void GLSLInstanceProcessor::Backend::init(GrGLSLVaryingHandler* varyingHandler,
+ GrGLSLVertexBuilder* v) {
+ if (fModifiedShapeCoords) {
+ v->codeAppendf("vec2 %s = %s;", fModifiedShapeCoords, fInputs.attr(Attrib::kShapeCoords));
+ }
+
+ this->onInit(varyingHandler, v);
+
+ if (!fColor.vsOut()) {
+ varyingHandler->addFlatVarying("color", &fColor, kLow_GrSLPrecision);
+ v->codeAppendf("%s = %s;", fColor.vsOut(), fInputs.attr(Attrib::kColor));
+ }
+}
+
+void GLSLInstanceProcessor::Backend::setupRRect(GrGLSLVertexBuilder* v) {
+ v->codeAppendf("uvec2 corner = uvec2(%s & 1, (%s >> 1) & 1);",
+ fInputs.attr(Attrib::kVertexAttrs), fInputs.attr(Attrib::kVertexAttrs));
+ v->codeAppend ("vec2 cornerSign = vec2(corner) * 2.0 - 1.0;");
+ v->codeAppendf("vec2 radii%s;", fNeedsNeighborRadii ? ", neighborRadii" : "");
+ v->codeAppend ("mat2 p = ");
+ fInputs.fetchNextParam(kMat22f_GrSLType);
+ v->codeAppend (";");
+ uint8_t types = fBatchInfo.fShapeTypes & kRRect_ShapesMask;
+ if (0 == (types & (types - 1))) {
+ if (kSimpleRRect_ShapeFlag == types) {
+ this->setupSimpleRadii(v);
+ } else if (kNinePatch_ShapeFlag == types) {
+ this->setupNinePatchRadii(v);
+ } else if (kComplexRRect_ShapeFlag == types) {
+ this->setupComplexRadii(v);
+ }
+ } else {
+ v->codeAppend("switch (shapeType) {");
+ if (types & kSimpleRRect_ShapeFlag) {
+ v->definef("SIMPLE_R_RECT_SHAPE_TYPE", "%du", (int)ShapeType::kSimpleRRect);
+ v->codeAppend ("case SIMPLE_R_RECT_SHAPE_TYPE: {");
+ this->setupSimpleRadii(v);
+ v->codeAppend ("} break;");
+ }
+ if (types & kNinePatch_ShapeFlag) {
+ v->definef("NINE_PATCH_SHAPE_TYPE", "%du", (int)ShapeType::kNinePatch);
+ v->codeAppend ("case NINE_PATCH_SHAPE_TYPE: {");
+ this->setupNinePatchRadii(v);
+ v->codeAppend ("} break;");
+ }
+ if (types & kComplexRRect_ShapeFlag) {
+ v->codeAppend ("default: {");
+ this->setupComplexRadii(v);
+ v->codeAppend ("} break;");
+ }
+ v->codeAppend("}");
+ }
+
+ this->adjustRRectVertices(v);
+
+ if (fArcCoords.vsOut()) {
+ v->codeAppendf("%s = (cornerSign * %s + radii - vec2(1)) / radii;",
+ fArcCoords.vsOut(), fModifiedShapeCoords);
+ }
+ if (fTriangleIsArc.vsOut()) {
+ v->codeAppendf("%s = int(all(equal(vec2(1), abs(%s))));",
+ fTriangleIsArc.vsOut(), fInputs.attr(Attrib::kShapeCoords));
+ }
+
+ this->onSetupRRect(v);
+}
+
+void GLSLInstanceProcessor::Backend::setupSimpleRadii(GrGLSLVertexBuilder* v) {
+ if (fNeedsNeighborRadii) {
+ v->codeAppend ("neighborRadii = ");
+ }
+ v->codeAppend("radii = p[0] * 2.0 / p[1];");
+}
+
+void GLSLInstanceProcessor::Backend::setupNinePatchRadii(GrGLSLVertexBuilder* v) {
+ v->codeAppend("radii = vec2(p[0][corner.x], p[1][corner.y]);");
+ if (fNeedsNeighborRadii) {
+ v->codeAppend("neighborRadii = vec2(p[0][1u - corner.x], p[1][1u - corner.y]);");
+ }
+}
+
+void GLSLInstanceProcessor::Backend::setupComplexRadii(GrGLSLVertexBuilder* v) {
+ /**
+ * The x and y radii of each arc are stored in separate vectors,
+ * in the following order:
+ *
+ * __x1 _ _ _ x3__
+ *
+ * y1 | | y2
+ *
+ * | |
+ *
+ * y3 |__ _ _ _ __| y4
+ * x2 x4
+ *
+ */
+ v->codeAppend("mat2 p2 = ");
+ fInputs.fetchNextParam(kMat22f_GrSLType);
+ v->codeAppend(";");
+ v->codeAppend("radii = vec2(p[corner.x][corner.y], p2[corner.y][corner.x]);");
+ if (fNeedsNeighborRadii) {
+ v->codeAppend("neighborRadii = vec2(p[1u - corner.x][corner.y], "
+ "p2[1u - corner.y][corner.x]);");
+ }
+}
+
+void GLSLInstanceProcessor::Backend::adjustRRectVertices(GrGLSLVertexBuilder* v) {
+ // Resize the 4 triangles that arcs are drawn into so they match their corresponding radii.
+ // 0.5 is a special value that indicates the edge of an arc triangle.
+ v->codeAppendf("if (abs(%s.x) == 0.5)"
+ "%s.x = cornerSign.x * (1.0 - radii.x);",
+ fInputs.attr(Attrib::kShapeCoords), fModifiedShapeCoords);
+ v->codeAppendf("if (abs(%s.y) == 0.5) "
+ "%s.y = cornerSign.y * (1.0 - radii.y);",
+ fInputs.attr(Attrib::kShapeCoords), fModifiedShapeCoords);
+}
+
+void GLSLInstanceProcessor::Backend::initInnerShape(GrGLSLVaryingHandler* varyingHandler,
+ GrGLSLVertexBuilder* v) {
+ SkASSERT(!(fBatchInfo.fInnerShapeTypes & (kNinePatch_ShapeFlag | kComplexRRect_ShapeFlag)));
+
+ this->onInitInnerShape(varyingHandler, v);
+
+ if (fInnerShapeCoords.vsOut()) {
+ v->codeAppendf("%s = innerShapeCoords;", fInnerShapeCoords.vsOut());
+ }
+}
+
+void GLSLInstanceProcessor::Backend::setupInnerRRect(GrGLSLVertexBuilder* v) {
+ v->codeAppend("mat2 innerP = ");
+ fInputs.fetchNextParam(kMat22f_GrSLType);
+ v->codeAppend(";");
+ v->codeAppend("vec2 innerRadii = innerP[0] * 2.0 / innerP[1];");
+ this->onSetupInnerRRect(v);
+}
+
+void GLSLInstanceProcessor::Backend::emitCode(GrGLSLVertexBuilder* v, GrGLSLPPFragmentBuilder* f,
+ const char* outCoverage, const char* outColor) {
+ SkASSERT(!fModifiesCoverage || outCoverage);
+ this->onEmitCode(v, f, fModifiesCoverage ? outCoverage : nullptr,
+ fModifiesColor ? outColor : nullptr);
+ if (outCoverage && !fModifiesCoverage) {
+ // Even though the subclass doesn't use coverage, we are expected to assign some value.
+ f->codeAppendf("%s = vec4(1);", outCoverage);
+ }
+ if (!fModifiesColor) {
+ // The subclass didn't assign a value to the output color.
+ f->codeAppendf("%s = %s;", outColor, fColor.fsIn());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+class GLSLInstanceProcessor::BackendNonAA : public Backend {
+public:
+ BackendNonAA(BatchInfo batchInfo, const VertexInputs& inputs)
+ : INHERITED(batchInfo, inputs) {
+ if (fBatchInfo.fCannotDiscard && !fBatchInfo.isSimpleRects()) {
+ fModifiesColor = !fBatchInfo.fCannotTweakAlphaForCoverage;
+ fModifiesCoverage = !fModifiesColor;
+ }
+ }
+
+private:
+ void onInit(GrGLSLVaryingHandler*, GrGLSLVertexBuilder*) override;
+ void setupRect(GrGLSLVertexBuilder*) override;
+ void setupOval(GrGLSLVertexBuilder*) override;
+
+ void onInitInnerShape(GrGLSLVaryingHandler*, GrGLSLVertexBuilder*) override;
+ void setupInnerRect(GrGLSLVertexBuilder*) override;
+ void setupInnerOval(GrGLSLVertexBuilder*) override;
+ void onSetupInnerRRect(GrGLSLVertexBuilder*) override;
+
+ void onEmitCode(GrGLSLVertexBuilder*, GrGLSLPPFragmentBuilder*, const char*,
+ const char*) override;
+
+ typedef Backend INHERITED;
+};
+
+void GLSLInstanceProcessor::BackendNonAA::onInit(GrGLSLVaryingHandler* varyingHandler,
+ GrGLSLVertexBuilder*) {
+ if (kRect_ShapeFlag != fBatchInfo.fShapeTypes) {
+ varyingHandler->addFlatVarying("triangleIsArc", &fTriangleIsArc, kHigh_GrSLPrecision);
+ varyingHandler->addVarying("arcCoords", &fArcCoords, kMedium_GrSLPrecision);
+ }
+}
+
+void GLSLInstanceProcessor::BackendNonAA::setupRect(GrGLSLVertexBuilder* v) {
+ if (fTriangleIsArc.vsOut()) {
+ v->codeAppendf("%s = 0;", fTriangleIsArc.vsOut());
+ }
+}
+
+void GLSLInstanceProcessor::BackendNonAA::setupOval(GrGLSLVertexBuilder* v) {
+ SkASSERT(fArcCoords.vsOut());
+ SkASSERT(fTriangleIsArc.vsOut());
+ v->codeAppendf("%s = %s;", fArcCoords.vsOut(), this->outShapeCoords());
+ v->codeAppendf("%s = %s & 1;", fTriangleIsArc.vsOut(), fInputs.attr(Attrib::kVertexAttrs));
+}
+
+void GLSLInstanceProcessor::BackendNonAA::onInitInnerShape(GrGLSLVaryingHandler* varyingHandler,
+ GrGLSLVertexBuilder*) {
+ varyingHandler->addVarying("innerShapeCoords", &fInnerShapeCoords, kMedium_GrSLPrecision);
+ if (kRect_ShapeFlag != fBatchInfo.fInnerShapeTypes &&
+ kOval_ShapeFlag != fBatchInfo.fInnerShapeTypes) {
+ varyingHandler->addFlatVarying("innerRRect", &fInnerRRect, kMedium_GrSLPrecision);
+ }
+}
+
+void GLSLInstanceProcessor::BackendNonAA::setupInnerRect(GrGLSLVertexBuilder* v) {
+ if (fInnerRRect.vsOut()) {
+ v->codeAppendf("%s = vec4(1);", fInnerRRect.vsOut());
+ }
+}
+
+void GLSLInstanceProcessor::BackendNonAA::setupInnerOval(GrGLSLVertexBuilder* v) {
+ if (fInnerRRect.vsOut()) {
+ v->codeAppendf("%s = vec4(0, 0, 1, 1);", fInnerRRect.vsOut());
+ }
+}
+
+void GLSLInstanceProcessor::BackendNonAA::onSetupInnerRRect(GrGLSLVertexBuilder* v) {
+ v->codeAppendf("%s = vec4(1.0 - innerRadii, 1.0 / innerRadii);", fInnerRRect.vsOut());
+}
+
+void GLSLInstanceProcessor::BackendNonAA::onEmitCode(GrGLSLVertexBuilder*,
+ GrGLSLPPFragmentBuilder* f,
+ const char* outCoverage,
+ const char* outColor) {
+ const char* dropFragment = nullptr;
+ if (!fBatchInfo.fCannotDiscard) {
+ dropFragment = "discard";
+ } else if (fModifiesCoverage) {
+ f->appendPrecisionModifier(kLow_GrSLPrecision);
+ f->codeAppend ("float covered = 1.0;");
+ dropFragment = "covered = 0.0";
+ } else if (fModifiesColor) {
+ f->appendPrecisionModifier(kLow_GrSLPrecision);
+ f->codeAppendf("vec4 color = %s;", fColor.fsIn());
+ dropFragment = "color = vec4(0)";
+ }
+ if (fTriangleIsArc.fsIn()) {
+ SkASSERT(dropFragment);
+ f->codeAppendf("if (%s != 0 && dot(%s, %s) > 1.0) %s;",
+ fTriangleIsArc.fsIn(), fArcCoords.fsIn(), fArcCoords.fsIn(), dropFragment);
+ }
+ if (fBatchInfo.fInnerShapeTypes) {
+ SkASSERT(dropFragment);
+ f->codeAppendf("// Inner shape.\n");
+ if (kRect_ShapeFlag == fBatchInfo.fInnerShapeTypes) {
+ f->codeAppendf("if (all(lessThanEqual(abs(%s), vec2(1)))) %s;",
+ fInnerShapeCoords.fsIn(), dropFragment);
+ } else if (kOval_ShapeFlag == fBatchInfo.fInnerShapeTypes) {
+ f->codeAppendf("if ((dot(%s, %s) <= 1.0)) %s;",
+ fInnerShapeCoords.fsIn(), fInnerShapeCoords.fsIn(), dropFragment);
+ } else {
+ f->codeAppendf("if (all(lessThan(abs(%s), vec2(1)))) {", fInnerShapeCoords.fsIn());
+ f->codeAppendf( "vec2 distanceToArcEdge = abs(%s) - %s.xy;",
+ fInnerShapeCoords.fsIn(), fInnerRRect.fsIn());
+ f->codeAppend ( "if (any(lessThan(distanceToArcEdge, vec2(0)))) {");
+ f->codeAppendf( "%s;", dropFragment);
+ f->codeAppend ( "} else {");
+ f->codeAppendf( "vec2 rrectCoords = distanceToArcEdge * %s.zw;",
+ fInnerRRect.fsIn());
+ f->codeAppend ( "if (dot(rrectCoords, rrectCoords) <= 1.0) {");
+ f->codeAppendf( "%s;", dropFragment);
+ f->codeAppend ( "}");
+ f->codeAppend ( "}");
+ f->codeAppend ("}");
+ }
+ }
+ if (fModifiesCoverage) {
+ f->codeAppendf("%s = vec4(covered);", outCoverage);
+ } else if (fModifiesColor) {
+ f->codeAppendf("%s = color;", outColor);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+class GLSLInstanceProcessor::BackendCoverage : public Backend {
+public:
+ BackendCoverage(BatchInfo batchInfo, const VertexInputs& inputs)
+ : INHERITED(batchInfo, inputs),
+ fColorTimesRectCoverage(kVec4f_GrSLType),
+ fRectCoverage(kFloat_GrSLType),
+ fEllipseCoords(kVec2f_GrSLType),
+ fEllipseName(kVec2f_GrSLType),
+ fBloatedRadius(kFloat_GrSLType),
+ fDistanceToInnerEdge(kVec2f_GrSLType),
+ fInnerShapeBloatedHalfSize(kVec2f_GrSLType),
+ fInnerEllipseCoords(kVec2f_GrSLType),
+ fInnerEllipseName(kVec2f_GrSLType) {
+ fShapeIsCircle = !fBatchInfo.fNonSquare && !(fBatchInfo.fShapeTypes & kRRect_ShapesMask);
+ fTweakAlphaForCoverage = !fBatchInfo.fCannotTweakAlphaForCoverage &&
+ !fBatchInfo.fInnerShapeTypes;
+ fModifiesCoverage = !fTweakAlphaForCoverage;
+ fModifiesColor = fTweakAlphaForCoverage;
+ fModifiedShapeCoords = "bloatedShapeCoords";
+ }
+
+private:
+ void onInit(GrGLSLVaryingHandler*, GrGLSLVertexBuilder*) override;
+ void setupRect(GrGLSLVertexBuilder*) override;
+ void setupOval(GrGLSLVertexBuilder*) override;
+ void adjustRRectVertices(GrGLSLVertexBuilder*) override;
+ void onSetupRRect(GrGLSLVertexBuilder*) override;
+
+ void onInitInnerShape(GrGLSLVaryingHandler*, GrGLSLVertexBuilder*) override;
+ void setupInnerRect(GrGLSLVertexBuilder*) override;
+ void setupInnerOval(GrGLSLVertexBuilder*) override;
+ void onSetupInnerRRect(GrGLSLVertexBuilder*) override;
+
+ void onEmitCode(GrGLSLVertexBuilder*, GrGLSLPPFragmentBuilder*, const char* outCoverage,
+ const char* outColor) override;
+
+ void emitRect(GrGLSLPPFragmentBuilder*, const char* outCoverage, const char* outColor);
+ void emitCircle(GrGLSLPPFragmentBuilder*, const char* outCoverage);
+ void emitArc(GrGLSLPPFragmentBuilder* f, const char* ellipseCoords, const char* ellipseName,
+ bool ellipseCoordsNeedClamp, bool ellipseCoordsMayBeNegative,
+ const char* outCoverage);
+ void emitInnerRect(GrGLSLPPFragmentBuilder*, const char* outCoverage);
+
+ GrGLSLVertToFrag fColorTimesRectCoverage;
+ GrGLSLVertToFrag fRectCoverage;
+ GrGLSLVertToFrag fEllipseCoords;
+ GrGLSLVertToFrag fEllipseName;
+ GrGLSLVertToFrag fBloatedRadius;
+ GrGLSLVertToFrag fDistanceToInnerEdge;
+ GrGLSLVertToFrag fInnerShapeBloatedHalfSize;
+ GrGLSLVertToFrag fInnerEllipseCoords;
+ GrGLSLVertToFrag fInnerEllipseName;
+ bool fShapeIsCircle;
+ bool fTweakAlphaForCoverage;
+
+ typedef Backend INHERITED;
+};
+
+void GLSLInstanceProcessor::BackendCoverage::onInit(GrGLSLVaryingHandler* varyingHandler,
+ GrGLSLVertexBuilder* v) {
+ v->codeAppend ("mat2 shapeTransposeMatrix = transpose(mat2(shapeMatrix));");
+ v->codeAppend ("vec2 shapeHalfSize = vec2(length(shapeTransposeMatrix[0]), "
+ "length(shapeTransposeMatrix[1]));");
+ v->codeAppend ("vec2 bloat = 0.5 / shapeHalfSize;");
+ v->codeAppendf("bloatedShapeCoords = %s * (1.0 + bloat);", fInputs.attr(Attrib::kShapeCoords));
+
+ if (kOval_ShapeFlag != fBatchInfo.fShapeTypes) {
+ if (fTweakAlphaForCoverage) {
+ varyingHandler->addVarying("colorTimesRectCoverage", &fColorTimesRectCoverage,
+ kLow_GrSLPrecision);
+ if (kRect_ShapeFlag == fBatchInfo.fShapeTypes) {
+ fColor = fColorTimesRectCoverage;
+ }
+ } else {
+ varyingHandler->addVarying("rectCoverage", &fRectCoverage, kLow_GrSLPrecision);
+ }
+ v->codeAppend("float rectCoverage = 0.0;");
+ }
+ if (kRect_ShapeFlag != fBatchInfo.fShapeTypes) {
+ varyingHandler->addFlatVarying("triangleIsArc", &fTriangleIsArc, kHigh_GrSLPrecision);
+ if (!fShapeIsCircle) {
+ varyingHandler->addVarying("ellipseCoords", &fEllipseCoords, kHigh_GrSLPrecision);
+ varyingHandler->addFlatVarying("ellipseName", &fEllipseName, kHigh_GrSLPrecision);
+ } else {
+ varyingHandler->addVarying("circleCoords", &fEllipseCoords, kMedium_GrSLPrecision);
+ varyingHandler->addFlatVarying("bloatedRadius", &fBloatedRadius, kMedium_GrSLPrecision);
+ }
+ }
+}
+
+void GLSLInstanceProcessor::BackendCoverage::setupRect(GrGLSLVertexBuilder* v) {
+ // Make the border one pixel wide. Inner vs outer is indicated by coordAttrs.
+ v->codeAppendf("vec2 rectBloat = (%s != 0) ? bloat : -bloat;",
+ fInputs.attr(Attrib::kVertexAttrs));
+ // Here we use the absolute value, because when the rect is thinner than a pixel, this makes it
+ // mark the spot where pixel center is within half a pixel of the *opposite* edge. This,
+ // combined with the "maxCoverage" logic below gives us mathematically correct coverage even for
+ // subpixel rectangles.
+ v->codeAppendf("bloatedShapeCoords = %s * abs(vec2(1.0 + rectBloat));",
+ fInputs.attr(Attrib::kShapeCoords));
+
+ // Determine coverage at the vertex. Coverage naturally ramps from 0 to 1 unless the rect is
+ // narrower than a pixel.
+ v->codeAppend ("float maxCoverage = 4.0 * min(0.5, shapeHalfSize.x) *"
+ "min(0.5, shapeHalfSize.y);");
+ v->codeAppendf("rectCoverage = (%s != 0) ? 0.0 : maxCoverage;",
+ fInputs.attr(Attrib::kVertexAttrs));
+
+ if (fTriangleIsArc.vsOut()) {
+ v->codeAppendf("%s = 0;", fTriangleIsArc.vsOut());
+ }
+}
+
+void GLSLInstanceProcessor::BackendCoverage::setupOval(GrGLSLVertexBuilder* v) {
+ // Offset the inner and outer octagons by one pixel. Inner vs outer is indicated by coordAttrs.
+ v->codeAppendf("vec2 ovalBloat = (%s != 0) ? bloat : -bloat;",
+ fInputs.attr(Attrib::kVertexAttrs));
+ v->codeAppendf("bloatedShapeCoords = %s * max(vec2(1.0 + ovalBloat), vec2(0));",
+ fInputs.attr(Attrib::kShapeCoords));
+ v->codeAppendf("%s = bloatedShapeCoords * shapeHalfSize;", fEllipseCoords.vsOut());
+ if (fEllipseName.vsOut()) {
+ v->codeAppendf("%s = 1.0 / (shapeHalfSize * shapeHalfSize);", fEllipseName.vsOut());
+ }
+ if (fBloatedRadius.vsOut()) {
+ SkASSERT(fShapeIsCircle);
+ v->codeAppendf("%s = shapeHalfSize.x + 0.5;", fBloatedRadius.vsOut());
+ }
+ if (fTriangleIsArc.vsOut()) {
+ v->codeAppendf("%s = int(%s != 0);",
+ fTriangleIsArc.vsOut(), fInputs.attr(Attrib::kVertexAttrs));
+ }
+ if (fColorTimesRectCoverage.vsOut() || fRectCoverage.vsOut()) {
+ v->codeAppendf("rectCoverage = 1.0;");
+ }
+}
+
+void GLSLInstanceProcessor::BackendCoverage::adjustRRectVertices(GrGLSLVertexBuilder* v) {
+ // We try to let the AA borders line up with the arc edges on their particular side, but we
+ // can't allow them to get closer than one half pixel to the edge or they might overlap with
+ // their neighboring border.
+ v->codeAppend("vec2 innerEdge = max(1.0 - bloat, vec2(0));");
+ v->codeAppend ("vec2 borderEdge = cornerSign * clamp(1.0 - radii, -innerEdge, innerEdge);");
+ // 0.5 is a special value that indicates this vertex is an arc edge.
+ v->codeAppendf("if (abs(%s.x) == 0.5)"
+ "bloatedShapeCoords.x = borderEdge.x;", fInputs.attr(Attrib::kShapeCoords));
+ v->codeAppendf("if (abs(%s.y) == 0.5)"
+ "bloatedShapeCoords.y = borderEdge.y;", fInputs.attr(Attrib::kShapeCoords));
+
+ // Adjust the interior border vertices to make the border one pixel wide. 0.75 is a special
+ // value to indicate these points.
+ v->codeAppendf("if (abs(%s.x) == 0.75) "
+ "bloatedShapeCoords.x = cornerSign.x * innerEdge.x;",
+ fInputs.attr(Attrib::kShapeCoords));
+ v->codeAppendf("if (abs(%s.y) == 0.75) "
+ "bloatedShapeCoords.y = cornerSign.y * innerEdge.y;",
+ fInputs.attr(Attrib::kShapeCoords));
+}
+
+void GLSLInstanceProcessor::BackendCoverage::onSetupRRect(GrGLSLVertexBuilder* v) {
+ // The geometry is laid out in such a way that rectCoverage will be 0 and 1 on the vertices, but
+ // we still need to recompute this value because when the rrect gets thinner than one pixel, the
+ // interior edge of the border will necessarily clamp, and we need to match the AA behavior of
+ // the arc segments (i.e. distance from bloated edge only; ignoring the fact that the pixel
+ // actully has less coverage because it's not completely inside the opposite edge.)
+ v->codeAppend("vec2 d = shapeHalfSize + 0.5 - abs(bloatedShapeCoords) * shapeHalfSize;");
+ v->codeAppend("rectCoverage = min(d.x, d.y);");
+
+ SkASSERT(!fShapeIsCircle);
+ // The AA border does not get closer than one half pixel to the edge of the rect, so to get a
+ // smooth transition from flat edge to arc, we don't allow the radii to be smaller than one half
+ // pixel. (We don't worry about the transition on the opposite side when a radius is so large
+ // that the border clamped on that side.)
+ v->codeAppendf("vec2 clampedRadii = max(radii, bloat);");
+ v->codeAppendf("%s = (cornerSign * bloatedShapeCoords + clampedRadii - vec2(1)) * "
+ "shapeHalfSize;", fEllipseCoords.vsOut());
+ v->codeAppendf("%s = 1.0 / (clampedRadii * clampedRadii * shapeHalfSize * shapeHalfSize);",
+ fEllipseName.vsOut());
+}
+
+void GLSLInstanceProcessor::BackendCoverage::onInitInnerShape(GrGLSLVaryingHandler* varyingHandler,
+ GrGLSLVertexBuilder* v) {
+ v->codeAppend("vec2 innerShapeHalfSize = shapeHalfSize / outer2Inner.xy;");
+
+ if (kOval_ShapeFlag == fBatchInfo.fInnerShapeTypes) {
+ varyingHandler->addVarying("innerEllipseCoords", &fInnerEllipseCoords,
+ kMedium_GrSLPrecision);
+ varyingHandler->addFlatVarying("innerEllipseName", &fInnerEllipseName,
+ kMedium_GrSLPrecision);
+ } else {
+ varyingHandler->addVarying("distanceToInnerEdge", &fDistanceToInnerEdge,
+ kMedium_GrSLPrecision);
+ varyingHandler->addFlatVarying("innerShapeBloatedHalfSize", &fInnerShapeBloatedHalfSize,
+ kMedium_GrSLPrecision);
+ if (kRect_ShapeFlag != fBatchInfo.fInnerShapeTypes) {
+ varyingHandler->addVarying("innerShapeCoords", &fInnerShapeCoords, kHigh_GrSLPrecision);
+ varyingHandler->addFlatVarying("innerEllipseName", &fInnerEllipseName,
+ kMedium_GrSLPrecision);
+ varyingHandler->addFlatVarying("innerRRect", &fInnerRRect, kHigh_GrSLPrecision);
+ }
+ }
+}
+
+void GLSLInstanceProcessor::BackendCoverage::setupInnerRect(GrGLSLVertexBuilder* v) {
+ if (fInnerRRect.vsOut()) {
+ // The fragment shader will generalize every inner shape as a round rect. Since this one
+ // is a rect, we simply emit bogus parameters for the round rect (effectively negative
+ // radii) that ensure the fragment shader always takes the "emitRect" codepath.
+ v->codeAppendf("%s.xy = abs(outer2Inner.xy) * (1.0 + bloat) + abs(outer2Inner.zw);",
+ fInnerRRect.vsOut());
+ }
+}
+
+void GLSLInstanceProcessor::BackendCoverage::setupInnerOval(GrGLSLVertexBuilder* v) {
+ v->codeAppendf("%s = 1.0 / (innerShapeHalfSize * innerShapeHalfSize);",
+ fInnerEllipseName.vsOut());
+ if (fInnerEllipseCoords.vsOut()) {
+ v->codeAppendf("%s = innerShapeCoords * innerShapeHalfSize;", fInnerEllipseCoords.vsOut());
+ }
+ if (fInnerRRect.vsOut()) {
+ v->codeAppendf("%s = vec4(0, 0, innerShapeHalfSize);", fInnerRRect.vsOut());
+ }
+}
+
+void GLSLInstanceProcessor::BackendCoverage::onSetupInnerRRect(GrGLSLVertexBuilder* v) {
+ // The distance to ellipse formula doesn't work well when the radii are less than half a pixel.
+ v->codeAppend ("innerRadii = max(innerRadii, bloat);");
+ v->codeAppendf("%s = 1.0 / (innerRadii * innerRadii * innerShapeHalfSize * "
+ "innerShapeHalfSize);",
+ fInnerEllipseName.vsOut());
+ v->codeAppendf("%s = vec4(1.0 - innerRadii, innerShapeHalfSize);", fInnerRRect.vsOut());
+}
+
+void GLSLInstanceProcessor::BackendCoverage::onEmitCode(GrGLSLVertexBuilder* v,
+ GrGLSLPPFragmentBuilder* f,
+ const char* outCoverage,
+ const char* outColor) {
+ if (fColorTimesRectCoverage.vsOut()) {
+ SkASSERT(!fRectCoverage.vsOut());
+ v->codeAppendf("%s = %s * rectCoverage;",
+ fColorTimesRectCoverage.vsOut(), fInputs.attr(Attrib::kColor));
+ }
+ if (fRectCoverage.vsOut()) {
+ SkASSERT(!fColorTimesRectCoverage.vsOut());
+ v->codeAppendf("%s = rectCoverage;", fRectCoverage.vsOut());
+ }
+
+ SkString coverage("float coverage");
+ if (f->getProgramBuilder()->glslCaps()->usesPrecisionModifiers()) {
+ coverage.prependf("lowp ");
+ }
+ if (fBatchInfo.fInnerShapeTypes || (!fTweakAlphaForCoverage && fTriangleIsArc.fsIn())) {
+ f->codeAppendf("%s;", coverage.c_str());
+ coverage = "coverage";
+ }
+ if (fTriangleIsArc.fsIn()) {
+ f->codeAppendf("if (%s == 0) {", fTriangleIsArc.fsIn());
+ this->emitRect(f, coverage.c_str(), outColor);
+ f->codeAppend ("} else {");
+ if (fShapeIsCircle) {
+ this->emitCircle(f, coverage.c_str());
+ } else {
+ bool ellipseCoordsMayBeNegative = SkToBool(fBatchInfo.fShapeTypes & kOval_ShapeFlag);
+ this->emitArc(f, fEllipseCoords.fsIn(), fEllipseName.fsIn(),
+ true /*ellipseCoordsNeedClamp*/, ellipseCoordsMayBeNegative,
+ coverage.c_str());
+ }
+ if (fTweakAlphaForCoverage) {
+ f->codeAppendf("%s = %s * coverage;", outColor, fColor.fsIn());
+ }
+ f->codeAppend ("}");
+ } else {
+ this->emitRect(f, coverage.c_str(), outColor);
+ }
+
+ if (fBatchInfo.fInnerShapeTypes) {
+ f->codeAppendf("// Inner shape.\n");
+ SkString innerCoverageDecl("float innerCoverage");
+ if (f->getProgramBuilder()->glslCaps()->usesPrecisionModifiers()) {
+ innerCoverageDecl.prependf("lowp ");
+ }
+ if (kOval_ShapeFlag == fBatchInfo.fInnerShapeTypes) {
+ this->emitArc(f, fInnerEllipseCoords.fsIn(), fInnerEllipseName.fsIn(),
+ true /*ellipseCoordsNeedClamp*/, true /*ellipseCoordsMayBeNegative*/,
+ innerCoverageDecl.c_str());
+ } else {
+ v->codeAppendf("%s = innerShapeCoords * innerShapeHalfSize;",
+ fDistanceToInnerEdge.vsOut());
+ v->codeAppendf("%s = innerShapeHalfSize + 0.5;", fInnerShapeBloatedHalfSize.vsOut());
+
+ if (kRect_ShapeFlag == fBatchInfo.fInnerShapeTypes) {
+ this->emitInnerRect(f, innerCoverageDecl.c_str());
+ } else {
+ f->codeAppendf("%s = 0.0;", innerCoverageDecl.c_str());
+ f->codeAppendf("vec2 distanceToArcEdge = abs(%s) - %s.xy;",
+ fInnerShapeCoords.fsIn(), fInnerRRect.fsIn());
+ f->codeAppend ("if (any(lessThan(distanceToArcEdge, vec2(1e-5)))) {");
+ this->emitInnerRect(f, "innerCoverage");
+ f->codeAppend ("} else {");
+ f->codeAppendf( "vec2 ellipseCoords = distanceToArcEdge * %s.zw;",
+ fInnerRRect.fsIn());
+ this->emitArc(f, "ellipseCoords", fInnerEllipseName.fsIn(),
+ false /*ellipseCoordsNeedClamp*/,
+ false /*ellipseCoordsMayBeNegative*/, "innerCoverage");
+ f->codeAppend ("}");
+ }
+ }
+ f->codeAppendf("%s = vec4(max(coverage - innerCoverage, 0.0));", outCoverage);
+ } else if (!fTweakAlphaForCoverage) {
+ f->codeAppendf("%s = vec4(coverage);", outCoverage);
+ }
+}
+
+void GLSLInstanceProcessor::BackendCoverage::emitRect(GrGLSLPPFragmentBuilder* f,
+ const char* outCoverage,
+ const char* outColor) {
+ if (fColorTimesRectCoverage.fsIn()) {
+ f->codeAppendf("%s = %s;", outColor, fColorTimesRectCoverage.fsIn());
+ } else if (fTweakAlphaForCoverage) {
+ // We are drawing just ovals. The interior rect always has 100% coverage.
+ f->codeAppendf("%s = %s;", outColor, fColor.fsIn());
+ } else if (fRectCoverage.fsIn()) {
+ f->codeAppendf("%s = %s;", outCoverage, fRectCoverage.fsIn());
+ } else {
+ f->codeAppendf("%s = 1.0;", outCoverage);
+ }
+}
+
+void GLSLInstanceProcessor::BackendCoverage::emitCircle(GrGLSLPPFragmentBuilder* f,
+ const char* outCoverage) {
+ // TODO: circleCoords = max(circleCoords, 0) if we decide to do this optimization on rrects.
+ SkASSERT(!(kRRect_ShapesMask & fBatchInfo.fShapeTypes));
+ f->codeAppendf("float distanceToEdge = %s - length(%s);",
+ fBloatedRadius.fsIn(), fEllipseCoords.fsIn());
+ f->codeAppendf("%s = clamp(distanceToEdge, 0.0, 1.0);", outCoverage);
+}
+
+void GLSLInstanceProcessor::BackendCoverage::emitArc(GrGLSLPPFragmentBuilder* f,
+ const char* ellipseCoords,
+ const char* ellipseName,
+ bool ellipseCoordsNeedClamp,
+ bool ellipseCoordsMayBeNegative,
+ const char* outCoverage) {
+ SkASSERT(!ellipseCoordsMayBeNegative || ellipseCoordsNeedClamp);
+ if (ellipseCoordsNeedClamp) {
+ // This serves two purposes:
+ // - To restrict the arcs of rounded rects to their positive quadrants.
+ // - To avoid inversesqrt(0) in the ellipse formula.
+ if (ellipseCoordsMayBeNegative) {
+ f->codeAppendf("vec2 ellipseClampedCoords = max(abs(%s), vec2(1e-4));", ellipseCoords);
+ } else {
+ f->codeAppendf("vec2 ellipseClampedCoords = max(%s, vec2(1e-4));", ellipseCoords);
+ }
+ ellipseCoords = "ellipseClampedCoords";
+ }
+ // ellipseCoords are in pixel space and ellipseName is 1 / rx^2, 1 / ry^2.
+ f->codeAppendf("vec2 Z = %s * %s;", ellipseCoords, ellipseName);
+ // implicit is the evaluation of (x/rx)^2 + (y/ry)^2 - 1.
+ f->codeAppendf("float implicit = dot(Z, %s) - 1.0;", ellipseCoords);
+ // gradDot is the squared length of the gradient of the implicit.
+ f->codeAppendf("float gradDot = 4.0 * dot(Z, Z);");
+ f->appendPrecisionModifier(kLow_GrSLPrecision);
+ f->codeAppend ("float approxDist = implicit * inversesqrt(gradDot);");
+ f->codeAppendf("%s = clamp(0.5 - approxDist, 0.0, 1.0);", outCoverage);
+}
+
+void GLSLInstanceProcessor::BackendCoverage::emitInnerRect(GrGLSLPPFragmentBuilder* f,
+ const char* outCoverage) {
+ f->appendPrecisionModifier(kLow_GrSLPrecision);
+ f->codeAppendf("vec2 c = %s - abs(%s);",
+ fInnerShapeBloatedHalfSize.fsIn(), fDistanceToInnerEdge.fsIn());
+ f->codeAppendf("%s = clamp(min(c.x, c.y), 0.0, 1.0);", outCoverage);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+class GLSLInstanceProcessor::BackendMultisample : public Backend {
+public:
+ BackendMultisample(BatchInfo batchInfo, const VertexInputs& inputs, int effectiveSampleCnt)
+ : INHERITED(batchInfo, inputs),
+ fEffectiveSampleCnt(effectiveSampleCnt),
+ fShapeCoords(kVec2f_GrSLType),
+ fShapeInverseMatrix(kMat22f_GrSLType),
+ fFragShapeHalfSpan(kVec2f_GrSLType),
+ fArcTest(kVec2f_GrSLType),
+ fArcInverseMatrix(kMat22f_GrSLType),
+ fFragArcHalfSpan(kVec2f_GrSLType),
+ fEarlyAccept(kInt_GrSLType),
+ fInnerShapeInverseMatrix(kMat22f_GrSLType),
+ fFragInnerShapeHalfSpan(kVec2f_GrSLType) {
+ fRectTrianglesMaySplit = fBatchInfo.fHasPerspective;
+ fNeedsNeighborRadii = this->isMixedSampled() && !fBatchInfo.fHasPerspective;
+ }
+
+private:
+ bool isMixedSampled() const { return AntialiasMode::kMixedSamples == fBatchInfo.fAntialiasMode; }
+
+ void onInit(GrGLSLVaryingHandler*, GrGLSLVertexBuilder*) override;
+ void setupRect(GrGLSLVertexBuilder*) override;
+ void setupOval(GrGLSLVertexBuilder*) override;
+ void adjustRRectVertices(GrGLSLVertexBuilder*) override;
+ void onSetupRRect(GrGLSLVertexBuilder*) override;
+
+ void onInitInnerShape(GrGLSLVaryingHandler*, GrGLSLVertexBuilder*) override;
+ void setupInnerRect(GrGLSLVertexBuilder*) override;
+ void setupInnerOval(GrGLSLVertexBuilder*) override;
+ void onSetupInnerRRect(GrGLSLVertexBuilder*) override;
+
+ void onEmitCode(GrGLSLVertexBuilder*, GrGLSLPPFragmentBuilder*, const char*,
+ const char*) override;
+
+ struct EmitShapeCoords {
+ const GrGLSLVarying* fVarying;
+ const char* fInverseMatrix;
+ const char* fFragHalfSpan;
+ };
+
+ struct EmitShapeOpts {
+ bool fIsTightGeometry;
+ bool fResolveMixedSamples;
+ bool fInvertCoverage;
+ };
+
+ void emitRect(GrGLSLPPFragmentBuilder*, const EmitShapeCoords&, const EmitShapeOpts&);
+ void emitArc(GrGLSLPPFragmentBuilder*, const EmitShapeCoords&, bool coordsMayBeNegative,
+ bool clampCoords, const EmitShapeOpts&);
+ void emitSimpleRRect(GrGLSLPPFragmentBuilder*, const EmitShapeCoords&, const char* rrect,
+ const EmitShapeOpts&);
+ void interpolateAtSample(GrGLSLPPFragmentBuilder*, const GrGLSLVarying&, const char* sampleIdx,
+ const char* interpolationMatrix);
+ void acceptOrRejectWholeFragment(GrGLSLPPFragmentBuilder*, bool inside, const EmitShapeOpts&);
+ void acceptCoverageMask(GrGLSLPPFragmentBuilder*, const char* shapeMask, const EmitShapeOpts&,
+ bool maybeSharedEdge = true);
+
+ int fEffectiveSampleCnt;
+ bool fRectTrianglesMaySplit;
+ GrGLSLVertToFrag fShapeCoords;
+ GrGLSLVertToFrag fShapeInverseMatrix;
+ GrGLSLVertToFrag fFragShapeHalfSpan;
+ GrGLSLVertToFrag fArcTest;
+ GrGLSLVertToFrag fArcInverseMatrix;
+ GrGLSLVertToFrag fFragArcHalfSpan;
+ GrGLSLVertToFrag fEarlyAccept;
+ GrGLSLVertToFrag fInnerShapeInverseMatrix;
+ GrGLSLVertToFrag fFragInnerShapeHalfSpan;
+ SkString fSquareFun;
+
+ typedef Backend INHERITED;
+};
+
+void GLSLInstanceProcessor::BackendMultisample::onInit(GrGLSLVaryingHandler* varyingHandler,
+ GrGLSLVertexBuilder* v) {
+ if (!this->isMixedSampled()) {
+ if (kRect_ShapeFlag != fBatchInfo.fShapeTypes) {
+ varyingHandler->addFlatVarying("triangleIsArc", &fTriangleIsArc,
+ kHigh_GrSLPrecision);
+ varyingHandler->addVarying("arcCoords", &fArcCoords, kHigh_GrSLPrecision);
+ if (!fBatchInfo.fHasPerspective) {
+ varyingHandler->addFlatVarying("arcInverseMatrix", &fArcInverseMatrix,
+ kHigh_GrSLPrecision);
+ varyingHandler->addFlatVarying("fragArcHalfSpan", &fFragArcHalfSpan,
+ kHigh_GrSLPrecision);
+ }
+ } else if (!fBatchInfo.fInnerShapeTypes) {
+ return;
+ }
+ } else {
+ varyingHandler->addVarying("shapeCoords", &fShapeCoords, kHigh_GrSLPrecision);
+ if (!fBatchInfo.fHasPerspective) {
+ varyingHandler->addFlatVarying("shapeInverseMatrix", &fShapeInverseMatrix,
+ kHigh_GrSLPrecision);
+ varyingHandler->addFlatVarying("fragShapeHalfSpan", &fFragShapeHalfSpan,
+ kHigh_GrSLPrecision);
+ }
+ if (fBatchInfo.fShapeTypes & kRRect_ShapesMask) {
+ varyingHandler->addVarying("arcCoords", &fArcCoords, kHigh_GrSLPrecision);
+ varyingHandler->addVarying("arcTest", &fArcTest, kHigh_GrSLPrecision);
+ if (!fBatchInfo.fHasPerspective) {
+ varyingHandler->addFlatVarying("arcInverseMatrix", &fArcInverseMatrix,
+ kHigh_GrSLPrecision);
+ varyingHandler->addFlatVarying("fragArcHalfSpan", &fFragArcHalfSpan,
+ kHigh_GrSLPrecision);
+ }
+ } else if (fBatchInfo.fShapeTypes & kOval_ShapeFlag) {
+ fArcCoords = fShapeCoords;
+ fArcInverseMatrix = fShapeInverseMatrix;
+ fFragArcHalfSpan = fFragShapeHalfSpan;
+ if (fBatchInfo.fShapeTypes & kRect_ShapeFlag) {
+ varyingHandler->addFlatVarying("triangleIsArc", &fTriangleIsArc,
+ kHigh_GrSLPrecision);
+ }
+ }
+ if (kRect_ShapeFlag != fBatchInfo.fShapeTypes) {
+ v->definef("SAMPLE_MASK_ALL", "0x%x", (1 << fEffectiveSampleCnt) - 1);
+ varyingHandler->addFlatVarying("earlyAccept", &fEarlyAccept, kHigh_GrSLPrecision);
+ }
+ }
+ if (!fBatchInfo.fHasPerspective) {
+ v->codeAppend("mat2 shapeInverseMatrix = inverse(mat2(shapeMatrix));");
+ v->codeAppend("vec2 fragShapeSpan = abs(vec4(shapeInverseMatrix).xz) + "
+ "abs(vec4(shapeInverseMatrix).yw);");
+ }
+}
+
+void GLSLInstanceProcessor::BackendMultisample::setupRect(GrGLSLVertexBuilder* v) {
+ if (fShapeCoords.vsOut()) {
+ v->codeAppendf("%s = %s;", fShapeCoords.vsOut(), this->outShapeCoords());
+ }
+ if (fShapeInverseMatrix.vsOut()) {
+ v->codeAppendf("%s = shapeInverseMatrix;", fShapeInverseMatrix.vsOut());
+ }
+ if (fFragShapeHalfSpan.vsOut()) {
+ v->codeAppendf("%s = 0.5 * fragShapeSpan;", fFragShapeHalfSpan.vsOut());
+ }
+ if (fArcTest.vsOut()) {
+ // Pick a value that is not > 0.
+ v->codeAppendf("%s = vec2(0);", fArcTest.vsOut());
+ }
+ if (fTriangleIsArc.vsOut()) {
+ v->codeAppendf("%s = 0;", fTriangleIsArc.vsOut());
+ }
+ if (fEarlyAccept.vsOut()) {
+ v->codeAppendf("%s = SAMPLE_MASK_ALL;", fEarlyAccept.vsOut());
+ }
+}
+
+void GLSLInstanceProcessor::BackendMultisample::setupOval(GrGLSLVertexBuilder* v) {
+ v->codeAppendf("%s = abs(%s);", fArcCoords.vsOut(), this->outShapeCoords());
+ if (fArcInverseMatrix.vsOut()) {
+ v->codeAppendf("vec2 s = sign(%s);", this->outShapeCoords());
+ v->codeAppendf("%s = shapeInverseMatrix * mat2(s.x, 0, 0 , s.y);",
+ fArcInverseMatrix.vsOut());
+ }
+ if (fFragArcHalfSpan.vsOut()) {
+ v->codeAppendf("%s = 0.5 * fragShapeSpan;", fFragArcHalfSpan.vsOut());
+ }
+ if (fArcTest.vsOut()) {
+ // Pick a value that is > 0.
+ v->codeAppendf("%s = vec2(1);", fArcTest.vsOut());
+ }
+ if (fTriangleIsArc.vsOut()) {
+ if (!this->isMixedSampled()) {
+ v->codeAppendf("%s = %s & 1;",
+ fTriangleIsArc.vsOut(), fInputs.attr(Attrib::kVertexAttrs));
+ } else {
+ v->codeAppendf("%s = 1;", fTriangleIsArc.vsOut());
+ }
+ }
+ if (fEarlyAccept.vsOut()) {
+ v->codeAppendf("%s = ~%s & SAMPLE_MASK_ALL;",
+ fEarlyAccept.vsOut(), fInputs.attr(Attrib::kVertexAttrs));
+ }
+}
+
+void GLSLInstanceProcessor::BackendMultisample::adjustRRectVertices(GrGLSLVertexBuilder* v) {
+ if (!this->isMixedSampled()) {
+ INHERITED::adjustRRectVertices(v);
+ return;
+ }
+
+ if (!fBatchInfo.fHasPerspective) {
+ // For the mixed samples algorithm it's best to bloat the corner triangles a bit so that
+ // more of the pixels that cross into the arc region are completely inside the shared edges.
+ // We also snap to a regular rect if the radii shrink smaller than a pixel.
+ v->codeAppend ("vec2 midpt = 0.5 * (neighborRadii - radii);");
+ v->codeAppend ("vec2 cornerSize = any(lessThan(radii, fragShapeSpan)) ? "
+ "vec2(0) : min(radii + 0.5 * fragShapeSpan, 1.0 - midpt);");
+ } else {
+ // TODO: We could still bloat the corner triangle in the perspective case; we would just
+ // need to find the screen-space derivative of shape coords at this particular point.
+ v->codeAppend ("vec2 cornerSize = any(lessThan(radii, vec2(1e-3))) ? vec2(0) : radii;");
+ }
+
+ v->codeAppendf("if (abs(%s.x) == 0.5)"
+ "%s.x = cornerSign.x * (1.0 - cornerSize.x);",
+ fInputs.attr(Attrib::kShapeCoords), fModifiedShapeCoords);
+ v->codeAppendf("if (abs(%s.y) == 0.5)"
+ "%s.y = cornerSign.y * (1.0 - cornerSize.y);",
+ fInputs.attr(Attrib::kShapeCoords), fModifiedShapeCoords);
+}
+
+void GLSLInstanceProcessor::BackendMultisample::onSetupRRect(GrGLSLVertexBuilder* v) {
+ if (fShapeCoords.vsOut()) {
+ v->codeAppendf("%s = %s;", fShapeCoords.vsOut(), this->outShapeCoords());
+ }
+ if (fShapeInverseMatrix.vsOut()) {
+ v->codeAppendf("%s = shapeInverseMatrix;", fShapeInverseMatrix.vsOut());
+ }
+ if (fFragShapeHalfSpan.vsOut()) {
+ v->codeAppendf("%s = 0.5 * fragShapeSpan;", fFragShapeHalfSpan.vsOut());
+ }
+ if (fArcInverseMatrix.vsOut()) {
+ v->codeAppend ("vec2 s = cornerSign / radii;");
+ v->codeAppendf("%s = shapeInverseMatrix * mat2(s.x, 0, 0, s.y);",
+ fArcInverseMatrix.vsOut());
+ }
+ if (fFragArcHalfSpan.vsOut()) {
+ v->codeAppendf("%s = 0.5 * (abs(vec4(%s).xz) + abs(vec4(%s).yw));",
+ fFragArcHalfSpan.vsOut(), fArcInverseMatrix.vsOut(),
+ fArcInverseMatrix.vsOut());
+ }
+ if (fArcTest.vsOut()) {
+ // The interior triangles are laid out as a fan. fArcTest is both distances from shared
+ // edges of a fan triangle to a point within that triangle. fArcTest is used to check if a
+ // fragment is too close to either shared edge, in which case we point sample the shape as a
+ // rect at that point in order to guarantee the mixed samples discard logic works correctly.
+ v->codeAppendf("%s = (cornerSize == vec2(0)) ? vec2(0) : "
+ "cornerSign * %s * mat2(1, cornerSize.x - 1.0, cornerSize.y - 1.0, 1);",
+ fArcTest.vsOut(), fModifiedShapeCoords);
+ if (!fBatchInfo.fHasPerspective) {
+ // Shift the point at which distances to edges are measured from the center of the pixel
+ // to the corner. This way the sign of fArcTest will quickly tell us whether a pixel
+ // is completely inside the shared edge. Perspective mode will accomplish this same task
+ // by finding the derivatives in the fragment shader.
+ v->codeAppendf("%s -= 0.5 * (fragShapeSpan.yx * abs(radii - 1.0) + fragShapeSpan);",
+ fArcTest.vsOut());
+ }
+ }
+ if (fEarlyAccept.vsOut()) {
+ SkASSERT(this->isMixedSampled());
+ v->codeAppendf("%s = all(equal(vec2(1), abs(%s))) ? 0 : SAMPLE_MASK_ALL;",
+ fEarlyAccept.vsOut(), fInputs.attr(Attrib::kShapeCoords));
+ }
+}
+
+void
+GLSLInstanceProcessor::BackendMultisample::onInitInnerShape(GrGLSLVaryingHandler* varyingHandler,
+ GrGLSLVertexBuilder* v) {
+ varyingHandler->addVarying("innerShapeCoords", &fInnerShapeCoords, kHigh_GrSLPrecision);
+ if (kOval_ShapeFlag != fBatchInfo.fInnerShapeTypes &&
+ kRect_ShapeFlag != fBatchInfo.fInnerShapeTypes) {
+ varyingHandler->addFlatVarying("innerRRect", &fInnerRRect, kHigh_GrSLPrecision);
+ }
+ if (!fBatchInfo.fHasPerspective) {
+ varyingHandler->addFlatVarying("innerShapeInverseMatrix", &fInnerShapeInverseMatrix,
+ kHigh_GrSLPrecision);
+ v->codeAppendf("%s = shapeInverseMatrix * mat2(outer2Inner.x, 0, 0, outer2Inner.y);",
+ fInnerShapeInverseMatrix.vsOut());
+ varyingHandler->addFlatVarying("fragInnerShapeHalfSpan", &fFragInnerShapeHalfSpan,
+ kHigh_GrSLPrecision);
+ v->codeAppendf("%s = 0.5 * fragShapeSpan * outer2Inner.xy;",
+ fFragInnerShapeHalfSpan.vsOut());
+ }
+}
+
+void GLSLInstanceProcessor::BackendMultisample::setupInnerRect(GrGLSLVertexBuilder* v) {
+ if (fInnerRRect.vsOut()) {
+ // The fragment shader will generalize every inner shape as a round rect. Since this one
+ // is a rect, we simply emit bogus parameters for the round rect (negative radii) that
+ // ensure the fragment shader always takes the "sample as rect" codepath.
+ v->codeAppendf("%s = vec4(2.0 * (inner.zw - inner.xy) / (outer.zw - outer.xy), vec2(0));",
+ fInnerRRect.vsOut());
+ }
+}
+
+void GLSLInstanceProcessor::BackendMultisample::setupInnerOval(GrGLSLVertexBuilder* v) {
+ if (fInnerRRect.vsOut()) {
+ v->codeAppendf("%s = vec4(0, 0, 1, 1);", fInnerRRect.vsOut());
+ }
+}
+
+void GLSLInstanceProcessor::BackendMultisample::onSetupInnerRRect(GrGLSLVertexBuilder* v) {
+ // Avoid numeric instability by not allowing the inner radii to get smaller than 1/10th pixel.
+ if (fFragInnerShapeHalfSpan.vsOut()) {
+ v->codeAppendf("innerRadii = max(innerRadii, 2e-1 * %s);", fFragInnerShapeHalfSpan.vsOut());
+ } else {
+ v->codeAppend ("innerRadii = max(innerRadii, vec2(1e-4));");
+ }
+ v->codeAppendf("%s = vec4(1.0 - innerRadii, 1.0 / innerRadii);", fInnerRRect.vsOut());
+}
+
+void GLSLInstanceProcessor::BackendMultisample::onEmitCode(GrGLSLVertexBuilder*,
+ GrGLSLPPFragmentBuilder* f,
+ const char*, const char*) {
+ f->define("SAMPLE_COUNT", fEffectiveSampleCnt);
+ if (this->isMixedSampled()) {
+ f->definef("SAMPLE_MASK_ALL", "0x%x", (1 << fEffectiveSampleCnt) - 1);
+ f->definef("SAMPLE_MASK_MSB", "0x%x", 1 << (fEffectiveSampleCnt - 1));
+ }
+
+ if (kRect_ShapeFlag != (fBatchInfo.fShapeTypes | fBatchInfo.fInnerShapeTypes)) {
+ GrGLSLShaderVar x("x", kVec2f_GrSLType, GrGLSLShaderVar::kNonArray, kHigh_GrSLPrecision);
+ f->emitFunction(kFloat_GrSLType, "square", 1, &x, "return dot(x, x);", &fSquareFun);
+ }
+
+ EmitShapeCoords shapeCoords;
+ shapeCoords.fVarying = &fShapeCoords;
+ shapeCoords.fInverseMatrix = fShapeInverseMatrix.fsIn();
+ shapeCoords.fFragHalfSpan = fFragShapeHalfSpan.fsIn();
+
+ EmitShapeCoords arcCoords;
+ arcCoords.fVarying = &fArcCoords;
+ arcCoords.fInverseMatrix = fArcInverseMatrix.fsIn();
+ arcCoords.fFragHalfSpan = fFragArcHalfSpan.fsIn();
+ bool clampArcCoords = this->isMixedSampled() && (fBatchInfo.fShapeTypes & kRRect_ShapesMask);
+
+ EmitShapeOpts opts;
+ opts.fIsTightGeometry = true;
+ opts.fResolveMixedSamples = this->isMixedSampled();
+ opts.fInvertCoverage = false;
+
+ if (fBatchInfo.fHasPerspective && fBatchInfo.fInnerShapeTypes) {
+ // This determines if the fragment should consider the inner shape in its sample mask.
+ // We take the derivative early in case discards may occur before we get to the inner shape.
+ f->appendPrecisionModifier(kHigh_GrSLPrecision);
+ f->codeAppendf("vec2 fragInnerShapeApproxHalfSpan = 0.5 * fwidth(%s);",
+ fInnerShapeCoords.fsIn());
+ }
+
+ if (!this->isMixedSampled()) {
+ SkASSERT(!fArcTest.fsIn());
+ if (fTriangleIsArc.fsIn()) {
+ f->codeAppendf("if (%s != 0) {", fTriangleIsArc.fsIn());
+ this->emitArc(f, arcCoords, false, clampArcCoords, opts);
+
+ f->codeAppend ("}");
+ }
+ } else {
+ const char* arcTest = fArcTest.fsIn();
+ SkASSERT(arcTest);
+ if (fBatchInfo.fHasPerspective) {
+ // The non-perspective version accounts for fwith() in the vertex shader.
+ // We make sure to take the derivative here, before a neighbor pixel may early accept.
+ f->enableFeature(GrGLSLPPFragmentBuilder::kStandardDerivatives_GLSLFeature);
+ f->appendPrecisionModifier(kHigh_GrSLPrecision);
+ f->codeAppendf("vec2 arcTest = %s - 0.5 * fwidth(%s);",
+ fArcTest.fsIn(), fArcTest.fsIn());
+ arcTest = "arcTest";
+ }
+ const char* earlyAccept = fEarlyAccept.fsIn() ? fEarlyAccept.fsIn() : "SAMPLE_MASK_ALL";
+ f->codeAppendf("if (gl_SampleMaskIn[0] == %s) {", earlyAccept);
+ f->overrideSampleCoverage(earlyAccept);
+ f->codeAppend ("} else {");
+ if (arcTest) {
+ // At this point, if the sample mask is all set it means we are inside an arc triangle.
+ f->codeAppendf("if (gl_SampleMaskIn[0] == SAMPLE_MASK_ALL || "
+ "all(greaterThan(%s, vec2(0)))) {", arcTest);
+ this->emitArc(f, arcCoords, false, clampArcCoords, opts);
+ f->codeAppend ("} else {");
+ this->emitRect(f, shapeCoords, opts);
+ f->codeAppend ("}");
+ } else if (fTriangleIsArc.fsIn()) {
+ f->codeAppendf("if (%s == 0) {", fTriangleIsArc.fsIn());
+ this->emitRect(f, shapeCoords, opts);
+ f->codeAppend ("} else {");
+ this->emitArc(f, arcCoords, false, clampArcCoords, opts);
+ f->codeAppend ("}");
+ } else if (fBatchInfo.fShapeTypes == kOval_ShapeFlag) {
+ this->emitArc(f, arcCoords, false, clampArcCoords, opts);
+ } else {
+ SkASSERT(fBatchInfo.fShapeTypes == kRect_ShapeFlag);
+ this->emitRect(f, shapeCoords, opts);
+ }
+ f->codeAppend ("}");
+ }
+
+ if (fBatchInfo.fInnerShapeTypes) {
+ f->codeAppendf("// Inner shape.\n");
+
+ EmitShapeCoords innerShapeCoords;
+ innerShapeCoords.fVarying = &fInnerShapeCoords;
+ if (!fBatchInfo.fHasPerspective) {
+ innerShapeCoords.fInverseMatrix = fInnerShapeInverseMatrix.fsIn();
+ innerShapeCoords.fFragHalfSpan = fFragInnerShapeHalfSpan.fsIn();
+ }
+
+ EmitShapeOpts innerOpts;
+ innerOpts.fIsTightGeometry = false;
+ innerOpts.fResolveMixedSamples = false; // Mixed samples are resolved in the outer shape.
+ innerOpts.fInvertCoverage = true;
+
+ if (kOval_ShapeFlag == fBatchInfo.fInnerShapeTypes) {
+ this->emitArc(f, innerShapeCoords, true, false, innerOpts);
+ } else {
+ f->codeAppendf("if (all(lessThan(abs(%s), 1.0 + %s))) {", fInnerShapeCoords.fsIn(),
+ !fBatchInfo.fHasPerspective ? innerShapeCoords.fFragHalfSpan
+ : "fragInnerShapeApproxHalfSpan"); // Above.
+ if (kRect_ShapeFlag == fBatchInfo.fInnerShapeTypes) {
+ this->emitRect(f, innerShapeCoords, innerOpts);
+ } else {
+ this->emitSimpleRRect(f, innerShapeCoords, fInnerRRect.fsIn(), innerOpts);
+ }
+ f->codeAppend ("}");
+ }
+ }
+}
+
+void GLSLInstanceProcessor::BackendMultisample::emitRect(GrGLSLPPFragmentBuilder* f,
+ const EmitShapeCoords& coords,
+ const EmitShapeOpts& opts) {
+ // Full MSAA doesn't need to do anything to draw a rect.
+ SkASSERT(!opts.fIsTightGeometry || opts.fResolveMixedSamples);
+ if (coords.fFragHalfSpan) {
+ f->codeAppendf("if (all(lessThanEqual(abs(%s), 1.0 - %s))) {",
+ coords.fVarying->fsIn(), coords.fFragHalfSpan);
+ // The entire pixel is inside the rect.
+ this->acceptOrRejectWholeFragment(f, true, opts);
+ f->codeAppend ("} else ");
+ if (opts.fIsTightGeometry && !fRectTrianglesMaySplit) {
+ f->codeAppendf("if (any(lessThan(abs(%s), 1.0 - %s))) {",
+ coords.fVarying->fsIn(), coords.fFragHalfSpan);
+ // The pixel falls on an edge of the rectangle and is known to not be on a shared edge.
+ this->acceptCoverageMask(f, "gl_SampleMaskIn[0]", opts, false);
+ f->codeAppend ("} else");
+ }
+ f->codeAppend ("{");
+ }
+ f->codeAppend ("int rectMask = 0;");
+ f->codeAppend ("for (int i = 0; i < SAMPLE_COUNT; i++) {");
+ f->appendPrecisionModifier(kHigh_GrSLPrecision);
+ f->codeAppend ( "vec2 pt = ");
+ this->interpolateAtSample(f, *coords.fVarying, "i", coords.fInverseMatrix);
+ f->codeAppend ( ";");
+ f->codeAppend ( "if (all(lessThan(abs(pt), vec2(1)))) rectMask |= (1 << i);");
+ f->codeAppend ("}");
+ this->acceptCoverageMask(f, "rectMask", opts);
+ if (coords.fFragHalfSpan) {
+ f->codeAppend ("}");
+ }
+}
+
+void GLSLInstanceProcessor::BackendMultisample::emitArc(GrGLSLPPFragmentBuilder* f,
+ const EmitShapeCoords& coords,
+ bool coordsMayBeNegative, bool clampCoords,
+ const EmitShapeOpts& opts) {
+ if (coords.fFragHalfSpan) {
+ SkString absArcCoords;
+ absArcCoords.printf(coordsMayBeNegative ? "abs(%s)" : "%s", coords.fVarying->fsIn());
+ if (clampCoords) {
+ f->codeAppendf("if (%s(max(%s + %s, vec2(0))) < 1.0) {",
+ fSquareFun.c_str(), absArcCoords.c_str(), coords.fFragHalfSpan);
+ } else {
+ f->codeAppendf("if (%s(%s + %s) < 1.0) {",
+ fSquareFun.c_str(), absArcCoords.c_str(), coords.fFragHalfSpan);
+ }
+ // The entire pixel is inside the arc.
+ this->acceptOrRejectWholeFragment(f, true, opts);
+ f->codeAppendf("} else if (%s(max(%s - %s, vec2(0))) >= 1.0) {",
+ fSquareFun.c_str(), absArcCoords.c_str(), coords.fFragHalfSpan);
+ // The entire pixel is outside the arc.
+ this->acceptOrRejectWholeFragment(f, false, opts);
+ f->codeAppend ("} else {");
+ }
+ f->codeAppend ( "int arcMask = 0;");
+ f->codeAppend ( "for (int i = 0; i < SAMPLE_COUNT; i++) {");
+ f->appendPrecisionModifier(kHigh_GrSLPrecision);
+ f->codeAppend ( "vec2 pt = ");
+ this->interpolateAtSample(f, *coords.fVarying, "i", coords.fInverseMatrix);
+ f->codeAppend ( ";");
+ if (clampCoords) {
+ SkASSERT(!coordsMayBeNegative);
+ f->codeAppend ( "pt = max(pt, vec2(0));");
+ }
+ f->codeAppendf( "if (%s(pt) < 1.0) arcMask |= (1 << i);", fSquareFun.c_str());
+ f->codeAppend ( "}");
+ this->acceptCoverageMask(f, "arcMask", opts);
+ if (coords.fFragHalfSpan) {
+ f->codeAppend ("}");
+ }
+}
+
+void GLSLInstanceProcessor::BackendMultisample::emitSimpleRRect(GrGLSLPPFragmentBuilder* f,
+ const EmitShapeCoords& coords,
+ const char* rrect,
+ const EmitShapeOpts& opts) {
+ f->appendPrecisionModifier(kHigh_GrSLPrecision);
+ f->codeAppendf("vec2 distanceToArcEdge = abs(%s) - %s.xy;", coords.fVarying->fsIn(), rrect);
+ f->codeAppend ("if (any(lessThan(distanceToArcEdge, vec2(0)))) {");
+ this->emitRect(f, coords, opts);
+ f->codeAppend ("} else {");
+ if (coords.fInverseMatrix && coords.fFragHalfSpan) {
+ f->appendPrecisionModifier(kHigh_GrSLPrecision);
+ f->codeAppendf("vec2 rrectCoords = distanceToArcEdge * %s.zw;", rrect);
+ f->appendPrecisionModifier(kHigh_GrSLPrecision);
+ f->codeAppendf("vec2 fragRRectHalfSpan = %s * %s.zw;", coords.fFragHalfSpan, rrect);
+ f->codeAppendf("if (%s(rrectCoords + fragRRectHalfSpan) <= 1.0) {", fSquareFun.c_str());
+ // The entire pixel is inside the round rect.
+ this->acceptOrRejectWholeFragment(f, true, opts);
+ f->codeAppendf("} else if (%s(max(rrectCoords - fragRRectHalfSpan, vec2(0))) >= 1.0) {",
+ fSquareFun.c_str());
+ // The entire pixel is outside the round rect.
+ this->acceptOrRejectWholeFragment(f, false, opts);
+ f->codeAppend ("} else {");
+ f->appendPrecisionModifier(kHigh_GrSLPrecision);
+ f->codeAppendf( "vec2 s = %s.zw * sign(%s);", rrect, coords.fVarying->fsIn());
+ f->appendPrecisionModifier(kHigh_GrSLPrecision);
+ f->codeAppendf( "mat2 innerRRectInverseMatrix = %s * mat2(s.x, 0, 0, s.y);",
+ coords.fInverseMatrix);
+ f->appendPrecisionModifier(kHigh_GrSLPrecision);
+ f->codeAppend ( "int rrectMask = 0;");
+ f->codeAppend ( "for (int i = 0; i < SAMPLE_COUNT; i++) {");
+ f->appendPrecisionModifier(kHigh_GrSLPrecision);
+ f->codeAppend ( "vec2 pt = rrectCoords + ");
+ f->appendOffsetToSample("i", GrGLSLFPFragmentBuilder::kSkiaDevice_Coordinates);
+ f->codeAppend ( "* innerRRectInverseMatrix;");
+ f->codeAppendf( "if (%s(max(pt, vec2(0))) < 1.0) rrectMask |= (1 << i);",
+ fSquareFun.c_str());
+ f->codeAppend ( "}");
+ this->acceptCoverageMask(f, "rrectMask", opts);
+ f->codeAppend ("}");
+ } else {
+ f->codeAppend ("int rrectMask = 0;");
+ f->codeAppend ("for (int i = 0; i < SAMPLE_COUNT; i++) {");
+ f->appendPrecisionModifier(kHigh_GrSLPrecision);
+ f->codeAppend ( "vec2 shapePt = ");
+ this->interpolateAtSample(f, *coords.fVarying, "i", nullptr);
+ f->codeAppend ( ";");
+ f->appendPrecisionModifier(kHigh_GrSLPrecision);
+ f->codeAppendf( "vec2 rrectPt = max(abs(shapePt) - %s.xy, vec2(0)) * %s.zw;",
+ rrect, rrect);
+ f->codeAppendf( "if (%s(rrectPt) < 1.0) rrectMask |= (1 << i);", fSquareFun.c_str());
+ f->codeAppend ("}");
+ this->acceptCoverageMask(f, "rrectMask", opts);
+ }
+ f->codeAppend ("}");
+}
+
+void GLSLInstanceProcessor::BackendMultisample::interpolateAtSample(GrGLSLPPFragmentBuilder* f,
+ const GrGLSLVarying& varying,
+ const char* sampleIdx,
+ const char* interpolationMatrix) {
+ if (interpolationMatrix) {
+ f->codeAppendf("(%s + ", varying.fsIn());
+ f->appendOffsetToSample(sampleIdx, GrGLSLFPFragmentBuilder::kSkiaDevice_Coordinates);
+ f->codeAppendf(" * %s)", interpolationMatrix);
+ } else {
+ SkAssertResult(
+ f->enableFeature(GrGLSLFragmentBuilder::kMultisampleInterpolation_GLSLFeature));
+ f->codeAppendf("interpolateAtOffset(%s, ", varying.fsIn());
+ f->appendOffsetToSample(sampleIdx, GrGLSLFPFragmentBuilder::kGLSLWindow_Coordinates);
+ f->codeAppend(")");
+ }
+}
+
+void
+GLSLInstanceProcessor::BackendMultisample::acceptOrRejectWholeFragment(GrGLSLPPFragmentBuilder* f,
+ bool inside,
+ const EmitShapeOpts& opts) {
+ if (inside != opts.fInvertCoverage) { // Accept the entire fragment.
+ if (opts.fResolveMixedSamples) {
+ // This is a mixed sampled fragment in the interior of the shape. Reassign 100% coverage
+ // to one fragment, and drop all other fragments that may fall on this same pixel. Since
+ // our geometry is water tight and non-overlapping, we can take advantage of the
+ // properties that (1) the incoming sample masks will be disjoint across fragments that
+ // fall on a common pixel, and (2) since the entire fragment is inside the shape, each
+ // sample's corresponding bit will be set in the incoming sample mask of exactly one
+ // fragment.
+ f->codeAppend("if ((gl_SampleMaskIn[0] & SAMPLE_MASK_MSB) == 0) {");
+ // Drop this fragment.
+ if (!fBatchInfo.fCannotDiscard) {
+ f->codeAppend("discard;");
+ } else {
+ f->overrideSampleCoverage("0");
+ }
+ f->codeAppend("} else {");
+ // Override the lone surviving fragment to full coverage.
+ f->overrideSampleCoverage("-1");
+ f->codeAppend("}");
+ }
+ } else { // Reject the entire fragment.
+ if (!fBatchInfo.fCannotDiscard) {
+ f->codeAppend("discard;");
+ } else if (opts.fResolveMixedSamples) {
+ f->overrideSampleCoverage("0");
+ } else {
+ f->maskSampleCoverage("0");
+ }
+ }
+}
+
+void GLSLInstanceProcessor::BackendMultisample::acceptCoverageMask(GrGLSLPPFragmentBuilder* f,
+ const char* shapeMask,
+ const EmitShapeOpts& opts,
+ bool maybeSharedEdge) {
+ if (opts.fResolveMixedSamples) {
+ if (maybeSharedEdge) {
+ // This is a mixed sampled fragment, potentially on the outer edge of the shape, with
+ // only partial shape coverage. Override the coverage of one fragment to "shapeMask",
+ // and drop all other fragments that may fall on this same pixel. Since our geometry is
+ // water tight, non-overlapping, and completely contains the shape, this means that each
+ // "on" bit from shapeMask is guaranteed to be set in the incoming sample mask of one,
+ // and only one, fragment that falls on this same pixel.
+ SkASSERT(!opts.fInvertCoverage);
+ f->codeAppendf("if ((gl_SampleMaskIn[0] & (1 << findMSB(%s))) == 0) {", shapeMask);
+ // Drop this fragment.
+ if (!fBatchInfo.fCannotDiscard) {
+ f->codeAppend ("discard;");
+ } else {
+ f->overrideSampleCoverage("0");
+ }
+ f->codeAppend ("} else {");
+ // Override the coverage of the lone surviving fragment to "shapeMask".
+ f->overrideSampleCoverage(shapeMask);
+ f->codeAppend ("}");
+ } else {
+ f->overrideSampleCoverage(shapeMask);
+ }
+ } else {
+ f->maskSampleCoverage(shapeMask, opts.fInvertCoverage);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+GLSLInstanceProcessor::Backend*
+GLSLInstanceProcessor::Backend::Create(const GrPipeline& pipeline, BatchInfo batchInfo,
+ const VertexInputs& inputs) {
+ switch (batchInfo.fAntialiasMode) {
+ default:
+ SkFAIL("Unexpected antialias mode.");
+ case AntialiasMode::kNone:
+ return new BackendNonAA(batchInfo, inputs);
+ case AntialiasMode::kCoverage:
+ return new BackendCoverage(batchInfo, inputs);
+ case AntialiasMode::kMSAA:
+ case AntialiasMode::kMixedSamples: {
+ const GrRenderTargetPriv& rtp = pipeline.getRenderTarget()->renderTargetPriv();
+ const GrGpu::MultisampleSpecs& specs = rtp.getMultisampleSpecs(pipeline.getStencil());
+ return new BackendMultisample(batchInfo, inputs, specs.fEffectiveSampleCnt);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+const ShapeVertex kVertexData[] = {
+ // Rectangle.
+ {+1, +1, ~0}, /*0*/
+ {-1, +1, ~0}, /*1*/
+ {-1, -1, ~0}, /*2*/
+ {+1, -1, ~0}, /*3*/
+ // The next 4 are for the bordered version.
+ {+1, +1, 0}, /*4*/
+ {-1, +1, 0}, /*5*/
+ {-1, -1, 0}, /*6*/
+ {+1, -1, 0}, /*7*/
+
+ // Octagon that inscribes the unit circle, cut by an interior unit octagon.
+ {+1.000000f, 0.000000f, 0}, /* 8*/
+ {+1.000000f, +0.414214f, ~0}, /* 9*/
+ {+0.707106f, +0.707106f, 0}, /*10*/
+ {+0.414214f, +1.000000f, ~0}, /*11*/
+ { 0.000000f, +1.000000f, 0}, /*12*/
+ {-0.414214f, +1.000000f, ~0}, /*13*/
+ {-0.707106f, +0.707106f, 0}, /*14*/
+ {-1.000000f, +0.414214f, ~0}, /*15*/
+ {-1.000000f, 0.000000f, 0}, /*16*/
+ {-1.000000f, -0.414214f, ~0}, /*17*/
+ {-0.707106f, -0.707106f, 0}, /*18*/
+ {-0.414214f, -1.000000f, ~0}, /*19*/
+ { 0.000000f, -1.000000f, 0}, /*20*/
+ {+0.414214f, -1.000000f, ~0}, /*21*/
+ {+0.707106f, -0.707106f, 0}, /*22*/
+ {+1.000000f, -0.414214f, ~0}, /*23*/
+ // This vertex is for the fanned versions.
+ { 0.000000f, 0.000000f, ~0}, /*24*/
+
+ // Rectangle with disjoint corner segments.
+ {+1.0, +0.5, 0x3}, /*25*/
+ {+1.0, +1.0, 0x3}, /*26*/
+ {+0.5, +1.0, 0x3}, /*27*/
+ {-0.5, +1.0, 0x2}, /*28*/
+ {-1.0, +1.0, 0x2}, /*29*/
+ {-1.0, +0.5, 0x2}, /*30*/
+ {-1.0, -0.5, 0x0}, /*31*/
+ {-1.0, -1.0, 0x0}, /*32*/
+ {-0.5, -1.0, 0x0}, /*33*/
+ {+0.5, -1.0, 0x1}, /*34*/
+ {+1.0, -1.0, 0x1}, /*35*/
+ {+1.0, -0.5, 0x1}, /*36*/
+ // The next 4 are for the fanned version.
+ { 0.0, 0.0, 0x3}, /*37*/
+ { 0.0, 0.0, 0x2}, /*38*/
+ { 0.0, 0.0, 0x0}, /*39*/
+ { 0.0, 0.0, 0x1}, /*40*/
+ // The next 8 are for the bordered version.
+ {+0.75, +0.50, 0x3}, /*41*/
+ {+0.50, +0.75, 0x3}, /*42*/
+ {-0.50, +0.75, 0x2}, /*43*/
+ {-0.75, +0.50, 0x2}, /*44*/
+ {-0.75, -0.50, 0x0}, /*45*/
+ {-0.50, -0.75, 0x0}, /*46*/
+ {+0.50, -0.75, 0x1}, /*47*/
+ {+0.75, -0.50, 0x1}, /*48*/
+
+ // 16-gon that inscribes the unit circle, cut by an interior unit 16-gon.
+ {+1.000000f, +0.000000f, 0}, /*49*/
+ {+1.000000f, +0.198913f, ~0}, /*50*/
+ {+0.923879f, +0.382683f, 0}, /*51*/
+ {+0.847760f, +0.566455f, ~0}, /*52*/
+ {+0.707106f, +0.707106f, 0}, /*53*/
+ {+0.566455f, +0.847760f, ~0}, /*54*/
+ {+0.382683f, +0.923879f, 0}, /*55*/
+ {+0.198913f, +1.000000f, ~0}, /*56*/
+ {+0.000000f, +1.000000f, 0}, /*57*/
+ {-0.198913f, +1.000000f, ~0}, /*58*/
+ {-0.382683f, +0.923879f, 0}, /*59*/
+ {-0.566455f, +0.847760f, ~0}, /*60*/
+ {-0.707106f, +0.707106f, 0}, /*61*/
+ {-0.847760f, +0.566455f, ~0}, /*62*/
+ {-0.923879f, +0.382683f, 0}, /*63*/
+ {-1.000000f, +0.198913f, ~0}, /*64*/
+ {-1.000000f, +0.000000f, 0}, /*65*/
+ {-1.000000f, -0.198913f, ~0}, /*66*/
+ {-0.923879f, -0.382683f, 0}, /*67*/
+ {-0.847760f, -0.566455f, ~0}, /*68*/
+ {-0.707106f, -0.707106f, 0}, /*69*/
+ {-0.566455f, -0.847760f, ~0}, /*70*/
+ {-0.382683f, -0.923879f, 0}, /*71*/
+ {-0.198913f, -1.000000f, ~0}, /*72*/
+ {-0.000000f, -1.000000f, 0}, /*73*/
+ {+0.198913f, -1.000000f, ~0}, /*74*/
+ {+0.382683f, -0.923879f, 0}, /*75*/
+ {+0.566455f, -0.847760f, ~0}, /*76*/
+ {+0.707106f, -0.707106f, 0}, /*77*/
+ {+0.847760f, -0.566455f, ~0}, /*78*/
+ {+0.923879f, -0.382683f, 0}, /*79*/
+ {+1.000000f, -0.198913f, ~0}, /*80*/
+};
+
+const uint8_t kIndexData[] = {
+ // Rectangle.
+ 0, 1, 2,
+ 0, 2, 3,
+
+ // Rectangle with a border.
+ 0, 1, 5,
+ 5, 4, 0,
+ 1, 2, 6,
+ 6, 5, 1,
+ 2, 3, 7,
+ 7, 6, 2,
+ 3, 0, 4,
+ 4, 7, 3,
+ 4, 5, 6,
+ 6, 7, 4,
+
+ // Octagon that inscribes the unit circle, cut by an interior unit octagon.
+ 10, 8, 9,
+ 12, 10, 11,
+ 14, 12, 13,
+ 16, 14, 15,
+ 18, 16, 17,
+ 20, 18, 19,
+ 22, 20, 21,
+ 8, 22, 23,
+ 8, 10, 12,
+ 12, 14, 16,
+ 16, 18, 20,
+ 20, 22, 8,
+ 8, 12, 16,
+ 16, 20, 8,
+
+ // Same octagons, but with the interior arranged as a fan. Used by mixed samples.
+ 10, 8, 9,
+ 12, 10, 11,
+ 14, 12, 13,
+ 16, 14, 15,
+ 18, 16, 17,
+ 20, 18, 19,
+ 22, 20, 21,
+ 8, 22, 23,
+ 24, 8, 10,
+ 12, 24, 10,
+ 24, 12, 14,
+ 16, 24, 14,
+ 24, 16, 18,
+ 20, 24, 18,
+ 24, 20, 22,
+ 8, 24, 22,
+
+ // Same octagons, but with the inner and outer disjoint. Used by coverage AA.
+ 8, 22, 23,
+ 9, 8, 23,
+ 10, 8, 9,
+ 11, 10, 9,
+ 12, 10, 11,
+ 13, 12, 11,
+ 14, 12, 13,
+ 15, 14, 13,
+ 16, 14, 15,
+ 17, 16, 15,
+ 18, 16, 17,
+ 19, 18, 17,
+ 20, 18, 19,
+ 21, 20, 19,
+ 22, 20, 21,
+ 23, 22, 21,
+ 22, 8, 10,
+ 10, 12, 14,
+ 14, 16, 18,
+ 18, 20, 22,
+ 22, 10, 14,
+ 14, 18, 22,
+
+ // Rectangle with disjoint corner segments.
+ 27, 25, 26,
+ 30, 28, 29,
+ 33, 31, 32,
+ 36, 34, 35,
+ 25, 27, 28,
+ 28, 30, 31,
+ 31, 33, 34,
+ 34, 36, 25,
+ 25, 28, 31,
+ 31, 34, 25,
+
+ // Same rectangle with disjoint corners, but with the interior arranged as a fan. Used by
+ // mixed samples.
+ 27, 25, 26,
+ 30, 28, 29,
+ 33, 31, 32,
+ 36, 34, 35,
+ 27, 37, 25,
+ 28, 37, 27,
+ 30, 38, 28,
+ 31, 38, 30,
+ 33, 39, 31,
+ 34, 39, 33,
+ 36, 40, 34,
+ 25, 40, 36,
+
+ // Same rectangle with disjoint corners, with a border as well. Used by coverage AA.
+ 41, 25, 26,
+ 42, 41, 26,
+ 27, 42, 26,
+ 43, 28, 29,
+ 44, 43, 29,
+ 30, 44, 29,
+ 45, 31, 32,
+ 46, 45, 32,
+ 33, 46, 32,
+ 47, 34, 35,
+ 48, 47, 35,
+ 36, 48, 35,
+ 27, 28, 42,
+ 42, 28, 43,
+ 30, 31, 44,
+ 44, 31, 45,
+ 33, 34, 46,
+ 46, 34, 47,
+ 36, 25, 48,
+ 48, 25, 41,
+ 41, 42, 43,
+ 43, 44, 45,
+ 45, 46, 47,
+ 47, 48, 41,
+ 41, 43, 45,
+ 45, 47, 41,
+
+ // Same as the disjoint octagons, but with 16-gons instead. Used by coverage AA when the oval is
+ // sufficiently large.
+ 49, 79, 80,
+ 50, 49, 80,
+ 51, 49, 50,
+ 52, 51, 50,
+ 53, 51, 52,
+ 54, 53, 52,
+ 55, 53, 54,
+ 56, 55, 54,
+ 57, 55, 56,
+ 58, 57, 56,
+ 59, 57, 58,
+ 60, 59, 58,
+ 61, 59, 60,
+ 62, 61, 60,
+ 63, 61, 62,
+ 64, 63, 62,
+ 65, 63, 64,
+ 66, 65, 64,
+ 67, 65, 66,
+ 68, 67, 66,
+ 69, 67, 68,
+ 70, 69, 68,
+ 71, 69, 70,
+ 72, 71, 70,
+ 73, 71, 72,
+ 74, 73, 72,
+ 75, 73, 74,
+ 76, 75, 74,
+ 77, 75, 76,
+ 78, 77, 76,
+ 79, 77, 78,
+ 80, 79, 78,
+ 49, 51, 53,
+ 53, 55, 57,
+ 57, 59, 61,
+ 61, 63, 65,
+ 65, 67, 69,
+ 69, 71, 73,
+ 73, 75, 77,
+ 77, 79, 49,
+ 49, 53, 57,
+ 57, 61, 65,
+ 65, 69, 73,
+ 73, 77, 49,
+ 49, 57, 65,
+ 65, 73, 49,
+};
+
+enum {
+ kRect_FirstIndex = 0,
+ kRect_TriCount = 2,
+
+ kFramedRect_FirstIndex = 6,
+ kFramedRect_TriCount = 10,
+
+ kOctagons_FirstIndex = 36,
+ kOctagons_TriCount = 14,
+
+ kOctagonsFanned_FirstIndex = 78,
+ kOctagonsFanned_TriCount = 16,
+
+ kDisjointOctagons_FirstIndex = 126,
+ kDisjointOctagons_TriCount = 22,
+
+ kCorneredRect_FirstIndex = 192,
+ kCorneredRect_TriCount = 10,
+
+ kCorneredRectFanned_FirstIndex = 222,
+ kCorneredRectFanned_TriCount = 12,
+
+ kCorneredFramedRect_FirstIndex = 258,
+ kCorneredFramedRect_TriCount = 26,
+
+ kDisjoint16Gons_FirstIndex = 336,
+ kDisjoint16Gons_TriCount = 46,
+};
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gShapeVertexBufferKey);
+
+const GrBuffer* InstanceProcessor::FindOrCreateVertexBuffer(GrGpu* gpu) {
+ GR_DEFINE_STATIC_UNIQUE_KEY(gShapeVertexBufferKey);
+ GrResourceCache* cache = gpu->getContext()->getResourceCache();
+ if (GrGpuResource* cached = cache->findAndRefUniqueResource(gShapeVertexBufferKey)) {
+ return static_cast<GrBuffer*>(cached);
+ }
+ if (GrBuffer* buffer = gpu->createBuffer(sizeof(kVertexData), kVertex_GrBufferType,
+ kStatic_GrAccessPattern, kVertexData)) {
+ buffer->resourcePriv().setUniqueKey(gShapeVertexBufferKey);
+ return buffer;
+ }
+ return nullptr;
+}
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gShapeIndexBufferKey);
+
+const GrBuffer* InstanceProcessor::FindOrCreateIndex8Buffer(GrGpu* gpu) {
+ GR_DEFINE_STATIC_UNIQUE_KEY(gShapeIndexBufferKey);
+ GrResourceCache* cache = gpu->getContext()->getResourceCache();
+ if (GrGpuResource* cached = cache->findAndRefUniqueResource(gShapeIndexBufferKey)) {
+ return static_cast<GrBuffer*>(cached);
+ }
+ if (GrBuffer* buffer = gpu->createBuffer(sizeof(kIndexData), kIndex_GrBufferType,
+ kStatic_GrAccessPattern, kIndexData)) {
+ buffer->resourcePriv().setUniqueKey(gShapeIndexBufferKey);
+ return buffer;
+ }
+ return nullptr;
+}
+
+IndexRange InstanceProcessor::GetIndexRangeForRect(AntialiasMode aa) {
+ static constexpr IndexRange kRectRanges[kNumAntialiasModes] = {
+ {kRect_FirstIndex, 3 * kRect_TriCount}, // kNone
+ {kFramedRect_FirstIndex, 3 * kFramedRect_TriCount}, // kCoverage
+ {kRect_FirstIndex, 3 * kRect_TriCount}, // kMSAA
+ {kRect_FirstIndex, 3 * kRect_TriCount} // kMixedSamples
+ };
+
+ SkASSERT(aa >= AntialiasMode::kNone && aa <= AntialiasMode::kMixedSamples);
+ return kRectRanges[(int)aa];
+
+ GR_STATIC_ASSERT(0 == (int)AntialiasMode::kNone);
+ GR_STATIC_ASSERT(1 == (int)AntialiasMode::kCoverage);
+ GR_STATIC_ASSERT(2 == (int)AntialiasMode::kMSAA);
+ GR_STATIC_ASSERT(3 == (int)AntialiasMode::kMixedSamples);
+}
+
+IndexRange InstanceProcessor::GetIndexRangeForOval(AntialiasMode aa, const SkRect& devBounds) {
+ if (AntialiasMode::kCoverage == aa && devBounds.height() * devBounds.width() >= 256 * 256) {
+ // This threshold was chosen quasi-scientifically on Tegra X1.
+ return {kDisjoint16Gons_FirstIndex, 3 * kDisjoint16Gons_TriCount};
+ }
+
+ static constexpr IndexRange kOvalRanges[kNumAntialiasModes] = {
+ {kOctagons_FirstIndex, 3 * kOctagons_TriCount}, // kNone
+ {kDisjointOctagons_FirstIndex, 3 * kDisjointOctagons_TriCount}, // kCoverage
+ {kOctagons_FirstIndex, 3 * kOctagons_TriCount}, // kMSAA
+ {kOctagonsFanned_FirstIndex, 3 * kOctagonsFanned_TriCount} // kMixedSamples
+ };
+
+ SkASSERT(aa >= AntialiasMode::kNone && aa <= AntialiasMode::kMixedSamples);
+ return kOvalRanges[(int)aa];
+
+ GR_STATIC_ASSERT(0 == (int)AntialiasMode::kNone);
+ GR_STATIC_ASSERT(1 == (int)AntialiasMode::kCoverage);
+ GR_STATIC_ASSERT(2 == (int)AntialiasMode::kMSAA);
+ GR_STATIC_ASSERT(3 == (int)AntialiasMode::kMixedSamples);
+}
+
+IndexRange InstanceProcessor::GetIndexRangeForRRect(AntialiasMode aa) {
+ static constexpr IndexRange kRRectRanges[kNumAntialiasModes] = {
+ {kCorneredRect_FirstIndex, 3 * kCorneredRect_TriCount}, // kNone
+ {kCorneredFramedRect_FirstIndex, 3 * kCorneredFramedRect_TriCount}, // kCoverage
+ {kCorneredRect_FirstIndex, 3 * kCorneredRect_TriCount}, // kMSAA
+ {kCorneredRectFanned_FirstIndex, 3 * kCorneredRectFanned_TriCount} // kMixedSamples
+ };
+
+ SkASSERT(aa >= AntialiasMode::kNone && aa <= AntialiasMode::kMixedSamples);
+ return kRRectRanges[(int)aa];
+
+ GR_STATIC_ASSERT(0 == (int)AntialiasMode::kNone);
+ GR_STATIC_ASSERT(1 == (int)AntialiasMode::kCoverage);
+ GR_STATIC_ASSERT(2 == (int)AntialiasMode::kMSAA);
+ GR_STATIC_ASSERT(3 == (int)AntialiasMode::kMixedSamples);
+}
+
+const char* InstanceProcessor::GetNameOfIndexRange(IndexRange range) {
+ switch (range.fStart) {
+ case kRect_FirstIndex: return "basic_rect";
+ case kFramedRect_FirstIndex: return "coverage_rect";
+
+ case kOctagons_FirstIndex: return "basic_oval";
+ case kDisjointOctagons_FirstIndex: return "coverage_oval";
+ case kDisjoint16Gons_FirstIndex: return "coverage_large_oval";
+ case kOctagonsFanned_FirstIndex: return "mixed_samples_oval";
+
+ case kCorneredRect_FirstIndex: return "basic_round_rect";
+ case kCorneredFramedRect_FirstIndex: return "coverage_round_rect";
+ case kCorneredRectFanned_FirstIndex: return "mixed_samples_round_rect";
+
+ default: return "unknown";
+ }
+}
+
+}
diff --git a/src/gpu/instanced/InstanceProcessor.h b/src/gpu/instanced/InstanceProcessor.h
new file mode 100644
index 0000000000..b0edde95b8
--- /dev/null
+++ b/src/gpu/instanced/InstanceProcessor.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef gr_instanced_InstanceProcessor_DEFINED
+#define gr_instanced_InstanceProcessor_DEFINED
+
+#include "GrBufferAccess.h"
+#include "GrGeometryProcessor.h"
+#include "instanced/InstancedRenderingTypes.h"
+
+namespace gr_instanced {
+
+/**
+ * This class provides a GP implementation that uses instanced rendering. Is sends geometry in as
+ * basic, pre-baked canonical shapes, and uses instanced vertex attribs to control how these shapes
+ * are transformed and drawn. MSAA is accomplished with the sample mask rather than finely
+ * tesselated geometry.
+ */
+class InstanceProcessor : public GrGeometryProcessor {
+public:
+ static bool IsSupported(const GrGLSLCaps&, const GrCaps&, AntialiasMode* lastSupportedAAMode);
+
+ InstanceProcessor(BatchInfo, GrBuffer* paramsBuffer);
+
+ const char* name() const override { return "Instance Processor"; }
+ BatchInfo batchInfo() const { return fBatchInfo; }
+
+ void getGLSLProcessorKey(const GrGLSLCaps&, GrProcessorKeyBuilder* b) const override {
+ b->add32(fBatchInfo.fData);
+ }
+ GrGLSLPrimitiveProcessor* createGLSLInstance(const GrGLSLCaps&) const override;
+
+ /**
+ * Returns a buffer of ShapeVertex that defines the canonical instanced geometry.
+ */
+ static const GrBuffer* SK_WARN_UNUSED_RESULT FindOrCreateVertexBuffer(GrGpu*);
+
+ /**
+ * Returns a buffer of 8-bit indices for the canonical instanced geometry. The client can call
+ * GetIndexRangeForXXX to know which indices to use for a specific shape.
+ */
+ static const GrBuffer* SK_WARN_UNUSED_RESULT FindOrCreateIndex8Buffer(GrGpu*);
+
+ static IndexRange GetIndexRangeForRect(AntialiasMode);
+ static IndexRange GetIndexRangeForOval(AntialiasMode, const SkRect& devBounds);
+ static IndexRange GetIndexRangeForRRect(AntialiasMode);
+
+ static const char* GetNameOfIndexRange(IndexRange);
+
+private:
+ const BatchInfo fBatchInfo;
+ GrBufferAccess fParamsAccess;
+
+ typedef GrGeometryProcessor INHERITED;
+};
+
+}
+
+#endif
diff --git a/src/gpu/instanced/InstancedRendering.cpp b/src/gpu/instanced/InstancedRendering.cpp
new file mode 100644
index 0000000000..d96bb39786
--- /dev/null
+++ b/src/gpu/instanced/InstancedRendering.cpp
@@ -0,0 +1,488 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "InstancedRendering.h"
+
+#include "GrBatchFlushState.h"
+#include "GrPipeline.h"
+#include "GrResourceProvider.h"
+#include "instanced/InstanceProcessor.h"
+
+namespace gr_instanced {
+
+InstancedRendering::InstancedRendering(GrGpu* gpu, AntialiasMode lastSupportedAAMode,
+ bool canRenderToFloat)
+ : fGpu(SkRef(gpu)),
+ fLastSupportedAAMode(lastSupportedAAMode),
+ fCanRenderToFloat(canRenderToFloat),
+ fState(State::kRecordingDraws),
+ fDrawPool(1024 * sizeof(Batch::Draw), 1024 * sizeof(Batch::Draw)) {
+}
+
+GrDrawBatch* InstancedRendering::recordRect(const SkRect& rect, const SkMatrix& viewMatrix,
+ GrColor color, bool antialias,
+ const GrInstancedPipelineInfo& info, bool* useHWAA) {
+ return this->recordShape(ShapeType::kRect, rect, viewMatrix, color, rect, antialias, info,
+ useHWAA);
+}
+
+GrDrawBatch* InstancedRendering::recordRect(const SkRect& rect, const SkMatrix& viewMatrix,
+ GrColor color, const SkRect& localRect, bool antialias,
+ const GrInstancedPipelineInfo& info, bool* useHWAA) {
+ return this->recordShape(ShapeType::kRect, rect, viewMatrix, color, localRect, antialias, info,
+ useHWAA);
+}
+
+GrDrawBatch* InstancedRendering::recordRect(const SkRect& rect, const SkMatrix& viewMatrix,
+ GrColor color, const SkMatrix& localMatrix,
+ bool antialias, const GrInstancedPipelineInfo& info,
+ bool* useHWAA) {
+ if (localMatrix.hasPerspective()) {
+ return nullptr; // Perspective is not yet supported in the local matrix.
+ }
+ if (Batch* batch = this->recordShape(ShapeType::kRect, rect, viewMatrix, color, rect, antialias,
+ info, useHWAA)) {
+ batch->getSingleInstance().fInfo |= kLocalMatrix_InfoFlag;
+ batch->appendParamsTexel(localMatrix.getScaleX(), localMatrix.getSkewX(),
+ localMatrix.getTranslateX());
+ batch->appendParamsTexel(localMatrix.getSkewY(), localMatrix.getScaleY(),
+ localMatrix.getTranslateY());
+ batch->fInfo.fHasLocalMatrix = true;
+ return batch;
+ }
+ return nullptr;
+}
+
+GrDrawBatch* InstancedRendering::recordOval(const SkRect& oval, const SkMatrix& viewMatrix,
+ GrColor color, bool antialias,
+ const GrInstancedPipelineInfo& info, bool* useHWAA) {
+ return this->recordShape(ShapeType::kOval, oval, viewMatrix, color, oval, antialias, info,
+ useHWAA);
+}
+
+GrDrawBatch* InstancedRendering::recordRRect(const SkRRect& rrect, const SkMatrix& viewMatrix,
+ GrColor color, bool antialias,
+ const GrInstancedPipelineInfo& info, bool* useHWAA) {
+ if (Batch* batch = this->recordShape(GetRRectShapeType(rrect), rrect.rect(), viewMatrix, color,
+ rrect.rect(), antialias, info, useHWAA)) {
+ batch->appendRRectParams(rrect);
+ return batch;
+ }
+ return nullptr;
+}
+
+GrDrawBatch* InstancedRendering::recordDRRect(const SkRRect& outer, const SkRRect& inner,
+ const SkMatrix& viewMatrix, GrColor color,
+ bool antialias, const GrInstancedPipelineInfo& info,
+ bool* useHWAA) {
+ if (inner.getType() > SkRRect::kSimple_Type) {
+ return nullptr; // Complex inner round rects are not yet supported.
+ }
+ if (SkRRect::kEmpty_Type == inner.getType()) {
+ return this->recordRRect(outer, viewMatrix, color, antialias, info, useHWAA);
+ }
+ if (Batch* batch = this->recordShape(GetRRectShapeType(outer), outer.rect(), viewMatrix, color,
+ outer.rect(), antialias, info, useHWAA)) {
+ batch->appendRRectParams(outer);
+ ShapeType innerShapeType = GetRRectShapeType(inner);
+ batch->fInfo.fInnerShapeTypes |= GetShapeFlag(innerShapeType);
+ batch->getSingleInstance().fInfo |= ((int)innerShapeType << kInnerShapeType_InfoBit);
+ batch->appendParamsTexel(inner.rect().asScalars(), 4);
+ batch->appendRRectParams(inner);
+ return batch;
+ }
+ return nullptr;
+}
+
+InstancedRendering::Batch* InstancedRendering::recordShape(ShapeType type, const SkRect& bounds,
+ const SkMatrix& viewMatrix,
+ GrColor color, const SkRect& localRect,
+ bool antialias,
+ const GrInstancedPipelineInfo& info,
+ bool* useHWAA) {
+ SkASSERT(State::kRecordingDraws == fState);
+
+ if (info.fIsRenderingToFloat && !fCanRenderToFloat) {
+ return nullptr;
+ }
+
+ AntialiasMode antialiasMode;
+ if (!this->selectAntialiasMode(viewMatrix, antialias, info, useHWAA, &antialiasMode)) {
+ return nullptr;
+ }
+
+ Batch* batch = this->createBatch();
+ batch->fInfo.fAntialiasMode = antialiasMode;
+ batch->fInfo.fShapeTypes = GetShapeFlag(type);
+ batch->fInfo.fCannotDiscard = !info.fCanDiscard;
+
+ Instance& instance = batch->getSingleInstance();
+ instance.fInfo = (int)type << kShapeType_InfoBit;
+
+ // The instanced shape renderer draws rectangles of [-1, -1, +1, +1], so we find the matrix that
+ // will map this rectangle to the same device coordinates as "viewMatrix * bounds".
+ float sx = 0.5f * bounds.width();
+ float sy = 0.5f * bounds.height();
+ float tx = sx + bounds.fLeft;
+ float ty = sy + bounds.fTop;
+ if (!viewMatrix.hasPerspective()) {
+ float* m = instance.fShapeMatrix2x3;
+ m[0] = viewMatrix.getScaleX() * sx;
+ m[1] = viewMatrix.getSkewX() * sy;
+ m[2] = viewMatrix.getTranslateX() +
+ viewMatrix.getScaleX() * tx + viewMatrix.getSkewX() * ty;
+
+ m[3] = viewMatrix.getSkewY() * sx;
+ m[4] = viewMatrix.getScaleY() * sy;
+ m[5] = viewMatrix.getTranslateY() +
+ viewMatrix.getSkewY() * tx + viewMatrix.getScaleY() * ty;
+
+ // Since 'm' is a 2x3 matrix that maps the rect [-1, +1] into the shape's device-space quad,
+ // it's quite simple to find the bounding rectangle:
+ float devBoundsHalfWidth = fabsf(m[0]) + fabsf(m[1]);
+ float devBoundsHalfHeight = fabsf(m[3]) + fabsf(m[4]);
+ batch->fBounds.fLeft = m[2] - devBoundsHalfWidth;
+ batch->fBounds.fRight = m[2] + devBoundsHalfWidth;
+ batch->fBounds.fTop = m[5] - devBoundsHalfHeight;
+ batch->fBounds.fBottom = m[5] + devBoundsHalfHeight;
+
+ // TODO: Is this worth the CPU overhead?
+ batch->fInfo.fNonSquare =
+ fabsf(devBoundsHalfHeight - devBoundsHalfWidth) > 0.5f || // Early out.
+ fabs(m[0] * m[3] + m[1] * m[4]) > 1e-3f || // Skew?
+ fabs(m[0] * m[0] + m[1] * m[1] - m[3] * m[3] - m[4] * m[4]) > 1e-2f; // Diff. lengths?
+ } else {
+ SkMatrix shapeMatrix(viewMatrix);
+ shapeMatrix.preTranslate(tx, ty);
+ shapeMatrix.preScale(sx, sy);
+ instance.fInfo |= kPerspective_InfoFlag;
+
+ float* m = instance.fShapeMatrix2x3;
+ m[0] = SkScalarToFloat(shapeMatrix.getScaleX());
+ m[1] = SkScalarToFloat(shapeMatrix.getSkewX());
+ m[2] = SkScalarToFloat(shapeMatrix.getTranslateX());
+ m[3] = SkScalarToFloat(shapeMatrix.getSkewY());
+ m[4] = SkScalarToFloat(shapeMatrix.getScaleY());
+ m[5] = SkScalarToFloat(shapeMatrix.getTranslateY());
+
+ // Send the perspective column as a param.
+ batch->appendParamsTexel(shapeMatrix[SkMatrix::kMPersp0], shapeMatrix[SkMatrix::kMPersp1],
+ shapeMatrix[SkMatrix::kMPersp2]);
+ batch->fInfo.fHasPerspective = true;
+
+ viewMatrix.mapRect(&batch->fBounds, bounds);
+
+ batch->fInfo.fNonSquare = true;
+ }
+
+ instance.fColor = color;
+
+ const float* rectAsFloats = localRect.asScalars(); // Ensure SkScalar == float.
+ memcpy(&instance.fLocalRect, rectAsFloats, 4 * sizeof(float));
+
+ batch->fPixelLoad = batch->fBounds.height() * batch->fBounds.width();
+ return batch;
+}
+
+inline bool InstancedRendering::selectAntialiasMode(const SkMatrix& viewMatrix, bool antialias,
+ const GrInstancedPipelineInfo& info,
+ bool* useHWAA, AntialiasMode* antialiasMode) {
+ SkASSERT(!info.fColorDisabled || info.fDrawingShapeToStencil);
+ SkASSERT(!info.fIsMixedSampled || info.fIsMultisampled);
+
+ if (!info.fIsMultisampled || fGpu->caps()->multisampleDisableSupport()) {
+ SkASSERT(fLastSupportedAAMode >= AntialiasMode::kCoverage);
+ if (!antialias) {
+ if (info.fDrawingShapeToStencil && !info.fCanDiscard) {
+ // We can't draw to the stencil buffer without discard (or sample mask if MSAA).
+ return false;
+ }
+ *antialiasMode = AntialiasMode::kNone;
+ *useHWAA = false;
+ return true;
+ }
+
+ if (info.canUseCoverageAA() && viewMatrix.preservesRightAngles()) {
+ *antialiasMode = AntialiasMode::kCoverage;
+ *useHWAA = false;
+ return true;
+ }
+ }
+
+ if (info.fIsMultisampled && fLastSupportedAAMode >= AntialiasMode::kMSAA) {
+ if (!info.fIsMixedSampled || info.fColorDisabled) {
+ *antialiasMode = AntialiasMode::kMSAA;
+ *useHWAA = true;
+ return true;
+ }
+ if (fLastSupportedAAMode >= AntialiasMode::kMixedSamples) {
+ *antialiasMode = AntialiasMode::kMixedSamples;
+ *useHWAA = true;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+InstancedRendering::Batch::Batch(uint32_t classID, InstancedRendering* ir)
+ : INHERITED(classID),
+ fInstancedRendering(ir),
+ fIsTracked(false),
+ fNumDraws(1),
+ fNumChangesInGeometry(0) {
+ fHeadDraw = fTailDraw = (Draw*)fInstancedRendering->fDrawPool.allocate(sizeof(Draw));
+#ifdef SK_DEBUG
+ fHeadDraw->fGeometry = {-1, 0};
+#endif
+ fHeadDraw->fNext = nullptr;
+}
+
+InstancedRendering::Batch::~Batch() {
+ if (fIsTracked) {
+ fInstancedRendering->fTrackedBatches.remove(this);
+ }
+
+ Draw* draw = fHeadDraw;
+ while (draw) {
+ Draw* next = draw->fNext;
+ fInstancedRendering->fDrawPool.release(draw);
+ draw = next;
+ }
+}
+
+void InstancedRendering::Batch::appendRRectParams(const SkRRect& rrect) {
+ SkASSERT(!fIsTracked);
+ switch (rrect.getType()) {
+ case SkRRect::kSimple_Type: {
+ const SkVector& radii = rrect.getSimpleRadii();
+ this->appendParamsTexel(radii.x(), radii.y(), rrect.width(), rrect.height());
+ return;
+ }
+ case SkRRect::kNinePatch_Type: {
+ float twoOverW = 2 / rrect.width();
+ float twoOverH = 2 / rrect.height();
+ const SkVector& radiiTL = rrect.radii(SkRRect::kUpperLeft_Corner);
+ const SkVector& radiiBR = rrect.radii(SkRRect::kLowerRight_Corner);
+ this->appendParamsTexel(radiiTL.x() * twoOverW, radiiBR.x() * twoOverW,
+ radiiTL.y() * twoOverH, radiiBR.y() * twoOverH);
+ return;
+ }
+ case SkRRect::kComplex_Type: {
+ /**
+ * The x and y radii of each arc are stored in separate vectors,
+ * in the following order:
+ *
+ * __x1 _ _ _ x3__
+ * y1 | | y2
+ *
+ * | |
+ *
+ * y3 |__ _ _ _ __| y4
+ * x2 x4
+ *
+ */
+ float twoOverW = 2 / rrect.width();
+ float twoOverH = 2 / rrect.height();
+ const SkVector& radiiTL = rrect.radii(SkRRect::kUpperLeft_Corner);
+ const SkVector& radiiTR = rrect.radii(SkRRect::kUpperRight_Corner);
+ const SkVector& radiiBR = rrect.radii(SkRRect::kLowerRight_Corner);
+ const SkVector& radiiBL = rrect.radii(SkRRect::kLowerLeft_Corner);
+ this->appendParamsTexel(radiiTL.x() * twoOverW, radiiBL.x() * twoOverW,
+ radiiTR.x() * twoOverW, radiiBR.x() * twoOverW);
+ this->appendParamsTexel(radiiTL.y() * twoOverH, radiiTR.y() * twoOverH,
+ radiiBL.y() * twoOverH, radiiBR.y() * twoOverH);
+ return;
+ }
+ default: return;
+ }
+}
+
+void InstancedRendering::Batch::appendParamsTexel(const SkScalar* vals, int count) {
+ SkASSERT(!fIsTracked);
+ SkASSERT(count <= 4 && count >= 0);
+ const float* valsAsFloats = vals; // Ensure SkScalar == float.
+ memcpy(&fParams.push_back(), valsAsFloats, count * sizeof(float));
+ fInfo.fHasParams = true;
+}
+
+void InstancedRendering::Batch::appendParamsTexel(SkScalar x, SkScalar y, SkScalar z, SkScalar w) {
+ SkASSERT(!fIsTracked);
+ ParamsTexel& texel = fParams.push_back();
+ texel.fX = SkScalarToFloat(x);
+ texel.fY = SkScalarToFloat(y);
+ texel.fZ = SkScalarToFloat(z);
+ texel.fW = SkScalarToFloat(w);
+ fInfo.fHasParams = true;
+}
+
+void InstancedRendering::Batch::appendParamsTexel(SkScalar x, SkScalar y, SkScalar z) {
+ SkASSERT(!fIsTracked);
+ ParamsTexel& texel = fParams.push_back();
+ texel.fX = SkScalarToFloat(x);
+ texel.fY = SkScalarToFloat(y);
+ texel.fZ = SkScalarToFloat(z);
+ fInfo.fHasParams = true;
+}
+
+void InstancedRendering::Batch::computePipelineOptimizations(GrInitInvariantOutput* color,
+ GrInitInvariantOutput* coverage,
+ GrBatchToXPOverrides* overrides) const {
+ color->setKnownFourComponents(this->getSingleInstance().fColor);
+
+ if (AntialiasMode::kCoverage == fInfo.fAntialiasMode ||
+ (AntialiasMode::kNone == fInfo.fAntialiasMode &&
+ !fInfo.isSimpleRects() && fInfo.fCannotDiscard)) {
+ coverage->setUnknownSingleComponent();
+ } else {
+ coverage->setKnownSingleComponent(255);
+ }
+}
+
+void InstancedRendering::Batch::initBatchTracker(const GrXPOverridesForBatch& overrides) {
+ Draw& draw = this->getSingleDraw(); // This will assert if we have > 1 command.
+ SkASSERT(draw.fGeometry.isEmpty());
+ SkASSERT(SkIsPow2(fInfo.fShapeTypes));
+ SkASSERT(!fIsTracked);
+
+ if (kRect_ShapeFlag == fInfo.fShapeTypes) {
+ draw.fGeometry = InstanceProcessor::GetIndexRangeForRect(fInfo.fAntialiasMode);
+ } else if (kOval_ShapeFlag == fInfo.fShapeTypes) {
+ draw.fGeometry = InstanceProcessor::GetIndexRangeForOval(fInfo.fAntialiasMode, fBounds);
+ } else {
+ draw.fGeometry = InstanceProcessor::GetIndexRangeForRRect(fInfo.fAntialiasMode);
+ }
+
+ if (!fParams.empty()) {
+ SkASSERT(fInstancedRendering->fParams.count() < (int)kParamsIdx_InfoMask); // TODO: cleaner.
+ this->getSingleInstance().fInfo |= fInstancedRendering->fParams.count();
+ fInstancedRendering->fParams.push_back_n(fParams.count(), fParams.begin());
+ }
+
+ GrColor overrideColor;
+ if (overrides.getOverrideColorIfSet(&overrideColor)) {
+ SkASSERT(State::kRecordingDraws == fInstancedRendering->fState);
+ this->getSingleInstance().fColor = overrideColor;
+ }
+ fInfo.fUsesLocalCoords = overrides.readsLocalCoords();
+ fInfo.fCannotTweakAlphaForCoverage = !overrides.canTweakAlphaForCoverage();
+
+ fInstancedRendering->fTrackedBatches.addToTail(this);
+ fIsTracked = true;
+}
+
+bool InstancedRendering::Batch::onCombineIfPossible(GrBatch* other, const GrCaps& caps) {
+ Batch* that = static_cast<Batch*>(other);
+ SkASSERT(fInstancedRendering == that->fInstancedRendering);
+ SkASSERT(fTailDraw);
+ SkASSERT(that->fTailDraw);
+
+ if (!BatchInfo::CanCombine(fInfo, that->fInfo) ||
+ !GrPipeline::CanCombine(*this->pipeline(), this->bounds(),
+ *that->pipeline(), that->bounds(), caps)) {
+ return false;
+ }
+
+ BatchInfo combinedInfo = fInfo | that->fInfo;
+ if (!combinedInfo.isSimpleRects()) {
+ // This threshold was chosen with the "shapes_mixed" bench on a MacBook with Intel graphics.
+ // There seems to be a wide range where it doesn't matter if we combine or not. What matters
+ // is that the itty bitty rects combine with other shapes and the giant ones don't.
+ constexpr SkScalar kMaxPixelsToGeneralizeRects = 256 * 256;
+ if (fInfo.isSimpleRects() && fPixelLoad > kMaxPixelsToGeneralizeRects) {
+ return false;
+ }
+ if (that->fInfo.isSimpleRects() && that->fPixelLoad > kMaxPixelsToGeneralizeRects) {
+ return false;
+ }
+ }
+
+ fBounds.join(that->fBounds);
+ fInfo = combinedInfo;
+ fPixelLoad += that->fPixelLoad;
+
+ // Adopt the other batch's draws.
+ fNumDraws += that->fNumDraws;
+ fNumChangesInGeometry += that->fNumChangesInGeometry;
+ if (fTailDraw->fGeometry != that->fHeadDraw->fGeometry) {
+ ++fNumChangesInGeometry;
+ }
+ fTailDraw->fNext = that->fHeadDraw;
+ fTailDraw = that->fTailDraw;
+
+ that->fHeadDraw = that->fTailDraw = nullptr;
+
+ return true;
+}
+
+void InstancedRendering::beginFlush(GrResourceProvider* rp) {
+ SkASSERT(State::kRecordingDraws == fState);
+ fState = State::kFlushing;
+
+ if (fTrackedBatches.isEmpty()) {
+ return;
+ }
+
+ if (!fVertexBuffer) {
+ fVertexBuffer.reset(InstanceProcessor::FindOrCreateVertexBuffer(fGpu));
+ if (!fVertexBuffer) {
+ return;
+ }
+ }
+
+ if (!fIndexBuffer) {
+ fIndexBuffer.reset(InstanceProcessor::FindOrCreateIndex8Buffer(fGpu));
+ if (!fIndexBuffer) {
+ return;
+ }
+ }
+
+ if (!fParams.empty()) {
+ fParamsBuffer.reset(rp->createBuffer(fParams.count() * sizeof(ParamsTexel),
+ kTexel_GrBufferType, kDynamic_GrAccessPattern,
+ GrResourceProvider::kNoPendingIO_Flag,
+ fParams.begin()));
+ if (!fParamsBuffer) {
+ return;
+ }
+ }
+
+ this->onBeginFlush(rp);
+}
+
+void InstancedRendering::Batch::onDraw(GrBatchFlushState* state) {
+ SkASSERT(State::kFlushing == fInstancedRendering->fState);
+ SkASSERT(state->gpu() == fInstancedRendering->gpu());
+
+ state->gpu()->handleDirtyContext();
+ if (GrXferBarrierType barrierType = this->pipeline()->xferBarrierType(*state->gpu()->caps())) {
+ state->gpu()->xferBarrier(this->pipeline()->getRenderTarget(), barrierType);
+ }
+
+ InstanceProcessor instProc(fInfo, fInstancedRendering->fParamsBuffer);
+ fInstancedRendering->onDraw(*this->pipeline(), instProc, this);
+}
+
+void InstancedRendering::endFlush() {
+ // The caller is expected to delete all tracked batches (i.e. batches whose initBatchTracker
+ // method has been called) before ending the flush.
+ SkASSERT(fTrackedBatches.isEmpty());
+ fParams.reset();
+ fParamsBuffer.reset();
+ this->onEndFlush();
+ fState = State::kRecordingDraws;
+ // Hold on to the shape coords and index buffers.
+}
+
+void InstancedRendering::resetGpuResources(ResetType resetType) {
+ fVertexBuffer.reset();
+ fIndexBuffer.reset();
+ fParamsBuffer.reset();
+ this->onResetGpuResources(resetType);
+}
+
+}
diff --git a/src/gpu/instanced/InstancedRendering.h b/src/gpu/instanced/InstancedRendering.h
new file mode 100644
index 0000000000..d5289663e0
--- /dev/null
+++ b/src/gpu/instanced/InstancedRendering.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef gr_instanced_InstancedRendering_DEFINED
+#define gr_instanced_InstancedRendering_DEFINED
+
+#include "GrMemoryPool.h"
+#include "SkTInternalLList.h"
+#include "batches/GrDrawBatch.h"
+#include "instanced/InstancedRenderingTypes.h"
+#include "../private/GrInstancedPipelineInfo.h"
+
+class GrResourceProvider;
+
+namespace gr_instanced {
+
+class InstanceProcessor;
+
+/**
+ * This class serves as a centralized clearinghouse for instanced rendering. It accumulates data for
+ * instanced draws into one location, and creates special batches that pull from this data. The
+ * nature of instanced rendering allows these batches to combine well and render efficiently.
+ *
+ * During a flush, this class assembles the accumulated draw data into a single vertex and texel
+ * buffer, and its subclass draws the batches using backend-specific instanced rendering APIs.
+ *
+ * This class is responsible for the CPU side of instanced rendering. Shaders are implemented by
+ * InstanceProcessor.
+ */
+class InstancedRendering : public SkNoncopyable {
+public:
+ virtual ~InstancedRendering() { SkASSERT(State::kRecordingDraws == fState); }
+
+ GrGpu* gpu() const { return fGpu; }
+
+ /**
+ * These methods make a new record internally for an instanced draw, and return a batch that is
+ * effectively just an index to that record. The returned batch is not self-contained, but
+ * rather relies on this class to handle the rendering. The client must call beginFlush() on
+ * this class before attempting to flush batches returned by it. It is invalid to record new
+ * draws between beginFlush() and endFlush().
+ */
+ GrDrawBatch* SK_WARN_UNUSED_RESULT recordRect(const SkRect&, const SkMatrix&, GrColor,
+ bool antialias, const GrInstancedPipelineInfo&,
+ bool* useHWAA);
+
+ GrDrawBatch* SK_WARN_UNUSED_RESULT recordRect(const SkRect&, const SkMatrix&, GrColor,
+ const SkRect& localRect, bool antialias,
+ const GrInstancedPipelineInfo&, bool* useHWAA);
+
+ GrDrawBatch* SK_WARN_UNUSED_RESULT recordRect(const SkRect&, const SkMatrix&, GrColor,
+ const SkMatrix& localMatrix, bool antialias,
+ const GrInstancedPipelineInfo&, bool* useHWAA);
+
+ GrDrawBatch* SK_WARN_UNUSED_RESULT recordOval(const SkRect&, const SkMatrix&, GrColor,
+ bool antialias, const GrInstancedPipelineInfo&,
+ bool* useHWAA);
+
+ GrDrawBatch* SK_WARN_UNUSED_RESULT recordRRect(const SkRRect&, const SkMatrix&, GrColor,
+ bool antialias, const GrInstancedPipelineInfo&,
+ bool* useHWAA);
+
+ GrDrawBatch* SK_WARN_UNUSED_RESULT recordDRRect(const SkRRect& outer, const SkRRect& inner,
+ const SkMatrix&, GrColor, bool antialias,
+ const GrInstancedPipelineInfo&, bool* useHWAA);
+
+ /**
+ * Compiles all recorded draws into GPU buffers and allows the client to begin flushing the
+ * batches created by this class.
+ */
+ void beginFlush(GrResourceProvider*);
+
+ /**
+ * Called once the batches created previously by this class have all been released. Allows the
+ * client to begin recording draws again.
+ */
+ void endFlush();
+
+ enum class ResetType : bool {
+ kDestroy,
+ kAbandon
+ };
+
+ /**
+ * Resets all GPU resources, including those that are held long term. They will be lazily
+ * reinitialized if the class begins to be used again.
+ */
+ void resetGpuResources(ResetType);
+
+protected:
+ class Batch : public GrDrawBatch {
+ public:
+ SK_DECLARE_INTERNAL_LLIST_INTERFACE(Batch);
+
+ ~Batch() override;
+ const char* name() const override { return "Instanced Batch"; }
+
+ struct Draw {
+ Instance fInstance;
+ IndexRange fGeometry;
+ Draw* fNext;
+ };
+
+ Draw& getSingleDraw() const { SkASSERT(fHeadDraw && !fHeadDraw->fNext); return *fHeadDraw; }
+ Instance& getSingleInstance() const { return this->getSingleDraw().fInstance; }
+
+ void appendRRectParams(const SkRRect&);
+ void appendParamsTexel(const SkScalar* vals, int count);
+ void appendParamsTexel(SkScalar x, SkScalar y, SkScalar z, SkScalar w);
+ void appendParamsTexel(SkScalar x, SkScalar y, SkScalar z);
+
+ protected:
+ Batch(uint32_t classID, InstancedRendering* ir);
+
+ void initBatchTracker(const GrXPOverridesForBatch&) override;
+ bool onCombineIfPossible(GrBatch* other, const GrCaps& caps) override;
+
+ void computePipelineOptimizations(GrInitInvariantOutput* color,
+ GrInitInvariantOutput* coverage,
+ GrBatchToXPOverrides*) const override;
+
+ void onPrepare(GrBatchFlushState*) override {}
+ void onDraw(GrBatchFlushState*) override;
+
+ InstancedRendering* const fInstancedRendering;
+ BatchInfo fInfo;
+ SkScalar fPixelLoad;
+ SkSTArray<5, ParamsTexel, true> fParams;
+ bool fIsTracked;
+ int fNumDraws;
+ int fNumChangesInGeometry;
+ Draw* fHeadDraw;
+ Draw* fTailDraw;
+
+ typedef GrDrawBatch INHERITED;
+
+ friend class InstancedRendering;
+ };
+
+ typedef SkTInternalLList<Batch> BatchList;
+
+ InstancedRendering(GrGpu* gpu, AntialiasMode lastSupportedAAMode, bool canRenderToFloat);
+
+ const BatchList& trackedBatches() const { return fTrackedBatches; }
+ const GrBuffer* vertexBuffer() const { SkASSERT(fVertexBuffer); return fVertexBuffer; }
+ const GrBuffer* indexBuffer() const { SkASSERT(fIndexBuffer); return fIndexBuffer; }
+
+ virtual void onBeginFlush(GrResourceProvider*) = 0;
+ virtual void onDraw(const GrPipeline&, const InstanceProcessor&, const Batch*) = 0;
+ virtual void onEndFlush() = 0;
+ virtual void onResetGpuResources(ResetType) = 0;
+
+private:
+ enum class State : bool {
+ kRecordingDraws,
+ kFlushing
+ };
+
+ Batch* SK_WARN_UNUSED_RESULT recordShape(ShapeType, const SkRect& bounds,
+ const SkMatrix& viewMatrix, GrColor,
+ const SkRect& localRect, bool antialias,
+ const GrInstancedPipelineInfo&, bool* requireHWAA);
+
+ bool selectAntialiasMode(const SkMatrix& viewMatrix, bool antialias,
+ const GrInstancedPipelineInfo&, bool* useHWAA, AntialiasMode*);
+
+ virtual Batch* createBatch() = 0;
+
+ const SkAutoTUnref<GrGpu> fGpu;
+ const AntialiasMode fLastSupportedAAMode;
+ const bool fCanRenderToFloat;
+ State fState;
+ GrMemoryPool fDrawPool;
+ SkSTArray<1024, ParamsTexel, true> fParams;
+ BatchList fTrackedBatches;
+ SkAutoTUnref<const GrBuffer> fVertexBuffer;
+ SkAutoTUnref<const GrBuffer> fIndexBuffer;
+ SkAutoTUnref<GrBuffer> fParamsBuffer;
+};
+
+}
+
+#endif
diff --git a/src/gpu/instanced/InstancedRenderingTypes.h b/src/gpu/instanced/InstancedRenderingTypes.h
new file mode 100644
index 0000000000..97f8946d03
--- /dev/null
+++ b/src/gpu/instanced/InstancedRenderingTypes.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef gr_instanced_InstancedRenderingTypes_DEFINED
+#define gr_instanced_InstancedRenderingTypes_DEFINED
+
+#include "GrTypes.h"
+#include "SkRRect.h"
+
+namespace gr_instanced {
+
+/**
+ * Per-vertex data. These values get fed into normal vertex attribs.
+ */
+struct ShapeVertex {
+ float fX, fY; //!< Shape coordinates.
+ int32_t fAttrs; //!< Shape-specific vertex attributes, if needed.
+};
+
+/**
+ * Per-instance data. These values get fed into instanced vertex attribs.
+ */
+struct Instance {
+ uint32_t fInfo; //!< Packed info about the instance. See InfoBits.
+ float fShapeMatrix2x3[6]; //!< Maps canonical shape coords -> device space coords.
+ uint32_t fColor; //!< Color to be written out by the primitive processor.
+ float fLocalRect[4]; //!< Local coords rect that spans [-1, +1] in shape coords.
+};
+
+enum class Attrib : uint8_t {
+ kShapeCoords,
+ kVertexAttrs,
+ kInstanceInfo,
+ kShapeMatrixX,
+ kShapeMatrixY,
+ kColor,
+ kLocalRect
+};
+constexpr int kNumAttribs = 1 + (int)Attrib::kLocalRect;
+
+enum class AntialiasMode : uint8_t {
+ kNone,
+ kCoverage,
+ kMSAA,
+ kMixedSamples
+};
+constexpr int kNumAntialiasModes = 1 + (int)AntialiasMode::kMixedSamples;
+
+enum class ShapeType : uint8_t {
+ kRect,
+ kOval,
+ kSimpleRRect,
+ kNinePatch,
+ kComplexRRect
+};
+constexpr int kNumShapeTypes = 1 + (int)ShapeType::kComplexRRect;
+
+inline static ShapeType GetRRectShapeType(const SkRRect& rrect) {
+ SkASSERT(rrect.getType() >= SkRRect::kRect_Type &&
+ rrect.getType() <= SkRRect::kComplex_Type);
+ return static_cast<ShapeType>(rrect.getType() - 1);
+
+ GR_STATIC_ASSERT((int)ShapeType::kRect == SkRRect::kRect_Type - 1);
+ GR_STATIC_ASSERT((int)ShapeType::kOval == SkRRect::kOval_Type - 1);
+ GR_STATIC_ASSERT((int)ShapeType::kSimpleRRect == SkRRect::kSimple_Type - 1);
+ GR_STATIC_ASSERT((int)ShapeType::kNinePatch == SkRRect::kNinePatch_Type - 1);
+ GR_STATIC_ASSERT((int)ShapeType::kComplexRRect == SkRRect::kComplex_Type - 1);
+ GR_STATIC_ASSERT(kNumShapeTypes == SkRRect::kComplex_Type);
+}
+
+enum ShapeFlag {
+ kRect_ShapeFlag = (1 << (int)ShapeType::kRect),
+ kOval_ShapeFlag = (1 << (int)ShapeType::kOval),
+ kSimpleRRect_ShapeFlag = (1 << (int)ShapeType::kSimpleRRect),
+ kNinePatch_ShapeFlag = (1 << (int)ShapeType::kNinePatch),
+ kComplexRRect_ShapeFlag = (1 << (int)ShapeType::kComplexRRect),
+
+ kRRect_ShapesMask = kSimpleRRect_ShapeFlag | kNinePatch_ShapeFlag | kComplexRRect_ShapeFlag
+};
+
+constexpr uint8_t GetShapeFlag(ShapeType type) { return 1 << (int)type; }
+
+/**
+ * Defines what data is stored at which bits in the fInfo field of the instanced data.
+ */
+enum InfoBits {
+ kShapeType_InfoBit = 29,
+ kInnerShapeType_InfoBit = 27,
+ kPerspective_InfoBit = 26,
+ kLocalMatrix_InfoBit = 25,
+ kParamsIdx_InfoBit = 0
+};
+
+enum InfoMasks {
+ kShapeType_InfoMask = 0u - (1 << kShapeType_InfoBit),
+ kInnerShapeType_InfoMask = (1 << kShapeType_InfoBit) - (1 << kInnerShapeType_InfoBit),
+ kPerspective_InfoFlag = (1 << kPerspective_InfoBit),
+ kLocalMatrix_InfoFlag = (1 << kLocalMatrix_InfoBit),
+ kParamsIdx_InfoMask = (1 << kLocalMatrix_InfoBit) - 1
+};
+
+GR_STATIC_ASSERT((kNumShapeTypes - 1) <= (uint32_t)kShapeType_InfoMask >> kShapeType_InfoBit);
+GR_STATIC_ASSERT((int)ShapeType::kSimpleRRect <=
+ kInnerShapeType_InfoMask >> kInnerShapeType_InfoBit);
+
+/**
+ * Additional parameters required by some instances (e.g. round rect radii, perspective column,
+ * local matrix). These are accessed via texel buffer.
+ */
+struct ParamsTexel {
+ float fX, fY, fZ, fW;
+};
+
+GR_STATIC_ASSERT(0 == offsetof(ParamsTexel, fX));
+GR_STATIC_ASSERT(4 * 4 == sizeof(ParamsTexel));
+
+/**
+ * Tracks all information needed in order to draw a batch of instances. This struct also serves
+ * as an all-in-one shader key for the batch.
+ */
+struct BatchInfo {
+ BatchInfo() : fData(0) {}
+ explicit BatchInfo(uint32_t data) : fData(data) {}
+
+ static bool CanCombine(const BatchInfo& a, const BatchInfo& b);
+
+ bool isSimpleRects() const {
+ return !((fShapeTypes & ~kRect_ShapeFlag) | fInnerShapeTypes);
+ }
+
+ union {
+ struct {
+ AntialiasMode fAntialiasMode;
+ uint8_t fShapeTypes;
+ uint8_t fInnerShapeTypes;
+ bool fHasPerspective : 1;
+ bool fHasLocalMatrix : 1;
+ bool fHasParams : 1;
+ bool fNonSquare : 1;
+ bool fUsesLocalCoords : 1;
+ bool fCannotTweakAlphaForCoverage : 1;
+ bool fCannotDiscard : 1;
+ };
+ uint32_t fData;
+ };
+};
+
+inline bool BatchInfo::CanCombine(const BatchInfo& a, const BatchInfo& b) {
+ if (a.fAntialiasMode != b.fAntialiasMode) {
+ return false;
+ }
+ if (SkToBool(a.fInnerShapeTypes) != SkToBool(b.fInnerShapeTypes)) {
+ // GrInstanceProcessor can't currently combine draws with and without inner shapes.
+ return false;
+ }
+ if (a.fCannotDiscard != b.fCannotDiscard) {
+ // For stencil draws, the use of discard can be a requirement.
+ return false;
+ }
+ return true;
+}
+
+inline BatchInfo operator|(const BatchInfo& a, const BatchInfo& b) {
+ SkASSERT(BatchInfo::CanCombine(a, b));
+ return BatchInfo(a.fData | b.fData);
+}
+
+// This is required since all the data must fit into 32 bits of a shader key.
+GR_STATIC_ASSERT(sizeof(uint32_t) == sizeof(BatchInfo));
+GR_STATIC_ASSERT(kNumShapeTypes <= 8);
+
+struct IndexRange {
+ bool operator ==(const IndexRange& that) const {
+ SkASSERT(fStart != that.fStart || fCount == that.fCount);
+ return fStart == that.fStart;
+ }
+ bool operator !=(const IndexRange& that) const { return !(*this == that); }
+
+ bool isEmpty() const { return fCount <= 0; }
+ int end() { return fStart + fCount; }
+
+ int16_t fStart;
+ int16_t fCount;
+};
+
+}
+
+#endif