Преглед изворни кода

Basic warning support implementation for the Godot Shading Language.

Yuri Roubinsky пре 4 година
родитељ
комит
084648bd18

+ 149 - 7
editor/plugins/shader_editor_plugin.cpp

@@ -37,12 +37,18 @@
 #include "editor/editor_node.h"
 #include "editor/editor_scale.h"
 #include "editor/editor_settings.h"
+#include "editor/project_settings_editor.h"
 #include "editor/property_editor.h"
 #include "servers/display_server.h"
 #include "servers/rendering/shader_types.h"
 
 /*** SHADER SCRIPT EDITOR ****/
 
+static bool saved_warnings_enabled = false;
+static bool saved_treat_warning_as_errors = false;
+static Map<ShaderWarning::Code, bool> saved_warnings;
+static uint32_t saved_warning_flags = 0U;
+
 Ref<Shader> ShaderTextEditor::get_edited_shader() const {
 	return shader;
 }
@@ -82,6 +88,10 @@ void ShaderTextEditor::reload_text() {
 	update_line_and_column();
 }
 
+void ShaderTextEditor::set_warnings_panel(RichTextLabel *p_warnings_panel) {
+	warnings_panel = p_warnings_panel;
+}
+
 void ShaderTextEditor::_load_theme_settings() {
 	CodeEdit *text_editor = get_text_editor();
 	Color updated_marked_line_color = EDITOR_GET("text_editor/highlighting/mark_color");
@@ -141,6 +151,12 @@ void ShaderTextEditor::_load_theme_settings() {
 	syntax_highlighter->clear_color_regions();
 	syntax_highlighter->add_color_region("/*", "*/", comment_color, false);
 	syntax_highlighter->add_color_region("//", "", comment_color, true);
+
+	if (warnings_panel) {
+		// Warnings panel
+		warnings_panel->add_theme_font_override("normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts"));
+		warnings_panel->add_theme_font_size_override("normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("main_size", "EditorFonts"));
+	}
 }
 
 void ShaderTextEditor::_check_shader_mode() {
@@ -187,6 +203,9 @@ void ShaderTextEditor::_validate_script() {
 
 	ShaderLanguage sl;
 
+	sl.enable_warning_checking(saved_warnings_enabled);
+	sl.set_warning_flags(saved_warning_flags);
+
 	Error err = sl.compile(code, ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())), ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())), ShaderLanguage::VaryingFunctionNames(), ShaderTypes::get_singleton()->get_types(), _get_global_variable_type);
 
 	if (err != OK) {
@@ -197,7 +216,6 @@ void ShaderTextEditor::_validate_script() {
 			get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
 		}
 		get_text_editor()->set_line_background_color(sl.get_error_line() - 1, marked_line_color);
-
 	} else {
 		for (int i = 0; i < get_text_editor()->get_line_count(); i++) {
 			get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
@@ -205,9 +223,60 @@ void ShaderTextEditor::_validate_script() {
 		set_error("");
 	}
 
+	if (warnings.size() > 0 || err != OK) {
+		warnings_panel->clear();
+	}
+	warnings.clear();
+	for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) {
+		warnings.push_back(E->get());
+	}
+	if (warnings.size() > 0 && err == OK) {
+		warnings.sort_custom<WarningsComparator>();
+		_update_warning_panel();
+	} else {
+		set_warning_nb(0);
+	}
 	emit_signal("script_changed");
 }
 
+void ShaderTextEditor::_update_warning_panel() {
+	int warning_count = 0;
+
+	warnings_panel->push_table(2);
+	for (int i = 0; i < warnings.size(); i++) {
+		ShaderWarning &w = warnings[i];
+
+		if (warning_count == 0) {
+			if (saved_treat_warning_as_errors) {
+				String error_text = "error(" + itos(w.get_line()) + "): " + w.get_message() + " " + TTR("Warnings should be fixed to prevent errors.");
+				set_error_pos(w.get_line() - 1, 0);
+				set_error(error_text);
+				get_text_editor()->set_line_background_color(w.get_line() - 1, marked_line_color);
+			}
+		}
+
+		warning_count++;
+
+		// First cell.
+		warnings_panel->push_cell();
+		warnings_panel->push_meta(w.get_line() - 1);
+		warnings_panel->push_color(warnings_panel->get_theme_color("warning_color", "Editor"));
+		warnings_panel->add_text(TTR("Line") + " " + itos(w.get_line()));
+		warnings_panel->add_text(" (" + w.get_name() + "):");
+		warnings_panel->pop(); // Color.
+		warnings_panel->pop(); // Meta goto.
+		warnings_panel->pop(); // Cell.
+
+		// Second cell.
+		warnings_panel->push_cell();
+		warnings_panel->add_text(w.get_message());
+		warnings_panel->pop(); // Cell.
+	}
+	warnings_panel->pop(); // Table.
+
+	set_warning_nb(warning_count);
+}
+
 void ShaderTextEditor::_bind_methods() {
 }
 
@@ -321,10 +390,6 @@ void ShaderEditor::_notification(int p_what) {
 	}
 }
 
-void ShaderEditor::_params_changed() {
-	shader_editor->_validate_script();
-}
-
 void ShaderEditor::_editor_settings_changed() {
 	shader_editor->update_editor_settings();
 
@@ -333,8 +398,19 @@ void ShaderEditor::_editor_settings_changed() {
 	shader_editor->get_text_editor()->set_draw_executing_lines_gutter(false);
 }
 
+void ShaderEditor::_show_warnings_panel(bool p_show) {
+	warnings_panel->set_visible(p_show);
+}
+
+void ShaderEditor::_warning_clicked(Variant p_line) {
+	if (p_line.get_type() == Variant::INT) {
+		shader_editor->get_text_editor()->cursor_set_line(p_line.operator int64_t());
+	}
+}
+
 void ShaderEditor::_bind_methods() {
-	ClassDB::bind_method("_params_changed", &ShaderEditor::_params_changed);
+	ClassDB::bind_method("_show_warnings_panel", &ShaderEditor::_show_warnings_panel);
+	ClassDB::bind_method("_warning_clicked", &ShaderEditor::_warning_clicked);
 }
 
 void ShaderEditor::ensure_select_current() {
@@ -352,6 +428,47 @@ void ShaderEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
 	shader_editor->goto_line_selection(p_line, p_begin, p_end);
 }
 
+void ShaderEditor::_project_settings_changed() {
+	_update_warnings(true);
+}
+
+void ShaderEditor::_update_warnings(bool p_validate) {
+	bool changed = false;
+
+	bool warnings_enabled = GLOBAL_GET("debug/shader_language/warnings/enable").booleanize();
+	if (warnings_enabled != saved_warnings_enabled) {
+		saved_warnings_enabled = warnings_enabled;
+		changed = true;
+	}
+
+	bool treat_warning_as_errors = GLOBAL_GET("debug/shader_language/warnings/treat_warnings_as_errors").booleanize();
+	if (treat_warning_as_errors != saved_treat_warning_as_errors) {
+		saved_treat_warning_as_errors = treat_warning_as_errors;
+		changed = true;
+	}
+
+	bool update_flags = false;
+
+	for (int i = 0; i < ShaderWarning::WARNING_MAX; i++) {
+		ShaderWarning::Code code = (ShaderWarning::Code)i;
+		bool value = GLOBAL_GET("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code(code).to_lower());
+
+		if (saved_warnings[code] != value) {
+			saved_warnings[code] = value;
+			update_flags = true;
+			changed = true;
+		}
+	}
+
+	if (update_flags) {
+		saved_warning_flags = (uint32_t)ShaderWarning::get_flags_from_codemap(saved_warnings);
+	}
+
+	if (p_validate && changed && shader_editor && shader_editor->get_edited_shader().is_valid()) {
+		shader_editor->validate_script();
+	}
+}
+
 void ShaderEditor::_check_for_external_edit() {
 	if (shader.is_null() || !shader.is_valid()) {
 		return;
@@ -523,13 +640,22 @@ void ShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) {
 }
 
 ShaderEditor::ShaderEditor(EditorNode *p_node) {
+	GLOBAL_DEF("debug/shader_language/warnings/enable", true);
+	GLOBAL_DEF("debug/shader_language/warnings/treat_warnings_as_errors", false);
+	for (int i = 0; i < (int)ShaderWarning::WARNING_MAX; i++) {
+		GLOBAL_DEF("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code((ShaderWarning::Code)i).to_lower(), true);
+	}
+	_update_warnings(false);
+
 	shader_editor = memnew(ShaderTextEditor);
 	shader_editor->set_v_size_flags(SIZE_EXPAND_FILL);
 	shader_editor->add_theme_constant_override("separation", 0);
 	shader_editor->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
 
+	shader_editor->connect("show_warnings_panel", callable_mp(this, &ShaderEditor::_show_warnings_panel));
 	shader_editor->connect("script_changed", callable_mp(this, &ShaderEditor::apply_shaders));
 	EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ShaderEditor::_editor_settings_changed));
+	ProjectSettingsEditor::get_singleton()->connect("confirmed", callable_mp(this, &ShaderEditor::_project_settings_changed));
 
 	shader_editor->get_text_editor()->set_callhint_settings(
 			EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"),
@@ -614,7 +740,23 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) {
 	hbc->add_child(goto_menu);
 	hbc->add_child(help_menu);
 	hbc->add_theme_style_override("panel", p_node->get_gui_base()->get_theme_stylebox("ScriptEditorPanel", "EditorStyles"));
-	main_container->add_child(shader_editor);
+
+	VSplitContainer *editor_box = memnew(VSplitContainer);
+	main_container->add_child(editor_box);
+	editor_box->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+	editor_box->set_v_size_flags(SIZE_EXPAND_FILL);
+	editor_box->add_child(shader_editor);
+
+	warnings_panel = memnew(RichTextLabel);
+	warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));
+	warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL);
+	warnings_panel->set_meta_underline(true);
+	warnings_panel->set_selection_enabled(true);
+	warnings_panel->set_focus_mode(FOCUS_CLICK);
+	warnings_panel->hide();
+	warnings_panel->connect("meta_clicked", callable_mp(this, &ShaderEditor::_warning_clicked));
+	editor_box->add_child(warnings_panel);
+	shader_editor->set_warnings_panel(warnings_panel);
 
 	goto_line_dialog = memnew(GotoLineDialog);
 	add_child(goto_line_dialog);

+ 14 - 1
editor/plugins/shader_editor_plugin.h

@@ -35,6 +35,7 @@
 #include "editor/editor_plugin.h"
 #include "scene/gui/menu_button.h"
 #include "scene/gui/panel_container.h"
+#include "scene/gui/rich_text_label.h"
 #include "scene/gui/tab_container.h"
 #include "scene/gui/text_edit.h"
 #include "scene/main/timer.h"
@@ -46,10 +47,17 @@ class ShaderTextEditor : public CodeTextEditor {
 
 	Color marked_line_color = Color(1, 1, 1);
 
+	struct WarningsComparator {
+		_ALWAYS_INLINE_ bool operator()(const ShaderWarning &p_a, const ShaderWarning &p_b) const { return (p_a.get_line() < p_b.get_line()); }
+	};
+
 	Ref<CodeHighlighter> syntax_highlighter;
+	RichTextLabel *warnings_panel = nullptr;
 	Ref<Shader> shader;
+	List<ShaderWarning> warnings;
 
 	void _check_shader_mode();
+	void _update_warning_panel();
 
 protected:
 	static void _bind_methods();
@@ -61,6 +69,7 @@ public:
 	virtual void _validate_script() override;
 
 	void reload_text();
+	void set_warnings_panel(RichTextLabel *p_warnings_panel);
 
 	Ref<Shader> get_edited_shader() const;
 	void set_edited_shader(const Ref<Shader> &p_shader);
@@ -102,6 +111,7 @@ class ShaderEditor : public PanelContainer {
 	PopupMenu *bookmarks_menu;
 	MenuButton *help_menu;
 	PopupMenu *context_menu;
+	RichTextLabel *warnings_panel = nullptr;
 	uint64_t idle;
 
 	GotoLineDialog *goto_line_dialog;
@@ -111,13 +121,16 @@ class ShaderEditor : public PanelContainer {
 	ShaderTextEditor *shader_editor;
 
 	void _menu_option(int p_option);
-	void _params_changed();
 	mutable Ref<Shader> shader;
 
 	void _editor_settings_changed();
+	void _project_settings_changed();
 
 	void _check_for_external_edit();
 	void _reload_shader_from_disk();
+	void _show_warnings_panel(bool p_show);
+	void _warning_clicked(Variant p_line);
+	void _update_warnings(bool p_validate);
 
 protected:
 	void _notification(int p_what);

+ 141 - 9
servers/rendering/shader_language.cpp

@@ -33,6 +33,8 @@
 #include "core/string/print_string.h"
 #include "servers/rendering_server.h"
 
+#define HAS_WARNING(flag) (warning_flags & flag)
+
 static bool _is_text_char(char32_t c) {
 	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
 }
@@ -901,6 +903,8 @@ bool ShaderLanguage::is_token_nonvoid_datatype(TokenType p_type) {
 
 void ShaderLanguage::clear() {
 	current_function = StringName();
+	last_name = StringName();
+	last_type = IDENTIFIER_MAX;
 
 	completion_type = COMPLETION_NONE;
 	completion_block = nullptr;
@@ -908,12 +912,20 @@ void ShaderLanguage::clear() {
 	completion_class = SubClassTag::TAG_GLOBAL;
 	completion_struct = StringName();
 
+#ifdef DEBUG_ENABLED
+	used_constants.clear();
+	used_varyings.clear();
+	used_uniforms.clear();
+	used_functions.clear();
+	used_structs.clear();
+	warnings.clear();
+#endif // DEBUG_ENABLED
+
 	error_line = 0;
 	tk_line = 1;
 	char_idx = 0;
 	error_set = false;
 	error_str = "";
-	last_const = false;
 	while (nodes) {
 		Node *n = nodes;
 		nodes = nodes->next;
@@ -921,6 +933,35 @@ void ShaderLanguage::clear() {
 	}
 }
 
+#ifdef DEBUG_ENABLED
+void ShaderLanguage::_parse_used_identifier(const StringName &p_identifier, IdentifierType p_type) {
+	switch (p_type) {
+		case IdentifierType::IDENTIFIER_CONSTANT:
+			if (HAS_WARNING(ShaderWarning::UNUSED_CONSTANT_FLAG) && used_constants.has(p_identifier)) {
+				used_constants[p_identifier].used = true;
+			}
+			break;
+		case IdentifierType::IDENTIFIER_VARYING:
+			if (HAS_WARNING(ShaderWarning::UNUSED_VARYING_FLAG) && used_varyings.has(p_identifier)) {
+				used_varyings[p_identifier].used = true;
+			}
+			break;
+		case IdentifierType::IDENTIFIER_UNIFORM:
+			if (HAS_WARNING(ShaderWarning::UNUSED_UNIFORM_FLAG) && used_uniforms.has(p_identifier)) {
+				used_uniforms[p_identifier].used = true;
+			}
+			break;
+		case IdentifierType::IDENTIFIER_FUNCTION:
+			if (HAS_WARNING(ShaderWarning::UNUSED_FUNCTION_FLAG) && used_functions.has(p_identifier)) {
+				used_functions[p_identifier].used = true;
+			}
+			break;
+		default:
+			break;
+	}
+}
+#endif // DEBUG_ENABLED
+
 bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_reassign, const FunctionInfo &p_function_info, const StringName &p_identifier, DataType *r_data_type, IdentifierType *r_type, bool *r_is_const, int *r_array_size, StringName *r_struct_name, ConstantNode::Value *r_constant_value) {
 	if (p_function_info.built_ins.has(p_identifier)) {
 		if (r_data_type) {
@@ -3602,6 +3643,11 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 
 			if (shader->structs.has(identifier)) {
 				pstruct = shader->structs[identifier].shader_struct;
+#ifdef DEBUG_ENABLED
+				if (check_warnings && HAS_WARNING(ShaderWarning::UNUSED_STRUCT_FLAG) && used_structs.has(identifier)) {
+					used_structs[identifier].used = true;
+				}
+#endif // DEBUG_ENABLED
 				struct_init = true;
 			}
 
@@ -3825,11 +3871,17 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 						}
 					}
 					expr = func;
+#ifdef DEBUG_ENABLED
+					if (check_warnings) {
+						_parse_used_identifier(name, IdentifierType::IDENTIFIER_FUNCTION);
+					}
+#endif // DEBUG_ENABLED
 				}
 			} else {
 				//an identifier
 
-				last_const = false;
+				last_name = identifier;
+				last_type = IDENTIFIER_MAX;
 				_set_tkpos(pos);
 
 				DataType data_type;
@@ -3874,12 +3926,16 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 							}
 						}
 					}
-					last_const = is_const;
 
 					if (ident_type == IDENTIFIER_FUNCTION) {
 						_set_error("Can't use function as identifier: " + String(identifier));
 						return nullptr;
 					}
+					if (is_const) {
+						last_type = IDENTIFIER_CONSTANT;
+					} else {
+						last_type = ident_type;
+					}
 				}
 
 				Node *index_expression = nullptr;
@@ -3953,7 +4009,6 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 					arrname->assign_expression = assign_expression;
 					arrname->is_const = is_const;
 					expr = arrname;
-
 				} else {
 					VariableNode *varname = alloc_node<VariableNode>();
 					varname->name = identifier;
@@ -3962,6 +4017,11 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 					varname->struct_name = struct_name;
 					expr = varname;
 				}
+#ifdef DEBUG_ENABLED
+				if (check_warnings) {
+					_parse_used_identifier(identifier, ident_type);
+				}
+#endif // DEBUG_ENABLED
 			}
 		} else if (tk.type == TK_OP_ADD) {
 			continue; //this one does nothing
@@ -4290,8 +4350,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 				if (array_size > 0) {
 					tk = _get_token();
 					if (tk.type == TK_OP_ASSIGN) {
-						if (last_const) {
-							last_const = false;
+						if (last_type == IDENTIFIER_CONSTANT) {
 							_set_error("Constants cannot be modified.");
 							return nullptr;
 						}
@@ -4648,9 +4707,10 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 
 			bool unary = false;
 			bool ternary = false;
+			Operator op = expression[i].op;
 
 			int priority;
-			switch (expression[i].op) {
+			switch (op) {
 				case OP_EQUAL:
 					priority = 8;
 					break;
@@ -4771,6 +4831,12 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
 					ERR_FAIL_V(nullptr); //unexpected operator
 			}
 
+#if DEBUG_ENABLED
+			if (check_warnings && HAS_WARNING(ShaderWarning::FLOAT_COMPARISON_FLAG) && (op == OP_EQUAL || op == OP_NOT_EQUAL) && expression[i - 1].node->get_datatype() == TYPE_FLOAT && expression[i + 1].node->get_datatype() == TYPE_FLOAT) {
+				_add_line_warning(ShaderWarning::FLOAT_COMPARISON);
+			}
+#endif // DEBUG_ENABLED
+
 			if (priority < min_priority) {
 				// < is used for left to right (default)
 				// <= is used for right to left
@@ -5483,7 +5549,6 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
 				p_block->statements.push_back(vardecl);
 
 				p_block->variables[name] = var;
-
 				if (tk.type == TK_COMMA) {
 					if (p_block->block_type == BlockNode::BLOCK_TYPE_FOR) {
 						_set_error("Multiple declarations in 'for' loop are not implemented yet.");
@@ -6313,7 +6378,11 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 				}
 				shader->structs[st.name] = st;
 				shader->vstructs.push_back(st); // struct's order is important!
-
+#ifdef DEBUG_ENABLED
+				if (check_warnings && HAS_WARNING(ShaderWarning::UNUSED_STRUCT_FLAG)) {
+					used_structs.insert(st.name, Usage(tk_line));
+				}
+#endif // DEBUG_ENABLED
 			} break;
 			case TK_GLOBAL: {
 				tk = _get_token();
@@ -6660,6 +6729,12 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 					}
 
 					shader->uniforms[name] = uniform2;
+#ifdef DEBUG_ENABLED
+					if (check_warnings && HAS_WARNING(ShaderWarning::UNUSED_UNIFORM_FLAG)) {
+						used_uniforms.insert(name, Usage(tk_line));
+					}
+#endif // DEBUG_ENABLED
+
 					//reset scope for next uniform
 					uniform_scope = ShaderNode::Uniform::SCOPE_LOCAL;
 
@@ -6703,6 +6778,11 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 					}
 
 					shader->varyings[name] = varying;
+#ifdef DEBUG_ENABLED
+					if (check_warnings && HAS_WARNING(ShaderWarning::UNUSED_VARYING_FLAG)) {
+						used_varyings.insert(name, Usage(tk_line));
+					}
+#endif // DEBUG_ENABLED
 				}
 
 			} break;
@@ -7049,6 +7129,11 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 
 						shader->constants[name] = constant;
 						shader->vconstants.push_back(constant);
+#ifdef DEBUG_ENABLED
+						if (check_warnings && HAS_WARNING(ShaderWarning::UNUSED_CONSTANT_FLAG)) {
+							used_constants.insert(name, Usage(tk_line));
+						}
+#endif // DEBUG_ENABLED
 
 						if (tk.type == TK_COMMA) {
 							tk = _get_token();
@@ -7110,6 +7195,12 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
 
 				if (p_functions.has(name)) {
 					func_node->can_discard = p_functions[name].can_discard;
+				} else {
+#ifdef DEBUG_ENABLED
+					if (check_warnings && HAS_WARNING(ShaderWarning::UNUSED_FUNCTION_FLAG)) {
+						used_functions.insert(name, Usage(tk_line));
+					}
+#endif // DEBUG_ENABLED
 				}
 
 				func_node->body = alloc_node<BlockNode>();
@@ -7443,6 +7534,33 @@ String ShaderLanguage::get_shader_type(const String &p_code) {
 	return String();
 }
 
+#ifdef DEBUG_ENABLED
+void ShaderLanguage::_check_warning_accums() {
+	for (Map<ShaderWarning::Code, Map<StringName, Usage> *>::Element *E = warnings_check_map.front(); E; E = E->next()) {
+		for (const Map<StringName, Usage>::Element *U = (*E->get()).front(); U; U = U->next()) {
+			if (!U->get().used) {
+				_add_warning(E->key(), U->get().decl_line, U->key());
+			}
+		}
+	}
+}
+List<ShaderWarning>::Element *ShaderLanguage::get_warnings_ptr() {
+	return warnings.front();
+}
+void ShaderLanguage::enable_warning_checking(bool p_enabled) {
+	check_warnings = p_enabled;
+}
+bool ShaderLanguage::is_warning_checking_enabled() const {
+	return check_warnings;
+}
+void ShaderLanguage::set_warning_flags(uint32_t p_flags) {
+	warning_flags = p_flags;
+}
+uint32_t ShaderLanguage::get_warning_flags() const {
+	return warning_flags;
+}
+#endif // DEBUG_ENABLED
+
 Error ShaderLanguage::compile(const String &p_code, const Map<StringName, FunctionInfo> &p_functions, const Vector<StringName> &p_render_modes, const VaryingFunctionNames &p_varying_function_names, const Set<String> &p_shader_types, GlobalVariableGetTypeFunc p_global_variable_type_func) {
 	clear();
 
@@ -7455,6 +7573,12 @@ Error ShaderLanguage::compile(const String &p_code, const Map<StringName, Functi
 	shader = alloc_node<ShaderNode>();
 	Error err = _parse_shader(p_functions, p_render_modes, p_shader_types);
 
+#ifdef DEBUG_ENABLED
+	if (check_warnings) {
+		_check_warning_accums();
+	}
+#endif // DEBUG_ENABLED
+
 	if (err != OK) {
 		return err;
 	}
@@ -7864,6 +7988,14 @@ ShaderLanguage::ShaderNode *ShaderLanguage::get_shader() {
 ShaderLanguage::ShaderLanguage() {
 	nodes = nullptr;
 	completion_class = TAG_GLOBAL;
+
+#if DEBUG_ENABLED
+	warnings_check_map.insert(ShaderWarning::UNUSED_CONSTANT, &used_constants);
+	warnings_check_map.insert(ShaderWarning::UNUSED_FUNCTION, &used_functions);
+	warnings_check_map.insert(ShaderWarning::UNUSED_STRUCT, &used_structs);
+	warnings_check_map.insert(ShaderWarning::UNUSED_UNIFORM, &used_uniforms);
+	warnings_check_map.insert(ShaderWarning::UNUSED_VARYING, &used_varyings);
+#endif // DEBUG_ENABLED
 }
 
 ShaderLanguage::~ShaderLanguage() {

+ 51 - 1
servers/rendering/shader_language.h

@@ -39,6 +39,10 @@
 #include "core/typedefs.h"
 #include "core/variant/variant.h"
 
+#ifdef DEBUG_ENABLED
+#include "shader_warnings.h"
+#endif // DEBUG_ENABLED
+
 class ShaderLanguage {
 public:
 	struct TkPos {
@@ -803,12 +807,42 @@ private:
 	String error_str;
 	int error_line;
 
+#ifdef DEBUG_ENABLED
+	struct Usage {
+		int decl_line;
+		bool used = false;
+		Usage(int p_decl_line = -1) {
+			decl_line = p_decl_line;
+		}
+	};
+
+	Map<StringName, Usage> used_constants;
+	Map<StringName, Usage> used_varyings;
+	Map<StringName, Usage> used_uniforms;
+	Map<StringName, Usage> used_functions;
+	Map<StringName, Usage> used_structs;
+	Map<ShaderWarning::Code, Map<StringName, Usage> *> warnings_check_map;
+
+	List<ShaderWarning> warnings;
+
+	bool check_warnings = false;
+	uint32_t warning_flags;
+
+	void _add_line_warning(ShaderWarning::Code p_code, const StringName &p_subject = "") {
+		warnings.push_back(ShaderWarning(p_code, tk_line, p_subject));
+	}
+	void _add_warning(ShaderWarning::Code p_code, int p_line, const StringName &p_subject = "") {
+		warnings.push_back(ShaderWarning(p_code, p_line, p_subject));
+	}
+	void _check_warning_accums();
+#endif // DEBUG_ENABLED
+
 	String code;
 	int char_idx;
 	int tk_line;
 
 	StringName current_function;
-	bool last_const = false;
+	StringName last_name;
 
 	VaryingFunctionNames varying_function_names;
 
@@ -849,9 +883,15 @@ private:
 		IDENTIFIER_LOCAL_VAR,
 		IDENTIFIER_BUILTIN_VAR,
 		IDENTIFIER_CONSTANT,
+		IDENTIFIER_MAX,
 	};
 
+	IdentifierType last_type = IDENTIFIER_MAX;
+
 	bool _find_identifier(const BlockNode *p_block, bool p_allow_reassign, const FunctionInfo &p_function_info, const StringName &p_identifier, DataType *r_data_type = nullptr, IdentifierType *r_type = nullptr, bool *r_is_const = nullptr, int *r_array_size = nullptr, StringName *r_struct_name = nullptr, ConstantNode::Value *r_constant_value = nullptr);
+#ifdef DEBUG_ENABLED
+	void _parse_used_identifier(const StringName &p_identifier, IdentifierType p_type);
+#endif // DEBUG_ENABLED
 	bool _is_operator_assign(Operator p_op) const;
 	bool _validate_assign(Node *p_node, const FunctionInfo &p_function_info, String *r_message = nullptr);
 	bool _validate_operator(OperatorNode *p_op, DataType *r_ret_type = nullptr);
@@ -910,6 +950,16 @@ private:
 	Error _find_last_flow_op_in_op(ControlFlowNode *p_flow, FlowOperation p_op);
 
 public:
+#ifdef DEBUG_ENABLED
+	List<ShaderWarning>::Element *get_warnings_ptr();
+
+	void enable_warning_checking(bool p_enabled);
+	bool is_warning_checking_enabled() const;
+
+	void set_warning_flags(uint32_t p_flags);
+	uint32_t get_warning_flags() const;
+#endif // DEBUG_ENABLED
+
 	//static void get_keyword_list(ShaderType p_type,List<String> *p_keywords);
 
 	void clear();

+ 131 - 0
servers/rendering/shader_warnings.cpp

@@ -0,0 +1,131 @@
+/*************************************************************************/
+/*  shader_warnings.cpp                                                  */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "shader_warnings.h"
+#include "core/variant/variant.h"
+
+#ifdef DEBUG_ENABLED
+
+ShaderWarning::Code ShaderWarning::get_code() const {
+	return code;
+}
+
+int ShaderWarning::get_line() const {
+	return line;
+}
+
+const StringName &ShaderWarning::get_subject() const {
+	return subject;
+}
+
+String ShaderWarning::get_message() const {
+	switch (code) {
+		case FLOAT_COMPARISON:
+			return vformat("Direct floating-point comparison (this may not evaluate to `true` as you expect). Instead, use `abs(a - b) < 0.0001` for an approximate but predictable comparison.");
+		case UNUSED_CONSTANT:
+			return vformat("The const '%s' is declared but never used.", subject);
+		case UNUSED_FUNCTION:
+			return vformat("The function '%s' is declared but never used.", subject);
+		case UNUSED_STRUCT:
+			return vformat("The struct '%s' is declared but never used.", subject);
+		case UNUSED_UNIFORM:
+			return vformat("The uniform '%s' is declared but never used.", subject);
+		case UNUSED_VARYING:
+			return vformat("The varying '%s' is declared but never used.", subject);
+		default:
+			break;
+	}
+	return String();
+}
+
+String ShaderWarning::get_name() const {
+	return get_name_from_code(code);
+}
+
+String ShaderWarning::get_name_from_code(Code p_code) {
+	ERR_FAIL_INDEX_V(p_code, WARNING_MAX, String());
+
+	static const char *names[] = {
+		"FLOAT_COMPARISON",
+		"UNUSED_CONSTANT",
+		"UNUSED_FUNCTION",
+		"UNUSED_STRUCT",
+		"UNUSED_UNIFORM",
+		"UNUSED_VARYING",
+	};
+
+	static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
+
+	return names[(int)p_code];
+}
+
+ShaderWarning::Code ShaderWarning::get_code_from_name(const String &p_name) {
+	for (int i = 0; i < WARNING_MAX; i++) {
+		if (get_name_from_code((Code)i) == p_name) {
+			return (Code)i;
+		}
+	}
+
+	ERR_FAIL_V_MSG(WARNING_MAX, "Invalid shader warning name: " + p_name);
+}
+
+static Map<int, uint32_t> *code_to_flags_map = nullptr;
+
+static void init_code_to_flags_map() {
+	code_to_flags_map = memnew((Map<int, uint32_t>));
+	code_to_flags_map->insert(ShaderWarning::FLOAT_COMPARISON, ShaderWarning::FLOAT_COMPARISON_FLAG);
+	code_to_flags_map->insert(ShaderWarning::UNUSED_CONSTANT, ShaderWarning::UNUSED_CONSTANT_FLAG);
+	code_to_flags_map->insert(ShaderWarning::UNUSED_FUNCTION, ShaderWarning::UNUSED_FUNCTION_FLAG);
+	code_to_flags_map->insert(ShaderWarning::UNUSED_STRUCT, ShaderWarning::UNUSED_STRUCT_FLAG);
+	code_to_flags_map->insert(ShaderWarning::UNUSED_UNIFORM, ShaderWarning::UNUSED_UNIFORM_FLAG);
+	code_to_flags_map->insert(ShaderWarning::UNUSED_VARYING, ShaderWarning::UNUSED_VARYING_FLAG);
+}
+
+ShaderWarning::CodeFlags ShaderWarning::get_flags_from_codemap(const Map<Code, bool> &p_map) {
+	uint32_t result = 0U;
+
+	if (code_to_flags_map == nullptr) {
+		init_code_to_flags_map();
+	}
+
+	for (Map<Code, bool>::Element *E = p_map.front(); E; E = E->next()) {
+		if (E->get()) {
+			ERR_FAIL_COND_V(!code_to_flags_map->has((int)E->key()), ShaderWarning::NONE_FLAG);
+			result |= (*code_to_flags_map)[(int)E->key()];
+		}
+	}
+	return (CodeFlags)result;
+}
+
+ShaderWarning::ShaderWarning(Code p_code, int p_line, const StringName &p_subject) :
+		code(p_code), line(p_line), subject(p_subject) {
+}
+
+#endif // DEBUG_ENABLED

+ 83 - 0
servers/rendering/shader_warnings.h

@@ -0,0 +1,83 @@
+/*************************************************************************/
+/*  shader_warnings.h                                                    */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef SHADER_WARNINGS
+#define SHADER_WARNINGS
+
+#ifdef DEBUG_ENABLED
+
+#include "core/string/string_name.h"
+#include "core/templates/list.h"
+#include "core/templates/map.h"
+
+class ShaderWarning {
+public:
+	enum Code {
+		FLOAT_COMPARISON,
+		UNUSED_CONSTANT,
+		UNUSED_FUNCTION,
+		UNUSED_STRUCT,
+		UNUSED_UNIFORM,
+		UNUSED_VARYING,
+		WARNING_MAX,
+	};
+
+	enum CodeFlags : uint32_t {
+		NONE_FLAG = 0U,
+		FLOAT_COMPARISON_FLAG = 1U,
+		UNUSED_CONSTANT_FLAG = 2U,
+		UNUSED_FUNCTION_FLAG = 4U,
+		UNUSED_STRUCT_FLAG = 8U,
+		UNUSED_UNIFORM_FLAG = 16U,
+		UNUSED_VARYING_FLAG = 32U,
+	};
+
+private:
+	Code code;
+	int line;
+	StringName subject;
+
+public:
+	Code get_code() const;
+	int get_line() const;
+	const StringName &get_subject() const;
+	String get_message() const;
+	String get_name() const;
+
+	static String get_name_from_code(Code p_code);
+	static Code get_code_from_name(const String &p_name);
+	static CodeFlags get_flags_from_codemap(const Map<Code, bool> &p_map);
+
+	ShaderWarning(Code p_code = WARNING_MAX, int p_line = -1, const StringName &p_subject = "");
+};
+
+#endif // DEBUG_ENABLED
+
+#endif // SHADER_WARNINGS