Store mipmap levels in deferred texture image

This is a follow-up to https://codereview.chromium.org/2115023002/ and
https://codereview.chromium.org/2034933003/ which were reverted due to
an access violation and a memory leak, respectively.

When creating the deferred texture image, detect if using medium / high
quality. If so, generate and store mipmaps in the deferred texture
image.

When creating a texture from that be sure to read it back out.

BUG=578304
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2242883004

Review-Url: https://codereview.chromium.org/2242883004
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index 53765b1..06ec224 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -5,11 +5,16 @@
  * found in the LICENSE file.
  */
 
+#include <cstddef>
+#include <cstring>
+#include <type_traits>
+
 #include "SkAutoPixmapStorage.h"
 #include "GrCaps.h"
 #include "GrContext.h"
 #include "GrDrawContext.h"
 #include "GrImageIDTextureAdjuster.h"
+#include "GrTexturePriv.h"
 #include "effects/GrYUVEffect.h"
 #include "SkCanvas.h"
 #include "SkBitmapCache.h"
@@ -358,35 +363,84 @@
 };
 
 struct DeferredTextureImage {
-    uint32_t    fContextUniqueID;
+    uint32_t               fContextUniqueID;
+    // Right now, the gamma treatment is only considered when generating mipmaps
+    SkSourceGammaTreatment fGammaTreatment;
     // We don't store a SkImageInfo because it contains a ref-counted SkColorSpace.
-    int         fWidth;
-    int         fHeight;
-    SkColorType fColorType;
-    SkAlphaType fAlphaType;
-    void*       fColorSpace;
-    size_t      fColorSpaceSize;
-    int         fColorTableCnt;
-    uint32_t*   fColorTableData;
-    int         fMipMapLevelCount;
+    int                    fWidth;
+    int                    fHeight;
+    SkColorType            fColorType;
+    SkAlphaType            fAlphaType;
+    void*                  fColorSpace;
+    size_t                 fColorSpaceSize;
+    int                    fColorTableCnt;
+    uint32_t*              fColorTableData;
+    int                    fMipMapLevelCount;
     // The fMipMapLevelData array may contain more than 1 element.
     // It contains fMipMapLevelCount elements.
     // That means this struct's size is not known at compile-time.
-    MipMapLevelData fMipMapLevelData[1];
+    MipMapLevelData        fMipMapLevelData[1];
 };
 }  // anonymous namespace
 
+static bool should_use_mip_maps(const SkImage::DeferredTextureImageUsageParams & param) {
+    bool shouldUseMipMaps = false;
+
+    // Use mipmaps if either
+    // 1.) it is a perspective matrix, or
+    // 2.) the quality is med/high and the scale is < 1
+    if (param.fMatrix.hasPerspective()) {
+        shouldUseMipMaps = true;
+    }
+    if (param.fQuality == kMedium_SkFilterQuality ||
+        param.fQuality == kHigh_SkFilterQuality) {
+        SkScalar minAxisScale = param.fMatrix.getMinScale();
+        if (minAxisScale != -1.f && minAxisScale < 1.f) {
+            shouldUseMipMaps = true;
+        }
+    }
+
+
+    return shouldUseMipMaps;
+}
+
+namespace {
+
+class DTIBufferFiller
+{
+public:
+    explicit DTIBufferFiller(intptr_t bufferAsInt)
+        : bufferAsInt_(bufferAsInt) {}
+
+    void fillMember(const void* source, size_t memberOffset, size_t size) {
+        memcpy(reinterpret_cast<void*>(bufferAsInt_ + memberOffset), source, size);
+    }
+
+private:
+
+    intptr_t bufferAsInt_;
+};
+}
+
+#define FILL_MEMBER(bufferFiller, member, source) \
+    bufferFiller.fillMember(source, \
+               offsetof(DeferredTextureImage, member), \
+               sizeof(DeferredTextureImage::member));
+
 size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy& proxy,
                                             const DeferredTextureImageUsageParams params[],
-                                            int paramCnt, void* buffer) const {
+                                            int paramCnt, void* buffer,
+                                            SkSourceGammaTreatment gammaTreatment) const {
     // Extract relevant min/max values from the params array.
     int lowestPreScaleMipLevel = params[0].fPreScaleMipLevel;
     SkFilterQuality highestFilterQuality = params[0].fQuality;
+    bool useMipMaps = should_use_mip_maps(params[0]);
     for (int i = 1; i < paramCnt; ++i) {
         if (lowestPreScaleMipLevel > params[i].fPreScaleMipLevel)
             lowestPreScaleMipLevel = params[i].fPreScaleMipLevel;
         if (highestFilterQuality < params[i].fQuality)
             highestFilterQuality = params[i].fQuality;
+        useMipMaps |= should_use_mip_maps(params[i]);
     }
 
     const bool fillMode = SkToBool(buffer);
@@ -455,11 +509,35 @@
             SkASSERT(!pixmap.ctable());
         }
     }
+    SkAlphaType at = this->isOpaque() ? kOpaque_SkAlphaType : kPremul_SkAlphaType;
     int mipMapLevelCount = 1;
+    if (useMipMaps) {
+        // SkMipMap only deals with the mipmap levels it generates, which does
+        // not include the base level.
+        // That means it generates and holds levels 1-x instead of 0-x.
+        // So the total mipmap level count is 1 more than what
+        // SkMipMap::ComputeLevelCount returns.
+        mipMapLevelCount = SkMipMap::ComputeLevelCount(scaledSize.width(), scaledSize.height()) + 1;
+
+        // We already initialized pixelSize to the size of the base level.
+        // SkMipMap will generate the extra mipmap levels. Their sizes need to
+        // be added to the total.
+        // Index 0 here does not refer to the base mipmap level -- it is
+        // SkMipMap's first generated mipmap level (level 1).
+        for (int currentMipMapLevelIndex = mipMapLevelCount - 2; currentMipMapLevelIndex >= 0;
+             currentMipMapLevelIndex--) {
+            SkISize mipSize = SkMipMap::ComputeLevelSize(scaledSize.width(), scaledSize.height(),
+                                                         currentMipMapLevelIndex);
+            SkImageInfo mipInfo = SkImageInfo::MakeN32(mipSize.fWidth, mipSize.fHeight, at);
+            pixelSize += SkAlign8(SkAutoPixmapStorage::AllocSize(mipInfo, nullptr));
+        }
+    }
     size_t size = 0;
     size_t dtiSize = SkAlign8(sizeof(DeferredTextureImage));
     size += dtiSize;
-    size += mipMapLevelCount * sizeof(MipMapLevelData);
+    size += (mipMapLevelCount - 1) * sizeof(MipMapLevelData);
+    // We subtract 1 because DeferredTextureImage already includes the base
+    // level in its size
     size_t pixelOffset = size;
     size += pixelSize;
     size_t ctOffset = size;
@@ -475,37 +553,106 @@
         return size;
     }
     intptr_t bufferAsInt = reinterpret_cast<intptr_t>(buffer);
-    void* pixels = reinterpret_cast<void*>(bufferAsInt + pixelOffset);
-    SkPMColor* ct = nullptr;
+    intptr_t pixelsAsInt = bufferAsInt + pixelOffset;
+    void* pixels = reinterpret_cast<void*>(pixelsAsInt);
+    void* ct = nullptr;
     if (ctSize) {
-        ct = reinterpret_cast<SkPMColor*>(bufferAsInt + ctOffset);
+        ct = reinterpret_cast<void*>(bufferAsInt + ctOffset);
     }
 
-    memcpy(pixels, pixmap.addr(), pixmap.getSafeSize());
+    memcpy(reinterpret_cast<void*>(SkAlign8(pixelsAsInt)), pixmap.addr(), pixmap.getSafeSize());
     if (ctSize) {
         memcpy(ct, pixmap.ctable()->readColors(), ctSize);
     }
 
     SkASSERT(info == pixmap.info());
     size_t rowBytes = pixmap.rowBytes();
-    DeferredTextureImage* dti = new (buffer) DeferredTextureImage();
-    dti->fContextUniqueID = proxy.fContextUniqueID;
-    dti->fWidth = info.width();
-    dti->fHeight = info.height();
-    dti->fColorType = info.colorType();
-    dti->fAlphaType = info.alphaType();
-    dti->fColorTableCnt = ctCount;
-    dti->fColorTableData = ct;
-    dti->fMipMapLevelCount = mipMapLevelCount;
-    dti->fMipMapLevelData[0].fPixelData = pixels;
-    dti->fMipMapLevelData[0].fRowBytes = rowBytes;
+    static_assert(std::is_standard_layout<DeferredTextureImage>::value,
+                  "offsetof, which we use below, requires the type have standard layout");
+    auto dtiBufferFiller = DTIBufferFiller{bufferAsInt};
+    FILL_MEMBER(dtiBufferFiller, fGammaTreatment, &gammaTreatment);
+    FILL_MEMBER(dtiBufferFiller, fContextUniqueID, &proxy.fContextUniqueID);
+    int width = info.width();
+    FILL_MEMBER(dtiBufferFiller, fWidth, &width);
+    int height = info.height();
+    FILL_MEMBER(dtiBufferFiller, fHeight, &height);
+    SkColorType colorType = info.colorType();
+    FILL_MEMBER(dtiBufferFiller, fColorType, &colorType);
+    SkAlphaType alphaType = info.alphaType();
+    FILL_MEMBER(dtiBufferFiller, fAlphaType, &alphaType);
+    FILL_MEMBER(dtiBufferFiller, fColorTableCnt, &ctCount);
+    FILL_MEMBER(dtiBufferFiller, fColorTableData, &ct);
+    FILL_MEMBER(dtiBufferFiller, fMipMapLevelCount, &mipMapLevelCount);
+//    FILL_MEMBER(dtiBufferFiller, fMipMapLevelCount[0].fPixelData, &pixels);
+    memcpy(reinterpret_cast<void*>(bufferAsInt +
+                                  offsetof(DeferredTextureImage, fMipMapLevelData[0].fPixelData)),
+           &pixels, sizeof(pixels));
+    memcpy(reinterpret_cast<void*>(bufferAsInt +
+                                  offsetof(DeferredTextureImage, fMipMapLevelData[0].fRowBytes)),
+           &rowBytes, sizeof(rowBytes));
     if (colorSpaceSize) {
-        dti->fColorSpace = reinterpret_cast<void*>(bufferAsInt + colorSpaceOffset);
-        dti->fColorSpaceSize = colorSpaceSize;
-        info.colorSpace()->writeToMemory(dti->fColorSpace);
+        void* colorSpace = reinterpret_cast<void*>(bufferAsInt + colorSpaceOffset);
+        FILL_MEMBER(dtiBufferFiller, fColorSpace, &colorSpace);
+        FILL_MEMBER(dtiBufferFiller, fColorSpaceSize, &colorSpaceSize);
+        info.colorSpace()->writeToMemory(reinterpret_cast<void*>(bufferAsInt + colorSpaceOffset));
     } else {
-        dti->fColorSpace = nullptr;
-        dti->fColorSpaceSize = 0;
+        memset(reinterpret_cast<void*>(bufferAsInt +
+                                       offsetof(DeferredTextureImage, fColorSpace)),
+               0, sizeof(DeferredTextureImage::fColorSpace));
+        memset(reinterpret_cast<void*>(bufferAsInt +
+                                       offsetof(DeferredTextureImage, fColorSpaceSize)),
+               0, sizeof(DeferredTextureImage::fColorSpaceSize));
+    }
+
+    // Fill in the mipmap levels if they exist
+    intptr_t mipLevelPtr = bufferAsInt + pixelOffset + SkAlign8(pixmap.getSafeSize());
+
+    if (useMipMaps) {
+        // offsetof, which we use below, requires the type have standard layout
+        SkASSERT(std::is_standard_layout<MipMapLevelData>::value);
+
+        SkAutoTDelete<SkMipMap> mipmaps(SkMipMap::Build(pixmap, gammaTreatment, nullptr));
+        // SkMipMap holds only the mipmap levels it generates.
+        // A programmer can use the data they provided to SkMipMap::Build as level 0.
+        // So the SkMipMap provides levels 1-x but it stores them in its own
+        // range 0-(x-1).
+        for (int generatedMipLevelIndex = 0; generatedMipLevelIndex < mipMapLevelCount - 1;
+             generatedMipLevelIndex++) {
+            SkISize mipSize = SkMipMap::ComputeLevelSize(scaledSize.width(), scaledSize.height(),
+                                                         generatedMipLevelIndex);
+
+            SkImageInfo mipInfo = SkImageInfo::MakeN32(mipSize.fWidth, mipSize.fHeight, at);
+            SkMipMap::Level mipLevel;
+            mipmaps->getLevel(generatedMipLevelIndex, &mipLevel);
+
+            // Make sure the mipmap data is after the start of the buffer
+            SkASSERT(mipLevelPtr > bufferAsInt);
+            // Make sure the mipmap data starts before the end of the buffer
+            SkASSERT(static_cast<size_t>(mipLevelPtr) < bufferAsInt + pixelOffset + pixelSize);
+            // Make sure the mipmap data ends before the end of the buffer
+            SkASSERT(mipLevelPtr + mipLevel.fPixmap.getSafeSize() <=
+                     bufferAsInt + pixelOffset + pixelSize);
+
+            // getSafeSize includes rowbyte padding except for the last row,
+            // right?
+
+            memcpy(reinterpret_cast<void*>(mipLevelPtr), mipLevel.fPixmap.addr(),
+                       mipLevel.fPixmap.getSafeSize());
+
+            memcpy(reinterpret_cast<void*>(bufferAsInt +
+                offsetof(DeferredTextureImage, fMipMapLevelData) +
+                sizeof(MipMapLevelData) * (generatedMipLevelIndex + 1) +
+                offsetof(MipMapLevelData, fPixelData)),
+                   &mipLevelPtr, sizeof(void*));
+            size_t rowBytes = mipLevel.fPixmap.rowBytes();
+            memcpy(reinterpret_cast<void*>(bufferAsInt +
+                offsetof(DeferredTextureImage, fMipMapLevelData) +
+                sizeof(MipMapLevelData) * (generatedMipLevelIndex + 1) +
+                offsetof(MipMapLevelData, fRowBytes)),
+                   &rowBytes, sizeof(rowBytes));
+
+            mipLevelPtr += SkAlign8(mipLevel.fPixmap.getSafeSize());
+        }
     }
     return size;
 }
@@ -525,24 +672,38 @@
         SkASSERT(dti->fColorTableData);
         colorTable.reset(new SkColorTable(dti->fColorTableData, dti->fColorTableCnt));
     }
-    SkASSERT(dti->fMipMapLevelCount == 1);
+    int mipLevelCount = dti->fMipMapLevelCount;
+    SkASSERT(mipLevelCount >= 1);
     sk_sp<SkColorSpace> colorSpace;
     if (dti->fColorSpaceSize) {
         colorSpace = SkColorSpace::Deserialize(dti->fColorSpace, dti->fColorSpaceSize);
     }
     SkImageInfo info = SkImageInfo::Make(dti->fWidth, dti->fHeight,
                                          dti->fColorType, dti->fAlphaType, colorSpace);
-    SkPixmap pixmap;
-    pixmap.reset(info, dti->fMipMapLevelData[0].fPixelData,
-                 dti->fMipMapLevelData[0].fRowBytes, colorTable.get());
-    return SkImage::MakeTextureFromPixmap(context, pixmap, budgeted);
+    if (mipLevelCount == 1) {
+        SkPixmap pixmap;
+        pixmap.reset(info, dti->fMipMapLevelData[0].fPixelData,
+                     dti->fMipMapLevelData[0].fRowBytes, colorTable.get());
+        return SkImage::MakeTextureFromPixmap(context, pixmap, budgeted);
+    } else {
+        SkAutoTDeleteArray<GrMipLevel> texels(new GrMipLevel[mipLevelCount]);
+        for (int i = 0; i < mipLevelCount; i++) {
+            texels[i].fPixels = dti->fMipMapLevelData[i].fPixelData;
+            texels[i].fRowBytes = dti->fMipMapLevelData[i].fRowBytes;
+        }
+
+        return SkImage::MakeTextureFromMipMap(context, info, texels.get(),
+                                              mipLevelCount, SkBudgeted::kYes,
+                                              dti->fGammaTreatment);
+    }
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 sk_sp<SkImage> SkImage::MakeTextureFromMipMap(GrContext* ctx, const SkImageInfo& info,
                                               const GrMipLevel* texels, int mipLevelCount,
-                                              SkBudgeted budgeted) {
+                                              SkBudgeted budgeted,
+                                              SkSourceGammaTreatment gammaTreatment) {
     if (!ctx) {
         return nullptr;
     }
@@ -550,6 +711,7 @@
     if (!texture) {
         return nullptr;
     }
+    texture->texturePriv().setGammaTreatment(gammaTreatment);
     return sk_make_sp<SkImage_Gpu>(texture->width(), texture->height(), kNeedNewImageUniqueID,
                                    info.alphaType(), texture, sk_ref_sp(info.colorSpace()),
                                    budgeted);