Move all YUVA image creation in GMs into sk_gpu_test::LazyYUVImage.

LazyYUVImage now supports making images from a generator and from
textures. It uses ManagedBackendTexture to manage texture plane
lifetime via ref-counting.

Adds some supporting utility functions to SkYUVAInfo and
SkYUVAPixmaps.

Eases transition of forthcoming MakeFromYUVATextures API change.

Bug: skia:10632

Change-Id: I8cfd747c27076d1627da6ea8a169e554a96049e0
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/326720
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/gm/asyncrescaleandread.cpp b/gm/asyncrescaleandread.cpp
index d6f151b..b4d55c9 100644
--- a/gm/asyncrescaleandread.cpp
+++ b/gm/asyncrescaleandread.cpp
@@ -11,14 +11,15 @@
 #include "include/core/SkPaint.h"
 #include "include/core/SkRect.h"
 #include "include/core/SkSurface.h"
-#include "include/core/SkYUVAIndex.h"
+#include "include/core/SkYUVAInfo.h"
+#include "include/core/SkYUVAPixmaps.h"
 #include "include/gpu/GrDirectContext.h"
 #include "include/gpu/GrRecordingContext.h"
 #include "src/core/SkAutoPixmapStorage.h"
-#include "src/core/SkConvertPixels.h"
 #include "src/core/SkScopeExit.h"
 #include "tools/Resources.h"
 #include "tools/ToolUtils.h"
+#include "tools/gpu/YUVUtils.h"
 
 namespace {
 struct AsyncContext {
@@ -93,33 +94,17 @@
     if (!asyncContext.fResult) {
         return nullptr;
     }
-    GrBackendTexture backendTextures[3];
-
-    SkPixmap yPM(yII, asyncContext.fResult->data(0), asyncContext.fResult->rowBytes(0));
-    SkPixmap uPM(uvII, asyncContext.fResult->data(1), asyncContext.fResult->rowBytes(1));
-    SkPixmap vPM(uvII, asyncContext.fResult->data(2), asyncContext.fResult->rowBytes(2));
-
-    backendTextures[0] = direct->createBackendTexture(yPM, GrRenderable::kNo, GrProtected::kNo);
-    backendTextures[1] = direct->createBackendTexture(uPM, GrRenderable::kNo, GrProtected::kNo);
-    backendTextures[2] = direct->createBackendTexture(vPM, GrRenderable::kNo, GrProtected::kNo);
-
-    SkYUVAIndex indices[4] = {
-        { 0, SkColorChannel::kR},
-        { 1, SkColorChannel::kR},
-        { 2, SkColorChannel::kR},
-        {-1, SkColorChannel::kR}
+    SkYUVAInfo yuvaInfo(size, SkYUVAInfo::PlanarConfig::kY_U_V_420, yuvCS);
+    SkPixmap yuvPMs[] = {
+            {yII,  asyncContext.fResult->data(0), asyncContext.fResult->rowBytes(0)},
+            {uvII, asyncContext.fResult->data(1), asyncContext.fResult->rowBytes(1)},
+            {uvII, asyncContext.fResult->data(2), asyncContext.fResult->rowBytes(2)}
     };
-
-    *cleanup = {[direct, backendTextures] {
-        direct->flush();
-        direct->submit(true);
-        direct->deleteBackendTexture(backendTextures[0]);
-        direct->deleteBackendTexture(backendTextures[1]);
-        direct->deleteBackendTexture(backendTextures[2]);
-    }};
-
-    return SkImage::MakeFromYUVATextures(direct, yuvCS, backendTextures, indices, size,
-                                         kTopLeft_GrSurfaceOrigin, SkColorSpace::MakeSRGB());
+    auto pixmaps = SkYUVAPixmaps::FromExternalPixmaps(yuvaInfo, yuvPMs);
+    SkASSERT(pixmaps.isValid());
+    auto lazyYUVImage = sk_gpu_test::LazyYUVImage::Make(pixmaps);
+    SkASSERT(lazyYUVImage);
+    return lazyYUVImage->refImage(direct, sk_gpu_test::LazyYUVImage::Type::kFromTextures);
 }
 
 // Draws a grid of rescales. The columns are none, low, and high filter quality. The rows are
diff --git a/gm/compositor_quads.cpp b/gm/compositor_quads.cpp
index 934afba..f3a684d 100644
--- a/gm/compositor_quads.cpp
+++ b/gm/compositor_quads.cpp
@@ -897,7 +897,8 @@
     int drawTiles(SkCanvas* canvas) override {
         // Refresh the SkImage at the start, so that it's not attempted for every set entry
         if (fYUVData) {
-            fImage = fYUVData->refImage(canvas->recordingContext());
+            fImage = fYUVData->refImage(canvas->recordingContext(),
+                                        sk_gpu_test::LazyYUVImage::Type::kFromPixmaps);
             if (!fImage) {
                 return 0;
             }
diff --git a/gm/ducky_yuv_blend.cpp b/gm/ducky_yuv_blend.cpp
index af45b89..bd39353 100644
--- a/gm/ducky_yuv_blend.cpp
+++ b/gm/ducky_yuv_blend.cpp
@@ -33,7 +33,7 @@
         auto lazyYUV = sk_gpu_test::LazyYUVImage::Make(GetResourceAsData("images/ducky.jpg"),
                                                        GrMipmapped::kYes);
         if (lazyYUV) {
-            duckyFG[1] = lazyYUV->refImage(rContext);
+            duckyFG[1] = lazyYUV->refImage(rContext, sk_gpu_test::LazyYUVImage::Type::kFromPixmaps);
         }
         if (!duckyFG[1]) {
             return skiagm::DrawResult::kFail;
diff --git a/gm/imagefromyuvtextures.cpp b/gm/imagefromyuvtextures.cpp
index ede9586..2a50973 100644
--- a/gm/imagefromyuvtextures.cpp
+++ b/gm/imagefromyuvtextures.cpp
@@ -32,8 +32,6 @@
 #include "tools/Resources.h"
 #include "tools/gpu/YUVUtils.h"
 
-using sk_gpu_test::YUVABackendReleaseContext;
-
 class GrRenderTargetContext;
 
 namespace skiagm {
@@ -50,128 +48,90 @@
 
     SkISize onISize() override { return {1420, 610}; }
 
-    static SkBitmap CreateBmpAndPlanes(const char* name, SkBitmap yuvaBmps[4]) {
+    static std::unique_ptr<sk_gpu_test::LazyYUVImage> CreatePlanes(const char* name) {
         SkBitmap bmp;
         if (!GetResourceAsBitmap(name, &bmp)) {
             return {};
         }
-        auto ii = SkImageInfo::Make(bmp.dimensions(), kRGBA_8888_SkColorType, kPremul_SkAlphaType);
-
-        SkBitmap rgbaBmp;
-        rgbaBmp.allocPixels(ii);
-        bmp.readPixels(rgbaBmp.pixmap(), 0, 0);
-
-        SkImageInfo yaInfo = SkImageInfo::Make(rgbaBmp.dimensions(), kAlpha_8_SkColorType,
-                                               kUnpremul_SkAlphaType);
-        yuvaBmps[0].allocPixels(yaInfo);
-        SkISize uvSize = {rgbaBmp.width()/2, rgbaBmp.height()/2};
-        SkImageInfo uvInfo = SkImageInfo::Make(uvSize, kAlpha_8_SkColorType, kUnpremul_SkAlphaType);
-        yuvaBmps[1].allocPixels(uvInfo);
-        yuvaBmps[2].allocPixels(uvInfo);
-        yuvaBmps[3].allocPixels(yaInfo);
+        if (bmp.colorType() != kRGBA_8888_SkColorType) {
+            auto info = bmp.info().makeColorType(kRGBA_8888_SkColorType);
+            SkBitmap copy;
+            copy.allocPixels(info);
+            SkAssertResult(bmp.readPixels(copy.pixmap()));
+            bmp = copy;
+        }
+        SkYUVAPixmapInfo pixmapInfo({bmp.dimensions(),
+                                     SkYUVAInfo::PlanarConfig::kY_U_V_A_4204,
+                                     kJPEG_Full_SkYUVColorSpace},
+                                    SkYUVAPixmapInfo::DataType::kUnorm8,
+                                    nullptr);
+        auto pixmaps = SkYUVAPixmaps::Allocate(pixmapInfo);
 
         unsigned char* yuvPixels[] = {
-                static_cast<unsigned char*>(yuvaBmps[0].getPixels()),
-                static_cast<unsigned char*>(yuvaBmps[1].getPixels()),
-                static_cast<unsigned char*>(yuvaBmps[2].getPixels()),
-                static_cast<unsigned char*>(yuvaBmps[3].getPixels()),
+                static_cast<unsigned char*>(pixmaps.planes()[0].writable_addr()),
+                static_cast<unsigned char*>(pixmaps.planes()[1].writable_addr()),
+                static_cast<unsigned char*>(pixmaps.planes()[2].writable_addr()),
+                static_cast<unsigned char*>(pixmaps.planes()[3].writable_addr()),
         };
 
         float m[20];
-        SkColorMatrix_RGB2YUV(kJPEG_SkYUVColorSpace, m);
+        SkColorMatrix_RGB2YUV(pixmaps.yuvaInfo().yuvColorSpace(), m);
         // Here we encode using the kJPEG_SkYUVColorSpace (i.e., full-swing Rec 601) even though
         // we will draw it with all the supported yuv color spaces when converted back to RGB
-        for (int j = 0; j < yaInfo.height(); ++j) {
-            for (int i = 0; i < yaInfo.width(); ++i) {
-                auto rgba = *rgbaBmp.getAddr32(i, j);
+        for (int j = 0; j < pixmaps.planes()[0].height(); ++j) {
+            for (int i = 0; i < pixmaps.planes()[0].width(); ++i) {
+                auto rgba = *bmp.getAddr32(i, j);
                 auto r = (rgba & 0x000000ff) >>  0;
                 auto g = (rgba & 0x0000ff00) >>  8;
                 auto b = (rgba & 0x00ff0000) >> 16;
                 auto a = (rgba & 0xff000000) >> 24;
-                yuvPixels[0][j*yaInfo.width() + i] = SkToU8(
+                yuvPixels[0][j*pixmaps.planes()[0].width() + i] = SkToU8(
                         sk_float_round2int(m[0]*r + m[1]*g + m[2]*b + m[3]*a + 255*m[4]));
-                yuvPixels[3][j*yaInfo.width() + i] = SkToU8(sk_float_round2int(
+                yuvPixels[3][j*pixmaps.planes()[0].width() + i] = SkToU8(sk_float_round2int(
                         m[15]*r + m[16]*g + m[17]*b + m[18]*a + 255*m[19]));
             }
         }
-        for (int j = 0; j < uvInfo.height(); ++j) {
-            for (int i = 0; i < uvInfo.width(); ++i) {
+        for (int j = 0; j < pixmaps.planes()[1].height(); ++j) {
+            for (int i = 0; i < pixmaps.planes()[1].width(); ++i) {
                 // Average together 4 pixels of RGB.
                 int rgba[] = {0, 0, 0, 0};
-                for (int y = 0; y < 2; ++y) {
-                    for (int x = 0; x < 2; ++x) {
-                        auto src = *rgbaBmp.getAddr32(2 * i + x, 2 * j + y);
+                int denom = 0;
+                int ylimit = std::min(2*j + 2, pixmaps.planes()[0].height());
+                int xlimit = std::min(2*i + 2, pixmaps.planes()[0].width());
+                for (int y = 2*j; y < ylimit; ++y) {
+                    for (int x = 2*i; x < xlimit; ++x) {
+                        auto src = *bmp.getAddr32(x, y);
                         rgba[0] += (src & 0x000000ff) >> 0;
                         rgba[1] += (src & 0x0000ff00) >> 8;
                         rgba[2] += (src & 0x00ff0000) >> 16;
                         rgba[3] += (src & 0xff000000) >> 24;
+                        ++denom;
                     }
                 }
                 for (int c = 0; c < 4; ++c) {
-                    rgba[c] /= 4;
+                    rgba[c] /= denom;
                 }
-                int uvIndex = j*uvInfo.width() + i;
+                int uvIndex = j*pixmaps.planes()[1].width() + i;
                 yuvPixels[1][uvIndex] = SkToU8(sk_float_round2int(
                         m[5]*rgba[0] + m[6]*rgba[1] + m[7]*rgba[2] + m[8]*rgba[3] + 255*m[9]));
                 yuvPixels[2][uvIndex] = SkToU8(sk_float_round2int(
                         m[10]*rgba[0] + m[11]*rgba[1] + m[12]*rgba[2] + m[13]*rgba[3] + 255*m[14]));
             }
         }
-        return rgbaBmp;
-    }
-
-    static bool CreateYUVBackendTextures(GrDirectContext* context, SkBitmap bmps[4],
-                                         SkYUVAIndex indices[4],
-                                         YUVABackendReleaseContext* beContext) {
-        for (int i = 0; i < 4; ++i) {
-            GrBackendTexture tmp = context->createBackendTexture(
-                                        bmps[i].pixmap(), GrRenderable::kNo, GrProtected::kNo,
-                                        YUVABackendReleaseContext::CreationCompleteProc(i),
-                                        beContext);
-            if (!tmp.isValid()) {
-                return false;
-            }
-
-            beContext->set(i, tmp);
-        }
-
-        for (int i = 0; i < 4; ++i) {
-            auto chanMask = beContext->beTexture(i).getBackendFormat().channelMask();
-            // We expect the single channel bitmaps to produce single channel textures.
-            SkASSERT(chanMask && SkIsPow2(chanMask));
-            if (chanMask & kGray_SkColorChannelFlag) {
-                indices[i].fChannel = SkColorChannel::kR;
-            } else {
-                indices[i].fChannel = static_cast<SkColorChannel>(31 - SkCLZ(chanMask));
-            }
-            indices[i].fIndex = i;
-        }
-
-        return true;
+        return sk_gpu_test::LazyYUVImage::Make(std::move(pixmaps));
     }
 
     sk_sp<SkImage> makeYUVAImage(GrDirectContext* context) {
-        auto releaseContext = new YUVABackendReleaseContext(context);
-        SkYUVAIndex indices[4];
-
-        if (!CreateYUVBackendTextures(context, fYUVABmps, indices, releaseContext)) {
-            YUVABackendReleaseContext::Unwind(context, releaseContext, false);
-            return nullptr;
-        }
-
-        return SkImage::MakeFromYUVATextures(context,
-                                             kJPEG_SkYUVColorSpace,
-                                             releaseContext->beTextures(),
-                                             indices,
-                                             fRGBABmp.dimensions(),
-                                             kTopLeft_GrSurfaceOrigin,
-                                             nullptr,
-                                             YUVABackendReleaseContext::Release,
-                                             releaseContext);
+        return fLazyYUVImage->refImage(context, sk_gpu_test::LazyYUVImage::Type::kFromTextures);
     }
 
     sk_sp<SkImage> createReferenceImage(GrDirectContext* dContext) {
-        auto resultInfo = SkImageInfo::Make(fRGBABmp.dimensions(),
+        auto planarImage = this->makeYUVAImage(dContext);
+        if (!planarImage) {
+            return nullptr;
+        }
+
+        auto resultInfo = SkImageInfo::Make(fLazyYUVImage->dimensions(),
                                             kRGBA_8888_SkColorType,
                                             kPremul_SkAlphaType);
         auto resultSurface = SkSurface::MakeRenderTarget(dContext,
@@ -184,27 +144,7 @@
             return nullptr;
         }
 
-        auto planeReleaseContext = new YUVABackendReleaseContext(dContext);
-        SkYUVAIndex indices[4];
-
-        if (!CreateYUVBackendTextures(dContext, fYUVABmps, indices, planeReleaseContext)) {
-            YUVABackendReleaseContext::Unwind(dContext, planeReleaseContext, false);
-            return nullptr;
-        }
-
-        auto tmp = SkImage::MakeFromYUVATextures(dContext,
-                                                 kJPEG_SkYUVColorSpace,
-                                                 planeReleaseContext->beTextures(),
-                                                 indices,
-                                                 fRGBABmp.dimensions(),
-                                                 kTopLeft_GrSurfaceOrigin,
-                                                 nullptr);
-        if (!tmp) {
-            YUVABackendReleaseContext::Unwind(dContext, planeReleaseContext, false);
-            return nullptr;
-        }
-        resultSurface->getCanvas()->drawImage(std::move(tmp), 0, 0);
-        YUVABackendReleaseContext::Unwind(dContext, planeReleaseContext, true);
+        resultSurface->getCanvas()->drawImage(std::move(planarImage), 0, 0);
         return resultSurface->makeImageSnapshot();
     }
 
@@ -213,7 +153,9 @@
             return DrawResult::kSkip;
         }
 
-        fRGBABmp = CreateBmpAndPlanes("images/mandrill_32.png", fYUVABmps);
+        if (!fLazyYUVImage) {
+            fLazyYUVImage = CreatePlanes("images/mandrill_32.png");
+        }
 
         // We make a version of this image for each draw because, if any draw flattens it to
         // RGBA, then all subsequent draws would use the RGBA texture.
@@ -320,8 +262,7 @@
      }
 
 private:
-    SkBitmap fRGBABmp; // TODO: oddly, it looks like this could just be an SkISize
-    SkBitmap fYUVABmps[4];
+    std::unique_ptr<sk_gpu_test::LazyYUVImage> fLazyYUVImage;
 
     // 3 draws x 3 scales x 4 filter qualities
     static constexpr int kNumImages = 3 * 3 * 4;
diff --git a/gm/wacky_yuv_formats.cpp b/gm/wacky_yuv_formats.cpp
index 559bdc8..c198715 100644
--- a/gm/wacky_yuv_formats.cpp
+++ b/gm/wacky_yuv_formats.cpp
@@ -57,7 +57,6 @@
 #include <memory>
 #include <utility>
 
-using sk_gpu_test::YUVABackendReleaseContext;
 class GrRenderTargetContext;
 
 static const int kTileWidthHeight = 128;
@@ -116,77 +115,51 @@
 
 class YUVAPlanarConfig {
 public:
-    enum class YUVAChannel { kY, kU, kV, kA };
-
     YUVAPlanarConfig(YUVFormat format, bool opaque) {
         switch (format) {
             case kP016_YUVFormat:
             case kP010_YUVFormat:
             case kP016F_YUVFormat:
             case kNV12_YUVFormat:
-                fLocations[0] = {0, 0};
-                fLocations[1] = {1, 0};
-                fLocations[2] = {1, 1};
                 if (opaque) {
                     fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_UV_420;
                 } else {
-                    fLocations[3] = {2, 0};
                     fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_UV_A_4204;
                 }
                 break;
             case kY416_YUVFormat:
             case kY410_YUVFormat:
-                fLocations[0] = {0, 1};
-                fLocations[1] = {0, 0};
-                fLocations[2] = {0, 2};
                 if (opaque) {
                     fPlanarConfig = SkYUVAInfo::PlanarConfig::kUYV_444;
                 } else {
-                    fLocations[3] = {0, 3};
                     fPlanarConfig = SkYUVAInfo::PlanarConfig::kUYVA_4444;
                 }
                 break;
             case kAYUV_YUVFormat:
-                fLocations[0] = {0, 0};
-                fLocations[1] = {0, 1};
-                fLocations[2] = {0, 2};
                 if (opaque) {
                     fPlanarConfig = SkYUVAInfo::PlanarConfig::kYUV_444;
                 } else {
-                    fLocations[3] = {0, 3};
                     fPlanarConfig = SkYUVAInfo::PlanarConfig::kYUVA_4444;
                 }
                 break;
             case kNV21_YUVFormat:
-                fLocations[0] = {0, 0};
-                fLocations[1] = {1, 1};
-                fLocations[2] = {1, 0};
                 if (opaque) {
                     fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_VU_420;
                 } else {
-                    fLocations[3] = {2, 0};
                     fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_VU_A_4204;
                 }
                 break;
             case kI420_YUVFormat:
-                fLocations[0] = {0, 0};
-                fLocations[1] = {1, 0};
-                fLocations[2] = {2, 0};
                 if (opaque) {
                     fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_420;
                 } else {
-                    fLocations[3] = {3, 0};
                     fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_A_4204;
                 }
                 break;
             case kYV12_YUVFormat:
-                fLocations[0] = {0, 0};
-                fLocations[1] = {2, 0};
-                fLocations[2] = {1, 0};
                 if (opaque) {
                     fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_V_U_420;
                 } else {
-                    fLocations[3] = {3, 0};
                     fPlanarConfig = SkYUVAInfo::PlanarConfig::kY_V_U_A_4204;
                 }
                 break;
@@ -195,129 +168,20 @@
 
     int numPlanes() const { return SkYUVAInfo::NumPlanes(fPlanarConfig); }
 
-    int planeIndex(YUVAChannel c) const { return fLocations[static_cast<int>(c)].fPlaneIdx; }
-
-    int channelIndex(YUVAChannel c) const { return fLocations[static_cast<int>(c)].fChannelIdx; }
-
-    bool hasAlpha() const { return SkYUVAInfo::HasAlpha(fPlanarConfig); }
-
-    /**
-     * Given a mask of SkColorChannelFlags choose a channel by index. Legal 'channelMask' values
-     * are:
-     *      kAlpha, kGray, kRed, kRG, kRGB, kRGBA.
-     * The channel index must be less than the number of bits set in the mask. The index order is
-     * the order listed above (e.g. if 'channelMask' is kRGB and 'channelIdx' is 1 then
-     * SkColorChannel::kG is returned as 'channel'). The function fails if 'channelMask' is not one
-     * of the listed allowed values or 'channelIdx' is invalid for the mask.
-     */
-    static bool ChannelIndexToChannel(uint32_t channelMask,
-                                      int channelIdx,
-                                      SkColorChannel* channel);
-
-    /**
-     * Goes from channel indices to actual channels given texture formats. Also supports adding
-     * on an external alpha plane if this format doesn't already have alpha. The extra alpha texture
-     * must be the last texture and the channel index is assumed to be 0.
-     */
-    bool getYUVAIndices(const GrBackendTexture textures[],
-                        int numTextures,
-                        SkYUVAIndex indices[4]) const;
-
-    SkYUVAInfo getYUVAInfo(SkISize dimensions, SkYUVColorSpace yuvColorSpace) const;
-
     SkYUVAPixmaps makeYUVAPixmaps(SkISize dimensions,
                                   SkYUVColorSpace yuvColorSpace,
                                   const SkBitmap bitmaps[],
                                   int numBitmaps) const;
 
 private:
-    struct YUVALocation {
-        int fPlaneIdx = -1;
-        int fChannelIdx = -1;
-    };
-
     SkYUVAInfo::PlanarConfig fPlanarConfig;
-    YUVALocation fLocations[4] = {};
 };
 
-bool YUVAPlanarConfig::ChannelIndexToChannel(uint32_t channelFlags,
-                                             int channelIdx,
-                                             SkColorChannel* channel) {
-    switch (channelFlags) {
-        case kGray_SkColorChannelFlag:  // For gray returning any of R, G, or B for index 0 is ok.
-        case kRed_SkColorChannelFlag:
-            if (channelIdx == 0) {
-                *channel = SkColorChannel::kR;
-                return true;
-            }
-            return false;
-        case kAlpha_SkColorChannelFlag:
-            if (channelIdx == 0) {
-                *channel = SkColorChannel::kA;
-                return true;
-            }
-            return false;
-        case kRG_SkColorChannelFlags:
-            if (channelIdx == 0 || channelIdx == 1) {
-                *channel = static_cast<SkColorChannel>(channelIdx);
-                return true;
-            }
-            return false;
-        case kRGB_SkColorChannelFlags:
-            if (channelIdx >= 0 && channelIdx <= 2) {
-                *channel = static_cast<SkColorChannel>(channelIdx);
-                return true;
-            }
-            return false;
-        case kRGBA_SkColorChannelFlags:
-            if (channelIdx >= 0 && channelIdx <= 3) {
-                *channel = static_cast<SkColorChannel>(channelIdx);
-                return true;
-            }
-            return false;
-        default:
-            return false;
-    }
-}
-
-bool YUVAPlanarConfig::getYUVAIndices(const GrBackendTexture textures[],
-                                      int numTextures,
-                                      SkYUVAIndex indices[4]) const {
-    if (numTextures != this->numPlanes()) {
-        return false;
-    }
-    uint32_t channelMasks[4] = {};
-    for (int i = 0; i < numTextures; ++i) {
-        channelMasks[i] = textures[i].getBackendFormat().channelMask();
-    }
-    for (int i = 0; i < 4; ++i) {
-        int plane = fLocations[i].fPlaneIdx;
-        if (plane < 0) {
-            indices[i].fIndex = -1;
-            indices[i].fChannel = SkColorChannel::kR;
-        } else {
-            indices[i].fIndex = plane;
-            if (!ChannelIndexToChannel(channelMasks[plane], fLocations[i].fChannelIdx,
-                                       &indices[i].fChannel)) {
-                return false;
-            }
-        }
-    }
-    SkDEBUGCODE(int checkNumPlanes;)
-    SkASSERT(SkYUVAIndex::AreValidIndices(indices, &checkNumPlanes));
-    SkASSERT(checkNumPlanes == this->numPlanes());
-    return true;
-}
-
-SkYUVAInfo YUVAPlanarConfig::getYUVAInfo(SkISize dimensions, SkYUVColorSpace yuvColorSpace) const {
-    return SkYUVAInfo(dimensions, fPlanarConfig, yuvColorSpace);
-}
-
 SkYUVAPixmaps YUVAPlanarConfig::makeYUVAPixmaps(SkISize dimensions,
                                                 SkYUVColorSpace yuvColorSpace,
                                                 const SkBitmap bitmaps[],
                                                 int numBitmaps) const {
-    SkYUVAInfo info = this->getYUVAInfo(dimensions, yuvColorSpace);
+    SkYUVAInfo info(dimensions, fPlanarConfig, yuvColorSpace);
     SkPixmap pmaps[SkYUVAInfo::kMaxPlanes];
     int n = info.numPlanes();
     if (numBitmaps < n) {
@@ -329,38 +193,6 @@
     return SkYUVAPixmaps::FromExternalPixmaps(info, pmaps);
 }
 
-static bool is_colorType_texturable(const GrCaps* caps, GrColorType ct) {
-    GrBackendFormat format = caps->getDefaultBackendFormat(ct, GrRenderable::kNo);
-    if (!format.isValid()) {
-        return false;
-    }
-
-    return caps->isFormatTexturable(format);
-}
-
-static bool is_format_natively_supported(GrRecordingContext* context, YUVFormat yuvFormat) {
-
-    const GrCaps* caps = context->priv().caps();
-
-    switch (yuvFormat) {
-        case kP016_YUVFormat:  // fall through
-        case kP010_YUVFormat:  return is_colorType_texturable(caps, GrColorType::kAlpha_16) &&
-                                      is_colorType_texturable(caps, GrColorType::kRG_1616);
-        case kP016F_YUVFormat: return is_colorType_texturable(caps, GrColorType::kAlpha_F16) &&
-                                      is_colorType_texturable(caps, GrColorType::kRG_F16);
-        case kY416_YUVFormat:  return is_colorType_texturable(caps, GrColorType::kRGBA_16161616);
-        case kAYUV_YUVFormat:  return is_colorType_texturable(caps, GrColorType::kRGBA_8888);
-        case kY410_YUVFormat:  return is_colorType_texturable(caps, GrColorType::kRGBA_1010102);
-        case kNV12_YUVFormat:  // fall through
-        case kNV21_YUVFormat:  return is_colorType_texturable(caps, GrColorType::kGray_8) &&
-                                      is_colorType_texturable(caps, GrColorType::kRG_88);
-        case kI420_YUVFormat: // fall through
-        case kYV12_YUVFormat: return is_colorType_texturable(caps, GrColorType::kGray_8);
-    }
-
-    SkUNREACHABLE;
-}
-
 // All the planes we need to construct the various YUV formats
 struct PlaneData {
    SkBitmap fYFull;
@@ -527,19 +359,6 @@
     yuv[3] = SkColorGetA(col);
 }
 
-static SkPMColor convert_yuva_to_rgba(const float mtx[20], uint8_t yuva[4]) {
-    uint8_t y = yuva[0];
-    uint8_t u = yuva[1];
-    uint8_t v = yuva[2];
-    uint8_t a = yuva[3];
-
-    uint8_t r = SkTPin(SkScalarRoundToInt(mtx[ 0]*y + mtx[ 1]*u + mtx[ 2]*v + mtx[ 4]*255), 0, 255);
-    uint8_t g = SkTPin(SkScalarRoundToInt(mtx[ 5]*y + mtx[ 6]*u + mtx[ 7]*v + mtx[ 9]*255), 0, 255);
-    uint8_t b = SkTPin(SkScalarRoundToInt(mtx[10]*y + mtx[11]*u + mtx[12]*v + mtx[14]*255), 0, 255);
-
-    return SkPremultiplyARGBInline(a, r, g, b);
-}
-
 static void extract_planes(const SkBitmap& bm, SkYUVColorSpace yuvColorSpace, PlaneData* planes) {
     if (kIdentity_SkYUVColorSpace == yuvColorSpace) {
         // To test the identity color space we use JPEG YUV planes
@@ -829,118 +648,6 @@
     return nextLayer;
 }
 
-static uint8_t look_up(float x1, float y1, const SkBitmap& bm, int channelIdx) {
-    SkASSERT(x1 > 0 && x1 < 1.0f);
-    SkASSERT(y1 > 0 && y1 < 1.0f);
-    int x = SkScalarFloorToInt(x1 * bm.width());
-    int y = SkScalarFloorToInt(y1 * bm.height());
-
-    auto channelMask = SkColorTypeChannelFlags(bm.colorType());
-    SkColorChannel channel;
-    SkAssertResult(YUVAPlanarConfig::ChannelIndexToChannel(channelMask, channelIdx, &channel));
-    auto ii = SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, bm.alphaType(), bm.refColorSpace());
-    uint32_t pixel;
-    SkAssertResult(bm.readPixels(ii, &pixel, sizeof(pixel), x, y));
-    int shift = static_cast<int>(channel) * 8;
-    return static_cast<uint8_t>((pixel >> shift) & 0xff);
-}
-
-class YUVGenerator : public SkImageGenerator {
-public:
-    YUVGenerator(const SkImageInfo& ii,
-                 YUVAPlanarConfig planarConfig,
-                 SkYUVColorSpace yuvColorSpace,
-                 SkBitmap bitmaps[SkYUVASizeInfo::kMaxCount])
-            : SkImageGenerator(ii), fPlanarConfig(planarConfig), fYUVColorSpace(yuvColorSpace) {
-        int numPlanes = fPlanarConfig.numPlanes();
-        for (int i = 0; i < numPlanes; ++i) {
-            fYUVBitmaps[i] = bitmaps[i];
-            SkASSERT(!bitmaps[i].drawsNothing());
-        }
-    }
-
-protected:
-    bool onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes,
-                     const Options&) override {
-
-        if (kUnknown_SkColorType == fFlattened.colorType()) {
-            fFlattened.allocPixels(info);
-            SkASSERT(kN32_SkColorType == info.colorType());
-            SkASSERT(kPremul_SkAlphaType == info.alphaType());
-
-            float mtx[20];
-            SkColorMatrix_YUV2RGB(fYUVColorSpace, mtx);
-
-            for (int y = 0; y < info.height(); ++y) {
-                for (int x = 0; x < info.width(); ++x) {
-
-                    float x1 = (x + 0.5f) / info.width();
-                    float y1 = (y + 0.5f) / info.height();
-
-                    uint8_t yuva[4] = {0, 0, 0, 255};
-
-                    using YUVAChannel = YUVAPlanarConfig::YUVAChannel;
-                    for (auto c : {YUVAChannel::kY, YUVAChannel::kU, YUVAChannel::kV}) {
-                        const auto& bmp = fYUVBitmaps[fPlanarConfig.planeIndex(c)];
-                        int channelIdx = fPlanarConfig.channelIndex(c);
-                        yuva[static_cast<int>(c)] = look_up(x1, y1, bmp, channelIdx);
-                    }
-                    if (fPlanarConfig.hasAlpha()) {
-                        const auto& bmp = fYUVBitmaps[fPlanarConfig.planeIndex(YUVAChannel::kA)];
-                        int channelIdx = fPlanarConfig.channelIndex(YUVAChannel::kA);
-                        yuva[3] = look_up(x1, y1, bmp, channelIdx);
-                    }
-
-                    // Making premul here.
-                    *fFlattened.getAddr32(x, y) = convert_yuva_to_rgba(mtx, yuva);
-                }
-            }
-        }
-
-        return fFlattened.readPixels(info, pixels, rowBytes, 0, 0);
-    }
-
-    bool onQueryYUVAInfo(const SkYUVAPixmapInfo::SupportedDataTypes& types,
-                         SkYUVAPixmapInfo* info) const override {
-        SkYUVAInfo yuvaInfo =
-                fPlanarConfig.getYUVAInfo(this->getInfo().dimensions(), fYUVColorSpace);
-        SkColorType colorTypes[SkYUVAInfo::kMaxPlanes] = {};
-        for (int i = 0; i < yuvaInfo.numPlanes(); ++i) {
-            colorTypes[i] = fYUVBitmaps[i].colorType();
-        }
-        *info = SkYUVAPixmapInfo(yuvaInfo, colorTypes, /* row bytes */ nullptr);
-        SkASSERT(info->isValid());
-        return true;
-    }
-
-    bool onGetYUVAPlanes(const SkYUVAPixmaps& pixmaps) override {
-        int n = pixmaps.numPlanes();
-        for (int i = 0; i < n; ++i) {
-            SkASSERT(pixmaps.plane(i).dimensions() == fYUVBitmaps[i].dimensions());
-            SkASSERT(pixmaps.plane(i).colorType() == fYUVBitmaps[i].colorType());
-            SkRectMemcpy(pixmaps.plane(i).writable_addr(), pixmaps.plane(i).rowBytes(),
-                         fYUVBitmaps[i].getPixels(), fYUVBitmaps[i].rowBytes(),
-                         fYUVBitmaps[i].info().minRowBytes(), fYUVBitmaps[i].height());
-        }
-        return true;
-    }
-
-private:
-    YUVAPlanarConfig fPlanarConfig;
-    SkYUVColorSpace  fYUVColorSpace;
-    SkBitmap         fYUVBitmaps[SkYUVAInfo::kMaxPlanes];
-    SkBitmap         fFlattened;
-};
-
-static sk_sp<SkImage> make_yuv_gen_image(const SkImageInfo& ii,
-                                         YUVAPlanarConfig planarConfig,
-                                         SkYUVColorSpace yuvColorSpace,
-                                         SkBitmap bitmaps[]) {
-    auto gen = std::make_unique<YUVGenerator>(ii, planarConfig, yuvColorSpace, bitmaps);
-
-    return SkImage::MakeFromGenerator(std::move(gen));
-}
-
 static void draw_col_label(SkCanvas* canvas, int x, int yuvColorSpace, bool opaque) {
     static const char* kYUVColorSpaceNames[] = {"JPEG",     "601",      "709F",     "709L",
                                                 "2020_8F",  "2020_8L",  "2020_10F", "2020_10L",
@@ -988,13 +695,6 @@
     canvas->drawString(rowLabel, 0, y, font, paint);
 }
 
-static GrBackendTexture create_yuva_texture(GrDirectContext* context, const SkBitmap& bm, int index,
-                                            YUVABackendReleaseContext* releaseContext) {
-    return context->createBackendTexture(bm.pixmap(), GrRenderable::kNo, GrProtected::kNo,
-                                         YUVABackendReleaseContext::CreationCompleteProc(index),
-                                         releaseContext);
-}
-
 static sk_sp<SkColorFilter> yuv_to_rgb_colorfilter() {
     static const float kJPEGConversionMatrix[20] = {
         1.0f,  0.0f,       1.402f,    0.0f, -180.0f/255,
@@ -1026,18 +726,10 @@
 // YV12
 class WackyYUVFormatsGM : public GM {
 public:
-    // This GM has a variety of ways in which the test images can be constructed.
-    enum class ImageType {
-        kYUVAPixmaps,      // SkImage::MakeFromYUVAPixmaps.
-        kYUVATextures,     // SkImage::MakeFromYUVATextures.
-        kGenerator,        // SkImage_Lazy backed by generator that supports YUVA. This is the only
-                           // mode that runs on CPU but CPU uses the flattening onGetPixels.
-    };
+    using Type = sk_gpu_test::LazyYUVImage::Type;
 
-    WackyYUVFormatsGM(bool useTargetColorSpace, bool useSubset, ImageType imageType)
-            : fUseTargetColorSpace(useTargetColorSpace)
-            , fUseSubset(useSubset)
-            , fImageType(imageType) {
+    WackyYUVFormatsGM(bool useTargetColorSpace, bool useSubset, Type type)
+            : fUseTargetColorSpace(useTargetColorSpace), fUseSubset(useSubset), fImageType(type) {
         this->setBGColor(0xFFCCCCCC);
     }
 
@@ -1051,12 +743,12 @@
             name += "_domain";
         }
         switch (fImageType) {
-            case ImageType::kYUVAPixmaps:
+            case Type::kFromPixmaps:
                 name += "_frompixmaps";
                 break;
-            case ImageType::kYUVATextures:
+            case Type::kFromTextures:
                 break;
-            case ImageType::kGenerator:
+            case Type::kFromGenerator:
                 name += "_imggen";
                 break;
         }
@@ -1108,65 +800,14 @@
 
                     int numPlanes = create_YUV(planes, format, resultBMs, opaque);
                     const YUVAPlanarConfig planarConfig(format, opaque);
-                    SkASSERT(numPlanes == planarConfig.numPlanes());
+                    SkYUVAPixmaps pixmaps =
+                            planarConfig.makeYUVAPixmaps(fOriginalBMs[opaque].dimensions(),
+                                                         static_cast<SkYUVColorSpace>(cs),
+                                                         resultBMs,
+                                                         numPlanes);
+                    auto lazyYUV = sk_gpu_test::LazyYUVImage::Make(std::move(pixmaps));
 
-                    if (fImageType == ImageType::kYUVATextures) {
-                        SkASSERT(dContext);
-
-                        if (dContext->abandoned()) {
-                            return false;
-                        }
-
-                        if (!is_format_natively_supported(dContext, format)) {
-                            continue;
-                        }
-
-                        auto releaseCtx = new YUVABackendReleaseContext(dContext);
-
-                        for (int i = 0; i < numPlanes; ++i) {
-                            GrBackendTexture tmp = create_yuva_texture(dContext, resultBMs[i], i,
-                                                                       releaseCtx);
-                            if (!tmp.isValid()) {
-                                YUVABackendReleaseContext::Unwind(dContext, releaseCtx, false);
-                                return false;
-                            }
-                            releaseCtx->set(i, tmp);
-                        }
-
-                        SkYUVAIndex yuvaIndices[4];
-                        if (!planarConfig.getYUVAIndices(releaseCtx->beTextures(),
-                                                         numPlanes,
-                                                         yuvaIndices)) {
-                            YUVABackendReleaseContext::Unwind(dContext, releaseCtx, false);
-                            continue;
-                        }
-
-                        fImages[opaque][cs][format] =
-                                SkImage::MakeFromYUVATextures(dContext,
-                                                              (SkYUVColorSpace)cs,
-                                                              releaseCtx->beTextures(),
-                                                              yuvaIndices,
-                                                              fOriginalBMs[opaque].dimensions(),
-                                                              kTopLeft_GrSurfaceOrigin,
-                                                              nullptr,
-                                                              YUVABackendReleaseContext::Release,
-                                                              releaseCtx);
-                    } else if (fImageType == ImageType::kGenerator) {
-                        SkImageInfo ii = SkImageInfo::MakeN32(fOriginalBMs[opaque].width(),
-                                                              fOriginalBMs[opaque].height(),
-                                                              kPremul_SkAlphaType);
-                        fImages[opaque][cs][format] = make_yuv_gen_image(
-                                ii, planarConfig, (SkYUVColorSpace)cs, resultBMs);
-                    } else if (fImageType == ImageType::kYUVAPixmaps) {
-                        SkASSERT(dContext);
-                        SkYUVAPixmaps pixmaps =
-                                planarConfig.makeYUVAPixmaps(fOriginalBMs[opaque].dimensions(),
-                                                             static_cast<SkYUVColorSpace>(cs),
-                                                             resultBMs,
-                                                             numPlanes);
-                        fImages[opaque][cs][format] =
-                                SkImage::MakeFromYUVAPixmaps(dContext, pixmaps);
-                    }
+                    fImages[opaque][cs][format] = lazyYUV->refImage(dContext, fImageType);
                 }
             }
         }
@@ -1192,7 +833,7 @@
         }
 
         // Only the generator is expected to work with the CPU backend.
-        if (fImageType != ImageType::kGenerator && !dContext) {
+        if (fImageType != Type::kFromGenerator && !dContext) {
             return DrawResult::kSkip;
         }
 
@@ -1278,7 +919,7 @@
     sk_sp<SkImage>             fImages[2][kLastEnum_SkYUVColorSpace + 1][kLast_YUVFormat + 1];
     bool                       fUseTargetColorSpace;
     bool                       fUseSubset;
-    ImageType                  fImageType;
+    Type                       fImageType;
     sk_sp<SkColorSpace>        fTargetColorSpace;
 
     using INHERITED = GM;
@@ -1288,19 +929,19 @@
 
 DEF_GM(return new WackyYUVFormatsGM(/* target cs */ false,
                                     /* subset */ false,
-                                    WackyYUVFormatsGM::ImageType::kYUVATextures);)
+                                    WackyYUVFormatsGM::Type::kFromTextures);)
 DEF_GM(return new WackyYUVFormatsGM(/* target cs */ false,
                                     /* subset */ true,
-                                    WackyYUVFormatsGM::ImageType::kYUVATextures);)
+                                    WackyYUVFormatsGM::Type::kFromTextures);)
 DEF_GM(return new WackyYUVFormatsGM(/* target cs */ true,
                                     /* subset */ false,
-                                    WackyYUVFormatsGM::ImageType::kYUVATextures);)
+                                    WackyYUVFormatsGM::Type::kFromTextures);)
 DEF_GM(return new WackyYUVFormatsGM(/* target cs */ false,
                                     /* subset */ false,
-                                    WackyYUVFormatsGM::ImageType::kGenerator);)
+                                    WackyYUVFormatsGM::Type::kFromGenerator);)
 DEF_GM(return new WackyYUVFormatsGM(/* target cs */ false,
                                     /* subset */ false,
-                                    WackyYUVFormatsGM::ImageType::kYUVAPixmaps);)
+                                    WackyYUVFormatsGM::Type::kFromPixmaps);)
 
 class YUVMakeColorSpaceGM : public GpuGM {
 public:
@@ -1352,52 +993,21 @@
             create_YUV(planes, kAYUV_YUVFormat, resultBMs, opaque);
 
             YUVAPlanarConfig planarConfig(kAYUV_YUVFormat, opaque);
-            int numPlanes = planarConfig.numPlanes();
 
-            auto releaseContext = new YUVABackendReleaseContext(context);
-            auto srgbReleaseContext = new YUVABackendReleaseContext(context);
+            auto yuvaPixmaps = planarConfig.makeYUVAPixmaps(fOriginalBMs[opaque].dimensions(),
+                                                            kJPEG_Full_SkYUVColorSpace,
+                                                            resultBMs,
+                                                            SK_ARRAY_COUNT(resultBMs));
 
-            for (int i = 0; i < numPlanes; ++i) {
-                GrBackendTexture tmp = create_yuva_texture(context, resultBMs[i], i,
-                                                           releaseContext);
-                if (!tmp.isValid()) {
-                    YUVABackendReleaseContext::Unwind(context, releaseContext, false);
-                    YUVABackendReleaseContext::Unwind(context, srgbReleaseContext, false);
-                    return false;
-                }
-
-                releaseContext->set(i, tmp);
-
-                tmp = create_yuva_texture(context, resultBMs[i], i, srgbReleaseContext);
-                if (!tmp.isValid()) {
-                    YUVABackendReleaseContext::Unwind(context, releaseContext, false);
-                    YUVABackendReleaseContext::Unwind(context, srgbReleaseContext, false);
-                    return false;
-                }
-
-                srgbReleaseContext->set(i, tmp);
+            int i = 0;
+            for (sk_sp<SkColorSpace> cs : {sk_sp<SkColorSpace>(nullptr),
+                                           SkColorSpace::MakeSRGB()}) {
+                auto lazyYUV = sk_gpu_test::LazyYUVImage::Make(yuvaPixmaps,
+                                                               GrMipmapped::kNo,
+                                                               std::move(cs));
+                fImages[opaque][i++] =
+                        lazyYUV->refImage(context, sk_gpu_test::LazyYUVImage::Type::kFromTextures);
             }
-
-            SkYUVAIndex yuvaIndices[4];
-            planarConfig.getYUVAIndices(releaseContext->beTextures(), numPlanes, yuvaIndices);
-
-            fImages[opaque][0] = SkImage::MakeFromYUVATextures(
-                    context,
-                    kJPEG_SkYUVColorSpace,
-                    releaseContext->beTextures(),
-                    yuvaIndices,
-                    { fOriginalBMs[opaque].width(), fOriginalBMs[opaque].height() },
-                    kTopLeft_GrSurfaceOrigin, nullptr,
-                    YUVABackendReleaseContext::Release, releaseContext);
-            fImages[opaque][1] = SkImage::MakeFromYUVATextures(
-                    context,
-                    kJPEG_SkYUVColorSpace,
-                    srgbReleaseContext->beTextures(),
-                    yuvaIndices,
-                    { fOriginalBMs[opaque].width(), fOriginalBMs[opaque].height() },
-                    kTopLeft_GrSurfaceOrigin,
-                    SkColorSpace::MakeSRGB(),
-                    YUVABackendReleaseContext::Release, srgbReleaseContext);
         }
 
         // Some backends (e.g., Vulkan) require all work be completed for backend textures before
diff --git a/gm/yuv420_odd_dim.cpp b/gm/yuv420_odd_dim.cpp
index 91647cf..0277208 100644
--- a/gm/yuv420_odd_dim.cpp
+++ b/gm/yuv420_odd_dim.cpp
@@ -49,7 +49,7 @@
     if (!imageHelper) {
         return nullptr;
     }
-    return imageHelper->refImage(rContext);
+    return imageHelper->refImage(rContext, sk_gpu_test::LazyYUVImage::Type::kFromPixmaps);
 }
 
 // This GM tests that the YUVA image code path in the GPU backend handles odd sized images with
diff --git a/include/core/SkYUVAInfo.h b/include/core/SkYUVAInfo.h
index d2659c4..da94ef7 100644
--- a/include/core/SkYUVAInfo.h
+++ b/include/core/SkYUVAInfo.h
@@ -11,6 +11,7 @@
 #include "include/codec/SkEncodedOrigin.h"
 #include "include/core/SkImageInfo.h"
 #include "include/core/SkSize.h"
+#include "include/core/SkYUVAIndex.h"
 
 /**
  * Specifies the structure of planes for a YUV image with optional alpha. The actual planar data
@@ -97,6 +98,15 @@
      */
     static constexpr int NumChannelsInPlane(PlanarConfig, int i);
 
+    /**
+     * Given a PlanarConfig and a set of channel flags for each plane, convert to SkYUVAIndex
+     * representation. Fails if channel flags aren't valid for the PlanarConfig (i.e. don't have
+     * enough channels in a plane).
+     */
+    static bool GetYUVAIndices(PlanarConfig,
+                               const uint32_t planeChannelFlags[kMaxPlanes],
+                               SkYUVAIndex indices[SkYUVAIndex::kIndexCount]);
+
     /** Does the PlanarConfig have alpha values? */
     static bool HasAlpha(PlanarConfig);
 
@@ -155,6 +165,15 @@
 
     int numChannelsInPlane(int i) const { return NumChannelsInPlane(fPlanarConfig, i); }
 
+    /**
+     * Given a set of channel flags for each plane, converts this->planarConfig() to SkYUVAIndex
+     * representation. Fails if the channel flags aren't valid for the PlanarConfig (i.e. don't have
+     * enough channels in a plane).
+     */
+    bool toYUVAIndices(const uint32_t channelFlags[4], SkYUVAIndex indices[4]) const {
+        return GetYUVAIndices(fPlanarConfig, channelFlags, indices);
+    }
+
     bool operator==(const SkYUVAInfo& that) const;
     bool operator!=(const SkYUVAInfo& that) const { return !(*this == that); }
 
diff --git a/include/core/SkYUVAPixmaps.h b/include/core/SkYUVAPixmaps.h
index 4a6601e..69e1085 100644
--- a/include/core/SkYUVAPixmaps.h
+++ b/include/core/SkYUVAPixmaps.h
@@ -183,6 +183,12 @@
     static SkYUVAPixmaps FromData(const SkYUVAPixmapInfo&, sk_sp<SkData>);
 
     /**
+     * Makes a deep copy of the src SkYUVAPixmaps. The returned SkYUVAPixmaps owns its planes'
+     * backing stores.
+     */
+    static SkYUVAPixmaps MakeCopy(const SkYUVAPixmaps& src);
+
+    /**
      * Use passed in memory as backing store for pixmaps' pixels. Caller must ensure memory remains
      * allocated while pixmaps are in use. There must be at least
      * SkYUVAPixmapInfo::computeTotalBytes() allocated starting at memory.
@@ -211,6 +217,8 @@
 
     const SkYUVAInfo& yuvaInfo() const { return fYUVAInfo; }
 
+    SkYUVAPixmapInfo pixmapsInfo() const;
+
     /** Number of pixmap planes or 0 if this SkYUVAPixmaps is invalid. */
     int numPlanes() const { return this->isValid() ? fYUVAInfo.numPlanes() : 0; }
 
@@ -227,9 +235,18 @@
     const SkPixmap& plane(int i) const { return fPlanes[SkToSizeT(i)]; }
 
     /**
+     * Computes a SkYUVAIndex representation of the planar layout. Returns true on success and
+     * false on failure. Will succeed whenever this->isValid() is true.
+     */
+    bool toYUVAIndices(SkYUVAIndex[SkYUVAIndex::kIndexCount]) const;
+
+    /** Does this SkPixmaps own the backing store of the planes? */
+    bool ownsStorage() const { return SkToBool(fData); }
+
+    /**
      * Conversion to legacy SkYUVA data structures.
      */
-    bool toLegacy(SkYUVASizeInfo*, SkYUVAIndex[4]) const;
+    bool toLegacy(SkYUVASizeInfo*, SkYUVAIndex[SkYUVAIndex::kIndexCount]) const;
 
 private:
     SkYUVAPixmaps(const SkYUVAPixmapInfo&, sk_sp<SkData>);
diff --git a/src/core/SkYUVAInfo.cpp b/src/core/SkYUVAInfo.cpp
index a43b477..5c46e0f 100644
--- a/src/core/SkYUVAInfo.cpp
+++ b/src/core/SkYUVAInfo.cpp
@@ -79,6 +79,135 @@
     SkUNREACHABLE;
 }
 
+static bool channel_index_to_channel(uint32_t channelFlags,
+                                     int channelIdx,
+                                     SkColorChannel* channel) {
+    switch (channelFlags) {
+        case kGray_SkColorChannelFlag:  // For gray returning any of R, G, or B for index 0 is ok.
+        case kRed_SkColorChannelFlag:
+            if (channelIdx == 0) {
+                *channel = SkColorChannel::kR;
+                return true;
+            }
+            return false;
+        case kAlpha_SkColorChannelFlag:
+            if (channelIdx == 0) {
+                *channel = SkColorChannel::kA;
+                return true;
+            }
+            return false;
+        case kRG_SkColorChannelFlags:
+            if (channelIdx == 0 || channelIdx == 1) {
+                *channel = static_cast<SkColorChannel>(channelIdx);
+                return true;
+            }
+            return false;
+        case kRGB_SkColorChannelFlags:
+            if (channelIdx >= 0 && channelIdx <= 2) {
+                *channel = static_cast<SkColorChannel>(channelIdx);
+                return true;
+            }
+            return false;
+        case kRGBA_SkColorChannelFlags:
+            if (channelIdx >= 0 && channelIdx <= 3) {
+                *channel = static_cast<SkColorChannel>(channelIdx);
+                return true;
+            }
+            return false;
+        default:
+            return false;
+    }
+}
+
+bool SkYUVAInfo::GetYUVAIndices(PlanarConfig config,
+                                const uint32_t planeChannelFlags[kMaxPlanes],
+                                SkYUVAIndex indices[SkYUVAIndex::kIndexCount]) {
+    struct Location {int plane, chanIdx;};
+    const Location* locations = nullptr;
+    switch (config) {
+        case PlanarConfig::kY_U_V_444:
+        case PlanarConfig::kY_U_V_422:
+        case PlanarConfig::kY_U_V_420:
+        case PlanarConfig::kY_U_V_440:
+        case PlanarConfig::kY_U_V_411:
+        case PlanarConfig::kY_U_V_410: {
+            static constexpr Location kLocations[] = {{0, 0}, {1, 0}, {2, 0}, {-1, -1}};
+            locations = kLocations;
+            break;
+        }
+        case PlanarConfig::kY_V_U_420: {
+            static constexpr Location kLocations[] = {{0, 0}, {2, 0}, {1, 0}, {-1, -1}};
+            locations = kLocations;
+            break;
+        }
+        case PlanarConfig::kY_U_V_A_4204: {
+            static constexpr Location kLocations[] = {{0, 0}, {1, 0}, {2, 0}, {3, 0}};
+            locations = kLocations;
+            break;
+        }
+        case PlanarConfig::kY_V_U_A_4204: {
+            static constexpr Location kLocations[] = {{0, 0}, {2, 0}, {1, 0}, {3, 0}};
+            locations = kLocations;
+            break;
+        }
+        case PlanarConfig::kY_UV_420: {
+            static constexpr Location kLocations[] = {{0, 0}, {1, 0}, {1, 1}, {-1, -1}};
+            locations = kLocations;
+            break;
+        }
+        case PlanarConfig::kY_VU_420: {
+            static constexpr Location kLocations[] = {{0, 0}, {1, 1}, {1, 0}, {-1, -1}};
+            locations = kLocations;
+            break;
+        }
+        case PlanarConfig::kY_UV_A_4204: {
+            static constexpr Location kLocations[] = {{0, 0}, {1, 0}, {1, 1}, {2, 0}};
+            locations = kLocations;
+            break;
+        }
+        case PlanarConfig::kY_VU_A_4204: {
+            static constexpr Location kLocations[] = {{0, 0}, {1, 1}, {1, 0}, {2, 0}};
+            locations = kLocations;
+            break;
+        }
+        case PlanarConfig::kYUV_444: {
+            static constexpr Location kLocations[] = {{0, 0}, {0, 1}, {0, 2}, {-1, -1}};
+            locations = kLocations;
+            break;
+        }
+        case PlanarConfig::kUYV_444: {
+            static constexpr Location kLocations[] = {{0, 1}, {0, 0}, {0, 2}, {-1, -1}};
+            locations = kLocations;
+            break;
+        }
+        case PlanarConfig::kYUVA_4444: {
+            static constexpr Location kLocations[] = {{0, 0}, {0, 1}, {0, 2}, {0, 3}};
+            locations = kLocations;
+            break;
+        }
+        case PlanarConfig::kUYVA_4444: {
+            static constexpr Location kLocations[] = {{0, 1}, {0, 0}, {0, 2}, {0, 3}};
+            locations = kLocations;
+            break;
+        }
+    }
+    SkASSERT(locations);
+    for (int i = 0; i < SkYUVAIndex::kIndexCount; ++i) {
+        const auto& [plane, chanIdx] = locations[i];
+        SkColorChannel channel;
+        if (plane >= 0) {
+            if (!channel_index_to_channel(planeChannelFlags[plane], chanIdx, &channel)) {
+                return false;
+            }
+            indices[i] = {plane, channel};
+        } else {
+            SkASSERT(i == 3);
+            indices[i] = {-1, SkColorChannel::kR};
+        }
+    }
+    return true;
+}
+
 bool SkYUVAInfo::HasAlpha(PlanarConfig planarConfig) {
     switch (planarConfig) {
         case PlanarConfig::kY_U_V_444:    return false;
diff --git a/src/core/SkYUVAPixmaps.cpp b/src/core/SkYUVAPixmaps.cpp
index d7bdef9..e840fc5 100644
--- a/src/core/SkYUVAPixmaps.cpp
+++ b/src/core/SkYUVAPixmaps.cpp
@@ -10,6 +10,7 @@
 #include "include/core/SkYUVAIndex.h"
 #include "include/core/SkYUVASizeInfo.h"
 #include "include/private/SkImageInfoPriv.h"
+#include "src/core/SkConvertPixels.h"
 
 #if SK_SUPPORT_GPU
 #include "include/private/GrImageContext.h"
@@ -96,7 +97,10 @@
     bool ok = true;
     for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
         fRowBytes[i] = rowBytes[i];
-        fPlaneInfos[i] = SkImageInfo::Make(planeDimensions[i], colorTypes[i], kPremul_SkAlphaType);
+        // Use kUnpremul so that we never multiply alpha when copying data in.
+        fPlaneInfos[i] = SkImageInfo::Make(planeDimensions[i],
+                                           colorTypes[i],
+                                           kUnpremul_SkAlphaType);
         int numRequiredChannels = yuvaInfo.numChannelsInPlane(i);
         SkASSERT(numRequiredChannels > 0);
         auto [numColorTypeChannels, colorTypeDataType] = NumChannelsAndDataType(colorTypes[i]);
@@ -191,6 +195,27 @@
     return SkYUVAPixmaps(yuvaPixmapInfo, std::move(data));
 }
 
+SkYUVAPixmaps SkYUVAPixmaps::MakeCopy(const SkYUVAPixmaps& src) {
+    if (!src.isValid()) {
+        return {};
+    }
+    SkYUVAPixmaps result = Allocate(src.pixmapsInfo());
+    int n = result.numPlanes();
+    for (int i = 0; i < n; ++i) {
+        // We use SkRectMemCpy rather than readPixels to ensure that we don't do any alpha type
+        // conversion.
+        const SkPixmap& s = src.plane(i);
+        const SkPixmap& d = result.plane(i);
+        SkRectMemcpy(d.writable_addr(),
+                     d.rowBytes(),
+                     s.addr(),
+                     s.rowBytes(),
+                     s.info().minRowBytes(),
+                     s.height());
+    }
+    return result;
+}
+
 SkYUVAPixmaps SkYUVAPixmaps::FromExternalMemory(const SkYUVAPixmapInfo& yuvaPixmapInfo,
                                                 void* memory) {
     if (!yuvaPixmapInfo.isValid()) {
@@ -230,168 +255,43 @@
     std::copy_n(pixmaps, yuvaInfo.numPlanes(), fPlanes.data());
 }
 
+SkYUVAPixmapInfo SkYUVAPixmaps::pixmapsInfo() const {
+    if (!this->isValid()) {
+        return {};
+    }
+    SkColorType colorTypes[kMaxPlanes] = {};
+    size_t rowBytes[kMaxPlanes] = {};
+    int numPlanes = this->numPlanes();
+    for (int i = 0; i < numPlanes; ++i) {
+        colorTypes[i] = fPlanes[i].colorType();
+        rowBytes[i] = fPlanes[i].rowBytes();
+    }
+    return {fYUVAInfo, colorTypes, rowBytes};
+}
+
+bool SkYUVAPixmaps::toYUVAIndices(SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount]) const {
+    SkASSERT(yuvaIndices);
+    uint32_t channelFlags[] = {SkColorTypeChannelFlags(fPlanes[0].colorType()),
+                               SkColorTypeChannelFlags(fPlanes[1].colorType()),
+                               SkColorTypeChannelFlags(fPlanes[2].colorType()),
+                               SkColorTypeChannelFlags(fPlanes[3].colorType())};
+    bool result = fYUVAInfo.toYUVAIndices(channelFlags, yuvaIndices);
+    SkASSERT(result == this->isValid());
+    return result;
+}
+
 bool SkYUVAPixmaps::toLegacy(SkYUVASizeInfo* yuvaSizeInfo, SkYUVAIndex yuvaIndices[4]) const {
     if (!this->isValid()) {
         return false;
     }
-    bool ok = true;
-    auto getIthChannel = [&ok](SkColorType ct, int idx) -> SkColorChannel {
-      switch (SkColorTypeChannelFlags(ct)) {
-          case kAlpha_SkColorChannelFlag:
-              ok &= idx == 0;
-              return SkColorChannel::kA;
-          case kGray_SkColorChannelFlag:
-          case kRed_SkColorChannelFlag:
-              ok &= idx == 0;
-              return SkColorChannel::kR;
-          case kRG_SkColorChannelFlags:
-              ok &= idx < 2;
-              return static_cast<SkColorChannel>(idx);
-          case kRGB_SkColorChannelFlags:
-              ok &= idx < 3;
-              return static_cast<SkColorChannel>(idx);
-          case kRGBA_SkColorChannelFlags:
-              ok &= idx < 4;
-              return static_cast<SkColorChannel>(idx);
-          default:
-              ok = false;
-              return SkColorChannel::kR;
-      }
-    };
-    SkColorType cts[] = {fPlanes[0].colorType(),
-                         fPlanes[1].colorType(),
-                         fPlanes[2].colorType(),
-                         fPlanes[3].colorType()};
-    switch (fYUVAInfo.planarConfig()) {
-        case SkYUVAInfo::PlanarConfig::kY_U_V_444:
-        case SkYUVAInfo::PlanarConfig::kY_U_V_422:
-        case SkYUVAInfo::PlanarConfig::kY_U_V_420:
-        case SkYUVAInfo::PlanarConfig::kY_U_V_440:
-        case SkYUVAInfo::PlanarConfig::kY_U_V_411:
-        case SkYUVAInfo::PlanarConfig::kY_U_V_410:
-            yuvaIndices[SkYUVAIndex::kY_Index].fIndex =  0;
-            yuvaIndices[SkYUVAIndex::kU_Index].fIndex =  1;
-            yuvaIndices[SkYUVAIndex::kV_Index].fIndex =  2;
-            yuvaIndices[SkYUVAIndex::kA_Index].fIndex = -1;
-            yuvaIndices[SkYUVAIndex::kY_Index].fChannel = getIthChannel(cts[0], 0);
-            yuvaIndices[SkYUVAIndex::kU_Index].fChannel = getIthChannel(cts[1], 0);
-            yuvaIndices[SkYUVAIndex::kV_Index].fChannel = getIthChannel(cts[2], 0);
-            yuvaIndices[SkYUVAIndex::kA_Index].fChannel = SkColorChannel::kR;  // arbitrary
-            break;
-        case SkYUVAInfo::PlanarConfig::kY_V_U_420:
-            yuvaIndices[SkYUVAIndex::kY_Index].fIndex =  0;
-            yuvaIndices[SkYUVAIndex::kU_Index].fIndex =  2;
-            yuvaIndices[SkYUVAIndex::kV_Index].fIndex =  1;
-            yuvaIndices[SkYUVAIndex::kA_Index].fIndex = -1;
-            yuvaIndices[SkYUVAIndex::kY_Index].fChannel = getIthChannel(cts[0], 0);
-            yuvaIndices[SkYUVAIndex::kU_Index].fChannel = getIthChannel(cts[2], 0);
-            yuvaIndices[SkYUVAIndex::kV_Index].fChannel = getIthChannel(cts[1], 0);
-            yuvaIndices[SkYUVAIndex::kA_Index].fChannel = SkColorChannel::kR;  // arbitrary
-            break;
-        case SkYUVAInfo::PlanarConfig::kY_U_V_A_4204:
-            yuvaIndices[SkYUVAIndex::kY_Index].fIndex = 0;
-            yuvaIndices[SkYUVAIndex::kU_Index].fIndex = 1;
-            yuvaIndices[SkYUVAIndex::kV_Index].fIndex = 2;
-            yuvaIndices[SkYUVAIndex::kA_Index].fIndex = 3;
-            yuvaIndices[SkYUVAIndex::kY_Index].fChannel = getIthChannel(cts[0], 0);
-            yuvaIndices[SkYUVAIndex::kU_Index].fChannel = getIthChannel(cts[1], 0);
-            yuvaIndices[SkYUVAIndex::kV_Index].fChannel = getIthChannel(cts[2], 0);
-            yuvaIndices[SkYUVAIndex::kA_Index].fChannel = getIthChannel(cts[3], 0);
-            break;
-        case SkYUVAInfo::PlanarConfig::kY_V_U_A_4204:
-            yuvaIndices[SkYUVAIndex::kY_Index].fIndex = 0;
-            yuvaIndices[SkYUVAIndex::kU_Index].fIndex = 2;
-            yuvaIndices[SkYUVAIndex::kV_Index].fIndex = 1;
-            yuvaIndices[SkYUVAIndex::kA_Index].fIndex = 3;
-            yuvaIndices[SkYUVAIndex::kY_Index].fChannel = getIthChannel(cts[0], 0);
-            yuvaIndices[SkYUVAIndex::kU_Index].fChannel = getIthChannel(cts[2], 0);
-            yuvaIndices[SkYUVAIndex::kV_Index].fChannel = getIthChannel(cts[1], 0);
-            yuvaIndices[SkYUVAIndex::kA_Index].fChannel = getIthChannel(cts[3], 0);
-            break;
-        case SkYUVAInfo::PlanarConfig::kY_UV_420:
-            yuvaIndices[SkYUVAIndex::kY_Index].fIndex =  0;
-            yuvaIndices[SkYUVAIndex::kU_Index].fIndex =  1;
-            yuvaIndices[SkYUVAIndex::kV_Index].fIndex =  1;
-            yuvaIndices[SkYUVAIndex::kA_Index].fIndex = -1;
-            yuvaIndices[SkYUVAIndex::kY_Index].fChannel = getIthChannel(cts[0], 0);
-            yuvaIndices[SkYUVAIndex::kU_Index].fChannel = getIthChannel(cts[1], 0);
-            yuvaIndices[SkYUVAIndex::kV_Index].fChannel = getIthChannel(cts[1], 1);
-            yuvaIndices[SkYUVAIndex::kA_Index].fChannel = SkColorChannel::kR;  // arbitrary
-            break;
-        case SkYUVAInfo::PlanarConfig::kY_VU_420:
-            yuvaIndices[SkYUVAIndex::kY_Index].fIndex =  0;
-            yuvaIndices[SkYUVAIndex::kU_Index].fIndex =  1;
-            yuvaIndices[SkYUVAIndex::kV_Index].fIndex =  1;
-            yuvaIndices[SkYUVAIndex::kA_Index].fIndex = -1;
-            yuvaIndices[SkYUVAIndex::kY_Index].fChannel = getIthChannel(cts[0], 0);
-            yuvaIndices[SkYUVAIndex::kU_Index].fChannel = getIthChannel(cts[1], 1);
-            yuvaIndices[SkYUVAIndex::kV_Index].fChannel = getIthChannel(cts[1], 0);
-            yuvaIndices[SkYUVAIndex::kA_Index].fChannel = SkColorChannel::kR;  // arbitrary
-            break;
-        case SkYUVAInfo::PlanarConfig::kY_UV_A_4204:
-            yuvaIndices[SkYUVAIndex::kY_Index].fIndex = 0;
-            yuvaIndices[SkYUVAIndex::kU_Index].fIndex = 1;
-            yuvaIndices[SkYUVAIndex::kV_Index].fIndex = 1;
-            yuvaIndices[SkYUVAIndex::kA_Index].fIndex = 2;
-            yuvaIndices[SkYUVAIndex::kY_Index].fChannel = getIthChannel(cts[0], 0);
-            yuvaIndices[SkYUVAIndex::kU_Index].fChannel = getIthChannel(cts[1], 0);
-            yuvaIndices[SkYUVAIndex::kV_Index].fChannel = getIthChannel(cts[1], 1);
-            yuvaIndices[SkYUVAIndex::kA_Index].fChannel = getIthChannel(cts[2], 0);
-            break;
-        case SkYUVAInfo::PlanarConfig::kY_VU_A_4204:
-            yuvaIndices[SkYUVAIndex::kY_Index].fIndex = 0;
-            yuvaIndices[SkYUVAIndex::kU_Index].fIndex = 1;
-            yuvaIndices[SkYUVAIndex::kV_Index].fIndex = 1;
-            yuvaIndices[SkYUVAIndex::kA_Index].fIndex = 2;
-            yuvaIndices[SkYUVAIndex::kY_Index].fChannel = getIthChannel(cts[0], 0);
-            yuvaIndices[SkYUVAIndex::kU_Index].fChannel = getIthChannel(cts[1], 1);
-            yuvaIndices[SkYUVAIndex::kV_Index].fChannel = getIthChannel(cts[1], 0);
-            yuvaIndices[SkYUVAIndex::kA_Index].fChannel = getIthChannel(cts[2], 0);
-            break;
-        case SkYUVAInfo::PlanarConfig::kYUV_444:
-            yuvaIndices[SkYUVAIndex::kY_Index].fIndex =  0;
-            yuvaIndices[SkYUVAIndex::kU_Index].fIndex =  0;
-            yuvaIndices[SkYUVAIndex::kV_Index].fIndex =  0;
-            yuvaIndices[SkYUVAIndex::kA_Index].fIndex = -1;
-            yuvaIndices[SkYUVAIndex::kY_Index].fChannel = getIthChannel(cts[0], 0);
-            yuvaIndices[SkYUVAIndex::kU_Index].fChannel = getIthChannel(cts[0], 1);
-            yuvaIndices[SkYUVAIndex::kV_Index].fChannel = getIthChannel(cts[0], 2);
-            yuvaIndices[SkYUVAIndex::kA_Index].fChannel = SkColorChannel::kR;  // arbitrary
-            break;
-        case SkYUVAInfo::PlanarConfig::kUYV_444:
-            yuvaIndices[SkYUVAIndex::kY_Index].fIndex =  0;
-            yuvaIndices[SkYUVAIndex::kU_Index].fIndex =  0;
-            yuvaIndices[SkYUVAIndex::kV_Index].fIndex =  0;
-            yuvaIndices[SkYUVAIndex::kA_Index].fIndex = -1;
-            yuvaIndices[SkYUVAIndex::kY_Index].fChannel = getIthChannel(cts[0], 1);
-            yuvaIndices[SkYUVAIndex::kU_Index].fChannel = getIthChannel(cts[0], 0);
-            yuvaIndices[SkYUVAIndex::kV_Index].fChannel = getIthChannel(cts[0], 2);
-            yuvaIndices[SkYUVAIndex::kA_Index].fChannel = SkColorChannel::kR;  // arbitrary
-            break;
-        case SkYUVAInfo::PlanarConfig::kYUVA_4444:
-            yuvaIndices[SkYUVAIndex::kY_Index].fIndex = 0;
-            yuvaIndices[SkYUVAIndex::kU_Index].fIndex = 0;
-            yuvaIndices[SkYUVAIndex::kV_Index].fIndex = 0;
-            yuvaIndices[SkYUVAIndex::kA_Index].fIndex = 0;
-            yuvaIndices[SkYUVAIndex::kY_Index].fChannel = getIthChannel(cts[0], 0);
-            yuvaIndices[SkYUVAIndex::kU_Index].fChannel = getIthChannel(cts[0], 1);
-            yuvaIndices[SkYUVAIndex::kV_Index].fChannel = getIthChannel(cts[0], 2);
-            yuvaIndices[SkYUVAIndex::kA_Index].fChannel = getIthChannel(cts[0], 3);
-            break;
-        case SkYUVAInfo::PlanarConfig::kUYVA_4444:
-            yuvaIndices[SkYUVAIndex::kY_Index].fIndex = 0;
-            yuvaIndices[SkYUVAIndex::kU_Index].fIndex = 0;
-            yuvaIndices[SkYUVAIndex::kV_Index].fIndex = 0;
-            yuvaIndices[SkYUVAIndex::kA_Index].fIndex = 0;
-            yuvaIndices[SkYUVAIndex::kY_Index].fChannel = getIthChannel(cts[0], 1);
-            yuvaIndices[SkYUVAIndex::kU_Index].fChannel = getIthChannel(cts[0], 0);
-            yuvaIndices[SkYUVAIndex::kV_Index].fChannel = getIthChannel(cts[0], 2);
-            yuvaIndices[SkYUVAIndex::kA_Index].fChannel = getIthChannel(cts[0], 3);
-            break;
+    SkYUVAIndex tempIndices[4];
+    if (!yuvaIndices) {
+        yuvaIndices = tempIndices;
     }
-    if (!ok) {
+    if (!this->toYUVAIndices(yuvaIndices)) {
         return false;
     }
+
     if (yuvaSizeInfo) {
         yuvaSizeInfo->fOrigin = fYUVAInfo.origin();
         int n = fYUVAInfo.numPlanes();
diff --git a/tools/gpu/ManagedBackendTexture.cpp b/tools/gpu/ManagedBackendTexture.cpp
index 192a57c..a1a7449 100644
--- a/tools/gpu/ManagedBackendTexture.cpp
+++ b/tools/gpu/ManagedBackendTexture.cpp
@@ -14,9 +14,9 @@
 namespace {
 
 struct Context {
-    sk_sp<sk_gpu_test::ManagedBackendTexture> fMBET;
     GrGpuFinishedProc fWrappedProc = nullptr;
     GrGpuFinishedContext fWrappedContext = nullptr;
+    sk_sp<sk_gpu_test::ManagedBackendTexture> fMBETs[SkYUVAInfo::kMaxPlanes];
 };
 
 }  // anonymous namespace
@@ -40,7 +40,16 @@
                                             GrGpuFinishedContext wrappedCtx) const {
     // Make sure we don't get a wrapped ctx without a wrapped proc
     SkASSERT(!wrappedCtx || wrappedProc);
-    return new Context{sk_ref_sp(this), wrappedProc, wrappedCtx};
+    return new Context{wrappedProc, wrappedCtx, {sk_ref_sp(this)}};
+}
+
+void* ManagedBackendTexture::MakeYUVAReleaseContext(
+        const sk_sp<ManagedBackendTexture> mbets[SkYUVAInfo::kMaxPlanes]) {
+    auto context = new Context;
+    for (int i = 0; i < SkYUVAInfo::kMaxPlanes; ++i) {
+        context->fMBETs[i] = mbets[i];
+    }
+    return context;
 }
 
 sk_sp<GrRefCntedCallback> ManagedBackendTexture::refCountedCallback() const {
diff --git a/tools/gpu/ManagedBackendTexture.h b/tools/gpu/ManagedBackendTexture.h
index 931e71b..7c65d54 100644
--- a/tools/gpu/ManagedBackendTexture.h
+++ b/tools/gpu/ManagedBackendTexture.h
@@ -9,6 +9,7 @@
 #define ManagedBackendTexture_DEFINED
 
 #include "include/core/SkRefCnt.h"
+#include "include/core/SkYUVAInfo.h"
 #include "include/gpu/GrDirectContext.h"
 
 class GrRefCntedCallback;
@@ -70,6 +71,14 @@
      */
     void wasAdopted();
 
+    /**
+     * SkImage::MakeFromYUVATextures takes a single release proc that is called once for all the
+     * textures. This makes a single release context for the group of textures. It's used with the
+     * standard ReleaseProc. Like releaseContext(), it must be balanced by a ReleaseProc call for
+     * proper ref counting.
+     */
+    static void* MakeYUVAReleaseContext(const sk_sp<ManagedBackendTexture>[SkYUVAInfo::kMaxPlanes]);
+
     const GrBackendTexture& texture() { return fTexture; }
 
 private:
diff --git a/tools/gpu/YUVUtils.cpp b/tools/gpu/YUVUtils.cpp
index 4e8ad24..93c011b 100644
--- a/tools/gpu/YUVUtils.cpp
+++ b/tools/gpu/YUVUtils.cpp
@@ -7,40 +7,154 @@
 
 #include "tools/gpu/YUVUtils.h"
 
+#include "include/core/SkColorPriv.h"
 #include "include/core/SkData.h"
 #include "include/gpu/GrRecordingContext.h"
 #include "src/codec/SkCodecImageGenerator.h"
+#include "src/core/SkYUVMath.h"
 #include "src/gpu/GrDirectContextPriv.h"
 #include "src/gpu/GrRecordingContextPriv.h"
+#include "tools/gpu/ManagedBackendTexture.h"
+
+namespace {
+
+static SkPMColor convert_yuva_to_rgba(const float mtx[20], uint8_t yuva[4]) {
+    uint8_t y = yuva[0];
+    uint8_t u = yuva[1];
+    uint8_t v = yuva[2];
+    uint8_t a = yuva[3];
+
+    uint8_t r = SkTPin(SkScalarRoundToInt(mtx[ 0]*y + mtx[ 1]*u + mtx[ 2]*v + mtx[ 4]*255), 0, 255);
+    uint8_t g = SkTPin(SkScalarRoundToInt(mtx[ 5]*y + mtx[ 6]*u + mtx[ 7]*v + mtx[ 9]*255), 0, 255);
+    uint8_t b = SkTPin(SkScalarRoundToInt(mtx[10]*y + mtx[11]*u + mtx[12]*v + mtx[14]*255), 0, 255);
+
+    return SkPremultiplyARGBInline(a, r, g, b);
+}
+
+static uint8_t look_up(float x1, float y1, const SkPixmap& pmap, SkColorChannel channel) {
+    SkASSERT(x1 > 0 && x1 < 1.0f);
+    SkASSERT(y1 > 0 && y1 < 1.0f);
+    int x = SkScalarFloorToInt(x1 * pmap.width());
+    int y = SkScalarFloorToInt(y1 * pmap.height());
+
+    auto ii = pmap.info().makeColorType(kRGBA_8888_SkColorType).makeWH(1, 1);
+    uint32_t pixel;
+    SkAssertResult(pmap.readPixels(ii, &pixel, sizeof(pixel), x, y));
+    int shift = static_cast<int>(channel) * 8;
+    return static_cast<uint8_t>((pixel >> shift) & 0xff);
+}
+
+class Generator : public SkImageGenerator {
+public:
+    Generator(SkYUVAPixmaps pixmaps, sk_sp<SkColorSpace> cs)
+            : SkImageGenerator(SkImageInfo::Make(pixmaps.yuvaInfo().dimensions(),
+                                                 kN32_SkColorType,
+                                                 kPremul_SkAlphaType,
+                                                 std::move(cs)))
+            , fPixmaps(std::move(pixmaps)) {}
+
+protected:
+    bool onGetPixels(const SkImageInfo& info,
+                     void* pixels,
+                     size_t rowBytes,
+                     const Options&) override {
+        if (kUnknown_SkColorType == fFlattened.colorType()) {
+            fFlattened.allocPixels(info);
+            SkASSERT(info == this->getInfo());
+
+            float mtx[20];
+            SkColorMatrix_YUV2RGB(fPixmaps.yuvaInfo().yuvColorSpace(), mtx);
+            SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount];
+            SkAssertResult(fPixmaps.toYUVAIndices(yuvaIndices));
+
+            for (int y = 0; y < info.height(); ++y) {
+                for (int x = 0; x < info.width(); ++x) {
+                    float x1 = (x + 0.5f) / info.width();
+                    float y1 = (y + 0.5f) / info.height();
+
+                    uint8_t yuva[4] = {0, 0, 0, 255};
+
+                    for (auto c : {SkYUVAIndex::kY_Index,
+                                   SkYUVAIndex::kU_Index,
+                                   SkYUVAIndex::kV_Index}) {
+                        const auto& pmap = fPixmaps.plane(yuvaIndices[c].fIndex);
+                        yuva[c] = look_up(x1, y1, pmap, yuvaIndices[c].fChannel);
+                    }
+                    if (yuvaIndices[SkYUVAIndex::kA_Index].fIndex >= 0) {
+                        const auto& pmap =
+                                fPixmaps.plane(yuvaIndices[SkYUVAIndex::kA_Index].fIndex);
+                        yuva[3] =
+                                look_up(x1, y1, pmap, yuvaIndices[SkYUVAIndex::kA_Index].fChannel);
+                    }
+
+                    // Making premul here.
+                    *fFlattened.getAddr32(x, y) = convert_yuva_to_rgba(mtx, yuva);
+                }
+            }
+        }
+
+        return fFlattened.readPixels(info, pixels, rowBytes, 0, 0);
+    }
+
+    bool onQueryYUVAInfo(const SkYUVAPixmapInfo::SupportedDataTypes& types,
+                         SkYUVAPixmapInfo* info) const override {
+        *info = fPixmaps.pixmapsInfo();
+        return info->isValid();
+    }
+
+    bool onGetYUVAPlanes(const SkYUVAPixmaps& pixmaps) override {
+        SkASSERT(pixmaps.yuvaInfo() == fPixmaps.yuvaInfo());
+        for (int i = 0; i < pixmaps.numPlanes(); ++i) {
+            SkASSERT(fPixmaps.plane(i).colorType() == pixmaps.plane(i).colorType());
+            SkASSERT(fPixmaps.plane(i).dimensions() == pixmaps.plane(i).dimensions());
+            SkASSERT(fPixmaps.plane(i).rowBytes() == pixmaps.plane(i).rowBytes());
+            fPixmaps.plane(i).readPixels(pixmaps.plane(i));
+        }
+        return true;
+    }
+
+private:
+    SkYUVAPixmaps fPixmaps;
+    SkBitmap      fFlattened;
+};
+
+}  // anonymous namespace
 
 namespace sk_gpu_test {
 
-std::unique_ptr<LazyYUVImage> LazyYUVImage::Make(sk_sp<SkData> data, GrMipmapped mipmapped) {
+std::unique_ptr<LazyYUVImage> LazyYUVImage::Make(sk_sp<SkData> data,
+                                                 GrMipmapped mipmapped,
+                                                 sk_sp<SkColorSpace> cs) {
     std::unique_ptr<LazyYUVImage> image(new LazyYUVImage());
-    if (image->reset(std::move(data), mipmapped)) {
+    if (image->reset(std::move(data), mipmapped, std::move(cs))) {
         return image;
     } else {
         return nullptr;
     }
 }
 
-sk_sp<SkImage> LazyYUVImage::refImage(GrRecordingContext* rContext) {
-    if (this->ensureYUVImage(rContext)) {
-        return fYUVImage;
+std::unique_ptr<LazyYUVImage> LazyYUVImage::Make(SkYUVAPixmaps pixmaps,
+                                                 GrMipmapped mipmapped,
+                                                 sk_sp<SkColorSpace> cs) {
+    std::unique_ptr<LazyYUVImage> image(new LazyYUVImage());
+    if (image->reset(std::move(pixmaps), mipmapped, std::move(cs))) {
+        return image;
     } else {
         return nullptr;
     }
 }
 
-const SkImage* LazyYUVImage::getImage(GrRecordingContext* rContext) {
-    if (this->ensureYUVImage(rContext)) {
-        return fYUVImage.get();
+sk_sp<SkImage> LazyYUVImage::refImage(GrRecordingContext* rContext, Type type) {
+    if (this->ensureYUVImage(rContext, type)) {
+        size_t idx = static_cast<size_t>(type);
+        SkASSERT(idx >= 0 && idx < SK_ARRAY_COUNT(fYUVImage));
+        return fYUVImage[idx];
     } else {
         return nullptr;
     }
 }
 
-bool LazyYUVImage::reset(sk_sp<SkData> data, GrMipmapped mipmapped) {
+bool LazyYUVImage::reset(sk_sp<SkData> data, GrMipmapped mipmapped, sk_sp<SkColorSpace> cs) {
     fMipmapped = mipmapped;
     auto codec = SkCodecImageGenerator::MakeFromEncodedCodec(data);
     if (!codec) {
@@ -60,78 +174,87 @@
         return false;
     }
 
-    if (!fPixmaps.toLegacy(&fSizeInfo, fComponents)) {
-        return false;
-    }
+    fColorSpace = std::move(cs);
+
     // The SkPixmap data is fully configured now for MakeFromYUVAPixmaps once we get a GrContext
     return true;
 }
 
-bool LazyYUVImage::ensureYUVImage(GrRecordingContext* rContext) {
-    if (!rContext) {
-        return false; // Cannot make a YUV image from planes
+bool LazyYUVImage::reset(SkYUVAPixmaps pixmaps, GrMipmapped mipmapped, sk_sp<SkColorSpace> cs) {
+    if (!pixmaps.isValid()) {
+        return false;
     }
-    if (fYUVImage && fYUVImage->isValid(rContext)) {
-        return true; // Have already made a YUV image valid for this context.
+    fMipmapped = mipmapped;
+    if (pixmaps.ownsStorage()) {
+        fPixmaps = std::move(pixmaps);
+    } else {
+        fPixmaps = SkYUVAPixmaps::MakeCopy(std::move(pixmaps));
+    }
+    fColorSpace = std::move(cs);
+    // The SkPixmap data is fully configured now for MakeFromYUVAPixmaps once we get a GrContext
+    return true;
+}
+
+bool LazyYUVImage::ensureYUVImage(GrRecordingContext* rContext, Type type) {
+    size_t idx = static_cast<size_t>(type);
+    SkASSERT(idx >= 0 && idx < SK_ARRAY_COUNT(fYUVImage));
+    if (fYUVImage[idx] && fYUVImage[idx]->isValid(rContext)) {
+        return true;  // Have already made a YUV image valid for this context.
     }
     // Try to make a new YUV image for this context.
-    fYUVImage = SkImage::MakeFromYUVAPixmaps(rContext, fPixmaps, fMipmapped, false, nullptr);
-    return fYUVImage != nullptr;
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-void YUVABackendReleaseContext::Unwind(GrDirectContext* dContext,
-                                       YUVABackendReleaseContext* beContext,
-                                       bool fullFlush) {
-
-    // Some backends (e.g., Vulkan) require that all work associated w/ texture
-    // creation be completed before deleting the textures.
-    if (fullFlush) {
-        // If the release context client performed some operations other than backend texture
-        // creation then we may require a full flush to ensure that all the work is completed.
-        dContext->flush();
-        dContext->submit(true);
-    } else {
-        dContext->submit();
-
-        while (!beContext->creationCompleted()) {
-            dContext->checkAsyncWorkCompletion();
+    switch (type) {
+        case Type::kFromPixmaps:
+            if (!rContext || rContext->abandoned()) {
+                return false;
+            }
+            fYUVImage[idx] = SkImage::MakeFromYUVAPixmaps(rContext,
+                                                          fPixmaps,
+                                                          fMipmapped,
+                                                          /*limit to max tex size*/ false,
+                                                          fColorSpace);
+            break;
+        case Type::kFromGenerator: {
+            // Make sure the generator has ownership of its backing planes.
+            auto generator = std::make_unique<Generator>(fPixmaps, fColorSpace);
+            fYUVImage[idx] = SkImage::MakeFromGenerator(std::move(generator));
+            break;
         }
+        case Type::kFromTextures:
+            if (!rContext || rContext->abandoned()) {
+                return false;
+            }
+            if (auto direct = rContext->asDirectContext()) {
+                sk_sp<sk_gpu_test::ManagedBackendTexture> mbets[SkYUVAInfo::kMaxPlanes];
+                GrBackendTexture textures[SkYUVAInfo::kMaxPlanes];
+                uint32_t componentFlags[SkYUVAInfo::kMaxPlanes] = {};
+                for (int i = 0; i < fPixmaps.numPlanes(); ++i) {
+                    mbets[i] = sk_gpu_test::ManagedBackendTexture::MakeWithData(
+                            direct, fPixmaps.plane(i), GrRenderable::kNo, GrProtected::kNo);
+                    if (mbets[i]) {
+                        textures[i] = mbets[i]->texture();
+                        componentFlags[i] = textures[i].getBackendFormat().channelMask();
+                    } else {
+                        return false;
+                    }
+                }
+                SkYUVAIndex indices[SkYUVAIndex::kIndexCount];
+                if (!fPixmaps.yuvaInfo().toYUVAIndices(componentFlags, indices)) {
+                    return false;
+                }
+                void* relContext =
+                        sk_gpu_test::ManagedBackendTexture::MakeYUVAReleaseContext(mbets);
+                fYUVImage[idx] = SkImage::MakeFromYUVATextures(
+                        direct,
+                        fPixmaps.yuvaInfo().yuvColorSpace(),
+                        textures,
+                        indices,
+                        fPixmaps.yuvaInfo().dimensions(),
+                        kTopLeft_GrSurfaceOrigin,
+                        fColorSpace,
+                        sk_gpu_test::ManagedBackendTexture::ReleaseProc,
+                        relContext);
+            }
     }
-
-    delete beContext;
+    return fYUVImage[idx] != nullptr;
 }
-
-YUVABackendReleaseContext::YUVABackendReleaseContext(GrDirectContext* dContext)
-        : fDContext(dContext) {
-}
-
-YUVABackendReleaseContext::~YUVABackendReleaseContext() {
-    for (int i = 0; i < 4; ++i) {
-        if (fBETextures[i].isValid()) {
-            SkASSERT(fCreationComplete[i]);
-            fDContext->deleteBackendTexture(fBETextures[i]);
-        }
-    }
-}
-
-template<int I> static void CreationComplete(void* releaseContext) {
-    auto beContext = reinterpret_cast<YUVABackendReleaseContext*>(releaseContext);
-    beContext->setCreationComplete(I);
-}
-
-GrGpuFinishedProc YUVABackendReleaseContext::CreationCompleteProc(int index) {
-    SkASSERT(index >= 0 && index < 4);
-
-    switch (index) {
-        case 0: return CreationComplete<0>;
-        case 1: return CreationComplete<1>;
-        case 2: return CreationComplete<2>;
-        case 3: return CreationComplete<3>;
-    }
-
-    SK_ABORT("Invalid YUVA Index.");
-    return nullptr;
-}
-
 } // namespace sk_gpu_test
diff --git a/tools/gpu/YUVUtils.h b/tools/gpu/YUVUtils.h
index 8363032..e9dad4f 100644
--- a/tools/gpu/YUVUtils.h
+++ b/tools/gpu/YUVUtils.h
@@ -25,93 +25,43 @@
 // the image if the context has changed, as in Viewer)
 class LazyYUVImage {
 public:
-    // Returns null if the data could not be extracted into YUVA8 planes
-    static std::unique_ptr<LazyYUVImage> Make(sk_sp<SkData> data, GrMipmapped = GrMipmapped::kNo);
+    // Returns null if the data could not be extracted into YUVA planes
+    static std::unique_ptr<LazyYUVImage> Make(sk_sp<SkData> data,
+                                              GrMipmapped = GrMipmapped::kNo,
+                                              sk_sp<SkColorSpace> = nullptr);
+    static std::unique_ptr<LazyYUVImage> Make(SkYUVAPixmaps,
+                                              GrMipmapped = GrMipmapped::kNo,
+                                              sk_sp<SkColorSpace> = nullptr);
 
-    sk_sp<SkImage> refImage(GrRecordingContext* rContext);
+    enum class Type {
+        kFromPixmaps,
+        kFromGenerator,
+        kFromTextures,
+    };
 
-    const SkImage* getImage(GrRecordingContext* rContext);
+    SkISize dimensions() const { return fPixmaps.yuvaInfo().dimensions(); }
+
+    sk_sp<SkImage> refImage(GrRecordingContext* rContext, Type);
 
 private:
     // Decoded YUV data
     SkYUVAPixmaps fPixmaps;
 
-    // Legacy representation used to import to SkImage.
-    SkYUVASizeInfo fSizeInfo;
-    SkYUVAIndex fComponents[SkYUVAIndex::kIndexCount];
-
     GrMipmapped fMipmapped;
 
-    // Memoized SkImage formed with planes
-    sk_sp<SkImage> fYUVImage;
+    sk_sp<SkColorSpace> fColorSpace;
+
+    // Memoized SkImages formed with planes, one for each Type.
+    sk_sp<SkImage> fYUVImage[3];
 
     LazyYUVImage() = default;
 
-    bool reset(sk_sp<SkData> data, GrMipmapped);
+    bool reset(sk_sp<SkData> data, GrMipmapped, sk_sp<SkColorSpace>);
+    bool reset(SkYUVAPixmaps pixmaps, GrMipmapped, sk_sp<SkColorSpace>);
 
-    bool ensureYUVImage(GrRecordingContext* rContext);
+    bool ensureYUVImage(GrRecordingContext* rContext, Type type);
 };
 
-// A helper for managing the lifetime of backend textures for YUVA images.
-class YUVABackendReleaseContext {
-public:
-    static GrGpuFinishedProc CreationCompleteProc(int index);
-
-    // A stock 'TextureReleaseProc' to use with this class
-    static void Release(void* releaseContext) {
-        auto beContext = reinterpret_cast<YUVABackendReleaseContext*>(releaseContext);
-
-        delete beContext;
-    }
-
-    // Given how and when backend textures are created, just deleting this object often
-    // isn't enough. This helper encapsulates the extra work needed.
-    static void Unwind(GrDirectContext*, YUVABackendReleaseContext* beContext, bool fullFlush);
-
-    YUVABackendReleaseContext(GrDirectContext*);
-    ~YUVABackendReleaseContext();
-
-    void set(int index, const GrBackendTexture& beTex) {
-        SkASSERT(index >= 0 && index < 4);
-        SkASSERT(!fBETextures[index].isValid());
-        SkASSERT(beTex.isValid());
-
-        fBETextures[index] = beTex;
-    }
-
-    void setCreationComplete(int index) {
-        SkASSERT(index >= 0 && index < 4);
-        // In GL, the finished proc can fire before the backend texture is returned to the client
-        // SkASSERT(fBETextures[index].isValid());
-
-        fCreationComplete[index] = true;
-    }
-
-    bool creationCompleted() const {
-        for (int i = 0; i < 4; ++i) {
-            if (fBETextures[i].isValid() && !fCreationComplete[i]) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    const GrBackendTexture* beTextures() const { return fBETextures; }
-
-    const GrBackendTexture& beTexture(int index) {
-        SkASSERT(index >= 0 && index < 4);
-        SkASSERT(fBETextures[index].isValid());
-        return fBETextures[index];
-    }
-
-private:
-    GrDirectContext* fDContext;
-    GrBackendTexture fBETextures[4];
-    bool             fCreationComplete[4] = { false };
-};
-
-
 } // namespace sk_gpu_test
 
 #endif // YUVUtils_DEFINED