blob: 22ca1deb7945ed7e3559e2ccd1ca12fafa30252a [file] [log] [blame]
Robert Phillipsd4f68312020-01-31 10:15:05 -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 "gm/gm.h"
9#include "include/core/SkCanvas.h"
10#include "include/core/SkImage.h"
11#include "include/core/SkStream.h"
Robert Phillips95c250c2020-06-29 15:36:12 -040012#include "include/gpu/GrContext.h"
Robert Phillipsb7bfbc22020-07-01 12:55:01 -040013#include "include/gpu/GrRecordingContext.h"
Robert Phillipsd4f68312020-01-31 10:15:05 -050014#include "src/core/SkCompressedDataUtils.h"
15#include "src/core/SkMipMap.h"
Robert Phillips95c250c2020-06-29 15:36:12 -040016#include "src/gpu/GrRecordingContextPriv.h"
Robert Phillipsd4f68312020-01-31 10:15:05 -050017#include "src/gpu/gl/GrGLDefines.h"
18#include "src/image/SkImage_Base.h"
19
20#include "tools/Resources.h"
21
22//-------------------------------------------------------------------------------------------------
23struct ImageInfo {
24 SkISize fDim;
25 GrMipMapped fMipMapped;
26 SkImage::CompressionType fCompressionType;
27};
28
29/*
30 * Get an int from a buffer
31 * This method is unsafe, the caller is responsible for performing a check
32 */
33static inline uint32_t get_uint(uint8_t* buffer, uint32_t i) {
34 uint32_t result;
35 memcpy(&result, &(buffer[i]), 4);
36 return result;
37}
38
39// This KTX loader is barely sufficient to load the specific files this GM requires. Use
40// at your own peril.
41static sk_sp<SkData> load_ktx(const char* filename, ImageInfo* imageInfo) {
42 SkFILEStream input(filename);
43 if (!input.isValid()) {
44 return nullptr;
45 }
46
47 constexpr int kKTXIdentifierSize = 12;
48 constexpr int kKTXHeaderSize = kKTXIdentifierSize + 13 * sizeof(uint32_t);
49 uint8_t header[kKTXHeaderSize];
50
51 if (input.read(header, kKTXHeaderSize) != kKTXHeaderSize) {
52 return nullptr;
53 }
54
55 static const uint8_t kExpectedIdentifier[kKTXIdentifierSize] = {
56 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
57 };
58
59 if (memcmp(header, kExpectedIdentifier, kKTXIdentifierSize)) {
60 return nullptr;
61 }
62
63 uint32_t endianness = get_uint(header, 12);
64 if (endianness != 0x04030201) {
65 // TODO: need to swap rest of header and, if glTypeSize is > 1, all
66 // the texture data.
67 return nullptr;
68 }
69
70 uint32_t glType = get_uint(header, 16);
71 SkDEBUGCODE(uint32_t glTypeSize = get_uint(header, 20);)
72 uint32_t glFormat = get_uint(header, 24);
73 uint32_t glInternalFormat = get_uint(header, 28);
74 //uint32_t glBaseInternalFormat = get_uint(header, 32);
75 uint32_t pixelWidth = get_uint(header, 36);
76 uint32_t pixelHeight = get_uint(header, 40);
77 uint32_t pixelDepth = get_uint(header, 44);
78 //uint32_t numberOfArrayElements = get_uint(header, 48);
79 uint32_t numberOfFaces = get_uint(header, 52);
80 int numberOfMipmapLevels = get_uint(header, 56);
81 uint32_t bytesOfKeyValueData = get_uint(header, 60);
82
83 if (glType != 0 || glFormat != 0) { // only care about compressed data for now
84 return nullptr;
85 }
86 SkASSERT(glTypeSize == 1); // required for compressed data
87
88 // We only handle these four formats right now
89 switch (glInternalFormat) {
90 case GR_GL_COMPRESSED_ETC1_RGB8:
91 case GR_GL_COMPRESSED_RGB8_ETC2:
92 imageInfo->fCompressionType = SkImage::CompressionType::kETC2_RGB8_UNORM;
93 break;
94 case GR_GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
95 imageInfo->fCompressionType = SkImage::CompressionType::kBC1_RGB8_UNORM;
96 break;
97 case GR_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
98 imageInfo->fCompressionType = SkImage::CompressionType::kBC1_RGBA8_UNORM;
99 break;
100 default:
101 return nullptr;
102 }
103
104 imageInfo->fDim.fWidth = pixelWidth;
105 imageInfo->fDim.fHeight = pixelHeight;
106
107 if (pixelDepth != 0) {
108 return nullptr; // pixel depth is always zero for 2D textures
109 }
110
111 if (numberOfFaces != 1) {
112 return nullptr; // we don't support cube maps right now
113 }
114
115 if (numberOfMipmapLevels == 1) {
116 imageInfo->fMipMapped = GrMipMapped::kNo;
117 } else {
118 int numRequiredMipLevels = SkMipMap::ComputeLevelCount(pixelWidth, pixelHeight)+1;
119 if (numberOfMipmapLevels != numRequiredMipLevels) {
120 return nullptr;
121 }
122 imageInfo->fMipMapped = GrMipMapped::kYes;
123 }
124
125 if (bytesOfKeyValueData != 0) {
126 return nullptr;
127 }
128
129 SkTArray<size_t> individualMipOffsets(numberOfMipmapLevels);
130
131 size_t dataSize = SkCompressedDataSize(imageInfo->fCompressionType,
132 { (int) pixelWidth, (int) pixelHeight },
133 &individualMipOffsets,
134 imageInfo->fMipMapped == GrMipMapped::kYes);
135 SkASSERT(individualMipOffsets.size() == (size_t) numberOfMipmapLevels);
136
137 sk_sp<SkData> data = SkData::MakeUninitialized(dataSize);
138
139 uint8_t* dest = (uint8_t*) data->writable_data();
140
141 size_t offset = 0;
142 for (int i = 0; i < numberOfMipmapLevels; ++i) {
143 uint32_t imageSize;
144
145 if (input.read(&imageSize, 4) != 4) {
146 return nullptr;
147 }
148
149 SkASSERT(offset + imageSize <= dataSize);
150 SkASSERT(offset == individualMipOffsets[i]);
151
152 if (input.read(&dest[offset], imageSize) != imageSize) {
153 return nullptr;
154 }
155
156 offset += imageSize;
157 }
158
159 return data;
160}
161
162//-------------------------------------------------------------------------------------------------
163typedef uint32_t DWORD;
164
165// Values for the DDS_PIXELFORMAT 'dwFlags' field
166constexpr unsigned int kDDPF_FOURCC = 0x4;
167
168struct DDS_PIXELFORMAT {
169 DWORD dwSize;
170 DWORD dwFlags;
171 DWORD dwFourCC;
172 DWORD dwRGBBitCount;
173 DWORD dwRBitMask;
174 DWORD dwGBitMask;
175 DWORD dwBBitMask;
176 DWORD dwABitMask;
177};
178
179// Values for the DDS_HEADER 'dwFlags' field
180constexpr unsigned int kDDSD_CAPS = 0x1; // required
181constexpr unsigned int kDDSD_HEIGHT = 0x2; // required
182constexpr unsigned int kDDSD_WIDTH = 0x4; // required
183constexpr unsigned int kDDSD_PITCH = 0x8;
184constexpr unsigned int kDDSD_PIXELFORMAT = 0x001000; // required
185constexpr unsigned int kDDSD_MIPMAPCOUNT = 0x020000;
186constexpr unsigned int kDDSD_LINEARSIZE = 0x080000;
187constexpr unsigned int kDDSD_DEPTH = 0x800000;
188
189constexpr unsigned int kDDSD_REQUIRED = kDDSD_CAPS | kDDSD_HEIGHT | kDDSD_WIDTH | kDDSD_PIXELFORMAT;
190
191typedef struct {
192 DWORD dwSize;
193 DWORD dwFlags;
194 DWORD dwHeight;
195 DWORD dwWidth;
196 DWORD dwPitchOrLinearSize;
197 DWORD dwDepth;
198 DWORD dwMipMapCount;
199 DWORD dwReserved1[11];
200 DDS_PIXELFORMAT ddspf;
201 DWORD dwCaps;
202 DWORD dwCaps2;
203 DWORD dwCaps3;
204 DWORD dwCaps4;
205 DWORD dwReserved2;
206} DDS_HEADER;
207
208// This DDS loader is barely sufficient to load the specific files this GM requires. Use
209// at your own peril.
210static sk_sp<SkData> load_dds(const char* filename, ImageInfo* imageInfo) {
211 SkFILEStream input(filename);
212 if (!input.isValid()) {
213 return nullptr;
214 }
215
216 constexpr uint32_t kMagic = 0x20534444;
217 uint32_t magic;
218
219 if (input.read(&magic, 4) != 4) {
220 return nullptr;
221 }
222
223 if (magic != kMagic) {
224 return nullptr;
225 }
226
227 constexpr size_t kDDSHeaderSize = sizeof(DDS_HEADER);
228 static_assert(kDDSHeaderSize == 124);
229 constexpr size_t kDDSPixelFormatSize = sizeof(DDS_PIXELFORMAT);
230 static_assert(kDDSPixelFormatSize == 32);
231
232 DDS_HEADER header;
233
234 if (input.read(&header, kDDSHeaderSize) != kDDSHeaderSize) {
235 return nullptr;
236 }
237
238 if (header.dwSize != kDDSHeaderSize ||
239 header.ddspf.dwSize != kDDSPixelFormatSize) {
240 return nullptr;
241 }
242
243 if ((header.dwFlags & kDDSD_REQUIRED) != kDDSD_REQUIRED) {
244 return nullptr;
245 }
246
247 if (header.dwFlags & (kDDSD_PITCH | kDDSD_LINEARSIZE | kDDSD_DEPTH)) {
248 // TODO: support these features
249 }
250
251 imageInfo->fDim.fWidth = header.dwWidth;
252 imageInfo->fDim.fHeight = header.dwHeight;
253
254 int numberOfMipmapLevels = 1;
255 if (header.dwFlags & kDDSD_MIPMAPCOUNT) {
256 if (header.dwMipMapCount == 1) {
257 imageInfo->fMipMapped = GrMipMapped::kNo;
258 } else {
259 int numRequiredLevels = SkMipMap::ComputeLevelCount(header.dwWidth, header.dwHeight)+1;
260 if (header.dwMipMapCount != (unsigned) numRequiredLevels) {
261 return nullptr;
262 }
263 imageInfo->fMipMapped = GrMipMapped::kYes;
264 numberOfMipmapLevels = numRequiredLevels;
265 }
266 } else {
267 imageInfo->fMipMapped = GrMipMapped::kNo;
268 }
269
270 if (!(header.ddspf.dwFlags & kDDPF_FOURCC)) {
271 return nullptr;
272 }
273
274 // We only handle these one format right now
275 switch (header.ddspf.dwFourCC) {
276 case 0x31545844: // DXT1
277 imageInfo->fCompressionType = SkImage::CompressionType::kBC1_RGB8_UNORM;
278 break;
279 default:
280 return nullptr;
281 }
282
283 SkTArray<size_t> individualMipOffsets(numberOfMipmapLevels);
284
285 size_t dataSize = SkCompressedDataSize(imageInfo->fCompressionType,
286 { (int) header.dwWidth, (int) header.dwHeight },
287 &individualMipOffsets,
288 imageInfo->fMipMapped == GrMipMapped::kYes);
289 SkASSERT(individualMipOffsets.size() == (size_t) numberOfMipmapLevels);
290
291 sk_sp<SkData> data = SkData::MakeUninitialized(dataSize);
292
293 uint8_t* dest = (uint8_t*) data->writable_data();
294
295 size_t amountRead = input.read(dest, dataSize);
296 if (amountRead != dataSize) {
297 return nullptr;
298 }
299
300 return data;
301}
302
303//-------------------------------------------------------------------------------------------------
304static sk_sp<SkImage> data_to_img(GrContext *context, sk_sp<SkData> data, const ImageInfo& info) {
305 if (context) {
306 return SkImage::MakeTextureFromCompressed(context, std::move(data),
307 info.fDim.fWidth,
308 info.fDim.fHeight,
309 info.fCompressionType,
310 info.fMipMapped);
311 } else {
312 return SkImage::MakeRasterFromCompressed(std::move(data),
313 info.fDim.fWidth,
314 info.fDim.fHeight,
315 info.fCompressionType);
316 }
317}
318
319namespace skiagm {
320
321// This GM exercises our handling of some of the more exotic formats using externally
322// generated content. Right now it only tests ETC1 and BC1.
323class ExoticFormatsGM : public GM {
324public:
325 ExoticFormatsGM() {
Robert Phillipseb77a762020-02-03 11:43:20 -0500326 this->setBGColor(SK_ColorBLACK);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500327 }
328
329protected:
330 SkString onShortName() override {
331 return SkString("exoticformats");
332 }
333
334 SkISize onISize() override {
Robert Phillipseb77a762020-02-03 11:43:20 -0500335 return SkISize::Make(2*kImgWidthHeight + 3 * kPad, kImgWidthHeight + 2 * kPad);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500336 }
337
338 void loadImages(GrContext *context) {
339
340 if (!fETC1Image) {
341 ImageInfo info;
342 sk_sp<SkData> data = load_ktx(GetResourcePath("images/flower-etc1.ktx").c_str(), &info);
343 if (data) {
344 SkASSERT(info.fDim.equals(kImgWidthHeight, kImgWidthHeight));
345 SkASSERT(info.fMipMapped == GrMipMapped::kNo);
346 SkASSERT(info.fCompressionType == SkImage::CompressionType::kETC2_RGB8_UNORM);
347
348 fETC1Image = data_to_img(context, std::move(data), info);
349 } else {
350 SkDebugf("failed to load flower-etc1.ktx\n");
351 }
352 }
353
354 if (!fBC1Image) {
355 ImageInfo info;
356 sk_sp<SkData> data = load_dds(GetResourcePath("images/flower-bc1.dds").c_str(), &info);
357 if (data) {
358 SkASSERT(info.fDim.equals(kImgWidthHeight, kImgWidthHeight));
359 SkASSERT(info.fMipMapped == GrMipMapped::kNo);
360 SkASSERT(info.fCompressionType == SkImage::CompressionType::kBC1_RGB8_UNORM);
361
362 fBC1Image = data_to_img(context, std::move(data), info);
363 } else {
364 SkDebugf("failed to load flower-bc1.dds\n");
365 }
366 }
367
368 }
369
Robert Phillips95c250c2020-06-29 15:36:12 -0400370 void drawImage(GrRecordingContext* context, SkCanvas* canvas, SkImage* image, int x, int y) {
Robert Phillipsd4f68312020-01-31 10:15:05 -0500371 if (!image) {
372 return;
373 }
374
375 bool isCompressed = false;
376 if (image->isTextureBacked()) {
377 const GrCaps* caps = context->priv().caps();
378
379 GrTextureProxy* proxy = as_IB(image)->peekProxy();
380 isCompressed = caps->isFormatCompressed(proxy->backendFormat());
381 }
382
383 canvas->drawImage(image, x, y);
384
385 if (!isCompressed) {
386 // Make it obvious which drawImages used decompressed images
387 SkRect r = SkRect::MakeXYWH(x, y, kImgWidthHeight, kImgWidthHeight);
388 SkPaint paint;
389 paint.setColor(SK_ColorRED);
390 paint.setStyle(SkPaint::kStroke_Style);
Robert Phillipseb77a762020-02-03 11:43:20 -0500391 paint.setStrokeWidth(2.0f);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500392 canvas->drawRect(r, paint);
393 }
394 }
395
396 void onDraw(SkCanvas* canvas) override {
397 GrContext* context = canvas->getGrContext();
398
399 this->loadImages(context);
400
Robert Phillipseb77a762020-02-03 11:43:20 -0500401 this->drawImage(context, canvas, fETC1Image.get(), kPad, kPad);
402 this->drawImage(context, canvas, fBC1Image.get(), kImgWidthHeight + 2 * kPad, kPad);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500403 }
404
405private:
406 static const int kImgWidthHeight = 128;
Robert Phillipseb77a762020-02-03 11:43:20 -0500407 static const int kPad = 4;
Robert Phillipsd4f68312020-01-31 10:15:05 -0500408
409 sk_sp<SkImage> fETC1Image;
410 sk_sp<SkImage> fBC1Image;
411
412 typedef GM INHERITED;
413};
414
415//////////////////////////////////////////////////////////////////////////////
416
417DEF_GM(return new ExoticFormatsGM;)
418}