CTexture.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. #include "common.h"
  2. #include "CTexture.h"
  3. #include <string>
  4. #include <cstring>
  5. #include <lz4.h>
  6. using namespace WallpaperEngine::Assets;
  7. CTexture::CTexture (const void* fileData) :
  8. m_resolution ()
  9. {
  10. // ensure the header is parsed
  11. this->m_header = parseHeader (static_cast <const char*> (fileData));
  12. GLint internalFormat;
  13. if (this->isAnimated ())
  14. {
  15. this->m_resolution = {
  16. this->m_header->textureWidth, this->m_header->textureHeight,
  17. this->m_header->gifWidth, this->m_header->gifHeight
  18. };
  19. }
  20. else
  21. {
  22. if (this->m_header->freeImageFormat != FREE_IMAGE_FORMAT::FIF_UNKNOWN)
  23. {
  24. // wpengine-texture format always has one mipmap
  25. // get first image size
  26. auto element = this->m_header->images.find (0)->second.begin ();
  27. // set the texture resolution
  28. this->m_resolution = {
  29. (*element)->width, (*element)->height,
  30. this->m_header->width, this->m_header->height
  31. };
  32. }
  33. else
  34. {
  35. // set the texture resolution
  36. this->m_resolution = {
  37. this->m_header->textureWidth, this->m_header->textureHeight,
  38. this->m_header->width, this->m_header->height
  39. };
  40. }
  41. }
  42. if (this->m_header->freeImageFormat != FREE_IMAGE_FORMAT::FIF_UNKNOWN)
  43. {
  44. internalFormat = GL_RGBA8;
  45. // set some extra information too as it's used for image sizing
  46. // this ensures that a_TexCoord uses the full image instead of just part of it
  47. // TODO: MAYBE IT'S BETTER TO CREATE A TEXTURE OF THE GIVEN SIZE AND COPY OVER WHAT WE READ FROM THE FILE?
  48. /*this->m_header->width = this->m_header->mipmaps [0]->width;
  49. this->m_header->height = this->m_header->mipmaps [0]->height;
  50. this->m_header->textureWidth = this->m_header->mipmaps [0]->width;
  51. this->m_header->textureHeight = this->m_header->mipmaps [0]->height;*/
  52. }
  53. else
  54. {
  55. // detect the image format and hand it to openGL to be used
  56. switch (this->m_header->format)
  57. {
  58. case TextureFormat::DXT5:
  59. internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
  60. break;
  61. case TextureFormat::DXT3:
  62. internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
  63. break;
  64. case TextureFormat::DXT1:
  65. internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
  66. break;
  67. case TextureFormat::ARGB8888:
  68. internalFormat = GL_RGBA8;
  69. break;
  70. case TextureFormat::R8:
  71. internalFormat = GL_R8;
  72. break;
  73. case TextureFormat::RG88:
  74. internalFormat = GL_RG8;
  75. break;
  76. default:
  77. delete this->m_header;
  78. sLog.exception ("Cannot determine texture format");
  79. }
  80. }
  81. // allocate texture ids list
  82. this->m_textureID = new GLuint [this->m_header->imageCount];
  83. // ask opengl for the correct amount of textures
  84. glGenTextures (this->m_header->imageCount, this->m_textureID);
  85. auto imgCur = this->m_header->images.begin ();
  86. auto imgEnd = this->m_header->images.end ();
  87. for (int index = 0; imgCur != imgEnd; imgCur ++, index ++)
  88. {
  89. // bind the texture to assign information to it
  90. glBindTexture (GL_TEXTURE_2D, this->m_textureID [index]);
  91. // set mipmap levels
  92. glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
  93. glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, this->m_header->mipmapCount - 1);
  94. // setup texture wrapping and filtering
  95. if (this->m_header->flags & TextureFlags::ClampUVs)
  96. {
  97. glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  98. glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  99. }
  100. else
  101. {
  102. glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  103. glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  104. }
  105. if (this->m_header->flags & TextureFlags::NoInterpolation)
  106. {
  107. glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  108. glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
  109. }
  110. else
  111. {
  112. glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  113. glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
  114. }
  115. glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, 8.0f);
  116. auto cur = (*imgCur).second.begin ();
  117. auto end = (*imgCur).second.end ();
  118. for (int32_t level = 0; cur != end; cur ++, level ++)
  119. {
  120. FIBITMAP* bitmap = nullptr;
  121. FIBITMAP* converted = nullptr;
  122. FIMEMORY* memory = nullptr;
  123. void* dataptr = (*cur)->uncompressedData;
  124. uint32_t width = (*cur)->width;
  125. uint32_t height = (*cur)->height;
  126. uint32_t bufferSize = (*cur)->uncompressedSize;
  127. GLenum textureFormat = GL_RGBA;
  128. if (this->m_header->freeImageFormat != FREE_IMAGE_FORMAT::FIF_UNKNOWN)
  129. {
  130. memory = FreeImage_OpenMemory (reinterpret_cast <BYTE *> ((*cur)->uncompressedData), (*cur)->uncompressedSize);
  131. // load the image and setup pointers so they can be used
  132. bitmap = FreeImage_LoadFromMemory (this->m_header->freeImageFormat, memory);
  133. // flip the image vertically
  134. FreeImage_FlipVertical (bitmap);
  135. // convert to a 32bits bytearray
  136. converted = FreeImage_ConvertTo32Bits (bitmap);
  137. dataptr = FreeImage_GetBits (converted);
  138. width = FreeImage_GetWidth (converted);
  139. height = FreeImage_GetHeight (converted);
  140. bufferSize = FreeImage_GetMemorySize (converted);
  141. textureFormat = GL_BGRA;
  142. }
  143. else
  144. {
  145. if (this->m_header->format == TextureFormat::R8)
  146. {
  147. // red textures are 1-byte-per-pixel, so it's alignment has to be set manually
  148. glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
  149. textureFormat = GL_RED;
  150. }
  151. else if (this->m_header->format == TextureFormat::RG88)
  152. textureFormat = GL_RG;
  153. }
  154. switch (internalFormat)
  155. {
  156. case GL_RGBA8:
  157. case GL_RG8:
  158. case GL_R8:
  159. glTexImage2D (
  160. GL_TEXTURE_2D, level, internalFormat,
  161. width, height, 0,
  162. textureFormat, GL_UNSIGNED_BYTE,
  163. dataptr
  164. );
  165. break;
  166. case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
  167. case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
  168. case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
  169. glCompressedTexImage2D (
  170. GL_TEXTURE_2D, level, internalFormat,
  171. width, height, 0,
  172. bufferSize, dataptr
  173. );
  174. break;
  175. default:
  176. sLog.exception ("Cannot load texture, unknown format", this->m_header->format);
  177. }
  178. // freeimage buffer won't be used anymore, so free memory
  179. if (this->m_header->freeImageFormat != FREE_IMAGE_FORMAT::FIF_UNKNOWN)
  180. {
  181. FreeImage_Unload (bitmap);
  182. FreeImage_Unload (converted);
  183. FreeImage_CloseMemory (memory);
  184. }
  185. }
  186. }
  187. }
  188. CTexture::~CTexture ()
  189. {
  190. if (this->getHeader () == nullptr)
  191. return;
  192. // free the header if it was allocated
  193. delete this->getHeader ();
  194. }
  195. const GLuint CTexture::getTextureID (uint32_t imageIndex) const
  196. {
  197. // ensure we do not go out of bounds
  198. if (imageIndex > this->m_header->imageCount)
  199. return this->m_textureID [0];
  200. return this->m_textureID [imageIndex];
  201. }
  202. const uint32_t CTexture::getTextureWidth (uint32_t imageIndex) const
  203. {
  204. if (imageIndex > this->m_header->imageCount)
  205. return this->getHeader ()->textureWidth;
  206. return (*this->m_header->images [imageIndex].begin ())->width;
  207. }
  208. const uint32_t CTexture::getTextureHeight (uint32_t imageIndex) const
  209. {
  210. if (imageIndex > this->m_header->imageCount)
  211. return this->getHeader ()->textureHeight;
  212. return (*this->m_header->images [imageIndex].begin ())->height;
  213. }
  214. const uint32_t CTexture::getRealWidth () const
  215. {
  216. return this->isAnimated () ? this->getHeader ()->gifWidth : this->getHeader ()->width;
  217. }
  218. const uint32_t CTexture::getRealHeight () const
  219. {
  220. return this->isAnimated () ? this->getHeader ()->gifHeight : this->getHeader ()->height;
  221. }
  222. const ITexture::TextureFormat CTexture::getFormat () const
  223. {
  224. return this->getHeader ()->format;
  225. }
  226. const ITexture::TextureFlags CTexture::getFlags () const
  227. {
  228. return this->getHeader ()->flags;
  229. }
  230. const CTexture::TextureHeader* CTexture::getHeader () const
  231. {
  232. return this->m_header;
  233. }
  234. const glm::vec4* CTexture::getResolution () const
  235. {
  236. return &this->m_resolution;
  237. }
  238. const std::vector<ITexture::TextureFrame*>& CTexture::getFrames () const
  239. {
  240. return this->getHeader ()->frames;
  241. }
  242. const bool CTexture::isAnimated () const
  243. {
  244. return this->getHeader ()->isAnimated ();
  245. }
  246. CTexture::TextureMipmap::TextureMipmap ()
  247. = default;
  248. CTexture::TextureMipmap::~TextureMipmap ()
  249. {
  250. if (this->compression == 1)
  251. delete this->compressedData;
  252. delete this->uncompressedData;
  253. }
  254. void CTexture::TextureMipmap::decompressData ()
  255. {
  256. if (this->compression == 1)
  257. {
  258. this->uncompressedData = new char [this->uncompressedSize];
  259. int result = LZ4_decompress_safe (
  260. this->compressedData, this->uncompressedData,
  261. this->compressedSize, this->uncompressedSize
  262. );
  263. if (!result)
  264. sLog.exception ("Cannot decompress texture data, LZ4_decompress_safe returned an error");
  265. }
  266. }
  267. CTexture::TextureFrame::TextureFrame () :
  268. frameNumber (0),
  269. frametime (0.0f),
  270. x (0),
  271. y (0),
  272. width1 (0),
  273. width2 (0),
  274. height1 (0),
  275. height2 (0)
  276. {
  277. }
  278. CTexture::TextureFrame::~TextureFrame ()
  279. = default;
  280. CTexture::TextureHeader::TextureHeader () :
  281. flags (NoFlags),
  282. width (0),
  283. height (0),
  284. textureWidth (0),
  285. textureHeight (0),
  286. gifWidth (0),
  287. gifHeight (0),
  288. format (TextureFormat::UNKNOWN),
  289. imageCount (0),
  290. mipmapCount (0)
  291. {
  292. }
  293. CTexture::TextureHeader::~TextureHeader ()
  294. {
  295. for (const auto& imgCur : this->images)
  296. for (auto cur : imgCur.second)
  297. delete cur;
  298. }
  299. CTexture::TextureHeader* CTexture::parseHeader (const char* fileData)
  300. {
  301. // check the magic value on the header first
  302. if (memcmp (fileData, "TEXV0005", 9) != 0)
  303. sLog.exception ("unexpected texture container type: ", std::string_view (fileData, 9));
  304. // jump to the next value
  305. fileData += 9;
  306. // check the sub-magic value on the header
  307. if (memcmp (fileData, "TEXI0001", 9) != 0)
  308. sLog.exception ("unexpected texture sub-container type: ", std::string_view (fileData, 9));
  309. // jump through the string again
  310. fileData += 9;
  311. auto* header = new TextureHeader;
  312. const auto* pointer = reinterpret_cast <const uint32_t*> (fileData);
  313. header->format = static_cast <TextureFormat>(*pointer ++);
  314. header->flags = static_cast <TextureFlags> (*pointer ++);
  315. header->textureWidth = *pointer ++;
  316. header->textureHeight = *pointer ++;
  317. header->width = *pointer ++;
  318. header->height = *pointer ++;
  319. pointer ++; // ignore some more bytes
  320. // now we're going to parse some more data that is string
  321. // so get the current position back as string
  322. fileData = reinterpret_cast <const char*> (pointer);
  323. // get the position of what comes after the texture data
  324. pointer = reinterpret_cast <const uint32_t*> (fileData + 9);
  325. if (memcmp (fileData, "TEXB0003", 9) == 0)
  326. {
  327. header->containerVersion = ContainerVersion::TEXB0003;
  328. header->imageCount = *pointer ++;
  329. header->freeImageFormat = static_cast <FREE_IMAGE_FORMAT> (*pointer++);
  330. }
  331. else if(memcmp (fileData, "TEXB0002", 9) == 0)
  332. {
  333. header->containerVersion = ContainerVersion::TEXB0002;
  334. header->imageCount = *pointer ++;
  335. }
  336. else if (memcmp (fileData, "TEXB0001", 9) == 0)
  337. {
  338. header->containerVersion = ContainerVersion::TEXB0001;
  339. header->imageCount = *pointer ++;
  340. }
  341. else
  342. {
  343. delete header;
  344. sLog.exception ("unknown texture format type: ", std::string_view (fileData, 9));
  345. }
  346. for (uint32_t image = 0; image < header->imageCount; image ++)
  347. {
  348. // read the number of mipmaps available for this image
  349. header->mipmapCount = *pointer ++;
  350. std::vector <TextureMipmap*> mipmaps;
  351. fileData = reinterpret_cast <const char*> (pointer);
  352. for (uint32_t i = 0; i < header->mipmapCount; i ++)
  353. mipmaps.emplace_back (parseMipmap (header, &fileData));
  354. // add the pixmaps back
  355. header->images.insert (std::pair <uint32_t, std::vector <TextureMipmap*>> (image, mipmaps));
  356. pointer = reinterpret_cast <const uint32_t*> (fileData);
  357. }
  358. // gifs have extra information after the mipmaps
  359. if (header->isAnimated () == true)
  360. {
  361. if (memcmp (fileData, "TEXS0002", 9) == 0)
  362. {
  363. header->animatedVersion = AnimatedVersion::TEXS0002;
  364. }
  365. else if (memcmp (fileData, "TEXS0003", 9) == 0)
  366. {
  367. header->animatedVersion = AnimatedVersion::TEXS0003;
  368. }
  369. else
  370. {
  371. delete header;
  372. sLog.exception ("found animation information of unknown type: ", std::string_view (fileData, 9));
  373. }
  374. // get an integer pointer back to read the frame count
  375. pointer = reinterpret_cast <const uint32_t*> (fileData + 9);
  376. uint32_t framecount = *pointer++;
  377. if (header->animatedVersion == AnimatedVersion::TEXS0003)
  378. {
  379. // ignore two extra integers as those are width and height of the git
  380. header->gifWidth = *pointer ++;
  381. header->gifHeight = *pointer ++;
  382. }
  383. // get back the pointer into filedata
  384. fileData = reinterpret_cast <const char*> (pointer);
  385. while (framecount > 0)
  386. {
  387. // add the frame to the list
  388. header->frames.push_back (parseAnimation (&fileData));
  389. framecount --;
  390. }
  391. // ensure gif width and height is right for TEXS0002
  392. if (header->animatedVersion == AnimatedVersion::TEXS0002)
  393. {
  394. TextureFrame* first = *header->frames.begin ();
  395. header->gifWidth = first->width1;
  396. header->gifHeight = first->height1;
  397. }
  398. }
  399. return header;
  400. }
  401. CTexture::TextureFrame* CTexture::parseAnimation (const char** originalFileData)
  402. {
  403. const char* fileData = *originalFileData;
  404. // get back the pointer into integer
  405. const auto* pointer = reinterpret_cast <const uint32_t*> (fileData);
  406. // start reading frame information
  407. auto* frame = new TextureFrame();
  408. frame->frameNumber = *pointer++;
  409. // reinterpret the pointer into float
  410. const auto* fPointer = reinterpret_cast <const float*> (pointer);
  411. frame->frametime = *fPointer ++;
  412. frame->x = *fPointer ++;
  413. frame->y = *fPointer ++;
  414. frame->width1 = *fPointer ++;
  415. frame->width2 = *fPointer ++;
  416. frame->height2 = *fPointer ++;
  417. frame->height1 = *fPointer ++;
  418. // get back the pointer into fileData so it can be reused later
  419. *originalFileData = reinterpret_cast <const char*> (fPointer);
  420. return frame;
  421. }
  422. CTexture::TextureMipmap* CTexture::parseMipmap (TextureHeader* header, const char** originalFileData)
  423. {
  424. auto* mipmap = new TextureMipmap ();
  425. // get the current position
  426. const char* fileData = *originalFileData;
  427. // get an integer pointer
  428. const auto* pointer = reinterpret_cast <const uint32_t*> (fileData);
  429. mipmap->width = *pointer++;
  430. mipmap->height = *pointer++;
  431. if (header->containerVersion == ContainerVersion::TEXB0002 ||
  432. header->containerVersion == ContainerVersion::TEXB0003)
  433. {
  434. mipmap->compression = *pointer++;
  435. mipmap->uncompressedSize = *pointer++;
  436. }
  437. mipmap->compressedSize = *pointer++;
  438. // get back a normal char pointer
  439. fileData = reinterpret_cast <const char*> (pointer);
  440. if (mipmap->compression == 0)
  441. {
  442. // this might be better named as mipmap_bytes_size instead of compressedSize
  443. // as in uncompressed files this variable actually holds the file length
  444. mipmap->uncompressedSize = mipmap->compressedSize;
  445. }
  446. mipmap->uncompressedData = new char [mipmap->uncompressedSize];
  447. if (mipmap->compression == 1)
  448. {
  449. mipmap->compressedData = new char [mipmap->compressedSize];
  450. memcpy (mipmap->compressedData, fileData, mipmap->compressedSize);
  451. mipmap->decompressData ();
  452. // advance to the end of the mipmap
  453. fileData += mipmap->compressedSize;
  454. }
  455. else
  456. {
  457. memcpy (mipmap->uncompressedData, fileData, mipmap->uncompressedSize);
  458. // advance to the end of the mipmap
  459. fileData += mipmap->uncompressedSize;
  460. }
  461. // ensure the pointer is updated with the latest position when reading the data
  462. *originalFileData = fileData;
  463. return mipmap;
  464. }
  465. bool CTexture::TextureHeader::isAnimated () const
  466. {
  467. return this->flags & TextureFlags::IsGif;
  468. }