LCOV - code coverage report
Current view: top level - source/graphics - TextureConverter.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 141 276 51.1 %
Date: 2023-01-19 00:18:29 Functions: 19 21 90.5 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. 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 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "TextureConverter.h"
      21             : 
      22             : #include "lib/allocators/shared_ptr.h"
      23             : #include "lib/bits.h"
      24             : #include "lib/regex.h"
      25             : #include "lib/tex/tex.h"
      26             : #include "lib/timer.h"
      27             : #include "maths/MD5.h"
      28             : #include "ps/CLogger.h"
      29             : #include "ps/CStr.h"
      30             : #include "ps/Profiler2.h"
      31             : #include "ps/Threading.h"
      32             : #include "ps/Util.h"
      33             : #include "ps/XML/Xeromyces.h"
      34             : 
      35             : #if CONFIG2_NVTT
      36             : 
      37             : #include "nvtt/nvtt.h"
      38             : 
      39             : // We assume NVTT is recent enough to support the alpha flag in the DXT1a format. If users try to use an
      40             : // old version of NVTT, the game will crash when trying to decode dds files generated by NVTT.
      41             : //
      42             : // The support was added upstream in https://github.com/castano/nvidia-texture-tools/commit/782a127071895f538c1ae49925a6e15687e3c966
      43             : // so, in theory, 2.0.7 and newer should be enough, but all 2.0.x releases define NVTT_VERSION as 200, so
      44             : // we can't distinguish them. NVTT_VERSION is 201 in all development versions of the 2.0.x era, so we also
      45             : // have to exclude that value.
      46             : #if !defined NVTT_VERSION || NVTT_VERSION == 200 || NVTT_VERSION == 201
      47             : #error Please use NVTT 2.1.0 or newer. \
      48             : If your system does not provide it, you should use the bundled version by NOT passing --with-system-nvtt to premake.
      49             : #endif
      50             : 
      51             : /**
      52             :  * Output handler to collect NVTT's output into a simplistic buffer.
      53             :  * WARNING: Used in the worker thread - must be thread-safe.
      54             :  */
      55          12 : struct BufferOutputHandler : public nvtt::OutputHandler
      56             : {
      57             :     std::vector<u8> buffer;
      58             : 
      59          39 :     virtual void beginImage(int UNUSED(size), int UNUSED(width), int UNUSED(height), int UNUSED(depth), int UNUSED(face), int UNUSED(miplevel))
      60             :     {
      61          39 :     }
      62             : 
      63          45 :     virtual bool writeData(const void* data, int size)
      64             :     {
      65          45 :         size_t off = buffer.size();
      66          45 :         buffer.resize(off + size);
      67          45 :         memcpy(&buffer[off], data, size);
      68          45 :         return true;
      69             :     }
      70             : 
      71          39 :     virtual void endImage()
      72             :     {
      73          39 :     }
      74             : };
      75             : 
      76             : /**
      77             :  * Request for worker thread to process.
      78             :  */
      79          12 : struct CTextureConverter::ConversionRequest
      80             : {
      81             :     VfsPath dest;
      82             :     CTexturePtr texture;
      83             :     nvtt::InputOptions inputOptions;
      84             :     nvtt::CompressionOptions compressionOptions;
      85             :     nvtt::OutputOptions outputOptions;
      86             : };
      87             : 
      88             : /**
      89             :  * Result from worker thread.
      90             :  */
      91          12 : struct CTextureConverter::ConversionResult
      92             : {
      93             :     VfsPath dest;
      94             :     CTexturePtr texture;
      95             :     BufferOutputHandler output;
      96             :     bool ret; // true if the conversion succeeded
      97             : };
      98             : 
      99             : #endif // CONFIG2_NVTT
     100             : 
     101          11 : void CTextureConverter::Settings::Hash(MD5& hash)
     102             : {
     103          11 :     hash.Update((const u8*)&format, sizeof(format));
     104          11 :     hash.Update((const u8*)&mipmap, sizeof(mipmap));
     105          11 :     hash.Update((const u8*)&normal, sizeof(normal));
     106          11 :     hash.Update((const u8*)&alpha, sizeof(alpha));
     107          11 :     hash.Update((const u8*)&filter, sizeof(filter));
     108          11 :     hash.Update((const u8*)&kaiserWidth, sizeof(kaiserWidth));
     109          11 :     hash.Update((const u8*)&kaiserAlpha, sizeof(kaiserAlpha));
     110          11 :     hash.Update((const u8*)&kaiserStretch, sizeof(kaiserStretch));
     111          11 : }
     112             : 
     113           0 : CTextureConverter::SettingsFile* CTextureConverter::LoadSettings(const VfsPath& path) const
     114             : {
     115           0 :     CXeromyces XeroFile;
     116           0 :     if (XeroFile.Load(m_VFS, path, "texture") != PSRETURN_OK)
     117           0 :         return NULL;
     118             : 
     119             :     // Define all the elements used in the XML file
     120             :     #define EL(x) int el_##x = XeroFile.GetElementID(#x)
     121             :     #define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
     122           0 :     EL(textures);
     123           0 :     EL(file);
     124           0 :     AT(pattern);
     125           0 :     AT(format);
     126           0 :     AT(mipmap);
     127           0 :     AT(normal);
     128           0 :     AT(alpha);
     129           0 :     AT(filter);
     130           0 :     AT(kaiserwidth);
     131           0 :     AT(kaiseralpha);
     132           0 :     AT(kaiserstretch);
     133             :     #undef AT
     134             :     #undef EL
     135             : 
     136           0 :     XMBElement root = XeroFile.GetRoot();
     137             : 
     138           0 :     if (root.GetNodeName() != el_textures)
     139             :     {
     140           0 :         LOGERROR("Invalid texture settings file \"%s\" (unrecognised root element)", path.string8());
     141           0 :         return NULL;
     142             :     }
     143             : 
     144           0 :     std::unique_ptr<SettingsFile> settings = std::make_unique<SettingsFile>();
     145             : 
     146           0 :     XERO_ITER_EL(root, child)
     147             :     {
     148           0 :         if (child.GetNodeName() == el_file)
     149             :         {
     150           0 :             Match p;
     151             : 
     152           0 :             XERO_ITER_ATTR(child, attr)
     153             :             {
     154           0 :                 if (attr.Name == at_pattern)
     155             :                 {
     156           0 :                     p.pattern = attr.Value.FromUTF8();
     157             :                 }
     158           0 :                 else if (attr.Name == at_format)
     159             :                 {
     160           0 :                     CStr v(attr.Value);
     161           0 :                     if (v == "dxt1")
     162           0 :                         p.settings.format = FMT_DXT1;
     163           0 :                     else if (v == "dxt3")
     164           0 :                         p.settings.format = FMT_DXT3;
     165           0 :                     else if (v == "dxt5")
     166           0 :                         p.settings.format = FMT_DXT5;
     167           0 :                     else if (v == "rgba")
     168           0 :                         p.settings.format = FMT_RGBA;
     169           0 :                     else if (v == "alpha")
     170           0 :                         p.settings.format = FMT_ALPHA;
     171             :                     else
     172           0 :                         LOGERROR("Invalid attribute value <file format='%s'>", v.c_str());
     173             :                 }
     174           0 :                 else if (attr.Name == at_mipmap)
     175             :                 {
     176           0 :                     CStr v(attr.Value);
     177           0 :                     if (v == "true")
     178           0 :                         p.settings.mipmap = MIP_TRUE;
     179           0 :                     else if (v == "false")
     180           0 :                         p.settings.mipmap = MIP_FALSE;
     181             :                     else
     182           0 :                         LOGERROR("Invalid attribute value <file mipmap='%s'>", v.c_str());
     183             :                 }
     184           0 :                 else if (attr.Name == at_normal)
     185             :                 {
     186           0 :                     CStr v(attr.Value);
     187           0 :                     if (v == "true")
     188           0 :                         p.settings.normal = NORMAL_TRUE;
     189           0 :                     else if (v == "false")
     190           0 :                         p.settings.normal = NORMAL_FALSE;
     191             :                     else
     192           0 :                         LOGERROR("Invalid attribute value <file normal='%s'>", v.c_str());
     193             :                 }
     194           0 :                 else if (attr.Name == at_alpha)
     195             :                 {
     196           0 :                     CStr v(attr.Value);
     197           0 :                     if (v == "none")
     198           0 :                         p.settings.alpha = ALPHA_NONE;
     199           0 :                     else if (v == "player")
     200           0 :                         p.settings.alpha = ALPHA_PLAYER;
     201           0 :                     else if (v == "transparency")
     202           0 :                         p.settings.alpha = ALPHA_TRANSPARENCY;
     203             :                     else
     204           0 :                         LOGERROR("Invalid attribute value <file alpha='%s'>", v.c_str());
     205             :                 }
     206           0 :                 else if (attr.Name == at_filter)
     207             :                 {
     208           0 :                     CStr v(attr.Value);
     209           0 :                     if (v == "box")
     210           0 :                         p.settings.filter = FILTER_BOX;
     211           0 :                     else if (v == "triangle")
     212           0 :                         p.settings.filter = FILTER_TRIANGLE;
     213           0 :                     else if (v == "kaiser")
     214           0 :                         p.settings.filter = FILTER_KAISER;
     215             :                     else
     216           0 :                         LOGERROR("Invalid attribute value <file filter='%s'>", v.c_str());
     217             :                 }
     218           0 :                 else if (attr.Name == at_kaiserwidth)
     219             :                 {
     220           0 :                     p.settings.kaiserWidth = CStr(attr.Value).ToFloat();
     221             :                 }
     222           0 :                 else if (attr.Name == at_kaiseralpha)
     223             :                 {
     224           0 :                     p.settings.kaiserAlpha = CStr(attr.Value).ToFloat();
     225             :                 }
     226           0 :                 else if (attr.Name == at_kaiserstretch)
     227             :                 {
     228           0 :                     p.settings.kaiserStretch = CStr(attr.Value).ToFloat();
     229             :                 }
     230             :                 else
     231             :                 {
     232           0 :                     LOGERROR("Invalid attribute name <file %s='...'>", XeroFile.GetAttributeString(attr.Name));
     233             :                 }
     234             :             }
     235             : 
     236           0 :             settings->patterns.push_back(p);
     237             :         }
     238             :     }
     239             : 
     240           0 :     return settings.release();
     241             : }
     242             : 
     243          18 : CTextureConverter::Settings CTextureConverter::ComputeSettings(const std::wstring& filename, const std::vector<SettingsFile*>& settingsFiles) const
     244             : {
     245             :     // Set sensible defaults
     246          18 :     Settings settings;
     247          18 :     settings.format = FMT_DXT1;
     248          18 :     settings.mipmap = MIP_TRUE;
     249          18 :     settings.normal = NORMAL_FALSE;
     250          18 :     settings.alpha = ALPHA_NONE;
     251          18 :     settings.filter = FILTER_BOX;
     252          18 :     settings.kaiserWidth = 3.f;
     253          18 :     settings.kaiserAlpha = 4.f;
     254          18 :     settings.kaiserStretch = 1.f;
     255             : 
     256          18 :     for (size_t i = 0; i < settingsFiles.size(); ++i)
     257             :     {
     258           0 :         for (size_t j = 0; j < settingsFiles[i]->patterns.size(); ++j)
     259             :         {
     260           0 :             Match p = settingsFiles[i]->patterns[j];
     261             : 
     262             :             // Check that the pattern matches the texture file
     263           0 :             if (!match_wildcard(filename.c_str(), p.pattern.c_str()))
     264           0 :                 continue;
     265             : 
     266           0 :             if (p.settings.format != FMT_UNSPECIFIED)
     267           0 :                 settings.format = p.settings.format;
     268             : 
     269           0 :             if (p.settings.mipmap != MIP_UNSPECIFIED)
     270           0 :                 settings.mipmap = p.settings.mipmap;
     271             : 
     272           0 :             if (p.settings.normal != NORMAL_UNSPECIFIED)
     273           0 :                 settings.normal = p.settings.normal;
     274             : 
     275           0 :             if (p.settings.alpha != ALPHA_UNSPECIFIED)
     276           0 :                 settings.alpha = p.settings.alpha;
     277             : 
     278           0 :             if (p.settings.filter != FILTER_UNSPECIFIED)
     279           0 :                 settings.filter = p.settings.filter;
     280             : 
     281           0 :             if (p.settings.kaiserWidth != -1.f)
     282           0 :                 settings.kaiserWidth = p.settings.kaiserWidth;
     283             : 
     284           0 :             if (p.settings.kaiserAlpha != -1.f)
     285           0 :                 settings.kaiserAlpha = p.settings.kaiserAlpha;
     286             : 
     287           0 :             if (p.settings.kaiserStretch != -1.f)
     288           0 :                 settings.kaiserStretch = p.settings.kaiserStretch;
     289             :         }
     290             :     }
     291             : 
     292          18 :     return settings;
     293             : }
     294             : 
     295          11 : CTextureConverter::CTextureConverter(PIVFS vfs, bool highQuality) :
     296          11 :     m_VFS(vfs), m_HighQuality(highQuality), m_Shutdown(false)
     297             : {
     298             : #if CONFIG2_NVTT
     299             :     // Verify that we are running with at least the version we were compiled with,
     300             :     // to avoid bugs caused by ABI changes
     301          11 :     ENSURE(nvtt::version() >= NVTT_VERSION);
     302             : 
     303          11 :     m_WorkerThread = std::thread(Threading::HandleExceptions<RunThread>::Wrapper, this);
     304             : 
     305             :     // Maybe we should share some centralised pool of worker threads?
     306             :     // For now we'll just stick with a single thread for this specific use.
     307             : #endif // CONFIG2_NVTT
     308          11 : }
     309             : 
     310          22 : CTextureConverter::~CTextureConverter()
     311             : {
     312             : #if CONFIG2_NVTT
     313             :     // Tell the thread to shut down
     314             :     {
     315          22 :         std::lock_guard<std::mutex> lock(m_WorkerMutex);
     316          11 :         m_Shutdown = true;
     317             :     }
     318             : 
     319             :     while (true)
     320             :     {
     321             :         // Wake the thread up so that it shutdowns.
     322             :         // If we send the message once, there is a chance it will be missed,
     323             :         // so keep sending until shtudown becomes false again, indicating that the thread has shut down.
     324       78437 :         std::lock_guard<std::mutex> lock(m_WorkerMutex);
     325       39224 :         m_WorkerCV.notify_all();
     326       39224 :         if (!m_Shutdown)
     327          11 :             break;
     328       39213 :     }
     329             : 
     330             :     // Wait for it to shut down cleanly
     331          11 :     m_WorkerThread.join();
     332             : #endif // CONFIG2_NVTT
     333          11 : }
     334             : 
     335           7 : bool CTextureConverter::ConvertTexture(const CTexturePtr& texture, const VfsPath& src, const VfsPath& dest, const Settings& settings)
     336             : {
     337          14 :     std::shared_ptr<u8> file;
     338             :     size_t fileSize;
     339           7 :     if (m_VFS->LoadFile(src, file, fileSize) < 0)
     340             :     {
     341           0 :         LOGERROR("Failed to load texture \"%s\"", src.string8());
     342           0 :         return false;
     343             :     }
     344             : 
     345          14 :     Tex tex;
     346           7 :     const Status decodeStatus = tex.decode(file, fileSize);
     347           7 :     if (decodeStatus != INFO::OK)
     348             :     {
     349           0 :         LOGERROR("Failed to decode texture \"%s\" %s", src.string8(), GetStatusAsString(decodeStatus).c_str());
     350           0 :         return false;
     351             :     }
     352             : 
     353           7 :     if (!is_pow2(tex.m_Width) || !is_pow2(tex.m_Height))
     354             :     {
     355           1 :         LOGERROR("Texture to convert \"%s\" should have width and height be power of two: %zux%zu",
     356             :             src.string8(), tex.m_Width, tex.m_Height);
     357           1 :         return false;
     358             :     }
     359             : 
     360             :     // Check whether there's any alpha channel
     361           6 :     bool hasAlpha = ((tex.m_Flags & TEX_ALPHA) != 0);
     362             : 
     363           6 :     if (settings.format == FMT_ALPHA)
     364             :     {
     365             :         // Convert to uncompressed 8-bit with no mipmaps
     366           0 :         if (tex.transform_to((tex.m_Flags | TEX_GREY) & ~(TEX_DXT | TEX_MIPMAPS | TEX_ALPHA)) < 0)
     367             :         {
     368           0 :             LOGERROR("Failed to transform texture \"%s\"", src.string8());
     369           0 :             return false;
     370             :         }
     371             :     }
     372             :     else
     373             :     {
     374             :         // Convert to uncompressed BGRA with no mipmaps
     375           6 :         if (tex.transform_to((tex.m_Flags | TEX_BGR | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS)) < 0)
     376             :         {
     377           0 :             LOGERROR("Failed to transform texture \"%s\"", src.string8());
     378           0 :             return false;
     379             :         }
     380             :     }
     381             : 
     382             :     // Check if the texture has all alpha=255, so we can automatically
     383             :     // switch from DXT3/DXT5 to DXT1 with no loss
     384           6 :     if (hasAlpha)
     385             :     {
     386           4 :         hasAlpha = false;
     387           4 :         u8* data = tex.get_data();
     388           4 :         for (size_t i = 0; i < tex.m_Width * tex.m_Height; ++i)
     389             :         {
     390           4 :             if (data[i*4+3] != 0xFF)
     391             :             {
     392           4 :                 hasAlpha = true;
     393           4 :                 break;
     394             :             }
     395             :         }
     396             :     }
     397             : 
     398             : #if CONFIG2_NVTT
     399             : 
     400          12 :     std::shared_ptr<ConversionRequest> request = std::make_shared<ConversionRequest>();
     401           6 :     request->dest = dest;
     402           6 :     request->texture = texture;
     403             : 
     404             :     // Apply the chosen settings:
     405             : 
     406           6 :     request->inputOptions.setMipmapGeneration(settings.mipmap == MIP_TRUE);
     407             : 
     408           6 :     if (settings.alpha == ALPHA_TRANSPARENCY)
     409           0 :         request->inputOptions.setAlphaMode(nvtt::AlphaMode_Transparency);
     410             :     else
     411           6 :         request->inputOptions.setAlphaMode(nvtt::AlphaMode_None);
     412             : 
     413           6 :     if (settings.format == FMT_RGBA)
     414             :     {
     415           0 :         request->compressionOptions.setFormat(nvtt::Format_RGBA);
     416             :         // Change the default component order (see tex_dds.cpp decode_pf)
     417           0 :         request->compressionOptions.setPixelFormat(32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000u);
     418             :     }
     419           6 :     else if (settings.format == FMT_ALPHA)
     420             :     {
     421           0 :         request->compressionOptions.setFormat(nvtt::Format_RGBA);
     422           0 :         request->compressionOptions.setPixelFormat(8, 0x00, 0x00, 0x00, 0xFF);
     423             :     }
     424           6 :     else if (!hasAlpha)
     425             :     {
     426             :         // if no alpha channel then there's no point using DXT3 or DXT5
     427           2 :         request->compressionOptions.setFormat(nvtt::Format_DXT1);
     428             :     }
     429           4 :     else if (settings.format == FMT_DXT1)
     430             :     {
     431           4 :         request->compressionOptions.setFormat(nvtt::Format_DXT1a);
     432             :     }
     433           0 :     else if (settings.format == FMT_DXT3)
     434             :     {
     435           0 :         request->compressionOptions.setFormat(nvtt::Format_DXT3);
     436             :     }
     437           0 :     else if (settings.format == FMT_DXT5)
     438             :     {
     439           0 :         request->compressionOptions.setFormat(nvtt::Format_DXT5);
     440             :     }
     441             : 
     442           6 :     if (settings.filter == FILTER_BOX)
     443           6 :         request->inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box);
     444           0 :     else if (settings.filter == FILTER_TRIANGLE)
     445           0 :         request->inputOptions.setMipmapFilter(nvtt::MipmapFilter_Triangle);
     446           0 :     else if (settings.filter == FILTER_KAISER)
     447           0 :         request->inputOptions.setMipmapFilter(nvtt::MipmapFilter_Kaiser);
     448             : 
     449           6 :     if (settings.normal == NORMAL_TRUE)
     450           0 :         request->inputOptions.setNormalMap(true);
     451             : 
     452           6 :     request->inputOptions.setKaiserParameters(settings.kaiserWidth, settings.kaiserAlpha, settings.kaiserStretch);
     453             : 
     454           6 :     request->inputOptions.setWrapMode(nvtt::WrapMode_Mirror); // TODO: should this be configurable?
     455             : 
     456           6 :     request->compressionOptions.setQuality(m_HighQuality ? nvtt::Quality_Production : nvtt::Quality_Fastest);
     457             : 
     458             :     // TODO: normal maps, gamma, etc
     459             : 
     460             :     // Load the texture data
     461           6 :     request->inputOptions.setTextureLayout(nvtt::TextureType_2D, tex.m_Width, tex.m_Height);
     462           6 :     if (tex.m_Bpp == 32)
     463             :     {
     464           6 :         request->inputOptions.setMipmapData(tex.get_data(), tex.m_Width, tex.m_Height);
     465             :     }
     466             :     else // bpp == 8
     467             :     {
     468             :         // NVTT requires 32-bit input data, so convert
     469           0 :         const u8* input = tex.get_data();
     470           0 :         u8* rgba = new u8[tex.m_Width * tex.m_Height * 4];
     471           0 :         u8* p = rgba;
     472           0 :         for (size_t i = 0; i < tex.m_Width * tex.m_Height; i++)
     473             :         {
     474           0 :             p[0] = p[1] = p[2] = p[3] = *input++;
     475           0 :             p += 4;
     476             :         }
     477           0 :         request->inputOptions.setMipmapData(rgba, tex.m_Width, tex.m_Height);
     478           0 :         delete[] rgba;
     479             :     }
     480             : 
     481             :     {
     482          12 :         std::lock_guard<std::mutex> lock(m_WorkerMutex);
     483           6 :         m_RequestQueue.push_back(request);
     484             :     }
     485             : 
     486             :     // Wake up the worker thread
     487           6 :     m_WorkerCV.notify_all();
     488             : 
     489           6 :     return true;
     490             : 
     491             : #else // CONFIG2_NVTT
     492             :     LOGERROR("Failed to convert texture \"%s\" (NVTT not available)", src.string8());
     493             :     return false;
     494             : #endif // !CONFIG2_NVTT
     495             : }
     496             : 
     497          22 : bool CTextureConverter::Poll(CTexturePtr& texture, VfsPath& dest, bool& ok)
     498             : {
     499             : #if CONFIG2_NVTT
     500          44 :     std::shared_ptr<ConversionResult> result;
     501             : 
     502             :     // Grab the first result (if any)
     503             :     {
     504          44 :         std::lock_guard<std::mutex> lock(m_WorkerMutex);
     505          22 :         if (!m_ResultQueue.empty())
     506             :         {
     507           6 :             result = m_ResultQueue.front();
     508           6 :             m_ResultQueue.pop_front();
     509             :         }
     510             :     }
     511             : 
     512          22 :     if (!result)
     513             :     {
     514             :         // no work to do
     515          16 :         return false;
     516             :     }
     517             : 
     518           6 :     if (!result->ret)
     519             :     {
     520             :         // conversion had failed
     521           0 :         ok = false;
     522           0 :         return true;
     523             :     }
     524             : 
     525             :     // Move output into a correctly-aligned buffer
     526           6 :     size_t size = result->output.buffer.size();
     527          12 :     std::shared_ptr<u8> file;
     528           6 :     AllocateAligned(file, size, maxSectorSize);
     529           6 :     memcpy(file.get(), &result->output.buffer[0], size);
     530           6 :     if (m_VFS->CreateFile(result->dest, file, size) < 0)
     531             :     {
     532             :         // error writing file
     533           0 :         ok = false;
     534           0 :         return true;
     535             :     }
     536             : 
     537             :     // Succeeded in converting texture
     538           6 :     texture = result->texture;
     539           6 :     dest = result->dest;
     540           6 :     ok = true;
     541           6 :     return true;
     542             : 
     543             : #else // CONFIG2_NVTT
     544             :     return false;
     545             : #endif // !CONFIG2_NVTT
     546             : }
     547             : 
     548          23 : bool CTextureConverter::IsBusy()
     549             : {
     550             : #if CONFIG2_NVTT
     551          46 :     std::lock_guard<std::mutex> lock(m_WorkerMutex);
     552          46 :     return !m_RequestQueue.empty();
     553             : #else // CONFIG2_NVTT
     554             :     return false;
     555             : #endif // !CONFIG2_NVTT
     556             : }
     557             : 
     558          11 : void CTextureConverter::RunThread(CTextureConverter* textureConverter)
     559             : {
     560             : #if CONFIG2_NVTT
     561          11 :     debug_SetThreadName("TextureConverter");
     562          11 :     g_Profiler2.RegisterCurrentThread("texconv");
     563             :     // Wait until the main thread wakes us up
     564             :     while (true)
     565             :     {
     566             :         // We may have several textures in the incoming queue, process them all before going back to sleep.
     567          17 :         if (!textureConverter->IsBusy()) {
     568          34 :             std::unique_lock<std::mutex> wait_lock(textureConverter->m_WorkerMutex);
     569             :             // Use the no-condition variant because spurious wake-ups don't matter that much here.
     570          17 :             textureConverter->m_WorkerCV.wait(wait_lock);
     571             :         }
     572             : 
     573          17 :         g_Profiler2.RecordSyncMarker();
     574          17 :         PROFILE2_EVENT("wakeup");
     575             : 
     576          23 :         std::shared_ptr<ConversionRequest> request;
     577             : 
     578             :         {
     579          23 :             std::lock_guard<std::mutex> wait_lock(textureConverter->m_WorkerMutex);
     580          17 :             if (textureConverter->m_Shutdown)
     581          11 :                 break;
     582             :             // If we weren't woken up for shutdown, we must have been woken up for
     583             :             // a new request, so grab it from the queue
     584           6 :             request = textureConverter->m_RequestQueue.front();
     585           6 :             textureConverter->m_RequestQueue.pop_front();
     586             :         }
     587             : 
     588             :         // Set up the result object
     589          12 :         std::shared_ptr<ConversionResult> result = std::make_shared<ConversionResult>();
     590           6 :         result->dest = request->dest;
     591           6 :         result->texture = request->texture;
     592             : 
     593           6 :         request->outputOptions.setOutputHandler(&result->output);
     594             : 
     595             : //      TIMER(L"TextureConverter compress");
     596             : 
     597             :         {
     598          12 :             PROFILE2("compress");
     599             : 
     600             :             // Perform the compression
     601          12 :             nvtt::Compressor compressor;
     602           6 :             result->ret = compressor.process(request->inputOptions, request->compressionOptions, request->outputOptions);
     603             :         }
     604             : 
     605             :         // Push the result onto the queue
     606          12 :         std::lock_guard<std::mutex> wait_lock(textureConverter->m_WorkerMutex);
     607           6 :         textureConverter->m_ResultQueue.push_back(result);
     608           6 :     }
     609             : 
     610          22 :     std::lock_guard<std::mutex> wait_lock(textureConverter->m_WorkerMutex);
     611          11 :     textureConverter->m_Shutdown = false;
     612             : #endif // CONFIG2_NVTT
     613          14 : }

Generated by: LCOV version 1.13