extends Node ## Dev-only screenshot harness. Boots a target scene inside the real (GPU) ## renderer, lets it run a few frames, then reads the rendered frame back off ## the viewport and writes it to disk. Nothing in the shipped game touches this. ## ## Driven entirely by environment variables so it needs no CLI parsing: ## AGP_SHOT_SCENE res:// path of the scene to capture (default: game.tscn) ## AGP_SHOT_OUT output PNG path (absolute) (default: /tmp/agp_shot.png) ## AGP_SHOT_FRAMES frames to settle before the first shot (default: 45) ## AGP_SHOT_SEQ "count:gap" -> take shots frames apart, ## suffixing the filename _01.._NN (default: single shot) ## ## Launch (hardware Vulkan on cloudbison's Radeon, via Xvfb): ## Xvfb :99 -screen 0 1280x720x24 & ## sg render -c "sg video -c 'DISPLAY=:99 AGP_SHOT_SCENE=res://scenes/game.tscn \ ## AGP_SHOT_OUT=/abs/shot.png ~/godot/godot --path . \ ## --rendering-driver vulkan --gpu-index 0 --resolution 1280x720 \ ## res://scenes/dev/screenshot_rig.tscn'" func _ready() -> void: _run.call_deferred() func _run() -> void: var target := OS.get_environment("AGP_SHOT_SCENE") if target.is_empty(): target = "res://scenes/game.tscn" var out := OS.get_environment("AGP_SHOT_OUT") if out.is_empty(): out = "/tmp/agp_shot.png" var settle := int(OS.get_environment("AGP_SHOT_FRAMES")) if settle <= 0: settle = 45 var seq_count := 1 var seq_gap := 0 var seq_env := OS.get_environment("AGP_SHOT_SEQ") if seq_env.contains(":"): var parts := seq_env.split(":") seq_count = maxi(1, int(parts[0])) seq_gap = maxi(1, int(parts[1])) var packed: PackedScene = load(target) if packed == null: push_error("screenshot_rig: cannot load %s" % target) get_tree().quit(1) return var inst: Node = packed.instantiate() get_tree().root.add_child(inst) get_tree().current_scene = inst for i in settle: await get_tree().process_frame for shot in seq_count: # get_image() on the viewport texture is a GPU->CPU readback of the # frame that was just rendered -- no window presentation needed, which # is why this works over Xvfb (no DRI3). var img: Image = get_viewport().get_texture().get_image() var path := out if seq_count > 1: path = "%s_%02d.png" % [out.get_basename(), shot + 1] var err := img.save_png(path) if err != OK: push_error("screenshot_rig: save_png(%s) failed: %d" % [path, err]) else: print("SHOT %s (%dx%d)" % [path, img.get_width(), img.get_height()]) if shot < seq_count - 1: for j in seq_gap: await get_tree().process_frame get_tree().quit()