Support sharing promise images between DDLs

- Migrate our code to SkImage::MakePromiseTexture
- Have DDLTileHelper share one SKP and one set of promise images across all tiles.
- Disallow on-the-fly allocation of mips for promise textures.

Bug: skia:10286
Change-Id: Ie35976958454fc520f3c9d860e6285441260c9f7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/291938
Commit-Queue: Adlai Holler <adlai@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 8a8b354..67c7a20 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -1790,7 +1790,7 @@
                            SkTaskGroup* recordingTaskGroup,
                            SkTaskGroup* gpuTaskGroup,
                            sk_gpu_test::TestContext* gpuTestCtx,
-                           GrDirectContext* gpuThreadCtx) const {
+                           GrDirectContext* dContext) const {
 
     // We have to do this here bc characterization can hit the SkGpuDevice's thread guard (i.e.,
     // leaving it until the DDLTileHelper ctor will result in multiple threads trying to use the
@@ -1800,8 +1800,8 @@
 
     auto size = src.size();
     SkPictureRecorder recorder;
-    Result result = src.draw(gpuThreadCtx, recorder.beginRecording(SkIntToScalar(size.width()),
-                                                                   SkIntToScalar(size.height())));
+    Result result = src.draw(dContext, recorder.beginRecording(SkIntToScalar(size.width()),
+                                                               SkIntToScalar(size.height())));
     if (!result.isOk()) {
         return result;
     }
@@ -1810,14 +1810,14 @@
     // this is our ultimate final drawing area/rect
     SkIRect viewport = SkIRect::MakeWH(size.fWidth, size.fHeight);
 
-    SkYUVAPixmapInfo::SupportedDataTypes supportedYUVADataTypes(*gpuThreadCtx);
+    SkYUVAPixmapInfo::SupportedDataTypes supportedYUVADataTypes(*dContext);
     DDLPromiseImageHelper promiseImageHelper(supportedYUVADataTypes);
     sk_sp<SkData> compressedPictureData = promiseImageHelper.deflateSKP(inputPicture.get());
     if (!compressedPictureData) {
         return Result::Fatal("GPUDDLSink: Couldn't deflate SkPicture");
     }
 
-    promiseImageHelper.createCallbackContexts(gpuThreadCtx);
+    promiseImageHelper.createCallbackContexts(dContext);
 
     // 'gpuTestCtx/gpuThreadCtx' is being shifted to the gpuThread. Leave the main (this)
     // thread w/o a context.
@@ -1827,21 +1827,21 @@
     gpuTaskGroup->add([gpuTestCtx] { gpuTestCtx->makeCurrent(); });
 
     // TODO: move the image upload to the utility thread
-    promiseImageHelper.uploadAllToGPU(gpuTaskGroup, gpuThreadCtx);
+    promiseImageHelper.uploadAllToGPU(gpuTaskGroup, dContext);
 
     // Care must be taken when using 'gpuThreadCtx' bc it moves between the gpu-thread and this
     // one. About all it can be consistently used for is GrCaps access and 'defaultBackendFormat'
     // calls.
     constexpr int kNumDivisions = 3;
-    DDLTileHelper tiles(gpuThreadCtx, dstCharacterization, viewport, kNumDivisions,
+    DDLTileHelper tiles(dContext, dstCharacterization, viewport, kNumDivisions,
                         /* addRandomPaddingToDst */ false);
 
-    tiles.createBackendTextures(gpuTaskGroup, gpuThreadCtx);
+    tiles.createBackendTextures(gpuTaskGroup, dContext);
 
-    // Reinflate the compressed picture individually for each thread.
-    tiles.createSKPPerTile(compressedPictureData.get(), promiseImageHelper);
+    // Reinflate the compressed picture.
+    tiles.createSKP(dContext->threadSafeProxy(), compressedPictureData.get(), promiseImageHelper);
 
-    tiles.kickOffThreadedWork(recordingTaskGroup, gpuTaskGroup, gpuThreadCtx);
+    tiles.kickOffThreadedWork(recordingTaskGroup, gpuTaskGroup, dContext);
 
     // We have to wait for the recording threads to schedule all their work on the gpu thread
     // before we can schedule the composition draw and the flush. Note that the gpu thread
@@ -1859,23 +1859,22 @@
                       });
 
     // This should be the only explicit flush for the entire DDL draw.
-    // TODO: remove the flushes in do_gpu_stuff
-    gpuTaskGroup->add([gpuThreadCtx]() {
+    gpuTaskGroup->add([dContext]() {
                                            // We need to ensure all the GPU work is finished so
                                            // the following 'deleteAllFromGPU' call will work
                                            // on Vulkan.
                                            // TODO: switch over to using the promiseImage callbacks
                                            // to free the backendTextures. This is complicated a
                                            // bit by which thread possesses the direct context.
-                                           gpuThreadCtx->flush();
-                                           gpuThreadCtx->submit(true);
+                                           dContext->flush();
+                                           dContext->submit(true);
                                        });
 
     // The backend textures are created on the gpuThread by the 'uploadAllToGPU' call.
     // It is simpler to also delete them at this point on the gpuThread.
-    promiseImageHelper.deleteAllFromGPU(gpuTaskGroup, gpuThreadCtx);
+    promiseImageHelper.deleteAllFromGPU(gpuTaskGroup, dContext);
 
-    tiles.deleteBackendTextures(gpuTaskGroup, gpuThreadCtx);
+    tiles.deleteBackendTextures(gpuTaskGroup, dContext);
 
     // A flush has already been scheduled on the gpu thread along with the clean up of the backend
     // textures so it is safe to schedule making 'gpuTestCtx' not current on the gpuThread.
@@ -2293,11 +2292,12 @@
 
             tiles.createBackendTextures(nullptr, direct);
 
-            // Second, reinflate the compressed picture individually for each thread
+            // Second, reinflate the compressed picture.
             // This recreates the promise SkImages on each replay iteration. We are currently
             // relying on this to test using a SkPromiseImageTexture to fulfill different
-            // SkImages. On each replay the promise SkImages are recreated in createSKPPerTile.
-            tiles.createSKPPerTile(compressedPictureData.get(), promiseImageHelper);
+            // SkImages. On each replay the promise SkImages are recreated in createSKP.
+            tiles.createSKP(direct->threadSafeProxy(), compressedPictureData.get(),
+                            promiseImageHelper);
 
             // Third, create the DDLs in parallel
             tiles.createDDLsInParallel();
diff --git a/src/gpu/GrProxyProvider.cpp b/src/gpu/GrProxyProvider.cpp
index 8e4bc2a..9562f07 100644
--- a/src/gpu/GrProxyProvider.cpp
+++ b/src/gpu/GrProxyProvider.cpp
@@ -689,17 +689,19 @@
 
     // We pass kReadOnly here since we should treat content of the client's texture as immutable.
     // The promise API provides no way for the client to indicate that the texture is protected.
-    return sk_sp<GrTextureProxy>(new GrTextureProxy(std::move(callback),
-                                                    format,
-                                                    dimensions,
-                                                    mipMapped,
-                                                    mipmapStatus,
-                                                    SkBackingFit::kExact,
-                                                    SkBudgeted::kNo,
-                                                    GrProtected::kNo,
-                                                    GrInternalSurfaceFlags::kReadOnly,
-                                                    GrSurfaceProxy::UseAllocator::kYes,
-                                                    GrDDLProvider::kYes));
+    auto proxy = sk_sp<GrTextureProxy>(new GrTextureProxy(std::move(callback),
+                                                          format,
+                                                          dimensions,
+                                                          mipMapped,
+                                                          mipmapStatus,
+                                                          SkBackingFit::kExact,
+                                                          SkBudgeted::kNo,
+                                                          GrProtected::kNo,
+                                                          GrInternalSurfaceFlags::kReadOnly,
+                                                          GrSurfaceProxy::UseAllocator::kYes,
+                                                          GrDDLProvider::kYes));
+    proxy->priv().setIsPromiseProxy();
+    return proxy;
 }
 
 sk_sp<GrTextureProxy> GrProxyProvider::createLazyProxy(LazyInstantiateCallback&& callback,
diff --git a/src/gpu/GrSurfaceProxy.h b/src/gpu/GrSurfaceProxy.h
index b6dcad0..f99a67e 100644
--- a/src/gpu/GrSurfaceProxy.h
+++ b/src/gpu/GrSurfaceProxy.h
@@ -337,6 +337,8 @@
 
     GrProtected isProtected() const { return fIsProtected; }
 
+    bool isPromiseProxy() { return fIsPromiseProxy; }
+
 protected:
     // Deferred version - takes a new UniqueID from the shared resource/proxy pool.
     GrSurfaceProxy(const GrBackendFormat&,
@@ -433,6 +435,7 @@
 
     bool                   fIgnoredByResourceAllocator = false;
     bool                   fIsDDLTarget = false;
+    bool                   fIsPromiseProxy = false;
     GrProtected            fIsProtected;
 
     // This entry is lazily evaluated so, when the proxy wraps a resource, the resource
diff --git a/src/gpu/GrSurfaceProxyPriv.h b/src/gpu/GrSurfaceProxyPriv.h
index aa98f96..9fe0d1a 100644
--- a/src/gpu/GrSurfaceProxyPriv.h
+++ b/src/gpu/GrSurfaceProxyPriv.h
@@ -42,6 +42,8 @@
 
     void setIsDDLTarget() { fProxy->fIsDDLTarget = true; }
 
+    void setIsPromiseProxy() { fProxy->fIsPromiseProxy = true; }
+
 private:
     explicit GrSurfaceProxyPriv(GrSurfaceProxy* proxy) : fProxy(proxy) {}
     GrSurfaceProxyPriv(const GrSurfaceProxyPriv&) = delete;
diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp
index 750630e..6d85242 100644
--- a/src/gpu/SkGr.cpp
+++ b/src/gpu/SkGr.cpp
@@ -98,13 +98,18 @@
                                                      SkBudgeted budgeted) {
     SkASSERT(baseProxy);
 
+    // We don't allow this for promise proxies i.e. if they need mips they need to give them
+    // to us upfront.
+    if (baseProxy->isPromiseProxy()) {
+        return nullptr;
+    }
     if (!ctx->priv().caps()->isFormatCopyable(baseProxy->backendFormat())) {
-        return {};
+        return nullptr;
     }
     auto copy = GrSurfaceProxy::Copy(ctx, std::move(baseProxy), origin, GrMipmapped::kYes,
                                      SkBackingFit::kExact, budgeted);
     if (!copy) {
-        return {};
+        return nullptr;
     }
     SkASSERT(copy->asTextureProxy());
     return copy;
diff --git a/src/image/SkImage_GpuBase.cpp b/src/image/SkImage_GpuBase.cpp
index d11a617..6a4d23e 100644
--- a/src/image/SkImage_GpuBase.cpp
+++ b/src/image/SkImage_GpuBase.cpp
@@ -35,13 +35,6 @@
 
 //////////////////////////////////////////////////////////////////////////////////////////////////
 
-#if GR_TEST_UTILS
-void SkImage_GpuBase::resetContext(sk_sp<GrImageContext> newContext) {
-    SkASSERT(fContext->priv().matches(newContext.get()));
-    fContext = newContext;
-}
-#endif
-
 bool SkImage_GpuBase::ValidateBackendTexture(const GrCaps* caps, const GrBackendTexture& tex,
                                              GrColorType grCT, SkColorType ct, SkAlphaType at,
                                              sk_sp<SkColorSpace> cs) {
diff --git a/src/image/SkImage_GpuBase.h b/src/image/SkImage_GpuBase.h
index d38f577..05c2123 100644
--- a/src/image/SkImage_GpuBase.h
+++ b/src/image/SkImage_GpuBase.h
@@ -37,10 +37,6 @@
 
     bool onIsValid(GrRecordingContext*) const final;
 
-#if GR_TEST_UTILS
-    void resetContext(sk_sp<GrImageContext> newContext);
-#endif
-
     static bool ValidateBackendTexture(const GrCaps*, const GrBackendTexture& tex,
                                        GrColorType grCT, SkColorType ct, SkAlphaType at,
                                        sk_sp<SkColorSpace> cs);
diff --git a/tests/DeferredDisplayListTest.cpp b/tests/DeferredDisplayListTest.cpp
index a12e1a7..5b8f802 100644
--- a/tests/DeferredDisplayListTest.cpp
+++ b/tests/DeferredDisplayListTest.cpp
@@ -893,11 +893,6 @@
     REPORTER_ASSERT(reporter, !image);
 }
 
-static sk_sp<SkPromiseImageTexture> dummy_fulfill_proc(void*) {
-    SkASSERT(0);
-    return nullptr;
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // Test out the behavior of an invalid DDLRecorder
 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DDLInvalidRecorder, reporter, ctxInfo) {
@@ -923,23 +918,6 @@
         REPORTER_ASSERT(reporter, !c.isValid());
         REPORTER_ASSERT(reporter, !recorder.getCanvas());
         REPORTER_ASSERT(reporter, !recorder.detach());
-
-        GrBackendFormat format = dContext->defaultBackendFormat(kRGBA_8888_SkColorType,
-                                                                GrRenderable::kNo);
-        SkASSERT(format.isValid());
-
-        sk_sp<SkImage> image = recorder.makePromiseTexture(
-                format,
-                32, 32,
-                GrMipmapped::kNo,
-                kTopLeft_GrSurfaceOrigin,
-                kRGBA_8888_SkColorType,
-                kPremul_SkAlphaType,
-                nullptr,
-                dummy_fulfill_proc,
-                /*release proc*/ nullptr,
-                nullptr);
-        REPORTER_ASSERT(reporter, !image);
     }
 }
 
@@ -1093,13 +1071,21 @@
                                                                GrRenderable::kNo);
         SkASSERT(format.isValid());
 
-        sk_sp<SkImage> promiseImage = recorder.makePromiseTexture(
-                format, 32, 32, GrMipmapped::kNo, kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
-                kPremul_SkAlphaType, nullptr, tracking_fulfill_proc, tracking_release_proc,
-                &fulfillInfo);
-
         SkCanvas* canvas = recorder.getCanvas();
 
+        sk_sp<SkImage> promiseImage = SkImage::MakePromiseTexture(
+                                                      canvas->recordingContext()->threadSafeProxy(),
+                                                      format,
+                                                      SkISize::Make(32, 32),
+                                                      GrMipmapped::kNo,
+                                                      kTopLeft_GrSurfaceOrigin,
+                                                      kRGBA_8888_SkColorType,
+                                                      kPremul_SkAlphaType,
+                                                      nullptr,
+                                                      tracking_fulfill_proc,
+                                                      tracking_release_proc,
+                                                      &fulfillInfo);
+
         canvas->clear(SK_ColorRED);
         canvas->drawImage(promiseImage, 0, 0);
         ddl = recorder.detach();
@@ -1185,6 +1171,12 @@
 }
 
 #ifdef SK_GL
+
+static sk_sp<SkPromiseImageTexture> dummy_fulfill_proc(void*) {
+    SkASSERT(0);
+    return nullptr;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Check that the texture-specific flags (i.e., for external & rectangle textures) work
 // for promise images. As such, this is a GL-only test.
@@ -1203,17 +1195,18 @@
         for (auto mipMapped : { GrMipmapped::kNo, GrMipmapped::kYes }) {
             GrBackendFormat format = GrBackendFormat::MakeGL(GR_GL_RGBA8, target);
 
-            sk_sp<SkImage> image = recorder.makePromiseTexture(
+            sk_sp<SkImage> image = SkImage::MakePromiseTexture(
+                    recorder.getCanvas()->recordingContext()->threadSafeProxy(),
                     format,
-                    32, 32,
+                    SkISize::Make(32, 32),
                     mipMapped,
                     kTopLeft_GrSurfaceOrigin,
                     kRGBA_8888_SkColorType,
                     kPremul_SkAlphaType,
-                    nullptr,
+                    /*color space*/nullptr,
                     dummy_fulfill_proc,
                     /*release proc*/ nullptr,
-                    nullptr);
+                    /*context*/nullptr);
             if (GR_GL_TEXTURE_2D != target && mipMapped == GrMipmapped::kYes) {
                 REPORTER_ASSERT(reporter, !image);
                 continue;
diff --git a/tools/DDLPromiseImageHelper.cpp b/tools/DDLPromiseImageHelper.cpp
index 66af4cb..1a4119c 100644
--- a/tools/DDLPromiseImageHelper.cpp
+++ b/tools/DDLPromiseImageHelper.cpp
@@ -278,13 +278,13 @@
 }
 
 sk_sp<SkPicture> DDLPromiseImageHelper::reinflateSKP(
-                                                   SkDeferredDisplayListRecorder* recorder,
+                                                   sk_sp<GrContextThreadSafeProxy> threadSafeProxy,
                                                    SkData* compressedPictureData,
                                                    SkTArray<sk_sp<SkImage>>* promiseImages) const {
-    PerRecorderContext perRecorderContext { recorder, this, promiseImages };
+    DeserialImageProcContext procContext { std::move(threadSafeProxy), this, promiseImages };
 
     SkDeserialProcs procs;
-    procs.fImageCtx = (void*) &perRecorderContext;
+    procs.fImageCtx = (void*) &procContext;
     procs.fImageProc = CreatePromiseImages;
 
     return SkPicture::MakeFromData(compressedPictureData, &procs);
@@ -295,9 +295,8 @@
 // promise images referring to the same GrBackendTexture.
 sk_sp<SkImage> DDLPromiseImageHelper::CreatePromiseImages(const void* rawData,
                                                           size_t length, void* ctxIn) {
-    PerRecorderContext* perRecorderContext = static_cast<PerRecorderContext*>(ctxIn);
-    const DDLPromiseImageHelper* helper = perRecorderContext->fHelper;
-    SkDeferredDisplayListRecorder* recorder = perRecorderContext->fRecorder;
+    DeserialImageProcContext* procContext = static_cast<DeserialImageProcContext*>(ctxIn);
+    const DDLPromiseImageHelper* helper = procContext->fHelper;
 
     SkASSERT(length == sizeof(int));
 
@@ -331,13 +330,13 @@
                                                      backendFormats,
                                                      GrMipmapped::kNo,
                                                      kTopLeft_GrSurfaceOrigin);
-
-        image = recorder->makeYUVAPromiseTexture(
-                yuvaBackendTextures,
-                curImage.refOverallColorSpace(),
-                PromiseImageCallbackContext::PromiseImageFulfillProc,
-                PromiseImageCallbackContext::PromiseImageReleaseProc,
-                contexts);
+        image = SkImage::MakePromiseYUVATexture(
+                                            procContext->fThreadSafeProxy,
+                                            yuvaBackendTextures,
+                                            curImage.refOverallColorSpace(),
+                                            PromiseImageCallbackContext::PromiseImageFulfillProc,
+                                            PromiseImageCallbackContext::PromiseImageReleaseProc,
+                                            contexts);
         if (!image) {
             return nullptr;
         }
@@ -349,22 +348,20 @@
         const GrBackendFormat& backendFormat = curImage.backendFormat(0);
         SkASSERT(backendFormat.isValid());
 
-        // Each DDL recorder gets its own ref on the promise callback context for the
-        // promise images it creates.
-        image = recorder->makePromiseTexture(backendFormat,
-                                             curImage.overallWidth(),
-                                             curImage.overallHeight(),
-                                             curImage.mipMapped(0),
-                                             GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
-                                             curImage.overallColorType(),
-                                             curImage.overallAlphaType(),
-                                             curImage.refOverallColorSpace(),
-                                             PromiseImageCallbackContext::PromiseImageFulfillProc,
-                                             PromiseImageCallbackContext::PromiseImageReleaseProc,
-                                             (void*)curImage.refCallbackContext(0).release());
+        image = SkImage::MakePromiseTexture(procContext->fThreadSafeProxy,
+                                            backendFormat,
+                                            curImage.overallDimensions(),
+                                            curImage.mipMapped(0),
+                                            GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
+                                            curImage.overallColorType(),
+                                            curImage.overallAlphaType(),
+                                            curImage.refOverallColorSpace(),
+                                            PromiseImageCallbackContext::PromiseImageFulfillProc,
+                                            PromiseImageCallbackContext::PromiseImageReleaseProc,
+                                            (void*)curImage.refCallbackContext(0).release());
         curImage.callbackContext(0)->wasAddedToImage();
     }
-    perRecorderContext->fPromiseImages->push_back(image);
+    procContext->fPromiseImages->push_back(image);
     SkASSERT(image);
     return image;
 }
diff --git a/tools/DDLPromiseImageHelper.h b/tools/DDLPromiseImageHelper.h
index cd65a7f..bdeaa75 100644
--- a/tools/DDLPromiseImageHelper.h
+++ b/tools/DDLPromiseImageHelper.h
@@ -119,7 +119,7 @@
     void deleteAllFromGPU(SkTaskGroup*, GrDirectContext*);
 
     // reinflate a deflated SKP, replacing all the indices with promise images.
-    sk_sp<SkPicture> reinflateSKP(SkDeferredDisplayListRecorder*,
+    sk_sp<SkPicture> reinflateSKP(sk_sp<GrContextThreadSafeProxy>,
                                   SkData* compressedPicture,
                                   SkTArray<sk_sp<SkImage>>* promiseImages) const;
 
@@ -140,8 +140,7 @@
         uint32_t originalUniqueID() const { return fOriginalUniqueID; }
         bool isYUV() const { return fYUVAPixmaps.isValid(); }
 
-        int overallWidth() const { return fImageInfo.width(); }
-        int overallHeight() const { return fImageInfo.height(); }
+        SkISize overallDimensions() const { return fImageInfo.dimensions(); }
         SkColorType overallColorType() const { return fImageInfo.colorType(); }
         SkAlphaType overallAlphaType() const { return fImageInfo.alphaType(); }
         sk_sp<SkColorSpace> refOverallColorSpace() const { return fImageInfo.refColorSpace(); }
@@ -212,12 +211,10 @@
         sk_sp<PromiseImageCallbackContext> fCallbackContexts[SkYUVAInfo::kMaxPlanes];
     };
 
-    // This stack-based context allows each thread to re-inflate the image indices into
-    // promise images while still using the same GrBackendTexture.
-    struct PerRecorderContext {
-        SkDeferredDisplayListRecorder* fRecorder;
-        const DDLPromiseImageHelper*   fHelper;
-        SkTArray<sk_sp<SkImage>>*      fPromiseImages;
+    struct DeserialImageProcContext {
+        sk_sp<GrContextThreadSafeProxy> fThreadSafeProxy;
+        const DDLPromiseImageHelper*    fHelper;
+        SkTArray<sk_sp<SkImage>>*       fPromiseImages;
     };
 
     static void CreateBETexturesForPromiseImage(GrDirectContext*, PromiseImageInfo*);
diff --git a/tools/DDLTileHelper.cpp b/tools/DDLTileHelper.cpp
index 1ebf6f4..7122bc1 100644
--- a/tools/DDLTileHelper.cpp
+++ b/tools/DDLTileHelper.cpp
@@ -43,66 +43,25 @@
 DDLTileHelper::TileData::TileData() {}
 DDLTileHelper::TileData::~TileData() {}
 
-void DDLTileHelper::TileData::createTileSpecificSKP(SkData* compressedPictureData,
-                                                    const DDLPromiseImageHelper& helper) {
-    if (!this->initialized()) {
-        return;
-    }
-
-    SkASSERT(!fReconstitutedPicture);
-
-    auto recordingChar = fPlaybackChar.createResized(fClip.width(), fClip.height());
-    SkASSERT(recordingChar.isValid());
-
-    // This is bending the DDLRecorder contract! The promise images in the SKP should be
-    // created by the same recorder used to create the matching DDL.
-    SkDeferredDisplayListRecorder recorder(recordingChar);
-
-    fReconstitutedPicture = helper.reinflateSKP(&recorder, compressedPictureData, &fPromiseImages);
-
-    auto ddl = recorder.detach();
-    if (ddl && ddl->priv().numRenderTasks()) {
-        // TODO: remove this once skbug.com/8424 is fixed. If the DDL resulting from the
-        // reinflation of the SKPs contains opsTasks that means some image subset operation
-        // created a draw.
-        fReconstitutedPicture.reset();
-    }
-}
-
-void DDLTileHelper::TileData::createDDL() {
-    if (!this->initialized()) {
-        return;
-    }
-
-    SkASSERT(!fDisplayList && fReconstitutedPicture);
+void DDLTileHelper::TileData::createDDL(const SkPicture* picture) {
+    SkASSERT(!fDisplayList && picture);
 
     auto recordingChar = fPlaybackChar.createResized(fClip.width(), fClip.height());
     SkASSERT(recordingChar.isValid());
 
     SkDeferredDisplayListRecorder recorder(recordingChar);
 
-    // DDL TODO: the DDLRecorder's GrContext isn't initialized until getCanvas is called.
+    // DDL TODO: the DDLRecorder's rContext isn't initialized until getCanvas is called.
     // Maybe set it up in the ctor?
     SkCanvas* recordingCanvas = recorder.getCanvas();
 
-    // Because we cheated in createTileSpecificSKP and used the wrong DDLRecorder, the GrContext's
-    // stored in fReconstitutedPicture's promise images are incorrect. Patch them with the correct
-    // one now.
-    for (int i = 0; i < fPromiseImages.count(); ++i) {
-        if (fPromiseImages[i]->isTextureBacked()) {
-            auto rContext = recordingCanvas->recordingContext();
-            SkImage_GpuBase* gpuImage = (SkImage_GpuBase*) fPromiseImages[i].get();
-            gpuImage->resetContext(sk_ref_sp(rContext));
-        }
-    }
-
     // We always record the DDL in the (0,0) .. (clipWidth, clipHeight) coordinates
     recordingCanvas->clipRect(SkRect::MakeWH(fClip.width(), fClip.height()));
     recordingCanvas->translate(-fClip.fLeft, -fClip.fTop);
 
     // Note: in this use case we only render a picture to the deferred canvas
     // but, more generally, clients will use arbitrary draw calls.
-    recordingCanvas->drawPicture(fReconstitutedPicture);
+    recordingCanvas->drawPicture(picture);
 
     fDisplayList = recorder.detach();
 }
@@ -120,7 +79,8 @@
             continue;
         }
 
-        sk_sp<SkImage> promiseImage = tile->makePromiseImageForDst(&recorder);
+        sk_sp<SkImage> promiseImage = tile->makePromiseImageForDst(
+                                           recordingCanvas->recordingContext()->threadSafeProxy());
 
         SkRect dstRect = SkRect::Make(tile->clipRect());
         SkIRect srcRect = tile->clipRect();
@@ -150,7 +110,7 @@
     }
 }
 
-sk_sp<SkSurface> DDLTileHelper::TileData::makeWrappedTileDest(GrRecordingContext* context) {
+sk_sp<SkSurface> DDLTileHelper::TileData::makeWrappedTileDest(GrRecordingContext* rContext) {
     SkASSERT(fCallbackContext && fCallbackContext->promiseImageTexture());
 
     auto promiseImageTexture = fCallbackContext->promiseImageTexture();
@@ -161,7 +121,7 @@
     // Here we are, unfortunately, aliasing the backend texture held by the SkPromiseImageTexture.
     // Both the tile's destination surface and the promise image used to draw the tile will be
     // backed by the same backendTexture - unbeknownst to Ganesh.
-    return SkSurface::MakeFromBackendTexture(context,
+    return SkSurface::MakeFromBackendTexture(rContext,
                                              promiseImageTexture->backendTexture(),
                                              fPlaybackChar.origin(),
                                              fPlaybackChar.sampleCount(),
@@ -170,10 +130,11 @@
                                              &fPlaybackChar.surfaceProps());
 }
 
-void DDLTileHelper::TileData::drawSKPDirectly(GrRecordingContext* context) {
-    SkASSERT(!fDisplayList && !fTileSurface && fReconstitutedPicture);
+void DDLTileHelper::TileData::drawSKPDirectly(GrDirectContext* dContext,
+                                              const SkPicture* picture) {
+    SkASSERT(!fDisplayList && !fTileSurface && picture);
 
-    fTileSurface = this->makeWrappedTileDest(context);
+    fTileSurface = this->makeWrappedTileDest(dContext);
     if (fTileSurface) {
         SkCanvas* tileCanvas = fTileSurface->getCanvas();
 
@@ -181,7 +142,7 @@
         tileCanvas->clipRect(SkRect::MakeWH(fClip.width(), fClip.height()));
         tileCanvas->translate(-fClip.fLeft, -fClip.fTop);
 
-        tileCanvas->drawPicture(fReconstitutedPicture);
+        tileCanvas->drawPicture(picture);
 
         // We can't snap an image here bc, since we're using wrapped backend textures for the
         // surfaces, that would incur a copy.
@@ -208,22 +169,22 @@
 }
 
 sk_sp<SkImage> DDLTileHelper::TileData::makePromiseImageForDst(
-                                                        SkDeferredDisplayListRecorder* recorder) {
+                                                sk_sp<GrContextThreadSafeProxy> threadSafeProxy) {
     SkASSERT(fCallbackContext);
 
     // The promise image gets a ref on the promise callback context
     sk_sp<SkImage> promiseImage =
-            recorder->makePromiseTexture(fCallbackContext->backendFormat(),
-                                         this->paddedRectSize().width(),
-                                         this->paddedRectSize().height(),
-                                         GrMipmapped::kNo,
-                                         GrSurfaceOrigin::kBottomLeft_GrSurfaceOrigin,
-                                         fPlaybackChar.colorType(),
-                                         kPremul_SkAlphaType,
-                                         fPlaybackChar.refColorSpace(),
-                                         PromiseImageCallbackContext::PromiseImageFulfillProc,
-                                         PromiseImageCallbackContext::PromiseImageReleaseProc,
-                                         (void*)this->refCallbackContext().release());
+                SkImage::MakePromiseTexture(std::move(threadSafeProxy),
+                                            fCallbackContext->backendFormat(),
+                                            this->paddedRectSize(),
+                                            GrMipmapped::kNo,
+                                            GrSurfaceOrigin::kBottomLeft_GrSurfaceOrigin,
+                                            fPlaybackChar.colorType(),
+                                            kPremul_SkAlphaType,
+                                            fPlaybackChar.refColorSpace(),
+                                            PromiseImageCallbackContext::PromiseImageFulfillProc,
+                                            PromiseImageCallbackContext::PromiseImageReleaseProc,
+                                            (void*)this->refCallbackContext().release());
     fCallbackContext->wasAddedToImage();
 
     return promiseImage;
@@ -297,22 +258,26 @@
     }
 }
 
-void DDLTileHelper::createSKPPerTile(SkData* compressedPictureData,
-                                     const DDLPromiseImageHelper& helper) {
-    for (int i = 0; i < this->numTiles(); ++i) {
-        fTiles[i].createTileSpecificSKP(compressedPictureData, helper);
-    }
+void DDLTileHelper::createSKP(sk_sp<GrContextThreadSafeProxy> threadSafeProxy,
+                              SkData* compressedPictureData,
+                              const DDLPromiseImageHelper& helper) {
+    SkASSERT(!fReconstitutedPicture);
+
+    fReconstitutedPicture = helper.reinflateSKP(std::move(threadSafeProxy), compressedPictureData,
+                                                &fPromiseImages);
 }
 
 void DDLTileHelper::createDDLsInParallel() {
 #if 1
-    SkTaskGroup().batch(this->numTiles(), [&](int i) { fTiles[i].createDDL(); });
+    SkTaskGroup().batch(this->numTiles(), [&](int i) {
+        fTiles[i].createDDL(fReconstitutedPicture.get());
+    });
     SkTaskGroup().add([this]{ this->createComposeDDL(); });
     SkTaskGroup().wait();
 #else
     // Use this code path to debug w/o threads
     for (int i = 0; i < this->numTiles(); ++i) {
-        fTiles[i].createDDL();
+        fTiles[i].createDDL(fReconstitutedPicture.get());
     }
     this->createComposeDDL();
 #endif
@@ -349,8 +314,8 @@
         //    schedule gpu-thread processing of the DDL
         // Note: a finer grained approach would be add a scheduling task which would evaluate
         //       which DDLs were ready to be rendered based on their prerequisites
-        recordingTaskGroup->add([tile, gpuTaskGroup, dContext]() {
-                                    tile->createDDL();
+        recordingTaskGroup->add([this, tile, gpuTaskGroup, dContext]() {
+                                    tile->createDDL(fReconstitutedPicture.get());
 
                                     gpuTaskGroup->add([dContext, tile]() {
                                         do_gpu_stuff(dContext, tile);
@@ -372,7 +337,7 @@
 // Only called from skpbench
 void DDLTileHelper::interleaveDDLCreationAndDraw(GrDirectContext* direct) {
     for (int i = 0; i < this->numTiles(); ++i) {
-        fTiles[i].createDDL();
+        fTiles[i].createDDL(fReconstitutedPicture.get());
         fTiles[i].draw(direct);
     }
 }
@@ -380,7 +345,7 @@
 // Only called from skpbench
 void DDLTileHelper::drawAllTilesDirectly(GrDirectContext* context) {
     for (int i = 0; i < this->numTiles(); ++i) {
-        fTiles[i].drawSKPDirectly(context);
+        fTiles[i].drawSKPDirectly(context, fReconstitutedPicture.get());
     }
 }
 
diff --git a/tools/DDLTileHelper.h b/tools/DDLTileHelper.h
index dee5eeb..8d893be 100644
--- a/tools/DDLTileHelper.h
+++ b/tools/DDLTileHelper.h
@@ -12,6 +12,7 @@
 #include "include/core/SkRect.h"
 #include "include/core/SkRefCnt.h"
 #include "include/core/SkSurfaceCharacterization.h"
+#include "src/core/SkSpan.h"
 
 class DDLPromiseImageHelper;
 class PromiseImageCallbackContext;
@@ -40,13 +41,8 @@
                   const SkIRect& clip,
                   const SkIRect& paddingOutsets);
 
-        // Convert the compressedPictureData into an SkPicture replacing each image-index
-        // with a promise image.
-        void createTileSpecificSKP(SkData* compressedPictureData,
-                                   const DDLPromiseImageHelper& helper);
-
         // Create the DDL for this tile (i.e., fill in 'fDisplayList').
-        void createDDL();
+        void createDDL(const SkPicture*);
 
         void dropDDL() { fDisplayList.reset(); }
 
@@ -56,7 +52,7 @@
         // Just draw the re-inflated per-tile SKP directly into this tile w/o going through a DDL
         // first. This is used for determining the overhead of using DDLs (i.e., it replaces
         // a 'createDDL' and 'draw' pair.
-        void drawSKPDirectly(GrRecordingContext*);
+        void drawSKPDirectly(GrDirectContext*, const SkPicture*);
 
         // Replay the recorded DDL into the tile surface - filling in 'fBackendTexture'.
         void draw(GrDirectContext*);
@@ -73,7 +69,7 @@
 
         SkDeferredDisplayList* ddl() { return fDisplayList.get(); }
 
-        sk_sp<SkImage> makePromiseImageForDst(SkDeferredDisplayListRecorder*);
+        sk_sp<SkImage> makePromiseImageForDst(sk_sp<GrContextThreadSafeProxy>);
         void dropCallbackContext() { fCallbackContext.reset(); }
 
         static void CreateBackendTexture(GrDirectContext*, TileData*);
@@ -99,9 +95,6 @@
         // TODO: fix the ref-order so we don't need 'fTileSurface' here
         sk_sp<SkSurface>              fTileSurface;
 
-        sk_sp<SkPicture>              fReconstitutedPicture;
-        SkTArray<sk_sp<SkImage>>      fPromiseImages;    // All the promise images in the
-                                                     // reconstituted picture
         sk_sp<SkDeferredDisplayList>  fDisplayList;
     };
 
@@ -111,7 +104,11 @@
                   int numDivisions,
                   bool addRandomPaddingToDst);
 
-    void createSKPPerTile(SkData* compressedPictureData, const DDLPromiseImageHelper&);
+    // TODO: Move this to PromiseImageHelper and have one method that does all the work and
+    // returns the shared SkP.
+    void createSKP(sk_sp<GrContextThreadSafeProxy>,
+                   SkData* compressedPictureData,
+                   const DDLPromiseImageHelper&);
 
     void kickOffThreadedWork(SkTaskGroup* recordingTaskGroup,
                              SkTaskGroup* gpuTaskGroup,
@@ -151,6 +148,9 @@
     sk_sp<SkDeferredDisplayList>           fComposeDDL;
 
     const SkSurfaceCharacterization        fDstCharacterization;
+    sk_sp<SkPicture>                       fReconstitutedPicture;
+    SkTArray<sk_sp<SkImage>>               fPromiseImages; // All the promise images in the
+                                                           // reconstituted picture
 };
 
 #endif
diff --git a/tools/skpbench/skpbench.cpp b/tools/skpbench/skpbench.cpp
index 34ab43b..49fbb92 100644
--- a/tools/skpbench/skpbench.cpp
+++ b/tools/skpbench/skpbench.cpp
@@ -276,7 +276,7 @@
 
     tiles.createBackendTextures(nullptr, context);
 
-    tiles.createSKPPerTile(compressedPictureData.get(), promiseImageHelper);
+    tiles.createSKP(context->threadSafeProxy(), compressedPictureData.get(), promiseImageHelper);
 
     // In comparable modes, there is no GPU thread. The following pointers are all null.
     // Otherwise, we transfer testContext onto the GPU thread until after the bench.