// SuperTuxKart - a fun racing game with go-kart // Copyright (C) 2017 SuperTuxKart-Team // // 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 3 // of the License, 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 this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "graphics/stk_tex_manager.hpp" #include "config/hardware_stats.hpp" #include "config/user_config.hpp" #include "graphics/central_settings.hpp" #include "graphics/materials.hpp" #include "graphics/threaded_tex_loader.hpp" #include "graphics/stk_texture.hpp" #include "io/file_manager.hpp" #include "utils/string_utils.hpp" #include "utils/log.hpp" #include // ---------------------------------------------------------------------------- STKTexManager::STKTexManager() : m_pbo(0), m_thread_size(0), m_threaded_load_textures_counter(0) { #if !(defined(SERVER_ONLY) || defined(USE_GLES2)) if (CVS->supportsThreadedTextureLoading()) { UserConfigParams::m_hq_mipmap = true; pthread_mutex_init(&m_threaded_load_textures_mutex, NULL); pthread_cond_init(&m_cond_request, NULL); m_thread_size = HardwareStats::getNumProcessors(); m_thread_size = core::clamp(m_thread_size, 1, 8); static const unsigned max_pbo_size = 128 * 1024 * 1024; const unsigned each_capacity = max_pbo_size / m_thread_size; Log::info("STKTexManager", "%d thread(s) for texture loading," " each capacity %d MB", m_thread_size, each_capacity / 1024 / 1024); glGenBuffers(1, &m_pbo); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo); glBufferStorage(GL_PIXEL_UNPACK_BUFFER, max_pbo_size, NULL, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); uint8_t* pbo_ptr = (uint8_t*)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, max_pbo_size, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); size_t offset = 0; for (int i = 0; i < m_thread_size; i++) { m_all_tex_loaders.push_back(new ThreadedTexLoader(each_capacity, offset, pbo_ptr + offset, &m_threaded_load_textures_mutex, &m_cond_request, this)); offset += each_capacity; } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } #endif } // STKTexManager // ---------------------------------------------------------------------------- STKTexManager::~STKTexManager() { removeTexture(NULL/*texture*/, true/*remove_all*/); #if !(defined(SERVER_ONLY) || defined(USE_GLES2)) if (CVS->supportsThreadedTextureLoading()) { STKTexture* delete_ttl = new STKTexture((uint8_t*)NULL, "delete_ttl", 0, false, true); for (int i = 0; i < m_thread_size; i++) addThreadedLoadTexture(delete_ttl); for (int i = 0; i < m_thread_size; i++) { if (!m_all_tex_loaders[i]->waitForReadyToDeleted(2.0f)) { Log::info("STKTexManager", "ThreadedTexLoader %d not stopping," "exiting anyway.", i); } delete m_all_tex_loaders[i]; } delete delete_ttl; glDeleteBuffers(1, &m_pbo); pthread_mutex_destroy(&m_threaded_load_textures_mutex); pthread_cond_destroy(&m_cond_request); } #endif } // ~STKTexManager // ---------------------------------------------------------------------------- STKTexture* STKTexManager::findTextureInFileSystem(const std::string& filename, std::string* full_path) { io::path relative_path = file_manager->searchTexture(filename).c_str(); if (relative_path.empty()) { if (!m_texture_error_message.empty()) Log::error("STKTexManager", "%s", m_texture_error_message.c_str()); Log::error("STKTexManager", "Failed to load %s.", filename.c_str()); return NULL; } *full_path = file_manager->getFileSystem()->getAbsolutePath(relative_path).c_str(); for (auto p : m_all_textures) { if (p.second == NULL) continue; if (*full_path == p.first) return p.second; } return NULL; } // findTextureInFileSystem // ---------------------------------------------------------------------------- video::ITexture* STKTexManager::getTexture(const std::string& path, bool srgb, bool premul_alpha, bool set_material, bool mesh_tex, bool no_upload, bool single_channel, bool create_if_unfound) { auto ret = m_all_textures.find(path); if (!no_upload && ret != m_all_textures.end()) return ret->second; STKTexture* new_texture = NULL; std::string full_path; if (path.find('/') == std::string::npos) { new_texture = findTextureInFileSystem(path, &full_path); if (full_path.empty()) return NULL; if (!no_upload && new_texture) return new_texture; } if (create_if_unfound) { new_texture = new STKTexture(full_path.empty() ? path : full_path, srgb, premul_alpha, set_material, mesh_tex, no_upload, single_channel); if (new_texture->getOpenGLTextureName() == 0 && !no_upload) { const char* name = new_texture->getName().getPtr(); if (!m_texture_error_message.empty()) { Log::error("STKTexManager", "%s", m_texture_error_message.c_str()); } Log::error("STKTexManager", "Texture %s not found or invalid.", name); m_all_textures[name] = NULL; delete new_texture; return NULL; } if (new_texture->useThreadedLoading()) { addThreadedLoadTexture(new_texture); checkThreadedLoadTextures(false/*util_queue_empty*/); } } if (create_if_unfound && !no_upload) addTexture(new_texture); return new_texture; } // getTexture // ---------------------------------------------------------------------------- video::ITexture* STKTexManager::addTexture(STKTexture* texture) { m_all_textures[texture->getName().getPtr()] = texture; return texture; } // addTexture // ---------------------------------------------------------------------------- void STKTexManager::removeTexture(STKTexture* texture, bool remove_all) { #ifdef DEBUG std::vector undeleted_texture; #endif auto p = m_all_textures.begin(); while (p != m_all_textures.end()) { if (remove_all || p->second == texture) { if (remove_all && p->second == NULL) { p = m_all_textures.erase(p); continue; } #ifdef DEBUG if (remove_all && p->second->getReferenceCount() != 1) undeleted_texture.push_back(p->second->getName().getPtr()); #endif p->second->drop(); p = m_all_textures.erase(p); } else { p++; } } #ifdef DEBUG if (!remove_all) return; for (const std::string& s : undeleted_texture) { Log::error("STKTexManager", "%s undeleted!", s.c_str()); } #endif } // removeTexture // ---------------------------------------------------------------------------- void STKTexManager::dumpAllTexture(bool mesh_texture) { for (auto p : m_all_textures) { if (!p.second || (mesh_texture && !p.second->isMeshTexture())) continue; Log::info("STKTexManager", "%s size: %0.2fK", p.first.c_str(), float(p.second->getTextureSize()) / 1024); } } // dumpAllTexture // ---------------------------------------------------------------------------- int STKTexManager::dumpTextureUsage() { int size = 0; for (auto p : m_all_textures) { if (p.second == NULL) continue; size += p.second->getTextureSize() / 1024 / 1024; } Log::info("STKTexManager", "Total %dMB", size); return size; } // dumpAllTexture // ---------------------------------------------------------------------------- video::ITexture* STKTexManager::getUnicolorTexture(const irr::video::SColor &c) { std::string name = StringUtils::toString(c.color) + "unic"; auto ret = m_all_textures.find(name); if (ret != m_all_textures.end()) return ret->second; uint8_t* data = new uint8_t[2 * 2 * 4]; memcpy(data, &c.color, sizeof(video::SColor)); memcpy(data + 4, &c.color, sizeof(video::SColor)); memcpy(data + 8, &c.color, sizeof(video::SColor)); memcpy(data + 12, &c.color, sizeof(video::SColor)); return addTexture(new STKTexture(data, name, 2)); } // getUnicolorTexture // ---------------------------------------------------------------------------- core::stringw STKTexManager::reloadTexture(const irr::core::stringw& name) { core::stringw result; #ifndef SERVER_ONLY if (CVS->isTextureCompressionEnabled()) return L"Please disable texture compression for reloading textures."; if (name.empty()) { for (auto p : m_all_textures) { if (p.second == NULL || !p.second->isMeshTexture()) continue; p.second->reload(); if (p.second->useThreadedLoading()) { addThreadedLoadTexture(p.second); } Log::info("STKTexManager", "%s reloaded", p.second->getName().getPtr()); } return L"All textures reloaded."; } core::stringw list = name; list.make_lower().replace(L'\u005C', L'\u002F'); std::vector names = StringUtils::split(StringUtils::wideToUtf8(list), ';'); for (const std::string& fname : names) { for (auto p : m_all_textures) { if (p.second == NULL || !p.second->isMeshTexture()) continue; std::string tex_path = StringUtils::toLowerCase(p.second->getName().getPtr()); std::string tex_name = StringUtils::getBasename(tex_path); if (fname == tex_name || fname == tex_path) { p.second->reload(); if (p.second->useThreadedLoading()) { addThreadedLoadTexture(p.second); } result += tex_name.c_str(); result += L" "; break; } } } if (result.empty()) return L"Texture(s) not found!"; #endif // !SERVER_ONLY return result + "reloaded."; } // reloadTexture // ---------------------------------------------------------------------------- void STKTexManager::reset() { #if !(defined(SERVER_ONLY) || defined(USE_GLES2)) if (!CVS->isAZDOEnabled()) return; for (auto p : m_all_textures) { if (p.second == NULL) continue; p.second->unloadHandle(); } // Driver seems to crash if texture handles are not cleared... ObjectPass1Shader::getInstance()->recreateTrilinearSampler(0); #endif } // reset // ---------------------------------------------------------------------------- /** Sets an error message to be displayed when a texture is not found. This * error message is shown before the "Texture %s not found or invalid" * message. It can be used to supply additional details like what kart is * currently being loaded. * \param error Error message, potentially with a '%' which will be replaced * with detail. * \param detail String to replace a '%' in the error message. */ void STKTexManager::setTextureErrorMessage(const std::string &error, const std::string &detail) { if (detail=="") m_texture_error_message = error; else m_texture_error_message = StringUtils::insertValues(error, detail); } // setTextureErrorMessage // ---------------------------------------------------------------------------- void STKTexManager::checkThreadedLoadTextures(bool util_queue_empty) { #if !(defined(SERVER_ONLY) || defined(USE_GLES2)) if (!CVS->supportsThreadedTextureLoading()) return; bool uploaded = false; bool empty_queue = false; if (util_queue_empty) { while (true) { pthread_mutex_lock(&m_threaded_load_textures_mutex); empty_queue = m_threaded_load_textures_counter == 0; pthread_mutex_unlock(&m_threaded_load_textures_mutex); if (empty_queue) { for (ThreadedTexLoader* ttl : m_all_tex_loaders) { if (ttl->lastQueueReady()) { ttl->lock(); ttl->setFinishLoading(); uploaded = true; ttl->unlock(false/*finish_it*/); } } break; } else { checkThreadedLoadTextures(false/*util_queue_empty*/); } } } if (empty_queue && !uploaded) return; uploaded = false; for (ThreadedTexLoader* ttl : m_all_tex_loaders) { ttl->lock(); if (ttl->finishedLoading()) { if (!uploaded) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_pbo); uploaded = true; } ttl->handleCompletedTextures(); } else { ttl->unlock(false/*finish_it*/); } } if (uploaded) { GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); GLenum reason = glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, 0); if (reason != GL_ALREADY_SIGNALED) { do { reason = glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, 1000000); } while (reason == GL_TIMEOUT_EXPIRED); } glDeleteSync(sync); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); for (ThreadedTexLoader* ttl : m_all_tex_loaders) ttl->unlock(true/*finish_it*/); } #endif } // checkThreadedLoadTextures // ---------------------------------------------------------------------------- bool STKTexManager::SmallestTexture::operator() (const video::ITexture* a, const video::ITexture* b) const { return a->getTextureSize() > b->getTextureSize(); } // SmallestTexture::operator