diff --git a/src/client/texturesource.cpp b/src/client/texturesource.cpp index ad4b8a6b0..cf5752ac3 100644 --- a/src/client/texturesource.cpp +++ b/src/client/texturesource.cpp @@ -24,6 +24,13 @@ struct TextureInfo std::set sourceImages{}; }; +// Stores internal information about a texture image. +struct ImageInfo +{ + video::IImage *image = nullptr; + std::set 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 &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 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 &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 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 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 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 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(0, 0), - texture->getOriginalSize()); + assert(std::this_thread::get_id() == m_main_thread); + + std::set 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(); + } +} diff --git a/src/client/texturesource.h b/src/client/texturesource.h index db2dcb208..bb04325ff 100644 --- a/src/client/texturesource.h +++ b/src/client/texturesource.h @@ -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; }; diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 735532ea9..d4dc16a61 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -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 }