Просмотр исходного кода

world: add shaders and materials hot-reloading

Part-of: #258
Daniele Bartolini 1 год назад
Родитель
Сommit
3046091118

+ 1 - 0
docs/changelog.rst

@@ -16,6 +16,7 @@ Changelog
 * Lua: fixed max temporaries check failing to trigger in some circumnstances.
 * Fixed a crash when moving many objects simultaneusly.
 * Fixed a crash when reloading unloaded or unsupported resources.
+* Added support to shaders and materials hot-reloading.
 
 0.54.0 --- 13 Jan 2025
 ----------------------

+ 12 - 0
src/device/device.cpp

@@ -941,6 +941,8 @@ void Device::refresh(const char *json)
 		StringId64 resource_name(resource.c_str(), len);
 		bool is_type_reloadable = resource_type == RESOURCE_TYPE_SCRIPT
 			|| resource_type == RESOURCE_TYPE_TEXTURE
+			|| resource_type == RESOURCE_TYPE_SHADER
+			|| resource_type == RESOURCE_TYPE_MATERIAL
 			;
 
 		if (is_type_reloadable && _resource_manager->can_get(resource_type, resource_name)) {
@@ -951,6 +953,16 @@ void Device::refresh(const char *json)
 				refresh_lua = true;
 			} else if (resource_type == RESOURCE_TYPE_TEXTURE) {
 				_material_manager->reload_textures((TextureResource *)old_resource, (TextureResource *)new_resource);
+			} else if (resource_type == RESOURCE_TYPE_SHADER) {
+				_pipeline->reload_shaders((ShaderResource *)old_resource, (ShaderResource *)new_resource);
+				_material_manager->reload_shaders((ShaderResource *)old_resource, (ShaderResource *)new_resource);
+			} else if (resource_type == RESOURCE_TYPE_MATERIAL) {
+				ListNode *cur;
+				list_for_each(cur, &_worlds)
+				{
+					World *w = (World *)container_of(cur, World, _node);
+					w->reload_materials((MaterialResource *)old_resource, (MaterialResource *)new_resource);
+				}
 			}
 		}
 	}

+ 6 - 0
src/device/pipeline.cpp

@@ -251,4 +251,10 @@ void Pipeline::render(u16 width, u16 height)
 #endif
 }
 
+void Pipeline::reload_shaders(const ShaderResource *old_resource, const ShaderResource *new_resource)
+{
+	CE_UNUSED_2(old_resource, new_resource);
+	lookup_default_shaders(*this);
+}
+
 } // namespace crown

+ 3 - 0
src/device/pipeline.h

@@ -74,6 +74,9 @@ struct Pipeline
 
 	///
 	void render(u16 width, u16 height);
+
+	///
+	void reload_shaders(const ShaderResource *old_resource, const ShaderResource *new_resource);
 };
 
 } // namespace crown

+ 4 - 0
src/resource/shader_resource.h

@@ -5,6 +5,7 @@
 
 #pragma once
 
+#include "config.h"
 #include "core/containers/types.h"
 #include "core/filesystem/types.h"
 #include "core/memory/types.h"
@@ -45,6 +46,9 @@ struct ShaderData
 	u64 state;
 	ShaderResource::Sampler samplers[4];
 	bgfx::ProgramHandle program;
+#if CROWN_CAN_RELOAD
+	const ShaderResource *resource;
+#endif
 };
 
 namespace shader_resource_internal

+ 20 - 0
src/world/material_manager.cpp

@@ -141,6 +141,26 @@ void MaterialManager::reload_textures(const TextureResource *old_resource, const
 	}
 #else
 	CE_UNUSED_2(old_resource, new_resource);
+	CE_NOOP();
+#endif
+}
+
+void MaterialManager::reload_shaders(const ShaderResource *old_resource, const ShaderResource *new_resource)
+{
+#if CROWN_CAN_RELOAD
+	auto cur = hash_map::begin(_materials);
+	auto end = hash_map::end(_materials);
+	for (; cur != end; ++cur) {
+		HASH_MAP_SKIP_HOLE(_materials, cur);
+
+		Material *m = cur->second;
+		if (m->_shader.resource == old_resource) {
+			m->_shader = _shader_manager->shader(m->_resource->shader);
+		}
+	}
+#else
+	CE_UNUSED_2(old_resource, new_resource);
+	CE_NOOP();
 #endif
 }
 

+ 3 - 0
src/world/material_manager.h

@@ -41,6 +41,9 @@ struct MaterialManager
 
 	///
 	void reload_textures(const TextureResource *old_resource, const TextureResource *new_resource);
+
+	///
+	void reload_shaders(const ShaderResource *old_resource, const ShaderResource *new_resource);
 };
 
 } // namespace crown

+ 59 - 0
src/world/render_world.cpp

@@ -483,6 +483,27 @@ void RenderWorld::unit_destroyed_callback(UnitId unit)
 	}
 }
 
+void RenderWorld::reload_materials(const MaterialResource *old_resource, const MaterialResource *new_resource)
+{
+#if CROWN_CAN_RELOAD
+	for (u32 i = 0; i < _mesh_manager._data.size; ++i) {
+		if (_mesh_manager._data.material_resource[i] == old_resource) {
+			_mesh_manager._data.material[i] = _material_manager->get(new_resource);
+			_mesh_manager._data.material_resource[i] = new_resource;
+		}
+	}
+
+	for (u32 i = 0; i < _sprite_manager._data.size; ++i) {
+		if (_sprite_manager._data.material_resource[i] == old_resource) {
+			_sprite_manager._data.material[i] = _material_manager->get(new_resource);
+			_sprite_manager._data.material_resource[i] = new_resource;
+		}
+	}
+#else
+	CE_UNUSED_2(old_resource, new_resource);
+#endif
+}
+
 void RenderWorld::MeshManager::allocate(u32 num)
 {
 	CE_ENSURE(num > _data.size);
@@ -495,6 +516,10 @@ void RenderWorld::MeshManager::allocate(u32 num)
 		+ num*sizeof(Material *) + alignof(Material *)
 		+ num*sizeof(Matrix4x4) + alignof(Matrix4x4)
 		+ num*sizeof(OBB) + alignof(OBB)
+#if CROWN_CAN_RELOAD
+		+ num*sizeof(MaterialResource *) + alignof(MaterialResource *)
+#endif
+		+ 0
 		;
 
 	MeshInstanceData new_data;
@@ -510,6 +535,9 @@ void RenderWorld::MeshManager::allocate(u32 num)
 	new_data.material      = (Material **          )memory::align_top(new_data.mesh + num,     alignof(Material *));
 	new_data.world         = (Matrix4x4 *          )memory::align_top(new_data.material + num, alignof(Matrix4x4));
 	new_data.obb           = (OBB *                )memory::align_top(new_data.world + num,    alignof(OBB));
+#if CROWN_CAN_RELOAD
+	new_data.material_resource = (const MaterialResource **)memory::align_top(new_data.obb + num, alignof(MaterialResource *));
+#endif
 
 	memcpy(new_data.unit, _data.unit, _data.size * sizeof(UnitId));
 	memcpy(new_data.resource, _data.resource, _data.size * sizeof(MeshResource *));
@@ -518,6 +546,9 @@ void RenderWorld::MeshManager::allocate(u32 num)
 	memcpy(new_data.material, _data.material, _data.size * sizeof(Material *));
 	memcpy(new_data.world, _data.world, _data.size * sizeof(Matrix4x4));
 	memcpy(new_data.obb, _data.obb, _data.size * sizeof(OBB));
+#if CROWN_CAN_RELOAD
+	memcpy(new_data.material_resource, _data.material_resource, _data.size * sizeof(MaterialResource *));
+#endif
 
 	_allocator->deallocate(_data.buffer);
 	_data = new_data;
@@ -548,6 +579,9 @@ MeshInstance RenderWorld::MeshManager::create(UnitId unit, const MeshResource *m
 	_data.material[last] = _render_world->_material_manager->get(mat_res);
 	_data.world[last]    = tr;
 	_data.obb[last]      = mg->obb;
+#if CROWN_CAN_RELOAD
+	_data.material_resource[last] = mat_res;
+#endif
 
 	hash_map::set(_map, unit, last);
 	++_data.size;
@@ -582,6 +616,9 @@ void RenderWorld::MeshManager::destroy(MeshInstance inst)
 	_data.material[inst.i] = _data.material[last];
 	_data.world[inst.i]    = _data.world[last];
 	_data.obb[inst.i]      = _data.obb[last];
+#if CROWN_CAN_RELOAD
+	_data.material_resource[inst.i] = _data.material_resource[last];
+#endif
 
 	hash_map::set(_map, last_u, inst.i);
 	hash_map::remove(_map, u);
@@ -613,6 +650,9 @@ void RenderWorld::MeshManager::swap(u32 inst_a, u32 inst_b)
 	exchange(_data.material[inst_a], _data.material[inst_b]);
 	exchange(_data.world[inst_a],    _data.world[inst_b]);
 	exchange(_data.obb[inst_a],      _data.obb[inst_b]);
+#if CROWN_CAN_RELOAD
+	exchange(_data.material_resource[inst_a], _data.material_resource[inst_b]);
+#endif
 
 	hash_map::set(_map, unit_a, inst_b);
 	hash_map::set(_map, unit_b, inst_a);
@@ -691,6 +731,10 @@ void RenderWorld::SpriteManager::allocate(u32 num)
 		+ num*sizeof(bool) + alignof(bool)
 		+ num*sizeof(u32) + alignof(u32)
 		+ num*sizeof(u32) + alignof(u32)
+#if CROWN_CAN_RELOAD
+		+ num*sizeof(MaterialResource **) + alignof(MaterialResource *)
+#endif
+		+ 0
 		;
 
 	SpriteInstanceData new_data;
@@ -709,6 +753,9 @@ void RenderWorld::SpriteManager::allocate(u32 num)
 	new_data.flip_y   = (bool *                 )memory::align_top(new_data.flip_x + num,   alignof(bool));
 	new_data.layer    = (u32 *                  )memory::align_top(new_data.flip_y + num,   alignof(u32));
 	new_data.depth    = (u32 *                  )memory::align_top(new_data.layer + num,    alignof(u32));
+#if CROWN_CAN_RELOAD
+	new_data.material_resource = (const MaterialResource **)memory::align_top(new_data.depth + num, alignof(MaterialResource *));
+#endif
 
 	memcpy(new_data.unit, _data.unit, _data.size * sizeof(UnitId));
 	memcpy(new_data.resource, _data.resource, _data.size * sizeof(SpriteResource**));
@@ -720,6 +767,9 @@ void RenderWorld::SpriteManager::allocate(u32 num)
 	memcpy(new_data.flip_y, _data.flip_y, _data.size * sizeof(bool));
 	memcpy(new_data.layer, _data.layer, _data.size * sizeof(u32));
 	memcpy(new_data.depth, _data.depth, _data.size * sizeof(u32));
+#if CROWN_CAN_RELOAD
+	memcpy(new_data.material_resource, _data.material_resource, _data.size * sizeof(MaterialResource *));
+#endif
 
 	_allocator->deallocate(_data.buffer);
 	_data = new_data;
@@ -751,6 +801,9 @@ SpriteInstance RenderWorld::SpriteManager::create(UnitId unit, const SpriteResou
 	_data.flip_y[last]   = false;
 	_data.layer[last]    = srd.layer;
 	_data.depth[last]    = srd.depth;
+#if CROWN_CAN_RELOAD
+	_data.material_resource[last] = mat_res;
+#endif
 
 	hash_map::set(_map, unit, last);
 	++_data.size;
@@ -787,6 +840,9 @@ void RenderWorld::SpriteManager::destroy(SpriteInstance inst)
 	_data.flip_y[inst.i]   = _data.flip_y[last];
 	_data.layer[inst.i]    = _data.layer[last];
 	_data.depth[inst.i]    = _data.depth[last];
+#if CROWN_CAN_RELOAD
+	_data.material_resource[inst.i] = _data.material_resource[last];
+#endif
 
 	hash_map::set(_map, last_u, inst.i);
 	hash_map::remove(_map, u);
@@ -821,6 +877,9 @@ void RenderWorld::SpriteManager::swap(u32 inst_a, u32 inst_b)
 	exchange(_data.flip_y[inst_a],   _data.flip_y[inst_b]);
 	exchange(_data.layer[inst_a],    _data.layer[inst_b]);
 	exchange(_data.depth[inst_a],    _data.depth[inst_b]);
+#if CROWN_CAN_RELOAD
+	exchange(_data.material_resource[inst_a], _data.material_resource[inst_b]);
+#endif
 
 	hash_map::set(_map, unit_a, inst_b);
 	hash_map::set(_map, unit_b, inst_a);

+ 9 - 0
src/world/render_world.h

@@ -163,6 +163,9 @@ struct RenderWorld
 	///
 	void unit_destroyed_callback(UnitId unit);
 
+	///
+	void reload_materials(const MaterialResource *old_resource, const MaterialResource *new_resource);
+
 	/// Callback to customize drawing of objects.
 	typedef void (*DrawOverride)(UnitId unit_id, RenderWorld *rw);
 
@@ -190,6 +193,9 @@ struct RenderWorld
 			Material **material;
 			Matrix4x4 *world;
 			OBB *obb;
+#if CROWN_CAN_RELOAD
+			const MaterialResource **material_resource;
+#endif
 		};
 
 		Allocator *_allocator;
@@ -267,6 +273,9 @@ struct RenderWorld
 			bool *flip_y;
 			u32 *layer;
 			u32 *depth;
+#if CROWN_CAN_RELOAD
+			const MaterialResource **material_resource;
+#endif
 		};
 
 		Allocator *_allocator;

+ 7 - 1
src/world/shader_manager.cpp

@@ -31,7 +31,10 @@ static ShaderData SHADER_DATA_INVALID =
 			0u, BGFX_INVALID_HANDLE
 		}
 	},
-	BGFX_INVALID_HANDLE
+	BGFX_INVALID_HANDLE,
+#if CROWN_CAN_RELOAD
+	NULL
+#endif
 };
 
 ShaderManager::ShaderManager(Allocator &a)
@@ -111,6 +114,9 @@ void ShaderManager::online(StringId64 id, ResourceManager &rm)
 		sd.state = data.state;
 		memcpy(sd.samplers, data.samplers, sizeof(sd.samplers));
 		sd.program = program;
+#if CROWN_CAN_RELOAD
+		sd.resource = shader;
+#endif
 		hash_map::set(_shader_map, data.name, sd);
 	}
 }

+ 10 - 0
src/world/world.cpp

@@ -594,6 +594,16 @@ void World::disable_unit_callbacks()
 #endif
 }
 
+void World::reload_materials(const MaterialResource *old_resource, const MaterialResource *new_resource)
+{
+#if CROWN_CAN_RELOAD
+	_render_world->reload_materials(old_resource, new_resource);
+#else
+	CE_UNUSED_2(old_resource, new_resource);
+	CE_NOOP();
+#endif
+}
+
 void spawn_units(World &w, const UnitResource *ur, const Vector3 &pos, const Quaternion &rot, const Vector3 &scl, const UnitId *unit_lookup)
 {
 	SceneGraph *scene_graph = w._scene_graph;

+ 3 - 0
src/world/world.h

@@ -221,6 +221,9 @@ struct World
 	/// other callback. This is used only by the editor to prevent user logic to
 	/// interfere with editor's assumptions.
 	void disable_unit_callbacks();
+
+	///
+	void reload_materials(const MaterialResource *old_resource, const MaterialResource *new_resource);
 };
 
 void spawn_units(World &w, const UnitResource *ur, const Vector3 &pos, const Quaternion &rot, const Vector3 &scl, const UnitId *unit_lookup);