blob: 3a41bfe383b7a1d83a4576e51c7ff5ed940287b1 [file] [log] [blame]
Robert Phillipsac908022020-01-14 16:54:17 -05001/*
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
Robert Phillips99dead92020-01-27 16:11:57 -050010#if !defined(SK_BUILD_FOR_GOOGLE3) // Google3 doesn't have etc1.h
Robert Phillips3da9e942020-01-27 21:05:23 +000011
Robert Phillipsac908022020-01-14 16:54:17 -050012#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"
Robert Phillips95c250c2020-06-29 15:36:12 -040024#include "include/gpu/GrContext.h"
Robert Phillipsb7bfbc22020-07-01 12:55:01 -040025#include "include/gpu/GrRecordingContext.h"
Robert Phillips99dead92020-01-27 16:11:57 -050026#include "src/core/SkCompressedDataUtils.h"
Robert Phillipsac908022020-01-14 16:54:17 -050027#include "src/core/SkMipMap.h"
Robert Phillips99dead92020-01-27 16:11:57 -050028#include "src/gpu/GrContextPriv.h"
Robert Phillipsac908022020-01-14 16:54:17 -050029#include "src/gpu/GrDataUtils.h"
Robert Phillips95c250c2020-06-29 15:36:12 -040030#include "src/gpu/GrRecordingContextPriv.h"
Robert Phillips99dead92020-01-27 16:11:57 -050031#include "src/image/SkImage_Base.h"
Robert Phillipsac908022020-01-14 16:54:17 -050032#include "third_party/etc1/etc1.h"
33
34class GrContext;
35class GrRenderTargetContext;
36
37static SkPoint gen_pt(float angle, const SkVector& scale) {
38 SkScalar s = SkScalarSin(angle);
39 SkScalar c = SkScalarCos(angle);
40
41 return { scale.fX * c, scale.fY * s };
42}
43
44// The resulting path will be centered at (0,0) and its size will match 'dimensions'
45static SkPath make_gear(SkISize dimensions, int numTeeth) {
46 SkVector outerRad{ dimensions.fWidth / 2.0f, dimensions.fHeight / 2.0f };
47 SkVector innerRad{ dimensions.fWidth / 2.5f, dimensions.fHeight / 2.5f };
Robert Phillips162e04b2020-01-28 14:22:43 -050048 const float kAnglePerTooth = 2.0f * SK_ScalarPI / (3 * numTeeth);
Robert Phillipsac908022020-01-14 16:54:17 -050049
50 float angle = 0.0f;
51
52 SkPath tmp;
53 tmp.setFillType(SkPathFillType::kWinding);
54
55 tmp.moveTo(gen_pt(angle, outerRad));
56
Robert Phillips162e04b2020-01-28 14:22:43 -050057 for (int i = 0; i < numTeeth; ++i, angle += 3*kAnglePerTooth) {
Robert Phillipsac908022020-01-14 16:54:17 -050058 tmp.lineTo(gen_pt(angle+kAnglePerTooth, outerRad));
Robert Phillips162e04b2020-01-28 14:22:43 -050059 tmp.lineTo(gen_pt(angle+(1.5f*kAnglePerTooth), innerRad));
60 tmp.lineTo(gen_pt(angle+(2.5f*kAnglePerTooth), innerRad));
61 tmp.lineTo(gen_pt(angle+(3.0f*kAnglePerTooth), outerRad));
Robert Phillipsac908022020-01-14 16:54:17 -050062 }
63
64 tmp.close();
65
Brian Osman788b9162020-02-07 10:36:46 -050066 float fInnerRad = 0.1f * std::min(dimensions.fWidth, dimensions.fHeight);
Robert Phillipsac908022020-01-14 16:54:17 -050067 if (fInnerRad > 0.5f) {
68 tmp.addCircle(0.0f, 0.0f, fInnerRad, SkPathDirection::kCCW);
69 }
70
71 return tmp;
72}
73
74// Render one level of a mipmap
75SkBitmap render_level(SkISize dimensions, SkColor color, SkColorType colorType, bool opaque) {
76 SkPath path = make_gear(dimensions, 9);
77
78 SkImageInfo ii = SkImageInfo::Make(dimensions.width(), dimensions.height(),
79 colorType, opaque ? kOpaque_SkAlphaType
80 : kPremul_SkAlphaType);
81 SkBitmap bm;
82 bm.allocPixels(ii);
83
84 bm.eraseColor(opaque ? SK_ColorBLACK : SK_ColorTRANSPARENT);
85
86 SkCanvas c(bm);
87
88 SkPaint paint;
89 paint.setColor(color | 0xFF000000);
90 paint.setAntiAlias(false);
91
92 c.translate(dimensions.width() / 2.0f, dimensions.height() / 2.0f);
93 c.drawPath(path, paint);
94
95 return bm;
96}
97
98// Create the compressed data blob needed to represent a mipmapped 2-color texture of the specified
99// compression format. In this case 2-color means either opaque black or transparent black plus
100// one other color.
101// Note that ETC1/ETC2_RGB8_UNORM only supports 565 opaque textures.
102static sk_sp<SkData> make_compressed_data(SkISize dimensions,
103 SkColorType colorType,
104 bool opaque,
105 SkImage::CompressionType compression) {
Robert Phillips99dead92020-01-27 16:11:57 -0500106 size_t totalSize = SkCompressedDataSize(compression, dimensions, nullptr, true);
Robert Phillipsac908022020-01-14 16:54:17 -0500107
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 for (int i = 0; i < numMipLevels; ++i) {
Robert Phillips99dead92020-01-27 16:11:57 -0500127 size_t levelSize = SkCompressedDataSize(compression, dimensions, nullptr, false);
Robert Phillipsac908022020-01-14 16:54:17 -0500128
129 SkBitmap bm = render_level(dimensions, kColors[i%7], colorType, opaque);
130 if (compression == SkImage::CompressionType::kETC2_RGB8_UNORM) {
131 SkASSERT(bm.colorType() == kRGB_565_SkColorType);
132 SkASSERT(opaque);
133
134 if (etc1_encode_image((unsigned char*)bm.getAddr16(0, 0),
135 bm.width(), bm.height(), 2, bm.rowBytes(),
136 (unsigned char*) &pixels[offset])) {
137 return nullptr;
138 }
139 } else {
140 GrTwoColorBC1Compress(bm.pixmap(), kColors[i%7], &pixels[offset]);
141 }
142
143 offset += levelSize;
Brian Osman788b9162020-02-07 10:36:46 -0500144 dimensions = {std::max(1, dimensions.width()/2), std::max(1, dimensions.height()/2)};
Robert Phillipsac908022020-01-14 16:54:17 -0500145 }
146
147 return tmp;
148}
149
150// Basic test of Ganesh's ETC1 and BC1 support
151// The layout is:
152// ETC2 BC1
153// --------------------------------------
154// RGB8 | kETC2_RGB8_UNORM | kBC1_RGB8_UNORM |
155// |--------------------------------------|
156// RGBA8 | | kBC1_RGBA8_UNORM |
157// --------------------------------------
158//
Robert Phillips162e04b2020-01-28 14:22:43 -0500159// The nonPowerOfTwo and nonMultipleOfFour cases exercise some compression edge cases.
Robert Phillips99dead92020-01-27 16:11:57 -0500160class CompressedTexturesGM : public skiagm::GM {
Robert Phillipsac908022020-01-14 16:54:17 -0500161public:
Robert Phillips162e04b2020-01-28 14:22:43 -0500162 enum class Type {
163 kNormal,
164 kNonPowerOfTwo,
165 kNonMultipleOfFour
166 };
167
168 CompressedTexturesGM(Type type) : fType(type) {
Robert Phillipsac908022020-01-14 16:54:17 -0500169 this->setBGColor(0xFFCCCCCC);
Robert Phillips162e04b2020-01-28 14:22:43 -0500170
171 switch (fType) {
172 case Type::kNonPowerOfTwo:
173 // These dimensions force the top two mip levels to be 1x3 and 1x1
174 fImgDimensions.set(20, 60);
175 break;
176 case Type::kNonMultipleOfFour:
177 // These dimensions force the top three mip levels to be 1x7, 1x3 and 1x1
178 fImgDimensions.set(13, 61); // prime numbers - just bc
179 break;
180 default:
181 fImgDimensions.set(kBaseTexWidth, kBaseTexHeight);
182 break;
183 }
184
Robert Phillipsac908022020-01-14 16:54:17 -0500185 }
186
187protected:
188 SkString onShortName() override {
Robert Phillips162e04b2020-01-28 14:22:43 -0500189 SkString name("compressed_textures");
190
191 if (fType == Type::kNonPowerOfTwo) {
192 name.append("_npot");
193 } else if (fType == Type::kNonMultipleOfFour) {
194 name.append("_nmof");
195 }
196
197 return name;
Robert Phillipsac908022020-01-14 16:54:17 -0500198 }
199
200 SkISize onISize() override {
Robert Phillips162e04b2020-01-28 14:22:43 -0500201 return SkISize::Make(2*kCellWidth + 3*kPad, 2*kBaseTexHeight + 3*kPad);
Robert Phillipsac908022020-01-14 16:54:17 -0500202 }
203
204 void onOnceBeforeDraw() override {
Robert Phillips162e04b2020-01-28 14:22:43 -0500205 fOpaqueETC2Data = make_compressed_data(fImgDimensions, kRGB_565_SkColorType, true,
Robert Phillipsac908022020-01-14 16:54:17 -0500206 SkImage::CompressionType::kETC2_RGB8_UNORM);
207
Robert Phillips162e04b2020-01-28 14:22:43 -0500208 fOpaqueBC1Data = make_compressed_data(fImgDimensions, kRGBA_8888_SkColorType, true,
Robert Phillipsac908022020-01-14 16:54:17 -0500209 SkImage::CompressionType::kBC1_RGB8_UNORM);
Robert Phillipsb0855272020-01-15 12:56:52 -0500210
Robert Phillips162e04b2020-01-28 14:22:43 -0500211 fTransparentBC1Data = make_compressed_data(fImgDimensions, kRGBA_8888_SkColorType, false,
Robert Phillipsb0855272020-01-15 12:56:52 -0500212 SkImage::CompressionType::kBC1_RGBA8_UNORM);
Robert Phillipsac908022020-01-14 16:54:17 -0500213 }
214
Robert Phillips99dead92020-01-27 16:11:57 -0500215 void onDraw(SkCanvas* canvas) override {
216 GrContext* context = canvas->getGrContext();
217
Robert Phillipsac908022020-01-14 16:54:17 -0500218 this->drawCell(context, canvas, fOpaqueETC2Data,
219 SkImage::CompressionType::kETC2_RGB8_UNORM, { kPad, kPad });
220
221 this->drawCell(context, canvas, fOpaqueBC1Data,
222 SkImage::CompressionType::kBC1_RGB8_UNORM, { 2*kPad + kCellWidth, kPad });
Robert Phillipsb0855272020-01-15 12:56:52 -0500223
224 this->drawCell(context, canvas, fTransparentBC1Data,
225 SkImage::CompressionType::kBC1_RGBA8_UNORM,
Robert Phillips162e04b2020-01-28 14:22:43 -0500226 { 2*kPad + kCellWidth, 2*kPad + kBaseTexHeight });
Robert Phillipsac908022020-01-14 16:54:17 -0500227 }
228
229private:
230 void drawCell(GrContext* context, SkCanvas* canvas, sk_sp<SkData> data,
231 SkImage::CompressionType compression, SkIVector offset) {
232
Robert Phillips99dead92020-01-27 16:11:57 -0500233 sk_sp<SkImage> image;
234 if (context) {
Robert Phillips45c94872020-01-28 15:59:31 -0500235 image = SkImage::MakeTextureFromCompressed(context, std::move(data),
236 fImgDimensions.width(),
237 fImgDimensions.height(),
238 compression, GrMipMapped::kYes);
Robert Phillips99dead92020-01-27 16:11:57 -0500239 } else {
Robert Phillips162e04b2020-01-28 14:22:43 -0500240 image = SkImage::MakeRasterFromCompressed(std::move(data),
241 fImgDimensions.width(),
242 fImgDimensions.height(),
Robert Phillips99dead92020-01-27 16:11:57 -0500243 compression);
244 }
245 if (!image) {
246 return;
247 }
248
Robert Phillips162e04b2020-01-28 14:22:43 -0500249 SkISize levelDimensions = fImgDimensions;
250 int numMipLevels = SkMipMap::ComputeLevelCount(levelDimensions.width(),
251 levelDimensions.height()) + 1;
Robert Phillipsac908022020-01-14 16:54:17 -0500252
Robert Phillipse0735522020-01-31 11:03:32 -0500253 SkPaint imagePaint;
254 imagePaint.setFilterQuality(kHigh_SkFilterQuality); // to force mipmapping
Robert Phillipsac908022020-01-14 16:54:17 -0500255
Robert Phillips99dead92020-01-27 16:11:57 -0500256 bool isCompressed = false;
257 if (image->isTextureBacked()) {
258 const GrCaps* caps = context->priv().caps();
259
260 GrTextureProxy* proxy = as_IB(image)->peekProxy();
261 isCompressed = caps->isFormatCompressed(proxy->backendFormat());
262 }
263
Robert Phillipse0735522020-01-31 11:03:32 -0500264 SkPaint redStrokePaint;
265 redStrokePaint.setColor(SK_ColorRED);
266 redStrokePaint.setStyle(SkPaint::kStroke_Style);
Robert Phillips99dead92020-01-27 16:11:57 -0500267
Robert Phillipsac908022020-01-14 16:54:17 -0500268 for (int i = 0; i < numMipLevels; ++i) {
269 SkRect r = SkRect::MakeXYWH(offset.fX, offset.fY,
Robert Phillips162e04b2020-01-28 14:22:43 -0500270 levelDimensions.width(), levelDimensions.height());
Robert Phillipsac908022020-01-14 16:54:17 -0500271
Robert Phillipse0735522020-01-31 11:03:32 -0500272 canvas->drawImageRect(image, r, &imagePaint);
273 if (!isCompressed) {
274 // Make it obvious which drawImages used decompressed images
275 canvas->drawRect(r, redStrokePaint);
276 }
Robert Phillipsac908022020-01-14 16:54:17 -0500277
278 if (i == 0) {
Robert Phillips162e04b2020-01-28 14:22:43 -0500279 offset.fX += levelDimensions.width()+1;
Robert Phillipsac908022020-01-14 16:54:17 -0500280 } else {
Robert Phillips162e04b2020-01-28 14:22:43 -0500281 offset.fY += levelDimensions.height()+1;
Robert Phillipsac908022020-01-14 16:54:17 -0500282 }
283
Brian Osman788b9162020-02-07 10:36:46 -0500284 levelDimensions = {std::max(1, levelDimensions.width()/2),
285 std::max(1, levelDimensions.height()/2)};
Robert Phillipsac908022020-01-14 16:54:17 -0500286 }
287 }
288
289 static const int kPad = 8;
Robert Phillips162e04b2020-01-28 14:22:43 -0500290 static const int kBaseTexWidth = 64;
291 static const int kCellWidth = 1.5f * kBaseTexWidth;
292 static const int kBaseTexHeight = 64;
Robert Phillipsac908022020-01-14 16:54:17 -0500293
Robert Phillips162e04b2020-01-28 14:22:43 -0500294 Type fType;
295 SkISize fImgDimensions;
Robert Phillipsac908022020-01-14 16:54:17 -0500296 sk_sp<SkData> fOpaqueETC2Data;
297 sk_sp<SkData> fOpaqueBC1Data;
Robert Phillipsb0855272020-01-15 12:56:52 -0500298 sk_sp<SkData> fTransparentBC1Data;
Robert Phillipsac908022020-01-14 16:54:17 -0500299
300 typedef GM INHERITED;
301};
302
303//////////////////////////////////////////////////////////////////////////////
304
Robert Phillips162e04b2020-01-28 14:22:43 -0500305DEF_GM(return new CompressedTexturesGM(CompressedTexturesGM::Type::kNormal);)
306DEF_GM(return new CompressedTexturesGM(CompressedTexturesGM::Type::kNonPowerOfTwo);)
307DEF_GM(return new CompressedTexturesGM(CompressedTexturesGM::Type::kNonMultipleOfFour);)
Robert Phillipsac908022020-01-14 16:54:17 -0500308
Robert Phillips3da9e942020-01-27 21:05:23 +0000309#endif