GrContext.cpp revision caef3450488f98aa0bc429c4e2d8e29d6a7fece4
1 2/* 3 * Copyright 2011 Google Inc. 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 */ 8 9#include "GrContext.h" 10#include "GrContextOptions.h" 11#include "GrDrawingManager.h" 12#include "GrDrawContext.h" 13#include "GrLayerCache.h" 14#include "GrResourceCache.h" 15#include "GrResourceProvider.h" 16#include "GrSoftwarePathRenderer.h" 17#include "GrSurfacePriv.h" 18#include "GrTextBlobCache.h" 19 20#include "SkConfig8888.h" 21#include "SkGrPriv.h" 22 23#include "effects/GrConfigConversionEffect.h" 24 25#define ASSERT_OWNED_RESOURCE(R) SkASSERT(!(R) || (R)->getContext() == this) 26#define RETURN_IF_ABANDONED if (fDrawingManager->abandoned()) { return; } 27#define RETURN_FALSE_IF_ABANDONED if (fDrawingManager->abandoned()) { return false; } 28#define RETURN_NULL_IF_ABANDONED if (fDrawingManager->abandoned()) { return nullptr; } 29 30//////////////////////////////////////////////////////////////////////////////// 31 32GrContext* GrContext::Create(GrBackend backend, GrBackendContext backendContext) { 33 GrContextOptions defaultOptions; 34 return Create(backend, backendContext, defaultOptions); 35} 36 37GrContext* GrContext::Create(GrBackend backend, GrBackendContext backendContext, 38 const GrContextOptions& options) { 39 GrContext* context = new GrContext; 40 41 if (context->init(backend, backendContext, options)) { 42 return context; 43 } else { 44 context->unref(); 45 return nullptr; 46 } 47} 48 49static int32_t gNextID = 1; 50static int32_t next_id() { 51 int32_t id; 52 do { 53 id = sk_atomic_inc(&gNextID); 54 } while (id == SK_InvalidGenID); 55 return id; 56} 57 58GrContext::GrContext() : fUniqueID(next_id()) { 59 fGpu = nullptr; 60 fCaps = nullptr; 61 fResourceCache = nullptr; 62 fResourceProvider = nullptr; 63 fBatchFontCache = nullptr; 64 fFlushToReduceCacheSize = false; 65} 66 67bool GrContext::init(GrBackend backend, GrBackendContext backendContext, 68 const GrContextOptions& options) { 69 SkASSERT(!fGpu); 70 71 fGpu = GrGpu::Create(backend, backendContext, options, this); 72 if (!fGpu) { 73 return false; 74 } 75 this->initCommon(); 76 return true; 77} 78 79void GrContext::initCommon() { 80 fCaps = SkRef(fGpu->caps()); 81 fResourceCache = new GrResourceCache(fCaps); 82 fResourceCache->setOverBudgetCallback(OverBudgetCB, this); 83 fResourceProvider = new GrResourceProvider(fGpu, fResourceCache); 84 85 fLayerCache.reset(new GrLayerCache(this)); 86 87 fDidTestPMConversions = false; 88 89 fDrawingManager.reset(new GrDrawingManager(this)); 90 91 // GrBatchFontCache will eventually replace GrFontCache 92 fBatchFontCache = new GrBatchFontCache(this); 93 94 fTextBlobCache.reset(new GrTextBlobCache(TextBlobCacheOverBudgetCB, this)); 95} 96 97GrContext::~GrContext() { 98 if (!fGpu) { 99 SkASSERT(!fCaps); 100 return; 101 } 102 103 this->flush(); 104 105 fDrawingManager->cleanup(); 106 107 for (int i = 0; i < fCleanUpData.count(); ++i) { 108 (*fCleanUpData[i].fFunc)(this, fCleanUpData[i].fInfo); 109 } 110 111 delete fResourceProvider; 112 delete fResourceCache; 113 delete fBatchFontCache; 114 115 fGpu->unref(); 116 fCaps->unref(); 117} 118 119void GrContext::abandonContext() { 120 fResourceProvider->abandon(); 121 // abandon first to so destructors 122 // don't try to free the resources in the API. 123 fResourceCache->abandonAll(); 124 125 fGpu->contextAbandoned(); 126 127 fDrawingManager->abandon(); 128 129 fBatchFontCache->freeAll(); 130 fLayerCache->freeAll(); 131 fTextBlobCache->freeAll(); 132} 133 134void GrContext::resetContext(uint32_t state) { 135 fGpu->markContextDirty(state); 136} 137 138void GrContext::freeGpuResources() { 139 this->flush(); 140 141 fBatchFontCache->freeAll(); 142 fLayerCache->freeAll(); 143 144 fDrawingManager->freeGpuResources(); 145 146 fResourceCache->purgeAllUnlocked(); 147} 148 149void GrContext::getResourceCacheUsage(int* resourceCount, size_t* resourceBytes) const { 150 if (resourceCount) { 151 *resourceCount = fResourceCache->getBudgetedResourceCount(); 152 } 153 if (resourceBytes) { 154 *resourceBytes = fResourceCache->getBudgetedResourceBytes(); 155 } 156} 157 158//////////////////////////////////////////////////////////////////////////////// 159 160void GrContext::OverBudgetCB(void* data) { 161 SkASSERT(data); 162 163 GrContext* context = reinterpret_cast<GrContext*>(data); 164 165 // Flush the GrBufferedDrawTarget to possibly free up some textures 166 context->fFlushToReduceCacheSize = true; 167} 168 169void GrContext::TextBlobCacheOverBudgetCB(void* data) { 170 SkASSERT(data); 171 172 // Unlike the GrResourceCache, TextBlobs are drawn at the SkGpuDevice level, therefore they 173 // cannot use fFlushTorReduceCacheSize because it uses AutoCheckFlush. The solution is to move 174 // drawText calls to below the GrContext level, but this is not trivial because they call 175 // drawPath on SkGpuDevice 176 GrContext* context = reinterpret_cast<GrContext*>(data); 177 context->flush(); 178} 179 180//////////////////////////////////////////////////////////////////////////////// 181 182void GrContext::flush(int flagsBitfield) { 183 RETURN_IF_ABANDONED 184 185 if (kDiscard_FlushBit & flagsBitfield) { 186 fDrawingManager->reset(); 187 } else { 188 fDrawingManager->flush(); 189 } 190 fResourceCache->notifyFlushOccurred(); 191 fFlushToReduceCacheSize = false; 192} 193 194bool sw_convert_to_premul(GrPixelConfig srcConfig, int width, int height, size_t inRowBytes, 195 const void* inPixels, size_t outRowBytes, void* outPixels) { 196 SkSrcPixelInfo srcPI; 197 if (!GrPixelConfig2ColorAndProfileType(srcConfig, &srcPI.fColorType, nullptr)) { 198 return false; 199 } 200 srcPI.fAlphaType = kUnpremul_SkAlphaType; 201 srcPI.fPixels = inPixels; 202 srcPI.fRowBytes = inRowBytes; 203 204 SkDstPixelInfo dstPI; 205 dstPI.fColorType = srcPI.fColorType; 206 dstPI.fAlphaType = kPremul_SkAlphaType; 207 dstPI.fPixels = outPixels; 208 dstPI.fRowBytes = outRowBytes; 209 210 return srcPI.convertPixelsTo(&dstPI, width, height); 211} 212 213bool GrContext::writeSurfacePixels(GrSurface* surface, 214 int left, int top, int width, int height, 215 GrPixelConfig srcConfig, const void* buffer, size_t rowBytes, 216 uint32_t pixelOpsFlags) { 217 RETURN_FALSE_IF_ABANDONED 218 ASSERT_OWNED_RESOURCE(surface); 219 SkASSERT(surface); 220 221 this->testPMConversionsIfNecessary(pixelOpsFlags); 222 223 // Trim the params here so that if we wind up making a temporary surface it can be as small as 224 // necessary and because GrGpu::getWritePixelsInfo requires it. 225 if (!GrSurfacePriv::AdjustWritePixelParams(surface->width(), surface->height(), 226 GrBytesPerPixel(srcConfig), &left, &top, &width, 227 &height, &buffer, &rowBytes)) { 228 return false; 229 } 230 231 bool applyPremulToSrc = false; 232 if (kUnpremul_PixelOpsFlag & pixelOpsFlags) { 233 if (!GrPixelConfigIs8888(srcConfig)) { 234 return false; 235 } 236 applyPremulToSrc = true; 237 } 238 239 GrGpu::DrawPreference drawPreference = GrGpu::kNoDraw_DrawPreference; 240 // Don't prefer to draw for the conversion (and thereby access a texture from the cache) when 241 // we've already determined that there isn't a roundtrip preserving conversion processor pair. 242 if (applyPremulToSrc && !this->didFailPMUPMConversionTest()) { 243 drawPreference = GrGpu::kCallerPrefersDraw_DrawPreference; 244 } 245 246 GrGpu::WritePixelTempDrawInfo tempDrawInfo; 247 if (!fGpu->getWritePixelsInfo(surface, width, height, rowBytes, srcConfig, &drawPreference, 248 &tempDrawInfo)) { 249 return false; 250 } 251 252 if (!(kDontFlush_PixelOpsFlag & pixelOpsFlags) && surface->surfacePriv().hasPendingIO()) { 253 this->flush(); 254 } 255 256 SkAutoTUnref<GrTexture> tempTexture; 257 if (GrGpu::kNoDraw_DrawPreference != drawPreference) { 258 tempTexture.reset( 259 this->textureProvider()->createApproxTexture(tempDrawInfo.fTempSurfaceDesc)); 260 if (!tempTexture && GrGpu::kRequireDraw_DrawPreference == drawPreference) { 261 return false; 262 } 263 } 264 265 // temp buffer for doing sw premul conversion, if needed. 266#if defined(GOOGLE3) 267 // Stack frame size is limited in GOOGLE3. 268 SkAutoSTMalloc<48 * 48, uint32_t> tmpPixels(0); 269#else 270 SkAutoSTMalloc<128 * 128, uint32_t> tmpPixels(0); 271#endif 272 if (tempTexture) { 273 SkAutoTUnref<const GrFragmentProcessor> fp; 274 SkMatrix textureMatrix; 275 textureMatrix.setIDiv(tempTexture->width(), tempTexture->height()); 276 GrPaint paint; 277 if (applyPremulToSrc) { 278 fp.reset(this->createUPMToPMEffect(tempTexture, tempDrawInfo.fSwapRAndB, 279 textureMatrix)); 280 // If premultiplying was the only reason for the draw, fall back to a straight write. 281 if (!fp) { 282 if (GrGpu::kCallerPrefersDraw_DrawPreference == drawPreference) { 283 tempTexture.reset(nullptr); 284 } 285 } else { 286 applyPremulToSrc = false; 287 } 288 } 289 if (tempTexture) { 290 if (!fp) { 291 fp.reset(GrConfigConversionEffect::Create(tempTexture, tempDrawInfo.fSwapRAndB, 292 GrConfigConversionEffect::kNone_PMConversion, textureMatrix)); 293 if (!fp) { 294 return false; 295 } 296 } 297 GrRenderTarget* renderTarget = surface->asRenderTarget(); 298 SkASSERT(renderTarget); 299 if (tempTexture->surfacePriv().hasPendingIO()) { 300 this->flush(); 301 } 302 if (applyPremulToSrc) { 303 size_t tmpRowBytes = 4 * width; 304 tmpPixels.reset(width * height); 305 if (!sw_convert_to_premul(srcConfig, width, height, rowBytes, buffer, tmpRowBytes, 306 tmpPixels.get())) { 307 return false; 308 } 309 rowBytes = tmpRowBytes; 310 buffer = tmpPixels.get(); 311 applyPremulToSrc = false; 312 } 313 if (!fGpu->writePixels(tempTexture, 0, 0, width, height, 314 tempDrawInfo.fTempSurfaceDesc.fConfig, buffer, 315 rowBytes)) { 316 return false; 317 } 318 SkMatrix matrix; 319 matrix.setTranslate(SkIntToScalar(left), SkIntToScalar(top)); 320 SkAutoTUnref<GrDrawContext> drawContext(this->drawContext(renderTarget)); 321 if (!drawContext) { 322 return false; 323 } 324 paint.addColorFragmentProcessor(fp); 325 SkRect rect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height)); 326 drawContext->drawRect(GrClip::WideOpen(), paint, matrix, rect, nullptr); 327 328 if (kFlushWrites_PixelOp & pixelOpsFlags) { 329 this->flushSurfaceWrites(surface); 330 } 331 } 332 } 333 if (!tempTexture) { 334 if (applyPremulToSrc) { 335 size_t tmpRowBytes = 4 * width; 336 tmpPixels.reset(width * height); 337 if (!sw_convert_to_premul(srcConfig, width, height, rowBytes, buffer, tmpRowBytes, 338 tmpPixels.get())) { 339 return false; 340 } 341 rowBytes = tmpRowBytes; 342 buffer = tmpPixels.get(); 343 applyPremulToSrc = false; 344 } 345 return fGpu->writePixels(surface, left, top, width, height, srcConfig, buffer, rowBytes); 346 } 347 return true; 348} 349 350bool GrContext::readSurfacePixels(GrSurface* src, 351 int left, int top, int width, int height, 352 GrPixelConfig dstConfig, void* buffer, size_t rowBytes, 353 uint32_t flags) { 354 RETURN_FALSE_IF_ABANDONED 355 ASSERT_OWNED_RESOURCE(src); 356 SkASSERT(src); 357 358 this->testPMConversionsIfNecessary(flags); 359 SkAutoMutexAcquire ama(fReadPixelsMutex); 360 361 // Adjust the params so that if we wind up using an intermediate surface we've already done 362 // all the trimming and the temporary can be the min size required. 363 if (!GrSurfacePriv::AdjustReadPixelParams(src->width(), src->height(), 364 GrBytesPerPixel(dstConfig), &left, 365 &top, &width, &height, &buffer, &rowBytes)) { 366 return false; 367 } 368 369 if (!(kDontFlush_PixelOpsFlag & flags) && src->surfacePriv().hasPendingWrite()) { 370 this->flush(); 371 } 372 373 bool unpremul = SkToBool(kUnpremul_PixelOpsFlag & flags); 374 if (unpremul && !GrPixelConfigIs8888(dstConfig)) { 375 // The unpremul flag is only allowed for 8888 configs. 376 return false; 377 } 378 379 GrGpu::DrawPreference drawPreference = GrGpu::kNoDraw_DrawPreference; 380 // Don't prefer to draw for the conversion (and thereby access a texture from the cache) when 381 // we've already determined that there isn't a roundtrip preserving conversion processor pair. 382 if (unpremul && !this->didFailPMUPMConversionTest()) { 383 drawPreference = GrGpu::kCallerPrefersDraw_DrawPreference; 384 } 385 386 GrGpu::ReadPixelTempDrawInfo tempDrawInfo; 387 if (!fGpu->getReadPixelsInfo(src, width, height, rowBytes, dstConfig, &drawPreference, 388 &tempDrawInfo)) { 389 return false; 390 } 391 392 SkAutoTUnref<GrSurface> surfaceToRead(SkRef(src)); 393 bool didTempDraw = false; 394 if (GrGpu::kNoDraw_DrawPreference != drawPreference) { 395 if (tempDrawInfo.fUseExactScratch) { 396 // We only respect this when the entire src is being read. Otherwise we can trigger too 397 // many odd ball texture sizes and trash the cache. 398 if (width != src->width() || height != src->height()) { 399 tempDrawInfo.fUseExactScratch = false; 400 } 401 } 402 SkAutoTUnref<GrTexture> temp; 403 if (tempDrawInfo.fUseExactScratch) { 404 temp.reset(this->textureProvider()->createTexture(tempDrawInfo.fTempSurfaceDesc, true)); 405 } else { 406 temp.reset(this->textureProvider()->createApproxTexture(tempDrawInfo.fTempSurfaceDesc)); 407 } 408 if (temp) { 409 SkMatrix textureMatrix; 410 textureMatrix.setTranslate(SkIntToScalar(left), SkIntToScalar(top)); 411 textureMatrix.postIDiv(src->width(), src->height()); 412 GrPaint paint; 413 SkAutoTUnref<const GrFragmentProcessor> fp; 414 if (unpremul) { 415 fp.reset(this->createPMToUPMEffect(src->asTexture(), tempDrawInfo.fSwapRAndB, 416 textureMatrix)); 417 if (fp) { 418 unpremul = false; // we no longer need to do this on CPU after the read back. 419 } else if (GrGpu::kCallerPrefersDraw_DrawPreference == drawPreference) { 420 // We only wanted to do the draw in order to perform the unpremul so don't 421 // bother. 422 temp.reset(nullptr); 423 } 424 } 425 if (!fp && temp) { 426 fp.reset(GrConfigConversionEffect::Create(src->asTexture(), tempDrawInfo.fSwapRAndB, 427 GrConfigConversionEffect::kNone_PMConversion, textureMatrix)); 428 } 429 if (fp) { 430 paint.addColorFragmentProcessor(fp); 431 SkRect rect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height)); 432 SkAutoTUnref<GrDrawContext> drawContext(this->drawContext(temp->asRenderTarget())); 433 drawContext->drawRect(GrClip::WideOpen(), paint, SkMatrix::I(), rect, nullptr); 434 surfaceToRead.reset(SkRef(temp.get())); 435 left = 0; 436 top = 0; 437 didTempDraw = true; 438 } 439 } 440 } 441 442 if (GrGpu::kRequireDraw_DrawPreference == drawPreference && !didTempDraw) { 443 return false; 444 } 445 GrPixelConfig configToRead = dstConfig; 446 if (didTempDraw) { 447 this->flushSurfaceWrites(surfaceToRead); 448 // We swapped R and B while doing the temp draw. Swap back on the read. 449 if (tempDrawInfo.fSwapRAndB) { 450 configToRead = GrPixelConfigSwapRAndB(dstConfig); 451 } 452 } 453 if (!fGpu->readPixels(surfaceToRead, left, top, width, height, configToRead, buffer, 454 rowBytes)) { 455 return false; 456 } 457 458 // Perform umpremul conversion if we weren't able to perform it as a draw. 459 if (unpremul) { 460 SkDstPixelInfo dstPI; 461 if (!GrPixelConfig2ColorAndProfileType(dstConfig, &dstPI.fColorType, nullptr)) { 462 return false; 463 } 464 dstPI.fAlphaType = kUnpremul_SkAlphaType; 465 dstPI.fPixels = buffer; 466 dstPI.fRowBytes = rowBytes; 467 468 SkSrcPixelInfo srcPI; 469 srcPI.fColorType = dstPI.fColorType; 470 srcPI.fAlphaType = kPremul_SkAlphaType; 471 srcPI.fPixels = buffer; 472 srcPI.fRowBytes = rowBytes; 473 474 return srcPI.convertPixelsTo(&dstPI, width, height); 475 } 476 return true; 477} 478 479void GrContext::prepareSurfaceForExternalIO(GrSurface* surface) { 480 RETURN_IF_ABANDONED 481 SkASSERT(surface); 482 ASSERT_OWNED_RESOURCE(surface); 483 if (surface->surfacePriv().hasPendingIO()) { 484 this->flush(); 485 } 486 GrRenderTarget* rt = surface->asRenderTarget(); 487 if (fGpu && rt) { 488 fGpu->resolveRenderTarget(rt); 489 } 490} 491 492void GrContext::copySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect, 493 const SkIPoint& dstPoint, uint32_t pixelOpsFlags) { 494 RETURN_IF_ABANDONED 495 if (!src || !dst) { 496 return; 497 } 498 ASSERT_OWNED_RESOURCE(src); 499 ASSERT_OWNED_RESOURCE(dst); 500 501 // Since we're going to the draw target and not GPU, no need to check kNoFlush 502 // here. 503 if (!dst->asRenderTarget()) { 504 return; 505 } 506 507 SkAutoTUnref<GrDrawContext> drawContext(this->drawContext(dst->asRenderTarget())); 508 if (!drawContext) { 509 return; 510 } 511 512 drawContext->copySurface(src, srcRect, dstPoint); 513 514 if (kFlushWrites_PixelOp & pixelOpsFlags) { 515 this->flush(); 516 } 517} 518 519void GrContext::flushSurfaceWrites(GrSurface* surface) { 520 RETURN_IF_ABANDONED 521 if (surface->surfacePriv().hasPendingWrite()) { 522 this->flush(); 523 } 524} 525 526//////////////////////////////////////////////////////////////////////////////// 527int GrContext::getRecommendedSampleCount(GrPixelConfig config, 528 SkScalar dpi) const { 529 if (!this->caps()->isConfigRenderable(config, true)) { 530 return 0; 531 } 532 int chosenSampleCount = 0; 533 if (fGpu->caps()->shaderCaps()->pathRenderingSupport()) { 534 if (dpi >= 250.0f) { 535 chosenSampleCount = 4; 536 } else { 537 chosenSampleCount = 16; 538 } 539 } 540 return chosenSampleCount <= fGpu->caps()->maxSampleCount() ? 541 chosenSampleCount : 0; 542} 543 544 545GrDrawContext* GrContext::drawContext(GrRenderTarget* rt, const SkSurfaceProps* surfaceProps) { 546 return fDrawingManager->drawContext(rt, surfaceProps); 547} 548 549bool GrContext::abandoned() const { 550 return fDrawingManager->abandoned(); 551} 552 553namespace { 554void test_pm_conversions(GrContext* ctx, int* pmToUPMValue, int* upmToPMValue) { 555 GrConfigConversionEffect::PMConversion pmToUPM; 556 GrConfigConversionEffect::PMConversion upmToPM; 557 GrConfigConversionEffect::TestForPreservingPMConversions(ctx, &pmToUPM, &upmToPM); 558 *pmToUPMValue = pmToUPM; 559 *upmToPMValue = upmToPM; 560} 561} 562 563void GrContext::testPMConversionsIfNecessary(uint32_t flags) { 564 if (SkToBool(kUnpremul_PixelOpsFlag & flags)) { 565 SkAutoMutexAcquire ama(fTestPMConversionsMutex); 566 if (!fDidTestPMConversions) { 567 test_pm_conversions(this, &fPMToUPMConversion, &fUPMToPMConversion); 568 fDidTestPMConversions = true; 569 } 570 } 571} 572 573const GrFragmentProcessor* GrContext::createPMToUPMEffect(GrTexture* texture, 574 bool swapRAndB, 575 const SkMatrix& matrix) const { 576 // We should have already called this->testPMConversionsIfNecessary(). 577 SkASSERT(fDidTestPMConversions); 578 GrConfigConversionEffect::PMConversion pmToUPM = 579 static_cast<GrConfigConversionEffect::PMConversion>(fPMToUPMConversion); 580 if (GrConfigConversionEffect::kNone_PMConversion != pmToUPM) { 581 return GrConfigConversionEffect::Create(texture, swapRAndB, pmToUPM, matrix); 582 } else { 583 return nullptr; 584 } 585} 586 587const GrFragmentProcessor* GrContext::createUPMToPMEffect(GrTexture* texture, 588 bool swapRAndB, 589 const SkMatrix& matrix) const { 590 // We should have already called this->testPMConversionsIfNecessary(). 591 SkASSERT(fDidTestPMConversions); 592 GrConfigConversionEffect::PMConversion upmToPM = 593 static_cast<GrConfigConversionEffect::PMConversion>(fUPMToPMConversion); 594 if (GrConfigConversionEffect::kNone_PMConversion != upmToPM) { 595 return GrConfigConversionEffect::Create(texture, swapRAndB, upmToPM, matrix); 596 } else { 597 return nullptr; 598 } 599} 600 601bool GrContext::didFailPMUPMConversionTest() const { 602 // We should have already called this->testPMConversionsIfNecessary(). 603 SkASSERT(fDidTestPMConversions); 604 // The PM<->UPM tests fail or succeed together so we only need to check one. 605 return GrConfigConversionEffect::kNone_PMConversion == fPMToUPMConversion; 606} 607 608////////////////////////////////////////////////////////////////////////////// 609 610void GrContext::getResourceCacheLimits(int* maxTextures, size_t* maxTextureBytes) const { 611 if (maxTextures) { 612 *maxTextures = fResourceCache->getMaxResourceCount(); 613 } 614 if (maxTextureBytes) { 615 *maxTextureBytes = fResourceCache->getMaxResourceBytes(); 616 } 617} 618 619void GrContext::setResourceCacheLimits(int maxTextures, size_t maxTextureBytes) { 620 fResourceCache->setLimits(maxTextures, maxTextureBytes); 621} 622 623////////////////////////////////////////////////////////////////////////////// 624 625void GrContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const { 626 fResourceCache->dumpMemoryStatistics(traceMemoryDump); 627} 628