From 0794912374c00474036dee3093d07d90cda3038c Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 5 Oct 2025 16:41:42 +0200 Subject: [PATCH] Refactor texture source to prepare for array textures --- src/client/texturesource.cpp | 215 ++++++++++++++++------------------- src/client/texturesource.h | 43 +++++-- 2 files changed, 130 insertions(+), 128 deletions(-) diff --git a/src/client/texturesource.cpp b/src/client/texturesource.cpp index 374cca13b6..f8d9cf353a 100644 --- a/src/client/texturesource.cpp +++ b/src/client/texturesource.cpp @@ -4,6 +4,7 @@ #include "texturesource.h" +#include #include #include "guiscalingfilter.h" #include "imagefilters.h" @@ -14,13 +15,38 @@ #include "util/thread.h" +struct TextureRequest +{ + std::string image; + + void print(std::ostream &to) const { + to << "image=\"" << image << "\""; + } + + bool operator==(const TextureRequest &other) const { + return image == other.image; + } + bool operator!=(const TextureRequest &other) const { + return !(*this == other); + } +}; + // Stores internal information about a texture. struct TextureInfo { + // Type the texture should have (when created) + video::E_TEXTURE_TYPE type = video::ETT_2D; + + // Name of the texture + // For standard textures this is equivalent to images[0] std::string name; + + // Name of the images that comprise this texture + // (multiple for array textures) + std::vector images; + video::ITexture *texture = nullptr; - // Stores source image names which ImageSource::generateImage used. std::set sourceImages{}; }; @@ -38,72 +64,19 @@ public: TextureSource(); virtual ~TextureSource(); - /* - Example case: - Now, assume a texture with the id 1 exists, and has the name - "stone.png^mineral1". - Then a random thread calls getTextureId for a texture called - "stone.png^mineral1^crack0". - ...Now, WTF should happen? Well: - - getTextureId strips off stuff recursively from the end until - the remaining part is found, or nothing is left when - something is stripped out - - But it is slow to search for textures by names and modify them - like that? - - ContentFeatures is made to contain ids for the basic plain - textures - - Crack textures can be slow by themselves, but the framework - must be fast. - - Example case #2: - - Assume a texture with the id 1 exists, and has the name - "stone.png^mineral_coal.png". - - Now getNodeTile() stumbles upon a node which uses - texture id 1, and determines that MATERIAL_FLAG_CRACK - must be applied to the tile - - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and - has received the current crack level 0 from the client. It - finds out the name of the texture with getTextureName(1), - appends "^crack0" to it and gets a new texture id with - getTextureId("stone.png^mineral_coal.png^crack0"). - - */ - - /* - Gets a texture id from cache or - - if main thread, generates the texture, adds to cache and returns id. - - if other thread, adds to request queue and waits for main thread. - - The id 0 points to a NULL texture. It is returned in case of error. - */ u32 getTextureId(const std::string &name); - /// @brief Finds out the name of a cached texture. - /// @note DO NOT USE IN NEW CODE std::string getTextureName(u32 id); - /* - If texture specified by the name pointed by the id doesn't - exist, create it, then return the cached texture. - - Can be called from any thread. If called from some other thread - and not found in cache, the call is queued to the main thread - for processing. - */ video::ITexture* getTexture(u32 id); - video::ITexture* getTexture(const std::string &name, u32 *id = NULL); + video::ITexture* getTexture(const std::string &name, u32 *id = nullptr); - /* - Get a texture specifically intended for mesh - application, i.e. not HUD, compositing, or other 2D - use. This texture may be a different size and may - have had additional filters applied. - */ - video::ITexture* getTextureForMesh(const std::string &name, u32 *id); + bool needFilterForMesh() const { + return mesh_filter_needed; + } - virtual Palette* getPalette(const std::string &name); + Palette *getPalette(const std::string &name); bool isKnownSourceImage(const std::string &name) { @@ -152,12 +125,16 @@ private: // (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 + // Rebuild a single texture void rebuildTexture(video::IVideoDriver *driver, TextureInfo &ti); - // Generate a texture + // Process texture request + u32 processRequestQueued(const TextureRequest &req); + + // Process texture request directly (main thread only) + u32 processRequest(const TextureRequest &req); + + // Generate standard texture u32 generateTexture(const std::string &name); // Thread-safe cache of what source images are known (true = known) @@ -172,7 +149,7 @@ private: std::mutex m_textureinfo_cache_mutex; // Queued texture fetches (to be processed by the main thread) - RequestQueue m_get_texture_queue; + RequestQueue m_get_texture_queue; // Textures that have been overwritten with other ones // but can't be deleted because the ITexture* might still be used @@ -195,7 +172,7 @@ TextureSource::TextureSource() m_main_thread = std::this_thread::get_id(); // Add a NULL TextureInfo as the first index, named "" - m_textureinfo_cache.emplace_back(TextureInfo{""}); + m_textureinfo_cache.emplace_back(TextureInfo{video::ETT_2D, "", {}}); m_name_to_id[""] = 0; // Cache some settings @@ -237,7 +214,8 @@ video::IImage *TextureSource::getOrGenerateImage(const std::string &name, { auto it = m_image_cache.find(name); if (it != m_image_cache.end()) { - source_image_names = it->second.sourceImages; + std::set copy(it->second.sourceImages); + source_image_names.merge(copy); it->second.image->grab(); return it->second.image; } @@ -248,10 +226,41 @@ video::IImage *TextureSource::getOrGenerateImage(const std::string &name, img->grab(); m_image_cache[name] = {img, tmp}; } - source_image_names = std::move(tmp); + source_image_names.merge(tmp); return img; } +u32 TextureSource::processRequestQueued(const TextureRequest &req) +{ + if (std::this_thread::get_id() == m_main_thread) { + // Generate directly + return processRequest(req); + } + + infostream << "TextureSource: Queued: "; + req.print(infostream); + infostream << std::endl; + + // We're gonna ask the result to be put into here + static thread_local decltype(m_get_texture_queue)::result_queue_type result_queue; + + // Throw a request in + m_get_texture_queue.add(req, std::this_thread::get_id(), 0, &result_queue); + + try { + // Wait for result for up to 1 seconds (empirical value) + auto result = result_queue.pop_front(1000); + + assert(result.key == req); + return result.item; + } catch (ItemNotFoundException &e) { + errorstream << "TextureSource: Waiting for texture "; + req.print(infostream); + infostream << " timed out." << std::endl; + return 0; + } +} + u32 TextureSource::getTextureId(const std::string &name) { { // See if texture already exists @@ -261,41 +270,17 @@ u32 TextureSource::getTextureId(const std::string &name) return n->second; } - // Get texture - if (std::this_thread::get_id() == m_main_thread) { - return generateTexture(name); - } + TextureRequest req{name}; - - infostream << "getTextureId(): Queued: name=\"" << name << "\"" << std::endl; - - // We're gonna ask the result to be put into here - static thread_local ResultQueue result_queue; - - // Throw a request in - m_get_texture_queue.add(name, std::this_thread::get_id(), 0, &result_queue); - - try { - while(true) { - // Wait for result for up to 1 seconds (empirical value) - GetResult - result = result_queue.pop_front(1000); - - if (result.key == name) { - return result.item; - } - } - } catch(ItemNotFoundException &e) { - errorstream << "Waiting for texture " << name << " timed out." << std::endl; - return 0; - } - - infostream << "getTextureId(): Failed" << std::endl; - - return 0; + return processRequestQueued(req); +} + +u32 TextureSource::processRequest(const TextureRequest &req) +{ + // No different types yet (TODO) + return generateTexture(req.image); } -// This method generates all the textures u32 TextureSource::generateTexture(const std::string &name) { // Empty name means texture 0 @@ -312,11 +297,7 @@ u32 TextureSource::generateTexture(const std::string &name) } // Calling only allowed from main thread - if (std::this_thread::get_id() != m_main_thread) { - errorstream << "TextureSource::generateTexture() " - "called not from main thread" << std::endl; - return 0; - } + sanity_check(std::this_thread::get_id() == m_main_thread); video::IVideoDriver *driver = RenderingEngine::get_video_driver(); sanity_check(driver); @@ -339,7 +320,7 @@ u32 TextureSource::generateTexture(const std::string &name) MutexAutoLock lock(m_textureinfo_cache_mutex); u32 id = m_textureinfo_cache.size(); - TextureInfo ti{name, tex, std::move(source_image_names)}; + TextureInfo ti{video::ETT_2D, name, {name}, tex, std::move(source_image_names)}; m_textureinfo_cache.emplace_back(std::move(ti)); m_name_to_id[name] = id; @@ -379,14 +360,6 @@ video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id) return getTexture(actual_id); } -video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id) -{ - // Avoid duplicating texture if it won't actually change - if (mesh_filter_needed && !name.empty()) - return getTexture(name + "^[applyfiltersformesh", id); - return getTexture(name, id); -} - Palette* TextureSource::getPalette(const std::string &name) { // Only the main thread may load images @@ -445,13 +418,10 @@ Palette* TextureSource::getPalette(const std::string &name) void TextureSource::processQueue() { - // Fetch textures - // NOTE: process outstanding requests from all mesh generation threads while (!m_get_texture_queue.empty()) { - GetRequest - request = m_get_texture_queue.pop(); + auto request = m_get_texture_queue.pop(); - m_get_texture_queue.pushResult(request, generateTexture(request.key)); + m_get_texture_queue.pushResult(request, processRequest(request.key)); } } @@ -518,6 +488,15 @@ void TextureSource::rebuildTexture(video::IVideoDriver *driver, TextureInfo &ti) assert(!ti.name.empty()); sanity_check(std::this_thread::get_id() == m_main_thread); + if (ti.type != video::ETT_2D) { + // It's unclear how this idea is supposed to work with array textures, + // since after a rebuild the dimensions of some images can mismatch + // so that creating an array is no longer possible. + infostream << "TextureSource::rebuildTexture(): " + "Refusing to rebuild array texture" << std::endl; + return; + } + std::set source_image_names; video::IImage *img = getOrGenerateImage(ti.name, source_image_names); diff --git a/src/client/texturesource.h b/src/client/texturesource.h index d89fd16897..445885e1fb 100644 --- a/src/client/texturesource.h +++ b/src/client/texturesource.h @@ -18,7 +18,15 @@ namespace video typedef std::vector Palette; /* - TextureSource creates and caches textures. + TextureSource creates and caches textures, which are created from images. + + Terminology: + texture string = e.g. "dirt.png^grass_side.png" + texture name = can be the same as the texture string + or something abstract like "" + texture ID = unique numeric identifier for a texture + standard texture = refers to a normal 2D texture as you would expect. + depending on the support, 2D array textures can exist too. */ class ISimpleTextureSource @@ -27,7 +35,7 @@ public: ISimpleTextureSource() = default; virtual ~ISimpleTextureSource() = default; - /// @brief Generates and gets a texture + /// @brief Generates a texture string into a standard texture virtual video::ITexture *getTexture( const std::string &name, u32 *id = nullptr) = 0; }; @@ -40,35 +48,50 @@ public: using ISimpleTextureSource::getTexture; - /// @brief Generates and gets ID of a texture - virtual u32 getTextureId(const std::string &name)=0; + /// @brief Generates a texture string into a standard texture + /// @return its ID + virtual u32 getTextureId(const std::string &image)=0; /// @brief Returns name of existing texture by ID + /// @warning Use sparingly. Mostly useful for debugging. virtual std::string getTextureName(u32 id)=0; /// @brief Returns existing texture by ID virtual video::ITexture *getTexture(u32 id)=0; + /// @return true if getTextureForMesh will apply a filter + virtual bool needFilterForMesh() const = 0; + /** - * @brief Generates and gets a texture + * @brief Generates a texture string into a standard texture * Filters will be applied to make the texture suitable for mipmapping and * linear filtering during rendering. + * @return its ID */ - virtual video::ITexture *getTextureForMesh( - const std::string &name, u32 *id = nullptr) = 0; + inline video::ITexture *getTextureForMesh( + const std::string &image, u32 *id = nullptr) + { + if (needFilterForMesh() && !image.empty()) + return getTexture(image + FILTER_FOR_MESH, id); + return getTexture(image, id); + } + + /// Filter needed for mesh-suitable textures, including leading ^ + static constexpr const char *FILTER_FOR_MESH = "^[applyfiltersformesh"; + /** - * Returns a palette from the given texture name. + * Returns a palette from the given texture string. * The pointer is valid until the texture source is * destructed. * Must be called from the main thread. */ - virtual Palette *getPalette(const std::string &name) = 0; + virtual Palette *getPalette(const std::string &image) = 0; /// @brief Check if given image name exists virtual bool isKnownSourceImage(const std::string &name)=0; /// @brief Return average color of a texture string - virtual video::SColor getTextureAverageColor(const std::string &name)=0; + virtual video::SColor getTextureAverageColor(const std::string &image)=0; // Note: this method is here because caching is the decision of the // API user, even if his access is read-only.