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