/* * 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 "GrTextureProducer.h" #include "GrRenderTargetContext.h" #include "GrTexture.h" #include "effects/GrBicubicEffect.h" #include "effects/GrSimpleTextureEffect.h" #include "effects/GrTextureDomain.h" GrTexture* GrTextureProducer::CopyOnGpu(GrTexture* inputTexture, const SkIRect* subset, const CopyParams& copyParams) { SkASSERT(!subset || !subset->isEmpty()); GrContext* context = inputTexture->getContext(); SkASSERT(context); GrPixelConfig config = GrMakePixelConfigUncompressed(inputTexture->config()); sk_sp copyRTC = context->makeRenderTargetContextWithFallback( SkBackingFit::kExact, copyParams.fWidth, copyParams.fHeight, config, nullptr); if (!copyRTC) { return nullptr; } GrPaint paint; paint.setGammaCorrect(true); SkScalar sx SK_INIT_TO_AVOID_WARNING; SkScalar sy SK_INIT_TO_AVOID_WARNING; if (subset) { sx = 1.f / inputTexture->width(); sy = 1.f / inputTexture->height(); } if (copyParams.fFilter != GrSamplerParams::kNone_FilterMode && subset && (subset->width() != copyParams.fWidth || subset->height() != copyParams.fHeight)) { SkRect domain; domain.fLeft = (subset->fLeft + 0.5f) * sx; domain.fTop = (subset->fTop + 0.5f)* sy; domain.fRight = (subset->fRight - 0.5f) * sx; domain.fBottom = (subset->fBottom - 0.5f) * sy; // This would cause us to read values from outside the subset. Surely, the caller knows // better! SkASSERT(copyParams.fFilter != GrSamplerParams::kMipMap_FilterMode); paint.addColorFragmentProcessor( GrTextureDomainEffect::Make(inputTexture, nullptr, SkMatrix::I(), domain, GrTextureDomain::kClamp_Mode, copyParams.fFilter)); } else { GrSamplerParams params(SkShader::kClamp_TileMode, copyParams.fFilter); paint.addColorTextureProcessor(inputTexture, nullptr, SkMatrix::I(), params); } paint.setPorterDuffXPFactory(SkBlendMode::kSrc); SkRect localRect; if (subset) { localRect = SkRect::Make(*subset); localRect.fLeft *= sx; localRect.fTop *= sy; localRect.fRight *= sx; localRect.fBottom *= sy; } else { localRect = SkRect::MakeWH(1.f, 1.f); } SkRect dstRect = SkRect::MakeIWH(copyParams.fWidth, copyParams.fHeight); copyRTC->fillRectToRect(GrNoClip(), paint, SkMatrix::I(), dstRect, localRect); return copyRTC->asTexture().release(); } /** Determines whether a texture domain is necessary and if so what domain to use. There are two * rectangles to consider: * - The first is the content area specified by the texture adjuster. We can *never* allow * filtering to cause bleed of pixels outside this rectangle. * - The second rectangle is the constraint rectangle, which is known to be contained by the * content area. The filterConstraint specifies whether we are allowed to bleed across this * rect. * * We want to avoid using a domain if possible. We consider the above rectangles, the filter type, * and whether the coords generated by the draw would all fall within the constraint rect. If the * latter is true we only need to consider whether the filter would extend beyond the rects. */ GrTextureProducer::DomainMode GrTextureProducer::DetermineDomainMode( const SkRect& constraintRect, FilterConstraint filterConstraint, bool coordsLimitedToConstraintRect, int texW, int texH, const SkIRect* textureContentArea, const GrSamplerParams::FilterMode* filterModeOrNullForBicubic, SkRect* domainRect) { SkASSERT(SkRect::MakeIWH(texW, texH).contains(constraintRect)); // We only expect a content area rect if there is some non-content area. SkASSERT(!textureContentArea || (!textureContentArea->contains(SkIRect::MakeWH(texW, texH)) && SkRect::Make(*textureContentArea).contains(constraintRect))); SkRect textureBounds = SkRect::MakeIWH(texW, texH); // If the src rectangle contains the whole texture then no need for a domain. if (constraintRect.contains(textureBounds)) { return kNoDomain_DomainMode; } bool restrictFilterToRect = (filterConstraint == GrTextureProducer::kYes_FilterConstraint); // If we can filter outside the constraint rect, and there is no non-content area of the // texture, and we aren't going to generate sample coords outside the constraint rect then we // don't need a domain. if (!restrictFilterToRect && !textureContentArea && coordsLimitedToConstraintRect) { return kNoDomain_DomainMode; } // Get the domain inset based on sampling mode (or bail if mipped) SkScalar filterHalfWidth = 0.f; if (filterModeOrNullForBicubic) { switch (*filterModeOrNullForBicubic) { case GrSamplerParams::kNone_FilterMode: if (coordsLimitedToConstraintRect) { return kNoDomain_DomainMode; } else { filterHalfWidth = 0.f; } break; case GrSamplerParams::kBilerp_FilterMode: filterHalfWidth = .5f; break; case GrSamplerParams::kMipMap_FilterMode: if (restrictFilterToRect || textureContentArea) { // No domain can save us here. return kTightCopy_DomainMode; } return kNoDomain_DomainMode; } } else { // bicubic does nearest filtering internally. filterHalfWidth = 1.5f; } // Both bilerp and bicubic use bilinear filtering and so need to be clamped to the center // of the edge texel. Pinning to the texel center has no impact on nearest mode and MIP-maps static const SkScalar kDomainInset = 0.5f; // Figure out the limits of pixels we're allowed to sample from. // Unless we know the amount of outset and the texture matrix we have to conservatively enforce // the domain. if (restrictFilterToRect) { domainRect->fLeft = constraintRect.fLeft + kDomainInset; domainRect->fTop = constraintRect.fTop + kDomainInset; domainRect->fRight = constraintRect.fRight - kDomainInset; domainRect->fBottom = constraintRect.fBottom - kDomainInset; } else if (textureContentArea) { // If we got here then: there is a textureContentArea, the coords are limited to the // constraint rect, and we're allowed to filter across the constraint rect boundary. So // we check whether the filter would reach across the edge of the content area. // We will only set the sides that are required. domainRect->setLargest(); if (coordsLimitedToConstraintRect) { // We may be able to use the fact that the texture coords are limited to the constraint // rect in order to avoid having to add a domain. bool needContentAreaConstraint = false; if (textureContentArea->fLeft > 0 && textureContentArea->fLeft + filterHalfWidth > constraintRect.fLeft) { domainRect->fLeft = textureContentArea->fLeft + kDomainInset; needContentAreaConstraint = true; } if (textureContentArea->fTop > 0 && textureContentArea->fTop + filterHalfWidth > constraintRect.fTop) { domainRect->fTop = textureContentArea->fTop + kDomainInset; needContentAreaConstraint = true; } if (textureContentArea->fRight < texW && textureContentArea->fRight - filterHalfWidth < constraintRect.fRight) { domainRect->fRight = textureContentArea->fRight - kDomainInset; needContentAreaConstraint = true; } if (textureContentArea->fBottom < texH && textureContentArea->fBottom - filterHalfWidth < constraintRect.fBottom) { domainRect->fBottom = textureContentArea->fBottom - kDomainInset; needContentAreaConstraint = true; } if (!needContentAreaConstraint) { return kNoDomain_DomainMode; } } else { // Our sample coords for the texture are allowed to be outside the constraintRect so we // don't consider it when computing the domain. if (textureContentArea->fLeft != 0) { domainRect->fLeft = textureContentArea->fLeft + kDomainInset; } if (textureContentArea->fTop != 0) { domainRect->fTop = textureContentArea->fTop + kDomainInset; } if (textureContentArea->fRight != texW) { domainRect->fRight = textureContentArea->fRight - kDomainInset; } if (textureContentArea->fBottom != texH) { domainRect->fBottom = textureContentArea->fBottom - kDomainInset; } } } else { return kNoDomain_DomainMode; } if (domainRect->fLeft > domainRect->fRight) { domainRect->fLeft = domainRect->fRight = SkScalarAve(domainRect->fLeft, domainRect->fRight); } if (domainRect->fTop > domainRect->fBottom) { domainRect->fTop = domainRect->fBottom = SkScalarAve(domainRect->fTop, domainRect->fBottom); } domainRect->fLeft /= texW; domainRect->fTop /= texH; domainRect->fRight /= texW; domainRect->fBottom /= texH; return kDomain_DomainMode; } sk_sp GrTextureProducer::CreateFragmentProcessorForDomainAndFilter( GrTexture* texture, sk_sp colorSpaceXform, const SkMatrix& textureMatrix, DomainMode domainMode, const SkRect& domain, const GrSamplerParams::FilterMode* filterOrNullForBicubic) { SkASSERT(kTightCopy_DomainMode != domainMode); if (filterOrNullForBicubic) { if (kDomain_DomainMode == domainMode) { return GrTextureDomainEffect::Make(texture, std::move(colorSpaceXform), textureMatrix, domain, GrTextureDomain::kClamp_Mode, *filterOrNullForBicubic); } else { GrSamplerParams params(SkShader::kClamp_TileMode, *filterOrNullForBicubic); return GrSimpleTextureEffect::Make(texture, std::move(colorSpaceXform), textureMatrix, params); } } else { if (kDomain_DomainMode == domainMode) { return GrBicubicEffect::Make(texture, std::move(colorSpaceXform), textureMatrix, domain); } else { static const SkShader::TileMode kClampClamp[] = { SkShader::kClamp_TileMode, SkShader::kClamp_TileMode }; return GrBicubicEffect::Make(texture, std::move(colorSpaceXform), textureMatrix, kClampClamp); } } }