| /* | |
| * Copyright 2019 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/core/SkAlphaType.h" | |
| #include "include/core/SkBlendMode.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColor.h" | |
| #include "include/core/SkColorSpace.h" | |
| #include "include/core/SkColorType.h" | |
| #include "include/core/SkImage.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkSamplingOptions.h" | |
| #include "include/core/SkSize.h" | |
| #include "include/core/SkString.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/core/SkTextureCompressionType.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/gpu/GpuTypes.h" | |
| #include "include/gpu/ganesh/GrBackendSurface.h" | |
| #include "include/gpu/ganesh/GrDirectContext.h" | |
| #include "include/gpu/ganesh/GrTypes.h" | |
| #include "include/gpu/ganesh/SkImageGanesh.h" | |
| #include "include/gpu/ganesh/SkSurfaceGanesh.h" | |
| #include "include/private/SkTArray.h" | |
| #include "include/private/gpu/ganesh/GrTypesPriv.h" | |
| #include "src/core/SkAutoPixmapStorage.h" | |
| #include "src/core/SkCompressedDataUtils.h" | |
| #include "src/core/SkMipmap.h" | |
| #include "src/gpu/DataUtils.h" | |
| #include "src/gpu/GpuTypesPriv.h" | |
| #include "src/gpu/ganesh/GrBackendUtils.h" | |
| #include "src/gpu/ganesh/GrCaps.h" | |
| #include "src/gpu/ganesh/GrDataUtils.h" | |
| #include "src/gpu/ganesh/GrDirectContextPriv.h" | |
| #include "tests/ComparePixels.h" | |
| #include "tests/CtsEnforcement.h" | |
| #include "tests/Test.h" | |
| #include <algorithm> | |
| #include <cstddef> | |
| #include <functional> | |
| #include <initializer_list> | |
| #include <memory> | |
| #include <utility> | |
| using namespace skia_private; | |
| class GrRecordingContext; | |
| class SkPixmap; | |
| struct GrContextOptions; | |
| // Just verify that 'actual' is entirely 'expected' | |
| static void check_solid_pixmap(skiatest::Reporter* reporter, | |
| const SkColor4f& expected, const SkPixmap& actual, | |
| const char* label0, const char* label1, const char* label2) { | |
| const float tols[4] = { 0.01f, 0.01f, 0.01f, 0.01f }; | |
| auto error = std::function<ComparePixmapsErrorReporter>( | |
| [reporter, label0, label1, label2](int x, int y, const float diffs[4]) { | |
| SkASSERT(x >= 0 && y >= 0); | |
| ERRORF(reporter, "%s %s %s - mismatch at %d, %d (%f, %f, %f %f)", | |
| label0, label1, label2, x, y, | |
| diffs[0], diffs[1], diffs[2], diffs[3]); | |
| }); | |
| CheckSolidPixels(expected, actual, tols, error); | |
| } | |
| // Create an SkImage to wrap 'backendTex' | |
| sk_sp<SkImage> create_image(GrDirectContext* dContext, const GrBackendTexture& backendTex) { | |
| SkTextureCompressionType compression = | |
| GrBackendFormatToCompressionType(backendTex.getBackendFormat()); | |
| SkAlphaType at = SkTextureCompressionTypeIsOpaque(compression) ? kOpaque_SkAlphaType | |
| : kPremul_SkAlphaType; | |
| return SkImages::TextureFromCompressedTexture( | |
| dContext, backendTex, kTopLeft_GrSurfaceOrigin, at, nullptr); | |
| } | |
| // Draw the compressed backend texture (wrapped in an SkImage) into an RGBA surface, attempting | |
| // to access all the mipMap levels. | |
| static void check_compressed_mipmaps(GrRecordingContext* rContext, | |
| sk_sp<SkImage> img, | |
| SkTextureCompressionType compressionType, | |
| const SkColor4f expectedColors[6], | |
| skgpu::Mipmapped mipmapped, | |
| skiatest::Reporter* reporter, | |
| const char* label) { | |
| SkImageInfo readbackSurfaceII = SkImageInfo::Make(32, 32, kRGBA_8888_SkColorType, | |
| kPremul_SkAlphaType); | |
| sk_sp<SkSurface> surf = SkSurfaces::RenderTarget(rContext, | |
| skgpu::Budgeted::kNo, | |
| readbackSurfaceII, | |
| 1, | |
| kTopLeft_GrSurfaceOrigin, | |
| nullptr); | |
| if (!surf) { | |
| return; | |
| } | |
| SkCanvas* canvas = surf->getCanvas(); | |
| // When MIP map sampling is biased (as it is by default), hitting a level exactly using | |
| // SkMipmap::kLinear is difficult so we use kNearest. | |
| const SkSamplingOptions sampling(SkFilterMode::kLinear, | |
| SkMipmapMode::kNearest); | |
| SkPaint p; | |
| p.setBlendMode(SkBlendMode::kSrc); | |
| int numMipLevels = 1; | |
| if (mipmapped == skgpu::Mipmapped::kYes) { | |
| numMipLevels = SkMipmap::ComputeLevelCount(32, 32)+1; | |
| } | |
| for (int i = 0, rectSize = 32; i < numMipLevels; ++i, rectSize /= 2) { | |
| SkASSERT(rectSize >= 1); | |
| canvas->clear(SK_ColorTRANSPARENT); | |
| SkRect r = SkRect::MakeWH(rectSize, rectSize); | |
| canvas->drawImageRect(img, r, sampling, &p); | |
| SkImageInfo readbackII = SkImageInfo::Make(rectSize, rectSize, | |
| kRGBA_8888_SkColorType, | |
| kUnpremul_SkAlphaType); | |
| SkAutoPixmapStorage actual2; | |
| SkAssertResult(actual2.tryAlloc(readbackII)); | |
| actual2.erase(SkColors::kTransparent); | |
| bool result = surf->readPixels(actual2, 0, 0); | |
| REPORTER_ASSERT(reporter, result); | |
| SkString str; | |
| str.appendf("mip-level %d", i); | |
| check_solid_pixmap(reporter, expectedColors[i], actual2, | |
| skgpu::CompressionTypeToStr(compressionType), label, str.c_str()); | |
| } | |
| } | |
| // Verify that we can readback from a compressed texture | |
| static void check_readback(GrDirectContext* dContext, sk_sp<SkImage> img, | |
| SkTextureCompressionType compressionType, | |
| const SkColor4f& expectedColor, | |
| skiatest::Reporter* reporter, const char* label) { | |
| #ifdef SK_BUILD_FOR_IOS | |
| // reading back ETC2 is broken on Metal/iOS (skbug.com/40041169) | |
| if (dContext->backend() == GrBackendApi::kMetal) { | |
| return; | |
| } | |
| #endif | |
| SkAutoPixmapStorage actual; | |
| SkImageInfo readBackII = SkImageInfo::Make(img->width(), img->height(), | |
| kRGBA_8888_SkColorType, | |
| kUnpremul_SkAlphaType); | |
| SkAssertResult(actual.tryAlloc(readBackII)); | |
| actual.erase(SkColors::kTransparent); | |
| bool result = img->readPixels(dContext, actual, 0, 0); | |
| REPORTER_ASSERT(reporter, result); | |
| check_solid_pixmap(reporter, expectedColor, actual, | |
| skgpu::CompressionTypeToStr(compressionType), label, ""); | |
| } | |
| // Test initialization of compressed GrBackendTextures to a specific color | |
| static void test_compressed_color_init( | |
| GrDirectContext* dContext, | |
| skiatest::Reporter* reporter, | |
| std::function<GrBackendTexture(GrDirectContext*, const SkColor4f&, skgpu::Mipmapped)> | |
| create, | |
| const SkColor4f& color, | |
| SkTextureCompressionType compression, | |
| skgpu::Mipmapped mipmapped) { | |
| GrBackendTexture backendTex = create(dContext, color, mipmapped); | |
| if (!backendTex.isValid()) { | |
| return; | |
| } | |
| sk_sp<SkImage> img = create_image(dContext, backendTex); | |
| if (!img) { | |
| return; | |
| } | |
| SkColor4f expectedColors[6] = { color, color, color, color, color, color }; | |
| check_compressed_mipmaps(dContext, img, compression, expectedColors, mipmapped, | |
| reporter, "colorinit"); | |
| check_readback(dContext, img, compression, color, reporter, "solid readback"); | |
| SkColor4f newColor; | |
| newColor.fR = color.fB; | |
| newColor.fG = color.fR; | |
| newColor.fB = color.fG; | |
| newColor.fA = color.fA; | |
| bool result = dContext->updateCompressedBackendTexture(backendTex, newColor, nullptr, nullptr); | |
| // Since we were able to create the compressed texture we should be able to update it. | |
| REPORTER_ASSERT(reporter, result); | |
| SkColor4f expectedNewColors[6] = {newColor, newColor, newColor, newColor, newColor, newColor}; | |
| check_compressed_mipmaps(dContext, img, compression, expectedNewColors, mipmapped, reporter, | |
| "colorinit"); | |
| check_readback(dContext, std::move(img), compression, newColor, reporter, "solid readback"); | |
| dContext->deleteBackendTexture(backendTex); | |
| } | |
| // Create compressed data pulling the color for each mipmap level from 'levelColors'. | |
| static std::unique_ptr<const char[]> make_compressed_data(SkTextureCompressionType compression, | |
| SkColor4f levelColors[6], | |
| skgpu::Mipmapped mipmapped) { | |
| SkISize dimensions { 32, 32 }; | |
| int numMipLevels = 1; | |
| if (mipmapped == skgpu::Mipmapped::kYes) { | |
| numMipLevels = SkMipmap::ComputeLevelCount(dimensions) + 1; | |
| } | |
| TArray<size_t> mipMapOffsets(numMipLevels); | |
| size_t dataSize = SkCompressedDataSize( | |
| compression, dimensions, &mipMapOffsets, mipmapped == skgpu::Mipmapped::kYes); | |
| char* data = new char[dataSize]; | |
| for (int level = 0; level < numMipLevels; ++level) { | |
| // We have to do this a level at a time bc we might have a different color for | |
| // each level | |
| skgpu::FillInCompressedData(compression, | |
| dimensions, | |
| skgpu::Mipmapped::kNo, | |
| &data[mipMapOffsets[level]], | |
| levelColors[level]); | |
| dimensions = {std::max(1, dimensions.width() /2), std::max(1, dimensions.height()/2)}; | |
| } | |
| return std::unique_ptr<const char[]>(data); | |
| } | |
| // Verify that we can initialize a compressed backend texture with data (esp. | |
| // the mipmap levels). | |
| static void test_compressed_data_init( | |
| GrDirectContext* dContext, | |
| skiatest::Reporter* reporter, | |
| std::function<GrBackendTexture( | |
| GrDirectContext*, const char* data, size_t dataSize, skgpu::Mipmapped)> create, | |
| SkTextureCompressionType compression, | |
| skgpu::Mipmapped mipmapped) { | |
| SkColor4f expectedColors[6] = { | |
| { 1.0f, 0.0f, 0.0f, 1.0f }, // R | |
| { 0.0f, 1.0f, 0.0f, 1.0f }, // G | |
| { 0.0f, 0.0f, 1.0f, 1.0f }, // B | |
| { 0.0f, 1.0f, 1.0f, 1.0f }, // C | |
| { 1.0f, 0.0f, 1.0f, 1.0f }, // M | |
| { 1.0f, 1.0f, 0.0f, 1.0f }, // Y | |
| }; | |
| std::unique_ptr<const char[]> data(make_compressed_data(compression, expectedColors, | |
| mipmapped)); | |
| size_t dataSize = SkCompressedDataSize( | |
| compression, {32, 32}, nullptr, mipmapped == skgpu::Mipmapped::kYes); | |
| GrBackendTexture backendTex = create(dContext, data.get(), dataSize, mipmapped); | |
| if (!backendTex.isValid()) { | |
| return; | |
| } | |
| sk_sp<SkImage> img = create_image(dContext, backendTex); | |
| if (!img) { | |
| return; | |
| } | |
| check_compressed_mipmaps(dContext, img, compression, expectedColors, | |
| mipmapped, reporter, "pixmap"); | |
| check_readback(dContext, img, compression, expectedColors[0], reporter, "data readback"); | |
| SkColor4f expectedColorsNew[6] = { | |
| {1.0f, 1.0f, 0.0f, 1.0f}, // Y | |
| {1.0f, 0.0f, 0.0f, 1.0f}, // R | |
| {0.0f, 1.0f, 0.0f, 1.0f}, // G | |
| {0.0f, 0.0f, 1.0f, 1.0f}, // B | |
| {0.0f, 1.0f, 1.0f, 1.0f}, // C | |
| {1.0f, 0.0f, 1.0f, 1.0f}, // M | |
| }; | |
| std::unique_ptr<const char[]> dataNew( | |
| make_compressed_data(compression, expectedColorsNew, mipmapped)); | |
| size_t dataNewSize = SkCompressedDataSize( | |
| compression, {32, 32}, nullptr, mipmapped == skgpu::Mipmapped::kYes); | |
| bool result = dContext->updateCompressedBackendTexture(backendTex, dataNew.get(), dataNewSize, | |
| nullptr, nullptr); | |
| // Since we were able to create the compressed texture we should be able to update it. | |
| REPORTER_ASSERT(reporter, result); | |
| check_compressed_mipmaps(dContext, img, compression, expectedColorsNew, mipmapped, reporter, | |
| "pixmap"); | |
| check_readback(dContext, std::move(img), compression, expectedColorsNew[0], reporter, | |
| "data readback"); | |
| dContext->deleteBackendTexture(backendTex); | |
| } | |
| DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(CompressedBackendAllocationTest, | |
| reporter, | |
| ctxInfo, | |
| CtsEnforcement::kApiLevel_T) { | |
| auto dContext = ctxInfo.directContext(); | |
| const GrCaps* caps = dContext->priv().caps(); | |
| struct { | |
| SkTextureCompressionType fCompression; | |
| SkColor4f fColor; | |
| } combinations[] = { | |
| { SkTextureCompressionType::kETC2_RGB8_UNORM, SkColors::kRed }, | |
| { SkTextureCompressionType::kBC1_RGB8_UNORM, SkColors::kBlue }, | |
| { SkTextureCompressionType::kBC1_RGBA8_UNORM, SkColors::kTransparent }, | |
| }; | |
| for (auto combo : combinations) { | |
| GrBackendFormat format = dContext->compressedBackendFormat(combo.fCompression); | |
| if (!format.isValid()) { | |
| continue; | |
| } | |
| if (!caps->isFormatTexturable(format, GrTextureType::k2D)) { | |
| continue; | |
| } | |
| for (auto mipmapped : {skgpu::Mipmapped::kNo, skgpu::Mipmapped::kYes}) { | |
| if (skgpu::Mipmapped::kYes == mipmapped && !caps->mipmapSupport()) { | |
| continue; | |
| } | |
| // color initialized | |
| { | |
| auto createWithColorMtd = [format](GrDirectContext* dContext, | |
| const SkColor4f& color, | |
| skgpu::Mipmapped mipmapped) { | |
| return dContext->createCompressedBackendTexture(32, 32, format, color, | |
| mipmapped, GrProtected::kNo); | |
| }; | |
| test_compressed_color_init(dContext, reporter, createWithColorMtd, | |
| combo.fColor, combo.fCompression, mipmapped); | |
| } | |
| // data initialized | |
| { | |
| auto createWithDataMtd = [format](GrDirectContext* dContext, | |
| const char* data, | |
| size_t dataSize, | |
| skgpu::Mipmapped mipmapped) { | |
| return dContext->createCompressedBackendTexture(32, 32, format, data, dataSize, | |
| mipmapped, GrProtected::kNo); | |
| }; | |
| test_compressed_data_init(dContext, reporter, createWithDataMtd, | |
| combo.fCompression, mipmapped); | |
| } | |
| } | |
| } | |
| } |