Merge pull request #22816 from CastagnaIT/fix_tx3g
[xbmc.git] / xbmc / guilib / TextureManager.cpp
blobddf2e77e7f99a3ba7be23647bf97afee425b5ac1
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "TextureManager.h"
11 #include "ServiceBroker.h"
12 #include "Texture.h"
13 #include "URL.h"
14 #include "commons/ilog.h"
15 #include "filesystem/Directory.h"
16 #include "filesystem/File.h"
17 #include "guilib/TextureBundle.h"
18 #include "guilib/TextureFormats.h"
19 #include "utils/StringUtils.h"
20 #include "utils/URIUtils.h"
21 #include "utils/log.h"
22 #include "windowing/GraphicContext.h"
23 #include "windowing/WinSystem.h"
25 #include <mutex>
27 #ifdef _DEBUG_TEXTURES
28 #include "utils/TimeUtils.h"
29 #endif
30 #if defined(TARGET_DARWIN_IOS)
31 #define WIN_SYSTEM_CLASS CWinSystemIOS
32 #include "windowing/ios/WinSystemIOS.h" // for g_Windowing in CGUITextureManager::FreeUnusedTextures
33 #elif defined(TARGET_DARWIN_TVOS)
34 #define WIN_SYSTEM_CLASS CWinSystemTVOS
35 #include "windowing/tvos/WinSystemTVOS.h" // for g_Windowing in CGUITextureManager::FreeUnusedTextures
36 #endif
38 #if defined(HAS_GL) || defined(HAS_GLES)
39 #include "system_gl.h"
40 #endif
42 #include "FFmpegImage.h"
44 #include <algorithm>
45 #include <cassert>
46 #include <exception>
48 /************************************************************************/
49 /* */
50 /************************************************************************/
51 CTextureArray::CTextureArray(int width, int height, int loops, bool texCoordsArePixels)
53 m_width = width;
54 m_height = height;
55 m_loops = loops;
56 m_orientation = 0;
57 m_texWidth = 0;
58 m_texHeight = 0;
59 m_texCoordsArePixels = false;
62 CTextureArray::CTextureArray()
64 Reset();
67 CTextureArray::~CTextureArray() = default;
69 unsigned int CTextureArray::size() const
71 return m_textures.size();
75 void CTextureArray::Reset()
77 m_textures.clear();
78 m_delays.clear();
79 m_width = 0;
80 m_height = 0;
81 m_loops = 0;
82 m_orientation = 0;
83 m_texWidth = 0;
84 m_texHeight = 0;
85 m_texCoordsArePixels = false;
88 void CTextureArray::Add(std::shared_ptr<CTexture> texture, int delay)
90 if (!texture)
91 return;
93 m_texWidth = texture->GetTextureWidth();
94 m_texHeight = texture->GetTextureHeight();
95 m_texCoordsArePixels = false;
97 m_textures.emplace_back(std::move(texture));
98 m_delays.push_back(delay);
101 void CTextureArray::Set(std::shared_ptr<CTexture> texture, int width, int height)
103 assert(!m_textures.size()); // don't try and set a texture if we already have one!
104 m_width = width;
105 m_height = height;
106 m_orientation = texture ? texture->GetOrientation() : 0;
107 Add(std::move(texture), 2);
110 void CTextureArray::Free()
112 std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
113 Reset();
117 /************************************************************************/
118 /* */
119 /************************************************************************/
121 CTextureMap::CTextureMap()
123 m_referenceCount = 0;
124 m_memUsage = 0;
127 CTextureMap::CTextureMap(const std::string& textureName, int width, int height, int loops)
128 : m_texture(width, height, loops)
129 , m_textureName(textureName)
131 m_referenceCount = 0;
132 m_memUsage = 0;
135 CTextureMap::~CTextureMap()
137 FreeTexture();
140 bool CTextureMap::Release()
142 if (!m_texture.m_textures.size())
143 return true;
144 if (!m_referenceCount)
145 return true;
147 m_referenceCount--;
148 if (!m_referenceCount)
150 return true;
152 return false;
155 const std::string& CTextureMap::GetName() const
157 return m_textureName;
160 const CTextureArray& CTextureMap::GetTexture()
162 m_referenceCount++;
163 return m_texture;
166 void CTextureMap::Dump() const
168 if (!m_referenceCount)
169 return; // nothing to see here
171 CLog::Log(LOGDEBUG, "{0}: texture:{1} has {2} frames {3} refcount", __FUNCTION__, m_textureName,
172 m_texture.m_textures.size(), m_referenceCount);
175 unsigned int CTextureMap::GetMemoryUsage() const
177 return m_memUsage;
180 void CTextureMap::Flush()
182 if (!m_referenceCount)
183 FreeTexture();
187 void CTextureMap::FreeTexture()
189 m_texture.Free();
192 void CTextureMap::SetHeight(int height)
194 m_texture.m_height = height;
197 void CTextureMap::SetWidth(int height)
199 m_texture.m_width = height;
202 bool CTextureMap::IsEmpty() const
204 return m_texture.m_textures.empty();
207 void CTextureMap::Add(std::unique_ptr<CTexture> texture, int delay)
209 if (texture)
210 m_memUsage += sizeof(CTexture) + (texture->GetTextureWidth() * texture->GetTextureHeight() * 4);
212 m_texture.Add(std::move(texture), delay);
215 /************************************************************************/
216 /* */
217 /************************************************************************/
218 CGUITextureManager::CGUITextureManager(void)
220 // we set the theme bundle to be the first bundle (thus prioritizing it)
221 m_TexBundle[0].SetThemeBundle(true);
224 CGUITextureManager::~CGUITextureManager(void)
226 Cleanup();
229 /************************************************************************/
230 /* */
231 /************************************************************************/
232 bool CGUITextureManager::CanLoad(const std::string &texturePath)
234 if (texturePath.empty())
235 return false;
237 if (!CURL::IsFullPath(texturePath))
238 return true; // assume we have it
240 // we can't (or shouldn't) be loading from remote paths, so check these
241 return URIUtils::IsHD(texturePath);
244 bool CGUITextureManager::HasTexture(const std::string &textureName, std::string *path, int *bundle, int *size)
246 std::unique_lock<CCriticalSection> lock(m_section);
248 // default values
249 if (bundle) *bundle = -1;
250 if (size) *size = 0;
251 if (path) *path = textureName;
253 if (textureName.empty())
254 return false;
256 if (!CanLoad(textureName))
257 return false;
259 // Check our loaded and bundled textures - we store in bundles using \\.
260 std::string bundledName = CTextureBundle::Normalize(textureName);
261 for (int i = 0; i < (int)m_vecTextures.size(); ++i)
263 CTextureMap *pMap = m_vecTextures[i];
264 if (pMap->GetName() == textureName)
266 if (size) *size = 1;
267 return true;
271 for (int i = 0; i < 2; i++)
273 if (m_TexBundle[i].HasFile(bundledName))
275 if (bundle) *bundle = i;
276 return true;
280 std::string fullPath = GetTexturePath(textureName);
281 if (path)
282 *path = fullPath;
284 return !fullPath.empty();
287 const CTextureArray& CGUITextureManager::Load(const std::string& strTextureName, bool checkBundleOnly /*= false */)
289 std::string strPath;
290 static CTextureArray emptyTexture;
291 int bundle = -1;
292 int size = 0;
294 if (strTextureName.empty())
295 return emptyTexture;
297 if (!HasTexture(strTextureName, &strPath, &bundle, &size))
298 return emptyTexture;
300 if (size) // we found the texture
302 for (int i = 0; i < (int)m_vecTextures.size(); ++i)
304 CTextureMap *pMap = m_vecTextures[i];
305 if (pMap->GetName() == strTextureName)
307 //CLog::Log(LOGDEBUG, "Total memusage {}", GetMemoryUsage());
308 return pMap->GetTexture();
311 // Whoops, not there.
312 return emptyTexture;
315 for (auto i = m_unusedTextures.begin(); i != m_unusedTextures.end(); ++i)
317 CTextureMap* pMap = i->first;
319 auto timestamp = i->second.time_since_epoch();
320 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(timestamp);
322 if (pMap->GetName() == strTextureName && duration.count() > 0)
324 m_vecTextures.push_back(pMap);
325 m_unusedTextures.erase(i);
326 return pMap->GetTexture();
330 if (checkBundleOnly && bundle == -1)
331 return emptyTexture;
333 //Lock here, we will do stuff that could break rendering
334 std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
336 #ifdef _DEBUG_TEXTURES
337 const auto start = std::chrono::steady_clock::now();
338 #endif
340 if (bundle >= 0 && StringUtils::EndsWithNoCase(strPath, ".gif"))
342 CTextureMap* pMap = nullptr;
343 std::vector<std::pair<std::unique_ptr<CTexture>, int>> textures;
344 int nLoops = 0, width = 0, height = 0;
345 bool success = m_TexBundle[bundle].LoadAnim(strTextureName, textures, width, height, nLoops);
346 if (!success)
348 CLog::Log(LOGERROR, "Texture manager unable to load bundled file: {}", strTextureName);
349 return emptyTexture;
352 unsigned int maxWidth = 0;
353 unsigned int maxHeight = 0;
354 pMap = new CTextureMap(strTextureName, width, height, nLoops);
355 for (auto& texture : textures)
357 maxWidth = std::max(maxWidth, texture.first->GetWidth());
358 maxHeight = std::max(maxHeight, texture.first->GetHeight());
359 pMap->Add(std::move(texture.first), texture.second);
362 pMap->SetWidth((int)maxWidth);
363 pMap->SetHeight((int)maxHeight);
365 m_vecTextures.push_back(pMap);
366 return pMap->GetTexture();
368 else if (StringUtils::EndsWithNoCase(strPath, ".gif") ||
369 StringUtils::EndsWithNoCase(strPath, ".apng"))
371 std::string mimeType;
372 if (StringUtils::EndsWithNoCase(strPath, ".gif"))
373 mimeType = "image/gif";
374 else if (StringUtils::EndsWithNoCase(strPath, ".apng"))
375 mimeType = "image/apng";
377 XFILE::CFile file;
378 std::vector<uint8_t> buf;
379 CFFmpegImage anim(mimeType);
381 if (file.LoadFile(strPath, buf) <= 0 || !anim.Initialize(buf.data(), buf.size()))
383 CLog::Log(LOGERROR, "Texture manager unable to load file: {}", CURL::GetRedacted(strPath));
384 file.Close();
385 return emptyTexture;
388 CTextureMap* pMap = new CTextureMap(strTextureName, 0, 0, 0);
389 unsigned int maxWidth = 0;
390 unsigned int maxHeight = 0;
391 uint64_t maxMemoryUsage = 91238400;// 1920*1080*4*11 bytes, i.e, a total of approx. 12 full hd frames
393 auto frame = anim.ReadFrame();
394 while (frame)
396 std::unique_ptr<CTexture> glTexture = CTexture::CreateTexture();
397 if (glTexture)
399 glTexture->LoadFromMemory(anim.Width(), anim.Height(), frame->GetPitch(), XB_FMT_A8R8G8B8, true, frame->m_pImage);
400 maxWidth = std::max(maxWidth, glTexture->GetWidth());
401 maxHeight = std::max(maxHeight, glTexture->GetHeight());
402 pMap->Add(std::move(glTexture), frame->m_delay);
405 if (pMap->GetMemoryUsage() <= maxMemoryUsage)
407 frame = anim.ReadFrame();
409 else
411 CLog::Log(LOGDEBUG, "Memory limit ({} bytes) exceeded, {} frames extracted from file : {}",
412 (maxMemoryUsage / 11) * 12, pMap->GetTexture().size(),
413 CURL::GetRedacted(strPath));
414 break;
418 pMap->SetWidth((int)maxWidth);
419 pMap->SetHeight((int)maxHeight);
421 file.Close();
423 m_vecTextures.push_back(pMap);
424 return pMap->GetTexture();
427 std::unique_ptr<CTexture> pTexture;
428 int width = 0, height = 0;
429 if (bundle >= 0)
431 if (!m_TexBundle[bundle].LoadTexture(strTextureName, pTexture, width, height))
433 CLog::Log(LOGERROR, "Texture manager unable to load bundled file: {}", strTextureName);
434 return emptyTexture;
437 else
439 pTexture = CTexture::LoadFromFile(strPath);
440 if (!pTexture)
441 return emptyTexture;
442 width = pTexture->GetWidth();
443 height = pTexture->GetHeight();
446 if (!pTexture) return emptyTexture;
448 CTextureMap* pMap = new CTextureMap(strTextureName, width, height, 0);
449 pMap->Add(std::move(pTexture), 100);
450 m_vecTextures.push_back(pMap);
452 #ifdef _DEBUG_TEXTURES
453 const auto end = std::chrono::steady_clock::now();
454 const std::chrono::duration<double, std::milli> duration = end - start;
455 CLog::Log(LOGDEBUG, "Load {}: {:.3f} ms {}", strPath, duration.count(),
456 (bundle >= 0) ? "(bundled)" : "");
457 #endif
459 return pMap->GetTexture();
463 void CGUITextureManager::ReleaseTexture(const std::string& strTextureName, bool immediately /*= false */)
465 std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
467 ivecTextures i;
468 i = m_vecTextures.begin();
469 while (i != m_vecTextures.end())
471 CTextureMap* pMap = *i;
472 if (pMap->GetName() == strTextureName)
474 if (pMap->Release())
476 //CLog::Log(LOGINFO, " cleanup:{}", strTextureName);
477 // add to our textures to free
478 std::chrono::time_point<std::chrono::steady_clock> timestamp;
480 if (!immediately)
481 timestamp = std::chrono::steady_clock::now();
483 m_unusedTextures.emplace_back(pMap, timestamp);
484 i = m_vecTextures.erase(i);
486 return;
488 ++i;
490 CLog::Log(LOGWARNING, "{}: Unable to release texture {}", __FUNCTION__, strTextureName);
493 void CGUITextureManager::FreeUnusedTextures(unsigned int timeDelay)
495 std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
496 for (auto i = m_unusedTextures.begin(); i != m_unusedTextures.end();)
498 auto now = std::chrono::steady_clock::now();
499 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - i->second);
501 if (duration.count() >= timeDelay)
503 delete i->first;
504 i = m_unusedTextures.erase(i);
506 else
507 ++i;
510 #if defined(HAS_GL) || defined(HAS_GLES)
511 for (unsigned int i = 0; i < m_unusedHwTextures.size(); ++i)
513 // on ios/tvos the hw textures might be deleted from the os
514 // when XBMC is backgrounded (e.x. for backgrounded music playback)
515 // sanity check before delete in that case.
516 #if defined(TARGET_DARWIN_EMBEDDED)
517 auto winSystem = dynamic_cast<WIN_SYSTEM_CLASS*>(CServiceBroker::GetWinSystem());
518 if (!winSystem->IsBackgrounded() || glIsTexture(m_unusedHwTextures[i]))
519 #endif
520 glDeleteTextures(1, (GLuint*) &m_unusedHwTextures[i]);
522 #endif
523 m_unusedHwTextures.clear();
526 void CGUITextureManager::ReleaseHwTexture(unsigned int texture)
528 std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
529 m_unusedHwTextures.push_back(texture);
532 void CGUITextureManager::Cleanup()
534 std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
536 ivecTextures i;
537 i = m_vecTextures.begin();
538 while (i != m_vecTextures.end())
540 CTextureMap* pMap = *i;
541 CLog::Log(LOGWARNING, "{}: Having to cleanup texture {}", __FUNCTION__, pMap->GetName());
542 delete pMap;
543 i = m_vecTextures.erase(i);
545 m_TexBundle[0].Close();
546 m_TexBundle[1].Close();
547 m_TexBundle[0] = CTextureBundle(true);
548 m_TexBundle[1] = CTextureBundle();
549 FreeUnusedTextures();
552 void CGUITextureManager::Dump() const
554 CLog::Log(LOGDEBUG, "{0}: total texturemaps size: {1}", __FUNCTION__, m_vecTextures.size());
556 for (int i = 0; i < (int)m_vecTextures.size(); ++i)
558 const CTextureMap* pMap = m_vecTextures[i];
559 if (!pMap->IsEmpty())
560 pMap->Dump();
564 void CGUITextureManager::Flush()
566 std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
568 ivecTextures i;
569 i = m_vecTextures.begin();
570 while (i != m_vecTextures.end())
572 CTextureMap* pMap = *i;
573 pMap->Flush();
574 if (pMap->IsEmpty() )
576 delete pMap;
577 i = m_vecTextures.erase(i);
579 else
581 ++i;
586 unsigned int CGUITextureManager::GetMemoryUsage() const
588 unsigned int memUsage = 0;
589 for (int i = 0; i < (int)m_vecTextures.size(); ++i)
591 memUsage += m_vecTextures[i]->GetMemoryUsage();
593 return memUsage;
596 void CGUITextureManager::SetTexturePath(const std::string &texturePath)
598 std::unique_lock<CCriticalSection> lock(m_section);
599 m_texturePaths.clear();
600 AddTexturePath(texturePath);
603 void CGUITextureManager::AddTexturePath(const std::string &texturePath)
605 std::unique_lock<CCriticalSection> lock(m_section);
606 if (!texturePath.empty())
607 m_texturePaths.push_back(texturePath);
610 void CGUITextureManager::RemoveTexturePath(const std::string &texturePath)
612 std::unique_lock<CCriticalSection> lock(m_section);
613 for (std::vector<std::string>::iterator it = m_texturePaths.begin(); it != m_texturePaths.end(); ++it)
615 if (*it == texturePath)
617 m_texturePaths.erase(it);
618 return;
623 std::string CGUITextureManager::GetTexturePath(const std::string &textureName, bool directory /* = false */)
625 if (CURL::IsFullPath(textureName))
626 return textureName;
627 else
628 { // texture doesn't include the full path, so check all fallbacks
629 std::unique_lock<CCriticalSection> lock(m_section);
630 for (const std::string& it : m_texturePaths)
632 std::string path = URIUtils::AddFileToFolder(it, "media", textureName);
633 if (directory)
635 if (XFILE::CDirectory::Exists(path))
636 return path;
638 else
640 if (XFILE::CFile::Exists(path))
641 return path;
646 CLog::Log(LOGDEBUG, "[Warning] CGUITextureManager::GetTexturePath: could not find texture '{}'",
647 textureName);
648 return "";
651 std::vector<std::string> CGUITextureManager::GetBundledTexturesFromPath(
652 const std::string& texturePath)
654 std::vector<std::string> items = m_TexBundle[0].GetTexturesFromPath(texturePath);
655 if (items.empty())
656 items = m_TexBundle[1].GetTexturesFromPath(texturePath);
657 return items;