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