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