extends SceneTree
## How long does the spliced material cost to actually RENDER -- not just parse?
##
## DISPLAY=:0 godot --path . --rendering-driver vulkan \
## --script res://scenes/test/bridged_density_render_cost.gd -- --taps 6
##
## WHY THIS EXISTS. bridged_density_splice_gate proves the text COMPILES, and
## measured 3.7 s for that on a real device. Neither number covers what the game
## actually does: putting the material on a visible surface, where Godot
## compiles a pipeline per render pass (opaque, shadow, depth) and each one is a
## separate specialization of a 196 KB shader. That cost lands as a FREEZE the
## first time the mountain is drawn, which is what "hanging at runtime" looks
## like from the outside. Reading shader.cpp cannot tell you how long it takes.
##
## --taps controls how many evaluate_density call sites the detail normal uses,
## because compile cost is driven by CODE SIZE at the call sites, and that is
## the one knob that reduces it without changing the field.
const BDM := preload("res://scenes/maps/bridged_density_material.gd")
const MOUNTAIN_SCENE := "res://blender/mountain_voronoi2.tscn"
const WORLD_SCALE := 100.0
var _taps := 6
var _frames := 0
var _t_first := 0
var _mat: ShaderMaterial = null
func _initialize() -> void:
var args := OS.get_cmdline_user_args()
for i in range(args.size() - 1):
if args[i] == "--taps": _taps = int(args[i + 1])
var ps: PackedScene = load(MOUNTAIN_SCENE)
var inst: Node = ps.instantiate()
var src: Material = inst.get_node(^"Mountain").get("material_override")
var graph: Resource = inst.get_node(^"Mountain/ProcGeoNodes").get("graph")
inst.free()
var gen = ClassDB.instantiate("DCTerrainGeneratorProcNodesBounded")
gen.set_world_scale(WORLD_SCALE)
gen.set_heightfield(true)
gen.set_graph(graph)
var t0 := Time.get_ticks_msec()
var built := BDM.build(src as ShaderMaterial, gen, _taps)
print("[COST] taps=%d build=%d ms bytes=%d %s"
% [_taps, Time.get_ticks_msec() - t0, String(built["code"]).length(),
", ".join(built["log"])])
if built["material"] == null:
quit(1)
return
_mat = built["material"]
# A visible surface, a light that casts shadows, and a camera pointing at
# it: that is the minimum that forces the opaque AND shadow pipelines.
var world := Node3D.new()
root.add_child(world)
var mi := MeshInstance3D.new()
var sphere := SphereMesh.new()
sphere.radius = 50.0
sphere.height = 100.0
mi.mesh = sphere
mi.material_override = _mat
world.add_child(mi)
var lt := DirectionalLight3D.new()
lt.shadow_enabled = true
lt.rotation_degrees = Vector3(-45, 30, 0)
world.add_child(lt)
var cam := Camera3D.new()
cam.position = Vector3(0, 40, 160)
world.add_child(cam)
cam.make_current()
_t_first = Time.get_ticks_msec()
print("[COST] scene up, waiting on first drawn frames")
func _process(_dt: float) -> bool:
_frames += 1
if _frames == 1:
print("[COST] taps=%d FIRST FRAME after %d ms <-- this is the freeze"
% [_taps, Time.get_ticks_msec() - _t_first])
if _frames >= 30:
print("[COST] taps=%d 30 frames in %d ms total"
% [_taps, Time.get_ticks_msec() - _t_first])
quit(0)
return true
return false