1
0
mirror of https://github.com/luanti-org/luanti.git synced 2025-11-05 17:55:31 +01:00

Refactor texture source to prepare for array textures

This commit is contained in:
sfan5
2025-10-05 16:41:42 +02:00
parent e924f425f2
commit 0794912374
2 changed files with 130 additions and 128 deletions

View File

@@ -4,6 +4,7 @@
#include "texturesource.h"
#include <cassert>
#include <IVideoDriver.h>
#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<std::string> images;
video::ITexture *texture = nullptr;
// Stores source image names which ImageSource::generateImage used.
std::set<std::string> 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<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
// 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<std::string, u32, std::thread::id, u8> m_get_texture_queue;
RequestQueue<TextureRequest, u32, std::thread::id, char> 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};
return processRequestQueued(req);
}
infostream << "getTextureId(): Queued: name=\"" << name << "\"" << std::endl;
// We're gonna ask the result to be put into here
static thread_local ResultQueue<std::string, u32, std::thread::id, u8> 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<std::string, u32, std::thread::id, u8>
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;
u32 TextureSource::processRequest(const TextureRequest &req)
{
// No different types yet (TODO)
return generateTexture(req.image);
}
infostream << "getTextureId(): Failed" << std::endl;
return 0;
}
// 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<std::string, u32, std::thread::id, u8>
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<std::string> source_image_names;
video::IImage *img = getOrGenerateImage(ti.name, source_image_names);

View File

@@ -18,7 +18,15 @@ namespace video
typedef std::vector<video::SColor> 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 "<texture12>"
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.