1 /* 2 * Copyright 2013 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 "SkGpuBlurUtils.h" 9 10 #include "SkRect.h" 11 12 #if SK_SUPPORT_GPU 13 #include "GrCaps.h" 14 #include "GrContext.h" 15 #include "GrFixedClip.h" 16 #include "GrRenderTargetContext.h" 17 #include "GrRenderTargetContextPriv.h" 18 #include "effects/GrGaussianConvolutionFragmentProcessor.h" 19 #include "effects/GrMatrixConvolutionEffect.h" 20 21 #include "SkGr.h" 22 23 #define MAX_BLUR_SIGMA 4.0f 24 25 using Direction = GrGaussianConvolutionFragmentProcessor::Direction; 26 27 static void scale_irect_roundout(SkIRect* rect, float xScale, float yScale) { 28 rect->fLeft = SkScalarFloorToInt(rect->fLeft * xScale); 29 rect->fTop = SkScalarFloorToInt(rect->fTop * yScale); 30 rect->fRight = SkScalarCeilToInt(rect->fRight * xScale); 31 rect->fBottom = SkScalarCeilToInt(rect->fBottom * yScale); 32 } 33 34 static void scale_irect(SkIRect* rect, int xScale, int yScale) { 35 rect->fLeft *= xScale; 36 rect->fTop *= yScale; 37 rect->fRight *= xScale; 38 rect->fBottom *= yScale; 39 } 40 41 #ifdef SK_DEBUG 42 static inline int is_even(int x) { return !(x & 1); } 43 #endif 44 45 static void shrink_irect_by_2(SkIRect* rect, bool xAxis, bool yAxis) { 46 if (xAxis) { 47 SkASSERT(is_even(rect->fLeft) && is_even(rect->fRight)); 48 rect->fLeft /= 2; 49 rect->fRight /= 2; 50 } 51 if (yAxis) { 52 SkASSERT(is_even(rect->fTop) && is_even(rect->fBottom)); 53 rect->fTop /= 2; 54 rect->fBottom /= 2; 55 } 56 } 57 58 static float adjust_sigma(float sigma, int maxTextureSize, int *scaleFactor, int *radius) { 59 *scaleFactor = 1; 60 while (sigma > MAX_BLUR_SIGMA) { 61 *scaleFactor *= 2; 62 sigma *= 0.5f; 63 if (*scaleFactor > maxTextureSize) { 64 *scaleFactor = maxTextureSize; 65 sigma = MAX_BLUR_SIGMA; 66 } 67 } 68 *radius = static_cast<int>(ceilf(sigma * 3.0f)); 69 SkASSERT(*radius <= GrGaussianConvolutionFragmentProcessor::kMaxKernelRadius); 70 return sigma; 71 } 72 73 static void convolve_gaussian_1d(GrRenderTargetContext* renderTargetContext, 74 const GrClip& clip, 75 const SkIRect& dstRect, 76 const SkIPoint& srcOffset, 77 sk_sp<GrTextureProxy> proxy, 78 Direction direction, 79 int radius, 80 float sigma, 81 GrTextureDomain::Mode mode, 82 int bounds[2]) { 83 GrPaint paint; 84 std::unique_ptr<GrFragmentProcessor> conv(GrGaussianConvolutionFragmentProcessor::Make( 85 std::move(proxy), direction, radius, sigma, mode, bounds)); 86 paint.addColorFragmentProcessor(std::move(conv)); 87 paint.setPorterDuffXPFactory(SkBlendMode::kSrc); 88 SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()), 89 -SkIntToScalar(srcOffset.y())); 90 renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), 91 SkRect::Make(dstRect), localMatrix); 92 } 93 94 static GrPixelConfig get_blur_config(GrTextureProxy* proxy) { 95 GrPixelConfig config = proxy->config(); 96 97 SkASSERT(kBGRA_8888_GrPixelConfig == config || kRGBA_8888_GrPixelConfig == config || 98 kRGB_888_GrPixelConfig == config || kRGBA_4444_GrPixelConfig == config || 99 kRGB_565_GrPixelConfig == config || kSRGBA_8888_GrPixelConfig == config || 100 kSBGRA_8888_GrPixelConfig == config || kRGBA_half_GrPixelConfig == config || 101 kAlpha_8_GrPixelConfig == config || kRGBA_1010102_GrPixelConfig == config); 102 103 return config; 104 } 105 106 static sk_sp<GrRenderTargetContext> convolve_gaussian_2d(GrContext* context, 107 sk_sp<GrTextureProxy> proxy, 108 const SkIRect& srcBounds, 109 const SkIPoint& srcOffset, 110 int radiusX, 111 int radiusY, 112 SkScalar sigmaX, 113 SkScalar sigmaY, 114 GrTextureDomain::Mode mode, 115 const SkImageInfo& dstII, 116 SkBackingFit dstFit) { 117 118 GrPixelConfig config = get_blur_config(proxy.get()); 119 120 GrBackendFormat format = proxy->backendFormat().makeTexture2D(); 121 if (!format.isValid()) { 122 return nullptr; 123 } 124 125 sk_sp<GrRenderTargetContext> renderTargetContext; 126 renderTargetContext = context->contextPriv().makeDeferredRenderTargetContext( 127 format, 128 dstFit, dstII.width(), dstII.height(), 129 config, dstII.refColorSpace(), 130 1, GrMipMapped::kNo, 131 proxy->origin()); 132 if (!renderTargetContext) { 133 return nullptr; 134 } 135 136 SkMatrix localMatrix = SkMatrix::MakeTrans(-SkIntToScalar(srcOffset.x()), 137 -SkIntToScalar(srcOffset.y())); 138 SkISize size = SkISize::Make(2 * radiusX + 1, 2 * radiusY + 1); 139 SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY); 140 GrPaint paint; 141 auto conv = GrMatrixConvolutionEffect::MakeGaussian(std::move(proxy), srcBounds, size, 1.0, 0.0, 142 kernelOffset, mode, true, sigmaX, sigmaY); 143 paint.addColorFragmentProcessor(std::move(conv)); 144 paint.setPorterDuffXPFactory(SkBlendMode::kSrc); 145 GrFixedClip clip(dstII.bounds()); 146 147 renderTargetContext->fillRectWithLocalMatrix(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), 148 SkRect::Make(dstII.bounds()), localMatrix); 149 150 return renderTargetContext; 151 } 152 153 static sk_sp<GrRenderTargetContext> convolve_gaussian(GrContext* context, 154 sk_sp<GrTextureProxy> proxy, 155 const SkIRect& srcRect, 156 const SkIPoint& srcOffset, 157 Direction direction, 158 int radius, 159 float sigma, 160 SkIRect* contentRect, 161 GrTextureDomain::Mode mode, 162 const SkImageInfo& dstII, 163 SkBackingFit fit) { 164 SkASSERT(srcRect.width() <= dstII.width() && srcRect.height() <= dstII.height()); 165 166 GrPixelConfig config = get_blur_config(proxy.get()); 167 168 GrBackendFormat format = proxy->backendFormat().makeTexture2D(); 169 if (!format.isValid()) { 170 return nullptr; 171 } 172 173 sk_sp<GrRenderTargetContext> dstRenderTargetContext; 174 dstRenderTargetContext = context->contextPriv().makeDeferredRenderTargetContext( 175 format, 176 fit, srcRect.width(), 177 srcRect.height(), 178 config, 179 dstII.refColorSpace(), 180 1, GrMipMapped::kNo, 181 proxy->origin()); 182 if (!dstRenderTargetContext) { 183 return nullptr; 184 } 185 186 GrFixedClip clip(dstII.bounds()); 187 188 int bounds[2] = { 0, 0 }; 189 SkIRect dstRect = SkIRect::MakeWH(srcRect.width(), srcRect.height()); 190 if (GrTextureDomain::kIgnore_Mode == mode) { 191 *contentRect = dstRect; 192 convolve_gaussian_1d(dstRenderTargetContext.get(), clip, dstRect, srcOffset, 193 std::move(proxy), direction, radius, sigma, 194 GrTextureDomain::kIgnore_Mode, bounds); 195 return dstRenderTargetContext; 196 } 197 198 SkIRect midRect = *contentRect, leftRect, rightRect; 199 midRect.offset(srcOffset); 200 SkIRect topRect, bottomRect; 201 if (Direction::kX == direction) { 202 bounds[0] = contentRect->left(); 203 bounds[1] = contentRect->right(); 204 topRect = SkIRect::MakeLTRB(0, 0, dstRect.right(), midRect.top()); 205 bottomRect = SkIRect::MakeLTRB(0, midRect.bottom(), dstRect.right(), dstRect.bottom()); 206 midRect.inset(radius, 0); 207 leftRect = SkIRect::MakeLTRB(0, midRect.top(), midRect.left(), midRect.bottom()); 208 rightRect = 209 SkIRect::MakeLTRB(midRect.right(), midRect.top(), dstRect.width(), midRect.bottom()); 210 dstRect.fTop = midRect.top(); 211 dstRect.fBottom = midRect.bottom(); 212 213 contentRect->fLeft = dstRect.fLeft; 214 contentRect->fTop = midRect.fTop; 215 contentRect->fRight = dstRect.fRight; 216 contentRect->fBottom = midRect.fBottom; 217 } else { 218 bounds[0] = contentRect->top(); 219 bounds[1] = contentRect->bottom(); 220 topRect = SkIRect::MakeLTRB(0, 0, midRect.left(), dstRect.bottom()); 221 bottomRect = SkIRect::MakeLTRB(midRect.right(), 0, dstRect.right(), dstRect.bottom()); 222 midRect.inset(0, radius); 223 leftRect = SkIRect::MakeLTRB(midRect.left(), 0, midRect.right(), midRect.top()); 224 rightRect = 225 SkIRect::MakeLTRB(midRect.left(), midRect.bottom(), midRect.right(), dstRect.height()); 226 dstRect.fLeft = midRect.left(); 227 dstRect.fRight = midRect.right(); 228 229 contentRect->fLeft = midRect.fLeft; 230 contentRect->fTop = dstRect.fTop; 231 contentRect->fRight = midRect.fRight; 232 contentRect->fBottom = dstRect.fBottom; 233 } 234 if (!topRect.isEmpty()) { 235 dstRenderTargetContext->clear(&topRect, SK_PMColor4fTRANSPARENT, 236 GrRenderTargetContext::CanClearFullscreen::kNo); 237 } 238 239 if (!bottomRect.isEmpty()) { 240 dstRenderTargetContext->clear(&bottomRect, SK_PMColor4fTRANSPARENT, 241 GrRenderTargetContext::CanClearFullscreen::kNo); 242 } 243 244 if (midRect.isEmpty()) { 245 // Blur radius covers srcBounds; use bounds over entire draw 246 convolve_gaussian_1d(dstRenderTargetContext.get(), clip, dstRect, srcOffset, 247 std::move(proxy), direction, radius, sigma, mode, bounds); 248 } else { 249 // Draw right and left margins with bounds; middle without. 250 convolve_gaussian_1d(dstRenderTargetContext.get(), clip, leftRect, srcOffset, 251 proxy, direction, radius, sigma, mode, bounds); 252 convolve_gaussian_1d(dstRenderTargetContext.get(), clip, rightRect, srcOffset, 253 proxy, direction, radius, sigma, mode, bounds); 254 convolve_gaussian_1d(dstRenderTargetContext.get(), clip, midRect, srcOffset, 255 std::move(proxy), direction, radius, sigma, 256 GrTextureDomain::kIgnore_Mode, bounds); 257 } 258 259 return dstRenderTargetContext; 260 } 261 262 static sk_sp<GrTextureProxy> decimate(GrContext* context, 263 sk_sp<GrTextureProxy> src, 264 SkIPoint* srcOffset, 265 SkIRect* contentRect, 266 int scaleFactorX, int scaleFactorY, 267 bool willBeXFiltering, bool willBeYFiltering, 268 int radiusX, int radiusY, 269 GrTextureDomain::Mode mode, 270 const SkImageInfo& dstII) { 271 SkASSERT(SkIsPow2(scaleFactorX) && SkIsPow2(scaleFactorY)); 272 SkASSERT(scaleFactorX > 1 || scaleFactorY > 1); 273 274 GrPixelConfig config = get_blur_config(src.get()); 275 276 SkIRect srcRect; 277 if (GrTextureDomain::kIgnore_Mode == mode) { 278 srcRect = dstII.bounds(); 279 } else { 280 srcRect = *contentRect; 281 srcRect.offset(*srcOffset); 282 } 283 284 scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); 285 scale_irect(&srcRect, scaleFactorX, scaleFactorY); 286 287 SkIRect dstRect(srcRect); 288 289 sk_sp<GrRenderTargetContext> dstRenderTargetContext; 290 291 GrBackendFormat format = src->backendFormat().makeTexture2D(); 292 if (!format.isValid()) { 293 return nullptr; 294 } 295 296 for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) { 297 shrink_irect_by_2(&dstRect, i < scaleFactorX, i < scaleFactorY); 298 299 // We know this will not be the final draw so we are free to make it an approx match. 300 dstRenderTargetContext = context->contextPriv().makeDeferredRenderTargetContext( 301 format, 302 SkBackingFit::kApprox, 303 dstRect.fRight, 304 dstRect.fBottom, 305 config, dstII.refColorSpace(), 306 1, GrMipMapped::kNo, 307 src->origin()); 308 if (!dstRenderTargetContext) { 309 return nullptr; 310 } 311 312 GrPaint paint; 313 if (GrTextureDomain::kIgnore_Mode != mode && i == 1) { 314 // GrTextureDomainEffect does not support kRepeat_Mode with GrSamplerState::Filter. 315 GrTextureDomain::Mode modeForScaling = GrTextureDomain::kRepeat_Mode == mode 316 ? GrTextureDomain::kDecal_Mode 317 : mode; 318 319 SkRect domain = SkRect::Make(*contentRect); 320 domain.inset((i < scaleFactorX) ? SK_ScalarHalf : 0.0f, 321 (i < scaleFactorY) ? SK_ScalarHalf : 0.0f); 322 auto fp = GrTextureDomainEffect::Make(std::move(src), 323 SkMatrix::I(), 324 domain, 325 modeForScaling, 326 GrSamplerState::Filter::kBilerp); 327 paint.addColorFragmentProcessor(std::move(fp)); 328 srcRect.offset(-(*srcOffset)); 329 // TODO: consume the srcOffset in both first draws and always set it to zero 330 // back in GaussianBlur 331 srcOffset->set(0, 0); 332 } else { 333 paint.addColorTextureProcessor(std::move(src), SkMatrix::I(), 334 GrSamplerState::ClampBilerp()); 335 } 336 paint.setPorterDuffXPFactory(SkBlendMode::kSrc); 337 338 GrFixedClip clip(dstRect); 339 dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, 340 SkMatrix::I(), SkRect::Make(dstRect), 341 SkRect::Make(srcRect)); 342 343 src = dstRenderTargetContext->asTextureProxyRef(); 344 if (!src) { 345 return nullptr; 346 } 347 srcRect = dstRect; 348 } 349 350 *contentRect = dstRect; 351 352 SkASSERT(dstRenderTargetContext); 353 354 if (willBeXFiltering) { 355 if (scaleFactorX > 1) { 356 // Clear out a radius to the right of the contentRect to prevent the 357 // X convolution from reading garbage. 358 SkIRect clearRect = SkIRect::MakeXYWH(contentRect->fRight, contentRect->fTop, 359 radiusX, contentRect->height()); 360 dstRenderTargetContext->priv().absClear(&clearRect, SK_PMColor4fTRANSPARENT); 361 } 362 } else { 363 if (scaleFactorY > 1) { 364 // Clear out a radius below the contentRect to prevent the Y 365 // convolution from reading garbage. 366 SkIRect clearRect = SkIRect::MakeXYWH(contentRect->fLeft, contentRect->fBottom, 367 contentRect->width(), radiusY); 368 dstRenderTargetContext->priv().absClear(&clearRect, SK_PMColor4fTRANSPARENT); 369 } 370 } 371 372 return dstRenderTargetContext->asTextureProxyRef(); 373 } 374 375 // Expand the contents of 'srcRenderTargetContext' to fit in 'dstII'. 376 static sk_sp<GrRenderTargetContext> reexpand(GrContext* context, 377 sk_sp<GrRenderTargetContext> srcRenderTargetContext, 378 const SkIRect& localSrcBounds, 379 int scaleFactorX, int scaleFactorY, 380 GrTextureDomain::Mode mode, 381 const SkImageInfo& dstII, 382 SkBackingFit fit) { 383 const SkIRect srcRect = SkIRect::MakeWH(srcRenderTargetContext->width(), 384 srcRenderTargetContext->height()); 385 386 // Clear one pixel to the right and below, to accommodate bilinear upsampling. 387 // TODO: it seems like we should actually be clamping here rather than darkening 388 // the bottom right edges. 389 SkIRect clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom, srcRect.width() + 1, 1); 390 srcRenderTargetContext->priv().absClear(&clearRect, SK_PMColor4fTRANSPARENT); 391 clearRect = SkIRect::MakeXYWH(srcRect.fRight, srcRect.fTop, 1, srcRect.height()); 392 srcRenderTargetContext->priv().absClear(&clearRect, SK_PMColor4fTRANSPARENT); 393 394 sk_sp<GrTextureProxy> srcProxy = srcRenderTargetContext->asTextureProxyRef(); 395 if (!srcProxy) { 396 return nullptr; 397 } 398 399 srcRenderTargetContext = nullptr; // no longer needed 400 401 GrPixelConfig config = get_blur_config(srcProxy.get()); 402 403 GrBackendFormat format = srcProxy->backendFormat().makeTexture2D(); 404 if (!format.isValid()) { 405 return nullptr; 406 } 407 408 sk_sp<GrRenderTargetContext> dstRenderTargetContext = 409 context->contextPriv().makeDeferredRenderTargetContext(format, 410 fit, dstII.width(), dstII.height(), 411 config, dstII.refColorSpace(), 412 1, GrMipMapped::kNo, 413 srcProxy->origin()); 414 if (!dstRenderTargetContext) { 415 return nullptr; 416 } 417 418 GrPaint paint; 419 if (GrTextureDomain::kIgnore_Mode != mode) { 420 // GrTextureDomainEffect does not support kRepeat_Mode with GrSamplerState::Filter. 421 GrTextureDomain::Mode modeForScaling = GrTextureDomain::kRepeat_Mode == mode 422 ? GrTextureDomain::kDecal_Mode 423 : mode; 424 425 SkRect domain = SkRect::Make(localSrcBounds); 426 auto fp = GrTextureDomainEffect::Make(std::move(srcProxy), 427 SkMatrix::I(), 428 domain, 429 modeForScaling, 430 GrSamplerState::Filter::kBilerp); 431 paint.addColorFragmentProcessor(std::move(fp)); 432 } else { 433 // FIXME: this should be mitchell, not bilinear. 434 paint.addColorTextureProcessor(std::move(srcProxy), SkMatrix::I(), 435 GrSamplerState::ClampBilerp()); 436 } 437 paint.setPorterDuffXPFactory(SkBlendMode::kSrc); 438 GrFixedClip clip(dstII.bounds()); 439 440 // TODO: using dstII as dstRect results in some image diffs - why? 441 SkIRect dstRect(srcRect); 442 scale_irect(&dstRect, scaleFactorX, scaleFactorY); 443 444 dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), 445 SkRect::Make(dstRect), SkRect::Make(srcRect)); 446 447 return dstRenderTargetContext; 448 } 449 450 namespace SkGpuBlurUtils { 451 452 sk_sp<GrRenderTargetContext> GaussianBlur(GrContext* context, 453 sk_sp<GrTextureProxy> srcProxy, 454 sk_sp<SkColorSpace> colorSpace, 455 const SkIRect& dstBounds, 456 const SkIRect& srcBounds, 457 float sigmaX, 458 float sigmaY, 459 GrTextureDomain::Mode mode, 460 SkAlphaType at, 461 SkBackingFit fit) { 462 SkASSERT(context); 463 464 const GrPixelConfig config = get_blur_config(srcProxy.get()); 465 SkColorType ct; 466 if (!GrPixelConfigToColorType(config, &ct)) { 467 return nullptr; 468 } 469 470 const SkImageInfo finalDestII = SkImageInfo::Make(dstBounds.width(), dstBounds.height(), 471 ct, at, std::move(colorSpace)); 472 473 int scaleFactorX, radiusX; 474 int scaleFactorY, radiusY; 475 int maxTextureSize = context->contextPriv().caps()->maxTextureSize(); 476 sigmaX = adjust_sigma(sigmaX, maxTextureSize, &scaleFactorX, &radiusX); 477 sigmaY = adjust_sigma(sigmaY, maxTextureSize, &scaleFactorY, &radiusY); 478 SkASSERT(sigmaX || sigmaY); 479 480 SkIPoint srcOffset = SkIPoint::Make(-dstBounds.x(), -dstBounds.y()); 481 482 // For really small blurs (certainly no wider than 5x5 on desktop gpus) it is faster to just 483 // launch a single non separable kernel vs two launches 484 if (sigmaX > 0.0f && sigmaY > 0.0f && 485 (2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) { 486 // We shouldn't be scaling because this is a small size blur 487 SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY)); 488 489 return convolve_gaussian_2d(context, std::move(srcProxy), srcBounds, srcOffset, 490 radiusX, radiusY, sigmaX, sigmaY, 491 mode, finalDestII, fit); 492 } 493 494 // Only the last rendered renderTargetContext needs to match the supplied 'fit' 495 SkBackingFit xFit = fit, yFit = fit; 496 if (scaleFactorX > 1 || scaleFactorY > 1) { 497 xFit = yFit = SkBackingFit::kApprox; // reexpand will be last 498 } else if (sigmaY > 0.0f) { 499 xFit = SkBackingFit::kApprox; // the y-pass will be last 500 } 501 502 SkIRect localSrcBounds = srcBounds; 503 504 if (scaleFactorX > 1 || scaleFactorY > 1) { 505 srcProxy = decimate(context, std::move(srcProxy), &srcOffset, &localSrcBounds, 506 scaleFactorX, scaleFactorY, sigmaX > 0.0f, sigmaY > 0.0f, 507 radiusX, radiusY, mode, finalDestII); 508 if (!srcProxy) { 509 return nullptr; 510 } 511 } 512 513 sk_sp<GrRenderTargetContext> dstRenderTargetContext; 514 515 SkIRect srcRect = finalDestII.bounds(); 516 scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); 517 if (sigmaX > 0.0f) { 518 dstRenderTargetContext = convolve_gaussian(context, std::move(srcProxy), srcRect, srcOffset, 519 Direction::kX, radiusX, sigmaX, &localSrcBounds, 520 mode, finalDestII, xFit); 521 if (!dstRenderTargetContext) { 522 return nullptr; 523 } 524 525 if (sigmaY > 0.0f) { 526 // Clear out a radius below the srcRect to prevent the Y 527 // convolution from reading garbage. 528 SkIRect clearRect = SkIRect::MakeXYWH(srcRect.fLeft, srcRect.fBottom, 529 srcRect.width(), radiusY); 530 dstRenderTargetContext->priv().absClear(&clearRect, SK_PMColor4fTRANSPARENT); 531 } 532 533 srcProxy = dstRenderTargetContext->asTextureProxyRef(); 534 if (!srcProxy) { 535 return nullptr; 536 } 537 538 srcRect.offsetTo(0, 0); 539 srcOffset.set(0, 0); 540 } 541 542 if (sigmaY > 0.0f) { 543 dstRenderTargetContext = convolve_gaussian(context, std::move(srcProxy), srcRect, srcOffset, 544 Direction::kY, radiusY, sigmaY, &localSrcBounds, 545 mode, finalDestII, yFit); 546 if (!dstRenderTargetContext) { 547 return nullptr; 548 } 549 550 srcProxy = dstRenderTargetContext->asTextureProxyRef(); 551 if (!srcProxy) { 552 return nullptr; 553 } 554 555 srcRect.offsetTo(0, 0); 556 srcOffset.set(0, 0); 557 } 558 559 SkASSERT(dstRenderTargetContext); 560 SkASSERT(srcProxy.get() == dstRenderTargetContext->asTextureProxy()); 561 562 if (scaleFactorX > 1 || scaleFactorY > 1) { 563 dstRenderTargetContext = reexpand(context, std::move(dstRenderTargetContext), 564 localSrcBounds, scaleFactorX, scaleFactorY, 565 mode, finalDestII, fit); 566 } 567 568 SkASSERT(!dstRenderTargetContext || dstRenderTargetContext->origin() == srcProxy->origin()); 569 return dstRenderTargetContext; 570 } 571 572 } 573 574 #endif 575