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