1/* 2 * Copyright 2019 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "include/core/SkCanvas.h" 9#include "include/gpu/GrDirectContext.h" 10#include "include/gpu/GrRecordingContext.h" 11#include "src/core/SkAutoPixmapStorage.h" 12#include "src/core/SkCompressedDataUtils.h" 13#include "src/core/SkMipmap.h" 14#include "src/core/SkPaintPriv.h" 15#include "src/gpu/GrBackendUtils.h" 16#include "src/gpu/GrDirectContextPriv.h" 17#include "src/image/SkImage_Base.h" 18#include "tests/Test.h" 19#include "tests/TestUtils.h" 20#include "tools/ToolUtils.h" 21 22// Just verify that 'actual' is entirely 'expected' 23static void check_solid_pixmap(skiatest::Reporter* reporter, 24 const SkColor4f& expected, const SkPixmap& actual, 25 const char* label0, const char* label1, const char* label2) { 26 const float tols[4] = { 0.01f, 0.01f, 0.01f, 0.01f }; 27 28 auto error = std::function<ComparePixmapsErrorReporter>( 29 [reporter, label0, label1, label2](int x, int y, const float diffs[4]) { 30 SkASSERT(x >= 0 && y >= 0); 31 ERRORF(reporter, "%s %s %s - mismatch at %d, %d (%f, %f, %f %f)", 32 label0, label1, label2, x, y, 33 diffs[0], diffs[1], diffs[2], diffs[3]); 34 }); 35 36 CheckSolidPixels(expected, actual, tols, error); 37} 38 39// Create an SkImage to wrap 'backendTex' 40sk_sp<SkImage> create_image(GrDirectContext* dContext, const GrBackendTexture& backendTex) { 41 SkImage::CompressionType compression = 42 GrBackendFormatToCompressionType(backendTex.getBackendFormat()); 43 44 SkAlphaType at = SkCompressionTypeIsOpaque(compression) ? kOpaque_SkAlphaType 45 : kPremul_SkAlphaType; 46 47 return SkImage::MakeFromCompressedTexture(dContext, 48 backendTex, 49 kTopLeft_GrSurfaceOrigin, 50 at, 51 nullptr); 52} 53 54// Draw the compressed backend texture (wrapped in an SkImage) into an RGBA surface, attempting 55// to access all the mipMap levels. 56static void check_compressed_mipmaps(GrRecordingContext* rContext, sk_sp<SkImage> img, 57 SkImage::CompressionType compressionType, 58 const SkColor4f expectedColors[6], 59 GrMipmapped mipMapped, 60 skiatest::Reporter* reporter, const char* label) { 61 62 SkImageInfo readbackSurfaceII = SkImageInfo::Make(32, 32, kRGBA_8888_SkColorType, 63 kPremul_SkAlphaType); 64 65 sk_sp<SkSurface> surf = SkSurface::MakeRenderTarget(rContext, 66 SkBudgeted::kNo, 67 readbackSurfaceII, 1, 68 kTopLeft_GrSurfaceOrigin, 69 nullptr); 70 if (!surf) { 71 return; 72 } 73 74 SkCanvas* canvas = surf->getCanvas(); 75 76 const SkSamplingOptions sampling(SkFilterMode::kLinear, 77 SkMipmapMode::kLinear); 78 SkPaint p; 79 p.setBlendMode(SkBlendMode::kSrc); 80 81 int numMipLevels = 1; 82 if (mipMapped == GrMipmapped::kYes) { 83 numMipLevels = SkMipmap::ComputeLevelCount(32, 32)+1; 84 } 85 86 for (int i = 0, rectSize = 32; i < numMipLevels; ++i, rectSize /= 2) { 87 SkASSERT(rectSize >= 1); 88 89 canvas->clear(SK_ColorTRANSPARENT); 90 91 SkRect r = SkRect::MakeWH(rectSize, rectSize); 92 canvas->drawImageRect(img, r, sampling, &p); 93 94 SkImageInfo readbackII = SkImageInfo::Make(rectSize, rectSize, 95 kRGBA_8888_SkColorType, 96 kUnpremul_SkAlphaType); 97 SkAutoPixmapStorage actual2; 98 SkAssertResult(actual2.tryAlloc(readbackII)); 99 actual2.erase(SkColors::kTransparent); 100 101 bool result = surf->readPixels(actual2, 0, 0); 102 REPORTER_ASSERT(reporter, result); 103 104 SkString str; 105 str.appendf("mip-level %d", i); 106 107 check_solid_pixmap(reporter, expectedColors[i], actual2, 108 GrCompressionTypeToStr(compressionType), label, str.c_str()); 109 } 110} 111 112// Verify that we can readback from a compressed texture 113static void check_readback(GrDirectContext* dContext, sk_sp<SkImage> img, 114 SkImage::CompressionType compressionType, 115 const SkColor4f& expectedColor, 116 skiatest::Reporter* reporter, const char* label) { 117#ifdef SK_BUILD_FOR_IOS 118 // reading back ETC2 is broken on Metal/iOS (skbug.com/9839) 119 if (dContext->backend() == GrBackendApi::kMetal) { 120 return; 121 } 122#endif 123 124 SkAutoPixmapStorage actual; 125 126 SkImageInfo readBackII = SkImageInfo::Make(img->width(), img->height(), 127 kRGBA_8888_SkColorType, 128 kUnpremul_SkAlphaType); 129 130 SkAssertResult(actual.tryAlloc(readBackII)); 131 actual.erase(SkColors::kTransparent); 132 133 bool result = img->readPixels(dContext, actual, 0, 0); 134 REPORTER_ASSERT(reporter, result); 135 136 check_solid_pixmap(reporter, expectedColor, actual, 137 GrCompressionTypeToStr(compressionType), label, ""); 138} 139 140// Test initialization of compressed GrBackendTextures to a specific color 141static void test_compressed_color_init(GrDirectContext* dContext, 142 skiatest::Reporter* reporter, 143 std::function<GrBackendTexture (GrDirectContext*, 144 const SkColor4f&, 145 GrMipmapped)> create, 146 const SkColor4f& color, 147 SkImage::CompressionType compression, 148 GrMipmapped mipMapped) { 149 GrBackendTexture backendTex = create(dContext, color, mipMapped); 150 if (!backendTex.isValid()) { 151 return; 152 } 153 154 sk_sp<SkImage> img = create_image(dContext, backendTex); 155 if (!img) { 156 return; 157 } 158 159 SkColor4f expectedColors[6] = { color, color, color, color, color, color }; 160 161 check_compressed_mipmaps(dContext, img, compression, expectedColors, mipMapped, 162 reporter, "colorinit"); 163 check_readback(dContext, img, compression, color, reporter, "solid readback"); 164 165 SkColor4f newColor; 166 newColor.fR = color.fB; 167 newColor.fG = color.fR; 168 newColor.fB = color.fG; 169 newColor.fA = color.fA; 170 171 bool result = dContext->updateCompressedBackendTexture(backendTex, newColor, nullptr, nullptr); 172 // Since we were able to create the compressed texture we should be able to update it. 173 REPORTER_ASSERT(reporter, result); 174 175 SkColor4f expectedNewColors[6] = {newColor, newColor, newColor, newColor, newColor, newColor}; 176 177 check_compressed_mipmaps(dContext, img, compression, expectedNewColors, mipMapped, reporter, 178 "colorinit"); 179 check_readback(dContext, std::move(img), compression, newColor, reporter, "solid readback"); 180 181 dContext->deleteBackendTexture(backendTex); 182} 183 184// Create compressed data pulling the color for each mipmap level from 'levelColors'. 185static std::unique_ptr<const char[]> make_compressed_data(SkImage::CompressionType compression, 186 SkColor4f levelColors[6], 187 GrMipmapped mipMapped) { 188 SkISize dimensions { 32, 32 }; 189 190 int numMipLevels = 1; 191 if (mipMapped == GrMipmapped::kYes) { 192 numMipLevels = SkMipmap::ComputeLevelCount(dimensions.width(), dimensions.height()) + 1; 193 } 194 195 SkTArray<size_t> mipMapOffsets(numMipLevels); 196 197 size_t dataSize = SkCompressedDataSize(compression, dimensions, &mipMapOffsets, 198 mipMapped == GrMipmapped::kYes); 199 char* data = new char[dataSize]; 200 201 for (int level = 0; level < numMipLevels; ++level) { 202 // We have to do this a level at a time bc we might have a different color for 203 // each level 204 GrFillInCompressedData(compression, dimensions, 205 GrMipmapped::kNo, &data[mipMapOffsets[level]], levelColors[level]); 206 207 dimensions = {std::max(1, dimensions.width() /2), std::max(1, dimensions.height()/2)}; 208 } 209 210 return std::unique_ptr<const char[]>(data); 211} 212 213// Verify that we can initialize a compressed backend texture with data (esp. 214// the mipmap levels). 215static void test_compressed_data_init(GrDirectContext* dContext, 216 skiatest::Reporter* reporter, 217 std::function<GrBackendTexture (GrDirectContext*, 218 const char* data, 219 size_t dataSize, 220 GrMipmapped)> create, 221 SkImage::CompressionType compression, 222 GrMipmapped mipMapped) { 223 224 SkColor4f expectedColors[6] = { 225 { 1.0f, 0.0f, 0.0f, 1.0f }, // R 226 { 0.0f, 1.0f, 0.0f, 1.0f }, // G 227 { 0.0f, 0.0f, 1.0f, 1.0f }, // B 228 { 0.0f, 1.0f, 1.0f, 1.0f }, // C 229 { 1.0f, 0.0f, 1.0f, 1.0f }, // M 230 { 1.0f, 1.0f, 0.0f, 1.0f }, // Y 231 }; 232 233 std::unique_ptr<const char[]> data(make_compressed_data(compression, expectedColors, 234 mipMapped)); 235 size_t dataSize = SkCompressedDataSize(compression, { 32, 32 }, nullptr, 236 mipMapped == GrMipmapped::kYes); 237 238 GrBackendTexture backendTex = create(dContext, data.get(), dataSize, mipMapped); 239 if (!backendTex.isValid()) { 240 return; 241 } 242 243 sk_sp<SkImage> img = create_image(dContext, backendTex); 244 if (!img) { 245 return; 246 } 247 248 check_compressed_mipmaps(dContext, img, compression, expectedColors, 249 mipMapped, reporter, "pixmap"); 250 check_readback(dContext, img, compression, expectedColors[0], reporter, "data readback"); 251 252 SkColor4f expectedColorsNew[6] = { 253 {1.0f, 1.0f, 0.0f, 1.0f}, // Y 254 {1.0f, 0.0f, 0.0f, 1.0f}, // R 255 {0.0f, 1.0f, 0.0f, 1.0f}, // G 256 {0.0f, 0.0f, 1.0f, 1.0f}, // B 257 {0.0f, 1.0f, 1.0f, 1.0f}, // C 258 {1.0f, 0.0f, 1.0f, 1.0f}, // M 259 }; 260 261 std::unique_ptr<const char[]> dataNew( 262 make_compressed_data(compression, expectedColorsNew, mipMapped)); 263 size_t dataNewSize = 264 SkCompressedDataSize(compression, {32, 32}, nullptr, mipMapped == GrMipMapped::kYes); 265 266 bool result = dContext->updateCompressedBackendTexture(backendTex, dataNew.get(), dataNewSize, 267 nullptr, nullptr); 268 // Since we were able to create the compressed texture we should be able to update it. 269 REPORTER_ASSERT(reporter, result); 270 271 check_compressed_mipmaps(dContext, img, compression, expectedColorsNew, mipMapped, reporter, 272 "pixmap"); 273 check_readback(dContext, std::move(img), compression, expectedColorsNew[0], reporter, 274 "data readback"); 275 276 dContext->deleteBackendTexture(backendTex); 277} 278 279DEF_GPUTEST_FOR_RENDERING_CONTEXTS(CompressedBackendAllocationTest, reporter, ctxInfo) { 280 auto dContext = ctxInfo.directContext(); 281 const GrCaps* caps = dContext->priv().caps(); 282 283 struct { 284 SkImage::CompressionType fCompression; 285 SkColor4f fColor; 286 } combinations[] = { 287 { SkImage::CompressionType::kETC2_RGB8_UNORM, SkColors::kRed }, 288 { SkImage::CompressionType::kBC1_RGB8_UNORM, SkColors::kBlue }, 289 { SkImage::CompressionType::kBC1_RGBA8_UNORM, SkColors::kTransparent }, 290 }; 291 292 for (auto combo : combinations) { 293 GrBackendFormat format = dContext->compressedBackendFormat(combo.fCompression); 294 if (!format.isValid()) { 295 continue; 296 } 297 298 if (!caps->isFormatTexturable(format, GrTextureType::k2D)) { 299 continue; 300 } 301 302 for (auto mipMapped : { GrMipmapped::kNo, GrMipmapped::kYes }) { 303 if (GrMipmapped::kYes == mipMapped && !caps->mipmapSupport()) { 304 continue; 305 } 306 307 // color initialized 308 { 309 auto createWithColorMtd = [format](GrDirectContext* dContext, 310 const SkColor4f& color, 311 GrMipmapped mipMapped) { 312 return dContext->createCompressedBackendTexture(32, 32, format, color, 313 mipMapped, GrProtected::kNo); 314 }; 315 316 test_compressed_color_init(dContext, reporter, createWithColorMtd, 317 combo.fColor, combo.fCompression, mipMapped); 318 } 319 320 // data initialized 321 { 322 auto createWithDataMtd = [format](GrDirectContext* dContext, 323 const char* data, size_t dataSize, 324 GrMipmapped mipMapped) { 325 return dContext->createCompressedBackendTexture(32, 32, format, data, dataSize, 326 mipMapped, GrProtected::kNo); 327 }; 328 329 test_compressed_data_init(dContext, reporter, createWithDataMtd, 330 combo.fCompression, mipMapped); 331 } 332 333 } 334 } 335} 336