extends CanvasLayer # Autoload: toggleable frame-time/bottleneck overlay (F3 by default — see # toggle_perf_overlay in project.godot's [input]). Read-only against # Performance monitors; never touches rendering or gameplay state. Exists so # 0.17/0.17b's graphics presets and resolution scaling are self-diagnosing — # TIME_PROCESS vs total frame time tells the player whether they're CPU- or # GPU-bound. See multiplayer-next.md task 0.20. # ~2s of history at 60 fps; enough to make p50/p99 meaningful without the # history itself being a rate-dependent quantity. const HISTORY_SIZE := 120 var _label: Label var _frame_times_ms: PackedFloat32Array = PackedFloat32Array() var _history_index := 0 var _history_filled := 0 func _ready() -> void: # A headless server never renders and has no input to toggle this with. if DisplayServer.get_name() == "headless": set_process(false) return layer = 100 _label = Label.new() _label.add_theme_font_size_override("font_size", 14) _label.add_theme_color_override("font_color", Color(0.4, 1.0, 0.5)) _label.add_theme_color_override("font_shadow_color", Color(0, 0, 0, 0.85)) _label.add_theme_constant_override("shadow_offset_x", 1) _label.add_theme_constant_override("shadow_offset_y", 1) _label.position = Vector2(12, 12) _label.visible = false add_child(_label) _frame_times_ms.resize(HISTORY_SIZE) _frame_times_ms.fill(0.0) func _unhandled_input(event: InputEvent) -> void: if event.is_action_pressed("toggle_perf_overlay") and _label: _label.visible = not _label.visible func _process(_delta: float) -> void: if not _label or not _label.visible: return var frame_ms := Performance.get_monitor(Performance.TIME_PROCESS) * 1000.0 var physics_ms := Performance.get_monitor(Performance.TIME_PHYSICS_PROCESS) * 1000.0 _frame_times_ms[_history_index] = frame_ms _history_index = (_history_index + 1) % HISTORY_SIZE _history_filled = mini(_history_filled + 1, HISTORY_SIZE) var sorted := _frame_times_ms.slice(0, _history_filled) sorted.sort() var p50 := sorted[sorted.size() / 2] var p99 := sorted[int(sorted.size() * 0.99)] _label.text = "FPS %d (p50 %.2fms p99 %.2fms)\nprocess %.2fms physics %.2fms\ndraw calls %d" % [ Performance.get_monitor(Performance.TIME_FPS), p50, p99, frame_ms, physics_ms, Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME), ]