1/* 2 * Copyright 2020 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/SkTypes.h" // IWYU pragma: keep 9 10#if !defined(SK_BUILD_FOR_GOOGLE3) // Google3 doesn't have etc1.h 11 12#include "gm/gm.h" 13#include "include/core/SkBitmap.h" 14#include "include/core/SkCanvas.h" 15#include "include/core/SkColor.h" 16#include "include/core/SkData.h" 17#include "include/core/SkImage.h" 18#include "include/core/SkImageInfo.h" 19#include "include/core/SkPath.h" 20#include "include/core/SkRect.h" 21#include "include/core/SkRefCnt.h" 22#include "include/core/SkSize.h" 23#include "include/core/SkString.h" 24#include "include/gpu/GrDirectContext.h" 25#include "include/gpu/GrRecordingContext.h" 26#include "src/core/SkCompressedDataUtils.h" 27#include "src/core/SkMipmap.h" 28#include "src/gpu/GrDataUtils.h" 29#include "src/gpu/GrImageContextPriv.h" 30#include "src/gpu/GrRecordingContextPriv.h" 31#include "src/image/SkImage_Base.h" 32#include "src/image/SkImage_GpuBase.h" 33#include "third_party/etc1/etc1.h" 34#include "tools/gpu/ProxyUtils.h" 35 36static SkPoint gen_pt(float angle, const SkVector& scale) { 37 SkScalar s = SkScalarSin(angle); 38 SkScalar c = SkScalarCos(angle); 39 40 return { scale.fX * c, scale.fY * s }; 41} 42 43// The resulting path will be centered at (0,0) and its size will match 'dimensions' 44static SkPath make_gear(SkISize dimensions, int numTeeth) { 45 SkVector outerRad{ dimensions.fWidth / 2.0f, dimensions.fHeight / 2.0f }; 46 SkVector innerRad{ dimensions.fWidth / 2.5f, dimensions.fHeight / 2.5f }; 47 const float kAnglePerTooth = 2.0f * SK_ScalarPI / (3 * numTeeth); 48 49 float angle = 0.0f; 50 51 SkPath tmp; 52 tmp.setFillType(SkPathFillType::kWinding); 53 54 tmp.moveTo(gen_pt(angle, outerRad)); 55 56 for (int i = 0; i < numTeeth; ++i, angle += 3*kAnglePerTooth) { 57 tmp.lineTo(gen_pt(angle+kAnglePerTooth, outerRad)); 58 tmp.lineTo(gen_pt(angle+(1.5f*kAnglePerTooth), innerRad)); 59 tmp.lineTo(gen_pt(angle+(2.5f*kAnglePerTooth), innerRad)); 60 tmp.lineTo(gen_pt(angle+(3.0f*kAnglePerTooth), outerRad)); 61 } 62 63 tmp.close(); 64 65 float fInnerRad = 0.1f * std::min(dimensions.fWidth, dimensions.fHeight); 66 if (fInnerRad > 0.5f) { 67 tmp.addCircle(0.0f, 0.0f, fInnerRad, SkPathDirection::kCCW); 68 } 69 70 return tmp; 71} 72 73// Render one level of a mipmap 74SkBitmap render_level(SkISize dimensions, SkColor color, SkColorType colorType, bool opaque) { 75 SkPath path = make_gear(dimensions, 9); 76 77 SkImageInfo ii = SkImageInfo::Make(dimensions.width(), dimensions.height(), 78 colorType, opaque ? kOpaque_SkAlphaType 79 : kPremul_SkAlphaType); 80 SkBitmap bm; 81 bm.allocPixels(ii); 82 83 bm.eraseColor(opaque ? SK_ColorBLACK : SK_ColorTRANSPARENT); 84 85 SkCanvas c(bm); 86 87 SkPaint paint; 88 paint.setColor(color | 0xFF000000); 89 paint.setAntiAlias(false); 90 91 c.translate(dimensions.width() / 2.0f, dimensions.height() / 2.0f); 92 c.drawPath(path, paint); 93 94 return bm; 95} 96 97// Create the compressed data blob needed to represent a mipmapped 2-color texture of the specified 98// compression format. In this case 2-color means either opaque black or transparent black plus 99// one other color. 100// Note that ETC1/ETC2_RGB8_UNORM only supports 565 opaque textures. 101static sk_sp<SkImage> make_compressed_image(GrDirectContext* dContext, 102 const SkISize dimensions, 103 SkColorType colorType, 104 bool opaque, 105 SkImage::CompressionType compression) { 106 size_t totalSize = SkCompressedDataSize(compression, dimensions, nullptr, true); 107 108 sk_sp<SkData> tmp = SkData::MakeUninitialized(totalSize); 109 char* pixels = (char*) tmp->writable_data(); 110 111 int numMipLevels = SkMipmap::ComputeLevelCount(dimensions.width(), dimensions.height()) + 1; 112 113 size_t offset = 0; 114 115 // Use a different color for each mipmap level so we can visually evaluate the draws 116 static const SkColor kColors[] = { 117 SK_ColorRED, 118 SK_ColorGREEN, 119 SK_ColorBLUE, 120 SK_ColorCYAN, 121 SK_ColorMAGENTA, 122 SK_ColorYELLOW, 123 SK_ColorWHITE, 124 }; 125 126 SkISize levelDims = dimensions; 127 for (int i = 0; i < numMipLevels; ++i) { 128 size_t levelSize = SkCompressedDataSize(compression, levelDims, nullptr, false); 129 130 SkBitmap bm = render_level(levelDims, kColors[i%7], colorType, opaque); 131 if (compression == SkImage::CompressionType::kETC2_RGB8_UNORM) { 132 SkASSERT(bm.colorType() == kRGB_565_SkColorType); 133 SkASSERT(opaque); 134 135 if (etc1_encode_image((unsigned char*)bm.getAddr16(0, 0), 136 bm.width(), bm.height(), 2, bm.rowBytes(), 137 (unsigned char*) &pixels[offset])) { 138 return nullptr; 139 } 140 } else { 141 GrTwoColorBC1Compress(bm.pixmap(), kColors[i%7], &pixels[offset]); 142 } 143 144 offset += levelSize; 145 levelDims = {std::max(1, levelDims.width()/2), std::max(1, levelDims.height()/2)}; 146 } 147 148 sk_sp<SkImage> image; 149 if (dContext) { 150 image = SkImage::MakeTextureFromCompressed(dContext, std::move(tmp), 151 dimensions.width(), 152 dimensions.height(), 153 compression, GrMipmapped::kYes); 154 } else { 155 image = SkImage::MakeRasterFromCompressed(std::move(tmp), 156 dimensions.width(), 157 dimensions.height(), 158 compression); 159 } 160 return image; 161} 162 163// Basic test of Ganesh's ETC1 and BC1 support 164// The layout is: 165// ETC2 BC1 166// -------------------------------------- 167// RGB8 | kETC2_RGB8_UNORM | kBC1_RGB8_UNORM | 168// |--------------------------------------| 169// RGBA8 | | kBC1_RGBA8_UNORM | 170// -------------------------------------- 171// 172// The nonPowerOfTwo and nonMultipleOfFour cases exercise some compression edge cases. 173class CompressedTexturesGM : public skiagm::GM { 174public: 175 enum class Type { 176 kNormal, 177 kNonPowerOfTwo, 178 kNonMultipleOfFour 179 }; 180 181 CompressedTexturesGM(Type type) : fType(type) { 182 this->setBGColor(0xFFCCCCCC); 183 184 switch (fType) { 185 case Type::kNonPowerOfTwo: 186 // These dimensions force the top two mip levels to be 1x3 and 1x1 187 fImgDimensions.set(20, 60); 188 break; 189 case Type::kNonMultipleOfFour: 190 // These dimensions force the top three mip levels to be 1x7, 1x3 and 1x1 191 fImgDimensions.set(13, 61); // prime numbers - just bc 192 break; 193 default: 194 fImgDimensions.set(kBaseTexWidth, kBaseTexHeight); 195 break; 196 } 197 198 } 199 200protected: 201 SkString onShortName() override { 202 SkString name("compressed_textures"); 203 204 if (fType == Type::kNonPowerOfTwo) { 205 name.append("_npot"); 206 } else if (fType == Type::kNonMultipleOfFour) { 207 name.append("_nmof"); 208 } 209 210 return name; 211 } 212 213 SkISize onISize() override { 214 return SkISize::Make(2*kCellWidth + 3*kPad, 2*kBaseTexHeight + 3*kPad); 215 } 216 217 DrawResult onGpuSetup(GrDirectContext* dContext, SkString* errorMsg) override { 218 if (dContext && dContext->abandoned()) { 219 // This isn't a GpuGM so a null 'context' is okay but an abandoned context 220 // if forbidden. 221 return DrawResult::kSkip; 222 } 223 224 if (dContext && 225 dContext->backend() == GrBackendApi::kDirect3D && fType == Type::kNonMultipleOfFour) { 226 // skbug.com/10541 - Are non-multiple-of-four BC1 textures supported in D3D? 227 return DrawResult::kSkip; 228 } 229 230 fOpaqueETC2Image = make_compressed_image(dContext, fImgDimensions, 231 kRGB_565_SkColorType, true, 232 SkImage::CompressionType::kETC2_RGB8_UNORM); 233 234 fOpaqueBC1Image = make_compressed_image(dContext, fImgDimensions, 235 kRGBA_8888_SkColorType, true, 236 SkImage::CompressionType::kBC1_RGB8_UNORM); 237 238 fTransparentBC1Image = make_compressed_image(dContext, fImgDimensions, 239 kRGBA_8888_SkColorType, false, 240 SkImage::CompressionType::kBC1_RGBA8_UNORM); 241 242 if (!fOpaqueETC2Image || !fOpaqueBC1Image || !fTransparentBC1Image) { 243 *errorMsg = "Failed to create compressed images."; 244 return DrawResult::kFail; 245 } 246 247 return DrawResult::kOk; 248 } 249 250 void onGpuTeardown() override { 251 fOpaqueETC2Image = nullptr; 252 fOpaqueBC1Image = nullptr; 253 fTransparentBC1Image = nullptr; 254 } 255 256 void onDraw(SkCanvas* canvas) override { 257 this->drawCell(canvas, fOpaqueETC2Image.get(), { kPad, kPad }); 258 259 this->drawCell(canvas, fOpaqueBC1Image.get(), { 2*kPad + kCellWidth, kPad }); 260 261 this->drawCell(canvas, fTransparentBC1Image.get(), 262 { 2*kPad + kCellWidth, 2*kPad + kBaseTexHeight }); 263 } 264 265private: 266 void drawCell(SkCanvas* canvas, SkImage* image, SkIVector offset) { 267 268 SkISize levelDimensions = fImgDimensions; 269 int numMipLevels = SkMipmap::ComputeLevelCount(levelDimensions.width(), 270 levelDimensions.height()) + 1; 271 272 SkSamplingOptions sampling(SkCubicResampler::Mitchell()); 273 274 bool isCompressed = false; 275 if (image->isTextureBacked()) { 276 const GrCaps* caps = as_IB(image)->context()->priv().caps(); 277 GrTextureProxy* proxy = sk_gpu_test::GetTextureImageProxy(image, 278 canvas->recordingContext()); 279 isCompressed = caps->isFormatCompressed(proxy->backendFormat()); 280 } 281 282 SkPaint redStrokePaint; 283 redStrokePaint.setColor(SK_ColorRED); 284 redStrokePaint.setStyle(SkPaint::kStroke_Style); 285 286 for (int i = 0; i < numMipLevels; ++i) { 287 SkRect r = SkRect::MakeXYWH(offset.fX, offset.fY, 288 levelDimensions.width(), levelDimensions.height()); 289 290 canvas->drawImageRect(image, r, sampling); 291 if (!isCompressed) { 292 // Make it obvious which drawImages used decompressed images 293 canvas->drawRect(r, redStrokePaint); 294 } 295 296 if (i == 0) { 297 offset.fX += levelDimensions.width()+1; 298 } else { 299 offset.fY += levelDimensions.height()+1; 300 } 301 302 levelDimensions = {std::max(1, levelDimensions.width()/2), 303 std::max(1, levelDimensions.height()/2)}; 304 } 305 } 306 307 static const int kPad = 8; 308 static const int kBaseTexWidth = 64; 309 static const int kCellWidth = 1.5f * kBaseTexWidth; 310 static const int kBaseTexHeight = 64; 311 312 Type fType; 313 SkISize fImgDimensions; 314 315 sk_sp<SkImage> fOpaqueETC2Image; 316 sk_sp<SkImage> fOpaqueBC1Image; 317 sk_sp<SkImage> fTransparentBC1Image; 318 319 using INHERITED = GM; 320}; 321 322////////////////////////////////////////////////////////////////////////////// 323 324DEF_GM(return new CompressedTexturesGM(CompressedTexturesGM::Type::kNormal);) 325DEF_GM(return new CompressedTexturesGM(CompressedTexturesGM::Type::kNonPowerOfTwo);) 326DEF_GM(return new CompressedTexturesGM(CompressedTexturesGM::Type::kNonMultipleOfFour);) 327 328#endif 329