aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Eric Karl <ericrk@chromium.org>2017-12-15 23:37:45 +0000
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-12-15 23:57:41 +0000
commitaae533e418f37d788cdb1a1177e882d0f8868b62 (patch)
tree6e2b29d025b4058f86fb834c83b037efec0d5232
parentb2e3a3ada25301a2141b800c9b9278d6d07975f7 (diff)
Revert "Remove SkImage deferred texture image data APIs."
This reverts commit 4f5e1d4ff3fa9f240398c9a08be94beb1c16dad0. Reason for revert: Unfortunately, we need this in Chrome for a bit longer. Working on understanding why the new path led to regressions. Will re-land this once the new path sticks. Original change's description: > Remove SkImage deferred texture image data APIs. > > These APIs existed for Chrome. Chrome is no longer using them. > > Change-Id: I15a5e2f88c7e8d1356188748fc68d4658f6f1849 > Reviewed-on: https://skia-review.googlesource.com/81021 > Reviewed-by: Brian Osman <brianosman@google.com> > Reviewed-by: Cary Clark <caryclark@google.com> > Commit-Queue: Brian Salomon <bsalomon@google.com> TBR=bsalomon@google.com,brianosman@google.com,caryclark@google.com,caryclark@skia.org # Not skipping CQ checks because original CL landed > 1 day ago. Change-Id: Ic9f683f262f2e1d0469156360f5ffaee977ca44a Reviewed-on: https://skia-review.googlesource.com/86280 Reviewed-by: Eric Karl <ericrk@chromium.org> Commit-Queue: Eric Karl <ericrk@chromium.org>
-rw-r--r--gm/deferredtextureimage.cpp222
-rw-r--r--gn/gm.gni1
-rw-r--r--include/core/SkImage.h54
-rw-r--r--src/image/SkImage.cpp13
-rw-r--r--src/image/SkImage_Gpu.cpp379
-rw-r--r--tests/ImageTest.cpp173
6 files changed, 842 insertions, 0 deletions
diff --git a/gm/deferredtextureimage.cpp b/gm/deferredtextureimage.cpp
new file mode 100644
index 0000000000..b034a4ef15
--- /dev/null
+++ b/gm/deferredtextureimage.cpp
@@ -0,0 +1,222 @@
+/*
+ * 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 "SkImage.h"
+#include "SkMipMap.h"
+#include "Resources.h"
+
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+
+// Helper function that uploads the given SkImage using MakeFromDeferredTextureImageData and then
+// draws the uploaded version at the specified coordinates.
+static void DrawDeferredTextureImageData(SkCanvas* canvas,
+ const char* resourceName,
+ SkImage::DeferredTextureImageUsageParams* params,
+ SkColorType dstColorType) {
+ GrContext* context = canvas->getGrContext();
+ if (!context) {
+ skiagm::GM::DrawGpuOnlyMessage(canvas);
+ return;
+ }
+ sk_sp<GrContextThreadSafeProxy> proxy(context->threadSafeProxy());
+
+
+
+ sk_sp<SkImage> encodedImage = GetResourceAsImage(resourceName);
+ if (!encodedImage) {
+ SkDebugf("\nCould not load resource.\n");
+ return;
+ }
+
+ size_t requiredMemoryInBytes = encodedImage->getDeferredTextureImageData(
+ *proxy, params, 1, nullptr, canvas->imageInfo().colorSpace(), dstColorType);
+ if (requiredMemoryInBytes == 0) {
+ SkDebugf("\nCould not create DeferredTextureImageData.\n");
+ return;
+ }
+
+ std::vector<uint8_t> memory;
+ memory.resize(requiredMemoryInBytes);
+ encodedImage->getDeferredTextureImageData(
+ *proxy, params, 1, memory.data(), canvas->imageInfo().colorSpace(), dstColorType);
+ sk_sp<SkImage> uploadedEncodedImage = SkImage::MakeFromDeferredTextureImageData(
+ context, memory.data(), SkBudgeted::kNo);
+
+ canvas->drawImage(uploadedEncodedImage, 10, 10);
+
+
+
+ SkBitmap bitmap;
+ if (!GetResourceAsBitmap(resourceName, &bitmap)) {
+ SkDebugf("\nCould not decode resource.\n");
+ return;
+ }
+ sk_sp<SkImage> decodedImage = SkImage::MakeFromBitmap(bitmap);
+
+ requiredMemoryInBytes = decodedImage->getDeferredTextureImageData(
+ *proxy, params, 1, nullptr, canvas->imageInfo().colorSpace(), dstColorType);
+ if (requiredMemoryInBytes == 0) {
+ SkDebugf("\nCould not create DeferredTextureImageData.\n");
+ return;
+ }
+
+ memory.resize(requiredMemoryInBytes);
+ decodedImage->getDeferredTextureImageData(
+ *proxy, params, 1, memory.data(), canvas->imageInfo().colorSpace(), dstColorType);
+ sk_sp<SkImage> uploadedDecodedImage = SkImage::MakeFromDeferredTextureImageData(
+ context, memory.data(), SkBudgeted::kNo);
+
+ canvas->drawImage(uploadedDecodedImage, encodedImage->width() + 20, 10);
+}
+
+static void DrawDeferredTextureImageMipMapTree(SkCanvas* canvas, SkImage* image,
+ SkImage::DeferredTextureImageUsageParams* params,
+ SkColorType dstColorType) {
+ GrContext* context = canvas->getGrContext();
+ if (!context) {
+ skiagm::GM::DrawGpuOnlyMessage(canvas);
+ return;
+ }
+ sk_sp<GrContextThreadSafeProxy> proxy(context->threadSafeProxy());
+
+ SkPaint paint;
+ paint.setFilterQuality(params->fQuality);
+
+ int mipLevelCount = SkMipMap::ComputeLevelCount(image->width(), image->height());
+ size_t requiredMemoryInBytes = image->getDeferredTextureImageData(
+ *proxy, params, 1, nullptr, canvas->imageInfo().colorSpace(), dstColorType);
+ if (requiredMemoryInBytes == 0) {
+ SkDebugf("\nCould not create DeferredTextureImageData.\n");
+ return;
+ }
+
+ std::vector<uint8_t> memory;
+ memory.resize(requiredMemoryInBytes);
+ image->getDeferredTextureImageData(
+ *proxy, params, 1, memory.data(), canvas->imageInfo().colorSpace(), dstColorType);
+ sk_sp<SkImage> uploadedImage = SkImage::MakeFromDeferredTextureImageData(
+ context, memory.data(), SkBudgeted::kNo);
+
+ // draw a column using deferred texture images
+ SkScalar offsetHeight = 10.f;
+ // handle base mipmap level
+ canvas->save();
+ canvas->translate(10.f, offsetHeight);
+ canvas->drawImage(uploadedImage, 0, 0, &paint);
+ canvas->restore();
+ offsetHeight += image->height() + 10;
+ // handle generated mipmap levels
+ for (int i = 0; i < mipLevelCount; i++) {
+ SkISize mipSize = SkMipMap::ComputeLevelSize(image->width(), image->height(), i);
+ canvas->save();
+ canvas->translate(10.f, offsetHeight);
+ canvas->scale(mipSize.width() / static_cast<float>(image->width()),
+ mipSize.height() / static_cast<float>(image->height()));
+ canvas->drawImage(uploadedImage, 0, 0, &paint);
+ canvas->restore();
+ offsetHeight += mipSize.height() + 10;
+ }
+
+ // draw a column using SkImage
+ offsetHeight = 10;
+ // handle base mipmap level
+ canvas->save();
+ canvas->translate(image->width() + 20.f, offsetHeight);
+ canvas->drawImage(image, 0, 0, &paint);
+ canvas->restore();
+ offsetHeight += image->height() + 10;
+ // handle generated mipmap levels
+ for (int i = 0; i < mipLevelCount; i++) {
+ SkISize mipSize = SkMipMap::ComputeLevelSize(image->width(), image->height(), i);
+ canvas->save();
+ canvas->translate(image->width() + 20.f, offsetHeight);
+ canvas->scale(mipSize.width() / static_cast<float>(image->width()),
+ mipSize.height() / static_cast<float>(image->height()));
+ canvas->drawImage(image, 0, 0, &paint);
+ canvas->restore();
+ offsetHeight += mipSize.height() + 10;
+ }
+}
+
+DEF_SIMPLE_GM(deferred_texture_image_none, canvas, 512 + 512 + 30, 512 + 20) {
+ auto params = SkImage::DeferredTextureImageUsageParams(SkMatrix::MakeScale(1.f, 1.f),
+ kNone_SkFilterQuality, 0);
+ DrawDeferredTextureImageData(canvas, "mandrill_512.png", &params, kN32_SkColorType);
+}
+
+DEF_SIMPLE_GM(deferred_texture_image_low, canvas, 512 + 512 + 30, 512 + 20) {
+ auto params = SkImage::DeferredTextureImageUsageParams(SkMatrix::MakeScale(1.f, 1.f),
+ kLow_SkFilterQuality, 0);
+ DrawDeferredTextureImageData(canvas, "mandrill_512.png", &params, kN32_SkColorType);
+}
+
+DEF_SIMPLE_GM(deferred_texture_image_low_dithered, canvas, 180 + 180 + 30, 180 + 20) {
+ auto params = SkImage::DeferredTextureImageUsageParams(SkMatrix::MakeScale(0.25f, 0.25f),
+ kLow_SkFilterQuality, 0);
+ DrawDeferredTextureImageData(canvas, "dog.jpg", &params, kARGB_4444_SkColorType);
+}
+
+DEF_SIMPLE_GM(deferred_texture_image_medium_encoded, canvas, 512 + 512 + 30, 1110) {
+ sk_sp<SkImage> encodedImage = GetResourceAsImage("mandrill_512.png");
+ if (!encodedImage) {
+ SkDebugf("\nCould not load resource.\n");
+ return;
+ }
+
+ auto params = SkImage::DeferredTextureImageUsageParams(SkMatrix::MakeScale(0.25f, 0.25f),
+ kMedium_SkFilterQuality, 0);
+ DrawDeferredTextureImageMipMapTree(canvas, encodedImage.get(), &params, kN32_SkColorType);
+}
+
+DEF_SIMPLE_GM(deferred_texture_image_medium_decoded, canvas, 512 + 512 + 30, 1110) {
+ SkBitmap bitmap;
+ if (!GetResourceAsBitmap("mandrill_512.png", &bitmap)) {
+ SkDebugf("\nCould not decode resource.\n");
+ return;
+ }
+ sk_sp<SkImage> decodedImage = SkImage::MakeFromBitmap(bitmap);
+
+ auto params = SkImage::DeferredTextureImageUsageParams(SkMatrix::MakeScale(0.25f, 0.25f),
+ kMedium_SkFilterQuality, 0);
+ DrawDeferredTextureImageMipMapTree(canvas, decodedImage.get(), &params, kN32_SkColorType);
+}
+
+DEF_SIMPLE_GM(deferred_texture_image_high, canvas, 512 + 512 + 30, 512 + 20) {
+ auto params = SkImage::DeferredTextureImageUsageParams(SkMatrix::MakeScale(1.f, 1.f),
+ kHigh_SkFilterQuality, 0);
+ DrawDeferredTextureImageData(canvas, "mandrill_512.png", &params, kN32_SkColorType);
+}
+
+DEF_SIMPLE_GM(deferred_texture_image_medium_encoded_indexed, canvas, 128 + 128 + 30, 340) {
+ sk_sp<SkImage> encodedImage = GetResourceAsImage("color_wheel.gif");
+ if (!encodedImage) {
+ SkDebugf("\nCould not load resource.\n");
+ return;
+ }
+
+ auto params = SkImage::DeferredTextureImageUsageParams(SkMatrix::MakeScale(0.25f, 0.25f),
+ kMedium_SkFilterQuality, 0);
+ DrawDeferredTextureImageMipMapTree(canvas, encodedImage.get(), &params, kN32_SkColorType);
+}
+
+DEF_SIMPLE_GM(deferred_texture_image_medium_decoded_indexed, canvas, 128 + 128 + 30, 340) {
+ SkBitmap bitmap;
+ if (!GetResourceAsBitmap("color_wheel.gif", &bitmap)) {
+ SkDebugf("\nCould not decode resource.\n");
+ return;
+ }
+ sk_sp<SkImage> decodedImage = SkImage::MakeFromBitmap(bitmap);
+
+ auto params = SkImage::DeferredTextureImageUsageParams(SkMatrix::MakeScale(0.25f, 0.25f),
+ kMedium_SkFilterQuality, 0);
+ DrawDeferredTextureImageMipMapTree(canvas, decodedImage.get(), &params, kN32_SkColorType);
+}
+
+#endif
diff --git a/gn/gm.gni b/gn/gm.gni
index 62be999944..c8297cfd24 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -99,6 +99,7 @@ gm_sources = [
"$_gm/dashcircle.cpp",
"$_gm/dashcubics.cpp",
"$_gm/dashing.cpp",
+ "$_gm/deferredtextureimage.cpp",
"$_gm/degeneratesegments.cpp",
"$_gm/dftext.cpp",
"$_gm/dftext_blob_persp.cpp",
diff --git a/include/core/SkImage.h b/include/core/SkImage.h
index 454c5e4b7b..c0f8272c48 100644
--- a/include/core/SkImage.h
+++ b/include/core/SkImage.h
@@ -436,6 +436,60 @@ public:
const SkIRect& clipBounds, SkIRect* outSubset,
SkIPoint* offset) const;
+ /** Drawing params for which a deferred texture image data should be optimized. */
+ struct DeferredTextureImageUsageParams {
+ DeferredTextureImageUsageParams(const SkMatrix matrix, const SkFilterQuality quality,
+ int preScaleMipLevel)
+ : fMatrix(matrix), fQuality(quality), fPreScaleMipLevel(preScaleMipLevel) {}
+ SkMatrix fMatrix;
+ SkFilterQuality fQuality;
+ int fPreScaleMipLevel;
+ };
+
+ /**
+ * This method allows clients to capture the data necessary to turn a SkImage into a texture-
+ * backed image. If the original image is codec-backed this will decode into a format optimized
+ * for the context represented by the proxy. This method is thread safe with respect to the
+ * GrContext whence the proxy came. Clients allocate and manage the storage of the deferred
+ * texture data and control its lifetime. No cleanup is required, thus it is safe to simply free
+ * the memory out from under the data.
+ *
+ * The same method is used both for getting the size necessary for pre-uploaded texture data
+ * and for retrieving the data. The params array represents the set of draws over which to
+ * optimize the pre-upload data.
+ *
+ * When called with a null buffer this returns the size that the client must allocate in order
+ * to create deferred texture data for this image (or zero if this is an inappropriate
+ * candidate). The buffer allocated by the client should be 8 byte aligned.
+ *
+ * When buffer is not null this fills in the deferred texture data for this image in the
+ * provided buffer (assuming this is an appropriate candidate image and the buffer is
+ * appropriately aligned). Upon success the size written is returned, otherwise 0.
+ *
+ * dstColorSpace is the color space of the surface where this texture will ultimately be used.
+ * If the method determines that mip-maps are needed, this helps determine the correct strategy
+ * for building them (gamma-correct or not).
+ *
+ * dstColorType is the color type of the surface where this texture will ultimately be used.
+ * This determines the format with which the image will be uploaded to the GPU. If dstColorType
+ * does not support color spaces (low bit depth types such as ARGB_4444), then dstColorSpace
+ * must be null.
+ */
+ size_t getDeferredTextureImageData(const GrContextThreadSafeProxy& contextThreadSafeProxy,
+ const DeferredTextureImageUsageParams deferredTextureImageUsageParams[],
+ int paramCnt,
+ void* buffer,
+ SkColorSpace* dstColorSpace = nullptr,
+ SkColorType dstColorType = kN32_SkColorType) const;
+
+ /**
+ * Returns a texture-backed image from data produced in SkImage::getDeferredTextureImageData.
+ * The context must be the context that provided the proxy passed to
+ * getDeferredTextureImageData.
+ */
+ static sk_sp<SkImage> MakeFromDeferredTextureImageData(GrContext* context, const void* data,
+ SkBudgeted budgeted);
+
typedef std::function<void(GrBackendTexture)> BackendTextureReleaseProc;
/**
diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp
index 31104d2548..af791b4380 100644
--- a/src/image/SkImage.cpp
+++ b/src/image/SkImage.cpp
@@ -358,6 +358,19 @@ sk_sp<SkImage> SkImage::MakeFromTexture(GrContext* ctx,
return nullptr;
}
+size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy&,
+ const DeferredTextureImageUsageParams[],
+ int paramCnt, void* buffer,
+ SkColorSpace* dstColorSpace,
+ SkColorType dstColorType) const {
+ return 0;
+}
+
+sk_sp<SkImage> SkImage::MakeFromDeferredTextureImageData(GrContext* context, const void*,
+ SkBudgeted) {
+ return nullptr;
+}
+
bool SkImage::MakeBackendTextureFromSkImage(GrContext*,
sk_sp<SkImage>,
GrBackendTexture*,
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index 05c2670ca4..8766dadeab 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -617,6 +617,385 @@ sk_sp<SkImage> SkImage::makeNonTextureImage() const {
///////////////////////////////////////////////////////////////////////////////////////////////////
+namespace {
+struct MipMapLevelData {
+ void* fPixelData;
+ size_t fRowBytes;
+};
+
+struct DeferredTextureImage {
+ uint32_t fContextUniqueID;
+ // Right now, the destination color mode is only considered when generating mipmaps
+ SkDestinationSurfaceColorMode fColorMode;
+ // 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 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];
+};
+} // anonymous namespace
+
+static bool should_use_mip_maps(const SkImage::DeferredTextureImageUsageParams & param) {
+ // There is a bug in the mipmap pre-generation logic in use in getDeferredTextureImageData.
+ // This can cause runaway memory leaks, so we are disabling this path until we can
+ // investigate further. crbug.com/669775
+ return false;
+}
+
+namespace {
+
+class DTIBufferFiller
+{
+public:
+ explicit DTIBufferFiller(char* bufferAsCharPtr)
+ : bufferAsCharPtr_(bufferAsCharPtr) {}
+
+ void fillMember(const void* source, size_t memberOffset, size_t size) {
+ memcpy(bufferAsCharPtr_ + memberOffset, source, size);
+ }
+
+private:
+
+ char* bufferAsCharPtr_;
+};
+}
+
+#define FILL_MEMBER(bufferFiller, member, source) \
+ bufferFiller.fillMember(source, \
+ offsetof(DeferredTextureImage, member), \
+ sizeof(DeferredTextureImage::member));
+
+static bool SupportsColorSpace(SkColorType colorType) {
+ switch (colorType) {
+ case kRGBA_8888_SkColorType:
+ case kBGRA_8888_SkColorType:
+ case kRGBA_F16_SkColorType:
+ return true;
+ default:
+ return false;
+ }
+}
+
+size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy& proxy,
+ const DeferredTextureImageUsageParams params[],
+ int paramCnt, void* buffer,
+ SkColorSpace* dstColorSpace,
+ SkColorType dstColorType) const {
+ // Some quick-rejects where is makes no sense to return CPU data
+ // e.g.
+ // - texture backed
+ // - picture backed
+ //
+ if (this->isTextureBacked()) {
+ return 0;
+ }
+ if (as_IB(this)->onCanLazyGenerateOnGPU()) {
+ return 0;
+ }
+
+ bool supportsColorSpace = SupportsColorSpace(dstColorType);
+ // Quick reject if the caller requests a color space with an unsupported color type.
+ if (SkToBool(dstColorSpace) && !supportsColorSpace) {
+ return 0;
+ }
+
+ // 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);
+ 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 (scaledSize.width() > maxTextureSize || scaledSize.height() > maxTextureSize) {
+ return 0;
+ }
+
+ SkAutoPixmapStorage pixmap;
+ SkImageInfo info;
+ size_t pixelSize = 0;
+ if (!isScaled && this->peekPixels(&pixmap) && pixmap.info().colorType() == dstColorType) {
+ info = pixmap.info();
+ pixelSize = SkAlign8(pixmap.computeByteSize());
+ if (!dstColorSpace) {
+ pixmap.setColorSpace(nullptr);
+ info = info.makeColorSpace(nullptr);
+ }
+ } else {
+ if (!this->isLazyGenerated() && !this->peekPixels(nullptr)) {
+ return 0;
+ }
+ if (SkImageCacherator* cacher = as_IB(this)->peekCacherator()) {
+ // Generator backed image. Tweak info to trigger correct kind of decode.
+ SkImageCacherator::CachedFormat cacheFormat = cacher->chooseCacheFormat(
+ dstColorSpace, proxy.fCaps.get());
+ info = cacher->buildCacheInfo(cacheFormat).makeWH(scaledSize.width(),
+ scaledSize.height());
+ } else {
+ info = as_IB(this)->onImageInfo().makeWH(scaledSize.width(), scaledSize.height());
+ if (!dstColorSpace) {
+ info = info.makeColorSpace(nullptr);
+ }
+ }
+ // Force color type to be the requested type.
+ info = info.makeColorType(dstColorType);
+ pixelSize = SkAlign8(SkAutoPixmapStorage::AllocSize(info, nullptr));
+ if (fillMode) {
+ // Always decode to N32 and convert to the requested type if necessary.
+ SkImageInfo decodeInfo = info.makeColorType(kN32_SkColorType);
+ SkAutoPixmapStorage decodePixmap;
+ decodePixmap.alloc(decodeInfo);
+
+ if (isScaled) {
+ if (!this->scalePixels(decodePixmap, scaleFilterQuality,
+ SkImage::kDisallow_CachingHint)) {
+ return 0;
+ }
+ } else {
+ if (!this->readPixels(decodePixmap, 0, 0, SkImage::kDisallow_CachingHint)) {
+ return 0;
+ }
+ }
+
+ if (decodeInfo.colorType() != info.colorType()) {
+ pixmap.alloc(info);
+ // Convert and copy the decoded pixmap to the target pixmap.
+ decodePixmap.readPixels(pixmap.info(), pixmap.writable_addr(), pixmap.rowBytes(), 0,
+ 0);
+ } else {
+ pixmap = std::move(decodePixmap);
+ }
+ }
+ }
+ 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 = info.makeWH(mipSize.fWidth, mipSize.fHeight);
+ pixelSize += SkAlign8(SkAutoPixmapStorage::AllocSize(mipInfo, nullptr));
+ }
+ }
+ size_t size = 0;
+ size_t dtiSize = SkAlign8(sizeof(DeferredTextureImage));
+ size += dtiSize;
+ 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 colorSpaceOffset = 0;
+ size_t colorSpaceSize = 0;
+ SkColorSpaceTransferFn fn;
+ if (info.colorSpace()) {
+ SkASSERT(dstColorSpace);
+ SkASSERT(supportsColorSpace);
+ colorSpaceOffset = size;
+ colorSpaceSize = info.colorSpace()->writeToMemory(nullptr);
+ size += colorSpaceSize;
+ } else if (supportsColorSpace && this->colorSpace() && this->colorSpace()->isNumericalTransferFn(&fn)) {
+ // In legacy mode, preserve the color space tag on the SkImage. This is only
+ // supported if the color space has a parametric transfer function.
+ SkASSERT(!dstColorSpace);
+ colorSpaceOffset = size;
+ colorSpaceSize = this->colorSpace()->writeToMemory(nullptr);
+ size += colorSpaceSize;
+ }
+ if (!fillMode) {
+ return size;
+ }
+ char* bufferAsCharPtr = reinterpret_cast<char*>(buffer);
+ char* pixelsAsCharPtr = bufferAsCharPtr + pixelOffset;
+ void* pixels = pixelsAsCharPtr;
+
+ memcpy(reinterpret_cast<void*>(SkAlign8(reinterpret_cast<uintptr_t>(pixelsAsCharPtr))),
+ pixmap.addr(), pixmap.computeByteSize());
+
+ // If the context has sRGB support, and we're intending to render to a surface with an attached
+ // color space, and the image has an sRGB-like color space attached, then use our gamma (sRGB)
+ // aware mip-mapping.
+ SkDestinationSurfaceColorMode colorMode = SkDestinationSurfaceColorMode::kLegacy;
+ if (proxy.fCaps->srgbSupport() && SkToBool(dstColorSpace) &&
+ info.colorSpace() && info.colorSpace()->gammaCloseToSRGB()) {
+ SkASSERT(supportsColorSpace);
+ colorMode = SkDestinationSurfaceColorMode::kGammaAndColorSpaceAware;
+ }
+
+ SkASSERT(info == pixmap.info());
+ size_t rowBytes = pixmap.rowBytes();
+ static_assert(std::is_standard_layout<DeferredTextureImage>::value,
+ "offsetof, which we use below, requires the type have standard layout");
+ auto dtiBufferFiller = DTIBufferFiller{bufferAsCharPtr};
+ FILL_MEMBER(dtiBufferFiller, fColorMode, &colorMode);
+ 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, fMipMapLevelCount, &mipMapLevelCount);
+ memcpy(bufferAsCharPtr + offsetof(DeferredTextureImage, fMipMapLevelData[0].fPixelData),
+ &pixels, sizeof(pixels));
+ memcpy(bufferAsCharPtr + offsetof(DeferredTextureImage, fMipMapLevelData[0].fRowBytes),
+ &rowBytes, sizeof(rowBytes));
+ if (colorSpaceSize) {
+ void* colorSpace = bufferAsCharPtr + colorSpaceOffset;
+ FILL_MEMBER(dtiBufferFiller, fColorSpace, &colorSpace);
+ FILL_MEMBER(dtiBufferFiller, fColorSpaceSize, &colorSpaceSize);
+ if (info.colorSpace()) {
+ info.colorSpace()->writeToMemory(bufferAsCharPtr + colorSpaceOffset);
+ } else {
+ SkASSERT(this->colorSpace() && this->colorSpace()->isNumericalTransferFn(&fn));
+ SkASSERT(!dstColorSpace);
+ this->colorSpace()->writeToMemory(bufferAsCharPtr + colorSpaceOffset);
+ }
+ } else {
+ memset(bufferAsCharPtr + offsetof(DeferredTextureImage, fColorSpace),
+ 0, sizeof(DeferredTextureImage::fColorSpace));
+ memset(bufferAsCharPtr + offsetof(DeferredTextureImage, fColorSpaceSize),
+ 0, sizeof(DeferredTextureImage::fColorSpaceSize));
+ }
+
+ // Fill in the mipmap levels if they exist
+ char* mipLevelPtr = pixelsAsCharPtr + SkAlign8(pixmap.computeByteSize());
+
+ if (useMipMaps) {
+ static_assert(std::is_standard_layout<MipMapLevelData>::value,
+ "offsetof, which we use below, requires the type have a standard layout");
+
+ std::unique_ptr<SkMipMap> mipmaps(SkMipMap::Build(pixmap, colorMode, 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++) {
+ SkMipMap::Level mipLevel;
+ mipmaps->getLevel(generatedMipLevelIndex, &mipLevel);
+
+ // Make sure the mipmap data is after the start of the buffer
+ SkASSERT(mipLevelPtr > bufferAsCharPtr);
+ // Make sure the mipmap data starts before the end of the buffer
+ SkASSERT(mipLevelPtr < bufferAsCharPtr + pixelOffset + pixelSize);
+ // Make sure the mipmap data ends before the end of the buffer
+ SkASSERT(mipLevelPtr + mipLevel.fPixmap.computeByteSize() <=
+ bufferAsCharPtr + pixelOffset + pixelSize);
+
+ memcpy(mipLevelPtr, mipLevel.fPixmap.addr(), mipLevel.fPixmap.computeByteSize());
+
+ memcpy(bufferAsCharPtr + offsetof(DeferredTextureImage, fMipMapLevelData) +
+ sizeof(MipMapLevelData) * (generatedMipLevelIndex + 1) +
+ offsetof(MipMapLevelData, fPixelData), &mipLevelPtr, sizeof(void*));
+ size_t rowBytes = mipLevel.fPixmap.rowBytes();
+ memcpy(bufferAsCharPtr + offsetof(DeferredTextureImage, fMipMapLevelData) +
+ sizeof(MipMapLevelData) * (generatedMipLevelIndex + 1) +
+ offsetof(MipMapLevelData, fRowBytes), &rowBytes, sizeof(rowBytes));
+
+ mipLevelPtr += SkAlign8(mipLevel.fPixmap.computeByteSize());
+ }
+ }
+ return size;
+}
+
+sk_sp<SkImage> SkImage::MakeFromDeferredTextureImageData(GrContext* context, const void* data,
+ SkBudgeted budgeted) {
+ if (!data) {
+ return nullptr;
+ }
+ const DeferredTextureImage* dti = reinterpret_cast<const DeferredTextureImage*>(data);
+
+ if (!context || context->uniqueID() != dti->fContextUniqueID || context->abandoned()) {
+ return nullptr;
+ }
+ 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);
+ if (mipLevelCount == 1) {
+ SkPixmap pixmap;
+ pixmap.reset(info, dti->fMipMapLevelData[0].fPixelData, dti->fMipMapLevelData[0].fRowBytes);
+
+ // Pass nullptr for the |dstColorSpace|. This opts in to more lenient color space
+ // verification. This is ok because we've already verified the color space in
+ // getDeferredTextureImageData().
+ sk_sp<GrTextureProxy> proxy(GrUploadPixmapToTextureProxy(
+ context->resourceProvider(), pixmap, budgeted, nullptr));
+ if (!proxy) {
+ return nullptr;
+ }
+ return sk_make_sp<SkImage_Gpu>(context, kNeedNewImageUniqueID, pixmap.alphaType(),
+ std::move(proxy), std::move(colorSpace), budgeted);
+ } else {
+ std::unique_ptr<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->fColorMode);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
bool SkImage::MakeBackendTextureFromSkImage(GrContext* ctx,
sk_sp<SkImage> image,
GrBackendTexture* backendTexture,
diff --git a/tests/ImageTest.cpp b/tests/ImageTest.cpp
index 483fbf7d18..31a5909d66 100644
--- a/tests/ImageTest.cpp
+++ b/tests/ImageTest.cpp
@@ -967,6 +967,179 @@ DEF_GPUTEST(SkImage_MakeCrossContextFromPixmapRelease, reporter, options) {
});
}
+static void check_images_same(skiatest::Reporter* reporter, const SkImage* a, const SkImage* b) {
+ if (a->width() != b->width() || a->height() != b->height()) {
+ ERRORF(reporter, "Images must have the same size");
+ return;
+ }
+ if (a->alphaType() != b->alphaType()) {
+ ERRORF(reporter, "Images must have the same alpha type");
+ return;
+ }
+
+ SkImageInfo info = SkImageInfo::MakeN32Premul(a->width(), a->height());
+ SkAutoPixmapStorage apm;
+ SkAutoPixmapStorage bpm;
+
+ apm.alloc(info);
+ bpm.alloc(info);
+
+ if (!a->readPixels(apm, 0, 0)) {
+ ERRORF(reporter, "Could not read image a's pixels");
+ return;
+ }
+ if (!b->readPixels(bpm, 0, 0)) {
+ ERRORF(reporter, "Could not read image b's pixels");
+ return;
+ }
+
+ for (auto y = 0; y < info.height(); ++y) {
+ for (auto x = 0; x < info.width(); ++x) {
+ uint32_t pixelA = *apm.addr32(x, y);
+ uint32_t pixelB = *bpm.addr32(x, y);
+ if (pixelA != pixelB) {
+ ERRORF(reporter, "Expected image pixels to be the same. At %d,%d 0x%08x != 0x%08x",
+ x, y, pixelA, pixelB);
+ return;
+ }
+ }
+ }
+}
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DeferredTextureImage, reporter, ctxInfo) {
+ GrContext* context = ctxInfo.grContext();
+ sk_gpu_test::TestContext* testContext = ctxInfo.testContext();
+ sk_sp<GrContextThreadSafeProxy> proxy = context->threadSafeProxy();
+
+ GrContextFactory otherFactory;
+ ContextInfo otherContextInfo = otherFactory.getContextInfo(ctxInfo.type());
+
+ testContext->makeCurrent();
+ REPORTER_ASSERT(reporter, proxy);
+ auto createLarge = [context] {
+ return create_image_large(context->caps()->maxTextureSize());
+ };
+ struct {
+ std::function<sk_sp<SkImage> ()> fImageFactory;
+ std::vector<SkImage::DeferredTextureImageUsageParams> fParams;
+ sk_sp<SkColorSpace> fColorSpace;
+ SkColorType fColorType;
+ SkFilterQuality fExpectedQuality;
+ int fExpectedScaleFactor;
+ bool fExpectation;
+ } testCases[] = {
+ { create_image, {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
+ nullptr, kN32_SkColorType, kNone_SkFilterQuality, 1, true },
+ { create_codec_image, {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
+ nullptr, kN32_SkColorType, kNone_SkFilterQuality, 1, true },
+ { create_data_image, {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
+ nullptr, kN32_SkColorType, kNone_SkFilterQuality, 1, true },
+ { create_picture_image, {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
+ nullptr, kN32_SkColorType, kNone_SkFilterQuality, 1, false },
+ { [context] { return create_gpu_image(context); },
+ {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
+ nullptr, kN32_SkColorType, 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;
+ }, {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
+ nullptr, kN32_SkColorType, kNone_SkFilterQuality, 1, false },
+ // Create an image that is too large to upload.
+ { createLarge, {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
+ nullptr, kN32_SkColorType, kNone_SkFilterQuality, 1, false },
+ // Create an image that is too large, but is scaled to an acceptable size.
+ { createLarge, {{SkMatrix::I(), kMedium_SkFilterQuality, 4}},
+ nullptr, kN32_SkColorType, kMedium_SkFilterQuality, 16, true},
+ // Create an image with multiple low filter qualities, make sure we round up.
+ { createLarge, {{SkMatrix::I(), kNone_SkFilterQuality, 4},
+ {SkMatrix::I(), kMedium_SkFilterQuality, 4}},
+ nullptr, kN32_SkColorType, kMedium_SkFilterQuality, 16, true},
+ // Create an image with multiple prescale levels, make sure we chose the minimum scale.
+ { createLarge, {{SkMatrix::I(), kMedium_SkFilterQuality, 5},
+ {SkMatrix::I(), kMedium_SkFilterQuality, 4}},
+ nullptr, kN32_SkColorType, kMedium_SkFilterQuality, 16, true},
+ // Create a images which are decoded to a 4444 backing.
+ { create_image, {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
+ nullptr, kARGB_4444_SkColorType, kNone_SkFilterQuality, 1, true },
+ { create_codec_image, {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
+ nullptr, kARGB_4444_SkColorType, kNone_SkFilterQuality, 1, true },
+ { create_data_image, {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
+ nullptr, kARGB_4444_SkColorType, kNone_SkFilterQuality, 1, true },
+ // Valid SkColorSpace and SkColorType.
+ { create_data_image, {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
+ SkColorSpace::MakeSRGB(), kN32_SkColorType, kNone_SkFilterQuality, 1, true },
+ // Invalid SkColorSpace and SkColorType.
+ { create_data_image, {{SkMatrix::I(), kNone_SkFilterQuality, 0}},
+ SkColorSpace::MakeSRGB(), kARGB_4444_SkColorType, kNone_SkFilterQuality, 1, false },
+ };
+
+
+ for (auto testCase : testCases) {
+ sk_sp<SkImage> image(testCase.fImageFactory());
+ if (!image) {
+ ERRORF(reporter, "Failed to create image!");
+ continue;
+ }
+
+ size_t size = image->getDeferredTextureImageData(*proxy, testCase.fParams.data(),
+ static_cast<int>(testCase.fParams.size()),
+ nullptr, testCase.fColorSpace.get(),
+ testCase.fColorType);
+ static const char *const kFS[] = { "fail", "succeed" };
+ if (SkToBool(size) != testCase.fExpectation) {
+ ERRORF(reporter, "This image was expected to %s but did not.",
+ kFS[testCase.fExpectation]);
+ }
+ if (size) {
+ void* buffer = sk_malloc_throw(size);
+ void* misaligned = reinterpret_cast<void*>(reinterpret_cast<intptr_t>(buffer) + 3);
+ if (image->getDeferredTextureImageData(*proxy, testCase.fParams.data(),
+ static_cast<int>(testCase.fParams.size()),
+ misaligned, testCase.fColorSpace.get(),
+ testCase.fColorType)) {
+ ERRORF(reporter, "Should fail when buffer is misaligned.");
+ }
+ if (!image->getDeferredTextureImageData(*proxy, testCase.fParams.data(),
+ static_cast<int>(testCase.fParams.size()),
+ buffer, testCase.fColorSpace.get(),
+ testCase.fColorType)) {
+ ERRORF(reporter, "deferred image size succeeded but creation failed.");
+ } else {
+ for (auto budgeted : { SkBudgeted::kNo, SkBudgeted::kYes }) {
+ sk_sp<SkImage> newImage(
+ SkImage::MakeFromDeferredTextureImageData(context, buffer, budgeted));
+ REPORTER_ASSERT(reporter, newImage != nullptr);
+ if (newImage) {
+ // Scale the image in software for comparison.
+ SkImageInfo scaled_info = SkImageInfo::MakeN32(
+ image->width() / testCase.fExpectedScaleFactor,
+ image->height() / testCase.fExpectedScaleFactor,
+ image->alphaType());
+ 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.
+ sk_sp<SkImage> newImage2(SkImage::MakeFromDeferredTextureImageData(
+ otherContextInfo.grContext(), buffer, budgeted));
+ REPORTER_ASSERT(reporter, !newImage2);
+ testContext->makeCurrent();
+ }
+ }
+ sk_free(buffer);
+ }
+
+ testContext->makeCurrent();
+ context->flush();
+ }
+}
+
static uint32_t GetIdForBackendObject(GrContext* ctx, GrBackendObject object) {
if (!object) {
return 0;