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