/* * 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 "threads/SystemClock.h" #include "GUILargeTextureManager.h" #include "settings/Settings.h" #include "guilib/Texture.h" #include "threads/SingleLock.h" #include "utils/TimeUtils.h" #include "utils/JobManager.h" #include "windowing/GraphicContext.h" #include "utils/log.h" #include "TextureCache.h" #include CImageLoader::CImageLoader(const std::string &path, const bool useCache): m_path(path) { m_texture = NULL; m_use_cache = useCache; } CImageLoader::~CImageLoader() { delete(m_texture); } bool CImageLoader::DoWork() { bool needsChecking = false; std::string loadPath; std::string texturePath = CServiceBroker::GetGUI()->GetTextureManager().GetTexturePath(m_path); if (texturePath.empty()) return false; if (m_use_cache) loadPath = CTextureCache::GetInstance().CheckCachedImage(texturePath, needsChecking); else loadPath = texturePath; if (!loadPath.empty()) { // direct route - load the image unsigned int start = XbmcThreads::SystemClockMillis(); m_texture = CBaseTexture::LoadFromFile(loadPath, CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()); if (XbmcThreads::SystemClockMillis() - start > 100) CLog::Log(LOGDEBUG, "%s - took %u ms to load %s", __FUNCTION__, XbmcThreads::SystemClockMillis() - start, loadPath.c_str()); if (m_texture) { if (needsChecking) CTextureCache::GetInstance().BackgroundCacheImage(texturePath); return true; } // Fallthrough on failure: CLog::Log(LOGERROR, "%s - Direct texture file loading failed for %s", __FUNCTION__, loadPath.c_str()); } if (!m_use_cache) return false; // We're done // not in our texture cache or it failed to load from it, so try and load directly and then cache the result CTextureCache::GetInstance().CacheImage(texturePath, &m_texture); return (m_texture != NULL); } CGUILargeTextureManager::CLargeTexture::CLargeTexture(const std::string &path): m_path(path) { m_refCount = 1; m_timeToDelete = 0; } CGUILargeTextureManager::CLargeTexture::~CLargeTexture() { assert(m_refCount == 0); m_texture.Free(); } void CGUILargeTextureManager::CLargeTexture::AddRef() { m_refCount++; } bool CGUILargeTextureManager::CLargeTexture::DecrRef(bool deleteImmediately) { assert(m_refCount); m_refCount--; if (m_refCount == 0) { if (deleteImmediately) delete this; else m_timeToDelete = CTimeUtils::GetFrameTime() + TIME_TO_DELETE; return true; } return false; } bool CGUILargeTextureManager::CLargeTexture::DeleteIfRequired(bool deleteImmediately) { if (m_refCount == 0 && (deleteImmediately || m_timeToDelete < CTimeUtils::GetFrameTime())) { delete this; return true; } return false; } void CGUILargeTextureManager::CLargeTexture::SetTexture(CBaseTexture* texture) { assert(!m_texture.size()); if (texture) m_texture.Set(texture, texture->GetWidth(), texture->GetHeight()); } CGUILargeTextureManager::CGUILargeTextureManager() = default; CGUILargeTextureManager::~CGUILargeTextureManager() = default; void CGUILargeTextureManager::CleanupUnusedImages(bool immediately) { CSingleLock lock(m_listSection); // check for items to remove from allocated list, and remove listIterator it = m_allocated.begin(); while (it != m_allocated.end()) { CLargeTexture *image = *it; if (image->DeleteIfRequired(immediately)) it = m_allocated.erase(it); else ++it; } } // if available, increment reference count, and return the image. // else, add to the queue list if appropriate. bool CGUILargeTextureManager::GetImage(const std::string &path, CTextureArray &texture, bool firstRequest, const bool useCache) { CSingleLock lock(m_listSection); for (listIterator it = m_allocated.begin(); it != m_allocated.end(); ++it) { CLargeTexture *image = *it; if (image->GetPath() == path) { if (firstRequest) image->AddRef(); texture = image->GetTexture(); return texture.size() > 0; } } if (firstRequest) QueueImage(path, useCache); return true; } void CGUILargeTextureManager::ReleaseImage(const std::string &path, bool immediately) { CSingleLock lock(m_listSection); for (listIterator it = m_allocated.begin(); it != m_allocated.end(); ++it) { CLargeTexture *image = *it; if (image->GetPath() == path) { if (image->DecrRef(immediately) && immediately) m_allocated.erase(it); return; } } for (queueIterator it = m_queued.begin(); it != m_queued.end(); ++it) { unsigned int id = it->first; CLargeTexture *image = it->second; if (image->GetPath() == path && image->DecrRef(true)) { // cancel this job CJobManager::GetInstance().CancelJob(id); m_queued.erase(it); return; } } } // queue the image, and start the background loader if necessary void CGUILargeTextureManager::QueueImage(const std::string &path, bool useCache) { if (path.empty()) return; CSingleLock lock(m_listSection); for (queueIterator it = m_queued.begin(); it != m_queued.end(); ++it) { CLargeTexture *image = it->second; if (image->GetPath() == path) { image->AddRef(); return; // already queued } } // queue the item CLargeTexture *image = new CLargeTexture(path); unsigned int jobID = CJobManager::GetInstance().AddJob(new CImageLoader(path, useCache), this, CJob::PRIORITY_NORMAL); m_queued.push_back(std::make_pair(jobID, image)); } void CGUILargeTextureManager::OnJobComplete(unsigned int jobID, bool success, CJob *job) { // see if we still have this job id CSingleLock lock(m_listSection); for (queueIterator it = m_queued.begin(); it != m_queued.end(); ++it) { if (it->first == jobID) { // found our job CImageLoader *loader = static_cast(job); CLargeTexture *image = it->second; image->SetTexture(loader->m_texture); loader->m_texture = NULL; // we want to keep the texture, and jobs are auto-deleted. m_queued.erase(it); m_allocated.push_back(image); return; } } }