2#ifndef LIBSBX_MODELS_MATERIAL_DRAW_LIST_HPP_
3#define LIBSBX_MODELS_MATERIAL_DRAW_LIST_HPP_
5#include <memory_resource>
9#include <magic_enum/magic_enum.hpp>
11#include <libsbx/reflection/description.hpp>
13#include <libsbx/math/half.hpp>
15#include <libsbx/assets/assets_module.hpp>
17#include <libsbx/utility/profiler.hpp>
19#include <libsbx/memory/tracking_allocator.hpp>
21#include <libsbx/graphics/graphics_module.hpp>
22#include <libsbx/graphics/draw_list.hpp>
24#include <libsbx/graphics/buffers/storage_buffer.hpp>
26#include <libsbx/scenes/scenes_module.hpp>
27#include <libsbx/scenes/components/static_mesh.hpp>
29#include <libsbx/models/material.hpp>
31namespace sbx::models {
39 std::uint32_t transform_index;
40 std::uint32_t material_index;
41 std::uint32_t object_id;
42 std::uint32_t payload;
45enum class bucket : std::uint8_t {
51template<
typename Traits>
54 using traits_type = Traits;
58 using mesh_type =
typename traits_type::mesh_type;
59 using instance_payload =
typename traits_type::instance_payload;
61 using bucket = models::bucket;
71 std::vector<std::uint32_t> command_instance_counts;
72 std::vector<range_reference> ranges;
75 using bucket_map = std::unordered_map<material_key, bucket_entry, material_key_hash>;
80 basic_material_draw_list() {
81 create_buffer(transform_data_buffer_name, graphics::storage_buffer::min_size, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
82 create_buffer(material_data_buffer_name, graphics::storage_buffer::min_size, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
84 traits_type::create_shared_buffers(*
this);
87 ~basic_material_draw_list()
override {
88 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
90 for (
const auto& [key, data] : _pipeline_data) {
91 graphics_module.remove_resource<graphics::storage_buffer>(data.draw_commands_buffer);
92 graphics_module.remove_resource<graphics::storage_buffer>(data.instance_data_buffer);
95 traits_type::destroy_shared_buffers(*
this);
98 auto update() ->
void override {
99 SBX_PROFILE_SCOPE(
"basic_material_draw_list::update");
101 SBX_PROFILE_SCOPE_START(s0,
"clear data");
103 _transform_data.clear();
104 _material_data.clear();
106 for (
auto& [key, pipeline_data] : _pipeline_data) {
107 pipeline_data.submesh_instances.clear();
110 for (
auto& buckets : _bucket_ranges) {
114 SBX_PROFILE_SCOPE_END(s0);
116 auto& assets_module = core::engine::get_module<assets::assets_module>();
118 auto& scenes_module = core::engine::get_module<scenes::scenes_module>();
119 auto& scene = scenes_module.active_scene();
120 auto& environment = scene.environment();
121 auto& graph = scene.graph();
123 SBX_PROFILE_SCOPE_START(s1,
"classify submissions");
125 auto memory = std::array<std::uint8_t, 4096u>{};
126 auto pool = std::pmr::monotonic_buffer_resource{memory.data(), memory.size()};
128 auto material_indices = std::pmr::unordered_map<math::uuid, std::uint32_t>{&pool};
130 using bucket_set = std::pmr::unordered_set<bucket>;
131 auto material_buckets = std::pmr::unordered_map<material_key, bucket_set, material_key_hash>{&pool};
133 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) {
134 const auto transform_index =
static_cast<std::uint32_t
>(_transform_data.size());
135 _transform_data.push_back(transform);
137 const auto& material = assets_module.get_asset<models::material>(material_id);
139 auto& pipeline = _get_or_create_pipeline_data(material);
141 auto [entry, created] = material_indices.try_emplace(material_id,
static_cast<std::uint32_t
>(_material_data.size()));
144 _push_material(material, material_buckets, pool);
147 const auto instance = traits_type::make_instance_data(
node, transform_index, entry->second, payload);
149 auto& per_mesh = pipeline.submesh_instances[mesh_id];
151 per_mesh.resize(std::max(per_mesh.size(),
static_cast<std::size_t
>(submesh_index + 1u)));
152 per_mesh[submesh_index].push_back(instance);
155 SBX_PROFILE_SCOPE_END(s1);
157 update_buffer(_transform_data, transform_data_buffer_name);
158 update_buffer(_material_data, material_data_buffer_name);
160 auto camera_node = environment.camera();
161 const auto camera_position = graph.world_position(camera_node);
163 traits_type::update_shared_buffers(*
this);
165 SBX_PROFILE_SCOPE_START(s2,
"build draw commands");
167 for (
auto& [key, pipeline_data] : _pipeline_data) {
168 if (pipeline_data.submesh_instances.empty()) {
172 for (
auto& [mesh_id, submesh_vectors] : pipeline_data.submesh_instances) {
173 for (
auto& instances : submesh_vectors) {
174 auto sort_function = [&](
const instance_data& a,
const instance_data& b) {
175 const auto& transform_a = _transform_data[a.transform_index];
176 const auto& transform_b = _transform_data[b.transform_index];
178 const auto position_a = math::vector3(transform_a.model[3]);
179 const auto position_b = math::vector3(transform_b.model[3]);
181 const auto distance_sq_a = math::vector3::distance_squared(camera_position, position_a);
182 const auto distance_sq_b = math::vector3::distance_squared(camera_position, position_b);
184 return distance_sq_a < distance_sq_b;
187 std::sort(std::execution::par_unseq, instances.begin(), instances.end(), sort_function);
191 _build_draw_commands(key, pipeline_data, material_buckets);
194 SBX_PROFILE_SCOPE_END(s2);
197 auto ranges(
const bucket bucket)
const ->
const bucket_map& {
198 return _bucket_ranges[utility::to_underlying(bucket)];
201 static auto surface_shader_path(
const std::uint32_t hash) -> std::string {
202 if (
auto entry = _surface_shader_paths.find(hash); entry != _surface_shader_paths.end()) {
203 return entry->second;
206 throw utility::runtime_error{
"Could not find shader path for hash {}", hash};
211 template<
typename EmitInstanced,
typename EmitSingle>
212 struct draw_command_emitter {
213 std::uint32_t base_instance;
214 EmitInstanced emit_instanced;
215 EmitSingle emit_single;
218 struct pipeline_data {
220 std::unordered_map<math::uuid, std::vector<std::vector<instance_data>>> submesh_instances;
222 graphics::storage_buffer_handle draw_commands_buffer;
223 graphics::storage_buffer_handle instance_data_buffer;
226 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
228 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);
229 instance_data_buffer = graphics_module.add_resource<graphics::storage_buffer>(graphics::storage_buffer::min_size, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
234 static auto _classify_bucket(
const models::material& material) -> bucket {
235 return (material.alpha == models::alpha_mode::blend) ? bucket::transparent : bucket::opaque;
238 auto _get_or_create_pipeline_data(
const material_key& key) -> pipeline_data& {
239 if (
auto entry = _pipeline_data.find(key); entry != _pipeline_data.end()) {
240 return entry->second;
243 auto [entry, inserted] = _pipeline_data.emplace(key, pipeline_data{});
245 return entry->second;
248 auto _get_or_create_sampler(
const texture_slot& texture_slot) -> graphics::sampler_state_handle {
249 auto key = texture_slot_hash{}(texture_slot);
251 if (
auto entry = _samplers.find(key); entry != _samplers.end()) {
252 return entry->second;
255 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
257 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));
259 utility::logger<
"models">::debug(
"New sampler created");
261 return entry->second;
264 static auto _pack_half2(std::float_t a, std::float_t b) -> std::float_t {
265 auto ha = math::float_to_half(a);
266 auto hb = math::float_to_half(b);
268 return std::bit_cast<std::float_t>(
static_cast<std::uint32_t
>(ha) | (
static_cast<std::uint32_t
>(hb) << 16));
271 auto _push_material(
const models::material& material, std::pmr::unordered_map<material_key, std::pmr::unordered_set<bucket>, material_key_hash>& material_buckets, std::pmr::monotonic_buffer_resource& pool) ->
void {
272 auto data = models::material_data{};
275 data.albedo_image_index = add_image(material.albedo.image);
276 data.normal_image_index = add_image(material.normal.image);
277 data.metallic_roughness_image_index = add_image(material.metallic_roughness.image);
278 data.occlusion_image_index = add_image(material.occlusion.image);
279 data.emissive_image_index = add_image(material.emissive.image);
280 data.height_image_index = add_image(material.height.image);
283 data.albedo_sampler_index = add_sampler_state(_get_or_create_sampler(material.albedo));
284 data.normal_sampler_index = add_sampler_state(_get_or_create_sampler(material.normal));
285 data.metallic_roughness_sampler_index = add_sampler_state(_get_or_create_sampler(material.metallic_roughness));
286 data.occlusion_sampler_index = add_sampler_state(_get_or_create_sampler(material.occlusion));
287 data.emissive_sampler_index = add_sampler_state(_get_or_create_sampler(material.emissive));
288 data.height_sampler_index = add_sampler_state(_get_or_create_sampler(material.height));
291 data.base_color = material.base_color;
292 data.metallic_factor = material.metallic_factor;
293 data.roughness_factor = material.roughness_factor;
294 data.occlusion_strength = material.occlusion_strength;
295 data.normal_scale = material.normal_scale;
296 data.emissive_strength = material.emissive_strength;
297 data.emissive_factor = material.emissive_factor;
298 data.specular_factor = material.specular_factor;
299 data.alpha_cutoff = material.alpha_cutoff;
300 data.flags = material.features.underlying();
303 data.height_scale = material.height_scale;
304 data.height_offset = material.height_offset;
305 data.parallax_min_layers = material.parallax_min_layers;
306 data.parallax_max_layers = material.parallax_max_layers;
309 data.uv_offset = material.uv_offset;
310 data.uv_scale = material.uv_scale;
312 data.sway_speed_strength = _pack_half2(material.sway_speed, material.sway_strength);
313 data.scrumble_speed_strength = _pack_half2(material.scrumble_speed, material.scrumble_strength);
314 data.falloff_exponents = _pack_half2(material.sway_falloff_exponent, material.scrumble_falloff_exponent);
316 _material_data.push_back(data);
318 const auto key =
static_cast<material_key
>(material);
320 _surface_shader_paths.emplace(key.surface_shader_hash, material.surface_shader.generic_string());
322 auto [entry, inserted] = material_buckets.try_emplace(key, std::pmr::unordered_set<bucket>{&pool});
324 auto& buckets = entry->second;
326 buckets.insert(_classify_bucket(material));
328 if (material.features.has(models::material_feature::cast_shadow) && material.alpha != models::alpha_mode::blend) {
329 buckets.insert(bucket::shadow);
333 auto _build_draw_commands(
const material_key& key, pipeline_data& pipeline,
const std::pmr::unordered_map<material_key, std::pmr::unordered_set<bucket>, material_key_hash>& material_buckets) ->
void {
334 SBX_PROFILE_SCOPE(
"build_draw_commands");
336 auto& assets_module = core::engine::get_module<assets::assets_module>();
338 auto draw_commands = std::vector<VkDrawIndexedIndirectCommand>{};
339 auto instance_data = std::vector<models::instance_data>{};
340 auto command_instance_counts = std::vector<std::uint32_t>{};
341 auto range = graphics::draw_command_range{};
343 const auto& buckets = material_buckets.at(key);
345 auto emitter = draw_command_emitter{
346 .base_instance = std::uint32_t{0u},
347 .emit_instanced = [&](
const VkDrawIndexedIndirectCommand& command, std::vector<models::instance_data>&& instances) ->
void {
348 draw_commands.push_back(command);
349 command_instance_counts.push_back(command.instanceCount);
350 utility::append(instance_data, std::move(instances));
353 .emit_single = [&](
const VkDrawIndexedIndirectCommand& command,
const models::instance_data& instance) ->
void {
354 draw_commands.push_back(command);
355 command_instance_counts.push_back(command.instanceCount);
356 instance_data.push_back(instance);
361 for (
auto& [mesh_id, submesh_vectors] : pipeline.submesh_instances) {
362 const auto& mesh = assets_module.get_asset<mesh_type>(mesh_id);
364 range.offset =
static_cast<std::uint32_t
>(draw_commands.size());
367 for (
auto&& [submesh_index, instances] : ranges::views::enumerate(submesh_vectors)) {
368 emitter.base_instance += traits_type::build_draw_commands(mesh, submesh_index, std::move(instances), emitter);
371 if (range.count > 0) {
372 const auto hash = material_key_hash{}(key);
374 push_draw_command_range(hash, mesh_id, range);
376 for (
const auto& bucket_type : buckets) {
377 auto& entry = _bucket_ranges[utility::to_underlying(bucket_type)][key];
379 entry.draw_commands_buffer = pipeline.draw_commands_buffer;
380 entry.instance_data_buffer = pipeline.instance_data_buffer;
381 entry.ranges.push_back(range_reference{ .mesh_id = mesh_id, .range = range });
386 utility::assert_that(emitter.base_instance == instance_data.size(),
"build_draw_commands is broken");
388 if (!draw_commands.empty()) {
389 _update_buffer(pipeline.draw_commands_buffer, draw_commands);
390 _update_buffer(pipeline.instance_data_buffer, instance_data);
392 for (
auto& bucket_ranges : _bucket_ranges) {
393 if (
auto entry = bucket_ranges.find(key); entry != bucket_ranges.end()) {
394 entry->second.command_instance_counts = command_instance_counts;
401 template <
typename Type>
402 static auto _update_buffer(graphics::storage_buffer_handle handle,
const std::vector<Type>& data) ->
void {
403 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
404 auto& buffer = graphics_module.get_resource<graphics::storage_buffer>(handle);
406 const auto required_size =
static_cast<std::uint32_t
>(data.size() *
sizeof(Type));
408 if (buffer.size() < required_size) {
409 buffer.resize(
static_cast<std::size_t
>(
static_cast<std::float_t
>(required_size) * 1.5f));
412 if (required_size > 0) {
413 buffer.update(data.data(), required_size);
417 std::vector<transform_data> _transform_data;
418 std::vector<material_data> _material_data;
420 std::unordered_map<material_key, pipeline_data, material_key_hash> _pipeline_data;
422 std::array<bucket_map, magic_enum::enum_count<bucket>()> _bucket_ranges;
424 inline static auto _surface_shader_paths = std::unordered_map<std::uint32_t, std::string>{};
426 inline static auto _samplers = std::unordered_map<std::size_t, graphics::sampler_state_handle>{};
435 static constexpr auto name() -> std::string_view {
439 static constexpr auto enumerators() {
440 return std::make_tuple(
441 enumerator{
"opaque", sbx::models::bucket::opaque},
442 enumerator{
"transparent", sbx::models::bucket::transparent},
443 enumerator{
"shadow", sbx::models::bucket::shadow}
Definition: draw_list.hpp:28
Definition: resource_storage.hpp:18
Definition: matrix4x4.hpp:26
Definition: material_draw_list.hpp:52
Definition: hashed_string.hpp:17
Definition: draw_list.hpp:23
Definition: material_draw_list.hpp:68
Definition: material_draw_list.hpp:63
Definition: material_draw_list.hpp:38
Definition: description.hpp:16
Definition: enumerator.hpp:10