sandbox
Loading...
Searching...
No Matches
material_draw_list.hpp
1// SPDX-License-Identifier: MIT
2#ifndef LIBSBX_MODELS_MATERIAL_DRAW_LIST_HPP_
3#define LIBSBX_MODELS_MATERIAL_DRAW_LIST_HPP_
4
5#include <memory_resource>
6#include <execution>
7#include <string>
8
9#include <magic_enum/magic_enum.hpp>
10
11#include <libsbx/reflection/description.hpp>
12
13#include <libsbx/math/half.hpp>
14
15#include <libsbx/assets/assets_module.hpp>
16
17#include <libsbx/memory/tracking_allocator.hpp>
18
19#include <libsbx/graphics/graphics_module.hpp>
20#include <libsbx/graphics/draw_list.hpp>
21
22#include <libsbx/graphics/buffers/storage_buffer.hpp>
23
24#include <libsbx/scenes/scenes_module.hpp>
25#include <libsbx/scenes/components/static_mesh.hpp>
26
27#include <libsbx/models/material.hpp>
28
29namespace sbx::models {
30
31struct alignas(16) transform_data {
32 math::matrix4x4 model;
33 math::matrix4x4 normal;
34}; // struct transform_data
35
36struct alignas(16) instance_data {
37 std::uint32_t transform_index;
38 std::uint32_t material_index;
39 std::uint32_t object_id;
40 std::uint32_t payload;
41}; // struct instance_data
42
43enum class bucket : std::uint8_t {
44 opaque,
45 transparent,
46 shadow
47}; // enum class bucket
48
49template<typename Traits>
51
52 using traits_type = Traits;
53
54public:
55
56 using mesh_type = typename traits_type::mesh_type;
57 using instance_payload = typename traits_type::instance_payload;
58
59 using bucket = models::bucket;
60
62 math::uuid mesh_id;
64 }; // struct range_reference
65
66 struct bucket_entry {
67 graphics::storage_buffer_handle draw_commands_buffer{};
68 graphics::storage_buffer_handle instance_data_buffer{};
69 std::vector<std::uint32_t> command_instance_counts;
70 std::vector<range_reference> ranges;
71 }; // struct bucket_entry
72
73 using bucket_map = std::unordered_map<material_key, bucket_entry, material_key_hash>;
74
75 inline static const auto transform_data_buffer_name = utility::hashed_string{"transform_data"};
76 inline static const auto material_data_buffer_name = utility::hashed_string{"material_data"};
77
78 basic_material_draw_list() {
79 create_buffer(transform_data_buffer_name, graphics::storage_buffer::min_size, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
80 create_buffer(material_data_buffer_name, graphics::storage_buffer::min_size, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
81
82 traits_type::create_shared_buffers(*this);
83 }
84
85 ~basic_material_draw_list() override {
86 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
87
88 for (const auto& [key, data] : _pipeline_data) {
89 graphics_module.remove_resource<graphics::storage_buffer>(data.draw_commands_buffer);
90 graphics_module.remove_resource<graphics::storage_buffer>(data.instance_data_buffer);
91 }
92
93 traits_type::destroy_shared_buffers(*this);
94 }
95
96 auto update() -> void override {
97 _transform_data.clear();
98 _material_data.clear();
99
100 for (auto& [key, pipeline_data] : _pipeline_data) {
101 pipeline_data.submesh_instances.clear();
102 }
103
104 for (auto& buckets : _bucket_ranges) {
105 buckets.clear();
106 }
107
108 auto& assets_module = core::engine::get_module<assets::assets_module>();
109
110 auto& scenes_module = core::engine::get_module<scenes::scenes_module>();
111 auto& scene = scenes_module.active_scene();
112 auto& environment = scene.environment();
113 auto& graph = scene.graph();
114
115 auto memory = std::array<std::uint8_t, 2048u>{};
116 auto pool = std::pmr::monotonic_buffer_resource{memory.data(), memory.size()};
117
118 auto material_indices = std::pmr::unordered_map<math::uuid, std::uint32_t>{&pool};
119
120 traits_type::for_each_submission(scene, [&](const scenes::node node, const math::uuid& mesh_id, std::uint32_t submesh_index, const math::uuid& material_id, const transform_data& transform, const instance_payload& payload) {
121 const auto transform_index = static_cast<std::uint32_t>(_transform_data.size());
122 _transform_data.push_back(transform);
123
124 const auto& material = assets_module.get_asset<models::material>(material_id);
125
126 auto& pipeline = _get_or_create_pipeline_data(material);
127
128 auto [entry, created] = material_indices.try_emplace(material_id, static_cast<std::uint32_t>(_material_data.size()));
129
130 if (created) {
131 _push_material(material);
132 }
133
134 const auto instance = traits_type::make_instance_data(node, transform_index, entry->second, payload);
135
136 auto& per_mesh = pipeline.submesh_instances[mesh_id];
137
138 per_mesh.resize(std::max(per_mesh.size(), static_cast<std::size_t>(submesh_index + 1u)));
139 per_mesh[submesh_index].push_back(instance);
140 });
141
142 update_buffer(_transform_data, transform_data_buffer_name);
143 update_buffer(_material_data, material_data_buffer_name);
144
145 auto camera_node = environment.camera();
146 const auto camera_position = graph.world_position(camera_node);
147
148 traits_type::update_shared_buffers(*this);
149
150 for (auto& [key, pipeline_data] : _pipeline_data) {
151 if (pipeline_data.submesh_instances.empty()) {
152 continue;
153 }
154
155 for (auto& [mesh_id, submesh_vectors] : pipeline_data.submesh_instances) {
156 for (auto& instances : submesh_vectors) {
157 auto sort_function = [&](const instance_data& a, const instance_data& b) {
158 const auto& transform_a = _transform_data[a.transform_index];
159 const auto& transform_b = _transform_data[b.transform_index];
160
161 const auto position_a = math::vector3(transform_a.model[3]);
162 const auto position_b = math::vector3(transform_b.model[3]);
163
164 const auto distance_sq_a = math::vector3::distance_squared(camera_position, position_a);
165 const auto distance_sq_b = math::vector3::distance_squared(camera_position, position_b);
166
167 return distance_sq_a < distance_sq_b;
168 };
169
170 std::sort(std::execution::par_unseq, instances.begin(), instances.end(), sort_function);
171 }
172 }
173
174 _build_draw_commands(key, pipeline_data);
175 }
176 }
177
178 auto ranges(const bucket bucket) const -> const bucket_map& {
179 return _bucket_ranges[utility::to_underlying(bucket)];
180 }
181
182 static auto surface_shader_path(const std::uint32_t hash) -> std::string {
183 if (auto entry = _surface_shader_paths.find(hash); entry != _surface_shader_paths.end()) {
184 return entry->second;
185 }
186
187 throw utility::runtime_error{"Could not find shader path for hash {}", hash};
188 }
189
190private:
191
192 template<typename EmitInstanced, typename EmitSingle>
193 struct draw_command_emitter {
194 std::uint32_t base_instance;
195 EmitInstanced emit_instanced;
196 EmitSingle emit_single;
197 }; // struct draw_command_emitter
198
199 struct pipeline_data {
200
201 std::unordered_map<math::uuid, std::vector<std::vector<instance_data>>> submesh_instances;
202
203 graphics::storage_buffer_handle draw_commands_buffer;
204 graphics::storage_buffer_handle instance_data_buffer;
205
206 pipeline_data() {
207 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
208
209 draw_commands_buffer = graphics_module.add_resource<graphics::storage_buffer>(graphics::storage_buffer::min_size, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT);
210 instance_data_buffer = graphics_module.add_resource<graphics::storage_buffer>(graphics::storage_buffer::min_size, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
211 }
212
213 }; // struct pipeline_data
214
215 static auto _classify_bucket(const models::material& material) -> bucket {
216 return (material.alpha == models::alpha_mode::blend) ? bucket::transparent : bucket::opaque;
217 }
218
219 auto _get_or_create_pipeline_data(const material_key& key) -> pipeline_data& {
220 if (auto entry = _pipeline_data.find(key); entry != _pipeline_data.end()) {
221 return entry->second;
222 }
223
224 auto [entry, inserted] = _pipeline_data.emplace(key, pipeline_data{});
225
226 return entry->second;
227 }
228
229 auto _get_or_create_sampler(const texture_slot& texture_slot) -> graphics::sampler_state_handle {
230 auto key = texture_slot_hash{}(texture_slot);
231
232 if (auto entry = _samplers.find(key); entry != _samplers.end()) {
233 return entry->second;
234 }
235
236 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
237
238 auto [entry, inserted] = _samplers.emplace(key, graphics_module.add_resource<graphics::sampler_state>(texture_slot.mag_filter, texture_slot.min_filter, texture_slot.address_mode_u, texture_slot.address_mode_v, texture_slot.anisotropy));
239
240 utility::logger<"models">::debug("New sampler created");
241
242 return entry->second;
243 }
244
245 static auto _pack_half2(std::float_t a, std::float_t b) -> std::float_t {
246 auto ha = math::float_to_half(a);
247 auto hb = math::float_to_half(b);
248
249 return std::bit_cast<std::float_t>(static_cast<std::uint32_t>(ha) | (static_cast<std::uint32_t>(hb) << 16));
250 }
251
252 auto _push_material(const models::material& material) -> void {
253 auto data = models::material_data{};
254
255 // Images
256 data.albedo_image_index = add_image(material.albedo.image);
257 data.normal_image_index = add_image(material.normal.image);
258 data.metallic_roughness_image_index = add_image(material.metallic_roughness.image);
259 data.occlusion_image_index = add_image(material.occlusion.image);
260 data.emissive_image_index = add_image(material.emissive.image);
261 data.height_image_index = add_image(material.height.image);
262
263 // Samplers
264 data.albedo_sampler_index = add_sampler_state(_get_or_create_sampler(material.albedo));
265 data.normal_sampler_index = add_sampler_state(_get_or_create_sampler(material.normal));
266 data.metallic_roughness_sampler_index = add_sampler_state(_get_or_create_sampler(material.metallic_roughness));
267 data.occlusion_sampler_index = add_sampler_state(_get_or_create_sampler(material.occlusion));
268 data.emissive_sampler_index = add_sampler_state(_get_or_create_sampler(material.emissive));
269 data.height_sampler_index = add_sampler_state(_get_or_create_sampler(material.height));
270
271 // Factors
272 data.base_color = material.base_color;
273 data.metallic_factor = material.metallic_factor;
274 data.roughness_factor = material.roughness_factor;
275 data.occlusion_strength = material.occlusion_strength;
276 data.normal_scale = material.normal_scale;
277 data.emissive_strength = material.emissive_strength;
278 data.emissive_factor = material.emissive_factor;
279 data.specular_factor = material.specular_factor;
280 data.alpha_cutoff = material.alpha_cutoff;
281 data.flags = material.features.underlying();
282
283 // Parallax
284 data.height_scale = material.height_scale;
285 data.height_offset = material.height_offset;
286 data.parallax_min_layers = material.parallax_min_layers;
287 data.parallax_max_layers = material.parallax_max_layers;
288
289 // UV
290 data.uv_offset = material.uv_offset;
291 data.uv_scale = material.uv_scale;
292
293 data.sway_speed_strength = _pack_half2(material.sway_speed, material.sway_strength);
294 data.scrumble_speed_strength = _pack_half2(material.scrumble_speed, material.scrumble_strength);
295 data.falloff_exponents = _pack_half2(material.sway_falloff_exponent, material.scrumble_falloff_exponent);
296
297 _material_data.push_back(data);
298
299 const auto key = static_cast<material_key>(material);
300
301 _surface_shader_paths.emplace(key.surface_shader_hash, material.surface_shader.generic_string());
302
303 auto& buckets = _material_buckets[key];
304
305 buckets.insert(_classify_bucket(material));
306
307 if (material.features.has(models::material_feature::cast_shadow) && material.alpha != models::alpha_mode::blend) {
308 buckets.insert(bucket::shadow);
309 }
310 }
311
312
313 auto _build_draw_commands(const material_key& key, pipeline_data& pipeline) -> void {
314 auto& assets_module = core::engine::get_module<assets::assets_module>();
315
316 auto draw_commands = std::vector<VkDrawIndexedIndirectCommand>{};
317 auto instance_data = std::vector<models::instance_data>{};
318 // auto base_instance = std::uint32_t{0u};
319 auto command_instance_counts = std::vector<std::uint32_t>{};
320 auto range = graphics::draw_command_range{};
321
322 const auto& buckets = _material_buckets.at(key);
323
324 auto emitter = draw_command_emitter{
325 .base_instance = std::uint32_t{0u},
326 .emit_instanced = [&](const VkDrawIndexedIndirectCommand& command, std::vector<models::instance_data>&& instances) -> void {
327 draw_commands.push_back(command);
328 command_instance_counts.push_back(command.instanceCount);
329 utility::append(instance_data, std::move(instances));
330 range.count++;
331 },
332 .emit_single = [&](const VkDrawIndexedIndirectCommand& command, const models::instance_data& instance) -> void {
333 draw_commands.push_back(command);
334 command_instance_counts.push_back(command.instanceCount);
335 instance_data.push_back(instance);
336 range.count++;
337 }
338 };
339
340 for (auto& [mesh_id, submesh_vectors] : pipeline.submesh_instances) {
341 const auto& mesh = assets_module.get_asset<mesh_type>(mesh_id);
342
343 range.offset = static_cast<std::uint32_t>(draw_commands.size());
344 range.count = 0u;
345
346 for (auto&& [submesh_index, instances] : ranges::views::enumerate(submesh_vectors)) {
347 emitter.base_instance += traits_type::build_draw_commands(mesh, submesh_index, std::move(instances), emitter);
348 }
349
350 if (range.count > 0) {
351 const auto hash = material_key_hash{}(key);
352
353 push_draw_command_range(hash, mesh_id, range);
354
355 for (const auto& bucket_type : buckets) {
356 auto& entry = _bucket_ranges[utility::to_underlying(bucket_type)][key];
357
358 entry.draw_commands_buffer = pipeline.draw_commands_buffer;
359 entry.instance_data_buffer = pipeline.instance_data_buffer;
360 entry.ranges.push_back(range_reference{ .mesh_id = mesh_id, .range = range });
361 }
362 }
363 }
364
365 utility::assert_that(emitter.base_instance == instance_data.size(), "build_draw_commands is broken");
366
367 if (!draw_commands.empty()) {
368 _update_buffer(pipeline.draw_commands_buffer, draw_commands);
369 _update_buffer(pipeline.instance_data_buffer, instance_data);
370
371 for (auto& bucket_ranges : _bucket_ranges) {
372 if (auto entry = bucket_ranges.find(key); entry != bucket_ranges.end()) {
373 entry->second.command_instance_counts = command_instance_counts;
374 }
375 }
376 }
377 }
378
379
380 template <typename Type>
381 static auto _update_buffer(graphics::storage_buffer_handle handle, const std::vector<Type>& data) -> void {
382 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
383 auto& buffer = graphics_module.get_resource<graphics::storage_buffer>(handle);
384
385 const auto required_size = static_cast<std::uint32_t>(data.size() * sizeof(Type));
386
387 if (buffer.size() < required_size) {
388 buffer.resize(static_cast<std::size_t>(static_cast<std::float_t>(required_size) * 1.5f));
389 }
390
391 if (required_size > 0) {
392 buffer.update(data.data(), required_size);
393 }
394 }
395
396 std::vector<transform_data> _transform_data;
397 std::vector<material_data> _material_data;
398
399 std::unordered_map<material_key, pipeline_data, material_key_hash> _pipeline_data;
400
401 std::array<bucket_map, magic_enum::enum_count<bucket>()> _bucket_ranges;
402
403 inline static auto _material_buckets = std::unordered_map<material_key, std::unordered_set<bucket>, material_key_hash>{};
404
405 inline static auto _surface_shader_paths = std::unordered_map<std::uint32_t, std::string>{};
406
407 inline static auto _samplers = std::unordered_map<std::size_t, graphics::sampler_state_handle>{};
408
409}; // class material_draw_list
410
411} // namespace sbx::models
412
413template<>
414struct sbx::reflection::description<sbx::models::bucket> {
415
416 static constexpr auto name() -> std::string_view {
417 return "bucket";
418 }
419
420 static constexpr auto enumerators() {
421 return std::make_tuple(
422 enumerator{"opaque", sbx::models::bucket::opaque},
423 enumerator{"transparent", sbx::models::bucket::transparent},
424 enumerator{"shadow", sbx::models::bucket::shadow}
425 );
426 }
427
428}; // struct sbx::reflection::description<sbx::models::bucket>
429
430#endif // LIBSBX_MODELS_MATERIAL_DRAW_LIST_HPP_
Definition: tests.cpp:6
Definition: draw_list.hpp:28
Definition: resource_storage.hpp:18
Definition: matrix4x4.hpp:26
Definition: uuid.hpp:160
Definition: material_draw_list.hpp:50
Definition: hashed_string.hpp:17
Definition: draw_list.hpp:23
Definition: material_draw_list.hpp:66
Definition: material_draw_list.hpp:61
Definition: material_draw_list.hpp:36
Definition: material_draw_list.hpp:31
Definition: description.hpp:16
Definition: enumerator.hpp:10