Dali 3D User Interface Engine
loader-ktx.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0
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 
18 // CLASS HEADER
19 #include "loader-ktx.h"
20 
21 // EXTERNAL INCLUDES
22 #include <cstdio>
23 #include <cstdlib>
24 #include <cstring>
25 #include <stdint.h>
30 
31 namespace Dali
32 {
33 using Integration::Bitmap;
35 
36 namespace TizenPlatform
37 {
38 
39 namespace
40 {
41 
43 const unsigned MAX_TEXTURE_DIMENSION = 4096;
45 const unsigned MAX_IMAGE_DATA_SIZE = MAX_TEXTURE_DIMENSION * MAX_TEXTURE_DIMENSION;
48 const unsigned MAX_BYTES_OF_KEYVALUE_DATA = 65536U;
49 
50 typedef uint8_t Byte;
51 
52 const Byte FileIdentifier[] = {
53  0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
54 };
55 
56 
62 {
64 
65  // GLES 2 Extension formats:
68 
69  // GLES 3 Standard compressed formats (values same as in gl3.h):
80 
81  // GLES 3.1 compressed formats:
110 
112 };
113 
114 const unsigned KtxInternalFormats[] =
115 {
116  // GLES 2 Extension formats:
119 
120  // GLES 3 Standard compressed formats:
131 
132  // GLES 3.1 Compressed formats:
161 
163 };
164 
166 {
167  Byte identifier[12];
168  uint32_t endianness;
169  uint32_t glType;
170  uint32_t glTypeSize;
171  uint32_t glFormat;
174  uint32_t pixelWidth;
175  uint32_t pixelHeight;
176  uint32_t pixelDepth;
178  uint32_t numberOfFaces;
181 } __attribute__ ( (__packed__));
182 // Packed attribute stops the structure from being aligned to compiler defaults
183 // so we can be sure of reading the whole thing from file in one call to fread.
191 template<typename T>
192 inline bool ReadHeader(FILE* fp, T& header)
193 {
194  unsigned int readLength = sizeof(T);
196  // Load the information directly into our structure
197  if (fread((void*)&header, 1, readLength, fp) != readLength)
198  {
199  return false;
200  }
201 
202  return true;
203 }
204 
207 template<int BYTES_IN_SIGNATURE>
208 bool CheckFileIdentifier(const Byte * const signature)
209 {
210  const unsigned signatureSize = BYTES_IN_SIGNATURE;
211  const unsigned identifierSize = sizeof(FileIdentifier);
212  DALI_COMPILE_TIME_ASSERT(signatureSize == identifierSize);
213  const bool signatureGood = 0 == memcmp( signature, FileIdentifier, std::min( signatureSize, identifierSize ) );
214  return signatureGood;
215 }
216 
220 bool ValidInternalFormat(const unsigned format)
221 {
222  unsigned candidateFormat = 0;
223  for(unsigned iFormat = 0; (candidateFormat = KtxInternalFormats[iFormat]) != KTX_SENTINEL; ++iFormat)
224  {
225  if(format == candidateFormat)
226  {
227  return true;
228  }
229  }
230  DALI_LOG_ERROR("Rejecting unsupported compressed format when loading compressed texture from KTX file: 0x%x.\n", format);
231  return false;
232 }
233 
238 bool ConvertPixelFormat(const uint32_t ktxPixelFormat, Dali::Pixel::Format& format)
239 {
240  using namespace Dali::Pixel;
241  switch(ktxPixelFormat)
242  {
243  // GLES 2 extension compressed formats:
244  case KTX_ETC1_RGB8_OES:
245  {
246  format = COMPRESSED_RGB8_ETC1;
247  break;
248  }
250  {
252  break;
253  }
254 
255  // GLES 3 extension compressed formats:
257  {
258  format = COMPRESSED_R11_EAC;
259  break;
260  }
262  {
263  format = COMPRESSED_SIGNED_R11_EAC;
264  break;
265  }
267  {
268  format = COMPRESSED_RG11_EAC;
269  break;
270  }
272  {
274  break;
275  }
277  {
278  format = COMPRESSED_RGB8_ETC2;
279  break;
280  }
282  {
283  format = COMPRESSED_SRGB8_ETC2;
284  break;
285  }
287  {
289  break;
290  }
292  {
294  break;
295  }
297  {
298  format = COMPRESSED_RGBA8_ETC2_EAC;
299  break;
300  }
302  {
304  break;
305  }
306 
307  // GLES 3.1 extension compressed formats:
309  {
311  break;
312  }
314  {
316  break;
317  }
319  {
321  break;
322  }
324  {
326  break;
327  }
329  {
331  break;
332  }
334  {
336  break;
337  }
339  {
341  break;
342  }
344  {
346  break;
347  }
349  {
351  break;
352  }
354  {
356  break;
357  }
359  {
361  break;
362  }
364  {
366  break;
367  }
369  {
371  break;
372  }
374  {
376  break;
377  }
379  {
381  break;
382  }
384  {
386  break;
387  }
389  {
391  break;
392  }
394  {
396  break;
397  }
399  {
401  break;
402  }
404  {
406  break;
407  }
409  {
411  break;
412  }
414  {
416  break;
417  }
419  {
421  break;
422  }
424  {
426  break;
427  }
429  {
431  break;
432  }
434  {
436  break;
437  }
439  {
441  break;
442  }
444  {
446  break;
447  }
448 
449  default:
450  {
451  return false;
452  }
453  }
454  return true;
455 }
456 
457 bool LoadKtxHeader(FILE * const fp, unsigned int &width, unsigned int &height, KtxFileHeader &fileHeader)
458 {
459  // Pull the bytes of the file header in as a block:
460  if ( !ReadHeader(fp, fileHeader) )
461  {
462  return false;
463  }
464  width = fileHeader.pixelWidth;
465  height = fileHeader.pixelHeight;
466 
467  if ( width > MAX_TEXTURE_DIMENSION || height > MAX_TEXTURE_DIMENSION )
468  {
469  return false;
470  }
471 
472  // Validate file header contents meet our minimal subset:
473  const bool signatureGood = CheckFileIdentifier<sizeof(fileHeader.identifier)>(fileHeader.identifier);
474  const bool fileEndiannessMatchesSystemEndianness = fileHeader.endianness == 0x04030201; // Magic number from KTX spec.
475  const bool glTypeIsCompressed = fileHeader.glType == 0;
476  const bool glTypeSizeCompatibleWithCompressedTex = fileHeader.glTypeSize == 1;
477  const bool glFormatCompatibleWithCompressedTex = fileHeader.glFormat == 0;
478  const bool glInternalFormatIsSupportedCompressedTex = ValidInternalFormat(fileHeader.glInternalFormat);
479  // Ignore glBaseInternalFormat
480  const bool textureIsNot3D = fileHeader.pixelDepth == 0 || fileHeader.pixelDepth == 1;
481  const bool textureIsNotAnArray = fileHeader.numberOfArrayElements == 0 || fileHeader.numberOfArrayElements == 1;
482  const bool textureIsNotACubemap = fileHeader.numberOfFaces == 0 || fileHeader.numberOfFaces == 1;
483  const bool textureHasNoMipmapLevels = fileHeader.numberOfMipmapLevels == 0 || fileHeader.numberOfMipmapLevels == 1;
484  const bool keyValueDataNotTooLarge = fileHeader.bytesOfKeyValueData <= MAX_BYTES_OF_KEYVALUE_DATA;
485 
486  const bool headerIsValid = signatureGood && fileEndiannessMatchesSystemEndianness && glTypeIsCompressed &&
487  glTypeSizeCompatibleWithCompressedTex && glFormatCompatibleWithCompressedTex &&
488  textureIsNot3D && textureIsNotAnArray && textureIsNotACubemap && textureHasNoMipmapLevels &&
489  glInternalFormatIsSupportedCompressedTex & keyValueDataNotTooLarge;
490  if( !headerIsValid )
491  {
492  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);
493  }
494 
495  // Warn if there is space wasted in the file:
496  if( fileHeader.bytesOfKeyValueData > 0U )
497  {
498  DALI_LOG_WARNING("Loading of KTX file with key/value header data requested. This should be stripped in application asset/resource build.\n");
499  }
500 
501  return headerIsValid;
502 }
503 
504 
505 } // unnamed namespace
506 
507 // File loading API entry-point:
508 bool LoadKtxHeader( const ImageLoader::Input& input, unsigned int& width, unsigned int& height )
509 {
510  KtxFileHeader fileHeader;
511  FILE* const fp = input.file;
512 
513  bool ret = LoadKtxHeader(fp, width, height, fileHeader);
514  return ret;
515 }
516 
517 // File loading API entry-point:
519 {
520  DALI_COMPILE_TIME_ASSERT( sizeof(Byte) == 1);
521  DALI_COMPILE_TIME_ASSERT( sizeof(uint32_t) == 4);
522 
523  FILE* const fp = input.file;
524  if( fp == NULL )
525  {
526  DALI_LOG_ERROR( "Null file handle passed to KTX compressed bitmap file loader.\n" );
527  return false;
528  }
529  KtxFileHeader fileHeader;
530 
531  // Load the header info
532  unsigned int width, height;
533 
534  if (!LoadKtxHeader(fp, width, height, fileHeader))
535  {
536  return false;
537  }
538 
539  // Skip the key-values:
540  const long int imageSizeOffset = sizeof(KtxFileHeader) + fileHeader.bytesOfKeyValueData;
541  if(fseek(fp, imageSizeOffset, SEEK_SET))
542  {
543  DALI_LOG_ERROR( "Seek past key/vals in KTX compressed bitmap file failed.\n" );
544  return false;
545  }
546 
547  // Load the size of the image data:
548  uint32_t imageByteCount = 0;
549  if (fread((void*)&imageByteCount, 1, 4, fp) != 4)
550  {
551  DALI_LOG_ERROR( "Read of image size failed.\n" );
552  return false;
553  }
554  // Sanity-check the image size:
555  if( imageByteCount > MAX_IMAGE_DATA_SIZE ||
556  // A compressed texture should certainly be less than 2 bytes per texel:
557  imageByteCount > width * height * 2)
558  {
559  DALI_LOG_ERROR( "KTX file with too-large image-data field.\n" );
560  return false;
561  }
562 
563  Pixel::Format pixelFormat;
564  const bool pixelFormatKnown = ConvertPixelFormat(fileHeader.glInternalFormat, pixelFormat);
565  if(!pixelFormatKnown)
566  {
567  DALI_LOG_ERROR( "No internal pixel format supported for KTX file pixel format.\n" );
568  return false;
569  }
570 
571  // Load up the image bytes:
572  PixelBuffer * const pixels = bitmap.GetCompressedProfile()->ReserveBufferOfSize( pixelFormat, width, height, (size_t) imageByteCount );
573  if(!pixels)
574  {
575  DALI_LOG_ERROR( "Unable to reserve a pixel buffer to load the requested bitmap into.\n" );
576  return false;
577  }
578  const size_t bytesRead = fread(pixels, 1, imageByteCount, fp);
579  if(bytesRead != imageByteCount)
580  {
581  DALI_LOG_ERROR( "Read of image pixel data failed.\n" );
582  return false;
583  }
584 
585  return true;
586 }
587 
588 } // namespace TizenPlatform
589 
590 } // namespace Dali
Dali Docs Home
Read more about Dali