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, ©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 */ 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 ©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 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