feat: implemented multithreading and chunk lods

This commit is contained in:
Sara Gerretsen 2025-11-22 19:19:43 +01:00
parent 49b65a7ade
commit 1951b560ed
6 changed files with 256 additions and 53 deletions

View file

@ -23,31 +23,79 @@ void TerrainMeshGenerator::_bind_methods() {
BIND_HPROPERTY(Variant::OBJECT, chunk_scene, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
ADD_SIGNAL(MethodInfo(sig_primitives_changed));
ADD_SIGNAL(MethodInfo(sig_primitive_list_changed, PropertyInfo(Variant::ARRAY, "array", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:TerrainPrimitive", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE))));
ClassDB::bind_method(D_METHOD("generate_grid", "area", "out_mesh", "side_points"), &self_type::generate_grid);
ClassDB::bind_method(D_METHOD("push_task", "area", "out_mesh", "side_points"), &self_type::push_task);
}
void TerrainMeshGenerator::ready() {
this->thread.instantiate();
if (this->chunks.is_empty()) {
rebuild_chunks();
void TerrainMeshGenerator::enter_tree() {
this->thread.start(&TerrainMeshGenerator::background_generation_thread, this);
}
void TerrainMeshGenerator::process() {
if (this->output_lock.try_lock()) {
for (TerrainMeshTask &task : this->output_queue) {
task.mesh->clear_surfaces();
task.out_surface->commit(task.mesh);
task.callback.call();
}
this->output_queue.clear();
this->output_lock.unlock();
}
if (this->input_lock.try_lock()) {
set_process(!this->input_queue.is_empty());
this->input_lock.unlock();
}
}
void TerrainMeshGenerator::on_configuration_changed() {
emit_signal(sig_primitives_changed);
this->settings_lock.lock();
// copy main thread primitives buffer data to worker thread buffer
this->primitives.clear();
for (Variant var : this->primitives_buffer) {
Ref<TerrainPrimitive> primitive{ var };
if (primitive.is_valid()) {
this->primitives.push_back(primitive->duplicate(true));
}
}
this->settings_lock.unlock();
if (is_ready()) {
emit_signal(sig_primitive_list_changed, get_primitives());
emit_signal(sig_primitives_changed);
}
}
void TerrainMeshGenerator::_notification(int what) {
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
switch (what) {
default:
return;
case NOTIFICATION_ENTER_TREE:
set_process(true);
enter_tree();
return;
case NOTIFICATION_READY:
ready();
rebuild_chunks();
break;
case NOTIFICATION_PROCESS:
process();
return;
case NOTIFICATION_EXIT_TREE:
if (this->thread.is_started()) {
this->input_lock.lock();
this->end_thread = true;
this->input_lock.unlock();
this->work_signal.post();
this->thread.wait_to_finish();
}
return;
}
}
void TerrainMeshGenerator::rebuild_chunks() {
if (!is_ready()) {
return;
}
for (TerrainChunk *chunk : this->chunks) {
chunk->queue_free();
}
@ -71,6 +119,43 @@ void TerrainMeshGenerator::rebuild_chunks() {
}
}
void TerrainMeshGenerator::background_generation_thread(void *user) {
TerrainMeshGenerator *self{ static_cast<TerrainMeshGenerator *>(user) };
if (self == nullptr) {
return;
}
TerrainMeshTask task{};
for (;;) {
// await more work signal
self->work_signal.wait();
// pull task from input queue
self->input_lock.lock();
if (self->end_thread) {
self->input_lock.unlock();
break;
} else {
task = self->input_queue[0];
self->input_queue.remove_at(0);
self->input_lock.unlock();
// create surface
task.out_surface.instantiate();
// lock settings and generate grid
self->settings_lock.lock();
if (self->primitives.is_empty()) {
print_error("Terrain mesh generator thread has no primitive list");
} else {
self->generate_grid(task.area, task.out_surface, task.side_points);
}
self->settings_lock.unlock();
// push output onto output queue
self->output_lock.lock();
self->output_queue.push_back(task);
self->call_deferred("set_process", true);
self->output_lock.unlock();
}
}
}
Color TerrainMeshGenerator::color_at_height(float height) const {
float const mapped{ Math::remap(height, this->color_gradient_start_height, this->color_gradient_end_height, 0.f, 1.f) };
return this->vertex_color_gradient.is_valid()
@ -111,19 +196,17 @@ void face_from(Ref<SurfaceTool> surface, size_t tl, size_t side_points) {
}
}
void TerrainMeshGenerator::generate_grid(Rect2 area, Ref<ArrayMesh> mesh, size_t side_points) {
mesh->clear_surfaces();
surface->clear();
void TerrainMeshGenerator::generate_grid(Rect2 area, Ref<SurfaceTool> mesh, size_t side_points) {
Vector2 point_distance{ area.size / (float)side_points };
++side_points;
Vector2 at{ area.position };
surface->begin(Mesh::PRIMITIVE_TRIANGLES);
mesh->begin(Mesh::PRIMITIVE_TRIANGLES);
for (size_t xpoint{ 0 }; xpoint < side_points; ++xpoint) {
for (size_t ypoint{ 0 }; ypoint < side_points; ++ypoint) {
float const height{ evaluate_point(at) };
surface->set_color(color_at_height(height));
surface->set_uv({ at / area.size });
surface->add_vertex({ at.x - area.position.x, height, at.y - area.position.y });
mesh->set_color(color_at_height(height));
mesh->set_uv({ at / area.size });
mesh->add_vertex({ at.x - area.position.x, height, at.y - area.position.y });
at.y += point_distance.y;
}
at.y = area.position.y;
@ -131,41 +214,68 @@ void TerrainMeshGenerator::generate_grid(Rect2 area, Ref<ArrayMesh> mesh, size_t
}
for (size_t xface{ 0 }; xface < side_points - 1; ++xface) {
for (size_t yface{ 0 }; yface < side_points - 1; ++yface) {
face_from(surface, xface + yface * (size_t)side_points, side_points);
face_from(mesh, xface + yface * (size_t)side_points, side_points);
}
}
surface->generate_normals();
surface->generate_tangents();
surface->commit(mesh);
mesh->generate_normals();
mesh->generate_tangents();
}
void TerrainMeshGenerator::push_task(Rect2 area, Ref<ArrayMesh> mesh, size_t points, Callable callback) {
this->input_lock.lock();
for (TerrainMeshTask &task : this->input_queue) {
if (task.mesh == mesh) {
task.area = area;
task.side_points = points;
task.callback = callback;
this->input_lock.unlock();
return;
}
}
TerrainMeshTask task{
.mesh = mesh,
.area = area,
.side_points = points,
.out_surface = nullptr,
.callback = callback
};
for (size_t i{ 0 }; i < this->input_queue.size(); ++i) {
if (this->input_queue[i].side_points >= points) {
this->input_queue.insert(i, task);
this->work_signal.post();
this->input_lock.unlock();
return;
}
}
this->input_queue.push_back(task);
this->work_signal.post();
this->input_lock.unlock();
}
void TerrainMeshGenerator::set_primitives(Array primitives) {
for (Ref<TerrainPrimitive> primitive : this->primitives) {
// synchronise primitives
for (Variant var : this->primitives_buffer) {
Ref<TerrainPrimitive> primitive{ var };
if (primitive.is_valid()) {
primitive->disconnect_changed(this->generation_changed);
}
}
this->primitives.clear();
for (Variant var : primitives) {
this->primitives_buffer = primitives;
for (Variant var : this->primitives_buffer) {
Ref<TerrainPrimitive> primitive{ var };
this->primitives.push_back(primitive);
if (primitive.is_valid()) {
primitive->connect_changed(this->generation_changed);
}
}
emit_signal(sig_primitive_list_changed, get_primitives());
on_configuration_changed();
}
Array TerrainMeshGenerator::get_primitives() const {
Array a;
for (Ref<TerrainPrimitive> primitive : this->primitives) {
a.push_back(primitive);
}
return a;
return this->primitives_buffer;
}
void TerrainMeshGenerator::set_vertex_color_gradient(Ref<Gradient> gradient) {
this->settings_lock.lock();
if (this->vertex_color_gradient.is_valid()) {
this->vertex_color_gradient->disconnect_changed(this->generation_changed);
}
@ -173,6 +283,7 @@ void TerrainMeshGenerator::set_vertex_color_gradient(Ref<Gradient> gradient) {
if (gradient.is_valid()) {
this->vertex_color_gradient->connect_changed(this->generation_changed);
}
this->settings_lock.unlock();
on_configuration_changed();
}
@ -181,7 +292,9 @@ Ref<Gradient> TerrainMeshGenerator::get_vertex_color_gradient() const {
}
void TerrainMeshGenerator::set_color_gradient_start_height(float value) {
this->settings_lock.lock();
this->color_gradient_start_height = value;
this->settings_lock.unlock();
on_configuration_changed();
}
@ -190,7 +303,9 @@ float TerrainMeshGenerator::get_color_gradient_start_height() const {
}
void TerrainMeshGenerator::set_color_gradient_end_height(float value) {
this->settings_lock.lock();
this->color_gradient_end_height = value;
this->settings_lock.unlock();
on_configuration_changed();
}
@ -199,10 +314,12 @@ float TerrainMeshGenerator::get_color_gradient_end_height() const {
}
void TerrainMeshGenerator::set_chunk_count(int num) {
this->settings_lock.lock();
if (num != this->chunks_per_side) {
this->chunks_per_side = num;
rebuild_chunks();
}
this->settings_lock.unlock();
}
int TerrainMeshGenerator::get_chunk_count() const {
@ -210,9 +327,11 @@ int TerrainMeshGenerator::get_chunk_count() const {
}
void TerrainMeshGenerator::set_chunk_scene(Ref<PackedScene> scene) {
this->settings_lock.lock();
if (!scene.is_valid() || scene->get_state()->get_node_type(0) == TerrainChunk::get_class_static()) {
this->chunk_scene = scene;
}
this->settings_lock.unlock();
}
Ref<PackedScene> TerrainMeshGenerator::get_chunk_scene() const {