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