From 73d5d9993c55c6696e42204cb9c37ad7b65c4c65 Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Wed, 8 May 2019 20:53:51 +0100 Subject: [PATCH] Load blocks on demand instead of speculatively, it's faster --- terrain/voxel_lod_terrain.cpp | 275 +++++++++++++++++----------------- terrain/voxel_lod_terrain.h | 6 +- terrain/voxel_map.cpp | 14 +- terrain/voxel_map.h | 7 +- 4 files changed, 154 insertions(+), 148 deletions(-) diff --git a/terrain/voxel_lod_terrain.cpp b/terrain/voxel_lod_terrain.cpp index 57ef4c7..82918c2 100644 --- a/terrain/voxel_lod_terrain.cpp +++ b/terrain/voxel_lod_terrain.cpp @@ -164,8 +164,6 @@ void VoxelLodTerrain::set_lod_count(unsigned int p_lod_count) { Lod &lod = _lods[lod_index]; - lod.blocks_in_meshing_area.clear(); - // Instance new maps if we have more lods, or clear them otherwise if (lod_index < get_lod_count()) { @@ -225,8 +223,6 @@ Dictionary VoxelLodTerrain::get_block_info(Vector3 fbpos, unsigned int lod_index } } - bool in_show_area = lod.blocks_in_meshing_area.has(bpos); - bool meshed = false; bool visible = false; const VoxelBlock *block = lod.map->get_block(bpos); @@ -236,7 +232,6 @@ Dictionary VoxelLodTerrain::get_block_info(Vector3 fbpos, unsigned int lod_index } d["state"] = state; - d["in_show_area"] = in_show_area; d["meshed"] = meshed; d["visible"] = visible; return d; @@ -329,41 +324,6 @@ Vector3 VoxelLodTerrain::get_viewer_pos() const { return Vector3(); } -void VoxelLodTerrain::make_block_dirty(Vector3i bpos, unsigned int lod_index) { - - CRASH_COND(lod_index >= get_lod_count()); - - // TODO Immediate update viewer distance? - - Lod &lod = _lods[lod_index]; - - Map::Element *state = lod.block_states.find(bpos); - - if (state == NULL) { - // The block is not dirty, so it will either be loaded or updated - - if (lod.map->has_block(bpos)) { - - lod.blocks_pending_update.push_back(bpos); - lod.block_states.insert(bpos, BLOCK_UPDATE_NOT_SENT); - - } else { - - lod.blocks_to_load.push_back(bpos); - lod.block_states.insert(bpos, BLOCK_LOAD); - } - - } else if (state->value() == BLOCK_UPDATE_SENT) { - // The updater is already processing the block, - // but the block was modified again so we schedule another update - state->value() = BLOCK_UPDATE_NOT_SENT; - lod.blocks_pending_update.push_back(bpos); - - } else if (state->value() == BLOCK_UPDATE_NOT_SENT) { - WARN_PRINT("Block already marked as BLOCK_UPDATE_NOT_SENT"); - } -} - static void remove_positions_outside_box( std::vector &positions, Rect3i box, @@ -381,6 +341,34 @@ static void remove_positions_outside_box( } } +void VoxelLodTerrain::load_block_and_neighbors(const Vector3i &p_bpos, unsigned int lod_index) { + CRASH_COND(lod_index >= get_lod_count()); + Lod &lod = _lods[lod_index]; + + Vector3i bpos; + + for (int y = -1; y < 2; ++y) { + for (int z = -1; z < 2; ++z) { + for (int x = -1; x < 2; ++x) { + + bpos.x = p_bpos.x + x; + bpos.y = p_bpos.y + y; + bpos.z = p_bpos.z + z; + + VoxelBlock *block = lod.map->get_block(bpos); + + if (block == nullptr) { + Map::Element *E = lod.block_states.find(bpos); + if (E == nullptr) { + lod.blocks_to_load.push_back(bpos); + lod.block_states.insert(bpos, BLOCK_LOAD); + } + } + } + } + } +} + void VoxelLodTerrain::_process() { if (get_lod_count() == 0) { @@ -397,10 +385,10 @@ void VoxelLodTerrain::_process() { // Here we go... - // Find out which blocks _data_ need to be loaded + // Remove blocks falling out of block region extent { // This should be the same distance relatively to each LOD - int view_distance_blocks_within_lod = get_block_region_extent(); + int block_region_extent = get_block_region_extent(); for (unsigned int lod_index = 0; lod_index < get_lod_count(); ++lod_index) { Lod &lod = _lods[lod_index]; @@ -411,13 +399,8 @@ void VoxelLodTerrain::_process() { int block_size_po2 = _lods[0].map->get_block_size_pow2() + lod_index; Vector3i viewer_block_pos_within_lod = VoxelMap::voxel_to_block_b(viewer_pos, block_size_po2); - Rect3i new_box = Rect3i::from_center_extents( - viewer_block_pos_within_lod, - Vector3i(view_distance_blocks_within_lod)); - - Rect3i prev_box = Rect3i::from_center_extents( - lod.last_viewer_block_pos, - Vector3i(lod.last_view_distance_blocks)); + Rect3i new_box = Rect3i::from_center_extents(viewer_block_pos_within_lod, Vector3i(block_region_extent)); + Rect3i prev_box = Rect3i::from_center_extents(lod.last_viewer_block_pos, Vector3i(lod.last_view_distance_blocks)); // Eliminate pending blocks that aren't needed remove_positions_outside_box(lod.blocks_to_load, new_box, lod.block_states); @@ -442,10 +425,6 @@ void VoxelLodTerrain::_process() { if (prev_contains && !new_contains) { // Unload block immerge_block(pos, lod_index); - - } else if (!prev_contains && new_contains) { - // Load or update block - make_block_dirty(pos, lod_index); } } } @@ -453,10 +432,105 @@ void VoxelLodTerrain::_process() { } lod.last_viewer_block_pos = viewer_block_pos_within_lod; - lod.last_view_distance_blocks = view_distance_blocks_within_lod; + lod.last_view_distance_blocks = block_region_extent; } } + // Find which blocks we need to load and see + { + struct SubdivideAction { + VoxelLodTerrain *self; + int blocked_count = 0; + + bool can_do(LodOctree::Node *node, unsigned int lod_index) { + CRASH_COND(lod_index == 0); + + unsigned int child_lod_index = lod_index - 1; + Lod &lod = self->_lods[child_lod_index]; + + bool can = true; + + // Can only subdivide if higher detail meshes are ready to be shown, otherwise it will produce holes + for (int i = 0; i < 8; ++i) { + Vector3i child_pos = LodOctree::get_child_position(node->position, i); + VoxelBlock *block = lod.map->get_block(child_pos); + + if (block == nullptr) { + can = false; + self->load_block_and_neighbors(child_pos, child_lod_index); + + } else if (!block->has_been_meshed) { + can = false; + } + } + + if (!can) { + ++blocked_count; + } + + return can; + } + + bool operator()(LodOctree::Node *node, unsigned int lod_index) { + Lod &lod = self->_lods[lod_index]; + Vector3i bpos = node->position; + VoxelBlock *block = lod.map->get_block(bpos); + CRASH_COND(block == nullptr); + block->set_visible(true); + return true; + } + }; + + struct UnsubdivideAction { + VoxelLodTerrain *self; + int blocked_count = 0; + + bool can_do(LodOctree::Node *node, unsigned int lod_index) { + // Can only unsubdivide if the parent mesh is ready + Lod &lod = self->_lods[lod_index]; + const Vector3i &bpos = node->position; + VoxelBlock *block = lod.map->get_block(bpos); + + bool can = true; + + if (block == nullptr) { + can = false; + self->load_block_and_neighbors(bpos, lod_index); + + } else if (!block->has_been_meshed) { + can = false; + } + + if (!can) { + ++blocked_count; + } + + return can; + } + + void operator()(LodOctree::Node *node, unsigned int lod_index) { + Lod &lod = self->_lods[lod_index]; + const Vector3i &bpos = node->position; + VoxelBlock *block = lod.map->get_block(bpos); + if (block) { + block->set_visible(false); + } + } + }; + + SubdivideAction subdivide_action; + subdivide_action.self = this; + + UnsubdivideAction unsubdivide_action; + unsubdivide_action.self = this; + + _lod_octree.update(viewer_pos, subdivide_action, unsubdivide_action); + + // Ideally, this stat should stabilize to zero. + // If not, something in the meshing process prevents LODs to properly show up and should be fixed. + _stats.blocked_lods = subdivide_action.blocked_count + unsubdivide_action.blocked_count; + } + // Send block loading requests { VoxelProviderThread::InputData input; @@ -476,6 +550,7 @@ void VoxelLodTerrain::_process() { lod.blocks_to_load.clear(); } + //print_line(String("Sending {0}").format(varray(input.blocks_to_emerge.size()))); _provider_thread->push(input); } @@ -489,6 +564,8 @@ void VoxelLodTerrain::_process() { _provider_thread->pop(output); _stats.provider = output.stats; + //print_line(String("Loaded {0} blocks").format(varray(output.emerged_blocks.size()))); + for (int i = 0; i < output.emerged_blocks.size(); ++i) { const VoxelProviderThread::EmergeOutput &eo = output.emerged_blocks[i]; @@ -514,7 +591,10 @@ void VoxelLodTerrain::_process() { // Store buffer bool check_neighbors = !lod.map->has_block(eo.block_position); - lod.map->set_block_buffer(eo.block_position, eo.voxels); + VoxelBlock *block = lod.map->set_block_buffer(eo.block_position, eo.voxels); + //print_line(String("Adding block {0} at lod {1}").format(varray(eo.block_position.to_vec3(), eo.lod))); + // The block will be made visible only by LodOctree + block->set_visible(false); lod.block_states.erase(state); @@ -602,6 +682,7 @@ void VoxelLodTerrain::_process() { lod.blocks_pending_update.clear(); } + //print_line(String("Sending {0} updates").format(varray(input.blocks.size()))); _block_updater->push(input); } @@ -680,7 +761,6 @@ void VoxelLodTerrain::_process() { } block->set_mesh(mesh, world); - block->set_visible(lod.blocks_in_meshing_area.has(ob.position)); block->has_been_meshed = true; } @@ -689,89 +769,6 @@ void VoxelLodTerrain::_process() { _stats.time_process_update_responses = profiling_clock.restart(); - // Find out which blocks need to be shown - { - struct SubdivideAction { - VoxelLodTerrain *self; - int non_meshed_blocks = 0; - - bool can_do(LodOctree::Node *node, unsigned int lod_index) { - CRASH_COND(lod_index == 0); - - unsigned int child_lod_index = lod_index - 1; - Lod &lod = self->_lods[child_lod_index]; - - // Can only subdivide if higher detail meshes are ready to be shown, otherwise it will produce holes - for (int i = 0; i < 8; ++i) { - Vector3i child_pos = LodOctree::get_child_position(node->position, i); - VoxelBlock *block = lod.map->get_block(child_pos); - if (block == nullptr) { - return false; - } - if (!block->has_been_meshed) { - ++non_meshed_blocks; - return false; - } - } - return true; - } - - bool operator()(LodOctree::Node *node, unsigned int lod_index) { - Lod &lod = self->_lods[lod_index]; - Vector3i bpos = node->position; - // TODO Meshing area is useless because now we prevent the octree from changing if blocks aren't available - lod.blocks_in_meshing_area.insert(bpos); - VoxelBlock *block = lod.map->get_block(bpos); - CRASH_COND(block == nullptr); - block->set_visible(true); - return true; - } - }; - - struct UnsubdivideAction { - VoxelLodTerrain *self; - int non_meshed_blocks = 0; - - bool can_do(LodOctree::Node *node, unsigned int lod_index) { - // Can only unsubdivide if the parent mesh is ready - Lod &lod = self->_lods[lod_index]; - VoxelBlock *block = lod.map->get_block(node->position); - if (block == nullptr) { - // Ok, that block got unloaded? Might happen if you teleport away - return true; - } - if (block->has_been_meshed) { - return true; - } else { - ++non_meshed_blocks; - return false; - } - } - - void operator()(LodOctree::Node *node, unsigned int lod_index) { - Lod &lod = self->_lods[lod_index]; - Vector3i bpos = node->position; - lod.blocks_in_meshing_area.erase(bpos); - VoxelBlock *block = lod.map->get_block(bpos); - if (block) { - block->set_visible(false); - } - } - }; - - SubdivideAction subdivide_action; - subdivide_action.self = this; - - UnsubdivideAction unsubdivide_action; - unsubdivide_action.self = this; - - _lod_octree.update(viewer_pos, subdivide_action, unsubdivide_action); - - // Ideally, this stat should stabilize to zero. - // If not, something in the meshing process prevents LODs to properly show up and should be fixed. - _stats.blocked_lods = subdivide_action.non_meshed_blocks + unsubdivide_action.non_meshed_blocks; - } - _stats.time_process_lod = profiling_clock.restart(); } diff --git a/terrain/voxel_lod_terrain.h b/terrain/voxel_lod_terrain.h index 62049cc..a24d18e 100644 --- a/terrain/voxel_lod_terrain.h +++ b/terrain/voxel_lod_terrain.h @@ -80,7 +80,7 @@ private: void immerge_block(Vector3i block_pos, unsigned int lod_index); void reset_updater(); Vector3 get_viewer_pos() const; - void make_block_dirty(Vector3i bpos, unsigned int lod_index); + void load_block_and_neighbors(const Vector3i &p_bpos, unsigned int lod_index); template void for_all_blocks(A &action) { @@ -111,10 +111,6 @@ private: Map block_states; std::vector blocks_pending_update; - // Reflects LodOctree but only in this LOD - // TODO Would be nice to use LodOctree directly! - Set blocks_in_meshing_area; - // These are relative to this LOD, in block coordinates Vector3i last_viewer_block_pos; int last_view_distance_blocks = 0; diff --git a/terrain/voxel_map.cpp b/terrain/voxel_map.cpp index 245343a..33dad99 100644 --- a/terrain/voxel_map.cpp +++ b/terrain/voxel_map.cpp @@ -138,8 +138,13 @@ void VoxelMap::set_block(Vector3i bpos, VoxelBlock *block) { _blocks.set(bpos, block); } -void VoxelMap::set_block_buffer(Vector3i bpos, Ref buffer) { - ERR_FAIL_COND(buffer.is_null()); +void VoxelMap::remove_block_internal(Vector3i bpos) { + // This function assumes the block is already freed + _blocks.erase(bpos); +} + +VoxelBlock *VoxelMap::set_block_buffer(Vector3i bpos, Ref buffer) { + ERR_FAIL_COND_V(buffer.is_null(), nullptr); VoxelBlock *block = get_block(bpos); if (block == NULL) { block = VoxelBlock::create(bpos, *buffer, _block_size, _lod_index); @@ -147,6 +152,7 @@ void VoxelMap::set_block_buffer(Vector3i bpos, Ref buffer) { } else { block->voxels = buffer; } + return block; } bool VoxelMap::has_block(Vector3i pos) const { @@ -224,6 +230,10 @@ void VoxelMap::clear() { _last_accessed_block = NULL; } +int VoxelMap::get_block_count() const { + return _blocks.size(); +} + void VoxelMap::_bind_methods() { ClassDB::bind_method(D_METHOD("get_voxel", "x", "y", "z", "c"), &VoxelMap::_get_voxel_binding, DEFVAL(0)); diff --git a/terrain/voxel_map.h b/terrain/voxel_map.h index b81957c..7bb257d 100644 --- a/terrain/voxel_map.h +++ b/terrain/voxel_map.h @@ -60,7 +60,7 @@ public: void get_buffer_copy(Vector3i min_pos, VoxelBuffer &dst_buffer, unsigned int channels_mask = 1); // Moves the given buffer into a block of the map. The buffer is referenced, no copy is made. - void set_block_buffer(Vector3i bpos, Ref buffer); + VoxelBlock *set_block_buffer(Vector3i bpos, Ref buffer); struct NoAction { inline void operator()(VoxelBlock *block) {} @@ -76,7 +76,7 @@ public: ERR_FAIL_COND(block == NULL); pre_delete(block); memdelete(block); - _blocks.erase(bpos); + remove_block_internal(bpos); } } @@ -118,6 +118,8 @@ public: void clear(); + int get_block_count() const; + template void for_all_blocks(Op_T op) { const Vector3i *key = NULL; @@ -132,6 +134,7 @@ public: private: void set_block(Vector3i bpos, VoxelBlock *block); VoxelBlock *get_or_create_block_at_voxel_pos(Vector3i pos); + void remove_block_internal(Vector3i bpos); void set_block_size_pow2(unsigned int p);