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