1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00

Add caching of generated textures as image

This commit is contained in:
sfan5 2025-04-20 14:27:14 +02:00
parent 9cb78f2dc5
commit 486fb7cc4d
3 changed files with 86 additions and 20 deletions

View file

@ -24,6 +24,13 @@ struct TextureInfo
std::set<std::string> sourceImages{};
};
// Stores internal information about a texture image.
struct ImageInfo
{
video::IImage *image = nullptr;
std::set<std::string> sourceImages;
};
// TextureSource
class TextureSource final : public IWritableTextureSource
{
@ -123,7 +130,13 @@ public:
video::SColor getTextureAverageColor(const std::string &name);
void setImageCaching(bool enabled);
private:
// Gets or generates an image for a texture string
// Caller needs to drop the returned image
video::IImage *getOrGenerateImage(const std::string &name,
std::set<std::string> &source_image_names);
// The id of the thread that is allowed to use irrlicht directly
std::thread::id m_main_thread;
@ -132,6 +145,12 @@ private:
// This should be only accessed from the main thread
ImageSource m_imagesource;
// Is the image cache enabled?
bool m_image_cache_enabled = false;
// Caches finished texture images before they are uploaded to the GPU
// (main thread use only)
std::unordered_map<std::string, ImageInfo> m_image_cache;
// Rebuild images and textures from the current set of source images
// Shall be called from the main thread.
// You ARE expected to be holding m_textureinfo_cache_mutex
@ -193,15 +212,18 @@ TextureSource::~TextureSource()
video::IVideoDriver *driver = RenderingEngine::get_video_driver();
u32 textures_before = driver->getTextureCount();
for (const auto &it : m_image_cache) {
assert(it.second.image);
it.second.image->drop();
}
for (const auto &iter : m_textureinfo_cache) {
// cleanup texture
if (iter.texture)
driver->removeTexture(iter.texture);
}
m_textureinfo_cache.clear();
for (auto t : m_texture_trash) {
// cleanup trashed texture
driver->removeTexture(t);
}
@ -209,6 +231,26 @@ TextureSource::~TextureSource()
<< " after: " << driver->getTextureCount() << std::endl;
}
video::IImage *TextureSource::getOrGenerateImage(const std::string &name,
std::set<std::string> &source_image_names)
{
auto it = m_image_cache.find(name);
if (it != m_image_cache.end()) {
source_image_names = it->second.sourceImages;
it->second.image->grab();
return it->second.image;
}
std::set<std::string> tmp;
auto *img = m_imagesource.generateImage(name, tmp);
if (img && m_image_cache_enabled) {
img->grab();
m_image_cache[name] = {img, tmp};
}
source_image_names = std::move(tmp);
return img;
}
u32 TextureSource::getTextureId(const std::string &name)
{
{ // See if texture already exists
@ -280,7 +322,7 @@ u32 TextureSource::generateTexture(const std::string &name)
// passed into texture info for dynamic media tracking
std::set<std::string> source_image_names;
video::IImage *img = m_imagesource.generateImage(name, source_image_names);
video::IImage *img = getOrGenerateImage(name, source_image_names);
video::ITexture *tex = nullptr;
@ -356,7 +398,7 @@ Palette* TextureSource::getPalette(const std::string &name)
if (it == m_palettes.end()) {
// Create palette
std::set<std::string> source_image_names; // unused, sadly.
video::IImage *img = m_imagesource.generateImage(name, source_image_names);
video::IImage *img = getOrGenerateImage(name, source_image_names);
if (!img) {
warningstream << "TextureSource::getPalette(): palette \"" << name
<< "\" could not be loaded." << std::endl;
@ -458,6 +500,8 @@ void TextureSource::rebuildImagesAndTextures()
infostream << "TextureSource: recreating " << m_textureinfo_cache.size()
<< " textures" << std::endl;
assert(!m_image_cache_enabled || m_image_cache.empty());
// Recreate textures
for (TextureInfo &ti : m_textureinfo_cache) {
if (ti.name.empty())
@ -474,7 +518,7 @@ void TextureSource::rebuildTexture(video::IVideoDriver *driver, TextureInfo &ti)
sanity_check(std::this_thread::get_id() == m_main_thread);
std::set<std::string> source_image_names;
video::IImage *img = m_imagesource.generateImage(ti.name, source_image_names);
video::IImage *img = getOrGenerateImage(ti.name, source_image_names);
// Create texture from resulting image
video::ITexture *t = nullptr, *t_old = ti.texture;
@ -510,14 +554,10 @@ void TextureSource::rebuildTexture(video::IVideoDriver *driver, TextureInfo &ti)
video::SColor TextureSource::getTextureAverageColor(const std::string &name)
{
video::IVideoDriver *driver = RenderingEngine::get_video_driver();
video::ITexture *texture = getTexture(name);
if (!texture)
return {0, 0, 0, 0};
// Note: this downloads the texture back from the GPU, which is pointless
video::IImage *image = driver->createImage(texture,
core::position2d<s32>(0, 0),
texture->getOriginalSize());
assert(std::this_thread::get_id() == m_main_thread);
std::set<std::string> unused;
auto *image = getOrGenerateImage(name, unused);
if (!image)
return {0, 0, 0, 0};
@ -526,3 +566,15 @@ video::SColor TextureSource::getTextureAverageColor(const std::string &name)
return c;
}
void TextureSource::setImageCaching(bool enabled)
{
m_image_cache_enabled = enabled;
if (!enabled) {
for (const auto &it : m_image_cache) {
assert(it.second.image);
it.second.image->drop();
}
m_image_cache.clear();
}
}

View file

@ -69,6 +69,17 @@ public:
/// @brief Return average color of a texture string
virtual video::SColor getTextureAverageColor(const std::string &name)=0;
// Note: this method is here because caching is the decision of the
// API user, even if his access is read-only.
/**
* Enables or disables the caching of finished texture images.
* This can be useful if you want to call getTextureAverageColor without
* duplicating work.
* @note Disabling caching will flush the cache.
*/
virtual void setImageCaching(bool enabled) {};
};
class IWritableTextureSource : public ITextureSource
@ -88,8 +99,8 @@ public:
/**
* Rebuilds all textures (in case-source images have changed)
* @note This won't invalidate old ITexture's, but you have to retrieve them
* again to see changes.
* @note This won't invalidate old ITexture's, but may or may not reuse them.
* So you have to re-get all textures anyway.
*/
virtual void rebuildImagesAndTextures()=0;
};

View file

@ -761,10 +761,6 @@ static bool isWorldAligned(AlignStyle style, WorldAlignMode mode, NodeDrawType d
void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc,
scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings)
{
// minimap pixel color - the average color of a texture
if (tsettings.enable_minimap && !tiledef[0].name.empty())
minimap_color = tsrc->getTextureAverageColor(tiledef[0].name);
// Figure out the actual tiles to use
TileDef tdef[6];
for (u32 j = 0; j < 6; j++) {
@ -909,6 +905,10 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
u32 overlay_shader = shdsrc->getShader("nodes_shader", overlay_material, drawtype);
// minimap pixel color = average color of top tile
if (tsettings.enable_minimap && !tdef[0].name.empty() && drawtype != NDT_AIRLIKE)
minimap_color = tsrc->getTextureAverageColor(tdef[0].name);
// Tiles (fill in f->tiles[])
bool any_polygon_offset = false;
for (u16 j = 0; j < 6; j++) {
@ -1457,13 +1457,16 @@ void NodeDefManager::updateTextures(IGameDef *gamedef, void *progress_callback_a
TextureSettings tsettings;
tsettings.readSettings();
u32 size = m_content_features.size();
tsrc->setImageCaching(true);
u32 size = m_content_features.size();
for (u32 i = 0; i < size; i++) {
ContentFeatures *f = &(m_content_features[i]);
f->updateTextures(tsrc, shdsrc, meshmanip, client, tsettings);
client->showUpdateProgressTexture(progress_callback_args, i, size);
}
tsrc->setImageCaching(false);
#endif
}