diff --git a/application/application.cpp b/application/application.cpp index a71c41f4..d1ac24a0 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -24,6 +24,7 @@ #include "application.hpp" #include "asset_manager.hpp" #include "thread_group.hpp" +#include "material_manager.hpp" #ifdef HAVE_GRANITE_RENDERER #include "common_renderer_data.hpp" #endif @@ -336,5 +337,8 @@ void Application::post_frame() if (auto *manager = GRANITE_ASSET_MANAGER()) manager->iterate(GRANITE_THREAD_GROUP()); } + + if (auto *manager = Global::material_manager()) + manager->iterate(Global::asset_manager()); } } diff --git a/application/global/global_managers.cpp b/application/global/global_managers.cpp index eca30b07..64e54330 100644 --- a/application/global/global_managers.cpp +++ b/application/global/global_managers.cpp @@ -37,6 +37,7 @@ struct GlobalManagers FilesystemInterface *filesystem; AssetManagerInterface *asset_manager; + MaterialManagerInterface *material_manager; EventManagerInterface *event_manager; ThreadGroupInterface *thread_group; UI::UIManagerInterface *ui_manager; @@ -93,6 +94,11 @@ AssetManagerInterface *asset_manager() return global_managers.asset_manager; } +MaterialManagerInterface *material_manager() +{ + return global_managers.material_manager; +} + EventManagerInterface *event_manager() { return global_managers.event_manager; @@ -152,6 +158,12 @@ void init(Factory &factory, ManagerFeatureFlags flags, unsigned max_threads, flo global_managers.asset_manager = factory.create_asset_manager(); } + if (flags & MANAGER_FEATURE_MATERIAL_MANAGER_BIT) + { + if (!global_managers.material_manager) + global_managers.material_manager = factory.create_material_manager(); + } + bool kick_threads = false; if (flags & MANAGER_FEATURE_THREAD_GROUP_BIT) { @@ -233,6 +245,7 @@ void deinit() delete global_managers.common_renderer_data; delete global_managers.ui_manager; delete global_managers.thread_group; + delete global_managers.material_manager; delete global_managers.asset_manager; delete global_managers.filesystem; delete global_managers.event_manager; @@ -243,6 +256,7 @@ void deinit() global_managers.physics = nullptr; global_managers.common_renderer_data = nullptr; global_managers.filesystem = nullptr; + global_managers.material_manager = nullptr; global_managers.asset_manager = nullptr; global_managers.event_manager = nullptr; global_managers.thread_group = nullptr; @@ -281,6 +295,7 @@ void stop_audio_system() FilesystemInterface *Factory::create_filesystem() { return nullptr; } AssetManagerInterface *Factory::create_asset_manager() { return nullptr; } +MaterialManagerInterface *Factory::create_material_manager() { return nullptr; } EventManagerInterface *Factory::create_event_manager() { return nullptr; } ThreadGroupInterface *Factory::create_thread_group() { return nullptr; } CommonRendererDataInterface *Factory::create_common_renderer_data() { return nullptr; } diff --git a/application/global/global_managers.hpp b/application/global/global_managers.hpp index 8dca3915..c1421171 100644 --- a/application/global/global_managers.hpp +++ b/application/global/global_managers.hpp @@ -44,8 +44,10 @@ enum ManagerFeatureFlagBits MANAGER_FEATURE_PHYSICS_BIT = 1 << 7, MANAGER_FEATURE_LOGGING_BIT = 1 << 8, MANAGER_FEATURE_ASSET_MANAGER_BIT = 1 << 9, + MANAGER_FEATURE_MATERIAL_MANAGER_BIT = 1 << 10, MANAGER_FEATURE_DEFAULT_BITS = (MANAGER_FEATURE_FILESYSTEM_BIT | MANAGER_FEATURE_ASSET_MANAGER_BIT | + MANAGER_FEATURE_MATERIAL_MANAGER_BIT | MANAGER_FEATURE_EVENT_BIT | MANAGER_FEATURE_THREAD_GROUP_BIT | MANAGER_FEATURE_COMMON_RENDERER_DATA_BIT | @@ -64,6 +66,7 @@ class Factory virtual FilesystemInterface *create_filesystem(); virtual AssetManagerInterface *create_asset_manager(); + virtual MaterialManagerInterface *create_material_manager(); virtual EventManagerInterface *create_event_manager(); virtual ThreadGroupInterface *create_thread_group(); virtual CommonRendererDataInterface *create_common_renderer_data(); @@ -100,6 +103,7 @@ void install_audio_system(Audio::BackendInterface *backend, Audio::MixerInterfac Util::MessageQueueInterface *message_queue(); FilesystemInterface *filesystem(); AssetManagerInterface *asset_manager(); +MaterialManagerInterface *material_manager(); EventManagerInterface *event_manager(); ThreadGroupInterface *thread_group(); UI::UIManagerInterface *ui_manager(); @@ -113,6 +117,7 @@ PhysicsSystemInterface *physics(); #define GRANITE_MESSAGE_QUEUE() static_cast<::Util::MessageQueue *>(::Granite::Global::message_queue()) #define GRANITE_FILESYSTEM() static_cast<::Granite::Filesystem *>(::Granite::Global::filesystem()) #define GRANITE_ASSET_MANAGER() static_cast<::Granite::AssetManager *>(::Granite::Global::asset_manager()) +#define GRANITE_MATERIAL_MANAGER() static_cast<::Granite::MaterialManager *>(::Granite::Global::material_manager()) #define GRANITE_EVENT_MANAGER() static_cast<::Granite::EventManager *>(::Granite::Global::event_manager()) #define GRANITE_THREAD_GROUP() static_cast<::Granite::ThreadGroup *>(::Granite::Global::thread_group()) #define GRANITE_UI_MANAGER() static_cast<::Granite::UI::UIManager *>(::Granite::Global::ui_manager()) diff --git a/application/global/global_managers_init.cpp b/application/global/global_managers_init.cpp index 7573e08f..cac4cc6b 100644 --- a/application/global/global_managers_init.cpp +++ b/application/global/global_managers_init.cpp @@ -26,6 +26,7 @@ #include "thread_group.hpp" #include "filesystem.hpp" #include "asset_manager.hpp" +#include "material_manager.hpp" #ifdef HAVE_GRANITE_RENDERER #include "common_renderer_data.hpp" #include "ui_manager.hpp" @@ -82,6 +83,15 @@ struct FactoryImplementation : Factory #endif } + MaterialManager *create_material_manager() override + { +#ifdef HAVE_GRANITE_RENDERER + return new MaterialManager; +#else + return nullptr; +#endif + } + Audio::MixerInterface *create_audio_mixer() override { #ifdef HAVE_GRANITE_AUDIO diff --git a/application/global/global_managers_interface.hpp b/application/global/global_managers_interface.hpp index b8910f97..7064eeaa 100644 --- a/application/global/global_managers_interface.hpp +++ b/application/global/global_managers_interface.hpp @@ -50,6 +50,13 @@ class AssetManagerInterface virtual ~AssetManagerInterface() = default; }; +class MaterialManagerInterface +{ +public: + virtual ~MaterialManagerInterface() = default; + virtual void iterate(AssetManagerInterface *iface) = 0; +}; + class ThreadGroupInterface { public: diff --git a/renderer/CMakeLists.txt b/renderer/CMakeLists.txt index 8fa5f237..8328f15b 100644 --- a/renderer/CMakeLists.txt +++ b/renderer/CMakeLists.txt @@ -13,6 +13,7 @@ add_granite_internal_lib(granite-renderer render_components.hpp render_components.cpp mesh_util.hpp mesh_util.cpp material_util.hpp material_util.cpp + material_manager.hpp material_manager.cpp renderer.hpp renderer.cpp flat_renderer.hpp flat_renderer.cpp renderer_enums.hpp diff --git a/renderer/material_manager.cpp b/renderer/material_manager.cpp new file mode 100644 index 00000000..767ae40e --- /dev/null +++ b/renderer/material_manager.cpp @@ -0,0 +1,126 @@ +/* Copyright (c) 2017-2023 Hans-Kristian Arntzen + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "material_manager.hpp" +#include "device.hpp" +#include "limits.hpp" +#include "hashmap.hpp" + +namespace Granite +{ +MaterialManager::MaterialManager() +{ + EVENT_MANAGER_REGISTER_LATCH(MaterialManager, on_device_created, on_device_destroyed, Vulkan::DeviceCreatedEvent); + material_payload.reserve(Vulkan::VULKAN_MAX_UBO_SIZE / MaterialPayloadSize); + bindless_texture_assets.reserve(Vulkan::VULKAN_NUM_BINDINGS_BINDLESS_VARYING); + allocator.reserve_max_resources_per_pool(256, 8 * Vulkan::VULKAN_NUM_BINDINGS_BINDLESS_VARYING); + allocator.set_bindless_resource_type(Vulkan::BindlessResourceType::Image); +} + +MaterialOffsets MaterialManager::register_material( + const AssetID *assets, unsigned count, const void *payload_data, + size_t payload_size, bool force_unique) +{ + Util::Hasher hasher; + if (!force_unique) + { + for (unsigned i = 0; i < count; i++) + hasher.u32(assets[i].id); + hasher.data(static_cast(payload_data), payload_size); + } + auto *group = force_unique ? nullptr : material.find(hasher.get()); + + if (group) + return group->offsets; + + if (bindless_texture_assets.size() + count > Vulkan::VULKAN_NUM_BINDINGS_BINDLESS_VARYING) + LOGE("Exceeding number of bindless slots.\n"); + if (payload_size && material_payload.size() >= Vulkan::VULKAN_MAX_UBO_SIZE / MaterialPayloadSize) + LOGE("Exceeding number of material payload slots.\n"); + + MaterialOffsets offsets = {}; + offsets.texture_offset = uint32_t(bindless_texture_assets.size()); + offsets.uniform_offset = payload_size ? uint32_t(material_payload.size()) : UINT16_MAX; + + if (!force_unique) + { + group = material.allocate(); + group->offsets = offsets; + material.insert_replace(hasher.get(), group); + } + + bindless_texture_assets.insert(bindless_texture_assets.end(), assets, assets + count); + + if (payload_size) + { + material_payload.emplace_back(); + memcpy(material_payload.back().raw, payload_data, payload_size); + } + + return offsets; +} + +void MaterialManager::set_material_payloads(Vulkan::CommandBuffer &cmd, unsigned set_index, unsigned binding) +{ + if (material_payload.empty()) + { + void *data = cmd.allocate_constant_data(set_index, binding, sizeof(MaterialRawPayload)); + memset(data, 0, sizeof(MaterialRawPayload)); + } + else + { + void *data = cmd.allocate_constant_data(set_index, binding, material_payload.size() * sizeof(MaterialRawPayload)); + memcpy(data, material_payload.data(), material_payload.size() * sizeof(MaterialRawPayload)); + } +} + +void MaterialManager::set_bindless(Vulkan::CommandBuffer &cmd, unsigned set_index) +{ + if (vk_set == VK_NULL_HANDLE) + { + allocator.begin(); + vk_set = allocator.commit(*device); + } + + cmd.set_bindless(set_index, vk_set); +} + +void MaterialManager::iterate(AssetManagerInterface *) +{ + auto &res = device->get_resource_manager(); + allocator.begin(); + for (auto &id : bindless_texture_assets) + allocator.push(*res.get_image_view(id)); + vk_set = allocator.commit(*device); +} + +void MaterialManager::on_device_created(const Vulkan::DeviceCreatedEvent &e) +{ + device = &e.get_device(); +} + +void MaterialManager::on_device_destroyed(const Vulkan::DeviceCreatedEvent &) +{ + allocator.reset(); + device = nullptr; +} +} diff --git a/renderer/material_manager.hpp b/renderer/material_manager.hpp new file mode 100644 index 00000000..c1fe3c16 --- /dev/null +++ b/renderer/material_manager.hpp @@ -0,0 +1,98 @@ +/* Copyright (c) 2017-2023 Hans-Kristian Arntzen + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#pragma once +#include "global_managers.hpp" +#include "event.hpp" +#include "descriptor_set.hpp" +#include "asset_manager.hpp" +#include "application_wsi_events.hpp" +#include "hashmap.hpp" +#include +#include + +namespace Vulkan +{ +class CommandBuffer; +}; + +namespace Granite +{ +static constexpr uint32_t MaterialPayloadSize = 32; + +struct MaterialOffsets +{ + uint16_t texture_offset; + uint16_t uniform_offset; +}; + +class MaterialManager final : public MaterialManagerInterface, public EventHandler +{ +public: + MaterialManager(); + void iterate(AssetManagerInterface *iface) override; + + MaterialOffsets register_material(const AssetID *assets, unsigned count, + const void *payload_data, size_t payload_size, + bool force_unique = false /* can be used for "animating" material properties */); + + template + inline T &get_material_payload(const MaterialOffsets &offsets) + { + static_assert(sizeof(T) <= MaterialPayloadSize, "sizeof(T) is too large."); + static_assert(alignof(T) <= alignof(max_align_t), "alignof(T) is too large."); + assert(offsets.uniform_offset < material_payload.size()); + return reinterpret_cast(material_payload[offsets.uniform_offset]); + } + + const AssetID *get_asset_ids(const MaterialOffsets &offsets) + { + assert(offsets.texture_offset < bindless_texture_assets.size()); + return bindless_texture_assets.data() + offsets.texture_offset; + } + + void set_bindless(Vulkan::CommandBuffer &cmd, unsigned set_index); + void set_material_payloads(Vulkan::CommandBuffer &cmd, unsigned set_index, unsigned binding); + +private: + Vulkan::BindlessAllocator allocator; + Vulkan::Device *device = nullptr; + + void on_device_created(const Vulkan::DeviceCreatedEvent &e); + void on_device_destroyed(const Vulkan::DeviceCreatedEvent &e); + + struct MaterialRawPayload + { + MaterialRawPayload() {} + uint32_t raw[MaterialPayloadSize / sizeof(uint32_t)]; + }; + + std::vector material_payload; + std::vector bindless_texture_assets; + struct MaterialGroup : Util::IntrusiveHashMapEnabled + { + MaterialOffsets offsets; + }; + Util::IntrusiveHashMap material; + VkDescriptorSet vk_set = VK_NULL_HANDLE; +}; +} diff --git a/tests/meshlet_viewer.cpp b/tests/meshlet_viewer.cpp index c2808520..a8227296 100644 --- a/tests/meshlet_viewer.cpp +++ b/tests/meshlet_viewer.cpp @@ -34,6 +34,7 @@ #include "event_manager.hpp" #include "meshlet_export.hpp" #include "render_context.hpp" +#include "material_manager.hpp" #include "gltf.hpp" #include #include @@ -61,7 +62,7 @@ static uint32_t style_to_u32_streams(MeshStyle style) struct MeshletRenderable : AbstractRenderable { AssetID mesh; - uint32_t albedo_index; + MaterialOffsets material; AABB aabb; void get_render_info(const RenderContext &, const RenderInfoComponent *, @@ -89,14 +90,19 @@ struct MeshletViewerApplication : Granite::Application, Granite::EventHandler std::vector mesh_assets; std::vector nodes; mesh_assets.reserve(parser.get_meshes().size()); - albedos.reserve(parser.get_materials().size()); + + std::vector materials; + materials.reserve(parser.get_materials().size()); + nodes.reserve(parser.get_nodes().size()); for (auto &mat : parser.get_materials()) { - albedos.push_back(GRANITE_ASSET_MANAGER()->register_asset( + AssetID albedo = GRANITE_ASSET_MANAGER()->register_asset( *GRANITE_FILESYSTEM(), mat.paths[int(TextureKind::BaseColor)], - Granite::AssetClass::ImageColor)); + Granite::AssetClass::ImageColor); + + materials.push_back(GRANITE_MATERIAL_MANAGER()->register_material(&albedo, 1, nullptr, 0)); } unsigned count = 0; @@ -148,7 +154,7 @@ struct MeshletViewerApplication : Granite::Application, Granite::EventHandler auto renderable = Util::make_handle(); renderable->mesh = mesh_assets[mesh]; renderable->aabb = parser.get_meshes()[mesh].static_aabb; - renderable->albedo_index = parser.get_meshes()[mesh].material_index; + renderable->material = materials[parser.get_meshes()[mesh].material_index]; scene.create_renderable(std::move(renderable), nodes[i].get()); } } @@ -168,7 +174,6 @@ struct MeshletViewerApplication : Granite::Application, Granite::EventHandler Scene scene; RenderContext render_context; VisibilityList list; - std::vector albedos; BindlessAllocator allocator; void on_device_create(const DeviceCreatedEvent &e) @@ -210,14 +215,10 @@ struct MeshletViewerApplication : Granite::Application, Granite::EventHandler std::vector task_params; - std::vector material_draws; - material_draws.reserve(list.size()); - for (auto &vis : list) { auto *meshlet = static_cast(vis.renderable); auto range = device.get_resource_manager().get_mesh_draw_range(meshlet->mesh); - material_draws.emplace_back(meshlet->albedo_index, unsigned(task_params.size()), (range.count + 31) / 32); TaskParameters draw = {}; draw.aabb_instance = vis.transform->aabb.offset; @@ -225,6 +226,7 @@ struct MeshletViewerApplication : Granite::Application, Granite::EventHandler auto *skin = node->get_skin(); draw.node_instance = skin ? skin->transform.offset : node->transform.offset; draw.node_count_material_index = skin ? skin->transform.count : 1; + draw.node_count_material_index |= meshlet->material.texture_offset << 8; assert((range.offset & 31) == 0); for (uint32_t i = 0; i < range.count; i += 32) @@ -234,10 +236,6 @@ struct MeshletViewerApplication : Granite::Application, Granite::EventHandler } } - std::sort(material_draws.begin(), material_draws.end(), [](const uvec3 &a, const uvec3 &b) { - return a.x < b.x; - }); - if (task_params.empty()) { cmd->begin_render_pass(device.get_swapchain_render_pass(SwapchainRenderPass::Depth)); @@ -246,35 +244,6 @@ struct MeshletViewerApplication : Granite::Application, Granite::EventHandler return; } - // TODO: We can improve this design quite a lot. Needs refactors of asset manager. - auto &manager = device.get_resource_manager(); - allocator.set_bindless_resource_type(BindlessResourceType::Image); - allocator.reserve_max_resources_per_pool(1, VULKAN_NUM_BINDINGS_BINDLESS_VARYING); - allocator.begin(); - - uint32_t asset_index = material_draws.front().x; - uint32_t remapped_index = 0; - allocator.push(*manager.get_image_view(albedos[asset_index])); - - for (unsigned j = 0; j < material_draws.front().z; j++) - task_params.at(material_draws.front().y + j).node_count_material_index |= remapped_index << 8; - - for (size_t i = 1, n = material_draws.size(); i < n; i++) - { - auto &d = material_draws[i]; - if (d.x != asset_index) - { - remapped_index++; - asset_index = d.x; - allocator.push(*manager.get_image_view(albedos[asset_index])); - } - - for (unsigned j = 0; j < d.z; j++) - task_params.at(d.y + j).node_count_material_index |= remapped_index << 8; - } - - VkDescriptorSet vk_set = allocator.commit(device); - BufferHandle task_buffer, cached_transform_buffer, aabb_buffer; { @@ -301,6 +270,8 @@ struct MeshletViewerApplication : Granite::Application, Granite::EventHandler aabb_buffer = device.create_buffer(info, scene.get_aabbs().get_aabbs()); } + auto &manager = device.get_resource_manager(); + if (manager.get_mesh_encoding() == Vulkan::ResourceManager::MeshEncoding::Meshlet) { auto *header_buffer = manager.get_meshlet_header_buffer(); @@ -329,7 +300,7 @@ struct MeshletViewerApplication : Granite::Application, Granite::EventHandler cmd->set_storage_buffer(0, 5, *payload_buffer); cmd->set_sampler(0, 6, StockSampler::DefaultGeometryFilterWrap); - cmd->set_bindless(2, vk_set); + GRANITE_MATERIAL_MANAGER()->set_bindless(*cmd, 2); cmd->set_subgroup_size_log2(true, 5, 5, VK_SHADER_STAGE_TASK_BIT_EXT); cmd->set_subgroup_size_log2(true, 5, 5, VK_SHADER_STAGE_MESH_BIT_EXT); @@ -401,7 +372,7 @@ struct MeshletViewerApplication : Granite::Application, Granite::EventHandler cmd->set_storage_buffer(0, 0, *compacted_params); cmd->set_storage_buffer(0, 1, *cached_transform_buffer); cmd->set_sampler(0, 2, StockSampler::DefaultGeometryFilterWrap); - cmd->set_bindless(2, vk_set); + GRANITE_MATERIAL_MANAGER()->set_bindless(*cmd, 2); cmd->draw_indexed_multi_indirect(*indirect_draws, 256, task_params.size() * 32, sizeof(VkDrawIndexedIndirectCommand),