From aae533e418f37d788cdb1a1177e882d0f8868b62 Mon Sep 17 00:00:00 2001 From: Eric Karl Date: Fri, 15 Dec 2017 23:37:45 +0000 Subject: [PATCH] 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 > Reviewed-by: Cary Clark > Commit-Queue: Brian Salomon 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 Commit-Queue: Eric Karl --- gm/deferredtextureimage.cpp | 222 +++++++++++++++++++++ gn/gm.gni | 1 + include/core/SkImage.h | 54 +++++ src/image/SkImage.cpp | 13 ++ src/image/SkImage_Gpu.cpp | 379 ++++++++++++++++++++++++++++++++++++ tests/ImageTest.cpp | 173 ++++++++++++++++ 6 files changed, 842 insertions(+) create mode 100644 gm/deferredtextureimage.cpp 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 + +#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 proxy(context->threadSafeProxy()); + + + + sk_sp 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 memory; + memory.resize(requiredMemoryInBytes); + encodedImage->getDeferredTextureImageData( + *proxy, params, 1, memory.data(), canvas->imageInfo().colorSpace(), dstColorType); + sk_sp 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 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 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 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 memory; + memory.resize(requiredMemoryInBytes); + image->getDeferredTextureImageData( + *proxy, params, 1, memory.data(), canvas->imageInfo().colorSpace(), dstColorType); + sk_sp 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(image->width()), + mipSize.height() / static_cast(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(image->width()), + mipSize.height() / static_cast(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", ¶ms, 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", ¶ms, 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", ¶ms, kARGB_4444_SkColorType); +} + +DEF_SIMPLE_GM(deferred_texture_image_medium_encoded, canvas, 512 + 512 + 30, 1110) { + sk_sp 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(), ¶ms, 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 decodedImage = SkImage::MakeFromBitmap(bitmap); + + auto params = SkImage::DeferredTextureImageUsageParams(SkMatrix::MakeScale(0.25f, 0.25f), + kMedium_SkFilterQuality, 0); + DrawDeferredTextureImageMipMapTree(canvas, decodedImage.get(), ¶ms, 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", ¶ms, kN32_SkColorType); +} + +DEF_SIMPLE_GM(deferred_texture_image_medium_encoded_indexed, canvas, 128 + 128 + 30, 340) { + sk_sp 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(), ¶ms, 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 decodedImage = SkImage::MakeFromBitmap(bitmap); + + auto params = SkImage::DeferredTextureImageUsageParams(SkMatrix::MakeScale(0.25f, 0.25f), + kMedium_SkFilterQuality, 0); + DrawDeferredTextureImageMipMapTree(canvas, decodedImage.get(), ¶ms, 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 MakeFromDeferredTextureImageData(GrContext* context, const void* data, + SkBudgeted budgeted); + typedef std::function 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::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::MakeFromDeferredTextureImageData(GrContext* context, const void*, + SkBudgeted) { + return nullptr; +} + bool SkImage::MakeBackendTextureFromSkImage(GrContext*, sk_sp, 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::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(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(buffer); + char* pixelsAsCharPtr = bufferAsCharPtr + pixelOffset; + void* pixels = pixelsAsCharPtr; + + memcpy(reinterpret_cast(SkAlign8(reinterpret_cast(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::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::value, + "offsetof, which we use below, requires the type have a standard layout"); + + std::unique_ptr 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::MakeFromDeferredTextureImageData(GrContext* context, const void* data, + SkBudgeted budgeted) { + if (!data) { + return nullptr; + } + const DeferredTextureImage* dti = reinterpret_cast(data); + + if (!context || context->uniqueID() != dti->fContextUniqueID || context->abandoned()) { + return nullptr; + } + int mipLevelCount = dti->fMipMapLevelCount; + SkASSERT(mipLevelCount >= 1); + sk_sp 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 proxy(GrUploadPixmapToTextureProxy( + context->resourceProvider(), pixmap, budgeted, nullptr)); + if (!proxy) { + return nullptr; + } + return sk_make_sp(context, kNeedNewImageUniqueID, pixmap.alphaType(), + std::move(proxy), std::move(colorSpace), budgeted); + } else { + std::unique_ptr 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 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 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 ()> fImageFactory; + std::vector fParams; + sk_sp 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 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 image(testCase.fImageFactory()); + if (!image) { + ERRORF(reporter, "Failed to create image!"); + continue; + } + + size_t size = image->getDeferredTextureImageData(*proxy, testCase.fParams.data(), + static_cast(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(reinterpret_cast(buffer) + 3); + if (image->getDeferredTextureImageData(*proxy, testCase.fParams.data(), + static_cast(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(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 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 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 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;