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::optional
<CTextureBundleXBT::Animation
> animation
=
344 m_TexBundle
[bundle
].LoadAnim(strTextureName
);
347 CLog::Log(LOGERROR
, "Texture manager unable to load bundled file: {}", strTextureName
);
351 int nLoops
= animation
.value().loops
;
352 int width
= animation
.value().width
;
353 int height
= animation
.value().height
;
355 unsigned int maxWidth
= 0;
356 unsigned int maxHeight
= 0;
357 pMap
= new CTextureMap(strTextureName
, width
, height
, nLoops
);
358 for (auto& texture
: animation
.value().textures
)
360 maxWidth
= std::max(maxWidth
, texture
.first
->GetWidth());
361 maxHeight
= std::max(maxHeight
, texture
.first
->GetHeight());
362 pMap
->Add(std::move(texture
.first
), texture
.second
);
365 pMap
->SetWidth((int)maxWidth
);
366 pMap
->SetHeight((int)maxHeight
);
368 m_vecTextures
.push_back(pMap
);
369 return pMap
->GetTexture();
371 else if (StringUtils::EndsWithNoCase(strPath
, ".gif") ||
372 StringUtils::EndsWithNoCase(strPath
, ".apng"))
374 std::string mimeType
;
375 if (StringUtils::EndsWithNoCase(strPath
, ".gif"))
376 mimeType
= "image/gif";
377 else if (StringUtils::EndsWithNoCase(strPath
, ".apng"))
378 mimeType
= "image/apng";
381 std::vector
<uint8_t> buf
;
382 CFFmpegImage
anim(mimeType
);
384 if (file
.LoadFile(strPath
, buf
) <= 0 || !anim
.Initialize(buf
.data(), buf
.size()))
386 CLog::Log(LOGERROR
, "Texture manager unable to load file: {}", CURL::GetRedacted(strPath
));
391 CTextureMap
* pMap
= new CTextureMap(strTextureName
, 0, 0, 0);
392 unsigned int maxWidth
= 0;
393 unsigned int maxHeight
= 0;
394 uint64_t maxMemoryUsage
= 91238400;// 1920*1080*4*11 bytes, i.e, a total of approx. 12 full hd frames
396 auto frame
= anim
.ReadFrame();
399 std::unique_ptr
<CTexture
> glTexture
= CTexture::CreateTexture();
402 glTexture
->LoadFromMemory(anim
.Width(), anim
.Height(), frame
->GetPitch(), XB_FMT_A8R8G8B8
, true, frame
->m_pImage
);
403 maxWidth
= std::max(maxWidth
, glTexture
->GetWidth());
404 maxHeight
= std::max(maxHeight
, glTexture
->GetHeight());
405 pMap
->Add(std::move(glTexture
), frame
->m_delay
);
408 if (pMap
->GetMemoryUsage() <= maxMemoryUsage
)
410 frame
= anim
.ReadFrame();
414 CLog::Log(LOGDEBUG
, "Memory limit ({} bytes) exceeded, {} frames extracted from file : {}",
415 (maxMemoryUsage
/ 11) * 12, pMap
->GetTexture().size(),
416 CURL::GetRedacted(strPath
));
421 pMap
->SetWidth((int)maxWidth
);
422 pMap
->SetHeight((int)maxHeight
);
426 m_vecTextures
.push_back(pMap
);
427 return pMap
->GetTexture();
430 std::unique_ptr
<CTexture
> pTexture
;
431 int width
= 0, height
= 0;
434 std::optional
<CTextureBundleXBT::Texture
> texture
=
435 m_TexBundle
[bundle
].LoadTexture(strTextureName
);
438 CLog::Log(LOGERROR
, "Texture manager unable to load bundled file: {}", strTextureName
);
442 pTexture
= std::move(texture
.value().texture
);
443 width
= texture
.value().width
;
444 height
= texture
.value().height
;
448 pTexture
= CTexture::LoadFromFile(strPath
);
451 width
= pTexture
->GetWidth();
452 height
= pTexture
->GetHeight();
455 if (!pTexture
) return emptyTexture
;
457 CTextureMap
* pMap
= new CTextureMap(strTextureName
, width
, height
, 0);
458 pMap
->Add(std::move(pTexture
), 100);
459 m_vecTextures
.push_back(pMap
);
461 #ifdef _DEBUG_TEXTURES
462 const auto end
= std::chrono::steady_clock::now();
463 const std::chrono::duration
<double, std::milli
> duration
= end
- start
;
464 CLog::Log(LOGDEBUG
, "Load {}: {:.3f} ms {}", strPath
, duration
.count(),
465 (bundle
>= 0) ? "(bundled)" : "");
468 return pMap
->GetTexture();
472 void CGUITextureManager::ReleaseTexture(const std::string
& strTextureName
, bool immediately
/*= false */)
474 std::unique_lock
<CCriticalSection
> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
477 i
= m_vecTextures
.begin();
478 while (i
!= m_vecTextures
.end())
480 CTextureMap
* pMap
= *i
;
481 if (pMap
->GetName() == strTextureName
)
485 //CLog::Log(LOGINFO, " cleanup:{}", strTextureName);
486 // add to our textures to free
487 std::chrono::time_point
<std::chrono::steady_clock
> timestamp
;
490 timestamp
= std::chrono::steady_clock::now();
492 m_unusedTextures
.emplace_back(pMap
, timestamp
);
493 i
= m_vecTextures
.erase(i
);
499 CLog::Log(LOGWARNING
, "{}: Unable to release texture {}", __FUNCTION__
, strTextureName
);
502 void CGUITextureManager::FreeUnusedTextures(unsigned int timeDelay
)
504 std::unique_lock
<CCriticalSection
> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
505 for (auto i
= m_unusedTextures
.begin(); i
!= m_unusedTextures
.end();)
507 auto now
= std::chrono::steady_clock::now();
508 auto duration
= std::chrono::duration_cast
<std::chrono::milliseconds
>(now
- i
->second
);
510 if (duration
.count() >= timeDelay
)
513 i
= m_unusedTextures
.erase(i
);
519 #if defined(HAS_GL) || defined(HAS_GLES)
520 for (unsigned int i
= 0; i
< m_unusedHwTextures
.size(); ++i
)
522 // on ios/tvos the hw textures might be deleted from the os
523 // when XBMC is backgrounded (e.x. for backgrounded music playback)
524 // sanity check before delete in that case.
525 #if defined(TARGET_DARWIN_EMBEDDED)
526 auto winSystem
= dynamic_cast<WIN_SYSTEM_CLASS
*>(CServiceBroker::GetWinSystem());
527 if (!winSystem
->IsBackgrounded() || glIsTexture(m_unusedHwTextures
[i
]))
529 glDeleteTextures(1, (GLuint
*) &m_unusedHwTextures
[i
]);
532 m_unusedHwTextures
.clear();
535 void CGUITextureManager::ReleaseHwTexture(unsigned int texture
)
537 std::unique_lock
<CCriticalSection
> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
538 m_unusedHwTextures
.push_back(texture
);
541 void CGUITextureManager::Cleanup()
543 std::unique_lock
<CCriticalSection
> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
546 i
= m_vecTextures
.begin();
547 while (i
!= m_vecTextures
.end())
549 CTextureMap
* pMap
= *i
;
550 CLog::Log(LOGWARNING
, "{}: Having to cleanup texture {}", __FUNCTION__
, pMap
->GetName());
552 i
= m_vecTextures
.erase(i
);
554 m_TexBundle
[0].Close();
555 m_TexBundle
[1].Close();
556 m_TexBundle
[0] = CTextureBundle(true);
557 m_TexBundle
[1] = CTextureBundle();
558 FreeUnusedTextures();
561 void CGUITextureManager::Dump() const
563 CLog::Log(LOGDEBUG
, "{0}: total texturemaps size: {1}", __FUNCTION__
, m_vecTextures
.size());
565 for (int i
= 0; i
< (int)m_vecTextures
.size(); ++i
)
567 const CTextureMap
* pMap
= m_vecTextures
[i
];
568 if (!pMap
->IsEmpty())
573 void CGUITextureManager::Flush()
575 std::unique_lock
<CCriticalSection
> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
578 i
= m_vecTextures
.begin();
579 while (i
!= m_vecTextures
.end())
581 CTextureMap
* pMap
= *i
;
583 if (pMap
->IsEmpty() )
586 i
= m_vecTextures
.erase(i
);
595 unsigned int CGUITextureManager::GetMemoryUsage() const
597 unsigned int memUsage
= 0;
598 for (int i
= 0; i
< (int)m_vecTextures
.size(); ++i
)
600 memUsage
+= m_vecTextures
[i
]->GetMemoryUsage();
605 void CGUITextureManager::SetTexturePath(const std::string
&texturePath
)
607 std::unique_lock
<CCriticalSection
> lock(m_section
);
608 m_texturePaths
.clear();
609 AddTexturePath(texturePath
);
612 void CGUITextureManager::AddTexturePath(const std::string
&texturePath
)
614 std::unique_lock
<CCriticalSection
> lock(m_section
);
615 if (!texturePath
.empty())
616 m_texturePaths
.push_back(texturePath
);
619 void CGUITextureManager::RemoveTexturePath(const std::string
&texturePath
)
621 std::unique_lock
<CCriticalSection
> lock(m_section
);
622 for (std::vector
<std::string
>::iterator it
= m_texturePaths
.begin(); it
!= m_texturePaths
.end(); ++it
)
624 if (*it
== texturePath
)
626 m_texturePaths
.erase(it
);
632 std::string
CGUITextureManager::GetTexturePath(const std::string
&textureName
, bool directory
/* = false */)
634 if (CURL::IsFullPath(textureName
))
637 { // texture doesn't include the full path, so check all fallbacks
638 std::unique_lock
<CCriticalSection
> lock(m_section
);
639 for (const std::string
& it
: m_texturePaths
)
641 std::string path
= URIUtils::AddFileToFolder(it
, "media", textureName
);
644 if (XFILE::CDirectory::Exists(path
))
649 if (XFILE::CFile::Exists(path
))
655 CLog::Log(LOGDEBUG
, "[Warning] CGUITextureManager::GetTexturePath: could not find texture '{}'",
660 std::vector
<std::string
> CGUITextureManager::GetBundledTexturesFromPath(
661 const std::string
& texturePath
)
663 std::vector
<std::string
> items
= m_TexBundle
[0].GetTexturesFromPath(texturePath
);
665 items
= m_TexBundle
[1].GetTexturesFromPath(texturePath
);