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