particle_system.cpp 9.54 KB
Newer Older
1
2
#include <mirrage/renderer/particle_system.hpp>

3
#include <mirrage/ecs/components/transform_comp.hpp>
4
#include <mirrage/graphic/device.hpp>
5
6
#include <mirrage/utils/ranges.hpp>

7
#include <vulkan/vulkan.hpp>
8
9

namespace mirrage::renderer {
10

11
	void Particle_script::bind(vk::CommandBuffer cb) const
12
13
14
	{
		cb.bindPipeline(vk::PipelineBindPoint::eCompute, *_pipeline);
	}
15

16
17
	void Particle_emitter_gpu_data::set(const std::uint64_t* rev,
	                                    vk::Buffer           buffer,
18
	                                    vk::DescriptorSet    uniforms,
19
20
21
22
23
	                                    std::int32_t         offset,
	                                    std::int32_t         count,
	                                    std::uint32_t        feedback_idx)
	{
		_buffer       = buffer;
24
		_uniforms     = uniforms;
25
26
27
28
29
30
31
		_live_rev     = rev;
		_rev          = *rev;
		_offset       = offset;
		_count        = count;
		_feedback_idx = feedback_idx;
	}

32
33
34
35
36
37
	void Particle_emitter::incr_time(float dt)
	{
		_time_accumulator += dt;
		_spawn_entry_timer += dt;
	}

38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
	auto Particle_emitter::spawn(util::default_rand& rand) -> std::int32_t
	{
		if(_cfg->spawn.empty())
			return 0;

		if(_spawn_idx >= _cfg->spawn.size()) {
			_spawn_idx         = 0;
			_spawn_entry_timer = 0;
		}

		auto& entry = _cfg->spawn[_spawn_idx];

		if(_spawn_entry_timer + _time_accumulator > entry.time) {
			_time_accumulator = util::max(1.f / 60, entry.time - _spawn_entry_timer);
			_spawn_idx++;
			_spawn_entry_timer = 0;
		}

Lotrado's avatar
Lotrado committed
56
57
58
		auto pps = (entry.stddev > 0.0f)
		                   ? std::normal_distribution<float>(entry.particles_per_second, entry.stddev)(rand)
		                   : entry.particles_per_second;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

		auto spawn = static_cast<std::int32_t>(_time_accumulator * pps);

		if(pps > 0.f) {
			_last_timestep = static_cast<float>(spawn) / pps;
			_time_accumulator -= _last_timestep;
		} else {
			_last_timestep    = 1.f / 60.f;
			_time_accumulator = 0;
		}
		_particles_to_spawn = spawn;

		return spawn;
	}
	auto Particle_emitter::gpu_data() -> std::shared_ptr<Particle_emitter_gpu_data>
	{
		if(!_gpu_data)
			_gpu_data = std::make_shared<Particle_emitter_gpu_data>();

		return _gpu_data;
	}

81
82
83
	Particle_system::Particle_system(asset::Ptr<Particle_system_config> cfg,
	                                 glm::vec3                          position,
	                                 glm::quat                          rotation)
84
	  : _cfg(std::move(cfg))
85
86
87
	  , _loaded(_cfg.ready())
	  , _emitters(!_loaded ? Emitter_list{}
	                       : util::build_vector(
88
89
	                                 _cfg->emitters.size(),
	                                 [&](auto idx) { return Particle_emitter(_cfg->emitters[idx]); }))
90
	  , _effectors(!_loaded ? Effector_list{} : _cfg->effectors)
91
92
	  , _position(position)
	  , _rotation(rotation)
93
94
	{
	}
95
96
97
98
99
100
101
102
103
104
105
106
	void Particle_system::_check_reload()
	{
		MIRRAGE_INVARIANT(_cfg.ready(),
		                  "Tried to access Particle_system emitters before the config was laoded!");

		if(!_loaded) {
			_loaded    = true;
			_emitters  = util::build_vector(_cfg->emitters.size(),
                                           [&](auto idx) { return Particle_emitter(_cfg->emitters[idx]); });
			_effectors = _cfg->effectors;
		}
	}
107

108
109
110
111
	namespace {
		auto comp_cfg_aid(const Particle_system_comp& comp)
		{
			return comp.particle_system.cfg_aid().process(std::string(), [](auto& aid) { return aid.str(); });
112
		}
113
	} // namespace
114

115
	void load_component(ecs::Deserializer& state, Particle_system_comp& comp)
116
	{
117
		auto aid = comp_cfg_aid(comp);
118

119
120
		auto new_aid = aid;
		state.read_virtual(sf2::vmember("cfg", new_aid));
121

122
123
124
125
126
		if(new_aid != aid) {
			comp.particle_system =
			        new_aid.empty()
			                ? Particle_system{}
			                : Particle_system{state.assets.load<Particle_system_config>(asset::AID(new_aid))};
127
		}
128
129
130
	}
	void save_component(ecs::Serializer& state, const Particle_system_comp& comp)
	{
131
		auto aid = comp_cfg_aid(comp);
132
		state.write_virtual(sf2::vmember("cfg", aid));
133
134
	}

135
136
137
138
139
140
141
142
143
144
145
146
147
	void load_component(ecs::Deserializer& state, Particle_effector_comp& comp) { state.read(comp.effector); }
	void save_component(ecs::Serializer& state, const Particle_effector_comp& comp)
	{
		state.write(comp.effector);
	}


	auto create_particle_shared_desc_set_layout(graphic::Device& device) -> vk::UniqueDescriptorSetLayout
	{
		// global effectors, particles_old, particles_new, feedback_buffer feedback_mapping
		const auto stage = vk::ShaderStageFlagBits::eCompute;

		auto bindings = std::array<vk::DescriptorSetLayoutBinding, 5>{
Florian Oetke's avatar
Florian Oetke committed
148
		        vk::DescriptorSetLayoutBinding{0, vk::DescriptorType::eStorageBuffer, 1, stage},
149
150
151
152
153
154
155
156
157
158
159
		        vk::DescriptorSetLayoutBinding{1, vk::DescriptorType::eStorageBuffer, 1, stage},
		        vk::DescriptorSetLayoutBinding{2, vk::DescriptorType::eStorageBuffer, 1, stage},
		        vk::DescriptorSetLayoutBinding{3, vk::DescriptorType::eStorageBuffer, 1, stage},
		        vk::DescriptorSetLayoutBinding{4, vk::DescriptorType::eStorageBuffer, 1, stage}};

		// vk::DescriptorSetLayoutBinding{5, vk::DescriptorType::eSampledImage, 1, stage},

		return device.create_descriptor_set_layout(bindings);
	}
	auto create_particle_script_pipeline_layout(graphic::Device&        device,
	                                            vk::DescriptorSetLayout shared_desc_set,
160
161
	                                            vk::DescriptorSetLayout storage_buffer,
	                                            vk::DescriptorSetLayout) -> vk::UniquePipelineLayout
162
163
	{
		// shared_data, emitter/particle_type data
164
		auto desc_sets      = std::array<vk::DescriptorSetLayout, 2>{shared_desc_set, storage_buffer};
165
166
		auto push_constants = vk::PushConstantRange{vk::ShaderStageFlagBits::eCompute, 0, 4 * 4 * 4 * 2};

Lotrado's avatar
Lotrado committed
167
168
		return device.vk_device()->createPipelineLayoutUnique(vk::PipelineLayoutCreateInfo{
		        {}, gsl::narrow<std::uint32_t>(desc_sets.size()), desc_sets.data(), 1, &push_constants});
169
170
	}

171
172
173
} // namespace mirrage::renderer

namespace mirrage::asset {
174

175
	Loader<renderer::Particle_script>::Loader(graphic::Device&        device,
176
177
	                                          vk::DescriptorSetLayout storage_buffer,
	                                          vk::DescriptorSetLayout uniform_buffer)
178
	  : _device(device)
179
180
181
	  , _shared_desc_set(renderer::create_particle_shared_desc_set_layout(device))
	  , _layout(renderer::create_particle_script_pipeline_layout(
	            device, *_shared_desc_set, storage_buffer, uniform_buffer))
182
183
184
	{
	}

185
	auto Loader<renderer::Particle_script>::load(istream in) -> renderer::Particle_script
186
	{
187
188
189
		auto code = in.bytes();
		auto module_info =
		        vk::ShaderModuleCreateInfo{{}, code.size(), reinterpret_cast<const uint32_t*>(code.data())};
190

191
		auto module = _device.vk_device()->createShaderModuleUnique(module_info);
192

193
194
		auto stage = vk::PipelineShaderStageCreateInfo{
		        {}, vk::ShaderStageFlagBits::eCompute, *module, "main", nullptr};
195

196
197
198
		return renderer::Particle_script{_device.vk_device()->createComputePipelineUnique(
		        _device.pipeline_cache(), vk::ComputePipelineCreateInfo{{}, stage, *_layout})};
	}
199
200


201
202
203
204
	auto Loader<renderer::Particle_system_config>::load(istream in)
	        -> async::task<renderer::Particle_system_config>
	{
		auto r = renderer::Particle_system_config();
205

206
207
208
209
210
211
		sf2::deserialize_json(in,
		                      [&](auto& msg, uint32_t row, uint32_t column) {
			                      LOG(plog::error) << "Error parsing JSON from " << in.aid().str() << " at "
			                                       << row << ":" << column << ": " << msg;
		                      },
		                      r);
212

213
214
		auto loads = std::vector<async::task<void>>();
		loads.reserve(r.emitters.size() * 2u);
215
216
217

		for(auto& e : r.emitters) {
			e.emit_script = in.manager().load<renderer::Particle_script>(e.emit_script_id);
218
219
220
221
			e.type        = in.manager().load<renderer::Particle_type_config>(e.type_id);

			loads.emplace_back(async::when_all(e.emit_script.internal_task(), e.type.internal_task())
			                           .then([](auto&&...) {}));
222
223
		}

224
225
226
		return async::when_all(loads.begin(), loads.end()).then([r = std::move(r)](auto&&...) mutable {
			return std::move(r);
		});
227
228
	}

229
230
231
232
233
	auto Loader<renderer::Particle_type_config>::load(istream in)
	        -> async::task<renderer::Particle_type_config>
	{
		auto r = renderer::Particle_type_config();

234
235
236
237
238
239
		sf2::deserialize_json(in,
		                      [&](auto& msg, uint32_t row, uint32_t column) {
			                      LOG(plog::error) << "Error parsing JSON from " << in.aid().str() << " at "
			                                       << row << ":" << column << ": " << msg;
		                      },
		                      r);
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266

		auto script     = in.manager().load<renderer::Particle_script>(r.update_script_id);
		r.update_script = script;

		if(!r.model_id.empty()) {
			auto model = in.manager().load<renderer::Model>(r.model_id);
			r.model    = model;

			return async::when_all(script.internal_task(), model.internal_task())
			        .then([r = std::move(r)](auto&&) mutable {
				        MIRRAGE_INVARIANT(!r.model->rigged(),
				                          "Animations are not supported for particle-models");
				        MIRRAGE_INVARIANT(r.model->sub_meshes().size() == 1,
				                          "Particle-models must have exacly one sub-mesh");
				        r.material = r.model->sub_meshes().at(0).material;
				        return std::move(r);
			        });

		} else {
			auto material = in.manager().load<renderer::Material>(r.material_id);
			r.material    = material;

			return async::when_all(script.internal_task(), material.internal_task())
			        .then([r = std::move(r)](auto&&) mutable { return std::move(r); });
		}
	}

267
} // namespace mirrage::asset