Home | History | Annotate | Download | only in gpu
      1 /*
      2  * Copyright 2015 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "GrTextureParamsAdjuster.h"
      9 
     10 #include "GrCaps.h"
     11 #include "GrContext.h"
     12 #include "GrDrawContext.h"
     13 #include "GrGpu.h"
     14 #include "GrGpuResourcePriv.h"
     15 #include "GrResourceKey.h"
     16 #include "GrTexture.h"
     17 #include "GrTextureParams.h"
     18 #include "GrTextureProvider.h"
     19 #include "SkCanvas.h"
     20 #include "SkGr.h"
     21 #include "SkGrPriv.h"
     22 #include "effects/GrBicubicEffect.h"
     23 #include "effects/GrTextureDomain.h"
     24 
     25 typedef GrTextureProducer::CopyParams CopyParams;
     26 
     27 //////////////////////////////////////////////////////////////////////////////
     28 
     29 static GrTexture* copy_on_gpu(GrTexture* inputTexture, const SkIRect* subset,
     30                               const CopyParams& copyParams) {
     31     SkASSERT(!subset || !subset->isEmpty());
     32     GrContext* context = inputTexture->getContext();
     33     SkASSERT(context);
     34     const GrCaps* caps = context->caps();
     35 
     36     // Either it's a cache miss or the original wasn't cached to begin with.
     37     GrSurfaceDesc rtDesc = inputTexture->desc();
     38     rtDesc.fFlags = rtDesc.fFlags | kRenderTarget_GrSurfaceFlag;
     39     rtDesc.fWidth = copyParams.fWidth;
     40     rtDesc.fHeight = copyParams.fHeight;
     41     rtDesc.fConfig = GrMakePixelConfigUncompressed(rtDesc.fConfig);
     42 
     43     // If the config isn't renderable try converting to either A8 or an 32 bit config. Otherwise,
     44     // fail.
     45     if (!caps->isConfigRenderable(rtDesc.fConfig, false)) {
     46         if (GrPixelConfigIsAlphaOnly(rtDesc.fConfig)) {
     47             if (caps->isConfigRenderable(kAlpha_8_GrPixelConfig, false)) {
     48                 rtDesc.fConfig = kAlpha_8_GrPixelConfig;
     49             } else if (caps->isConfigRenderable(kSkia8888_GrPixelConfig, false)) {
     50                 rtDesc.fConfig = kSkia8888_GrPixelConfig;
     51             } else {
     52                 return nullptr;
     53             }
     54         } else if (kRGB_GrColorComponentFlags ==
     55                    (kRGB_GrColorComponentFlags & GrPixelConfigComponentMask(rtDesc.fConfig))) {
     56             if (caps->isConfigRenderable(kSkia8888_GrPixelConfig, false)) {
     57                 rtDesc.fConfig = kSkia8888_GrPixelConfig;
     58             } else {
     59                 return nullptr;
     60             }
     61         } else {
     62             return nullptr;
     63         }
     64     }
     65 
     66     SkAutoTUnref<GrTexture> copy(context->textureProvider()->createTexture(rtDesc,
     67                                                                            SkBudgeted::kYes));
     68     if (!copy) {
     69         return nullptr;
     70     }
     71 
     72     // TODO: If no scaling is being performed then use copySurface.
     73 
     74     GrPaint paint;
     75 
     76     // TODO: Initializing these values for no reason cause the compiler is complaining
     77     SkScalar sx = 0.f;
     78     SkScalar sy = 0.f;
     79     if (subset) {
     80         sx = 1.f / inputTexture->width();
     81         sy = 1.f / inputTexture->height();
     82     }
     83 
     84     if (copyParams.fFilter != GrTextureParams::kNone_FilterMode && subset &&
     85         (subset->width() != copyParams.fWidth || subset->height() != copyParams.fHeight)) {
     86         SkRect domain;
     87         domain.fLeft = (subset->fLeft + 0.5f) * sx;
     88         domain.fTop = (subset->fTop + 0.5f)* sy;
     89         domain.fRight = (subset->fRight - 0.5f) * sx;
     90         domain.fBottom = (subset->fBottom - 0.5f) * sy;
     91         // This would cause us to read values from outside the subset. Surely, the caller knows
     92         // better!
     93         SkASSERT(copyParams.fFilter != GrTextureParams::kMipMap_FilterMode);
     94         paint.addColorFragmentProcessor(
     95             GrTextureDomainEffect::Create(inputTexture, SkMatrix::I(), domain,
     96                                           GrTextureDomain::kClamp_Mode,
     97                                           copyParams.fFilter))->unref();
     98     } else {
     99         GrTextureParams params(SkShader::kClamp_TileMode, copyParams.fFilter);
    100         paint.addColorTextureProcessor(inputTexture, SkMatrix::I(), params);
    101     }
    102     paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode);
    103 
    104     SkRect localRect;
    105     if (subset) {
    106         localRect = SkRect::Make(*subset);
    107         localRect.fLeft *= sx;
    108         localRect.fTop *= sy;
    109         localRect.fRight *= sx;
    110         localRect.fBottom *= sy;
    111     } else {
    112         localRect = SkRect::MakeWH(1.f, 1.f);
    113     }
    114 
    115     SkAutoTUnref<GrDrawContext> drawContext(context->drawContext(copy->asRenderTarget()));
    116     if (!drawContext) {
    117         return nullptr;
    118     }
    119 
    120     SkRect dstRect = SkRect::MakeWH(SkIntToScalar(rtDesc.fWidth), SkIntToScalar(rtDesc.fHeight));
    121     drawContext->fillRectToRect(GrClip::WideOpen(), paint, SkMatrix::I(), dstRect, localRect);
    122     return copy.detach();
    123 }
    124 
    125 GrTextureAdjuster::GrTextureAdjuster(GrTexture* original,
    126                                      const SkIRect& contentArea,
    127                                      bool isAlphaOnly)
    128     : INHERITED(contentArea.width(), contentArea.height(), isAlphaOnly)
    129     , fOriginal(original) {
    130     SkASSERT(SkIRect::MakeWH(original->width(), original->height()).contains(contentArea));
    131     if (contentArea.fLeft > 0 || contentArea.fTop > 0 ||
    132         contentArea.fRight < original->width() || contentArea.fBottom < original->height()) {
    133         fContentArea.set(contentArea);
    134     }
    135 }
    136 
    137 GrTexture* GrTextureAdjuster::refCopy(const CopyParams& copyParams) {
    138     GrTexture* texture = this->originalTexture();
    139     GrContext* context = texture->getContext();
    140     const SkIRect* contentArea = this->contentAreaOrNull();
    141     GrUniqueKey key;
    142     this->makeCopyKey(copyParams, &key);
    143     if (key.isValid()) {
    144         GrTexture* cachedCopy = context->textureProvider()->findAndRefTextureByUniqueKey(key);
    145         if (cachedCopy) {
    146             return cachedCopy;
    147         }
    148     }
    149     GrTexture* copy = copy_on_gpu(texture, contentArea, copyParams);
    150     if (copy) {
    151         if (key.isValid()) {
    152             copy->resourcePriv().setUniqueKey(key);
    153             this->didCacheCopy(key);
    154         }
    155     }
    156     return copy;
    157 }
    158 
    159 GrTexture* GrTextureAdjuster::refTextureSafeForParams(const GrTextureParams& params,
    160                                                       SkIPoint* outOffset) {
    161     GrTexture* texture = this->originalTexture();
    162     GrContext* context = texture->getContext();
    163     CopyParams copyParams;
    164     const SkIRect* contentArea = this->contentAreaOrNull();
    165 
    166     if (contentArea && GrTextureParams::kMipMap_FilterMode == params.filterMode()) {
    167         // If we generate a MIP chain for texture it will read pixel values from outside the content
    168         // area.
    169         copyParams.fWidth = contentArea->width();
    170         copyParams.fHeight = contentArea->height();
    171         copyParams.fFilter = GrTextureParams::kBilerp_FilterMode;
    172     } else if (!context->getGpu()->makeCopyForTextureParams(texture, params, &copyParams)) {
    173         if (outOffset) {
    174             if (contentArea) {
    175                 outOffset->set(contentArea->fLeft, contentArea->fRight);
    176             } else {
    177                 outOffset->set(0, 0);
    178             }
    179         }
    180         return SkRef(texture);
    181     }
    182 
    183     GrTexture* copy = this->refCopy(copyParams);
    184     if (copy && outOffset) {
    185         outOffset->set(0, 0);
    186     }
    187     return copy;
    188 }
    189 
    190 enum DomainMode {
    191     kNoDomain_DomainMode,
    192     kDomain_DomainMode,
    193     kTightCopy_DomainMode
    194 };
    195 
    196 /** Determines whether a texture domain is necessary and if so what domain to use. There are two
    197  *  rectangles to consider:
    198  *  - The first is the content area specified by the texture adjuster. We can *never* allow
    199  *    filtering to cause bleed of pixels outside this rectangle.
    200  *  - The second rectangle is the constraint rectangle, which is known to be contained by the
    201  *    content area. The filterConstraint specifies whether we are allowed to bleed across this
    202  *    rect.
    203  *
    204  *  We want to avoid using a domain if possible. We consider the above rectangles, the filter type,
    205  *  and whether the coords generated by the draw would all fall within the constraint rect. If the
    206  *  latter is true we only need to consider whether the filter would extend beyond the rects.
    207  */
    208 static DomainMode determine_domain_mode(
    209                                     const SkRect& constraintRect,
    210                                     GrTextureAdjuster::FilterConstraint filterConstraint,
    211                                     bool coordsLimitedToConstraintRect,
    212                                     int texW, int texH,
    213                                     const SkIRect* textureContentArea,
    214                                     const GrTextureParams::FilterMode* filterModeOrNullForBicubic,
    215                                     SkRect* domainRect) {
    216 
    217     SkASSERT(SkRect::MakeIWH(texW, texH).contains(constraintRect));
    218     // We only expect a content area rect if there is some non-content area.
    219     SkASSERT(!textureContentArea ||
    220              (!textureContentArea->contains(SkIRect::MakeWH(texW, texH)) &&
    221               SkRect::Make(*textureContentArea).contains(constraintRect)));
    222 
    223     SkRect textureBounds = SkRect::MakeIWH(texW, texH);
    224     // If the src rectangle contains the whole texture then no need for a domain.
    225     if (constraintRect.contains(textureBounds)) {
    226         return kNoDomain_DomainMode;
    227     }
    228 
    229     bool restrictFilterToRect = (filterConstraint == GrTextureProducer::kYes_FilterConstraint);
    230 
    231     // If we can filter outside the constraint rect, and there is no non-content area of the
    232     // texture, and we aren't going to generate sample coords outside the constraint rect then we
    233     // don't need a domain.
    234     if (!restrictFilterToRect && !textureContentArea && coordsLimitedToConstraintRect) {
    235         return kNoDomain_DomainMode;
    236     }
    237 
    238     // Get the domain inset based on sampling mode (or bail if mipped)
    239     SkScalar filterHalfWidth = 0.f;
    240     if (filterModeOrNullForBicubic) {
    241         switch (*filterModeOrNullForBicubic) {
    242             case GrTextureParams::kNone_FilterMode:
    243                 if (coordsLimitedToConstraintRect) {
    244                     return kNoDomain_DomainMode;
    245                 } else {
    246                     filterHalfWidth = 0.f;
    247                 }
    248                 break;
    249             case GrTextureParams::kBilerp_FilterMode:
    250                 filterHalfWidth = .5f;
    251                 break;
    252             case GrTextureParams::kMipMap_FilterMode:
    253                 if (restrictFilterToRect || textureContentArea) {
    254                     // No domain can save us here.
    255                     return kTightCopy_DomainMode;
    256                 }
    257                 return kNoDomain_DomainMode;
    258         }
    259     } else {
    260         // bicubic does nearest filtering internally.
    261         filterHalfWidth = 1.5f;
    262     }
    263 
    264     // Both bilerp and bicubic use bilinear filtering and so need to be clamped to the center
    265     // of the edge texel. Pinning to the texel center has no impact on nearest mode and MIP-maps
    266 
    267     static const SkScalar kDomainInset = 0.5f;
    268     // Figure out the limits of pixels we're allowed to sample from.
    269     // Unless we know the amount of outset and the texture matrix we have to conservatively enforce
    270     // the domain.
    271     if (restrictFilterToRect) {
    272         domainRect->fLeft = constraintRect.fLeft + kDomainInset;
    273         domainRect->fTop = constraintRect.fTop + kDomainInset;
    274         domainRect->fRight = constraintRect.fRight - kDomainInset;
    275         domainRect->fBottom = constraintRect.fBottom - kDomainInset;
    276     } else if (textureContentArea) {
    277         // If we got here then: there is a textureContentArea, the coords are limited to the
    278         // constraint rect, and we're allowed to filter across the constraint rect boundary. So
    279         // we check whether the filter would reach across the edge of the content area.
    280         // We will only set the sides that are required.
    281 
    282         domainRect->setLargest();
    283         if (coordsLimitedToConstraintRect) {
    284             // We may be able to use the fact that the texture coords are limited to the constraint
    285             // rect in order to avoid having to add a domain.
    286             bool needContentAreaConstraint = false;
    287             if (textureContentArea->fLeft > 0 &&
    288                 textureContentArea->fLeft + filterHalfWidth > constraintRect.fLeft) {
    289                 domainRect->fLeft = textureContentArea->fLeft + kDomainInset;
    290                 needContentAreaConstraint = true;
    291             }
    292             if (textureContentArea->fTop > 0 &&
    293                 textureContentArea->fTop + filterHalfWidth > constraintRect.fTop) {
    294                 domainRect->fTop = textureContentArea->fTop + kDomainInset;
    295                 needContentAreaConstraint = true;
    296             }
    297             if (textureContentArea->fRight < texW &&
    298                 textureContentArea->fRight - filterHalfWidth < constraintRect.fRight) {
    299                 domainRect->fRight = textureContentArea->fRight - kDomainInset;
    300                 needContentAreaConstraint = true;
    301             }
    302             if (textureContentArea->fBottom < texH &&
    303                 textureContentArea->fBottom - filterHalfWidth < constraintRect.fBottom) {
    304                 domainRect->fBottom = textureContentArea->fBottom - kDomainInset;
    305                 needContentAreaConstraint = true;
    306             }
    307             if (!needContentAreaConstraint) {
    308                 return kNoDomain_DomainMode;
    309             }
    310         } else {
    311             // Our sample coords for the texture are allowed to be outside the constraintRect so we
    312             // don't consider it when computing the domain.
    313             if (textureContentArea->fLeft != 0) {
    314                 domainRect->fLeft = textureContentArea->fLeft + kDomainInset;
    315             }
    316             if (textureContentArea->fTop != 0) {
    317                 domainRect->fTop = textureContentArea->fTop + kDomainInset;
    318             }
    319             if (textureContentArea->fRight != texW) {
    320                 domainRect->fRight = textureContentArea->fRight - kDomainInset;
    321             }
    322             if (textureContentArea->fBottom != texH) {
    323                 domainRect->fBottom = textureContentArea->fBottom - kDomainInset;
    324             }
    325         }
    326     } else {
    327         return kNoDomain_DomainMode;
    328     }
    329 
    330     if (domainRect->fLeft > domainRect->fRight) {
    331         domainRect->fLeft = domainRect->fRight = SkScalarAve(domainRect->fLeft, domainRect->fRight);
    332     }
    333     if (domainRect->fTop > domainRect->fBottom) {
    334         domainRect->fTop = domainRect->fBottom = SkScalarAve(domainRect->fTop, domainRect->fBottom);
    335     }
    336     domainRect->fLeft /= texW;
    337     domainRect->fTop /= texH;
    338     domainRect->fRight /= texW;
    339     domainRect->fBottom /= texH;
    340     return kDomain_DomainMode;
    341 }
    342 
    343 static const GrFragmentProcessor* create_fp_for_domain_and_filter(
    344                                         GrTexture* texture,
    345                                         const SkMatrix& textureMatrix,
    346                                         DomainMode domainMode,
    347                                         const SkRect& domain,
    348                                         const GrTextureParams::FilterMode* filterOrNullForBicubic) {
    349     SkASSERT(kTightCopy_DomainMode != domainMode);
    350     if (filterOrNullForBicubic) {
    351         if (kDomain_DomainMode == domainMode) {
    352             return GrTextureDomainEffect::Create(texture, textureMatrix, domain,
    353                                                  GrTextureDomain::kClamp_Mode,
    354                                                  *filterOrNullForBicubic);
    355         } else {
    356             GrTextureParams params(SkShader::kClamp_TileMode, *filterOrNullForBicubic);
    357             return GrSimpleTextureEffect::Create(texture, textureMatrix, params);
    358         }
    359     } else {
    360         if (kDomain_DomainMode == domainMode) {
    361             return GrBicubicEffect::Create(texture, textureMatrix, domain);
    362         } else {
    363             static const SkShader::TileMode kClampClamp[] =
    364                 { SkShader::kClamp_TileMode, SkShader::kClamp_TileMode };
    365             return GrBicubicEffect::Create(texture, textureMatrix, kClampClamp);
    366         }
    367     }
    368 }
    369 
    370 const GrFragmentProcessor* GrTextureAdjuster::createFragmentProcessor(
    371                                         const SkMatrix& origTextureMatrix,
    372                                         const SkRect& origConstraintRect,
    373                                         FilterConstraint filterConstraint,
    374                                         bool coordsLimitedToConstraintRect,
    375                                         const GrTextureParams::FilterMode* filterOrNullForBicubic) {
    376 
    377     SkMatrix textureMatrix = origTextureMatrix;
    378     const SkIRect* contentArea = this->contentAreaOrNull();
    379     // Convert the constraintRect to be relative to the texture rather than the content area so
    380     // that both rects are in the same coordinate system.
    381     SkTCopyOnFirstWrite<SkRect> constraintRect(origConstraintRect);
    382     if (contentArea) {
    383         SkScalar l = SkIntToScalar(contentArea->fLeft);
    384         SkScalar t = SkIntToScalar(contentArea->fTop);
    385         constraintRect.writable()->offset(l, t);
    386         textureMatrix.postTranslate(l, t);
    387     }
    388 
    389     SkRect domain;
    390     GrTextureParams params;
    391     if (filterOrNullForBicubic) {
    392         params.setFilterMode(*filterOrNullForBicubic);
    393     }
    394     SkAutoTUnref<GrTexture> texture(this->refTextureSafeForParams(params, nullptr));
    395     if (!texture) {
    396         return nullptr;
    397     }
    398     // If we made a copy then we only copied the contentArea, in which case the new texture is all
    399     // content.
    400     if (texture != this->originalTexture()) {
    401         contentArea = nullptr;
    402     }
    403 
    404     DomainMode domainMode =
    405         determine_domain_mode(*constraintRect, filterConstraint, coordsLimitedToConstraintRect,
    406                               texture->width(), texture->height(),
    407                               contentArea, filterOrNullForBicubic,
    408                               &domain);
    409     if (kTightCopy_DomainMode == domainMode) {
    410         // TODO: Copy the texture and adjust the texture matrix (both parts need to consider
    411         // non-int constraint rect)
    412         // For now: treat as bilerp and ignore what goes on above level 0.
    413 
    414         // We only expect MIP maps to require a tight copy.
    415         SkASSERT(filterOrNullForBicubic &&
    416                  GrTextureParams::kMipMap_FilterMode == *filterOrNullForBicubic);
    417         static const GrTextureParams::FilterMode kBilerp = GrTextureParams::kBilerp_FilterMode;
    418         domainMode =
    419             determine_domain_mode(*constraintRect, filterConstraint, coordsLimitedToConstraintRect,
    420                                   texture->width(), texture->height(),
    421                                   contentArea, &kBilerp, &domain);
    422         SkASSERT(kTightCopy_DomainMode != domainMode);
    423     }
    424     SkASSERT(kNoDomain_DomainMode == domainMode ||
    425              (domain.fLeft <= domain.fRight && domain.fTop <= domain.fBottom));
    426     textureMatrix.postIDiv(texture->width(), texture->height());
    427     return create_fp_for_domain_and_filter(texture, textureMatrix, domainMode, domain,
    428                                            filterOrNullForBicubic);
    429 }
    430 
    431 //////////////////////////////////////////////////////////////////////////////
    432 
    433 GrTexture* GrTextureMaker::refTextureForParams(const GrTextureParams& params) {
    434     CopyParams copyParams;
    435     if (!fContext->getGpu()->makeCopyForTextureParams(this->width(), this->height(), params,
    436                                                       &copyParams)) {
    437         return this->refOriginalTexture();
    438     }
    439     GrUniqueKey copyKey;
    440     this->makeCopyKey(copyParams, &copyKey);
    441     if (copyKey.isValid()) {
    442         GrTexture* result = fContext->textureProvider()->findAndRefTextureByUniqueKey(copyKey);
    443         if (result) {
    444             return result;
    445         }
    446     }
    447 
    448     GrTexture* result = this->generateTextureForParams(copyParams);
    449     if (!result) {
    450         return nullptr;
    451     }
    452 
    453     if (copyKey.isValid()) {
    454         fContext->textureProvider()->assignUniqueKeyToTexture(copyKey, result);
    455         this->didCacheCopy(copyKey);
    456     }
    457     return result;
    458 }
    459 
    460 const GrFragmentProcessor* GrTextureMaker::createFragmentProcessor(
    461                                         const SkMatrix& textureMatrix,
    462                                         const SkRect& constraintRect,
    463                                         FilterConstraint filterConstraint,
    464                                         bool coordsLimitedToConstraintRect,
    465                                         const GrTextureParams::FilterMode* filterOrNullForBicubic) {
    466 
    467     const GrTextureParams::FilterMode* fmForDetermineDomain = filterOrNullForBicubic;
    468     if (filterOrNullForBicubic && GrTextureParams::kMipMap_FilterMode == *filterOrNullForBicubic &&
    469         kYes_FilterConstraint == filterConstraint) {
    470         // TODo: Here we should force a copy restricted to the constraintRect since MIP maps will
    471         // read outside the constraint rect. However, as in the adjuster case, we aren't currently
    472         // doing that.
    473         // We instead we compute the domain as though were bilerping which is only correct if we
    474         // only sample level 0.
    475         static const GrTextureParams::FilterMode kBilerp = GrTextureParams::kBilerp_FilterMode;
    476         fmForDetermineDomain = &kBilerp;
    477     }
    478 
    479     GrTextureParams params;
    480     if (filterOrNullForBicubic) {
    481         params.reset(SkShader::kClamp_TileMode, *filterOrNullForBicubic);
    482     } else {
    483         // Bicubic doesn't use filtering for it's texture accesses.
    484         params.reset(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode);
    485     }
    486     SkAutoTUnref<GrTexture> texture(this->refTextureForParams(params));
    487     if (!texture) {
    488         return nullptr;
    489     }
    490     SkRect domain;
    491     DomainMode domainMode =
    492         determine_domain_mode(constraintRect, filterConstraint, coordsLimitedToConstraintRect,
    493                               texture->width(), texture->height(), nullptr, fmForDetermineDomain,
    494                               &domain);
    495     SkASSERT(kTightCopy_DomainMode != domainMode);
    496     SkMatrix normalizedTextureMatrix = textureMatrix;
    497     normalizedTextureMatrix.postIDiv(texture->width(), texture->height());
    498     return create_fp_for_domain_and_filter(texture, normalizedTextureMatrix, domainMode, domain,
    499                                            filterOrNullForBicubic);
    500 }
    501 
    502 GrTexture* GrTextureMaker::generateTextureForParams(const CopyParams& copyParams) {
    503     SkAutoTUnref<GrTexture> original(this->refOriginalTexture());
    504     if (!original) {
    505         return nullptr;
    506     }
    507     return copy_on_gpu(original, nullptr, copyParams);
    508 }
    509