Removed legacy resource tracking / logging
[platform/core/uifw/dali-adaptor.git] / platform-abstractions / slp / resource-loader / loader-ktx.cpp
1 //
2 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
3 //
4 // Licensed under the Flora License, Version 1.0 (the License);
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //     http://floralicense.org/license/
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an AS IS BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16
17 #include "loader-ktx.h"
18
19 #include <cstdio>
20 #include <cstdlib>
21 #include <cstring>
22 #include <stdint.h>
23 #include <dali/public-api/math/compile-time-assert.h>
24 #include <dali/public-api/dali-core.h>
25 #include <dali/integration-api/debug.h>
26 #include <dali/integration-api/bitmap.h>
27 #include <dali/public-api/images/pixel.h>
28 #include <dali/public-api/images/image-attributes.h>
29
30 namespace Dali
31 {
32 using Integration::Bitmap;
33 using Dali::Integration::PixelBuffer;
34
35 namespace SlpPlatform
36 {
37
38 namespace
39 {
40
41 /** Max width or height of an image. */
42 const unsigned MAX_TEXTURE_DIMENSION = 4096;
43 /** Max bytes of image data allowed. Not a precise number, just a sanity check. */
44 const unsigned MAX_IMAGE_DATA_SIZE = MAX_TEXTURE_DIMENSION * MAX_TEXTURE_DIMENSION;
45 /** We don't read any of this but limit it to a resonable amount in order to be
46  * friendly to files from random tools. */
47 const unsigned MAX_BYTES_OF_KEYVALUE_DATA = 65536U;
48
49 typedef uint8_t Byte;
50
51 const Byte FileIdentifier[] = {
52    0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
53 };
54
55 /** The formats we support inside a KTX file container.
56  *  Currently only compressed formats are allowed as we'd rather
57  *  use a PNG or JPEG with their own compression for the general
58  *  cases. */
59 enum KtxInternalFormat
60 {
61   KTX_NOTEXIST = 0,
62   // GLES 3 Standard compressed formats (values same as in gl3.h):
63   KTX_COMPRESSED_R11_EAC                          = 0x9270,
64   KTX_COMPRESSED_SIGNED_R11_EAC                   = 0x9271,
65   KTX_COMPRESSED_RG11_EAC                         = 0x9272,
66   KTX_COMPRESSED_SIGNED_RG11_EAC                  = 0x9273,
67   KTX_COMPRESSED_RGB8_ETC2                        = 0x9274,
68   KTX_COMPRESSED_SRGB8_ETC2                       = 0x9275,
69   KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2    = 0x9276,
70   KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2   = 0x9277,
71   KTX_COMPRESSED_RGBA8_ETC2_EAC                   = 0x9278,
72   KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC            = 0x9279,
73
74   // GLES 2 EXTENSION FORMATS:
75   KTX_ETC1_RGB8_OES                               = 0x8D64,
76   KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG             = 0x8C00,
77   KTX_SENTINEL = ~0u,
78 };
79
80 const unsigned KtxInternalFormats[] =
81 {
82   // GLES 3 Standard compressed formats:
83   KTX_COMPRESSED_R11_EAC,
84   KTX_COMPRESSED_SIGNED_R11_EAC,
85   KTX_COMPRESSED_RG11_EAC,
86   KTX_COMPRESSED_SIGNED_RG11_EAC,
87   KTX_COMPRESSED_RGB8_ETC2,
88   KTX_COMPRESSED_SRGB8_ETC2,
89   KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2,
90   KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2,
91   KTX_COMPRESSED_RGBA8_ETC2_EAC,
92   KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC,
93   // GLES 2 EXTENSION FORMATS:
94   KTX_ETC1_RGB8_OES,
95   KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG,
96   KTX_SENTINEL
97 };
98
99 struct KtxFileHeader
100 {
101   Byte   identifier[12];
102   uint32_t endianness;
103   uint32_t glType;
104   uint32_t glTypeSize;
105   uint32_t glFormat;
106   uint32_t glInternalFormat;
107   uint32_t glBaseInternalFormat;
108   uint32_t pixelWidth;
109   uint32_t pixelHeight;
110   uint32_t pixelDepth;
111   uint32_t numberOfArrayElements;
112   uint32_t numberOfFaces;
113   uint32_t numberOfMipmapLevels;
114   uint32_t bytesOfKeyValueData;
115 } __attribute__ ( (__packed__));
116 // Packed attribute stops the structure from being aligned to compiler defaults
117 // so we can be sure of reading the whole thing from file in one call to fread.
118
119 /**
120  * Template function to read from the file directly into our structure.
121  * @param[in]  fp     The file to read from
122  * @param[out] header The structure we want to store our information in
123  * @return true, if read successful, false otherwise
124  */
125 template<typename T>
126 inline bool ReadHeader(FILE* fp, T& header)
127 {
128   unsigned int readLength = sizeof(T);
129
130   // Load the information directly into our structure
131   if (fread((void*)&header, 1, readLength, fp) != readLength)
132   {
133     return false;
134   }
135
136   return true;
137 }
138
139 /** Check whether the array passed in is the right size and matches the magic
140  *  values defined to be at the start of a KTX file by the specification.*/
141 template<int BYTES_IN_SIGNATURE>
142 bool CheckFileIdentifier(const Byte * const signature)
143 {
144   const unsigned signatureSize = BYTES_IN_SIGNATURE;
145   const unsigned identifierSize = sizeof(FileIdentifier);
146   DALI_COMPILE_TIME_ASSERT(signatureSize == identifierSize);
147   const bool signatureGood = 0 == memcmp( signature, FileIdentifier, std::min( signatureSize, identifierSize ) );
148   return signatureGood;
149 }
150
151 /**
152  * @returns True if the argument is a GLES compressed texture format that we support.
153  */
154 bool ValidInternalFormat(const unsigned format)
155 {
156   unsigned candidateFormat = 0;
157   for(unsigned iFormat = 0; (candidateFormat = KtxInternalFormats[iFormat]) != KTX_SENTINEL; ++iFormat)
158   {
159     if(format == candidateFormat)
160     {
161       return true;
162     }
163   }
164   DALI_LOG_ERROR("Rejecting unsupported compressed format when loading compressed texture from KTX file: 0x%x.\n", format);
165   return false;
166 }
167
168 /**
169  * @returns The Pixel::Format Dali enum corresponding to the KTX internal format
170  *          passed in, or Pixel::INVALID_PIXEL_FORMAT if the format is not valid.
171  **/
172 bool ConvertPixelFormat(const uint32_t ktxPixelFormat, Dali::Pixel::Format& format)
173 {
174   using namespace Dali::Pixel;
175   switch(ktxPixelFormat)
176   {
177     case KTX_COMPRESSED_R11_EAC:
178     {
179       format = Dali::Pixel::COMPRESSED_R11_EAC;
180       break;
181     }
182     case KTX_COMPRESSED_SIGNED_R11_EAC:
183     {
184       format = COMPRESSED_SIGNED_R11_EAC;
185       break;
186     }
187     case KTX_COMPRESSED_RG11_EAC:
188     {
189       format = COMPRESSED_RG11_EAC;
190       break;
191     }
192     case KTX_COMPRESSED_SIGNED_RG11_EAC:
193     {
194       format = COMPRESSED_SIGNED_RG11_EAC;
195       break;
196     }
197     case KTX_COMPRESSED_RGB8_ETC2:
198     {
199       format = COMPRESSED_RGB8_ETC2;
200       break;
201     }
202     case KTX_COMPRESSED_SRGB8_ETC2:
203     {
204       format = COMPRESSED_SRGB8_ETC2;
205       break;
206     }
207     case KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
208     {
209       format = COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
210       break;
211     }
212     case KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
213     {
214       format = COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2;
215       break;
216     }
217     case KTX_COMPRESSED_RGBA8_ETC2_EAC:
218     {
219       format = COMPRESSED_RGBA8_ETC2_EAC;
220       break;
221     }
222     case KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
223     {
224       format = COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
225       break;
226     }
227     // GLES 2 extension compressed formats:
228     case KTX_ETC1_RGB8_OES:
229     {
230       format = COMPRESSED_RGB8_ETC1;
231       break;
232     }
233     case KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
234     {
235       format = COMPRESSED_RGB_PVRTC_4BPPV1;
236       break;
237     }
238
239     default:
240     {
241        return false;
242     }
243   }
244   return true;
245 }
246
247 bool LoadKtxHeader(FILE * const fp, unsigned int &width, unsigned int &height, KtxFileHeader &fileHeader)
248 {
249   // Pull the bytes of the file header in as a block:
250   if ( !ReadHeader(fp, fileHeader) )
251   {
252     return false;
253   }
254   width = fileHeader.pixelWidth;
255   height = fileHeader.pixelHeight;
256
257   if ( width > MAX_TEXTURE_DIMENSION || height > MAX_TEXTURE_DIMENSION )
258   {
259     return false;
260   }
261
262   // Validate file header contents meet our minimal subset:
263   const bool signatureGood                            = CheckFileIdentifier<sizeof(fileHeader.identifier)>(fileHeader.identifier);
264   const bool fileEndiannessMatchesSystemEndianness    = fileHeader.endianness == 0x04030201; // Magic number from KTX spec.
265   const bool glTypeIsCompressed                       = fileHeader.glType == 0;
266   const bool glTypeSizeCompatibleWithCompressedTex    = fileHeader.glTypeSize == 1;
267   const bool glFormatCompatibleWithCompressedTex      = fileHeader.glFormat == 0;
268   const bool glInternalFormatIsSupportedCompressedTex = ValidInternalFormat(fileHeader.glInternalFormat);
269   // Ignore glBaseInternalFormat
270   const bool textureIsNot3D                           = fileHeader.pixelDepth == 0 || fileHeader.pixelDepth == 1;
271   const bool textureIsNotAnArray                      = fileHeader.numberOfArrayElements == 0 || fileHeader.numberOfArrayElements == 1;
272   const bool textureIsNotACubemap                     = fileHeader.numberOfFaces == 0 || fileHeader.numberOfFaces == 1;
273   const bool textureHasNoMipmapLevels                 = fileHeader.numberOfMipmapLevels == 0 || fileHeader.numberOfMipmapLevels == 1;
274   const bool keyValueDataNotTooLarge                  = fileHeader.bytesOfKeyValueData <= MAX_BYTES_OF_KEYVALUE_DATA;
275
276   const bool headerIsValid = signatureGood && fileEndiannessMatchesSystemEndianness && glTypeIsCompressed &&
277                            glTypeSizeCompatibleWithCompressedTex && glFormatCompatibleWithCompressedTex &&
278                            textureIsNot3D && textureIsNotAnArray && textureIsNotACubemap && textureHasNoMipmapLevels &&
279                            glInternalFormatIsSupportedCompressedTex & keyValueDataNotTooLarge;
280   if( !headerIsValid )
281   {
282      DALI_LOG_ERROR( "KTX file invalid or using unsupported features. Header tests: sig: %d, endian: %d, gl_type: %d, gl_type_size: %d, gl_format: %d, internal_format: %d, depth: %d, array: %d, faces: %d, mipmap: %d, vey-vals: %d.\n", 0+signatureGood, 0+fileEndiannessMatchesSystemEndianness, 0+glTypeIsCompressed, 0+glTypeSizeCompatibleWithCompressedTex, 0+glFormatCompatibleWithCompressedTex, 0+glInternalFormatIsSupportedCompressedTex, 0+textureIsNot3D, 0+textureIsNotAnArray, 0+textureIsNotACubemap, 0+textureHasNoMipmapLevels, 0+keyValueDataNotTooLarge);
283   }
284
285   // Warn if there is space wasted in the file:
286   if( fileHeader.bytesOfKeyValueData > 0U )
287   {
288     DALI_LOG_WARNING("Loading of KTX file with key/value header data requested. This should be stripped in application asset/resource build.\n");
289   }
290
291   return headerIsValid;
292 }
293
294
295 } // unnamed namespace
296
297 // File loading API entry-point:
298 bool LoadKtxHeader(FILE * const fp, const ImageAttributes& attributes, unsigned int &width, unsigned int &height)
299 {
300   KtxFileHeader fileHeader;
301   bool ret = LoadKtxHeader(fp, width, height, fileHeader);
302   return ret;
303 }
304
305 // File loading API entry-point:
306 bool LoadBitmapFromKtx(FILE * const fp, Bitmap& bitmap, ImageAttributes& attributes)
307 {
308   DALI_COMPILE_TIME_ASSERT( sizeof(Byte) == 1);
309   DALI_COMPILE_TIME_ASSERT( sizeof(uint32_t) == 4);
310   if( fp == NULL )
311   {
312     DALI_LOG_ERROR( "Null file handle passed to KTX compressed bitmap file loader.\n" );
313     return false;
314   }
315   KtxFileHeader fileHeader;
316
317   // Load the header info
318   unsigned int width, height;
319
320   if (!LoadKtxHeader(fp, width, height, fileHeader))
321   {
322       return false;
323   }
324
325   // Skip the key-values:
326   const long int imageSizeOffset = sizeof(KtxFileHeader) + fileHeader.bytesOfKeyValueData;
327   if(fseek(fp, imageSizeOffset, SEEK_SET))
328   {
329     DALI_LOG_ERROR( "Seek past key/vals in KTX compressed bitmap file failed.\n" );
330     return false;
331   }
332
333   // Load the size of the image data:
334   uint32_t imageByteCount = 0;
335   if (fread((void*)&imageByteCount, 1, 4, fp) != 4)
336   {
337     DALI_LOG_ERROR( "Read of image size failed.\n" );
338     return false;
339   }
340   // Sanity-check the image size:
341   if( imageByteCount > MAX_IMAGE_DATA_SIZE ||
342       // A compressed texture should certainly be less than 2 bytes per texel:
343       imageByteCount > width * height * 2)
344   {
345     DALI_LOG_ERROR( "KTX file with too-large image-data field.\n" );
346     return false;
347   }
348
349   Pixel::Format pixelFormat;
350   const bool pixelFormatKnown = ConvertPixelFormat(fileHeader.glInternalFormat, pixelFormat);
351   if(!pixelFormatKnown)
352   {
353     DALI_LOG_ERROR( "No internal pixel format supported for KTX file pixel format.\n" );
354     return false;
355   }
356
357   // Load up the image bytes:
358   PixelBuffer * const pixels = bitmap.GetCompressedProfile()->ReserveBufferOfSize( pixelFormat, width, height, (size_t) imageByteCount );
359   if(!pixels)
360   {
361     DALI_LOG_ERROR( "Unable to reserve a pixel buffer to load the requested bitmap into.\n" );
362     return false;
363   }
364   const size_t bytesRead = fread(pixels, 1, imageByteCount, fp);
365   if(bytesRead != imageByteCount)
366   {
367     DALI_LOG_ERROR( "Read of image pixel data failed.\n" );
368     return false;
369   }
370
371   // Ignore all input requests from image attributes and set the available metadata:
372   attributes.SetSize(width, height);
373   attributes.SetPixelFormat(pixelFormat);
374
375   return true;
376 }
377
378 } // namespace SlpPlatform
379
380 } // namespace Dali