| /* |
| * Copyright 2019 Google Inc. |
| * |
| * 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/base/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/CtsEnforcement.h" |
| #include "tests/Test.h" |
| #include "tests/TestUtils.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/9839) |
| 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.width(), dimensions.height()) + 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); |
| } |
| } |
| } |
| } |