diff --git a/application/scene_viewer_application.cpp b/application/scene_viewer_application.cpp index 72765b4e..1c1807f8 100644 --- a/application/scene_viewer_application.cpp +++ b/application/scene_viewer_application.cpp @@ -92,10 +92,10 @@ void SceneViewerApplication::read_lights() auto node = scene.create_node(); for (int i = 0; i < 3; i++) - node->transform.translation[i] = spot["position"][i].GetFloat(); + node->get_transform().translation[i] = spot["position"][i].GetFloat(); auto &dir = spot["direction"]; - node->transform.rotation = conjugate(look_at_arbitrary_up(vec3( + node->get_transform().rotation = conjugate(look_at_arbitrary_up(vec3( dir[0].GetFloat(), dir[1].GetFloat(), dir[2].GetFloat()))); scene.get_root_node()->add_child(node); auto entity = scene.create_light(info, node.get()); @@ -118,7 +118,7 @@ void SceneViewerApplication::read_lights() auto node = scene.create_node(); for (int i = 0; i < 3; i++) - node->transform.translation[i] = point["position"][i].GetFloat(); + node->get_transform().translation[i] = point["position"][i].GetFloat(); scene.get_root_node()->add_child(node); auto entity = scene.create_light(info, node.get()); entity->allocate_component(); @@ -304,8 +304,8 @@ SceneViewerApplication::SceneViewerApplication(const std::string &path, const st { auto &scene = scene_loader.get_scene(); auto node = scene.create_node(); - node->transform.scale = vec3(32.0f, 8.0f, 32.0f); - node->transform.translation = vec3(0.0f, 3.5f, 0.0f); + node->get_transform().scale = vec3(32.0f, 8.0f, 32.0f); + node->get_transform().translation = vec3(0.0f, 3.5f, 0.0f); node->invalidate_cached_transform(); scene.create_volumetric_diffuse_light(uvec3(32, 8, 32), node.get()); scene.get_root_node()->add_child(std::move(node)); @@ -315,8 +315,8 @@ SceneViewerApplication::SceneViewerApplication(const std::string &path, const st { auto &scene = scene_loader.get_scene(); auto node = scene.create_node(); - node->transform.scale = vec3(40.0f); - node->transform.translation = vec3(0.0f, 20.0f, 0.0f); + node->get_transform().scale = vec3(40.0f); + node->get_transform().translation = vec3(0.0f, 20.0f, 0.0f); node->invalidate_cached_transform(); scene.create_volumetric_fog_region(node.get()); scene.get_root_node()->add_child(std::move(node)); @@ -529,12 +529,12 @@ void SceneViewerApplication::rescale_scene(float radius) .get_entity_pool() .get_component_group(); for (auto &caster : objects) - aabb.expand(get_component(caster)->world_aabb); + aabb.expand(get_component(caster)->get_aabb()); float scale_factor = radius / aabb.get_radius(); auto root_node = scene_loader.get_scene().get_root_node(); auto new_root_node = scene_loader.get_scene().create_node(); - new_root_node->transform.scale = vec3(scale_factor); + new_root_node->get_transform().scale = vec3(scale_factor); new_root_node->add_child(root_node); scene_loader.get_scene().set_root_node(new_root_node); } @@ -575,8 +575,8 @@ bool SceneViewerApplication::on_key_down(const KeyboardEvent &e) light.inner_cone = 0.92f; light.color = vec3(10.0f); - node->transform.translation = pos; - node->transform.rotation = conjugate(look_at_arbitrary_up(selected_camera->get_front())); + node->get_transform().translation = pos; + node->get_transform().rotation = conjugate(look_at_arbitrary_up(selected_camera->get_front())); auto *entity = scene.create_light(light, node.get()); entity->allocate_component(); @@ -593,7 +593,7 @@ bool SceneViewerApplication::on_key_down(const KeyboardEvent &e) SceneFormats::LightInfo light; light.type = SceneFormats::LightInfo::Type::Point; light.color = vec3(10.0f); - node->transform.translation = pos; + node->get_transform().translation = pos; auto *entity = scene.create_light(light, node.get()); entity->allocate_component(); @@ -1261,7 +1261,7 @@ void SceneViewerApplication::update_shadow_scene_aabb() .get_component_group(); AABB aabb(vec3(FLT_MAX), vec3(-FLT_MAX)); for (auto &caster : shadow_casters) - aabb.expand(get_component(caster)->world_aabb); + aabb.expand(get_component(caster)->get_aabb()); shadow_scene_aabb = aabb; } diff --git a/renderer/CMakeLists.txt b/renderer/CMakeLists.txt index ca573aa0..8fa5f237 100644 --- a/renderer/CMakeLists.txt +++ b/renderer/CMakeLists.txt @@ -10,7 +10,7 @@ add_granite_internal_lib(granite-renderer camera.hpp camera.cpp material.hpp abstract_renderable.hpp - render_components.hpp + render_components.hpp render_components.cpp mesh_util.hpp mesh_util.cpp material_util.hpp material_util.cpp renderer.hpp renderer.cpp diff --git a/renderer/animation_system.cpp b/renderer/animation_system.cpp index 0e0b0d6d..fecc7e50 100644 --- a/renderer/animation_system.cpp +++ b/renderer/animation_system.cpp @@ -89,7 +89,7 @@ uint32_t AnimationUnrolled::get_multi_node_index(unsigned channel) const return multi_node_indices[channel]; } -void AnimationUnrolled::animate(Transform *const *transforms, unsigned num_transforms, float offset_time) const +void AnimationUnrolled::animate(Transform *transforms, const uint32_t *transform_indices, unsigned num_transforms, float offset_time) const { if (num_transforms != get_num_channels()) throw std::logic_error("Incorrect number of transforms."); @@ -102,7 +102,7 @@ void AnimationUnrolled::animate(Transform *const *transforms, unsigned num_trans for (unsigned i = 0; i < num_transforms; i++) { - auto *t = transforms[i]; + auto *t = &transforms[transform_indices[i]]; animate_single(*t, i, lo, hi, l); } } @@ -302,9 +302,11 @@ AnimationStateID AnimationSystem::start_animation(Node &node, Granite::Animation return 0; } - Util::SmallVector target_transforms = { &node.transform }; + Util::SmallVector target_transforms = { node.transform.offset }; Util::SmallVector nodes = { &node }; - id = animation_state_pool.emplace(*animation, std::move(target_transforms), std::move(nodes), start_time); + id = animation_state_pool.emplace(*animation, + node.get_transform_base(), + std::move(target_transforms), std::move(nodes), start_time); } auto *state = &animation_state_pool.get(id); @@ -331,7 +333,9 @@ void AnimationSystem::set_fixed_pose(Node &node, Granite::AnimationID id, float return; } - animation->animate(node.get_skin()->skin.data(), node.get_skin()->skin.size(), offset); + animation->animate(node.get_transform_base(), + node.get_skin()->skin.data(), node.get_skin()->skin.size(), offset); + node.invalidate_cached_transform(); } else @@ -342,8 +346,8 @@ void AnimationSystem::set_fixed_pose(Node &node, Granite::AnimationID id, float return; } - Transform *t = &node.transform; - animation->animate(&t, 1, offset); + uint32_t transform_index = node.transform.offset; + animation->animate(node.get_transform_base(), &transform_index, 1, offset); node.invalidate_cached_transform(); } } @@ -364,11 +368,12 @@ void AnimationSystem::set_fixed_pose_multi(NodeHandle *nodes, unsigned num_nodes return; } + if (!num_nodes) + return; + // Not very efficient. - Util::SmallVector target_transforms; - Util::SmallVector target_nodes; + Util::SmallVector target_transforms; target_transforms.reserve(animation->get_num_channels()); - target_nodes.reserve(animation->get_num_channels()); for (unsigned channel = 0; channel < animation->get_num_channels(); channel++) { @@ -379,11 +384,12 @@ void AnimationSystem::set_fixed_pose_multi(NodeHandle *nodes, unsigned num_nodes return; } - target_transforms.push_back(&nodes[index]->transform); + target_transforms.push_back(nodes[index]->transform.offset); nodes[index]->invalidate_cached_transform(); } - animation->animate(target_transforms.data(), target_transforms.size(), offset); + animation->animate(nodes[0]->get_transform_base(), + target_transforms.data(), target_transforms.size(), offset); } AnimationStateID AnimationSystem::start_animation_multi(NodeHandle *nodes, unsigned num_nodes, @@ -402,7 +408,13 @@ AnimationStateID AnimationSystem::start_animation_multi(NodeHandle *nodes, unsig return 0; } - Util::SmallVector target_transforms; + if (!num_nodes) + { + LOGE("Number of nodes must not be 0.\n"); + return 0; + } + + Util::SmallVector target_transforms; Util::SmallVector target_nodes; target_transforms.reserve(animation->get_num_channels()); target_nodes.reserve(animation->get_num_channels()); @@ -416,11 +428,12 @@ AnimationStateID AnimationSystem::start_animation_multi(NodeHandle *nodes, unsig return 0; } - target_transforms.push_back(&nodes[index]->transform); + target_transforms.push_back(nodes[index]->transform.offset); target_nodes.push_back(nodes[index].get()); } - auto id = animation_state_pool.emplace(*animation, std::move(target_transforms), std::move(target_nodes), start_time); + auto id = animation_state_pool.emplace(*animation, target_nodes.front()->get_transform_base(), + std::move(target_transforms), std::move(target_nodes), start_time); auto *state = &animation_state_pool.get(id); state->id = id; active_animation.add(state); @@ -472,12 +485,14 @@ void AnimationSystem::update(AnimationState *anim, double frame_time, double ela if (anim->animation.is_skinned()) { auto *node = anim->skinned_node; - anim->animation.animate(node->get_skin()->skin.data(), node->get_skin()->skin.size(), float(offset)); + anim->animation.animate(anim->transforms_base, + node->get_skin()->skin.data(), node->get_skin()->skin.size(), float(offset)); node->invalidate_cached_transform(); } else { - anim->animation.animate(anim->channel_transforms.data(), anim->channel_transforms.size(), float(offset)); + anim->animation.animate(anim->transforms_base, + anim->channel_transforms.data(), anim->channel_transforms.size(), float(offset)); for (auto *node : anim->channel_nodes) node->invalidate_cached_transform(); } @@ -538,10 +553,12 @@ void AnimationSystem::animate(TaskComposer &composer, double frame_time, double } AnimationSystem::AnimationState::AnimationState(const AnimationUnrolled &anim, - Util::SmallVector channel_transforms_, + Transform *transforms_base_, + Util::SmallVector channel_transforms_, Util::SmallVector channel_nodes_, double start_time_) - : channel_transforms(std::move(channel_transforms_)), + : transforms_base(transforms_base_), + channel_transforms(std::move(channel_transforms_)), channel_nodes(std::move(channel_nodes_)), animation(anim), start_time(start_time_) @@ -550,7 +567,8 @@ AnimationSystem::AnimationState::AnimationState(const AnimationUnrolled &anim, AnimationSystem::AnimationState::AnimationState(const Granite::AnimationUnrolled &anim, Node *node, double start_time_) - : skinned_node(node), animation(anim), start_time(start_time_) + : transforms_base(node->get_transform_base()), + skinned_node(node), animation(anim), start_time(start_time_) { } } \ No newline at end of file diff --git a/renderer/animation_system.hpp b/renderer/animation_system.hpp index bea47df7..92a31fab 100644 --- a/renderer/animation_system.hpp +++ b/renderer/animation_system.hpp @@ -37,7 +37,7 @@ class AnimationUnrolled : public Util::IntrusiveHashMapEnabled channel_transforms_, + Transform *transforms_base_, + Util::SmallVector channel_transforms_, Util::SmallVector channel_nodes_, double start_time_); @@ -113,9 +114,10 @@ class AnimationSystem Node *node, double start_time_); + Transform *transforms_base; Node *skinned_node = nullptr; AnimationStateID id = 0; - Util::SmallVector channel_transforms; + Util::SmallVector channel_transforms; Util::SmallVector channel_nodes; const AnimationUnrolled &animation; double start_time = 0.0; diff --git a/renderer/ground.cpp b/renderer/ground.cpp index 57072e87..d1f18b51 100644 --- a/renderer/ground.cpp +++ b/renderer/ground.cpp @@ -161,7 +161,7 @@ GroundPatch::~GroundPatch() void GroundPatch::refresh(const RenderContext &context, const RenderInfoComponent *transform, TaskComposer &) { - vec3 center = transform->world_aabb.get_center(); + vec3 center = transform->get_aabb().get_center(); const auto &camera_pos = context.get_render_parameters().camera_position; vec3 diff = center - camera_pos; float dist_log2 = 0.5f * muglm::log2(dot(diff, diff) + 0.001f); @@ -348,7 +348,7 @@ void Ground::get_render_info(const RenderContext &context, const RenderInfoCompo hasher.s32(base_lod); hasher.s32(info.bandlimited_pixel); auto sorting_key = RenderInfo::get_sort_key(context, Queue::Opaque, pipe_hash, hasher.get(), - transform->world_aabb.get_center(), + transform->get_aabb().get_center(), StaticLayer::Last); hasher.u64(heightmap->get_cookie()); diff --git a/renderer/lights/clusterer.cpp b/renderer/lights/clusterer.cpp index 0e22346b..c0854ecd 100644 --- a/renderer/lights/clusterer.cpp +++ b/renderer/lights/clusterer.cpp @@ -1470,8 +1470,8 @@ void LightClusterer::refresh_bindless(const RenderContext &context_, TaskCompose static void sort_decals(VolumetricDecalList &list, const vec3 &front) { std::sort(list.begin(), list.end(), [&front](const VolumetricDecalInfo &a, const VolumetricDecalInfo &b) -> bool { - float a_dist = dot(front, a.transform->world_aabb.get_center()); - float b_dist = dot(front, b.transform->world_aabb.get_center()); + float a_dist = dot(front, a.transform->get_aabb().get_center()); + float b_dist = dot(front, b.transform->get_aabb().get_center()); return a_dist < b_dist; }); } diff --git a/renderer/lights/deferred_lights.cpp b/renderer/lights/deferred_lights.cpp index abfd9a5f..2a10c9a6 100644 --- a/renderer/lights/deferred_lights.cpp +++ b/renderer/lights/deferred_lights.cpp @@ -60,7 +60,7 @@ void DeferredLights::refresh(const RenderContext &context, TaskComposer &) float cluster_max = 0.0f; for (auto &light : visible) { - auto &aabb = light.transform->world_aabb; + auto &aabb = light.transform->get_aabb(); float to_center = dot(aabb.get_center() - params.camera_position, params.camera_front); cluster_min = min(to_center, cluster_min); cluster_max = max(to_center, cluster_max); @@ -73,7 +73,7 @@ void DeferredLights::refresh(const RenderContext &context, TaskComposer &) // Assign each renderable to a cluster index based on their position. for (auto &light : visible) { - auto &aabb = light.transform->world_aabb; + auto &aabb = light.transform->get_aabb(); float to_center = dot(aabb.get_center() - params.camera_position, params.camera_front); int cluster_index = clamp(int((to_center - cluster_min) * cluster_inv_range), 0, NumClusters - 1); clusters[cluster_index].push_back(light); diff --git a/renderer/mesh.cpp b/renderer/mesh.cpp index 15e3f484..ec0db1a0 100644 --- a/renderer/mesh.cpp +++ b/renderer/mesh.cpp @@ -259,7 +259,7 @@ void StaticMesh::get_render_info(const RenderContext &context, const RenderInfoC h.u64(vbo_position->get_cookie()); auto instance_key = get_baked_instance_key(); - auto sorting_key = RenderInfo::get_sort_key(context, type, pipe_hash, h.get(), transform->world_aabb.get_center()); + auto sorting_key = RenderInfo::get_sort_key(context, type, pipe_hash, h.get(), transform->get_aabb().get_center()); auto *instance_data = queue.allocate_one(); instance_data->vertex.Model = transform->get_world_transform(); @@ -268,7 +268,6 @@ void StaticMesh::get_render_info(const RenderContext &context, const RenderInfoC instance_data->vertex.PrevModel = queue.allocate_one(); *instance_data->vertex.PrevModel = transform->get_prev_world_transform(); } - //instance_data->vertex.Normal = t->normal_transform; auto *mesh_info = queue.push(type, instance_key, sorting_key, RenderFunctions::static_mesh_render, @@ -328,22 +327,24 @@ void SkinnedMesh::get_render_info(const RenderContext &context, const RenderInfo h.u64(vbo_position->get_cookie()); auto instance_key = get_baked_instance_key() ^ 1; - auto sorting_key = RenderInfo::get_sort_key(context, type, pipe_hash, h.get(), transform->world_aabb.get_center()); + auto sorting_key = RenderInfo::get_sort_key(context, type, pipe_hash, h.get(), transform->get_aabb().get_center()); auto *instance_data = queue.allocate_one(); auto *skin = transform->get_skin(); - unsigned num_bones = skin->cached_skin_transform.bone_world_transforms.size(); + unsigned num_bones = skin->transform.count; instance_data->num_bones = num_bones; instance_data->world_transforms = queue.allocate_many(num_bones); - //instance_data->normal_transforms = queue.allocate_many(num_bones); - memcpy(instance_data->world_transforms, skin->cached_skin_transform.bone_world_transforms.data(), num_bones * sizeof(mat4)); - //memcpy(instance_data->normal_transforms, transform->skin_transform->bone_normal_transforms.data(), num_bones * sizeof(mat4)); + memcpy(instance_data->world_transforms, + transform->scene_node->parent_scene.get_transforms().get_cached_transforms() + skin->transform.offset, + num_bones * sizeof(mat4)); if (mv) { instance_data->prev_world_transforms = queue.allocate_many(num_bones); - memcpy(instance_data->prev_world_transforms, skin->prev_cached_skin_transform.bone_world_transforms.data(), num_bones * sizeof(mat4)); + memcpy(instance_data->prev_world_transforms, + transform->scene_node->parent_scene.get_transforms().get_cached_prev_transforms() + skin->transform.offset, + num_bones * sizeof(mat4)); } auto *mesh_info = queue.push(type, instance_key, sorting_key, diff --git a/renderer/node.cpp b/renderer/node.cpp index 6146ad36..a65a85be 100644 --- a/renderer/node.cpp +++ b/renderer/node.cpp @@ -27,29 +27,85 @@ namespace Granite { Node::Node(Scene &parent_) : parent_scene(parent_) - , transform(*parent_scene.transform_pool.allocate()) - , cached_transform(*parent_scene.cached_transform_pool.allocate()) - , prev_cached_transform(*parent_scene.cached_transform_pool.allocate()) { + if (!parent_scene.get_transforms().allocate(1, &transform)) + { + LOGE("Transform pool is exhausted.\n"); + } + else + { + auto &t = get_transform(); + t.rotation = quat(1.0f, 0.0f, 0.0f, 0.0f); + t.translation = vec3(0.0f); + t.scale = vec3(1.0f); + } + node_is_pending_update.store(false, std::memory_order_relaxed); invalidate_cached_transform(); assert(node_is_pending_update.is_lock_free()); } +Transform &Node::get_transform() +{ + return parent_scene.get_transforms().get_transforms()[transform.offset]; +} + +mat4 &Node::get_cached_transform() +{ + return parent_scene.get_transforms().get_cached_transforms()[transform.offset]; +} + +mat4 &Node::get_cached_prev_transform() +{ + return parent_scene.get_transforms().get_cached_prev_transforms()[transform.offset]; +} + +Transform *Node::get_transform_base() +{ + return parent_scene.get_transforms().get_transforms(); +} + +mat4 *Node::get_skin_cached() +{ + assert(skinning); + return parent_scene.get_transforms().get_cached_transforms() + skinning->transform.offset; +} + +mat4 *Node::get_skin_prev_cached() +{ + assert(skinning); + return parent_scene.get_transforms().get_cached_prev_transforms() + skinning->transform.offset; +} + Node::~Node() { if (skinning) + { + if (skinning->transform.count) + parent_scene.get_transforms().free(skinning->transform); parent_scene.skinning_pool.free(skinning); - parent_scene.transform_pool.free(&transform); - parent_scene.cached_transform_pool.free(&cached_transform); - parent_scene.cached_transform_pool.free(&prev_cached_transform); + } + + if (transform.count) + parent_scene.get_transforms().free(transform); } void Node::set_skin(Skinning *skinning_) { if (skinning) + { + if (skinning->transform.count) + parent_scene.get_transforms().free(skinning->transform); parent_scene.skinning_pool.free(skinning); + } skinning = skinning_; + + if (!parent_scene.get_transforms().allocate(skinning->skin.size(), &skinning->transform)) + { + LOGE("Transform pool is exhausted.\n"); + parent_scene.skinning_pool.free(skinning); + skinning = nullptr; + } } unsigned Node::get_dirty_transform_depth() const diff --git a/renderer/node.hpp b/renderer/node.hpp index 0b092583..8cf46bad 100644 --- a/renderer/node.hpp +++ b/renderer/node.hpp @@ -25,6 +25,7 @@ #include "intrusive.hpp" #include "math.hpp" #include "hash.hpp" +#include "arena_allocator.hpp" #include namespace Granite @@ -34,21 +35,9 @@ class Scene; struct Transform { - vec3 scale = vec3(1.0f); - vec3 translation = vec3(0.0f); - quat rotation = quat(1.0f, 0.0f, 0.0f, 0.0f); -}; - -struct CachedTransform -{ - mat4 world_transform; - //mat4 normal_transform; -}; - -struct CachedSkinTransform -{ - std::vector bone_world_transforms; - //std::vector bone_normal_transforms; + vec3 scale; + vec3 translation; + quat rotation; }; struct NodeDeleter @@ -66,9 +55,14 @@ class Node : public Util::IntrusivePtrEnabled ~Node(); Scene &parent_scene; - Transform &transform; - CachedTransform &cached_transform; - CachedTransform &prev_cached_transform; + Util::AllocatedSlice transform; + + Transform &get_transform(); + mat4 &get_cached_transform(); + mat4 &get_cached_prev_transform(); + Transform *get_transform_base(); + mat4 *get_skin_cached(); + mat4 *get_skin_prev_cached(); void invalidate_cached_transform(); void add_child(Util::IntrusivePtr node); @@ -92,10 +86,8 @@ class Node : public Util::IntrusivePtrEnabled struct Skinning { - CachedSkinTransform cached_skin_transform; - CachedSkinTransform prev_cached_skin_transform; - std::vector cached_skin; - std::vector skin; + Util::AllocatedSlice transform; + std::vector skin; std::vector inverse_bind_poses; Util::Hash skin_compat = 0; }; diff --git a/renderer/ocean.cpp b/renderer/ocean.cpp index 53b4e7de..be6c5f3a 100644 --- a/renderer/ocean.cpp +++ b/renderer/ocean.cpp @@ -156,7 +156,7 @@ void Ocean::refresh(const RenderContext &context_, TaskComposer &) last_camera_position = context_.get_render_parameters().camera_position; if (node) - node_center_position = node->cached_transform.world_transform[3].xyz(); + node_center_position = node->get_cached_transform()[3].xyz(); else node_center_position = vec3(0.0f); } diff --git a/renderer/render_components.cpp b/renderer/render_components.cpp new file mode 100644 index 00000000..28ab0304 --- /dev/null +++ b/renderer/render_components.cpp @@ -0,0 +1,51 @@ +/* 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 "render_components.hpp" +#include "scene.hpp" + +namespace Granite +{ +const mat4 &RenderInfoComponent::get_world_transform() const +{ + assert(scene_node->transform.count); + return scene_node->parent_scene.get_transforms().get_cached_transforms()[scene_node->transform.offset]; +} + +const mat4 &RenderInfoComponent::get_prev_world_transform() const +{ + assert(scene_node->transform.count); + return scene_node->parent_scene.get_transforms().get_cached_prev_transforms()[scene_node->transform.offset]; +} + +const AABB &RenderInfoComponent::get_aabb() const +{ + assert(aabb.count); + return scene_node->parent_scene.get_aabbs().get_aabbs()[aabb.offset]; +} + +RenderInfoComponent::~RenderInfoComponent() +{ + if (aabb.count) + scene_node->parent_scene.get_aabbs().free(aabb); +} +} diff --git a/renderer/render_components.hpp b/renderer/render_components.hpp index 9a83da87..65b5ed09 100644 --- a/renderer/render_components.hpp +++ b/renderer/render_components.hpp @@ -159,9 +159,11 @@ struct PerFrameUpdateComponent : ComponentBase struct RenderInfoComponent : ComponentBase { GRANITE_COMPONENT_TYPE_DECL(RenderInfoComponent) - AABB world_aabb; Node *scene_node = nullptr; + ~RenderInfoComponent(); + Util::AllocatedSlice aabb; + // If set, the transform changed last frame and motion vectors will need to be rendered explicitly. bool requires_motion_vectors = false; @@ -169,15 +171,9 @@ struct RenderInfoComponent : ComponentBase // e.g. per instance material information. const void *extra_data = nullptr; - inline const mat4 &get_world_transform() const - { - return scene_node->cached_transform.world_transform; - } - - inline const mat4 &get_prev_world_transform() const - { - return scene_node->prev_cached_transform.world_transform; - } + const mat4 &get_world_transform() const; + const mat4 &get_prev_world_transform() const; + const AABB &get_aabb() const; inline const Node::Skinning *get_skin() const { @@ -193,7 +189,7 @@ struct RenderInfoComponent : ComponentBase struct CachedTransformComponent : ComponentBase { GRANITE_COMPONENT_TYPE_DECL(CachedTransformComponent) - CachedTransform *transform = nullptr; + mat4 *transform = nullptr; }; struct CachedSpatialTransformTimestampComponent : ComponentBase diff --git a/renderer/scene.cpp b/renderer/scene.cpp index 22c0ac44..62f5845a 100644 --- a/renderer/scene.cpp +++ b/renderer/scene.cpp @@ -88,7 +88,7 @@ static void gather_visible_renderables(const Frustum &frustum, VisibilityList &l if (transform->has_scene_node()) { if ((flags & RENDERABLE_FORCE_VISIBLE_BIT) != 0 || - SIMD::frustum_cull(transform->world_aabb, frustum.get_planes())) + SIMD::frustum_cull(transform->get_aabb(), frustum.get_planes())) { list.push_back({ renderable->renderable.get(), transform, h.get() }); } @@ -343,7 +343,7 @@ static void gather_positional_lights(const Frustum &frustum, VisibilityList &lis if (transform->has_scene_node()) { - if (SIMD::frustum_cull(transform->world_aabb, frustum.get_planes())) + if (SIMD::frustum_cull(transform->get_aabb(), frustum.get_planes())) list.push_back({ renderable->renderable.get(), transform, h.get() }); } else @@ -371,7 +371,7 @@ static void gather_positional_lights(const Frustum &frustum, PositionalLightList if (transform->has_scene_node()) { - if (SIMD::frustum_cull(transform->world_aabb, frustum.get_planes())) + if (SIMD::frustum_cull(transform->get_aabb(), frustum.get_planes())) list.push_back({ light, transform, h.get() }); } else @@ -415,7 +415,7 @@ void Scene::gather_visible_volumetric_diffuse_lights(const Frustum &frustum, Vol { if (transform->has_scene_node()) { - if (SIMD::frustum_cull(transform->world_aabb, frustum.get_planes())) + if (SIMD::frustum_cull(transform->get_aabb(), frustum.get_planes())) list.push_back({ light, transform }); } else @@ -435,7 +435,7 @@ void Scene::gather_visible_volumetric_decals(const Frustum &frustum, VolumetricD { if (transform->has_scene_node()) { - if (SIMD::frustum_cull(transform->world_aabb, frustum.get_planes())) + if (SIMD::frustum_cull(transform->get_aabb(), frustum.get_planes())) list.push_back({ decal, transform }); } else @@ -455,7 +455,7 @@ void Scene::gather_visible_volumetric_fog_regions(const Frustum &frustum, Volume { if (transform->has_scene_node()) { - if (SIMD::frustum_cull(transform->world_aabb, frustum.get_planes())) + if (SIMD::frustum_cull(transform->get_aabb(), frustum.get_planes())) list.push_back({ region, transform }); } else @@ -536,20 +536,20 @@ static void log_node_transforms(const Scene::Node &node) static void update_skinning(Node &node) { auto &skin = *node.get_skin(); - if (!skin.cached_skin_transform.bone_world_transforms.empty()) + if (skin.transform.count) { - skin.prev_cached_skin_transform = skin.cached_skin_transform; + auto &transforms = node.parent_scene.get_transforms(); + auto *cached = transforms.get_cached_transforms(); + auto *prev_cached = transforms.get_cached_prev_transforms(); - auto len = skin.skin.size(); - assert(skin.skin.size() == skin.cached_skin_transform.bone_world_transforms.size()); - //assert(node.get_skin().cached_skin.size() == node.cached_skin_transform.bone_normal_transforms.size()); - for (size_t i = 0; i < len; i++) - { - SIMD::mul(skin.cached_skin_transform.bone_world_transforms[i], - skin.cached_skin[i]->world_transform, - skin.inverse_bind_poses[i]); - //node.cached_skin_transform.bone_normal_transforms[i] = node.get_skin().cached_skin[i]->normal_transform; - } + auto *cached_skin = cached + skin.transform.offset; + auto *prev_cached_skin = prev_cached + skin.transform.offset; + + for (uint32_t i = 0; i < skin.transform.count; i++) + prev_cached_skin[i] = cached_skin[i]; + + for (size_t i = 0; i < skin.transform.count; i++) + SIMD::mul(cached_skin[i], cached[skin.skin[i]], skin.inverse_bind_poses[i]); //log_node_transforms(node); } } @@ -689,7 +689,7 @@ void Scene::update_transform_listener_components() CameraComponent *cam; CachedTransformComponent *transform; std::tie(cam, transform) = c; - cam->camera.set_transform(transform->transform->world_transform); + cam->camera.set_transform(*transform->transform); } // Update directional light transforms. @@ -700,7 +700,7 @@ void Scene::update_transform_listener_components() std::tie(l, transform) = light; // v = [0, 0, 1, 0]. - l->direction = normalize(transform->transform->world_transform[2].xyz()); + l->direction = normalize((*transform->transform)[2].xyz()); } for (auto &light : volumetric_diffuse_lights) @@ -726,8 +726,8 @@ void Scene::update_transform_listener_components() l->world_to_texture[i] = world_to_texture[i]; l->texture_to_world[i] = texture_to_world[i]; } - l->world_lo = transform->world_aabb.get_minimum4(); - l->world_hi = transform->world_aabb.get_maximum4(); + l->world_lo = transform->get_aabb().get_minimum4(); + l->world_hi = transform->get_aabb().get_maximum4(); l->timestamp = timestamp->last_timestamp; } } @@ -749,8 +749,8 @@ void Scene::update_transform_listener_components() for (int i = 0; i < 3; i++) r->world_to_texture[i] = world_to_texture[i]; - r->world_lo = transform->world_aabb.get_minimum4(); - r->world_hi = transform->world_aabb.get_maximum4(); + r->world_lo = transform->get_aabb().get_minimum4(); + r->world_hi = transform->get_aabb().get_maximum4(); r->timestamp = timestamp->last_timestamp; } } @@ -798,20 +798,19 @@ void Scene::update_cached_transforms_range(size_t begin_range, size_t end_range) { if (cached_transform->has_scene_node()) { + auto &bb = get_aabbs().get_aabbs()[cached_transform->aabb.offset]; if (cached_transform->get_skin()) { // TODO: Isolate the AABB per bone. - cached_transform->world_aabb = - AABB(vec3(std::numeric_limits::max()), - vec3(-std::numeric_limits::max())); - for (auto &m : cached_transform->get_skin()->cached_skin_transform.bone_world_transforms) - SIMD::transform_and_expand_aabb(cached_transform->world_aabb, *aabb->aabb, m); + bb = AABB(vec3(std::numeric_limits::max()), vec3(-std::numeric_limits::max())); + + auto *cached_skin = cached_transform->scene_node->get_skin_cached(); + for (size_t j = 0, n = cached_transform->get_skin()->transform.count; j < n; j++) + SIMD::transform_and_expand_aabb(bb, *aabb->aabb, cached_skin[j]); } else { - SIMD::transform_aabb(cached_transform->world_aabb, - *aabb->aabb, - cached_transform->get_world_transform()); + SIMD::transform_aabb(bb, *aabb->aabb, cached_transform->get_world_transform()); } } @@ -878,12 +877,10 @@ void Scene::distribute_per_level_updates(TaskGroup *group) static void update_transform_tree_node(Node &node, const mat4 &transform) { - node.prev_cached_transform = node.cached_transform; - compute_model_transform(node.cached_transform.world_transform, - node.transform.scale, node.transform.rotation, node.transform.translation, - transform); + node.get_cached_prev_transform() = node.get_cached_transform(); + auto &t = node.get_transform(); + compute_model_transform(node.get_cached_transform(), t.scale, t.rotation, t.translation, transform); - //compute_normal_transform(node.cached_transform.normal_transform, node.cached_transform.world_transform); node.update_timestamp(); node.clear_pending_update_no_atomic(); } @@ -894,7 +891,7 @@ static void perform_updates(Node * const *updates, size_t count) { auto *update = updates[i]; auto *parent = update->get_parent(); - auto &transform = parent ? parent->cached_transform.world_transform : identity_transform; + auto &transform = parent ? parent->get_cached_transform() : identity_transform; update_transform_tree_node(*update, transform); } } @@ -944,26 +941,22 @@ NodeHandle Scene::create_skinned_node(const SceneFormats::Skin &skin) for (size_t i = 0; i < skin.joint_transforms.size(); i++) { bones.push_back(create_node()); - bones[i]->transform.translation = skin.joint_transforms[i].translation; - bones[i]->transform.scale = skin.joint_transforms[i].scale; - bones[i]->transform.rotation = skin.joint_transforms[i].rotation; + auto &t = bones[i]->get_transform(); + t.translation = skin.joint_transforms[i].translation; + t.scale = skin.joint_transforms[i].scale; + t.rotation = skin.joint_transforms[i].rotation; } - node->set_skin(skinning_pool.allocate()); - auto &node_skin = *node->get_skin(); - node_skin.cached_skin_transform.bone_world_transforms.resize(skin.joint_transforms.size()); - node_skin.prev_cached_skin_transform.bone_world_transforms.resize(skin.joint_transforms.size()); - //node->cached_skin_transform.bone_normal_transforms.resize(skin.joint_transforms.size()); + Node::Skinning *pskin = skinning_pool.allocate(); - node_skin.skin.reserve(skin.joint_transforms.size()); - node_skin.cached_skin.reserve(skin.joint_transforms.size()); - node_skin.inverse_bind_poses.reserve(skin.joint_transforms.size()); + pskin->skin.reserve(skin.joint_transforms.size()); + pskin->inverse_bind_poses.reserve(skin.joint_transforms.size()); for (size_t i = 0; i < skin.joint_transforms.size(); i++) { - node_skin.cached_skin.push_back(&bones[i]->cached_transform); - node_skin.skin.push_back(&bones[i]->transform); - node_skin.inverse_bind_poses.push_back(skin.inverse_bind_pose[i]); + pskin->skin.push_back(bones[i]->transform.offset); + pskin->inverse_bind_poses.push_back(skin.inverse_bind_pose[i]); } + node->set_skin(pskin); for (auto &skeleton : skin.skeletons) { @@ -972,7 +965,7 @@ NodeHandle Scene::create_skinned_node(const SceneFormats::Skin &skin) add_bone(bones.data(), skeleton.index, child); } - node_skin.skin_compat = skin.skin_compat; + pskin->skin_compat = skin.skin_compat; return node; } @@ -998,6 +991,9 @@ Entity *Scene::create_volumetric_diffuse_light(uvec3 resolution, Node *node) auto *bounded = entity->allocate_component(); bounded->aabb = &VolumetricDiffuseLight::get_static_aabb(); + if (!get_aabbs().allocate(1, &transform->aabb)) + LOGE("Exhausted AABB pool.\n"); + if (node) { transform->scene_node = node; @@ -1020,6 +1016,9 @@ Entity *Scene::create_volumetric_fog_region(Node *node) auto *bounded = entity->allocate_component(); bounded->aabb = &VolumetricFogRegion::get_static_aabb(); + if (!get_aabbs().allocate(1, &transform->aabb)) + LOGE("Exhausted AABB pool.\n"); + if (node) { transform->scene_node = node; @@ -1042,6 +1041,9 @@ Entity *Scene::create_volumetric_decal(Node *node) auto *bounded = entity->allocate_component(); bounded->aabb = &VolumetricDecal::get_static_aabb(); + if (!get_aabbs().allocate(1, &transform->aabb)) + LOGE("Exhausted AABB pool.\n"); + if (node) { transform->scene_node = node; @@ -1063,7 +1065,7 @@ Entity *Scene::create_light(const SceneFormats::LightInfo &light, Node *node) { auto *dir = entity->allocate_component(); auto *transform = entity->allocate_component(); - transform->transform = &node->cached_transform; + transform->transform = &node->get_cached_transform(); dir->color = light.color; break; } @@ -1101,6 +1103,10 @@ Entity *Scene::create_light(const SceneFormats::LightInfo &light, Node *node) auto *bounded = entity->allocate_component(); bounded->aabb = renderable->get_static_aabb(); + + if (!get_aabbs().allocate(1, &transform->aabb)) + LOGE("Exhausted AABB pool.\n"); + break; } } @@ -1125,6 +1131,9 @@ Entity *Scene::create_renderable(AbstractRenderableHandle renderable, Node *node } auto *bounded = entity->allocate_component(); bounded->aabb = renderable->get_static_aabb(); + + if (!get_aabbs().allocate(1, &transform->aabb)) + LOGE("Exhausted AABB pool.\n"); } else entity->allocate_component(); @@ -1206,4 +1215,79 @@ void Scene::queue_destroy_entity(Entity *entity) } } +TransformAllocator::TransformAllocator() +{ + init(1, 20, &allocator); + prime(nullptr); +} + +bool TransformAllocator::allocate(uint32_t count, Util::AllocatedSlice *slice) +{ + if (!Util::SliceAllocator::allocate(count, slice)) + return false; + high_water_mark = std::max(count + slice->offset, high_water_mark); + return true; +} + +uint32_t TransformBackingAllocator::allocate(uint32_t count) +{ + if (!allocated_global) + { + prime(count, nullptr); + allocated_global = true; + return 0; + } + else + return UINT32_MAX; +} + +void TransformBackingAllocator::free(uint32_t index) +{ + if (index == 0) + allocated_global = false; +} + +void TransformBackingAllocator::prime(uint32_t count, const void *) +{ + transforms.reserve(count); + cached_transforms.reserve(count); + cached_prev_transforms.reserve(count); +} + +TransformAllocatorAABB::TransformAllocatorAABB() +{ + init(1, 20, &allocator); + prime(nullptr); +} + +bool TransformAllocatorAABB::allocate(uint32_t count, Util::AllocatedSlice *slice) +{ + if (!Util::SliceAllocator::allocate(count, slice)) + return false; + high_water_mark = std::max(count + slice->offset, high_water_mark); + return true; +} + +uint32_t TransformBackingAllocatorAABB::allocate(uint32_t count) +{ + if (!allocated_global) + { + prime(count, nullptr); + allocated_global = true; + return 0; + } + else + return UINT32_MAX; +} + +void TransformBackingAllocatorAABB::free(uint32_t index) +{ + if (index == 0) + allocated_global = false; +} + +void TransformBackingAllocatorAABB::prime(uint32_t count, const void *) +{ + aabb.reserve(count); +} } diff --git a/renderer/scene.hpp b/renderer/scene.hpp index c69e3fdf..847291be 100644 --- a/renderer/scene.hpp +++ b/renderer/scene.hpp @@ -29,6 +29,7 @@ #include "no_init_pod.hpp" #include "thread_group.hpp" #include "atomic_append_buffer.hpp" +#include "arena_allocator.hpp" #include namespace Granite @@ -38,6 +39,62 @@ struct EnvironmentComponent; class Node; class Scene; +struct TransformBackingAllocator final : Util::SliceBackingAllocator +{ + uint32_t allocate(uint32_t count) override; + void free(uint32_t index) override; + void prime(uint32_t count, const void *opaque_meta) override; + + Util::DynamicArray transforms; + // TODO: Move to 3x4 affine. + Util::DynamicArray cached_transforms; + Util::DynamicArray cached_prev_transforms; + bool allocated_global = false; +}; + +struct TransformBackingAllocatorAABB final : Util::SliceBackingAllocator +{ + uint32_t allocate(uint32_t count) override; + void free(uint32_t index) override; + void prime(uint32_t count, const void *opaque_meta) override; + + Util::DynamicArray aabb; + bool allocated_global = false; +}; + +class TransformAllocator : public Util::SliceAllocator +{ +public: + TransformAllocator(); + inline Transform *get_transforms() { return allocator.transforms.data(); } + inline mat4 *get_cached_transforms() { return allocator.cached_transforms.data(); } + inline mat4 *get_cached_prev_transforms() { return allocator.cached_prev_transforms.data(); } + inline const Transform *get_transforms() const { return allocator.transforms.data(); } + inline const mat4 *get_cached_transforms() const { return allocator.cached_transforms.data(); } + inline const mat4 *get_cached_prev_transforms() const { return allocator.cached_prev_transforms.data(); } + + uint32_t get_count() const { return high_water_mark; } + bool allocate(uint32_t count, Util::AllocatedSlice *slice); + +private: + TransformBackingAllocator allocator; + uint32_t high_water_mark = 0; +}; + +class TransformAllocatorAABB : public Util::SliceAllocator +{ +public: + TransformAllocatorAABB(); + inline AABB *get_aabbs() { return allocator.aabb.data(); } + inline const AABB *get_aabbs() const { return allocator.aabb.data(); } + + uint32_t get_count() const { return high_water_mark; } + bool allocate(uint32_t count, Util::AllocatedSlice *slice); + +private: + TransformBackingAllocatorAABB allocator; + uint32_t high_water_mark = 0; +}; class Scene { @@ -142,12 +199,17 @@ class Scene void remove_entities_with_component(ComponentType id); + inline TransformAllocator &get_transforms() { return transform_allocator; } + inline const TransformAllocator &get_transforms() const { return transform_allocator; } + inline TransformAllocatorAABB &get_aabbs() { return transform_allocator_aabb; } + inline const TransformAllocatorAABB &get_aabbs() const { return transform_allocator_aabb; } + private: EntityPool pool; Util::ObjectPool skinning_pool; - Util::ObjectPool transform_pool; - Util::ObjectPool cached_transform_pool; Util::ObjectPool node_pool; + TransformAllocator transform_allocator; + TransformAllocatorAABB transform_allocator_aabb; NodeHandle root_node; // Sets up the default useful component groups up front. diff --git a/renderer/scene_loader.cpp b/renderer/scene_loader.cpp index f63fade7..2b187caf 100644 --- a/renderer/scene_loader.cpp +++ b/renderer/scene_loader.cpp @@ -109,9 +109,10 @@ NodeHandle SceneLoader::build_tree_for_subscene(const SubsceneData &subscene) nodeptr = scene->create_node(); nodes.push_back(nodeptr); - nodeptr->transform.translation = node.transform.translation; - nodeptr->transform.rotation = node.transform.rotation; - nodeptr->transform.scale = node.transform.scale; + auto &node_transform = nodeptr->get_transform(); + node_transform.translation = node.transform.translation; + node_transform.rotation = node.transform.rotation; + node_transform.scale = node.transform.scale; } else nodes.push_back({}); @@ -157,7 +158,7 @@ NodeHandle SceneLoader::build_tree_for_subscene(const SubsceneData &subscene) if (camera.attached_to_node && touched.count(camera.node_index)) { auto *t = cam_entity->allocate_component(); - t->transform = &nodes[camera.node_index]->cached_transform; + t->transform = &nodes[camera.node_index]->get_cached_transform(); } } @@ -376,18 +377,24 @@ NodeHandle SceneLoader::parse_scene_format(const std::string &path, const std::s auto &t = elem["translation"]; transform.translation = vec3(t[0].GetFloat(), t[1].GetFloat(), t[2].GetFloat()); } + else + transform.translation = vec3(0.0f); if (elem.HasMember("rotation")) { auto &r = elem["rotation"]; transform.rotation = normalize(quat(r[3].GetFloat(), r[0].GetFloat(), r[1].GetFloat(), r[2].GetFloat())); } + else + transform.rotation = quat(1.0f, 0.0f, 0.0f, 0.0f); if (elem.HasMember("scale")) { auto &s = elem["scale"]; transform.scale = vec3(s[0].GetFloat(), s[1].GetFloat(), s[2].GetFloat()); } + else + transform.scale = vec3(1.0f); if (has_scene) { @@ -403,7 +410,7 @@ NodeHandle SceneLoader::parse_scene_format(const std::string &path, const std::s for (unsigned x = 0; x < instance_size.x; x++) { auto child = build_tree_for_subscene(scene_itr->second); - child->transform.translation = vec3(x, y, z) * stride; + child->get_transform().translation = vec3(x, y, z) * stride; subroot->add_child(child); } } @@ -414,7 +421,7 @@ NodeHandle SceneLoader::parse_scene_format(const std::string &path, const std::s else hierarchy.push_back(scene->create_node()); - hierarchy.back()->transform = transform; + hierarchy.back()->get_transform() = transform; } if (doc.HasMember("animations")) @@ -573,18 +580,24 @@ NodeHandle SceneLoader::parse_scene_format(const std::string &path, const std::s auto &s = value["scale"]; transform.scale = vec3(s[0].GetFloat(), s[1].GetFloat(), s[2].GetFloat()); } + else + transform.scale = vec3(1.0f); if (value.HasMember("translation")) { auto &t = value["translation"]; transform.translation = vec3(t[0].GetFloat(), t[1].GetFloat(), t[2].GetFloat()); } + else + transform.translation = vec3(0.0f); if (value.HasMember("rotation")) { auto &r = value["rotation"]; transform.rotation = normalize(quat(r[3].GetFloat(), r[0].GetFloat(), r[1].GetFloat(), r[2].GetFloat())); } + else + transform.rotation = quat(1.0f, 0.0f, 0.0f, 0.0f); }; if (doc.HasMember("terrain")) @@ -637,7 +650,7 @@ NodeHandle SceneLoader::parse_scene_format(const std::string &path, const std::s info.normal_size = terrain["normalSize"].GetUint(); auto handles = Ground::add_to_scene(*scene, size, tiling_factor, info); - read_transform(handles.node->transform, terrain); + read_transform(handles.node->get_transform(), terrain); root->add_child(handles.node); } diff --git a/tests/bindless_test.cpp b/tests/bindless_test.cpp index a6ec4ead..a7149a2f 100644 --- a/tests/bindless_test.cpp +++ b/tests/bindless_test.cpp @@ -74,10 +74,10 @@ struct BindlessApplication : Granite::Application, Granite::EventHandler rp.clear_color[0].float32[2] = 0.3f; cmd->begin_render_pass(rp); - auto bindless = device.create_bindless_descriptor_pool(BindlessResourceType::ImageFP, 1, 1024); + auto bindless = device.create_bindless_descriptor_pool(BindlessResourceType::Image, 1, 1024); bindless->allocate_descriptors(1024); for (unsigned i = 0; i < 1024; i++) - bindless->set_texture(i, images[i & 3]->get_view()); + bindless->push_texture(images[i & 3]->get_view()); cmd->set_bindless(0, bindless->get_descriptor_set()); cmd->set_bindless(2, bindless->get_descriptor_set()); cmd->set_sampler(1, 2, StockSampler::LinearClamp); diff --git a/tests/meshlet_viewer.cpp b/tests/meshlet_viewer.cpp index ffed4437..7c1204f9 100644 --- a/tests/meshlet_viewer.cpp +++ b/tests/meshlet_viewer.cpp @@ -110,9 +110,10 @@ struct MeshletViewerApplication : Granite::Application, Granite::EventHandler } auto nodeptr = scene.create_node(); - nodeptr->transform.translation = node.transform.translation; - nodeptr->transform.rotation = node.transform.rotation; - nodeptr->transform.scale = node.transform.scale; + auto &node_transform = nodeptr->get_transform(); + node_transform.translation = node.transform.translation; + node_transform.rotation = node.transform.rotation; + node_transform.scale = node.transform.scale; nodes.push_back(std::move(nodeptr)); } diff --git a/vulkan/managers/resource_manager.hpp b/vulkan/managers/resource_manager.hpp index 3b24df75..97b3061d 100644 --- a/vulkan/managers/resource_manager.hpp +++ b/vulkan/managers/resource_manager.hpp @@ -37,7 +37,7 @@ class MemoryMappedTexture; namespace Internal { -struct MeshGlobalAllocator : Util::SliceBackingAllocator +struct MeshGlobalAllocator final : Util::SliceBackingAllocator { explicit MeshGlobalAllocator(Device &device); uint32_t allocate(uint32_t count) override;