Reland "Push SkYUVAInfo into GrYUVToRGBEffect."

This is a reland of b60255033ddeb6a7e80f5699b0883b580d30dcbf

Original change's description:
> Push SkYUVAInfo into GrYUVToRGBEffect.
>
> Wrap up SkYUVAInfo and proxies into new type GrYUVATextureProxies.
>
> Bug: skia:10632
> Change-Id: Ic907d78a1a40af3c8ef838021749839c422d62dc
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/353042
> Commit-Queue: Brian Salomon <bsalomon@google.com>
> Reviewed-by: Jim Van Verth <jvanverth@google.com>

Bug: skia:10632
Change-Id: I1878609153e3fc763620cb71a85d3b012f915155
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/353621
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/gm/yuvtorgbsubset.cpp b/gm/yuvtorgbsubset.cpp
index 642dc35..04081c3 100644
--- a/gm/yuvtorgbsubset.cpp
+++ b/gm/yuvtorgbsubset.cpp
@@ -16,14 +16,15 @@
 #include "include/core/SkScalar.h"
 #include "include/core/SkSize.h"
 #include "include/core/SkString.h"
-#include "include/private/GrTypesPriv.h"
-#include "src/core/SkYUVAInfoLocation.h"
+#include "include/core/SkYUVAInfo.h"
+#include "include/core/SkYUVAPixmaps.h"
 #include "src/gpu/GrBitmapTextureMaker.h"
 #include "src/gpu/GrDirectContextPriv.h"
 #include "src/gpu/GrPaint.h"
 #include "src/gpu/GrSamplerState.h"
 #include "src/gpu/GrSurfaceDrawContext.h"
 #include "src/gpu/GrTextureProxy.h"
+#include "src/gpu/GrYUVATextureProxies.h"
 #include "src/gpu/effects/GrYUVtoRGBEffect.h"
 
 #include <memory>
@@ -31,10 +32,6 @@
 
 class SkCanvas;
 
-#define YSIZE 8
-#define USIZE 4
-#define VSIZE 4
-
 namespace skiagm {
 
 //////////////////////////////////////////////////////////////////////////////
@@ -55,13 +52,16 @@
 
     SkISize onISize() override { return {1310, 540}; }
 
-    void onOnceBeforeDraw() override {
-        SkImageInfo yinfo = SkImageInfo::MakeA8(YSIZE, YSIZE);
-        fBitmaps[0].allocPixels(yinfo);
-        SkImageInfo uinfo = SkImageInfo::MakeA8(USIZE, USIZE);
-        fBitmaps[1].allocPixels(uinfo);
-        SkImageInfo vinfo = SkImageInfo::MakeA8(VSIZE, VSIZE);
-        fBitmaps[2].allocPixels(vinfo);
+    void makePixmaps() {
+        SkYUVAInfo yuvaInfo = SkYUVAInfo({8, 8},
+                                         SkYUVAInfo::PlaneConfig::kY_U_V,
+                                         SkYUVAInfo::Subsampling::k420,
+                                         kJPEG_Full_SkYUVColorSpace);
+        SkColorType colorTypes[] = {kAlpha_8_SkColorType,
+                                    kAlpha_8_SkColorType,
+                                    kAlpha_8_SkColorType};
+        SkYUVAPixmapInfo pmapInfo(yuvaInfo, colorTypes, nullptr);
+        fPixmaps = SkYUVAPixmaps::Allocate(pmapInfo);
 
         unsigned char innerY[16] = {149, 160, 130, 105,
                                     160, 130, 105, 149,
@@ -70,45 +70,62 @@
         unsigned char innerU[4] = {43, 75, 145, 200};
         unsigned char innerV[4] = {88, 180, 200, 43};
         int outerYUV[] = {128, 128, 128};
+        SkBitmap bitmaps[3];
         for (int i = 0; i < 3; ++i) {
-            fBitmaps[i].eraseColor(SkColorSetARGB(outerYUV[i], 0, 0, 0));
+            bitmaps[i].installPixels(fPixmaps.plane(i));
+            bitmaps[i].eraseColor(SkColorSetARGB(outerYUV[i], 0, 0, 0));
         }
         SkPixmap innerYPM(SkImageInfo::MakeA8(4, 4), innerY, 4);
         SkPixmap innerUPM(SkImageInfo::MakeA8(2, 2), innerU, 2);
         SkPixmap innerVPM(SkImageInfo::MakeA8(2, 2), innerV, 2);
-        fBitmaps[0].writePixels(innerYPM, 2, 2);
-        fBitmaps[1].writePixels(innerUPM, 1, 1);
-        fBitmaps[2].writePixels(innerVPM, 1, 1);
-        for (auto& fBitmap : fBitmaps) {
-            fBitmap.setImmutable();
-        }
+        bitmaps[0].writePixels(innerYPM, 2, 2);
+        bitmaps[1].writePixels(innerUPM, 1, 1);
+        bitmaps[2].writePixels(innerVPM, 1, 1);
     }
 
-    DrawResult onDraw(GrRecordingContext* context, GrSurfaceDrawContext* surfaceDrawContext,
-                      SkCanvas* canvas, SkString* errorMsg) override {
-        GrSurfaceProxyView views[3];
-
-        for (int i = 0; i < 3; ++i) {
-            GrBitmapTextureMaker maker(context, fBitmaps[i], GrImageTexGenPolicy::kDraw);
+    DrawResult onGpuSetup(GrDirectContext* context, SkString* errorMsg) override {
+        if (!context) {
+            return DrawResult::kSkip;
+        }
+        if (!fPixmaps.isValid()) {
+            this->makePixmaps();
+        }
+        GrSurfaceProxyView views[SkYUVAInfo::kMaxPlanes];
+        GrColorType colorTypes[SkYUVAInfo::kMaxPlanes];
+        for (int i = 0; i < fPixmaps.numPlanes(); ++i) {
+            SkBitmap bitmap;
+            bitmap.installPixels(fPixmaps.plane(i));
+            bitmap.setImmutable();
+            GrBitmapTextureMaker maker(
+                    context, bitmap, GrImageTexGenPolicy::kNew_Uncached_Budgeted);
             views[i] = maker.view(GrMipmapped::kNo);
             if (!views[i]) {
                 *errorMsg = "Failed to create proxy";
-                return DrawResult::kFail;
+                return context->abandoned() ? DrawResult::kSkip : DrawResult::kFail;
             }
+            colorTypes[i] = SkColorTypeToGrColorType(bitmap.colorType());
         }
+        fProxies = GrYUVATextureProxies(fPixmaps.yuvaInfo(), views, colorTypes);
+        if (!fProxies.isValid()) {
+            *errorMsg = "Failed to create GrYUVATextureProxies";
+            return DrawResult::kFail;
+        }
+        return DrawResult::kOk;
+    }
 
+    void onGpuTeardown() override { fProxies = {}; }
+
+    DrawResult onDraw(GrRecordingContext* context,
+                      GrSurfaceDrawContext* surfaceDrawContext,
+                      SkCanvas* canvas,
+                      SkString* errorMsg) override {
         static const GrSamplerState::Filter kFilters[] = {GrSamplerState::Filter::kNearest,
                                                           GrSamplerState::Filter::kLinear};
         static const SkRect kColorRect = SkRect::MakeLTRB(2.f, 2.f, 6.f, 6.f);
 
-        SkYUVAInfo::YUVALocations yuvaLocations = {{
-            { 0, SkColorChannel::kA},
-            { 1, SkColorChannel::kA},
-            { 2, SkColorChannel::kA},
-            {-1, SkColorChannel::kA}
-        }};
         // Outset to visualize wrap modes.
-        SkRect rect = SkRect::MakeWH(YSIZE, YSIZE).makeOutset(YSIZE/2, YSIZE/2);
+        SkRect rect = SkRect::Make(fProxies.yuvaInfo().dimensions());
+        rect = rect.makeOutset(fProxies.yuvaInfo().width()/2.f, fProxies.yuvaInfo().height()/2.f);
 
         SkScalar y = kTestPad;
         // Rows are filter modes.
@@ -129,9 +146,8 @@
                     samplerState.setWrapModeY(wm);
                 }
                 const auto& caps = *context->priv().caps();
-                std::unique_ptr<GrFragmentProcessor> fp(
-                        GrYUVtoRGBEffect::Make(views, yuvaLocations, kJPEG_SkYUVColorSpace,
-                                               samplerState, caps, SkMatrix::I(), subset));
+                std::unique_ptr<GrFragmentProcessor> fp =
+                        GrYUVtoRGBEffect::Make(fProxies, samplerState, caps, SkMatrix::I(), subset);
                 if (fp) {
                     GrPaint grPaint;
                     grPaint.setColorFragmentProcessor(std::move(fp));
@@ -145,10 +161,11 @@
         }
 
         return DrawResult::kOk;
-     }
+    }
 
 private:
-    SkBitmap fBitmaps[3];
+    SkYUVAPixmaps fPixmaps;
+    GrYUVATextureProxies fProxies;
 
     static constexpr SkScalar kTestPad = 10.f;
 
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 6bff75a..daa2e41 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -277,6 +277,8 @@
   "$_src/gpu/GrXferProcessor.cpp",
   "$_src/gpu/GrXferProcessor.h",
   "$_src/gpu/GrYUVABackendTextures.cpp",
+  "$_src/gpu/GrYUVATextureProxies.cpp",
+  "$_src/gpu/GrYUVATextureProxies.h",
 
   # Ops
   "$_src/gpu/effects/GrBezierEffect.cpp",
diff --git a/include/core/SkYUVAInfo.h b/include/core/SkYUVAInfo.h
index 3683335..a3cf210 100644
--- a/include/core/SkYUVAInfo.h
+++ b/include/core/SkYUVAInfo.h
@@ -93,6 +93,16 @@
 
     static constexpr int kMaxPlanes = 4;
 
+    /** ratio of Y/A values to U/V values in x and y. */
+    static std::tuple<int, int> SubsamplingFactors(Subsampling);
+
+    /**
+     * SubsamplingFactors(Subsampling) if planedIdx refers to a U/V plane and otherwise {1, 1} if
+     * inputs are valid. Invalid inputs consist of incompatible PlaneConfig/Subsampling/planeIdx
+     * combinations. {0, 0} is returned for invalid inputs.
+     */
+    static std::tuple<int, int> PlaneSubsamplingFactors(PlaneConfig, Subsampling, int planeIdx);
+
     /**
      * Given image dimensions, a planer configuration, subsampling, and origin, determine the
      * expected size of each plane. Returns the number of expected planes. planeDimensions[0]
@@ -146,6 +156,10 @@
     PlaneConfig planeConfig() const { return fPlaneConfig; }
     Subsampling subsampling() const { return fSubsampling; }
 
+    std::tuple<int, int> planeSubsamplingFactors(int planeIdx) const {
+        return PlaneSubsamplingFactors(fPlaneConfig, fSubsampling, planeIdx);
+    }
+
     /**
      * Dimensions of the full resolution image (after planes have been oriented to how the image
      * is displayed as indicated by fOrigin).
@@ -160,6 +174,10 @@
 
     SkEncodedOrigin origin() const { return fOrigin; }
 
+    SkMatrix originMatrix() const {
+        return SkEncodedOriginToMatrix(fOrigin, this->width(), this->height());
+    }
+
     bool hasAlpha() const { return HasAlpha(fPlaneConfig); }
 
     /**
@@ -199,6 +217,12 @@
      */
     SkYUVAInfo makeSubsampling(SkYUVAInfo::Subsampling) const;
 
+    /**
+     * Makes a SkYUVAInfo that is identical to this one but with the passed dimensions. If the
+     * passed dimensions is empty then the result will be an invalid SkYUVAInfo.
+     */
+    SkYUVAInfo makeDimensions(SkISize) const;
+
     bool operator==(const SkYUVAInfo& that) const;
     bool operator!=(const SkYUVAInfo& that) const { return !(*this == that); }
 
diff --git a/src/core/SkYUVAInfo.cpp b/src/core/SkYUVAInfo.cpp
index 8e4ce2b..74b3a84 100644
--- a/src/core/SkYUVAInfo.cpp
+++ b/src/core/SkYUVAInfo.cpp
@@ -24,6 +24,54 @@
             config != SkYUVAInfo::PlaneConfig::kUYVA);
 }
 
+std::tuple<int, int> SkYUVAInfo::SubsamplingFactors(Subsampling subsampling) {
+    switch (subsampling) {
+        case Subsampling::kUnknown: return {0, 0};
+        case Subsampling::k444:     return {1, 1};
+        case Subsampling::k422:     return {2, 1};
+        case Subsampling::k420:     return {2, 2};
+        case Subsampling::k440:     return {1, 2};
+        case Subsampling::k411:     return {4, 1};
+        case Subsampling::k410:     return {4, 2};
+    }
+    SkUNREACHABLE;
+}
+
+std::tuple<int, int> SkYUVAInfo::PlaneSubsamplingFactors(PlaneConfig planeConfig,
+                                                         Subsampling subsampling,
+                                                         int planeIdx) {
+    if (!is_plane_config_compatible_with_subsampling(planeConfig, subsampling) ||
+        planeIdx < 0                                                           ||
+        planeIdx > NumPlanes(planeConfig)) {
+        return {0, 0};
+    }
+    bool isSubsampledPlane = false;
+    switch (planeConfig) {
+        case PlaneConfig::kUnknown:     SkUNREACHABLE;
+
+        case PlaneConfig::kY_U_V:
+        case PlaneConfig::kY_V_U:
+        case PlaneConfig::kY_U_V_A:
+        case PlaneConfig::kY_V_U_A:
+            isSubsampledPlane = planeIdx == 1 || planeIdx == 2;
+            break;
+
+        case PlaneConfig::kY_UV:
+        case PlaneConfig::kY_VU:
+        case PlaneConfig::kY_UV_A:
+        case PlaneConfig::kY_VU_A:
+            isSubsampledPlane = planeIdx == 1;
+            break;
+
+        case PlaneConfig::kYUV:
+        case PlaneConfig::kUYV:
+        case PlaneConfig::kYUVA:
+        case PlaneConfig::kUYVA:
+            break;
+    }
+    return isSubsampledPlane ? SubsamplingFactors(subsampling) : std::make_tuple(1, 1);
+}
+
 int SkYUVAInfo::PlaneDimensions(SkISize imageDimensions,
                                 PlaneConfig planeConfig,
                                 Subsampling subsampling,
@@ -310,13 +358,11 @@
 }
 
 SkYUVAInfo SkYUVAInfo::makeSubsampling(SkYUVAInfo::Subsampling subsampling) const {
-    return {fDimensions,
-            fPlaneConfig,
-            subsampling,
-            fYUVColorSpace,
-            fOrigin,
-            fSitingX,
-            fSitingY};
+    return {fDimensions, fPlaneConfig, subsampling, fYUVColorSpace, fOrigin, fSitingX, fSitingY};
+}
+
+SkYUVAInfo SkYUVAInfo::makeDimensions(SkISize dimensions) const {
+    return {dimensions, fPlaneConfig, fSubsampling, fYUVColorSpace, fOrigin, fSitingX, fSitingY};
 }
 
 bool SkYUVAInfo::operator==(const SkYUVAInfo& that) const {
diff --git a/src/gpu/GrImageTextureMaker.cpp b/src/gpu/GrImageTextureMaker.cpp
index d8a4e71..2934e30 100644
--- a/src/gpu/GrImageTextureMaker.cpp
+++ b/src/gpu/GrImageTextureMaker.cpp
@@ -77,12 +77,16 @@
     }
 
     const auto& caps = *fImage->context()->priv().caps();
-    auto fp = GrYUVtoRGBEffect::Make(fImage->fViews, fImage->fYUVALocations, fImage->fYUVColorSpace,
-                                     samplerState, caps, textureMatrix, subset, domain);
+    auto fp = GrYUVtoRGBEffect::Make(fImage->fYUVAProxies,
+                                     samplerState,
+                                     caps,
+                                     textureMatrix,
+                                     subset,
+                                     domain);
     if (fImage->fFromColorSpace) {
-        fp = GrColorSpaceXformEffect::Make(std::move(fp), fImage->fFromColorSpace.get(),
-                                           fImage->alphaType(), fImage->colorSpace(),
-                                           kPremul_SkAlphaType);
+        fp = GrColorSpaceXformEffect::Make(std::move(fp),
+                                           fImage->fFromColorSpace.get(), fImage->alphaType(),
+                                           fImage->colorSpace(), kPremul_SkAlphaType);
     }
     return fp;
 }
@@ -96,12 +100,12 @@
         SkImage::CubicResampler kernel) {
     const auto& caps = *fImage->context()->priv().caps();
     GrSamplerState samplerState(wrapX, wrapY, GrSamplerState::Filter::kNearest);
-    auto fp = GrYUVtoRGBEffect::Make(fImage->fViews, fImage->fYUVALocations, fImage->fYUVColorSpace,
-                                     samplerState, caps, SkMatrix::I(), subset, domain);
+    auto fp = GrYUVtoRGBEffect::Make(fImage->fYUVAProxies, samplerState, caps, SkMatrix::I(),
+                                     subset, domain);
     fp = GrBicubicEffect::Make(std::move(fp),
                                fImage->alphaType(),
                                textureMatrix,
-                               kernel /*GrBicubicEffect::gMitchell*/,
+                               kernel,
                                GrBicubicEffect::Direction::kXY);
     if (fImage->fFromColorSpace) {
         fp = GrColorSpaceXformEffect::Make(std::move(fp),
diff --git a/src/gpu/GrYUVATextureProxies.cpp b/src/gpu/GrYUVATextureProxies.cpp
new file mode 100644
index 0000000..4715a0f
--- /dev/null
+++ b/src/gpu/GrYUVATextureProxies.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/GrYUVATextureProxies.h"
+
+#ifdef SK_DEBUG
+static int num_channels(uint32_t channelFlags) {
+    switch (channelFlags) {
+        case kRed_SkColorChannelFlag        : return 1;
+        case kAlpha_SkColorChannelFlag      : return 1;
+        case kGray_SkColorChannelFlag       : return 1;
+        case kGrayAlpha_SkColorChannelFlags : return 2;
+        case kRG_SkColorChannelFlags        : return 2;
+        case kRGB_SkColorChannelFlags       : return 3;
+        case kRGBA_SkColorChannelFlags      : return 4;
+
+        default:
+            SkDEBUGFAILF("Unexpected channel combination 0x%08x", channelFlags);
+            return 0;
+    }
+}
+#endif
+
+GrYUVATextureProxies::GrYUVATextureProxies(const SkYUVAInfo& yuvaInfo,
+                                           sk_sp<GrSurfaceProxy> proxies[SkYUVAInfo::kMaxPlanes],
+                                           GrSurfaceOrigin textureOrigin)
+        : fYUVAInfo(yuvaInfo), fTextureOrigin(textureOrigin) {
+    int n = yuvaInfo.numPlanes();
+    if (n == 0) {
+        *this = {};
+        SkASSERT(!this->isValid());
+        return;
+    }
+    uint32_t textureChannelMasks[SkYUVAInfo::kMaxPlanes];
+    for (int i = 0; i < n; ++i) {
+        if (!proxies[i]) {
+            *this = {};
+            SkASSERT(!this->isValid());
+            return;
+        }
+        textureChannelMasks[i] = proxies[i]->backendFormat().channelMask();
+    }
+    fYUVALocations = yuvaInfo.toYUVALocations(textureChannelMasks);
+    if (fYUVALocations[0].fPlane < 0) {
+        *this = {};
+        SkASSERT(!this->isValid());
+        return;
+    }
+    fMipmapped = GrMipmapped::kYes;
+    for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
+        if (!proxies[i]) {
+            *this = {};
+            SkASSERT(!this->isValid());
+            return;
+        }
+        SkASSERT(proxies[i]->asTextureProxy());
+        if (proxies[i]->asTextureProxy()->mipmapped() == GrMipmapped::kNo) {
+            fMipmapped = GrMipmapped::kNo;
+        }
+        fProxies[i] = std::move(proxies[i]);
+    }
+    SkASSERT(this->isValid());
+}
+
+GrYUVATextureProxies::GrYUVATextureProxies(const SkYUVAInfo& yuvaInfo,
+                                           GrSurfaceProxyView views[SkYUVAInfo::kMaxPlanes],
+                                           GrColorType colorTypes[SkYUVAInfo::kMaxPlanes])
+        : fYUVAInfo(yuvaInfo) {
+    uint32_t pixmapChannelMasks[SkYUVAInfo::kMaxPlanes];
+    int n = yuvaInfo.numPlanes();
+    if (n == 0) {
+        *this = {};
+        SkASSERT(!this->isValid());
+        return;
+    }
+    fMipmapped = GrMipmapped::kYes;
+    for (int i = 0; i < n; ++i) {
+        pixmapChannelMasks[i] = GrColorTypeChannelFlags(colorTypes[i]);
+        SkASSERT(num_channels(pixmapChannelMasks[i]) <=
+                 num_channels(views[i].proxy()->backendFormat().channelMask()));
+        if (!views[i] || views[i].origin() != views[0].origin()) {
+            *this = {};
+            SkASSERT(!this->isValid());
+            return;
+        }
+        if (views[i].proxy()->asTextureProxy()->mipmapped() == GrMipmapped::kNo) {
+            fMipmapped = GrMipmapped::kNo;
+        }
+    }
+    // Initial locations refer to the CPU pixmap channels.
+    fYUVALocations = yuvaInfo.toYUVALocations(pixmapChannelMasks);
+    if (fYUVALocations[0].fPlane < 0) {
+        *this = {};
+        SkASSERT(!this->isValid());
+        return;
+    }
+
+    // Run each location through the proxy view's swizzle to get the actual texture format channel.
+    for (int i = 0; i < SkYUVAInfo::kYUVAChannelCount; ++i) {
+        int plane = fYUVALocations[i].fPlane;
+        if (plane >= 0) {
+            int chanAsIdx = static_cast<int>(fYUVALocations[i].fChannel);
+            switch (views[plane].swizzle()[chanAsIdx]) {
+                case 'r': fYUVALocations[i].fChannel = SkColorChannel::kR; break;
+                case 'g': fYUVALocations[i].fChannel = SkColorChannel::kG; break;
+                case 'b': fYUVALocations[i].fChannel = SkColorChannel::kB; break;
+                case 'a': fYUVALocations[i].fChannel = SkColorChannel::kA; break;
+
+                default:
+                    SkDEBUGFAILF("Unexpected swizzle value: %c", views[i].swizzle()[chanAsIdx]);
+                    *this = {};
+                    SkASSERT(!this->isValid());
+                    return;
+            }
+        }
+    }
+    for (int i = 0; i < n; ++i) {
+        fProxies[i] = views[i].detachProxy();
+    }
+    fTextureOrigin = views[0].origin();
+    SkASSERT(this->isValid());
+}
diff --git a/src/gpu/GrYUVATextureProxies.h b/src/gpu/GrYUVATextureProxies.h
new file mode 100644
index 0000000..c6c4a68
--- /dev/null
+++ b/src/gpu/GrYUVATextureProxies.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrYUVATextureProxies_DEFINED
+#define GrYUVATextureProxies_DEFINED
+
+#include "include/core/SkYUVAInfo.h"
+#include "src/core/SkYUVAInfoLocation.h"
+#include "src/gpu/GrSurfaceProxy.h"
+#include "src/gpu/GrSurfaceProxyView.h"
+
+class GrSurfaceProxyView;
+
+class GrYUVATextureProxies {
+public:
+    GrYUVATextureProxies() = default;
+
+    /** Assumes all planes are sampled with a default "rgba" swizzle. */
+    GrYUVATextureProxies(const SkYUVAInfo&,
+                         sk_sp<GrSurfaceProxy>[SkYUVAInfo::kMaxPlanes],
+                         GrSurfaceOrigin textureOrigin);
+
+    /**
+     * When uploading pixmaps to textures it is important that we account for how the original
+     * pixmaps' channels are swizzled into the texture during upload. This will compute a swizzle
+     * for each texture based on the original color types and the views' swizzles. The views must
+     * all have the same origin or the result will be an invalid GrYUVATextureProxies.
+     */
+    GrYUVATextureProxies(const SkYUVAInfo&,
+                         GrSurfaceProxyView[SkYUVAInfo::kMaxPlanes],
+                         GrColorType[SkYUVAInfo::kMaxPlanes]);
+
+    GrYUVATextureProxies(const GrYUVATextureProxies&) = default;
+    GrYUVATextureProxies(GrYUVATextureProxies&&) = default;
+
+    GrYUVATextureProxies& operator=(const GrYUVATextureProxies&) = default;
+    GrYUVATextureProxies& operator=(GrYUVATextureProxies&&) = default;
+
+    const SkYUVAInfo& yuvaInfo() const { return fYUVAInfo; }
+
+    int numPlanes() const { return fYUVAInfo.numPlanes(); }
+
+    GrSurfaceOrigin textureOrigin() const { return fTextureOrigin; }
+
+    // Overall set of YUVA proxies is mip mapped if each plane is mip mapped.
+    GrMipmapped mipmapped() const { return fMipmapped; }
+
+    GrSurfaceProxy* proxy(int i) const { return fProxies[i].get(); }
+
+    const std::array<sk_sp<GrSurfaceProxy>, SkYUVAInfo::kMaxPlanes>& proxies() const {
+        return fProxies;
+    }
+
+    sk_sp<GrSurfaceProxy> refProxy(int i) const { return fProxies[i]; }
+
+    GrSurfaceProxyView makeView(int i) const {
+        return {fProxies[i], fTextureOrigin, GrSwizzle::RGBA()};
+    }
+
+    bool isValid() const { return fYUVAInfo.isValid(); }
+
+    const SkYUVAInfo::YUVALocations& yuvaLocations() const { return fYUVALocations; }
+
+private:
+    std::array<sk_sp<GrSurfaceProxy>, SkYUVAInfo::kMaxPlanes> fProxies;
+    SkYUVAInfo fYUVAInfo;
+    GrSurfaceOrigin fTextureOrigin = kTopLeft_GrSurfaceOrigin;
+    GrMipmapped fMipmapped = GrMipmapped::kNo;
+    SkYUVAInfo::YUVALocations fYUVALocations = {};
+};
+
+#endif
diff --git a/src/gpu/effects/GrYUVtoRGBEffect.cpp b/src/gpu/effects/GrYUVtoRGBEffect.cpp
index a8f516e..45361d4 100644
--- a/src/gpu/effects/GrYUVtoRGBEffect.cpp
+++ b/src/gpu/effects/GrYUVtoRGBEffect.cpp
@@ -7,8 +7,10 @@
 
 #include "src/gpu/effects/GrYUVtoRGBEffect.h"
 
+#include "include/core/SkYUVAInfo.h"
 #include "src/core/SkYUVMath.h"
 #include "src/gpu/GrTexture.h"
+#include "src/gpu/GrYUVATextureProxies.h"
 #include "src/gpu/effects/GrMatrixEffect.h"
 #include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
@@ -16,88 +18,82 @@
 #include "src/sksl/SkSLCPP.h"
 #include "src/sksl/SkSLUtil.h"
 
-static void border_colors(SkYUVColorSpace cs,
-                          const SkYUVAInfo::YUVALocations& locations,
-                          float planeBorders[4][4]) {
+static void border_colors(const GrYUVATextureProxies& yuvaProxies, float planeBorders[4][4]) {
     float m[20];
-    SkColorMatrix_RGB2YUV(cs, m);
-    int i = 0;
-    for (auto [plane, channel] : locations) {
+    SkColorMatrix_RGB2YUV(yuvaProxies.yuvaInfo().yuvColorSpace(), m);
+    for (int i = 0; i < SkYUVAInfo::kYUVAChannelCount; ++i) {
+        auto [plane, channel] = yuvaProxies.yuvaLocations()[i];
         if (plane == -1) {
-            continue;
+            return;
         }
         auto c = static_cast<int>(channel);
         planeBorders[plane][c] = m[i*5 + 4];
-        ++i;
     }
 }
 
-std::unique_ptr<GrFragmentProcessor> GrYUVtoRGBEffect::Make(
-        GrSurfaceProxyView views[],
-        const SkYUVAInfo::YUVALocations& locations,
-        SkYUVColorSpace yuvColorSpace,
-        GrSamplerState samplerState,
-        const GrCaps& caps,
-        const SkMatrix& localMatrix,
-        const SkRect* subset,
-        const SkRect* domain) {
-    int numPlanes;
-    SkAssertResult(SkYUVAInfo::YUVALocation::AreValidLocations(locations, &numPlanes));
-
-    const SkISize yDimensions =
-            views[locations[SkYUVAInfo::YUVAChannels::kY].fPlane].proxy()->dimensions();
+std::unique_ptr<GrFragmentProcessor> GrYUVtoRGBEffect::Make(const GrYUVATextureProxies& yuvaProxies,
+                                                            GrSamplerState samplerState,
+                                                            const GrCaps& caps,
+                                                            const SkMatrix& localMatrix,
+                                                            const SkRect* subset,
+                                                            const SkRect* domain) {
+    int numPlanes = yuvaProxies.yuvaInfo().numPlanes();
+    if (!yuvaProxies.isValid()) {
+        return nullptr;
+    }
 
     bool usesBorder = samplerState.wrapModeX() == GrSamplerState::WrapMode::kClampToBorder ||
                       samplerState.wrapModeY() == GrSamplerState::WrapMode::kClampToBorder;
     float planeBorders[4][4] = {};
     if (usesBorder) {
-        border_colors(yuvColorSpace, locations, planeBorders);
+        border_colors(yuvaProxies, planeBorders);
     }
 
     bool snap[2] = {false, false};
-    std::unique_ptr<GrFragmentProcessor> planeFPs[4];
+    std::unique_ptr<GrFragmentProcessor> planeFPs[SkYUVAInfo::kMaxPlanes];
     for (int i = 0; i < numPlanes; ++i) {
-        SkISize dimensions = views[i].proxy()->dimensions();
-        SkTCopyOnFirstWrite<SkMatrix> planeMatrix(&SkMatrix::I());
+        GrSurfaceProxyView view = yuvaProxies.makeView(i);
+        SkMatrix planeMatrix = yuvaProxies.yuvaInfo().originMatrix();
+        // The returned matrix is a view matrix but we need a local matrix.
+        SkAssertResult(planeMatrix.invert(&planeMatrix));
         SkRect planeSubset;
         SkRect planeDomain;
         bool makeLinearWithSnap = false;
-        float sx = 1.f,
-              sy = 1.f;
-        if (dimensions != yDimensions) {
+        auto [ssx, ssy] = yuvaProxies.yuvaInfo().planeSubsamplingFactors(i);
+        SkASSERT(ssx > 0 && ssx <= 4);
+        SkASSERT(ssy > 0 && ssy <= 2);
+        float scaleX = 1.f;
+        float scaleY = 1.f;
+        if (ssx > 1 || ssy > 1) {
             // JPEG chroma subsampling of odd dimensions produces U and V planes with the ceiling of
             // the image size divided by the subsampling factor (2). Our API for creating YUVA
             // doesn't capture the intended subsampling (and we should fix that). This fixes up 2x
             // subsampling for images with odd widths/heights (e.g. JPEG 420 or 422).
-            sx = (float)dimensions.width()  / yDimensions.width();
-            sy = (float)dimensions.height() / yDimensions.height();
-            if ((yDimensions.width() & 0b1) && dimensions.width() == yDimensions.width() / 2 + 1) {
-                sx = 0.5f;
-            }
-            if ((yDimensions.height() & 0b1) &&
-                dimensions.height() == yDimensions.height() / 2 + 1) {
-                sy = 0.5f;
-            }
-            *planeMatrix.writable() = SkMatrix::Scale(sx, sy);
+            scaleX = 1.f/ssx;
+            scaleY = 1.f/ssy;
+            // We would want to add a translation to this matrix to handle other sitings.
+            SkASSERT(yuvaProxies.yuvaInfo().sitingX() == SkYUVAInfo::Siting::kCentered);
+            SkASSERT(yuvaProxies.yuvaInfo().sitingY() == SkYUVAInfo::Siting::kCentered);
+            planeMatrix.postConcat(SkMatrix::Scale(scaleX, scaleY));
             if (subset) {
-                planeSubset = {subset->fLeft   * sx,
-                               subset->fTop    * sy,
-                               subset->fRight  * sx,
-                               subset->fBottom * sy};
+                planeSubset = {subset->fLeft  *scaleX,
+                               subset->fTop   *scaleY,
+                               subset->fRight *scaleX,
+                               subset->fBottom*scaleY};
             }
             if (domain) {
-                planeDomain = {domain->fLeft   * sx,
-                               domain->fTop    * sy,
-                               domain->fRight  * sx,
-                               domain->fBottom * sy};
+                planeDomain = {domain->fLeft  *scaleX,
+                               domain->fTop   *scaleY,
+                               domain->fRight *scaleX,
+                               domain->fBottom*scaleY};
             }
             // This promotion of nearest to linear filtering for UV planes exists to mimic
             // libjpeg[-turbo]'s do_fancy_upsampling option. We will filter the subsampled plane,
             // however we want to filter at a fixed point for each logical image pixel to simulate
             // nearest neighbor.
             if (samplerState.filter() == GrSamplerState::Filter::kNearest) {
-                bool snapX = (sx != 1.f),
-                     snapY = (sy != 1.f);
+                bool snapX = (ssx != 1),
+                     snapY = (ssy != 1);
                 makeLinearWithSnap = snapX || snapY;
                 snap[0] |= snapX;
                 snap[1] |= snapY;
@@ -131,30 +127,53 @@
                 // planeSubset but allows linear filtering to read pixels from the plane that are
                 // just outside planeSubset.
                 SkRect* domainRect = domain ? &planeDomain : nullptr;
-                planeFPs[i] = GrTextureEffect::MakeCustomLinearFilterInset(
-                        views[i], kUnknown_SkAlphaType, *planeMatrix, samplerState.wrapModeX(),
-                        samplerState.wrapModeY(), planeSubset, domainRect, {sx / 2.f, sy / 2.f},
-                        caps, planeBorders[i]);
+                planeFPs[i] = GrTextureEffect::MakeCustomLinearFilterInset(std::move(view),
+                                                                           kUnknown_SkAlphaType,
+                                                                           planeMatrix,
+                                                                           samplerState.wrapModeX(),
+                                                                           samplerState.wrapModeY(),
+                                                                           planeSubset,
+                                                                           domainRect,
+                                                                           {scaleX/2.f, scaleY/2.f},
+                                                                           caps,
+                                                                           planeBorders[i]);
             } else if (domain) {
-                planeFPs[i] = GrTextureEffect::MakeSubset(views[i], kUnknown_SkAlphaType,
-                                                          *planeMatrix, samplerState, planeSubset,
-                                                          planeDomain, caps, planeBorders[i]);
+                planeFPs[i] = GrTextureEffect::MakeSubset(std::move(view),
+                                                          kUnknown_SkAlphaType,
+                                                          planeMatrix,
+                                                          samplerState,
+                                                          planeSubset,
+                                                          planeDomain,
+                                                          caps,
+                                                          planeBorders[i]);
             } else {
-                planeFPs[i] = GrTextureEffect::MakeSubset(views[i], kUnknown_SkAlphaType,
-                                                          *planeMatrix, samplerState, planeSubset,
-                                                          caps, planeBorders[i]);
+                planeFPs[i] = GrTextureEffect::MakeSubset(std::move(view),
+                                                          kUnknown_SkAlphaType,
+                                                          planeMatrix,
+                                                          samplerState,
+                                                          planeSubset,
+                                                          caps,
+                                                          planeBorders[i]);
             }
         } else {
             GrSamplerState planeSampler = samplerState;
             if (makeLinearWithSnap) {
                 planeSampler.setFilterMode(GrSamplerState::Filter::kLinear);
             }
-            planeFPs[i] = GrTextureEffect::Make(views[i], kUnknown_SkAlphaType, *planeMatrix,
-                                                planeSampler, caps, planeBorders[i]);
+            planeFPs[i] = GrTextureEffect::Make(std::move(view),
+                                                kUnknown_SkAlphaType,
+                                                planeMatrix,
+                                                planeSampler,
+                                                caps,
+                                                planeBorders[i]);
         }
     }
-    auto fp = std::unique_ptr<GrFragmentProcessor>(
-            new GrYUVtoRGBEffect(planeFPs, numPlanes, locations, snap, yuvColorSpace));
+    std::unique_ptr<GrFragmentProcessor> fp(
+            new GrYUVtoRGBEffect(planeFPs,
+                                 numPlanes,
+                                 yuvaProxies.yuvaLocations(),
+                                 snap,
+                                 yuvaProxies.yuvaInfo().yuvColorSpace()));
     return GrMatrixEffect::Make(localMatrix, std::move(fp));
 }
 
diff --git a/src/gpu/effects/GrYUVtoRGBEffect.h b/src/gpu/effects/GrYUVtoRGBEffect.h
index 3f1d090..92a8bf3 100644
--- a/src/gpu/effects/GrYUVtoRGBEffect.h
+++ b/src/gpu/effects/GrYUVtoRGBEffect.h
@@ -12,11 +12,11 @@
 #include "src/core/SkYUVAInfoLocation.h"
 #include "src/gpu/GrFragmentProcessor.h"
 
+class GrYUVATextureProxies;
+
 class GrYUVtoRGBEffect : public GrFragmentProcessor {
 public:
-    static std::unique_ptr<GrFragmentProcessor> Make(GrSurfaceProxyView views[],
-                                                     const SkYUVAInfo::YUVALocations&,
-                                                     SkYUVColorSpace yuvColorSpace,
+    static std::unique_ptr<GrFragmentProcessor> Make(const GrYUVATextureProxies& yuvaProxies,
                                                      GrSamplerState samplerState,
                                                      const GrCaps&,
                                                      const SkMatrix& localMatrix = SkMatrix::I(),
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index ffe40bc..483949e 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -39,6 +39,7 @@
 #include "src/gpu/GrTextureAdjuster.h"
 #include "src/gpu/GrTextureProxy.h"
 #include "src/gpu/GrTextureProxyPriv.h"
+#include "src/gpu/GrYUVATextureProxies.h"
 #include "src/gpu/SkGr.h"
 #include "src/gpu/gl/GrGLTexture.h"
 
diff --git a/src/image/SkImage_GpuBase.cpp b/src/image/SkImage_GpuBase.cpp
index a528f84..90172a2 100644
--- a/src/image/SkImage_GpuBase.cpp
+++ b/src/image/SkImage_GpuBase.cpp
@@ -12,6 +12,7 @@
 #include "include/gpu/GrBackendSurface.h"
 #include "include/gpu/GrDirectContext.h"
 #include "include/gpu/GrRecordingContext.h"
+#include "include/gpu/GrYUVABackendTextures.h"
 #include "src/core/SkBitmapCache.h"
 #include "src/core/SkTLList.h"
 #include "src/gpu/GrDirectContextPriv.h"
@@ -22,6 +23,7 @@
 #include "src/gpu/GrSurfaceDrawContext.h"
 #include "src/gpu/GrTexture.h"
 #include "src/gpu/GrTextureAdjuster.h"
+#include "src/gpu/GrYUVATextureProxies.h"
 #include "src/gpu/effects/GrYUVtoRGBEffect.h"
 #include "src/image/SkImage_Gpu.h"
 #include "src/image/SkReadPixelsRec.h"
diff --git a/src/image/SkImage_GpuBase.h b/src/image/SkImage_GpuBase.h
index ed68a7c..ad6c8b6 100644
--- a/src/image/SkImage_GpuBase.h
+++ b/src/image/SkImage_GpuBase.h
@@ -60,11 +60,6 @@
     static bool ValidateCompressedBackendTexture(const GrCaps*, const GrBackendTexture& tex,
                                                  SkAlphaType);
 
-    static SkAlphaType GetAlphaTypeFromYUVALocations(const SkYUVAInfo::YUVALocations locations) {
-        return locations[SkYUVAInfo::YUVAChannels::kA].fPlane >= 0 ? kPremul_SkAlphaType
-                                                                   : kOpaque_SkAlphaType;
-    }
-
     using PromiseImageTextureContext = SkDeferredDisplayListRecorder::PromiseImageTextureContext;
     using PromiseImageTextureFulfillProc =
             SkDeferredDisplayListRecorder::PromiseImageTextureFulfillProc;
diff --git a/src/image/SkImage_GpuYUVA.cpp b/src/image/SkImage_GpuYUVA.cpp
index e38fd34..9a91ba3 100644
--- a/src/image/SkImage_GpuYUVA.cpp
+++ b/src/image/SkImage_GpuYUVA.cpp
@@ -34,33 +34,21 @@
 static constexpr auto kAssumedColorType = kRGBA_8888_SkColorType;
 
 SkImage_GpuYUVA::SkImage_GpuYUVA(sk_sp<GrImageContext> context,
-                                 SkISize size,
                                  uint32_t uniqueID,
-                                 SkYUVColorSpace colorSpace,
-                                 GrSurfaceProxyView views[],
-                                 int numViews,
-                                 const SkYUVAInfo::YUVALocations& yuvaLocations,
+                                 GrYUVATextureProxies proxies,
                                  sk_sp<SkColorSpace> imageColorSpace)
         : INHERITED(std::move(context),
-                    size,
+                    proxies.yuvaInfo().dimensions(),
                     uniqueID,
                     kAssumedColorType,
-                    // If an alpha channel is present we always switch to kPremul. This is because,
+                    // If an alpha channel is present we always use kPremul. This is because,
                     // although the planar data is always un-premul, the final interleaved RGB image
                     // is/would-be premul.
-                    GetAlphaTypeFromYUVALocations(yuvaLocations),
+                    proxies.yuvaInfo().hasAlpha() ? kPremul_SkAlphaType : kOpaque_SkAlphaType,
                     std::move(imageColorSpace))
-        , fNumViews(numViews)
-        , fYUVALocations(yuvaLocations)
-        , fYUVColorSpace(colorSpace) {
-    // The caller should have done this work, just verifying
-    SkDEBUGCODE(int textureCount;)
-    SkASSERT(SkYUVAInfo::YUVALocation::AreValidLocations(fYUVALocations, &textureCount));
-    SkASSERT(textureCount == fNumViews);
-
-    for (int i = 0; i < numViews; ++i) {
-        fViews[i] = std::move(views[i]);
-    }
+        , fYUVAProxies(std::move(proxies)) {
+    // The caller should have checked this, just verifying.
+    SkASSERT(fYUVAProxies.isValid());
 }
 
 // For onMakeColorSpace()
@@ -71,55 +59,46 @@
                     image->dimensions(),
                     kNeedNewImageUniqueID,
                     kAssumedColorType,
-                    // If an alpha channel is present we always switch to kPremul. This is because,
-                    // although the planar data is always un-premul, the final interleaved RGB image
-                    // is/would-be premul.
-                    GetAlphaTypeFromYUVALocations(image->fYUVALocations),
+                    image->alphaType(),
                     std::move(targetCS))
-        , fNumViews(image->fNumViews)
-        , fYUVALocations(image->fYUVALocations)
-        , fYUVColorSpace(image->fYUVColorSpace)
+        , fYUVAProxies(image->fYUVAProxies)
+        , fRGBView(image->fRGBView)
         // Since null fFromColorSpace means no GrColorSpaceXform, we turn a null
         // image->refColorSpace() into an explicit SRGB.
         , fFromColorSpace(image->colorSpace() ? image->refColorSpace() : SkColorSpace::MakeSRGB()) {
-    // The caller should have done this work, just verifying
-    SkDEBUGCODE(int textureCount;)
-    SkASSERT(SkYUVAInfo::YUVALocation::AreValidLocations(image->fYUVALocations, &textureCount));
-    SkASSERT(textureCount == fNumViews);
-
-    if (image->fRGBView.proxy()) {
-        fRGBView = image->fRGBView;  // we ref in this case, not move
-    } else {
-        for (int i = 0; i < fNumViews; ++i) {
-            fViews[i] = image->fViews[i];  // we ref in this case, not move
-        }
-    }
+    // We should either have a RGB proxy *or* a set of YUVA proxies.
+    SkASSERT(fYUVAProxies.isValid() != SkToBool(image->fRGBView));
 }
 
 bool SkImage_GpuYUVA::setupMipmapsForPlanes(GrRecordingContext* context) const {
     // We shouldn't get here if the planes were already flattened to RGBA.
-    SkASSERT(fViews[0].proxy() && !fRGBView.proxy());
+    SkASSERT(fYUVAProxies.isValid() && !fRGBView);
     if (!context || !fContext->priv().matches(context)) {
         return false;
     }
-    GrSurfaceProxyView newViews[4];
     if (!context->priv().caps()->mipmapSupport()) {
         // We succeed in this case by doing nothing.
         return true;
     }
-    for (int i = 0; i < fNumViews; ++i) {
-        auto* t = fViews[i].asTextureProxy();
+    int n = fYUVAProxies.yuvaInfo().numPlanes();
+    sk_sp<GrSurfaceProxy> newProxies[4];
+    for (int i = 0; i < n; ++i) {
+        auto* t = fYUVAProxies.proxy(i)->asTextureProxy();
         if (t->mipmapped() == GrMipmapped::kNo && (t->width() > 1 || t->height() > 1)) {
-            if (!(newViews[i] = GrCopyBaseMipMapToView(context, fViews[i]))) {
+            auto newView = GrCopyBaseMipMapToView(context, fYUVAProxies.makeView(i));
+            if (!newView) {
                 return false;
             }
+            SkASSERT(newView.swizzle() == fYUVAProxies.makeView(i).swizzle());
+            newProxies[i] = newView.detachProxy();
         } else {
-            newViews[i] = fViews[i];
+            newProxies[i] = fYUVAProxies.refProxy(i);
         }
     }
-    for (int i = 0; i < fNumViews; ++i) {
-        fViews[i] = std::move(newViews[i]);
-    }
+    fYUVAProxies = GrYUVATextureProxies(fYUVAProxies.yuvaInfo(),
+                                        newProxies,
+                                        fYUVAProxies.textureOrigin());
+    SkASSERT(fYUVAProxies.isValid());
     return true;
 }
 
@@ -136,15 +115,19 @@
         return GrSemaphoresSubmitted::kNo;
     }
 
-    GrSurfaceProxy* proxies[4] = {fViews[0].proxy(), fViews[1].proxy(), fViews[2].proxy(),
-                                  fViews[3].proxy()};
-    size_t numProxies = fNumViews;
-    if (fRGBView.proxy()) {
+    GrSurfaceProxy* proxies[SkYUVAInfo::kMaxPlanes] = {};
+    size_t numProxies;
+    if (fRGBView) {
         // Either we've already flushed the flattening draw or the flattening is unflushed. In the
         // latter case it should still be ok to just pass fRGBView proxy because it in turn depends
         // on the planar proxies and will cause all of their work to flush as well.
         proxies[0] = fRGBView.proxy();
         numProxies = 1;
+    } else {
+        numProxies = fYUVAProxies.numPlanes();
+        for (size_t i = 0; i < numProxies; ++i) {
+            proxies[i] = fYUVAProxies.proxy(i);
+        }
     }
     return dContext->priv().flushSurfaces({proxies, numProxies},
                                           SkSurface::BackendSurfaceAccess::kNoAccess,
@@ -153,49 +136,6 @@
 
 GrTextureProxy* SkImage_GpuYUVA::peekProxy() const { return fRGBView.asTextureProxy(); }
 
-bool SkImage_GpuYUVA::MakeTempTextureProxies(GrRecordingContext* rContext,
-                                             const GrBackendTexture yuvaTextures[],
-                                             int numTextures,
-                                             const SkYUVAInfo::YUVALocations& yuvaLocations,
-                                             GrSurfaceOrigin imageOrigin,
-                                             GrSurfaceProxyView tempViews[4],
-                                             sk_sp<GrRefCntedCallback> releaseHelper) {
-    GrProxyProvider* proxyProvider = rContext->priv().proxyProvider();
-    for (int textureIndex = 0; textureIndex < numTextures; ++textureIndex) {
-        const GrBackendFormat& backendFormat = yuvaTextures[textureIndex].getBackendFormat();
-        if (!backendFormat.isValid()) {
-            return false;
-        }
-
-        SkASSERT(yuvaTextures[textureIndex].isValid());
-
-        auto proxy = proxyProvider->wrapBackendTexture(yuvaTextures[textureIndex],
-                                                       kBorrow_GrWrapOwnership,
-                                                       GrWrapCacheable::kNo,
-                                                       kRead_GrIOType,
-                                                       releaseHelper);
-        if (!proxy) {
-            return false;
-        }
-        tempViews[textureIndex] =
-                GrSurfaceProxyView(std::move(proxy), imageOrigin, GrSwizzle("rgba"));
-#ifdef SK_DEBUG
-        // Check that each texture contains the channel data for the corresponding YUVA location
-        auto formatChannelMask = backendFormat.channelMask();
-        if (formatChannelMask & kGray_SkColorChannelFlag) {
-            formatChannelMask |= kRGB_SkColorChannelFlags;
-        }
-        for (int i = 0; i < SkYUVAInfo::kYUVAChannelCount; ++i) {
-            if (yuvaLocations[i].fPlane == textureIndex) {
-                uint32_t channelAsMask = 1 << static_cast<int>(yuvaLocations[i].fChannel);
-                SkASSERT(channelAsMask & formatChannelMask);
-            }
-        }
-#endif
-    }
-    return true;
-}
-
 void SkImage_GpuYUVA::flattenToRGB(GrRecordingContext* context) const {
     if (fRGBView.proxy()) {
         return;
@@ -222,23 +162,18 @@
 
     const GrCaps& caps = *context->priv().caps();
 
-    auto fp = GrYUVtoRGBEffect::Make(
-            fViews, fYUVALocations, fYUVColorSpace, GrSamplerState::Filter::kNearest, caps);
+    auto fp = GrYUVtoRGBEffect::Make(fYUVAProxies, GrSamplerState::Filter::kNearest, caps);
     if (fFromColorSpace) {
-        auto colorSpaceXform = GrColorSpaceXform::Make(fFromColorSpace.get(),
-                                                       this->alphaType(),
-                                                       this->colorSpace(),
-                                                       this->alphaType());
-        fp = GrColorSpaceXformEffect::Make(std::move(fp), std::move(colorSpaceXform));
+        fp = GrColorSpaceXformEffect::Make(std::move(fp),
+                                           fFromColorSpace.get(), this->alphaType(),
+                                           this->colorSpace(),    this->alphaType());
     }
 
     surfaceFillContext->fillWithFP(std::move(fp));
 
     fRGBView = surfaceFillContext->readSurfaceView();
     SkASSERT(fRGBView.swizzle() == GrSwizzle());
-    for (auto& v : fViews) {
-        v.reset();
-    }
+    fYUVAProxies = {};
 }
 
 GrSurfaceProxyView SkImage_GpuYUVA::refMippedView(GrRecordingContext* context) const {
@@ -286,9 +221,7 @@
 }
 
 sk_sp<SkImage> SkImage_GpuYUVA::onReinterpretColorSpace(sk_sp<SkColorSpace> newCS) const {
-    return sk_make_sp<SkImage_GpuYUVA>(fContext, this->dimensions(), kNeedNewImageUniqueID,
-                                       fYUVColorSpace, fViews, fNumViews, fYUVALocations,
-                                       std::move(newCS));
+    return sk_sp<SkImage>(new SkImage_GpuYUVA(fContext, this, std::move(newCS)));
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////////////
@@ -300,32 +233,36 @@
                                              ReleaseContext releaseContext) {
     auto releaseHelper = GrRefCntedCallback::Make(textureReleaseProc, releaseContext);
 
-    SkYUVAInfo::YUVALocations yuvaLocations = yuvaTextures.toYUVALocations();
+    GrProxyProvider* proxyProvider = context->priv().proxyProvider();
+    int numPlanes = yuvaTextures.yuvaInfo().numPlanes();
+    sk_sp<GrSurfaceProxy> proxies[SkYUVAInfo::kMaxPlanes];
+    for (int plane = 0; plane < numPlanes; ++plane) {
+        proxies[plane] = proxyProvider->wrapBackendTexture(yuvaTextures.texture(plane),
+                                                           kBorrow_GrWrapOwnership,
+                                                           GrWrapCacheable::kNo,
+                                                           kRead_GrIOType,
+                                                           releaseHelper);
+        if (!proxies[plane]) {
+            return {};
+        }
+    }
+    GrYUVATextureProxies yuvaProxies(yuvaTextures.yuvaInfo(),
+                                     proxies,
+                                     yuvaTextures.textureOrigin());
 
-    GrSurfaceProxyView tempViews[4];
-    if (!SkImage_GpuYUVA::MakeTempTextureProxies(context,
-                                                 yuvaTextures.textures().data(),
-                                                 yuvaTextures.numPlanes(),
-                                                 yuvaLocations,
-                                                 yuvaTextures.textureOrigin(),
-                                                 tempViews,
-                                                 std::move(releaseHelper))) {
+    if (!yuvaProxies.isValid()) {
         return nullptr;
     }
 
     return sk_make_sp<SkImage_GpuYUVA>(sk_ref_sp(context),
-                                       yuvaTextures.yuvaInfo().dimensions(),
                                        kNeedNewImageUniqueID,
-                                       yuvaTextures.yuvaInfo().yuvColorSpace(),
-                                       tempViews,
-                                       yuvaTextures.numPlanes(),
-                                       yuvaLocations,
+                                       yuvaProxies,
                                        imageColorSpace);
 }
 
 sk_sp<SkImage> SkImage::MakeFromYUVAPixmaps(GrRecordingContext* context,
                                             const SkYUVAPixmaps& pixmaps,
-                                            GrMipMapped buildMips,
+                                            GrMipmapped buildMips,
                                             bool limitToMaxTextureSize,
                                             sk_sp<SkColorSpace> imageColorSpace) {
     if (!context) {
@@ -336,8 +273,6 @@
         return nullptr;
     }
 
-    SkYUVAInfo::YUVALocations yuvaLocations = pixmaps.toYUVALocations();
-
     // SkImage_GpuYUVA doesn't yet support different encoded origins.
     if (pixmaps.yuvaInfo().origin() != kTopLeft_SkEncodedOrigin) {
         return nullptr;
@@ -347,45 +282,58 @@
         buildMips = GrMipMapped::kNo;
     }
 
-    // Make proxies
-    GrSurfaceProxyView tempViews[4];
+    // Resize the pixmaps if necessary.
     int numPlanes = pixmaps.numPlanes();
     int maxTextureSize = context->priv().caps()->maxTextureSize();
-    for (int i = 0; i < numPlanes; ++i) {
-        const SkPixmap* pixmap = &pixmaps.plane(i);
-        SkAutoPixmapStorage resized;
-        int maxDim = std::max(pixmap->width(), pixmap->height());
-        if (maxDim > maxTextureSize) {
-            if (!limitToMaxTextureSize) {
-                return nullptr;
-            }
-            float scale = static_cast<float>(maxTextureSize)/maxDim;
-            int newWidth  = std::min(static_cast<int>(pixmap->width() *scale), maxTextureSize);
-            int newHeight = std::min(static_cast<int>(pixmap->height()*scale), maxTextureSize);
-            SkImageInfo info = pixmap->info().makeWH(newWidth, newHeight);
-            SkSamplingOptions sampling(SkFilterMode::kLinear, SkMipmapMode::kNone);
-            if (!resized.tryAlloc(info) || !pixmap->scalePixels(resized, sampling)) {
-                return nullptr;
-            }
-            pixmap = &resized;
-        }
-        // Turn the pixmap into a GrTextureProxy
-        SkBitmap bmp;
-        bmp.installPixels(*pixmap);
-        GrBitmapTextureMaker bitmapMaker(context, bmp, GrImageTexGenPolicy::kNew_Uncached_Budgeted);
-        tempViews[i] = bitmapMaker.view(buildMips);
-        if (!tempViews[i]) {
+    int maxDim = std::max(pixmaps.yuvaInfo().width(), pixmaps.yuvaInfo().height());
+
+    SkYUVAPixmaps tempPixmaps;
+    const SkYUVAPixmaps* pixmapsToUpload = &pixmaps;
+    // We assume no plane is larger than the image size (and at least one plane is as big).
+    if (maxDim > maxTextureSize) {
+        if (!limitToMaxTextureSize) {
             return nullptr;
         }
+        float scale = static_cast<float>(maxTextureSize)/maxDim;
+        SkISize newDimensions = {
+            std::min(static_cast<int>(pixmaps.yuvaInfo().width() *scale), maxTextureSize),
+            std::min(static_cast<int>(pixmaps.yuvaInfo().height()*scale), maxTextureSize)
+        };
+        SkYUVAInfo newInfo = pixmaps.yuvaInfo().makeDimensions(newDimensions);
+        SkYUVAPixmapInfo newPixmapInfo(newInfo, pixmaps.dataType(), /*row bytes*/ nullptr);
+        tempPixmaps = SkYUVAPixmaps::Allocate(newPixmapInfo);
+        SkSamplingOptions sampling(SkFilterMode::kLinear, SkMipmapMode::kNone);
+        if (!tempPixmaps.isValid()) {
+            return nullptr;
+        }
+        for (int i = 0; i < numPlanes; ++i) {
+            if (!pixmaps.plane(i).scalePixels(tempPixmaps.plane(i), sampling)) {
+                return nullptr;
+            }
+        }
+        pixmapsToUpload = &tempPixmaps;
     }
 
+    // Convert to texture proxies.
+    GrSurfaceProxyView views[SkYUVAInfo::kMaxPlanes];
+    GrColorType pixmapColorTypes[SkYUVAInfo::kMaxPlanes];
+    for (int i = 0; i < numPlanes; ++i) {
+        // Turn the pixmap into a GrTextureProxy
+        SkBitmap bmp;
+        bmp.installPixels(pixmapsToUpload->plane(i));
+        GrBitmapTextureMaker bitmapMaker(context, bmp, GrImageTexGenPolicy::kNew_Uncached_Budgeted);
+        views[i] = bitmapMaker.view(buildMips);
+        if (!views[i]) {
+            return nullptr;
+        }
+        pixmapColorTypes[i] = SkColorTypeToGrColorType(bmp.colorType());
+    }
+
+    GrYUVATextureProxies yuvaProxies(pixmapsToUpload->yuvaInfo(), views, pixmapColorTypes);
+    SkASSERT(yuvaProxies.isValid());
     return sk_make_sp<SkImage_GpuYUVA>(sk_ref_sp(context),
-                                       pixmaps.yuvaInfo().dimensions(),
                                        kNeedNewImageUniqueID,
-                                       pixmaps.yuvaInfo().yuvColorSpace(),
-                                       tempViews,
-                                       numPlanes,
-                                       yuvaLocations,
+                                       std::move(yuvaProxies),
                                        std::move(imageColorSpace));
 }
 
@@ -413,7 +361,6 @@
         releaseHelpers[i] = GrRefCntedCallback::Make(textureReleaseProc, textureContexts[i]);
     }
 
-    SkYUVAInfo::YUVALocations yuvaLocations = yuvaBackendTextureInfo.toYUVALocations();
     if (yuvaBackendTextureInfo.yuvaInfo().origin() != SkEncodedOrigin::kDefault_SkEncodedOrigin) {
         // SkImage_GpuYUVA does not support this yet. This will get removed
         // when the old APIs are gone and we only have to support YUVA configs described by
@@ -434,23 +381,24 @@
     }
 
     // Make a lazy proxy for each plane and wrap in a view.
-    GrSurfaceProxyView views[4];
-    for (int texIdx = 0; texIdx < n; ++texIdx) {
-        auto proxy = MakePromiseImageLazyProxy(context,
-                                               planeDimensions[texIdx],
-                                               yuvaBackendTextureInfo.planeFormat(texIdx),
+    sk_sp<GrSurfaceProxy> proxies[4];
+    for (int i = 0; i < n; ++i) {
+        proxies[i] = MakePromiseImageLazyProxy(context,
+                                               planeDimensions[i],
+                                               yuvaBackendTextureInfo.planeFormat(i),
                                                GrMipmapped::kNo,
                                                textureFulfillProc,
-                                               std::move(releaseHelpers[texIdx]));
-        if (!proxy) {
+                                               std::move(releaseHelpers[i]));
+        if (!proxies[i]) {
             return nullptr;
         }
-        views[texIdx] = GrSurfaceProxyView(std::move(proxy), yuvaBackendTextureInfo.textureOrigin(),
-                                           GrSwizzle("rgba"));
     }
-
-    return sk_make_sp<SkImage_GpuYUVA>(
-            sk_ref_sp(context), yuvaBackendTextureInfo.yuvaInfo().dimensions(),
-            kNeedNewImageUniqueID, yuvaBackendTextureInfo.yuvColorSpace(), views, n, yuvaLocations,
-            std::move(imageColorSpace));
+    GrYUVATextureProxies yuvaTextureProxies(yuvaBackendTextureInfo.yuvaInfo(),
+                                            proxies,
+                                            yuvaBackendTextureInfo.textureOrigin());
+    SkASSERT(yuvaTextureProxies.isValid());
+    return sk_make_sp<SkImage_GpuYUVA>(sk_ref_sp(context),
+                                       kNeedNewImageUniqueID,
+                                       std::move(yuvaTextureProxies),
+                                       std::move(imageColorSpace));
 }
diff --git a/src/image/SkImage_GpuYUVA.h b/src/image/SkImage_GpuYUVA.h
index 2378e0e..ddaf2bb 100644
--- a/src/image/SkImage_GpuYUVA.h
+++ b/src/image/SkImage_GpuYUVA.h
@@ -10,13 +10,14 @@
 
 #include "include/gpu/GrBackendSurface.h"
 #include "src/core/SkCachedData.h"
+#include "src/gpu/GrYUVATextureProxies.h"
 #include "src/image/SkImage_GpuBase.h"
 
 class GrDirectContext;
 class GrRecordingContext;
 class GrTexture;
 
-// Wraps the 3 or 4 planes of a YUVA image for consumption by the GPU.
+// Wraps the 1 to 4 planes of a YUVA image for consumption by the GPU.
 // Initially any direct rendering will be done by passing the individual planes to a shader.
 // Once any method requests a flattened image (e.g., onReadPixels), the flattened RGB
 // proxy will be stored and used for any future rendering.
@@ -25,12 +26,8 @@
     friend class GrYUVAImageTextureMaker;
 
     SkImage_GpuYUVA(sk_sp<GrImageContext>,
-                    SkISize size,
                     uint32_t uniqueID,
-                    SkYUVColorSpace,
-                    GrSurfaceProxyView views[],
-                    int numViews,
-                    const SkYUVAInfo::YUVALocations&,
+                    GrYUVATextureProxies proxies,
                     sk_sp<SkColorSpace>);
 
     GrSemaphoresSubmitted onFlush(GrDirectContext*, const GrFlushInfo&) override;
@@ -42,7 +39,8 @@
     const GrSurfaceProxyView* view(GrRecordingContext* context) const override;
 
     bool onIsTextureBacked() const override {
-        SkASSERT(fViews[0].proxy() || fRGBView.proxy());
+        // We should have YUVA proxies or a RGBA proxy,but not both.
+        SkASSERT(fYUVAProxies.isValid() != SkToBool(fRGBView));
         return true;
     }
 
@@ -61,7 +59,7 @@
 #if GR_TEST_UTILS
     bool testingOnly_IsFlattened() const {
         // We should only have the flattened proxy or the planar proxies at one point in time.
-        SkASSERT(SkToBool(fRGBView.proxy()) != SkToBool(fViews[0].proxy()));
+        SkASSERT(SkToBool(fRGBView) != fYUVAProxies.isValid());
         return SkToBool(fRGBView.proxy());
     }
 #endif
@@ -76,25 +74,17 @@
                                                  PromiseImageTextureReleaseProc textureReleaseProc,
                                                  PromiseImageTextureContext textureContexts[]);
 
-    static bool MakeTempTextureProxies(GrRecordingContext*,
-                                       const GrBackendTexture yuvaTextures[],
-                                       int numTextures,
-                                       const SkYUVAInfo::YUVALocations&,
-                                       GrSurfaceOrigin imageOrigin,
-                                       GrSurfaceProxyView tempViews[4],
-                                       sk_sp<GrRefCntedCallback> releaseHelper);
-
 private:
     SkImage_GpuYUVA(sk_sp<GrImageContext>, const SkImage_GpuYUVA* image, sk_sp<SkColorSpace>);
 
     void flattenToRGB(GrRecordingContext*) const;
 
-    // This array will usually only be sparsely populated.
-    // The actual non-null fields are dictated by the 'fYUVAIndices' indices
-    mutable GrSurfaceProxyView       fViews[4];
-    int                              fNumViews;
-    SkYUVAInfo::YUVALocations        fYUVALocations;
-    const SkYUVColorSpace            fYUVColorSpace;
+    mutable GrYUVATextureProxies     fYUVAProxies;
+
+    // This is only allocated when the image needs to be flattened rather than
+    // using the separate YUVA planes. From thence forth we will only use the
+    // the RGBView.
+    mutable GrSurfaceProxyView       fRGBView;
 
     // If this is non-null then the planar data should be converted from fFromColorSpace to
     // this->colorSpace(). Otherwise we assume the planar data (post YUV->RGB conversion) is already
@@ -106,10 +96,6 @@
     mutable sk_sp<SkColorSpace>      fOnMakeColorSpaceTarget;
     mutable sk_sp<SkImage>           fOnMakeColorSpaceResult;
 
-    // This is only allocated when the image needs to be flattened rather than
-    // using the separate YUVA planes. From thence forth we will only use the
-    // the RGBView.
-    mutable GrSurfaceProxyView       fRGBView;
     using INHERITED = SkImage_GpuBase;
 };
 
diff --git a/src/image/SkImage_Lazy.cpp b/src/image/SkImage_Lazy.cpp
index b1582a8..37e3afd 100644
--- a/src/image/SkImage_Lazy.cpp
+++ b/src/image/SkImage_Lazy.cpp
@@ -30,7 +30,8 @@
 #include "src/gpu/GrProxyProvider.h"
 #include "src/gpu/GrRecordingContextPriv.h"
 #include "src/gpu/GrSamplerState.h"
-#include "src/gpu/GrSurfaceDrawContext.h"
+#include "src/gpu/GrSurfaceFillContext.h"
+#include "src/gpu/GrYUVATextureProxies.h"
 #include "src/gpu/SkGr.h"
 #include "src/gpu/effects/GrYUVtoRGBEffect.h"
 #endif
@@ -267,7 +268,8 @@
         return {};
     }
 
-    GrSurfaceProxyView yuvViews[SkYUVAInfo::kMaxPlanes];
+    GrSurfaceProxyView views[SkYUVAInfo::kMaxPlanes];
+    GrColorType pixmapColorTypes[SkYUVAInfo::kMaxPlanes];
     for (int i = 0; i < yuvaPixmaps.numPlanes(); ++i) {
         // If the sizes of the components are not all the same we choose to create exact-match
         // textures for the smaller ones rather than add a texture domain to the draw.
@@ -296,11 +298,12 @@
         bitmap.setImmutable();
 
         GrBitmapTextureMaker maker(ctx, bitmap, fit);
-        yuvViews[i] = maker.view(GrMipmapped::kNo);
+        views[i] = maker.view(GrMipmapped::kNo);
 
-        if (!yuvViews[i]) {
+        if (!views[i]) {
             return {};
         }
+        pixmapColorTypes[i] = SkColorTypeToGrColorType(bitmap.colorType());
     }
 
     // TODO: investigate preallocating mip maps here
@@ -320,12 +323,13 @@
         return {};
     }
 
-    std::unique_ptr<GrFragmentProcessor> yuvToRgbProcessor =
-            GrYUVtoRGBEffect::Make(yuvViews,
-                                   yuvaPixmaps.toYUVALocations(),
-                                   yuvaPixmaps.yuvaInfo().yuvColorSpace(),
-                                   GrSamplerState::Filter::kNearest,
-                                   *ctx->priv().caps());
+    GrYUVATextureProxies yuvaProxies(yuvaPixmaps.yuvaInfo(), views, pixmapColorTypes);
+    SkAssertResult(yuvaProxies.isValid());
+
+    std::unique_ptr<GrFragmentProcessor> fp = GrYUVtoRGBEffect::Make(
+            yuvaProxies,
+            GrSamplerState::Filter::kNearest,
+            *ctx->priv().caps());
 
     // The pixels after yuv->rgb will be in the generator's color space.
     // If onMakeColorTypeAndColorSpace has been called then this will not match this image's
@@ -340,18 +344,11 @@
 
     // If the caller expects the pixels in a different color space than the one from the image,
     // apply a color conversion to do this.
-    std::unique_ptr<GrFragmentProcessor> colorConversionProcessor =
-            GrColorSpaceXformEffect::Make(std::move(yuvToRgbProcessor),
-                                          srcColorSpace, kOpaque_SkAlphaType,
-                                          dstColorSpace, kOpaque_SkAlphaType);
-    SkMatrix m = SkEncodedOriginToMatrix(yuvaPixmaps.yuvaInfo().origin(),
-                                         this->width(),
-                                         this->height());
-    // The returned matrix is a view matrix but we need a local matrix.
-    SkAssertResult(m.invert(&m));
-    surfaceFillContext->fillWithFP(m, std::move(colorConversionProcessor));
+    fp = GrColorSpaceXformEffect::Make(std::move(fp),
+                                       srcColorSpace, kOpaque_SkAlphaType,
+                                       dstColorSpace, kOpaque_SkAlphaType);
+    surfaceFillContext->fillWithFP(std::move(fp));
 
-    SkASSERT(surfaceFillContext->asTextureProxy());
     return surfaceFillContext->readSurfaceView();
 }