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