435 lines
15 KiB
C++
435 lines
15 KiB
C++
// 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 <algorithm>
|
|
|
|
// ----------------------------------------------------------------------------
|
|
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<std::string> 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<std::string> 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
|