Revert "Remove SkImage deferred texture image data APIs."

This reverts commit 4f5e1d4ff3.

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>
This commit is contained in:
Eric Karl 2017-12-15 23:37:45 +00:00 committed by Skia Commit-Bot
parent b2e3a3ada2
commit aae533e418
6 changed files with 842 additions and 0 deletions

222
gm/deferredtextureimage.cpp Normal file
View File

@ -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

View File

@ -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",

View File

@ -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;
/**

View File

@ -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*,

View File

@ -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,

View File

@ -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;