## 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