diff options
author | Brian Salomon <bsalomon@google.com> | 2018-02-09 08:50:22 -0500 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-02-09 14:19:51 +0000 |
commit | 67f8584b6f899876ca4187dba4f449ce5489f9c8 (patch) | |
tree | ef23d8ccb9430be5ed954ff4cc0eb345baeafc98 | |
parent | c4616804bb407506c6ac1046c7e25e2016911449 (diff) |
Revert "Revert "Remove SkImage deferred texture image data APIs.""
This reverts commit aae533e418f37d788cdb1a1177e882d0f8868b62.
Change-Id: I0434892d8946490a75e17620c49c594eaa18158e
Reviewed-on: https://skia-review.googlesource.com/100603
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
-rw-r--r-- | docs/SkImage_Reference.bmh | 111 | ||||
-rw-r--r-- | gm/deferredtextureimage.cpp | 222 | ||||
-rw-r--r-- | gn/gm.gni | 1 | ||||
-rw-r--r-- | include/core/SkImage.h | 54 | ||||
-rw-r--r-- | infra/bots/recipes/test.expected/Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-Vulkan.json | 1 | ||||
-rw-r--r-- | infra/bots/recipes/test.py | 1 | ||||
-rw-r--r-- | src/image/SkImage.cpp | 20 | ||||
-rw-r--r-- | src/image/SkImage_Gpu.cpp | 379 | ||||
-rw-r--r-- | tests/ImageTest.cpp | 173 |
9 files changed, 42 insertions, 920 deletions
diff --git a/docs/SkImage_Reference.bmh b/docs/SkImage_Reference.bmh index dc4d4efa4d..fa08509d9b 100644 --- a/docs/SkImage_Reference.bmh +++ b/docs/SkImage_Reference.bmh @@ -2113,33 +2113,6 @@ By translating canvas by returned offset, Image appears stationary. # ------------------------------------------------------------------------------ -#Struct DeferredTextureImageUsageParams -#Deprecated soon - -Used only by Chrome. - -## - -#Method size_t getDeferredTextureImageData(const GrContextThreadSafeProxy& contextThreadSafeProxy, - const DeferredTextureImageUsageParams deferredTextureImageUsageParams[], - int paramCnt, - void* buffer, - SkColorSpace* dstColorSpace = nullptr, - SkColorType dstColorType = kN32_SkColorType) const -#Deprecated soon - -Used only by Chrome. -## - -#Method static sk_sp<SkImage> MakeFromDeferredTextureImageData(GrContext* context, const void* data, - SkBudgeted budgeted) -#Deprecated soon - -Used only by Chrome. -## - -# ------------------------------------------------------------------------------ - #Typedef std::function<void(GrBackendTexture)> BackendTextureReleaseProc ## @@ -2179,34 +2152,34 @@ If Image is not texture backed, returns texture with Image contents. #Platform gpu #Height 64 #Function -static sk_sp<SkImage> create_gpu_image(GrContext* grContext) {
- const SkImageInfo info = SkImageInfo::MakeN32(20, 20, kOpaque_SkAlphaType);
- auto surface(SkSurface::MakeRenderTarget(grContext, SkBudgeted::kNo, info));
- SkCanvas* canvas = surface->getCanvas();
- canvas->clear(SK_ColorWHITE);
- SkPaint paint;
- paint.setColor(SK_ColorBLACK);
- canvas->drawRect(SkRect::MakeXYWH(5, 5, 10, 10), paint);
- return surface->makeImageSnapshot();
-}
-##
-
-void draw(SkCanvas* canvas) {
- GrContext* grContext = canvas->getGrContext();
- if (!grContext) {
- return;
- }
- sk_sp<SkImage> backEndImage = create_gpu_image(grContext);
- canvas->drawImage(backEndImage, 0, 0);
- GrBackendTexture texture;
- SkImage::BackendTextureReleaseProc proc;
- if (!SkImage::MakeBackendTextureFromSkImage(grContext, std::move(backEndImage),
- &texture, &proc)) {
- return;
- }
- sk_sp<SkImage> i2 = SkImage::MakeFromTexture(grContext, texture, kTopLeft_GrSurfaceOrigin,
- kN32_SkColorType, kOpaque_SkAlphaType, nullptr);
- canvas->drawImage(i2, 30, 30);
+static sk_sp<SkImage> create_gpu_image(GrContext* grContext) { + const SkImageInfo info = SkImageInfo::MakeN32(20, 20, kOpaque_SkAlphaType); + auto surface(SkSurface::MakeRenderTarget(grContext, SkBudgeted::kNo, info)); + SkCanvas* canvas = surface->getCanvas(); + canvas->clear(SK_ColorWHITE); + SkPaint paint; + paint.setColor(SK_ColorBLACK); + canvas->drawRect(SkRect::MakeXYWH(5, 5, 10, 10), paint); + return surface->makeImageSnapshot(); +} +## + +void draw(SkCanvas* canvas) { + GrContext* grContext = canvas->getGrContext(); + if (!grContext) { + return; + } + sk_sp<SkImage> backEndImage = create_gpu_image(grContext); + canvas->drawImage(backEndImage, 0, 0); + GrBackendTexture texture; + SkImage::BackendTextureReleaseProc proc; + if (!SkImage::MakeBackendTextureFromSkImage(grContext, std::move(backEndImage), + &texture, &proc)) { + return; + } + sk_sp<SkImage> i2 = SkImage::MakeFromTexture(grContext, texture, kTopLeft_GrSurfaceOrigin, + kN32_SkColorType, kOpaque_SkAlphaType, nullptr); + canvas->drawImage(i2, 30, 30); } ## @@ -2248,20 +2221,20 @@ Bitmap write did not succeed. #Example #Image 4 #Platform gpu - SkBitmap bitImage;
- if (image->asLegacyBitmap(&bitImage, SkImage::kRO_LegacyBitmapMode)) {
- canvas->drawBitmap(bitImage, 0, 0);
- }
- GrContext* grContext = canvas->getGrContext();
- if (!grContext) {
- return;
- }
- sk_sp<SkImage> textureImage(SkImage::MakeFromTexture(grContext, backEndTexture,
- kTopLeft_GrSurfaceOrigin, kOpaque_SkAlphaType, nullptr));
- canvas->drawImage(textureImage, 45, 45);
- if (textureImage->asLegacyBitmap(&bitImage, SkImage::kRO_LegacyBitmapMode)) {
- canvas->drawBitmap(bitImage, 90, 90);
- }
+ SkBitmap bitImage; + if (image->asLegacyBitmap(&bitImage, SkImage::kRO_LegacyBitmapMode)) { + canvas->drawBitmap(bitImage, 0, 0); + } + GrContext* grContext = canvas->getGrContext(); + if (!grContext) { + return; + } + sk_sp<SkImage> textureImage(SkImage::MakeFromTexture(grContext, backEndTexture, + kTopLeft_GrSurfaceOrigin, kOpaque_SkAlphaType, nullptr)); + canvas->drawImage(textureImage, 45, 45); + if (textureImage->asLegacyBitmap(&bitImage, SkImage::kRO_LegacyBitmapMode)) { + canvas->drawBitmap(bitImage, 90, 90); + } ## #SeeAlso MakeRasterData makeRasterImage makeNonTextureImage diff --git a/gm/deferredtextureimage.cpp b/gm/deferredtextureimage.cpp deleted file mode 100644 index 00953b1e2f..0000000000 --- a/gm/deferredtextureimage.cpp +++ /dev/null @@ -1,222 +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 "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, "images/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, "images/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, "images/dog.jpg", ¶ms, kARGB_4444_SkColorType); -} - -DEF_SIMPLE_GM(deferred_texture_image_medium_encoded, canvas, 512 + 512 + 30, 1110) { - sk_sp<SkImage> encodedImage = GetResourceAsImage("images/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("images/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(), ¶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, "images/mandrill_512.png", ¶ms, kN32_SkColorType); -} - -DEF_SIMPLE_GM(deferred_texture_image_medium_encoded_indexed, canvas, 128 + 128 + 30, 340) { - sk_sp<SkImage> encodedImage = GetResourceAsImage("images/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("images/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(), ¶ms, kN32_SkColorType); -} - -#endif @@ -99,7 +99,6 @@ 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 5cf6ebe475..041e11ec23 100644 --- a/include/core/SkImage.h +++ b/include/core/SkImage.h @@ -504,60 +504,6 @@ public: const SkIRect& clipBounds, SkIRect* outSubset, SkIPoint* offset) const; - /** Drawing params for which a deferred texture image data should be optimized. */ - struct DeferredTextureImageUsageParams { - DeferredTextureImageUsageParams(const SkMatrix matrix, const SkFilterQuality quality, - int preScaleMipLevel) - : fMatrix(matrix), fQuality(quality), fPreScaleMipLevel(preScaleMipLevel) {} - SkMatrix fMatrix; - SkFilterQuality fQuality; - int fPreScaleMipLevel; - }; - - /** - * This method allows clients to capture the data necessary to turn a SkImage into a texture- - * backed image. If the original image is codec-backed this will decode into a format optimized - * for the context represented by the proxy. This method is thread safe with respect to the - * GrContext whence the proxy came. Clients allocate and manage the storage of the deferred - * texture data and control its lifetime. No cleanup is required, thus it is safe to simply free - * the memory out from under the data. - * - * The same method is used both for getting the size necessary for pre-uploaded texture data - * and for retrieving the data. The params array represents the set of draws over which to - * optimize the pre-upload data. - * - * When called with a null buffer this returns the size that the client must allocate in order - * to create deferred texture data for this image (or zero if this is an inappropriate - * candidate). The buffer allocated by the client should be 8 byte aligned. - * - * When buffer is not null this fills in the deferred texture data for this image in the - * provided buffer (assuming this is an appropriate candidate image and the buffer is - * appropriately aligned). Upon success the size written is returned, otherwise 0. - * - * dstColorSpace is the color space of the surface where this texture will ultimately be used. - * If the method determines that mip-maps are needed, this helps determine the correct strategy - * for building them (gamma-correct or not). - * - * dstColorType is the color type of the surface where this texture will ultimately be used. - * This determines the format with which the image will be uploaded to the GPU. If dstColorType - * does not support color spaces (low bit depth types such as ARGB_4444), then dstColorSpace - * must be null. - */ - size_t getDeferredTextureImageData(const GrContextThreadSafeProxy& contextThreadSafeProxy, - const DeferredTextureImageUsageParams deferredTextureImageUsageParams[], - int paramCnt, - void* buffer, - SkColorSpace* dstColorSpace = nullptr, - SkColorType dstColorType = kN32_SkColorType) const; - - /** - * Returns a texture-backed image from data produced in SkImage::getDeferredTextureImageData. - * The context must be the context that provided the proxy passed to - * getDeferredTextureImageData. - */ - static sk_sp<SkImage> MakeFromDeferredTextureImageData(GrContext* context, const void* data, - SkBudgeted budgeted); - typedef std::function<void(GrBackendTexture)> BackendTextureReleaseProc; /** diff --git a/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-Vulkan.json b/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-Vulkan.json index b353c66f1b..dbc9f3b888 100644 --- a/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-Vulkan.json +++ b/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-Vulkan.json @@ -593,7 +593,6 @@ "--match", "~ApplyGamma", "~ComposedImageFilterBounds_Gpu", - "~DeferredTextureImage", "~GrMeshTest", "~ImageFilterFailAffectsTransparentBlack_Gpu", "~ImageFilterZeroBlurSigma_Gpu", diff --git a/infra/bots/recipes/test.py b/infra/bots/recipes/test.py index c844931a92..63665068f4 100644 --- a/infra/bots/recipes/test.py +++ b/infra/bots/recipes/test.py @@ -659,7 +659,6 @@ def dm_flags(api, bot): blacklist(['vk', 'gm', '_', 'xfermodeimagefilter']) match.append('~ApplyGamma') match.append('~ComposedImageFilterBounds_Gpu') - match.append('~DeferredTextureImage') match.append('~GrMeshTest') match.append('~ImageFilterFailAffectsTransparentBlack_Gpu') match.append('~ImageFilterZeroBlurSigma_Gpu') diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp index 4d54a1c838..ad70af7451 100644 --- a/src/image/SkImage.cpp +++ b/src/image/SkImage.cpp @@ -355,26 +355,6 @@ 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; -} - -sk_sp<SkImage> SkImage::MakeFromTexture(GrContext* ctx, - const GrBackendTexture& tex, GrSurfaceOrigin origin, - SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs, - TextureReleaseProc releaseP, ReleaseContext releaseC) { - return nullptr; -} - bool SkImage::MakeBackendTextureFromSkImage(GrContext*, sk_sp<SkImage>, GrBackendTexture*, diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp index b0311d5b55..d14f001caf 100644 --- a/src/image/SkImage_Gpu.cpp +++ b/src/image/SkImage_Gpu.cpp @@ -669,385 +669,6 @@ sk_sp<SkImage> SkImage::MakeFromAHardwareBuffer(AHardwareBuffer* graphicBuffer, /////////////////////////////////////////////////////////////////////////////////////////////////// -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->contextPriv().proxyProvider(), pixmap, budgeted, nullptr)); - if (!proxy) { - return nullptr; - } - return sk_make_sp<SkImage_Gpu>(context, kNeedNewImageUniqueID, pixmap.alphaType(), - std::move(proxy), std::move(colorSpace), budgeted); - } else { - std::unique_ptr<GrMipLevel[]> texels(new GrMipLevel[mipLevelCount]); - for (int i = 0; i < mipLevelCount; i++) { - texels[i].fPixels = dti->fMipMapLevelData[i].fPixelData; - texels[i].fRowBytes = dti->fMipMapLevelData[i].fRowBytes; - } - - return SkImage::MakeTextureFromMipMap(context, info, texels.get(), - mipLevelCount, SkBudgeted::kYes, - dti->fColorMode); - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - bool SkImage::MakeBackendTextureFromSkImage(GrContext* ctx, sk_sp<SkImage> image, GrBackendTexture* backendTexture, diff --git a/tests/ImageTest.cpp b/tests/ImageTest.cpp index 700584e5af..873f0eea95 100644 --- a/tests/ImageTest.cpp +++ b/tests/ImageTest.cpp @@ -1017,179 +1017,6 @@ 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; |