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, ©Params)) {
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 ©Params)) {
437 return this->refOriginalTexture();
438 }
439 GrUniqueKey copyKey;
440 this->makeCopyKey(copyParams, ©Key);
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