Add SkImage_GpuYUVA

Bug: skia:7903
Change-Id: I06edc155b0a0a0697dc0d0aab74b6876d631ca0d
Reviewed-on: https://skia-review.googlesource.com/156942
Commit-Queue: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 95e0394..530fcd8 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -544,6 +544,8 @@
 
   "$_src/image/SkImage_Gpu.h",
   "$_src/image/SkImage_Gpu.cpp",
+  "$_src/image/SkImage_GpuYUVA.h",
+  "$_src/image/SkImage_GpuYUVA.cpp",
   "$_src/image/SkSurface_Gpu.h",
   "$_src/image/SkSurface_Gpu.cpp",
 ]
diff --git a/include/gpu/GrBackendSurface.h b/include/gpu/GrBackendSurface.h
index dc13795..8caebb8 100644
--- a/include/gpu/GrBackendSurface.h
+++ b/include/gpu/GrBackendSurface.h
@@ -205,6 +205,7 @@
     // Friending for access to the GrPixelConfig
     friend class SkImage;
     friend class SkImage_Gpu;
+    friend class SkImage_GpuYUVA;
     friend class SkSurface;
     friend class GrAHardwareBufferImageGenerator;
     friend class GrBackendTextureImageGenerator;
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index 38cc683..ad0a150 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -291,8 +291,9 @@
                                    std::move(colorSpace), SkBudgeted::kNo);
 }
 
-bool validate_backend_texture(GrContext* ctx, const GrBackendTexture& tex, GrPixelConfig* config,
-                              SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs) {
+static bool validate_backend_texture(GrContext* ctx, const GrBackendTexture& tex,
+                                     GrPixelConfig* config, SkColorType ct, SkAlphaType at,
+                                     sk_sp<SkColorSpace> cs) {
     if (!tex.isValid()) {
         return false;
     }
diff --git a/src/image/SkImage_GpuYUVA.cpp b/src/image/SkImage_GpuYUVA.cpp
new file mode 100644
index 0000000..a89d54b
--- /dev/null
+++ b/src/image/SkImage_GpuYUVA.cpp
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <cstddef>
+#include <cstring>
+#include <type_traits>
+
+#include "GrClip.h"
+#include "GrContext.h"
+#include "GrContextPriv.h"
+#include "GrRenderTargetContext.h"
+#include "GrTexture.h"
+#include "GrTextureAdjuster.h"
+#include "SkImage_Gpu.h"
+#include "SkImage_GpuYUVA.h"
+#include "SkReadPixelsRec.h"
+#include "effects/GrYUVtoRGBEffect.h"
+
+SkImage_GpuYUVA::SkImage_GpuYUVA(sk_sp<GrContext> context, uint32_t uniqueID,
+                                 SkYUVColorSpace colorSpace, sk_sp<GrTextureProxy> proxies[],
+                                 SkYUVAIndex yuvaIndices[4], SkISize size, GrSurfaceOrigin origin,
+                                 sk_sp<SkColorSpace> imageColorSpace, SkBudgeted budgeted)
+        : INHERITED(size.width(), size.height(), uniqueID)
+        , fContext(std::move(context))
+        , fBudgeted(budgeted)
+        , fColorSpace(colorSpace)
+        , fOrigin(origin)
+        , fImageAlphaType(kOpaque_SkAlphaType)
+        , fImageColorSpace(std::move(imageColorSpace)) {
+    for (int i = 0; i < 4; ++i) {
+        fProxies[i] = std::move(proxies[i]);
+    }
+    memcpy(fYUVAIndices, yuvaIndices, 4*sizeof(SkYUVAIndex));
+    // 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.
+    if (-1 != yuvaIndices[3].fIndex) {
+        fImageAlphaType = kPremul_SkAlphaType;
+    }
+}
+
+SkImage_GpuYUVA::~SkImage_GpuYUVA() {}
+
+SkImageInfo SkImage_GpuYUVA::onImageInfo() const {
+    // Note: this is the imageInfo for the flattened image, not the YUV planes
+    return SkImageInfo::Make(this->width(), this->height(), kRGBA_8888_SkColorType,
+                             fImageAlphaType, fImageColorSpace);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// need shared defs with SkImage_Gpu
+
+static bool validate_backend_texture(GrContext* ctx, const GrBackendTexture& tex,
+                                     GrPixelConfig* config, SkColorType ct, SkAlphaType at,
+                                     sk_sp<SkColorSpace> cs) {
+    if (!tex.isValid()) {
+        return false;
+    }
+    // TODO: Create a SkImageColorInfo struct for color, alpha, and color space so we don't need to
+    // create a fake image info here.
+    SkImageInfo info = SkImageInfo::Make(1, 1, ct, at, cs);
+    if (!SkImageInfoIsValid(info)) {
+        return false;
+    }
+
+    return ctx->contextPriv().caps()->validateBackendTexture(tex, ct, config);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+sk_sp<GrTextureProxy> SkImage_GpuYUVA::asTextureProxyRef() const {
+    if (!fRGBProxy) {
+        sk_sp<GrTextureProxy> yProxy = fProxies[fYUVAIndices[0].fIndex];
+        sk_sp<GrTextureProxy> uProxy = fProxies[fYUVAIndices[1].fIndex];
+        sk_sp<GrTextureProxy> vProxy = fProxies[fYUVAIndices[2].fIndex];
+
+        if (!yProxy || !uProxy || !vProxy) {
+            return nullptr;
+        }
+
+        GrPaint paint;
+        paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
+        // TODO: Modify the fragment processor to sample from different channel
+        // instead of taking nv12 bool.
+        bool nv12 = (fYUVAIndices[1].fIndex == fYUVAIndices[2].fIndex);
+        // TODO: modify the YUVtoRGBEffect to do premul if fImageAlphaType is kPremul_AlphaType
+        paint.addColorFragmentProcessor(GrYUVtoRGBEffect::Make(std::move(yProxy), std::move(uProxy),
+                                                               std::move(vProxy), fColorSpace,
+                                                               nv12));
+
+        const SkRect rect = SkRect::MakeIWH(this->width(), this->height());
+
+        // Needs to create a render target in order to draw to it for the yuv->rgb conversion.
+        sk_sp<GrRenderTargetContext> renderTargetContext(
+            fContext->contextPriv().makeDeferredRenderTargetContext(
+                SkBackingFit::kExact, this->width(), this->height(), kRGBA_8888_GrPixelConfig,
+                std::move(fImageColorSpace), 1, GrMipMapped::kNo, fOrigin));
+        if (!renderTargetContext) {
+            return nullptr;
+        }
+        renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), rect);
+
+        if (!renderTargetContext->asSurfaceProxy()) {
+            return nullptr;
+        }
+
+        // DDL TODO: in the promise image version we must not flush here
+        fContext->contextPriv().flushSurfaceWrites(renderTargetContext->asSurfaceProxy());
+
+        // cast to non-const
+        (sk_sp<GrTextureProxy>)(fRGBProxy) = renderTargetContext->asTextureProxyRef();
+    }
+
+    return fRGBProxy;
+}
+
+sk_sp<GrTextureProxy> SkImage_GpuYUVA::asTextureProxyRef(GrContext* context,
+                                                         const GrSamplerState& params,
+                                                         SkColorSpace* dstColorSpace,
+                                                         sk_sp<SkColorSpace>* texColorSpace,
+                                                         SkScalar scaleAdjust[2]) const {
+    if (context->uniqueID() != fContext->uniqueID()) {
+        SkASSERT(0);
+        return nullptr;
+    }
+
+    GrTextureAdjuster adjuster(fContext.get(), this->asTextureProxyRef(), this->alphaType(),
+                               this->uniqueID(), this->fImageColorSpace.get());
+    return adjuster.refTextureProxyForParams(params, dstColorSpace, texColorSpace, scaleAdjust);
+}
+
+bool SkImage_GpuYUVA::onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB,
+                                   int srcX, int srcY, CachingHint) const {
+    if (!fContext->contextPriv().resourceProvider()) {
+        // DDL TODO: buffer up the readback so it occurs when the DDL is drawn?
+        return false;
+    }
+
+    if (!SkImageInfoValidConversion(dstInfo, this->onImageInfo())) {
+        return false;
+    }
+
+    SkReadPixelsRec rec(dstInfo, dstPixels, dstRB, srcX, srcY);
+    if (!rec.trim(this->width(), this->height())) {
+        return false;
+    }
+
+    // Flatten the YUVA planes to a single texture
+    sk_sp<GrSurfaceProxy> proxy = this->asTextureProxyRef();
+
+    // TODO: this seems to duplicate code in GrTextureContext::onReadPixels and
+    // GrRenderTargetContext::onReadPixels
+    uint32_t flags = 0;
+    if (kUnpremul_SkAlphaType == rec.fInfo.alphaType() && kPremul_SkAlphaType == fImageAlphaType) {
+        // let the GPU perform this transformation for us
+        flags = GrContextPriv::kUnpremul_PixelOpsFlag;
+    }
+
+    sk_sp<GrSurfaceContext> sContext = fContext->contextPriv().makeWrappedSurfaceContext(
+        proxy, fImageColorSpace);
+    if (!sContext) {
+        return false;
+    }
+
+    if (!sContext->readPixels(rec.fInfo, rec.fPixels, rec.fRowBytes, rec.fX, rec.fY, flags)) {
+        return false;
+    }
+
+    return true;
+}
+
+sk_sp<SkImage> SkImage_GpuYUVA::onMakeSubset(const SkIRect& subset) const {
+    // Flatten the YUVA planes to a single texture
+    sk_sp<GrSurfaceProxy> proxy = this->asTextureProxyRef();
+
+    GrSurfaceDesc desc;
+    desc.fWidth = subset.width();
+    desc.fHeight = subset.height();
+    desc.fConfig = proxy->config();
+
+    sk_sp<GrSurfaceContext> sContext(fContext->contextPriv().makeDeferredSurfaceContext(
+        desc, proxy->origin(), GrMipMapped::kNo, SkBackingFit::kExact, fBudgeted));
+    if (!sContext) {
+        return nullptr;
+    }
+
+    if (!sContext->copy(proxy.get(), subset, SkIPoint::Make(0, 0))) {
+        return nullptr;
+    }
+
+    // MDB: this call is okay bc we know 'sContext' was kExact
+    return sk_make_sp<SkImage_Gpu>(fContext, kNeedNewImageUniqueID,
+                                   fImageAlphaType, sContext->asTextureProxyRef(),
+                                   fImageColorSpace, fBudgeted);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+//*** bundle this into a helper function used by this and SkImage_Gpu?
+sk_sp<SkImage> SkImage_GpuYUVA::MakeFromYUVATextures(GrContext* ctx,
+                                                     SkYUVColorSpace colorSpace,
+                                                     const GrBackendTexture yuvaTextures[],
+                                                     SkYUVAIndex yuvaIndices[4],
+                                                     SkISize size,
+                                                     GrSurfaceOrigin origin,
+                                                     sk_sp<SkColorSpace> imageColorSpace) {
+    GrProxyProvider* proxyProvider = ctx->contextPriv().proxyProvider();
+
+    // Right now this still only deals with YUV and NV12 formats. Assuming that YUV has different
+    // textures for U and V planes, while NV12 uses same texture for U and V planes.
+    bool nv12 = (yuvaIndices[1].fIndex == yuvaIndices[2].fIndex);
+    auto ct = nv12 ? kRGBA_8888_SkColorType : kAlpha_8_SkColorType;
+
+    // We need to make a copy of the input backend textures because we need to preserve the result
+    // of validate_backend_texture.
+    GrBackendTexture yuvaTexturesCopy[4];
+    for (int i = 0; i < 4; ++i) {
+        // Validate that the yuvaIndices refer to valid backend textures.
+        const SkYUVAIndex& yuvaIndex = yuvaIndices[i];
+        if (3 == i && yuvaIndex.fIndex == -1) {
+            // Meaning the A plane isn't passed in.
+            continue;
+        }
+        if (yuvaIndex.fIndex == -1 || yuvaIndex.fIndex > 3) {
+            // Y plane, U plane, and V plane must refer to image sources being passed in. There are
+            // at most 4 image sources being passed in, could not have a index more than 3.
+            return nullptr;
+        }
+        if (!yuvaTexturesCopy[yuvaIndex.fIndex].isValid()) {
+            yuvaTexturesCopy[yuvaIndex.fIndex] = yuvaTextures[yuvaIndex.fIndex];
+            // TODO: Instead of using assumption about whether it is NV12 format to guess colorType,
+            // actually use channel information here.
+            if (!validate_backend_texture(ctx, yuvaTexturesCopy[i], &yuvaTexturesCopy[i].fConfig,
+                                          ct, kUnpremul_SkAlphaType, nullptr)) {
+                return nullptr;
+            }
+        }
+
+        // TODO: Check that for each plane, the channel actually exist in the image source we are
+        // reading from.
+    }
+
+    sk_sp<GrTextureProxy> tempTextureProxies[4]; // build from yuvaTextures
+    for (int i = 0; i < 4; ++i) {
+        // Fill in tempTextureProxies to avoid duplicate texture proxies.
+        int textureIndex = yuvaIndices[i].fIndex;
+
+        // Safely ignore since this means we are missing the A plane.
+        if (textureIndex == -1) {
+            SkASSERT(3 == i);
+            continue;
+        }
+
+        if (!tempTextureProxies[textureIndex]) {
+            SkASSERT(yuvaTexturesCopy[textureIndex].isValid());
+            tempTextureProxies[textureIndex] =
+                proxyProvider->wrapBackendTexture(yuvaTexturesCopy[textureIndex], origin);
+        }
+    }
+    if (!tempTextureProxies[yuvaIndices[0].fIndex] || !tempTextureProxies[yuvaIndices[1].fIndex] ||
+        !tempTextureProxies[yuvaIndices[2].fIndex]) {
+        return nullptr;
+    }
+
+    return sk_make_sp<SkImage_GpuYUVA>(sk_ref_sp(ctx), kNeedNewImageUniqueID, colorSpace,
+                                       tempTextureProxies, yuvaIndices, size, origin,
+                                       imageColorSpace, SkBudgeted::kYes);
+}
+
diff --git a/src/image/SkImage_GpuYUVA.h b/src/image/SkImage_GpuYUVA.h
new file mode 100644
index 0000000..8f9ebfd
--- /dev/null
+++ b/src/image/SkImage_GpuYUVA.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkImage_GpuYUVA_DEFINED
+#define SkImage_GpuYUVA_DEFINED
+
+#include "GrBackendSurface.h"
+#include "GrContext.h"
+#include "SkCachedData.h"
+#include "SkImagePriv.h"
+#include "SkImage_Base.h"
+
+class GrTexture;
+
+// Wraps the 3 or 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.
+class SkImage_GpuYUVA : public SkImage_Base {
+public:
+    SkImage_GpuYUVA(sk_sp<GrContext>, uint32_t uniqueID, SkYUVColorSpace,
+                    sk_sp<GrTextureProxy> proxies[], SkYUVAIndex yuvaIndices[4], SkISize size,
+                    GrSurfaceOrigin, sk_sp<SkColorSpace>, SkBudgeted);
+    ~SkImage_GpuYUVA() override;
+
+    SkImageInfo onImageInfo() const override;
+
+    bool getROPixels(SkBitmap*, SkColorSpace* dstColorSpace, CachingHint) const override {
+        return false;
+    }
+    sk_sp<SkImage> onMakeSubset(const SkIRect&) const override;
+
+    GrContext* context() const override { return fContext.get(); }
+    GrTextureProxy* peekProxy() const override { return nullptr; }
+    sk_sp<GrTextureProxy> asTextureProxyRef() const override;
+    sk_sp<GrTextureProxy> asTextureProxyRef(GrContext*, const GrSamplerState&, SkColorSpace*,
+                                            sk_sp<SkColorSpace>*,
+                                            SkScalar scaleAdjust[2]) const override;
+
+    sk_sp<GrTextureProxy> refPinnedTextureProxy(uint32_t* uniqueID) const override {
+        return nullptr;
+    }
+
+    GrBackendTexture onGetBackendTexture(bool flushPendingGrContextIO,
+                                         GrSurfaceOrigin* origin) const override {
+        return GrBackendTexture(); // invalid
+    }
+
+    GrTexture* onGetTexture() const override { return nullptr; }
+
+    bool onReadPixels(const SkImageInfo&, void* dstPixels, size_t dstRowBytes,
+                      int srcX, int srcY, CachingHint) const override;
+
+    sk_sp<SkCachedData> getPlanes(SkYUVSizeInfo*, SkYUVColorSpace*,
+                                  const void* planes[3]) override { return nullptr; }
+
+    sk_sp<SkImage> onMakeColorSpace(sk_sp<SkColorSpace>) const override { return nullptr; }
+
+    // These need to match the ones defined elsewhere
+    typedef ReleaseContext TextureContext;
+    typedef void (*TextureFulfillProc)(TextureContext textureContext, GrBackendTexture* outTexture);
+    typedef void (*PromiseDoneProc)(TextureContext textureContext);
+
+    /**
+        Create a new SkImage_GpuYUVA that's very similar to SkImage created by MakeFromYUVATextures.
+        The main difference is that the client doesn't have the backend textures on the gpu yet but
+        they know all the properties of the texture. So instead of passing in GrBackendTextures the
+        client supplies GrBackendFormats and the image size.
+
+        When we actually send the draw calls to the GPU, we will call the textureFulfillProc and
+        the client will return the GrBackendTextures to us. The properties of the GrBackendTextures
+        must match those set during the SkImage creation, and it must have valid backend gpu
+        textures. The gpu textures supplied by the client must stay valid until we call the
+        textureReleaseProc.
+
+        When we are done with the texture returned by the textureFulfillProc we will call the
+        textureReleaseProc passing in the textureContext. This is a signal to the client that they
+        are free to delete the underlying gpu textures. If future draws also use the same promise
+        image we will call the textureFulfillProc again if we've already called the
+        textureReleaseProc. We will always call textureFulfillProc and textureReleaseProc in pairs.
+        In other words we will never call textureFulfillProc or textureReleaseProc multiple times
+        for the same textureContext before calling the other.
+
+        We call the promiseDoneProc when we will no longer call the textureFulfillProc again. We
+        also guarantee that there will be no outstanding textureReleaseProcs that still need to be
+        called when we call the textureDoneProc. Thus when the textureDoneProc gets called the
+        client is able to cleanup all GPU objects and meta data needed for the textureFulfill call.
+
+        @param context             Gpu context
+        @param yuvColorSpace       color range of expected YUV pixels
+        @param yuvaFormats         formats of promised gpu textures for each YUVA plane
+        @param yuvaIndices         mapping from yuv plane index to texture representing that plane
+        @param imageSize           width and height of promised gpu texture
+        @param imageOrigin         one of: kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin
+        @param imageColorSpace     range of colors; may be nullptr
+        @param textureFulfillProc  function called to get actual gpu texture
+        @param textureReleaseProc  function called when texture can be released
+        @param promiseDoneProc     function called when we will no longer call textureFulfillProc
+        @param textureContext      state passed to textureFulfillProc and textureReleaseProc
+        @return                    created SkImage, or nullptr
+     */
+    static sk_sp<SkImage> MakePromiseYUVATexture(GrContext* context,
+                                                 SkYUVColorSpace yuvColorSpace,
+                                                 const GrBackendFormat yuvaFormats[],
+                                                 const SkYUVAIndex yuvaIndices[4],
+                                                 SkISize imageSize,
+                                                 GrSurfaceOrigin imageOrigin,
+                                                 sk_sp<SkColorSpace> imageColorSpace,
+                                                 TextureFulfillProc textureFulfillProc,
+                                                 TextureReleaseProc textureReleaseProc,
+                                                 PromiseDoneProc promiseDoneProc,
+                                                 TextureContext textureContexts[]) {
+        return nullptr;
+    }
+
+    static sk_sp<SkImage> MakeFromYUVATextures(GrContext* context,
+                                               SkYUVColorSpace yuvColorSpace,
+                                               const GrBackendTexture yuvaTextures[],
+                                               SkYUVAIndex yuvaIndices[4],
+                                               SkISize imageSize,
+                                               GrSurfaceOrigin imageOrigin,
+                                               sk_sp<SkColorSpace> imageColorSpace);
+
+    bool onIsValid(GrContext*) const override { return false; }
+
+private:
+    sk_sp<GrContext>                 fContext;
+    // This array will usually only be sparsely populated.
+    // The actual non-null fields are dictated by the 'fYUVAIndices' indices
+    sk_sp<GrTextureProxy>            fProxies[4];
+    SkYUVAIndex                      fYUVAIndices[4];
+    // 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 RGBProxy.
+    sk_sp<GrTextureProxy>            fRGBProxy;
+    const SkBudgeted                 fBudgeted;
+    const SkYUVColorSpace            fColorSpace;
+    GrSurfaceOrigin                  fOrigin;
+    SkAlphaType                      fImageAlphaType;
+    sk_sp<SkColorSpace>              fImageColorSpace;
+
+    typedef SkImage_Base INHERITED;
+};
+
+#endif