Add prescale option to deferred params
authorericrk <ericrk@chromium.org>
Mon, 13 Jun 2016 18:18:14 +0000 (11:18 -0700)
committerCommit bot <commit-bot@chromium.org>
Mon, 13 Jun 2016 18:18:14 +0000 (11:18 -0700)
Currently, Skia always uploads GPU textures at full resolution. This
change allows us to pass a pre-scale mip level to the
deferred texture image logic, which causes us to pre-scale the image
to the given mip level, and upload that mip level instead of the full
image.
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2007113008

Review-Url: https://codereview.chromium.org/2007113008

gm/deferredtextureimagedata.cpp [new file with mode: 0644]
include/core/SkImage.h
src/image/SkImage_Gpu.cpp
tests/ImageTest.cpp

diff --git a/gm/deferredtextureimagedata.cpp b/gm/deferredtextureimagedata.cpp
new file mode 100644 (file)
index 0000000..951e8db
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include <vector>
+
+#include "gm.h"
+#include "GrContext.h"
+#include "Resources.h"
+#include "SkImage.h"
+
+#if SK_SUPPORT_GPU
+
+// Helper function that uploads the given SkImage using MakdeFromDeferredTextureImageData and then
+// draws the uploaded version at the specified coordinates.
+static bool DrawDeferredTextureImageData(GrContext* context, SkCanvas* canvas, SkImage* image,
+                                         SkImage::DeferredTextureImageUsageParams* params,
+                                         SkScalar x, SkScalar y) {
+  size_t deferredSize = 
+      image->getDeferredTextureImageData(*context->threadSafeProxy(), params, 1, nullptr);
+  if (deferredSize == 0) {
+      SkDebugf("\nCould not create DeferredTextureImageData.\n");
+      return false;
+  }
+
+  std::vector<uint8_t> memory;
+  memory.resize(deferredSize);
+  image->getDeferredTextureImageData(*context->threadSafeProxy(), params, 1, memory.data());
+  sk_sp<SkImage> uploadedImage =
+      SkImage::MakeFromDeferredTextureImageData(context, memory.data(), SkBudgeted::kNo);
+  canvas->drawImage(uploadedImage, x, y);
+
+  return true;
+}
+
+DEF_SIMPLE_GM(deferred_texture_image_data, canvas, 60, 10) {
+    GrContext* context = canvas->getGrContext();
+    if (!context) {
+        skiagm::GM::DrawGpuOnlyMessage(canvas);
+        return;
+    }
+
+    sk_sp<SkImage> encodedImage = GetResourceAsImage("randPixels.png");
+    if (!encodedImage) {
+        SkDebugf("\nCould not load resource.\n");
+        return;
+    }
+
+    SkBitmap bitmap;
+    if (!GetResourceAsBitmap("randPixels.png", &bitmap)) {
+        SkDebugf("\nCould not decode resource.\n");
+        return;
+    }
+
+    sk_sp<SkImage> decodedImage = SkImage::MakeFromBitmap(bitmap);
+
+    // Draw both encoded and decoded image via deferredTextureImageData.
+    SkImage::DeferredTextureImageUsageParams params;
+    DrawDeferredTextureImageData(context, canvas, encodedImage.get(), &params, 0, 0);
+    DrawDeferredTextureImageData(context, canvas, decodedImage.get(), &params, 10, 0);
+
+    // Draw 50% scaled versions of the encoded and decoded images at medium quality.
+    SkImage::DeferredTextureImageUsageParams mediumScaledParams;
+    mediumScaledParams.fPreScaleMipLevel = 1;
+    mediumScaledParams.fQuality = kMedium_SkFilterQuality;
+
+    DrawDeferredTextureImageData(context, canvas, encodedImage.get(), &mediumScaledParams, 20, 0);
+    DrawDeferredTextureImageData(context, canvas, decodedImage.get(), &mediumScaledParams, 30, 0);
+
+    // Draw 50% scaled versions of the encoded and decoded images at none quality.
+    SkImage::DeferredTextureImageUsageParams noneScaledParams;
+    noneScaledParams.fPreScaleMipLevel = 1;
+    noneScaledParams.fQuality = kNone_SkFilterQuality;
+
+    DrawDeferredTextureImageData(context, canvas, encodedImage.get(), &noneScaledParams, 40, 0);
+    DrawDeferredTextureImageData(context, canvas, decodedImage.get(), &noneScaledParams, 50, 0);
+}
+
+#endif
index 52a2bf7af76674125225ab82f17a4b84b169debd..c9c56684604f8b1f12406c67593ed7e0b4b129fc 100644 (file)
@@ -352,8 +352,13 @@ public:
 
     /** Drawing params for which a deferred texture image data should be optimized. */
     struct DeferredTextureImageUsageParams {
+        DeferredTextureImageUsageParams() : fPreScaleMipLevel(0) {}
+        DeferredTextureImageUsageParams(const SkMatrix matrix, const SkFilterQuality quality, 
+                                        int preScaleMipLevel)
+            : fMatrix(matrix), fQuality(quality), fPreScaleMipLevel(preScaleMipLevel) {}
         SkMatrix        fMatrix;
         SkFilterQuality fQuality;
+        int             fPreScaleMipLevel;
     };
 
     /**
index 9ad33ea2baa013a8a5e4939522f6a7eb3886beea..7f459376e7de8c173966f2b8af8b758a38c98ac2 100644 (file)
@@ -16,6 +16,7 @@
 #include "SkGrPixelRef.h"
 #include "SkGrPriv.h"
 #include "SkImage_Gpu.h"
+#include "SkMipMap.h"
 #include "SkPixelRef.h"
 
 SkImage_Gpu::SkImage_Gpu(int w, int h, uint32_t uniqueID, SkAlphaType at, GrTexture* tex,
@@ -374,15 +375,44 @@ private:
 };
 
 size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy& proxy,
-                                            const DeferredTextureImageUsageParams[],
+                                            const DeferredTextureImageUsageParams params[],
                                             int paramCnt, void* buffer) const {
+    // Extract relevant min/max values from the params array.
+    int lowestPreScaleMipLevel = params[0].fPreScaleMipLevel;
+    SkFilterQuality highestFilterQuality = params[0].fQuality;
+    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;
+    }
+
     const bool fillMode = SkToBool(buffer);
     if (fillMode && !SkIsAlign8(reinterpret_cast<intptr_t>(buffer))) {
         return 0;
     }
 
+    // Calculate scaling parameters.
+    bool isScaled = lowestPreScaleMipLevel != 0;
+
+    SkISize scaledSize;
+    if (isScaled) {
+        // SkMipMap::ComputeLevelSize takes an index into an SkMipMap. SkMipMaps don't contain the
+        // base level, so to get an SkMipMap index we must subtract one from the GL MipMap level.
+        scaledSize = SkMipMap::ComputeLevelSize(this->width(), this->height(),
+                                                lowestPreScaleMipLevel - 1);
+    } else {
+        scaledSize = SkISize::Make(this->width(), this->height());
+    }
+
+    // We never want to scale at higher than SW medium quality, as SW medium matches GPU high.
+    SkFilterQuality scaleFilterQuality = highestFilterQuality;
+    if (scaleFilterQuality > kMedium_SkFilterQuality) {
+        scaleFilterQuality = kMedium_SkFilterQuality;
+    }
+
     const int maxTextureSize = proxy.fCaps->maxTextureSize();
-    if (width() > maxTextureSize || height() > maxTextureSize) {
+    if (scaledSize.width() > maxTextureSize || scaledSize.height() > maxTextureSize) {
         return 0;
     }
 
@@ -391,7 +421,7 @@ size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy& prox
     size_t pixelSize = 0;
     size_t ctSize = 0;
     int ctCount = 0;
-    if (this->peekPixels(&pixmap)) {
+    if (!isScaled && this->peekPixels(&pixmap)) {
         info = pixmap.info();
         pixelSize = SkAlign8(pixmap.getSafeSize());
         if (pixmap.ctable()) {
@@ -403,16 +433,23 @@ size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy& prox
         // In the future we will access the cacherator and get the exact data that we want to (e.g.
         // yuv planes) upload.
         SkAutoTUnref<SkData> data(this->refEncoded());
-        if (!data) {
+        if (!data && !this->peekPixels(nullptr)) {
             return 0;
         }
         SkAlphaType at = this->isOpaque() ? kOpaque_SkAlphaType : kPremul_SkAlphaType;
-        info = SkImageInfo::MakeN32(this->width(), this->height(), at);
+        info = SkImageInfo::MakeN32(scaledSize.width(), scaledSize.height(), at);
         pixelSize = SkAlign8(SkAutoPixmapStorage::AllocSize(info, nullptr));
         if (fillMode) {
             pixmap.alloc(info);
-            if (!this->readPixels(pixmap, 0, 0, SkImage::kDisallow_CachingHint)) {
-                return 0;
+            if (isScaled) {
+                if (!this->scalePixels(pixmap, scaleFilterQuality,
+                                       SkImage::kDisallow_CachingHint)) {
+                    return 0;
+                }
+            } else {
+                if (!this->readPixels(pixmap, 0, 0, SkImage::kDisallow_CachingHint)) {
+                    return 0;
+                }
             }
             SkASSERT(!pixmap.ctable());
         }
index 0922364d0fda07742188528c36c1c4ee9a028a3a..354476478259534e45befe5815a06a2df241344f 100644 (file)
@@ -7,6 +7,7 @@
 
 #include <functional>
 #include <initializer_list>
+#include <vector>
 #include "DMGpuSupport.h"
 
 #include "SkAutoPixmapStorage.h"
@@ -67,6 +68,15 @@ static sk_sp<SkImage> create_image() {
     draw_image_test_pattern(surface->getCanvas());
     return surface->makeImageSnapshot();
 }
+static sk_sp<SkImage> create_image_large() {
+    const SkImageInfo info = SkImageInfo::MakeN32(32000, 32, kOpaque_SkAlphaType);
+    auto surface(SkSurface::MakeRaster(info));
+    surface->getCanvas()->clear(SK_ColorWHITE);
+    SkPaint paint;
+    paint.setColor(SK_ColorBLACK);
+    surface->getCanvas()->drawRect(SkRect::MakeXYWH(4000, 2, 28000, 30), paint);
+    return surface->makeImageSnapshot();
+}
 
 static SkData* create_image_data(SkImageInfo* info) {
     *info = SkImageInfo::MakeN32(20, 20, kOpaque_SkAlphaType);
@@ -837,33 +847,45 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DeferredTextureImage, reporter, ctxInfo) {
     testContext->makeCurrent();
     REPORTER_ASSERT(reporter, proxy);
     struct {
-        std::function<sk_sp<SkImage> ()> fImageFactory;
-        bool                       fExpectation;
+        std::function<sk_sp<SkImage> ()>                      fImageFactory;
+        std::vector<SkImage::DeferredTextureImageUsageParams> fParams;
+        SkFilterQuality                                       fExpectedQuality;
+        int                                                   fExpectedScaleFactor;
+        bool                                                  fExpectation;
     } testCases[] = {
-        { create_image,          true },
-        { create_codec_image,    true },
-        { create_data_image,     true },
-        { create_picture_image,  false },
-        { [context] { return create_gpu_image(context); }, false },
+        { create_image,          {{}}, kNone_SkFilterQuality, 1, true },
+        { create_codec_image,    {{}}, kNone_SkFilterQuality, 1, true },
+        { create_data_image,     {{}}, kNone_SkFilterQuality, 1, true },
+        { create_picture_image,  {{}}, kNone_SkFilterQuality, 1, false },
+        { [context] { return create_gpu_image(context); }, {{}}, kNone_SkFilterQuality, 1, false },
         // Create a texture image in a another GrContext.
         { [testContext, otherContextInfo] {
             otherContextInfo.testContext()->makeCurrent();
             sk_sp<SkImage> otherContextImage = create_gpu_image(otherContextInfo.grContext());
             testContext->makeCurrent();
             return otherContextImage;
-          }, false },
+          }, {{}}, kNone_SkFilterQuality, 1, false },
+        // Create an image that is too large to upload.
+        { create_image_large,    {{}}, kNone_SkFilterQuality, 1, false },
+        // Create an image that is too large, but is scaled to an acceptable size.
+        { create_image_large, {{SkMatrix::I(), kMedium_SkFilterQuality, 4}},
+          kMedium_SkFilterQuality, 16, true},
+        // Create an image with multiple low filter qualities, make sure we round up.
+        { create_image_large, {{SkMatrix::I(), kNone_SkFilterQuality, 4},
+                               {SkMatrix::I(), kMedium_SkFilterQuality, 4}},
+          kMedium_SkFilterQuality, 16, true},
+        // Create an image with multiple prescale levels, make sure we chose the minimum scale.
+        { create_image_large, {{SkMatrix::I(), kMedium_SkFilterQuality, 5},
+                               {SkMatrix::I(), kMedium_SkFilterQuality, 4}},
+          kMedium_SkFilterQuality, 16, true},
     };
 
 
     for (auto testCase : testCases) {
         sk_sp<SkImage> image(testCase.fImageFactory());
-
-        // This isn't currently used in the implementation, just set any old values.
-        SkImage::DeferredTextureImageUsageParams params;
-        params.fQuality = kLow_SkFilterQuality;
-        params.fMatrix = SkMatrix::I();
-
-        size_t size = image->getDeferredTextureImageData(*proxy, &params, 1, nullptr);
+        size_t size = image->getDeferredTextureImageData(*proxy, testCase.fParams.data(),
+                                                         static_cast<int>(testCase.fParams.size()),
+                                                         nullptr);
 
         static const char *const kFS[] = { "fail", "succeed" };
         if (SkToBool(size) != testCase.fExpectation) {
@@ -873,10 +895,14 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DeferredTextureImage, reporter, ctxInfo) {
         if (size) {
             void* buffer = sk_malloc_throw(size);
             void* misaligned = reinterpret_cast<void*>(reinterpret_cast<intptr_t>(buffer) + 3);
-            if (image->getDeferredTextureImageData(*proxy, &params, 1, misaligned)) {
+            if (image->getDeferredTextureImageData(*proxy, testCase.fParams.data(),
+                                                   static_cast<int>(testCase.fParams.size()),
+                                                   misaligned)) {
                 ERRORF(reporter, "Should fail when buffer is misaligned.");
             }
-            if (!image->getDeferredTextureImageData(*proxy, &params, 1, buffer)) {
+            if (!image->getDeferredTextureImageData(*proxy, testCase.fParams.data(),
+                                                    static_cast<int>(testCase.fParams.size()),
+                                                    buffer)) {
                 ERRORF(reporter, "deferred image size succeeded but creation failed.");
             } else {
                 for (auto budgeted : { SkBudgeted::kNo, SkBudgeted::kYes }) {
@@ -884,7 +910,16 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DeferredTextureImage, reporter, ctxInfo) {
                         SkImage::MakeFromDeferredTextureImageData(context, buffer, budgeted));
                     REPORTER_ASSERT(reporter, newImage != nullptr);
                     if (newImage) {
-                        check_images_same(reporter, image.get(), newImage.get());
+                        // Scale the image in software for comparison.
+                        SkImageInfo scaled_info = SkImageInfo::MakeN32(
+                                image->width() / testCase.fExpectedScaleFactor,
+                                image->height() / testCase.fExpectedScaleFactor,
+                                image->isOpaque() ? kOpaque_SkAlphaType : kPremul_SkAlphaType);
+                        SkAutoPixmapStorage scaled;
+                        scaled.alloc(scaled_info);
+                        image->scalePixels(scaled, testCase.fExpectedQuality);
+                        sk_sp<SkImage> scaledImage = SkImage::MakeRasterCopy(scaled);
+                        check_images_same(reporter, scaledImage.get(), newImage.get());
                     }
                     // The other context should not be able to create images from texture data
                     // created by the original context.