| /* | |
| * 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 "GrAnalyticRectBatch.h" | |
| #include "GrBatchFlushState.h" | |
| #include "GrBatchTest.h" | |
| #include "GrGeometryProcessor.h" | |
| #include "GrInvariantOutput.h" | |
| #include "GrProcessor.h" | |
| #include "GrResourceProvider.h" | |
| #include "SkRRect.h" | |
| #include "SkStrokeRec.h" | |
| #include "batches/GrVertexBatch.h" | |
| #include "glsl/GrGLSLFragmentShaderBuilder.h" | |
| #include "glsl/GrGLSLGeometryProcessor.h" | |
| #include "glsl/GrGLSLProgramDataManager.h" | |
| #include "glsl/GrGLSLVarying.h" | |
| #include "glsl/GrGLSLVertexShaderBuilder.h" | |
| #include "glsl/GrGLSLUniformHandler.h" | |
| #include "glsl/GrGLSLUtil.h" | |
| namespace { | |
| struct RectVertex { | |
| SkPoint fPos; | |
| GrColor fColor; | |
| SkPoint fCenter; | |
| SkVector fDownDir; | |
| SkScalar fHalfWidth; | |
| SkScalar fHalfHeight; | |
| }; | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * The output of this effect is the input color and coverage for an arbitrarily oriented rect. The | |
| * rect is specified as: | |
| * Center of the rect | |
| * Unit vector point down the height of the rect | |
| * Half width + 0.5 | |
| * Half height + 0.5 | |
| * The center and vector are stored in a vec4 varying ("RectEdge") with the | |
| * center in the xy components and the vector in the zw components. | |
| * The munged width and height are stored in a vec2 varying ("WidthHeight") | |
| * with the width in x and the height in y. | |
| */ | |
| class RectGeometryProcessor : public GrGeometryProcessor { | |
| public: | |
| RectGeometryProcessor(const SkMatrix& localMatrix) : fLocalMatrix(localMatrix) { | |
| this->initClassID<RectGeometryProcessor>(); | |
| fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType, | |
| kHigh_GrSLPrecision); | |
| fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType); | |
| fInRectEdge = &this->addVertexAttrib("inRectEdge", kVec4f_GrVertexAttribType); | |
| fInWidthHeight = &this->addVertexAttrib("inWidthHeight", kVec2f_GrVertexAttribType); | |
| } | |
| bool implementsDistanceVector() const override { return true; }; | |
| const Attribute* inPosition() const { return fInPosition; } | |
| const Attribute* inColor() const { return fInColor; } | |
| const Attribute* inRectEdge() const { return fInRectEdge; } | |
| const Attribute* inWidthHeight() const { return fInWidthHeight; } | |
| const SkMatrix& localMatrix() const { return fLocalMatrix; } | |
| virtual ~RectGeometryProcessor() {} | |
| const char* name() const override { return "RectEdge"; } | |
| class GLSLProcessor : public GrGLSLGeometryProcessor { | |
| public: | |
| GLSLProcessor() {} | |
| void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{ | |
| const RectGeometryProcessor& rgp = args.fGP.cast<RectGeometryProcessor>(); | |
| GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; | |
| GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; | |
| GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; | |
| // emit attributes | |
| varyingHandler->emitAttributes(rgp); | |
| // setup the varying for the position | |
| GrGLSLVertToFrag positionVary(kVec2f_GrSLType); | |
| varyingHandler->addVarying("Position", &positionVary); | |
| vertBuilder->codeAppendf("%s = %s;", positionVary.vsOut(), rgp.inPosition()->fName); | |
| // setup the varying for the center point and the unit vector that points down the | |
| // height of the rect | |
| GrGLSLVertToFrag rectEdgeVary(kVec4f_GrSLType); | |
| varyingHandler->addVarying("RectEdge", &rectEdgeVary); | |
| vertBuilder->codeAppendf("%s = %s;", rectEdgeVary.vsOut(), rgp.inRectEdge()->fName); | |
| // setup the varying for the width/2+.5 and height/2+.5 | |
| GrGLSLVertToFrag widthHeightVary(kVec2f_GrSLType); | |
| varyingHandler->addVarying("WidthHeight", &widthHeightVary); | |
| vertBuilder->codeAppendf("%s = %s;", | |
| widthHeightVary.vsOut(), rgp.inWidthHeight()->fName); | |
| GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder; | |
| // setup pass through color | |
| varyingHandler->addPassThroughAttribute(rgp.inColor(), args.fOutputColor); | |
| // Setup position | |
| this->setupPosition(vertBuilder, gpArgs, rgp.inPosition()->fName); | |
| // emit transforms | |
| this->emitTransforms(vertBuilder, | |
| varyingHandler, | |
| uniformHandler, | |
| gpArgs->fPositionVar, | |
| rgp.inPosition()->fName, | |
| rgp.localMatrix(), | |
| args.fTransformsIn, | |
| args.fTransformsOut); | |
| // TODO: compute all these offsets, spans, and scales in the VS | |
| fragBuilder->codeAppendf("float insetW = min(1.0, %s.x) - 0.5;", | |
| widthHeightVary.fsIn()); | |
| fragBuilder->codeAppendf("float insetH = min(1.0, %s.y) - 0.5;", | |
| widthHeightVary.fsIn()); | |
| fragBuilder->codeAppend("float outset = 0.5;"); | |
| // For rects > 1 pixel wide and tall the span's are noops (i.e., 1.0). For rects | |
| // < 1 pixel wide or tall they serve to normalize the < 1 ramp to a 0 .. 1 range. | |
| fragBuilder->codeAppend("float spanW = insetW + outset;"); | |
| fragBuilder->codeAppend("float spanH = insetH + outset;"); | |
| // For rects < 1 pixel wide or tall, these scale factors are used to cap the maximum | |
| // value of coverage that is used. In other words it is the coverage that is | |
| // used in the interior of the rect after the ramp. | |
| fragBuilder->codeAppend("float scaleW = min(1.0, 2.0*insetW/spanW);"); | |
| fragBuilder->codeAppend("float scaleH = min(1.0, 2.0*insetH/spanH);"); | |
| // Compute the coverage for the rect's width | |
| fragBuilder->codeAppendf("vec2 offset = %s.xy - %s.xy;", | |
| positionVary.fsIn(), rectEdgeVary.fsIn()); | |
| fragBuilder->codeAppendf("float perpDot = abs(offset.x * %s.w - offset.y * %s.z);", | |
| rectEdgeVary.fsIn(), rectEdgeVary.fsIn()); | |
| if (args.fDistanceVectorName) { | |
| fragBuilder->codeAppendf("float widthDistance = %s.x - perpDot;", | |
| widthHeightVary.fsIn()); | |
| } | |
| fragBuilder->codeAppendf( | |
| "float coverage = scaleW*clamp((%s.x-perpDot)/spanW, 0.0, 1.0);", | |
| widthHeightVary.fsIn()); | |
| // Compute the coverage for the rect's height and merge with the width | |
| fragBuilder->codeAppendf("perpDot = abs(dot(offset, %s.zw));", | |
| rectEdgeVary.fsIn()); | |
| if (args.fDistanceVectorName) { | |
| fragBuilder->codeAppendf("float heightDistance = %s.y - perpDot;", | |
| widthHeightVary.fsIn()); | |
| } | |
| fragBuilder->codeAppendf( | |
| "coverage = coverage*scaleH*clamp((%s.y-perpDot)/spanH, 0.0, 1.0);", | |
| widthHeightVary.fsIn()); | |
| fragBuilder->codeAppendf("%s = vec4(coverage);", args.fOutputCoverage); | |
| if (args.fDistanceVectorName) { | |
| fragBuilder->codeAppend( "// Calculating distance vector\n"); | |
| fragBuilder->codeAppend( "vec2 dvAxis;"); | |
| fragBuilder->codeAppend( "float dvLength;"); | |
| fragBuilder->codeAppend( "if (heightDistance < widthDistance) {"); | |
| fragBuilder->codeAppendf(" dvAxis = %s.zw;", rectEdgeVary.fsIn()); | |
| fragBuilder->codeAppend( " dvLength = heightDistance;"); | |
| fragBuilder->codeAppend( "} else {"); | |
| fragBuilder->codeAppendf(" dvAxis = vec2(-%s.w, %s.z);", | |
| rectEdgeVary.fsIn(), rectEdgeVary.fsIn()); | |
| fragBuilder->codeAppend( " dvLength = widthDistance;"); | |
| fragBuilder->codeAppend( "}"); | |
| fragBuilder->codeAppend( "float dvSign = sign(dot(offset, dvAxis));"); | |
| fragBuilder->codeAppendf("%s = vec4(dvSign * dvAxis, dvLength, 0.0);", | |
| args.fDistanceVectorName); | |
| } | |
| } | |
| static void GenKey(const GrGeometryProcessor& gp, | |
| const GrGLSLCaps&, | |
| GrProcessorKeyBuilder* b) { | |
| b->add32(0x0); | |
| } | |
| void setData(const GrGLSLProgramDataManager& pdman, | |
| const GrPrimitiveProcessor& gp) override {} | |
| void setTransformData(const GrPrimitiveProcessor& primProc, | |
| const GrGLSLProgramDataManager& pdman, | |
| int index, | |
| const SkTArray<const GrCoordTransform*, true>& transforms) override { | |
| this->setTransformDataHelper(primProc.cast<RectGeometryProcessor>().fLocalMatrix, pdman, | |
| index, transforms); | |
| } | |
| private: | |
| typedef GrGLSLGeometryProcessor INHERITED; | |
| }; | |
| void getGLSLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override { | |
| GLSLProcessor::GenKey(*this, caps, b); | |
| } | |
| GrGLSLPrimitiveProcessor* createGLSLInstance(const GrGLSLCaps&) const override { | |
| return new GLSLProcessor(); | |
| } | |
| private: | |
| SkMatrix fLocalMatrix; | |
| const Attribute* fInPosition; | |
| const Attribute* fInColor; | |
| const Attribute* fInRectEdge; | |
| const Attribute* fInWidthHeight; | |
| GR_DECLARE_GEOMETRY_PROCESSOR_TEST; | |
| typedef GrGeometryProcessor INHERITED; | |
| }; | |
| GR_DEFINE_GEOMETRY_PROCESSOR_TEST(RectGeometryProcessor); | |
| sk_sp<GrGeometryProcessor> RectGeometryProcessor::TestCreate(GrProcessorTestData* d) { | |
| return sk_sp<GrGeometryProcessor>( | |
| new RectGeometryProcessor(GrTest::TestMatrix(d->fRandom))); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| class AnalyticRectBatch : public GrVertexBatch { | |
| public: | |
| DEFINE_BATCH_CLASS_ID | |
| AnalyticRectBatch(GrColor color, const SkMatrix& viewMatrix, const SkRect& rect, | |
| const SkRect& croppedRect, const SkRect& bounds) | |
| : INHERITED(ClassID()) | |
| , fViewMatrixIfUsingLocalCoords(viewMatrix) { | |
| SkPoint center = SkPoint::Make(rect.centerX(), rect.centerY()); | |
| viewMatrix.mapPoints(¢er, 1); | |
| SkScalar halfWidth = viewMatrix.mapRadius(SkScalarHalf(rect.width())); | |
| SkScalar halfHeight = viewMatrix.mapRadius(SkScalarHalf(rect.height())); | |
| SkVector downDir = viewMatrix.mapVector(0.0f, 1.0f); | |
| downDir.normalize(); | |
| SkRect deviceSpaceCroppedRect = croppedRect; | |
| viewMatrix.mapRect(&deviceSpaceCroppedRect); | |
| fGeoData.emplace_back(Geometry {color, center, downDir, halfWidth, halfHeight, | |
| deviceSpaceCroppedRect}); | |
| this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo); | |
| } | |
| const char* name() const override { return "AnalyticRectBatch"; } | |
| SkString dumpInfo() const override { | |
| SkString string; | |
| for (int i = 0; i < fGeoData.count(); ++i) { | |
| string.appendf("Color: 0x%08x Rect [C:(%.2f, %.2f) D:<%.2f,%.3f> W/2:%.2f H/2:%.2f]\n", | |
| fGeoData[i].fColor, | |
| fGeoData[i].fCenter.x(), fGeoData[i].fCenter.y(), | |
| fGeoData[i].fDownDir.x(), fGeoData[i].fDownDir.y(), | |
| fGeoData[i].fHalfWidth, | |
| fGeoData[i].fHalfHeight); | |
| } | |
| string.append(INHERITED::dumpInfo()); | |
| return string; | |
| } | |
| void computePipelineOptimizations(GrInitInvariantOutput* color, | |
| GrInitInvariantOutput* coverage, | |
| GrBatchToXPOverrides* overrides) const override { | |
| // When this is called on a batch, there is only one geometry bundle | |
| color->setKnownFourComponents(fGeoData[0].fColor); | |
| coverage->setUnknownSingleComponent(); | |
| } | |
| private: | |
| void initBatchTracker(const GrXPOverridesForBatch& overrides) override { | |
| // Handle any overrides that affect our GP. | |
| overrides.getOverrideColorIfSet(&fGeoData[0].fColor); | |
| if (!overrides.readsLocalCoords()) { | |
| fViewMatrixIfUsingLocalCoords.reset(); | |
| } | |
| } | |
| void onPrepareDraws(Target* target) const override { | |
| SkMatrix localMatrix; | |
| if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { | |
| return; | |
| } | |
| // Setup geometry processor | |
| SkAutoTUnref<GrGeometryProcessor> gp(new RectGeometryProcessor(localMatrix)); | |
| int instanceCount = fGeoData.count(); | |
| size_t vertexStride = gp->getVertexStride(); | |
| SkASSERT(vertexStride == sizeof(RectVertex)); | |
| QuadHelper helper; | |
| RectVertex* verts = reinterpret_cast<RectVertex*>(helper.init(target, vertexStride, | |
| instanceCount)); | |
| if (!verts) { | |
| return; | |
| } | |
| for (int i = 0; i < instanceCount; i++) { | |
| const Geometry& geom = fGeoData[i]; | |
| GrColor color = geom.fColor; | |
| SkPoint center = geom.fCenter; | |
| SkVector downDir = geom.fDownDir; | |
| SkScalar halfWidth = geom.fHalfWidth; | |
| SkScalar halfHeight = geom.fHalfHeight; | |
| SkRect croppedRect = geom.fCroppedRect; | |
| SkVector rightDir; | |
| downDir.rotateCCW(&rightDir); | |
| verts[0].fPos = {croppedRect.fLeft, croppedRect.fTop}; | |
| verts[0].fColor = color; | |
| verts[0].fCenter = center; | |
| verts[0].fDownDir = downDir; | |
| verts[0].fHalfWidth = halfWidth; | |
| verts[0].fHalfHeight = halfHeight; | |
| verts[1].fPos = {croppedRect.fRight, croppedRect.fTop}; | |
| verts[1].fColor = color; | |
| verts[1].fCenter = center; | |
| verts[1].fDownDir = downDir; | |
| verts[1].fHalfWidth = halfWidth; | |
| verts[1].fHalfHeight = halfHeight; | |
| verts[2].fPos = {croppedRect.fRight, croppedRect.fBottom}; | |
| verts[2].fColor = color; | |
| verts[2].fCenter = center; | |
| verts[2].fDownDir = downDir; | |
| verts[2].fHalfWidth = halfWidth; | |
| verts[2].fHalfHeight = halfHeight; | |
| verts[3].fPos = {croppedRect.fLeft, croppedRect.fBottom}; | |
| verts[3].fColor = color; | |
| verts[3].fCenter = center; | |
| verts[3].fDownDir = downDir; | |
| verts[3].fHalfWidth = halfWidth; | |
| verts[3].fHalfHeight = halfHeight; | |
| verts += kVerticesPerQuad; | |
| } | |
| helper.recordDraw(target, gp); | |
| } | |
| bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override { | |
| AnalyticRectBatch* that = t->cast<AnalyticRectBatch>(); | |
| if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(), | |
| that->bounds(), caps)) { | |
| return false; | |
| } | |
| if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) { | |
| return false; | |
| } | |
| fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin()); | |
| this->joinBounds(*that); | |
| return true; | |
| } | |
| struct Geometry { | |
| GrColor fColor; | |
| SkPoint fCenter; | |
| SkVector fDownDir; | |
| SkScalar fHalfWidth; | |
| SkScalar fHalfHeight; | |
| SkRect fCroppedRect; | |
| }; | |
| SkMatrix fViewMatrixIfUsingLocalCoords; | |
| SkSTArray<1, Geometry, true> fGeoData; | |
| typedef GrVertexBatch INHERITED; | |
| }; | |
| GrDrawBatch* GrAnalyticRectBatch::CreateAnalyticRectBatch(GrColor color, | |
| const SkMatrix& viewMatrix, | |
| const SkRect& rect, | |
| const SkRect& croppedRect, | |
| const SkRect& bounds) { | |
| return new AnalyticRectBatch(color, viewMatrix, rect, croppedRect, bounds); | |
| } | |
| #ifdef GR_TEST_UTILS | |
| DRAW_BATCH_TEST_DEFINE(AnalyticRectBatch) { | |
| SkMatrix viewMatrix = GrTest::TestMatrix(random); | |
| GrColor color = GrRandomColor(random); | |
| SkRect rect = GrTest::TestSquare(random); | |
| SkRect croppedRect = GrTest::TestSquare(random); | |
| SkRect bounds = GrTest::TestSquare(random); | |
| return new AnalyticRectBatch(color, viewMatrix, rect, croppedRect, bounds); | |
| } | |
| #endif |