blob: 3bd0fad0199013779ef47a59c64228f95117c47b [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"
Brian Salomone6662542021-02-23 10:45:39 -050021#include "tools/gpu/ProxyUtils.h"
Robert Phillipsd4f68312020-01-31 10:15:05 -050022
23#include "tools/Resources.h"
24
25//-------------------------------------------------------------------------------------------------
26struct ImageInfo {
27 SkISize fDim;
Brian Salomon40a40622020-07-21 10:32:07 -040028 GrMipmapped fMipmapped;
Robert Phillipsd4f68312020-01-31 10:15:05 -050029 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 */
36static 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.
44static 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
John Stilesc1c3c6d2020-08-15 23:22:53 -040062 if (0 != memcmp(header, kExpectedIdentifier, kKTXIdentifierSize)) {
Robert Phillipsd4f68312020-01-31 10:15:05 -050063 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) {
Brian Salomon40a40622020-07-21 10:32:07 -0400119 imageInfo->fMipmapped = GrMipmapped::kNo;
Robert Phillipsd4f68312020-01-31 10:15:05 -0500120 } else {
Mike Reed13711eb2020-07-14 17:16:32 -0400121 int numRequiredMipLevels = SkMipmap::ComputeLevelCount(pixelWidth, pixelHeight)+1;
Robert Phillipsd4f68312020-01-31 10:15:05 -0500122 if (numberOfMipmapLevels != numRequiredMipLevels) {
123 return nullptr;
124 }
Brian Salomon40a40622020-07-21 10:32:07 -0400125 imageInfo->fMipmapped = GrMipmapped::kYes;
Robert Phillipsd4f68312020-01-31 10:15:05 -0500126 }
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,
Brian Salomon40a40622020-07-21 10:32:07 -0400137 imageInfo->fMipmapped == GrMipmapped::kYes);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500138 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//-------------------------------------------------------------------------------------------------
166typedef uint32_t DWORD;
167
168// Values for the DDS_PIXELFORMAT 'dwFlags' field
169constexpr unsigned int kDDPF_FOURCC = 0x4;
170
171struct 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
183constexpr unsigned int kDDSD_CAPS = 0x1; // required
184constexpr unsigned int kDDSD_HEIGHT = 0x2; // required
185constexpr unsigned int kDDSD_WIDTH = 0x4; // required
186constexpr unsigned int kDDSD_PITCH = 0x8;
187constexpr unsigned int kDDSD_PIXELFORMAT = 0x001000; // required
188constexpr unsigned int kDDSD_MIPMAPCOUNT = 0x020000;
189constexpr unsigned int kDDSD_LINEARSIZE = 0x080000;
190constexpr unsigned int kDDSD_DEPTH = 0x800000;
191
192constexpr unsigned int kDDSD_REQUIRED = kDDSD_CAPS | kDDSD_HEIGHT | kDDSD_WIDTH | kDDSD_PIXELFORMAT;
193
194typedef 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.
213static 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) {
Brian Salomon40a40622020-07-21 10:32:07 -0400260 imageInfo->fMipmapped = GrMipmapped::kNo;
Robert Phillipsd4f68312020-01-31 10:15:05 -0500261 } else {
Mike Reed13711eb2020-07-14 17:16:32 -0400262 int numRequiredLevels = SkMipmap::ComputeLevelCount(header.dwWidth, header.dwHeight)+1;
Robert Phillipsd4f68312020-01-31 10:15:05 -0500263 if (header.dwMipMapCount != (unsigned) numRequiredLevels) {
264 return nullptr;
265 }
Brian Salomon40a40622020-07-21 10:32:07 -0400266 imageInfo->fMipmapped = GrMipmapped::kYes;
Robert Phillipsd4f68312020-01-31 10:15:05 -0500267 numberOfMipmapLevels = numRequiredLevels;
268 }
269 } else {
Brian Salomon40a40622020-07-21 10:32:07 -0400270 imageInfo->fMipmapped = GrMipmapped::kNo;
Robert Phillipsd4f68312020-01-31 10:15:05 -0500271 }
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,
Brian Salomon40a40622020-07-21 10:32:07 -0400291 imageInfo->fMipmapped == GrMipmapped::kYes);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500292 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//-------------------------------------------------------------------------------------------------
Adlai Holler4caa9352020-07-16 10:58:58 -0400307static 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),
Robert Phillipsd4f68312020-01-31 10:15:05 -0500311 info.fDim.fWidth,
312 info.fDim.fHeight,
313 info.fCompressionType,
Brian Salomon40a40622020-07-21 10:32:07 -0400314 info.fMipmapped);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500315 } else {
316 return SkImage::MakeRasterFromCompressed(std::move(data),
317 info.fDim.fWidth,
318 info.fDim.fHeight,
319 info.fCompressionType);
320 }
321}
322
323namespace 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.
327class ExoticFormatsGM : public GM {
328public:
329 ExoticFormatsGM() {
Robert Phillipseb77a762020-02-03 11:43:20 -0500330 this->setBGColor(SK_ColorBLACK);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500331 }
332
333protected:
334 SkString onShortName() override {
335 return SkString("exoticformats");
336 }
337
338 SkISize onISize() override {
Robert Phillipseb77a762020-02-03 11:43:20 -0500339 return SkISize::Make(2*kImgWidthHeight + 3 * kPad, kImgWidthHeight + 2 * kPad);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500340 }
341
Robert Phillipsa84caa32020-07-28 09:57:26 -0400342 bool loadImages(GrDirectContext *direct) {
343 SkASSERT(!fETC1Image && !fBC1Image);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500344
Robert Phillipsa84caa32020-07-28 09:57:26 -0400345 {
Robert Phillipsd4f68312020-01-31 10:15:05 -0500346 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));
Brian Salomon40a40622020-07-21 10:32:07 -0400350 SkASSERT(info.fMipmapped == GrMipmapped::kNo);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500351 SkASSERT(info.fCompressionType == SkImage::CompressionType::kETC2_RGB8_UNORM);
352
Adlai Holler4caa9352020-07-16 10:58:58 -0400353 fETC1Image = data_to_img(direct, std::move(data), info);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500354 } else {
355 SkDebugf("failed to load flower-etc1.ktx\n");
Robert Phillipsa84caa32020-07-28 09:57:26 -0400356 return false;
Robert Phillipsd4f68312020-01-31 10:15:05 -0500357 }
358 }
359
Robert Phillipsa84caa32020-07-28 09:57:26 -0400360 {
Robert Phillipsd4f68312020-01-31 10:15:05 -0500361 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));
Brian Salomon40a40622020-07-21 10:32:07 -0400365 SkASSERT(info.fMipmapped == GrMipmapped::kNo);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500366 SkASSERT(info.fCompressionType == SkImage::CompressionType::kBC1_RGB8_UNORM);
367
Adlai Holler4caa9352020-07-16 10:58:58 -0400368 fBC1Image = data_to_img(direct, std::move(data), info);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500369 } else {
370 SkDebugf("failed to load flower-bc1.dds\n");
Robert Phillipsa84caa32020-07-28 09:57:26 -0400371 return false;
Robert Phillipsd4f68312020-01-31 10:15:05 -0500372 }
373 }
374
Robert Phillipsa84caa32020-07-28 09:57:26 -0400375 return true;
Robert Phillipsd4f68312020-01-31 10:15:05 -0500376 }
377
Robert Phillipsa84caa32020-07-28 09:57:26 -0400378 void drawImage(SkCanvas* canvas, SkImage* image, int x, int y) {
Robert Phillipsd4f68312020-01-31 10:15:05 -0500379 if (!image) {
380 return;
381 }
382
383 bool isCompressed = false;
384 if (image->isTextureBacked()) {
Adlai Holler302e8fb2020-09-14 11:58:06 -0400385 const GrCaps* caps = as_IB(image)->context()->priv().caps();
Brian Salomone6662542021-02-23 10:45:39 -0500386 GrTextureProxy* proxy = sk_gpu_test::GetTextureImageProxy(image,
387 canvas->recordingContext());
Robert Phillipsd4f68312020-01-31 10:15:05 -0500388 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);
Robert Phillipseb77a762020-02-03 11:43:20 -0500399 paint.setStrokeWidth(2.0f);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500400 canvas->drawRect(r, paint);
401 }
402 }
403
Robert Phillipsa84caa32020-07-28 09:57:26 -0400404 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;
Adlai Holler4caa9352020-07-16 10:58:58 -0400409 }
Robert Phillipsd4f68312020-01-31 10:15:05 -0500410
Robert Phillipsa84caa32020-07-28 09:57:26 -0400411 if (!this->loadImages(dContext)) {
412 *errorMsg = "Failed to create images.";
413 return DrawResult::kFail;
414 }
Adlai Holler4caa9352020-07-16 10:58:58 -0400415
Robert Phillipsa84caa32020-07-28 09:57:26 -0400416 return DrawResult::kOk;
417 }
418
419 void onGpuTeardown() override {
420 fETC1Image = nullptr;
421 fBC1Image = nullptr;
422 }
423
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);
Robert Phillipsd4f68312020-01-31 10:15:05 -0500429 }
430
431private:
432 static const int kImgWidthHeight = 128;
Robert Phillipseb77a762020-02-03 11:43:20 -0500433 static const int kPad = 4;
Robert Phillipsd4f68312020-01-31 10:15:05 -0500434
435 sk_sp<SkImage> fETC1Image;
436 sk_sp<SkImage> fBC1Image;
437
John Stiles7571f9e2020-09-02 22:42:33 -0400438 using INHERITED = GM;
Robert Phillipsd4f68312020-01-31 10:15:05 -0500439};
440
441//////////////////////////////////////////////////////////////////////////////
442
443DEF_GM(return new ExoticFormatsGM;)
John Stilesa6841be2020-08-06 14:11:56 -0400444} // namespace skiagm