Store mipmap levels in deferred texture image

This is a follow-up to https://codereview.chromium.org/2034933003/ which
was reverted due to a memory leak.

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

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

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

Review-Url: https://codereview.chromium.org/2115023002
This commit is contained in:
cblume 2016-08-09 13:45:56 -07:00 committed by Commit bot
parent 8eccc308c8
commit d6113140f7
6 changed files with 317 additions and 106 deletions

195
gm/deferredtextureimage.cpp Normal file
View File

@ -0,0 +1,195 @@
/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <vector>
#include "gm.h"
#include "GrContext.h"
#include "SkMipMap.h"
#include "Resources.h"
#if SK_SUPPORT_GPU
// Helper function that uploads the given SkImage using MakdeFromDeferredTextureImageData and then
// draws the uploaded version at the specified coordinates.
static void DrawDeferredTextureImageData(SkCanvas* canvas,
SkImage::DeferredTextureImageUsageParams* params) {
GrContext* context = canvas->getGrContext();
if (!context) {
skiagm::GM::DrawGpuOnlyMessage(canvas);
return;
}
SkAutoTUnref<GrContextThreadSafeProxy> proxy(context->threadSafeProxy());
sk_sp<SkImage> encodedImage = GetResourceAsImage("mandrill_512.png");
if (!encodedImage) {
SkDebugf("\nCould not load resource.\n");
return;
}
size_t requiredMemoryInBytes = encodedImage->getDeferredTextureImageData(
*proxy, params, 1, nullptr, SkSourceGammaTreatment::kRespect);
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(), SkSourceGammaTreatment::kRespect);
sk_sp<SkImage> uploadedEncodedImage = SkImage::MakeFromDeferredTextureImageData(
context, memory.data(), SkBudgeted::kNo);
canvas->drawImage(uploadedEncodedImage, 10, 10);
SkBitmap bitmap;
if (!GetResourceAsBitmap("mandrill_512.png", &bitmap)) {
SkDebugf("\nCould not decode resource.\n");
return;
}
sk_sp<SkImage> decodedImage = SkImage::MakeFromBitmap(bitmap);
requiredMemoryInBytes = decodedImage->getDeferredTextureImageData(
*proxy, params, 1, nullptr, SkSourceGammaTreatment::kRespect);
if (requiredMemoryInBytes == 0) {
SkDebugf("\nCould not create DeferredTextureImageData.\n");
return;
}
memory.resize(requiredMemoryInBytes);
decodedImage->getDeferredTextureImageData(
*proxy, params, 1, memory.data(), SkSourceGammaTreatment::kRespect);
sk_sp<SkImage> uploadedDecodedImage = SkImage::MakeFromDeferredTextureImageData(
context, memory.data(), SkBudgeted::kNo);
canvas->drawImage(uploadedDecodedImage, 512 + 20, 10);
}
static void DrawDeferredTextureImageMipMapTree(SkCanvas* canvas, SkImage* image,
SkImage::DeferredTextureImageUsageParams* params) {
GrContext* context = canvas->getGrContext();
if (!context) {
skiagm::GM::DrawGpuOnlyMessage(canvas);
return;
}
SkAutoTUnref<GrContextThreadSafeProxy> proxy(context->threadSafeProxy());
SkPaint paint;
paint.setFilterQuality(params->fQuality);
int mipLevelCount = SkMipMap::ComputeLevelCount(512, 512);
size_t requiredMemoryInBytes = image->getDeferredTextureImageData(
*proxy, params, 1, nullptr, SkSourceGammaTreatment::kRespect);
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(), SkSourceGammaTreatment::kRespect);
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 += 512 + 10;
// handle generated mipmap levels
for (int i = 0; i < mipLevelCount; i++) {
SkISize mipSize = SkMipMap::ComputeLevelSize(512, 512, i);
canvas->save();
canvas->translate(10.f, offsetHeight);
canvas->scale(mipSize.width() / 512.f, mipSize.height() / 512.f);
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(512.f + 20.f, offsetHeight);
canvas->drawImage(image, 0, 0, &paint);
canvas->restore();
offsetHeight += 512 + 10;
// handle generated mipmap levels
for (int i = 0; i < mipLevelCount; i++) {
SkISize mipSize = SkMipMap::ComputeLevelSize(512, 512, i);
canvas->save();
canvas->translate(512.f + 20.f, offsetHeight);
canvas->scale(mipSize.width() / 512.f, mipSize.height() / 512.f);
canvas->drawImage(image, 0, 0, &paint);
canvas->restore();
offsetHeight += mipSize.height() + 10;
}
}
DEF_SIMPLE_GM(deferred_texture_image_default, canvas, 512 + 512 + 30, 512 + 20) {
SkImage::DeferredTextureImageUsageParams params;
DrawDeferredTextureImageData(canvas, &params);
}
DEF_SIMPLE_GM(deferred_texture_image_none, canvas, 512 + 512 + 30, 512 + 20) {
SkImage::DeferredTextureImageUsageParams params;
params.fPreScaleMipLevel = 0;
params.fQuality = kNone_SkFilterQuality;
DrawDeferredTextureImageData(canvas, &params);
}
DEF_SIMPLE_GM(deferred_texture_image_low, canvas, 512 + 512 + 30, 512 + 20) {
SkImage::DeferredTextureImageUsageParams params;
params.fPreScaleMipLevel = 0;
params.fQuality = kLow_SkFilterQuality;
DrawDeferredTextureImageData(canvas, &params);
}
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;
}
SkImage::DeferredTextureImageUsageParams params;
params.fMatrix = SkMatrix::MakeScale(0.25f, 0.25f);
params.fQuality = kMedium_SkFilterQuality;
DrawDeferredTextureImageMipMapTree(canvas, encodedImage.get(), &params);
}
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);
SkImage::DeferredTextureImageUsageParams params;
params.fMatrix = SkMatrix::MakeScale(0.25f, 0.25f);
params.fQuality = kMedium_SkFilterQuality;
DrawDeferredTextureImageMipMapTree(canvas, decodedImage.get(), &params);
}
DEF_SIMPLE_GM(deferred_texture_image_high, canvas, 512 + 512 + 30, 512 + 20) {
SkImage::DeferredTextureImageUsageParams params;
params.fPreScaleMipLevel = 0;
params.fQuality = kHigh_SkFilterQuality;
DrawDeferredTextureImageData(canvas, &params);
}
#endif

View File

@ -1,81 +0,0 @@
/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <vector>
#include "gm.h"
#include "GrContext.h"
#include "Resources.h"
#include "SkImage.h"
#if SK_SUPPORT_GPU
// Helper function that uploads the given SkImage using MakdeFromDeferredTextureImageData and then
// draws the uploaded version at the specified coordinates.
static bool DrawDeferredTextureImageData(GrContext* context, SkCanvas* canvas, SkImage* image,
SkImage::DeferredTextureImageUsageParams* params,
SkScalar x, SkScalar y) {
SkAutoTUnref<GrContextThreadSafeProxy> proxy(context->threadSafeProxy());
size_t deferredSize = image->getDeferredTextureImageData(*proxy, params, 1, nullptr);
if (deferredSize == 0) {
SkDebugf("\nCould not create DeferredTextureImageData.\n");
return false;
}
std::vector<uint8_t> memory;
memory.resize(deferredSize);
image->getDeferredTextureImageData(*proxy, params, 1, memory.data());
sk_sp<SkImage> uploadedImage =
SkImage::MakeFromDeferredTextureImageData(context, memory.data(), SkBudgeted::kNo);
canvas->drawImage(uploadedImage, x, y);
return true;
}
DEF_SIMPLE_GM(deferred_texture_image_data, canvas, 60, 10) {
GrContext* context = canvas->getGrContext();
if (!context) {
skiagm::GM::DrawGpuOnlyMessage(canvas);
return;
}
sk_sp<SkImage> encodedImage = GetResourceAsImage("randPixels.png");
if (!encodedImage) {
SkDebugf("\nCould not load resource.\n");
return;
}
SkBitmap bitmap;
if (!GetResourceAsBitmap("randPixels.png", &bitmap)) {
SkDebugf("\nCould not decode resource.\n");
return;
}
sk_sp<SkImage> decodedImage = SkImage::MakeFromBitmap(bitmap);
// Draw both encoded and decoded image via deferredTextureImageData.
SkImage::DeferredTextureImageUsageParams params;
DrawDeferredTextureImageData(context, canvas, encodedImage.get(), &params, 0, 0);
DrawDeferredTextureImageData(context, canvas, decodedImage.get(), &params, 10, 0);
// Draw 50% scaled versions of the encoded and decoded images at medium quality.
SkImage::DeferredTextureImageUsageParams mediumScaledParams;
mediumScaledParams.fPreScaleMipLevel = 1;
mediumScaledParams.fQuality = kMedium_SkFilterQuality;
DrawDeferredTextureImageData(context, canvas, encodedImage.get(), &mediumScaledParams, 20, 0);
DrawDeferredTextureImageData(context, canvas, decodedImage.get(), &mediumScaledParams, 30, 0);
// Draw 50% scaled versions of the encoded and decoded images at none quality.
SkImage::DeferredTextureImageUsageParams noneScaledParams;
noneScaledParams.fPreScaleMipLevel = 1;
noneScaledParams.fQuality = kNone_SkFilterQuality;
DrawDeferredTextureImageData(context, canvas, encodedImage.get(), &noneScaledParams, 40, 0);
DrawDeferredTextureImageData(context, canvas, decodedImage.get(), &noneScaledParams, 50, 0);
}
#endif

View File

@ -396,7 +396,9 @@ public:
size_t getDeferredTextureImageData(const GrContextThreadSafeProxy&,
const DeferredTextureImageUsageParams[],
int paramCnt,
void* buffer) const;
void* buffer,
SkSourceGammaTreatment treatment =
SkSourceGammaTreatment::kIgnore) const;
/**
* Returns a texture-backed image from data produced in SkImage::getDeferredTextureImageData.
@ -469,7 +471,7 @@ protected:
private:
static sk_sp<SkImage> MakeTextureFromMipMap(GrContext*, const SkImageInfo&,
const GrMipLevel* texels, int mipLevelCount,
SkBudgeted);
SkBudgeted, SkSourceGammaTreatment);
const int fWidth;
const int fHeight;

View File

@ -395,6 +395,11 @@ sk_sp<SkImage> SkImage::MakeTextureFromPixmap(GrContext*, const SkPixmap&, SkBud
return nullptr;
}
sk_sp<SkImage> MakeTextureFromMipMap(GrContext*, const SkImageInfo&, const GrMipLevel* texels,
int mipLevelCount, SkBudgeted, SkSourceGammaTreatment) {
return nullptr;
}
sk_sp<SkImage> SkImage::MakeFromTexture(GrContext*, const GrBackendTextureDesc&, SkAlphaType,
sk_sp<SkColorSpace>, TextureReleaseProc, ReleaseContext) {
return nullptr;
@ -402,7 +407,8 @@ sk_sp<SkImage> SkImage::MakeFromTexture(GrContext*, const GrBackendTextureDesc&,
size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy&,
const DeferredTextureImageUsageParams[],
int paramCnt, void* buffer) const {
int paramCnt, void* buffer,
SkSourceGammaTreatment treatment) const {
return 0;
}

View File

@ -10,6 +10,7 @@
#include "GrContext.h"
#include "GrDrawContext.h"
#include "GrImageIDTextureAdjuster.h"
#include "GrTexturePriv.h"
#include "effects/GrYUVEffect.h"
#include "SkCanvas.h"
#include "SkBitmapCache.h"
@ -364,35 +365,60 @@ struct MipMapLevelData {
};
struct DeferredTextureImage {
uint32_t fContextUniqueID;
uint32_t fContextUniqueID;
// Right now, the gamma treatment is only considered when generating mipmaps
SkSourceGammaTreatment fGammaTreatment;
// We don't store a SkImageInfo because it contains a ref-counted SkColorSpace.
int fWidth;
int fHeight;
SkColorType fColorType;
SkAlphaType fAlphaType;
void* fColorSpace;
size_t fColorSpaceSize;
int fColorTableCnt;
uint32_t* fColorTableData;
int fMipMapLevelCount;
int fWidth;
int fHeight;
SkColorType fColorType;
SkAlphaType fAlphaType;
void* fColorSpace;
size_t fColorSpaceSize;
int fColorTableCnt;
uint32_t* fColorTableData;
int fMipMapLevelCount;
// The fMipMapLevelData array may contain more than 1 element.
// It contains fMipMapLevelCount elements.
// That means this struct's size is not known at compile-time.
MipMapLevelData fMipMapLevelData[1];
MipMapLevelData fMipMapLevelData[1];
};
} // anonymous namespace
static bool should_use_mip_maps(const SkImage::DeferredTextureImageUsageParams & param) {
bool shouldUseMipMaps = false;
// Use mipmaps if either
// 1.) it is a perspective matrix, or
// 2.) the quality is med/high and the scale is < 1
if (param.fMatrix.hasPerspective()) {
shouldUseMipMaps = true;
}
if (param.fQuality == kMedium_SkFilterQuality ||
param.fQuality == kHigh_SkFilterQuality) {
SkScalar minAxisScale = param.fMatrix.getMinScale();
if (minAxisScale != -1.f && minAxisScale < 1.f) {
shouldUseMipMaps = true;
}
}
return shouldUseMipMaps;
}
size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy& proxy,
const DeferredTextureImageUsageParams params[],
int paramCnt, void* buffer) const {
int paramCnt, void* buffer,
SkSourceGammaTreatment gammaTreatment) const {
// Extract relevant min/max values from the params array.
int lowestPreScaleMipLevel = params[0].fPreScaleMipLevel;
SkFilterQuality highestFilterQuality = params[0].fQuality;
bool useMipMaps = should_use_mip_maps(params[0]);
for (int i = 1; i < paramCnt; ++i) {
if (lowestPreScaleMipLevel > params[i].fPreScaleMipLevel)
lowestPreScaleMipLevel = params[i].fPreScaleMipLevel;
if (highestFilterQuality < params[i].fQuality)
highestFilterQuality = params[i].fQuality;
useMipMaps |= should_use_mip_maps(params[i]);
}
const bool fillMode = SkToBool(buffer);
@ -462,7 +488,29 @@ size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy& prox
SkASSERT(!pixmap.ctable());
}
}
SkAlphaType at = this->isOpaque() ? kOpaque_SkAlphaType : kPremul_SkAlphaType;
int mipMapLevelCount = 1;
if (useMipMaps) {
// SkMipMap only deals with the mipmap levels it generates, which does
// not include the base level.
// That means it generates and holds levels 1-x instead of 0-x.
// So the total mipmap level count is 1 more than what
// SkMipMap::ComputeLevelCount returns.
mipMapLevelCount = SkMipMap::ComputeLevelCount(scaledSize.width(), scaledSize.height()) + 1;
// We already initialized pixelSize to the size of the base level.
// SkMipMap will generate the extra mipmap levels. Their sizes need to
// be added to the total.
// Index 0 here does not refer to the base mipmap level -- it is
// SkMipMap's first generated mipmap level (level 1).
for (int currentMipMapLevelIndex = mipMapLevelCount - 1; currentMipMapLevelIndex >= 0;
currentMipMapLevelIndex--) {
SkISize mipSize = SkMipMap::ComputeLevelSize(scaledSize.width(), scaledSize.height(),
currentMipMapLevelIndex);
SkImageInfo mipInfo = SkImageInfo::MakeN32(mipSize.fWidth, mipSize.fHeight, at);
pixelSize += SkAlign8(SkAutoPixmapStorage::AllocSize(mipInfo, nullptr));
}
}
size_t size = 0;
size_t dtiSize = SkAlign8(sizeof(DeferredTextureImage));
size += dtiSize;
@ -496,6 +544,7 @@ size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy& prox
SkASSERT(info == pixmap.info());
size_t rowBytes = pixmap.rowBytes();
DeferredTextureImage* dti = new (buffer) DeferredTextureImage();
dti->fGammaTreatment = gammaTreatment;
dti->fContextUniqueID = proxy.fContextUniqueID;
dti->fWidth = info.width();
dti->fHeight = info.height();
@ -514,6 +563,32 @@ size_t SkImage::getDeferredTextureImageData(const GrContextThreadSafeProxy& prox
dti->fColorSpace = nullptr;
dti->fColorSpaceSize = 0;
}
// Fill in the mipmap levels if they exist
intptr_t mipLevelPtr = bufferAsInt + pixelOffset + SkAlign8(SkAutoPixmapStorage::AllocSize(
info, nullptr));
if (useMipMaps) {
SkAutoTDelete<SkMipMap> mipmaps(SkMipMap::Build(pixmap, gammaTreatment, nullptr));
// SkMipMap holds only the mipmap levels it generates.
// A programmer can use the data they provided to SkMipMap::Build as level 0.
// So the SkMipMap provides levels 1-x but it stores them in its own
// range 0-(x-1).
for (int generatedMipLevelIndex = 0; generatedMipLevelIndex < mipMapLevelCount - 1;
generatedMipLevelIndex++) {
SkISize mipSize = SkMipMap::ComputeLevelSize(scaledSize.width(), scaledSize.height(),
generatedMipLevelIndex);
SkImageInfo mipInfo = SkImageInfo::MakeN32(mipSize.fWidth, mipSize.fHeight, at);
SkMipMap::Level mipLevel;
mipmaps->getLevel(generatedMipLevelIndex, &mipLevel);
memcpy(reinterpret_cast<void*>(mipLevelPtr), mipLevel.fPixmap.addr(),
mipLevel.fPixmap.getSafeSize());
dti->fMipMapLevelData[generatedMipLevelIndex + 1].fPixelData =
reinterpret_cast<void*>(mipLevelPtr);
dti->fMipMapLevelData[generatedMipLevelIndex + 1].fRowBytes =
mipLevel.fPixmap.rowBytes();
mipLevelPtr += SkAlign8(mipLevel.fPixmap.getSafeSize());
}
}
return size;
}
@ -532,17 +607,30 @@ sk_sp<SkImage> SkImage::MakeFromDeferredTextureImageData(GrContext* context, con
SkASSERT(dti->fColorTableData);
colorTable.reset(new SkColorTable(dti->fColorTableData, dti->fColorTableCnt));
}
SkASSERT(dti->fMipMapLevelCount == 1);
int mipLevelCount = dti->fMipMapLevelCount;
SkASSERT(mipLevelCount >= 1);
sk_sp<SkColorSpace> colorSpace;
if (dti->fColorSpaceSize) {
colorSpace = SkColorSpace::Deserialize(dti->fColorSpace, dti->fColorSpaceSize);
}
SkImageInfo info = SkImageInfo::Make(dti->fWidth, dti->fHeight,
dti->fColorType, dti->fAlphaType, colorSpace);
SkPixmap pixmap;
pixmap.reset(info, dti->fMipMapLevelData[0].fPixelData,
dti->fMipMapLevelData[0].fRowBytes, colorTable.get());
return SkImage::MakeTextureFromPixmap(context, pixmap, budgeted);
if (mipLevelCount == 1) {
SkPixmap pixmap;
pixmap.reset(info, dti->fMipMapLevelData[0].fPixelData,
dti->fMipMapLevelData[0].fRowBytes, colorTable.get());
return SkImage::MakeTextureFromPixmap(context, pixmap, budgeted);
} else {
SkAutoTDeleteArray<GrMipLevel> texels(new GrMipLevel[mipLevelCount]);
for (int i = 0; i < mipLevelCount; i++) {
texels[i].fPixels = dti->fMipMapLevelData[i].fPixelData;
texels[i].fRowBytes = dti->fMipMapLevelData[i].fRowBytes;
}
return SkImage::MakeTextureFromMipMap(context, info, texels.get(),
mipLevelCount, SkBudgeted::kYes,
dti->fGammaTreatment);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -565,7 +653,8 @@ GrTexture* GrDeepCopyTexture(GrTexture* src, SkBudgeted budgeted) {
sk_sp<SkImage> SkImage::MakeTextureFromMipMap(GrContext* ctx, const SkImageInfo& info,
const GrMipLevel* texels, int mipLevelCount,
SkBudgeted budgeted) {
SkBudgeted budgeted,
SkSourceGammaTreatment gammaTreatment) {
if (!ctx) {
return nullptr;
}
@ -573,6 +662,7 @@ sk_sp<SkImage> SkImage::MakeTextureFromMipMap(GrContext* ctx, const SkImageInfo&
if (!texture) {
return nullptr;
}
texture->texturePriv().setGammaTreatment(gammaTreatment);
return sk_make_sp<SkImage_Gpu>(texture->width(), texture->height(), kNeedNewImageUniqueID,
info.alphaType(), texture, sk_ref_sp(info.colorSpace()),
budgeted);

View File

@ -921,8 +921,7 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DeferredTextureImage, reporter, ctxInfo) {
sk_sp<SkImage> image(testCase.fImageFactory());
size_t size = image->getDeferredTextureImageData(*proxy, testCase.fParams.data(),
static_cast<int>(testCase.fParams.size()),
nullptr);
nullptr, SkSourceGammaTreatment::kIgnore);
static const char *const kFS[] = { "fail", "succeed" };
if (SkToBool(size) != testCase.fExpectation) {
ERRORF(reporter, "This image was expected to %s but did not.",
@ -933,12 +932,12 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DeferredTextureImage, reporter, ctxInfo) {
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)) {
misaligned, SkSourceGammaTreatment::kIgnore)) {
ERRORF(reporter, "Should fail when buffer is misaligned.");
}
if (!image->getDeferredTextureImageData(*proxy, testCase.fParams.data(),
static_cast<int>(testCase.fParams.size()),
buffer)) {
buffer, SkSourceGammaTreatment::kIgnore)) {
ERRORF(reporter, "deferred image size succeeded but creation failed.");
} else {
for (auto budgeted : { SkBudgeted::kNo, SkBudgeted::kYes }) {