From 22df2c527bb1d7ad026e12064b0d87f8da12a1b2 Mon Sep 17 00:00:00 2001 From: Yuri Rubinsky Date: Wed, 3 Aug 2022 15:19:31 +0300 Subject: [PATCH] Implement coloring for disabled branches in the shader editor --- editor/plugins/shader_editor_plugin.cpp | 67 ++++++++++++++++++++++- editor/plugins/shader_editor_plugin.h | 17 +++++- scene/resources/shader.cpp | 2 +- scene/resources/shader_include.cpp | 2 +- servers/rendering/shader_preprocessor.cpp | 62 +++++++++++++++++---- servers/rendering/shader_preprocessor.h | 16 +++++- 6 files changed, 149 insertions(+), 17 deletions(-) diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 74ce5ef128b..d70c50f72ae 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -47,6 +47,50 @@ #include "servers/rendering/shader_preprocessor.h" #include "servers/rendering/shader_types.h" +/*** SHADER SYNTAX HIGHLIGHTER ****/ + +Dictionary GDShaderSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) { + Dictionary color_map; + + for (const Point2i ®ion : disabled_branch_regions) { + if (p_line >= region.x && p_line <= region.y) { + Dictionary highlighter_info; + highlighter_info["color"] = disabled_branch_color; + + color_map[0] = highlighter_info; + return color_map; + } + } + + return CodeHighlighter::_get_line_syntax_highlighting_impl(p_line); +} + +void GDShaderSyntaxHighlighter::add_disabled_branch_region(const Point2i &p_region) { + ERR_FAIL_COND(p_region.x < 0); + ERR_FAIL_COND(p_region.y < 0); + + for (int i = 0; i < disabled_branch_regions.size(); i++) { + ERR_FAIL_COND_MSG(disabled_branch_regions[i].x == p_region.x, "Branch region with a start line '" + itos(p_region.x) + "' already exists."); + } + + Point2i disabled_branch_region; + disabled_branch_region.x = p_region.x; + disabled_branch_region.y = p_region.y; + disabled_branch_regions.push_back(disabled_branch_region); + + clear_highlighting_cache(); +} + +void GDShaderSyntaxHighlighter::clear_disabled_branch_regions() { + disabled_branch_regions.clear(); + clear_highlighting_cache(); +} + +void GDShaderSyntaxHighlighter::set_disabled_branch_color(const Color &p_color) { + disabled_branch_color = p_color; + clear_highlighting_cache(); +} + /*** SHADER SCRIPT EDITOR ****/ static bool saved_warnings_enabled = false; @@ -264,6 +308,7 @@ 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); + syntax_highlighter->set_disabled_branch_color(comment_color); text_editor->clear_comment_delimiters(); text_editor->add_comment_delimiter("/*", "*/", false); @@ -346,7 +391,7 @@ void ShaderTextEditor::_code_complete_script(const String &p_code, List err_positions; - last_compile_result = preprocessor.preprocess(code, code_pp, &error_pp, &err_positions); + List regions; + String filename; + if (shader.is_valid()) { + filename = shader->get_path(); + } else if (shader_inc.is_valid()) { + filename = shader_inc->get_path(); + } + last_compile_result = preprocessor.preprocess(code, filename, code_pp, &error_pp, &err_positions, ®ions); 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)); } + + syntax_highlighter->clear_disabled_branch_regions(); + for (const ShaderPreprocessor::Region ®ion : regions) { + if (!region.enabled) { + if (filename != region.file) { + continue; + } + syntax_highlighter->add_disabled_branch_region(Point2i(region.from_line, region.to_line)); + } + } + set_error(""); set_error_count(0); diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index 9a59e861922..0980cc4db24 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -48,6 +48,21 @@ class VisualShaderEditor; class HSplitContainer; class ShaderCreateDialog; +class GDShaderSyntaxHighlighter : public CodeHighlighter { + GDCLASS(GDShaderSyntaxHighlighter, CodeHighlighter) + +private: + Vector disabled_branch_regions; + Color disabled_branch_color; + +public: + virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override; + + void add_disabled_branch_region(const Point2i &p_region); + void clear_disabled_branch_regions(); + void set_disabled_branch_color(const Color &p_color); +}; + class ShaderTextEditor : public CodeTextEditor { GDCLASS(ShaderTextEditor, CodeTextEditor); @@ -57,7 +72,7 @@ class ShaderTextEditor : public CodeTextEditor { _ALWAYS_INLINE_ bool operator()(const ShaderWarning &p_a, const ShaderWarning &p_b) const { return (p_a.get_line() < p_b.get_line()); } }; - Ref syntax_highlighter; + Ref syntax_highlighter; RichTextLabel *warnings_panel = nullptr; Ref shader; Ref shader_inc; diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp index db7b03f2be8..48d06934e31 100644 --- a/scene/resources/shader.cpp +++ b/scene/resources/shader.cpp @@ -82,7 +82,7 @@ void Shader::set_code(const String &p_code) { // 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); + preprocessor.preprocess(p_code, "", pp_code, nullptr, nullptr, nullptr, &new_include_dependencies); } // This ensures previous include resources are not freed and then re-loaded during parse (which would make compiling slower) diff --git a/scene/resources/shader_include.cpp b/scene/resources/shader_include.cpp index 42435fe3c7b..fe628dd3235 100644 --- a/scene/resources/shader_include.cpp +++ b/scene/resources/shader_include.cpp @@ -47,7 +47,7 @@ void ShaderInclude::set_code(const String &p_code) { { String pp_code; ShaderPreprocessor preprocessor; - preprocessor.preprocess(p_code, pp_code, nullptr, nullptr, &new_dependencies); + preprocessor.preprocess(p_code, "", pp_code, nullptr, nullptr, nullptr, &new_dependencies); } // This ensures previous include resources are not freed and then re-loaded during parse (which would make compiling slower) diff --git a/servers/rendering/shader_preprocessor.cpp b/servers/rendering/shader_preprocessor.cpp index a7b274b3e27..d118c73c4a7 100644 --- a/servers/rendering/shader_preprocessor.cpp +++ b/servers/rendering/shader_preprocessor.cpp @@ -416,16 +416,22 @@ void ShaderPreprocessor::process_define(Tokenizer *p_tokenizer) { } void ShaderPreprocessor::process_else(Tokenizer *p_tokenizer) { + const int line = p_tokenizer->get_line(); + if (state->skip_stack_else.is_empty()) { - set_error(RTR("Unmatched else."), p_tokenizer->get_line()); + set_error(RTR("Unmatched else."), line); return; } + if (state->previous_region != nullptr) { + state->previous_region->to_line = line - 1; + } + p_tokenizer->advance('\n'); bool skip = state->skip_stack_else[state->skip_stack_else.size() - 1]; state->skip_stack_else.remove_at(state->skip_stack_else.size() - 1); - Vector vec = state->skipped_conditions[state->current_include]; + Vector vec = state->skipped_conditions[state->current_filename]; int index = vec.size() - 1; if (index >= 0) { SkippedCondition *cond = vec[index]; @@ -434,6 +440,10 @@ void ShaderPreprocessor::process_else(Tokenizer *p_tokenizer) { } } + if (state->save_regions) { + add_region(line + 1, !skip, state->previous_region->parent); + } + if (skip) { Vector ends; ends.push_back("endif"); @@ -447,8 +457,12 @@ void ShaderPreprocessor::process_endif(Tokenizer *p_tokenizer) { set_error(RTR("Unmatched endif."), p_tokenizer->get_line()); return; } + if (state->previous_region != nullptr) { + state->previous_region->to_line = p_tokenizer->get_line() - 1; + state->previous_region = state->previous_region->parent; + } - Vector vec = state->skipped_conditions[state->current_include]; + Vector vec = state->skipped_conditions[state->current_filename]; int index = vec.size() - 1; if (index >= 0) { SkippedCondition *cond = vec[index]; @@ -461,7 +475,7 @@ void ShaderPreprocessor::process_endif(Tokenizer *p_tokenizer) { } void ShaderPreprocessor::process_if(Tokenizer *p_tokenizer) { - int line = p_tokenizer->get_line(); + const int line = p_tokenizer->get_line(); String body = tokens_to_string(p_tokenizer->advance('\n')).strip_edges(); if (body.is_empty()) { @@ -490,6 +504,10 @@ void ShaderPreprocessor::process_if(Tokenizer *p_tokenizer) { bool success = v.booleanize(); start_branch_condition(p_tokenizer, success); + + if (state->save_regions) { + add_region(line + 1, success, state->previous_region); + } } void ShaderPreprocessor::process_ifdef(Tokenizer *p_tokenizer) { @@ -510,6 +528,10 @@ void ShaderPreprocessor::process_ifdef(Tokenizer *p_tokenizer) { bool success = state->defines.has(label); start_branch_condition(p_tokenizer, success); + + if (state->save_regions) { + add_region(line + 1, success, state->previous_region); + } } void ShaderPreprocessor::process_ifndef(Tokenizer *p_tokenizer) { @@ -530,6 +552,10 @@ void ShaderPreprocessor::process_ifndef(Tokenizer *p_tokenizer) { bool success = !state->defines.has(label); start_branch_condition(p_tokenizer, success); + + if (state->save_regions) { + add_region(line + 1, success, state->previous_region); + } } void ShaderPreprocessor::process_include(Tokenizer *p_tokenizer) { @@ -594,15 +620,15 @@ void ShaderPreprocessor::process_include(Tokenizer *p_tokenizer) { return; } - String old_include = state->current_include; - state->current_include = real_path; + String old_filename = state->current_filename; + state->current_filename = real_path; ShaderPreprocessor processor; int prev_condition_depth = state->condition_depth; state->condition_depth = 0; FilePosition fp; - fp.file = state->current_include; + fp.file = state->current_filename; fp.line = line; state->include_positions.push_back(fp); @@ -614,7 +640,7 @@ void ShaderPreprocessor::process_include(Tokenizer *p_tokenizer) { // 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->current_filename = old_filename; state->include_positions.pop_back(); } else { return; @@ -668,6 +694,15 @@ void ShaderPreprocessor::process_undef(Tokenizer *p_tokenizer) { state->defines.erase(label); } +void ShaderPreprocessor::add_region(int p_line, bool p_enabled, Region *p_parent_region) { + Region region; + region.file = state->current_filename; + region.enabled = p_enabled; + region.from_line = p_line; + region.parent = p_parent_region; + state->previous_region = &state->regions[region.file].push_back(region)->get(); +} + void ShaderPreprocessor::start_branch_condition(Tokenizer *p_tokenizer, bool p_success) { state->condition_depth++; @@ -676,7 +711,7 @@ void ShaderPreprocessor::start_branch_condition(Tokenizer *p_tokenizer, bool p_s } else { SkippedCondition *cond = memnew(SkippedCondition()); cond->start_line = p_tokenizer->get_line(); - state->skipped_conditions[state->current_include].push_back(cond); + state->skipped_conditions[state->current_filename].push_back(cond); Vector ends; ends.push_back("else"); @@ -969,8 +1004,12 @@ Error ShaderPreprocessor::preprocess(State *p_state, const String &p_code, Strin return OK; } -Error ShaderPreprocessor::preprocess(const String &p_code, String &r_result, String *r_error_text, List *r_error_position, HashSet> *r_includes, List *r_completion_options, IncludeCompletionFunction p_include_completion_func) { +Error ShaderPreprocessor::preprocess(const String &p_code, const String &p_filename, String &r_result, String *r_error_text, List *r_error_position, List *r_regions, HashSet> *r_includes, List *r_completion_options, IncludeCompletionFunction p_include_completion_func) { State pp_state; + if (!p_filename.is_empty()) { + pp_state.current_filename = p_filename; + pp_state.save_regions = r_regions != nullptr; + } Error err = preprocess(&pp_state, p_code, r_result); if (err != OK) { if (r_error_text) { @@ -980,6 +1019,9 @@ Error ShaderPreprocessor::preprocess(const String &p_code, String &r_result, Str *r_error_position = pp_state.include_positions; } } + if (r_regions) { + *r_regions = pp_state.regions[p_filename]; + } if (r_includes) { *r_includes = pp_state.shader_includes; } diff --git a/servers/rendering/shader_preprocessor.h b/servers/rendering/shader_preprocessor.h index a93fb680dd2..41b574298d5 100644 --- a/servers/rendering/shader_preprocessor.h +++ b/servers/rendering/shader_preprocessor.h @@ -58,6 +58,14 @@ public: int line = 0; }; + struct Region { + String file; + int from_line = -1; + int to_line = -1; + bool enabled = false; + Region *parent = nullptr; + }; + private: struct Token { char32_t text; @@ -134,10 +142,13 @@ private: RBSet includes; List cyclic_include_hashes; // Holds code hash of includes. int include_depth = 0; - String current_include; + String current_filename; String current_shader_type; String error; List include_positions; + bool save_regions = false; + RBMap> regions; + Region *previous_region = nullptr; RBMap> skipped_conditions; bool disabled = false; CompletionType completion_type = COMPLETION_TYPE_NONE; @@ -167,6 +178,7 @@ private: void process_pragma(Tokenizer *p_tokenizer); void process_undef(Tokenizer *p_tokenizer); + void add_region(int p_line, bool p_enabled, Region *p_parent_region); void start_branch_condition(Tokenizer *p_tokenizer, bool p_success); void expand_output_macros(int p_start, int p_line); @@ -188,7 +200,7 @@ private: public: typedef void (*IncludeCompletionFunction)(List *); - Error preprocess(const String &p_code, String &r_result, String *r_error_text = nullptr, List *r_error_position = nullptr, HashSet> *r_includes = nullptr, List *r_completion_options = nullptr, IncludeCompletionFunction p_include_completion_func = nullptr); + Error preprocess(const String &p_code, const String &p_filename, String &r_result, String *r_error_text = nullptr, List *r_error_position = nullptr, List *r_regions = nullptr, HashSet> *r_includes = nullptr, List *r_completion_options = nullptr, IncludeCompletionFunction p_include_completion_func = nullptr); static void get_keyword_list(List *r_keywords, bool p_include_shader_keywords); static void get_pragma_list(List *r_pragmas);