• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
copy_on_gpu(GrTexture * inputTexture,const SkIRect * subset,const CopyParams & copyParams)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 
GrTextureAdjuster(GrTexture * original,const SkIRect & contentArea,bool isAlphaOnly)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 
refCopy(const CopyParams & copyParams)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 
refTextureSafeForParams(const GrTextureParams & params,SkIPoint * outOffset)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  */
determine_domain_mode(const SkRect & constraintRect,GrTextureAdjuster::FilterConstraint filterConstraint,bool coordsLimitedToConstraintRect,int texW,int texH,const SkIRect * textureContentArea,const GrTextureParams::FilterMode * filterModeOrNullForBicubic,SkRect * domainRect)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 
create_fp_for_domain_and_filter(GrTexture * texture,const SkMatrix & textureMatrix,DomainMode domainMode,const SkRect & domain,const GrTextureParams::FilterMode * filterOrNullForBicubic)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 
createFragmentProcessor(const SkMatrix & origTextureMatrix,const SkRect & origConstraintRect,FilterConstraint filterConstraint,bool coordsLimitedToConstraintRect,const GrTextureParams::FilterMode * filterOrNullForBicubic)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 
refTextureForParams(const GrTextureParams & params)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 
createFragmentProcessor(const SkMatrix & textureMatrix,const SkRect & constraintRect,FilterConstraint filterConstraint,bool coordsLimitedToConstraintRect,const GrTextureParams::FilterMode * filterOrNullForBicubic)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 
generateTextureForParams(const CopyParams & copyParams)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