1 //  SuperTuxKart - a fun racing game with go-kart
2 //  Copyright (C) 2018 SuperTuxKart-Team
3 //
4 //  This program is free software; you can redistribute it and/or
5 //  modify it under the terms of the GNU General Public License
6 //  as published by the Free Software Foundation; either version 3
7 //  of the License, or (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program; if not, write to the Free Software
16 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17 
18 #include "graphics/sp/sp_texture.hpp"
19 #include "config/stk_config.hpp"
20 #include "config/user_config.hpp"
21 #include "io/file_manager.hpp"
22 #include "graphics/sp/sp_base.hpp"
23 #include "graphics/sp/sp_shader.hpp"
24 #include "graphics/sp/sp_shader_manager.hpp"
25 #include "graphics/sp/sp_texture_manager.hpp"
26 #include "graphics/central_settings.hpp"
27 #include "graphics/irr_driver.hpp"
28 #include "graphics/material.hpp"
29 #include "utils/log.hpp"
30 #include "utils/string_utils.hpp"
31 
32 #if !(defined(SERVER_ONLY) || defined(MOBILE_STK))
33 #include <squish.h>
34 static_assert(squish::kColourClusterFit == (1 << 5), "Wrong header");
35 static_assert(squish::kColourRangeFit == (1 << 6), "Wrong header");
36 static_assert(squish::kColourIterativeClusterFit == (1 << 8), "Wrong header");
37 #endif
38 
39 #if !(defined(SERVER_ONLY) || defined(MOBILE_STK))
40 extern "C"
41 {
42     #include <mipmap/img.h>
43     #include <mipmap/imgresize.h>
44 }
45 #endif
46 
47 #include <numeric>
48 
49 #if !defined(MOBILE_STK)
50 static const uint8_t CACHE_VERSION = 1;
51 #endif
52 
53 namespace SP
54 {
55 // ----------------------------------------------------------------------------
SPTexture(const std::string & path,Material * m,bool undo_srgb,const std::string & container_id)56 SPTexture::SPTexture(const std::string& path, Material* m, bool undo_srgb,
57                      const std::string& container_id)
58          : m_path(path), m_width(0), m_height(0), m_material(m),
59            m_undo_srgb(undo_srgb)
60 {
61 #ifndef SERVER_ONLY
62     glGenTextures(1, &m_texture_name);
63 
64     createWhite(false/*private_init*/);
65 
66     if (!CVS->isTextureCompressionEnabled() || container_id.empty())
67     {
68         return;
69     }
70 
71     std::string cache_subdir = "hd";
72     if ((UserConfigParams::m_high_definition_textures & 0x01) == 0x01)
73     {
74         cache_subdir = "hd";
75     }
76     else
77     {
78         cache_subdir = StringUtils::insertValues("resized_%i",
79             (int)UserConfigParams::m_max_texture_size);
80     }
81 
82 #ifdef USE_GLES2
83     if (m_undo_srgb && !CVS->isEXTTextureCompressionS3TCSRGBUsable())
84     {
85         cache_subdir += "-linear";
86     }
87 #endif
88 
89     m_cache_directory = file_manager->getCachedTexturesDir() +
90         cache_subdir + "/" + container_id;
91     file_manager->checkAndCreateDirectoryP(m_cache_directory);
92 
93 #endif
94 }   // SPTexture
95 
96 // ----------------------------------------------------------------------------
SPTexture(bool white)97 SPTexture::SPTexture(bool white)
98          : m_width(0), m_height(0), m_undo_srgb(false)
99 {
100 #ifndef SERVER_ONLY
101     glGenTextures(1, &m_texture_name);
102     if (white)
103     {
104         createWhite();
105     }
106     else
107     {
108         createTransparent();
109     }
110 #endif
111 }   // SPTexture
112 
113 // ----------------------------------------------------------------------------
~SPTexture()114 SPTexture::~SPTexture()
115 {
116 #ifndef SERVER_ONLY
117     if (m_texture_name != 0)
118     {
119         glDeleteTextures(1, &m_texture_name);
120     }
121 #endif
122 }   // ~SPTexture
123 
124 // ----------------------------------------------------------------------------
getImageFromPath(const std::string & path) const125 std::shared_ptr<video::IImage> SPTexture::getImageFromPath
126                                                 (const std::string& path) const
127 {
128     video::IImageLoader* img_loader =
129         irr_driver->getVideoDriver()->getImageLoaderForFile(path.c_str());
130     if (img_loader == NULL)
131     {
132         Log::error("SPTexture", "No image loader for %s", path.c_str());
133         return NULL;
134     }
135 
136     io::IReadFile* file = irr::io::createReadFile(path.c_str());
137     video::IImage* image = img_loader->loadImage(file);
138     if (image == NULL || image->getDimension().Width == 0 ||
139         image->getDimension().Height == 0)
140     {
141         Log::error("SPTexture", "Failed to load image %s", path.c_str());
142         if (image)
143         {
144             image->drop();
145         }
146         if (file)
147         {
148             file->drop();
149         }
150         return NULL;
151     }
152     file->drop();
153     assert(image->getReferenceCount() == 1);
154     return std::shared_ptr<video::IImage>(image);
155 }   // getImagefromPath
156 
157 // ----------------------------------------------------------------------------
getTextureImage() const158 std::shared_ptr<video::IImage> SPTexture::getTextureImage() const
159 {
160     std::shared_ptr<video::IImage> image;
161 #ifndef SERVER_ONLY
162     image = getImageFromPath(m_path);
163     if (!image)
164     {
165         return NULL;
166     }
167     core::dimension2du img_size = image->getDimension();
168     core::dimension2du tex_size = img_size.getOptimalSize
169         (true/*requirePowerOfTwo*/, false/*requireSquare*/, false/*larger*/);
170     unsigned max = sp_max_texture_size.load();
171     core::dimension2du max_size = core::dimension2du(max, max);
172 
173     if (tex_size.Width > max_size.Width)
174     {
175         tex_size.Width = max_size.Width;
176     }
177     if (tex_size.Height > max_size.Height)
178     {
179         tex_size.Height = max_size.Height;
180     }
181     if (image->getColorFormat() != video::ECF_A8R8G8B8 ||
182         tex_size != img_size)
183     {
184         video::IImage* new_texture = irr_driver
185             ->getVideoDriver()->createImage(video::ECF_A8R8G8B8, tex_size);
186         if (tex_size != img_size)
187         {
188             image->copyToScaling(new_texture);
189         }
190         else
191         {
192             image->copyTo(new_texture);
193         }
194         assert(new_texture->getReferenceCount() == 1);
195         image.reset(new_texture);
196     }
197 
198     uint8_t* data = (uint8_t*)image->lock();
199     for (unsigned int i = 0; i < image->getDimension().Width *
200         image->getDimension().Height; i++)
201     {
202         const bool use_tex_compress = CVS->isTextureCompressionEnabled() &&
203             !m_cache_directory.empty();
204 #ifndef USE_GLES2
205         if (use_tex_compress)
206         {
207 #endif
208             // to RGBA for libsquish or for gles it's always true
209             uint8_t tmp_val = data[i * 4];
210             data[i * 4] = data[i * 4 + 2];
211             data[i * 4 + 2] = tmp_val;
212 #ifndef USE_GLES2
213         }
214 #endif
215 
216         bool force_undo_srgb = use_tex_compress &&
217                                   !CVS->isEXTTextureCompressionS3TCSRGBUsable();
218 
219         if (m_undo_srgb && (!use_tex_compress || force_undo_srgb))
220         {
221             data[i * 4] = srgb255ToLinear(data[i * 4]);
222             data[i * 4 + 1] = srgb255ToLinear(data[i * 4 + 1]);
223             data[i * 4 + 2] = srgb255ToLinear(data[i * 4 + 2]);
224         }
225     }
226 #endif
227     return image;
228 }   // getTextureImage
229 
230 // ----------------------------------------------------------------------------
compressedTexImage2d(std::shared_ptr<video::IImage> texture,const std::vector<std::pair<core::dimension2du,unsigned>> & mipmap_sizes)231 bool SPTexture::compressedTexImage2d(std::shared_ptr<video::IImage> texture,
232                                      const std::vector<std::pair
233                                      <core::dimension2du, unsigned> >&
234                                      mipmap_sizes)
235 {
236 #if !defined(SERVER_ONLY) && !defined(MOBILE_STK)
237     unsigned format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
238     if (m_undo_srgb && CVS->isEXTTextureCompressionS3TCSRGBUsable())
239     {
240         format = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
241     }
242     glDeleteTextures(1, &m_texture_name);
243     glGenTextures(1, &m_texture_name);
244     glBindTexture(GL_TEXTURE_2D, m_texture_name);
245     uint8_t* compressed = (uint8_t*)texture->lock();
246     unsigned cur_mipmap_size = 0;
247     for (unsigned i = 0; i < mipmap_sizes.size(); i++)
248     {
249         cur_mipmap_size = mipmap_sizes[i].second;
250         glCompressedTexImage2D(GL_TEXTURE_2D, i, format,
251             mipmap_sizes[i].first.Width, mipmap_sizes[i].first.Height, 0,
252             cur_mipmap_size, compressed);
253         compressed += cur_mipmap_size;
254     }
255     glBindTexture(GL_TEXTURE_2D, 0);
256     m_width.store(mipmap_sizes[0].first.Width);
257     m_height.store(mipmap_sizes[0].first.Height);
258 #endif
259     return true;
260 }   // compressedTexImage2d
261 
262 // ----------------------------------------------------------------------------
texImage2d(std::shared_ptr<video::IImage> texture,std::shared_ptr<video::IImage> mipmaps)263 bool SPTexture::texImage2d(std::shared_ptr<video::IImage> texture,
264                            std::shared_ptr<video::IImage> mipmaps)
265 {
266 #ifndef SERVER_ONLY
267     if (texture)
268     {
269 #ifdef USE_GLES2
270         unsigned upload_format = GL_RGBA;
271 #else
272         unsigned upload_format = GL_BGRA;
273 #endif
274         glDeleteTextures(1, &m_texture_name);
275         glGenTextures(1, &m_texture_name);
276         glBindTexture(GL_TEXTURE_2D, m_texture_name);
277         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
278             texture->getDimension().Width, texture->getDimension().Height,
279             0, upload_format, GL_UNSIGNED_BYTE, texture->lock());
280         if (mipmaps)
281         {
282             std::vector<std::pair<core::dimension2du, unsigned> >
283                 mipmap_sizes;
284             unsigned width = texture->getDimension().Width;
285             unsigned height = texture->getDimension().Height;
286             mipmap_sizes.emplace_back(core::dimension2du(width, height),
287                 width * height * 4);
288             while (true)
289             {
290                 width = width < 2 ? 1 : width >> 1;
291                 height = height < 2 ? 1 : height >> 1;
292                 mipmap_sizes.emplace_back
293                     (core::dimension2du(width, height), width * height * 4);
294                 if (width == 1 && height == 1)
295                 {
296                     break;
297                 }
298             }
299             uint8_t* ptr = (uint8_t*)mipmaps->lock();
300             for (unsigned i = 1; i < mipmap_sizes.size(); i++)
301             {
302                 glTexImage2D(GL_TEXTURE_2D, i, GL_RGBA,
303                     mipmap_sizes[i].first.Width, mipmap_sizes[i].first.Height,
304                     0, upload_format, GL_UNSIGNED_BYTE, ptr);
305                 ptr += mipmap_sizes[i].second;
306             }
307         }
308         else
309         {
310             glGenerateMipmap(GL_TEXTURE_2D);
311         }
312         glBindTexture(GL_TEXTURE_2D, 0);
313     }
314     if (texture)
315     {
316         m_width.store(texture->getDimension().Width);
317         m_height.store(texture->getDimension().Height);
318     }
319     else
320     {
321         m_width.store(2);
322         m_height.store(2);
323     }
324 #endif
325     return true;
326 }   // texImage2d
327 
328 // ----------------------------------------------------------------------------
saveCompressedTexture(std::shared_ptr<video::IImage> texture,const std::vector<std::pair<core::dimension2du,unsigned>> & sizes,const std::string & cache_location)329 bool SPTexture::saveCompressedTexture(std::shared_ptr<video::IImage> texture,
330                                       const std::vector<std::pair
331                                       <core::dimension2du, unsigned> >& sizes,
332                                       const std::string& cache_location)
333 {
334 #if !defined(SERVER_ONLY) && !defined(MOBILE_STK)
335     const unsigned total_size = std::accumulate(sizes.begin(), sizes.end(), 0,
336         [] (const unsigned int previous, const std::pair
337         <core::dimension2du, unsigned>& cur_sizes)
338        { return previous + cur_sizes.second; });
339     io::IWriteFile* file = irr::io::createWriteFile(cache_location.c_str(),
340         false);
341     if (file == NULL)
342     {
343         return true;
344     }
345     file->write(&CACHE_VERSION, 1);
346     const unsigned mm_sizes = (unsigned)sizes.size();
347     file->write(&mm_sizes, 4);
348     for (auto& p : sizes)
349     {
350         file->write(&p.first.Width, 4);
351         file->write(&p.first.Height, 4);
352         file->write(&p.second, 4);
353     }
354     file->write(texture->lock(), total_size);
355     file->drop();
356 #endif
357     return true;
358 }   // saveCompressedTexture
359 
360 // ----------------------------------------------------------------------------
useTextureCache(const std::string & full_path,std::string * cache_loc)361 bool SPTexture::useTextureCache(const std::string& full_path,
362                                 std::string* cache_loc)
363 {
364 #ifndef SERVER_ONLY
365     if (!CVS->isTextureCompressionEnabled() || m_cache_directory.empty())
366     {
367         return false;
368     }
369 
370     std::string basename = StringUtils::getBasename(m_path);
371     *cache_loc = m_cache_directory + "/" + basename + ".sptz";
372 
373     if (file_manager->fileExists(*cache_loc) &&
374         file_manager->fileIsNewer(*cache_loc, m_path))
375     {
376         if (m_material && (!m_material->getColorizationMask().empty() ||
377             m_material->getAlphaMask().empty()))
378         {
379             std::string mask_path = StringUtils::getPath(m_path) + "/" +
380                 (!m_material->getColorizationMask().empty() ?
381                 m_material->getColorizationMask() :
382                 m_material->getAlphaMask());
383             if (!file_manager->fileIsNewer(*cache_loc, mask_path))
384             {
385                 return false;
386             }
387         }
388         return true;
389     }
390 #endif
391     return false;
392 }   // useTextureCache
393 
394 // ----------------------------------------------------------------------------
getTextureCache(const std::string & p,std::vector<std::pair<core::dimension2du,unsigned>> * sizes)395 std::shared_ptr<video::IImage> SPTexture::getTextureCache(const std::string& p,
396     std::vector<std::pair<core::dimension2du, unsigned> >* sizes)
397 {
398     std::shared_ptr<video::IImage> cache;
399 #if !(defined(SERVER_ONLY) || defined(MOBILE_STK))
400     io::IReadFile* file = irr::io::createReadFile(p.c_str());
401     if (file == NULL)
402     {
403         return cache;
404     }
405 
406     uint8_t cache_version;
407     file->read(&cache_version, 1);
408     if (cache_version != CACHE_VERSION)
409     {
410         return cache;
411     }
412 
413     unsigned mm_sizes;
414     file->read(&mm_sizes, 4);
415     sizes->resize(mm_sizes);
416     for (unsigned i = 0; i < mm_sizes; i++)
417     {
418         file->read(&((*sizes)[i].first.Width), 4);
419         file->read(&((*sizes)[i].first.Height), 4);
420         file->read(&((*sizes)[i].second), 4);
421     }
422 
423     const unsigned total_cache_size = std::accumulate(sizes->begin(),
424         sizes->end(), 0,[] (const unsigned int previous, const std::pair
425         <core::dimension2du, unsigned>& cur_sizes)
426        { return previous + cur_sizes.second; });
427     cache.reset(irr_driver->getVideoDriver()->createImage(video::ECF_A8R8G8B8,
428         (*sizes)[0].first));
429     assert(cache->getReferenceCount() == 1);
430     file->read(cache->lock(), total_cache_size);
431     file->drop();
432 #endif
433     return cache;
434 }   // getTextureCache
435 
436 // ----------------------------------------------------------------------------
threadedLoad()437 bool SPTexture::threadedLoad()
438 {
439 #ifndef SERVER_ONLY
440     std::string cache_loc;
441     if (useTextureCache(m_path, &cache_loc))
442     {
443         std::vector<std::pair<core::dimension2du, unsigned> > sizes;
444         std::shared_ptr<video::IImage> cache = getTextureCache(cache_loc,
445             &sizes);
446         if (cache)
447         {
448             SPTextureManager::get()->increaseGLCommandFunctionCount(1);
449             SPTextureManager::get()->addGLCommandFunction(
450                 [this, cache, sizes]()->bool
451                 { return compressedTexImage2d(cache, sizes); });
452             return true;
453         }
454     }
455 
456     std::shared_ptr<video::IImage> image = getTextureImage();
457     if (!image)
458     {
459         m_width.store(2);
460         m_height.store(2);
461         return true;
462     }
463     std::shared_ptr<video::IImage> mask = getMask(image->getDimension());
464     if (mask)
465     {
466         applyMask(image.get(), mask.get());
467     }
468     std::shared_ptr<video::IImage> mipmaps;
469 
470     if (!m_cache_directory.empty() && CVS->isTextureCompressionEnabled() &&
471         image->getDimension().Width >= 4 && image->getDimension().Height >= 4)
472     {
473         auto r = compressTexture(image);
474         SPTextureManager::get()->increaseGLCommandFunctionCount(1);
475         SPTextureManager::get()->addGLCommandFunction(
476             [this, image, r]()->bool
477             { return compressedTexImage2d(image, r); });
478         if (!cache_loc.empty())
479         {
480             SPTextureManager::get()->addThreadedFunction(
481                 [this, image, r, cache_loc]()->bool
482                 {
483                     return saveCompressedTexture(image, r, cache_loc);
484                 });
485         }
486     }
487     else
488     {
489 #ifndef MOBILE_STK
490         if (UserConfigParams::m_hq_mipmap && image->getDimension().Width > 1 &&
491             image->getDimension().Height > 1)
492         {
493             std::vector<std::pair<core::dimension2du, unsigned> >
494                 mipmap_sizes;
495             unsigned width = image->getDimension().Width;
496             unsigned height = image->getDimension().Height;
497             mipmap_sizes.emplace_back(core::dimension2du(width, height),
498                 0);
499             while (true)
500             {
501                 width = width < 2 ? 1 : width >> 1;
502                 height = height < 2 ? 1 : height >> 1;
503                 mipmap_sizes.emplace_back
504                     (core::dimension2du(width, height), 0);
505                 if (width == 1 && height == 1)
506                 {
507                     break;
508                 }
509             }
510             mipmaps.reset(irr_driver->getVideoDriver()->createImage
511                 (video::ECF_A8R8G8B8, mipmap_sizes[0].first));
512             generateHQMipmap(image->lock(), mipmap_sizes,
513                 (uint8_t*)mipmaps->lock());
514         }
515 #endif
516         SPTextureManager::get()->increaseGLCommandFunctionCount(1);
517         SPTextureManager::get()->addGLCommandFunction(
518             [this, image, mipmaps]()->bool
519             { return texImage2d(image, mipmaps); });
520     }
521 
522 #endif
523     return true;
524 }   // threadedLoad
525 
526 // ----------------------------------------------------------------------------
527 std::shared_ptr<video::IImage>
getMask(const core::dimension2du & s) const528     SPTexture::getMask(const core::dimension2du& s) const
529 {
530 #ifndef SERVER_ONLY
531     if (!m_material)
532     {
533         return NULL;
534     }
535     const unsigned total_size = s.Width * s.Height;
536     if (!m_material->getColorizationMask().empty() ||
537         m_material->getColorizationFactor() > 0.0f ||
538         m_material->isColorizable())
539     {
540         // Load colorization mask
541         std::shared_ptr<video::IImage> mask;
542         std::shared_ptr<SPShader> sps =
543             SPShaderManager::get()->getSPShader(m_material->getShaderName());
544         if (sps && sps->useAlphaChannel())
545         {
546             Log::debug("SPTexture", "Don't use colorization mask or factor"
547                 " with shader using alpha channel for %s", m_path.c_str());
548             // Shader using alpha channel will be colorized as a whole
549             return NULL;
550         }
551 
552         uint8_t colorization_factor_encoded = uint8_t
553             (irr::core::clamp(
554             int(m_material->getColorizationFactor() * 0.4f * 255.0f), 0, 255));
555 
556         if (!m_material->getColorizationMask().empty())
557         {
558             // Assume all maskes are in the same directory
559             std::string mask_path = StringUtils::getPath(m_path) + "/" +
560                 m_material->getColorizationMask();
561             mask = getImageFromPath(mask_path);
562             if (!mask)
563             {
564                 return NULL;
565             }
566             core::dimension2du img_size = mask->getDimension();
567             if (mask->getColorFormat() != video::ECF_A8R8G8B8 ||
568                 s != img_size)
569             {
570                 video::IImage* new_mask = irr_driver
571                     ->getVideoDriver()->createImage(video::ECF_A8R8G8B8, s);
572                 if (s != img_size)
573                 {
574                     mask->copyToScaling(new_mask);
575                 }
576                 else
577                 {
578                     mask->copyTo(new_mask);
579                 }
580                 assert(new_mask->getReferenceCount() == 1);
581                 mask.reset(new_mask);
582             }
583         }
584         else
585         {
586             video::IImage* tmp = irr_driver
587                 ->getVideoDriver()->createImage(video::ECF_A8R8G8B8, s);
588             memset(tmp->lock(), 0, total_size * 4);
589             assert(tmp->getReferenceCount() == 1);
590             mask.reset(tmp);
591         }
592         uint8_t* data = (uint8_t*)mask->lock();
593         for (unsigned int i = 0; i < total_size; i++)
594         {
595             if (!m_material->getColorizationMask().empty()
596                 && data[i * 4 + 3] > 127)
597             {
598                 continue;
599             }
600             data[i * 4 + 3] = colorization_factor_encoded;
601         }
602         return mask;
603     }
604     else if (!m_material->getAlphaMask().empty())
605     {
606         std::string mask_path = StringUtils::getPath(m_path) + "/" +
607             m_material->getAlphaMask();
608         std::shared_ptr<video::IImage> mask = getImageFromPath(mask_path);
609         if (!mask)
610         {
611             return NULL;
612         }
613         core::dimension2du img_size = mask->getDimension();
614         if (mask->getColorFormat() != video::ECF_A8R8G8B8 ||
615             s != img_size)
616         {
617             video::IImage* new_mask = irr_driver
618                 ->getVideoDriver()->createImage(video::ECF_A8R8G8B8, s);
619             if (s != img_size)
620             {
621                 mask->copyToScaling(new_mask);
622             }
623             else
624             {
625                 mask->copyTo(new_mask);
626             }
627             assert(new_mask->getReferenceCount() == 1);
628             mask.reset(new_mask);
629         }
630         uint8_t* data = (uint8_t*)mask->lock();
631         for (unsigned int i = 0; i < total_size; i++)
632         {
633             // Red channel to alpha channel
634             data[i * 4 + 3] = data[i * 4];
635         }
636         return mask;
637     }
638 #endif
639     return NULL;
640 }   // getMask
641 
642 // ----------------------------------------------------------------------------
applyMask(video::IImage * texture,video::IImage * mask)643 void SPTexture::applyMask(video::IImage* texture, video::IImage* mask)
644 {
645     assert(texture->getDimension() == mask->getDimension());
646     const core::dimension2du& dim = texture->getDimension();
647     for (unsigned int x = 0; x < dim.Width; x++)
648     {
649         for (unsigned int y = 0; y < dim.Height; y++)
650         {
651             video::SColor col = texture->getPixel(x, y);
652             video::SColor alpha = mask->getPixel(x, y);
653             col.setAlpha(alpha.getAlpha());
654             texture->setPixel(x, y, col, false);
655         }
656     }
657 }   // applyMask
658 
659 // ----------------------------------------------------------------------------
generateQuickMipmap(std::shared_ptr<video::IImage> first_image,const std::vector<std::pair<core::dimension2du,unsigned>> & mms,uint8_t * out)660 void SPTexture::generateQuickMipmap(std::shared_ptr<video::IImage> first_image,
661                                     const std::vector<std::pair
662                                     <core::dimension2du, unsigned> >& mms,
663                                     uint8_t* out)
664 {
665 #ifndef SERVER_ONLY
666     for (unsigned mip = 1; mip < mms.size(); mip++)
667     {
668         video::IImage* ti = irr_driver->getVideoDriver()
669             ->createImage(video::ECF_A8R8G8B8,
670             core::dimension2du(mms[mip].first.Width,
671             mms[mip].first.Height));
672         first_image->copyToScaling(ti);
673         const unsigned copy_size = ti->getDimension().getArea() * 4;
674         memcpy(out, ti->lock(), copy_size);
675         ti->drop();
676         out += copy_size;
677     }
678 #endif
679 }   // generateQuickMipmap
680 
681 // ----------------------------------------------------------------------------
generateHQMipmap(void * in,const std::vector<std::pair<core::dimension2du,unsigned>> & mms,uint8_t * out)682 void SPTexture::generateHQMipmap(void* in,
683                                  const std::vector<std::pair
684                                  <core::dimension2du, unsigned> >& mms,
685                                  uint8_t* out)
686 {
687 #if !(defined(SERVER_ONLY) || defined(MOBILE_STK))
688     imMipmapCascade cascade;
689     imReduceOptions options;
690     imReduceSetOptions(&options,
691         m_path.find("_Normal.") != std::string::npos ?
692         IM_REDUCE_FILTER_NORMALMAP: IM_REDUCE_FILTER_LINEAR/*filter*/,
693         2/*hopcount*/, 2.0f/*alpha*/, 1.0f/*amplifynormal*/,
694         0.0f/*normalsustainfactor*/);
695 #ifdef DEBUG
696     int ret = imBuildMipmapCascade(&cascade, in, mms[0].first.Width,
697         mms[0].first.Height, 1/*layercount*/, 4, mms[0].first.Width * 4,
698         &options, 0);
699     assert(ret == 1);
700 #else
701     imBuildMipmapCascade(&cascade, in, mms[0].first.Width,
702         mms[0].first.Height, 1/*layercount*/, 4, mms[0].first.Width * 4,
703         &options, 0);
704 #endif
705     for (unsigned int i = 1; i < mms.size(); i++)
706     {
707         const unsigned copy_size = mms[i].first.getArea() * 4;
708         memcpy(out, cascade.mipmap[i], copy_size);
709         out += copy_size;
710     }
711     imFreeMipmapCascade(&cascade);
712 #endif
713 }   // generateHQMipmap
714 
715 // ----------------------------------------------------------------------------
squishCompressImage(uint8_t * rgba,int width,int height,int pitch,void * blocks,unsigned flags)716 void SPTexture::squishCompressImage(uint8_t* rgba, int width, int height,
717                                     int pitch, void* blocks, unsigned flags)
718 {
719 #if !(defined(SERVER_ONLY) || defined(MOBILE_STK))
720     // This function is copied from CompressImage in libsquish to avoid omp
721     // if enabled by shared libsquish, because we are already using
722     // multiple thread
723     for (int y = 0; y < height; y += 4)
724     {
725         // initialise the block output
726         uint8_t* target_block = reinterpret_cast<uint8_t*>(blocks);
727         target_block += ((y >> 2) * ((width + 3) >> 2)) * 16;
728         for (int x = 0; x < width; x += 4)
729         {
730             // build the 4x4 block of pixels
731             uint8_t source_rgba[16 * 4];
732             uint8_t* target_pixel = source_rgba;
733             int mask = 0;
734             for (int py = 0; py < 4; py++)
735             {
736                 for (int px = 0; px < 4; px++)
737                 {
738                     // get the source pixel in the image
739                     int sx = x + px;
740                     int sy = y + py;
741                     // enable if we're in the image
742                     if (sx < width && sy < height)
743                     {
744                         // copy the rgba value
745                         uint8_t* source_pixel = rgba + pitch * sy + 4 * sx;
746                         memcpy(target_pixel, source_pixel, 4);
747                         // enable this pixel
748                         mask |= (1 << (4 * py + px));
749                     }
750                     // advance to the next pixel
751                     target_pixel += 4;
752                 }
753             }
754             // compress it into the output
755             squish::CompressMasked(source_rgba, mask, target_block, flags);
756             // advance
757             target_block += 16;
758         }
759     }
760 #endif
761 }   // squishCompressImage
762 
763 // ----------------------------------------------------------------------------
764 std::vector<std::pair<core::dimension2du, unsigned> >
compressTexture(std::shared_ptr<video::IImage> image)765                SPTexture::compressTexture(std::shared_ptr<video::IImage> image)
766 {
767     std::vector<std::pair<core::dimension2du, unsigned> > mipmap_sizes;
768 
769 #if !(defined(SERVER_ONLY) || defined(MOBILE_STK))
770     unsigned width = image->getDimension().Width;
771     unsigned height = image->getDimension().Height;
772     mipmap_sizes.emplace_back(core::dimension2du(width, height), 0);
773     while (true)
774     {
775         width = width < 2 ? 1 : width >> 1;
776         height = height < 2 ? 1 : height >> 1;
777         mipmap_sizes.emplace_back(core::dimension2du(width, height), 0);
778         if (width == 1 && height == 1)
779         {
780             break;
781         }
782     }
783 
784     const unsigned tc_flag = squish::kDxt5 | stk_config->m_tc_quality;
785     const unsigned compressed_size = squish::GetStorageRequirements(
786         mipmap_sizes[0].first.Width, mipmap_sizes[0].first.Height,
787         tc_flag);
788     mipmap_sizes[0].second = compressed_size;
789     uint8_t* tmp = new uint8_t[image->getDimension().getArea() * 4]();
790     uint8_t* ptr_loc = tmp + compressed_size;
791 
792     generateHQMipmap(image->lock(), mipmap_sizes, ptr_loc);
793     squishCompressImage((uint8_t*)image->lock(),
794         mipmap_sizes[0].first.Width, mipmap_sizes[0].first.Height,
795         mipmap_sizes[0].first.Width * 4, tmp, tc_flag);
796     memcpy(image->lock(), tmp, image->getDimension().getArea() * 4);
797 
798     // Now compress mipmap
799     ptr_loc = (uint8_t*)image->lock();
800     ptr_loc += compressed_size;
801     for (unsigned mip = 1; mip < mipmap_sizes.size(); mip++)
802     {
803         mipmap_sizes[mip].second = squish::GetStorageRequirements(
804             mipmap_sizes[mip].first.Width, mipmap_sizes[mip].first.Height,
805             tc_flag);
806         squishCompressImage(ptr_loc,
807             mipmap_sizes[mip].first.Width, mipmap_sizes[mip].first.Height,
808             mipmap_sizes[mip].first.Width * 4, tmp, tc_flag);
809         memcpy(ptr_loc, tmp, mipmap_sizes[mip].second);
810         ptr_loc += mipmap_sizes[mip].first.Width *
811             mipmap_sizes[mip].first.Height * 4;
812     }
813     delete [] tmp;
814 
815     if (mipmap_sizes.size() < 3)
816     {
817         return mipmap_sizes;
818     }
819     ptr_loc = (uint8_t*)image->lock();
820     ptr_loc = ptr_loc + compressed_size + mipmap_sizes[1].second;
821     uint8_t* in_memory = (uint8_t*)image->lock() + compressed_size +
822         (mipmap_sizes[1].first.Height * mipmap_sizes[1].first.Width * 4);
823 
824     // Adjust for saving the cache
825     for (unsigned mip = 2; mip < mipmap_sizes.size(); mip++)
826     {
827         memcpy(ptr_loc, in_memory, mipmap_sizes[mip].second);
828         ptr_loc += mipmap_sizes[mip].second;
829         in_memory += mipmap_sizes[mip].first.Height *
830             mipmap_sizes[mip].first.Width * 4;
831     }
832 
833 #endif
834     return mipmap_sizes;
835 }
836 
837 }
838