• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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