diff options
-rw-r--r-- | bench/ShapesBench.cpp | 6 | ||||
-rw-r--r-- | gyp/gpu.gypi | 9 | ||||
-rw-r--r-- | include/gpu/GrDrawContext.h | 2 | ||||
-rw-r--r-- | include/private/GrInstancedPipelineInfo.h | 49 | ||||
-rw-r--r-- | src/gpu/GrDrawContext.cpp | 94 | ||||
-rw-r--r-- | src/gpu/GrDrawContextPriv.h | 5 | ||||
-rw-r--r-- | src/gpu/GrDrawTarget.cpp | 12 | ||||
-rw-r--r-- | src/gpu/GrDrawTarget.h | 33 | ||||
-rw-r--r-- | src/gpu/GrDrawingManager.cpp | 14 | ||||
-rw-r--r-- | src/gpu/GrGpu.h | 10 | ||||
-rw-r--r-- | src/gpu/gl/GrGLGpu.cpp | 7 | ||||
-rw-r--r-- | src/gpu/gl/GrGLGpu.h | 5 | ||||
-rw-r--r-- | src/gpu/instanced/GLInstancedRendering.cpp | 301 | ||||
-rw-r--r-- | src/gpu/instanced/GLInstancedRendering.h | 60 | ||||
-rw-r--r-- | src/gpu/instanced/InstanceProcessor.cpp | 2096 | ||||
-rw-r--r-- | src/gpu/instanced/InstanceProcessor.h | 63 | ||||
-rw-r--r-- | src/gpu/instanced/InstancedRendering.cpp | 488 | ||||
-rw-r--r-- | src/gpu/instanced/InstancedRendering.h | 187 | ||||
-rw-r--r-- | src/gpu/instanced/InstancedRenderingTypes.h | 192 |
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 |