├── .gitignore ├── icon.png.import ├── icon.png ├── addons └── debug_menu │ ├── plugin.cfg │ ├── plugin.gd │ ├── LICENSE.md │ ├── debug_menu.tscn │ └── debug_menu.gd ├── .editorconfig ├── .gitattributes ├── .pre-commit-config.yaml ├── test.gd ├── project.godot ├── icon.svg.import ├── LICENSE.md ├── README.md ├── icon.svg └── test.tscn /.gitignore: -------------------------------------------------------------------------------- 1 | .godot/ 2 | -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="keep" 4 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godot-extended-libraries/godot-debug-menu-demo/HEAD/icon.png -------------------------------------------------------------------------------- /addons/debug_menu/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Debug Menu" 4 | description="In-game debug menu displaying performance metrics and hardware information" 5 | author="Calinou" 6 | version="1.2.0" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.gd] 12 | indent_style = tab 13 | indent_size = 4 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # The add-on code in this repository is copied from 2 | # regularly. 3 | # Please open pull requests for the add-on code itself there, not in 4 | # this demo repository. 5 | /addons linguist-vendored 6 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.4.0 4 | hooks: 5 | - id: fix-byte-order-marker 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | 9 | - id: mixed-line-ending 10 | args: [--fix=lf] 11 | -------------------------------------------------------------------------------- /test.gd: -------------------------------------------------------------------------------- 1 | extends Node3D 2 | 3 | 4 | func _on_cycle_debug_menu_display_mode_pressed() -> void: 5 | @warning_ignore("int_as_enum_without_cast") 6 | DebugMenu.style = wrapi(DebugMenu.style + 1, 0, DebugMenu.Style.MAX) 7 | 8 | 9 | func _on_pause_toggled(toggled_on: bool) -> void: 10 | get_tree().paused = toggled_on 11 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="Debug Menu Demo" 14 | config/description="Demo for the debug menu add-on." 15 | config/version="1.2.0" 16 | run/main_scene="res://test.tscn" 17 | config/features=PackedStringArray("4.2") 18 | config/icon="res://icon.svg" 19 | 20 | [autoload] 21 | 22 | DebugMenu="*res://addons/debug_menu/debug_menu.tscn" 23 | 24 | [display] 25 | 26 | window/stretch/mode="canvas_items" 27 | window/stretch/aspect="expand" 28 | 29 | [editor_plugins] 30 | 31 | enabled=PackedStringArray("res://addons/debug_menu/plugin.cfg") 32 | 33 | [rendering] 34 | 35 | scaling_3d/mode=2 36 | scaling_3d/scale=0.67 37 | anti_aliasing/quality/msaa_3d=1 38 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bgh7cxirlvjtm" 6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/debug_menu/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | func _enter_tree() -> void: 5 | add_autoload_singleton("DebugMenu", "res://addons/debug_menu/debug_menu.tscn") 6 | 7 | # FIXME: This appears to do nothing. 8 | # if not ProjectSettings.has_setting("application/config/version"): 9 | # ProjectSettings.set_setting("application/config/version", "1.0.0") 10 | # 11 | # ProjectSettings.set_initial_value("application/config/version", "1.0.0") 12 | # ProjectSettings.add_property_info({ 13 | # name = "application/config/version", 14 | # type = TYPE_STRING, 15 | # }) 16 | # 17 | # if not InputMap.has_action("cycle_debug_menu"): 18 | # InputMap.add_action("cycle_debug_menu") 19 | # var event := InputEventKey.new() 20 | # event.keycode = KEY_F3 21 | # InputMap.action_add_event("cycle_debug_menu", event) 22 | # 23 | # ProjectSettings.save() 24 | 25 | 26 | func _exit_tree() -> void: 27 | remove_autoload_singleton("DebugMenu") 28 | # Don't remove the project setting's value and input map action, 29 | # as the plugin may be re-enabled in the future. 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright © 2023-present Hugo Locurcio and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/debug_menu/LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright © 2023-present Hugo Locurcio and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Debug Menu add-on demo project for Godot 4.x 2 | 3 | **Displays performance information in a Godot project during gameplay.** 4 | Can be used when running from the editor and in exported projects. 5 | Inspired by id Tech 6/7's performance overlay. 6 | 7 | ![Screenshot](https://raw.githubusercontent.com/Calinou/media/master/godot-debug-menu-demo/screenshot.png) 8 | 9 | This repository contains the demo project for the 10 | [Debug Menu add-on](https://github.com/godot-extended-libraries/godot-debug-menu). 11 | The add-on's code is included in this repository and is mirrored periodically. 12 | 13 | Please report issues specific to the add-on 14 | [here](https://github.com/godot-extended-libraries/godot-debug-menu), not in this repository. 15 | 16 | ## Try it out 17 | 18 | > **Note** 19 | > 20 | > This add-on only supports Godot 4.x, not Godot 3.x. 21 | 22 | ### Using the Asset Library 23 | 24 | - Open the Godot project manager. 25 | - Navigate to the **Templates** tab and search for "debug menu". 26 | - Install the [*Debug Menu Demo*](https://godotengine.org/asset-library/asset/1903) project. 27 | 28 | ### Manual installation 29 | 30 | Manual installation lets you try pre-release versions of this demo by following its 31 | `master` branch. 32 | 33 | - Clone this Git repository: 34 | 35 | ```bash 36 | git clone https://github.com/godot-extended-libraries/godot-debug-menu-demo.git 37 | ``` 38 | 39 | Alternatively, you can 40 | [download a ZIP archive](https://github.com/godot-extended-libraries/godot-debug-menu-demo/archive/master.zip) 41 | if you do not have Git installed. 42 | 43 | - Import the Godot project using the project manager and open it in the editor. 44 | - Run the main scene by pressing F5. 45 | 46 | ## Usage 47 | 48 | Press F3 while the project is running. This cycles between no debug 49 | menu, a compact debug menu (only FPS and frametime visible) and a full debug 50 | menu. 51 | 52 | The key to cycle the debug menu is set to F3 by default. This can be 53 | changed by setting the `cycle_debug_menu` action in the Input Map to a different 54 | key. This action is not created by the plugin in the editor, so you will have to 55 | create it in the Project Settings if you wish to override the key. 56 | 57 | To toggle the debug menu from code, use: 58 | 59 | - `DebugMenu.style = DebugMenu.Style.HIDDEN` to hide the debug menu. 60 | - `DebugMenu.style = DebugMenu.Style.VISIBLE_COMPACT` to show the compact debug menu. 61 | - `DebugMenu.style = DebugMenu.Style.VISIBLE_DETAILED` to show the detailed debug menu. 62 | 63 | ## License 64 | 65 | Copyright © 2023-present Hugo Locurcio and contributors 66 | 67 | Unless otherwise specified, files in this repository are licensed under the 68 | MIT license. See [LICENSE.md](LICENSE.md) for more information. 69 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 38 | 42 | 46 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /test.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=17 format=3 uid="uid://cyn6ed84egpjf"] 2 | 3 | [ext_resource type="Script" path="res://test.gd" id="1_j7f4p"] 4 | 5 | [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_5ncgh"] 6 | sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) 7 | ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) 8 | 9 | [sub_resource type="Sky" id="Sky_1t3ds"] 10 | sky_material = SubResource("ProceduralSkyMaterial_5ncgh") 11 | 12 | [sub_resource type="Environment" id="Environment_3o4rc"] 13 | background_mode = 2 14 | sky = SubResource("Sky_1t3ds") 15 | tonemap_mode = 2 16 | ssr_enabled = true 17 | ssao_enabled = true 18 | ssil_enabled = true 19 | sdfgi_enabled = true 20 | glow_enabled = true 21 | 22 | [sub_resource type="BoxMesh" id="BoxMesh_wetkq"] 23 | size = Vector3(1024, 1, 1024) 24 | 25 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_xt6kg"] 26 | albedo_color = Color(0.294118, 0.431373, 0.396078, 1) 27 | 28 | [sub_resource type="TorusMesh" id="TorusMesh_5v4of"] 29 | 30 | [sub_resource type="Gradient" id="Gradient_yerv8"] 31 | 32 | [sub_resource type="GradientTexture2D" id="GradientTexture2D_w3fs2"] 33 | gradient = SubResource("Gradient_yerv8") 34 | fill = 1 35 | fill_from = Vector2(0.5, 0.5) 36 | 37 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_guhnx"] 38 | albedo_texture = SubResource("GradientTexture2D_w3fs2") 39 | metallic = 1.0 40 | metallic_texture = SubResource("GradientTexture2D_w3fs2") 41 | roughness_texture = SubResource("GradientTexture2D_w3fs2") 42 | uv1_scale = Vector3(8, 8, 8) 43 | texture_filter = 5 44 | 45 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_b641o"] 46 | albedo_color = Color(0.796078, 0.431373, 0.101961, 1) 47 | 48 | [sub_resource type="Animation" id="Animation_physp"] 49 | length = 0.001 50 | tracks/0/type = "value" 51 | tracks/0/imported = false 52 | tracks/0/enabled = true 53 | tracks/0/path = NodePath("Pivot:rotation") 54 | tracks/0/interp = 1 55 | tracks/0/loop_wrap = true 56 | tracks/0/keys = { 57 | "times": PackedFloat32Array(0), 58 | "transitions": PackedFloat32Array(1), 59 | "update": 0, 60 | "values": [Vector3(0, 0, 0)] 61 | } 62 | 63 | [sub_resource type="Animation" id="Animation_bniwj"] 64 | resource_name = "move" 65 | length = 10.0 66 | loop_mode = 1 67 | tracks/0/type = "value" 68 | tracks/0/imported = false 69 | tracks/0/enabled = true 70 | tracks/0/path = NodePath("Pivot:rotation") 71 | tracks/0/interp = 1 72 | tracks/0/loop_wrap = true 73 | tracks/0/keys = { 74 | "times": PackedFloat32Array(0, 10), 75 | "transitions": PackedFloat32Array(1, 1), 76 | "update": 0, 77 | "values": [Vector3(0, 0, 0), Vector3(0, 6.28319, 0)] 78 | } 79 | 80 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_sld3c"] 81 | _data = { 82 | "RESET": SubResource("Animation_physp"), 83 | "move": SubResource("Animation_bniwj") 84 | } 85 | 86 | [sub_resource type="Gradient" id="Gradient_8hfrt"] 87 | 88 | [sub_resource type="GradientTexture2D" id="GradientTexture2D_dlbxm"] 89 | gradient = SubResource("Gradient_8hfrt") 90 | 91 | [node name="Node3D" type="Node3D"] 92 | script = ExtResource("1_j7f4p") 93 | 94 | [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] 95 | transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0) 96 | shadow_enabled = true 97 | shadow_blur = 2.0 98 | directional_shadow_mode = 0 99 | directional_shadow_fade_start = 1.0 100 | directional_shadow_max_distance = 12.0 101 | 102 | [node name="WorldEnvironment" type="WorldEnvironment" parent="."] 103 | environment = SubResource("Environment_3o4rc") 104 | 105 | [node name="Pivot" type="Node3D" parent="."] 106 | 107 | [node name="Camera3D" type="Camera3D" parent="Pivot"] 108 | transform = Transform3D(0.667861, -0.434826, 0.604059, 0, 0.811596, 0.58422, -0.744286, -0.390177, 0.542033, 3.42352, 3.8767, 2.99318) 109 | fov = 60.0 110 | 111 | [node name="Ground" type="MeshInstance3D" parent="."] 112 | mesh = SubResource("BoxMesh_wetkq") 113 | surface_material_override/0 = SubResource("StandardMaterial3D_xt6kg") 114 | 115 | [node name="Torus" type="MeshInstance3D" parent="."] 116 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8, 0) 117 | mesh = SubResource("TorusMesh_5v4of") 118 | surface_material_override/0 = SubResource("StandardMaterial3D_guhnx") 119 | 120 | [node name="Torus2" type="MeshInstance3D" parent="."] 121 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.8, 0) 122 | mesh = SubResource("TorusMesh_5v4of") 123 | surface_material_override/0 = SubResource("StandardMaterial3D_b641o") 124 | 125 | [node name="Torus3" type="MeshInstance3D" parent="."] 126 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.16509, 0.8, 0.130013) 127 | mesh = SubResource("TorusMesh_5v4of") 128 | 129 | [node name="Torus4" type="MeshInstance3D" parent="."] 130 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.38156, 0.8, -2.10068) 131 | mesh = SubResource("TorusMesh_5v4of") 132 | 133 | [node name="Torus5" type="MeshInstance3D" parent="."] 134 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.81478, 0.8, -2.87771) 135 | mesh = SubResource("TorusMesh_5v4of") 136 | 137 | [node name="Torus6" type="MeshInstance3D" parent="."] 138 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.60249, 0.8, 0.779648) 139 | mesh = SubResource("TorusMesh_5v4of") 140 | 141 | [node name="Torus7" type="MeshInstance3D" parent="."] 142 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.44719, 0.8, -1.73311) 143 | mesh = SubResource("TorusMesh_5v4of") 144 | 145 | [node name="Torus8" type="MeshInstance3D" parent="."] 146 | transform = Transform3D(1, 0, 0, 0, 0.149363, -0.988782, 0, 0.988782, 0.149363, -2.49761, 1.85614, -1.5474) 147 | mesh = SubResource("TorusMesh_5v4of") 148 | 149 | [node name="Torus9" type="MeshInstance3D" parent="."] 150 | transform = Transform3D(0.730007, -0.675773, -0.10208, 0, 0.149363, -0.988782, 0.683439, 0.721818, 0.109036, -0.129876, 1.94662, -1.5474) 151 | mesh = SubResource("TorusMesh_5v4of") 152 | 153 | [node name="Torus10" type="MeshInstance3D" parent="."] 154 | transform = Transform3D(-0.969145, -0.243727, -0.0368167, 5.43898e-09, 0.149363, -0.988782, 0.246492, -0.958273, -0.144754, 1.64747, 2.03829, -1.5474) 155 | mesh = SubResource("TorusMesh_5v4of") 156 | 157 | [node name="Torus11" type="MeshInstance3D" parent="."] 158 | transform = Transform3D(0.792644, -0.517793, 0.321878, -0.381576, -0.833075, -0.400481, 0.475515, 0.194618, -0.85791, 0.491417, 1.71169, 2.59955) 159 | mesh = SubResource("TorusMesh_5v4of") 160 | 161 | [node name="OmniLight3D" type="OmniLight3D" parent="."] 162 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.22689, 0) 163 | light_color = Color(0.372549, 0.305882, 0.12549, 1) 164 | light_volumetric_fog_energy = 500.0 165 | shadow_enabled = true 166 | omni_range = 15.0 167 | 168 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 169 | libraries = { 170 | "": SubResource("AnimationLibrary_sld3c") 171 | } 172 | autoplay = "move" 173 | 174 | [node name="Camera2D" type="Camera2D" parent="."] 175 | position = Vector2(707, 368) 176 | 177 | [node name="Sprite2D" type="Sprite2D" parent="."] 178 | position = Vector2(1087, 148) 179 | texture = SubResource("GradientTexture2D_dlbxm") 180 | 181 | [node name="CycleDebugMenuDisplayMode" type="Button" parent="."] 182 | process_mode = 3 183 | offset_left = 144.0 184 | offset_top = 58.0 185 | offset_right = 315.0 186 | offset_bottom = 102.0 187 | text = "Cycle Debug Menu" 188 | 189 | [node name="Pause" type="Button" parent="."] 190 | process_mode = 3 191 | offset_left = 144.0 192 | offset_top = 114.0 193 | offset_right = 315.0 194 | offset_bottom = 158.0 195 | toggle_mode = true 196 | text = "Pause" 197 | 198 | [node name="Label" type="Label" parent="."] 199 | modulate = Color(1, 1, 1, 0.501961) 200 | offset_left = 922.0 201 | offset_top = 197.0 202 | offset_right = 1233.0 203 | offset_bottom = 249.0 204 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 205 | theme_override_constants/outline_size = 4 206 | text = "2D drawing example 207 | (should appear behind the debug menu). 208 | Also notice the Camera2D offset in the editor." 209 | 210 | [connection signal="pressed" from="CycleDebugMenuDisplayMode" to="." method="_on_cycle_debug_menu_display_mode_pressed"] 211 | [connection signal="toggled" from="Pause" to="." method="_on_pause_toggled"] 212 | -------------------------------------------------------------------------------- /addons/debug_menu/debug_menu.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://cggqb75a8w8r"] 2 | 3 | [ext_resource type="Script" path="res://addons/debug_menu/debug_menu.gd" id="1_p440y"] 4 | 5 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ki0n8"] 6 | bg_color = Color(0, 0, 0, 0.25098) 7 | 8 | [node name="CanvasLayer" type="CanvasLayer" node_paths=PackedStringArray("fps", "frame_time", "frame_number", "frame_history_total_avg", "frame_history_total_min", "frame_history_total_max", "frame_history_total_last", "frame_history_cpu_avg", "frame_history_cpu_min", "frame_history_cpu_max", "frame_history_cpu_last", "frame_history_gpu_avg", "frame_history_gpu_min", "frame_history_gpu_max", "frame_history_gpu_last", "fps_graph", "total_graph", "cpu_graph", "gpu_graph", "information", "settings")] 9 | process_mode = 3 10 | layer = 128 11 | script = ExtResource("1_p440y") 12 | fps = NodePath("DebugMenu/VBoxContainer/FPS") 13 | frame_time = NodePath("DebugMenu/VBoxContainer/FrameTime") 14 | frame_number = NodePath("DebugMenu/VBoxContainer/FrameNumber") 15 | frame_history_total_avg = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalAvg") 16 | frame_history_total_min = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalMin") 17 | frame_history_total_max = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalMax") 18 | frame_history_total_last = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/TotalLast") 19 | frame_history_cpu_avg = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPUAvg") 20 | frame_history_cpu_min = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPUMin") 21 | frame_history_cpu_max = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPUMax") 22 | frame_history_cpu_last = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/CPULast") 23 | frame_history_gpu_avg = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPUAvg") 24 | frame_history_gpu_min = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPUMin") 25 | frame_history_gpu_max = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPUMax") 26 | frame_history_gpu_last = NodePath("DebugMenu/VBoxContainer/FrameTimeHistory/GPULast") 27 | fps_graph = NodePath("DebugMenu/VBoxContainer/FPSGraph/Graph") 28 | total_graph = NodePath("DebugMenu/VBoxContainer/TotalGraph/Graph") 29 | cpu_graph = NodePath("DebugMenu/VBoxContainer/CPUGraph/Graph") 30 | gpu_graph = NodePath("DebugMenu/VBoxContainer/GPUGraph/Graph") 31 | information = NodePath("DebugMenu/VBoxContainer/Information") 32 | settings = NodePath("DebugMenu/VBoxContainer/Settings") 33 | 34 | [node name="DebugMenu" type="Control" parent="."] 35 | custom_minimum_size = Vector2(400, 400) 36 | layout_mode = 3 37 | anchors_preset = 1 38 | anchor_left = 1.0 39 | anchor_right = 1.0 40 | offset_left = -416.0 41 | offset_top = 8.0 42 | offset_right = -16.0 43 | offset_bottom = 408.0 44 | grow_horizontal = 0 45 | size_flags_horizontal = 8 46 | size_flags_vertical = 4 47 | mouse_filter = 2 48 | 49 | [node name="VBoxContainer" type="VBoxContainer" parent="DebugMenu"] 50 | layout_mode = 1 51 | anchors_preset = 1 52 | anchor_left = 1.0 53 | anchor_right = 1.0 54 | offset_left = -300.0 55 | offset_bottom = 374.0 56 | grow_horizontal = 0 57 | mouse_filter = 2 58 | theme_override_constants/separation = 0 59 | 60 | [node name="FPS" type="Label" parent="DebugMenu/VBoxContainer"] 61 | modulate = Color(0, 1, 0, 1) 62 | layout_mode = 2 63 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 64 | theme_override_constants/outline_size = 5 65 | theme_override_constants/line_spacing = 0 66 | theme_override_font_sizes/font_size = 18 67 | text = "60 FPS" 68 | horizontal_alignment = 2 69 | 70 | [node name="FrameTime" type="Label" parent="DebugMenu/VBoxContainer"] 71 | modulate = Color(0, 1, 0, 1) 72 | layout_mode = 2 73 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 74 | theme_override_constants/outline_size = 3 75 | theme_override_font_sizes/font_size = 12 76 | text = "16.67 mspf (cap: 123 FPS + Adaptive V-Sync)" 77 | horizontal_alignment = 2 78 | 79 | [node name="FrameNumber" type="Label" parent="DebugMenu/VBoxContainer"] 80 | layout_mode = 2 81 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 82 | theme_override_constants/outline_size = 3 83 | theme_override_font_sizes/font_size = 12 84 | text = "Frame: 1234" 85 | horizontal_alignment = 2 86 | 87 | [node name="FrameTimeHistory" type="GridContainer" parent="DebugMenu/VBoxContainer"] 88 | layout_mode = 2 89 | size_flags_horizontal = 8 90 | mouse_filter = 2 91 | theme_override_constants/h_separation = 0 92 | theme_override_constants/v_separation = 0 93 | columns = 5 94 | 95 | [node name="Spacer" type="Control" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 96 | custom_minimum_size = Vector2(60, 0) 97 | layout_mode = 2 98 | mouse_filter = 2 99 | 100 | [node name="AvgHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 101 | custom_minimum_size = Vector2(50, 0) 102 | layout_mode = 2 103 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 104 | theme_override_constants/outline_size = 3 105 | theme_override_font_sizes/font_size = 12 106 | text = "Average" 107 | horizontal_alignment = 2 108 | 109 | [node name="MinHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 110 | custom_minimum_size = Vector2(50, 0) 111 | layout_mode = 2 112 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 113 | theme_override_constants/outline_size = 3 114 | theme_override_font_sizes/font_size = 12 115 | text = "Best" 116 | horizontal_alignment = 2 117 | 118 | [node name="MaxHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 119 | custom_minimum_size = Vector2(50, 0) 120 | layout_mode = 2 121 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 122 | theme_override_constants/outline_size = 3 123 | theme_override_font_sizes/font_size = 12 124 | text = "Worst" 125 | horizontal_alignment = 2 126 | 127 | [node name="LastHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 128 | custom_minimum_size = Vector2(50, 0) 129 | layout_mode = 2 130 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 131 | theme_override_constants/outline_size = 3 132 | theme_override_font_sizes/font_size = 12 133 | text = "Last" 134 | horizontal_alignment = 2 135 | 136 | [node name="TotalHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 137 | custom_minimum_size = Vector2(50, 0) 138 | layout_mode = 2 139 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 140 | theme_override_constants/outline_size = 3 141 | theme_override_font_sizes/font_size = 12 142 | text = "Total:" 143 | horizontal_alignment = 2 144 | 145 | [node name="TotalAvg" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 146 | modulate = Color(0, 1, 0, 1) 147 | custom_minimum_size = Vector2(50, 0) 148 | layout_mode = 2 149 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 150 | theme_override_constants/outline_size = 3 151 | theme_override_font_sizes/font_size = 12 152 | text = "123.45" 153 | horizontal_alignment = 2 154 | 155 | [node name="TotalMin" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 156 | modulate = Color(0, 1, 0, 1) 157 | custom_minimum_size = Vector2(50, 0) 158 | layout_mode = 2 159 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 160 | theme_override_constants/outline_size = 3 161 | theme_override_font_sizes/font_size = 12 162 | text = "123.45" 163 | horizontal_alignment = 2 164 | 165 | [node name="TotalMax" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 166 | modulate = Color(0, 1, 0, 1) 167 | custom_minimum_size = Vector2(50, 0) 168 | layout_mode = 2 169 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 170 | theme_override_constants/outline_size = 3 171 | theme_override_font_sizes/font_size = 12 172 | text = "123.45" 173 | horizontal_alignment = 2 174 | 175 | [node name="TotalLast" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 176 | modulate = Color(0, 1, 0, 1) 177 | custom_minimum_size = Vector2(50, 0) 178 | layout_mode = 2 179 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 180 | theme_override_constants/outline_size = 3 181 | theme_override_font_sizes/font_size = 12 182 | text = "123.45" 183 | horizontal_alignment = 2 184 | 185 | [node name="CPUHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 186 | custom_minimum_size = Vector2(50, 0) 187 | layout_mode = 2 188 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 189 | theme_override_constants/outline_size = 3 190 | theme_override_font_sizes/font_size = 12 191 | text = "CPU:" 192 | horizontal_alignment = 2 193 | 194 | [node name="CPUAvg" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 195 | modulate = Color(0, 1, 0, 1) 196 | custom_minimum_size = Vector2(50, 0) 197 | layout_mode = 2 198 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 199 | theme_override_constants/outline_size = 3 200 | theme_override_font_sizes/font_size = 12 201 | text = "123.45" 202 | horizontal_alignment = 2 203 | 204 | [node name="CPUMin" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 205 | modulate = Color(0, 1, 0, 1) 206 | custom_minimum_size = Vector2(50, 0) 207 | layout_mode = 2 208 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 209 | theme_override_constants/outline_size = 3 210 | theme_override_font_sizes/font_size = 12 211 | text = "12.34" 212 | horizontal_alignment = 2 213 | 214 | [node name="CPUMax" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 215 | modulate = Color(0, 1, 0, 1) 216 | custom_minimum_size = Vector2(50, 0) 217 | layout_mode = 2 218 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 219 | theme_override_constants/outline_size = 3 220 | theme_override_font_sizes/font_size = 12 221 | text = "123.45" 222 | horizontal_alignment = 2 223 | 224 | [node name="CPULast" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 225 | modulate = Color(0, 1, 0, 1) 226 | custom_minimum_size = Vector2(50, 0) 227 | layout_mode = 2 228 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 229 | theme_override_constants/outline_size = 3 230 | theme_override_font_sizes/font_size = 12 231 | text = "123.45" 232 | horizontal_alignment = 2 233 | 234 | [node name="GPUHeader" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 235 | custom_minimum_size = Vector2(50, 0) 236 | layout_mode = 2 237 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 238 | theme_override_constants/outline_size = 3 239 | theme_override_font_sizes/font_size = 12 240 | text = "GPU:" 241 | horizontal_alignment = 2 242 | 243 | [node name="GPUAvg" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 244 | modulate = Color(0, 1, 0, 1) 245 | custom_minimum_size = Vector2(50, 0) 246 | layout_mode = 2 247 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 248 | theme_override_constants/outline_size = 3 249 | theme_override_font_sizes/font_size = 12 250 | text = "123.45" 251 | horizontal_alignment = 2 252 | 253 | [node name="GPUMin" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 254 | modulate = Color(0, 1, 0, 1) 255 | custom_minimum_size = Vector2(50, 0) 256 | layout_mode = 2 257 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 258 | theme_override_constants/outline_size = 3 259 | theme_override_font_sizes/font_size = 12 260 | text = "1.23" 261 | horizontal_alignment = 2 262 | 263 | [node name="GPUMax" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 264 | modulate = Color(0, 1, 0, 1) 265 | custom_minimum_size = Vector2(50, 0) 266 | layout_mode = 2 267 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 268 | theme_override_constants/outline_size = 3 269 | theme_override_font_sizes/font_size = 12 270 | text = "123.45" 271 | horizontal_alignment = 2 272 | 273 | [node name="GPULast" type="Label" parent="DebugMenu/VBoxContainer/FrameTimeHistory"] 274 | modulate = Color(0, 1, 0, 1) 275 | custom_minimum_size = Vector2(50, 0) 276 | layout_mode = 2 277 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 278 | theme_override_constants/outline_size = 3 279 | theme_override_font_sizes/font_size = 12 280 | text = "123.45" 281 | horizontal_alignment = 2 282 | 283 | [node name="FPSGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer"] 284 | layout_mode = 2 285 | mouse_filter = 2 286 | alignment = 2 287 | 288 | [node name="Title" type="Label" parent="DebugMenu/VBoxContainer/FPSGraph"] 289 | custom_minimum_size = Vector2(0, 27) 290 | layout_mode = 2 291 | size_flags_horizontal = 8 292 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 293 | theme_override_constants/outline_size = 3 294 | theme_override_font_sizes/font_size = 12 295 | text = "FPS: ↑" 296 | vertical_alignment = 1 297 | 298 | [node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/FPSGraph"] 299 | custom_minimum_size = Vector2(150, 25) 300 | layout_mode = 2 301 | size_flags_vertical = 0 302 | mouse_filter = 2 303 | theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8") 304 | 305 | [node name="TotalGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer"] 306 | layout_mode = 2 307 | mouse_filter = 2 308 | alignment = 2 309 | 310 | [node name="Title" type="Label" parent="DebugMenu/VBoxContainer/TotalGraph"] 311 | custom_minimum_size = Vector2(0, 27) 312 | layout_mode = 2 313 | size_flags_horizontal = 8 314 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 315 | theme_override_constants/outline_size = 3 316 | theme_override_font_sizes/font_size = 12 317 | text = "Total: ↓" 318 | vertical_alignment = 1 319 | 320 | [node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/TotalGraph"] 321 | custom_minimum_size = Vector2(150, 25) 322 | layout_mode = 2 323 | size_flags_vertical = 0 324 | mouse_filter = 2 325 | theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8") 326 | 327 | [node name="CPUGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer"] 328 | layout_mode = 2 329 | mouse_filter = 2 330 | alignment = 2 331 | 332 | [node name="Title" type="Label" parent="DebugMenu/VBoxContainer/CPUGraph"] 333 | custom_minimum_size = Vector2(0, 27) 334 | layout_mode = 2 335 | size_flags_horizontal = 8 336 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 337 | theme_override_constants/outline_size = 3 338 | theme_override_font_sizes/font_size = 12 339 | text = "CPU: ↓" 340 | vertical_alignment = 1 341 | 342 | [node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/CPUGraph"] 343 | custom_minimum_size = Vector2(150, 25) 344 | layout_mode = 2 345 | size_flags_vertical = 0 346 | mouse_filter = 2 347 | theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8") 348 | 349 | [node name="GPUGraph" type="HBoxContainer" parent="DebugMenu/VBoxContainer"] 350 | layout_mode = 2 351 | mouse_filter = 2 352 | alignment = 2 353 | 354 | [node name="Title" type="Label" parent="DebugMenu/VBoxContainer/GPUGraph"] 355 | custom_minimum_size = Vector2(0, 27) 356 | layout_mode = 2 357 | size_flags_horizontal = 8 358 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 359 | theme_override_constants/outline_size = 3 360 | theme_override_font_sizes/font_size = 12 361 | text = "GPU: ↓" 362 | vertical_alignment = 1 363 | 364 | [node name="Graph" type="Panel" parent="DebugMenu/VBoxContainer/GPUGraph"] 365 | custom_minimum_size = Vector2(150, 25) 366 | layout_mode = 2 367 | size_flags_vertical = 0 368 | mouse_filter = 2 369 | theme_override_styles/panel = SubResource("StyleBoxFlat_ki0n8") 370 | 371 | [node name="Information" type="Label" parent="DebugMenu/VBoxContainer"] 372 | modulate = Color(1, 1, 1, 0.752941) 373 | layout_mode = 2 374 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 375 | theme_override_constants/outline_size = 3 376 | theme_override_font_sizes/font_size = 12 377 | text = "12th Gen Intel(R) Core(TM) i0-1234K 378 | Windows 12 64-bit (double precision), Vulkan 1.2.34 379 | NVIDIA GeForce RTX 1234, 123.45.67" 380 | horizontal_alignment = 2 381 | 382 | [node name="Settings" type="Label" parent="DebugMenu/VBoxContainer"] 383 | modulate = Color(0.8, 0.84, 1, 0.752941) 384 | layout_mode = 2 385 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 386 | theme_override_constants/outline_size = 3 387 | theme_override_font_sizes/font_size = 12 388 | text = "Project Version: 1.2.3 389 | Rendering Method: Forward+ 390 | Window: 1234×567, Viewport: 1234×567 391 | 3D Scale (FSR 1.0): 100% = 1234×567 392 | 3D Antialiasing: TAA + 2× MSAA + FXAA 393 | SSR: 123 Steps 394 | SSAO: On 395 | SSIL: On 396 | SDFGI: 1 Cascades 397 | Glow: On 398 | Volumetric Fog: On 399 | 2D Antialiasing: 2× MSAA" 400 | horizontal_alignment = 2 401 | 402 | [connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"] 403 | -------------------------------------------------------------------------------- /addons/debug_menu/debug_menu.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | @export var fps: Label 4 | @export var frame_time: Label 5 | @export var frame_number: Label 6 | @export var frame_history_total_avg: Label 7 | @export var frame_history_total_min: Label 8 | @export var frame_history_total_max: Label 9 | @export var frame_history_total_last: Label 10 | @export var frame_history_cpu_avg: Label 11 | @export var frame_history_cpu_min: Label 12 | @export var frame_history_cpu_max: Label 13 | @export var frame_history_cpu_last: Label 14 | @export var frame_history_gpu_avg: Label 15 | @export var frame_history_gpu_min: Label 16 | @export var frame_history_gpu_max: Label 17 | @export var frame_history_gpu_last: Label 18 | @export var fps_graph: Panel 19 | @export var total_graph: Panel 20 | @export var cpu_graph: Panel 21 | @export var gpu_graph: Panel 22 | @export var information: Label 23 | @export var settings: Label 24 | 25 | ## The number of frames to keep in history for graph drawing and best/worst calculations. 26 | ## Currently, this also affects how FPS is measured. 27 | const HISTORY_NUM_FRAMES = 150 28 | 29 | const GRAPH_SIZE = Vector2(150, 25) 30 | const GRAPH_MIN_FPS = 10 31 | const GRAPH_MAX_FPS = 160 32 | const GRAPH_MIN_FRAMETIME = 1.0 / GRAPH_MIN_FPS 33 | const GRAPH_MAX_FRAMETIME = 1.0 / GRAPH_MAX_FPS 34 | 35 | ## Debug menu display style. 36 | enum Style { 37 | HIDDEN, ## Debug menu is hidden. 38 | VISIBLE_COMPACT, ## Debug menu is visible, with only the FPS, FPS cap (if any) and time taken to render the last frame. 39 | VISIBLE_DETAILED, ## Debug menu is visible with full information, including graphs. 40 | MAX, ## Represents the size of the Style enum. 41 | } 42 | 43 | ## The style to use when drawing the debug menu. 44 | var style := Style.HIDDEN: 45 | set(value): 46 | style = value 47 | match style: 48 | Style.HIDDEN: 49 | visible = false 50 | Style.VISIBLE_COMPACT, Style.VISIBLE_DETAILED: 51 | visible = true 52 | frame_number.visible = style == Style.VISIBLE_DETAILED 53 | $DebugMenu/VBoxContainer/FrameTimeHistory.visible = style == Style.VISIBLE_DETAILED 54 | $DebugMenu/VBoxContainer/FPSGraph.visible = style == Style.VISIBLE_DETAILED 55 | $DebugMenu/VBoxContainer/TotalGraph.visible = style == Style.VISIBLE_DETAILED 56 | $DebugMenu/VBoxContainer/CPUGraph.visible = style == Style.VISIBLE_DETAILED 57 | $DebugMenu/VBoxContainer/GPUGraph.visible = style == Style.VISIBLE_DETAILED 58 | information.visible = style == Style.VISIBLE_DETAILED 59 | settings.visible = style == Style.VISIBLE_DETAILED 60 | 61 | # Value of `Time.get_ticks_usec()` on the previous frame. 62 | var last_tick := 0 63 | 64 | var thread := Thread.new() 65 | 66 | ## Returns the sum of all values of an array (use as a parameter to `Array.reduce()`). 67 | var sum_func := func avg(accum: float, number: float) -> float: return accum + number 68 | 69 | # History of the last `HISTORY_NUM_FRAMES` rendered frames. 70 | var frame_history_total: Array[float] = [] 71 | var frame_history_cpu: Array[float] = [] 72 | var frame_history_gpu: Array[float] = [] 73 | var fps_history: Array[float] = [] # Only used for graphs. 74 | 75 | var frametime_avg := GRAPH_MIN_FRAMETIME 76 | var frametime_cpu_avg := GRAPH_MAX_FRAMETIME 77 | var frametime_gpu_avg := GRAPH_MIN_FRAMETIME 78 | var frames_per_second := float(GRAPH_MIN_FPS) 79 | var frame_time_gradient := Gradient.new() 80 | 81 | func _init() -> void: 82 | # This must be done here instead of `_ready()` to avoid having `visibility_changed` be emitted immediately. 83 | visible = false 84 | 85 | if not InputMap.has_action("cycle_debug_menu"): 86 | # Create default input action if no user-defined override exists. 87 | # We can't do it in the editor plugin's activation code as it doesn't seem to work there. 88 | InputMap.add_action("cycle_debug_menu") 89 | var event := InputEventKey.new() 90 | event.keycode = KEY_F3 91 | InputMap.action_add_event("cycle_debug_menu", event) 92 | 93 | 94 | func _ready() -> void: 95 | fps_graph.draw.connect(_fps_graph_draw) 96 | total_graph.draw.connect(_total_graph_draw) 97 | cpu_graph.draw.connect(_cpu_graph_draw) 98 | gpu_graph.draw.connect(_gpu_graph_draw) 99 | 100 | fps_history.resize(HISTORY_NUM_FRAMES) 101 | frame_history_total.resize(HISTORY_NUM_FRAMES) 102 | frame_history_cpu.resize(HISTORY_NUM_FRAMES) 103 | frame_history_gpu.resize(HISTORY_NUM_FRAMES) 104 | 105 | # NOTE: Both FPS and frametimes are colored following FPS logic 106 | # (red = 10 FPS, yellow = 60 FPS, green = 110 FPS, cyan = 160 FPS). 107 | # This makes the color gradient non-linear. 108 | # Colors are taken from . 109 | frame_time_gradient.set_color(0, Color8(239, 68, 68)) # red-500 110 | frame_time_gradient.set_color(1, Color8(56, 189, 248)) # light-blue-400 111 | frame_time_gradient.add_point(0.3333, Color8(250, 204, 21)) # yellow-400 112 | frame_time_gradient.add_point(0.6667, Color8(128, 226, 95)) # 50-50 mix of lime-400 and green-400 113 | 114 | get_viewport().size_changed.connect(update_settings_label) 115 | 116 | # Display loading text while information is being queried, 117 | # in case the user toggles the full debug menu just after starting the project. 118 | information.text = "Loading hardware information...\n\n " 119 | settings.text = "Loading project information..." 120 | thread.start( 121 | func(): 122 | # Disable thread safety checks as they interfere with this add-on. 123 | # This only affects this particular thread, not other thread instances in the project. 124 | # See for details. 125 | # Use a Callable so that this can be ignored on Godot 4.0 without causing a script error 126 | # (thread safety checks were added in Godot 4.1). 127 | if Engine.get_version_info()["hex"] >= 0x040100: 128 | Callable(Thread, "set_thread_safety_checks_enabled").call(false) 129 | 130 | # Enable required time measurements to display CPU/GPU frame time information. 131 | # These lines are time-consuming operations, so run them in a separate thread. 132 | RenderingServer.viewport_set_measure_render_time(get_viewport().get_viewport_rid(), true) 133 | update_information_label() 134 | update_settings_label() 135 | ) 136 | 137 | 138 | func _input(event: InputEvent) -> void: 139 | if event.is_action_pressed("cycle_debug_menu"): 140 | style = wrapi(style + 1, 0, Style.MAX) as Style 141 | 142 | 143 | func _exit_tree() -> void: 144 | thread.wait_to_finish() 145 | 146 | 147 | ## Update hardware information label (this can change at runtime based on window 148 | ## size and graphics settings). This is only called when the window is resized. 149 | ## To update when graphics settings are changed, the function must be called manually 150 | ## using `DebugMenu.update_settings_label()`. 151 | func update_settings_label() -> void: 152 | settings.text = "" 153 | if ProjectSettings.has_setting("application/config/version"): 154 | settings.text += "Project Version: %s\n" % ProjectSettings.get_setting("application/config/version") 155 | 156 | var rendering_method := str(ProjectSettings.get_setting_with_override("rendering/renderer/rendering_method")) 157 | var rendering_method_string := rendering_method 158 | match rendering_method: 159 | "forward_plus": 160 | rendering_method_string = "Forward+" 161 | "mobile": 162 | rendering_method_string = "Forward Mobile" 163 | "gl_compatibility": 164 | rendering_method_string = "Compatibility" 165 | settings.text += "Rendering Method: %s\n" % rendering_method_string 166 | 167 | var viewport := get_viewport() 168 | 169 | # The size of the viewport rendering, which determines which resolution 3D is rendered at. 170 | var viewport_render_size := Vector2i() 171 | 172 | if viewport.content_scale_mode == Window.CONTENT_SCALE_MODE_VIEWPORT: 173 | viewport_render_size = viewport.get_visible_rect().size 174 | settings.text += "Viewport: %d×%d, Window: %d×%d\n" % [viewport.get_visible_rect().size.x, viewport.get_visible_rect().size.y, viewport.size.x, viewport.size.y] 175 | else: 176 | # Window size matches viewport size. 177 | viewport_render_size = viewport.size 178 | settings.text += "Viewport: %d×%d\n" % [viewport.size.x, viewport.size.y] 179 | 180 | # Display 3D settings only if relevant. 181 | if viewport.get_camera_3d(): 182 | var scaling_3d_mode_string := "(unknown)" 183 | match viewport.scaling_3d_mode: 184 | Viewport.SCALING_3D_MODE_BILINEAR: 185 | scaling_3d_mode_string = "Bilinear" 186 | Viewport.SCALING_3D_MODE_FSR: 187 | scaling_3d_mode_string = "FSR 1.0" 188 | Viewport.SCALING_3D_MODE_FSR2: 189 | scaling_3d_mode_string = "FSR 2.2" 190 | 191 | var antialiasing_3d_string := "" 192 | if viewport.scaling_3d_mode == Viewport.SCALING_3D_MODE_FSR2: 193 | # The FSR2 scaling mode includes its own temporal antialiasing implementation. 194 | antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "FSR 2.2" 195 | if viewport.scaling_3d_mode != Viewport.SCALING_3D_MODE_FSR2 and viewport.use_taa: 196 | # Godot's own TAA is ignored when using FSR2 scaling mode, as FSR2 provides its own TAA implementation. 197 | antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "TAA" 198 | if viewport.msaa_3d >= Viewport.MSAA_2X: 199 | antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "%d× MSAA" % pow(2, viewport.msaa_3d) 200 | if viewport.screen_space_aa == Viewport.SCREEN_SPACE_AA_FXAA: 201 | antialiasing_3d_string += (" + " if not antialiasing_3d_string.is_empty() else "") + "FXAA" 202 | 203 | settings.text += "3D scale (%s): %d%% = %d×%d" % [ 204 | scaling_3d_mode_string, 205 | viewport.scaling_3d_scale * 100, 206 | viewport_render_size.x * viewport.scaling_3d_scale, 207 | viewport_render_size.y * viewport.scaling_3d_scale, 208 | ] 209 | 210 | if not antialiasing_3d_string.is_empty(): 211 | settings.text += "\n3D Antialiasing: %s" % antialiasing_3d_string 212 | 213 | var environment := viewport.get_camera_3d().get_world_3d().environment 214 | if environment: 215 | if environment.ssr_enabled: 216 | settings.text += "\nSSR: %d Steps" % environment.ssr_max_steps 217 | 218 | if environment.ssao_enabled: 219 | settings.text += "\nSSAO: On" 220 | if environment.ssil_enabled: 221 | settings.text += "\nSSIL: On" 222 | 223 | if environment.sdfgi_enabled: 224 | settings.text += "\nSDFGI: %d Cascades" % environment.sdfgi_cascades 225 | 226 | if environment.glow_enabled: 227 | settings.text += "\nGlow: On" 228 | 229 | if environment.volumetric_fog_enabled: 230 | settings.text += "\nVolumetric Fog: On" 231 | var antialiasing_2d_string := "" 232 | if viewport.msaa_2d >= Viewport.MSAA_2X: 233 | antialiasing_2d_string = "%d× MSAA" % pow(2, viewport.msaa_2d) 234 | 235 | if not antialiasing_2d_string.is_empty(): 236 | settings.text += "\n2D Antialiasing: %s" % antialiasing_2d_string 237 | 238 | 239 | ## Update hardware/software information label (this never changes at runtime). 240 | func update_information_label() -> void: 241 | var adapter_string := "" 242 | # Make "NVIDIA Corporation" and "NVIDIA" be considered identical (required when using OpenGL to avoid redundancy). 243 | if RenderingServer.get_video_adapter_vendor().trim_suffix(" Corporation") in RenderingServer.get_video_adapter_name(): 244 | # Avoid repeating vendor name before adapter name. 245 | # Trim redundant suffix sometimes reported by NVIDIA graphics cards when using OpenGL. 246 | adapter_string = RenderingServer.get_video_adapter_name().trim_suffix("/PCIe/SSE2") 247 | else: 248 | adapter_string = RenderingServer.get_video_adapter_vendor() + " - " + RenderingServer.get_video_adapter_name().trim_suffix("/PCIe/SSE2") 249 | 250 | # Graphics driver version information isn't always availble. 251 | var driver_info := OS.get_video_adapter_driver_info() 252 | var driver_info_string := "" 253 | if driver_info.size() >= 2: 254 | driver_info_string = driver_info[1] 255 | else: 256 | driver_info_string = "(unknown)" 257 | 258 | var release_string := "" 259 | if OS.has_feature("editor"): 260 | # Editor build (implies `debug`). 261 | release_string = "editor" 262 | elif OS.has_feature("debug"): 263 | # Debug export template build. 264 | release_string = "debug" 265 | else: 266 | # Release export template build. 267 | release_string = "release" 268 | 269 | var rendering_method := str(ProjectSettings.get_setting_with_override("rendering/renderer/rendering_method")) 270 | var rendering_driver := str(ProjectSettings.get_setting_with_override("rendering/rendering_device/driver")) 271 | var graphics_api_string := rendering_driver 272 | if rendering_method != "gl_compatibility": 273 | if rendering_driver == "d3d12": 274 | graphics_api_string = "Direct3D 12" 275 | elif rendering_driver == "metal": 276 | graphics_api_string = "Metal" 277 | elif rendering_driver == "vulkan": 278 | if OS.has_feature("macos") or OS.has_feature("ios"): 279 | graphics_api_string = "Vulkan via MoltenVK" 280 | else: 281 | graphics_api_string = "Vulkan" 282 | else: 283 | if rendering_driver == "opengl3_angle": 284 | graphics_api_string = "OpenGL via ANGLE" 285 | elif OS.has_feature("mobile") or rendering_driver == "opengl3_es": 286 | graphics_api_string = "OpenGL ES" 287 | elif OS.has_feature("web"): 288 | graphics_api_string = "WebGL" 289 | elif rendering_driver == "opengl3": 290 | graphics_api_string = "OpenGL" 291 | 292 | information.text = ( 293 | "%s, %d threads\n" % [OS.get_processor_name().replace("(R)", "").replace("(TM)", ""), OS.get_processor_count()] 294 | + "%s %s (%s %s), %s %s\n" % [OS.get_name(), "64-bit" if OS.has_feature("64") else "32-bit", release_string, "double" if OS.has_feature("double") else "single", graphics_api_string, RenderingServer.get_video_adapter_api_version()] 295 | + "%s, %s" % [adapter_string, driver_info_string] 296 | ) 297 | 298 | 299 | func _fps_graph_draw() -> void: 300 | var fps_polyline := PackedVector2Array() 301 | fps_polyline.resize(HISTORY_NUM_FRAMES) 302 | for fps_index in fps_history.size(): 303 | fps_polyline[fps_index] = Vector2( 304 | remap(fps_index, 0, fps_history.size(), 0, GRAPH_SIZE.x), 305 | remap(clampf(fps_history[fps_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0) 306 | ) 307 | # Don't use antialiasing to speed up line drawing, but use a width that scales with 308 | # viewport scale to keep the line easily readable on hiDPI displays. 309 | fps_graph.draw_polyline(fps_polyline, frame_time_gradient.sample(remap(frames_per_second, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0) 310 | 311 | 312 | func _total_graph_draw() -> void: 313 | var total_polyline := PackedVector2Array() 314 | total_polyline.resize(HISTORY_NUM_FRAMES) 315 | for total_index in frame_history_total.size(): 316 | total_polyline[total_index] = Vector2( 317 | remap(total_index, 0, frame_history_total.size(), 0, GRAPH_SIZE.x), 318 | remap(clampf(frame_history_total[total_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0) 319 | ) 320 | # Don't use antialiasing to speed up line drawing, but use a width that scales with 321 | # viewport scale to keep the line easily readable on hiDPI displays. 322 | total_graph.draw_polyline(total_polyline, frame_time_gradient.sample(remap(1000.0 / frametime_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0) 323 | 324 | 325 | func _cpu_graph_draw() -> void: 326 | var cpu_polyline := PackedVector2Array() 327 | cpu_polyline.resize(HISTORY_NUM_FRAMES) 328 | for cpu_index in frame_history_cpu.size(): 329 | cpu_polyline[cpu_index] = Vector2( 330 | remap(cpu_index, 0, frame_history_cpu.size(), 0, GRAPH_SIZE.x), 331 | remap(clampf(frame_history_cpu[cpu_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0) 332 | ) 333 | # Don't use antialiasing to speed up line drawing, but use a width that scales with 334 | # viewport scale to keep the line easily readable on hiDPI displays. 335 | cpu_graph.draw_polyline(cpu_polyline, frame_time_gradient.sample(remap(1000.0 / frametime_cpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0) 336 | 337 | 338 | func _gpu_graph_draw() -> void: 339 | var gpu_polyline := PackedVector2Array() 340 | gpu_polyline.resize(HISTORY_NUM_FRAMES) 341 | for gpu_index in frame_history_gpu.size(): 342 | gpu_polyline[gpu_index] = Vector2( 343 | remap(gpu_index, 0, frame_history_gpu.size(), 0, GRAPH_SIZE.x), 344 | remap(clampf(frame_history_gpu[gpu_index], GRAPH_MIN_FPS, GRAPH_MAX_FPS), GRAPH_MIN_FPS, GRAPH_MAX_FPS, GRAPH_SIZE.y, 0.0) 345 | ) 346 | # Don't use antialiasing to speed up line drawing, but use a width that scales with 347 | # viewport scale to keep the line easily readable on hiDPI displays. 348 | gpu_graph.draw_polyline(gpu_polyline, frame_time_gradient.sample(remap(1000.0 / frametime_gpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)), 1.0) 349 | 350 | 351 | func _process(_delta: float) -> void: 352 | if visible: 353 | fps_graph.queue_redraw() 354 | total_graph.queue_redraw() 355 | cpu_graph.queue_redraw() 356 | gpu_graph.queue_redraw() 357 | 358 | # Difference between the last two rendered frames in milliseconds. 359 | var frametime := (Time.get_ticks_usec() - last_tick) * 0.001 360 | 361 | frame_history_total.push_back(frametime) 362 | if frame_history_total.size() > HISTORY_NUM_FRAMES: 363 | frame_history_total.pop_front() 364 | 365 | # Frametimes are colored following FPS logic (red = 10 FPS, yellow = 60 FPS, green = 110 FPS, cyan = 160 FPS). 366 | # This makes the color gradient non-linear. 367 | frametime_avg = frame_history_total.reduce(sum_func) / frame_history_total.size() 368 | frame_history_total_avg.text = str(frametime_avg).pad_decimals(2) 369 | frame_history_total_avg.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) 370 | 371 | var frametime_min: float = frame_history_total.min() 372 | frame_history_total_min.text = str(frametime_min).pad_decimals(2) 373 | frame_history_total_min.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_min, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) 374 | 375 | var frametime_max: float = frame_history_total.max() 376 | frame_history_total_max.text = str(frametime_max).pad_decimals(2) 377 | frame_history_total_max.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_max, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) 378 | 379 | frame_history_total_last.text = str(frametime).pad_decimals(2) 380 | frame_history_total_last.modulate = frame_time_gradient.sample(remap(1000.0 / frametime, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) 381 | 382 | var viewport_rid := get_viewport().get_viewport_rid() 383 | var frametime_cpu := RenderingServer.viewport_get_measured_render_time_cpu(viewport_rid) + RenderingServer.get_frame_setup_time_cpu() 384 | frame_history_cpu.push_back(frametime_cpu) 385 | if frame_history_cpu.size() > HISTORY_NUM_FRAMES: 386 | frame_history_cpu.pop_front() 387 | 388 | frametime_cpu_avg = frame_history_cpu.reduce(sum_func) / frame_history_cpu.size() 389 | frame_history_cpu_avg.text = str(frametime_cpu_avg).pad_decimals(2) 390 | frame_history_cpu_avg.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) 391 | 392 | var frametime_cpu_min: float = frame_history_cpu.min() 393 | frame_history_cpu_min.text = str(frametime_cpu_min).pad_decimals(2) 394 | frame_history_cpu_min.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu_min, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) 395 | 396 | var frametime_cpu_max: float = frame_history_cpu.max() 397 | frame_history_cpu_max.text = str(frametime_cpu_max).pad_decimals(2) 398 | frame_history_cpu_max.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu_max, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) 399 | 400 | frame_history_cpu_last.text = str(frametime_cpu).pad_decimals(2) 401 | frame_history_cpu_last.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_cpu, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) 402 | 403 | var frametime_gpu := RenderingServer.viewport_get_measured_render_time_gpu(viewport_rid) 404 | frame_history_gpu.push_back(frametime_gpu) 405 | if frame_history_gpu.size() > HISTORY_NUM_FRAMES: 406 | frame_history_gpu.pop_front() 407 | 408 | frametime_gpu_avg = frame_history_gpu.reduce(sum_func) / frame_history_gpu.size() 409 | frame_history_gpu_avg.text = str(frametime_gpu_avg).pad_decimals(2) 410 | frame_history_gpu_avg.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu_avg, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) 411 | 412 | var frametime_gpu_min: float = frame_history_gpu.min() 413 | frame_history_gpu_min.text = str(frametime_gpu_min).pad_decimals(2) 414 | frame_history_gpu_min.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu_min, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) 415 | 416 | var frametime_gpu_max: float = frame_history_gpu.max() 417 | frame_history_gpu_max.text = str(frametime_gpu_max).pad_decimals(2) 418 | frame_history_gpu_max.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu_max, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) 419 | 420 | frame_history_gpu_last.text = str(frametime_gpu).pad_decimals(2) 421 | frame_history_gpu_last.modulate = frame_time_gradient.sample(remap(1000.0 / frametime_gpu, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) 422 | 423 | frames_per_second = 1000.0 / frametime_avg 424 | fps_history.push_back(frames_per_second) 425 | if fps_history.size() > HISTORY_NUM_FRAMES: 426 | fps_history.pop_front() 427 | 428 | fps.text = str(floor(frames_per_second)) + " FPS" 429 | var frame_time_color := frame_time_gradient.sample(remap(frames_per_second, GRAPH_MIN_FPS, GRAPH_MAX_FPS, 0.0, 1.0)) 430 | fps.modulate = frame_time_color 431 | 432 | frame_time.text = str(frametime).pad_decimals(2) + " mspf" 433 | frame_time.modulate = frame_time_color 434 | 435 | var vsync_string := "" 436 | match DisplayServer.window_get_vsync_mode(): 437 | DisplayServer.VSYNC_ENABLED: 438 | vsync_string = "V-Sync" 439 | DisplayServer.VSYNC_ADAPTIVE: 440 | vsync_string = "Adaptive V-Sync" 441 | DisplayServer.VSYNC_MAILBOX: 442 | vsync_string = "Mailbox V-Sync" 443 | 444 | if Engine.max_fps > 0 or OS.low_processor_usage_mode: 445 | # Display FPS cap determined by `Engine.max_fps` or low-processor usage mode sleep duration 446 | # (the lowest FPS cap is used). 447 | var low_processor_max_fps := roundi(1000000.0 / OS.low_processor_usage_mode_sleep_usec) 448 | var fps_cap := low_processor_max_fps 449 | if Engine.max_fps > 0: 450 | fps_cap = mini(Engine.max_fps, low_processor_max_fps) 451 | frame_time.text += " (cap: " + str(fps_cap) + " FPS" 452 | 453 | if not vsync_string.is_empty(): 454 | frame_time.text += " + " + vsync_string 455 | 456 | frame_time.text += ")" 457 | else: 458 | if not vsync_string.is_empty(): 459 | frame_time.text += " (" + vsync_string + ")" 460 | 461 | frame_number.text = "Frame: " + str(Engine.get_frames_drawn()) 462 | 463 | last_tick = Time.get_ticks_usec() 464 | 465 | 466 | func _on_visibility_changed() -> void: 467 | if visible: 468 | # Reset graphs to prevent them from looking strange before `HISTORY_NUM_FRAMES` frames 469 | # have been drawn. 470 | var frametime_last := (Time.get_ticks_usec() - last_tick) * 0.001 471 | fps_history.resize(HISTORY_NUM_FRAMES) 472 | fps_history.fill(1000.0 / frametime_last) 473 | frame_history_total.resize(HISTORY_NUM_FRAMES) 474 | frame_history_total.fill(frametime_last) 475 | frame_history_cpu.resize(HISTORY_NUM_FRAMES) 476 | var viewport_rid := get_viewport().get_viewport_rid() 477 | frame_history_cpu.fill(RenderingServer.viewport_get_measured_render_time_cpu(viewport_rid) + RenderingServer.get_frame_setup_time_cpu()) 478 | frame_history_gpu.resize(HISTORY_NUM_FRAMES) 479 | frame_history_gpu.fill(RenderingServer.viewport_get_measured_render_time_gpu(viewport_rid)) 480 | --------------------------------------------------------------------------------