OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / demo / volume_bake_gate.gd

91 lines · gd ref:
## A proc-nodes ProcVolume, meshed ON THE GPU by dc-terrain's own pipeline.
##
## ⚠ NOT HEADLESS. The dummy rendering driver has no RenderingDevice, so there
## is no GPU thread and this would report failure for the wrong reason. Run with
## a real driver.
##
## ⚠ IT NEEDS demo/bin/linux/shaders. DCGpuMeshPipeline loads its .glsl files
## relative to the library, and dc-terrain/bin/linux has a `shaders` symlink
## while the demo project's did not -- so the mesh pipeline failed to open
## edge_classify.glsl, fell back to the CPU, and is_available() stayed false.
## demo/bin is gitignored, so that symlink is NOT in the repo:
##
##     ln -s ../../../shaders demo/bin/linux/shaders
##
## ⚠ AND IT WAITS. GPU bring-up is not instant -- hyper-games #58 measured 30.2s
## for a starved second device, which is exactly the mistake this entry point
## avoids by borrowing the terrain's one device. So poll rather than assume.
extends SceneTree

var _t: DCTerrain = null
var _glsl := ""
var _frames := 0
var _done := false
# ⚠ A TIME BUDGET, NOT A FRAME COUNT. The first version waited 601 FRAMES, and
# an uncapped SceneTree burns those in a fraction of a second -- the GPU became
# ready on the last line of the log, after every one of them had already failed.
# Bring-up is seconds of wall clock; frames are not a unit of time here.
var _t0 := 0.0
const TIMEOUT_S := 60.0

func _build_volume_glsl() -> String:
	var g := ProcNodeGraph.new()
	var s1 := g.add_node(SphereSdfNode.new())
	var vtm := g.add_node(VolumeToMeshNode.new())
	g.connect_sockets(s1, 0, vtm, 0)
	var geo = g.evaluate_socket(vtm, 0)
	if geo == null:
		return ""
	var vol = geo.get_volume()
	return "" if vol == null else vol.get_glsl_source()

func _process(_d: float) -> bool:
	if _t == null:
		_glsl = _build_volume_glsl()
		print("  volume GLSL: %d chars" % _glsl.length())
		_t = DCTerrain.new()
		_t.use_gpu = true
		root.add_child(_t)
		_t0 = Time.get_ticks_msec()
		return false
	_frames += 1
	if _done:
		return true
	# 3 units across, centred on the origin: the unit sphere sits inside.
	var m: ArrayMesh = _t.bake_volume_mesh(_glsl, Vector3(-1.5, -1.5, -1.5), 3.0 / 16.0, 16)
	if m != null and m.get_surface_count() > 0:
		var arrays := m.surface_get_arrays(0)
		var verts: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
		var idx: PackedInt32Array = arrays[Mesh.ARRAY_INDEX]
		var worst := 0.0
		var lo := Vector3(1e9, 1e9, 1e9)
		var hi := Vector3(-1e9, -1e9, -1e9)
		for v in verts:
			worst = max(worst, abs(v.length() - 1.0))
			lo = Vector3(min(lo.x, v.x), min(lo.y, v.y), min(lo.z, v.z))
			hi = Vector3(max(hi.x, v.x), max(hi.y, v.y), max(hi.z, v.z))
		print("  AABB: %v .. %v" % [lo, hi])
		print("  first 3: %v %v %v" % [verts[0], verts[1], verts[2]])
		# If the mesh comes back GRID-LOCAL, re-centring on the grid middle is
		# what makes it a unit sphere again. Measure both readings rather than
		# assume which one the pipeline returns.
		var worst_local := 0.0
		for v in verts:
			worst_local = max(worst_local, abs((v - Vector3(1.5, 1.5, 1.5)).length() - 1.0))
		print("  worst |r-1| world-reading=%.4f  grid-local-reading=%.4f" % [worst, worst_local])
		worst = min(worst, worst_local)
		print("  BAKED: %d verts, %d tris, worst |r-1| = %.4f  (frame %d)"
				% [verts.size(), idx.size() / 3, worst, _frames])
		print("  after %.1fs of GPU bring-up" % ((Time.get_ticks_msec() - _t0) / 1000.0))
		print("  RESULT: %s" % ("PASS" if verts.size() > 0 and idx.size() > 0 and worst < 0.3 else "FAIL"))
		_done = true
		quit(0)
		return true
	var elapsed := (Time.get_ticks_msec() - _t0) / 1000.0
	if elapsed > TIMEOUT_S:
		print("  RESULT: FAIL -- no mesh after %.1fs / %d frames (GPU never became available?)"
				% [elapsed, _frames])
		quit(1)
		return true
	return false