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