/* * 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/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(); 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(); 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.fFPCoordTransformHandler); // 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& primProc, FPCoordTransformIter&& transformIter) override { const RectGeometryProcessor& rgp = primProc.cast(); this->setTransformDataHelper(rgp.fLocalMatrix, pdman,&transformIter); } 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 RectGeometryProcessor::TestCreate(GrProcessorTestData* d) { return sk_sp( 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 gp(new RectGeometryProcessor(localMatrix)); int instanceCount = fGeoData.count(); size_t vertexStride = gp->getVertexStride(); SkASSERT(vertexStride == sizeof(RectVertex)); QuadHelper helper; RectVertex* verts = reinterpret_cast(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(); 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