blob: 42f67515c49d9aedd98b033bd4bd875211166248 [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"
Adlai Holler4caa9352020-07-16 10:58:58 -040024#include "include/gpu/GrDirectContext.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"
Mike Reed13711eb2020-07-14 17:16:32 -040027#include "src/core/SkMipmap.h"
Robert Phillipsac908022020-01-14 16:54:17 -050028#include "src/gpu/GrDataUtils.h"
Adlai Holler302e8fb2020-09-14 11:58:06 -040029#include "src/gpu/GrImageContextPriv.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 Phillipsa84caa32020-07-28 09:57:26 -040032#include "src/image/SkImage_GpuBase.h"
Robert Phillipsac908022020-01-14 16:54:17 -050033#include "third_party/etc1/etc1.h"
34
Brian Salomoneebe7352020-12-09 16:37:04 -050035class GrSurfaceDrawContext;
Robert Phillipsac908022020-01-14 16:54:17 -050036
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.
Robert Phillipsa84caa32020-07-28 09:57:26 -0400102static sk_sp<SkImage> make_compressed_image(GrDirectContext* dContext,
103 const SkISize dimensions,
104 SkColorType colorType,
105 bool opaque,
106 SkImage::CompressionType compression) {
Robert Phillips99dead92020-01-27 16:11:57 -0500107 size_t totalSize = SkCompressedDataSize(compression, dimensions, nullptr, true);
Robert Phillipsac908022020-01-14 16:54:17 -0500108
109 sk_sp<SkData> tmp = SkData::MakeUninitialized(totalSize);
110 char* pixels = (char*) tmp->writable_data();
111
Mike Reed13711eb2020-07-14 17:16:32 -0400112 int numMipLevels = SkMipmap::ComputeLevelCount(dimensions.width(), dimensions.height()) + 1;
Robert Phillipsac908022020-01-14 16:54:17 -0500113
114 size_t offset = 0;
115
116 // Use a different color for each mipmap level so we can visually evaluate the draws
117 static const SkColor kColors[] = {
118 SK_ColorRED,
119 SK_ColorGREEN,
120 SK_ColorBLUE,
121 SK_ColorCYAN,
122 SK_ColorMAGENTA,
123 SK_ColorYELLOW,
124 SK_ColorWHITE,
125 };
126
Robert Phillipsa84caa32020-07-28 09:57:26 -0400127 SkISize levelDims = dimensions;
Robert Phillipsac908022020-01-14 16:54:17 -0500128 for (int i = 0; i < numMipLevels; ++i) {
Robert Phillipsa84caa32020-07-28 09:57:26 -0400129 size_t levelSize = SkCompressedDataSize(compression, levelDims, nullptr, false);
Robert Phillipsac908022020-01-14 16:54:17 -0500130
Robert Phillipsa84caa32020-07-28 09:57:26 -0400131 SkBitmap bm = render_level(levelDims, kColors[i%7], colorType, opaque);
Robert Phillipsac908022020-01-14 16:54:17 -0500132 if (compression == SkImage::CompressionType::kETC2_RGB8_UNORM) {
133 SkASSERT(bm.colorType() == kRGB_565_SkColorType);
134 SkASSERT(opaque);
135
136 if (etc1_encode_image((unsigned char*)bm.getAddr16(0, 0),
137 bm.width(), bm.height(), 2, bm.rowBytes(),
138 (unsigned char*) &pixels[offset])) {
139 return nullptr;
140 }
141 } else {
142 GrTwoColorBC1Compress(bm.pixmap(), kColors[i%7], &pixels[offset]);
143 }
144
145 offset += levelSize;
Robert Phillipsa84caa32020-07-28 09:57:26 -0400146 levelDims = {std::max(1, levelDims.width()/2), std::max(1, levelDims.height()/2)};
Robert Phillipsac908022020-01-14 16:54:17 -0500147 }
148
Robert Phillipsa84caa32020-07-28 09:57:26 -0400149 sk_sp<SkImage> image;
150 if (dContext) {
151 image = SkImage::MakeTextureFromCompressed(dContext, std::move(tmp),
152 dimensions.width(),
153 dimensions.height(),
154 compression, GrMipmapped::kYes);
155 } else {
156 image = SkImage::MakeRasterFromCompressed(std::move(tmp),
157 dimensions.width(),
158 dimensions.height(),
159 compression);
160 }
161 return image;
Robert Phillipsac908022020-01-14 16:54:17 -0500162}
163
164// Basic test of Ganesh's ETC1 and BC1 support
165// The layout is:
166// ETC2 BC1
167// --------------------------------------
168// RGB8 | kETC2_RGB8_UNORM | kBC1_RGB8_UNORM |
169// |--------------------------------------|
170// RGBA8 | | kBC1_RGBA8_UNORM |
171// --------------------------------------
172//
Robert Phillips162e04b2020-01-28 14:22:43 -0500173// The nonPowerOfTwo and nonMultipleOfFour cases exercise some compression edge cases.
Robert Phillips99dead92020-01-27 16:11:57 -0500174class CompressedTexturesGM : public skiagm::GM {
Robert Phillipsac908022020-01-14 16:54:17 -0500175public:
Robert Phillips162e04b2020-01-28 14:22:43 -0500176 enum class Type {
177 kNormal,
178 kNonPowerOfTwo,
179 kNonMultipleOfFour
180 };
181
182 CompressedTexturesGM(Type type) : fType(type) {
Robert Phillipsac908022020-01-14 16:54:17 -0500183 this->setBGColor(0xFFCCCCCC);
Robert Phillips162e04b2020-01-28 14:22:43 -0500184
185 switch (fType) {
186 case Type::kNonPowerOfTwo:
187 // These dimensions force the top two mip levels to be 1x3 and 1x1
188 fImgDimensions.set(20, 60);
189 break;
190 case Type::kNonMultipleOfFour:
191 // These dimensions force the top three mip levels to be 1x7, 1x3 and 1x1
192 fImgDimensions.set(13, 61); // prime numbers - just bc
193 break;
194 default:
195 fImgDimensions.set(kBaseTexWidth, kBaseTexHeight);
196 break;
197 }
198
Robert Phillipsac908022020-01-14 16:54:17 -0500199 }
200
201protected:
202 SkString onShortName() override {
Robert Phillips162e04b2020-01-28 14:22:43 -0500203 SkString name("compressed_textures");
204
205 if (fType == Type::kNonPowerOfTwo) {
206 name.append("_npot");
207 } else if (fType == Type::kNonMultipleOfFour) {
208 name.append("_nmof");
209 }
210
211 return name;
Robert Phillipsac908022020-01-14 16:54:17 -0500212 }
213
214 SkISize onISize() override {
Robert Phillips162e04b2020-01-28 14:22:43 -0500215 return SkISize::Make(2*kCellWidth + 3*kPad, 2*kBaseTexHeight + 3*kPad);
Robert Phillipsac908022020-01-14 16:54:17 -0500216 }
217
Robert Phillipsa84caa32020-07-28 09:57:26 -0400218 DrawResult onGpuSetup(GrDirectContext* dContext, SkString* errorMsg) override {
219 if (dContext && dContext->abandoned()) {
220 // This isn't a GpuGM so a null 'context' is okay but an abandoned context
221 // if forbidden.
222 return DrawResult::kSkip;
223 }
Robert Phillipsac908022020-01-14 16:54:17 -0500224
Robert Phillipsa84caa32020-07-28 09:57:26 -0400225 if (dContext &&
226 dContext->backend() == GrBackendApi::kDirect3D && fType == Type::kNonMultipleOfFour) {
227 // skbug.com/10541 - Are non-multiple-of-four BC1 textures supported in D3D?
228 return DrawResult::kSkip;
229 }
Robert Phillipsb0855272020-01-15 12:56:52 -0500230
Robert Phillipsa84caa32020-07-28 09:57:26 -0400231 fOpaqueETC2Image = make_compressed_image(dContext, fImgDimensions,
232 kRGB_565_SkColorType, true,
233 SkImage::CompressionType::kETC2_RGB8_UNORM);
234
235 fOpaqueBC1Image = make_compressed_image(dContext, fImgDimensions,
236 kRGBA_8888_SkColorType, true,
237 SkImage::CompressionType::kBC1_RGB8_UNORM);
238
239 fTransparentBC1Image = make_compressed_image(dContext, fImgDimensions,
240 kRGBA_8888_SkColorType, false,
241 SkImage::CompressionType::kBC1_RGBA8_UNORM);
242
243 if (!fOpaqueETC2Image || !fOpaqueBC1Image || !fTransparentBC1Image) {
244 *errorMsg = "Failed to create compressed images.";
245 return DrawResult::kFail;
246 }
247
248 return DrawResult::kOk;
249 }
250
251 void onGpuTeardown() override {
252 fOpaqueETC2Image = nullptr;
253 fOpaqueBC1Image = nullptr;
254 fTransparentBC1Image = nullptr;
Robert Phillipsac908022020-01-14 16:54:17 -0500255 }
256
Robert Phillips99dead92020-01-27 16:11:57 -0500257 void onDraw(SkCanvas* canvas) override {
Robert Phillipsa84caa32020-07-28 09:57:26 -0400258 this->drawCell(canvas, fOpaqueETC2Image.get(), { kPad, kPad });
Robert Phillips99dead92020-01-27 16:11:57 -0500259
Robert Phillipsa84caa32020-07-28 09:57:26 -0400260 this->drawCell(canvas, fOpaqueBC1Image.get(), { 2*kPad + kCellWidth, kPad });
Robert Phillipsac908022020-01-14 16:54:17 -0500261
Robert Phillipsa84caa32020-07-28 09:57:26 -0400262 this->drawCell(canvas, fTransparentBC1Image.get(),
Robert Phillips162e04b2020-01-28 14:22:43 -0500263 { 2*kPad + kCellWidth, 2*kPad + kBaseTexHeight });
Robert Phillipsac908022020-01-14 16:54:17 -0500264 }
265
266private:
Robert Phillipsa84caa32020-07-28 09:57:26 -0400267 void drawCell(SkCanvas* canvas, SkImage* image, SkIVector offset) {
Robert Phillips99dead92020-01-27 16:11:57 -0500268
Robert Phillips162e04b2020-01-28 14:22:43 -0500269 SkISize levelDimensions = fImgDimensions;
Mike Reed13711eb2020-07-14 17:16:32 -0400270 int numMipLevels = SkMipmap::ComputeLevelCount(levelDimensions.width(),
Robert Phillips162e04b2020-01-28 14:22:43 -0500271 levelDimensions.height()) + 1;
Robert Phillipsac908022020-01-14 16:54:17 -0500272
Robert Phillipse0735522020-01-31 11:03:32 -0500273 SkPaint imagePaint;
274 imagePaint.setFilterQuality(kHigh_SkFilterQuality); // to force mipmapping
Robert Phillipsac908022020-01-14 16:54:17 -0500275
Robert Phillips99dead92020-01-27 16:11:57 -0500276 bool isCompressed = false;
277 if (image->isTextureBacked()) {
Adlai Holler302e8fb2020-09-14 11:58:06 -0400278 const GrCaps* caps = as_IB(image)->context()->priv().caps();
Robert Phillips99dead92020-01-27 16:11:57 -0500279
280 GrTextureProxy* proxy = as_IB(image)->peekProxy();
281 isCompressed = caps->isFormatCompressed(proxy->backendFormat());
282 }
283
Robert Phillipse0735522020-01-31 11:03:32 -0500284 SkPaint redStrokePaint;
285 redStrokePaint.setColor(SK_ColorRED);
286 redStrokePaint.setStyle(SkPaint::kStroke_Style);
Robert Phillips99dead92020-01-27 16:11:57 -0500287
Robert Phillipsac908022020-01-14 16:54:17 -0500288 for (int i = 0; i < numMipLevels; ++i) {
289 SkRect r = SkRect::MakeXYWH(offset.fX, offset.fY,
Robert Phillips162e04b2020-01-28 14:22:43 -0500290 levelDimensions.width(), levelDimensions.height());
Robert Phillipsac908022020-01-14 16:54:17 -0500291
Robert Phillipse0735522020-01-31 11:03:32 -0500292 canvas->drawImageRect(image, r, &imagePaint);
293 if (!isCompressed) {
294 // Make it obvious which drawImages used decompressed images
295 canvas->drawRect(r, redStrokePaint);
296 }
Robert Phillipsac908022020-01-14 16:54:17 -0500297
298 if (i == 0) {
Robert Phillips162e04b2020-01-28 14:22:43 -0500299 offset.fX += levelDimensions.width()+1;
Robert Phillipsac908022020-01-14 16:54:17 -0500300 } else {
Robert Phillips162e04b2020-01-28 14:22:43 -0500301 offset.fY += levelDimensions.height()+1;
Robert Phillipsac908022020-01-14 16:54:17 -0500302 }
303
Brian Osman788b9162020-02-07 10:36:46 -0500304 levelDimensions = {std::max(1, levelDimensions.width()/2),
305 std::max(1, levelDimensions.height()/2)};
Robert Phillipsac908022020-01-14 16:54:17 -0500306 }
307 }
308
309 static const int kPad = 8;
Robert Phillips162e04b2020-01-28 14:22:43 -0500310 static const int kBaseTexWidth = 64;
311 static const int kCellWidth = 1.5f * kBaseTexWidth;
312 static const int kBaseTexHeight = 64;
Robert Phillipsac908022020-01-14 16:54:17 -0500313
Robert Phillipsa84caa32020-07-28 09:57:26 -0400314 Type fType;
315 SkISize fImgDimensions;
316
317 sk_sp<SkImage> fOpaqueETC2Image;
318 sk_sp<SkImage> fOpaqueBC1Image;
319 sk_sp<SkImage> fTransparentBC1Image;
Robert Phillipsac908022020-01-14 16:54:17 -0500320
John Stiles7571f9e2020-09-02 22:42:33 -0400321 using INHERITED = GM;
Robert Phillipsac908022020-01-14 16:54:17 -0500322};
323
324//////////////////////////////////////////////////////////////////////////////
325
Robert Phillips162e04b2020-01-28 14:22:43 -0500326DEF_GM(return new CompressedTexturesGM(CompressedTexturesGM::Type::kNormal);)
327DEF_GM(return new CompressedTexturesGM(CompressedTexturesGM::Type::kNonPowerOfTwo);)
328DEF_GM(return new CompressedTexturesGM(CompressedTexturesGM::Type::kNonMultipleOfFour);)
Robert Phillipsac908022020-01-14 16:54:17 -0500329
Robert Phillips3da9e942020-01-27 21:05:23 +0000330#endif