/* * Copyright 2017 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "GrTextureOp.h" #include <new> #include "GrAppliedClip.h" #include "GrCaps.h" #include "GrDrawOpTest.h" #include "GrGeometryProcessor.h" #include "GrGpu.h" #include "GrMemoryPool.h" #include "GrMeshDrawOp.h" #include "GrOpFlushState.h" #include "GrQuad.h" #include "GrQuadPerEdgeAA.h" #include "GrRecordingContext.h" #include "GrRecordingContextPriv.h" #include "GrResourceProvider.h" #include "GrResourceProviderPriv.h" #include "GrShaderCaps.h" #include "GrTexture.h" #include "GrTexturePriv.h" #include "GrTextureProxy.h" #include "SkGr.h" #include "SkMathPriv.h" #include "SkMatrixPriv.h" #include "SkPoint.h" #include "SkPoint3.h" #include "SkRectPriv.h" #include "SkTo.h" #include "glsl/GrGLSLVarying.h" namespace { using Domain = GrQuadPerEdgeAA::Domain; using VertexSpec = GrQuadPerEdgeAA::VertexSpec; using ColorType = GrQuadPerEdgeAA::ColorType; // if normalizing the domain then pass 1/width, 1/height, 1 for iw, ih, h. Otherwise pass // 1, 1, and height. static SkRect compute_domain(Domain domain, GrSamplerState::Filter filter, GrSurfaceOrigin origin, const SkRect& srcRect, float iw, float ih, float h) { static constexpr SkRect kLargeRect = {-100000, -100000, 1000000, 1000000}; if (domain == Domain::kNo) { // Either the quad has no domain constraint and is batched with a domain constrained op // (in which case we want a domain that doesn't restrict normalized tex coords), or the // entire op doesn't use the domain, in which case the returned value is ignored. return kLargeRect; } auto ltrb = Sk4f::Load(&srcRect); if (filter == GrSamplerState::Filter::kBilerp) { auto rblt = SkNx_shuffle<2, 3, 0, 1>(ltrb); auto whwh = (rblt - ltrb).abs(); auto c = (rblt + ltrb) * 0.5f; static const Sk4f kOffsets = {0.5f, 0.5f, -0.5f, -0.5f}; ltrb = (whwh < 1.f).thenElse(c, ltrb + kOffsets); } ltrb *= Sk4f(iw, ih, iw, ih); if (origin == kBottomLeft_GrSurfaceOrigin) { static const Sk4f kMul = {1.f, -1.f, 1.f, -1.f}; const Sk4f kAdd = {0.f, h, 0.f, h}; ltrb = SkNx_shuffle<0, 3, 2, 1>(kMul * ltrb + kAdd); } SkRect domainRect; ltrb.store(&domainRect); return domainRect; } // If normalizing the src quad then pass 1/width, 1/height, 1 for iw, ih, h. Otherwise pass // 1, 1, and height. static GrPerspQuad compute_src_quad_from_rect(GrSurfaceOrigin origin, const SkRect& srcRect, float iw, float ih, float h) { // Convert the pixel-space src rectangle into normalized texture coordinates SkRect texRect = { iw * srcRect.fLeft, ih * srcRect.fTop, iw * srcRect.fRight, ih * srcRect.fBottom }; if (origin == kBottomLeft_GrSurfaceOrigin) { texRect.fTop = h - texRect.fTop; texRect.fBottom = h - texRect.fBottom; } return GrPerspQuad(texRect); } // Normalizes logical src coords and corrects for origin static GrPerspQuad compute_src_quad(GrSurfaceOrigin origin, const GrPerspQuad& srcQuad, float iw, float ih, float h) { // The src quad should not have any perspective SkASSERT(!srcQuad.hasPerspective()); Sk4f xs = srcQuad.x4f() * iw; Sk4f ys = srcQuad.y4f() * ih; if (origin == kBottomLeft_GrSurfaceOrigin) { ys = h - ys; } return GrPerspQuad(xs, ys); } /** * Op that implements GrTextureOp::Make. It draws textured quads. Each quad can modulate against a * the texture by color. The blend with the destination is always src-over. The edges are non-AA. */ class TextureOp final : public GrMeshDrawOp { public: static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context, sk_sp<GrTextureProxy> proxy, GrSamplerState::Filter filter, const SkPMColor4f& color, const SkRect& srcRect, const SkRect& dstRect, GrAAType aaType, GrQuadAAFlags aaFlags, SkCanvas::SrcRectConstraint constraint, const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> textureColorSpaceXform) { GrPerspQuad dstQuad = GrPerspQuad::MakeFromRect(dstRect, viewMatrix); GrQuadType dstQuadType = GrQuadTypeForTransformedRect(viewMatrix); if (dstQuadType == GrQuadType::kRect) { // Disable filtering if possible (note AA optimizations for rects are automatically // handled above in GrResolveAATypeForQuad). if (filter != GrSamplerState::Filter::kNearest && !GrTextureOp::GetFilterHasEffect(viewMatrix, srcRect, dstRect)) { filter = GrSamplerState::Filter::kNearest; } } GrOpMemoryPool* pool = context->priv().opMemoryPool(); // srcRect provides both local coords and domain (if needed), so use nullptr for srcQuad return pool->allocate<TextureOp>( std::move(proxy), filter, color, dstQuad, dstQuadType, srcRect, constraint, nullptr, GrQuadType::kRect, aaType, aaFlags, std::move(textureColorSpaceXform)); } static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context, sk_sp<GrTextureProxy> proxy, GrSamplerState::Filter filter, const SkPMColor4f& color, const SkPoint srcQuad[4], const SkPoint dstQuad[4], GrAAType aaType, GrQuadAAFlags aaFlags, const SkRect* domain, const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> textureColorSpaceXform) { GrPerspQuad grDstQuad = GrPerspQuad::MakeFromSkQuad(dstQuad, viewMatrix); GrQuadType dstQuadType = viewMatrix.hasPerspective() ? GrQuadType::kPerspective : GrQuadType::kStandard; GrPerspQuad grSrcQuad = GrPerspQuad::MakeFromSkQuad(srcQuad, SkMatrix::I()); // If constraint remains fast, the value in srcRect will be ignored since srcQuads provides // the local coordinates and a domain won't be used. SkRect srcRect = SkRect::MakeEmpty(); SkCanvas::SrcRectConstraint constraint = SkCanvas::kFast_SrcRectConstraint; if (domain) { srcRect = *domain; constraint = SkCanvas::kStrict_SrcRectConstraint; } GrOpMemoryPool* pool = context->priv().opMemoryPool(); // Pass domain as srcRect if provided, but send srcQuad as a GrPerspQuad for local coords return pool->allocate<TextureOp>( std::move(proxy), filter, color, grDstQuad, dstQuadType, srcRect, constraint, &grSrcQuad, GrQuadType::kStandard, aaType, aaFlags, std::move(textureColorSpaceXform)); } static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context, const GrRenderTargetContext::TextureSetEntry set[], int cnt, GrSamplerState::Filter filter, GrAAType aaType, const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> textureColorSpaceXform) { size_t size = sizeof(TextureOp) + sizeof(Proxy) * (cnt - 1); GrOpMemoryPool* pool = context->priv().opMemoryPool(); void* mem = pool->allocate(size); return std::unique_ptr<GrDrawOp>(new (mem) TextureOp(set, cnt, filter, aaType, viewMatrix, std::move(textureColorSpaceXform))); } ~TextureOp() override { for (unsigned p = 0; p < fProxyCnt; ++p) { if (fFinalized) { fProxies[p].fProxy->completedRead(); } else { fProxies[p].fProxy->unref(); } } } const char* name() const override { return "TextureOp"; } void visitProxies(const VisitProxyFunc& func, VisitorType visitor) const override { if (visitor == VisitorType::kAllocatorGather && fCanSkipAllocatorGather) { return; } for (unsigned p = 0; p < fProxyCnt; ++p) { func(fProxies[p].fProxy); } } #ifdef SK_DEBUG SkString dumpInfo() const override { SkString str; str.appendf("# draws: %d\n", fQuads.count()); int q = 0; for (unsigned p = 0; p < fProxyCnt; ++p) { str.appendf("Proxy ID: %d, Filter: %d\n", fProxies[p].fProxy->uniqueID().asUInt(), static_cast<int>(fFilter)); for (int i = 0; i < fProxies[p].fQuadCnt; ++i, ++q) { GrPerspQuad quad = fQuads[q]; const ColorDomainAndAA& info = fQuads.metadata(i); str.appendf( "%d: Color: 0x%08x, TexRect [L: %.2f, T: %.2f, R: %.2f, B: %.2f] " "Quad [(%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f)]\n", i, info.fColor.toBytes_RGBA(), info.fSrcRect.fLeft, info.fSrcRect.fTop, info.fSrcRect.fRight, info.fSrcRect.fBottom, quad.point(0).fX, quad.point(0).fY, quad.point(1).fX, quad.point(1).fY, quad.point(2).fX, quad.point(2).fY, quad.point(3).fX, quad.point(3).fY); } } str += INHERITED::dumpInfo(); return str; } #endif GrProcessorSet::Analysis finalize( const GrCaps&, const GrAppliedClip*, GrFSAAType, GrClampType) override { SkASSERT(!fFinalized); fFinalized = true; for (unsigned p = 0; p < fProxyCnt; ++p) { fProxies[p].fProxy->addPendingRead(); fProxies[p].fProxy->unref(); } return GrProcessorSet::EmptySetAnalysis(); } FixedFunctionFlags fixedFunctionFlags() const override { return this->aaType() == GrAAType::kMSAA ? FixedFunctionFlags::kUsesHWAA : FixedFunctionFlags::kNone; } DEFINE_OP_CLASS_ID private: friend class ::GrOpMemoryPool; // dstQuad and dstQuadType should be the geometry transformed by the view matrix. // srcRect represents original src rect and will be used as the domain when constraint is strict // If srcQuad is provided, it will be used for the local coords instead of srcRect, although // srcRect will still specify the domain constraint if needed. TextureOp(sk_sp<GrTextureProxy> proxy, GrSamplerState::Filter filter, const SkPMColor4f& color, const GrPerspQuad& dstQuad, GrQuadType dstQuadType, const SkRect& srcRect, SkCanvas::SrcRectConstraint constraint, const GrPerspQuad* srcQuad, GrQuadType srcQuadType, GrAAType aaType, GrQuadAAFlags aaFlags, sk_sp<GrColorSpaceXform> textureColorSpaceXform) : INHERITED(ClassID()) , fTextureColorSpaceXform(std::move(textureColorSpaceXform)) , fFilter(static_cast<unsigned>(filter)) , fFinalized(0) { // Clean up disparities between the overall aa type and edge configuration and apply // optimizations based on the rect and matrix when appropriate GrResolveAATypeForQuad(aaType, aaFlags, dstQuad, dstQuadType, &aaType, &aaFlags); fAAType = static_cast<unsigned>(aaType); // We expect our caller to have already caught this optimization. SkASSERT(!srcRect.contains(proxy->getWorstCaseBoundsRect()) || constraint == SkCanvas::kFast_SrcRectConstraint); // We may have had a strict constraint with nearest filter solely due to possible AA bloat. // If we don't have (or determined we don't need) coverage AA then we can skip using a // domain. if (constraint == SkCanvas::kStrict_SrcRectConstraint && this->filter() == GrSamplerState::Filter::kNearest && aaType != GrAAType::kCoverage) { constraint = SkCanvas::kFast_SrcRectConstraint; } Domain domain = constraint == SkCanvas::kStrict_SrcRectConstraint ? Domain::kYes : Domain::kNo; // Initially, if srcQuad is provided it will always be at index 0 of fSrcQuads fQuads.push_back(dstQuad, dstQuadType, {color, srcRect, srcQuad ? 0 : -1, domain, aaFlags}); if (srcQuad) { fSrcQuads.push_back(*srcQuad, srcQuadType); } fProxyCnt = 1; fProxies[0] = {proxy.release(), 1}; auto bounds = dstQuad.bounds(dstQuadType); this->setBounds(bounds, HasAABloat(aaType == GrAAType::kCoverage), IsZeroArea::kNo); fDomain = static_cast<unsigned>(domain); fColorType = static_cast<unsigned>(GrQuadPerEdgeAA::MinColorType(color)); fCanSkipAllocatorGather = static_cast<unsigned>(fProxies[0].fProxy->canSkipResourceAllocator()); } TextureOp(const GrRenderTargetContext::TextureSetEntry set[], int cnt, GrSamplerState::Filter filter, GrAAType aaType, const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> textureColorSpaceXform) : INHERITED(ClassID()) , fTextureColorSpaceXform(std::move(textureColorSpaceXform)) , fFilter(static_cast<unsigned>(filter)) , fFinalized(0) { fProxyCnt = SkToUInt(cnt); SkRect bounds = SkRectPriv::MakeLargestInverted(); GrAAType overallAAType = GrAAType::kNone; // aa type maximally compatible with all dst rects bool mustFilter = false; fCanSkipAllocatorGather = static_cast<unsigned>(true); // Most dst rects are transformed by the same view matrix, so their quad types start // identical, unless an entry provides a dstClip or additional transform that changes it. // The quad list will automatically adapt to that. fQuads.reserve(cnt, GrQuadTypeForTransformedRect(viewMatrix)); bool allOpaque = true; for (unsigned p = 0; p < fProxyCnt; ++p) { fProxies[p].fProxy = SkRef(set[p].fProxy.get()); fProxies[p].fQuadCnt = 1; SkASSERT(fProxies[p].fProxy->textureType() == fProxies[0].fProxy->textureType()); SkASSERT(fProxies[p].fProxy->config() == fProxies[0].fProxy->config()); if (!fProxies[p].fProxy->canSkipResourceAllocator()) { fCanSkipAllocatorGather = static_cast<unsigned>(false); } SkMatrix ctm = viewMatrix; if (set[p].fPreViewMatrix) { ctm.preConcat(*set[p].fPreViewMatrix); } // Use dstRect unless dstClip is provided, which is assumed to be a quad auto quad = set[p].fDstClipQuad == nullptr ? GrPerspQuad::MakeFromRect(set[p].fDstRect, ctm) : GrPerspQuad::MakeFromSkQuad(set[p].fDstClipQuad, ctm); GrQuadType quadType = GrQuadTypeForTransformedRect(ctm); if (set[p].fDstClipQuad && quadType != GrQuadType::kPerspective) { quadType = GrQuadType::kStandard; } bounds.joinPossiblyEmptyRect(quad.bounds(quadType)); GrQuadAAFlags aaFlags; // Don't update the overall aaType, might be inappropriate for some of the quads GrAAType aaForQuad; GrResolveAATypeForQuad(aaType, set[p].fAAFlags, quad, quadType, &aaForQuad, &aaFlags); // Resolve sets aaForQuad to aaType or None, there is never a change between aa methods SkASSERT(aaForQuad == GrAAType::kNone || aaForQuad == aaType); if (overallAAType == GrAAType::kNone && aaForQuad != GrAAType::kNone) { overallAAType = aaType; } if (!mustFilter && this->filter() != GrSamplerState::Filter::kNearest) { mustFilter = quadType != GrQuadType::kRect || GrTextureOp::GetFilterHasEffect(ctm, set[p].fSrcRect, set[p].fDstRect); } float alpha = SkTPin(set[p].fAlpha, 0.f, 1.f); allOpaque &= (1.f == alpha); SkPMColor4f color{alpha, alpha, alpha, alpha}; int srcQuadIndex = -1; if (set[p].fDstClipQuad) { // Derive new source coordinates that match dstClip's relative locations in dstRect, // but with respect to srcRect SkPoint srcQuad[4]; GrMapRectPoints(set[p].fDstRect, set[p].fSrcRect, set[p].fDstClipQuad, srcQuad, 4); fSrcQuads.push_back(GrPerspQuad::MakeFromSkQuad(srcQuad, SkMatrix::I()), GrQuadType::kStandard); srcQuadIndex = fSrcQuads.count() - 1; } fQuads.push_back(quad, quadType, {color, set[p].fSrcRect, srcQuadIndex, Domain::kNo, aaFlags}); } fAAType = static_cast<unsigned>(overallAAType); if (!mustFilter) { fFilter = static_cast<unsigned>(GrSamplerState::Filter::kNearest); } this->setBounds(bounds, HasAABloat(this->aaType() == GrAAType::kCoverage), IsZeroArea::kNo); fDomain = static_cast<unsigned>(false); fColorType = static_cast<unsigned>(allOpaque ? ColorType::kNone : ColorType::kByte); } void tess(void* v, const VertexSpec& spec, const GrTextureProxy* proxy, int start, int cnt) const { TRACE_EVENT0("skia", TRACE_FUNC); auto origin = proxy->origin(); const auto* texture = proxy->peekTexture(); float iw, ih, h; if (proxy->textureType() == GrTextureType::kRectangle) { iw = ih = 1.f; h = texture->height(); } else { iw = 1.f / texture->width(); ih = 1.f / texture->height(); h = 1.f; } for (int i = start; i < start + cnt; ++i) { const GrPerspQuad& device = fQuads[i]; const ColorDomainAndAA& info = fQuads.metadata(i); GrPerspQuad srcQuad = info.fSrcQuadIndex >= 0 ? compute_src_quad(origin, fSrcQuads[info.fSrcQuadIndex], iw, ih, h) : compute_src_quad_from_rect(origin, info.fSrcRect, iw, ih, h); SkRect domain = compute_domain(info.domain(), this->filter(), origin, info.fSrcRect, iw, ih, h); v = GrQuadPerEdgeAA::Tessellate(v, spec, device, info.fColor, srcQuad, domain, info.aaFlags()); } } void onPrepareDraws(Target* target) override { TRACE_EVENT0("skia", TRACE_FUNC); GrQuadType quadType = GrQuadType::kRect; GrQuadType srcQuadType = GrQuadType::kRect; Domain domain = Domain::kNo; ColorType colorType = ColorType::kNone; int numProxies = 0; int numTotalQuads = 0; auto textureType = fProxies[0].fProxy->textureType(); auto config = fProxies[0].fProxy->config(); GrAAType aaType = this->aaType(); for (const auto& op : ChainRange<TextureOp>(this)) { if (op.fQuads.quadType() > quadType) { quadType = op.fQuads.quadType(); } if (op.fSrcQuads.quadType() > srcQuadType) { // Should only become more general if there are quads to use instead of fSrcRect SkASSERT(op.fSrcQuads.count() > 0); srcQuadType = op.fSrcQuads.quadType(); } if (op.fDomain) { domain = Domain::kYes; } colorType = SkTMax(colorType, static_cast<ColorType>(op.fColorType)); numProxies += op.fProxyCnt; for (unsigned p = 0; p < op.fProxyCnt; ++p) { numTotalQuads += op.fProxies[p].fQuadCnt; auto* proxy = op.fProxies[p].fProxy; if (!proxy->instantiate(target->resourceProvider())) { return; } SkASSERT(proxy->config() == config); SkASSERT(proxy->textureType() == textureType); } if (op.aaType() == GrAAType::kCoverage) { SkASSERT(aaType == GrAAType::kCoverage || aaType == GrAAType::kNone); aaType = GrAAType::kCoverage; } } VertexSpec vertexSpec(quadType, colorType, srcQuadType, /* hasLocal */ true, domain, aaType, /* alpha as coverage */ true); GrSamplerState samplerState = GrSamplerState(GrSamplerState::WrapMode::kClamp, this->filter()); GrGpu* gpu = target->resourceProvider()->priv().gpu(); uint32_t extraSamplerKey = gpu->getExtraSamplerKeyForProgram( samplerState, fProxies[0].fProxy->backendFormat()); sk_sp<GrGeometryProcessor> gp = GrQuadPerEdgeAA::MakeTexturedProcessor( vertexSpec, *target->caps().shaderCaps(), textureType, config, samplerState, extraSamplerKey, std::move(fTextureColorSpaceXform)); // We'll use a dynamic state array for the GP textures when there are multiple ops. // Otherwise, we use fixed dynamic state to specify the single op's proxy. GrPipeline::DynamicStateArrays* dynamicStateArrays = nullptr; GrPipeline::FixedDynamicState* fixedDynamicState; if (numProxies > 1) { dynamicStateArrays = target->allocDynamicStateArrays(numProxies, 1, false); fixedDynamicState = target->makeFixedDynamicState(0); } else { fixedDynamicState = target->makeFixedDynamicState(1); fixedDynamicState->fPrimitiveProcessorTextures[0] = fProxies[0].fProxy; } size_t vertexSize = gp->vertexStride(); GrMesh* meshes = target->allocMeshes(numProxies); sk_sp<const GrBuffer> vbuffer; int vertexOffsetInBuffer = 0; int numQuadVerticesLeft = numTotalQuads * vertexSpec.verticesPerQuad(); int numAllocatedVertices = 0; void* vdata = nullptr; int m = 0; for (const auto& op : ChainRange<TextureOp>(this)) { int q = 0; for (unsigned p = 0; p < op.fProxyCnt; ++p) { int quadCnt = op.fProxies[p].fQuadCnt; auto* proxy = op.fProxies[p].fProxy; int meshVertexCnt = quadCnt * vertexSpec.verticesPerQuad(); if (numAllocatedVertices < meshVertexCnt) { vdata = target->makeVertexSpaceAtLeast( vertexSize, meshVertexCnt, numQuadVerticesLeft, &vbuffer, &vertexOffsetInBuffer, &numAllocatedVertices); SkASSERT(numAllocatedVertices <= numQuadVerticesLeft); if (!vdata) { SkDebugf("Could not allocate vertices\n"); return; } } SkASSERT(numAllocatedVertices >= meshVertexCnt); op.tess(vdata, vertexSpec, proxy, q, quadCnt); if (!GrQuadPerEdgeAA::ConfigureMeshIndices(target, &(meshes[m]), vertexSpec, quadCnt)) { SkDebugf("Could not allocate indices"); return; } meshes[m].setVertexData(vbuffer, vertexOffsetInBuffer); if (dynamicStateArrays) { dynamicStateArrays->fPrimitiveProcessorTextures[m] = proxy; } ++m; numAllocatedVertices -= meshVertexCnt; numQuadVerticesLeft -= meshVertexCnt; vertexOffsetInBuffer += meshVertexCnt; vdata = reinterpret_cast<char*>(vdata) + vertexSize * meshVertexCnt; q += quadCnt; } } SkASSERT(!numQuadVerticesLeft); SkASSERT(!numAllocatedVertices); target->recordDraw( std::move(gp), meshes, numProxies, fixedDynamicState, dynamicStateArrays); } void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { auto pipelineFlags = (GrAAType::kMSAA == this->aaType()) ? GrPipeline::kHWAntialias_Flag : 0; flushState->executeDrawsAndUploadsForMeshDrawOp( this, chainBounds, GrProcessorSet::MakeEmptySet(), pipelineFlags); } CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override { TRACE_EVENT0("skia", TRACE_FUNC); const auto* that = t->cast<TextureOp>(); if (!GrColorSpaceXform::Equals(fTextureColorSpaceXform.get(), that->fTextureColorSpaceXform.get())) { return CombineResult::kCannotCombine; } bool upgradeToCoverageAAOnMerge = false; if (this->aaType() != that->aaType()) { if (!((this->aaType() == GrAAType::kCoverage && that->aaType() == GrAAType::kNone) || (that->aaType() == GrAAType::kCoverage && this->aaType() == GrAAType::kNone))) { return CombineResult::kCannotCombine; } upgradeToCoverageAAOnMerge = true; } if (fFilter != that->fFilter) { return CombineResult::kCannotCombine; } auto thisProxy = fProxies[0].fProxy; auto thatProxy = that->fProxies[0].fProxy; if (fProxyCnt > 1 || that->fProxyCnt > 1 || thisProxy->uniqueID() != thatProxy->uniqueID()) { // We can't merge across different proxies. Check if 'this' can be chained with 'that'. if (GrTextureProxy::ProxiesAreCompatibleAsDynamicState(thisProxy, thatProxy) && caps.dynamicStateArrayGeometryProcessorTextureSupport()) { return CombineResult::kMayChain; } return CombineResult::kCannotCombine; } fDomain |= that->fDomain; fColorType = SkTMax(fColorType, that->fColorType); if (upgradeToCoverageAAOnMerge) { fAAType = static_cast<unsigned>(GrAAType::kCoverage); } // Concatenate quad lists together, updating the fSrcQuadIndex in the appended quads // to account for the new starting index in fSrcQuads int srcQuadOffset = fSrcQuads.count(); int oldQuadCount = fQuads.count(); fSrcQuads.concat(that->fSrcQuads); fQuads.concat(that->fQuads); fProxies[0].fQuadCnt += that->fQuads.count(); if (that->fSrcQuads.count() > 0) { // Some of the concatenated quads pointed to fSrcQuads, so adjust the indices for the // newly appended quads for (int i = oldQuadCount; i < fQuads.count(); ++i) { if (fQuads.metadata(i).fSrcQuadIndex >= 0) { fQuads.metadata(i).fSrcQuadIndex += srcQuadOffset; } } } // Confirm all tracked state makes sense when in debug builds #ifdef SK_DEBUG SkASSERT(fSrcQuads.count() <= fQuads.count()); for (int i = 0; i < fQuads.count(); ++i) { int srcIndex = fQuads.metadata(i).fSrcQuadIndex; if (srcIndex >= 0) { // Make sure it points to a valid index, in the right region of the list SkASSERT(srcIndex < fSrcQuads.count()); SkASSERT((i < oldQuadCount && srcIndex < srcQuadOffset) || (i >= oldQuadCount && srcIndex >= srcQuadOffset)); } } #endif return CombineResult::kMerged; } GrAAType aaType() const { return static_cast<GrAAType>(fAAType); } GrSamplerState::Filter filter() const { return static_cast<GrSamplerState::Filter>(fFilter); } struct ColorDomainAndAA { // Special constructor to convert enums into the packed bits, which should not delete // the implicit move constructor (but it does require us to declare an empty ctor for // use with the GrTQuadList). ColorDomainAndAA(const SkPMColor4f& color, const SkRect& srcRect, int srcQuadIndex, Domain hasDomain, GrQuadAAFlags aaFlags) : fColor(color) , fSrcRect(srcRect) , fSrcQuadIndex(srcQuadIndex) , fHasDomain(static_cast<unsigned>(hasDomain)) , fAAFlags(static_cast<unsigned>(aaFlags)) { SkASSERT(fHasDomain == static_cast<unsigned>(hasDomain)); SkASSERT(fAAFlags == static_cast<unsigned>(aaFlags)); } ColorDomainAndAA() = default; SkPMColor4f fColor; // Even if fSrcQuadIndex provides source coords, use fSrcRect for domain constraint SkRect fSrcRect; // If >= 0, use to access fSrcQuads instead of fSrcRect for the source coordinates int fSrcQuadIndex; unsigned fHasDomain : 1; unsigned fAAFlags : 4; Domain domain() const { return Domain(fHasDomain); } GrQuadAAFlags aaFlags() const { return static_cast<GrQuadAAFlags>(fAAFlags); } }; struct Proxy { GrTextureProxy* fProxy; int fQuadCnt; }; GrTQuadList<ColorDomainAndAA> fQuads; // The majority of texture ops will not track a complete src quad so this is indexed separately // and may be of different size to fQuads. GrQuadList fSrcQuads; sk_sp<GrColorSpaceXform> fTextureColorSpaceXform; unsigned fFilter : 2; unsigned fAAType : 2; unsigned fDomain : 1; unsigned fColorType : 2; GR_STATIC_ASSERT(GrQuadPerEdgeAA::kColorTypeCount <= 4); // Used to track whether fProxy is ref'ed or has a pending IO after finalize() is called. unsigned fFinalized : 1; unsigned fCanSkipAllocatorGather : 1; unsigned fProxyCnt : 32 - 9; Proxy fProxies[1]; static_assert(kGrQuadTypeCount <= 4, "GrQuadType does not fit in 2 bits"); typedef GrMeshDrawOp INHERITED; }; } // anonymous namespace namespace GrTextureOp { std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context, sk_sp<GrTextureProxy> proxy, GrSamplerState::Filter filter, const SkPMColor4f& color, const SkRect& srcRect, const SkRect& dstRect, GrAAType aaType, GrQuadAAFlags aaFlags, SkCanvas::SrcRectConstraint constraint, const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> textureColorSpaceXform) { return TextureOp::Make(context, std::move(proxy), filter, color, srcRect, dstRect, aaType, aaFlags, constraint, viewMatrix, std::move(textureColorSpaceXform)); } std::unique_ptr<GrDrawOp> MakeQuad(GrRecordingContext* context, sk_sp<GrTextureProxy> proxy, GrSamplerState::Filter filter, const SkPMColor4f& color, const SkPoint srcQuad[4], const SkPoint dstQuad[4], GrAAType aaType, GrQuadAAFlags aaFlags, const SkRect* domain, const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> textureXform) { return TextureOp::Make(context, std::move(proxy), filter, color, srcQuad, dstQuad, aaType, aaFlags, domain, viewMatrix, std::move(textureXform)); } std::unique_ptr<GrDrawOp> MakeSet(GrRecordingContext* context, const GrRenderTargetContext::TextureSetEntry set[], int cnt, GrSamplerState::Filter filter, GrAAType aaType, const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> textureColorSpaceXform) { return TextureOp::Make(context, set, cnt, filter, aaType, viewMatrix, std::move(textureColorSpaceXform)); } bool GetFilterHasEffect(const SkMatrix& viewMatrix, const SkRect& srcRect, const SkRect& dstRect) { // Hypothetically we could disable bilerp filtering when flipping or rotating 90 degrees, but // that makes the math harder and we don't want to increase the overhead of the checks if (!viewMatrix.isScaleTranslate() || viewMatrix.getScaleX() < 0.0f || viewMatrix.getScaleY() < 0.0f) { return true; } // Given the matrix conditions ensured above, this computes the device space coordinates for // the top left corner of dstRect and its size. SkScalar dw = viewMatrix.getScaleX() * dstRect.width(); SkScalar dh = viewMatrix.getScaleY() * dstRect.height(); SkScalar dl = viewMatrix.getScaleX() * dstRect.fLeft + viewMatrix.getTranslateX(); SkScalar dt = viewMatrix.getScaleY() * dstRect.fTop + viewMatrix.getTranslateY(); // Disable filtering when there is no scaling of the src rect and the src rect and dst rect // align fractionally. If we allow inverted src rects this logic needs to consider that. SkASSERT(srcRect.isSorted()); return dw != srcRect.width() || dh != srcRect.height() || SkScalarFraction(dl) != SkScalarFraction(srcRect.fLeft) || SkScalarFraction(dt) != SkScalarFraction(srcRect.fTop); } } // namespace GrTextureOp #if GR_TEST_UTILS #include "GrProxyProvider.h" #include "GrRecordingContext.h" #include "GrRecordingContextPriv.h" GR_DRAW_OP_TEST_DEFINE(TextureOp) { GrSurfaceDesc desc; desc.fConfig = kRGBA_8888_GrPixelConfig; desc.fHeight = random->nextULessThan(90) + 10; desc.fWidth = random->nextULessThan(90) + 10; auto origin = random->nextBool() ? kTopLeft_GrSurfaceOrigin : kBottomLeft_GrSurfaceOrigin; GrMipMapped mipMapped = random->nextBool() ? GrMipMapped::kYes : GrMipMapped::kNo; SkBackingFit fit = SkBackingFit::kExact; if (mipMapped == GrMipMapped::kNo) { fit = random->nextBool() ? SkBackingFit::kApprox : SkBackingFit::kExact; } const GrBackendFormat format = context->priv().caps()->getBackendFormatFromColorType(kRGBA_8888_SkColorType); GrProxyProvider* proxyProvider = context->priv().proxyProvider(); sk_sp<GrTextureProxy> proxy = proxyProvider->createProxy(format, desc, origin, mipMapped, fit, SkBudgeted::kNo, GrInternalSurfaceFlags::kNone); SkRect rect = GrTest::TestRect(random); SkRect srcRect; srcRect.fLeft = random->nextRangeScalar(0.f, proxy->width() / 2.f); srcRect.fRight = random->nextRangeScalar(0.f, proxy->width()) + proxy->width() / 2.f; srcRect.fTop = random->nextRangeScalar(0.f, proxy->height() / 2.f); srcRect.fBottom = random->nextRangeScalar(0.f, proxy->height()) + proxy->height() / 2.f; SkMatrix viewMatrix = GrTest::TestMatrixPreservesRightAngles(random); SkPMColor4f color = SkPMColor4f::FromBytes_RGBA(SkColorToPremulGrColor(random->nextU())); GrSamplerState::Filter filter = (GrSamplerState::Filter)random->nextULessThan( static_cast<uint32_t>(GrSamplerState::Filter::kMipMap) + 1); while (mipMapped == GrMipMapped::kNo && filter == GrSamplerState::Filter::kMipMap) { filter = (GrSamplerState::Filter)random->nextULessThan( static_cast<uint32_t>(GrSamplerState::Filter::kMipMap) + 1); } auto texXform = GrTest::TestColorXform(random); GrAAType aaType = GrAAType::kNone; if (random->nextBool()) { aaType = (fsaaType == GrFSAAType::kUnifiedMSAA) ? GrAAType::kMSAA : GrAAType::kCoverage; } GrQuadAAFlags aaFlags = GrQuadAAFlags::kNone; aaFlags |= random->nextBool() ? GrQuadAAFlags::kLeft : GrQuadAAFlags::kNone; aaFlags |= random->nextBool() ? GrQuadAAFlags::kTop : GrQuadAAFlags::kNone; aaFlags |= random->nextBool() ? GrQuadAAFlags::kRight : GrQuadAAFlags::kNone; aaFlags |= random->nextBool() ? GrQuadAAFlags::kBottom : GrQuadAAFlags::kNone; auto constraint = random->nextBool() ? SkCanvas::kStrict_SrcRectConstraint : SkCanvas::kFast_SrcRectConstraint; return GrTextureOp::Make(context, std::move(proxy), filter, color, srcRect, rect, aaType, aaFlags, constraint, viewMatrix, std::move(texXform)); } #endif