Load blocks on demand instead of speculatively, it's faster

This commit is contained in:
Marc Gilleron 2019-05-08 20:53:51 +01:00
parent 035e45d0a3
commit 73d5d9993c
4 changed files with 154 additions and 148 deletions

View File

@ -164,8 +164,6 @@ void VoxelLodTerrain::set_lod_count(unsigned int p_lod_count) {
Lod &lod = _lods[lod_index]; Lod &lod = _lods[lod_index];
lod.blocks_in_meshing_area.clear();
// Instance new maps if we have more lods, or clear them otherwise // Instance new maps if we have more lods, or clear them otherwise
if (lod_index < get_lod_count()) { 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 meshed = false;
bool visible = false; bool visible = false;
const VoxelBlock *block = lod.map->get_block(bpos); 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["state"] = state;
d["in_show_area"] = in_show_area;
d["meshed"] = meshed; d["meshed"] = meshed;
d["visible"] = visible; d["visible"] = visible;
return d; return d;
@ -329,41 +324,6 @@ Vector3 VoxelLodTerrain::get_viewer_pos() const {
return Vector3(); 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<Vector3i, BlockState>::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( static void remove_positions_outside_box(
std::vector<Vector3i> &positions, std::vector<Vector3i> &positions,
Rect3i box, 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<Vector3i, BlockState>::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() { void VoxelLodTerrain::_process() {
if (get_lod_count() == 0) { if (get_lod_count() == 0) {
@ -397,10 +385,10 @@ void VoxelLodTerrain::_process() {
// Here we go... // 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 // 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) { for (unsigned int lod_index = 0; lod_index < get_lod_count(); ++lod_index) {
Lod &lod = _lods[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; 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); Vector3i viewer_block_pos_within_lod = VoxelMap::voxel_to_block_b(viewer_pos, block_size_po2);
Rect3i new_box = Rect3i::from_center_extents( Rect3i new_box = Rect3i::from_center_extents(viewer_block_pos_within_lod, Vector3i(block_region_extent));
viewer_block_pos_within_lod, Rect3i prev_box = Rect3i::from_center_extents(lod.last_viewer_block_pos, Vector3i(lod.last_view_distance_blocks));
Vector3i(view_distance_blocks_within_lod));
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 // Eliminate pending blocks that aren't needed
remove_positions_outside_box(lod.blocks_to_load, new_box, lod.block_states); 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) { if (prev_contains && !new_contains) {
// Unload block // Unload block
immerge_block(pos, lod_index); 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_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<bool>::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<bool>::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<bool>::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<bool>::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<bool>::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 // Send block loading requests
{ {
VoxelProviderThread::InputData input; VoxelProviderThread::InputData input;
@ -476,6 +550,7 @@ void VoxelLodTerrain::_process() {
lod.blocks_to_load.clear(); lod.blocks_to_load.clear();
} }
//print_line(String("Sending {0}").format(varray(input.blocks_to_emerge.size())));
_provider_thread->push(input); _provider_thread->push(input);
} }
@ -489,6 +564,8 @@ void VoxelLodTerrain::_process() {
_provider_thread->pop(output); _provider_thread->pop(output);
_stats.provider = output.stats; _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) { for (int i = 0; i < output.emerged_blocks.size(); ++i) {
const VoxelProviderThread::EmergeOutput &eo = output.emerged_blocks[i]; const VoxelProviderThread::EmergeOutput &eo = output.emerged_blocks[i];
@ -514,7 +591,10 @@ void VoxelLodTerrain::_process() {
// Store buffer // Store buffer
bool check_neighbors = !lod.map->has_block(eo.block_position); 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); lod.block_states.erase(state);
@ -602,6 +682,7 @@ void VoxelLodTerrain::_process() {
lod.blocks_pending_update.clear(); lod.blocks_pending_update.clear();
} }
//print_line(String("Sending {0} updates").format(varray(input.blocks.size())));
_block_updater->push(input); _block_updater->push(input);
} }
@ -680,7 +761,6 @@ void VoxelLodTerrain::_process() {
} }
block->set_mesh(mesh, world); block->set_mesh(mesh, world);
block->set_visible(lod.blocks_in_meshing_area.has(ob.position));
block->has_been_meshed = true; block->has_been_meshed = true;
} }
@ -689,89 +769,6 @@ void VoxelLodTerrain::_process() {
_stats.time_process_update_responses = profiling_clock.restart(); _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<bool>::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<bool>::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<bool>::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<bool>::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<bool>::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(); _stats.time_process_lod = profiling_clock.restart();
} }

View File

@ -80,7 +80,7 @@ private:
void immerge_block(Vector3i block_pos, unsigned int lod_index); void immerge_block(Vector3i block_pos, unsigned int lod_index);
void reset_updater(); void reset_updater();
Vector3 get_viewer_pos() const; 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 <typename A> template <typename A>
void for_all_blocks(A &action) { void for_all_blocks(A &action) {
@ -111,10 +111,6 @@ private:
Map<Vector3i, BlockState> block_states; Map<Vector3i, BlockState> block_states;
std::vector<Vector3i> blocks_pending_update; std::vector<Vector3i> blocks_pending_update;
// Reflects LodOctree but only in this LOD
// TODO Would be nice to use LodOctree directly!
Set<Vector3i> blocks_in_meshing_area;
// These are relative to this LOD, in block coordinates // These are relative to this LOD, in block coordinates
Vector3i last_viewer_block_pos; Vector3i last_viewer_block_pos;
int last_view_distance_blocks = 0; int last_view_distance_blocks = 0;

View File

@ -138,8 +138,13 @@ void VoxelMap::set_block(Vector3i bpos, VoxelBlock *block) {
_blocks.set(bpos, block); _blocks.set(bpos, block);
} }
void VoxelMap::set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer) { void VoxelMap::remove_block_internal(Vector3i bpos) {
ERR_FAIL_COND(buffer.is_null()); // This function assumes the block is already freed
_blocks.erase(bpos);
}
VoxelBlock *VoxelMap::set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer) {
ERR_FAIL_COND_V(buffer.is_null(), nullptr);
VoxelBlock *block = get_block(bpos); VoxelBlock *block = get_block(bpos);
if (block == NULL) { if (block == NULL) {
block = VoxelBlock::create(bpos, *buffer, _block_size, _lod_index); block = VoxelBlock::create(bpos, *buffer, _block_size, _lod_index);
@ -147,6 +152,7 @@ void VoxelMap::set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer) {
} else { } else {
block->voxels = buffer; block->voxels = buffer;
} }
return block;
} }
bool VoxelMap::has_block(Vector3i pos) const { bool VoxelMap::has_block(Vector3i pos) const {
@ -224,6 +230,10 @@ void VoxelMap::clear() {
_last_accessed_block = NULL; _last_accessed_block = NULL;
} }
int VoxelMap::get_block_count() const {
return _blocks.size();
}
void VoxelMap::_bind_methods() { void VoxelMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_voxel", "x", "y", "z", "c"), &VoxelMap::_get_voxel_binding, DEFVAL(0)); ClassDB::bind_method(D_METHOD("get_voxel", "x", "y", "z", "c"), &VoxelMap::_get_voxel_binding, DEFVAL(0));

View File

@ -60,7 +60,7 @@ public:
void get_buffer_copy(Vector3i min_pos, VoxelBuffer &dst_buffer, unsigned int channels_mask = 1); 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. // 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<VoxelBuffer> buffer); VoxelBlock *set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> buffer);
struct NoAction { struct NoAction {
inline void operator()(VoxelBlock *block) {} inline void operator()(VoxelBlock *block) {}
@ -76,7 +76,7 @@ public:
ERR_FAIL_COND(block == NULL); ERR_FAIL_COND(block == NULL);
pre_delete(block); pre_delete(block);
memdelete(block); memdelete(block);
_blocks.erase(bpos); remove_block_internal(bpos);
} }
} }
@ -118,6 +118,8 @@ public:
void clear(); void clear();
int get_block_count() const;
template <typename Op_T> template <typename Op_T>
void for_all_blocks(Op_T op) { void for_all_blocks(Op_T op) {
const Vector3i *key = NULL; const Vector3i *key = NULL;
@ -132,6 +134,7 @@ public:
private: private:
void set_block(Vector3i bpos, VoxelBlock *block); void set_block(Vector3i bpos, VoxelBlock *block);
VoxelBlock *get_or_create_block_at_voxel_pos(Vector3i pos); VoxelBlock *get_or_create_block_at_voxel_pos(Vector3i pos);
void remove_block_internal(Vector3i bpos);
void set_block_size_pow2(unsigned int p); void set_block_size_pow2(unsigned int p);