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.
9 #include "TextureManager.h"
11 #include "ServiceBroker.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"
27 #ifdef _DEBUG_TEXTURES
28 #include "utils/TimeUtils.h"
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
38 #if defined(HAS_GL) || defined(HAS_GLES)
39 #include "system_gl.h"
42 #include "FFmpegImage.h"
48 /************************************************************************/
50 /************************************************************************/
51 CTextureArray::CTextureArray(int width
, int height
, int loops
, bool texCoordsArePixels
)
59 m_texCoordsArePixels
= false;
62 CTextureArray::CTextureArray()
67 CTextureArray::~CTextureArray() = default;
69 unsigned int CTextureArray::size() const
71 return m_textures
.size();
75 void CTextureArray::Reset()
85 m_texCoordsArePixels
= false;
88 void CTextureArray::Add(std::shared_ptr
<CTexture
> texture
, int delay
)
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!
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());
117 /************************************************************************/
119 /************************************************************************/
121 CTextureMap::CTextureMap()
123 m_referenceCount
= 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;
135 CTextureMap::~CTextureMap()
140 bool CTextureMap::Release()
142 if (!m_texture
.m_textures
.size())
144 if (!m_referenceCount
)
148 if (!m_referenceCount
)
155 const std::string
& CTextureMap::GetName() const
157 return m_textureName
;
160 const CTextureArray
& CTextureMap::GetTexture()
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
180 void CTextureMap::Flush()
182 if (!m_referenceCount
)
187 void CTextureMap::FreeTexture()
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
)
210 m_memUsage
+= sizeof(CTexture
) + (texture
->GetTextureWidth() * texture
->GetTextureHeight() * 4);
212 m_texture
.Add(std::move(texture
), delay
);
215 /************************************************************************/
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)
229 /************************************************************************/
231 /************************************************************************/
232 bool CGUITextureManager::CanLoad(const std::string
&texturePath
)
234 if (texturePath
.empty())
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
);
249 if (bundle
) *bundle
= -1;
251 if (path
) *path
= textureName
;
253 if (textureName
.empty())
256 if (!CanLoad(textureName
))
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
)
271 for (int i
= 0; i
< 2; i
++)
273 if (m_TexBundle
[i
].HasFile(bundledName
))
275 if (bundle
) *bundle
= i
;
280 std::string fullPath
= GetTexturePath(textureName
);
284 return !fullPath
.empty();
287 const CTextureArray
& CGUITextureManager::Load(const std::string
& strTextureName
, bool checkBundleOnly
/*= false */)
290 static CTextureArray emptyTexture
;
294 if (strTextureName
.empty())
297 if (!HasTexture(strTextureName
, &strPath
, &bundle
, &size
))
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.
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)
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();
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
);
348 CLog::Log(LOGERROR
, "Texture manager unable to load bundled file: {}", strTextureName
);
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";
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
));
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();
396 std::unique_ptr
<CTexture
> glTexture
= CTexture::CreateTexture();
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();
411 CLog::Log(LOGDEBUG
, "Memory limit ({} bytes) exceeded, {} frames extracted from file : {}",
412 (maxMemoryUsage
/ 11) * 12, pMap
->GetTexture().size(),
413 CURL::GetRedacted(strPath
));
418 pMap
->SetWidth((int)maxWidth
);
419 pMap
->SetHeight((int)maxHeight
);
423 m_vecTextures
.push_back(pMap
);
424 return pMap
->GetTexture();
427 std::unique_ptr
<CTexture
> pTexture
;
428 int width
= 0, height
= 0;
431 if (!m_TexBundle
[bundle
].LoadTexture(strTextureName
, pTexture
, width
, height
))
433 CLog::Log(LOGERROR
, "Texture manager unable to load bundled file: {}", strTextureName
);
439 pTexture
= CTexture::LoadFromFile(strPath
);
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)" : "");
459 return pMap
->GetTexture();
463 void CGUITextureManager::ReleaseTexture(const std::string
& strTextureName
, bool immediately
/*= false */)
465 std::unique_lock
<CCriticalSection
> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
468 i
= m_vecTextures
.begin();
469 while (i
!= m_vecTextures
.end())
471 CTextureMap
* pMap
= *i
;
472 if (pMap
->GetName() == strTextureName
)
476 //CLog::Log(LOGINFO, " cleanup:{}", strTextureName);
477 // add to our textures to free
478 std::chrono::time_point
<std::chrono::steady_clock
> timestamp
;
481 timestamp
= std::chrono::steady_clock::now();
483 m_unusedTextures
.emplace_back(pMap
, timestamp
);
484 i
= m_vecTextures
.erase(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
)
504 i
= m_unusedTextures
.erase(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
]))
520 glDeleteTextures(1, (GLuint
*) &m_unusedHwTextures
[i
]);
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());
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());
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())
564 void CGUITextureManager::Flush()
566 std::unique_lock
<CCriticalSection
> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
569 i
= m_vecTextures
.begin();
570 while (i
!= m_vecTextures
.end())
572 CTextureMap
* pMap
= *i
;
574 if (pMap
->IsEmpty() )
577 i
= m_vecTextures
.erase(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();
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
);
623 std::string
CGUITextureManager::GetTexturePath(const std::string
&textureName
, bool directory
/* = false */)
625 if (CURL::IsFullPath(textureName
))
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
);
635 if (XFILE::CDirectory::Exists(path
))
640 if (XFILE::CFile::Exists(path
))
646 CLog::Log(LOGDEBUG
, "[Warning] CGUITextureManager::GetTexturePath: could not find texture '{}'",
651 std::vector
<std::string
> CGUITextureManager::GetBundledTexturesFromPath(
652 const std::string
& texturePath
)
654 std::vector
<std::string
> items
= m_TexBundle
[0].GetTexturesFromPath(texturePath
);
656 items
= m_TexBundle
[1].GetTexturesFromPath(texturePath
);