text: /*
   * 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 "GrClip.h"
  #include "GrRenderTargetContext.h"
  #include "GrResourceProvider.h"
  #include "GrSurfaceProxy.h"
  #include "GrSurfaceProxyPriv.h"
  #include "GrTexture.h"
  #include "effects/GrBicubicEffect.h"
  #include "effects/GrSimpleTextureEffect.h"
  #include "effects/GrTextureDomain.h"
  
  sk_sp<GrTextureProxy> GrTextureProducer::CopyOnGpu(GrContext* context,
                                                     sk_sp<GrTextureProxy> inputProxy,
                                                     const SkIRect* subset,
                                                     const CopyParams& copyParams) {
      SkASSERT(!subset || !subset->isEmpty());
      SkASSERT(context);
  
      GrPixelConfig config = GrMakePixelConfigUncompressed(inputProxy->config());
  
      const SkRect dstRect = SkRect::MakeIWH(copyParams.fWidth, copyParams.fHeight);
  
      sk_sp<GrRenderTargetContext> copyRTC = context->makeRenderTargetContextWithFallback(
          SkBackingFit::kExact, dstRect.width(), dstRect.height(), config, nullptr);
      if (!copyRTC) {
          return nullptr;
      }
  
      GrPaint paint;
      paint.setGammaCorrect(true);
  
      SkRect localRect;
      if (subset) {
          localRect = SkRect::Make(*subset);
      } else {
          localRect = SkRect::MakeWH(inputProxy->width(), inputProxy->height());
      }
  
      bool needsDomain = false;
      if (copyParams.fFilter != GrSamplerParams::kNone_FilterMode) {
          bool resizing = localRect.width()  != dstRect.width() ||
                          localRect.height() != dstRect.height();
  
          if (GrResourceProvider::IsFunctionallyExact(inputProxy.get())) {
              needsDomain = subset && resizing;
          } else {
              needsDomain = resizing;
          }
      }
  
      if (needsDomain) {
          const SkRect domain = localRect.makeInset(0.5f, 0.5f);
          // 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(context->resourceProvider(), std::move(inputProxy),
                                          nullptr, SkMatrix::I(),
                                          domain, GrTextureDomain::kClamp_Mode, copyParams.fFilter));
      } else {
          GrSamplerParams params(SkShader::kClamp_TileMode, copyParams.fFilter);
          paint.addColorTextureProcessor(context->resourceProvider(), std::move(inputProxy),
                                         nullptr, SkMatrix::I(), params);
      }
      paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
  
      copyRTC->fillRectToRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect,
                              localRect);
      return copyRTC->asTextureProxyRef();
  }
  
  /** 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 (i.e., textureContentArea).
   *    We can *never* allow filtering to cause bleed of pixels outside this rectangle.
   *  - The second rectangle is the constraint rectangle (i.e., constraintRect), 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,
                                  GrTextureProxy* proxy,
                                  const SkIRect* contentRect,
                                  const GrSamplerParams::FilterMode* filterModeOrNullForBicubic,
                                  SkRect* domainRect) {
      const SkIRect proxyBounds = SkIRect::MakeWH(proxy->width(), proxy->height());
  
      SkASSERT(proxyBounds.contains(constraintRect));
      // We only expect a content area rect if there is some non-content area.
      SkASSERT(!contentRect ||
               (!contentRect->contains(proxyBounds) &&
                proxyBounds.contains(*contentRect) &&
                contentRect->contains(constraintRect)));
  
      const bool proxyIsExact = GrResourceProvider::IsFunctionallyExact(proxy);
  
      // If the constraint rectangle contains the whole proxy then no need for a domain.
      if (constraintRect.contains(proxyBounds) && proxyIsExact) {
          return kNoDomain_DomainMode;
      }
  
      if (!contentRect && !proxyIsExact) {
          contentRect = &proxyBounds;
      }
  
      bool restrictFilterToRect = (filterConstraint == GrTextureProducer::kYes_FilterConstraint);
  
      // If we can filter outside the constraint rect, and there is no non-content area of the
      // proxy, and we aren't going to generate sample coords outside the constraint rect then we
      // don't need a domain.
      if (!restrictFilterToRect && !contentRect && 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 || contentRect) {
                      // 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 = constraintRect.makeInset(kDomainInset, kDomainInset);
      } else if (contentRect) {
          // If we got here then: there is a contentRect, 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 (contentRect->fLeft > 0 &&
                  contentRect->fLeft + filterHalfWidth > constraintRect.fLeft) {
                  domainRect->fLeft = contentRect->fLeft + kDomainInset;
                  needContentAreaConstraint = true;
              }
              if (contentRect->fTop > 0 &&
                  contentRect->fTop + filterHalfWidth > constraintRect.fTop) {
                  domainRect->fTop = contentRect->fTop + kDomainInset;
                  needContentAreaConstraint = true;
              }
              if ((!proxyIsExact || contentRect->fRight < proxy->width()) &&
                  contentRect->fRight - filterHalfWidth < constraintRect.fRight) {
                  domainRect->fRight = contentRect->fRight - kDomainInset;
                  needContentAreaConstraint = true;
              }
              if ((!proxyIsExact || contentRect->fBottom < proxy->height()) &&
                  contentRect->fBottom - filterHalfWidth < constraintRect.fBottom) {
                  domainRect->fBottom = contentRect->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 (contentRect->fLeft > 0) {
                  domainRect->fLeft = contentRect->fLeft + kDomainInset;
              }
              if (contentRect->fTop > 0) {
                  domainRect->fTop = contentRect->fTop + kDomainInset;
              }
              if (!proxyIsExact || contentRect->fRight < proxy->width()) {
                  domainRect->fRight = contentRect->fRight - kDomainInset;
              }
              if (!proxyIsExact || contentRect->fBottom < proxy->height()) {
                  domainRect->fBottom = contentRect->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);
      }
      return kDomain_DomainMode;
  }
  
  sk_sp<GrFragmentProcessor> GrTextureProducer::CreateFragmentProcessorForDomainAndFilter(
                                          GrResourceProvider* resourceProvider,
                                          sk_sp<GrTextureProxy> proxy,
                                          sk_sp<GrColorSpaceXform> 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(resourceProvider, std::move(proxy),
                                                 std::move(colorSpaceXform), textureMatrix,
                                                 domain, GrTextureDomain::kClamp_Mode,
                                                 *filterOrNullForBicubic);
          } else {
              GrSamplerParams params(SkShader::kClamp_TileMode, *filterOrNullForBicubic);
              return GrSimpleTextureEffect::Make(resourceProvider, std::move(proxy),
                                                 std::move(colorSpaceXform), textureMatrix,
                                                 params);
          }
      } else {
          if (kDomain_DomainMode == domainMode) {
              return GrBicubicEffect::Make(resourceProvider, std::move(proxy),
                                           std::move(colorSpaceXform),
                                           textureMatrix, domain);
          } else {
              static const SkShader::TileMode kClampClamp[] =
                  { SkShader::kClamp_TileMode, SkShader::kClamp_TileMode };
              return GrBicubicEffect::Make(resourceProvider, std::move(proxy),
                                           std::move(colorSpaceXform),
                                           textureMatrix, kClampClamp);
          }
      }
  }
  