├── .gitignore
├── LICENSE
├── README.md
├── addons
└── smart_graphics_settings
│ ├── LICENSE
│ ├── README.md
│ ├── adaptive_graphics.gd
│ ├── adaptive_graphics.gd.uid
│ ├── adaptive_graphics_ui.gd
│ ├── adaptive_graphics_ui.gd.uid
│ ├── adaptive_graphics_ui.tscn
│ ├── demo
│ ├── README.md
│ ├── demo_scene.gd
│ ├── demo_scene.gd.uid
│ └── demo_scene.tscn
│ ├── fps_monitor.gd
│ ├── fps_monitor.gd.uid
│ ├── graphics_settings_manager.gd
│ ├── graphics_settings_manager.gd.uid
│ ├── images
│ ├── icon.png
│ ├── icon.png.import
│ ├── icon.svg
│ ├── icon.svg.import
│ ├── logo.png
│ ├── logo.png.import
│ ├── logo.svg
│ ├── logo.svg.import
│ ├── smart-graphics-settings-icon.svg
│ ├── smart-graphics-settings-icon.svg.import
│ ├── title-text.png
│ └── title-text.png.import
│ ├── plugin.cfg
│ ├── plugin.gd
│ ├── plugin.gd.uid
│ ├── smart_graphics_settings.gd
│ ├── smart_graphics_settings.gd.uid
│ └── ui_panel_background.tres
└── project.godot
/.gitignore:
--------------------------------------------------------------------------------
1 | # Godot 4+ specific ignores
2 | .godot/
3 |
4 | # Godot-specific ignores
5 | .import/
6 | export.cfg
7 | export_presets.cfg
8 | *.translation
9 |
10 | # Mono-specific ignores
11 | .mono/
12 | data_*/
13 | mono_crash.*.json
14 |
15 | # Build artifacts
16 | *.o
17 | *.os
18 | *.obj
19 | *.bc
20 | *.pyc
21 | *.dblite
22 | *.pdb
23 | *.lib
24 | *.config
25 | *.creator
26 | *.creator.user
27 | *.files
28 | *.includes
29 | *.idb
30 |
31 | # Editor-specific ignores
32 | .vscode/
33 | .idea/
34 | .vs/
35 | *.swp
36 | *~
37 | *.tmp
38 | *.bak
39 |
40 | # OS-specific ignores
41 | .DS_Store
42 | ._*
43 | .Spotlight-V100
44 | .Trashes
45 | ehthumbs.db
46 | Thumbs.db
47 | desktop.ini
48 |
49 | # Demo exports
50 | addons/smart_graphics_settings/demo/exports/
51 | *.exe
52 | *.pck
53 | *.zip
54 |
55 | # Log files
56 | *.log
57 |
58 | # Temporary files
59 | *.tmp
60 | *.temp
61 |
62 | # Cursor
63 | .cursorrules
64 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Lucas Becker
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 |
2 |
3 |
4 |
5 | # Smart Graphics Settings for Godot 4.4
6 |
7 | A powerful adaptive graphics settings system for Godot 4.4 that automatically adjusts visual quality based on performance to maintain a smooth framerate.
8 |
9 | ## Features
10 |
11 | - **Adaptive Quality**: Automatically adjusts graphics settings to maintain target FPS
12 | - **Comprehensive Settings Management**: Controls render scale, anti-aliasing, shadows, reflections, and more
13 | - **User-friendly UI**: Built-in settings panel for players to customize their experience
14 | - **Performance Monitoring**: Real-time FPS tracking and performance analysis
15 | - **Platform-specific Optimizations**: Detects and applies optimal settings for different devices
16 | - **Fully Customizable**: Extensive configuration options for developers
17 |
18 | ## Installation
19 |
20 | 1. Download or clone this repository
21 | 2. Copy the `addons/smart_graphics_settings` folder into your Godot project's `addons` directory
22 | 3. Enable the plugin in Project Settings → Plugins
23 |
24 | ## Quick Start
25 |
26 | 1. Add the SmartGraphicsSettings node to your main scene or use the autoload singleton
27 | 2. Configure your desired target FPS and adjustment settings
28 | 3. Run your game - graphics will automatically adjust to maintain performance
29 |
30 | ## Access the SmartGraphicsSettings singleton directly
31 |
32 | ### Enable or disable adaptive graphics
33 | SmartGraphicsSettings.set_enabled(true)
34 |
35 | ### Set target FPS
36 | SmartGraphicsSettings.set_target_fps(60)
37 |
38 | ### Show the settings UI
39 | SmartGraphicsSettings.toggle_ui()
40 |
41 | ## Important Note on Initialization
42 |
43 | When accessing `SmartGraphicsSettings` properties like `adaptive_graphics` or calling functions like `set_target_fps` immediately in your script's `_ready()` function, you might encounter issues because the addon performs some initialization steps deferred (after `_ready()` has finished).
44 |
45 | To ensure the system is fully initialized before you interact with it at startup, connect to the `SmartGraphicsSettings.initialized` signal:
46 |
47 | ```gdscript
48 | func _ready():
49 | if SmartGraphicsSettings.get_adaptive_graphics(): # Check if already initialized
50 | _on_graphics_ready()
51 | else:
52 | SmartGraphicsSettings.initialized.connect(_on_graphics_ready)
53 |
54 | func _on_graphics_ready():
55 | # Now it's safe to access SmartGraphicsSettings functions and properties
56 | print("Smart Graphics Settings Ready!")
57 | SmartGraphicsSettings.set_target_fps(90)
58 | ```
59 |
60 | See the [technical documentation](addons/smart_graphics_settings/README.md#handling-initialization-timing) for more details.
61 |
62 | ## Demo
63 |
64 | A demo scene is included to showcase the functionality. Open `addons/smart_graphics_settings/demo/demo_scene.tscn` to try it out.
65 |
66 | ## Documentation
67 |
68 | For detailed documentation on all features and configuration options, see the [README](addons/smart_graphics_settings/README.md) in the addon directory.
69 |
70 | ## License
71 |
72 | This project is licensed under the MIT License - see the [LICENSE](addons/smart_graphics_settings/LICENSE) file for details.
73 |
74 | ## Credits
75 |
76 | Created by Lucas Becker. Open to community-submitted issues and pull requests.
77 |
78 | ## Support
79 |
80 | If you find this plugin useful, please consider:
81 |
82 | - Starring the repository on GitHub
83 | - Contributing to the project
84 | - Reporting any issues you encounter
85 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Lucas Becker
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/smart_graphics_settings/README.md:
--------------------------------------------------------------------------------
1 | # Smart Graphics Settings - Technical Documentation
2 |
3 | This document provides detailed technical information about the Smart Graphics Settings addon for Godot 4.4, including its architecture, available settings, and API reference.
4 |
5 | ## Architecture
6 |
7 | The addon consists of several key components:
8 |
9 | 1. **SmartGraphicsSettings** (`smart_graphics_settings.gd`): The main controller that manages the UI and serves as the entry point.
10 | 2. **AdaptiveGraphics** (`adaptive_graphics.gd`): Handles the automatic adjustment of graphics settings based on performance.
11 | 3. **GraphicsSettingsManager** (`graphics_settings_manager.gd`): Manages all available graphics settings and their application.
12 | 4. **FPSMonitor** (`fps_monitor.gd`): Monitors and analyzes framerate performance.
13 | 5. **AdaptiveGraphicsUI** (`adaptive_graphics_ui.gd`): Provides the user interface for adjusting settings.
14 |
15 | ## Installation and Setup
16 |
17 | ### Manual Installation
18 |
19 | 1. Copy the `addons/smart_graphics_settings` folder to your project's `addons` directory
20 | 2. Enable the plugin in Project Settings → Plugins
21 |
22 | ### Autoload Singleton (Recommended)
23 |
24 | The plugin automatically registers a singleton named `SmartGraphicsSettings`. To use it:
25 |
26 | ```gdscript
27 | SmartGraphicsSettings
28 | ```
29 |
30 | ### Manual Node Addition
31 |
32 | You can also add the SmartGraphicsSettings node manually to any scene:
33 |
34 | ```gdscript
35 | var settings_node: Node = preload("res://addons/smart_graphics_settings/smart_graphics_settings.gd").new()
36 | add_child(settings_node)
37 | ```
38 |
39 | ## Configuration Options
40 |
41 | ### AdaptiveGraphics Properties
42 |
43 | | Property | Type | Description |
44 | |----------|------|-------------|
45 | | `target_fps` | `int` | Target framerate to maintain (default: 60) |
46 | | `fps_tolerance` | `int` | Acceptable FPS range around target (default: 5) |
47 | | `adjustment_cooldown` | `float` | Seconds between adjustments (default: 3.0) |
48 | | `measurement_period` | `float` | Seconds to measure FPS before adjusting (default: 2.0) |
49 | | `enabled` | `bool` | Whether adaptive graphics is enabled (default: true) |
50 | | `allow_quality_increase` | `bool` | Whether to increase quality when FPS is high (default: false) |
51 | | `use_threading` | `bool` | Whether to use threading for adjustments (default: true) |
52 | | `setting_change_delay` | `float` | Seconds between applying each setting change (default: 0.5) |
53 | | `match_refresh_rate` | `bool` | Whether to match target FPS to display refresh rate (default: false) |
54 |
55 | ### Available Graphics Settings
56 |
57 | The following settings can be adjusted automatically or manually:
58 |
59 | #### Render Scale
60 |
61 | - Controls the internal rendering resolution
62 | - Priority: Highest (adjusted first)
63 | - Values: 0.5, 0.6, 0.7, 0.75, 0.8, 0.9, 1.0
64 |
65 | #### Anti-Aliasing
66 |
67 | - Controls the anti-aliasing method
68 | - Priority: High
69 | - Values: Off, FXAA, MSAA 2x, MSAA 4x, MSAA 8x, TAA
70 |
71 | #### Shadows
72 |
73 | - Controls shadow quality and resolution
74 | - Priority: Medium
75 | - Values: Off, Low, Medium, High, Ultra
76 |
77 | #### Reflections
78 |
79 | - Controls screen-space reflections quality
80 | - Priority: Low
81 | - Values: Off, Low, Medium, High
82 |
83 | #### Global Illumination
84 |
85 | - Controls global illumination method and quality
86 | - Priority: Lowest (adjusted last)
87 | - Values: Off, Low, Medium, High
88 |
89 | ## API Reference
90 |
91 | ### SmartGraphicsSettings
92 |
93 | ```gdscript
94 | # Signal emitted when the AdaptiveGraphics node has been initialized.
95 | # Connect to this signal to safely access adaptive_graphics after startup.
96 | signal initialized
97 |
98 | # Toggle the settings UI visibility
99 | func toggle_ui() -> void
100 |
101 | # Apply a specific quality preset (1-5)
102 | func apply_quality_preset(preset_index: int) -> void
103 |
104 | # Save current settings to config file
105 | func save_settings() -> void
106 |
107 | # Load settings from config file
108 | func load_settings() -> void
109 |
110 | # Get the adaptive graphics controller
111 | func get_adaptive_graphics() -> AdaptiveGraphics
112 | ```
113 |
114 | ### AdaptiveGraphics
115 |
116 | ```gdscript
117 | # Signal emitted when settings change
118 | signal changed_settings
119 |
120 | # Start the adaptive graphics system
121 | func start() -> void
122 |
123 | # Stop the adaptive graphics system
124 | func stop() -> void
125 |
126 | # Force an immediate adjustment
127 | func force_adjustment() -> void
128 |
129 | # Get the current action being performed
130 | func get_current_action() -> String
131 |
132 | # Set a specific setting value
133 | func set_setting(setting_name: String, value_index: int) -> void
134 |
135 | # Get the current value of a setting
136 | func get_setting(setting_name: String) -> int
137 | ```
138 |
139 | ### GraphicsSettingsManager
140 |
141 | ```gdscript
142 | # Apply a specific quality preset (1-5)
143 | func apply_preset(preset_index: int) -> void
144 |
145 | # Get all available settings
146 | func get_available_settings() -> Dictionary
147 |
148 | # Get the current value of a setting
149 | func get_setting_value(setting_name: String) -> Variant
150 |
151 | # Set a specific setting value
152 | func set_setting_value(setting_name: String, value_index: int) -> void
153 |
154 | # Register a custom setting
155 | func register_setting(setting_name: String, values: Array[Variant],
156 | current_index: int, priority: SettingPriority,
157 | type: SettingType) -> void
158 | ```
159 |
160 | ## Custom Settings
161 |
162 | You can register custom settings to be managed by the system:
163 |
164 | ```gdscript
165 | # Access SmartGraphicsSettings singleton directly
166 | # No need for get_node() as it's registered as an autoload
167 | var settings_manager = SmartGraphicsSettings.adaptive_graphics.settings_manager
168 |
169 | # Register a custom setting
170 | var values: Array[Variant] = [false, true]
171 | settings_manager.register_setting(
172 | "my_custom_setting",
173 | values,
174 | 1, # Current index (true)
175 | GraphicsSettingsManager.SettingPriority.POST_PROCESSING,
176 | GraphicsSettingsManager.SettingType.ENVIRONMENT
177 | )
178 |
179 | # Connect to the changed_settings signal
180 | SmartGraphicsSettings.adaptive_graphics.changed_settings.connect(_on_settings_changed)
181 |
182 | func _on_settings_changed() -> void:
183 | # Handle custom setting changes
184 | var custom_value = settings_manager.get_setting_value("my_custom_setting")
185 | print("Custom setting value: ", custom_value)
186 | ```
187 |
188 | ## Handling Initialization Timing
189 |
190 | Due to Godot's node initialization order and the use of `call_deferred` within the addon, the `SmartGraphicsSettings.adaptive_graphics` instance might not be immediately available when accessed from another node's `_ready()` function. Trying to access it too early can result in getting a `null` value.
191 |
192 | To safely access `adaptive_graphics` and its properties or methods at startup, you should connect to the `initialized` signal emitted by the `SmartGraphicsSettings` singleton. This signal is emitted once the internal `AdaptiveGraphics` node is ready.
193 |
194 | **Example:**
195 |
196 | ```gdscript
197 | # In a script that needs to customize settings (e.g., attached to your main scene root)
198 |
199 | func _ready():
200 | # Check if the singleton and its property are already available (e.g., if the scene is reloaded)
201 | if SmartGraphicsSettings != null and SmartGraphicsSettings.adaptive_graphics != null:
202 | _on_smart_graphics_ready()
203 | elif SmartGraphicsSettings != null:
204 | # Connect to the signal if not yet ready
205 | SmartGraphicsSettings.initialized.connect(_on_smart_graphics_ready)
206 | else:
207 | push_error("SmartGraphicsSettings singleton not found!")
208 |
209 | func _on_smart_graphics_ready():
210 | # Now it's safe to access adaptive_graphics
211 | if SmartGraphicsSettings.adaptive_graphics:
212 | print("SmartGraphicsSettings is ready! Customizing...")
213 | # Example: Set a custom target FPS
214 | SmartGraphicsSettings.set_target_fps(90)
215 | # Apply other custom settings here using SmartGraphicsSettings wrapper functions
216 | # or directly via SmartGraphicsSettings.adaptive_graphics if needed.
217 | else:
218 | push_error("Failed to get adaptive_graphics even after initialized signal.")
219 |
220 | ```
221 |
222 | ## Troubleshooting
223 |
224 | ### Performance Issues
225 |
226 | - If you experience stuttering during adjustments, try setting `use_threading` to false
227 | - Increase `adjustment_cooldown` to reduce the frequency of adjustments
228 |
229 | ### UI Issues
230 |
231 | - If the UI doesn't appear, check that the input action `toggle_graphics_settings` is properly registered
232 | - The default key to toggle the UI is F7
233 | - If you encounter errors related to `adaptive_graphics` being `null` at startup, ensure you are waiting for the `initialized` signal as described in the "Handling Initialization Timing" section.
234 |
235 | ### Custom Renderer Support
236 |
237 | - For custom renderers, you may need to manually register compatible settings
238 | - Use the `register_setting` method to add renderer-specific settings
239 |
240 | ## Best Practices
241 |
242 | 1. **Initialize Early**: Configure the addon's parameters (e.g., via Project Settings if applicable, or saved configuration files) before the game starts intensive rendering. For runtime access to the `adaptive_graphics` object from your scripts at startup, wait for the `SmartGraphicsSettings.initialized` signal.
243 | 2. **Default Presets**: Provide sensible default presets for different hardware capabilities
244 | 3. **Save User Preferences**: Always save and restore user settings between sessions
245 | 4. **Testing**: Test on various hardware configurations to ensure proper adaptation
246 | 5. **Feedback**: Provide visual feedback when settings are being adjusted automatically
247 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/adaptive_graphics.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | @icon("res://addons/smart_graphics_settings/images/smart-graphics-settings-icon.svg")
3 | class_name AdaptiveGraphics
4 | extends Node
5 |
6 | ## Signal emitted when graphics settings are changed
7 | signal changed_settings
8 |
9 | ## Target FPS to maintain
10 | @export var target_fps: int = 60
11 |
12 | ## Allow FPS to be within this range of target
13 | @export var fps_tolerance: int = 5
14 |
15 | ## Seconds between adjustments
16 | @export var adjustment_cooldown: float = 3.0
17 |
18 | ## Seconds to measure FPS before adjusting
19 | @export var measurement_period: float = 2.0
20 |
21 | ## Whether adaptive graphics is enabled
22 | @export var enabled: bool = true
23 |
24 | ## Whether to increase quality when FPS is high
25 | @export var allow_quality_increase: bool = false
26 |
27 | ## Whether to use threading for adjustments
28 | @export var use_threading: bool = true
29 |
30 | ## Seconds between applying each setting change
31 | @export var setting_change_delay: float = 0.5
32 |
33 | ## Whether to automatically set target FPS to match display refresh rate
34 | @export var match_refresh_rate: bool = false
35 |
36 | ## Internal state
37 | var fps_monitor: FPSMonitor
38 | var settings_manager: GraphicsSettingsManager
39 | var cooldown_timer: float = 0.0
40 | var measurement_timer: float = 0.0
41 | var is_measuring: bool = false
42 | var is_adjusting: bool = false
43 | var settings_changed: bool = false
44 | var threading_supported: bool = false
45 | var current_vsync_mode: int = -1
46 | var display_refresh_rate: float = 60.0
47 | var _current_action: String = "Idle" # Internal current action storage
48 |
49 | ## Threading components
50 | var adjustment_thread: Thread
51 | var adjustment_mutex: Mutex
52 | var thread_exit: bool = false
53 | var pending_adjustments: Array[Dictionary] = []
54 | var adjustment_timer: Timer
55 |
56 | ## Platform information for debugging
57 | var platform_info: Dictionary = {}
58 |
59 | ## Presets for quick configuration
60 | enum QualityPreset {
61 | ULTRA_LOW,
62 | LOW,
63 | MEDIUM,
64 | HIGH,
65 | ULTRA
66 | }
67 |
68 | ## Preset configurations
69 | var presets: Dictionary[int, Dictionary] = {
70 | QualityPreset.ULTRA_LOW: {
71 | "render_scale": 0, # 0.5
72 | "msaa": 0, # Disabled
73 | "shadow_quality": 0, # Disabled
74 | "shadow_size": 0, # 1024
75 | "fxaa": 0, # Disabled
76 | "ssao": 0, # Disabled
77 | "ssr": 0, # Disabled
78 | "sdfgi": 0, # Disabled
79 | "glow": 0, # Disabled
80 | "volumetric_fog": 0, # Disabled
81 | "dof": 0, # Disabled
82 | "motion_blur": 0 # Disabled
83 | },
84 | QualityPreset.LOW: {
85 | "render_scale": 1, # 0.6
86 | "msaa": 0, # Disabled
87 | "shadow_quality": 1, # Low
88 | "shadow_size": 0, # 1024
89 | "fxaa": 1, # Enabled
90 | "ssao": 0, # Disabled
91 | "ssr": 0, # Disabled
92 | "sdfgi": 0, # Disabled
93 | "glow": 1, # Enabled
94 | "volumetric_fog": 0, # Disabled
95 | "dof": 0, # Disabled
96 | "motion_blur": 0 # Disabled
97 | },
98 | QualityPreset.MEDIUM: {
99 | "render_scale": 3, # 0.8
100 | "msaa": 1, # 2X
101 | "shadow_quality": 2, # Medium
102 | "shadow_size": 1, # 2048
103 | "fxaa": 1, # Enabled
104 | "ssao": 1, # Enabled
105 | "ssao_quality": 1, # Low
106 | "ssr": 0, # Disabled
107 | "sdfgi": 0, # Disabled
108 | "glow": 1, # Enabled
109 | "volumetric_fog": 0, # Disabled
110 | "dof": 1, # Enabled
111 | "motion_blur": 0 # Disabled
112 | },
113 | QualityPreset.HIGH: {
114 | "render_scale": 4, # 0.9
115 | "msaa": 2, # 4X
116 | "shadow_quality": 3, # High
117 | "shadow_size": 2, # 4096
118 | "fxaa": 1, # Enabled
119 | "ssao": 1, # Enabled
120 | "ssao_quality": 2, # Medium
121 | "ssr": 1, # Enabled
122 | "ssr_max_steps": 2, # 32
123 | "sdfgi": 1, # Enabled
124 | "glow": 1, # Enabled
125 | "volumetric_fog": 1, # Enabled
126 | "volumetric_fog_density": 2, # 0.03
127 | "dof": 1, # Enabled
128 | "motion_blur": 1 # Enabled
129 | },
130 | QualityPreset.ULTRA: {
131 | "render_scale": 5, # 1.0
132 | "msaa": 3, # 8X
133 | "shadow_quality": 4, # Ultra
134 | "shadow_size": 3, # 8192
135 | "fxaa": 1, # Enabled
136 | "ssao": 1, # Enabled
137 | "ssao_quality": 3, # High
138 | "ssr": 1, # Enabled
139 | "ssr_max_steps": 3, # 64
140 | "sdfgi": 1, # Enabled
141 | "glow": 1, # Enabled
142 | "volumetric_fog": 1, # Enabled
143 | "volumetric_fog_density": 3, # 0.05
144 | "dof": 1, # Enabled
145 | "motion_blur": 1 # Enabled
146 | }
147 | }
148 |
149 | func _init() -> void:
150 | # Initialize current action
151 | current_action = "Initializing"
152 |
153 | # Gather platform information for better debugging
154 | platform_info = {
155 | "os_name": OS.get_name(),
156 | "model_name": OS.get_model_name(),
157 | "processor_name": OS.get_processor_name(),
158 | "processor_count": OS.get_processor_count()
159 | }
160 |
161 | # Check if threading is supported on this platform using multiple methods
162 | var has_threads_feature: bool = OS.has_feature("threads")
163 | var has_multiple_processors: bool = OS.get_processor_count() > 1
164 | var is_web_platform: bool = OS.has_feature("web")
165 |
166 | # Determine threading support based on platform and features
167 | threading_supported = has_threads_feature and not is_web_platform
168 |
169 | # Additional platform-specific checks
170 | if OS.get_name() == "Web":
171 | threading_supported = false
172 | print_debug("Smart Graphics Settings: Threading disabled on Web platform")
173 | elif OS.get_name() == "Android" or OS.get_name() == "iOS":
174 | # Mobile platforms may have threading limitations
175 | threading_supported = has_threads_feature and has_multiple_processors
176 | print_debug("Smart Graphics Settings: Mobile platform detected, threading support: ", threading_supported)
177 |
178 | # Apply mobile-specific optimizations
179 | fps_tolerance = 8 # Allow more variation on mobile
180 | adjustment_cooldown = 5.0 # Less frequent adjustments to save battery
181 | measurement_period = 3.0 # Longer measurement period for more stable readings
182 | elif OS.get_name() == "Windows" or OS.get_name() == "macOS" or OS.get_name() == "Linux":
183 | # Desktop platforms can use more precise settings
184 | fps_tolerance = 3
185 | adjustment_cooldown = 2.0
186 | measurement_period = 1.5
187 |
188 | # Default to threaded mode if supported
189 | use_threading = threading_supported
190 |
191 | # Log threading support status
192 | print_debug("Smart Graphics Settings: Threading support detected: ", threading_supported)
193 | if threading_supported:
194 | print_debug("Smart Graphics Settings: Using ", OS.get_processor_count(), " processors")
195 | else:
196 | print_debug("Smart Graphics Settings: Threading disabled, using single-threaded mode")
197 |
198 | # Set current action to idle after initialization
199 | current_action = "Idle"
200 |
201 | func _ready() -> void:
202 | if not Engine.is_editor_hint():
203 | current_action = "Setting Up"
204 |
205 | # Initialize FPS monitor
206 | fps_monitor = FPSMonitor.new()
207 | add_child(fps_monitor)
208 |
209 | # Setup threading if supported
210 | if threading_supported and use_threading:
211 | setup_threading()
212 |
213 | # Get the current VSync mode and display refresh rate
214 | update_vsync_and_refresh_rate()
215 |
216 | # If match_refresh_rate is enabled, set target FPS to match display refresh rate
217 | if match_refresh_rate:
218 | target_fps = int(display_refresh_rate)
219 | if target_fps <= 0: # Fallback if we couldn't get the refresh rate
220 | target_fps = 60
221 |
222 | settings_manager = GraphicsSettingsManager.new()
223 | add_child(settings_manager)
224 |
225 | # Try to load saved settings
226 | settings_manager.load_graphics_settings()
227 |
228 | # Create timer for staggered setting application
229 | adjustment_timer = Timer.new()
230 | adjustment_timer.one_shot = true
231 | adjustment_timer.timeout.connect(_on_adjustment_timer_timeout)
232 | add_child(adjustment_timer)
233 |
234 | current_action = "Ready"
235 |
236 | # Set to Idle after a short delay to allow UI to update
237 | await get_tree().create_timer(0.5).timeout
238 | current_action = "Idle"
239 |
240 | func _exit_tree() -> void:
241 | # Clean up threading resources
242 | if threading_supported and use_threading and adjustment_thread != null and adjustment_thread.is_started():
243 | thread_exit = true
244 | adjustment_thread.wait_to_finish()
245 |
246 | func setup_threading() -> void:
247 | # Create mutex for thread synchronization
248 | adjustment_mutex = Mutex.new()
249 |
250 | # Create timer for delayed setting changes
251 | adjustment_timer = Timer.new()
252 | adjustment_timer.wait_time = setting_change_delay
253 | adjustment_timer.one_shot = true
254 | adjustment_timer.timeout.connect(_on_adjustment_timer_timeout)
255 | add_child(adjustment_timer)
256 |
257 | # Start adjustment thread
258 | thread_exit = false
259 | adjustment_thread = Thread.new()
260 | var thread_start_error = adjustment_thread.start(thread_function)
261 |
262 | if thread_start_error != OK:
263 | push_error("Smart Graphics Settings: Failed to start adjustment thread. Error code: ", thread_start_error)
264 | threading_supported = false
265 | use_threading = false
266 | print_debug("Smart Graphics Settings: Falling back to single-threaded mode")
267 | else:
268 | print_debug("Smart Graphics Settings: Adjustment thread started successfully")
269 |
270 | func _thread_evaluate_performance() -> void:
271 | # Get a thread-safe copy of FPS data
272 | var avg_fps: float = fps_monitor.get_average_fps()
273 | var is_stable: bool = fps_monitor.is_fps_stable()
274 |
275 | # Reset measuring flag with proper locking
276 | adjustment_mutex.lock()
277 | is_measuring = false
278 | var current_target_fps: int = target_fps
279 | var current_fps_tolerance: int = fps_tolerance
280 | var current_allow_increase: bool = allow_quality_increase
281 | adjustment_mutex.unlock()
282 |
283 | # Get the effective maximum FPS based on VSync settings
284 | var max_fps: float = get_effective_max_fps()
285 | var effective_target: int = current_target_fps
286 |
287 | # If VSync is limiting our FPS and our target is higher, adjust the target
288 | if max_fps > 0 and current_target_fps > max_fps:
289 | effective_target = int(max_fps)
290 |
291 | # Only adjust if FPS is stable (to avoid reacting to temporary spikes)
292 | if is_stable:
293 | if avg_fps < effective_target - current_fps_tolerance:
294 | queue_quality_decrease()
295 |
296 | adjustment_mutex.lock()
297 | cooldown_timer = adjustment_cooldown
298 | adjustment_mutex.unlock()
299 |
300 | call_deferred("set_settings_changed", true)
301 | elif current_allow_increase and avg_fps > effective_target + current_fps_tolerance * 2:
302 | queue_quality_increase()
303 |
304 | adjustment_mutex.lock()
305 | cooldown_timer = adjustment_cooldown * 2 # Longer cooldown for increases
306 | adjustment_mutex.unlock()
307 |
308 | call_deferred("set_settings_changed", true)
309 | else:
310 | # If FPS is not stable, just reset the measuring state
311 | adjustment_mutex.lock()
312 | is_adjusting = false
313 | adjustment_mutex.unlock()
314 |
315 | ## Helper function to safely set settings_changed from thread
316 | func set_settings_changed(value: bool) -> void:
317 | adjustment_mutex.lock()
318 | settings_changed = value
319 | adjustment_mutex.unlock()
320 |
321 | if value:
322 | settings_manager.save_graphics_settings()
323 |
324 | func _process(delta: float) -> void:
325 | # Skip processing in editor
326 | if Engine.is_editor_hint() or not enabled:
327 | return
328 |
329 | # Update timers with proper locking
330 | adjustment_mutex.lock()
331 | if cooldown_timer > 0:
332 | cooldown_timer -= delta
333 |
334 | var current_cooldown: float = cooldown_timer
335 | var current_is_measuring: bool = is_measuring
336 | var current_is_adjusting: bool = is_adjusting
337 | var current_use_threading: bool = use_threading
338 | var current_threading_supported: bool = threading_supported
339 | adjustment_mutex.unlock()
340 |
341 | if not current_use_threading or not current_threading_supported:
342 | # Synchronous mode
343 | if current_is_measuring:
344 | adjustment_mutex.lock()
345 | measurement_timer -= delta
346 | var current_measurement_timer: float = measurement_timer
347 | adjustment_mutex.unlock()
348 |
349 | if current_measurement_timer <= 0:
350 | adjustment_mutex.lock()
351 | is_measuring = false
352 | adjustment_mutex.unlock()
353 | evaluate_performance()
354 | elif current_cooldown <= 0 and not current_is_adjusting:
355 | # Start a new measurement period
356 | start_measurement()
357 | else:
358 | # Threaded mode - just start measurement when ready
359 | if current_cooldown <= 0 and not current_is_measuring and not current_is_adjusting:
360 | # Check if there are pending adjustments
361 | adjustment_mutex.lock()
362 | var has_pending: bool = not pending_adjustments.is_empty()
363 | adjustment_mutex.unlock()
364 |
365 | if not has_pending:
366 | start_measurement()
367 |
368 | func start_measurement() -> void:
369 | fps_monitor.clear_history()
370 |
371 | adjustment_mutex.lock()
372 | measurement_timer = measurement_period
373 | is_measuring = true
374 | current_action = "Measuring Performance..."
375 | adjustment_mutex.unlock()
376 |
377 | func evaluate_performance() -> void:
378 | current_action = "Analyzing Performance..."
379 |
380 | var avg_fps: float = fps_monitor.get_average_fps()
381 | var is_stable: bool = fps_monitor.is_fps_stable()
382 |
383 | # Get the effective maximum FPS based on VSync settings
384 | var max_fps = get_effective_max_fps()
385 | var effective_target = target_fps
386 |
387 | # If VSync is limiting our FPS and our target is higher, adjust the target
388 | if max_fps > 0 and target_fps > max_fps:
389 | effective_target = max_fps
390 |
391 | # Only adjust if FPS is stable (to avoid reacting to temporary spikes)
392 | if is_stable:
393 | if avg_fps < effective_target - fps_tolerance:
394 | decrease_quality()
395 | cooldown_timer = adjustment_cooldown
396 | settings_changed = true
397 | elif allow_quality_increase and avg_fps > effective_target + fps_tolerance * 2:
398 | increase_quality()
399 | cooldown_timer = adjustment_cooldown * 2 # Longer cooldown for increases
400 | settings_changed = true
401 |
402 | # Save settings if they've changed
403 | if settings_changed:
404 | settings_manager.save_graphics_settings()
405 | settings_changed = false
406 |
407 | current_action = "Performance Analysis Complete"
408 | await get_tree().create_timer(1.0).timeout
409 | current_action = "Monitoring Performance"
410 |
411 | func decrease_quality() -> void:
412 | is_adjusting = true
413 | current_action = "Optimizing Performance..."
414 |
415 | # Try to decrease quality of settings in priority order
416 | for setting_name in settings_manager.get_settings_by_priority():
417 | if settings_manager.decrease_setting_quality(setting_name):
418 | print("Decreased quality of ", setting_name, " to maintain target FPS")
419 | current_action = "Decreased " + setting_name + " Quality"
420 | is_adjusting = false
421 | return
422 |
423 | is_adjusting = false
424 | current_action = "Monitoring Performance"
425 |
426 | func increase_quality() -> void:
427 | is_adjusting = true
428 | current_action = "Improving Visual Quality..."
429 |
430 | # Try to increase quality of settings in reverse priority order
431 | var settings: Array[String] = settings_manager.get_settings_by_priority()
432 | settings.reverse()
433 |
434 | for setting_name in settings:
435 | if settings_manager.increase_setting_quality(setting_name):
436 | print("Increased quality of ", setting_name, " as performance allows")
437 | current_action = "Increased " + setting_name + " Quality"
438 | is_adjusting = false
439 | return
440 |
441 | is_adjusting = false
442 | current_action = "Monitoring Performance"
443 |
444 | ## Queue a quality decrease for threaded processing
445 | func queue_quality_decrease() -> void:
446 | adjustment_mutex.lock()
447 | is_adjusting = true
448 | current_action = "Preparing Performance Optimization..."
449 | adjustment_mutex.unlock()
450 |
451 | # Try to decrease quality of settings in priority order
452 | for setting_name in settings_manager.get_settings_by_priority():
453 | var setting: GraphicsSettingsManager.Setting = settings_manager.available_settings[setting_name]
454 | if setting.current_index > 0:
455 | var new_index: int = setting.current_index - 1
456 |
457 | adjustment_mutex.lock()
458 | pending_adjustments.append({
459 | "setting_name": setting_name,
460 | "index": new_index,
461 | "is_decrease": true
462 | })
463 | adjustment_mutex.unlock()
464 |
465 | # Start processing the queue if not already processing
466 | call_deferred("process_next_adjustment")
467 |
468 | print("Queued decrease of quality for ", setting_name)
469 | return
470 |
471 | adjustment_mutex.lock()
472 | is_adjusting = false
473 | current_action = "Monitoring Performance"
474 | adjustment_mutex.unlock()
475 |
476 | ## Queue a quality increase for threaded processing
477 | func queue_quality_increase() -> void:
478 | adjustment_mutex.lock()
479 | is_adjusting = true
480 | current_action = "Preparing Quality Improvement..."
481 | adjustment_mutex.unlock()
482 |
483 | # Try to increase quality of settings in reverse priority order
484 | var settings: Array[String] = settings_manager.get_settings_by_priority()
485 | settings.reverse()
486 |
487 | for setting_name in settings:
488 | var setting: GraphicsSettingsManager.Setting = settings_manager.available_settings[setting_name]
489 | if setting.current_index < setting.values.size() - 1:
490 | var new_index: int = setting.current_index + 1
491 |
492 | adjustment_mutex.lock()
493 | pending_adjustments.append({
494 | "setting_name": setting_name,
495 | "index": new_index,
496 | "is_decrease": false
497 | })
498 | adjustment_mutex.unlock()
499 |
500 | # Start processing the queue if not already processing
501 | call_deferred("process_next_adjustment")
502 |
503 | print("Queued increase of quality for ", setting_name)
504 | return
505 |
506 | adjustment_mutex.lock()
507 | is_adjusting = false
508 | current_action = "Monitoring Performance"
509 | adjustment_mutex.unlock()
510 |
511 | ## Process the next adjustment in the queue
512 | func process_next_adjustment() -> void:
513 | adjustment_mutex.lock()
514 | var has_adjustments: bool = not pending_adjustments.is_empty()
515 | var adjustment: Dictionary = {}
516 |
517 | if has_adjustments:
518 | adjustment = pending_adjustments[0]
519 | pending_adjustments.remove_at(0)
520 |
521 | adjustment_mutex.unlock()
522 |
523 | if has_adjustments:
524 | var setting_name: String = adjustment.setting_name
525 | var new_index: int = adjustment.index
526 | var is_decrease: bool = adjustment.is_decrease
527 |
528 | # Update current action
529 | adjustment_mutex.lock()
530 | if is_decrease:
531 | current_action = "Decreasing " + setting_name + " Quality..."
532 | else:
533 | current_action = "Increasing " + setting_name + " Quality..."
534 | adjustment_mutex.unlock()
535 |
536 | # Apply the setting change
537 | settings_manager.available_settings[setting_name].current_index = new_index
538 | settings_manager.apply_setting(setting_name)
539 |
540 | # Emit the changed_settings signal
541 | emit_signal("changed_settings")
542 |
543 | if is_decrease:
544 | print("Decreased quality of ", setting_name, " to maintain target FPS")
545 | else:
546 | print("Increased quality of ", setting_name, " as performance allows")
547 |
548 | # Schedule the next adjustment after a delay
549 | adjustment_mutex.lock()
550 | var has_more: bool = not pending_adjustments.is_empty()
551 | adjustment_mutex.unlock()
552 |
553 | if has_more:
554 | adjustment_timer.start(setting_change_delay)
555 | else:
556 | # No more adjustments, reset state and start cooldown
557 | adjustment_mutex.lock()
558 | is_adjusting = false
559 | current_action = "Monitoring Performance"
560 | cooldown_timer = adjustment_cooldown
561 | adjustment_mutex.unlock()
562 |
563 | ## Timer callback for staggered setting application
564 | func _on_adjustment_timer_timeout() -> void:
565 | process_next_adjustment()
566 |
567 | ## Apply a quality preset
568 | func apply_preset(preset: QualityPreset) -> void:
569 | if not presets.has(preset):
570 | return
571 |
572 | var preset_settings: Dictionary = get_preset_for_current_renderer(preset)
573 |
574 | # Stop any ongoing adjustments
575 | if use_threading and threading_supported:
576 | adjustment_mutex.lock()
577 | pending_adjustments.clear()
578 | is_adjusting = true
579 | current_action = "Applying " + QualityPreset.keys()[preset] + " Quality Preset..."
580 | adjustment_mutex.unlock()
581 | else:
582 | is_adjusting = true
583 | current_action = "Applying " + QualityPreset.keys()[preset] + " Quality Preset..."
584 |
585 | # Apply all preset settings
586 | for setting_name in preset_settings:
587 | if settings_manager.available_settings.has(setting_name) and settings_manager.is_setting_applicable(setting_name):
588 | settings_manager.available_settings[setting_name].current_index = preset_settings[setting_name]
589 | settings_manager.apply_setting(setting_name)
590 |
591 | settings_changed = true
592 | settings_manager.save_graphics_settings()
593 |
594 | # Emit the changed_settings signal
595 | emit_signal("changed_settings")
596 |
597 | # Reset state
598 | if use_threading and threading_supported:
599 | adjustment_mutex.lock()
600 | is_adjusting = false
601 | current_action = "Applied " + QualityPreset.keys()[preset] + " Quality Preset"
602 | adjustment_mutex.unlock()
603 | else:
604 | is_adjusting = false
605 | current_action = "Applied " + QualityPreset.keys()[preset] + " Quality Preset"
606 |
607 | # Show the completion message briefly before returning to monitoring
608 | await get_tree().create_timer(1.0).timeout
609 | current_action = "Monitoring Performance"
610 |
611 | cooldown_timer = adjustment_cooldown
612 | print("Applied preset: ", QualityPreset.keys()[preset])
613 |
614 | ## Get a preset adjusted for the current renderer
615 | func get_preset_for_current_renderer(preset_type: QualityPreset) -> Dictionary:
616 | var base_preset: Dictionary = presets[preset_type].duplicate()
617 | var renderer_type: GraphicsSettingsManager.RendererType = settings_manager.current_renderer
618 |
619 | # Filter out settings that don't apply to the current renderer
620 | var filtered_preset: Dictionary = {}
621 | for setting_name in base_preset:
622 | if settings_manager.is_setting_applicable(setting_name):
623 | filtered_preset[setting_name] = base_preset[setting_name]
624 |
625 | return filtered_preset
626 |
627 | ## Toggle threading mode
628 | func set_threading_enabled(enabled: bool) -> void:
629 | if enabled == use_threading:
630 | return
631 |
632 | if enabled and not threading_supported:
633 | push_warning("Smart Graphics Settings: Threading is not supported on this platform")
634 | return
635 |
636 | use_threading = enabled
637 |
638 | # If enabling threading and it's supported, set up the thread
639 | if use_threading and threading_supported:
640 | if adjustment_thread == null or not adjustment_thread.is_started():
641 | setup_threading()
642 | # If disabling threading, clean up the thread
643 | elif adjustment_thread != null and adjustment_thread.is_started():
644 | cleanup_threading()
645 |
646 | ## Updates the current VSync mode and display refresh rate
647 | func update_vsync_and_refresh_rate() -> void:
648 | current_vsync_mode = DisplayServer.window_get_vsync_mode()
649 | display_refresh_rate = DisplayServer.screen_get_refresh_rate()
650 | if display_refresh_rate <= 0: # Fallback if we couldn't get the refresh rate
651 | display_refresh_rate = 60.0
652 |
653 | ## Get the effective maximum FPS based on VSync settings
654 | func get_effective_max_fps() -> float:
655 | update_vsync_and_refresh_rate()
656 |
657 | # If VSync is enabled or adaptive, the max FPS is limited by the refresh rate
658 | if current_vsync_mode == DisplayServer.VSYNC_ENABLED:
659 | return display_refresh_rate
660 | elif current_vsync_mode == DisplayServer.VSYNC_ADAPTIVE:
661 | # For adaptive VSync, we can go below the refresh rate but not above
662 | return display_refresh_rate
663 |
664 | # For disabled VSync or mailbox, there's no upper limit
665 | return 0.0 # 0 means no limit
666 |
667 | ## Set the target FPS to match the display refresh rate
668 | func set_target_fps_to_refresh_rate() -> void:
669 | update_vsync_and_refresh_rate()
670 | target_fps = int(display_refresh_rate)
671 | if target_fps <= 0: # Fallback if we couldn't get the refresh rate
672 | target_fps = 60
673 |
674 | ## Set the VSync mode
675 | func set_vsync_mode(mode: int) -> void:
676 | DisplayServer.window_set_vsync_mode(mode)
677 | update_vsync_and_refresh_rate()
678 |
679 | # If match_refresh_rate is enabled, update the target FPS
680 | if match_refresh_rate:
681 | set_target_fps_to_refresh_rate()
682 |
683 | ## Get detailed threading support information
684 | func get_threading_support_info() -> Dictionary:
685 | return {
686 | "threading_supported": threading_supported,
687 | "use_threading": use_threading,
688 | "platform": OS.get_name(),
689 | "processor_count": OS.get_processor_count(),
690 | "has_threads_feature": OS.has_feature("threads"),
691 | "is_web_platform": OS.has_feature("web"),
692 | "thread_active": adjustment_thread != null and adjustment_thread.is_started() if threading_supported else false
693 | }
694 |
695 | ## Thread function for performance evaluation
696 | func thread_function() -> void:
697 | while not thread_exit:
698 | # Sleep to avoid busy waiting
699 | OS.delay_msec(100)
700 |
701 | # Check if we need to analyze performance
702 | var should_analyze: bool = false
703 |
704 | adjustment_mutex.lock()
705 | should_analyze = is_measuring and fps_monitor.fps_history.size() >= fps_monitor.history_size
706 | adjustment_mutex.unlock()
707 |
708 | if should_analyze:
709 | _thread_evaluate_performance()
710 |
711 | ## Clean up threading resources
712 | func cleanup_threading() -> void:
713 | if adjustment_thread != null and adjustment_thread.is_started():
714 | # Signal thread to exit
715 | thread_exit = true
716 | # Wait for thread to finish
717 | adjustment_thread.wait_to_finish()
718 | adjustment_thread = null
719 |
720 | # Clean up mutex
721 | adjustment_mutex = null
722 |
723 | # Remove timer if it exists
724 | if adjustment_timer != null and adjustment_timer.is_inside_tree():
725 | adjustment_timer.queue_free()
726 | adjustment_timer = null
727 |
728 | print_debug("Smart Graphics Settings: Threading resources cleaned up")
729 |
730 | ## Get the current action in a thread-safe way
731 | func get_current_action() -> String:
732 | if use_threading and threading_supported and adjustment_mutex != null:
733 | adjustment_mutex.lock()
734 | var action = _current_action
735 | adjustment_mutex.unlock()
736 | return action
737 | return _current_action
738 |
739 | ## Set the current action in a thread-safe way
740 | func set_current_action(action: String) -> void:
741 | if use_threading and threading_supported and adjustment_mutex != null:
742 | adjustment_mutex.lock()
743 | _current_action = action
744 | adjustment_mutex.unlock()
745 | else:
746 | _current_action = action
747 |
748 | ## Current action property
749 | var current_action: String:
750 | get:
751 | return get_current_action()
752 | set(value):
753 | set_current_action(value)
754 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/adaptive_graphics.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bmw8w2g48j3tt
2 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/adaptive_graphics_ui.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | @icon("res://addons/smart_graphics_settings/images/smart-graphics-settings-icon.svg")
3 | class_name AdaptiveGraphicsUI
4 | extends Control
5 |
6 | ## Path to the AdaptiveGraphics node
7 | @export var adaptive_graphics_path: NodePath = NodePath("")
8 |
9 | ## Reference to the AdaptiveGraphics node
10 | var adaptive_graphics: AdaptiveGraphics
11 |
12 | ## UI Controls
13 | @onready var target_fps_slider: HSlider = $CenterContainer/PanelContainer/MarginContainer/VBoxContent/TargetFPSContainer/TargetFPSSlider
14 | @onready var target_fps_value: SpinBox = $CenterContainer/PanelContainer/MarginContainer/VBoxContent/TargetFPSContainer/TargetFPSValue
15 | @onready var target_fps_reset: Button = $CenterContainer/PanelContainer/MarginContainer/VBoxContent/TargetFPSContainer/ResetButton
16 | @onready var enabled_checkbox: CheckBox = $CenterContainer/PanelContainer/MarginContainer/VBoxContent/EnabledCheckbox
17 | @onready var allow_increase_checkbox: CheckBox = $CenterContainer/PanelContainer/MarginContainer/VBoxContent/AllowIncreaseCheckbox
18 | @onready var threading_checkbox: CheckBox = $CenterContainer/PanelContainer/MarginContainer/VBoxContent/ThreadingCheckbox
19 | @onready var match_refresh_rate_checkbox: CheckBox = $CenterContainer/PanelContainer/MarginContainer/VBoxContent/MatchRefreshRateCheckbox
20 | @onready var vsync_option: OptionButton = $CenterContainer/PanelContainer/MarginContainer/VBoxContent/VSyncOption
21 | @onready var preset_option: OptionButton = $CenterContainer/PanelContainer/MarginContainer/VBoxContent/PresetOption
22 | @onready var fps_label: Label = $CenterContainer/PanelContainer/MarginContainer/VBoxContent/FPSLabel
23 | @onready var status_label: Label = $CenterContainer/PanelContainer/MarginContainer/VBoxContent/StatusLabel
24 | @onready var renderer_label: Label = $CenterContainer/PanelContainer/MarginContainer/VBoxContent/RendererLabel
25 |
26 | ## Default target FPS value
27 | var default_target_fps: int = 60
28 |
29 | ## Timer for updating FPS display
30 | var update_timer: Timer
31 |
32 | ## Current quality preset (for UI feedback)
33 | var current_preset: String = "Custom"
34 |
35 | ## Current settings state (for detecting changes)
36 | var current_settings: Dictionary = {}
37 |
38 | func _ready() -> void:
39 | # If adaptive_graphics is already set directly, use it
40 | if adaptive_graphics:
41 | # Already set directly, no need to look up
42 | pass
43 | # Otherwise, try to get the AdaptiveGraphics node from the path
44 | elif not adaptive_graphics_path.is_empty():
45 | adaptive_graphics = get_node_or_null(adaptive_graphics_path)
46 |
47 | # If we couldn't get it from the path, try to find it through the singleton
48 | if not adaptive_graphics and Engine.has_singleton("SmartGraphicsSettings"):
49 | # Access SmartGraphicsSettings directly as an autoload
50 | if SmartGraphicsSettings and SmartGraphicsSettings.has_method("get_adaptive_graphics"):
51 | adaptive_graphics = SmartGraphicsSettings.get_adaptive_graphics()
52 |
53 | if not adaptive_graphics:
54 | push_error("AdaptiveGraphicsUI: Failed to find AdaptiveGraphics node at path: " + str(adaptive_graphics_path))
55 | return
56 |
57 | # Connect to the settings_changed signal
58 | if not adaptive_graphics.changed_settings.is_connected(_on_changed_settings):
59 | adaptive_graphics.changed_settings.connect(_on_changed_settings)
60 |
61 | # Store default target FPS
62 | default_target_fps = adaptive_graphics.target_fps
63 |
64 | # Initialize UI with current values
65 | target_fps_slider.value = adaptive_graphics.target_fps
66 | target_fps_value.value = adaptive_graphics.target_fps
67 | enabled_checkbox.button_pressed = adaptive_graphics.enabled
68 | allow_increase_checkbox.button_pressed = adaptive_graphics.allow_quality_increase
69 |
70 | # Setup threading checkbox
71 | threading_checkbox.button_pressed = adaptive_graphics.use_threading
72 | threading_checkbox.disabled = not adaptive_graphics.threading_supported
73 | if not adaptive_graphics.threading_supported:
74 | var platform_name: String = OS.get_name()
75 | threading_checkbox.tooltip_text = "Threading not supported on this platform (%s)" % platform_name
76 |
77 | # Add a small warning icon next to the checkbox if threading is not supported
78 | var warning_icon = TextureRect.new()
79 | warning_icon.texture = get_theme_icon("NodeWarning", "EditorIcons")
80 | warning_icon.tooltip_text = "Threading is not available on %s or has been disabled due to platform limitations. The extension will use single-threaded mode instead." % platform_name
81 | warning_icon.custom_minimum_size = Vector2(16, 16)
82 | warning_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
83 |
84 | # Add the warning icon as a sibling to the checkbox
85 | var parent = threading_checkbox.get_parent()
86 | parent.add_child(warning_icon)
87 | parent.move_child(warning_icon, threading_checkbox.get_index() + 1)
88 | else:
89 | # Add processor count information to the tooltip
90 | var processor_count = OS.get_processor_count()
91 | threading_checkbox.tooltip_text = "Enable multi-threaded processing (%d processors available)" % processor_count
92 |
93 | # Setup match refresh rate checkbox
94 | if match_refresh_rate_checkbox:
95 | match_refresh_rate_checkbox.button_pressed = adaptive_graphics.match_refresh_rate
96 | match_refresh_rate_checkbox.tooltip_text = "Set target FPS to match display refresh rate (%d Hz)" % int(adaptive_graphics.display_refresh_rate)
97 |
98 | # Apply simple theme to dropdowns for Godot 4.4
99 | _apply_simple_dropdown_theme()
100 |
101 | # Setup VSync dropdown - Simple approach for Godot 4.4
102 | if vsync_option:
103 | # Clear existing items
104 | vsync_option.clear()
105 |
106 | # Add VSync options
107 | vsync_option.add_item("Disabled", 0)
108 | vsync_option.add_item("Enabled", 1)
109 | vsync_option.add_item("Adaptive", 2)
110 | vsync_option.add_item("Mailbox", 3)
111 |
112 | # Select the current VSync mode
113 | adaptive_graphics.update_vsync_and_refresh_rate()
114 | vsync_option.select(adaptive_graphics.current_vsync_mode)
115 |
116 | # Update tooltip with current refresh rate
117 | vsync_option.tooltip_text = "Control vertical synchronization with the display (Refresh rate: %d Hz)" % int(adaptive_graphics.display_refresh_rate)
118 |
119 | # Setup preset dropdown - Simple approach for Godot 4.4
120 | preset_option.clear()
121 | preset_option.add_item("Ultra Low", AdaptiveGraphics.QualityPreset.ULTRA_LOW)
122 | preset_option.add_item("Low", AdaptiveGraphics.QualityPreset.LOW)
123 | preset_option.add_item("Medium", AdaptiveGraphics.QualityPreset.MEDIUM)
124 | preset_option.add_item("High", AdaptiveGraphics.QualityPreset.HIGH)
125 | preset_option.add_item("Ultra", AdaptiveGraphics.QualityPreset.ULTRA)
126 | preset_option.add_item("Custom", -1)
127 |
128 | # Store current settings for change detection
129 | _store_current_settings()
130 |
131 | # Detect and select the current preset
132 | current_preset = _detect_preset_from_settings()
133 |
134 | # Select the appropriate preset in the dropdown
135 | if current_preset == "Custom":
136 | # Select the "Custom" option (last item)
137 | preset_option.select(preset_option.item_count - 1)
138 | else:
139 | # Find the preset by name and select it
140 | var preset_index: int = AdaptiveGraphics.QualityPreset.get(current_preset, -1)
141 | if preset_index >= 0:
142 | for i in range(preset_option.item_count):
143 | if preset_option.get_item_id(i) == preset_index:
144 | preset_option.select(i)
145 | break
146 |
147 | # Create timer for updating UI
148 | update_timer = Timer.new()
149 | update_timer.wait_time = 0.5
150 | update_timer.timeout.connect(_on_update_timer_timeout)
151 | update_timer.autostart = true
152 | add_child(update_timer)
153 |
154 | # Update UI immediately
155 | _update_ui()
156 |
157 | func _store_current_settings() -> void:
158 | if not adaptive_graphics or not adaptive_graphics.settings_manager:
159 | return
160 |
161 | current_settings.clear()
162 |
163 | for setting_name in adaptive_graphics.settings_manager.available_settings:
164 | if adaptive_graphics.settings_manager.is_setting_applicable(setting_name):
165 | var setting = adaptive_graphics.settings_manager.available_settings[setting_name]
166 | current_settings[setting_name] = setting.current_index
167 |
168 | func _detect_preset_from_settings() -> String:
169 | if not adaptive_graphics:
170 | return "Custom"
171 |
172 | # Check if current settings match any preset
173 | for preset_name in AdaptiveGraphics.QualityPreset.keys():
174 | var preset_index = AdaptiveGraphics.QualityPreset[preset_name]
175 | if _settings_match_preset(preset_index):
176 | return preset_name
177 |
178 | return "Custom"
179 |
180 | func _settings_match_preset(preset_index: int) -> bool:
181 | if not adaptive_graphics or not adaptive_graphics.settings_manager:
182 | return false
183 |
184 | if not adaptive_graphics.presets.has(preset_index):
185 | return false
186 |
187 | var preset_settings = adaptive_graphics.get_preset_for_current_renderer(preset_index)
188 |
189 | for setting_name in preset_settings:
190 | if not adaptive_graphics.settings_manager.available_settings.has(setting_name):
191 | continue
192 |
193 | if not adaptive_graphics.settings_manager.is_setting_applicable(setting_name):
194 | continue
195 |
196 | var current_index = adaptive_graphics.settings_manager.available_settings[setting_name].current_index
197 | if current_index != preset_settings[setting_name]:
198 | return false
199 |
200 | return true
201 |
202 | func _apply_simple_dropdown_theme() -> void:
203 | # Simple theme adjustments for Godot 4.4
204 | if vsync_option:
205 | vsync_option.custom_minimum_size.y = 30
206 |
207 | if preset_option:
208 | preset_option.custom_minimum_size.y = 30
209 |
210 | func _on_update_timer_timeout() -> void:
211 | _update_ui()
212 |
213 | func _update_ui() -> void:
214 | if not adaptive_graphics:
215 | return
216 |
217 | # Update FPS display
218 | if fps_label:
219 | # Make sure the FPS monitor exists
220 | if adaptive_graphics.fps_monitor:
221 | var avg_fps: float = adaptive_graphics.fps_monitor.get_average_fps()
222 | var is_stable: bool = adaptive_graphics.fps_monitor.is_fps_stable()
223 | var stability_text: String = "stable" if is_stable else "unstable"
224 |
225 | # Ensure we're not showing 0 FPS
226 | if avg_fps < 0.1:
227 | avg_fps = Engine.get_frames_per_second()
228 |
229 | fps_label.text = "FPS: %.1f (%s)" % [avg_fps, stability_text]
230 |
231 | # Color code based on performance
232 | var target_fps: int = adaptive_graphics.target_fps
233 | var tolerance: int = adaptive_graphics.fps_tolerance
234 |
235 | if avg_fps >= target_fps - tolerance:
236 | fps_label.modulate = Color(0.2, 1.0, 0.2) # Green for good performance
237 | elif avg_fps >= target_fps - tolerance * 2:
238 | fps_label.modulate = Color(1.0, 1.0, 0.2) # Yellow for borderline performance
239 | else:
240 | fps_label.modulate = Color(1.0, 0.2, 0.2) # Red for poor performance
241 | else:
242 | # Fallback if FPS monitor doesn't exist
243 | var current_fps: float = Engine.get_frames_per_second()
244 | fps_label.text = "FPS: %.1f (unknown)" % current_fps
245 | fps_label.modulate = Color(1.0, 1.0, 1.0) # White for unknown status
246 |
247 | # Update status display
248 | if status_label:
249 | # Get the current action directly from adaptive_graphics
250 | var current_action: String = adaptive_graphics.current_action
251 |
252 | # Get other status information
253 | var renderer_type: GraphicsSettingsManager.RendererType = adaptive_graphics.settings_manager.current_renderer
254 | var renderer_name: String = GraphicsSettingsManager.RendererType.keys()[renderer_type]
255 |
256 | var vsync_mode: int = adaptive_graphics.current_vsync_mode
257 | var vsync_modes: Array[String] = ["Disabled", "Enabled", "Adaptive", "Mailbox"]
258 |
259 | var threading_info: Dictionary = adaptive_graphics.get_threading_support_info()
260 | var threading_active: bool = threading_info.thread_active
261 |
262 | # Build the status text
263 | var status_text: String = "Status: %s\n" % current_action
264 | status_text += "Threading: %s" % ("Active" if threading_active else "Inactive")
265 |
266 | status_label.text = status_text
267 |
268 | # Update renderer display
269 | if renderer_label and adaptive_graphics.settings_manager:
270 | var renderer_type = adaptive_graphics.settings_manager.current_renderer
271 | var renderer_name = GraphicsSettingsManager.RendererType.keys()[renderer_type]
272 | renderer_label.text = "Renderer: " + renderer_name
273 |
274 | # Add FSR info if available
275 | if adaptive_graphics.settings_manager.fsr_available:
276 | renderer_label.text += " (FSR supported)"
277 |
278 | # Check if settings have changed and update the preset dropdown
279 | if _have_settings_changed():
280 | # Store the new settings
281 | _store_current_settings()
282 |
283 | # Detect which preset matches the current settings
284 | var detected_preset: String = _detect_preset_from_settings()
285 |
286 | # Update the preset dropdown
287 | if detected_preset != current_preset:
288 | current_preset = detected_preset
289 |
290 | # Find and select the matching preset in the dropdown
291 | if detected_preset == "Custom":
292 | # Select the "Custom" option (last item)
293 | preset_option.select(preset_option.item_count - 1)
294 | else:
295 | # Find the preset by name and select it
296 | var preset_index: int = AdaptiveGraphics.QualityPreset.get(detected_preset, -1)
297 | if preset_index >= 0:
298 | for i in range(preset_option.item_count):
299 | if preset_option.get_item_id(i) == preset_index:
300 | preset_option.select(i)
301 | break
302 |
303 | func _have_settings_changed() -> bool:
304 | if not adaptive_graphics or not adaptive_graphics.settings_manager:
305 | return false
306 |
307 | for setting_name in adaptive_graphics.settings_manager.available_settings:
308 | if adaptive_graphics.settings_manager.is_setting_applicable(setting_name):
309 | var setting = adaptive_graphics.settings_manager.available_settings[setting_name]
310 |
311 | if not current_settings.has(setting_name) or current_settings[setting_name] != setting.current_index:
312 | return true
313 |
314 | return false
315 |
316 | func _on_target_fps_slider_value_changed(value: float) -> void:
317 | if adaptive_graphics:
318 | adaptive_graphics.target_fps = int(value)
319 | target_fps_value.value = value
320 |
321 | func _on_target_fps_value_changed(value: float) -> void:
322 | if adaptive_graphics:
323 | adaptive_graphics.target_fps = int(value)
324 | target_fps_slider.value = value
325 |
326 | func _on_reset_button_pressed() -> void:
327 | if adaptive_graphics:
328 | adaptive_graphics.target_fps = default_target_fps
329 | target_fps_slider.value = default_target_fps
330 | target_fps_value.value = default_target_fps
331 |
332 | func _on_enabled_checkbox_toggled(button_pressed: bool) -> void:
333 | if adaptive_graphics:
334 | adaptive_graphics.enabled = button_pressed
335 |
336 | func _on_allow_increase_checkbox_toggled(button_pressed: bool) -> void:
337 | if adaptive_graphics:
338 | adaptive_graphics.allow_quality_increase = button_pressed
339 |
340 | func _on_threading_checkbox_toggled(button_pressed: bool) -> void:
341 | if adaptive_graphics:
342 | adaptive_graphics.set_threading_enabled(button_pressed)
343 |
344 | func _on_match_refresh_rate_checkbox_toggled(button_pressed: bool) -> void:
345 | if adaptive_graphics:
346 | adaptive_graphics.match_refresh_rate = button_pressed
347 | if button_pressed:
348 | adaptive_graphics.set_target_fps_to_refresh_rate()
349 | target_fps_slider.value = adaptive_graphics.target_fps
350 | target_fps_value.value = adaptive_graphics.target_fps
351 |
352 | func _on_vsync_option_item_selected(index: int) -> void:
353 | if adaptive_graphics:
354 | adaptive_graphics.set_vsync_mode(index)
355 |
356 | func _on_preset_option_item_selected(index: int) -> void:
357 | var preset_id = preset_option.get_item_id(index)
358 |
359 | if preset_id >= 0 and adaptive_graphics:
360 | adaptive_graphics.apply_preset(preset_id)
361 | current_preset = AdaptiveGraphics.QualityPreset.keys()[preset_id]
362 | _store_current_settings()
363 |
364 | # Update UI immediately
365 | _update_ui()
366 |
367 | func _on_changed_settings() -> void:
368 | # Update the UI when settings are changed from any source
369 | _update_ui()
370 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/adaptive_graphics_ui.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bkwbb0swp5fw1
2 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/adaptive_graphics_ui.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=7 format=3 uid="uid://c45xs6yq7yahr"]
2 |
3 | [ext_resource type="Script" uid="uid://bkwbb0swp5fw1" path="res://addons/smart_graphics_settings/adaptive_graphics_ui.gd" id="1_yvmqe"]
4 | [ext_resource type="StyleBox" uid="uid://by7x1kn8sb4eu" path="res://addons/smart_graphics_settings/ui_panel_background.tres" id="2_dxjne"]
5 | [ext_resource type="Texture2D" uid="uid://lddqovy3e0cd" path="res://addons/smart_graphics_settings/images/title-text.png" id="3_mpp0l"]
6 |
7 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mpp0l"]
8 | content_margin_left = 8.0
9 | content_margin_top = 4.0
10 | content_margin_right = 8.0
11 | content_margin_bottom = 4.0
12 | bg_color = Color(0.2, 0.2, 0.2, 1)
13 | corner_radius_top_left = 4
14 | corner_radius_top_right = 4
15 | corner_radius_bottom_right = 4
16 | corner_radius_bottom_left = 4
17 |
18 | [sub_resource type="Theme" id="Theme_popup"]
19 | PopupMenu/constants/h_separation = 8
20 | PopupMenu/constants/item_end_padding = 8
21 | PopupMenu/constants/item_start_padding = 8
22 | PopupMenu/constants/v_separation = 8
23 | PopupMenu/styles/hover = SubResource("StyleBoxFlat_mpp0l")
24 | PopupMenu/styles/panel = SubResource("StyleBoxFlat_mpp0l")
25 |
26 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_option"]
27 | content_margin_left = 8.0
28 | content_margin_top = 4.0
29 | content_margin_right = 8.0
30 | content_margin_bottom = 4.0
31 | bg_color = Color(0.15, 0.15, 0.15, 1)
32 | corner_radius_top_left = 4
33 | corner_radius_top_right = 4
34 | corner_radius_bottom_right = 4
35 | corner_radius_bottom_left = 4
36 |
37 | [node name="AdaptiveGraphicsUI" type="Control"]
38 | layout_mode = 3
39 | anchors_preset = 15
40 | anchor_right = 1.0
41 | anchor_bottom = 1.0
42 | grow_horizontal = 2
43 | grow_vertical = 2
44 | script = ExtResource("1_yvmqe")
45 |
46 | [node name="CenterContainer" type="CenterContainer" parent="."]
47 | layout_mode = 1
48 | anchors_preset = 15
49 | anchor_right = 1.0
50 | anchor_bottom = 1.0
51 | grow_horizontal = 2
52 | grow_vertical = 2
53 |
54 | [node name="PanelContainer" type="PanelContainer" parent="CenterContainer"]
55 | custom_minimum_size = Vector2(500, 0)
56 | layout_mode = 2
57 | theme_override_styles/panel = ExtResource("2_dxjne")
58 |
59 | [node name="MarginContainer" type="MarginContainer" parent="CenterContainer/PanelContainer"]
60 | layout_mode = 2
61 | theme_override_constants/margin_left = 20
62 | theme_override_constants/margin_top = 20
63 | theme_override_constants/margin_right = 20
64 | theme_override_constants/margin_bottom = 20
65 |
66 | [node name="VBoxContent" type="VBoxContainer" parent="CenterContainer/PanelContainer/MarginContainer"]
67 | layout_mode = 2
68 | theme_override_constants/separation = 10
69 |
70 | [node name="Godot-smart-graphics-settings-text" type="Sprite2D" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent"]
71 | position = Vector2(230, 0)
72 | scale = Vector2(1.5, 1.5)
73 | texture = ExtResource("3_mpp0l")
74 | region_enabled = true
75 | region_rect = Rect2(40, 142, 220, 16)
76 |
77 | [node name="TitleLabel" type="RichTextLabel" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent"]
78 | layout_mode = 2
79 | horizontal_alignment = 1
80 |
81 | [node name="StatusLabel" type="Label" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent"]
82 | layout_mode = 2
83 | text = "Status: Initializing..."
84 | horizontal_alignment = 1
85 | text_overrun_behavior = 3
86 |
87 | [node name="FPSLabel" type="Label" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent"]
88 | layout_mode = 2
89 | text = "Current FPS: 0.0"
90 | text_overrun_behavior = 3
91 |
92 | [node name="TargetFPSLabel" type="Label" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent"]
93 | layout_mode = 2
94 | text = "Target FPS"
95 |
96 | [node name="TargetFPSContainer" type="HBoxContainer" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent"]
97 | layout_mode = 2
98 | theme_override_constants/separation = 10
99 |
100 | [node name="TargetFPSSlider" type="HSlider" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent/TargetFPSContainer"]
101 | layout_mode = 2
102 | size_flags_horizontal = 3
103 | min_value = 30.0
104 | max_value = 144.0
105 | value = 60.0
106 | rounded = true
107 | tick_count = 8
108 | ticks_on_borders = true
109 |
110 | [node name="TargetFPSValue" type="SpinBox" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent/TargetFPSContainer"]
111 | layout_mode = 2
112 | min_value = 30.0
113 | max_value = 144.0
114 | value = 60.0
115 | rounded = true
116 |
117 | [node name="ResetButton" type="Button" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent/TargetFPSContainer"]
118 | layout_mode = 2
119 | tooltip_text = "Reset to default"
120 | text = "Reset"
121 |
122 | [node name="EnabledCheckbox" type="CheckBox" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent"]
123 | layout_mode = 2
124 | button_pressed = true
125 | text = "Enable Adaptive Graphics"
126 |
127 | [node name="AllowIncreaseCheckbox" type="CheckBox" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent"]
128 | layout_mode = 2
129 | tooltip_text = "When enabled, the system will increase quality if performance is good"
130 | text = "Allow Quality Increase"
131 |
132 | [node name="ThreadingCheckbox" type="CheckBox" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent"]
133 | layout_mode = 2
134 | tooltip_text = "Use a separate thread for performance analysis (recommended)"
135 | button_pressed = true
136 | text = "Use Threading"
137 |
138 | [node name="MatchRefreshRateCheckbox" type="CheckBox" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent"]
139 | layout_mode = 2
140 | tooltip_text = "Set target FPS to match your display's refresh rate"
141 | text = "Match Display Refresh Rate"
142 |
143 | [node name="VSyncLabel" type="Label" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent"]
144 | layout_mode = 2
145 | text = "VSync Mode"
146 |
147 | [node name="VSyncOption" type="OptionButton" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent"]
148 | layout_mode = 2
149 | tooltip_text = "Control vertical synchronization with the display"
150 | focus_mode = 0
151 | theme = SubResource("Theme_popup")
152 | theme_override_constants/h_separation = 8
153 | theme_override_styles/focus = SubResource("StyleBoxFlat_option")
154 | theme_override_styles/hover = SubResource("StyleBoxFlat_option")
155 | theme_override_styles/pressed = SubResource("StyleBoxFlat_option")
156 | theme_override_styles/normal = SubResource("StyleBoxFlat_option")
157 | theme_override_constants/arrow_margin = 8
158 | selected = 0
159 | item_count = 4
160 | popup/item_0/text = "Disabled"
161 | popup/item_0/id = 0
162 | popup/item_1/text = "Enabled"
163 | popup/item_1/id = 1
164 | popup/item_2/text = "Adaptive"
165 | popup/item_2/id = 2
166 | popup/item_3/text = "Mailbox"
167 | popup/item_3/id = 3
168 |
169 | [node name="PresetLabel" type="Label" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent"]
170 | layout_mode = 2
171 | text = "Quality Preset"
172 |
173 | [node name="PresetOption" type="OptionButton" parent="CenterContainer/PanelContainer/MarginContainer/VBoxContent"]
174 | layout_mode = 2
175 | size_flags_horizontal = 3
176 | focus_mode = 0
177 | theme = SubResource("Theme_popup")
178 | theme_override_constants/h_separation = 8
179 | theme_override_styles/focus = SubResource("StyleBoxFlat_option")
180 | theme_override_styles/hover = SubResource("StyleBoxFlat_option")
181 | theme_override_styles/pressed = SubResource("StyleBoxFlat_option")
182 | theme_override_styles/normal = SubResource("StyleBoxFlat_option")
183 | theme_override_constants/arrow_margin = 8
184 | selected = 2
185 | fit_to_longest_item = false
186 | item_count = 5
187 | popup/item_0/text = "Ultra Low"
188 | popup/item_0/id = 0
189 | popup/item_1/text = "Low"
190 | popup/item_1/id = 1
191 | popup/item_2/text = "Medium"
192 | popup/item_2/id = 2
193 | popup/item_3/text = "High"
194 | popup/item_3/id = 3
195 | popup/item_4/text = "Ultra"
196 | popup/item_4/id = 4
197 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/demo/README.md:
--------------------------------------------------------------------------------
1 | # Smart Graphics Settings Demo
2 |
3 | This demo scene showcases the functionality of the Smart Graphics Settings addon for Godot 4.4. It provides a practical example of how the adaptive graphics system works in a real-world scenario.
4 |
5 | ## Getting Started
6 |
7 | 1. Open the demo scene: `addons/smart_graphics_settings/demo/demo_scene.tscn`
8 | 2. Run the scene to see the adaptive graphics in action
9 | 3. Use the keyboard shortcuts to interact with the demo
10 |
11 | ## Demo Features
12 |
13 | The demo includes:
14 |
15 | - A 3D environment with dynamic lighting and shadows
16 | - Stress testing capabilities to simulate different performance scenarios
17 | - Real-time FPS display and performance metrics
18 | - UI for adjusting and monitoring graphics settings
19 |
20 | ## Controls
21 |
22 | | Key | Action |
23 | |-----|--------|
24 | | F7 | Toggle graphics settings UI |
25 | | 1-5 | Apply quality presets (1=Low, 5=Ultra) |
26 | | Space | Spawn stress test objects |
27 | | Esc | Clear stress test objects |
28 |
29 | ## Stress Testing
30 |
31 | The demo includes a stress test feature that allows you to spawn multiple 3D objects to test how the adaptive graphics system responds to changing performance conditions:
32 |
33 | 1. Press Space to spawn a batch of objects (default: 100 objects per batch)
34 | 2. Observe how the FPS drops and the adaptive graphics system automatically adjusts settings
35 | 3. Press Esc to clear all stress test objects
36 |
37 | ## Quality Presets
38 |
39 | The demo includes 5 quality presets that you can apply using the number keys 1-5:
40 |
41 | 1. **Low**: Optimized for low-end hardware
42 | 2. **Medium**: Balanced performance and quality
43 | 3. **High**: Good visual quality with reasonable performance
44 | 4. **Ultra**: Maximum visual quality
45 | 5. **Custom**: User-defined settings
46 |
47 | ## Customizing the Demo
48 |
49 | You can modify the demo scene to test different scenarios:
50 |
51 | ### Changing the Stress Test Parameters
52 |
53 | Open `demo_scene.gd` and modify the following variables:
54 |
55 | ```gdscript
56 | # Number of objects to spawn per batch
57 | var spawn_count: int = 100
58 |
59 | # Change this to adjust the types of meshes used in the stress test
60 | func _ready() -> void:
61 | # Initialize mesh types
62 | mesh_types.append(BoxMesh.new())
63 | mesh_types.append(SphereMesh.new())
64 | mesh_types.append(TorusMesh.new())
65 | mesh_types.append(CylinderMesh.new())
66 | ```
67 |
68 | ### Testing Different Environments
69 |
70 | The demo scene uses a standard 3D environment, but you can modify it to test specific rendering features:
71 |
72 | 1. Add more complex geometry to test geometry-heavy scenes
73 | 2. Add more lights to test lighting performance
74 | 3. Add reflective surfaces to test reflection performance
75 | 4. Add transparent materials to test transparency performance
76 |
77 | ## Performance Analysis
78 |
79 | The demo includes real-time performance metrics that help you understand how the adaptive graphics system is working:
80 |
81 | 1. **FPS Counter**: Shows the current framerate
82 | 2. **Settings Display**: Shows which settings are currently active
83 | 3. **Adjustment Status**: Indicates when the system is measuring or adjusting settings
84 |
85 | ## Troubleshooting
86 |
87 | If you encounter issues with the demo:
88 |
89 | - Make sure the plugin is properly installed and enabled
90 | - Check that the SmartGraphicsSettings singleton is available
91 | - Verify that your Godot version is 4.4 or later
92 | - Check the console for any error messages
93 |
94 | ## Next Steps
95 |
96 | After exploring the demo, you can:
97 |
98 | 1. Integrate the Smart Graphics Settings into your own project
99 | 2. Customize the settings and presets to match your game's requirements
100 | 3. Extend the system with custom settings specific to your game
101 |
102 | For more information, refer to the [main documentation](../README.md).
103 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/demo/demo_scene.gd:
--------------------------------------------------------------------------------
1 | extends Node3D
2 |
3 | ## Available mesh types for stress testing
4 | var mesh_types: Array[Mesh] = []
5 |
6 | ## Stress test objects container
7 | @onready var stress_objects: Node3D = $StressObjects
8 |
9 | ## Reference to the SmartGraphicsSettings singleton
10 | # No need to store a reference as we can access it directly
11 |
12 | ## Number of objects to spawn per batch
13 | var spawn_count: int = 100
14 |
15 | func _ready() -> void:
16 | # Initialize mesh types
17 | mesh_types.append(BoxMesh.new())
18 | mesh_types.append(SphereMesh.new())
19 | mesh_types.append(TorusMesh.new())
20 | mesh_types.append(CylinderMesh.new())
21 |
22 | # Set up input actions for quality presets
23 | if not InputMap.has_action("quality_preset_1"):
24 | for i in range(1, 6):
25 | var action_name: String = "quality_preset_%d" % i
26 | InputMap.add_action(action_name)
27 | var event: InputEventKey = InputEventKey.new()
28 | event.keycode = KEY_1 + i - 1 # KEY_1, KEY_2, etc.
29 | InputMap.action_add_event(action_name, event)
30 |
31 | # Set up stress test input
32 | if not InputMap.has_action("stress_test"):
33 | InputMap.add_action("stress_test")
34 | var event: InputEventKey = InputEventKey.new()
35 | event.keycode = KEY_SPACE
36 | InputMap.action_add_event("stress_test", event)
37 |
38 | # SmartGraphicsSettings is available directly as an autoload singleton
39 | if not is_instance_valid(SmartGraphicsSettings):
40 | push_error("Demo: Failed to find SmartGraphicsSettings singleton")
41 | return
42 |
43 | # Create a timer to update the UI
44 | var timer: Timer = Timer.new()
45 | timer.wait_time = 0.5
46 | timer.timeout.connect(_update_ui)
47 | timer.autostart = true
48 | add_child(timer)
49 |
50 | # Update UI immediately
51 | _update_ui()
52 |
53 | func _input(event: InputEvent) -> void:
54 | # Handle quality preset hotkeys
55 | for i in range(1, 6):
56 | var action_name: String = "quality_preset_%d" % i
57 | if event.is_action_pressed(action_name):
58 | var preset: int = i - 1 # Convert to 0-based index
59 | if is_instance_valid(SmartGraphicsSettings):
60 | SmartGraphicsSettings.apply_preset(preset)
61 | print("Applied quality preset: ", i)
62 | return
63 |
64 | # Handle stress test
65 | if event.is_action_pressed("stress_test"):
66 | spawn_stress_objects()
67 |
68 | ## Spawn a batch of objects to stress the renderer
69 | func spawn_stress_objects() -> void:
70 | for i in range(spawn_count):
71 | var mesh_instance: MeshInstance3D = MeshInstance3D.new()
72 | var mesh_type: Mesh = mesh_types[randi() % mesh_types.size()]
73 | mesh_instance.mesh = mesh_type
74 |
75 | # Random position
76 | var x: float = randf_range(-10, 10)
77 | var z: float = randf_range(-10, 10)
78 | mesh_instance.position = Vector3(x, 1.0, z)
79 |
80 | # Random rotation
81 | mesh_instance.rotation = Vector3(
82 | randf_range(0, TAU),
83 | randf_range(0, TAU),
84 | randf_range(0, TAU)
85 | )
86 |
87 | # Random scale
88 | var scale: float = randf_range(0.5, 1.5)
89 | mesh_instance.scale = Vector3(scale, scale, scale)
90 |
91 | stress_objects.add_child(mesh_instance)
92 |
93 | print("Spawned %d more objects. Total: %d" % [spawn_count, stress_objects.get_child_count()])
94 |
95 | ## Update the UI with current status information
96 | func _update_ui() -> void:
97 | if not is_instance_valid(SmartGraphicsSettings):
98 | return
99 |
100 | ## Helper function to get the name of a VSync mode
101 | func _get_vsync_name(mode: int) -> String:
102 | var vsync_modes: Array[String] = ["Disabled", "Enabled", "Adaptive", "Mailbox"]
103 | if mode >= 0 and mode < vsync_modes.size():
104 | return vsync_modes[mode]
105 | return "Unknown"
106 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/demo/demo_scene.gd.uid:
--------------------------------------------------------------------------------
1 | uid://covnqac8lnxhh
2 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/demo/demo_scene.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=12 format=3 uid="uid://c8yvnqnvqnvn"]
2 |
3 | [ext_resource type="Script" uid="uid://covnqac8lnxhh" path="res://addons/smart_graphics_settings/demo/demo_scene.gd" id="1_yvmqe"]
4 | [ext_resource type="StyleBox" uid="uid://by7x1kn8sb4eu" path="res://addons/smart_graphics_settings/ui_panel_background.tres" id="2_p853p"]
5 |
6 | [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_yvmqe"]
7 | sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
8 | ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
9 |
10 | [sub_resource type="Sky" id="Sky_yvmqe"]
11 | sky_material = SubResource("ProceduralSkyMaterial_yvmqe")
12 |
13 | [sub_resource type="Environment" id="Environment_yvmqe"]
14 | background_mode = 2
15 | sky = SubResource("Sky_yvmqe")
16 | tonemap_mode = 2
17 | glow_enabled = true
18 | volumetric_fog_enabled = true
19 | volumetric_fog_density = 0.01
20 |
21 | [sub_resource type="CameraAttributesPractical" id="CameraAttributesPractical_p853p"]
22 |
23 | [sub_resource type="BoxMesh" id="BoxMesh_yvmqe"]
24 |
25 | [sub_resource type="SphereMesh" id="SphereMesh_yvmqe"]
26 |
27 | [sub_resource type="TorusMesh" id="TorusMesh_yvmqe"]
28 |
29 | [sub_resource type="CylinderMesh" id="CylinderMesh_yvmqe"]
30 |
31 | [sub_resource type="PlaneMesh" id="PlaneMesh_yvmqe"]
32 | size = Vector2(20, 20)
33 |
34 | [node name="DemoScene" type="Node3D"]
35 | script = ExtResource("1_yvmqe")
36 |
37 | [node name="WorldEnvironment" type="WorldEnvironment" parent="."]
38 | environment = SubResource("Environment_yvmqe")
39 |
40 | [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
41 | transform = Transform3D(-0.866025, -0.433013, 0.25, 0, 0.5, 0.866025, -0.5, 0.75, -0.433013, 0, 0, 0)
42 | shadow_enabled = true
43 |
44 | [node name="Camera3D" type="Camera3D" parent="."]
45 | transform = Transform3D(1, 0, 0, 0, 0.965926, 0.258819, 0, -0.258819, 0.965926, 0, 2, 8)
46 | attributes = SubResource("CameraAttributesPractical_p853p")
47 |
48 | [node name="Objects" type="Node3D" parent="."]
49 |
50 | [node name="Box" type="MeshInstance3D" parent="Objects"]
51 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4, 0.5, 0)
52 | mesh = SubResource("BoxMesh_yvmqe")
53 |
54 | [node name="Sphere" type="MeshInstance3D" parent="Objects"]
55 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0.5, 0)
56 | mesh = SubResource("SphereMesh_yvmqe")
57 |
58 | [node name="Torus" type="MeshInstance3D" parent="Objects"]
59 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
60 | mesh = SubResource("TorusMesh_yvmqe")
61 |
62 | [node name="Cylinder" type="MeshInstance3D" parent="Objects"]
63 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0.5, 0)
64 | mesh = SubResource("CylinderMesh_yvmqe")
65 |
66 | [node name="Box2" type="MeshInstance3D" parent="Objects"]
67 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0.5, 0)
68 | mesh = SubResource("BoxMesh_yvmqe")
69 |
70 | [node name="Ground" type="MeshInstance3D" parent="."]
71 | mesh = SubResource("PlaneMesh_yvmqe")
72 |
73 | [node name="StressObjects" type="Node3D" parent="."]
74 |
75 | [node name="PanelContainer" type="PanelContainer" parent="."]
76 | self_modulate = Color(1, 1, 1, 0.5)
77 | custom_minimum_size = Vector2(450, 0)
78 | theme_override_styles/panel = ExtResource("2_p853p")
79 |
80 | [node name="MarginContainer" type="MarginContainer" parent="PanelContainer"]
81 | layout_mode = 2
82 | theme_override_constants/margin_left = 16
83 | theme_override_constants/margin_top = 16
84 | theme_override_constants/margin_right = 16
85 | theme_override_constants/margin_bottom = 16
86 |
87 | [node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/MarginContainer"]
88 | layout_mode = 2
89 |
90 | [node name="Instructions" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer"]
91 | layout_mode = 2
92 | text = "Smart Graphics Settings Demo
93 | Press F7 to toggle settings UI
94 | Press 1-5 to apply quality presets (1=Ultra Low, 5=Ultra)
95 | Press Space to spawn 100 more objects (stress test)"
96 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/fps_monitor.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | @icon("res://addons/smart_graphics_settings/images/smart-graphics-settings-icon.svg")
3 | class_name FPSMonitor
4 | extends Node
5 |
6 | ## Stores the history of FPS values for analysis
7 | var fps_history: Array[float] = []
8 |
9 | ## Running sum of FPS values for efficient average calculation
10 | var running_sum: float = 0.0
11 |
12 | ## Number of frames to keep in history (about 1 second at 60fps)
13 | var history_size: int = 60
14 |
15 | ## Threshold for determining if FPS is stable (variance must be below this)
16 | var stable_threshold: float = 5.0
17 |
18 | ## Mutex for thread-safe access to FPS data
19 | var fps_mutex: Mutex = Mutex.new()
20 |
21 | ## Hysteresis for stability detection to prevent rapid fluctuations
22 | var stability_hysteresis: int = 5
23 | var stability_counter: int = 0
24 | var last_stability_state: bool = true # Default to stable
25 |
26 | ## Process function that collects FPS data each frame
27 | func _process(_delta: float) -> void:
28 | # Skip processing in editor
29 | if Engine.is_editor_hint():
30 | return
31 |
32 | var current_fps: float = Engine.get_frames_per_second()
33 |
34 | fps_mutex.lock()
35 | # Update running sum and history
36 | running_sum += current_fps
37 | fps_history.append(current_fps)
38 |
39 | if fps_history.size() > history_size:
40 | running_sum -= fps_history.pop_front()
41 | fps_mutex.unlock()
42 |
43 | ## Calculate the average FPS from the history in a thread-safe way
44 | func get_average_fps() -> float:
45 | fps_mutex.lock()
46 | var result: float = 0.0
47 | var size: int = fps_history.size()
48 |
49 | if size > 0:
50 | result = running_sum / size
51 | else:
52 | # Return current FPS if no history is available
53 | result = Engine.get_frames_per_second()
54 |
55 | fps_mutex.unlock()
56 |
57 | return result
58 |
59 | ## Get a thread-safe copy of the FPS history
60 | func get_fps_history_copy() -> Array[float]:
61 | fps_mutex.lock()
62 | var copy: Array[float] = fps_history.duplicate()
63 | fps_mutex.unlock()
64 | return copy
65 |
66 | ## Determine if the FPS is stable (low variance) in a thread-safe way
67 | ## Uses hysteresis to prevent rapid fluctuations between stable and unstable states
68 | func is_fps_stable() -> bool:
69 | fps_mutex.lock()
70 | var size: int = fps_history.size()
71 |
72 | # Need at least a few frames to make a determination
73 | if size < 10:
74 | fps_mutex.unlock()
75 | return true # Default to stable with insufficient data
76 |
77 | var avg: float = running_sum / size
78 | var variance: float = 0.0
79 |
80 | for fps in fps_history:
81 | variance += pow(fps - avg, 2)
82 |
83 | variance /= size
84 |
85 | # Calculate the coefficient of variation (CV) - a normalized measure of dispersion
86 | var cv: float = 0.0
87 | if avg > 0:
88 | cv = sqrt(variance) / avg
89 |
90 | fps_mutex.unlock()
91 |
92 | # Determine the raw stability state
93 | # More lenient threshold for stability
94 | var raw_stability: bool = variance < stable_threshold * 2 and cv < 0.15
95 |
96 | # Apply hysteresis to prevent rapid fluctuations
97 | if raw_stability != last_stability_state:
98 | stability_counter += 1
99 | if stability_counter >= stability_hysteresis:
100 | last_stability_state = raw_stability
101 | stability_counter = 0
102 | else:
103 | stability_counter = 0
104 |
105 | return last_stability_state
106 |
107 | ## Clear the FPS history in a thread-safe way
108 | func clear_history() -> void:
109 | fps_mutex.lock()
110 | fps_history.clear()
111 | running_sum = 0.0
112 | stability_counter = 0
113 | last_stability_state = true # Default to stable when cleared
114 | fps_mutex.unlock()
115 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/fps_monitor.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dtu4fp0v58hio
2 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/graphics_settings_manager.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | @icon("res://addons/smart_graphics_settings/images/smart-graphics-settings-icon.svg")
3 | class_name GraphicsSettingsManager
4 | extends Node
5 |
6 | ## Enum for setting priority (determines adjustment order)
7 | enum SettingPriority {
8 | RENDER_SCALE = 0, # Minimal visual impact, adjust first
9 | ANTI_ALIASING = 10, # Low visual impact
10 | POST_PROCESSING = 20, # Medium visual impact
11 | SHADOWS = 30, # Medium-high visual impact
12 | REFLECTIONS = 40, # High visual impact
13 | GLOBAL_ILLUMINATION = 50 # Major visual impact, adjust last
14 | }
15 |
16 | ## Enum for setting type (determines how the setting is applied)
17 | enum SettingType {
18 | VIEWPORT,
19 | ENVIRONMENT,
20 | CAMERA,
21 | RENDER_SCALE
22 | }
23 |
24 | ## Enum for renderer type
25 | enum RendererType {
26 | FORWARD_PLUS,
27 | MOBILE,
28 | COMPATIBILITY,
29 | CUSTOM
30 | }
31 |
32 | ## Current renderer being used
33 | var current_renderer: RendererType = RendererType.FORWARD_PLUS
34 |
35 | ## Define setting structure with strong typing
36 | class Setting:
37 | var values: Array[Variant]
38 | var current_index: int
39 | var priority: SettingPriority
40 | var type: SettingType
41 |
42 | func _init(p_values: Array[Variant], p_current_index: int, p_priority: SettingPriority, p_type: SettingType) -> void:
43 | values = p_values
44 | current_index = p_current_index
45 | priority = p_priority
46 | type = p_type
47 |
48 | ## Dictionary of available settings
49 | var available_settings: Dictionary[String, Setting] = {}
50 |
51 | ## References to nodes
52 | var viewport: Viewport
53 | var environment: Environment
54 | var camera: Camera3D
55 | var original_window_size: Vector2i
56 |
57 | ## Platform information
58 | var platform_info: Dictionary = {}
59 |
60 | ## Whether FSR is available on this platform
61 | var fsr_available: bool = false
62 |
63 | func _init() -> void:
64 | # Gather platform information
65 | platform_info = {
66 | "os_name": OS.get_name(),
67 | "model_name": OS.get_model_name(),
68 | "processor_name": OS.get_processor_name(),
69 | "processor_count": OS.get_processor_count(),
70 | "renderer": RenderingServer.get_rendering_device().get_device_name() if RenderingServer.get_rendering_device() else "Unknown"
71 | }
72 |
73 | # Check if FSR is available
74 | fsr_available = OS.has_feature("fsr")
75 |
76 | # Initialize common settings with strong typing and more granular priorities
77 | available_settings = {
78 | # Render Scale (highest performance impact, lowest visual degradation)
79 | "render_scale": Setting.new(
80 | [0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
81 | 5, # Start with highest quality
82 | SettingPriority.RENDER_SCALE,
83 | SettingType.RENDER_SCALE
84 | ),
85 |
86 | # Common Viewport Settings for all renderers
87 | "msaa": Setting.new(
88 | [Viewport.MSAA_DISABLED, Viewport.MSAA_2X, Viewport.MSAA_4X, Viewport.MSAA_8X],
89 | 3,
90 | SettingPriority.ANTI_ALIASING,
91 | SettingType.VIEWPORT
92 | ),
93 | "shadow_quality": Setting.new(
94 | [
95 | Viewport.SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED,
96 | Viewport.SHADOW_ATLAS_QUADRANT_SUBDIV_1,
97 | Viewport.SHADOW_ATLAS_QUADRANT_SUBDIV_4,
98 | Viewport.SHADOW_ATLAS_QUADRANT_SUBDIV_16,
99 | Viewport.SHADOW_ATLAS_QUADRANT_SUBDIV_64
100 | ],
101 | 4,
102 | SettingPriority.SHADOWS,
103 | SettingType.VIEWPORT
104 | ),
105 | "shadow_size": Setting.new(
106 | [1024, 2048, 4096, 8192],
107 | 2,
108 | SettingPriority.SHADOWS,
109 | SettingType.VIEWPORT
110 | ),
111 | "fxaa": Setting.new(
112 | [Viewport.SCREEN_SPACE_AA_DISABLED, Viewport.SCREEN_SPACE_AA_FXAA],
113 | 1,
114 | SettingPriority.ANTI_ALIASING,
115 | SettingType.VIEWPORT
116 | ),
117 |
118 | # Camera Settings (common to all renderers)
119 | "dof": Setting.new(
120 | [false, true],
121 | 1,
122 | SettingPriority.POST_PROCESSING,
123 | SettingType.CAMERA
124 | )
125 | # Removed motion_blur as it's not supported in Godot 4.4
126 | }
127 |
128 | # Renderer-specific settings will be initialized in _ready()
129 |
130 | func _ready() -> void:
131 | # Skip initialization in editor
132 | if Engine.is_editor_hint():
133 | return
134 |
135 | # Get viewport with error handling
136 | viewport = get_viewport()
137 | if not viewport:
138 | push_error("Smart Graphics Settings: Failed to get viewport")
139 | return
140 |
141 | original_window_size = DisplayServer.window_get_size()
142 |
143 | # Detect current renderer
144 | detect_renderer()
145 |
146 | # Initialize renderer-specific settings
147 | initialize_renderer_specific_settings()
148 |
149 | # Find WorldEnvironment and Camera3D in the scene
150 | var world_env: WorldEnvironment = find_world_environment()
151 | if world_env:
152 | environment = world_env.environment
153 | else:
154 | # Try to get environment from the viewport's world
155 | if viewport and viewport.get_world_3d() and viewport.get_world_3d().environment:
156 | environment = viewport.get_world_3d().environment
157 | else:
158 | push_warning("Smart Graphics Settings: No WorldEnvironment found. Environment settings will not be applied.")
159 |
160 | camera = find_main_camera()
161 | if not camera:
162 | push_warning("Smart Graphics Settings: No Camera3D found. Camera settings will not be applied.")
163 |
164 | ## Detect which renderer is currently being used
165 | func detect_renderer() -> void:
166 | var rendering_method: String = ProjectSettings.get_setting("rendering/renderer/rendering_method", "forward_plus")
167 |
168 | # Check for custom rendering pipelines
169 | if ProjectSettings.has_setting("rendering/custom_pipeline/enabled") and ProjectSettings.get_setting("rendering/custom_pipeline/enabled", false):
170 | print("Smart Graphics Settings: Detected custom rendering pipeline")
171 | current_renderer = RendererType.CUSTOM
172 | return
173 |
174 | match rendering_method:
175 | "forward_plus":
176 | current_renderer = RendererType.FORWARD_PLUS
177 | "mobile":
178 | current_renderer = RendererType.MOBILE
179 | "gl_compatibility":
180 | current_renderer = RendererType.COMPATIBILITY
181 | _:
182 | # Default to Forward+ if unknown
183 | current_renderer = RendererType.FORWARD_PLUS
184 | push_warning("Smart Graphics Settings: Unknown rendering method: %s. Defaulting to Forward+." % rendering_method)
185 |
186 | print("Smart Graphics Settings: Detected renderer: ", RendererType.keys()[current_renderer])
187 |
188 | ## Initialize settings specific to the detected renderer
189 | func initialize_renderer_specific_settings() -> void:
190 | match current_renderer:
191 | RendererType.FORWARD_PLUS:
192 | # Forward+ specific settings
193 | available_settings["ssao"] = Setting.new(
194 | [false, true],
195 | 1,
196 | SettingPriority.POST_PROCESSING,
197 | SettingType.VIEWPORT
198 | )
199 | available_settings["ssao_quality"] = Setting.new(
200 | [0, 1, 2, 3, 4], # Very Low, Low, Medium, High, Ultra
201 | 3,
202 | SettingPriority.POST_PROCESSING,
203 | SettingType.VIEWPORT
204 | )
205 | available_settings["ssr"] = Setting.new(
206 | [false, true],
207 | 1,
208 | SettingPriority.REFLECTIONS,
209 | SettingType.VIEWPORT
210 | )
211 | available_settings["ssr_max_steps"] = Setting.new(
212 | [8, 16, 32, 64],
213 | 3,
214 | SettingPriority.REFLECTIONS,
215 | SettingType.VIEWPORT
216 | )
217 | available_settings["sdfgi"] = Setting.new(
218 | [false, true],
219 | 1,
220 | SettingPriority.GLOBAL_ILLUMINATION,
221 | SettingType.VIEWPORT
222 | )
223 | available_settings["glow"] = Setting.new(
224 | [false, true],
225 | 1,
226 | SettingPriority.POST_PROCESSING,
227 | SettingType.VIEWPORT
228 | )
229 |
230 | # Environment Settings (Forward+ specific)
231 | available_settings["volumetric_fog"] = Setting.new(
232 | [false, true],
233 | 1,
234 | SettingPriority.POST_PROCESSING,
235 | SettingType.ENVIRONMENT
236 | )
237 | available_settings["volumetric_fog_density"] = Setting.new(
238 | [0.01, 0.02, 0.03, 0.05],
239 | 3,
240 | SettingPriority.POST_PROCESSING,
241 | SettingType.ENVIRONMENT
242 | )
243 |
244 | RendererType.MOBILE:
245 | # Mobile-specific settings
246 | # Mobile has more limited features
247 | available_settings["glow"] = Setting.new(
248 | [false, true],
249 | 1,
250 | SettingPriority.POST_PROCESSING,
251 | SettingType.VIEWPORT
252 | )
253 |
254 | # Mobile-specific optimizations
255 | # Adjust render scale values for mobile
256 | if available_settings.has("render_scale"):
257 | available_settings["render_scale"] = Setting.new(
258 | [0.5, 0.6, 0.7, 0.75, 0.8, 1.0], # More aggressive scaling options
259 | 4, # Default to 0.8 on mobile
260 | SettingPriority.RENDER_SCALE,
261 | SettingType.RENDER_SCALE
262 | )
263 |
264 | RendererType.COMPATIBILITY:
265 | # Compatibility-specific settings
266 | available_settings["glow"] = Setting.new(
267 | [false, true],
268 | 1,
269 | SettingPriority.POST_PROCESSING,
270 | SettingType.VIEWPORT
271 | )
272 | # Some limited SSAO might be available in compatibility mode
273 | available_settings["ssao"] = Setting.new(
274 | [false, true],
275 | 1,
276 | SettingPriority.POST_PROCESSING,
277 | SettingType.VIEWPORT
278 | )
279 |
280 | RendererType.CUSTOM:
281 | # For custom renderers, we'll keep only the most basic settings
282 | # and let the user extend as needed
283 | push_warning("Smart Graphics Settings: Custom rendering pipeline detected. Only basic settings will be available.")
284 |
285 | ## Find the WorldEnvironment node in the scene
286 | func find_world_environment() -> WorldEnvironment:
287 | var root: Node = get_tree().root
288 | if not root:
289 | push_error("Smart Graphics Settings: Unable to access scene tree root")
290 | return null
291 |
292 | var world_env: WorldEnvironment = find_node_of_type(root, "WorldEnvironment") as WorldEnvironment
293 |
294 | # If not found directly, try to get it from the current scene
295 | if not world_env and get_tree().current_scene:
296 | world_env = find_node_of_type(get_tree().current_scene, "WorldEnvironment") as WorldEnvironment
297 |
298 | if not world_env:
299 | push_warning("Smart Graphics Settings: No WorldEnvironment node found in the scene")
300 |
301 | return world_env
302 |
303 | ## Find the main Camera3D in the scene
304 | func find_main_camera() -> Camera3D:
305 | var root: Node = get_tree().root
306 | if not root:
307 | push_error("Smart Graphics Settings: Unable to access scene tree root")
308 | return null
309 |
310 | var camera: Camera3D = find_node_of_type(root, "Camera3D") as Camera3D
311 |
312 | # If not found directly, try to get it from the current scene
313 | if not camera and get_tree().current_scene:
314 | camera = find_node_of_type(get_tree().current_scene, "Camera3D") as Camera3D
315 |
316 | # If still not found, try to get the current camera
317 | if not camera and get_viewport() and get_viewport().get_camera_3d():
318 | camera = get_viewport().get_camera_3d()
319 |
320 | return camera
321 |
322 | ## Helper function to find a node of a specific type
323 | func find_node_of_type(node: Node, type_name: String) -> Node:
324 | if not node:
325 | return null
326 |
327 | if node.get_class() == type_name:
328 | return node
329 |
330 | for child in node.get_children():
331 | var found: Node = find_node_of_type(child, type_name)
332 | if found:
333 | return found
334 |
335 | return null
336 |
337 | ## Check if a setting is applicable to the current renderer
338 | func is_setting_applicable(setting_name: String) -> bool:
339 | if not available_settings.has(setting_name):
340 | return false
341 |
342 | # Define which settings apply to which renderers
343 | match current_renderer:
344 | RendererType.FORWARD_PLUS:
345 | return true # All settings apply to Forward+
346 |
347 | RendererType.MOBILE:
348 | # Return false for Forward+ specific settings
349 | if setting_name in ["sdfgi", "ssr", "ssr_max_steps", "volumetric_fog",
350 | "volumetric_fog_density", "ssao", "ssao_quality"]:
351 | return false
352 | return true
353 |
354 | RendererType.COMPATIBILITY:
355 | # Similar to Mobile but might have different constraints
356 | if setting_name in ["sdfgi", "ssr", "ssr_max_steps", "volumetric_fog",
357 | "volumetric_fog_density"]:
358 | return false
359 | return true
360 |
361 | RendererType.CUSTOM:
362 | # For custom renderers, only allow basic settings
363 | if setting_name in ["render_scale", "msaa", "shadow_quality", "shadow_size", "fxaa"]:
364 | return true
365 | return false
366 |
367 | return false
368 |
369 | ## Order settings by priority for adjustment
370 | func get_settings_by_priority() -> Array[String]:
371 | var settings_by_priority: Dictionary = {}
372 |
373 | # Initialize dictionary with all priority levels
374 | for priority in range(SettingPriority.RENDER_SCALE, SettingPriority.GLOBAL_ILLUMINATION + 10, 10):
375 | settings_by_priority[priority] = []
376 |
377 | # Only include settings applicable to the current renderer
378 | for setting_name in available_settings:
379 | if is_setting_applicable(setting_name):
380 | var priority: SettingPriority = available_settings[setting_name].priority
381 | if not settings_by_priority.has(priority):
382 | settings_by_priority[priority] = []
383 | settings_by_priority[priority].append(setting_name)
384 |
385 | var result: Array[String] = []
386 |
387 | # Sort by priority
388 | var priorities: Array = settings_by_priority.keys()
389 | priorities.sort()
390 |
391 | for priority in priorities:
392 | result.append_array(settings_by_priority[priority])
393 |
394 | return result
395 |
396 | ## Decrease quality of a specific setting
397 | func decrease_setting_quality(setting_name: String) -> bool:
398 | if not available_settings.has(setting_name):
399 | push_warning("Smart Graphics Settings: Attempted to decrease non-existent setting: %s" % setting_name)
400 | return false
401 |
402 | if not is_setting_applicable(setting_name):
403 | push_warning("Smart Graphics Settings: Setting %s is not applicable to the current renderer" % setting_name)
404 | return false
405 |
406 | var setting: Setting = available_settings[setting_name]
407 | if setting.current_index > 0:
408 | setting.current_index -= 1
409 | apply_setting(setting_name)
410 | return true
411 | return false
412 |
413 | ## Increase quality of a specific setting
414 | func increase_setting_quality(setting_name: String) -> bool:
415 | if not available_settings.has(setting_name):
416 | push_warning("Smart Graphics Settings: Attempted to increase non-existent setting: %s" % setting_name)
417 | return false
418 |
419 | if not is_setting_applicable(setting_name):
420 | push_warning("Smart Graphics Settings: Setting %s is not applicable to the current renderer" % setting_name)
421 | return false
422 |
423 | var setting: Setting = available_settings[setting_name]
424 | if setting.current_index < setting.values.size() - 1:
425 | setting.current_index += 1
426 | apply_setting(setting_name)
427 | return true
428 | return false
429 |
430 | ## Apply the current setting value to the game
431 | func apply_setting(setting_name: String) -> void:
432 | # Check if this setting exists
433 | if not available_settings.has(setting_name):
434 | push_warning("Smart Graphics Settings: Attempted to apply non-existent setting: %s" % setting_name)
435 | return
436 |
437 | # Check if this setting is applicable to the current renderer
438 | if not is_setting_applicable(setting_name):
439 | push_warning("Smart Graphics Settings: Setting %s is not applicable to the current renderer: %s" % [setting_name, RendererType.keys()[current_renderer]])
440 | return
441 |
442 | var setting: Setting = available_settings[setting_name]
443 | var value = setting.values[setting.current_index]
444 |
445 | # Apply the setting based on the setting type and name
446 | match setting.type:
447 | SettingType.VIEWPORT:
448 | if not viewport:
449 | push_warning("Smart Graphics Settings: Cannot apply viewport setting %s - no viewport found" % setting_name)
450 | return
451 |
452 | match setting_name:
453 | "msaa":
454 | # In Godot 4.4, we need to use different approaches for Window vs Viewport
455 | if viewport is Window:
456 | # For the main viewport (Window), we need to use the viewport's own methods
457 | var vp: Viewport = get_viewport()
458 | if vp:
459 | vp.msaa_3d = value
460 | else:
461 | viewport.msaa = value
462 | "shadow_quality":
463 | if viewport is Window:
464 | # For the main viewport (Window)
465 | var vp: Viewport = get_viewport()
466 | if vp:
467 | # Get the viewport RID
468 | var viewport_rid: RID = vp.get_viewport_rid()
469 | # Set the shadow atlas quadrant subdivision
470 | RenderingServer.viewport_set_positional_shadow_atlas_quadrant_subdivision(viewport_rid, 0, value)
471 | else:
472 | viewport.shadow_atlas_quad_0 = value
473 | "shadow_size":
474 | if viewport is Window:
475 | # For the main viewport (Window)
476 | var vp: Viewport = get_viewport()
477 | if vp:
478 | # Get the viewport RID
479 | var viewport_rid: RID = vp.get_viewport_rid()
480 | RenderingServer.viewport_set_positional_shadow_atlas_size(viewport_rid, value)
481 | else:
482 | viewport.shadow_atlas_size = value
483 | "fxaa":
484 | if viewport is Window:
485 | # For the main viewport (Window)
486 | var vp: Viewport = get_viewport()
487 | if vp:
488 | vp.screen_space_aa = value
489 | else:
490 | viewport.screen_space_aa = value
491 | "ssao":
492 | if viewport is Window:
493 | # For the main viewport (Window)
494 | if get_viewport() and get_viewport().get_world_3d() and get_viewport().get_world_3d().environment:
495 | get_viewport().get_world_3d().environment.ssao_enabled = value
496 | else:
497 | viewport.ssao_enabled = value
498 | "ssao_quality":
499 | # In Godot 4.4, SSAO quality is controlled via ProjectSettings
500 | ProjectSettings.set_setting("rendering/environment/ssao/quality", value)
501 | "ssr":
502 | if viewport is Window:
503 | # For the main viewport (Window)
504 | if get_viewport() and get_viewport().get_world_3d() and get_viewport().get_world_3d().environment:
505 | get_viewport().get_world_3d().environment.ssr_enabled = value
506 | else:
507 | viewport.ssr_enabled = value
508 | "ssr_max_steps":
509 | if viewport is Window:
510 | # For the main viewport (Window)
511 | if get_viewport() and get_viewport().get_world_3d() and get_viewport().get_world_3d().environment:
512 | get_viewport().get_world_3d().environment.ssr_max_steps = value
513 | else:
514 | viewport.ssr_max_steps = value
515 | "sdfgi":
516 | if viewport is Window:
517 | # For the main viewport (Window)
518 | if get_viewport() and get_viewport().get_world_3d() and get_viewport().get_world_3d().environment:
519 | get_viewport().get_world_3d().environment.sdfgi_enabled = value
520 | else:
521 | viewport.sdfgi_enabled = value
522 | "glow":
523 | if viewport is Window:
524 | # For the main viewport (Window)
525 | if get_viewport() and get_viewport().get_world_3d() and get_viewport().get_world_3d().environment:
526 | get_viewport().get_world_3d().environment.glow_enabled = value
527 | else:
528 | viewport.glow_enabled = value
529 |
530 | SettingType.ENVIRONMENT:
531 | if not environment:
532 | # Try to get the environment from the world if not directly set
533 | if viewport and viewport.get_world_3d() and viewport.get_world_3d().environment:
534 | environment = viewport.get_world_3d().environment
535 | else:
536 | push_warning("Smart Graphics Settings: Cannot apply environment setting %s - no environment found" % setting_name)
537 | return
538 |
539 | match setting_name:
540 | "volumetric_fog":
541 | environment.volumetric_fog_enabled = value
542 | "volumetric_fog_density":
543 | environment.volumetric_fog_density = value
544 |
545 | SettingType.CAMERA:
546 | if not camera:
547 | push_warning("Smart Graphics Settings: Cannot apply camera setting %s - no camera found" % setting_name)
548 | return
549 |
550 | match setting_name:
551 | "dof":
552 | if camera.attributes:
553 | camera.attributes.dof_blur_far_enabled = value
554 | else:
555 | push_warning("Smart Graphics Settings: Camera has no attributes resource assigned. Cannot set DOF settings.")
556 |
557 | SettingType.RENDER_SCALE:
558 | if setting_name == "render_scale":
559 | var scale_factor: float = value
560 |
561 | if viewport is Window:
562 | # For the main viewport (Window)
563 | var vp: Viewport = get_viewport()
564 | if not vp:
565 | push_warning("Smart Graphics Settings: Cannot apply render scale - no viewport found")
566 | return
567 |
568 | # Set the scaling mode
569 | if scale_factor < 1.0:
570 | if fsr_available:
571 | vp.scaling_3d_mode = Viewport.SCALING_3D_MODE_FSR
572 | else:
573 | vp.scaling_3d_mode = Viewport.SCALING_3D_MODE_BILINEAR
574 | vp.scaling_3d_scale = scale_factor
575 | else:
576 | vp.scaling_3d_mode = Viewport.SCALING_3D_MODE_BILINEAR
577 | vp.scaling_3d_scale = 1.0
578 | else:
579 | # For other viewports
580 | viewport.size = Vector2i(original_window_size.x * scale_factor, original_window_size.y * scale_factor)
581 | if scale_factor < 1.0:
582 | if fsr_available:
583 | viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_FSR
584 | else:
585 | viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_BILINEAR
586 | viewport.scaling_3d_scale = scale_factor
587 | else:
588 | viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_BILINEAR
589 | viewport.scaling_3d_scale = 1.0
590 |
591 | print("Smart Graphics Settings: Applied setting: ", setting_name, " = ", value)
592 |
593 | ## Save current graphics settings to user://graphics_settings.cfg
594 | func save_graphics_settings() -> void:
595 | var config: ConfigFile = ConfigFile.new()
596 |
597 | # Save renderer type
598 | config.set_value("system", "renderer", current_renderer)
599 |
600 | # Save platform info for debugging
601 | config.set_value("system", "platform", OS.get_name())
602 |
603 | for setting_name in available_settings:
604 | var setting: Setting = available_settings[setting_name]
605 | config.set_value("graphics", setting_name, setting.current_index)
606 |
607 | var err: Error = config.save("user://graphics_settings.cfg")
608 | if err != OK:
609 | push_error("Smart Graphics Settings: Error saving graphics settings: %s" % error_string(err))
610 |
611 | ## Load graphics settings from user://graphics_settings.cfg
612 | func load_graphics_settings() -> void:
613 | var config: ConfigFile = ConfigFile.new()
614 | var err: Error = config.load("user://graphics_settings.cfg")
615 |
616 | if err != OK:
617 | push_warning("Smart Graphics Settings: No saved settings found or error loading settings: %s" % error_string(err))
618 | return
619 |
620 | # Check if settings were saved with a different renderer
621 | if config.has_section_key("system", "renderer"):
622 | var saved_renderer: int = config.get_value("system", "renderer")
623 | if saved_renderer != current_renderer:
624 | push_warning("Smart Graphics Settings: Saved settings were for a different renderer. Some settings may not apply.")
625 |
626 | for setting_name in available_settings:
627 | if config.has_section_key("graphics", setting_name):
628 | var index: int = config.get_value("graphics", setting_name)
629 |
630 | # Validate index is within bounds
631 | if index >= 0 and index < available_settings[setting_name].values.size():
632 | available_settings[setting_name].current_index = index
633 |
634 | # Only apply if the setting is applicable to the current renderer
635 | if is_setting_applicable(setting_name):
636 | apply_setting(setting_name)
637 | else:
638 | push_warning("Smart Graphics Settings: Invalid index %d for setting %s" % [index, setting_name])
639 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/graphics_settings_manager.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dxiesa4lkyahn
2 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasbecker-dev/godot-smart-graphics-settings/9b13563421a1b8392746ebbd4ada8ef0ecc71dc7/addons/smart_graphics_settings/images/icon.png
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/images/icon.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cbgd20ttbe1so"
6 | path="res://.godot/imported/icon.png-6edfe3cd310a893587d7cbd42a585bf7.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/smart_graphics_settings/images/icon.png"
14 | dest_files=["res://.godot/imported/icon.png-6edfe3cd310a893587d7cbd42a585bf7.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 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/images/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
126 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/images/icon.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://c7sfc7rk0pmk3"
6 | path="res://.godot/imported/icon.svg-2e8b671c9495e19f2be6245bd98bb60b.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/smart_graphics_settings/images/icon.svg"
14 | dest_files=["res://.godot/imported/icon.svg-2e8b671c9495e19f2be6245bd98bb60b.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/smart_graphics_settings/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasbecker-dev/godot-smart-graphics-settings/9b13563421a1b8392746ebbd4ada8ef0ecc71dc7/addons/smart_graphics_settings/images/logo.png
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/images/logo.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dhnf6os2ilgoc"
6 | path="res://.godot/imported/logo.png-c61a5866afd139c3a2f9b887bc98f9e8.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/smart_graphics_settings/images/logo.png"
14 | dest_files=["res://.godot/imported/logo.png-c61a5866afd139c3a2f9b887bc98f9e8.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 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
33 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/images/logo.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cwjnxnk1s4g5y"
6 | path="res://.godot/imported/logo.svg-2fa80a039b847a2a85a5d303589929a2.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/smart_graphics_settings/images/logo.svg"
14 | dest_files=["res://.godot/imported/logo.svg-2fa80a039b847a2a85a5d303589929a2.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/smart_graphics_settings/images/smart-graphics-settings-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
52 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/images/smart-graphics-settings-icon.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://djk3dq2vcmnf3"
6 | path="res://.godot/imported/smart-graphics-settings-icon.svg-4d2d8773c531c43062886eae43689de4.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/smart_graphics_settings/images/smart-graphics-settings-icon.svg"
14 | dest_files=["res://.godot/imported/smart-graphics-settings-icon.svg-4d2d8773c531c43062886eae43689de4.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/smart_graphics_settings/images/title-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucasbecker-dev/godot-smart-graphics-settings/9b13563421a1b8392746ebbd4ada8ef0ecc71dc7/addons/smart_graphics_settings/images/title-text.png
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/images/title-text.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://lddqovy3e0cd"
6 | path="res://.godot/imported/title-text.png-25aa98b4f9a3d7969c3e9bc241e8ce22.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/smart_graphics_settings/images/title-text.png"
14 | dest_files=["res://.godot/imported/title-text.png-25aa98b4f9a3d7969c3e9bc241e8ce22.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 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/plugin.cfg:
--------------------------------------------------------------------------------
1 | [plugin]
2 |
3 | name="Smart Graphics Settings"
4 | description="Adaptive graphics settings that automatically adjust based on performance"
5 | author="Smart Graphics Team"
6 | version="0.1.1"
7 | script="plugin.gd"
8 | icon="icon.svg"
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/plugin.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends EditorPlugin
3 |
4 | func _enter_tree() -> void:
5 | # Register the addon
6 | add_autoload_singleton("SmartGraphicsSettings", "res://addons/smart_graphics_settings/smart_graphics_settings.gd")
7 |
8 | # Register custom types with the icon
9 | var icon_path: String = "res://addons/smart_graphics_settings/images/smart-graphics-settings-icon.svg"
10 | var icon: Texture2D
11 |
12 | if ResourceLoader.exists(icon_path):
13 | icon = load(icon_path)
14 | else:
15 | push_warning("Smart Graphics Settings: Icon not found at %s" % icon_path)
16 |
17 | # Register custom classes with the icon
18 | var adaptive_graphics_script: String = "res://addons/smart_graphics_settings/adaptive_graphics.gd"
19 | var fps_monitor_script: String = "res://addons/smart_graphics_settings/fps_monitor.gd"
20 | var graphics_settings_manager_script: String = "res://addons/smart_graphics_settings/graphics_settings_manager.gd"
21 | var adaptive_graphics_ui_script: String = "res://addons/smart_graphics_settings/adaptive_graphics_ui.gd"
22 |
23 | if ResourceLoader.exists(adaptive_graphics_script):
24 | add_custom_type("AdaptiveGraphics", "Node", load(adaptive_graphics_script), icon)
25 | else:
26 | push_error("Smart Graphics Settings: AdaptiveGraphics script not found at %s" % adaptive_graphics_script)
27 |
28 | if ResourceLoader.exists(fps_monitor_script):
29 | add_custom_type("FPSMonitor", "Node", load(fps_monitor_script), icon)
30 | else:
31 | push_error("Smart Graphics Settings: FPSMonitor script not found at %s" % fps_monitor_script)
32 |
33 | if ResourceLoader.exists(graphics_settings_manager_script):
34 | add_custom_type("GraphicsSettingsManager", "Node", load(graphics_settings_manager_script), icon)
35 | else:
36 | push_error("Smart Graphics Settings: GraphicsSettingsManager script not found at %s" % graphics_settings_manager_script)
37 |
38 | if ResourceLoader.exists(adaptive_graphics_ui_script):
39 | add_custom_type("AdaptiveGraphicsUI", "Control", load(adaptive_graphics_ui_script), icon)
40 | else:
41 | push_error("Smart Graphics Settings: AdaptiveGraphicsUI script not found at %s" % adaptive_graphics_ui_script)
42 |
43 | print("Smart Graphics Settings: Plugin initialized successfully")
44 |
45 | func _exit_tree() -> void:
46 | # Clean up when the plugin is disabled
47 | remove_autoload_singleton("SmartGraphicsSettings")
48 |
49 | # Remove custom types
50 | remove_custom_type("AdaptiveGraphics")
51 | remove_custom_type("FPSMonitor")
52 | remove_custom_type("GraphicsSettingsManager")
53 | remove_custom_type("AdaptiveGraphicsUI")
54 |
55 | print("Smart Graphics Settings: Plugin cleaned up successfully")
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/plugin.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bootef3oowc4q
2 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/smart_graphics_settings.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | @icon("res://addons/smart_graphics_settings/images/smart-graphics-settings-icon.svg")
3 | extends Node
4 |
5 | ## Signal emitted when the AdaptiveGraphics node has been initialized.
6 | ## Connect to this signal to safely access adaptive_graphics after startup.
7 | signal initialized
8 |
9 | ## The main AdaptiveGraphics controller
10 | var adaptive_graphics: AdaptiveGraphics
11 |
12 | ## Whether the UI is currently visible
13 | var ui_visible: bool = false
14 |
15 | ## The UI instance
16 | var ui_instance: AdaptiveGraphicsUI
17 |
18 | ## UI scene resource
19 | var ui_scene: PackedScene = preload("res://addons/smart_graphics_settings/adaptive_graphics_ui.tscn")
20 |
21 | ## Platform information for optimizations
22 | var platform_info: Dictionary = {}
23 |
24 | func _ready() -> void:
25 | # Gather platform information
26 | platform_info = {
27 | "os_name": OS.get_name(),
28 | "model_name": OS.get_model_name(),
29 | "processor_name": OS.get_processor_name(),
30 | "processor_count": OS.get_processor_count()
31 | }
32 |
33 | # Only initialize adaptive graphics when running the game, not in the editor
34 | if not Engine.is_editor_hint():
35 | # Create the adaptive graphics controller using a deferred call
36 | # This ensures classes are registered before we try to use them
37 | call_deferred("_initialize_adaptive_graphics")
38 |
39 | # Register input action for toggling UI
40 | if not InputMap.has_action("toggle_graphics_settings"):
41 | InputMap.add_action("toggle_graphics_settings")
42 | var event: InputEventKey = InputEventKey.new()
43 | event.keycode = KEY_F7
44 | InputMap.action_add_event("toggle_graphics_settings", event)
45 |
46 | # Apply platform-specific optimizations
47 | call_deferred("_apply_platform_optimizations")
48 |
49 | func _initialize_adaptive_graphics() -> void:
50 | # Try to create the adaptive graphics controller
51 | if ClassDB.class_exists("AdaptiveGraphics") or Engine.has_singleton("AdaptiveGraphics") or ResourceLoader.exists("res://addons/smart_graphics_settings/adaptive_graphics.gd"):
52 | adaptive_graphics = AdaptiveGraphics.new()
53 |
54 | # Check if creation was successful before proceeding
55 | if adaptive_graphics:
56 | add_child(adaptive_graphics)
57 | # Emit the signal AFTER initialization is complete
58 | initialized.emit()
59 |
60 | # Wait for the settings manager to be initialized
61 | await get_tree().process_frame
62 |
63 | # Log the detected renderer
64 | if adaptive_graphics.settings_manager:
65 | var renderer_type: GraphicsSettingsManager.RendererType = adaptive_graphics.settings_manager.current_renderer
66 | var renderer_name: String = GraphicsSettingsManager.RendererType.keys()[renderer_type]
67 | print("Smart Graphics Settings: Detected renderer - ", renderer_name)
68 | else:
69 | push_error("Smart Graphics Settings: Failed to instantiate AdaptiveGraphics.")
70 | else:
71 | push_error("Smart Graphics Settings: AdaptiveGraphics class not found. Make sure the addon is properly installed.")
72 |
73 | func _apply_platform_optimizations() -> void:
74 | if not adaptive_graphics:
75 | await get_tree().process_frame
76 | if not adaptive_graphics:
77 | push_error("Smart Graphics Settings: Failed to apply platform optimizations - AdaptiveGraphics not initialized")
78 | return
79 |
80 | # Apply platform-specific optimizations
81 | match OS.get_name():
82 | "Android", "iOS":
83 | # Mobile platforms often benefit from more aggressive settings
84 | adaptive_graphics.fps_tolerance = 8 # Allow more variation on mobile
85 | adaptive_graphics.adjustment_cooldown = 5.0 # Less frequent adjustments to save battery
86 | print("Smart Graphics Settings: Applied mobile platform optimizations")
87 | "Web":
88 | # Web platform limitations
89 | adaptive_graphics.use_threading = false
90 | adaptive_graphics.threading_supported = false
91 | print("Smart Graphics Settings: Applied web platform optimizations")
92 | "Windows", "macOS", "Linux":
93 | # Desktop platforms can use more precise settings
94 | adaptive_graphics.fps_tolerance = 3
95 | adaptive_graphics.adjustment_cooldown = 2.0
96 | print("Smart Graphics Settings: Applied desktop platform optimizations")
97 |
98 | func _input(event: InputEvent) -> void:
99 | # Only process input when running the game, not in the editor
100 | if Engine.is_editor_hint():
101 | return
102 |
103 | # Only check for the action if it exists
104 | if InputMap.has_action("toggle_graphics_settings") and event.is_action_pressed("toggle_graphics_settings"):
105 | toggle_ui()
106 |
107 | ## Toggle the graphics settings UI
108 | func toggle_ui() -> void:
109 | if ui_visible:
110 | hide_ui()
111 | else:
112 | show_ui()
113 |
114 | ## Show the graphics settings UI
115 | func show_ui() -> void:
116 | if ui_instance:
117 | ui_instance.show()
118 | else:
119 | if not ui_scene:
120 | push_error("Smart Graphics Settings: UI scene not found")
121 | return
122 |
123 | ui_instance = ui_scene.instantiate() as AdaptiveGraphicsUI
124 | if not ui_instance:
125 | push_error("Smart Graphics Settings: Failed to instantiate UI")
126 | return
127 |
128 | if not adaptive_graphics:
129 | push_error("Smart Graphics Settings: AdaptiveGraphics not initialized")
130 | return
131 |
132 | # Set the path directly to the adaptive_graphics node
133 | if adaptive_graphics.is_inside_tree():
134 | ui_instance.adaptive_graphics_path = adaptive_graphics.get_path()
135 | else:
136 | # If the node isn't in the tree yet, we'll set the reference directly
137 | ui_instance.adaptive_graphics = adaptive_graphics
138 |
139 | var root: Viewport = get_tree().root
140 | if not root:
141 | push_error("Smart Graphics Settings: Failed to get scene root")
142 | return
143 |
144 | root.add_child(ui_instance)
145 |
146 | ui_visible = true
147 |
148 | ## Hide the graphics settings UI
149 | func hide_ui() -> void:
150 | if ui_instance:
151 | ui_instance.hide()
152 |
153 | ui_visible = false
154 |
155 | ## Apply a quality preset
156 | func apply_preset(preset: AdaptiveGraphics.QualityPreset) -> void:
157 | if adaptive_graphics:
158 | adaptive_graphics.apply_preset(preset)
159 |
160 | ## Enable or disable adaptive graphics
161 | func set_enabled(enabled: bool) -> void:
162 | if adaptive_graphics:
163 | adaptive_graphics.enabled = enabled
164 |
165 | ## Set the target FPS
166 | func set_target_fps(fps: int) -> void:
167 | if adaptive_graphics:
168 | adaptive_graphics.target_fps = fps
169 |
170 | ## Get the current average FPS
171 | func get_average_fps() -> float:
172 | if adaptive_graphics and adaptive_graphics.fps_monitor:
173 | return adaptive_graphics.fps_monitor.get_average_fps()
174 | return 0.0
175 |
176 | ## Check if FPS is stable
177 | func is_fps_stable() -> bool:
178 | if adaptive_graphics and adaptive_graphics.fps_monitor:
179 | return adaptive_graphics.fps_monitor.is_fps_stable()
180 | return false
181 |
182 | ## Set whether to match the target FPS to the display refresh rate
183 | func set_match_refresh_rate(enabled: bool) -> void:
184 | if adaptive_graphics:
185 | adaptive_graphics.match_refresh_rate = enabled
186 | if enabled:
187 | adaptive_graphics.set_target_fps_to_refresh_rate()
188 |
189 | ## Set the target FPS to match the display refresh rate
190 | func set_target_fps_to_refresh_rate() -> void:
191 | if adaptive_graphics:
192 | adaptive_graphics.set_target_fps_to_refresh_rate()
193 |
194 | ## Get the display refresh rate
195 | func get_display_refresh_rate() -> float:
196 | if adaptive_graphics:
197 | adaptive_graphics.update_vsync_and_refresh_rate()
198 | return adaptive_graphics.display_refresh_rate
199 | return 60.0
200 |
201 | ## Set the VSync mode
202 | func set_vsync_mode(mode: int) -> void:
203 | if adaptive_graphics:
204 | adaptive_graphics.set_vsync_mode(mode)
205 |
206 | ## Get the current VSync mode
207 | func get_vsync_mode() -> int:
208 | if adaptive_graphics:
209 | adaptive_graphics.update_vsync_and_refresh_rate()
210 | return adaptive_graphics.current_vsync_mode
211 | return DisplayServer.VSYNC_ENABLED # Default fallback
212 |
213 | ## Get detailed information about the current state
214 | func get_status_info() -> Dictionary:
215 | var info: Dictionary = {
216 | "enabled": false,
217 | "target_fps": 60,
218 | "current_fps": 0.0,
219 | "fps_stable": false,
220 | "vsync_mode": DisplayServer.VSYNC_ENABLED,
221 | "refresh_rate": 60.0,
222 | "renderer": "Unknown",
223 | "current_action": "Not initialized",
224 | "threading": false,
225 | "platform": platform_info
226 | }
227 |
228 | if adaptive_graphics:
229 | info.enabled = adaptive_graphics.enabled
230 | info.target_fps = adaptive_graphics.target_fps
231 | info.current_fps = get_average_fps()
232 | info.fps_stable = is_fps_stable()
233 | info.vsync_mode = get_vsync_mode()
234 | info.refresh_rate = get_display_refresh_rate()
235 | info.current_action = adaptive_graphics.current_action
236 | info.threading = adaptive_graphics.use_threading
237 |
238 | if adaptive_graphics.settings_manager:
239 | var renderer_type: GraphicsSettingsManager.RendererType = adaptive_graphics.settings_manager.current_renderer
240 | info.renderer = GraphicsSettingsManager.RendererType.keys()[renderer_type]
241 |
242 | return info
243 |
244 | ## Get the adaptive graphics controller
245 | func get_adaptive_graphics() -> AdaptiveGraphics:
246 | return adaptive_graphics
247 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/smart_graphics_settings.gd.uid:
--------------------------------------------------------------------------------
1 | uid://d1pnver04h28x
2 |
--------------------------------------------------------------------------------
/addons/smart_graphics_settings/ui_panel_background.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="StyleBoxFlat" format=3 uid="uid://by7x1kn8sb4eu"]
2 |
3 | [resource]
4 | bg_color = Color(0.1, 0.1, 0.1, 0.8)
5 | corner_radius_top_left = 8
6 | corner_radius_top_right = 8
7 | corner_radius_bottom_right = 8
8 | corner_radius_bottom_left = 8
9 | shadow_color = Color(0, 0, 0, 0.3)
10 | shadow_size = 4
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="Smart Graphics Settings"
14 | config/description="Adaptive graphics settings for Godot 4.4"
15 | config/version="0.1.1"
16 | run/main_scene="uid://c8yvnqnvqnvn"
17 | config/features=PackedStringArray("4.4", "Forward Plus")
18 | config/icon="res://addons/smart_graphics_settings/smart-graphics-settings-icon.svg"
19 |
20 | [autoload]
21 |
22 | SmartGraphicsSettings="*res://addons/smart_graphics_settings/smart_graphics_settings.gd"
23 |
24 | [editor_plugins]
25 |
26 | enabled=PackedStringArray("res://addons/smart_graphics_settings/plugin.cfg")
27 |
28 | [input]
29 |
30 | ui_f7={
31 | "deadzone": 0.5,
32 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194342,"physical_keycode":4194342,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------