Interesting optimization issue (fun!)

Godot Version

Godot Engine v4.7.stable.official

Question

Screenshot 2026-06-23 at 3.26.48 PM

Why does the region on the left appear to run at over 60fps according to the profiler but visually have framerate stutter? The stutter disappears when the frame time jumps to around 60fps. This happens each time after creating and loading a procgenned level, and takes less time to resolve when the level is smaller. This would suggest a relation to the generated meshes, used both for visuals and collision, but those are created fully during level generation. Currently on MacOS and Compatibility.

You using CSGs?

No, levels are procedurally generated using surfacetools on arraymeshes.

There’s the visual profiler too. Check that.

Already did, it shows a similar pattern.

You’ll need to provide more context.

Wasn’t there this thing that shaders in compatibility take some initial time to load into some cache or somesuch?

Used to be especially slow initially on web builds back in 4.3 times. :person_shrugging:

I don’t think it’s the shaders, which are quite small and already functioning during the stuttering period.

Here’s the mesh creation code:

	for voxtype in Global.Vox.values():
		if voxtype in surfacetools:
			var st = surfacetools[voxtype]
			var meshinstance = MeshInstance3D.new()
			meshinstance.mesh = st.commit()
			if meshinstance.mesh.get_surface_count() != 0:
				if voxtype != Global.Vox.PILLARVINE:
					meshinstance.create_trimesh_collision()
				add_child(meshinstance)

And a game screenshot:

Okay! The sadly, I’m out of my depth already! :pensive_face:

Wouldn’t it have been nice though if it were this. Maybe you should provide more context:

Edit: ah, I see you actually did provide a little more (my bad)

The level-loading function:

func loadchamber():
	$AmbientLoadingShader.process_mode = Node.PROCESS_MODE_ALWAYS
	$AmbientLoadingShader.visible = true
	$AmbientLoadingShader.material.set_shader_parameter("displacement", randf())
	if Global.chamber != null:
		remove_child(Global.chamber)
	for i in 2:
		await get_tree().process_frame
	var chamber = chamberscene.instantiate()
	chamber.create(dice)
	add_child(chamber)
	$AmbientLoadingShader.process_mode = Node.PROCESS_MODE_DISABLED
	$AmbientLoadingShader.visible = false

The create function:

func create(dice: RandomNumberGenerator):
	print("starting!")
	Global.chamber = self
	self.dice = dice
	terragen()
	print("terra genned!")
	placefeatures()
	print("features placed!")
	createmeshes()
	print("meshes created!")
	welcomeplayer()
	print("player welcomed!")
	anomalize()
	print("anomalies materialized!")
	print("done!")
	self.starttime = Time.get_ticks_msec()

How is this “stutter” looking? Can you post a video?

Also post the surface tool code. Is it running in a thread?

Some of the surfacetool code is posted above; it is not running in a thread, and is finished by the time the level is loaded in. Here’s a video.

That’s not actual surface tool code that does the work.

Here’s the entire function:

func createmeshes():
	for voxtype in Global.Vox.values():
		if voxtype != Global.Vox.AIR:
			var st = SurfaceTool.new()
			st.begin(Mesh.PRIMITIVE_TRIANGLES)
			st.set_material(Global.materials[voxtype])
			surfacetools[voxtype] = st
	for x in size:
		for y in size:
			for z in size:
				var point = Vector3(x, y, z)
				var voxtype = voxmap[point]
				if voxtype != Global.Vox.AIR:
					var st = surfacetools[voxtype]
					var meshtype = Global.meshtypes[voxtype]
					if meshtype == Global.MeshType.CUBE:
						for disp in cardinals:
							var npoint = point + disp
							if npoint in voxmap and (voxmap[npoint] == Global.Vox.AIR or Global.meshtypes[voxmap[npoint]] != Global.MeshType.CUBE):
								st.append_from(face, 0, Transform3D(bases[disp], point + disp / 2. + Vector3.ONE / 2.))
					if meshtype == Global.MeshType.PLANT:
						for basis in plantbases:
							st.append_from(face, 0, Transform3D(basis, point + Vector3.ONE / 2.))
	for voxtype in Global.Vox.values():
		if voxtype in surfacetools:
			var st = surfacetools[voxtype]
			var meshinstance = MeshInstance3D.new()
			meshinstance.mesh = st.commit()
			if meshinstance.mesh.get_surface_count() != 0:
				if voxtype != Global.Vox.PILLARVINE:
					meshinstance.create_trimesh_collision()
				add_child(meshinstance)

At which timestamps in the video is the stutter happening? I didn’t notice it.

It’s not very visible in the video, but the framerate wavers between smooth and ~10fps the entire time up until the point when the profiler starts looking different.