2#ifndef LIBSBX_MODELS_MATERIAL_DRAW_LIST_HPP_
3#define LIBSBX_MODELS_MATERIAL_DRAW_LIST_HPP_
5#include <memory_resource>
8#include <magic_enum/magic_enum.hpp>
10#include <libsbx/assets/assets_module.hpp>
12#include <libsbx/memory/tracking_allocator.hpp>
14#include <libsbx/graphics/graphics_module.hpp>
15#include <libsbx/graphics/draw_list.hpp>
17#include <libsbx/graphics/buffers/storage_buffer.hpp>
19#include <libsbx/scenes/scenes_module.hpp>
20#include <libsbx/scenes/components/static_mesh.hpp>
22#include <libsbx/models/material.hpp>
24namespace sbx::models {
32 std::uint32_t transform_index;
33 std::uint32_t material_index;
34 std::uint32_t object_id;
35 std::uint32_t payload;
38enum class bucket : std::uint8_t {
44template<
typename Traits>
47 using traits_type = Traits;
51 using mesh_type =
typename traits_type::mesh_type;
52 using instance_payload =
typename traits_type::instance_payload;
54 using bucket = models::bucket;
64 std::vector<range_reference> ranges;
67 using bucket_map = std::unordered_map<material_key, bucket_entry, material_key_hash>;
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);
76 traits_type::create_shared_buffers(*
this);
79 ~basic_material_draw_list()
override {
80 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
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);
87 traits_type::destroy_shared_buffers(*
this);
90 auto update() ->
void override {
91 _transform_data.clear();
92 _material_data.clear();
94 for (
auto& [key, pipeline_data] : _pipeline_data) {
95 pipeline_data.submesh_instances.clear();
98 for (
auto& buckets : _bucket_ranges) {
102 auto& assets_module = core::engine::get_module<assets::assets_module>();
104 auto& scenes_module = core::engine::get_module<scenes::scenes_module>();
105 auto& scene = scenes_module.scene();
107 auto memory = std::array<std::uint8_t, 2048u>{};
108 auto pool = std::pmr::monotonic_buffer_resource{memory.data(), memory.size()};
110 auto material_indices = std::pmr::unordered_map<math::uuid, std::uint32_t>{&pool};
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);
116 const auto& material = assets_module.get_asset<models::material>(material_id);
118 auto& pipeline = _get_or_create_pipeline_data(material);
120 auto [entry, created] = material_indices.try_emplace(material_id,
static_cast<std::uint32_t
>(_material_data.size()));
123 _push_material(material);
126 const auto instance = traits_type::make_instance_data(
node, transform_index, entry->second, payload);
128 auto& per_mesh = pipeline.submesh_instances[mesh_id];
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);
134 update_buffer(_transform_data, transform_data_buffer_name);
135 update_buffer(_material_data, material_data_buffer_name);
137 auto camer_node = scene.camera();
138 const auto camera_position = scene.world_position(camer_node);
140 traits_type::update_shared_buffers(*
this);
142 for (
auto& [key, pipeline_data] : _pipeline_data) {
143 if (pipeline_data.submesh_instances.empty()) {
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];
153 const auto position_a = math::vector3(transform_a.model[3]);
154 const auto position_b = math::vector3(transform_b.model[3]);
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);
159 return distance_sq_a < distance_sq_b;
162 std::sort(std::execution::par_unseq, instances.begin(), instances.end(), sort_function);
166 _build_draw_commands(key, pipeline_data);
170 auto ranges(
const bucket bucket)
const ->
const bucket_map& {
171 return _bucket_ranges[magic_enum::enum_underlying(bucket)];
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;
183 struct pipeline_data {
185 std::unordered_map<math::uuid, std::vector<std::vector<instance_data>>> submesh_instances;
187 graphics::storage_buffer_handle draw_commands_buffer;
188 graphics::storage_buffer_handle instance_data_buffer;
191 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
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);
199 static auto _classify_bucket(
const models::material& material) -> bucket {
200 return (material.alpha == models::alpha_mode::blend) ? bucket::transparent : bucket::opaque;
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);
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;
212 auto [entry, inserted] = _pipeline_data.emplace(key, pipeline_data{});
214 return entry->second;
217 auto _get_or_create_sampler(
const texture_slot& texture_slot) -> graphics::sampler_state_handle {
218 auto key = texture_slot_hash{}(texture_slot);
220 if (
auto entry = _samplers.find(key); entry != _samplers.end()) {
221 return entry->second;
224 auto& graphics_module = core::engine::get_module<graphics::graphics_module>();
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));
228 utility::logger<
"models">::debug(
"New sampler created");
230 return entry->second;
233 auto _push_material(
const models::material& material) ->
void {
234 auto data = models::material_data{};
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);
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));
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;
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;
268 data.uv_offset = material.uv_offset;
269 data.uv_scale = material.uv_scale;
271 data.alpha_cutoff = material.alpha_cutoff;
272 data.flags = material.features.underlying();
274 _material_data.push_back(data);
276 const auto key =
static_cast<material_key
>(material);
277 auto& buckets = _material_buckets[key];
279 buckets.insert(_classify_bucket(material));
281 if (_submits_to_shadow(material)) {
282 buckets.insert(bucket::shadow);
287 auto _build_draw_commands(
const material_key& key, pipeline_data& pipeline) ->
void {
288 auto& assets_module = core::engine::get_module<assets::assets_module>();
290 auto draw_commands = std::vector<VkDrawIndexedIndirectCommand>{};
291 auto instance_data = std::vector<models::instance_data>{};
293 auto range = graphics::draw_command_range{};
295 const auto& buckets = _material_buckets.at(key);
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));
304 .emit_single = [&](
const VkDrawIndexedIndirectCommand& command,
const models::instance_data& instance) ->
void {
305 draw_commands.push_back(command);
306 instance_data.push_back(instance);
311 for (
auto& [mesh_id, submesh_vectors] : pipeline.submesh_instances) {
312 const auto& mesh = assets_module.get_asset<mesh_type>(mesh_id);
314 range.offset =
static_cast<std::uint32_t
>(draw_commands.size());
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);
321 if (range.count > 0) {
322 const auto hash = material_key_hash{}(key);
324 push_draw_command_range(hash, mesh_id, range);
326 for (
const auto& bucket_type : buckets) {
327 auto& entry = _bucket_ranges[magic_enum::enum_underlying(bucket_type)][key];
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 });
336 utility::assert_that(emitter.base_instance == instance_data.size(),
"build_draw_commands is broken");
338 if (!draw_commands.empty()) {
339 _update_buffer(pipeline.draw_commands_buffer, draw_commands);
340 _update_buffer(pipeline.instance_data_buffer, instance_data);
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);
350 const auto required_size =
static_cast<std::uint32_t
>(data.size() *
sizeof(Type));
352 if (buffer.size() < required_size) {
353 buffer.resize(
static_cast<std::size_t
>(
static_cast<std::float_t
>(required_size) * 1.5f));
356 if (required_size > 0) {
357 buffer.update(data.data(), required_size);
361 std::vector<transform_data> _transform_data;
362 std::vector<material_data> _material_data;
364 std::unordered_map<material_key, pipeline_data, material_key_hash> _pipeline_data;
366 std::array<bucket_map, magic_enum::enum_count<bucket>()> _bucket_ranges;
368 inline static auto _material_buckets = std::unordered_map<material_key, std::unordered_set<bucket>, material_key_hash>{};
370 inline static auto _samplers = std::unordered_map<std::size_t, graphics::sampler_state_handle>{};
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"}
Definition: draw_list.hpp:28
Definition: resource_storage.hpp:18
Definition: matrix4x4.hpp:26
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