extends Node3D var rd: RenderingDevice var shader_file1: Resource var shader_file2: Resource var shader_spirv1: RDShaderSPIRV var shader_spirv2: RDShaderSPIRV var shader_pass1: RID var shader_pass2: RID var start_time := Time.get_ticks_msec() / 1000.0 var color = Color.CORAL @export var iout_surface_points = [] var player_edits = PackedFloat32Array([0.0, 0.0, 0.0, 1.0]) var pipeline1 var pipeline2 var buffer var surface_buffer var normal_buffer var idx_buffer var counter_buffer var chunk_position_buffer var params_buffer var player_edits_buffer func create_device(world_size: int): signals_service.add_matter.connect(_on_player_add_matter) rd = RenderingServer.create_local_rendering_device() # 1. Load Shaders shader_file1 = load("res://SurfaceNetsWorld/compute_surface_points.glsl") shader_pass1 = rd.shader_create_from_spirv(shader_file1.get_spirv()) shader_file2 = load("res://SurfaceNetsWorld/sdf_mesh_generation.glsl") shader_pass2 = rd.shader_create_from_spirv(shader_file2.get_spirv()) # 2. Create Pipelines pipeline1 = rd.compute_pipeline_create(shader_pass1) pipeline2 = rd.compute_pipeline_create(shader_pass2) # 3. Pre-allocate Buffers (assuming world_size is constant) # add 2 to look at adjacent voxels for chunk boundaries var total = (world_size + 2) ** 3 # We create them once with empty/zero data of the correct size buffer = rd.storage_buffer_create(total * 4) surface_buffer = rd.storage_buffer_create(total * 3 * 4) normal_buffer = rd.storage_buffer_create(total * 3 * 4) idx_buffer = rd.storage_buffer_create(total * 6 * 4) counter_buffer = rd.storage_buffer_create(4) chunk_position_buffer = rd.storage_buffer_create(16) # vec4 params_buffer = rd.uniform_buffer_create(16) # world_size, threshold, time, etc player_edits_buffer = rd.storage_buffer_create(256) print("device created") func compute_mesh(world_size: int, threshold: float, chunk_pos: Vector3) -> ArrayMesh: await Engine.get_main_loop().process_frame world_size += 2 # 1. Update existing buffers with NEW data for THIS chunk var chunk_pos_data = PackedFloat32Array([chunk_pos.x, chunk_pos.y, chunk_pos.z, 0.0]).to_byte_array() rd.buffer_update(chunk_position_buffer, 0, chunk_pos_data.size(), chunk_pos_data) # Reset the counter to 0 for the new chunk var counter_reset = PackedInt32Array([0]).to_byte_array() rd.buffer_update(counter_buffer, 0, 4, counter_reset) # Chunk position (offset) var chunk_position_peer := PackedFloat32Array() chunk_position_peer.resize(4) chunk_position_peer.set(0, chunk_pos.x) chunk_position_peer.set(1, chunk_pos.y) chunk_position_peer.set(2, chunk_pos.z) chunk_position_peer.set(3, 0.0) var chunk_position_bytes := chunk_position_peer.to_byte_array() rd.buffer_update(chunk_position_buffer, 0, chunk_position_bytes.size(), chunk_position_bytes) var player_edits_bytes = player_edits.to_byte_array() rd.buffer_update(player_edits_buffer, 0, player_edits_bytes.size(), player_edits_bytes) var u_time := Time.get_ticks_msec() / 1000.0 - start_time var peer := StreamPeerBuffer.new() peer.put_32(world_size) peer.put_float(threshold) peer.put_float(u_time) peer.put_32(0) var uniform_params_bytes := peer.data_array rd.buffer_update(params_buffer, 0, uniform_params_bytes.size(), uniform_params_bytes) # Create a uniform to assign the buffer to the rendering device var uniform_buf := RDUniform.new() uniform_buf.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER uniform_buf.binding = 0 # this needs to match the "binding" in our shader file uniform_buf.add_id(buffer) # Create a uniform to assign the buffer to the rendering device var surface_uniform_buf := RDUniform.new() surface_uniform_buf.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER surface_uniform_buf.binding = 2 surface_uniform_buf.add_id(surface_buffer) var normal_uniform = RDUniform.new() normal_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER normal_uniform.binding = 3 normal_uniform.add_id(normal_buffer) var idx_uniform := RDUniform.new() idx_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER idx_uniform.binding = 4 idx_uniform.add_id(idx_buffer) var counter_uniform := RDUniform.new() counter_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER counter_uniform.binding = 5 counter_uniform.add_id(counter_buffer) var chunk_position_uniform := RDUniform.new() chunk_position_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER chunk_position_uniform.binding = 6 chunk_position_uniform.add_id(chunk_position_buffer) var player_edits_uniform := RDUniform.new() player_edits_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER player_edits_uniform.binding = 7 player_edits_uniform.add_id(player_edits_buffer) var uniform_params := RDUniform.new() uniform_params.uniform_type = RenderingDevice.UNIFORM_TYPE_UNIFORM_BUFFER uniform_params.binding = 1 uniform_params.add_id(params_buffer) var uniform_set1 := rd.uniform_set_create([uniform_buf, uniform_params, surface_uniform_buf, normal_uniform, idx_uniform, counter_uniform, chunk_position_uniform, player_edits_uniform], shader_pass1, 0) # the last parameter (the 0) needs to match the "set" in our shader file var uniform_set2 := rd.uniform_set_create([uniform_buf, uniform_params, surface_uniform_buf, idx_uniform, counter_uniform], shader_pass2, 0) var dispatch_count = int(ceil(world_size / 4.0)) # 1. Dispatch PASS 1 (Calculate Points) pipeline1 = rd.compute_pipeline_create(shader_pass1) # Points only var compute_list = rd.compute_list_begin() rd.compute_list_bind_compute_pipeline(compute_list, pipeline1) rd.compute_list_bind_uniform_set(compute_list, uniform_set1, 0) rd.compute_list_dispatch(compute_list, dispatch_count, dispatch_count, dispatch_count) rd.compute_list_end() rd.compute_list_add_barrier(compute_list) # 2. Dispatch PASS 2 (Generate Indices) pipeline2 = rd.compute_pipeline_create(shader_pass2) # Indices only compute_list = rd.compute_list_begin() rd.compute_list_bind_compute_pipeline(compute_list, pipeline2) rd.compute_list_bind_uniform_set(compute_list, uniform_set2, 0) rd.compute_list_dispatch(compute_list, dispatch_count, dispatch_count, dispatch_count) rd.compute_list_end() # Submit to GPU and wait for sync rd.submit() rd.sync() # Read back the data from the buffer var out_verts = rd.buffer_get_data(surface_buffer).to_float32_array() var out_norms = rd.buffer_get_data(normal_buffer).to_float32_array() var out_indices = rd.buffer_get_data(idx_buffer).to_int32_array() var final_count = rd.buffer_get_data(counter_buffer).to_int32_array()[0] var out_surface_points = rd.buffer_get_data(surface_buffer).to_float32_array() # 5. Build the Mesh var mesh = ArrayMesh.new() var arrays = [] arrays.resize(Mesh.ARRAY_MAX) # Instead of blindly appending every voxel, we check if the voxel was "active" # Or, even better, map the original voxel indices to new packed indices var active_map = {} var packed_verts := PackedVector3Array() var packed_normals := PackedVector3Array() var packed_indices := PackedInt32Array() var offset = Vector3(world_size / 2.0, world_size / 2.0, world_size / 2.0) var final_indices = out_indices.slice(0, final_count) for old_idx in final_indices: var v_base = old_idx * 3 # 1. Skip if the shader marked this as an empty voxel if out_verts[v_base] < -0.5: continue if not active_map.has(old_idx): active_map[old_idx] = packed_verts.size() # 2. Re-center the vertex so the mesh isn't floating in the corner var pos = Vector3(out_verts[v_base], out_verts[v_base+1], out_verts[v_base+2]) - offset packed_verts.append(pos) packed_normals.append(Vector3(out_norms[v_base], out_norms[v_base+1], out_norms[v_base+2])) packed_indices.append(active_map[old_idx]) iout_surface_points = out_surface_points if packed_verts.size() > 0: arrays[Mesh.ARRAY_VERTEX] = packed_verts arrays[Mesh.ARRAY_NORMAL] = packed_normals arrays[Mesh.ARRAY_INDEX] = packed_indices mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) return mesh func build_sample_dict(world_size: int, flat_buffer: PackedFloat32Array) -> Dictionary: var dict := {} var total = world_size * world_size * world_size for idx in total: var voxel_x = idx % world_size var voxel_y = (idx / world_size) % world_size var voxel_z = idx / (world_size * world_size) var voxel_id = Vector3i(voxel_x, voxel_y, voxel_z) var distance = flat_buffer[idx] dict[voxel_id] = distance return dict func build_surface_dict(world_size: int, flat_buffer: PackedFloat32Array) -> Dictionary: var dict := {} var total = world_size * world_size * world_size for idx in total: var base = idx * 3 var x = flat_buffer[base] var y = flat_buffer[base + 1] var z = flat_buffer[base + 2] var voxel_x = idx % world_size var voxel_y = (idx / world_size) % world_size var voxel_z = idx / (world_size * world_size) var voxel_id = Vector3i(voxel_x, voxel_y, voxel_z) var surface_pos = Vector3(x, y, z) dict[voxel_id] = surface_pos return dict func _on_player_add_matter(pposition: Vector3, size: float) -> void: player_edits.append(pposition.x) player_edits.append(pposition.y) player_edits.append(pposition.z) player_edits.append(size) func _exit_tree(): # If the rendering device wasn't initialized, we have nothing to free if not rd: return # 1. Free Shader and Pipeline RIDs # Pipelines depend on shaders, so free them first if pipeline1.is_valid(): rd.free_rid(pipeline1) if pipeline2.is_valid(): rd.free_rid(pipeline2) if shader_pass1.is_valid(): rd.free_rid(shader_pass1) if shader_pass2.is_valid(): rd.free_rid(shader_pass2) # 2. Free Buffer RIDs # These are the actual memory allocations on the VRAM var buffers_to_free = [ buffer, surface_buffer, normal_buffer, idx_buffer, counter_buffer, chunk_position_buffer, params_buffer ] for b_rid in buffers_to_free: if b_rid.is_valid(): rd.free_rid(b_rid) # 3. Finalize the Rendering Device # This tells Godot we are done with this local device entirely rd.free() rd = null