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.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<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(
std::vector<Vector3i> &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<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() {
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<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
{
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<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();
}

View File

@ -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 <typename A>
void for_all_blocks(A &action) {
@ -111,10 +111,6 @@ private:
Map<Vector3i, BlockState> block_states;
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
Vector3i last_viewer_block_pos;
int last_view_distance_blocks = 0;

View File

@ -138,8 +138,13 @@ void VoxelMap::set_block(Vector3i bpos, VoxelBlock *block) {
_blocks.set(bpos, block);
}
void VoxelMap::set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> 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<VoxelBuffer> 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<VoxelBuffer> 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));

View File

@ -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<VoxelBuffer> buffer);
VoxelBlock *set_block_buffer(Vector3i bpos, Ref<VoxelBuffer> 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 <typename Op_T>
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);