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/memory/tracking_allocator.hpp>
19#include <libsbx/graphics/graphics_module.hpp>
20#include <libsbx/graphics/draw_list.hpp>
22#include <libsbx/graphics/buffers/storage_buffer.hpp>
24#include <libsbx/scenes/scenes_module.hpp>
25#include <libsbx/scenes/components/static_mesh.hpp>
27#include <libsbx/models/material.hpp>
29namespace sbx::models {
37 std::uint32_t transform_index;
38 std::uint32_t material_index;
39 std::uint32_t object_id;
40 std::uint32_t payload;
43enum class bucket : std::uint8_t {
49template<
typename Traits>
52 using traits_type = Traits;
56 using mesh_type =
typename traits_type::mesh_type;
57 using instance_payload =
typename traits_type::instance_payload;
59 using bucket = models::bucket;
69 std::vector<std::uint32_t> command_instance_counts;
70 std::vector<range_reference> ranges;
73 using bucket_map = std::unordered_map<material_key, bucket_entry, material_key_hash>;
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);
82 traits_type::create_shared_buffers(*
this);
85 ~basic_material_draw_list()
override {
86 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
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);
93 traits_type::destroy_shared_buffers(*
this);
96 auto update() ->
void override {
97 _transform_data.clear();
98 _material_data.clear();
100 for (
auto& [key, pipeline_data] : _pipeline_data) {
101 pipeline_data.submesh_instances.clear();
104 for (
auto& buckets : _bucket_ranges) {
108 auto& assets_module = core::engine::get_module<assets::assets_module>();
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();
115 auto memory = std::array<std::uint8_t, 2048u>{};
116 auto pool = std::pmr::monotonic_buffer_resource{memory.data(), memory.size()};
118 auto material_indices = std::pmr::unordered_map<math::uuid, std::uint32_t>{&pool};
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);
124 const auto& material = assets_module.get_asset<models::material>(material_id);
126 auto& pipeline = _get_or_create_pipeline_data(material);
128 auto [entry, created] = material_indices.try_emplace(material_id,
static_cast<std::uint32_t
>(_material_data.size()));
131 _push_material(material);
134 const auto instance = traits_type::make_instance_data(
node, transform_index, entry->second, payload);
136 auto& per_mesh = pipeline.submesh_instances[mesh_id];
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);
142 update_buffer(_transform_data, transform_data_buffer_name);
143 update_buffer(_material_data, material_data_buffer_name);
145 auto camera_node = environment.camera();
146 const auto camera_position = graph.world_position(camera_node);
148 traits_type::update_shared_buffers(*
this);
150 for (
auto& [key, pipeline_data] : _pipeline_data) {
151 if (pipeline_data.submesh_instances.empty()) {
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];
161 const auto position_a = math::vector3(transform_a.model[3]);
162 const auto position_b = math::vector3(transform_b.model[3]);
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);
167 return distance_sq_a < distance_sq_b;
170 std::sort(std::execution::par_unseq, instances.begin(), instances.end(), sort_function);
174 _build_draw_commands(key, pipeline_data);
178 auto ranges(
const bucket bucket)
const ->
const bucket_map& {
179 return _bucket_ranges[utility::to_underlying(bucket)];
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;
187 throw utility::runtime_error{
"Could not find shader path for hash {}", hash};
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;
199 struct pipeline_data {
201 std::unordered_map<math::uuid, std::vector<std::vector<instance_data>>> submesh_instances;
203 graphics::storage_buffer_handle draw_commands_buffer;
204 graphics::storage_buffer_handle instance_data_buffer;
207 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
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);
215 static auto _classify_bucket(
const models::material& material) -> bucket {
216 return (material.alpha == models::alpha_mode::blend) ? bucket::transparent : bucket::opaque;
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;
224 auto [entry, inserted] = _pipeline_data.emplace(key, pipeline_data{});
226 return entry->second;
229 auto _get_or_create_sampler(
const texture_slot& texture_slot) -> graphics::sampler_state_handle {
230 auto key = texture_slot_hash{}(texture_slot);
232 if (
auto entry = _samplers.find(key); entry != _samplers.end()) {
233 return entry->second;
236 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
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));
240 utility::logger<
"models">::debug(
"New sampler created");
242 return entry->second;
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);
249 return std::bit_cast<std::float_t>(
static_cast<std::uint32_t
>(ha) | (
static_cast<std::uint32_t
>(hb) << 16));
252 auto _push_material(
const models::material& material) ->
void {
253 auto data = models::material_data{};
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);
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));
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();
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;
290 data.uv_offset = material.uv_offset;
291 data.uv_scale = material.uv_scale;
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);
297 _material_data.push_back(data);
299 const auto key =
static_cast<material_key
>(material);
301 _surface_shader_paths.emplace(key.surface_shader_hash, material.surface_shader.generic_string());
303 auto& buckets = _material_buckets[key];
305 buckets.insert(_classify_bucket(material));
307 if (material.features.has(models::material_feature::cast_shadow) && material.alpha != models::alpha_mode::blend) {
308 buckets.insert(bucket::shadow);
313 auto _build_draw_commands(
const material_key& key, pipeline_data& pipeline) ->
void {
314 auto& assets_module = core::engine::get_module<assets::assets_module>();
316 auto draw_commands = std::vector<VkDrawIndexedIndirectCommand>{};
317 auto instance_data = std::vector<models::instance_data>{};
319 auto command_instance_counts = std::vector<std::uint32_t>{};
320 auto range = graphics::draw_command_range{};
322 const auto& buckets = _material_buckets.at(key);
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));
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);
340 for (
auto& [mesh_id, submesh_vectors] : pipeline.submesh_instances) {
341 const auto& mesh = assets_module.get_asset<mesh_type>(mesh_id);
343 range.offset =
static_cast<std::uint32_t
>(draw_commands.size());
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);
350 if (range.count > 0) {
351 const auto hash = material_key_hash{}(key);
353 push_draw_command_range(hash, mesh_id, range);
355 for (
const auto& bucket_type : buckets) {
356 auto& entry = _bucket_ranges[utility::to_underlying(bucket_type)][key];
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 });
365 utility::assert_that(emitter.base_instance == instance_data.size(),
"build_draw_commands is broken");
367 if (!draw_commands.empty()) {
368 _update_buffer(pipeline.draw_commands_buffer, draw_commands);
369 _update_buffer(pipeline.instance_data_buffer, instance_data);
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;
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);
385 const auto required_size =
static_cast<std::uint32_t
>(data.size() *
sizeof(Type));
387 if (buffer.size() < required_size) {
388 buffer.resize(
static_cast<std::size_t
>(
static_cast<std::float_t
>(required_size) * 1.5f));
391 if (required_size > 0) {
392 buffer.update(data.data(), required_size);
396 std::vector<transform_data> _transform_data;
397 std::vector<material_data> _material_data;
399 std::unordered_map<material_key, pipeline_data, material_key_hash> _pipeline_data;
401 std::array<bucket_map, magic_enum::enum_count<bucket>()> _bucket_ranges;
403 inline static auto _material_buckets = std::unordered_map<material_key, std::unordered_set<bucket>, material_key_hash>{};
405 inline static auto _surface_shader_paths = std::unordered_map<std::uint32_t, std::string>{};
407 inline static auto _samplers = std::unordered_map<std::size_t, graphics::sampler_state_handle>{};
416 static constexpr auto name() -> std::string_view {
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}
Definition: draw_list.hpp:28
Definition: resource_storage.hpp:18
Definition: matrix4x4.hpp:26
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: description.hpp:16
Definition: enumerator.hpp:10