diff --git a/gm/deferredtextureimage.cpp b/gm/deferredtextureimage.cpp new file mode 100644 index 0000000000..6c7dd447cc --- /dev/null +++ b/gm/deferredtextureimage.cpp @@ -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 + +#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 proxy(context->threadSafeProxy()); + + + + sk_sp 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 memory; + memory.resize(requiredMemoryInBytes); + encodedImage->getDeferredTextureImageData( + *proxy, params, 1, memory.data(), SkSourceGammaTreatment::kRespect); + sk_sp 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 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 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 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 memory; + memory.resize(requiredMemoryInBytes); + image->getDeferredTextureImageData( + *proxy, params, 1, memory.data(), SkSourceGammaTreatment::kRespect); + 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 += 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, ¶ms); +} + +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, ¶ms); +} + +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, ¶ms); +} + +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; + } + + SkImage::DeferredTextureImageUsageParams params; + params.fMatrix = SkMatrix::MakeScale(0.25f, 0.25f); + params.fQuality = kMedium_SkFilterQuality; + DrawDeferredTextureImageMipMapTree(canvas, encodedImage.get(), ¶ms); +} + +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); + + SkImage::DeferredTextureImageUsageParams params; + params.fMatrix = SkMatrix::MakeScale(0.25f, 0.25f); + params.fQuality = kMedium_SkFilterQuality; + DrawDeferredTextureImageMipMapTree(canvas, decodedImage.get(), ¶ms); +} + +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, ¶ms); +} + +#endif diff --git a/gm/deferredtextureimagedata.cpp b/gm/deferredtextureimagedata.cpp deleted file mode 100644 index 0a3b40213a..0000000000 --- a/gm/deferredtextureimagedata.cpp +++ /dev/null @@ -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 - -#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 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 memory; - memory.resize(deferredSize); - image->getDeferredTextureImageData(*proxy, params, 1, memory.data()); - sk_sp 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 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 decodedImage = SkImage::MakeFromBitmap(bitmap); - - // Draw both encoded and decoded image via deferredTextureImageData. - SkImage::DeferredTextureImageUsageParams params; - DrawDeferredTextureImageData(context, canvas, encodedImage.get(), ¶ms, 0, 0); - DrawDeferredTextureImageData(context, canvas, decodedImage.get(), ¶ms, 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 diff --git a/include/core/SkImage.h b/include/core/SkImage.h index fd20e5e0e5..cc0f1f1655 100644 --- a/include/core/SkImage.h +++ b/include/core/SkImage.h @@ -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 MakeTextureFromMipMap(GrContext*, const SkImageInfo&, const GrMipLevel* texels, int mipLevelCount, - SkBudgeted); + SkBudgeted, SkSourceGammaTreatment); const int fWidth; const int fHeight; diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp index 2d434aee19..a6011875e8 100644 --- a/src/image/SkImage.cpp +++ b/src/image/SkImage.cpp @@ -395,6 +395,11 @@ sk_sp SkImage::MakeTextureFromPixmap(GrContext*, const SkPixmap&, SkBud return nullptr; } +sk_sp MakeTextureFromMipMap(GrContext*, const SkImageInfo&, const GrMipLevel* texels, + int mipLevelCount, SkBudgeted, SkSourceGammaTreatment) { + return nullptr; +} + sk_sp SkImage::MakeFromTexture(GrContext*, const GrBackendTextureDesc&, SkAlphaType, sk_sp, TextureReleaseProc, ReleaseContext) { return nullptr; @@ -402,7 +407,8 @@ sk_sp 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; } diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp index 44bb71c088..94fd8a3f3f 100644 --- a/src/image/SkImage_Gpu.cpp +++ b/src/image/SkImage_Gpu.cpp @@ -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 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(mipLevelPtr), mipLevel.fPixmap.addr(), + mipLevel.fPixmap.getSafeSize()); + dti->fMipMapLevelData[generatedMipLevelIndex + 1].fPixelData = + reinterpret_cast(mipLevelPtr); + dti->fMipMapLevelData[generatedMipLevelIndex + 1].fRowBytes = + mipLevel.fPixmap.rowBytes(); + mipLevelPtr += SkAlign8(mipLevel.fPixmap.getSafeSize()); + } + } return size; } @@ -532,17 +607,30 @@ sk_sp 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 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 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::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::MakeTextureFromMipMap(GrContext* ctx, const SkImageInfo& if (!texture) { return nullptr; } + texture->texturePriv().setGammaTreatment(gammaTreatment); return sk_make_sp(texture->width(), texture->height(), kNeedNewImageUniqueID, info.alphaType(), texture, sk_ref_sp(info.colorSpace()), budgeted); diff --git a/tests/ImageTest.cpp b/tests/ImageTest.cpp index ca4788c449..b24ded0fb3 100644 --- a/tests/ImageTest.cpp +++ b/tests/ImageTest.cpp @@ -921,8 +921,7 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DeferredTextureImage, reporter, ctxInfo) { sk_sp image(testCase.fImageFactory()); size_t size = image->getDeferredTextureImageData(*proxy, testCase.fParams.data(), static_cast(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(reinterpret_cast(buffer) + 3); if (image->getDeferredTextureImageData(*proxy, testCase.fParams.data(), static_cast(testCase.fParams.size()), - misaligned)) { + misaligned, SkSourceGammaTreatment::kIgnore)) { ERRORF(reporter, "Should fail when buffer is misaligned."); } if (!image->getDeferredTextureImageData(*proxy, testCase.fParams.data(), static_cast(testCase.fParams.size()), - buffer)) { + buffer, SkSourceGammaTreatment::kIgnore)) { ERRORF(reporter, "deferred image size succeeded but creation failed."); } else { for (auto budgeted : { SkBudgeted::kNo, SkBudgeted::kYes }) {