| /* | |
| * Copyright 2015 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "GrColor.h" | |
| #include "GrDefaultGeoProcFactory.h" | |
| #include "GrMeshDrawOp.h" | |
| #include "GrOpFlushState.h" | |
| #include "GrRectOpFactory.h" | |
| #include "GrResourceKey.h" | |
| #include "GrResourceProvider.h" | |
| #include "GrTypes.h" | |
| #include "SkMatrix.h" | |
| #include "SkMatrixPriv.h" | |
| #include "SkRect.h" | |
| #include "SkPointPriv.h" | |
| #include "ops/GrSimpleMeshDrawOpHelper.h" | |
| #include <new> | |
| GR_DECLARE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey); | |
| static inline bool view_matrix_ok_for_aa_fill_rect(const SkMatrix& viewMatrix) { | |
| return viewMatrix.preservesRightAngles(); | |
| } | |
| static inline void set_inset_fan(SkPoint* pts, size_t stride, const SkRect& r, SkScalar dx, | |
| SkScalar dy) { | |
| SkPointPriv::SetRectFan(pts, r.fLeft + dx, r.fTop + dy, r.fRight - dx, r.fBottom - dy, stride); | |
| } | |
| static const int kNumAAFillRectsInIndexBuffer = 256; | |
| static const int kVertsPerAAFillRect = 8; | |
| static const int kIndicesPerAAFillRect = 30; | |
| static sk_sp<const GrBuffer> get_index_buffer(GrResourceProvider* resourceProvider) { | |
| GR_DEFINE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey); | |
| // clang-format off | |
| static const uint16_t gFillAARectIdx[] = { | |
| 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, | |
| }; | |
| // clang-format on | |
| GR_STATIC_ASSERT(SK_ARRAY_COUNT(gFillAARectIdx) == kIndicesPerAAFillRect); | |
| return resourceProvider->findOrCreatePatternedIndexBuffer( | |
| gFillAARectIdx, kIndicesPerAAFillRect, kNumAAFillRectsInIndexBuffer, | |
| kVertsPerAAFillRect, gAAFillRectIndexBufferKey); | |
| } | |
| static void generate_aa_fill_rect_geometry(intptr_t verts, | |
| size_t vertexStride, | |
| GrColor color, | |
| const SkMatrix& viewMatrix, | |
| const SkRect& rect, | |
| const SkRect& devRect, | |
| bool tweakAlphaForCoverage, | |
| const SkMatrix* localMatrix) { | |
| SkPoint* fan0Pos = reinterpret_cast<SkPoint*>(verts); | |
| SkPoint* fan1Pos = reinterpret_cast<SkPoint*>(verts + 4 * vertexStride); | |
| SkScalar inset; | |
| if (viewMatrix.rectStaysRect()) { | |
| inset = SkMinScalar(devRect.width(), SK_Scalar1); | |
| inset = SK_ScalarHalf * SkMinScalar(inset, devRect.height()); | |
| set_inset_fan(fan0Pos, vertexStride, devRect, -SK_ScalarHalf, -SK_ScalarHalf); | |
| set_inset_fan(fan1Pos, vertexStride, devRect, inset, inset); | |
| } else { | |
| // compute transformed (1, 0) and (0, 1) vectors | |
| SkVector vec[2] = {{viewMatrix[SkMatrix::kMScaleX], viewMatrix[SkMatrix::kMSkewY]}, | |
| {viewMatrix[SkMatrix::kMSkewX], viewMatrix[SkMatrix::kMScaleY]}}; | |
| SkScalar len1 = SkPoint::Normalize(&vec[0]); | |
| vec[0].scale(SK_ScalarHalf); | |
| SkScalar len2 = SkPoint::Normalize(&vec[1]); | |
| vec[1].scale(SK_ScalarHalf); | |
| inset = SkMinScalar(len1 * rect.width(), SK_Scalar1); | |
| inset = SK_ScalarHalf * SkMinScalar(inset, len2 * rect.height()); | |
| // create the rotated rect | |
| SkPointPriv::SetRectFan(fan0Pos, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, | |
| vertexStride); | |
| SkMatrixPriv::MapPointsWithStride(viewMatrix, fan0Pos, vertexStride, 4); | |
| // Now create the inset points and then outset the original | |
| // rotated points | |
| // TL | |
| *((SkPoint*)((intptr_t)fan1Pos + 0 * vertexStride)) = | |
| *((SkPoint*)((intptr_t)fan0Pos + 0 * vertexStride)) + vec[0] + vec[1]; | |
| *((SkPoint*)((intptr_t)fan0Pos + 0 * vertexStride)) -= vec[0] + vec[1]; | |
| // BL | |
| *((SkPoint*)((intptr_t)fan1Pos + 1 * vertexStride)) = | |
| *((SkPoint*)((intptr_t)fan0Pos + 1 * vertexStride)) + vec[0] - vec[1]; | |
| *((SkPoint*)((intptr_t)fan0Pos + 1 * vertexStride)) -= vec[0] - vec[1]; | |
| // BR | |
| *((SkPoint*)((intptr_t)fan1Pos + 2 * vertexStride)) = | |
| *((SkPoint*)((intptr_t)fan0Pos + 2 * vertexStride)) - vec[0] - vec[1]; | |
| *((SkPoint*)((intptr_t)fan0Pos + 2 * vertexStride)) += vec[0] + vec[1]; | |
| // TR | |
| *((SkPoint*)((intptr_t)fan1Pos + 3 * vertexStride)) = | |
| *((SkPoint*)((intptr_t)fan0Pos + 3 * vertexStride)) - vec[0] + vec[1]; | |
| *((SkPoint*)((intptr_t)fan0Pos + 3 * vertexStride)) += vec[0] - vec[1]; | |
| } | |
| if (localMatrix) { | |
| SkMatrix invViewMatrix; | |
| if (!viewMatrix.invert(&invViewMatrix)) { | |
| SkDebugf("View matrix is non-invertible, local coords will be wrong."); | |
| invViewMatrix = SkMatrix::I(); | |
| } | |
| SkMatrix localCoordMatrix; | |
| localCoordMatrix.setConcat(*localMatrix, invViewMatrix); | |
| SkPoint* fan0Loc = reinterpret_cast<SkPoint*>(verts + sizeof(SkPoint) + sizeof(GrColor)); | |
| SkMatrixPriv::MapPointsWithStride(localCoordMatrix, fan0Loc, vertexStride, fan0Pos, | |
| vertexStride, 8); | |
| } | |
| // Make verts point to vertex color and then set all the color and coverage vertex attrs | |
| // values. | |
| verts += sizeof(SkPoint); | |
| // The coverage offset is always the last vertex attribute | |
| intptr_t coverageOffset = vertexStride - sizeof(GrColor) - sizeof(SkPoint); | |
| for (int i = 0; i < 4; ++i) { | |
| if (tweakAlphaForCoverage) { | |
| *reinterpret_cast<GrColor*>(verts + i * vertexStride) = 0; | |
| } else { | |
| *reinterpret_cast<GrColor*>(verts + i * vertexStride) = color; | |
| *reinterpret_cast<float*>(verts + i * vertexStride + coverageOffset) = 0; | |
| } | |
| } | |
| int scale; | |
| if (inset < SK_ScalarHalf) { | |
| scale = SkScalarFloorToInt(512.0f * inset / (inset + SK_ScalarHalf)); | |
| SkASSERT(scale >= 0 && scale <= 255); | |
| } else { | |
| scale = 0xff; | |
| } | |
| verts += 4 * vertexStride; | |
| float innerCoverage = GrNormalizeByteToFloat(scale); | |
| GrColor scaledColor = (0xff == scale) ? color : SkAlphaMulQ(color, scale); | |
| for (int i = 0; i < 4; ++i) { | |
| if (tweakAlphaForCoverage) { | |
| *reinterpret_cast<GrColor*>(verts + i * vertexStride) = scaledColor; | |
| } else { | |
| *reinterpret_cast<GrColor*>(verts + i * vertexStride) = color; | |
| *reinterpret_cast<float*>(verts + i * vertexStride + coverageOffset) = innerCoverage; | |
| } | |
| } | |
| } | |
| namespace { | |
| class AAFillRectOp final : public GrMeshDrawOp { | |
| private: | |
| using Helper = GrSimpleMeshDrawOpHelperWithStencil; | |
| public: | |
| DEFINE_OP_CLASS_ID | |
| static std::unique_ptr<GrDrawOp> Make(GrContext* context, | |
| GrPaint&& paint, | |
| const SkMatrix& viewMatrix, | |
| const SkRect& rect, | |
| const SkRect& devRect, | |
| const SkMatrix* localMatrix, | |
| const GrUserStencilSettings* stencil) { | |
| SkASSERT(view_matrix_ok_for_aa_fill_rect(viewMatrix)); | |
| return Helper::FactoryHelper<AAFillRectOp>(context, std::move(paint), viewMatrix, rect, | |
| devRect, localMatrix, stencil); | |
| } | |
| AAFillRectOp(const Helper::MakeArgs& helperArgs, | |
| GrColor color, | |
| const SkMatrix& viewMatrix, | |
| const SkRect& rect, | |
| const SkRect& devRect, | |
| const SkMatrix* localMatrix, | |
| const GrUserStencilSettings* stencil) | |
| : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage, stencil) { | |
| if (localMatrix) { | |
| void* mem = fRectData.push_back_n(sizeof(RectWithLocalMatrixInfo)); | |
| new (mem) RectWithLocalMatrixInfo(color, viewMatrix, rect, devRect, *localMatrix); | |
| } else { | |
| void* mem = fRectData.push_back_n(sizeof(RectInfo)); | |
| new (mem) RectInfo(color, viewMatrix, rect, devRect); | |
| } | |
| IsZeroArea zeroArea = | |
| (!rect.width() || !rect.height()) ? IsZeroArea::kYes : IsZeroArea::kNo; | |
| this->setBounds(devRect, HasAABloat::kYes, zeroArea); | |
| fRectCnt = 1; | |
| } | |
| const char* name() const override { return "AAFillRectOp"; } | |
| void visitProxies(const VisitProxyFunc& func) const override { | |
| fHelper.visitProxies(func); | |
| } | |
| SkString dumpInfo() const override { | |
| SkString str; | |
| str.append(INHERITED::dumpInfo()); | |
| str.appendf("# combined: %d\n", fRectCnt); | |
| const RectInfo* info = this->first(); | |
| for (int i = 0; i < fRectCnt; ++i) { | |
| const SkRect& rect = info->rect(); | |
| str.appendf("%d: Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", i, | |
| info->color(), rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); | |
| info = this->next(info); | |
| } | |
| str += fHelper.dumpInfo(); | |
| str += INHERITED::dumpInfo(); | |
| return str; | |
| } | |
| FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); } | |
| RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override { | |
| GrColor color = this->first()->color(); | |
| auto result = fHelper.xpRequiresDstTexture( | |
| caps, clip, GrProcessorAnalysisCoverage::kSingleChannel, &color); | |
| this->first()->setColor(color); | |
| return result; | |
| } | |
| private: | |
| void onPrepareDraws(Target* target) override { | |
| using namespace GrDefaultGeoProcFactory; | |
| size_t vertexStride = sizeof(SkPoint) + sizeof(GrColor); | |
| Color color(Color::kPremulGrColorAttribute_Type); | |
| Coverage::Type coverageType = Coverage::kSolid_Type; | |
| if (!fHelper.compatibleWithAlphaAsCoverage()) { | |
| coverageType = Coverage::kAttribute_Type; | |
| vertexStride += sizeof(float); | |
| } | |
| LocalCoords lc = LocalCoords::kUnused_Type; | |
| if (fHelper.usesLocalCoords()) { | |
| lc = LocalCoords::kHasExplicit_Type; | |
| vertexStride += sizeof(SkPoint); | |
| } | |
| sk_sp<GrGeometryProcessor> gp = | |
| GrDefaultGeoProcFactory::Make(target->caps().shaderCaps(), color, coverageType, | |
| lc, SkMatrix::I()); | |
| if (!gp) { | |
| SkDebugf("Couldn't create GrGeometryProcessor\n"); | |
| return; | |
| } | |
| SkASSERT(vertexStride == gp->debugOnly_vertexStride()); | |
| sk_sp<const GrBuffer> indexBuffer = get_index_buffer(target->resourceProvider()); | |
| PatternHelper helper(target, GrPrimitiveType::kTriangles, vertexStride, indexBuffer.get(), | |
| kVertsPerAAFillRect, kIndicesPerAAFillRect, fRectCnt); | |
| void* vertices = helper.vertices(); | |
| if (!vertices || !indexBuffer) { | |
| SkDebugf("Could not allocate vertices\n"); | |
| return; | |
| } | |
| const RectInfo* info = this->first(); | |
| const SkMatrix* localMatrix = nullptr; | |
| for (int i = 0; i < fRectCnt; i++) { | |
| intptr_t verts = | |
| reinterpret_cast<intptr_t>(vertices) + i * kVertsPerAAFillRect * vertexStride; | |
| if (fHelper.usesLocalCoords()) { | |
| if (info->hasLocalMatrix()) { | |
| localMatrix = &static_cast<const RectWithLocalMatrixInfo*>(info)->localMatrix(); | |
| } else { | |
| localMatrix = &SkMatrix::I(); | |
| } | |
| } | |
| generate_aa_fill_rect_geometry(verts, vertexStride, info->color(), info->viewMatrix(), | |
| info->rect(), info->devRect(), | |
| fHelper.compatibleWithAlphaAsCoverage(), localMatrix); | |
| info = this->next(info); | |
| } | |
| auto pipe = fHelper.makePipeline(target); | |
| helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState); | |
| } | |
| CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { | |
| AAFillRectOp* that = t->cast<AAFillRectOp>(); | |
| if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { | |
| return CombineResult::kCannotCombine; | |
| } | |
| fRectData.push_back_n(that->fRectData.count(), that->fRectData.begin()); | |
| fRectCnt += that->fRectCnt; | |
| this->joinBounds(*that); | |
| return CombineResult::kMerged; | |
| } | |
| struct RectInfo { | |
| public: | |
| RectInfo(GrColor color, const SkMatrix& viewMatrix, const SkRect& rect, | |
| const SkRect& devRect) | |
| : RectInfo(color, viewMatrix, rect, devRect, HasLocalMatrix::kNo) {} | |
| bool hasLocalMatrix() const { return HasLocalMatrix::kYes == fHasLocalMatrix; } | |
| GrColor color() const { return fColor; } | |
| const SkMatrix& viewMatrix() const { return fViewMatrix; } | |
| const SkRect& rect() const { return fRect; } | |
| const SkRect& devRect() const { return fDevRect; } | |
| void setColor(GrColor color) { fColor = color; } | |
| protected: | |
| enum class HasLocalMatrix : uint32_t { kNo, kYes }; | |
| RectInfo(GrColor color, const SkMatrix& viewMatrix, const SkRect& rect, | |
| const SkRect& devRect, HasLocalMatrix hasLM) | |
| : fHasLocalMatrix(hasLM) | |
| , fColor(color) | |
| , fViewMatrix(viewMatrix) | |
| , fRect(rect) | |
| , fDevRect(devRect) {} | |
| HasLocalMatrix fHasLocalMatrix; | |
| GrColor fColor; | |
| SkMatrix fViewMatrix; | |
| SkRect fRect; | |
| SkRect fDevRect; | |
| }; | |
| struct RectWithLocalMatrixInfo : public RectInfo { | |
| public: | |
| RectWithLocalMatrixInfo(GrColor color, const SkMatrix& viewMatrix, const SkRect& rect, | |
| const SkRect& devRect, const SkMatrix& localMatrix) | |
| : RectInfo(color, viewMatrix, rect, devRect, HasLocalMatrix::kYes) | |
| , fLocalMatrix(localMatrix) {} | |
| const SkMatrix& localMatrix() const { return fLocalMatrix; } | |
| private: | |
| SkMatrix fLocalMatrix; | |
| }; | |
| RectInfo* first() { return reinterpret_cast<RectInfo*>(fRectData.begin()); } | |
| const RectInfo* first() const { return reinterpret_cast<const RectInfo*>(fRectData.begin()); } | |
| const RectInfo* next(const RectInfo* prev) const { | |
| intptr_t next = | |
| reinterpret_cast<intptr_t>(prev) + | |
| (prev->hasLocalMatrix() ? sizeof(RectWithLocalMatrixInfo) : sizeof(RectInfo)); | |
| return reinterpret_cast<const RectInfo*>(next); | |
| } | |
| SkSTArray<4 * sizeof(RectWithLocalMatrixInfo), uint8_t, true> fRectData; | |
| Helper fHelper; | |
| int fRectCnt; | |
| typedef GrMeshDrawOp INHERITED; | |
| }; | |
| } // anonymous namespace | |
| namespace GrRectOpFactory { | |
| std::unique_ptr<GrDrawOp> MakeAAFill(GrContext* context, | |
| GrPaint&& paint, | |
| const SkMatrix& viewMatrix, | |
| const SkRect& rect, | |
| const GrUserStencilSettings* stencil) { | |
| if (!view_matrix_ok_for_aa_fill_rect(viewMatrix)) { | |
| return nullptr; | |
| } | |
| SkRect devRect; | |
| viewMatrix.mapRect(&devRect, rect); | |
| return AAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, devRect, | |
| nullptr, stencil); | |
| } | |
| std::unique_ptr<GrDrawOp> MakeAAFillWithLocalMatrix(GrContext* context, | |
| GrPaint&& paint, | |
| const SkMatrix& viewMatrix, | |
| const SkMatrix& localMatrix, | |
| const SkRect& rect) { | |
| if (!view_matrix_ok_for_aa_fill_rect(viewMatrix)) { | |
| return nullptr; | |
| } | |
| SkRect devRect; | |
| viewMatrix.mapRect(&devRect, rect); | |
| return AAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, devRect, | |
| &localMatrix, nullptr); | |
| } | |
| std::unique_ptr<GrDrawOp> MakeAAFillWithLocalRect(GrContext* context, | |
| GrPaint&& paint, | |
| const SkMatrix& viewMatrix, | |
| const SkRect& rect, | |
| const SkRect& localRect) { | |
| if (!view_matrix_ok_for_aa_fill_rect(viewMatrix)) { | |
| return nullptr; | |
| } | |
| SkRect devRect; | |
| viewMatrix.mapRect(&devRect, rect); | |
| SkMatrix localMatrix; | |
| if (!localMatrix.setRectToRect(rect, localRect, SkMatrix::kFill_ScaleToFit)) { | |
| return nullptr; | |
| } | |
| return AAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, devRect, | |
| &localMatrix, nullptr); | |
| } | |
| } // namespace GrRectOpFactory | |
| /////////////////////////////////////////////////////////////////////////////////////////////////// | |
| #if GR_TEST_UTILS | |
| #include "GrDrawOpTest.h" | |
| GR_DRAW_OP_TEST_DEFINE(AAFillRectOp) { | |
| SkMatrix viewMatrix; | |
| do { | |
| viewMatrix = GrTest::TestMatrixInvertible(random); | |
| } while (!view_matrix_ok_for_aa_fill_rect(viewMatrix)); | |
| SkRect rect = GrTest::TestRect(random); | |
| SkRect devRect; | |
| viewMatrix.mapRect(&devRect, rect); | |
| const SkMatrix* localMatrix = nullptr; | |
| SkMatrix m; | |
| if (random->nextBool()) { | |
| m = GrTest::TestMatrix(random); | |
| } | |
| const GrUserStencilSettings* stencil = random->nextBool() ? nullptr | |
| : GrGetRandomStencil(random, context); | |
| return AAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, | |
| devRect, localMatrix, stencil); | |
| } | |
| #endif |