summaryrefslogtreecommitdiffstats
path: root/xbmc/guilib/TextureManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/guilib/TextureManager.cpp')
-rw-r--r--xbmc/guilib/TextureManager.cpp658
1 files changed, 658 insertions, 0 deletions
diff --git a/xbmc/guilib/TextureManager.cpp b/xbmc/guilib/TextureManager.cpp
new file mode 100644
index 0000000..ddf2e77
--- /dev/null
+++ b/xbmc/guilib/TextureManager.cpp
@@ -0,0 +1,658 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "TextureManager.h"
+
+#include "ServiceBroker.h"
+#include "Texture.h"
+#include "URL.h"
+#include "commons/ilog.h"
+#include "filesystem/Directory.h"
+#include "filesystem/File.h"
+#include "guilib/TextureBundle.h"
+#include "guilib/TextureFormats.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+
+#include <mutex>
+
+#ifdef _DEBUG_TEXTURES
+#include "utils/TimeUtils.h"
+#endif
+#if defined(TARGET_DARWIN_IOS)
+#define WIN_SYSTEM_CLASS CWinSystemIOS
+#include "windowing/ios/WinSystemIOS.h" // for g_Windowing in CGUITextureManager::FreeUnusedTextures
+#elif defined(TARGET_DARWIN_TVOS)
+#define WIN_SYSTEM_CLASS CWinSystemTVOS
+#include "windowing/tvos/WinSystemTVOS.h" // for g_Windowing in CGUITextureManager::FreeUnusedTextures
+#endif
+
+#if defined(HAS_GL) || defined(HAS_GLES)
+#include "system_gl.h"
+#endif
+
+#include "FFmpegImage.h"
+
+#include <algorithm>
+#include <cassert>
+#include <exception>
+
+/************************************************************************/
+/* */
+/************************************************************************/
+CTextureArray::CTextureArray(int width, int height, int loops, bool texCoordsArePixels)
+{
+ m_width = width;
+ m_height = height;
+ m_loops = loops;
+ m_orientation = 0;
+ m_texWidth = 0;
+ m_texHeight = 0;
+ m_texCoordsArePixels = false;
+}
+
+CTextureArray::CTextureArray()
+{
+ Reset();
+}
+
+CTextureArray::~CTextureArray() = default;
+
+unsigned int CTextureArray::size() const
+{
+ return m_textures.size();
+}
+
+
+void CTextureArray::Reset()
+{
+ m_textures.clear();
+ m_delays.clear();
+ m_width = 0;
+ m_height = 0;
+ m_loops = 0;
+ m_orientation = 0;
+ m_texWidth = 0;
+ m_texHeight = 0;
+ m_texCoordsArePixels = false;
+}
+
+void CTextureArray::Add(std::shared_ptr<CTexture> texture, int delay)
+{
+ if (!texture)
+ return;
+
+ m_texWidth = texture->GetTextureWidth();
+ m_texHeight = texture->GetTextureHeight();
+ m_texCoordsArePixels = false;
+
+ m_textures.emplace_back(std::move(texture));
+ m_delays.push_back(delay);
+}
+
+void CTextureArray::Set(std::shared_ptr<CTexture> texture, int width, int height)
+{
+ assert(!m_textures.size()); // don't try and set a texture if we already have one!
+ m_width = width;
+ m_height = height;
+ m_orientation = texture ? texture->GetOrientation() : 0;
+ Add(std::move(texture), 2);
+}
+
+void CTextureArray::Free()
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ Reset();
+}
+
+
+/************************************************************************/
+/* */
+/************************************************************************/
+
+CTextureMap::CTextureMap()
+{
+ m_referenceCount = 0;
+ m_memUsage = 0;
+}
+
+CTextureMap::CTextureMap(const std::string& textureName, int width, int height, int loops)
+: m_texture(width, height, loops)
+, m_textureName(textureName)
+{
+ m_referenceCount = 0;
+ m_memUsage = 0;
+}
+
+CTextureMap::~CTextureMap()
+{
+ FreeTexture();
+}
+
+bool CTextureMap::Release()
+{
+ if (!m_texture.m_textures.size())
+ return true;
+ if (!m_referenceCount)
+ return true;
+
+ m_referenceCount--;
+ if (!m_referenceCount)
+ {
+ return true;
+ }
+ return false;
+}
+
+const std::string& CTextureMap::GetName() const
+{
+ return m_textureName;
+}
+
+const CTextureArray& CTextureMap::GetTexture()
+{
+ m_referenceCount++;
+ return m_texture;
+}
+
+void CTextureMap::Dump() const
+{
+ if (!m_referenceCount)
+ return; // nothing to see here
+
+ CLog::Log(LOGDEBUG, "{0}: texture:{1} has {2} frames {3} refcount", __FUNCTION__, m_textureName,
+ m_texture.m_textures.size(), m_referenceCount);
+}
+
+unsigned int CTextureMap::GetMemoryUsage() const
+{
+ return m_memUsage;
+}
+
+void CTextureMap::Flush()
+{
+ if (!m_referenceCount)
+ FreeTexture();
+}
+
+
+void CTextureMap::FreeTexture()
+{
+ m_texture.Free();
+}
+
+void CTextureMap::SetHeight(int height)
+{
+ m_texture.m_height = height;
+}
+
+void CTextureMap::SetWidth(int height)
+{
+ m_texture.m_width = height;
+}
+
+bool CTextureMap::IsEmpty() const
+{
+ return m_texture.m_textures.empty();
+}
+
+void CTextureMap::Add(std::unique_ptr<CTexture> texture, int delay)
+{
+ if (texture)
+ m_memUsage += sizeof(CTexture) + (texture->GetTextureWidth() * texture->GetTextureHeight() * 4);
+
+ m_texture.Add(std::move(texture), delay);
+}
+
+/************************************************************************/
+/* */
+/************************************************************************/
+CGUITextureManager::CGUITextureManager(void)
+{
+ // we set the theme bundle to be the first bundle (thus prioritizing it)
+ m_TexBundle[0].SetThemeBundle(true);
+}
+
+CGUITextureManager::~CGUITextureManager(void)
+{
+ Cleanup();
+}
+
+/************************************************************************/
+/* */
+/************************************************************************/
+bool CGUITextureManager::CanLoad(const std::string &texturePath)
+{
+ if (texturePath.empty())
+ return false;
+
+ if (!CURL::IsFullPath(texturePath))
+ return true; // assume we have it
+
+ // we can't (or shouldn't) be loading from remote paths, so check these
+ return URIUtils::IsHD(texturePath);
+}
+
+bool CGUITextureManager::HasTexture(const std::string &textureName, std::string *path, int *bundle, int *size)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ // default values
+ if (bundle) *bundle = -1;
+ if (size) *size = 0;
+ if (path) *path = textureName;
+
+ if (textureName.empty())
+ return false;
+
+ if (!CanLoad(textureName))
+ return false;
+
+ // Check our loaded and bundled textures - we store in bundles using \\.
+ std::string bundledName = CTextureBundle::Normalize(textureName);
+ for (int i = 0; i < (int)m_vecTextures.size(); ++i)
+ {
+ CTextureMap *pMap = m_vecTextures[i];
+ if (pMap->GetName() == textureName)
+ {
+ if (size) *size = 1;
+ return true;
+ }
+ }
+
+ for (int i = 0; i < 2; i++)
+ {
+ if (m_TexBundle[i].HasFile(bundledName))
+ {
+ if (bundle) *bundle = i;
+ return true;
+ }
+ }
+
+ std::string fullPath = GetTexturePath(textureName);
+ if (path)
+ *path = fullPath;
+
+ return !fullPath.empty();
+}
+
+const CTextureArray& CGUITextureManager::Load(const std::string& strTextureName, bool checkBundleOnly /*= false */)
+{
+ std::string strPath;
+ static CTextureArray emptyTexture;
+ int bundle = -1;
+ int size = 0;
+
+ if (strTextureName.empty())
+ return emptyTexture;
+
+ if (!HasTexture(strTextureName, &strPath, &bundle, &size))
+ return emptyTexture;
+
+ if (size) // we found the texture
+ {
+ for (int i = 0; i < (int)m_vecTextures.size(); ++i)
+ {
+ CTextureMap *pMap = m_vecTextures[i];
+ if (pMap->GetName() == strTextureName)
+ {
+ //CLog::Log(LOGDEBUG, "Total memusage {}", GetMemoryUsage());
+ return pMap->GetTexture();
+ }
+ }
+ // Whoops, not there.
+ return emptyTexture;
+ }
+
+ for (auto i = m_unusedTextures.begin(); i != m_unusedTextures.end(); ++i)
+ {
+ CTextureMap* pMap = i->first;
+
+ auto timestamp = i->second.time_since_epoch();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(timestamp);
+
+ if (pMap->GetName() == strTextureName && duration.count() > 0)
+ {
+ m_vecTextures.push_back(pMap);
+ m_unusedTextures.erase(i);
+ return pMap->GetTexture();
+ }
+ }
+
+ if (checkBundleOnly && bundle == -1)
+ return emptyTexture;
+
+ //Lock here, we will do stuff that could break rendering
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+#ifdef _DEBUG_TEXTURES
+ const auto start = std::chrono::steady_clock::now();
+#endif
+
+ if (bundle >= 0 && StringUtils::EndsWithNoCase(strPath, ".gif"))
+ {
+ CTextureMap* pMap = nullptr;
+ std::vector<std::pair<std::unique_ptr<CTexture>, int>> textures;
+ int nLoops = 0, width = 0, height = 0;
+ bool success = m_TexBundle[bundle].LoadAnim(strTextureName, textures, width, height, nLoops);
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "Texture manager unable to load bundled file: {}", strTextureName);
+ return emptyTexture;
+ }
+
+ unsigned int maxWidth = 0;
+ unsigned int maxHeight = 0;
+ pMap = new CTextureMap(strTextureName, width, height, nLoops);
+ for (auto& texture : textures)
+ {
+ maxWidth = std::max(maxWidth, texture.first->GetWidth());
+ maxHeight = std::max(maxHeight, texture.first->GetHeight());
+ pMap->Add(std::move(texture.first), texture.second);
+ }
+
+ pMap->SetWidth((int)maxWidth);
+ pMap->SetHeight((int)maxHeight);
+
+ m_vecTextures.push_back(pMap);
+ return pMap->GetTexture();
+ }
+ else if (StringUtils::EndsWithNoCase(strPath, ".gif") ||
+ StringUtils::EndsWithNoCase(strPath, ".apng"))
+ {
+ std::string mimeType;
+ if (StringUtils::EndsWithNoCase(strPath, ".gif"))
+ mimeType = "image/gif";
+ else if (StringUtils::EndsWithNoCase(strPath, ".apng"))
+ mimeType = "image/apng";
+
+ XFILE::CFile file;
+ std::vector<uint8_t> buf;
+ CFFmpegImage anim(mimeType);
+
+ if (file.LoadFile(strPath, buf) <= 0 || !anim.Initialize(buf.data(), buf.size()))
+ {
+ CLog::Log(LOGERROR, "Texture manager unable to load file: {}", CURL::GetRedacted(strPath));
+ file.Close();
+ return emptyTexture;
+ }
+
+ CTextureMap* pMap = new CTextureMap(strTextureName, 0, 0, 0);
+ unsigned int maxWidth = 0;
+ unsigned int maxHeight = 0;
+ uint64_t maxMemoryUsage = 91238400;// 1920*1080*4*11 bytes, i.e, a total of approx. 12 full hd frames
+
+ auto frame = anim.ReadFrame();
+ while (frame)
+ {
+ std::unique_ptr<CTexture> glTexture = CTexture::CreateTexture();
+ if (glTexture)
+ {
+ glTexture->LoadFromMemory(anim.Width(), anim.Height(), frame->GetPitch(), XB_FMT_A8R8G8B8, true, frame->m_pImage);
+ maxWidth = std::max(maxWidth, glTexture->GetWidth());
+ maxHeight = std::max(maxHeight, glTexture->GetHeight());
+ pMap->Add(std::move(glTexture), frame->m_delay);
+ }
+
+ if (pMap->GetMemoryUsage() <= maxMemoryUsage)
+ {
+ frame = anim.ReadFrame();
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Memory limit ({} bytes) exceeded, {} frames extracted from file : {}",
+ (maxMemoryUsage / 11) * 12, pMap->GetTexture().size(),
+ CURL::GetRedacted(strPath));
+ break;
+ }
+ }
+
+ pMap->SetWidth((int)maxWidth);
+ pMap->SetHeight((int)maxHeight);
+
+ file.Close();
+
+ m_vecTextures.push_back(pMap);
+ return pMap->GetTexture();
+ }
+
+ std::unique_ptr<CTexture> pTexture;
+ int width = 0, height = 0;
+ if (bundle >= 0)
+ {
+ if (!m_TexBundle[bundle].LoadTexture(strTextureName, pTexture, width, height))
+ {
+ CLog::Log(LOGERROR, "Texture manager unable to load bundled file: {}", strTextureName);
+ return emptyTexture;
+ }
+ }
+ else
+ {
+ pTexture = CTexture::LoadFromFile(strPath);
+ if (!pTexture)
+ return emptyTexture;
+ width = pTexture->GetWidth();
+ height = pTexture->GetHeight();
+ }
+
+ if (!pTexture) return emptyTexture;
+
+ CTextureMap* pMap = new CTextureMap(strTextureName, width, height, 0);
+ pMap->Add(std::move(pTexture), 100);
+ m_vecTextures.push_back(pMap);
+
+#ifdef _DEBUG_TEXTURES
+ const auto end = std::chrono::steady_clock::now();
+ const std::chrono::duration<double, std::milli> duration = end - start;
+ CLog::Log(LOGDEBUG, "Load {}: {:.3f} ms {}", strPath, duration.count(),
+ (bundle >= 0) ? "(bundled)" : "");
+#endif
+
+ return pMap->GetTexture();
+}
+
+
+void CGUITextureManager::ReleaseTexture(const std::string& strTextureName, bool immediately /*= false */)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ ivecTextures i;
+ i = m_vecTextures.begin();
+ while (i != m_vecTextures.end())
+ {
+ CTextureMap* pMap = *i;
+ if (pMap->GetName() == strTextureName)
+ {
+ if (pMap->Release())
+ {
+ //CLog::Log(LOGINFO, " cleanup:{}", strTextureName);
+ // add to our textures to free
+ std::chrono::time_point<std::chrono::steady_clock> timestamp;
+
+ if (!immediately)
+ timestamp = std::chrono::steady_clock::now();
+
+ m_unusedTextures.emplace_back(pMap, timestamp);
+ i = m_vecTextures.erase(i);
+ }
+ return;
+ }
+ ++i;
+ }
+ CLog::Log(LOGWARNING, "{}: Unable to release texture {}", __FUNCTION__, strTextureName);
+}
+
+void CGUITextureManager::FreeUnusedTextures(unsigned int timeDelay)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ for (auto i = m_unusedTextures.begin(); i != m_unusedTextures.end();)
+ {
+ auto now = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - i->second);
+
+ if (duration.count() >= timeDelay)
+ {
+ delete i->first;
+ i = m_unusedTextures.erase(i);
+ }
+ else
+ ++i;
+ }
+
+#if defined(HAS_GL) || defined(HAS_GLES)
+ for (unsigned int i = 0; i < m_unusedHwTextures.size(); ++i)
+ {
+ // on ios/tvos the hw textures might be deleted from the os
+ // when XBMC is backgrounded (e.x. for backgrounded music playback)
+ // sanity check before delete in that case.
+#if defined(TARGET_DARWIN_EMBEDDED)
+ auto winSystem = dynamic_cast<WIN_SYSTEM_CLASS*>(CServiceBroker::GetWinSystem());
+ if (!winSystem->IsBackgrounded() || glIsTexture(m_unusedHwTextures[i]))
+#endif
+ glDeleteTextures(1, (GLuint*) &m_unusedHwTextures[i]);
+ }
+#endif
+ m_unusedHwTextures.clear();
+}
+
+void CGUITextureManager::ReleaseHwTexture(unsigned int texture)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ m_unusedHwTextures.push_back(texture);
+}
+
+void CGUITextureManager::Cleanup()
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ ivecTextures i;
+ i = m_vecTextures.begin();
+ while (i != m_vecTextures.end())
+ {
+ CTextureMap* pMap = *i;
+ CLog::Log(LOGWARNING, "{}: Having to cleanup texture {}", __FUNCTION__, pMap->GetName());
+ delete pMap;
+ i = m_vecTextures.erase(i);
+ }
+ m_TexBundle[0].Close();
+ m_TexBundle[1].Close();
+ m_TexBundle[0] = CTextureBundle(true);
+ m_TexBundle[1] = CTextureBundle();
+ FreeUnusedTextures();
+}
+
+void CGUITextureManager::Dump() const
+{
+ CLog::Log(LOGDEBUG, "{0}: total texturemaps size: {1}", __FUNCTION__, m_vecTextures.size());
+
+ for (int i = 0; i < (int)m_vecTextures.size(); ++i)
+ {
+ const CTextureMap* pMap = m_vecTextures[i];
+ if (!pMap->IsEmpty())
+ pMap->Dump();
+ }
+}
+
+void CGUITextureManager::Flush()
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ ivecTextures i;
+ i = m_vecTextures.begin();
+ while (i != m_vecTextures.end())
+ {
+ CTextureMap* pMap = *i;
+ pMap->Flush();
+ if (pMap->IsEmpty() )
+ {
+ delete pMap;
+ i = m_vecTextures.erase(i);
+ }
+ else
+ {
+ ++i;
+ }
+ }
+}
+
+unsigned int CGUITextureManager::GetMemoryUsage() const
+{
+ unsigned int memUsage = 0;
+ for (int i = 0; i < (int)m_vecTextures.size(); ++i)
+ {
+ memUsage += m_vecTextures[i]->GetMemoryUsage();
+ }
+ return memUsage;
+}
+
+void CGUITextureManager::SetTexturePath(const std::string &texturePath)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_texturePaths.clear();
+ AddTexturePath(texturePath);
+}
+
+void CGUITextureManager::AddTexturePath(const std::string &texturePath)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (!texturePath.empty())
+ m_texturePaths.push_back(texturePath);
+}
+
+void CGUITextureManager::RemoveTexturePath(const std::string &texturePath)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ for (std::vector<std::string>::iterator it = m_texturePaths.begin(); it != m_texturePaths.end(); ++it)
+ {
+ if (*it == texturePath)
+ {
+ m_texturePaths.erase(it);
+ return;
+ }
+ }
+}
+
+std::string CGUITextureManager::GetTexturePath(const std::string &textureName, bool directory /* = false */)
+{
+ if (CURL::IsFullPath(textureName))
+ return textureName;
+ else
+ { // texture doesn't include the full path, so check all fallbacks
+ std::unique_lock<CCriticalSection> lock(m_section);
+ for (const std::string& it : m_texturePaths)
+ {
+ std::string path = URIUtils::AddFileToFolder(it, "media", textureName);
+ if (directory)
+ {
+ if (XFILE::CDirectory::Exists(path))
+ return path;
+ }
+ else
+ {
+ if (XFILE::CFile::Exists(path))
+ return path;
+ }
+ }
+ }
+
+ CLog::Log(LOGDEBUG, "[Warning] CGUITextureManager::GetTexturePath: could not find texture '{}'",
+ textureName);
+ return "";
+}
+
+std::vector<std::string> CGUITextureManager::GetBundledTexturesFromPath(
+ const std::string& texturePath)
+{
+ std::vector<std::string> items = m_TexBundle[0].GetTexturesFromPath(texturePath);
+ if (items.empty())
+ items = m_TexBundle[1].GetTexturesFromPath(texturePath);
+ return items;
+}