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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user