• 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/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