/* * Copyright 2013 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ // This test only works with the GPU backend. #include "gm/gm.h" #include "include/core/SkBlendMode.h" #include "include/core/SkCanvas.h" #include "include/core/SkMatrix.h" #include "include/core/SkPaint.h" #include "include/core/SkPoint.h" #include "include/core/SkPoint3.h" #include "include/core/SkRect.h" #include "include/core/SkRefCnt.h" #include "include/core/SkScalar.h" #include "include/core/SkSize.h" #include "include/core/SkString.h" #include "include/core/SkTypes.h" #include "include/gpu/GrRecordingContext.h" #include "include/private/GrSharedEnums.h" #include "include/private/GrTypesPriv.h" #include "include/private/SkColorData.h" #include "include/utils/SkRandom.h" #include "src/core/SkGeometry.h" #include "src/core/SkPointPriv.h" #include "src/gpu/GrCaps.h" #include "src/gpu/GrDirectContextPriv.h" #include "src/gpu/GrGeometryProcessor.h" #include "src/gpu/GrMemoryPool.h" #include "src/gpu/GrOpFlushState.h" #include "src/gpu/GrOpsRenderPass.h" #include "src/gpu/GrPaint.h" #include "src/gpu/GrProcessorAnalysis.h" #include "src/gpu/GrProcessorSet.h" #include "src/gpu/GrProgramInfo.h" #include "src/gpu/GrRecordingContextPriv.h" #include "src/gpu/GrSurfaceDrawContext.h" #include "src/gpu/GrUserStencilSettings.h" #include "src/gpu/effects/GrBezierEffect.h" #include "src/gpu/effects/GrPorterDuffXferProcessor.h" #include "src/gpu/geometry/GrPathUtils.h" #include "src/gpu/ops/GrDrawOp.h" #include "src/gpu/ops/GrMeshDrawOp.h" #include "src/gpu/ops/GrOp.h" #include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" #include #include class GrAppliedClip; namespace skiagm { class BezierTestOp : public GrMeshDrawOp { public: FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; } GrProcessorSet::Analysis finalize( const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage, GrClampType clampType) override { return fProcessorSet.finalize( fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, &GrUserStencilSettings::kUnused, hasMixedSampledCoverage, caps, clampType, &fColor); } void visitProxies(const VisitProxyFunc& func) const override { if (fProgramInfo) { fProgramInfo->visitFPProxies(func); } else { fProcessorSet.visitProxies(func); } } protected: BezierTestOp(const SkRect& rect, const SkPMColor4f& color, int32_t classID) : INHERITED(classID) , fRect(rect) , fColor(color) , fProcessorSet(SkBlendMode::kSrc) { this->setBounds(rect, HasAABloat::kYes, IsHairline::kNo); } virtual GrGeometryProcessor* makeGP(const GrCaps& caps, SkArenaAlloc* arena) = 0; GrProgramInfo* programInfo() override { return fProgramInfo; } void onCreateProgramInfo(const GrCaps* caps, SkArenaAlloc* arena, const GrSurfaceProxyView& writeView, GrAppliedClip&& appliedClip, const GrXferProcessor::DstProxyView& dstProxyView, GrXferBarrierFlags renderPassXferBarriers, GrLoadOp colorLoadOp) override { auto gp = this->makeGP(*caps, arena); if (!gp) { return; } GrPipeline::InputFlags flags = GrPipeline::InputFlags::kNone; fProgramInfo = GrSimpleMeshDrawOpHelper::CreateProgramInfo(caps, arena, writeView, std::move(appliedClip), dstProxyView, gp, std::move(fProcessorSet), GrPrimitiveType::kTriangles, renderPassXferBarriers, colorLoadOp, flags); } void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) final { if (!fProgramInfo) { this->createProgramInfo(flushState); } if (!fProgramInfo) { return; } flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds); flushState->bindTextures(fProgramInfo->primProc(), nullptr, fProgramInfo->pipeline()); flushState->drawMesh(*fMesh); } const SkRect& rect() const { return fRect; } const SkPMColor4f& color() const { return fColor; } protected: GrSimpleMesh* fMesh = nullptr; // filled in by the derived classes private: SkRect fRect; SkPMColor4f fColor; GrProcessorSet fProcessorSet; GrProgramInfo* fProgramInfo = nullptr; using INHERITED = GrMeshDrawOp; }; /** * This GM directly exercises effects that draw Bezier curves in the GPU backend. */ class BezierConicTestOp : public BezierTestOp { public: DEFINE_OP_CLASS_ID const char* name() const final { return "BezierConicTestOp"; } static GrOp::Owner Make(GrRecordingContext* context, const SkRect& rect, const SkPMColor4f& color, const SkMatrix& klm) { return GrOp::Make(context, rect, color, klm); } private: friend class ::GrOp; // for ctor BezierConicTestOp(const SkRect& rect, const SkPMColor4f& color, const SkMatrix& klm) : INHERITED(rect, color, ClassID()) , fKLM(klm) {} struct Vertex { SkPoint fPosition; float fKLM[4]; // The last value is ignored. The effect expects a vec4f. }; GrGeometryProcessor* makeGP(const GrCaps& caps, SkArenaAlloc* arena) final { auto tmp = GrConicEffect::Make(arena, this->color(), SkMatrix::I(), caps, SkMatrix::I(), false); if (!tmp) { return nullptr; } SkASSERT(tmp->vertexStride() == sizeof(Vertex)); return tmp; } void onPrepareDraws(Target* target) final { QuadHelper helper(target, sizeof(Vertex), 1); Vertex* verts = reinterpret_cast(helper.vertices()); if (!verts) { return; } SkRect rect = this->rect(); SkPointPriv::SetRectTriStrip(&verts[0].fPosition, rect, sizeof(Vertex)); for (int v = 0; v < 4; ++v) { SkPoint3 pt3 = {verts[v].fPosition.x(), verts[v].fPosition.y(), 1.f}; fKLM.mapHomogeneousPoints((SkPoint3* ) verts[v].fKLM, &pt3, 1); } fMesh = helper.mesh(); } SkMatrix fKLM; static constexpr int kVertsPerCubic = 4; static constexpr int kIndicesPerCubic = 6; using INHERITED = BezierTestOp; }; /** * This GM directly exercises effects that draw Bezier curves in the GPU backend. */ class BezierConicEffects : public GpuGM { public: BezierConicEffects() { this->setBGColor(0xFFFFFFFF); } protected: static const int kNumConics = 10; static const int kCellWidth = 128; static const int kCellHeight = 128; SkString onShortName() override { return SkString("bezier_conic_effects"); } SkISize onISize() override { return SkISize::Make(kCellWidth, kNumConics*kCellHeight); } void onDraw(GrRecordingContext* context, GrSurfaceDrawContext* surfaceDrawContext, SkCanvas* canvas) override { const SkScalar w = kCellWidth, h = kCellHeight; const SkPMColor4f kOpaqueBlack = SkPMColor4f::FromBytes_RGBA(0xff000000); const SkPoint baseControlPts[kNumConics][3] = { { { 0.31f * w, 0.01f * h}, { 0.48f * w, 0.74f * h }, { 0.19f * w, 0.33f * h } }, { { 0.00f * w, 0.07f * h}, { 0.30f * w, 0.70f * h }, { 0.47f * w, 0.37f * h } }, { { 0.15f * w, 0.23f * h}, { 0.49f * w, 0.87f * h }, { 0.85f * w, 0.66f * h } }, { { 0.09f * w, 0.15f * h}, { 0.42f * w, 0.33f * h }, { 0.17f * w, 0.38f * h } }, { { 0.98f * w, 0.54f * h}, { 0.83f * w, 0.91f * h }, { 0.62f * w, 0.40f * h } }, { { 0.96f * w, 0.65f * h}, { 0.03f * w, 0.79f * h }, { 0.24f * w, 0.56f * h } }, { { 0.57f * w, 0.12f * h}, { 0.33f * w, 0.67f * h }, { 0.59f * w, 0.33f * h } }, { { 0.12f * w, 0.72f * h}, { 0.69f * w, 0.85f * h }, { 0.46f * w, 0.32f * h } }, { { 0.27f * w, 0.49f * h}, { 0.41f * w, 0.02f * h }, { 0.11f * w, 0.42f * h } }, { { 0.40f * w, 0.13f * h}, { 0.83f * w, 0.30f * h }, { 0.31f * w, 0.68f * h } }, }; const SkScalar weights[kNumConics] = { 0.62f, 0.01f, 0.95f, 1.48f, 0.37f, 0.66f, 0.15f, 0.14f, 0.61f, 1.4f }; SkPaint ctrlPtPaint; ctrlPtPaint.setColor(SK_ColorRED); SkPaint choppedPtPaint; choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000); SkPaint polyPaint; polyPaint.setColor(0xffA0A0A0); polyPaint.setStrokeWidth(0); polyPaint.setStyle(SkPaint::kStroke_Style); SkPaint boundsPaint; boundsPaint.setColor(0xff808080); boundsPaint.setStrokeWidth(0); boundsPaint.setStyle(SkPaint::kStroke_Style); for (int row = 0; row < kNumConics; ++row) { SkScalar x = 0; SkScalar y = row * h; SkPoint controlPts[] = { {x + baseControlPts[row][0].fX, y + baseControlPts[row][0].fY}, {x + baseControlPts[row][1].fX, y + baseControlPts[row][1].fY}, {x + baseControlPts[row][2].fX, y + baseControlPts[row][2].fY} }; for (int i = 0; i < 3; ++i) { canvas->drawCircle(controlPts[i], 6.f, ctrlPtPaint); } canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, controlPts, polyPaint); SkConic dst[4]; SkMatrix klm; int cnt = ChopConic(controlPts, dst, weights[row]); GrPathUtils::getConicKLM(controlPts, weights[row], &klm); for (int c = 0; c < cnt; ++c) { SkPoint* pts = dst[c].fPts; for (int i = 0; i < 3; ++i) { canvas->drawCircle(pts[i], 3.f, choppedPtPaint); } SkRect bounds; bounds.setBounds(pts, 3); canvas->drawRect(bounds, boundsPaint); GrOp::Owner op = BezierConicTestOp::Make(context, bounds, kOpaqueBlack, klm); surfaceDrawContext->addDrawOp(std::move(op)); } } } private: // Uses the max curvature function for quads to estimate // where to chop the conic. If the max curvature is not // found along the curve segment it will return 1 and // dst[0] is the original conic. If it returns 2 the dst[0] // and dst[1] are the two new conics. static int SplitConic(const SkPoint src[3], SkConic dst[2], const SkScalar weight) { SkScalar t = SkFindQuadMaxCurvature(src); if (t == 0 || t == 1) { if (dst) { dst[0].set(src, weight); } return 1; } else { if (dst) { SkConic conic; conic.set(src, weight); if (!conic.chopAt(t, dst)) { dst[0].set(src, weight); return 1; } } return 2; } } // Calls SplitConic on the entire conic and then once more on each subsection. // Most cases will result in either 1 conic (chop point is not within t range) // or 3 points (split once and then one subsection is split again). static int ChopConic(const SkPoint src[3], SkConic dst[4], const SkScalar weight) { SkConic dstTemp[2]; int conicCnt = SplitConic(src, dstTemp, weight); if (2 == conicCnt) { int conicCnt2 = SplitConic(dstTemp[0].fPts, dst, dstTemp[0].fW); conicCnt = conicCnt2 + SplitConic(dstTemp[1].fPts, &dst[conicCnt2], dstTemp[1].fW); } else { dst[0] = dstTemp[0]; } return conicCnt; } using INHERITED = GM; }; ////////////////////////////////////////////////////////////////////////////// class BezierQuadTestOp : public BezierTestOp { public: DEFINE_OP_CLASS_ID const char* name() const override { return "BezierQuadTestOp"; } static GrOp::Owner Make(GrRecordingContext* context, const SkRect& rect, const SkPMColor4f& color, const GrPathUtils::QuadUVMatrix& devToUV) { return GrOp::Make(context, rect, color, devToUV); } private: friend class ::GrOp; // for ctor BezierQuadTestOp(const SkRect& rect, const SkPMColor4f& color, const GrPathUtils::QuadUVMatrix& devToUV) : INHERITED(rect, color, ClassID()) , fDevToUV(devToUV) {} struct Vertex { SkPoint fPosition; float fKLM[4]; // The last value is ignored. The effect expects a vec4f. }; GrGeometryProcessor* makeGP(const GrCaps& caps, SkArenaAlloc* arena) final { auto tmp = GrQuadEffect::Make(arena, this->color(), SkMatrix::I(), caps, SkMatrix::I(), false); if (!tmp) { return nullptr; } SkASSERT(tmp->vertexStride() == sizeof(Vertex)); return tmp; } void onPrepareDraws(Target* target) final { QuadHelper helper(target, sizeof(Vertex), 1); Vertex* verts = reinterpret_cast(helper.vertices()); if (!verts) { return; } SkRect rect = this->rect(); SkPointPriv::SetRectTriStrip(&verts[0].fPosition, rect, sizeof(Vertex)); fDevToUV.apply(verts, 4, sizeof(Vertex), sizeof(SkPoint)); fMesh = helper.mesh(); } GrPathUtils::QuadUVMatrix fDevToUV; static constexpr int kVertsPerCubic = 4; static constexpr int kIndicesPerCubic = 6; using INHERITED = BezierTestOp; }; /** * This GM directly exercises effects that draw Bezier quad curves in the GPU backend. */ class BezierQuadEffects : public GpuGM { public: BezierQuadEffects() { this->setBGColor(0xFFFFFFFF); } protected: static const int kNumQuads = 5; static const int kCellWidth = 128; static const int kCellHeight = 128; SkString onShortName() override { return SkString("bezier_quad_effects"); } SkISize onISize() override { return SkISize::Make(kCellWidth, kNumQuads*kCellHeight); } void onDraw(GrRecordingContext* context, GrSurfaceDrawContext* surfaceDrawContext, SkCanvas* canvas) override { const SkScalar w = kCellWidth, h = kCellHeight; const SkPMColor4f kOpaqueBlack = SkPMColor4f::FromBytes_RGBA(0xff000000); const SkPoint baseControlPts[kNumQuads][3] = { { { 0.31f * w, 0.01f * h}, { 0.48f * w, 0.74f * h }, { 0.19f * w, 0.33f * h } }, { { 0.00f * w, 0.07f * h}, { 0.30f * w, 0.70f * h }, { 0.47f * w, 0.37f * h } }, { { 0.15f * w, 0.23f * h}, { 0.49f * w, 0.87f * h }, { 0.85f * w, 0.66f * h } }, { { 0.09f * w, 0.15f * h}, { 0.42f * w, 0.33f * h }, { 0.17f * w, 0.38f * h } }, { { 0.98f * w, 0.54f * h}, { 0.83f * w, 0.91f * h }, { 0.62f * w, 0.40f * h } }, }; SkPaint ctrlPtPaint; ctrlPtPaint.setColor(SK_ColorRED); SkPaint choppedPtPaint; choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000); SkPaint polyPaint; polyPaint.setColor(0xffA0A0A0); polyPaint.setStrokeWidth(0); polyPaint.setStyle(SkPaint::kStroke_Style); SkPaint boundsPaint; boundsPaint.setColor(0xff808080); boundsPaint.setStrokeWidth(0); boundsPaint.setStyle(SkPaint::kStroke_Style); for (int row = 0; row < kNumQuads; ++row) { SkScalar x = 0; SkScalar y = row * h; SkPoint controlPts[] = { {x + baseControlPts[row][0].fX, y + baseControlPts[row][0].fY}, {x + baseControlPts[row][1].fX, y + baseControlPts[row][1].fY}, {x + baseControlPts[row][2].fX, y + baseControlPts[row][2].fY} }; for (int i = 0; i < 3; ++i) { canvas->drawCircle(controlPts[i], 6.f, ctrlPtPaint); } canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, controlPts, polyPaint); SkPoint chopped[5]; int cnt = SkChopQuadAtMaxCurvature(controlPts, chopped); for (int c = 0; c < cnt; ++c) { SkPoint* pts = chopped + 2 * c; for (int i = 0; i < 3; ++i) { canvas->drawCircle(pts[i], 3.f, choppedPtPaint); } SkRect bounds; bounds.setBounds(pts, 3); canvas->drawRect(bounds, boundsPaint); GrPathUtils::QuadUVMatrix DevToUV(pts); GrOp::Owner op = BezierQuadTestOp::Make(context, bounds, kOpaqueBlack, DevToUV); surfaceDrawContext->addDrawOp(std::move(op)); } } } private: using INHERITED = GM; }; DEF_GM(return new BezierConicEffects;) DEF_GM(return new BezierQuadEffects;) } // namespace skiagm