Coverage Report

Created: 2024-05-20 07:14

/src/skia/tools/DDLPromiseImageHelper.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2018 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 "tools/DDLPromiseImageHelper.h"
9
10
#include "include/core/SkPicture.h"
11
#include "include/core/SkSerialProcs.h"
12
#include "include/gpu/GrContextThreadSafeProxy.h"
13
#include "include/gpu/GrDirectContext.h"
14
#include "include/gpu/GrYUVABackendTextures.h"
15
#include "include/gpu/ganesh/SkImageGanesh.h"
16
#include "include/private/chromium/SkImageChromium.h"
17
#include "src/codec/SkCodecImageGenerator.h"
18
#include "src/core/SkCachedData.h"
19
#include "src/core/SkMipmap.h"
20
#include "src/core/SkTaskGroup.h"
21
#include "src/gpu/ganesh/GrCaps.h"
22
#include "src/gpu/ganesh/GrDirectContextPriv.h"
23
#include "src/gpu/ganesh/image/SkImage_GaneshYUVA.h"
24
#include "src/image/SkImage_Base.h"
25
26
DDLPromiseImageHelper::PromiseImageInfo::PromiseImageInfo(int index,
27
                                                          uint32_t originalUniqueID,
28
                                                          const SkImageInfo& ii)
29
        : fIndex(index)
30
        , fOriginalUniqueID(originalUniqueID)
31
0
        , fImageInfo(ii) {
32
0
}
33
34
DDLPromiseImageHelper::PromiseImageInfo::PromiseImageInfo(PromiseImageInfo&& other)
35
        : fIndex(other.fIndex)
36
        , fOriginalUniqueID(other.fOriginalUniqueID)
37
        , fImageInfo(other.fImageInfo)
38
        , fBaseLevel(other.fBaseLevel)
39
        , fMipLevels(std::move(other.fMipLevels))
40
0
        , fYUVAPixmaps(std::move(other.fYUVAPixmaps)) {
41
0
    for (int i = 0; i < SkYUVAInfo::kMaxPlanes; ++i) {
42
0
        fCallbackContexts[i] = std::move(other.fCallbackContexts[i]);
43
0
    }
44
0
}
45
46
0
DDLPromiseImageHelper::PromiseImageInfo::~PromiseImageInfo() {}
47
48
0
std::unique_ptr<SkPixmap[]> DDLPromiseImageHelper::PromiseImageInfo::normalMipLevels() const {
49
0
    SkASSERT(!this->isYUV());
50
0
    std::unique_ptr<SkPixmap[]> pixmaps(new SkPixmap[this->numMipLevels()]);
51
0
    pixmaps[0] = fBaseLevel.pixmap();
52
0
    if (fMipLevels) {
53
0
        for (int i = 0; i < fMipLevels->countLevels(); ++i) {
54
0
            SkMipmap::Level mipLevel;
55
0
            fMipLevels->getLevel(i, &mipLevel);
56
0
            pixmaps[i+1] = mipLevel.fPixmap;
57
0
        }
58
0
    }
59
0
    return pixmaps;
60
0
}
Unexecuted instantiation: DDLPromiseImageHelper::PromiseImageInfo::normalMipLevels() const
Unexecuted instantiation: DDLPromiseImageHelper::PromiseImageInfo::normalMipLevels() const
61
62
0
int DDLPromiseImageHelper::PromiseImageInfo::numMipLevels() const {
63
0
    SkASSERT(!this->isYUV());
64
0
    return fMipLevels ? fMipLevels->countLevels()+1 : 1;
65
0
}
Unexecuted instantiation: DDLPromiseImageHelper::PromiseImageInfo::numMipLevels() const
Unexecuted instantiation: DDLPromiseImageHelper::PromiseImageInfo::numMipLevels() const
66
67
void DDLPromiseImageHelper::PromiseImageInfo::setMipLevels(const SkBitmap& baseLevel,
68
0
                                                           std::unique_ptr<SkMipmap> mipLevels) {
69
0
    fBaseLevel = baseLevel;
70
0
    fMipLevels = std::move(mipLevels);
71
0
}
72
73
///////////////////////////////////////////////////////////////////////////////////////////////////
74
0
PromiseImageCallbackContext::~PromiseImageCallbackContext() {
75
0
    SkASSERT(fDoneCnt == fNumImages);
76
0
    SkASSERT(!fTotalFulfills || fDoneCnt);
77
78
0
    if (fPromiseImageTexture) {
79
0
        fContext->deleteBackendTexture(fPromiseImageTexture->backendTexture());
80
0
    }
81
0
}
Unexecuted instantiation: PromiseImageCallbackContext::~PromiseImageCallbackContext()
Unexecuted instantiation: PromiseImageCallbackContext::~PromiseImageCallbackContext()
82
83
0
void PromiseImageCallbackContext::setBackendTexture(const GrBackendTexture& backendTexture) {
84
0
    SkASSERT(!fPromiseImageTexture);
85
0
    SkASSERT(fBackendFormat == backendTexture.getBackendFormat());
86
0
    fPromiseImageTexture = GrPromiseImageTexture::Make(backendTexture);
87
0
}
Unexecuted instantiation: PromiseImageCallbackContext::setBackendTexture(GrBackendTexture const&)
Unexecuted instantiation: PromiseImageCallbackContext::setBackendTexture(GrBackendTexture const&)
88
89
0
void PromiseImageCallbackContext::destroyBackendTexture() {
90
0
    SkASSERT(!fPromiseImageTexture || fPromiseImageTexture->unique());
91
92
0
    if (fPromiseImageTexture) {
93
0
        fContext->deleteBackendTexture(fPromiseImageTexture->backendTexture());
94
0
    }
95
0
    fPromiseImageTexture = nullptr;
96
0
}
Unexecuted instantiation: PromiseImageCallbackContext::destroyBackendTexture()
Unexecuted instantiation: PromiseImageCallbackContext::destroyBackendTexture()
97
98
///////////////////////////////////////////////////////////////////////////////////////////////////
99
100
sk_sp<SkPicture> DDLPromiseImageHelper::recreateSKP(GrDirectContext* dContext,
101
0
                                                    SkPicture* inputPicture) {
102
0
    SkSerialProcs procs;
103
104
0
    procs.fImageCtx = this;
105
0
    procs.fImageProc = [](SkImage* image, void* ctx) -> sk_sp<SkData> {
106
0
        auto helper = static_cast<DDLPromiseImageHelper*>(ctx);
107
108
0
        int id = helper->findOrDefineImage(image);
109
110
        // Even if 'id' is invalid (i.e., -1) write it to the SKP
111
0
        return SkData::MakeWithCopy(&id, sizeof(id));
112
0
    };
113
114
0
    sk_sp<SkData> compressedPictureData = inputPicture->serialize(&procs);
115
0
    if (!compressedPictureData) {
116
0
        return nullptr;
117
0
    }
118
119
0
    this->createCallbackContexts(dContext);
120
121
0
    return this->reinflateSKP(dContext->threadSafeProxy(), compressedPictureData.get());
122
0
}
123
124
static GrBackendTexture create_yuva_texture(GrDirectContext* direct,
125
                                            const SkPixmap& pm,
126
0
                                            int texIndex) {
127
0
    SkASSERT(texIndex >= 0 && texIndex <= 3);
128
129
0
    bool finishedBECreate = false;
130
0
    auto markFinished = [](void* context) {
131
0
        *(bool*)context = true;
132
0
    };
Unexecuted instantiation: DDLPromiseImageHelper.cpp:create_yuva_texture(GrDirectContext*, SkPixmap const&, int)::$_0::operator()(void*) const
Unexecuted instantiation: DDLPromiseImageHelper.cpp:create_yuva_texture(GrDirectContext*, SkPixmap const&, int)::$_1::operator()(void*) const
133
0
    auto beTex = direct->createBackendTexture(pm,
134
0
                                              kTopLeft_GrSurfaceOrigin,
135
0
                                              GrRenderable::kNo,
136
0
                                              GrProtected::kNo,
137
0
                                              markFinished,
138
0
                                              &finishedBECreate,
139
0
                                              /*label=*/"CreateYuvaTexture");
140
0
    if (beTex.isValid()) {
141
0
        direct->submit();
142
0
        while (!finishedBECreate) {
143
0
            direct->checkAsyncWorkCompletion();
144
0
        }
145
0
    }
146
0
    return beTex;
147
0
}
Unexecuted instantiation: DDLPromiseImageHelper.cpp:create_yuva_texture(GrDirectContext*, SkPixmap const&, int)
Unexecuted instantiation: DDLPromiseImageHelper.cpp:create_yuva_texture(GrDirectContext*, SkPixmap const&, int)
148
149
/*
150
 * Create backend textures and upload data to them for all the textures required to satisfy
151
 * a single promise image.
152
 * For YUV textures this will result in up to 4 actual textures.
153
 */
154
void DDLPromiseImageHelper::CreateBETexturesForPromiseImage(GrDirectContext* direct,
155
0
                                                            PromiseImageInfo* info) {
156
0
    if (info->isYUV()) {
157
0
        int numPixmaps = info->yuvaInfo().numPlanes();
158
0
        for (int j = 0; j < numPixmaps; ++j) {
159
0
            const SkPixmap& yuvPixmap = info->yuvPixmap(j);
160
161
0
            PromiseImageCallbackContext* callbackContext = info->callbackContext(j);
162
0
            SkASSERT(callbackContext);
163
164
            // DDL TODO: what should we do with mipmapped YUV images
165
0
            callbackContext->setBackendTexture(create_yuva_texture(direct, yuvPixmap, j));
166
0
            SkASSERT(callbackContext->promiseImageTexture());
167
0
        }
168
0
    } else {
169
0
        PromiseImageCallbackContext* callbackContext = info->callbackContext(0);
170
0
        if (!callbackContext) {
171
            // This texture would've been too large to fit on the GPU
172
0
            return;
173
0
        }
174
175
0
        std::unique_ptr<SkPixmap[]> mipLevels = info->normalMipLevels();
176
177
0
        bool finishedBECreate = false;
178
0
        auto markFinished = [](void* context) {
179
0
            *(bool*)context = true;
180
0
        };
Unexecuted instantiation: DDLPromiseImageHelper.cpp:DDLPromiseImageHelper::CreateBETexturesForPromiseImage(GrDirectContext*, DDLPromiseImageHelper::PromiseImageInfo*)::$_0::operator()(void*) const
Unexecuted instantiation: DDLPromiseImageHelper.cpp:DDLPromiseImageHelper::CreateBETexturesForPromiseImage(GrDirectContext*, DDLPromiseImageHelper::PromiseImageInfo*)::$_2::operator()(void*) const
181
0
        auto backendTex = direct->createBackendTexture(mipLevels.get(),
182
0
                                                       info->numMipLevels(),
183
0
                                                       kTopLeft_GrSurfaceOrigin,
184
0
                                                       GrRenderable::kNo,
185
0
                                                       GrProtected::kNo,
186
0
                                                       markFinished,
187
0
                                                       &finishedBECreate,
188
0
                                                       /*label=*/"CreateBETexturesForPromiseImage");
189
0
        SkASSERT(backendTex.isValid());
190
0
        direct->submit();
191
0
        while (!finishedBECreate) {
192
0
            direct->checkAsyncWorkCompletion();
193
0
        }
194
195
0
        callbackContext->setBackendTexture(backendTex);
196
0
    }
197
0
}
Unexecuted instantiation: DDLPromiseImageHelper::CreateBETexturesForPromiseImage(GrDirectContext*, DDLPromiseImageHelper::PromiseImageInfo*)
Unexecuted instantiation: DDLPromiseImageHelper::CreateBETexturesForPromiseImage(GrDirectContext*, DDLPromiseImageHelper::PromiseImageInfo*)
198
199
0
void DDLPromiseImageHelper::DeleteBETexturesForPromiseImage(PromiseImageInfo* info) {
200
0
    if (info->isYUV()) {
201
0
        int numPixmaps = info->yuvaInfo().numPlanes();
202
0
        for (int j = 0; j < numPixmaps; ++j) {
203
0
            PromiseImageCallbackContext* callbackContext = info->callbackContext(j);
204
0
            SkASSERT(callbackContext);
205
206
0
            callbackContext->destroyBackendTexture();
207
0
            SkASSERT(!callbackContext->promiseImageTexture());
208
0
        }
209
0
    } else {
210
0
        PromiseImageCallbackContext* callbackContext = info->callbackContext(0);
211
0
        if (!callbackContext) {
212
            // This texture would've been too large to fit on the GPU
213
0
            return;
214
0
        }
215
216
0
        callbackContext->destroyBackendTexture();
217
0
        SkASSERT(!callbackContext->promiseImageTexture());
218
0
    }
219
0
}
Unexecuted instantiation: DDLPromiseImageHelper::DeleteBETexturesForPromiseImage(DDLPromiseImageHelper::PromiseImageInfo*)
Unexecuted instantiation: DDLPromiseImageHelper::DeleteBETexturesForPromiseImage(DDLPromiseImageHelper::PromiseImageInfo*)
220
221
0
void DDLPromiseImageHelper::createCallbackContexts(GrDirectContext* direct) {
222
0
    const GrCaps* caps = direct->priv().caps();
223
0
    const int maxDimension = caps->maxTextureSize();
224
225
0
    for (int i = 0; i < fImageInfo.size(); ++i) {
226
0
        PromiseImageInfo& info = fImageInfo[i];
227
228
0
        if (info.isYUV()) {
229
0
            int numPixmaps = info.yuvaInfo().numPlanes();
230
231
0
            for (int j = 0; j < numPixmaps; ++j) {
232
0
                const SkPixmap& yuvPixmap = info.yuvPixmap(j);
233
234
0
                GrBackendFormat backendFormat = direct->defaultBackendFormat(yuvPixmap.colorType(),
235
0
                                                                             GrRenderable::kNo);
236
237
0
                sk_sp<PromiseImageCallbackContext> callbackContext(
238
0
                    new PromiseImageCallbackContext(direct, backendFormat));
239
240
0
                info.setCallbackContext(j, std::move(callbackContext));
241
0
            }
242
0
        } else {
243
0
            const SkBitmap& baseLevel = info.baseLevel();
244
245
            // TODO: explicitly mark the PromiseImageInfo as too big and check in uploadAllToGPU
246
0
            if (maxDimension < std::max(baseLevel.width(), baseLevel.height())) {
247
                // This won't fit on the GPU. Fallback to a raster-backed image per tile.
248
0
                continue;
249
0
            }
250
251
0
            GrBackendFormat backendFormat = direct->defaultBackendFormat(baseLevel.colorType(),
252
0
                                                                         GrRenderable::kNo);
253
0
            if (!caps->isFormatTexturable(backendFormat, GrTextureType::k2D)) {
254
0
                continue;
255
0
            }
256
257
0
            sk_sp<PromiseImageCallbackContext> callbackContext(
258
0
                new PromiseImageCallbackContext(direct, backendFormat));
259
260
0
            info.setCallbackContext(0, std::move(callbackContext));
261
0
        }
262
0
    }
263
0
}
264
265
0
void DDLPromiseImageHelper::uploadAllToGPU(SkTaskGroup* taskGroup, GrDirectContext* direct) {
266
0
    if (taskGroup) {
267
0
        for (int i = 0; i < fImageInfo.size(); ++i) {
268
0
            PromiseImageInfo* info = &fImageInfo[i];
269
270
0
            taskGroup->add([direct, info]() { CreateBETexturesForPromiseImage(direct, info); });
271
0
        }
272
0
    } else {
273
0
        for (int i = 0; i < fImageInfo.size(); ++i) {
274
0
            CreateBETexturesForPromiseImage(direct, &fImageInfo[i]);
275
0
        }
276
0
    }
277
0
}
278
279
0
void DDLPromiseImageHelper::deleteAllFromGPU(SkTaskGroup* taskGroup, GrDirectContext* direct) {
280
0
    if (taskGroup) {
281
0
        for (int i = 0; i < fImageInfo.size(); ++i) {
282
0
            PromiseImageInfo* info = &fImageInfo[i];
283
284
0
            taskGroup->add([info]() { DeleteBETexturesForPromiseImage(info); });
285
0
        }
286
0
    } else {
287
0
        for (int i = 0; i < fImageInfo.size(); ++i) {
288
0
            DeleteBETexturesForPromiseImage(&fImageInfo[i]);
289
0
        }
290
0
    }
291
0
}
292
293
sk_sp<SkPicture> DDLPromiseImageHelper::reinflateSKP(
294
                                                   sk_sp<GrContextThreadSafeProxy> threadSafeProxy,
295
0
                                                   SkData* compressedPictureData) {
296
0
    DeserialImageProcContext procContext { std::move(threadSafeProxy), this };
297
298
0
    SkDeserialProcs procs;
299
0
    procs.fImageCtx = (void*) &procContext;
300
0
    procs.fImageProc = CreatePromiseImages;
301
302
0
    return SkPicture::MakeFromData(compressedPictureData, &procs);
303
0
}
304
305
// This generates promise images to replace the indices in the compressed picture.
306
sk_sp<SkImage> DDLPromiseImageHelper::CreatePromiseImages(const void* rawData,
307
                                                          size_t length,
308
0
                                                          void* ctxIn) {
309
0
    DeserialImageProcContext* procContext = static_cast<DeserialImageProcContext*>(ctxIn);
310
0
    DDLPromiseImageHelper* helper = procContext->fHelper;
311
312
0
    SkASSERT(length == sizeof(int));
313
314
0
    const int* indexPtr = static_cast<const int*>(rawData);
315
0
    if (!helper->isValidID(*indexPtr)) {
316
0
        return nullptr;
317
0
    }
318
319
0
    const DDLPromiseImageHelper::PromiseImageInfo& curImage = helper->getInfo(*indexPtr);
320
321
    // If there is no callback context that means 'createCallbackContexts' determined the
322
    // texture wouldn't fit on the GPU. Create a bitmap-backed image.
323
0
    if (!curImage.isYUV() && !curImage.callbackContext(0)) {
324
0
        SkASSERT(curImage.baseLevel().isImmutable());
325
0
        return curImage.baseLevel().asImage();
326
0
    }
327
328
0
    SkASSERT(curImage.index() == *indexPtr);
329
330
0
    sk_sp<SkImage> image;
331
0
    if (curImage.isYUV()) {
332
0
        GrBackendFormat backendFormats[SkYUVAInfo::kMaxPlanes];
333
0
        const SkYUVAInfo& yuvaInfo = curImage.yuvaInfo();
334
0
        void* contexts[SkYUVAInfo::kMaxPlanes] = {nullptr, nullptr, nullptr, nullptr};
335
0
        int textureCount = yuvaInfo.numPlanes();
336
0
        for (int i = 0; i < textureCount; ++i) {
337
0
            backendFormats[i] = curImage.backendFormat(i);
338
0
            contexts[i] = curImage.refCallbackContext(i).release();
339
0
        }
340
0
        GrYUVABackendTextureInfo yuvaBackendTextures(
341
0
                yuvaInfo, backendFormats, skgpu::Mipmapped::kNo, kTopLeft_GrSurfaceOrigin);
342
0
        image = SkImages::PromiseTextureFromYUVA(
343
0
                procContext->fThreadSafeProxy,
344
0
                yuvaBackendTextures,
345
0
                curImage.refOverallColorSpace(),
346
0
                PromiseImageCallbackContext::PromiseImageFulfillProc,
347
0
                PromiseImageCallbackContext::PromiseImageReleaseProc,
348
0
                contexts);
349
0
        if (!image) {
350
0
            return nullptr;
351
0
        }
352
0
        for (int i = 0; i < textureCount; ++i) {
353
0
            curImage.callbackContext(i)->wasAddedToImage();
354
0
        }
355
356
0
    } else {
357
0
        const GrBackendFormat& backendFormat = curImage.backendFormat(0);
358
0
        SkASSERT(backendFormat.isValid());
359
360
0
        image = SkImages::PromiseTextureFrom(procContext->fThreadSafeProxy,
361
0
                                             backendFormat,
362
0
                                             curImage.overallDimensions(),
363
0
                                             curImage.mipmapped(0),
364
0
                                             GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
365
0
                                             curImage.overallColorType(),
366
0
                                             curImage.overallAlphaType(),
367
0
                                             curImage.refOverallColorSpace(),
368
0
                                             PromiseImageCallbackContext::PromiseImageFulfillProc,
369
0
                                             PromiseImageCallbackContext::PromiseImageReleaseProc,
370
0
                                             (void*)curImage.refCallbackContext(0).release());
371
0
        curImage.callbackContext(0)->wasAddedToImage();
372
0
    }
373
0
    helper->fPromiseImages.push_back(image);
374
0
    SkASSERT(image);
375
0
    return image;
376
0
}
Unexecuted instantiation: DDLPromiseImageHelper::CreatePromiseImages(void const*, unsigned long, void*)
Unexecuted instantiation: DDLPromiseImageHelper::CreatePromiseImages(void const*, unsigned long, void*)
377
378
0
int DDLPromiseImageHelper::findImage(SkImage* image) const {
379
0
    for (int i = 0; i < fImageInfo.size(); ++i) {
380
0
        if (fImageInfo[i].originalUniqueID() == image->uniqueID()) { // trying to dedup here
381
0
            SkASSERT(fImageInfo[i].index() == i);
382
0
            SkASSERT(this->isValidID(i) && this->isValidID(fImageInfo[i].index()));
383
0
            return i;
384
0
        }
385
0
    }
386
0
    return -1;
387
0
}
Unexecuted instantiation: DDLPromiseImageHelper::findImage(SkImage*) const
Unexecuted instantiation: DDLPromiseImageHelper::findImage(SkImage*) const
388
389
0
int DDLPromiseImageHelper::addImage(SkImage* image) {
390
0
    SkImage_Base* ib = as_IB(image);
391
392
0
    SkImageInfo overallII = SkImageInfo::Make(image->width(), image->height(),
393
0
                                              image->colorType() == kBGRA_8888_SkColorType
394
0
                                                        ? kRGBA_8888_SkColorType
395
0
                                                        : image->colorType(),
396
0
                                              image->alphaType(),
397
0
                                              image->refColorSpace());
398
399
0
    PromiseImageInfo& newImageInfo = fImageInfo.emplace_back(fImageInfo.size(),
400
0
                                                             image->uniqueID(),
401
0
                                                             overallII);
402
403
0
    auto codec = SkCodecImageGenerator::MakeFromEncodedCodec(ib->refEncodedData());
404
0
    SkYUVAPixmapInfo yuvaInfo;
405
0
    if (codec && codec->queryYUVAInfo(fSupportedYUVADataTypes, &yuvaInfo)) {
406
0
        auto yuvaPixmaps = SkYUVAPixmaps::Allocate(yuvaInfo);
407
0
        if (!codec->getYUVAPlanes(yuvaPixmaps)) {
408
0
            return -1;
409
0
        }
410
0
        SkASSERT(yuvaPixmaps.isValid());
411
0
        newImageInfo.setYUVPlanes(std::move(yuvaPixmaps));
412
0
    } else {
413
0
        sk_sp<SkImage> rasterImage = image->makeRasterImage(); // force decoding of lazy images
414
0
        if (!rasterImage) {
415
0
            return -1;
416
0
        }
417
418
0
        SkBitmap tmp;
419
0
        tmp.allocPixels(overallII);
420
421
0
        if (!rasterImage->readPixels(nullptr, tmp.pixmap(), 0, 0)) {
422
0
            return -1;
423
0
        }
424
425
0
        tmp.setImmutable();
426
427
        // Given how the DDL testing harness works (i.e., only modifying the SkImages w/in an
428
        // SKP) we don't know if a given SkImage will require mipmapping. To work around this
429
        // we just try to create all the backend textures as mipmapped but, failing that, fall
430
        // back to un-mipped.
431
0
        std::unique_ptr<SkMipmap> mipmaps(SkMipmap::Build(tmp.pixmap(), nullptr));
432
433
0
        newImageInfo.setMipLevels(tmp, std::move(mipmaps));
434
0
    }
435
    // In either case newImageInfo's PromiseImageCallbackContext is filled in by uploadAllToGPU
436
437
0
    return fImageInfo.size()-1;
438
0
}
Unexecuted instantiation: DDLPromiseImageHelper::addImage(SkImage*)
Unexecuted instantiation: DDLPromiseImageHelper::addImage(SkImage*)
439
440
0
int DDLPromiseImageHelper::findOrDefineImage(SkImage* image) {
441
0
    int preExistingID = this->findImage(image);
442
0
    if (preExistingID >= 0) {
443
0
        SkASSERT(this->isValidID(preExistingID));
444
0
        return preExistingID;
445
0
    }
446
447
0
    int newID = this->addImage(image);
448
0
    return newID;
449
0
}
Unexecuted instantiation: DDLPromiseImageHelper::findOrDefineImage(SkImage*)
Unexecuted instantiation: DDLPromiseImageHelper::findOrDefineImage(SkImage*)