Procházet zdrojové kódy

Clean up Shader Preprocessor

* Moved preprocessor to Shader and ShaderInclude
* Clean up RenderingServer side
* Preprocessor is separate from parser now, but it emits tokens with include location hints.
* Improved ShaderEditor validation code
* Added include file code completion
* Added notification for all files affected by a broken include.
reduz před 3 roky
rodič
revize
f649678402
32 změnil soubory, kde provedl 548 přidání a 291 odebrání
  1. 14 0
      doc/classes/RenderingServer.xml
  2. 7 0
      drivers/gles3/storage/material_storage.cpp
  3. 2 0
      drivers/gles3/storage/material_storage.h
  4. 203 58
      editor/plugins/shader_editor_plugin.cpp
  5. 18 0
      editor/plugins/shader_editor_plugin.h
  6. 29 12
      scene/resources/shader.cpp
  7. 3 0
      scene/resources/shader.h
  8. 6 1
      scene/resources/shader_include.cpp
  9. 2 0
      servers/rendering/dummy/storage/material_storage.h
  10. 4 0
      servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
  11. 1 0
      servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
  12. 4 0
      servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
  13. 2 0
      servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h
  14. 4 0
      servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
  15. 1 0
      servers/rendering/renderer_rd/renderer_canvas_render_rd.h
  16. 4 0
      servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
  17. 1 0
      servers/rendering/renderer_rd/renderer_scene_render_rd.h
  18. 4 0
      servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp
  19. 1 0
      servers/rendering/renderer_rd/renderer_scene_sky_rd.h
  20. 11 0
      servers/rendering/renderer_rd/storage_rd/material_storage.cpp
  21. 3 0
      servers/rendering/renderer_rd/storage_rd/material_storage.h
  22. 3 0
      servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
  23. 1 0
      servers/rendering/renderer_rd/storage_rd/particles_storage.h
  24. 1 0
      servers/rendering/rendering_server_default.h
  25. 64 6
      servers/rendering/shader_compiler.cpp
  26. 43 164
      servers/rendering/shader_language.cpp
  27. 9 4
      servers/rendering/shader_language.h
  28. 77 30
      servers/rendering/shader_preprocessor.cpp
  29. 22 16
      servers/rendering/shader_preprocessor.h
  30. 1 0
      servers/rendering/storage/material_storage.h
  31. 2 0
      servers/rendering_server.cpp
  32. 1 0
      servers/rendering_server.h

+ 14 - 0
doc/classes/RenderingServer.xml

@@ -2748,6 +2748,13 @@
 				Returns the parameters of a shader.
 			</description>
 		</method>
+		<method name="shader_set_code">
+			<return type="void" />
+			<argument index="0" name="shader" type="RID" />
+			<argument index="1" name="code" type="String" />
+			<description>
+			</description>
+		</method>
 		<method name="shader_set_default_texture_param">
 			<return type="void" />
 			<argument index="0" name="shader" type="RID" />
@@ -2759,6 +2766,13 @@
 				[b]Note:[/b] If the sampler array is used use [code]index[/code] to access the specified texture.
 			</description>
 		</method>
+		<method name="shader_set_path_hint">
+			<return type="void" />
+			<argument index="0" name="shader" type="RID" />
+			<argument index="1" name="path" type="String" />
+			<description>
+			</description>
+		</method>
 		<method name="skeleton_allocate_data">
 			<return type="void" />
 			<argument index="0" name="skeleton" type="RID" />

+ 7 - 0
drivers/gles3/storage/material_storage.cpp

@@ -2462,6 +2462,13 @@ void MaterialStorage::shader_set_code(RID p_shader, const String &p_code) {
 	}
 }
 
+void MaterialStorage::shader_set_path_hint(RID p_shader, const String &p_path) {
+	GLES3::Shader *shader = shader_owner.get_or_null(p_shader);
+	ERR_FAIL_COND(!shader);
+
+	shader->path_hint = p_path;
+}
+
 String MaterialStorage::shader_get_code(RID p_shader) const {
 	const GLES3::Shader *shader = shader_owner.get_or_null(p_shader);
 	ERR_FAIL_COND_V(!shader, String());

+ 2 - 0
drivers/gles3/storage/material_storage.h

@@ -73,6 +73,7 @@ struct Material;
 struct Shader {
 	ShaderData *data = nullptr;
 	String code;
+	String path_hint;
 	RS::ShaderMode mode;
 	HashMap<StringName, HashMap<int, RID>> default_texture_parameter;
 	HashSet<Material *> owners;
@@ -542,6 +543,7 @@ public:
 	virtual void shader_free(RID p_rid) override;
 
 	virtual void shader_set_code(RID p_shader, const String &p_code) override;
+	virtual void shader_set_path_hint(RID p_shader, const String &p_path) override;
 	virtual String shader_get_code(RID p_shader) const override;
 	virtual void shader_get_param_list(RID p_shader, List<PropertyInfo> *p_param_list) const override;
 

+ 203 - 58
editor/plugins/shader_editor_plugin.cpp

@@ -45,6 +45,7 @@
 #include "editor/shader_create_dialog.h"
 #include "scene/gui/split_container.h"
 #include "servers/display_server.h"
+#include "servers/rendering/shader_preprocessor.h"
 #include "servers/rendering/shader_types.h"
 
 /*** SHADER SCRIPT EDITOR ****/
@@ -84,24 +85,47 @@ void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader, const Stri
 	if (shader == p_shader) {
 		return;
 	}
+	if (shader.is_valid()) {
+		shader->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed));
+	}
 	shader = p_shader;
 	shader_inc = Ref<ShaderInclude>();
 
 	set_edited_code(p_code);
+
+	if (shader.is_valid()) {
+		shader->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed));
+	}
 }
 
 void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc) {
 	set_edited_shader_include(p_shader_inc, p_shader_inc->get_code());
 }
 
+void ShaderTextEditor::_shader_changed() {
+	// This function is used for dependencies (include changing changes main shader and forces it to revalidate)
+	if (block_shader_changed) {
+		return;
+	}
+	dependencies_version++;
+	_validate_script();
+}
+
 void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc, const String &p_code) {
 	if (shader_inc == p_shader_inc) {
 		return;
 	}
+	if (shader_inc.is_valid()) {
+		shader_inc->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed));
+	}
 	shader_inc = p_shader_inc;
 	shader = Ref<Shader>();
 
 	set_edited_code(p_code);
+
+	if (shader_inc.is_valid()) {
+		shader_inc->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed));
+	}
 }
 
 void ShaderTextEditor::set_edited_code(const String &p_code) {
@@ -174,7 +198,7 @@ void ShaderTextEditor::_load_theme_settings() {
 	}
 
 	List<String> pp_keywords;
-	ShaderLanguage::get_preprocessor_keyword_list(&pp_keywords, false);
+	ShaderPreprocessor::get_keyword_list(&pp_keywords, false);
 
 	for (const String &E : pp_keywords) {
 		syntax_highlighter->add_keyword_color(E, keyword_color);
@@ -255,7 +279,9 @@ void ShaderTextEditor::_check_shader_mode() {
 	}
 
 	if (shader->get_mode() != mode) {
+		set_block_shader_changed(true);
 		shader->set_code(get_text_editor()->get_text());
+		set_block_shader_changed(false);
 		_load_theme_settings();
 	}
 }
@@ -265,7 +291,47 @@ static ShaderLanguage::DataType _get_global_variable_type(const StringName &p_va
 	return (ShaderLanguage::DataType)RS::global_variable_type_get_shader_datatype(gvt);
 }
 
+static String complete_from_path;
+
+static void _complete_include_paths_search(EditorFileSystemDirectory *p_efsd, List<ScriptLanguage::CodeCompletionOption> *r_options) {
+	if (!p_efsd) {
+		return;
+	}
+	for (int i = 0; i < p_efsd->get_file_count(); i++) {
+		if (p_efsd->get_file_type(i) == SNAME("ShaderInclude")) {
+			String path = p_efsd->get_file_path(i);
+			if (path.begins_with(complete_from_path)) {
+				path = path.replace_first(complete_from_path, "");
+			}
+			r_options->push_back(ScriptLanguage::CodeCompletionOption(path, ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH));
+		}
+	}
+	for (int j = 0; j < p_efsd->get_subdir_count(); j++) {
+		_complete_include_paths_search(p_efsd->get_subdir(j), r_options);
+	}
+}
+
+static void _complete_include_paths(List<ScriptLanguage::CodeCompletionOption> *r_options) {
+	_complete_include_paths_search(EditorFileSystem::get_singleton()->get_filesystem(), r_options);
+}
+
 void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) {
+	List<ScriptLanguage::CodeCompletionOption> pp_options;
+	ShaderPreprocessor preprocessor;
+	String code;
+	complete_from_path = (shader.is_valid() ? shader->get_path() : shader_inc->get_path()).get_base_dir();
+	if (!complete_from_path.ends_with("/")) {
+		complete_from_path += "/";
+	}
+	preprocessor.preprocess(p_code, code, nullptr, nullptr, nullptr, &pp_options, _complete_include_paths);
+	complete_from_path = String();
+	if (pp_options.size()) {
+		for (const ScriptLanguage::CodeCompletionOption &E : pp_options) {
+			r_options->push_back(E);
+		}
+		return;
+	}
+
 	ShaderLanguage sl;
 	String calltip;
 	ShaderLanguage::ShaderCompileInfo info;
@@ -289,6 +355,8 @@ void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptLa
 }
 
 void ShaderTextEditor::_validate_script() {
+	emit_signal(SNAME("script_changed")); // Ensure to notify that it changed, so it is applied
+
 	String code;
 
 	if (shader.is_valid()) {
@@ -298,72 +366,117 @@ void ShaderTextEditor::_validate_script() {
 		code = shader_inc->get_code();
 	}
 
-	ShaderLanguage sl;
+	ShaderPreprocessor preprocessor;
+	String code_pp;
+	String error_pp;
+	List<ShaderPreprocessor::FilePosition> err_positions;
+	last_compile_result = preprocessor.preprocess(code, code_pp, &error_pp, &err_positions);
 
-	sl.enable_warning_checking(saved_warnings_enabled);
-	uint32_t flags = saved_warning_flags;
-	if (shader.is_null()) {
-		if (flags & ShaderWarning::UNUSED_CONSTANT) {
-			flags &= ~(ShaderWarning::UNUSED_CONSTANT);
-		}
-		if (flags & ShaderWarning::UNUSED_FUNCTION) {
-			flags &= ~(ShaderWarning::UNUSED_FUNCTION);
-		}
-		if (flags & ShaderWarning::UNUSED_STRUCT) {
-			flags &= ~(ShaderWarning::UNUSED_STRUCT);
-		}
-		if (flags & ShaderWarning::UNUSED_UNIFORM) {
-			flags &= ~(ShaderWarning::UNUSED_UNIFORM);
-		}
-		if (flags & ShaderWarning::UNUSED_VARYING) {
-			flags &= ~(ShaderWarning::UNUSED_VARYING);
-		}
-	}
-	sl.set_warning_flags(flags);
-
-	ShaderLanguage::ShaderCompileInfo info;
-	info.global_variable_type_func = _get_global_variable_type;
-
-	if (shader.is_null()) {
-		info.is_include = true;
-	} else {
-		Shader::Mode mode = shader->get_mode();
-		info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(mode));
-		info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(mode));
-		info.shader_types = ShaderTypes::get_singleton()->get_types();
+	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));
 	}
-
-	last_compile_result = sl.compile(code, info);
+	set_error("");
 
 	if (last_compile_result != OK) {
-		String error_text = "error(" + itos(sl.get_error_line()) + "): " + sl.get_error_text();
+		//preprocessor error
+		ERR_FAIL_COND(err_positions.size() == 0);
+
+		String error_text;
+		int error_line = err_positions.front()->get().line;
+		if (err_positions.size() == 1) {
+			// Error in main file
+			error_text = "error(" + itos(error_line) + "): " + error_text;
+		} else {
+			error_text = "error(" + itos(error_line) + ") in include " + err_positions.back()->get().file.get_file() + ":" + itos(err_positions.back()->get().line) + ": " + error_text;
+			set_error_count(err_positions.size() - 1);
+		}
+
 		set_error(error_text);
-		set_error_pos(sl.get_error_line() - 1, 0);
+		set_error_pos(error_line - 1, 0);
 		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));
 		}
-		get_text_editor()->set_line_background_color(sl.get_error_line() - 1, marked_line_color);
+		get_text_editor()->set_line_background_color(error_line - 1, marked_line_color);
+
+		set_warning_count(0);
+
 	} 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));
+		ShaderLanguage sl;
+
+		sl.enable_warning_checking(saved_warnings_enabled);
+		uint32_t flags = saved_warning_flags;
+		if (shader.is_null()) {
+			if (flags & ShaderWarning::UNUSED_CONSTANT) {
+				flags &= ~(ShaderWarning::UNUSED_CONSTANT);
+			}
+			if (flags & ShaderWarning::UNUSED_FUNCTION) {
+				flags &= ~(ShaderWarning::UNUSED_FUNCTION);
+			}
+			if (flags & ShaderWarning::UNUSED_STRUCT) {
+				flags &= ~(ShaderWarning::UNUSED_STRUCT);
+			}
+			if (flags & ShaderWarning::UNUSED_UNIFORM) {
+				flags &= ~(ShaderWarning::UNUSED_UNIFORM);
+			}
+			if (flags & ShaderWarning::UNUSED_VARYING) {
+				flags &= ~(ShaderWarning::UNUSED_VARYING);
+			}
 		}
-		set_error("");
-	}
+		sl.set_warning_flags(flags);
 
-	if (warnings.size() > 0 || last_compile_result != 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 && last_compile_result == OK) {
-		warnings.sort_custom<WarningsComparator>();
-		_update_warning_panel();
-	} else {
-		set_warning_count(0);
+		ShaderLanguage::ShaderCompileInfo info;
+		info.global_variable_type_func = _get_global_variable_type;
+
+		if (shader.is_null()) {
+			info.is_include = true;
+		} else {
+			Shader::Mode mode = shader->get_mode();
+			info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(mode));
+			info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(mode));
+			info.shader_types = ShaderTypes::get_singleton()->get_types();
+		}
+
+		code = code_pp;
+		//compiler error
+		last_compile_result = sl.compile(code, info);
+
+		if (last_compile_result != OK) {
+			String error_text;
+			int error_line;
+			Vector<ShaderLanguage::FilePosition> include_positions = sl.get_include_positions();
+			if (include_positions.size() > 1) {
+				//error is in an include
+				error_line = include_positions[0].line;
+				error_text = "error(" + itos(error_line) + ") in include " + include_positions[include_positions.size() - 1].file + ":" + itos(include_positions[include_positions.size() - 1].line) + ": " + sl.get_error_text();
+				set_error_count(include_positions.size() - 1);
+			} else {
+				error_line = sl.get_error_line();
+				error_text = "error(" + itos(error_line) + "): " + sl.get_error_text();
+				set_error_count(0);
+			}
+			set_error(error_text);
+			set_error_pos(error_line - 1, 0);
+			get_text_editor()->set_line_background_color(error_line - 1, marked_line_color);
+		} else {
+			set_error("");
+		}
+
+		if (warnings.size() > 0 || last_compile_result != 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 && last_compile_result == OK) {
+			warnings.sort_custom<WarningsComparator>();
+			_update_warning_panel();
+		} else {
+			set_warning_count(0);
+		}
 	}
-	emit_signal(SNAME("script_changed"));
+
+	emit_signal(SNAME("script_validated"), last_compile_result == OK); // Notify that validation finished, to update the list of scripts
 }
 
 void ShaderTextEditor::_update_warning_panel() {
@@ -410,6 +523,7 @@ void ShaderTextEditor::_update_warning_panel() {
 }
 
 void ShaderTextEditor::_bind_methods() {
+	ADD_SIGNAL(MethodInfo("script_validated", PropertyInfo(Variant::BOOL, "valid")));
 }
 
 ShaderTextEditor::ShaderTextEditor() {
@@ -545,6 +659,8 @@ void ShaderEditor::_warning_clicked(Variant p_line) {
 void ShaderEditor::_bind_methods() {
 	ClassDB::bind_method("_show_warnings_panel", &ShaderEditor::_show_warnings_panel);
 	ClassDB::bind_method("_warning_clicked", &ShaderEditor::_warning_clicked);
+
+	ADD_SIGNAL(MethodInfo("validation_changed"));
 }
 
 void ShaderEditor::ensure_select_current() {
@@ -626,7 +742,9 @@ void ShaderEditor::_reload_shader_from_disk() {
 	Ref<Shader> rel_shader = ResourceLoader::load(shader->get_path(), shader->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
 	ERR_FAIL_COND(!rel_shader.is_valid());
 
+	shader_editor->set_block_shader_changed(true);
 	shader->set_code(rel_shader->get_code());
+	shader_editor->set_block_shader_changed(false);
 	shader->set_last_modified_time(rel_shader->get_last_modified_time());
 	shader_editor->reload_text();
 }
@@ -635,7 +753,9 @@ void ShaderEditor::_reload_shader_include_from_disk() {
 	Ref<ShaderInclude> rel_shader_include = ResourceLoader::load(shader_inc->get_path(), shader_inc->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
 	ERR_FAIL_COND(!rel_shader_include.is_valid());
 
+	shader_editor->set_block_shader_changed(true);
 	shader_inc->set_code(rel_shader_include->get_code());
+	shader_editor->set_block_shader_changed(false);
 	shader_inc->set_last_modified_time(rel_shader_include->get_last_modified_time());
 	shader_editor->reload_text();
 }
@@ -713,18 +833,24 @@ void ShaderEditor::apply_shaders() {
 	String editor_code = shader_editor->get_text_editor()->get_text();
 	if (shader.is_valid()) {
 		String shader_code = shader->get_code();
-		if (shader_code != editor_code) {
+		if (shader_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) {
+			shader_editor->set_block_shader_changed(true);
 			shader->set_code(editor_code);
+			shader_editor->set_block_shader_changed(false);
 			shader->set_edited(true);
 		}
 	}
 	if (shader_inc.is_valid()) {
 		String shader_inc_code = shader_inc->get_code();
-		if (shader_inc_code != editor_code) {
+		if (shader_inc_code != editor_code || dependencies_version != shader_editor->get_dependencies_version()) {
+			shader_editor->set_block_shader_changed(true);
 			shader_inc->set_code(editor_code);
+			shader_editor->set_block_shader_changed(false);
 			shader_inc->set_edited(true);
 		}
 	}
+
+	dependencies_version = shader_editor->get_dependencies_version();
 }
 
 void ShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
@@ -838,6 +964,8 @@ ShaderEditor::ShaderEditor() {
 
 	shader_editor = memnew(ShaderTextEditor);
 
+	shader_editor->connect("script_validated", callable_mp(this, &ShaderEditor::_script_validated));
+
 	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_FULL_RECT);
@@ -1023,6 +1151,21 @@ void ShaderEditorPlugin::_update_shader_list() {
 	for (int i = 1; i < FILE_MAX; i++) {
 		file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), edited_shaders.size() == 0);
 	}
+
+	_update_shader_list_status();
+}
+
+void ShaderEditorPlugin::_update_shader_list_status() {
+	for (int i = 0; i < shader_list->get_item_count(); i++) {
+		ShaderEditor *se = Object::cast_to<ShaderEditor>(shader_tabs->get_tab_control(i));
+		if (se) {
+			if (se->was_compilation_successful()) {
+				shader_list->set_item_tag_icon(i, Ref<Texture2D>());
+			} else {
+				shader_list->set_item_tag_icon(i, shader_list->get_theme_icon(SNAME("Error"), SNAME("EditorIcons")));
+			}
+		}
+	}
 }
 
 void ShaderEditorPlugin::edit(Object *p_object) {
@@ -1041,6 +1184,7 @@ void ShaderEditorPlugin::edit(Object *p_object) {
 		es.shader_editor = memnew(ShaderEditor);
 		es.shader_editor->edit(si);
 		shader_tabs->add_child(es.shader_editor);
+		es.shader_editor->connect("validation_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list_status));
 	} else {
 		Shader *s = Object::cast_to<Shader>(p_object);
 		for (uint32_t i = 0; i < edited_shaders.size(); i++) {
@@ -1060,6 +1204,7 @@ void ShaderEditorPlugin::edit(Object *p_object) {
 			es.shader_editor = memnew(ShaderEditor);
 			es.shader_editor->edit(s);
 			shader_tabs->add_child(es.shader_editor);
+			es.shader_editor->connect("validation_changed", callable_mp(this, &ShaderEditorPlugin::_update_shader_list_status));
 		}
 	}
 
@@ -1233,7 +1378,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() {
 	file_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditorPlugin::_menu_item_pressed));
 	file_hb->add_child(file_menu);
 
-	for (int i = 1; i < FILE_MAX; i++) {
+	for (int i = 2; i < FILE_MAX; i++) {
 		file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), true);
 	}
 

+ 18 - 0
editor/plugins/shader_editor_plugin.h

@@ -67,6 +67,11 @@ class ShaderTextEditor : public CodeTextEditor {
 	void _check_shader_mode();
 	void _update_warning_panel();
 
+	bool block_shader_changed = false;
+	void _shader_changed();
+
+	uint32_t dependencies_version = 0; // Incremented if deps changed
+
 protected:
 	void _notification(int p_what);
 	static void _bind_methods();
@@ -75,6 +80,9 @@ protected:
 	virtual void _code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) override;
 
 public:
+	void set_block_shader_changed(bool p_block) { block_shader_changed = p_block; }
+	uint32_t get_dependencies_version() const { return dependencies_version; }
+
 	virtual void _validate_script() override;
 
 	void reload_text();
@@ -135,6 +143,7 @@ class ShaderEditor : public PanelContainer {
 	ConfirmationDialog *disk_changed = nullptr;
 
 	ShaderTextEditor *shader_editor = nullptr;
+	bool compilation_success = true;
 
 	void _menu_option(int p_option);
 	mutable Ref<Shader> shader;
@@ -151,6 +160,13 @@ class ShaderEditor : public PanelContainer {
 	void _warning_clicked(Variant p_line);
 	void _update_warnings(bool p_validate);
 
+	void _script_validated(bool p_valid) {
+		compilation_success = p_valid;
+		emit_signal(SNAME("validation_changed"));
+	}
+
+	uint32_t dependencies_version = 0xFFFFFFFF;
+
 protected:
 	void _notification(int p_what);
 	static void _bind_methods();
@@ -161,6 +177,7 @@ protected:
 	void _bookmark_item_pressed(int p_idx);
 
 public:
+	bool was_compilation_successful() const { return compilation_success; }
 	void apply_shaders();
 	void ensure_select_current();
 	void edit(const Ref<Shader> &p_shader);
@@ -215,6 +232,7 @@ class ShaderEditorPlugin : public EditorPlugin {
 
 	void _shader_created(Ref<Shader> p_shader);
 	void _shader_include_created(Ref<ShaderInclude> p_shader_inc);
+	void _update_shader_list_status();
 
 public:
 	virtual String get_name() const override { return "Shader"; }

+ 29 - 12
scene/resources/shader.cpp

@@ -33,6 +33,7 @@
 #include "core/io/file_access.h"
 #include "scene/scene_string_names.h"
 #include "servers/rendering/shader_language.h"
+#include "servers/rendering/shader_preprocessor.h"
 #include "servers/rendering_server.h"
 #include "texture.h"
 
@@ -47,21 +48,17 @@ void Shader::_dependency_changed() {
 	emit_changed();
 }
 
-void Shader::set_code(const String &p_code) {
-	HashSet<Ref<ShaderInclude>> new_include_dependencies;
+void Shader::set_path(const String &p_path, bool p_take_over) {
+	Resource::set_path(p_path, p_take_over);
+	RS::get_singleton()->shader_set_path_hint(shader, p_path);
+}
 
+void Shader::set_code(const String &p_code) {
 	for (Ref<ShaderInclude> E : include_dependencies) {
 		E->disconnect(SNAME("changed"), callable_mp(this, &Shader::_dependency_changed));
 	}
 
-	String type = ShaderLanguage::get_shader_type_and_dependencies(p_code, &new_include_dependencies);
-
-	// This ensures previous include resources are not freed and then re-loaded during parse (which would make compiling slower)
-	include_dependencies = new_include_dependencies;
-
-	for (Ref<ShaderInclude> E : include_dependencies) {
-		E->connect(SNAME("changed"), callable_mp(this, &Shader::_dependency_changed));
-	}
+	String type = ShaderLanguage::get_shader_type(p_code);
 
 	if (type == "canvas_item") {
 		mode = MODE_CANVAS_ITEM;
@@ -75,7 +72,27 @@ void Shader::set_code(const String &p_code) {
 		mode = MODE_SPATIAL;
 	}
 
-	RenderingServer::get_singleton()->shader_set_code(shader, p_code);
+	code = p_code;
+	String pp_code = p_code;
+
+	HashSet<Ref<ShaderInclude>> new_include_dependencies;
+
+	{
+		// Preprocessor must run here and not in the server because:
+		// 1) Need to keep track of include dependencies at resource level
+		// 2) Server does not do interaction with Resource filetypes, this is a scene level feature.
+		ShaderPreprocessor preprocessor;
+		preprocessor.preprocess(p_code, pp_code, nullptr, nullptr, &new_include_dependencies);
+	}
+
+	// This ensures previous include resources are not freed and then re-loaded during parse (which would make compiling slower)
+	include_dependencies = new_include_dependencies;
+
+	for (Ref<ShaderInclude> E : include_dependencies) {
+		E->connect(SNAME("changed"), callable_mp(this, &Shader::_dependency_changed));
+	}
+
+	RenderingServer::get_singleton()->shader_set_code(shader, pp_code);
 	params_cache_dirty = true;
 
 	emit_changed();
@@ -83,7 +100,7 @@ void Shader::set_code(const String &p_code) {
 
 String Shader::get_code() const {
 	_update_shader();
-	return RenderingServer::get_singleton()->shader_get_code(shader);
+	return code;
 }
 
 void Shader::get_param_list(List<PropertyInfo> *p_params) const {

+ 3 - 0
scene/resources/shader.h

@@ -55,6 +55,7 @@ private:
 	RID shader;
 	Mode mode = MODE_SPATIAL;
 	HashSet<Ref<ShaderInclude>> include_dependencies;
+	String code;
 
 	// hack the name of performance
 	// shaders keep a list of ShaderMaterial -> RenderingServer name translations, to make
@@ -72,6 +73,8 @@ public:
 	//void set_mode(Mode p_mode);
 	virtual Mode get_mode() const;
 
+	virtual void set_path(const String &p_path, bool p_take_over = false) override;
+
 	void set_code(const String &p_code);
 	String get_code() const;
 

+ 6 - 1
scene/resources/shader_include.cpp

@@ -30,6 +30,7 @@
 
 #include "shader_include.h"
 #include "servers/rendering/shader_language.h"
+#include "servers/rendering/shader_preprocessor.h"
 
 void ShaderInclude::_dependency_changed() {
 	emit_changed();
@@ -43,7 +44,11 @@ void ShaderInclude::set_code(const String &p_code) {
 		E->disconnect(SNAME("changed"), callable_mp(this, &ShaderInclude::_dependency_changed));
 	}
 
-	ShaderLanguage::get_shader_dependencies(p_code, &new_dependencies);
+	{
+		String pp_code;
+		ShaderPreprocessor preprocessor;
+		preprocessor.preprocess(p_code, pp_code, nullptr, nullptr, &new_dependencies);
+	}
 
 	// This ensures previous include resources are not freed and then re-loaded during parse (which would make compiling slower)
 	dependencies = new_dependencies;

+ 2 - 0
servers/rendering/dummy/storage/material_storage.h

@@ -63,6 +63,8 @@ public:
 	virtual void shader_free(RID p_rid) override{};
 
 	virtual void shader_set_code(RID p_shader, const String &p_code) override {}
+	virtual void shader_set_path_hint(RID p_shader, const String &p_code) override {}
+
 	virtual String shader_get_code(RID p_shader) const override { return ""; }
 	virtual void shader_get_param_list(RID p_shader, List<PropertyInfo> *p_param_list) const override {}
 

+ 4 - 0
servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp

@@ -37,6 +37,10 @@
 
 using namespace RendererSceneRenderImplementation;
 
+void SceneShaderForwardClustered::ShaderData::set_path_hint(const String &p_path) {
+	path = p_path;
+}
+
 void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
 	//compile
 

+ 1 - 0
servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h

@@ -180,6 +180,7 @@ public:
 		uint32_t index = 0;
 
 		virtual void set_code(const String &p_Code);
+		virtual void set_path_hint(const String &p_path);
 		virtual void set_default_texture_param(const StringName &p_name, RID p_texture, int p_index);
 		virtual void get_param_list(List<PropertyInfo> *p_param_list) const;
 		void get_instance_param_list(List<RendererMaterialStorage::InstanceShaderParam> *p_param_list) const;

+ 4 - 0
servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp

@@ -39,6 +39,10 @@ using namespace RendererSceneRenderImplementation;
 
 /* ShaderData */
 
+void SceneShaderForwardMobile::ShaderData::set_path_hint(const String &p_path) {
+	path = p_path;
+}
+
 void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
 	//compile
 

+ 2 - 0
servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h

@@ -139,6 +139,8 @@ public:
 		uint32_t index = 0;
 
 		virtual void set_code(const String &p_Code);
+		virtual void set_path_hint(const String &p_path);
+
 		virtual void set_default_texture_param(const StringName &p_name, RID p_texture, int p_index);
 		virtual void get_param_list(List<PropertyInfo> *p_param_list) const;
 		void get_instance_param_list(List<RendererMaterialStorage::InstanceShaderParam> *p_param_list) const;

+ 4 - 0
servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp

@@ -1968,6 +1968,10 @@ void RendererCanvasRenderRD::occluder_polygon_set_cull_mode(RID p_occluder, RS::
 	oc->cull_mode = p_mode;
 }
 
+void RendererCanvasRenderRD::CanvasShaderData::set_path_hint(const String &p_path) {
+	path = p_path;
+}
+
 void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) {
 	//compile
 

+ 1 - 0
servers/rendering/renderer_rd/renderer_canvas_render_rd.h

@@ -178,6 +178,7 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
 		bool uses_time = false;
 
 		virtual void set_code(const String &p_Code);
+		virtual void set_path_hint(const String &p_path);
 		virtual void set_default_texture_param(const StringName &p_name, RID p_texture, int p_index);
 		virtual void get_param_list(List<PropertyInfo> *p_param_list) const;
 		virtual void get_instance_param_list(List<RendererMaterialStorage::InstanceShaderParam> *p_param_list) const;

+ 4 - 0
servers/rendering/renderer_rd/renderer_scene_render_rd.cpp

@@ -3978,6 +3978,10 @@ void RendererSceneRenderRD::_setup_decals(const PagedArray<RID> &p_decals, const
 ////////////////////////////////////////////////////////////////////////////////
 // FOG SHADER
 
+void RendererSceneRenderRD::FogShaderData::set_path_hint(const String &p_path) {
+	path = p_path;
+}
+
 void RendererSceneRenderRD::FogShaderData::set_code(const String &p_code) {
 	//compile
 

+ 1 - 0
servers/rendering/renderer_rd/renderer_scene_render_rd.h

@@ -947,6 +947,7 @@ private:
 		bool uses_time = false;
 
 		virtual void set_code(const String &p_Code);
+		virtual void set_path_hint(const String &p_hint);
 		virtual void set_default_texture_param(const StringName &p_name, RID p_texture, int p_index);
 		virtual void get_param_list(List<PropertyInfo> *p_param_list) const;
 		virtual void get_instance_param_list(List<RendererMaterialStorage::InstanceShaderParam> *p_param_list) const;

+ 4 - 0
servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp

@@ -42,6 +42,10 @@
 ////////////////////////////////////////////////////////////////////////////////
 // SKY SHADER
 
+void RendererSceneSkyRD::SkyShaderData::set_path_hint(const String &p_path) {
+	path = p_path;
+}
+
 void RendererSceneSkyRD::SkyShaderData::set_code(const String &p_code) {
 	//compile
 

+ 1 - 0
servers/rendering/renderer_rd/renderer_scene_sky_rd.h

@@ -128,6 +128,7 @@ private:
 		bool uses_light = false;
 
 		virtual void set_code(const String &p_Code);
+		virtual void set_path_hint(const String &p_hint);
 		virtual void set_default_texture_param(const StringName &p_name, RID p_texture, int p_index);
 		virtual void get_param_list(List<PropertyInfo> *p_param_list) const;
 		virtual void get_instance_param_list(List<RendererMaterialStorage::InstanceShaderParam> *p_param_list) const;

+ 11 - 0
servers/rendering/renderer_rd/storage_rd/material_storage.cpp

@@ -2341,6 +2341,7 @@ void MaterialStorage::shader_set_code(RID p_shader, const String &p_code) {
 	}
 
 	if (shader->data) {
+		shader->data->set_path_hint(shader->path_hint);
 		shader->data->set_code(p_code);
 	}
 
@@ -2351,6 +2352,16 @@ void MaterialStorage::shader_set_code(RID p_shader, const String &p_code) {
 	}
 }
 
+void MaterialStorage::shader_set_path_hint(RID p_shader, const String &p_path) {
+	Shader *shader = shader_owner.get_or_null(p_shader);
+	ERR_FAIL_COND(!shader);
+
+	shader->path_hint = p_path;
+	if (shader->data) {
+		shader->data->set_path_hint(p_path);
+	}
+}
+
 String MaterialStorage::shader_get_code(RID p_shader) const {
 	Shader *shader = shader_owner.get_or_null(p_shader);
 	ERR_FAIL_COND_V(!shader, String());

+ 3 - 0
servers/rendering/renderer_rd/storage_rd/material_storage.h

@@ -57,6 +57,7 @@ enum ShaderType {
 
 struct ShaderData {
 	virtual void set_code(const String &p_Code) = 0;
+	virtual void set_path_hint(const String &p_hint) = 0;
 	virtual void set_default_texture_param(const StringName &p_name, RID p_texture, int p_index) = 0;
 	virtual void get_param_list(List<PropertyInfo> *p_param_list) const = 0;
 
@@ -77,6 +78,7 @@ struct Material;
 struct Shader {
 	ShaderData *data = nullptr;
 	String code;
+	String path_hint;
 	ShaderType type;
 	HashMap<StringName, HashMap<int, RID>> default_texture_parameter;
 	HashSet<Material *> owners;
@@ -364,6 +366,7 @@ public:
 	virtual void shader_free(RID p_rid) override;
 
 	virtual void shader_set_code(RID p_shader, const String &p_code) override;
+	virtual void shader_set_path_hint(RID p_shader, const String &p_path) override;
 	virtual String shader_get_code(RID p_shader) const override;
 	virtual void shader_get_param_list(RID p_shader, List<PropertyInfo> *p_param_list) const override;
 

+ 3 - 0
servers/rendering/renderer_rd/storage_rd/particles_storage.cpp

@@ -1512,6 +1512,9 @@ bool ParticlesStorage::particles_is_inactive(RID p_particles) const {
 
 /* Particles SHADER */
 
+void ParticlesStorage::ParticlesShaderData::set_path_hint(const String &p_path) {
+	path = p_path;
+}
 void ParticlesStorage::ParticlesShaderData::set_code(const String &p_code) {
 	ParticlesStorage *particles_storage = ParticlesStorage::get_singleton();
 	//compile

+ 1 - 0
servers/rendering/renderer_rd/storage_rd/particles_storage.h

@@ -363,6 +363,7 @@ private:
 		uint32_t userdata_count = 0;
 
 		virtual void set_code(const String &p_Code);
+		virtual void set_path_hint(const String &p_hint);
 		virtual void set_default_texture_param(const StringName &p_name, RID p_texture, int p_index);
 		virtual void get_param_list(List<PropertyInfo> *p_param_list) const;
 		virtual void get_instance_param_list(List<RendererMaterialStorage::InstanceShaderParam> *p_param_list) const;

+ 1 - 0
servers/rendering/rendering_server_default.h

@@ -224,6 +224,7 @@ public:
 	FUNCRIDSPLIT(shader)
 
 	FUNC2(shader_set_code, RID, const String &)
+	FUNC2(shader_set_path_hint, RID, const String &)
 	FUNC1RC(String, shader_get_code, RID)
 
 	FUNC2SC(shader_get_param_list, RID, List<PropertyInfo> *)

+ 64 - 6
servers/rendering/shader_compiler.cpp

@@ -1323,18 +1323,76 @@ Error ShaderCompiler::compile(RS::ShaderMode p_mode, const String &p_code, Ident
 	Error err = parser.compile(p_code, info);
 
 	if (err != OK) {
+		Vector<ShaderLanguage::FilePosition> include_positions = parser.get_include_positions();
+
+		String current;
+		HashMap<String, Vector<String>> includes;
+		includes[""] = Vector<String>();
+		Vector<String> include_stack;
 		Vector<String> shader = p_code.split("\n");
+
+		// Reconstruct the files.
 		for (int i = 0; i < shader.size(); i++) {
-			if (i + 1 == parser.get_error_line()) {
-				// Mark the error line to be visible without having to look at
-				// the trace at the end.
-				print_line(vformat("E%4d-> %s", i + 1, shader[i]));
+			String l = shader[i];
+			if (l.begins_with("@@>")) {
+				String inc_path = l.replace_first("@@>", "");
+
+				l = "#include \"" + inc_path + "\"";
+				includes[current].append("#include \"" + inc_path + "\""); // Restore the include directive
+				include_stack.push_back(current);
+				current = inc_path;
+				includes[inc_path] = Vector<String>();
+
+			} else if (l.begins_with("@@<")) {
+				if (include_stack.size()) {
+					current = include_stack[include_stack.size() - 1];
+					include_stack.resize(include_stack.size() - 1);
+				}
+			} else {
+				includes[current].push_back(l);
+			}
+		}
+
+		// Print the files.
+		for (const KeyValue<String, Vector<String>> &E : includes) {
+			if (E.key.is_empty()) {
+				if (p_path == "") {
+					print_line("--Main Shader--");
+				} else {
+					print_line("--" + p_path + "--");
+				}
 			} else {
-				print_line(vformat("%5d | %s", i + 1, shader[i]));
+				print_line("--" + E.key + "--");
 			}
+			int err_line = -1;
+			for (int i = 0; i < include_positions.size(); i++) {
+				if (include_positions[i].file == E.key) {
+					err_line = include_positions[i].line;
+				}
+			}
+			const Vector<String> &V = E.value;
+			for (int i = 0; i < V.size(); i++) {
+				if (i == err_line - 1) {
+					// Mark the error line to be visible without having to look at
+					// the trace at the end.
+					print_line(vformat("E%4d-> %s", i + 1, V[i]));
+				} else {
+					print_line(vformat("%5d | %s", i + 1, V[i]));
+				}
+			}
+		}
+
+		String file;
+		int line;
+		if (include_positions.size() > 1) {
+			file = include_positions[include_positions.size() - 1].file;
+			line = include_positions[include_positions.size() - 1].line;
+		} else {
+			file = p_path;
+			line = parser.get_error_line();
 		}
 
-		_err_print_error(nullptr, p_path.utf8().get_data(), parser.get_error_line(), parser.get_error_text().utf8().get_data(), false, ERR_HANDLER_SHADER);
+		_err_print_error(nullptr, file.utf8().get_data(), line, parser.get_error_text().utf8().get_data(), false, ERR_HANDLER_SHADER);
 		return err;
 	}
 

+ 43 - 164
servers/rendering/shader_language.cpp

@@ -32,8 +32,8 @@
 
 #include "core/os/os.h"
 #include "core/string/print_string.h"
+#include "core/templates/local_vector.h"
 #include "servers/rendering_server.h"
-#include "shader_preprocessor.h"
 
 #define HAS_WARNING(flag) (warning_flags & flag)
 
@@ -575,6 +575,37 @@ ShaderLanguage::Token ShaderLanguage::_get_token() {
 
 				return _make_token(TK_OP_MOD);
 			} break;
+			case '@': {
+				if (GETCHAR(0) == '@' && GETCHAR(1) == '>') {
+					char_idx += 2;
+
+					LocalVector<char32_t> incp;
+					while (GETCHAR(0) != '\n') {
+						incp.push_back(GETCHAR(0));
+						char_idx++;
+					}
+					incp.push_back(0); // Zero end it.
+					String include_path(incp.ptr());
+					include_positions.write[include_positions.size() - 1].line = tk_line;
+					FilePosition fp;
+					fp.file = include_path;
+					fp.line = 0;
+					tk_line = 0;
+					include_positions.push_back(fp);
+
+				} else if (GETCHAR(0) == '@' && GETCHAR(1) == '<') {
+					if (include_positions.size() == 1) {
+						return _make_token(TK_ERROR, "Invalid include exit hint @@< without matching enter hint.");
+					}
+					char_idx += 2;
+
+					include_positions.resize(include_positions.size() - 1); // Pop back.
+					tk_line = include_positions[include_positions.size() - 1].line; // Restore line.
+
+				} else {
+					return _make_token(TK_ERROR, "Invalid include enter/exit hint token (@@> and @@<)");
+				}
+			} break;
 			default: {
 				char_idx--; //go back one, since we have no idea what this is
 
@@ -1123,6 +1154,9 @@ void ShaderLanguage::clear() {
 	completion_base = TYPE_VOID;
 	completion_base_array = false;
 
+	include_positions.clear();
+	include_positions.push_back(FilePosition());
+
 #ifdef DEBUG_ENABLED
 	keyword_completion_context = CF_GLOBAL_SPACE;
 	used_constants.clear();
@@ -4119,10 +4153,6 @@ void ShaderLanguage::get_keyword_list(List<String> *r_keywords) {
 	}
 }
 
-void ShaderLanguage::get_preprocessor_keyword_list(List<String> *r_keywords, bool p_include_shader_keywords) {
-	ShaderPreprocessor::get_keyword_list(r_keywords, p_include_shader_keywords);
-}
-
 bool ShaderLanguage::is_control_flow_keyword(String p_keyword) {
 	return p_keyword == "break" ||
 			p_keyword == "case" ||
@@ -7682,27 +7712,6 @@ Error ShaderLanguage::_validate_precision(DataType p_type, DataPrecision p_preci
 	return OK;
 }
 
-Error ShaderLanguage::_preprocess_shader(const String &p_code, String &r_result, int *r_completion_type) {
-	Error error = OK;
-
-	ShaderPreprocessor processor(p_code);
-	processor.preprocess(r_result);
-
-	ShaderPreprocessor::State *state = processor.get_state();
-	if (!state->error.is_empty()) {
-		error_line = state->error_line;
-		error_set = true;
-		error_str = state->error;
-		error = FAILED;
-	}
-
-	if (r_completion_type != nullptr) {
-		*r_completion_type = (int)state->completion_type;
-	}
-
-	return error;
-}
-
 Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_functions, const Vector<ModeInfo> &p_render_modes, const HashSet<String> &p_shader_types, bool p_is_include) {
 	Token tk;
 	TkPos prev_pos;
@@ -9500,97 +9509,6 @@ String ShaderLanguage::get_shader_type(const String &p_code) {
 	return String();
 }
 
-void ShaderLanguage::get_shader_dependencies(const String &p_code, HashSet<Ref<ShaderInclude>> *r_dependencies) {
-	bool reading_inc = false;
-	String cur_identifier;
-
-	for (int i = _get_first_ident_pos(p_code); i < p_code.length(); i++) {
-		if (p_code[i] == ';') {
-			continue;
-
-		} else if (p_code[i] <= 32) {
-			if (cur_identifier == "#include") {
-				reading_inc = true;
-				cur_identifier = String();
-			} else {
-				if (reading_inc) {
-					String path = cur_identifier;
-					if (path.begins_with("\"") && path.ends_with("\"")) {
-						path = path.substr(1, path.length() - 2);
-						if (!path.begins_with("res://")) {
-							path = path.insert(0, "res://");
-						}
-						Ref<ShaderInclude> inc = ResourceLoader::load(path);
-						if (inc.is_valid()) {
-							r_dependencies->insert(inc);
-						}
-					}
-					reading_inc = false;
-				}
-			}
-		} else {
-			cur_identifier += String::chr(p_code[i]);
-		}
-	}
-}
-
-String ShaderLanguage::get_shader_type_and_dependencies(const String &p_code, HashSet<Ref<ShaderInclude>> *r_dependencies) {
-	bool read_type = true;
-	bool reading_type = false;
-	bool reading_inc = false;
-	String type;
-
-	String cur_identifier;
-
-	for (int i = _get_first_ident_pos(p_code); i < p_code.length(); i++) {
-		if (p_code[i] == ';') {
-			continue;
-
-		} else if (p_code[i] <= 32) {
-			if (!cur_identifier.is_empty()) {
-				if (read_type) {
-					if (!reading_type) {
-						if (cur_identifier == "shader_type") {
-							reading_type = true;
-							cur_identifier = String();
-						}
-					} else {
-						type = cur_identifier;
-						read_type = false;
-						cur_identifier = String();
-					}
-				} else if (cur_identifier == "#include") {
-					reading_inc = true;
-					cur_identifier = String();
-				} else {
-					if (reading_inc) {
-						String path = cur_identifier;
-						if (path.begins_with("\"") && path.ends_with("\"")) {
-							path = path.substr(1, path.length() - 2);
-							if (!path.begins_with("res://")) {
-								path = path.insert(0, "res://");
-							}
-							Ref<ShaderInclude> inc = ResourceLoader::load(path);
-							if (inc.is_valid()) {
-								r_dependencies->insert(inc);
-							}
-						}
-						reading_inc = false;
-					}
-				}
-			}
-		} else {
-			cur_identifier += String::chr(p_code[i]);
-		}
-	}
-
-	if (reading_type) {
-		return type;
-	}
-
-	return String();
-}
-
 #ifdef DEBUG_ENABLED
 void ShaderLanguage::_check_warning_accums() {
 	for (const KeyValue<ShaderWarning::Code, HashMap<StringName, HashMap<StringName, Usage>> *> &E : warnings_check_map2) {
@@ -9630,21 +9548,15 @@ uint32_t ShaderLanguage::get_warning_flags() const {
 Error ShaderLanguage::compile(const String &p_code, const ShaderCompileInfo &p_info) {
 	clear();
 
-	Error err = _preprocess_shader(p_code, code);
-	if (err != OK) {
-		return err;
-	}
-
-	// Clear after preprocessing. Because preprocess uses the resource loader, it means if this instance is held in a singleton, it can have a changed state after.
-	clear();
-
+	code = p_code;
 	global_var_get_type_func = p_info.global_variable_type_func;
+
 	varying_function_names = p_info.varying_function_names;
 
 	nodes = nullptr;
 
 	shader = alloc_node<ShaderNode>();
-	err = _parse_shader(p_info.functions, p_info.render_modes, p_info.shader_types, p_info.is_include);
+	Error err = _parse_shader(p_info.functions, p_info.render_modes, p_info.shader_types, p_info.is_include);
 
 #ifdef DEBUG_ENABLED
 	if (check_warnings) {
@@ -9661,44 +9573,7 @@ Error ShaderLanguage::compile(const String &p_code, const ShaderCompileInfo &p_i
 Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_info, List<ScriptLanguage::CodeCompletionOption> *r_options, String &r_call_hint) {
 	clear();
 
-	int preprocessor_completion_type;
-	Error error = _preprocess_shader(p_code, code, &preprocessor_completion_type);
-
-	switch (preprocessor_completion_type) {
-		case ShaderPreprocessor::COMPLETION_TYPE_DIRECTIVE: {
-			static List<String> options;
-
-			if (options.is_empty()) {
-				ShaderPreprocessor::get_keyword_list(&options, true);
-			}
-
-			for (const String &E : options) {
-				ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
-				r_options->push_back(option);
-			}
-
-			return OK;
-		} break;
-		case ShaderPreprocessor::COMPLETION_TYPE_PRAGMA: {
-			static List<String> options;
-
-			if (options.is_empty()) {
-				ShaderPreprocessor::get_pragma_list(&options);
-			}
-
-			for (const String &E : options) {
-				ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
-				r_options->push_back(option);
-			}
-
-			return OK;
-		} break;
-	}
-
-	if (error != OK) {
-		return error;
-	}
-
+	code = p_code;
 	varying_function_names = p_info.varying_function_names;
 
 	nodes = nullptr;
@@ -10231,6 +10106,10 @@ String ShaderLanguage::get_error_text() {
 	return error_str;
 }
 
+Vector<ShaderLanguage::FilePosition> ShaderLanguage::get_include_positions() {
+	return include_positions;
+}
+
 int ShaderLanguage::get_error_line() {
 	return error_line;
 }

+ 9 - 4
servers/rendering/shader_language.h

@@ -777,7 +777,6 @@ public:
 	static uint32_t get_datatype_size(DataType p_type);
 
 	static void get_keyword_list(List<String> *r_keywords);
-	static void get_preprocessor_keyword_list(List<String> *r_keywords, bool p_include_shader_keywords);
 	static bool is_control_flow_keyword(String p_keyword);
 	static void get_builtin_funcs(List<String> *r_keywords);
 
@@ -869,6 +868,11 @@ public:
 
 	typedef DataType (*GlobalVariableGetTypeFunc)(const StringName &p_name);
 
+	struct FilePosition {
+		String file;
+		int line = 0;
+	};
+
 private:
 	struct KeyWord {
 		TokenType token;
@@ -886,6 +890,8 @@ private:
 	String error_str;
 	int error_line = 0;
 
+	Vector<FilePosition> include_positions;
+
 #ifdef DEBUG_ENABLED
 	struct Usage {
 		int decl_line;
@@ -953,6 +959,7 @@ private:
 		error_line = tk_line;
 		error_set = true;
 		error_str = p_str;
+		include_positions.write[include_positions.size() - 1].line = tk_line;
 	}
 
 	void _set_expected_error(const String &p_what) {
@@ -1072,7 +1079,6 @@ private:
 	String _get_shader_type_list(const HashSet<String> &p_shader_types) const;
 	String _get_qualifier_str(ArgumentQualifier p_qualifier) const;
 
-	Error _preprocess_shader(const String &p_code, String &r_result, int *r_completion_type = nullptr);
 	Error _parse_shader(const HashMap<StringName, FunctionInfo> &p_functions, const Vector<ModeInfo> &p_render_modes, const HashSet<String> &p_shader_types, bool p_is_include);
 
 	Error _find_last_flow_op_in_block(BlockNode *p_block, FlowOperation p_op);
@@ -1094,8 +1100,6 @@ public:
 	void clear();
 
 	static String get_shader_type(const String &p_code);
-	static void get_shader_dependencies(const String &p_code, HashSet<Ref<ShaderInclude>> *r_dependencies);
-	static String get_shader_type_and_dependencies(const String &p_code, HashSet<Ref<ShaderInclude>> *r_dependencies);
 
 	struct ShaderCompileInfo {
 		HashMap<StringName, FunctionInfo> functions;
@@ -1110,6 +1114,7 @@ public:
 	Error complete(const String &p_code, const ShaderCompileInfo &p_info, List<ScriptLanguage::CodeCompletionOption> *r_options, String &r_call_hint);
 
 	String get_error_text();
+	Vector<FilePosition> get_include_positions();
 	int get_error_line();
 
 	ShaderNode *get_shader();

+ 77 - 30
servers/rendering/shader_preprocessor.cpp

@@ -555,6 +555,15 @@ void ShaderPreprocessor::process_include(Tokenizer *p_tokenizer) {
 
 	p_tokenizer->advance('"');
 	String path = tokens_to_string(p_tokenizer->advance('"'));
+	for (int i = 0; i < path.length(); i++) {
+		if (path[i] == '\n') {
+			break; //stop parsing
+		}
+		if (path[i] == CURSOR) {
+			state->completion_type = COMPLETION_TYPE_INCLUDE_PATH;
+			break;
+		}
+	}
 	path = path.substr(0, path.length() - 1);
 	p_tokenizer->skip_whitespace();
 
@@ -569,7 +578,7 @@ void ShaderPreprocessor::process_include(Tokenizer *p_tokenizer) {
 		return;
 	}
 
-	Ref<ShaderInclude> shader_inc = Object::cast_to<ShaderInclude>(*res);
+	Ref<ShaderInclude> shader_inc = res;
 	if (shader_inc.is_null()) {
 		set_error(RTR("Shader include resource type is wrong."), line);
 		return;
@@ -584,6 +593,8 @@ void ShaderPreprocessor::process_include(Tokenizer *p_tokenizer) {
 		}
 	}
 
+	state->shader_includes.insert(shader_inc);
+
 	const String real_path = shader_inc->get_path();
 	if (state->includes.has(real_path)) {
 		// Already included, skip.
@@ -603,18 +614,26 @@ void ShaderPreprocessor::process_include(Tokenizer *p_tokenizer) {
 
 	String old_include = state->current_include;
 	state->current_include = real_path;
-	ShaderPreprocessor processor(included);
+	ShaderPreprocessor processor;
 
 	int prev_condition_depth = state->condition_depth;
 	state->condition_depth = 0;
 
+	FilePosition fp;
+	fp.file = state->current_include;
+	fp.line = line;
+	state->include_positions.push_back(fp);
+
 	String result;
-	processor.preprocess(state, result);
+	processor.preprocess(state, included, result);
+	add_to_output("@@>" + real_path + "\n"); // Add token for enter include path
 	add_to_output(result);
+	add_to_output("\n@@<\n"); // Add token for exit include path
 
 	// Reset to last include if there are no errors. We want to use this as context.
 	if (state->error.is_empty()) {
 		state->current_include = old_include;
+		state->include_positions.pop_back();
 	} else {
 		return;
 	}
@@ -855,7 +874,9 @@ void ShaderPreprocessor::add_to_output(const String &p_str) {
 void ShaderPreprocessor::set_error(const String &p_error, int p_line) {
 	if (state->error.is_empty()) {
 		state->error = p_error;
-		state->error_line = p_line + 1;
+		FilePosition fp;
+		fp.line = p_line + 1;
+		state->include_positions.push_back(fp);
 	}
 }
 
@@ -867,18 +888,6 @@ bool ShaderPreprocessor::check_directive_before_type(Tokenizer *p_tokenizer, con
 	return true;
 }
 
-ShaderPreprocessor::State *ShaderPreprocessor::create_state() {
-	State *new_state = memnew(State);
-
-	String platform = OS::get_singleton()->get_name().replace(" ", "_").to_upper();
-	new_state->defines[platform] = create_define("true");
-
-	Engine *engine = Engine::get_singleton();
-	new_state->defines["EDITOR"] = create_define(engine->is_editor_hint() ? "true" : "false");
-
-	return new_state;
-}
-
 ShaderPreprocessor::Define *ShaderPreprocessor::create_define(const String &p_body) {
 	ShaderPreprocessor::Define *define = memnew(Define);
 	define->body = p_body;
@@ -903,18 +912,14 @@ void ShaderPreprocessor::clear() {
 	state = nullptr;
 }
 
-Error ShaderPreprocessor::preprocess(State *p_state, String &r_result) {
+Error ShaderPreprocessor::preprocess(State *p_state, const String &p_code, String &r_result) {
 	clear();
 
 	output.clear();
 
 	state = p_state;
-	if (state == nullptr) {
-		state = create_state();
-		state_owner = true;
-	}
 
-	CommentRemover remover(code);
+	CommentRemover remover(p_code);
 	String stripped = remover.strip();
 	String error = remover.get_error();
 	if (!error.is_empty()) {
@@ -923,7 +928,7 @@ Error ShaderPreprocessor::preprocess(State *p_state, String &r_result) {
 	}
 
 	// Track code hashes to prevent cyclic include.
-	uint64_t code_hash = code.hash64();
+	uint64_t code_hash = p_code.hash64();
 	state->cyclic_include_hashes.push_back(code_hash);
 
 	Tokenizer p_tokenizer(stripped);
@@ -990,12 +995,55 @@ Error ShaderPreprocessor::preprocess(State *p_state, String &r_result) {
 	return OK;
 }
 
-Error ShaderPreprocessor::preprocess(String &r_result) {
-	return preprocess(nullptr, r_result);
-}
+Error ShaderPreprocessor::preprocess(const String &p_code, String &r_result, String *r_error_text, List<FilePosition> *r_error_position, HashSet<Ref<ShaderInclude>> *r_includes, List<ScriptLanguage::CodeCompletionOption> *r_completion_options, IncludeCompletionFunction p_include_completion_func) {
+	State pp_state;
+	Error err = preprocess(&pp_state, p_code, r_result);
+	if (err != OK) {
+		if (r_error_text) {
+			*r_error_text = pp_state.error;
+		}
+		if (r_error_position) {
+			*r_error_position = pp_state.include_positions;
+		}
+	}
+	if (r_includes) {
+		*r_includes = pp_state.shader_includes;
+	}
+
+	if (r_completion_options) {
+		switch (pp_state.completion_type) {
+			case COMPLETION_TYPE_DIRECTIVE: {
+				List<String> options;
+				get_keyword_list(&options, true);
+
+				for (const String &E : options) {
+					ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+					r_completion_options->push_back(option);
+				}
+
+			} break;
+			case COMPLETION_TYPE_PRAGMA: {
+				List<String> options;
+
+				ShaderPreprocessor::get_pragma_list(&options);
 
-ShaderPreprocessor::State *ShaderPreprocessor::get_state() {
-	return state;
+				for (const String &E : options) {
+					ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+					r_completion_options->push_back(option);
+				}
+
+			} break;
+			case COMPLETION_TYPE_INCLUDE_PATH: {
+				if (p_include_completion_func && r_completion_options) {
+					p_include_completion_func(r_completion_options);
+				}
+
+			} break;
+			default: {
+			}
+		}
+	}
+	return err;
 }
 
 void ShaderPreprocessor::get_keyword_list(List<String> *r_keywords, bool p_include_shader_keywords) {
@@ -1018,8 +1066,7 @@ void ShaderPreprocessor::get_pragma_list(List<String> *r_pragmas) {
 	r_pragmas->push_back("disable_preprocessor");
 }
 
-ShaderPreprocessor::ShaderPreprocessor(const String &p_code) :
-		code(p_code) {
+ShaderPreprocessor::ShaderPreprocessor() {
 }
 
 ShaderPreprocessor::~ShaderPreprocessor() {

+ 22 - 16
servers/rendering/shader_preprocessor.h

@@ -44,6 +44,20 @@
 #include "scene/resources/shader_include.h"
 
 class ShaderPreprocessor {
+public:
+	enum CompletionType {
+		COMPLETION_TYPE_NONE,
+		COMPLETION_TYPE_DIRECTIVE,
+		COMPLETION_TYPE_PRAGMA_DIRECTIVE,
+		COMPLETION_TYPE_PRAGMA,
+		COMPLETION_TYPE_INCLUDE_PATH,
+	};
+
+	struct FilePosition {
+		String file;
+		int line = 0;
+	};
+
 private:
 	struct Token {
 		char32_t text;
@@ -113,14 +127,6 @@ private:
 		int end_line = -1;
 	};
 
-public:
-	enum CompletionType {
-		COMPLETION_TYPE_NONE,
-		COMPLETION_TYPE_DIRECTIVE,
-		COMPLETION_TYPE_PRAGMA_DIRECTIVE,
-		COMPLETION_TYPE_PRAGMA,
-	};
-
 	struct State {
 		RBMap<String, Define *> defines;
 		Vector<bool> skip_stack_else;
@@ -132,14 +138,14 @@ public:
 		String current_shader_type;
 		int shader_type_pos = -1;
 		String error;
-		int error_line = -1;
+		List<FilePosition> include_positions;
 		RBMap<String, Vector<SkippedCondition *>> skipped_conditions;
 		bool disabled = false;
 		CompletionType completion_type = COMPLETION_TYPE_NONE;
+		HashSet<Ref<ShaderInclude>> shader_includes;
 	};
 
 private:
-	String code;
 	LocalVector<char32_t> output;
 	State *state = nullptr;
 	bool state_owner = false;
@@ -175,21 +181,21 @@ private:
 	void set_error(const String &p_error, int p_line);
 	bool check_directive_before_type(Tokenizer *p_tokenizer, const String &p_directive);
 
-	static State *create_state();
 	static Define *create_define(const String &p_body);
 
 	void clear();
 
+	Error preprocess(State *p_state, const String &p_code, String &r_result);
+
 public:
-	Error preprocess(State *p_state, String &r_result);
-	Error preprocess(String &r_result);
+	typedef void (*IncludeCompletionFunction)(List<ScriptLanguage::CodeCompletionOption> *);
 
-	State *get_state();
+	Error preprocess(const String &p_code, String &r_result, String *r_error_text = nullptr, List<FilePosition> *r_error_position = nullptr, HashSet<Ref<ShaderInclude>> *r_includes = nullptr, List<ScriptLanguage::CodeCompletionOption> *r_completion_options = nullptr, IncludeCompletionFunction p_include_completion_func = nullptr);
 
-	static void get_keyword_list(List<String> *r_keywords, bool p_include_shader_keywords = false);
+	static void get_keyword_list(List<String> *r_keywords, bool p_include_shader_keywords);
 	static void get_pragma_list(List<String> *r_pragmas);
 
-	ShaderPreprocessor(const String &p_code);
+	ShaderPreprocessor();
 	~ShaderPreprocessor();
 };
 

+ 1 - 0
servers/rendering/storage/material_storage.h

@@ -61,6 +61,7 @@ public:
 	virtual void shader_free(RID p_rid) = 0;
 
 	virtual void shader_set_code(RID p_shader, const String &p_code) = 0;
+	virtual void shader_set_path_hint(RID p_shader, const String &p_path) = 0;
 	virtual String shader_get_code(RID p_shader) const = 0;
 	virtual void shader_get_param_list(RID p_shader, List<PropertyInfo> *p_param_list) const = 0;
 

+ 2 - 0
servers/rendering_server.cpp

@@ -1709,6 +1709,8 @@ void RenderingServer::_bind_methods() {
 	/* SHADER */
 
 	ClassDB::bind_method(D_METHOD("shader_create"), &RenderingServer::shader_create);
+	ClassDB::bind_method(D_METHOD("shader_set_code", "shader", "code"), &RenderingServer::shader_set_code);
+	ClassDB::bind_method(D_METHOD("shader_set_path_hint", "shader", "path"), &RenderingServer::shader_set_path_hint);
 	ClassDB::bind_method(D_METHOD("shader_get_code", "shader"), &RenderingServer::shader_get_code);
 	ClassDB::bind_method(D_METHOD("shader_get_param_list", "shader"), &RenderingServer::_shader_get_param_list);
 	ClassDB::bind_method(D_METHOD("shader_get_param_default", "shader", "param"), &RenderingServer::shader_get_param_default);

+ 1 - 0
servers/rendering_server.h

@@ -170,6 +170,7 @@ public:
 	virtual RID shader_create() = 0;
 
 	virtual void shader_set_code(RID p_shader, const String &p_code) = 0;
+	virtual void shader_set_path_hint(RID p_shader, const String &p_path) = 0;
 	virtual String shader_get_code(RID p_shader) const = 0;
 	virtual void shader_get_param_list(RID p_shader, List<PropertyInfo> *p_param_list) const = 0;
 	virtual Variant shader_get_param_default(RID p_shader, const StringName &p_param) const = 0;