2 * Copyright 2015 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #include "GrTextureParamsAdjuster.h"
11 #include "GrContext.h"
12 #include "GrDrawContext.h"
14 #include "GrGpuResourcePriv.h"
15 #include "GrResourceKey.h"
16 #include "GrTexture.h"
17 #include "GrTextureParams.h"
18 #include "GrTextureProvider.h"
22 #include "effects/GrBicubicEffect.h"
23 #include "effects/GrTextureDomain.h"
25 typedef GrTextureProducer::CopyParams CopyParams;
27 //////////////////////////////////////////////////////////////////////////////
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();
34 const GrCaps* caps = context->caps();
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);
43 // If the config isn't renderable try converting to either A8 or an 32 bit config. Otherwise,
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;
54 } else if (kRGB_GrColorComponentFlags ==
55 (kRGB_GrColorComponentFlags & GrPixelConfigComponentMask(rtDesc.fConfig))) {
56 if (caps->isConfigRenderable(kSkia8888_GrPixelConfig, false)) {
57 rtDesc.fConfig = kSkia8888_GrPixelConfig;
66 SkAutoTUnref<GrTexture> copy(context->textureProvider()->createTexture(rtDesc,
72 // TODO: If no scaling is being performed then use copySurface.
75 paint.setGammaCorrect(true);
77 // TODO: Initializing these values for no reason cause the compiler is complaining
81 sx = 1.f / inputTexture->width();
82 sy = 1.f / inputTexture->height();
85 if (copyParams.fFilter != GrTextureParams::kNone_FilterMode && subset &&
86 (subset->width() != copyParams.fWidth || subset->height() != copyParams.fHeight)) {
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
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();
100 GrTextureParams params(SkShader::kClamp_TileMode, copyParams.fFilter);
101 paint.addColorTextureProcessor(inputTexture, SkMatrix::I(), params);
103 paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode);
107 localRect = SkRect::Make(*subset);
108 localRect.fLeft *= sx;
109 localRect.fTop *= sy;
110 localRect.fRight *= sx;
111 localRect.fBottom *= sy;
113 localRect = SkRect::MakeWH(1.f, 1.f);
116 sk_sp<GrDrawContext> drawContext(context->drawContext(sk_ref_sp(copy->asRenderTarget())));
121 SkRect dstRect = SkRect::MakeWH(SkIntToScalar(rtDesc.fWidth), SkIntToScalar(rtDesc.fHeight));
122 drawContext->fillRectToRect(GrNoClip(), paint, SkMatrix::I(), dstRect, localRect);
123 return copy.release();
126 GrTextureAdjuster::GrTextureAdjuster(GrTexture* original,
127 const SkIRect& contentArea,
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);
138 GrTexture* GrTextureAdjuster::refCopy(const CopyParams& copyParams) {
139 GrTexture* texture = this->originalTexture();
140 GrContext* context = texture->getContext();
141 const SkIRect* contentArea = this->contentAreaOrNull();
143 this->makeCopyKey(copyParams, &key);
145 GrTexture* cachedCopy = context->textureProvider()->findAndRefTextureByUniqueKey(key);
150 GrTexture* copy = copy_on_gpu(texture, contentArea, copyParams);
153 copy->resourcePriv().setUniqueKey(key);
154 this->didCacheCopy(key);
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();
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
171 copyParams.fWidth = contentArea->width();
172 copyParams.fHeight = contentArea->height();
173 copyParams.fFilter = GrTextureParams::kBilerp_FilterMode;
174 } else if (!context->getGpu()->makeCopyForTextureParams(texture, params, ©Params)) {
177 outOffset->set(contentArea->fLeft, contentArea->fRight);
179 outOffset->set(0, 0);
182 return SkRef(texture);
185 GrTexture* copy = this->refCopy(copyParams);
186 if (copy && outOffset) {
187 outOffset->set(0, 0);
193 kNoDomain_DomainMode,
195 kTightCopy_DomainMode
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
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.
210 static DomainMode determine_domain_mode(
211 const SkRect& constraintRect,
212 GrTextureAdjuster::FilterConstraint filterConstraint,
213 bool coordsLimitedToConstraintRect,
215 const SkIRect* textureContentArea,
216 const GrTextureParams::FilterMode* filterModeOrNullForBicubic,
217 SkRect* domainRect) {
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)));
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;
231 bool restrictFilterToRect = (filterConstraint == GrTextureProducer::kYes_FilterConstraint);
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;
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;
248 filterHalfWidth = 0.f;
251 case GrTextureParams::kBilerp_FilterMode:
252 filterHalfWidth = .5f;
254 case GrTextureParams::kMipMap_FilterMode:
255 if (restrictFilterToRect || textureContentArea) {
256 // No domain can save us here.
257 return kTightCopy_DomainMode;
259 return kNoDomain_DomainMode;
262 // bicubic does nearest filtering internally.
263 filterHalfWidth = 1.5f;
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
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
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.
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;
294 if (textureContentArea->fTop > 0 &&
295 textureContentArea->fTop + filterHalfWidth > constraintRect.fTop) {
296 domainRect->fTop = textureContentArea->fTop + kDomainInset;
297 needContentAreaConstraint = true;
299 if (textureContentArea->fRight < texW &&
300 textureContentArea->fRight - filterHalfWidth < constraintRect.fRight) {
301 domainRect->fRight = textureContentArea->fRight - kDomainInset;
302 needContentAreaConstraint = true;
304 if (textureContentArea->fBottom < texH &&
305 textureContentArea->fBottom - filterHalfWidth < constraintRect.fBottom) {
306 domainRect->fBottom = textureContentArea->fBottom - kDomainInset;
307 needContentAreaConstraint = true;
309 if (!needContentAreaConstraint) {
310 return kNoDomain_DomainMode;
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;
318 if (textureContentArea->fTop != 0) {
319 domainRect->fTop = textureContentArea->fTop + kDomainInset;
321 if (textureContentArea->fRight != texW) {
322 domainRect->fRight = textureContentArea->fRight - kDomainInset;
324 if (textureContentArea->fBottom != texH) {
325 domainRect->fBottom = textureContentArea->fBottom - kDomainInset;
329 return kNoDomain_DomainMode;
332 if (domainRect->fLeft > domainRect->fRight) {
333 domainRect->fLeft = domainRect->fRight = SkScalarAve(domainRect->fLeft, domainRect->fRight);
335 if (domainRect->fTop > domainRect->fBottom) {
336 domainRect->fTop = domainRect->fBottom = SkScalarAve(domainRect->fTop, domainRect->fBottom);
338 domainRect->fLeft /= texW;
339 domainRect->fTop /= texH;
340 domainRect->fRight /= texW;
341 domainRect->fBottom /= texH;
342 return kDomain_DomainMode;
345 static const GrFragmentProcessor* create_fp_for_domain_and_filter(
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);
358 GrTextureParams params(SkShader::kClamp_TileMode, *filterOrNullForBicubic);
359 return GrSimpleTextureEffect::Create(texture, textureMatrix, params);
362 if (kDomain_DomainMode == domainMode) {
363 return GrBicubicEffect::Create(texture, textureMatrix, domain);
365 static const SkShader::TileMode kClampClamp[] =
366 { SkShader::kClamp_TileMode, SkShader::kClamp_TileMode };
367 return GrBicubicEffect::Create(texture, textureMatrix, kClampClamp);
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) {
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);
386 SkScalar l = SkIntToScalar(contentArea->fLeft);
387 SkScalar t = SkIntToScalar(contentArea->fTop);
388 constraintRect.writable()->offset(l, t);
389 textureMatrix.postTranslate(l, t);
393 GrTextureParams params;
394 if (filterOrNullForBicubic) {
395 params.setFilterMode(*filterOrNullForBicubic);
397 SkAutoTUnref<GrTexture> texture(this->refTextureSafeForParams(params, gammaTreatment, nullptr));
401 // If we made a copy then we only copied the contentArea, in which case the new texture is all
403 if (texture != this->originalTexture()) {
404 contentArea = nullptr;
407 DomainMode domainMode =
408 determine_domain_mode(*constraintRect, filterConstraint, coordsLimitedToConstraintRect,
409 texture->width(), texture->height(),
410 contentArea, filterOrNullForBicubic,
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.
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;
422 determine_domain_mode(*constraintRect, filterConstraint, coordsLimitedToConstraintRect,
423 texture->width(), texture->height(),
424 contentArea, &kBilerp, &domain);
425 SkASSERT(kTightCopy_DomainMode != domainMode);
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);
434 //////////////////////////////////////////////////////////////////////////////
436 GrTexture* GrTextureMaker::refTextureForParams(const GrTextureParams& params,
437 SkSourceGammaTreatment gammaTreatment) {
438 CopyParams copyParams;
439 bool willBeMipped = params.filterMode() == GrTextureParams::kMipMap_FilterMode;
441 if (!fContext->caps()->mipMapSupport()) {
442 willBeMipped = false;
445 if (!fContext->getGpu()->makeCopyForTextureParams(this->width(), this->height(), params,
447 return this->refOriginalTexture(willBeMipped, gammaTreatment);
450 this->makeCopyKey(copyParams, ©Key);
451 if (copyKey.isValid()) {
452 GrTexture* result = fContext->textureProvider()->findAndRefTextureByUniqueKey(copyKey);
458 GrTexture* result = this->generateTextureForParams(copyParams, willBeMipped, gammaTreatment);
463 if (copyKey.isValid()) {
464 fContext->textureProvider()->assignUniqueKeyToTexture(copyKey, result);
465 this->didCacheCopy(copyKey);
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) {
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
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;
490 GrTextureParams params;
491 if (filterOrNullForBicubic) {
492 params.reset(SkShader::kClamp_TileMode, *filterOrNullForBicubic);
494 // Bicubic doesn't use filtering for it's texture accesses.
495 params.reset(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode);
497 SkAutoTUnref<GrTexture> texture(this->refTextureForParams(params, gammaTreatment));
502 DomainMode domainMode =
503 determine_domain_mode(constraintRect, filterConstraint, coordsLimitedToConstraintRect,
504 texture->width(), texture->height(), nullptr, fmForDetermineDomain,
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);
513 GrTexture* GrTextureMaker::generateTextureForParams(const CopyParams& copyParams, bool willBeMipped,
514 SkSourceGammaTreatment gammaTreatment) {
515 SkAutoTUnref<GrTexture> original(this->refOriginalTexture(willBeMipped, gammaTreatment));
519 return copy_on_gpu(original, nullptr, copyParams);