Skip to content

Commit

Permalink
Create collision shapes in the meshing thread instead of the main thr…
Browse files Browse the repository at this point in the history
…ead. UNTESTED

I did not test this because PhysicsServer doesn't look thread-safe at all.
See #124
And godotengine/godot-proposals#483
  • Loading branch information
Zylann committed Feb 15, 2020
1 parent 5925196 commit ce90ba8
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 94 deletions.
56 changes: 3 additions & 53 deletions terrain/voxel_block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,6 @@
#include "../util/zprofiling.h"
#include "../voxel_string_names.h"
#include <scene/3d/spatial.h>
#include <scene/resources/concave_polygon_shape.h>

// Faster version of Mesh::create_trimesh_shape()
// See https://github.com/Zylann/godot_voxel/issues/54
//
static Ref<ConcavePolygonShape> create_concave_polygon_shape(Array surface_arrays) {

PoolVector<Vector3> positions = surface_arrays[Mesh::ARRAY_VERTEX];
PoolVector<int> indices = surface_arrays[Mesh::ARRAY_INDEX];

ERR_FAIL_COND_V(positions.size() < 3, Ref<ConcavePolygonShape>());
ERR_FAIL_COND_V(indices.size() < 3, Ref<ConcavePolygonShape>());
ERR_FAIL_COND_V(indices.size() % 3 != 0, Ref<ConcavePolygonShape>());

int face_points_count = indices.size();

PoolVector<Vector3> face_points;
face_points.resize(face_points_count);

{
PoolVector<Vector3>::Write w = face_points.write();
PoolVector<int>::Read index_r = indices.read();
PoolVector<Vector3>::Read position_r = positions.read();

for (int i = 0; i < face_points_count; ++i) {
w[i] = position_r[index_r[i]];
}
}

Ref<ConcavePolygonShape> shape = memnew(ConcavePolygonShape);
shape->set_faces(face_points);
return shape;
}

// Helper
VoxelBlock *VoxelBlock::create(Vector3i bpos, Ref<VoxelBuffer> buffer, unsigned int size, unsigned int p_lod_index) {
Expand Down Expand Up @@ -85,11 +52,7 @@ void VoxelBlock::set_world(Ref<World> p_world) {
}
}

void VoxelBlock::set_mesh(Ref<Mesh> mesh, Spatial *node, bool generate_collision, Array surface_arrays, bool debug_collision) {
// TODO Don't add mesh instance to the world if it's not visible.
// I suspect Godot is trying to include invisible mesh instances into the culling process,
// which is killing performance when LOD is used (i.e many meshes are in pool but hidden)
// This needs investigation.
void VoxelBlock::set_mesh(Ref<Mesh> mesh, Spatial *node, Ref<Shape> collision_shape, bool collision_enabled, bool debug_collision) {

if (mesh.is_valid()) {

Expand All @@ -115,9 +78,7 @@ void VoxelBlock::set_mesh(Ref<Mesh> mesh, Spatial *node, bool generate_collision
_mesh_instance.set_material_override(_debug_material);
#endif

if (generate_collision) {
Ref<Shape> shape = create_concave_polygon_shape(surface_arrays);

if (collision_enabled) {
if (!_static_body.is_valid()) {
_static_body.create();
_static_body.set_world(*_world);
Expand All @@ -126,7 +87,7 @@ void VoxelBlock::set_mesh(Ref<Mesh> mesh, Spatial *node, bool generate_collision
} else {
_static_body.remove_shape(0);
}
_static_body.add_shape(shape);
_static_body.add_shape(collision_shape);
_static_body.set_debug(debug_collision, *_world);
_static_body.set_shape_enabled(0, _visible);
}
Expand Down Expand Up @@ -226,17 +187,6 @@ void VoxelBlock::set_shader_material(Ref<ShaderMaterial> material) {
_shader_material = material;
}

//void VoxelBlock::set_transition_bit(uint8_t side, bool value) {
// CRASH_COND(side >= Cube::SIDE_COUNT);
// uint32_t m = _transition_mask;
// if (value) {
// m |= (1 << side);
// } else {
// m &= ~(1 << side);
// }
// set_transition_mask(m);
//}

void VoxelBlock::set_transition_mask(uint8_t m) {
CRASH_COND(m >= (1 << Cube::SIDE_COUNT));
uint8_t diff = _transition_mask ^ m;
Expand Down
6 changes: 4 additions & 2 deletions terrain/voxel_block.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class VoxelBlock {
// Visuals and physics

void set_world(Ref<World> p_world);
void set_mesh(Ref<Mesh> mesh, Spatial *node, bool generate_collision, Array surface_arrays, bool debug_collision);
void set_mesh(Ref<Mesh> mesh, Spatial *node, Ref<Shape> collision_shape, bool collision_enabled, bool debug_collision);
void set_transition_mesh(Ref<Mesh> mesh, int side);
bool has_mesh() const;

Expand All @@ -50,7 +50,6 @@ class VoxelBlock {
void set_parent_visible(bool parent_visible);

void set_transition_mask(uint8_t m);
//void set_transition_bit(uint8_t side, bool value);
inline uint8_t get_transition_mask() const { return _transition_mask; }

// Voxel data
Expand All @@ -68,6 +67,9 @@ class VoxelBlock {
inline bool _is_transition_visible(int side) const { return _transition_mask & (1 << side); }

inline void set_mesh_instance_visible(DirectMeshInstance &mi, bool visible) {
// Right now, invisible objects still take space in Godot renderer's culling data structure,
// which slows down culling by a very significant amount.
// So instead, we remove them from world completely.
if (visible) {
mi.set_world(*_world);
} else {
Expand Down
23 changes: 7 additions & 16 deletions terrain/voxel_lod_terrain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const uint32_t MAIN_THREAD_MESHING_BUDGET_MS = 8;
namespace {

Ref<ArrayMesh> build_mesh(const Vector<Array> surfaces, Mesh::PrimitiveType primitive, int compression_flags,
Ref<Material> material, Array *collidable_surface) {
Ref<Material> material) {

Ref<ArrayMesh> mesh;
mesh.instance();
Expand All @@ -32,10 +32,6 @@ Ref<ArrayMesh> build_mesh(const Vector<Array> surfaces, Mesh::PrimitiveType prim
continue;
}

if (collidable_surface != nullptr && collidable_surface->empty()) {
*collidable_surface = surface;
}

mesh->add_surface_from_arrays(primitive, surface, Array(), compression_flags);
mesh->surface_set_material(surface_index, material);
// No multi-material supported yet
Expand Down Expand Up @@ -259,6 +255,7 @@ void VoxelLodTerrain::start_updater() {
// TODO Thread-safe way to change those parameters
VoxelMeshUpdater::MeshingParams params;
params.smooth_surface = true;
params.collision_lod_count = _generate_collisions ? _collision_lod_count : 0;

_block_updater = memnew(VoxelMeshUpdater(2, params));
}
Expand Down Expand Up @@ -1184,22 +1181,16 @@ void VoxelLodTerrain::_process() {
block->set_mesh_state(VoxelBlock::MESH_UP_TO_DATE);
}

const VoxelMesher::Output mesh_data = ob.data.smooth_surfaces;
const VoxelMeshUpdater::OutputBlockData &data = ob.data;
const VoxelMesher::Output mesh_data = data.smooth_surfaces;

// TODO Allow multiple collision surfaces
Array collidable_surface;
Ref<ArrayMesh> mesh = build_mesh(
mesh_data.surfaces,
mesh_data.primitive_type,
mesh_data.compression_flags,
_material, &collidable_surface);

bool has_collision = _generate_collisions;
if (has_collision && _collision_lod_count != -1) {
has_collision = ob.lod < _collision_lod_count;
}
_material);

block->set_mesh(mesh, this, has_collision, collidable_surface, get_tree()->is_debugging_collisions_hint());
block->set_mesh(mesh, this, data.collision_shape, _generate_collisions, get_tree()->is_debugging_collisions_hint());

{
VOXEL_PROFILE_SCOPE(profile_process_receive_mesh_updates_block_update_transitions);
Expand All @@ -1209,7 +1200,7 @@ void VoxelLodTerrain::_process() {
mesh_data.transition_surfaces[dir],
mesh_data.primitive_type,
mesh_data.compression_flags,
_material, nullptr);
_material);

block->set_transition_mesh(transition_mesh, dir);
}
Expand Down
70 changes: 62 additions & 8 deletions terrain/voxel_mesh_updater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,39 @@
#include "../util/utility.h"
#include "voxel_lod_terrain.h"
#include <core/os/os.h>
#include <scene/resources/concave_polygon_shape.h>

// Faster version of Mesh::create_trimesh_shape()
// See https://github.com/Zylann/godot_voxel/issues/54
//
static Ref<ConcavePolygonShape> create_concave_polygon_shape(Array surface_arrays) {

PoolVector<Vector3> positions = surface_arrays[Mesh::ARRAY_VERTEX];
PoolVector<int> indices = surface_arrays[Mesh::ARRAY_INDEX];

ERR_FAIL_COND_V(positions.size() < 3, Ref<ConcavePolygonShape>());
ERR_FAIL_COND_V(indices.size() < 3, Ref<ConcavePolygonShape>());
ERR_FAIL_COND_V(indices.size() % 3 != 0, Ref<ConcavePolygonShape>());

int face_points_count = indices.size();

PoolVector<Vector3> face_points;
face_points.resize(face_points_count);

{
PoolVector<Vector3>::Write w = face_points.write();
PoolVector<int>::Read index_r = indices.read();
PoolVector<Vector3>::Read position_r = positions.read();

for (int i = 0; i < face_points_count; ++i) {
w[i] = position_r[index_r[i]];
}
}

Ref<ConcavePolygonShape> shape = memnew(ConcavePolygonShape);
shape->set_faces(face_points);
return shape;
}

VoxelMeshUpdater::VoxelMeshUpdater(unsigned int thread_count, MeshingParams params) {

Expand All @@ -14,6 +47,8 @@ VoxelMeshUpdater::VoxelMeshUpdater(unsigned int thread_count, MeshingParams para
_minimum_padding = 0;
_maximum_padding = 0;

_collision_lod_count = params.collision_lod_count;

if (params.library.is_valid()) {
blocky_mesher.instance();
blocky_mesher->set_library(params.library);
Expand All @@ -30,6 +65,7 @@ VoxelMeshUpdater::VoxelMeshUpdater(unsigned int thread_count, MeshingParams para
}

FixedArray<Mgr::BlockProcessingFunc, VoxelConstants::MAX_LOD> processors;
const int collision_lod_count = _collision_lod_count;

for (unsigned int i = 0; i < thread_count; ++i) {

Expand All @@ -44,8 +80,8 @@ VoxelMeshUpdater::VoxelMeshUpdater(unsigned int thread_count, MeshingParams para
}
}

processors[i] = [this, blocky_mesher, smooth_mesher](const ArraySlice<InputBlock> inputs, ArraySlice<OutputBlock> outputs, Mgr::ProcessorStats &_) {
this->process_blocks_thread_func(inputs, outputs, blocky_mesher, smooth_mesher);
processors[i] = [blocky_mesher, smooth_mesher, collision_lod_count](const ArraySlice<InputBlock> inputs, ArraySlice<OutputBlock> outputs, Mgr::ProcessorStats &_) {
process_blocks_thread_func(inputs, outputs, blocky_mesher, smooth_mesher, collision_lod_count);
};
}

Expand All @@ -63,10 +99,27 @@ void VoxelMeshUpdater::process_blocks_thread_func(
const ArraySlice<InputBlock> inputs,
ArraySlice<OutputBlock> outputs,
Ref<VoxelMesher> blocky_mesher,
Ref<VoxelMesher> smooth_mesher) {
Ref<VoxelMesher> smooth_mesher,
int collision_lod_count) {

CRASH_COND(inputs.size() != outputs.size());

struct L {
static void build(Ref<VoxelMesher> mesher, VoxelMesher::Output &output, Ref<Shape> *shape, const VoxelMesher::Input &input) {
if (mesher.is_valid()) {
mesher->build(output, input);
// TODO Decide which surfaces should be collidable, currently defaults to 0
// TODO Support multiple shapes
if (shape != nullptr && output.surfaces.size() > 0 && shape->is_null()) {
Array surface = output.surfaces[0];
if (is_surface_triangulated(surface)) {
*shape = create_concave_polygon_shape(surface);
}
}
}
}
};

for (unsigned int i = 0; i < inputs.size(); ++i) {

const InputBlock &ib = inputs[i];
Expand All @@ -77,11 +130,12 @@ void VoxelMeshUpdater::process_blocks_thread_func(

VoxelMesher::Input input = { **block.voxels, ib.lod };

if (blocky_mesher.is_valid()) {
blocky_mesher->build(output.blocky_surfaces, input);
}
if (smooth_mesher.is_valid()) {
smooth_mesher->build(output.smooth_surfaces, input);
Ref<Shape> *shape = nullptr;
if (ib.lod < collision_lod_count) {
shape = &output.collision_shape;
}

L::build(blocky_mesher, output.blocky_surfaces, shape, input);
L::build(smooth_mesher, output.smooth_surfaces, shape, input);
}
}
9 changes: 7 additions & 2 deletions terrain/voxel_mesh_updater.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ class VoxelMeshUpdater {
struct OutputBlockData {
VoxelMesher::Output blocky_surfaces;
VoxelMesher::Output smooth_surfaces;
// TODO Support multiple collision shapes
Ref<Shape> collision_shape;
};

struct MeshingParams {
Ref<VoxelLibrary> library;
bool baked_ao = true;
float baked_ao_darkness = 0.75;
bool smooth_surface = false;
int collision_lod_count = 1;
};

typedef VoxelBlockThreadManager<InputBlockData, OutputBlockData> Mgr;
Expand All @@ -45,14 +48,16 @@ class VoxelMeshUpdater {
int get_maximum_padding() const { return _maximum_padding; }

private:
void process_blocks_thread_func(const ArraySlice<InputBlock> inputs,
static void process_blocks_thread_func(const ArraySlice<InputBlock> inputs,
ArraySlice<OutputBlock> outputs,
Ref<VoxelMesher> blocky_mesher,
Ref<VoxelMesher> smooth_mesher);
Ref<VoxelMesher> smooth_mesher,
int collision_lod_count);

Mgr *_mgr = nullptr;
int _minimum_padding = 0;
int _maximum_padding = 0;
int _collision_lod_count = 0;
};

#endif // VOXEL_MESH_UPDATER_H
16 changes: 3 additions & 13 deletions terrain/voxel_terrain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ void VoxelTerrain::start_updater() {
VoxelMeshUpdater::MeshingParams params;
params.smooth_surface = _smooth_meshing_enabled;
params.library = _library;
params.collision_lod_count = _generate_collisions ? 1 : 0;

_block_updater = memnew(VoxelMeshUpdater(1, params));
}
Expand Down Expand Up @@ -911,7 +912,7 @@ void VoxelTerrain::_process() {
CRASH_COND(block->get_mesh_state() != VoxelBlock::MESH_UPDATE_NOT_SENT);

// The block contains empty voxels
block->set_mesh(Ref<Mesh>(), this, _generate_collisions, Array(), get_tree()->is_debugging_collisions_hint());
block->set_mesh(Ref<Mesh>(), this, Ref<Shape>(), _generate_collisions, get_tree()->is_debugging_collisions_hint());
block->set_mesh_state(VoxelBlock::MESH_UP_TO_DATE);

// Optional, but I guess it might spare some memory
Expand Down Expand Up @@ -996,9 +997,6 @@ void VoxelTerrain::_process() {
Ref<ArrayMesh> mesh;
mesh.instance();

// TODO Allow multiple collision surfaces
Array collidable_surface;

int surface_index = 0;
const VoxelMeshUpdater::OutputBlockData &data = ob.data;
for (int i = 0; i < data.blocky_surfaces.surfaces.size(); ++i) {
Expand All @@ -1013,10 +1011,6 @@ void VoxelTerrain::_process() {
continue;
}

if (collidable_surface.empty()) {
collidable_surface = surface;
}

mesh->add_surface_from_arrays(data.blocky_surfaces.primitive_type, surface, Array(), data.blocky_surfaces.compression_flags);
mesh->surface_set_material(surface_index, _materials[i]);
++surface_index;
Expand All @@ -1034,10 +1028,6 @@ void VoxelTerrain::_process() {
continue;
}

if (collidable_surface.empty()) {
collidable_surface = surface;
}

mesh->add_surface_from_arrays(data.smooth_surfaces.primitive_type, surface, Array(), data.smooth_surfaces.compression_flags);
mesh->surface_set_material(surface_index, _materials[i]);
++surface_index;
Expand All @@ -1047,7 +1037,7 @@ void VoxelTerrain::_process() {
mesh = Ref<Mesh>();
}

block->set_mesh(mesh, this, _generate_collisions, collidable_surface, get_tree()->is_debugging_collisions_hint());
block->set_mesh(mesh, this, data.collision_shape, _generate_collisions, get_tree()->is_debugging_collisions_hint());
block->set_parent_visible(is_visible());
}

Expand Down

0 comments on commit ce90ba8

Please sign in to comment.