diff --git a/register_types.cpp b/register_types.cpp index 3e4d8b3b..08b3c706 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -119,6 +119,9 @@ void register_voxel_types() { PRINT_VERBOSE(String("Size of VoxelBuffer: {0}").format(varray((int)sizeof(VoxelBuffer)))); PRINT_VERBOSE(String("Size of VoxelBlock: {0}").format(varray((int)sizeof(VoxelBlock)))); + PRINT_VERBOSE(String("Size of VoxelTerrain: {0}").format(varray((int)sizeof(VoxelTerrain)))); + PRINT_VERBOSE(String("Size of VoxelLodTerrain: {0}").format(varray((int)sizeof(VoxelLodTerrain)))); + PRINT_VERBOSE(String("Size of VoxelInstancer: {0}").format(varray((int)sizeof(VoxelInstancer)))); #ifdef TOOLS_ENABLED EditorPlugins::add_by_type(); diff --git a/terrain/voxel_block.h b/terrain/voxel_block.h index ac1979fc..667d49a1 100644 --- a/terrain/voxel_block.h +++ b/terrain/voxel_block.h @@ -28,6 +28,9 @@ public: bool pending_transition_update = false; VoxelViewerRefCount viewers; bool got_first_mesh_update = false; + uint32_t last_collider_update_time = 0; + bool has_deferred_collider_update = false; + Vector deferred_collider_data; static VoxelBlock *create(Vector3i bpos, Ref buffer, unsigned int size, unsigned int p_lod_index); diff --git a/terrain/voxel_lod_terrain.cpp b/terrain/voxel_lod_terrain.cpp index 05535b2b..cad4a8ea 100644 --- a/terrain/voxel_lod_terrain.cpp +++ b/terrain/voxel_lod_terrain.cpp @@ -113,6 +113,10 @@ struct ScheduleSaveAction { } }; +static inline uint64_t get_ticks_msec() { + return OS::get_singleton()->get_ticks_msec(); +} + } // namespace VoxelLodTerrain::VoxelLodTerrain() { @@ -454,6 +458,8 @@ void VoxelLodTerrain::reset_maps() { } else { lod.map.clear(); } + + lod.deferred_collision_updates.clear(); } // Reset previous state caches to force rebuilding the view area @@ -1222,14 +1228,14 @@ void VoxelLodTerrain::_process() { _stats.time_request_blocks_to_update = profiling_clock.restart(); - // Receive mesh updates + const uint32_t main_thread_task_timeout = get_ticks_msec() + VoxelConstants::MAIN_THREAD_MESHING_BUDGET_MS; + + // Receive mesh updates: + // This contains work that should normally be threaded, but isn't because of Godot limitations. + // So after a timeout, it stops processing and will resume next frame. { VOXEL_PROFILE_SCOPE_NAMED("Receive mesh updates"); - // Allocate milliseconds max to upload meshes - const OS &os = *OS::get_singleton(); - const uint32_t timeout = os.get_ticks_msec() + VoxelConstants::MAIN_THREAD_MESHING_BUDGET_MS; - const Transform global_transform = get_global_transform(); // The following is done on the main thread because Godot doesn't really support multithreaded Mesh allocation. @@ -1237,7 +1243,9 @@ void VoxelLodTerrain::_process() { // hopefully Vulkan will allow us to upload graphical resources without stalling rendering as they upload? size_t queue_index = 0; - for (; queue_index < _reception_buffers.mesh_output.size() && os.get_ticks_msec() < timeout; ++queue_index) { + for (; queue_index < _reception_buffers.mesh_output.size() && get_ticks_msec() < main_thread_task_timeout; + ++queue_index) { + VOXEL_PROFILE_SCOPE(); const VoxelServer::BlockMeshOutput &ob = _reception_buffers.mesh_output[queue_index]; @@ -1293,14 +1301,9 @@ void VoxelLodTerrain::_process() { } block->set_mesh(mesh); - if (has_collision) { - block->set_collision_mesh(mesh_data.surfaces, get_tree()->is_debugging_collisions_hint(), this); - } - { VOXEL_PROFILE_SCOPE(); for (unsigned int dir = 0; dir < mesh_data.transition_surfaces.size(); ++dir) { - Ref transition_mesh = build_mesh( mesh_data.transition_surfaces[dir], mesh_data.primitive_type, @@ -1311,6 +1314,22 @@ void VoxelLodTerrain::_process() { } } + const uint32_t now = get_ticks_msec(); + if (has_collision) { + if (_collision_update_delay == 0 || now - block->last_collider_update_time > _collision_update_delay) { + block->set_collision_mesh(mesh_data.surfaces, get_tree()->is_debugging_collisions_hint(), this); + block->last_collider_update_time = now; + block->has_deferred_collider_update = false; + block->deferred_collider_data.clear(); + } else { + if (!block->has_deferred_collider_update) { + lod.deferred_collision_updates.push_back(ob.position); + block->has_deferred_collider_update = true; + } + block->deferred_collider_data = mesh_data.surfaces; + } + } + block->set_parent_transform(global_transform); } @@ -1324,6 +1343,8 @@ void VoxelLodTerrain::_process() { _stats.time_process_update_responses = profiling_clock.restart(); + process_deferred_collision_updates(main_thread_task_timeout); + #ifdef TOOLS_ENABLED if (is_showing_gizmos() && is_visible_in_tree()) { update_gizmos(); @@ -1331,6 +1352,44 @@ void VoxelLodTerrain::_process() { #endif } +void VoxelLodTerrain::process_deferred_collision_updates(uint32_t timeout_msec) { + VOXEL_PROFILE_SCOPE(); + + for (unsigned int lod_index = 0; lod_index < _lod_count; ++lod_index) { + Lod &lod = _lods[lod_index]; + + for (unsigned int i = 0; i < lod.deferred_collision_updates.size(); ++i) { + const Vector3i block_pos = lod.deferred_collision_updates[i]; + VoxelBlock *block = lod.map.get_block(block_pos); + + if (block == nullptr || block->has_deferred_collider_update == false) { + // Block was unloaded or no longer needs a collision update + unordered_remove(lod.deferred_collision_updates, i); + --i; + continue; + } + + const uint32_t now = get_ticks_msec(); + + if (now - block->last_collider_update_time > _collision_update_delay) { + block->set_collision_mesh( + block->deferred_collider_data, get_tree()->is_debugging_collisions_hint(), this); + block->last_collider_update_time = now; + block->has_deferred_collider_update = false; + block->deferred_collider_data.clear(); + + unordered_remove(lod.deferred_collision_updates, i); + --i; + } + + // We always process at least one, then we to check the timeout + if (get_ticks_msec() >= timeout_msec) { + return; + } + } + } +} + void VoxelLodTerrain::flush_pending_lod_edits() { // Propagates edits performed so far to other LODs. // These LODs must be currently in memory, otherwise terrain data will miss it. @@ -1609,6 +1668,12 @@ const VoxelLodTerrain::Stats &VoxelLodTerrain::get_stats() const { Dictionary VoxelLodTerrain::_b_get_statistics() const { Dictionary d; + int deferred_collision_updates = 0; + for (int lod_index = 0; lod_index < _lod_count; ++lod_index) { + const Lod &lod = _lods[lod_index]; + deferred_collision_updates += lod.deferred_collision_updates.size(); + } + // Breakdown of time spent in _process d["time_detect_required_blocks"] = _stats.time_detect_required_blocks; d["time_request_blocks_to_load"] = _stats.time_request_blocks_to_load; @@ -1616,7 +1681,7 @@ Dictionary VoxelLodTerrain::_b_get_statistics() const { d["time_request_blocks_to_update"] = _stats.time_request_blocks_to_update; d["time_process_update_responses"] = _stats.time_process_update_responses; - d["remaining_main_thread_blocks"] = _stats.remaining_main_thread_blocks; + d["remaining_main_thread_blocks"] = _stats.remaining_main_thread_blocks + deferred_collision_updates; d["dropped_block_loads"] = _stats.dropped_block_loads; d["dropped_block_meshs"] = _stats.dropped_block_meshs; d["updated_blocks"] = _stats.updated_blocks; @@ -1674,6 +1739,14 @@ void VoxelLodTerrain::set_voxel_bounds(Rect3i p_box) { } } +void VoxelLodTerrain::set_collision_update_delay(int delay_msec) { + _collision_update_delay = clamp(delay_msec, 0, 4000); +} + +int VoxelLodTerrain::get_collision_update_delay() const { + return _collision_update_delay; +} + void VoxelLodTerrain::_b_save_modified_blocks() { save_all_modified_blocks(true); } @@ -1886,6 +1959,10 @@ void VoxelLodTerrain::_bind_methods() { ClassDB::bind_method(D_METHOD("get_collision_lod_count"), &VoxelLodTerrain::get_collision_lod_count); ClassDB::bind_method(D_METHOD("set_collision_lod_count", "count"), &VoxelLodTerrain::set_collision_lod_count); + ClassDB::bind_method(D_METHOD("get_collision_update_delay"), &VoxelLodTerrain::get_collision_update_delay); + ClassDB::bind_method(D_METHOD("set_collision_update_delay", "delay_msec"), + &VoxelLodTerrain::set_collision_update_delay); + ClassDB::bind_method(D_METHOD("set_lod_count", "lod_count"), &VoxelLodTerrain::set_lod_count); ClassDB::bind_method(D_METHOD("get_lod_count"), &VoxelLodTerrain::get_lod_count); @@ -1932,6 +2009,8 @@ void VoxelLodTerrain::_bind_methods() { "set_generate_collisions", "get_generate_collisions"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_lod_count"), "set_collision_lod_count", "get_collision_lod_count"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_update_delay"), + "set_collision_update_delay", "get_collision_update_delay"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "run_stream_in_editor"), "set_run_stream_in_editor", "is_stream_running_in_editor"); ADD_PROPERTY(PropertyInfo(Variant::AABB, "voxel_bounds"), "set_voxel_bounds", "get_voxel_bounds"); diff --git a/terrain/voxel_lod_terrain.h b/terrain/voxel_lod_terrain.h index 3530eafd..10a774e8 100644 --- a/terrain/voxel_lod_terrain.h +++ b/terrain/voxel_lod_terrain.h @@ -69,6 +69,9 @@ public: void set_voxel_bounds(Rect3i p_box); inline Rect3i get_voxel_bounds() const { return _bounds_in_voxels; } + void set_collision_update_delay(int delay_msec); + int get_collision_update_delay() const; + enum ProcessMode { PROCESS_MODE_IDLE = 0, PROCESS_MODE_PHYSICS, @@ -91,11 +94,11 @@ public: int dropped_block_loads = 0; int dropped_block_meshs = 0; int remaining_main_thread_blocks = 0; - uint64_t time_detect_required_blocks = 0; - uint64_t time_request_blocks_to_load = 0; - uint64_t time_process_load_responses = 0; - uint64_t time_request_blocks_to_update = 0; - uint64_t time_process_update_responses = 0; + uint32_t time_detect_required_blocks = 0; + uint32_t time_request_blocks_to_load = 0; + uint32_t time_process_load_responses = 0; + uint32_t time_request_blocks_to_update = 0; + uint32_t time_process_update_responses = 0; }; const Stats &get_stats() const; @@ -161,6 +164,7 @@ private: void flush_pending_lod_edits(); void save_all_modified_blocks(bool with_copy); void send_block_data_requests(); + void process_deferred_collision_updates(uint32_t timeout_msec); void add_transition_update(VoxelBlock *block); void add_transition_updates_around(Vector3i block_pos, int lod_index); @@ -215,6 +219,7 @@ private: bool _generate_collisions = true; int _collision_lod_count = -1; + int _collision_update_delay = 0; VoxelInstancer *_instancer = nullptr; @@ -231,6 +236,8 @@ private: Vector3i last_viewer_block_pos; int last_view_distance_blocks = 0; + std::vector deferred_collision_updates; + // Members for memory caching std::vector blocks_to_load; }; diff --git a/terrain/voxel_terrain.h b/terrain/voxel_terrain.h index e2a15354..66cc334a 100644 --- a/terrain/voxel_terrain.h +++ b/terrain/voxel_terrain.h @@ -70,11 +70,11 @@ public: int dropped_block_loads = 0; int dropped_block_meshs = 0; int remaining_main_thread_blocks = 0; - uint64_t time_detect_required_blocks = 0; - uint64_t time_request_blocks_to_load = 0; - uint64_t time_process_load_responses = 0; - uint64_t time_request_blocks_to_update = 0; - uint64_t time_process_update_responses = 0; + uint32_t time_detect_required_blocks = 0; + uint32_t time_request_blocks_to_load = 0; + uint32_t time_process_load_responses = 0; + uint32_t time_request_blocks_to_update = 0; + uint32_t time_process_update_responses = 0; }; const Stats &get_stats() const;