/* * Copyright (C) 2005-2008 Team XBMC * http://www.xbmc.org * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with XBMC; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #include "TextureManager.h" #include "Texture.h" #include "AnimatedGif.h" #include "GraphicContext.h" #include "utils/SingleLock.h" #include "utils/CharsetConverter.h" #include "utils/log.h" #include "utils/log.h" #include "addons/Skin.h" #ifdef _DEBUG #include "utils/TimeUtils.h" #endif #include "../xbmc/Util.h" #include "../xbmc/FileSystem/File.h" #include "../xbmc/FileSystem/Directory.h" #include "SystemGlobals.h" #include using namespace std; CGUITextureManager& g_TextureManager = g_SystemGlobals.m_TextureManager; /************************************************************************/ /* */ /************************************************************************/ 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() { } 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(CBaseTexture *texture, int delay) { if (!texture) return; m_textures.push_back(texture); m_delays.push_back(delay ? delay * 2 : 100); m_texWidth = texture->GetTextureWidth(); m_texHeight = texture->GetTextureHeight(); m_texCoordsArePixels = false; } void CTextureArray::Set(CBaseTexture *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(texture, 100); } void CTextureArray::Free() { CSingleLock lock(g_graphicsContext); for (unsigned int i = 0; i < m_textures.size(); i++) { delete m_textures[i]; } m_textures.clear(); m_delays.clear(); Reset(); } /************************************************************************/ /* */ /************************************************************************/ CTextureMap::CTextureMap() { m_textureName = ""; m_referenceCount = 0; m_memUsage = 0; } CTextureMap::CTextureMap(const CStdString& 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 CStdString& 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 CStdString strLog; strLog.Format(" texture:%s has %i frames %i refcount\n", m_textureName.c_str(), m_texture.m_textures.size(), m_referenceCount); OutputDebugString(strLog.c_str()); } unsigned int CTextureMap::GetMemoryUsage() const { return m_memUsage; } void CTextureMap::Flush() { if (!m_referenceCount) FreeTexture(); } void CTextureMap::FreeTexture() { m_texture.Free(); } bool CTextureMap::IsEmpty() const { return m_texture.m_textures.size() == 0; } void CTextureMap::Add(CBaseTexture* texture, int delay) { m_texture.Add(texture, delay); if (texture) m_memUsage += sizeof(CTexture) + (texture->GetTextureWidth() * texture->GetTextureHeight() * 4); } /************************************************************************/ /* */ /************************************************************************/ 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(); } const CTextureArray& CGUITextureManager::GetTexture(const CStdString& strTextureName) { static CTextureArray emptyTexture; // CLog::Log(LOGINFO, " refcount++ for GetTexture(%s)\n", strTextureName.c_str()); for (int i = 0; i < (int)m_vecTextures.size(); ++i) { CTextureMap *pMap = m_vecTextures[i]; if (pMap->GetName() == strTextureName) { //CLog::Log(LOGDEBUG, "Total memusage %u", GetMemoryUsage()); return pMap->GetTexture(); } } return emptyTexture; } /************************************************************************/ /* */ /************************************************************************/ bool CGUITextureManager::CanLoad(const CStdString &texturePath) const { if (texturePath == "-") 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 CUtil::IsHD(texturePath); } bool CGUITextureManager::HasTexture(const CStdString &textureName, CStdString *path, int *bundle, int *size) { // default values if (bundle) *bundle = -1; if (size) *size = 0; if (path) *path = textureName; if (!CanLoad(textureName)) return false; // Check our loaded and bundled textures - we store in bundles using \\. CStdString 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; } } CStdString fullPath = GetTexturePath(textureName); if (path) *path = fullPath; return !fullPath.IsEmpty(); } int CGUITextureManager::Load(const CStdString& strTextureName, bool checkBundleOnly /*= false */) { CStdString strPath; int bundle = -1; int size = 0; if (!HasTexture(strTextureName, &strPath, &bundle, &size)) return 0; if (size) // we found the texture return size; if (checkBundleOnly && bundle == -1) return 0; //Lock here, we will do stuff that could break rendering CSingleLock lock(g_graphicsContext); #ifdef _DEBUG int64_t start; start = CurrentHostCounter(); #endif if (strPath.Right(4).ToLower() == ".gif") { CTextureMap* pMap; if (bundle >= 0) { CBaseTexture **pTextures; int nLoops = 0, width = 0, height = 0; int* Delay; int nImages = m_TexBundle[bundle].LoadAnim(strTextureName, &pTextures, width, height, nLoops, &Delay); if (!nImages) { CLog::Log(LOGERROR, "Texture manager unable to load bundled file: %s", strTextureName.c_str()); return 0; } pMap = new CTextureMap(strTextureName, width, height, nLoops); for (int iImage = 0; iImage < nImages; ++iImage) { pMap->Add(pTextures[iImage], Delay[iImage]); } delete [] pTextures; delete [] Delay; } else { CAnimatedGifSet AnimatedGifSet; int iImages = AnimatedGifSet.LoadGIF(strPath.c_str()); if (iImages == 0) { CStdString rootPath = strPath.Left(g_SkinInfo->Path().GetLength()); if (0 == rootPath.CompareNoCase(g_SkinInfo->Path())) CLog::Log(LOGERROR, "Texture manager unable to load file: %s", strPath.c_str()); return 0; } int iWidth = AnimatedGifSet.FrameWidth; int iHeight = AnimatedGifSet.FrameHeight; // fixup our palette COLOR *palette = AnimatedGifSet.m_vecimg[0]->Palette; // set the alpha values to fully opaque for (int i = 0; i < 256; i++) palette[i].x = 0xff; // and set the transparent colour if (AnimatedGifSet.m_vecimg[0]->Transparency && AnimatedGifSet.m_vecimg[0]->Transparent >= 0) palette[AnimatedGifSet.m_vecimg[0]->Transparent].x = 0; pMap = new CTextureMap(strTextureName, iWidth, iHeight, AnimatedGifSet.nLoops); for (int iImage = 0; iImage < iImages; iImage++) { CTexture *glTexture = new CTexture(); if (glTexture) { CAnimatedGif* pImage = AnimatedGifSet.m_vecimg[iImage]; glTexture->LoadPaletted(pImage->Width, pImage->Height, pImage->BytesPerRow, XB_FMT_A8R8G8B8, (unsigned char *)pImage->Raster, palette); pMap->Add(glTexture, pImage->Delay); } } // of for (int iImage=0; iImage < iImages; iImage++) } #ifdef _DEBUG int64_t end, freq; end = CurrentHostCounter(); freq = CurrentHostFrequency(); char temp[200]; sprintf(temp, "Load %s: %.1fms%s\n", strPath.c_str(), 1000.f * (end - start) / freq, (bundle >= 0) ? " (bundled)" : ""); OutputDebugString(temp); #endif m_vecTextures.push_back(pMap); return 1; } // of if (strPath.Right(4).ToLower()==".gif") CBaseTexture *pTexture = NULL; int width = 0, height = 0; if (bundle >= 0) { if (FAILED(m_TexBundle[bundle].LoadTexture(strTextureName, &pTexture, width, height))) { CLog::Log(LOGERROR, "Texture manager unable to load bundled file: %s", strTextureName.c_str()); return 0; } } else { pTexture = new CTexture(); if(!pTexture->LoadFromFile(strPath)) return 0; width = pTexture->GetWidth(); height = pTexture->GetHeight(); } if (!pTexture) return 0; CTextureMap* pMap = new CTextureMap(strTextureName, width, height, 0); pMap->Add(pTexture, 100); m_vecTextures.push_back(pMap); #ifdef _DEBUG_TEXTURES int64_t end, freq; end = CurrentHostCounter(); freq = CurrentHostFrequency(); char temp[200]; sprintf(temp, "Load %s: %.1fms%s\n", strPath.c_str(), 1000.f * (end - start) / freq, (bundle >= 0) ? " (bundled)" : ""); OutputDebugString(temp); #endif return 1; } void CGUITextureManager::ReleaseTexture(const CStdString& strTextureName) { CSingleLock lock(g_graphicsContext); 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:%s", strTextureName.c_str()); // add to our textures to free m_unusedTextures.push_back(pMap); i = m_vecTextures.erase(i); } return; } ++i; } CLog::Log(LOGWARNING, "%s: Unable to release texture %s", __FUNCTION__, strTextureName.c_str()); } void CGUITextureManager::FreeUnusedTextures() { CSingleLock lock(g_graphicsContext); for (ivecTextures i = m_unusedTextures.begin(); i != m_unusedTextures.end(); ++i) delete *i; m_unusedTextures.clear(); } void CGUITextureManager::Cleanup() { CSingleLock lock(g_graphicsContext); ivecTextures i; i = m_vecTextures.begin(); while (i != m_vecTextures.end()) { CTextureMap* pMap = *i; CLog::Log(LOGWARNING, "%s: Having to cleanup texture %s", __FUNCTION__, pMap->GetName().c_str()); delete pMap; i = m_vecTextures.erase(i); } for (int i = 0; i < 2; i++) m_TexBundle[i].Cleanup(); } void CGUITextureManager::Dump() const { CStdString strLog; strLog.Format("total texturemaps size:%i\n", m_vecTextures.size()); OutputDebugString(strLog.c_str()); for (int i = 0; i < (int)m_vecTextures.size(); ++i) { const CTextureMap* pMap = m_vecTextures[i]; if (!pMap->IsEmpty()) pMap->Dump(); } } void CGUITextureManager::Flush() { CSingleLock lock(g_graphicsContext); 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 CStdString &texturePath) { m_texturePaths.clear(); AddTexturePath(texturePath); } void CGUITextureManager::AddTexturePath(const CStdString &texturePath) { if (!texturePath.IsEmpty()) m_texturePaths.push_back(texturePath); } void CGUITextureManager::RemoveTexturePath(const CStdString &texturePath) { for (vector::iterator it = m_texturePaths.begin(); it != m_texturePaths.end(); ++it) { if (*it == texturePath) { m_texturePaths.erase(it); return; } } } CStdString CGUITextureManager::GetTexturePath(const CStdString &textureName, bool directory /* = false */) { if (CURL::IsFullPath(textureName)) return textureName; else { // texture doesn't include the full path, so check all fallbacks for (vector::iterator it = m_texturePaths.begin(); it != m_texturePaths.end(); ++it) { CStdString path = CUtil::AddFileToFolder(it->c_str(), "media"); path = CUtil::AddFileToFolder(path, textureName); if (directory) { if (XFILE::CDirectory::Exists(path)) return path; } else { if (XFILE::CFile::Exists(path)) return path; } } } return ""; } void CGUITextureManager::GetBundledTexturesFromPath(const CStdString& texturePath, std::vector &items) { m_TexBundle[0].GetTexturesFromPath(texturePath, items); if (items.empty()) m_TexBundle[1].GetTexturesFromPath(texturePath, items); }