mirror of
				https://github.com/luanti-org/luanti.git
				synced 2025-11-04 09:15:29 +01:00 
			
		
		
		
	Introduce array textures for node rendering (#16574)
This commit is contained in:
		@@ -1799,10 +1799,10 @@ struct TextureUpdateArgs {
 | 
			
		||||
	std::wstring text_base;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progress)
 | 
			
		||||
void Client::showUpdateProgressTexture(void *args, float progress)
 | 
			
		||||
{
 | 
			
		||||
	auto *targs = reinterpret_cast<TextureUpdateArgs*>(args);
 | 
			
		||||
	u16 cur_percent = std::ceil(progress * 100.f / max_progress);
 | 
			
		||||
	u16 cur_percent = std::ceil(100 * progress);
 | 
			
		||||
 | 
			
		||||
	// Throttle menu drawing
 | 
			
		||||
	bool do_draw = false;
 | 
			
		||||
@@ -1817,7 +1817,8 @@ void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progres
 | 
			
		||||
 | 
			
		||||
	std::wostringstream strm;
 | 
			
		||||
	strm << targs->text_base << L" " << cur_percent << L"%...";
 | 
			
		||||
	int shown_progress = 72 + std::ceil(0.18f * cur_percent);
 | 
			
		||||
	// 70% -> 99%
 | 
			
		||||
	int shown_progress = 70 + std::ceil(0.29f * cur_percent);
 | 
			
		||||
	m_rendering_engine->draw_load_screen(strm.str(), guienv, m_tsrc,
 | 
			
		||||
		0, shown_progress);
 | 
			
		||||
}
 | 
			
		||||
@@ -1837,19 +1838,19 @@ void Client::afterContentReceived()
 | 
			
		||||
	// Rebuild inherited images and recreate textures
 | 
			
		||||
	infostream<<"- Rebuilding images and textures"<<std::endl;
 | 
			
		||||
	m_rendering_engine->draw_load_screen(wstrgettext("Loading textures..."),
 | 
			
		||||
			guienv, m_tsrc, 0, 70);
 | 
			
		||||
			guienv, m_tsrc, 0, 66);
 | 
			
		||||
	m_tsrc->rebuildImagesAndTextures();
 | 
			
		||||
 | 
			
		||||
	// Rebuild shaders
 | 
			
		||||
	infostream<<"- Rebuilding shaders"<<std::endl;
 | 
			
		||||
	m_rendering_engine->draw_load_screen(wstrgettext("Rebuilding shaders..."),
 | 
			
		||||
			guienv, m_tsrc, 0, 71);
 | 
			
		||||
			guienv, m_tsrc, 0, 68);
 | 
			
		||||
	m_shsrc->rebuildShaders();
 | 
			
		||||
 | 
			
		||||
	// Update node aliases
 | 
			
		||||
	infostream<<"- Updating node aliases"<<std::endl;
 | 
			
		||||
	m_rendering_engine->draw_load_screen(wstrgettext("Initializing nodes..."),
 | 
			
		||||
			guienv, m_tsrc, 0, 72);
 | 
			
		||||
			guienv, m_tsrc, 0, 70);
 | 
			
		||||
	m_nodedef->updateAliases(m_itemdef);
 | 
			
		||||
	for (const auto &path : getTextureDirs()) {
 | 
			
		||||
		TextureOverrideSource override_source(path + DIR_DELIM + "override.txt");
 | 
			
		||||
 
 | 
			
		||||
@@ -357,7 +357,7 @@ public:
 | 
			
		||||
 | 
			
		||||
	void drawLoadScreen(const std::wstring &text, float dtime, int percent);
 | 
			
		||||
	void afterContentReceived();
 | 
			
		||||
	void showUpdateProgressTexture(void *args, u32 progress, u32 max_progress);
 | 
			
		||||
	void showUpdateProgressTexture(void *args, float progress);
 | 
			
		||||
 | 
			
		||||
	float getRTT();
 | 
			
		||||
	float getCurRate();
 | 
			
		||||
 
 | 
			
		||||
@@ -1088,6 +1088,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 | 
			
		||||
	u32 vertex_count = 0;
 | 
			
		||||
	u32 drawcall_count = 0;
 | 
			
		||||
	u32 material_swaps = 0;
 | 
			
		||||
	u32 array_texture_use = 0;
 | 
			
		||||
 | 
			
		||||
	// Render all mesh buffers in order
 | 
			
		||||
	drawcall_count += draw_order.size();
 | 
			
		||||
@@ -1117,8 +1118,12 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 | 
			
		||||
				layer.MagFilter = video::ETMAGF_NEAREST;
 | 
			
		||||
				layer.AnisotropicFilter = 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			driver->setMaterial(material);
 | 
			
		||||
			++material_swaps;
 | 
			
		||||
			if (auto *tex = material.getTexture(0); tex && tex->getType() == video::ETT_2D_ARRAY)
 | 
			
		||||
				++array_texture_use;
 | 
			
		||||
 | 
			
		||||
			material.TextureLayers[ShadowRenderer::TEXTURE_LAYER_SHADOW].Texture = nullptr;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -1158,6 +1163,10 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 | 
			
		||||
	g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
 | 
			
		||||
	g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
 | 
			
		||||
	g_profiler->avg(prefix + "material swaps [#]", material_swaps);
 | 
			
		||||
	if (material_swaps && array_texture_use) {
 | 
			
		||||
		int percent = (100.0f * array_texture_use) / material_swaps;
 | 
			
		||||
		g_profiler->avg(prefix + "array texture use [%]", percent);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ClientMap::invalidateMapBlockMesh(MapBlockMesh *mesh)
 | 
			
		||||
@@ -1480,14 +1489,14 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
 | 
			
		||||
 | 
			
		||||
	bool translucent_foliage = g_settings->getBool("enable_translucent_foliage");
 | 
			
		||||
 | 
			
		||||
	video::E_MATERIAL_TYPE leaves_material = video::EMT_SOLID;
 | 
			
		||||
 | 
			
		||||
	// For translucent leaves, we want to use backface culling instead of frontface.
 | 
			
		||||
	std::vector<video::E_MATERIAL_TYPE> leaves_material;
 | 
			
		||||
	if (translucent_foliage) {
 | 
			
		||||
		// this is the material leaves would use, compare to nodedef.cpp
 | 
			
		||||
		auto* shdsrc = m_client->getShaderSource();
 | 
			
		||||
		const u32 leaves_shader = shdsrc->getShader("nodes_shader", TILE_MATERIAL_WAVING_LEAVES, NDT_ALLFACES);
 | 
			
		||||
		leaves_material = shdsrc->getShaderInfo(leaves_shader).material;
 | 
			
		||||
		auto *shdsrc = m_client->getShaderSource();
 | 
			
		||||
		// Find out all materials used by leaves so we can identify them
 | 
			
		||||
		leaves_material.reserve(m_nodedef->m_leaves_materials.size());
 | 
			
		||||
		for (u32 shader_id : m_nodedef->m_leaves_materials)
 | 
			
		||||
			leaves_material.push_back(shdsrc->getShaderInfo(shader_id).material);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (auto &descriptor : draw_order) {
 | 
			
		||||
@@ -1502,7 +1511,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
 | 
			
		||||
				local_material.BackfaceCulling = material.BackfaceCulling;
 | 
			
		||||
				local_material.FrontfaceCulling = material.FrontfaceCulling;
 | 
			
		||||
			}
 | 
			
		||||
			if (local_material.MaterialType == leaves_material && translucent_foliage) {
 | 
			
		||||
			if (translucent_foliage && CONTAINS(leaves_material, local_material.MaterialType)) {
 | 
			
		||||
				local_material.BackfaceCulling = true;
 | 
			
		||||
				local_material.FrontfaceCulling = false;
 | 
			
		||||
			}
 | 
			
		||||
@@ -1522,7 +1531,8 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
 | 
			
		||||
	video::SMaterial clean;
 | 
			
		||||
	clean.BlendOperation = video::EBO_ADD;
 | 
			
		||||
	driver->setMaterial(clean); // reset material to defaults
 | 
			
		||||
	// FIXME: why is this here?
 | 
			
		||||
	// This is somehow needed to fully reset the rendering state, or later operations
 | 
			
		||||
	// will be broken.
 | 
			
		||||
	driver->draw3DLine(v3f(), v3f(), video::SColor(0));
 | 
			
		||||
 | 
			
		||||
	g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
 | 
			
		||||
 
 | 
			
		||||
@@ -212,6 +212,8 @@ static scene::SMesh *generateNodeMesh(Client *client, MapNode n,
 | 
			
		||||
		MapblockMeshGenerator(&mmd, &collector).generate();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const AlphaMode alpha_mode = ndef->get(n).alpha;
 | 
			
		||||
 | 
			
		||||
	auto mesh = make_irr<scene::SMesh>();
 | 
			
		||||
	animation.clear();
 | 
			
		||||
	for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
 | 
			
		||||
@@ -224,10 +226,8 @@ static scene::SMesh *generateNodeMesh(Client *client, MapNode n,
 | 
			
		||||
			p.applyTileColor();
 | 
			
		||||
 | 
			
		||||
			if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
 | 
			
		||||
				const FrameSpec &frame = (*p.layer.frames)[0];
 | 
			
		||||
				p.layer.texture = frame.texture;
 | 
			
		||||
 | 
			
		||||
				animation.emplace_back(MeshAnimationInfo{mesh->getMeshBufferCount(), 0, p.layer});
 | 
			
		||||
				animation.emplace_back(MeshAnimationInfo{
 | 
			
		||||
					mesh->getMeshBufferCount(), 0, p.layer});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			auto buf = make_irr<scene::SMeshBuffer>();
 | 
			
		||||
@@ -236,9 +236,8 @@ static scene::SMesh *generateNodeMesh(Client *client, MapNode n,
 | 
			
		||||
 | 
			
		||||
			// Set up material
 | 
			
		||||
			auto &mat = buf->Material;
 | 
			
		||||
			u32 shader_id = shdsrc->getShader("object_shader", p.layer.material_type, NDT_NORMAL);
 | 
			
		||||
			mat.MaterialType = shdsrc->getShaderInfo(shader_id).material;
 | 
			
		||||
			p.layer.applyMaterialOptions(mat, layer);
 | 
			
		||||
			getAdHocNodeShader(mat, shdsrc, "object_shader", alpha_mode, layer == 1);
 | 
			
		||||
 | 
			
		||||
			mesh->addMeshBuffer(buf.get());
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -1021,8 +1021,9 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
 | 
			
		||||
 | 
			
		||||
	// Optionally render internal liquid level defined by param2
 | 
			
		||||
	// Liquid is textured with 1 tile defined in nodedef 'special_tiles'
 | 
			
		||||
	if (param2 > 0 && cur_node.f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL &&
 | 
			
		||||
			cur_node.f->special_tiles[0].layers[0].texture) {
 | 
			
		||||
	auto &cf = *cur_node.f;
 | 
			
		||||
	if (param2 > 0 && cf.param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL &&
 | 
			
		||||
			!cf.special_tiles[0].layers[0].empty()) {
 | 
			
		||||
		// Internal liquid level has param2 range 0 .. 63,
 | 
			
		||||
		// convert it to -0.5 .. 0.5
 | 
			
		||||
		float vlev = (param2 / 63.0f) * 2.0f - 1.0f;
 | 
			
		||||
 
 | 
			
		||||
@@ -961,12 +961,12 @@ bool Game::createClient(const GameStartData &start_data)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Pre-calculate crack length
 | 
			
		||||
	video::ITexture *t = texture_src->getTexture("crack_anylength.png");
 | 
			
		||||
	if (t) {
 | 
			
		||||
		v2u32 size = t->getOriginalSize();
 | 
			
		||||
		crack_animation_length = size.Y / size.X;
 | 
			
		||||
	} else {
 | 
			
		||||
		crack_animation_length = 5;
 | 
			
		||||
	{
 | 
			
		||||
		auto size = texture_src->getTextureDimensions("crack_anylength.png");
 | 
			
		||||
		if (size.Width && size.Height)
 | 
			
		||||
			crack_animation_length = size.Height / size.Width;
 | 
			
		||||
		else
 | 
			
		||||
			crack_animation_length = 5;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	shader_src->addShaderConstantSetter(
 | 
			
		||||
@@ -1283,7 +1283,8 @@ bool Game::getServerContent(bool *aborted)
 | 
			
		||||
				message << " (" << cur << ' ' << cur_unit << ")";
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
 | 
			
		||||
			// 30% -> 65%
 | 
			
		||||
			progress = 30 + std::ceil(client->mediaReceiveProgress() * 35 + 0.5f);
 | 
			
		||||
			m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
 | 
			
		||||
				texture_src, dtime, progress);
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -651,9 +651,6 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data):
 | 
			
		||||
			if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
 | 
			
		||||
				// Add to MapBlockMesh in order to animate these tiles
 | 
			
		||||
				m_animation_info.emplace(std::make_pair(layer, i), AnimationInfo(p.layer));
 | 
			
		||||
				// Replace tile texture with the first animation frame
 | 
			
		||||
				assert(p.layer.frames);
 | 
			
		||||
				p.layer.texture = (*p.layer.frames)[0].texture;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Create material
 | 
			
		||||
 
 | 
			
		||||
@@ -23,10 +23,12 @@ void MeshCollector::append(const TileLayer &layer, const video::S3DVertex *verti
 | 
			
		||||
{
 | 
			
		||||
	PreMeshBuffer &p = findBuffer(layer, layernum, numVertices);
 | 
			
		||||
 | 
			
		||||
	const u16 aux = layer.texture_layer_idx;
 | 
			
		||||
 | 
			
		||||
	u32 vertex_count = p.vertices.size();
 | 
			
		||||
	for (u32 i = 0; i < numVertices; i++) {
 | 
			
		||||
		p.vertices.emplace_back(vertices[i].Pos + offset, vertices[i].Normal,
 | 
			
		||||
				vertices[i].Color, vertices[i].TCoords);
 | 
			
		||||
				vertices[i].Color, vertices[i].TCoords, aux);
 | 
			
		||||
		m_bounding_radius_sq = std::max(m_bounding_radius_sq,
 | 
			
		||||
				(vertices[i].Pos - m_center_pos).getLengthSQ());
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -42,8 +42,11 @@ static video::ITexture *extractTexture(const TileDef &def, const TileLayer &laye
 | 
			
		||||
{
 | 
			
		||||
	// If animated take first frame from tile layer (so we don't have to handle
 | 
			
		||||
	// that manually), otherwise look up by name.
 | 
			
		||||
	if (!layer.empty() && (layer.material_flags & MATERIAL_FLAG_ANIMATION))
 | 
			
		||||
		return (*layer.frames)[0].texture;
 | 
			
		||||
	if (!layer.empty() && (layer.material_flags & MATERIAL_FLAG_ANIMATION)) {
 | 
			
		||||
		auto *ret = (*layer.frames)[0].texture;
 | 
			
		||||
		assert(ret->getType() == video::ETT_2D);
 | 
			
		||||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
	if (!def.name.empty())
 | 
			
		||||
		return tsrc->getTexture(def.name);
 | 
			
		||||
	return nullptr;
 | 
			
		||||
 
 | 
			
		||||
@@ -666,9 +666,11 @@ void ShaderSource::generateShader(ShaderInfo &shaderinfo)
 | 
			
		||||
			uniform mediump mat4 mTexture;
 | 
			
		||||
 | 
			
		||||
			attribute highp vec4 inVertexPosition;
 | 
			
		||||
			attribute lowp vec4 inVertexColor;
 | 
			
		||||
			attribute mediump vec2 inTexCoord0;
 | 
			
		||||
			attribute mediump vec3 inVertexNormal;
 | 
			
		||||
			attribute lowp vec4 inVertexColor;
 | 
			
		||||
			attribute mediump float inVertexAux;
 | 
			
		||||
			attribute mediump vec2 inTexCoord0;
 | 
			
		||||
			attribute mediump vec2 inTexCoord1;
 | 
			
		||||
			attribute mediump vec4 inVertexTangent;
 | 
			
		||||
			attribute mediump vec4 inVertexBinormal;
 | 
			
		||||
		)";
 | 
			
		||||
@@ -787,11 +789,13 @@ void ShaderSource::generateShader(ShaderInfo &shaderinfo)
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
u32 IShaderSource::getShader(const std::string &name,
 | 
			
		||||
	MaterialType material_type, NodeDrawType drawtype)
 | 
			
		||||
	MaterialType material_type, NodeDrawType drawtype, bool array_texture)
 | 
			
		||||
{
 | 
			
		||||
	ShaderConstants input_const;
 | 
			
		||||
	input_const["MATERIAL_TYPE"] = (int)material_type;
 | 
			
		||||
	(void) drawtype; // unused
 | 
			
		||||
	if (array_texture)
 | 
			
		||||
		input_const["USE_ARRAY_TEXTURE"] = 1;
 | 
			
		||||
 | 
			
		||||
	video::E_MATERIAL_TYPE base_mat = video::EMT_SOLID;
 | 
			
		||||
	switch (material_type) {
 | 
			
		||||
 
 | 
			
		||||
@@ -267,7 +267,8 @@ public:
 | 
			
		||||
 | 
			
		||||
	/// @brief Helper: Generates or gets a shader suitable for nodes and entities
 | 
			
		||||
	u32 getShader(const std::string &name,
 | 
			
		||||
		MaterialType material_type, NodeDrawType drawtype = NDT_NORMAL);
 | 
			
		||||
		MaterialType material_type, NodeDrawType drawtype = NDT_NORMAL,
 | 
			
		||||
		bool array_texture = false);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Helper: Generates or gets a shader for common, general use.
 | 
			
		||||
 
 | 
			
		||||
@@ -14,17 +14,25 @@
 | 
			
		||||
#include "texturepaths.h"
 | 
			
		||||
#include "util/thread.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Represents a to-be-generated texture for queuing purposes
 | 
			
		||||
struct TextureRequest
 | 
			
		||||
{
 | 
			
		||||
	std::string image;
 | 
			
		||||
	video::E_TEXTURE_TYPE type = video::ETT_2D;
 | 
			
		||||
	std::vector<std::string> images;
 | 
			
		||||
 | 
			
		||||
	void print(std::ostream &to) const {
 | 
			
		||||
		to << "image=\"" << image << "\"";
 | 
			
		||||
		if (images.size() == 1) {
 | 
			
		||||
			to << "image=\"" << images[0] << "\"";
 | 
			
		||||
		} else {
 | 
			
		||||
			to << "images={";
 | 
			
		||||
			for (auto &image : images)
 | 
			
		||||
				to << "\"" << image << "\" ";
 | 
			
		||||
			to << "}";
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool operator==(const TextureRequest &other) const {
 | 
			
		||||
		return image == other.image;
 | 
			
		||||
		return type == other.type && images == other.images;
 | 
			
		||||
	}
 | 
			
		||||
	bool operator!=(const TextureRequest &other) const {
 | 
			
		||||
		return !(*this == other);
 | 
			
		||||
@@ -72,6 +80,9 @@ public:
 | 
			
		||||
 | 
			
		||||
	video::ITexture* getTexture(const std::string &name, u32 *id = nullptr);
 | 
			
		||||
 | 
			
		||||
	video::ITexture *addArrayTexture(
 | 
			
		||||
		const std::vector<std::string> &images, u32 *id = nullptr);
 | 
			
		||||
 | 
			
		||||
	bool needFilterForMesh() const {
 | 
			
		||||
		return mesh_filter_needed;
 | 
			
		||||
	}
 | 
			
		||||
@@ -104,6 +115,8 @@ public:
 | 
			
		||||
 | 
			
		||||
	video::SColor getTextureAverageColor(const std::string &name);
 | 
			
		||||
 | 
			
		||||
	core::dimension2du getTextureDimensions(const std::string &image);
 | 
			
		||||
 | 
			
		||||
	void setImageCaching(bool enabled);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
@@ -137,6 +150,9 @@ private:
 | 
			
		||||
	// Generate standard texture
 | 
			
		||||
	u32 generateTexture(const std::string &name);
 | 
			
		||||
 | 
			
		||||
	// Generate array texture
 | 
			
		||||
	u32 generateArrayTexture(const std::vector<std::string> &names);
 | 
			
		||||
 | 
			
		||||
	// Thread-safe cache of what source images are known (true = known)
 | 
			
		||||
	MutexedMap<std::string, bool> m_source_image_existence;
 | 
			
		||||
 | 
			
		||||
@@ -270,15 +286,97 @@ u32 TextureSource::getTextureId(const std::string &name)
 | 
			
		||||
			return n->second;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TextureRequest req{name};
 | 
			
		||||
	TextureRequest req{video::ETT_2D, {name}};
 | 
			
		||||
 | 
			
		||||
	return processRequestQueued(req);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
video::ITexture *TextureSource::addArrayTexture(
 | 
			
		||||
	const std::vector<std::string> &images, u32 *ret_id)
 | 
			
		||||
{
 | 
			
		||||
	if (images.empty())
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	TextureRequest req{video::ETT_2D_ARRAY, images};
 | 
			
		||||
 | 
			
		||||
	u32 id = processRequestQueued(req);
 | 
			
		||||
	if (ret_id)
 | 
			
		||||
		*ret_id = id;
 | 
			
		||||
	return getTexture(id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u32 TextureSource::processRequest(const TextureRequest &req)
 | 
			
		||||
{
 | 
			
		||||
	// No different types yet (TODO)
 | 
			
		||||
	return generateTexture(req.image);
 | 
			
		||||
	if (req.type == video::ETT_2D) {
 | 
			
		||||
		assert(req.images.size() == 1);
 | 
			
		||||
		return generateTexture(req.images[0]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (req.type == video::ETT_2D_ARRAY) {
 | 
			
		||||
		assert(!req.images.empty());
 | 
			
		||||
		return generateArrayTexture(req.images);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errorstream << "TextureSource::processRequest(): unknown type "
 | 
			
		||||
			<< (int)req.type << std::endl;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u32 TextureSource::generateArrayTexture(const std::vector<std::string> &images)
 | 
			
		||||
{
 | 
			
		||||
	std::set<std::string> source_image_names;
 | 
			
		||||
	std::vector<video::IImage*> imgs;
 | 
			
		||||
	const auto &drop_imgs = [&imgs] () {
 | 
			
		||||
		for (auto *img : imgs) {
 | 
			
		||||
			if (img)
 | 
			
		||||
				img->drop();
 | 
			
		||||
		}
 | 
			
		||||
		imgs.clear();
 | 
			
		||||
	};
 | 
			
		||||
	for (auto &name : images) {
 | 
			
		||||
		video::IImage *img = getOrGenerateImage(name, source_image_names);
 | 
			
		||||
		if (!img) {
 | 
			
		||||
			// Since the caller needs to make sure of the dimensions beforehand
 | 
			
		||||
			// anyway, this should not ever happen. So the "unhelpful" error is ok.
 | 
			
		||||
			errorstream << "generateArrayTexture(): one of " << images.size()
 | 
			
		||||
				<< " images failed to generate, aborting." << std::endl;
 | 
			
		||||
			drop_imgs();
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
		imgs.push_back(img);
 | 
			
		||||
	}
 | 
			
		||||
	assert(!imgs.empty());
 | 
			
		||||
 | 
			
		||||
	video::IVideoDriver *driver = RenderingEngine::get_video_driver();
 | 
			
		||||
	sanity_check(driver);
 | 
			
		||||
	assert(driver->queryFeature(video::EVDF_TEXTURE_2D_ARRAY));
 | 
			
		||||
 | 
			
		||||
	MutexAutoLock lock(m_textureinfo_cache_mutex);
 | 
			
		||||
	const u32 id = m_textureinfo_cache.size();
 | 
			
		||||
	std::string name;
 | 
			
		||||
	{ // automatically choose a name
 | 
			
		||||
		char buf[64];
 | 
			
		||||
		porting::mt_snprintf(buf, sizeof(buf), "array#%u %ux%ux%u", id,
 | 
			
		||||
			imgs[0]->getDimension().Width, imgs[0]->getDimension().Height,
 | 
			
		||||
			imgs.size());
 | 
			
		||||
		name = buf;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	video::ITexture *tex = driver->addArrayTexture(name, imgs.data(), imgs.size());
 | 
			
		||||
	drop_imgs();
 | 
			
		||||
 | 
			
		||||
	if (!tex) {
 | 
			
		||||
		warningstream << "generateArrayTexture(): failed to upload texture \""
 | 
			
		||||
				<< name << "\"" << std::endl;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add texture to caches (add NULL textures too)
 | 
			
		||||
 | 
			
		||||
	TextureInfo ti{video::ETT_2D_ARRAY, name, images, tex, std::move(source_image_names)};
 | 
			
		||||
	m_textureinfo_cache.emplace_back(std::move(ti));
 | 
			
		||||
	m_name_to_id[name] = id;
 | 
			
		||||
 | 
			
		||||
	return id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u32 TextureSource::generateTexture(const std::string &name)
 | 
			
		||||
@@ -302,7 +400,6 @@ u32 TextureSource::generateTexture(const std::string &name)
 | 
			
		||||
	video::IVideoDriver *driver = RenderingEngine::get_video_driver();
 | 
			
		||||
	sanity_check(driver);
 | 
			
		||||
 | 
			
		||||
	// passed into texture info for dynamic media tracking
 | 
			
		||||
	std::set<std::string> source_image_names;
 | 
			
		||||
	video::IImage *img = getOrGenerateImage(name, source_image_names);
 | 
			
		||||
 | 
			
		||||
@@ -314,12 +411,16 @@ u32 TextureSource::generateTexture(const std::string &name)
 | 
			
		||||
		guiScalingCache(io::path(name.c_str()), driver, img);
 | 
			
		||||
		img->drop();
 | 
			
		||||
	}
 | 
			
		||||
	if (!tex) {
 | 
			
		||||
		warningstream << "generateTexture(): failed to upload texture \""
 | 
			
		||||
				<< name << "\"" << std::endl;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add texture to caches (add NULL textures too)
 | 
			
		||||
 | 
			
		||||
	MutexAutoLock lock(m_textureinfo_cache_mutex);
 | 
			
		||||
 | 
			
		||||
	u32 id = m_textureinfo_cache.size();
 | 
			
		||||
	const u32 id = m_textureinfo_cache.size();
 | 
			
		||||
	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;
 | 
			
		||||
@@ -535,6 +636,8 @@ void TextureSource::rebuildTexture(video::IVideoDriver *driver, TextureInfo &ti)
 | 
			
		||||
video::SColor TextureSource::getTextureAverageColor(const std::string &name)
 | 
			
		||||
{
 | 
			
		||||
	assert(std::this_thread::get_id() == m_main_thread);
 | 
			
		||||
	if (name.empty())
 | 
			
		||||
		return {0, 0, 0, 0};
 | 
			
		||||
 | 
			
		||||
	std::set<std::string> unused;
 | 
			
		||||
	auto *image = getOrGenerateImage(name, unused);
 | 
			
		||||
@@ -547,6 +650,23 @@ video::SColor TextureSource::getTextureAverageColor(const std::string &name)
 | 
			
		||||
	return c;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
core::dimension2du TextureSource::getTextureDimensions(const std::string &name)
 | 
			
		||||
{
 | 
			
		||||
	assert(std::this_thread::get_id() == m_main_thread);
 | 
			
		||||
 | 
			
		||||
	core::dimension2du ret;
 | 
			
		||||
	if (!name.empty()) {
 | 
			
		||||
		std::set<std::string> unused;
 | 
			
		||||
		auto *image = getOrGenerateImage(name, unused);
 | 
			
		||||
		if (image) {
 | 
			
		||||
			ret = image->getDimension();
 | 
			
		||||
			image->drop();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TextureSource::setImageCaching(bool enabled)
 | 
			
		||||
{
 | 
			
		||||
	m_image_cache_enabled = enabled;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
 | 
			
		||||
#include "irrlichttypes.h"
 | 
			
		||||
#include <SColor.h>
 | 
			
		||||
#include <dimension2d.h>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
@@ -59,8 +60,11 @@ public:
 | 
			
		||||
	/// @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 texture string(s) into an array texture
 | 
			
		||||
	/// @note Unlike the other getters this will always add a *new* texture.
 | 
			
		||||
	/// @return its ID
 | 
			
		||||
	virtual video::ITexture *addArrayTexture(
 | 
			
		||||
		const std::vector<std::string> &images, u32 *id = nullptr) = 0;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @brief Generates a texture string into a standard texture
 | 
			
		||||
@@ -76,6 +80,9 @@ public:
 | 
			
		||||
		return getTexture(image, id);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// @return true if getTextureForMesh will apply a filter
 | 
			
		||||
	virtual bool needFilterForMesh() const = 0;
 | 
			
		||||
 | 
			
		||||
	/// Filter needed for mesh-suitable textures, including leading ^
 | 
			
		||||
	static constexpr const char *FILTER_FOR_MESH = "^[applyfiltersformesh";
 | 
			
		||||
 | 
			
		||||
@@ -90,6 +97,10 @@ public:
 | 
			
		||||
	/// @brief Check if given image name exists
 | 
			
		||||
	virtual bool isKnownSourceImage(const std::string &name)=0;
 | 
			
		||||
 | 
			
		||||
	/// @brief Return dimensions of a texture string
 | 
			
		||||
	/// (will avoid actually creating the texture)
 | 
			
		||||
	virtual core::dimension2du getTextureDimensions(const std::string &image)=0;
 | 
			
		||||
 | 
			
		||||
	/// @brief Return average color of a texture string
 | 
			
		||||
	virtual video::SColor getTextureAverageColor(const std::string &image)=0;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,21 @@ enum MaterialType : u8 {
 | 
			
		||||
	TILE_MATERIAL_PLAIN_ALPHA
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief change type so it has at least simple transparency
 | 
			
		||||
 */
 | 
			
		||||
static inline MaterialType material_type_with_alpha(MaterialType type)
 | 
			
		||||
{
 | 
			
		||||
	switch (type) {
 | 
			
		||||
		case TILE_MATERIAL_OPAQUE:
 | 
			
		||||
			return TILE_MATERIAL_BASIC;
 | 
			
		||||
		case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
 | 
			
		||||
			return TILE_MATERIAL_WAVING_LIQUID_BASIC;
 | 
			
		||||
		default:
 | 
			
		||||
			return type;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Material flags
 | 
			
		||||
// Should backface culling be enabled?
 | 
			
		||||
#define MATERIAL_FLAG_BACKFACE_CULLING 0x01
 | 
			
		||||
@@ -39,6 +54,7 @@ enum MaterialType : u8 {
 | 
			
		||||
	This fully defines the looks of a tile.
 | 
			
		||||
	The SMaterial of a tile is constructed according to this.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
struct FrameSpec
 | 
			
		||||
{
 | 
			
		||||
	FrameSpec() = default;
 | 
			
		||||
@@ -60,18 +76,18 @@ struct TileLayer
 | 
			
		||||
	TileLayer() = default;
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
	 * Two layers are equal if they can be merged.
 | 
			
		||||
	 * Two layers are equal if they can be merged (same material).
 | 
			
		||||
	 */
 | 
			
		||||
	bool operator==(const TileLayer &other) const
 | 
			
		||||
	{
 | 
			
		||||
		return
 | 
			
		||||
			texture_id == other.texture_id &&
 | 
			
		||||
			material_type == other.material_type &&
 | 
			
		||||
			shader_id == other.shader_id &&
 | 
			
		||||
			material_flags == other.material_flags &&
 | 
			
		||||
			has_color == other.has_color &&
 | 
			
		||||
			color == other.color &&
 | 
			
		||||
			scale == other.scale &&
 | 
			
		||||
			need_polygon_offset == other.need_polygon_offset;
 | 
			
		||||
		// texture_layer_idx and scale are notably part of the vertex data
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
@@ -84,7 +100,7 @@ struct TileLayer
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Set some material parameters accordingly.
 | 
			
		||||
	 * @note does not set `MaterialType`
 | 
			
		||||
	 * @note does not set `MaterialType`!
 | 
			
		||||
	 * @param material material to mody
 | 
			
		||||
	 * @param layer index of this layer in the `TileSpec`
 | 
			
		||||
	 */
 | 
			
		||||
@@ -122,12 +138,16 @@ struct TileLayer
 | 
			
		||||
	u16 animation_frame_length_ms = 0;
 | 
			
		||||
	u16 animation_frame_count = 1;
 | 
			
		||||
 | 
			
		||||
	/// Layer index to use, if the texture is an array texture
 | 
			
		||||
	u16 texture_layer_idx = 0;
 | 
			
		||||
 | 
			
		||||
	MaterialType material_type = TILE_MATERIAL_BASIC;
 | 
			
		||||
	u8 material_flags =
 | 
			
		||||
		MATERIAL_FLAG_BACKFACE_CULLING |
 | 
			
		||||
		MATERIAL_FLAG_TILEABLE_HORIZONTAL|
 | 
			
		||||
		MATERIAL_FLAG_TILEABLE_VERTICAL;
 | 
			
		||||
 | 
			
		||||
	/// Texture scale in both directions (used for world-align)
 | 
			
		||||
	u8 scale = 1;
 | 
			
		||||
 | 
			
		||||
	/// does this tile need to have a positive polygon offset set?
 | 
			
		||||
 
 | 
			
		||||
@@ -125,55 +125,32 @@ static video::ITexture *extractTexture(const TileDef &def, const TileLayer &laye
 | 
			
		||||
{
 | 
			
		||||
	// If animated take first frame from tile layer (so we don't have to handle
 | 
			
		||||
	// that manually), otherwise look up by name.
 | 
			
		||||
	if (!layer.empty() && (layer.material_flags & MATERIAL_FLAG_ANIMATION))
 | 
			
		||||
		return (*layer.frames)[0].texture;
 | 
			
		||||
	if (!layer.empty() && (layer.material_flags & MATERIAL_FLAG_ANIMATION)) {
 | 
			
		||||
		auto *ret = (*layer.frames)[0].texture;
 | 
			
		||||
		assert(ret->getType() == video::ETT_2D);
 | 
			
		||||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
	if (!def.name.empty())
 | 
			
		||||
		return tsrc->getTextureForMesh(def.name);
 | 
			
		||||
	return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// (the function name represents the amount of time wasted on all of this)
 | 
			
		||||
 | 
			
		||||
static void setAlphaBullshit(video::SMaterial &mat,
 | 
			
		||||
		AlphaMode mode, bool overlay)
 | 
			
		||||
void getAdHocNodeShader(video::SMaterial &mat, IShaderSource *shdsrc,
 | 
			
		||||
		const char *shader, AlphaMode mode, int layer)
 | 
			
		||||
{
 | 
			
		||||
	switch (mode) {
 | 
			
		||||
	case ALPHAMODE_BLEND:
 | 
			
		||||
		mat.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
 | 
			
		||||
		mat.MaterialTypeParam = 0;
 | 
			
		||||
		return;
 | 
			
		||||
	case ALPHAMODE_OPAQUE:
 | 
			
		||||
		if (!overlay) {
 | 
			
		||||
			mat.MaterialType = video::EMT_SOLID;
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		[[fallthrough]];
 | 
			
		||||
	case ALPHAMODE_CLIP:
 | 
			
		||||
	default:
 | 
			
		||||
		mat.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
 | 
			
		||||
		mat.MaterialTypeParam = 0.5f;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
	assert(shdsrc);
 | 
			
		||||
	MaterialType type = alpha_mode_to_material_type(mode);
 | 
			
		||||
	if (layer == 1)
 | 
			
		||||
		type = material_type_with_alpha(type);
 | 
			
		||||
 | 
			
		||||
static void setAlphaBullshit(video::SMaterial &mat, IShaderSource *shdsrc,
 | 
			
		||||
		AlphaMode mode, bool overlay)
 | 
			
		||||
{
 | 
			
		||||
	MaterialType mt;
 | 
			
		||||
	switch (mode) {
 | 
			
		||||
	case ALPHAMODE_BLEND:
 | 
			
		||||
		mt = TILE_MATERIAL_ALPHA;
 | 
			
		||||
		break;
 | 
			
		||||
	case ALPHAMODE_OPAQUE:
 | 
			
		||||
		mt = overlay ? TILE_MATERIAL_BASIC : TILE_MATERIAL_OPAQUE;
 | 
			
		||||
		break;
 | 
			
		||||
	case ALPHAMODE_CLIP:
 | 
			
		||||
	default:
 | 
			
		||||
		mt = TILE_MATERIAL_BASIC;
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
	// Note: logic wise this duplicates what `ContentFeatures::updateTextures`
 | 
			
		||||
	// and related functions do.
 | 
			
		||||
 | 
			
		||||
	u32 shader_id = shdsrc->getShader("object_shader", mt, NDT_NORMAL);
 | 
			
		||||
	bool array_texture = false;
 | 
			
		||||
	if (mat.getTexture(0))
 | 
			
		||||
		array_texture = mat.getTexture(0)->getType() == video::ETT_2D_ARRAY;
 | 
			
		||||
 | 
			
		||||
	u32 shader_id = shdsrc->getShader(shader, type, NDT_NORMAL, array_texture);
 | 
			
		||||
	mat.MaterialType = shdsrc->getShaderInfo(shader_id).material;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -387,10 +364,6 @@ static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n,
 | 
			
		||||
	for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
 | 
			
		||||
		auto &prebuffers = collector.prebuffers[layer];
 | 
			
		||||
		for (PreMeshBuffer &p : prebuffers) {
 | 
			
		||||
			if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
 | 
			
		||||
				const FrameSpec &frame = (*p.layer.frames)[0];
 | 
			
		||||
				p.layer.texture = frame.texture;
 | 
			
		||||
			}
 | 
			
		||||
			for (video::S3DVertex &v : p.vertices)
 | 
			
		||||
				v.Color.setAlpha(255);
 | 
			
		||||
 | 
			
		||||
@@ -425,20 +398,19 @@ std::vector<FrameSpec> createAnimationFrames(ITextureSource *tsrc,
 | 
			
		||||
		return {{id, texture}};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	video::ITexture *orginal_texture = tsrc->getTexture(image_name);
 | 
			
		||||
	if (!orginal_texture)
 | 
			
		||||
	auto texture_size = tsrc->getTextureDimensions(image_name);
 | 
			
		||||
	if (!texture_size.Width || !texture_size.Height)
 | 
			
		||||
		return {};
 | 
			
		||||
 | 
			
		||||
	int frame_count = 1;
 | 
			
		||||
	auto orginal_size = orginal_texture->getOriginalSize();
 | 
			
		||||
	animation.determineParams(orginal_size, &frame_count, &result_frame_length_ms, nullptr);
 | 
			
		||||
	animation.determineParams(texture_size, &frame_count, &result_frame_length_ms, nullptr);
 | 
			
		||||
 | 
			
		||||
	std::vector<FrameSpec> frames(frame_count);
 | 
			
		||||
	std::ostringstream os(std::ios::binary);
 | 
			
		||||
	for (int i = 0; i < frame_count; i++) {
 | 
			
		||||
		os.str("");
 | 
			
		||||
		os << image_name;
 | 
			
		||||
		animation.getTextureModifer(os, orginal_size, i);
 | 
			
		||||
		animation.getTextureModifer(os, texture_size, i);
 | 
			
		||||
 | 
			
		||||
		u32 id;
 | 
			
		||||
		frames[i].texture = tsrc->getTextureForMesh(os.str(), &id);
 | 
			
		||||
@@ -559,7 +531,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
 | 
			
		||||
		for (u32 i = 0; i < material_count; ++i) {
 | 
			
		||||
			video::SMaterial &material = m_meshnode->getMaterial(i);
 | 
			
		||||
			// apply node's alpha mode
 | 
			
		||||
			setAlphaBullshit(material, shdsrc, f.alpha,
 | 
			
		||||
			getAdHocNodeShader(material, shdsrc, "object_shader", f.alpha,
 | 
			
		||||
				m_buffer_info[i].layer == 1);
 | 
			
		||||
			material.forEachTexture([this] (auto &tex) {
 | 
			
		||||
				setMaterialFilters(tex, m_bilinear_filter, m_trilinear_filter,
 | 
			
		||||
@@ -668,6 +640,7 @@ void createItemMesh(Client *client, const ItemDefinition &def,
 | 
			
		||||
		ItemMesh *result)
 | 
			
		||||
{
 | 
			
		||||
	ITextureSource *tsrc = client->getTextureSource();
 | 
			
		||||
	IShaderSource *shdsrc = client->getShaderSource();
 | 
			
		||||
	const NodeDefManager *ndef = client->getNodeDefManager();
 | 
			
		||||
	const ContentFeatures &f = ndef->get(def.name);
 | 
			
		||||
	assert(result);
 | 
			
		||||
@@ -676,8 +649,8 @@ void createItemMesh(Client *client, const ItemDefinition &def,
 | 
			
		||||
 | 
			
		||||
	scene::SMesh *mesh = nullptr;
 | 
			
		||||
 | 
			
		||||
	// Shading is on by default
 | 
			
		||||
	result->needs_shading = true;
 | 
			
		||||
	// Shading is off by default
 | 
			
		||||
	result->needs_shading = false;
 | 
			
		||||
 | 
			
		||||
	video::ITexture *inventory_texture = animation_normal.getTexture(0.0f),
 | 
			
		||||
		*inventory_overlay_texture = animation_overlay.getTexture(0.0f);
 | 
			
		||||
@@ -691,8 +664,6 @@ void createItemMesh(Client *client, const ItemDefinition &def,
 | 
			
		||||
		// overlay is white, if present
 | 
			
		||||
		result->buffer_info.emplace_back(1, &animation_overlay,
 | 
			
		||||
				true, video::SColor(0xFFFFFFFF));
 | 
			
		||||
		// Items with inventory images do not need shading
 | 
			
		||||
		result->needs_shading = false;
 | 
			
		||||
	} else if (def.type == ITEM_NODE && f.drawtype == NDT_AIRLIKE) {
 | 
			
		||||
		// Fallback image for airlike node
 | 
			
		||||
		mesh = getExtrudedMesh(tsrc->getTexture("no_texture_airlike.png"),
 | 
			
		||||
@@ -701,7 +672,6 @@ void createItemMesh(Client *client, const ItemDefinition &def,
 | 
			
		||||
 | 
			
		||||
		// overlay is white, if present
 | 
			
		||||
		result->buffer_info.emplace_back(1, true, video::SColor(0xFFFFFFFF));
 | 
			
		||||
		result->needs_shading = false;
 | 
			
		||||
	} else if (def.type == ITEM_NODE) {
 | 
			
		||||
		switch (f.drawtype) {
 | 
			
		||||
		case NDT_PLANTLIKE: {
 | 
			
		||||
@@ -732,6 +702,7 @@ void createItemMesh(Client *client, const ItemDefinition &def,
 | 
			
		||||
 | 
			
		||||
			mesh = createGenericNodeMesh(client, n, &result->buffer_info, f);
 | 
			
		||||
			scaleMesh(mesh, v3f(0.12f));
 | 
			
		||||
			result->needs_shading = true;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
		}
 | 
			
		||||
@@ -740,7 +711,7 @@ void createItemMesh(Client *client, const ItemDefinition &def,
 | 
			
		||||
			scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
 | 
			
		||||
			video::SMaterial &material = buf->getMaterial();
 | 
			
		||||
			// apply node's alpha mode
 | 
			
		||||
			setAlphaBullshit(material, f.alpha,
 | 
			
		||||
			getAdHocNodeShader(material, shdsrc, "inventory_shader", f.alpha,
 | 
			
		||||
				result->buffer_info[i].layer == 1);
 | 
			
		||||
			material.forEachTexture([] (auto &tex) {
 | 
			
		||||
				tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST;
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@
 | 
			
		||||
#include <SColor.h>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include "tile.h"
 | 
			
		||||
#include "nodedef.h"
 | 
			
		||||
 | 
			
		||||
namespace scene
 | 
			
		||||
{
 | 
			
		||||
@@ -28,6 +29,7 @@ class Client;
 | 
			
		||||
class ITextureSource;
 | 
			
		||||
struct ItemDefinition;
 | 
			
		||||
struct TileAnimationParams;
 | 
			
		||||
class IShaderSource;
 | 
			
		||||
class ShadowRenderer;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@@ -180,6 +182,19 @@ std::vector<FrameSpec> createAnimationFrames(ITextureSource *tsrc,
 | 
			
		||||
scene::SMesh *getExtrudedMesh(video::ITexture *texture,
 | 
			
		||||
	video::ITexture *overlay_texture = nullptr);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Replace the material's shader with a custom one while respecting the usual
 | 
			
		||||
 * things expected of node rendering (texture type, alpha mode, overlay).
 | 
			
		||||
 * Call this after `TileLayer::applyMaterialOptions`.
 | 
			
		||||
 * @param mat material to modify
 | 
			
		||||
 * @param shdsrc shader source
 | 
			
		||||
 * @param shader name of shader
 | 
			
		||||
 * @param mode alpha mode from nodedef
 | 
			
		||||
 * @param layer index of this layer
 | 
			
		||||
 */
 | 
			
		||||
void getAdHocNodeShader(video::SMaterial &mat, IShaderSource *shdsrc,
 | 
			
		||||
		const char *shader, AlphaMode mode, int layer);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * NOTE: The item mesh is only suitable for inventory rendering (due to its
 | 
			
		||||
 * material types). In-world rendering of items must go through WieldMeshSceneNode.
 | 
			
		||||
 
 | 
			
		||||
@@ -260,6 +260,7 @@ void set_default_settings()
 | 
			
		||||
	settings->setDefault("connected_glass", "false");
 | 
			
		||||
	settings->setDefault("smooth_lighting", "true");
 | 
			
		||||
	settings->setDefault("performance_tradeoffs", "false");
 | 
			
		||||
	settings->setDefault("array_texture_max", "65535");
 | 
			
		||||
	settings->setDefault("lighting_alpha", "0.0");
 | 
			
		||||
	settings->setDefault("lighting_beta", "1.5");
 | 
			
		||||
	settings->setDefault("display_gamma", "1.0");
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										371
									
								
								src/nodedef.cpp
									
									
									
									
									
								
							
							
						
						
									
										371
									
								
								src/nodedef.cpp
									
									
									
									
									
								
							@@ -269,6 +269,10 @@ void TileDef::deSerialize(std::istream &is, NodeDrawType drawtype, u16 protocol_
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	TextureSettings
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
void TextureSettings::readSettings()
 | 
			
		||||
{
 | 
			
		||||
	connected_glass                = g_settings->getBool("connected_glass");
 | 
			
		||||
@@ -305,6 +309,56 @@ void TextureSettings::readSettings()
 | 
			
		||||
		autoscale_mode = AUTOSCALE_DISABLE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	Texture pool and related
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
#if CHECK_CLIENT_BUILD()
 | 
			
		||||
struct PreLoadedTexture {
 | 
			
		||||
	video::ITexture *texture = nullptr;
 | 
			
		||||
	u32 texture_id = 0;
 | 
			
		||||
	u16 texture_layer_idx = 0;
 | 
			
		||||
	bool used = false; // For debugging
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct PreLoadedTextures {
 | 
			
		||||
	std::unordered_map<std::string, PreLoadedTexture> pool;
 | 
			
		||||
	std::unordered_set<std::string> missed; // For debugging
 | 
			
		||||
 | 
			
		||||
	PreLoadedTexture find(const std::string &name);
 | 
			
		||||
	void add(const std::string &name, const PreLoadedTexture &t);
 | 
			
		||||
 | 
			
		||||
	void printStats(std::ostream &to) const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
PreLoadedTexture PreLoadedTextures::find(const std::string &name)
 | 
			
		||||
{
 | 
			
		||||
	auto it = pool.find(name);
 | 
			
		||||
	if (it == pool.end()) {
 | 
			
		||||
		missed.emplace(name);
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
	it->second.used = true;
 | 
			
		||||
	return it->second;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PreLoadedTextures::add(const std::string &name, const PreLoadedTexture &t)
 | 
			
		||||
{
 | 
			
		||||
	assert(pool.find(name) == pool.end());
 | 
			
		||||
	pool[name] = t;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PreLoadedTextures::printStats(std::ostream &to) const
 | 
			
		||||
{
 | 
			
		||||
	size_t unused = 0;
 | 
			
		||||
	for (auto &it : pool)
 | 
			
		||||
		unused += it.second.used ? 0 : 1;
 | 
			
		||||
	to << "PreLoadedTextures: " << pool.size() << "\n  wasted: " << unused
 | 
			
		||||
		<< " missed: " << missed.size() << std::endl;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	ContentFeatures
 | 
			
		||||
*/
 | 
			
		||||
@@ -321,8 +375,10 @@ ContentFeatures::~ContentFeatures()
 | 
			
		||||
		delete tiles[j].layers[0].frames;
 | 
			
		||||
		delete tiles[j].layers[1].frames;
 | 
			
		||||
	}
 | 
			
		||||
	for (u16 j = 0; j < CF_SPECIAL_COUNT; j++)
 | 
			
		||||
	for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) {
 | 
			
		||||
		delete special_tiles[j].layers[0].frames;
 | 
			
		||||
		delete special_tiles[j].layers[1].frames;
 | 
			
		||||
	}
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -670,20 +726,47 @@ void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if CHECK_CLIENT_BUILD()
 | 
			
		||||
static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer,
 | 
			
		||||
		const TileSpec &tile, const TileDef &tiledef, video::SColor color,
 | 
			
		||||
		MaterialType material_type, u32 shader_id, bool backface_culling,
 | 
			
		||||
		const TextureSettings &tsettings)
 | 
			
		||||
{
 | 
			
		||||
	layer->shader_id     = shader_id;
 | 
			
		||||
	layer->texture       = tsrc->getTextureForMesh(tiledef.name, &layer->texture_id);
 | 
			
		||||
	layer->material_type = material_type;
 | 
			
		||||
struct TileAttribContext {
 | 
			
		||||
	ITextureSource *tsrc;
 | 
			
		||||
	PreLoadedTextures *texture_pool;
 | 
			
		||||
	video::SColor base_color;
 | 
			
		||||
	const TextureSettings &tsettings;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ShaderIds {
 | 
			
		||||
	u32 normal;
 | 
			
		||||
	// Shader that will handle an array texture and texture_layer_idx
 | 
			
		||||
	u32 with_layers;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void fillTileAttribs(TileLayer *layer, TileAttribContext context,
 | 
			
		||||
		const TileSpec &tile, const TileDef &tiledef,
 | 
			
		||||
		MaterialType material_type, ShaderIds shader)
 | 
			
		||||
{
 | 
			
		||||
	auto *tsrc = context.tsrc;
 | 
			
		||||
	const auto &tsettings = context.tsettings;
 | 
			
		||||
 | 
			
		||||
	std::string texture_image;
 | 
			
		||||
	if (!tiledef.name.empty()) {
 | 
			
		||||
		texture_image = tiledef.name;
 | 
			
		||||
		if (tsrc->needFilterForMesh())
 | 
			
		||||
			texture_image += tsrc->FILTER_FOR_MESH;
 | 
			
		||||
	} else {
 | 
			
		||||
		// Tile is empty, nothing to do.
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	core::dimension2du texture_size;
 | 
			
		||||
	if (!texture_image.empty())
 | 
			
		||||
		texture_size = tsrc->getTextureDimensions(texture_image);
 | 
			
		||||
	if (!texture_size.Width || !texture_size.Height)
 | 
			
		||||
		texture_size = {1, 1}; // dummy if there's an error
 | 
			
		||||
 | 
			
		||||
	// Scale
 | 
			
		||||
	bool has_scale = tiledef.scale > 0;
 | 
			
		||||
	bool use_autoscale = tsettings.autoscale_mode == AUTOSCALE_FORCE ||
 | 
			
		||||
		(tsettings.autoscale_mode == AUTOSCALE_ENABLE && !has_scale);
 | 
			
		||||
	if (use_autoscale && layer->texture) {
 | 
			
		||||
		auto texture_size = layer->texture->getOriginalSize();
 | 
			
		||||
	if (use_autoscale) {
 | 
			
		||||
		float base_size = tsettings.node_texture_size;
 | 
			
		||||
		float size = std::fmin(texture_size.Width, texture_size.Height);
 | 
			
		||||
		layer->scale = std::fmax(base_size, size) / base_size;
 | 
			
		||||
@@ -695,9 +778,10 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer,
 | 
			
		||||
	if (!tile.world_aligned)
 | 
			
		||||
		layer->scale = 1;
 | 
			
		||||
 | 
			
		||||
	// Material flags
 | 
			
		||||
	// Material
 | 
			
		||||
	layer->material_type = material_type;
 | 
			
		||||
	layer->material_flags = 0;
 | 
			
		||||
	if (backface_culling)
 | 
			
		||||
	if (tiledef.backface_culling)
 | 
			
		||||
		layer->material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
 | 
			
		||||
	if (tiledef.animation.type != TAT_NONE)
 | 
			
		||||
		layer->material_flags |= MATERIAL_FLAG_ANIMATION;
 | 
			
		||||
@@ -711,7 +795,7 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer,
 | 
			
		||||
	if (tiledef.has_color)
 | 
			
		||||
		layer->color = tiledef.color;
 | 
			
		||||
	else
 | 
			
		||||
		layer->color = color;
 | 
			
		||||
		layer->color = context.base_color;
 | 
			
		||||
 | 
			
		||||
	// Animation
 | 
			
		||||
	if (layer->material_flags & MATERIAL_FLAG_ANIMATION) {
 | 
			
		||||
@@ -722,10 +806,74 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer,
 | 
			
		||||
			layer->frames = new std::vector<FrameSpec>(frames);
 | 
			
		||||
			layer->animation_frame_count = layer->frames->size();
 | 
			
		||||
			layer->animation_frame_length_ms = frame_length_ms;
 | 
			
		||||
 | 
			
		||||
			// Set default texture to first frame (not used practice)
 | 
			
		||||
			layer->texture = (*layer->frames)[0].texture;
 | 
			
		||||
			layer->texture_id = (*layer->frames)[0].texture_id;
 | 
			
		||||
		} else {
 | 
			
		||||
			layer->material_flags &= ~MATERIAL_FLAG_ANIMATION;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!(layer->material_flags & MATERIAL_FLAG_ANIMATION)) {
 | 
			
		||||
		// Grab texture
 | 
			
		||||
		auto tex = context.texture_pool->find(texture_image);
 | 
			
		||||
		if (!tex.texture) {
 | 
			
		||||
			// wasn't pre-loaded: create standard texture on the fly
 | 
			
		||||
			layer->texture = tsrc->getTexture(texture_image, &layer->texture_id);
 | 
			
		||||
		} else {
 | 
			
		||||
			layer->texture = tex.texture;
 | 
			
		||||
			layer->texture_id = tex.texture_id;
 | 
			
		||||
			layer->texture_layer_idx = tex.texture_layer_idx;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Decide on shader to use
 | 
			
		||||
	if (layer->texture) {
 | 
			
		||||
		layer->shader_id = (layer->texture->getType() == video::ETT_2D_ARRAY) ?
 | 
			
		||||
			shader.with_layers : shader.normal;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ContentFeatures::preUpdateTextures(ITextureSource *tsrc,
 | 
			
		||||
		std::unordered_set<std::string> &pool, const TextureSettings &tsettings)
 | 
			
		||||
{
 | 
			
		||||
	// Find out the exact texture strings this node might use, and put them into the pool
 | 
			
		||||
	// (this should match updateTextures, but it's not the end of the world if
 | 
			
		||||
	// a mismatch occurs)
 | 
			
		||||
	std::string append;
 | 
			
		||||
	if (tsrc->needFilterForMesh())
 | 
			
		||||
		append = ITextureSource::FILTER_FOR_MESH;
 | 
			
		||||
	std::string append_overlay = append, append_special = append;
 | 
			
		||||
	bool use = true, use_overlay = true, use_special = true;
 | 
			
		||||
 | 
			
		||||
	if (drawtype == NDT_ALLFACES_OPTIONAL) {
 | 
			
		||||
		use_special = (tsettings.leaves_style == LEAVES_SIMPLE);
 | 
			
		||||
		use = !use_special;
 | 
			
		||||
		if (tsettings.leaves_style == LEAVES_OPAQUE)
 | 
			
		||||
			append.insert(0, "^[noalpha");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto &consider_tile = [&] (const TileDef &def, const std::string &append) {
 | 
			
		||||
		// Animations are chopped into frames later, so we won't actually need
 | 
			
		||||
		// the source texture
 | 
			
		||||
		if (!def.name.empty() && def.animation.type == TAT_NONE) {
 | 
			
		||||
			pool.insert(def.name + append);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	for (u32 j = 0; j < 6; j++) {
 | 
			
		||||
		if (use)
 | 
			
		||||
			consider_tile(tiledef[j], append);
 | 
			
		||||
	}
 | 
			
		||||
	for (u32 j = 0; j < 6; j++) {
 | 
			
		||||
		if (use_overlay)
 | 
			
		||||
			consider_tile(tiledef_overlay[j], append_overlay);
 | 
			
		||||
	}
 | 
			
		||||
	for (u32 j = 0; j < CF_SPECIAL_COUNT; j++) {
 | 
			
		||||
		if (use_special)
 | 
			
		||||
			consider_tile(tiledef_special[j], append_special);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool isWorldAligned(AlignStyle style, WorldAlignMode mode, NodeDrawType drawtype)
 | 
			
		||||
@@ -743,8 +891,32 @@ static bool isWorldAligned(AlignStyle style, WorldAlignMode mode, NodeDrawType d
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// @return maximum number of layers in array textures we can use (0 if unsupported)
 | 
			
		||||
static size_t getArrayTextureMax(video::IVideoDriver *driver)
 | 
			
		||||
{
 | 
			
		||||
	// needs to actually support array textures
 | 
			
		||||
	if (!driver->queryFeature(video::EVDF_TEXTURE_2D_ARRAY))
 | 
			
		||||
		return 0;
 | 
			
		||||
	// must not be the legacy driver, due to custom vertex format
 | 
			
		||||
	if (driver->getDriverType() == video::EDT_OPENGL)
 | 
			
		||||
		return 0;
 | 
			
		||||
	// doesn't work on GLES yet (TODO)
 | 
			
		||||
	if (driver->getDriverType() == video::EDT_OGLES2)
 | 
			
		||||
		return 0;
 | 
			
		||||
	// shadow shaders can't handle array textures yet (TODO)
 | 
			
		||||
	if (g_settings->getBool("enable_dynamic_shadows"))
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	u32 n = driver->getLimits().MaxArrayTextureImages;
 | 
			
		||||
	constexpr u32 type_max = std::numeric_limits<decltype(TileLayer::texture_layer_idx)>::max();
 | 
			
		||||
	n = std::min(n, type_max);
 | 
			
		||||
	n = std::min(n, g_settings->getU32("array_texture_max"));
 | 
			
		||||
	return n;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc,
 | 
			
		||||
	scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings)
 | 
			
		||||
	Client *client, PreLoadedTextures *texture_pool,
 | 
			
		||||
	const TextureSettings &tsettings)
 | 
			
		||||
{
 | 
			
		||||
	// Figure out the actual tiles to use
 | 
			
		||||
	TileDef tdef[6];
 | 
			
		||||
@@ -767,9 +939,7 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
 | 
			
		||||
 | 
			
		||||
	bool is_liquid = false;
 | 
			
		||||
 | 
			
		||||
	MaterialType material_type = alpha == ALPHAMODE_OPAQUE ?
 | 
			
		||||
		TILE_MATERIAL_OPAQUE : (alpha == ALPHAMODE_CLIP ? TILE_MATERIAL_BASIC :
 | 
			
		||||
		TILE_MATERIAL_ALPHA);
 | 
			
		||||
	MaterialType material_type = alpha_mode_to_material_type(alpha);
 | 
			
		||||
 | 
			
		||||
	switch (drawtype) {
 | 
			
		||||
	default:
 | 
			
		||||
@@ -880,15 +1050,21 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	u32 tile_shader = shdsrc->getShader("nodes_shader", material_type, drawtype);
 | 
			
		||||
	const bool texture_2d_array = getArrayTextureMax(RenderingEngine::get_video_driver()) > 1;
 | 
			
		||||
	const auto &getNodeShader = [&] (MaterialType my_material, NodeDrawType my_drawtype) {
 | 
			
		||||
		ShaderIds ret;
 | 
			
		||||
		ret.normal = shdsrc->getShader("nodes_shader", my_material, my_drawtype);
 | 
			
		||||
		// need to avoid generating the shader if unsupported
 | 
			
		||||
		if (texture_2d_array)
 | 
			
		||||
			ret.with_layers = shdsrc->getShader("nodes_shader", my_material, my_drawtype, true);
 | 
			
		||||
		return ret;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	MaterialType overlay_material = material_type;
 | 
			
		||||
	if (overlay_material == TILE_MATERIAL_OPAQUE)
 | 
			
		||||
		overlay_material = TILE_MATERIAL_BASIC;
 | 
			
		||||
	else if (overlay_material == TILE_MATERIAL_LIQUID_OPAQUE)
 | 
			
		||||
		overlay_material = TILE_MATERIAL_LIQUID_TRANSPARENT;
 | 
			
		||||
	ShaderIds tile_shader = getNodeShader(material_type, drawtype);
 | 
			
		||||
 | 
			
		||||
	u32 overlay_shader = shdsrc->getShader("nodes_shader", overlay_material, drawtype);
 | 
			
		||||
	MaterialType overlay_material = material_type_with_alpha(material_type);
 | 
			
		||||
 | 
			
		||||
	ShaderIds overlay_shader = getNodeShader(overlay_material, drawtype);
 | 
			
		||||
 | 
			
		||||
	// minimap pixel color = average color of top tile
 | 
			
		||||
	if (tsettings.enable_minimap && drawtype != NDT_AIRLIKE && !tdef[0].name.empty())
 | 
			
		||||
@@ -904,16 +1080,18 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
 | 
			
		||||
 | 
			
		||||
	// Tiles (fill in f->tiles[])
 | 
			
		||||
	bool any_polygon_offset = false;
 | 
			
		||||
	TileAttribContext tac{tsrc, texture_pool, color, tsettings};
 | 
			
		||||
 | 
			
		||||
	for (u16 j = 0; j < 6; j++) {
 | 
			
		||||
		tiles[j].world_aligned = isWorldAligned(tdef[j].align_style,
 | 
			
		||||
				tsettings.world_aligned_mode, drawtype);
 | 
			
		||||
		fillTileAttribs(tsrc, &tiles[j].layers[0], tiles[j], tdef[j],
 | 
			
		||||
				color, material_type, tile_shader,
 | 
			
		||||
				tdef[j].backface_culling, tsettings);
 | 
			
		||||
		if (!tdef_overlay[j].name.empty())
 | 
			
		||||
			fillTileAttribs(tsrc, &tiles[j].layers[1], tiles[j], tdef_overlay[j],
 | 
			
		||||
					color, overlay_material, overlay_shader,
 | 
			
		||||
					tdef[j].backface_culling, tsettings);
 | 
			
		||||
		fillTileAttribs(&tiles[j].layers[0], tac, tiles[j], tdef[j],
 | 
			
		||||
				material_type, tile_shader);
 | 
			
		||||
		if (!tdef_overlay[j].name.empty()) {
 | 
			
		||||
			tdef_overlay[j].backface_culling = tdef[j].backface_culling;
 | 
			
		||||
			fillTileAttribs(&tiles[j].layers[1], tac, tiles[j], tdef_overlay[j],
 | 
			
		||||
					overlay_material, overlay_shader);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tiles[j].layers[0].need_polygon_offset = !tiles[j].layers[1].empty();
 | 
			
		||||
		any_polygon_offset |= tiles[j].layers[0].need_polygon_offset;
 | 
			
		||||
@@ -936,13 +1114,14 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
 | 
			
		||||
		else if (waving == 2)
 | 
			
		||||
			special_material = TILE_MATERIAL_WAVING_LEAVES;
 | 
			
		||||
	}
 | 
			
		||||
	u32 special_shader = shdsrc->getShader("nodes_shader", special_material, drawtype);
 | 
			
		||||
 | 
			
		||||
	ShaderIds special_shader = getNodeShader(special_material, drawtype);
 | 
			
		||||
 | 
			
		||||
	// Special tiles (fill in f->special_tiles[])
 | 
			
		||||
	for (u16 j = 0; j < CF_SPECIAL_COUNT; j++)
 | 
			
		||||
		fillTileAttribs(tsrc, &special_tiles[j].layers[0], special_tiles[j], tdef_spec[j],
 | 
			
		||||
				color, special_material, special_shader,
 | 
			
		||||
				tdef_spec[j].backface_culling, tsettings);
 | 
			
		||||
	for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) {
 | 
			
		||||
		fillTileAttribs(&special_tiles[j].layers[0], tac,
 | 
			
		||||
				special_tiles[j], tdef_spec[j], special_material, special_shader);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (param_type_2 == CPT2_COLOR ||
 | 
			
		||||
			param_type_2 == CPT2_COLORED_FACEDIR ||
 | 
			
		||||
@@ -951,6 +1130,13 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
 | 
			
		||||
			param_type_2 == CPT2_COLORED_DEGROTATE)
 | 
			
		||||
		palette = tsrc->getPalette(palette_name);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ContentFeatures::updateMesh(Client *client, const TextureSettings &tsettings)
 | 
			
		||||
{
 | 
			
		||||
	auto *manip = client->getSceneManager()->getMeshManipulator();
 | 
			
		||||
	(void)tsettings;
 | 
			
		||||
 | 
			
		||||
	if (drawtype == NDT_MESH && !mesh.empty()) {
 | 
			
		||||
		// Note: By freshly reading, we get an unencumbered mesh.
 | 
			
		||||
		if (scene::IMesh *src_mesh = client->getMesh(mesh)) {
 | 
			
		||||
@@ -979,13 +1165,27 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
 | 
			
		||||
				// TODO this should be done consistently when the mesh is loaded
 | 
			
		||||
				infostream << "ContentFeatures: recalculating normals for mesh "
 | 
			
		||||
					<< mesh << std::endl;
 | 
			
		||||
				meshmanip->recalculateNormals(mesh_ptr, true, false);
 | 
			
		||||
				manip->recalculateNormals(mesh_ptr, true, false);
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			mesh_ptr = nullptr;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ContentFeatures::collectMaterials(std::vector<u32> &leaves_materials)
 | 
			
		||||
{
 | 
			
		||||
	if (drawtype == NDT_AIRLIKE)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	for (u16 j = 0; j < 6; j++) {
 | 
			
		||||
		auto &l = tiles[j].layers;
 | 
			
		||||
		if (!l[0].empty() && l[0].material_type == TILE_MATERIAL_WAVING_LEAVES)
 | 
			
		||||
			leaves_materials.push_back(l[0].shader_id);
 | 
			
		||||
		if (!l[1].empty() && l[1].material_type == TILE_MATERIAL_WAVING_LEAVES)
 | 
			
		||||
			leaves_materials.push_back(l[1].shader_id);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@@ -1021,13 +1221,13 @@ void NodeDefManager::clear()
 | 
			
		||||
	m_next_id = 0;
 | 
			
		||||
	m_selection_box_union.reset(0,0,0);
 | 
			
		||||
	m_selection_box_int_union.reset(0,0,0);
 | 
			
		||||
#if CHECK_CLIENT_BUILD()
 | 
			
		||||
	m_leaves_materials.clear();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	resetNodeResolveState();
 | 
			
		||||
 | 
			
		||||
	u32 initial_length = 0;
 | 
			
		||||
	initial_length = MYMAX(initial_length, CONTENT_UNKNOWN + 1);
 | 
			
		||||
	initial_length = MYMAX(initial_length, CONTENT_AIR + 1);
 | 
			
		||||
	initial_length = MYMAX(initial_length, CONTENT_IGNORE + 1);
 | 
			
		||||
	constexpr u32 initial_length = std::max({CONTENT_UNKNOWN, CONTENT_AIR, CONTENT_IGNORE}) + 1;
 | 
			
		||||
	m_content_features.resize(initial_length);
 | 
			
		||||
 | 
			
		||||
	// Set CONTENT_UNKNOWN
 | 
			
		||||
@@ -1164,7 +1364,7 @@ content_t NodeDefManager::allocateId()
 | 
			
		||||
 * @param[in]      boxes     the vector containing the boxes
 | 
			
		||||
 * @param[in, out] box_union the union of the arguments
 | 
			
		||||
 */
 | 
			
		||||
void boxVectorUnion(const std::vector<aabb3f> &boxes, aabb3f *box_union)
 | 
			
		||||
static void boxVectorUnion(const std::vector<aabb3f> &boxes, aabb3f *box_union)
 | 
			
		||||
{
 | 
			
		||||
	for (const aabb3f &box : boxes) {
 | 
			
		||||
		box_union->addInternalBox(box);
 | 
			
		||||
@@ -1180,7 +1380,7 @@ void boxVectorUnion(const std::vector<aabb3f> &boxes, aabb3f *box_union)
 | 
			
		||||
 * can be rotated
 | 
			
		||||
 * @param[in, out] box_union the union of the arguments
 | 
			
		||||
 */
 | 
			
		||||
void getNodeBoxUnion(const NodeBox &nodebox, const ContentFeatures &features,
 | 
			
		||||
static void getNodeBoxUnion(const NodeBox &nodebox, const ContentFeatures &features,
 | 
			
		||||
	aabb3f *box_union)
 | 
			
		||||
{
 | 
			
		||||
	switch(nodebox.type) {
 | 
			
		||||
@@ -1450,32 +1650,101 @@ void NodeDefManager::applyTextureOverrides(const std::vector<TextureOverride> &o
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if CHECK_CLIENT_BUILD()
 | 
			
		||||
void NodeDefManager::updateTextures(IGameDef *gamedef, void *progress_callback_args)
 | 
			
		||||
{
 | 
			
		||||
#if CHECK_CLIENT_BUILD()
 | 
			
		||||
	infostream << "NodeDefManager::updateTextures(): Updating "
 | 
			
		||||
		"textures in node definitions" << std::endl;
 | 
			
		||||
 | 
			
		||||
	Client *client = (Client *)gamedef;
 | 
			
		||||
	ITextureSource *tsrc = client->tsrc();
 | 
			
		||||
	IShaderSource *shdsrc = client->getShaderSource();
 | 
			
		||||
	auto smgr = client->getSceneManager();
 | 
			
		||||
	scene::IMeshManipulator *meshmanip = smgr->getMeshManipulator();
 | 
			
		||||
	TextureSettings tsettings;
 | 
			
		||||
	tsettings.readSettings();
 | 
			
		||||
 | 
			
		||||
	tsrc->setImageCaching(true);
 | 
			
		||||
	const u32 size = m_content_features.size();
 | 
			
		||||
 | 
			
		||||
	u32 size = m_content_features.size();
 | 
			
		||||
	/* collect all textures we might use */
 | 
			
		||||
	std::unordered_set<std::string> pool;
 | 
			
		||||
	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);
 | 
			
		||||
		f->preUpdateTextures(tsrc, pool, tsettings);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* texture pre-loading stage */
 | 
			
		||||
	const size_t arraymax = getArrayTextureMax(RenderingEngine::get_video_driver());
 | 
			
		||||
	// Group by size
 | 
			
		||||
	std::unordered_map<v2u32, std::vector<std::string_view>> sizes;
 | 
			
		||||
	if (arraymax > 1) {
 | 
			
		||||
		infostream << "Using array textures with " << arraymax << " layers" << std::endl;
 | 
			
		||||
		size_t i = 0;
 | 
			
		||||
		for (auto &image : pool) {
 | 
			
		||||
			core::dimension2du dim = tsrc->getTextureDimensions(image);
 | 
			
		||||
			client->showUpdateProgressTexture(progress_callback_args,
 | 
			
		||||
				0.33333f * ++i / pool.size());
 | 
			
		||||
			if (!dim.Width || !dim.Height) // error
 | 
			
		||||
				continue;
 | 
			
		||||
			sizes[v2u32(dim)].emplace_back(image);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// create array textures as far as possible
 | 
			
		||||
	size_t num_preloadable = 0, preload_progress = 0;
 | 
			
		||||
	for (auto &it : sizes) {
 | 
			
		||||
		if (it.second.size() < 2)
 | 
			
		||||
			continue;
 | 
			
		||||
		num_preloadable += it.second.size();
 | 
			
		||||
	}
 | 
			
		||||
	PreLoadedTextures plt;
 | 
			
		||||
	const auto &doBunch = [&] (const std::vector<std::string> &bunch) {
 | 
			
		||||
		PreLoadedTexture t;
 | 
			
		||||
		t.texture = tsrc->addArrayTexture(bunch, &t.texture_id);
 | 
			
		||||
		preload_progress += bunch.size();
 | 
			
		||||
		client->showUpdateProgressTexture(progress_callback_args,
 | 
			
		||||
			0.33333f + 0.33333f * preload_progress / num_preloadable);
 | 
			
		||||
		if (t.texture) {
 | 
			
		||||
			// Success: all of the images in this bunch can now refer to this texture
 | 
			
		||||
			for (size_t idx = 0; idx < bunch.size(); idx++) {
 | 
			
		||||
				t.texture_layer_idx = idx;
 | 
			
		||||
				plt.add(bunch[idx], t);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	for (auto &it : sizes) {
 | 
			
		||||
		if (it.second.size() < 2)
 | 
			
		||||
			continue;
 | 
			
		||||
		std::vector<std::string> bunch;
 | 
			
		||||
		for (auto &image : it.second) {
 | 
			
		||||
			bunch.emplace_back(image);
 | 
			
		||||
			if (bunch.size() == arraymax) {
 | 
			
		||||
				doBunch(bunch);
 | 
			
		||||
				bunch.clear();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (!bunch.empty())
 | 
			
		||||
			doBunch(bunch);
 | 
			
		||||
	}
 | 
			
		||||
	// note that standard textures aren't preloaded
 | 
			
		||||
 | 
			
		||||
	/* final step */
 | 
			
		||||
	for (u32 i = 0; i < size; i++) {
 | 
			
		||||
		ContentFeatures *f = &(m_content_features[i]);
 | 
			
		||||
		f->updateTextures(tsrc, shdsrc, client, &plt, tsettings);
 | 
			
		||||
		f->updateMesh(client, tsettings);
 | 
			
		||||
		f->collectMaterials(m_leaves_materials);
 | 
			
		||||
 | 
			
		||||
		client->showUpdateProgressTexture(progress_callback_args,
 | 
			
		||||
			0.66666f + 0.33333f * i / size);
 | 
			
		||||
	}
 | 
			
		||||
	SORT_AND_UNIQUE(m_leaves_materials);
 | 
			
		||||
	verbosestream << "m_leaves_materials.size() = " << m_leaves_materials.size()
 | 
			
		||||
		<< std::endl;
 | 
			
		||||
 | 
			
		||||
	plt.printStats(infostream);
 | 
			
		||||
	tsrc->setImageCaching(false);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void NodeDefManager::serialize(std::ostream &os, u16 protocol_version) const
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,9 @@
 | 
			
		||||
#if CHECK_CLIENT_BUILD()
 | 
			
		||||
#include "client/tile.h"
 | 
			
		||||
#include <IMeshManipulator.h>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
class Client;
 | 
			
		||||
struct PreLoadedTextures;
 | 
			
		||||
#endif
 | 
			
		||||
#include "itemgroup.h"
 | 
			
		||||
#include "sound.h" // SoundSpec
 | 
			
		||||
@@ -259,6 +261,24 @@ enum AlphaMode : u8 {
 | 
			
		||||
	AlphaMode_END // Dummy for validity check
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#if CHECK_CLIENT_BUILD()
 | 
			
		||||
/**
 | 
			
		||||
 * @brief get fitting material type for an alpha mode
 | 
			
		||||
 */
 | 
			
		||||
static inline MaterialType alpha_mode_to_material_type(AlphaMode mode)
 | 
			
		||||
{
 | 
			
		||||
	switch (mode) {
 | 
			
		||||
	case ALPHAMODE_BLEND:
 | 
			
		||||
		return TILE_MATERIAL_ALPHA;
 | 
			
		||||
	case ALPHAMODE_OPAQUE:
 | 
			
		||||
		return TILE_MATERIAL_OPAQUE;
 | 
			
		||||
	case ALPHAMODE_CLIP:
 | 
			
		||||
	default:
 | 
			
		||||
		return TILE_MATERIAL_BASIC;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	Stand-alone definition of a TileSpec (basically a server-side TileSpec)
 | 
			
		||||
@@ -505,8 +525,13 @@ struct ContentFeatures
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#if CHECK_CLIENT_BUILD()
 | 
			
		||||
	void preUpdateTextures(ITextureSource *tsrc,
 | 
			
		||||
		std::unordered_set<std::string> &pool, const TextureSettings &tsettings);
 | 
			
		||||
	void updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc,
 | 
			
		||||
		scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings);
 | 
			
		||||
		Client *client, PreLoadedTextures *texture_pool,
 | 
			
		||||
		const TextureSettings &tsettings);
 | 
			
		||||
	void updateMesh(Client *client, const TextureSettings &tsettings);
 | 
			
		||||
	void collectMaterials(std::vector<u32> &leaves_materials);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
@@ -675,8 +700,9 @@ public:
 | 
			
		||||
	 */
 | 
			
		||||
	void applyTextureOverrides(const std::vector<TextureOverride> &overrides);
 | 
			
		||||
 | 
			
		||||
#if CHECK_CLIENT_BUILD()
 | 
			
		||||
	/*!
 | 
			
		||||
	 * Only the client uses this. Loads textures and shaders required for
 | 
			
		||||
	 * Loads textures and shaders required for
 | 
			
		||||
	 * rendering the nodes.
 | 
			
		||||
	 * @param gamedef must be a Client.
 | 
			
		||||
	 * @param progress_cbk called each time a node is loaded. Arguments:
 | 
			
		||||
@@ -685,6 +711,7 @@ public:
 | 
			
		||||
	 * @param progress_cbk_args passed to the callback function
 | 
			
		||||
	 */
 | 
			
		||||
	void updateTextures(IGameDef *gamedef, void *progress_cbk_args);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	/*!
 | 
			
		||||
	 * Writes the content of this manager to the given output stream.
 | 
			
		||||
@@ -728,6 +755,12 @@ public:
 | 
			
		||||
	 */
 | 
			
		||||
	void resolveCrossrefs();
 | 
			
		||||
 | 
			
		||||
#if CHECK_CLIENT_BUILD()
 | 
			
		||||
	// Set of all shader IDs used by leaves-like nodes
 | 
			
		||||
	// (kind of a hack but is needed for dynamic shadows)
 | 
			
		||||
	std::vector<u32> m_leaves_materials;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	/*!
 | 
			
		||||
	 * Resets the manager to its initial state.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user