diff options
author | RĂ©mi Verschelde <remi@verschelde.fr> | 2022-07-23 00:02:33 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-23 00:02:33 +0200 |
commit | fe929d4787b2b11390891fb03da1dda78b18eb65 (patch) | |
tree | de748bb0f4cd015fb50d137c10653645769a8a29 /servers/rendering | |
parent | 0c815022178ca23fa55085981384caa749f7f18a (diff) | |
parent | f649678402350a210cbb40e312be7b20592e85e0 (diff) |
Merge pull request #62513 from reduz/shader_preprocessor_remake
Diffstat (limited to 'servers/rendering')
22 files changed, 1468 insertions, 31 deletions
diff --git a/servers/rendering/dummy/storage/material_storage.h b/servers/rendering/dummy/storage/material_storage.h index d4809f81e3..5d1f64991b 100644 --- a/servers/rendering/dummy/storage/material_storage.h +++ b/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 {} diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp index 1951bfe915..a559241846 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp +++ b/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 diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h index 1cfe723174..05dabc1c56 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h +++ b/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; diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp index dd00dc2bf9..afe4eac0b3 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp +++ b/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 diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h index 88c2143b09..651155932a 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h +++ b/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; diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index 7d55be1216..b8ad4e4511 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/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 diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h index 2ab5a7c831..c0e6b27587 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h +++ b/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; diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 73028f38bf..3176af07b6 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -3689,6 +3689,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 diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.h b/servers/rendering/renderer_rd/renderer_scene_render_rd.h index 16cf99314a..8ee2f87feb 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.h @@ -915,6 +915,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; diff --git a/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp index 73175d3cf3..689fbba885 100644 --- a/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp +++ b/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 diff --git a/servers/rendering/renderer_rd/renderer_scene_sky_rd.h b/servers/rendering/renderer_rd/renderer_scene_sky_rd.h index a8ee406abc..4376fe4c5b 100644 --- a/servers/rendering/renderer_rd/renderer_scene_sky_rd.h +++ b/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; diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp index fcd25852eb..af2f5aafed 100644 --- a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp +++ b/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()); diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.h b/servers/rendering/renderer_rd/storage_rd/material_storage.h index e35d5e7669..b083968e5f 100644 --- a/servers/rendering/renderer_rd/storage_rd/material_storage.h +++ b/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; diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp index 5200e0d318..ea4bd6ab0b 100644 --- a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp +++ b/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 diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.h b/servers/rendering/renderer_rd/storage_rd/particles_storage.h index 70ac6f0349..f3a4e97fa7 100644 --- a/servers/rendering/renderer_rd/storage_rd/particles_storage.h +++ b/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; diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 429b8a06e2..4e41a87a2c 100644 --- a/servers/rendering/rendering_server_default.h +++ b/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> *) diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp index 463b67033d..e291246d01 100644 --- a/servers/rendering/shader_compiler.cpp +++ b/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; } diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index ad9b51ac0c..d2b80fb277 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -32,6 +32,7 @@ #include "core/os/os.h" #include "core/string/print_string.h" +#include "core/templates/local_vector.h" #include "servers/rendering_server.h" #define HAS_WARNING(flag) (warning_flags & flag) @@ -574,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 @@ -1122,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(); @@ -7677,35 +7712,39 @@ Error ShaderLanguage::_validate_precision(DataType p_type, DataPrecision p_preci return OK; } -Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_functions, const Vector<ModeInfo> &p_render_modes, const HashSet<String> &p_shader_types) { - Token tk = _get_token(); +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; Token next; - if (tk.type != TK_SHADER_TYPE) { - _set_error(vformat(RTR("Expected '%s' at the beginning of shader. Valid types are: %s."), "shader_type", _get_shader_type_list(p_shader_types))); - return ERR_PARSE_ERROR; - } + if (!p_is_include) { + tk = _get_token(); + + if (tk.type != TK_SHADER_TYPE) { + _set_error(vformat(RTR("Expected '%s' at the beginning of shader. Valid types are: %s."), "shader_type", _get_shader_type_list(p_shader_types))); + return ERR_PARSE_ERROR; + } #ifdef DEBUG_ENABLED - keyword_completion_context = CF_UNSPECIFIED; + keyword_completion_context = CF_UNSPECIFIED; #endif // DEBUG_ENABLED - _get_completable_identifier(nullptr, COMPLETION_SHADER_TYPE, shader_type_identifier); - if (shader_type_identifier == StringName()) { - _set_error(vformat(RTR("Expected an identifier after '%s', indicating the type of shader. Valid types are: %s."), "shader_type", _get_shader_type_list(p_shader_types))); - return ERR_PARSE_ERROR; - } - if (!p_shader_types.has(shader_type_identifier)) { - _set_error(vformat(RTR("Invalid shader type. Valid types are: %s"), _get_shader_type_list(p_shader_types))); - return ERR_PARSE_ERROR; - } - prev_pos = _get_tkpos(); - tk = _get_token(); + _get_completable_identifier(nullptr, COMPLETION_SHADER_TYPE, shader_type_identifier); + if (shader_type_identifier == StringName()) { + _set_error(vformat(RTR("Expected an identifier after '%s', indicating the type of shader. Valid types are: %s."), "shader_type", _get_shader_type_list(p_shader_types))); + return ERR_PARSE_ERROR; + } + if (!p_shader_types.has(shader_type_identifier)) { + _set_error(vformat(RTR("Invalid shader type. Valid types are: %s"), _get_shader_type_list(p_shader_types))); + return ERR_PARSE_ERROR; + } + prev_pos = _get_tkpos(); + tk = _get_token(); - if (tk.type != TK_SEMICOLON) { - _set_tkpos(prev_pos); - _set_expected_after_error(";", "shader_type " + String(shader_type_identifier)); - return ERR_PARSE_ERROR; + if (tk.type != TK_SEMICOLON) { + _set_tkpos(prev_pos); + _set_expected_after_error(";", "shader_type " + String(shader_type_identifier)); + return ERR_PARSE_ERROR; + } } #ifdef DEBUG_ENABLED @@ -9511,12 +9550,13 @@ Error ShaderLanguage::compile(const String &p_code, const ShaderCompileInfo &p_i 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>(); - Error err = _parse_shader(p_info.functions, p_info.render_modes, p_info.shader_types); + 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) { @@ -9540,7 +9580,7 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_ global_var_get_type_func = p_info.global_variable_type_func; shader = alloc_node<ShaderNode>(); - _parse_shader(p_info.functions, p_info.render_modes, p_info.shader_types); + _parse_shader(p_info.functions, p_info.render_modes, p_info.shader_types, p_info.is_include); #ifdef DEBUG_ENABLED // Adds context keywords. @@ -10066,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; } diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h index 2b147fbeb1..4e7283a714 100644 --- a/servers/rendering/shader_language.h +++ b/servers/rendering/shader_language.h @@ -38,6 +38,7 @@ #include "core/templates/rb_map.h" #include "core/typedefs.h" #include "core/variant/variant.h" +#include "scene/resources/shader_include.h" #ifdef DEBUG_ENABLED #include "shader_warnings.h" @@ -867,6 +868,11 @@ public: typedef DataType (*GlobalVariableGetTypeFunc)(const StringName &p_name); + struct FilePosition { + String file; + int line = 0; + }; + private: struct KeyWord { TokenType token; @@ -884,6 +890,8 @@ private: String error_str; int error_line = 0; + Vector<FilePosition> include_positions; + #ifdef DEBUG_ENABLED struct Usage { int decl_line; @@ -951,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) { @@ -1070,7 +1079,7 @@ private: String _get_shader_type_list(const HashSet<String> &p_shader_types) const; String _get_qualifier_str(ArgumentQualifier p_qualifier) const; - Error _parse_shader(const HashMap<StringName, FunctionInfo> &p_functions, const Vector<ModeInfo> &p_render_modes, const HashSet<String> &p_shader_types); + 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); Error _find_last_flow_op_in_op(ControlFlowNode *p_flow, FlowOperation p_op); @@ -1098,12 +1107,14 @@ public: VaryingFunctionNames varying_function_names = VaryingFunctionNames(); HashSet<String> shader_types; GlobalVariableGetTypeFunc global_variable_type_func = nullptr; + bool is_include = false; }; Error compile(const String &p_code, const ShaderCompileInfo &p_info); 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(); diff --git a/servers/rendering/shader_preprocessor.cpp b/servers/rendering/shader_preprocessor.cpp new file mode 100644 index 0000000000..9623d11010 --- /dev/null +++ b/servers/rendering/shader_preprocessor.cpp @@ -0,0 +1,1074 @@ +/*************************************************************************/ +/* shader_preprocessor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_preprocessor.h" +#include "core/math/expression.h" + +const char32_t CURSOR = 0xFFFF; + +// Tokenizer + +void ShaderPreprocessor::Tokenizer::add_generated(const ShaderPreprocessor::Token &p_t) { + generated.push_back(p_t); +} + +char32_t ShaderPreprocessor::Tokenizer::next() { + if (index < size) { + return code[index++]; + } + return 0; +} + +int ShaderPreprocessor::Tokenizer::get_line() const { + return line; +} + +int ShaderPreprocessor::Tokenizer::get_index() const { + return index; +} + +void ShaderPreprocessor::Tokenizer::get_and_clear_generated(Vector<ShaderPreprocessor::Token> *r_out) { + for (int i = 0; i < generated.size(); i++) { + r_out->push_back(generated[i]); + } + generated.clear(); +} + +void ShaderPreprocessor::Tokenizer::backtrack(char32_t p_what) { + while (index >= 0) { + char32_t c = code[index]; + if (c == p_what) { + break; + } + index--; + } +} + +char32_t ShaderPreprocessor::Tokenizer::peek() { + if (index < size) { + return code[index]; + } + return 0; +} + +LocalVector<ShaderPreprocessor::Token> ShaderPreprocessor::Tokenizer::advance(char32_t p_what) { + LocalVector<ShaderPreprocessor::Token> tokens; + + while (index < size) { + char32_t c = code[index++]; + + tokens.push_back(ShaderPreprocessor::Token(c, line)); + + if (c == '\n') { + add_generated(ShaderPreprocessor::Token('\n', line)); + line++; + } + + if (c == p_what || c == 0) { + return tokens; + } + } + return LocalVector<ShaderPreprocessor::Token>(); +} + +void ShaderPreprocessor::Tokenizer::skip_whitespace() { + while (is_char_space(peek())) { + next(); + } +} + +String ShaderPreprocessor::Tokenizer::get_identifier(bool *r_is_cursor, bool p_started) { + if (r_is_cursor != nullptr) { + *r_is_cursor = false; + } + + LocalVector<char32_t> text; + + while (true) { + char32_t c = peek(); + if (is_char_end(c) || c == '(' || c == ')' || c == ',' || c == ';') { + break; + } + + if (is_whitespace(c) && p_started) { + break; + } + if (!is_whitespace(c)) { + p_started = true; + } + + char32_t n = next(); + if (n == CURSOR) { + if (r_is_cursor != nullptr) { + *r_is_cursor = true; + } + } else { + if (p_started) { + text.push_back(n); + } + } + } + + String id = vector_to_string(text); + if (!id.is_valid_identifier()) { + return ""; + } + + return id; +} + +String ShaderPreprocessor::Tokenizer::peek_identifier() { + const int original = index; + String id = get_identifier(); + index = original; + return id; +} + +ShaderPreprocessor::Token ShaderPreprocessor::Tokenizer::get_token() { + while (index < size) { + const char32_t c = code[index++]; + const Token t = ShaderPreprocessor::Token(c, line); + + switch (c) { + case ' ': + case '\t': + skip_whitespace(); + return ShaderPreprocessor::Token(' ', line); + case '\n': + line++; + return t; + default: + return t; + } + } + return ShaderPreprocessor::Token(char32_t(0), line); +} + +ShaderPreprocessor::Tokenizer::Tokenizer(const String &p_code) { + code = p_code; + line = 0; + index = 0; + size = code.size(); +} + +// ShaderPreprocessor::CommentRemover + +String ShaderPreprocessor::CommentRemover::get_error() const { + if (comments_open != 0) { + return "Block comment mismatch"; + } + return ""; +} + +int ShaderPreprocessor::CommentRemover::get_error_line() const { + if (comments_open != 0) { + return comment_line_open; + } + return -1; +} + +char32_t ShaderPreprocessor::CommentRemover::peek() const { + if (index < code.size()) { + return code[index]; + } + return 0; +} + +bool ShaderPreprocessor::CommentRemover::advance(char32_t p_what) { + while (index < code.size()) { + char32_t c = code[index++]; + + if (c == '\n') { + line++; + stripped.push_back('\n'); + } + + if (c == p_what) { + return true; + } + } + return false; +} + +String ShaderPreprocessor::CommentRemover::strip() { + stripped.clear(); + index = 0; + line = 0; + comment_line_open = 0; + comments_open = 0; + strings_open = 0; + + while (index < code.size()) { + char32_t c = code[index++]; + + if (c == CURSOR) { + // Cursor. Maintain. + stripped.push_back(c); + } else if (c == '"') { + if (strings_open <= 0) { + strings_open++; + } else { + strings_open--; + } + stripped.push_back(c); + } else if (c == '/' && strings_open == 0) { + char32_t p = peek(); + if (p == '/') { // Single line comment. + advance('\n'); + } else if (p == '*') { // Start of a block comment. + index++; + comment_line_open = line; + comments_open++; + while (advance('*')) { + if (peek() == '/') { // End of a block comment. + comments_open--; + index++; + break; + } + } + } else { + stripped.push_back(c); + } + } else if (c == '*' && strings_open == 0) { + if (peek() == '/') { // Unmatched end of a block comment. + comment_line_open = line; + comments_open--; + } else { + stripped.push_back(c); + } + } else if (c == '\n') { + line++; + stripped.push_back(c); + } else { + stripped.push_back(c); + } + } + return vector_to_string(stripped); +} + +ShaderPreprocessor::CommentRemover::CommentRemover(const String &p_code) { + code = p_code; + index = 0; + line = 0; + comment_line_open = 0; + comments_open = 0; + strings_open = 0; +} + +// ShaderPreprocessor::Token + +ShaderPreprocessor::Token::Token() { + text = 0; + line = -1; +} + +ShaderPreprocessor::Token::Token(char32_t p_text, int p_line) { + text = p_text; + line = p_line; +} + +// ShaderPreprocessor + +bool ShaderPreprocessor::is_char_word(char32_t p_char) { + if ((p_char >= '0' && p_char <= '9') || + (p_char >= 'a' && p_char <= 'z') || + (p_char >= 'A' && p_char <= 'Z') || + p_char == '_') { + return true; + } + + return false; +} + +bool ShaderPreprocessor::is_char_space(char32_t p_char) { + return p_char == ' ' || p_char == '\t'; +} + +bool ShaderPreprocessor::is_char_end(char32_t p_char) { + return p_char == '\n' || p_char == 0; +} + +String ShaderPreprocessor::vector_to_string(const LocalVector<char32_t> &p_v, int p_start, int p_end) { + const int stop = (p_end == -1) ? p_v.size() : p_end; + const int count = stop - p_start; + + String result; + result.resize(count + 1); + for (int i = 0; i < count; i++) { + result[i] = p_v[p_start + i]; + } + result[count] = 0; // Ensure string is null terminated for length() to work. + return result; +} + +String ShaderPreprocessor::tokens_to_string(const LocalVector<Token> &p_tokens) { + LocalVector<char32_t> result; + for (uint32_t i = 0; i < p_tokens.size(); i++) { + result.push_back(p_tokens[i].text); + } + return vector_to_string(result); +} + +void ShaderPreprocessor::process_directive(Tokenizer *p_tokenizer) { + bool is_cursor; + String directive = p_tokenizer->get_identifier(&is_cursor, true); + if (is_cursor) { + state->completion_type = COMPLETION_TYPE_DIRECTIVE; + } + + if (directive == "if") { + if (check_directive_before_type(p_tokenizer, directive)) { + process_if(p_tokenizer); + } + } else if (directive == "ifdef") { + if (check_directive_before_type(p_tokenizer, directive)) { + process_ifdef(p_tokenizer); + } + } else if (directive == "ifndef") { + if (check_directive_before_type(p_tokenizer, directive)) { + process_ifndef(p_tokenizer); + } + } else if (directive == "else") { + if (check_directive_before_type(p_tokenizer, directive)) { + process_else(p_tokenizer); + } + } else if (directive == "endif") { + if (check_directive_before_type(p_tokenizer, directive)) { + process_endif(p_tokenizer); + } + } else if (directive == "define") { + if (check_directive_before_type(p_tokenizer, directive)) { + process_define(p_tokenizer); + } + } else if (directive == "undef") { + if (check_directive_before_type(p_tokenizer, directive)) { + process_undef(p_tokenizer); + } + } else if (directive == "include") { + if (check_directive_before_type(p_tokenizer, directive)) { + process_include(p_tokenizer); + } + } else if (directive == "pragma") { + if (check_directive_before_type(p_tokenizer, directive)) { + process_pragma(p_tokenizer); + } + } else { + set_error(RTR("Unknown directive."), p_tokenizer->get_line()); + } +} + +void ShaderPreprocessor::process_define(Tokenizer *p_tokenizer) { + const int line = p_tokenizer->get_line(); + + String label = p_tokenizer->get_identifier(); + if (label.is_empty()) { + set_error(RTR("Invalid macro name."), line); + return; + } + + if (state->defines.has(label)) { + set_error(RTR("Macro redefinition."), line); + return; + } + + if (p_tokenizer->peek() == '(') { + // Macro has arguments. + p_tokenizer->get_token(); + + Vector<String> args; + while (true) { + String name = p_tokenizer->get_identifier(); + if (name.is_empty()) { + set_error(RTR("Invalid argument name."), line); + return; + } + args.push_back(name); + + p_tokenizer->skip_whitespace(); + char32_t next = p_tokenizer->get_token().text; + if (next == ')') { + break; + } else if (next != ',') { + set_error(RTR("Expected a comma in the macro argument list."), line); + return; + } + } + + Define *define = memnew(Define); + define->arguments = args; + define->body = tokens_to_string(p_tokenizer->advance('\n')).strip_edges(); + state->defines[label] = define; + } else { + // Simple substitution macro. + Define *define = memnew(Define); + define->body = tokens_to_string(p_tokenizer->advance('\n')).strip_edges(); + state->defines[label] = define; + } +} + +void ShaderPreprocessor::process_else(Tokenizer *p_tokenizer) { + if (state->skip_stack_else.is_empty()) { + set_error(RTR("Unmatched else."), p_tokenizer->get_line()); + return; + } + 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<SkippedCondition *> vec = state->skipped_conditions[state->current_include]; + int index = vec.size() - 1; + if (index >= 0) { + SkippedCondition *cond = vec[index]; + if (cond->end_line == -1) { + cond->end_line = p_tokenizer->get_line(); + } + } + + if (skip) { + Vector<String> ends; + ends.push_back("endif"); + next_directive(p_tokenizer, ends); + } +} + +void ShaderPreprocessor::process_endif(Tokenizer *p_tokenizer) { + state->condition_depth--; + if (state->condition_depth < 0) { + set_error(RTR("Unmatched endif."), p_tokenizer->get_line()); + return; + } + + Vector<SkippedCondition *> vec = state->skipped_conditions[state->current_include]; + int index = vec.size() - 1; + if (index >= 0) { + SkippedCondition *cond = vec[index]; + if (cond->end_line == -1) { + cond->end_line = p_tokenizer->get_line(); + } + } + + p_tokenizer->advance('\n'); +} + +void ShaderPreprocessor::process_if(Tokenizer *p_tokenizer) { + int line = p_tokenizer->get_line(); + + String body = tokens_to_string(p_tokenizer->advance('\n')).strip_edges(); + if (body.is_empty()) { + set_error(RTR("Missing condition."), line); + return; + } + + Error error = expand_macros(body, line, body); + if (error != OK) { + return; + } + + Expression expression; + Vector<String> names; + error = expression.parse(body, names); + if (error != OK) { + set_error(expression.get_error_text(), line); + return; + } + + Variant v = expression.execute(Array(), nullptr, false); + if (v.get_type() == Variant::NIL) { + set_error(RTR("Condition evaluation error."), line); + return; + } + + bool success = v.booleanize(); + start_branch_condition(p_tokenizer, success); +} + +void ShaderPreprocessor::process_ifdef(Tokenizer *p_tokenizer) { + const int line = p_tokenizer->get_line(); + + String label = p_tokenizer->get_identifier(); + if (label.is_empty()) { + set_error(RTR("Invalid macro name."), line); + return; + } + + p_tokenizer->skip_whitespace(); + if (!is_char_end(p_tokenizer->peek())) { + set_error(RTR("Invalid ifdef."), line); + return; + } + p_tokenizer->advance('\n'); + + bool success = state->defines.has(label); + start_branch_condition(p_tokenizer, success); +} + +void ShaderPreprocessor::process_ifndef(Tokenizer *p_tokenizer) { + const int line = p_tokenizer->get_line(); + + String label = p_tokenizer->get_identifier(); + if (label.is_empty()) { + set_error(RTR("Invalid macro name."), line); + return; + } + + p_tokenizer->skip_whitespace(); + if (!is_char_end(p_tokenizer->peek())) { + set_error(RTR("Invalid ifndef."), line); + return; + } + p_tokenizer->advance('\n'); + + bool success = !state->defines.has(label); + start_branch_condition(p_tokenizer, success); +} + +void ShaderPreprocessor::process_include(Tokenizer *p_tokenizer) { + const int line = p_tokenizer->get_line(); + + 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(); + + if (path.is_empty() || !is_char_end(p_tokenizer->peek())) { + set_error(RTR("Invalid path."), line); + return; + } + + Ref<Resource> res = ResourceLoader::load(path); + if (res.is_null()) { + set_error(RTR("Shader include load failed. Does the shader include exist? Is there a cyclic dependency?"), line); + return; + } + + Ref<ShaderInclude> shader_inc = res; + if (shader_inc.is_null()) { + set_error(RTR("Shader include resource type is wrong."), line); + return; + } + + String included = shader_inc->get_code(); + if (!included.is_empty()) { + uint64_t code_hash = included.hash64(); + if (state->cyclic_include_hashes.find(code_hash)) { + set_error(RTR("Cyclic include found."), line); + return; + } + } + + state->shader_includes.insert(shader_inc); + + const String real_path = shader_inc->get_path(); + if (state->includes.has(real_path)) { + // Already included, skip. + // This is a valid check because 2 separate include paths could use some + // of the same shared functions from a common shader include. + return; + } + + // Mark as included. + state->includes.insert(real_path); + + state->include_depth++; + if (state->include_depth > 25) { + set_error(RTR("Shader max include depth exceeded."), line); + return; + } + + String old_include = state->current_include; + state->current_include = real_path; + 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, 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; + } + + state->include_depth--; + state->condition_depth = prev_condition_depth; +} + +void ShaderPreprocessor::process_pragma(Tokenizer *p_tokenizer) { + const int line = p_tokenizer->get_line(); + + bool is_cursor; + const String label = p_tokenizer->get_identifier(&is_cursor); + if (is_cursor) { + state->completion_type = COMPLETION_TYPE_PRAGMA; + } + + if (label.is_empty()) { + set_error(RTR("Invalid pragma directive."), line); + return; + } + + // Rxplicitly handle pragma values here. + // If more pragma options are created, then refactor into a more defined structure. + if (label == "disable_preprocessor") { + state->disabled = true; + } else { + set_error(RTR("Invalid pragma directive."), line); + return; + } + + p_tokenizer->advance('\n'); +} + +void ShaderPreprocessor::process_undef(Tokenizer *p_tokenizer) { + const int line = p_tokenizer->get_line(); + const String label = p_tokenizer->get_identifier(); + if (label.is_empty() || !state->defines.has(label)) { + set_error(RTR("Invalid name."), line); + return; + } + + p_tokenizer->skip_whitespace(); + if (!is_char_end(p_tokenizer->peek())) { + set_error(RTR("Invalid undef."), line); + return; + } + + memdelete(state->defines[label]); + state->defines.erase(label); +} + +void ShaderPreprocessor::start_branch_condition(Tokenizer *p_tokenizer, bool p_success) { + state->condition_depth++; + + if (p_success) { + state->skip_stack_else.push_back(true); + } else { + SkippedCondition *cond = memnew(SkippedCondition()); + cond->start_line = p_tokenizer->get_line(); + state->skipped_conditions[state->current_include].push_back(cond); + + Vector<String> ends; + ends.push_back("else"); + ends.push_back("endif"); + if (next_directive(p_tokenizer, ends) == "else") { + state->skip_stack_else.push_back(false); + } else { + state->skip_stack_else.push_back(true); + } + } +} + +void ShaderPreprocessor::expand_output_macros(int p_start, int p_line_number) { + String line = vector_to_string(output, p_start, output.size()); + + Error error = expand_macros(line, p_line_number - 1, line); // We are already on next line, so -1. + if (error != OK) { + return; + } + + output.resize(p_start); + + add_to_output(line); +} + +Error ShaderPreprocessor::expand_macros(const String &p_string, int p_line, String &r_expanded) { + Vector<Pair<String, Define *>> active_defines; + active_defines.resize(state->defines.size()); + int index = 0; + for (const RBMap<String, Define *>::Element *E = state->defines.front(); E; E = E->next()) { + active_defines.set(index++, Pair<String, Define *>(E->key(), E->get())); + } + + return expand_macros(p_string, p_line, active_defines, r_expanded); +} + +Error ShaderPreprocessor::expand_macros(const String &p_string, int p_line, Vector<Pair<String, Define *>> p_defines, String &r_expanded) { + r_expanded = p_string; + // When expanding macros we must only evaluate them once. + // Later we continue expanding but with the already + // evaluated macros removed. + for (int i = 0; i < p_defines.size(); i++) { + Pair<String, Define *> define_pair = p_defines[i]; + + Error error = expand_macros_once(r_expanded, p_line, define_pair, r_expanded); + if (error != OK) { + return error; + } + + // Remove expanded macro and recursively replace remaining. + p_defines.remove_at(i); + return expand_macros(r_expanded, p_line, p_defines, r_expanded); + } + + return OK; +} + +Error ShaderPreprocessor::expand_macros_once(const String &p_line, int p_line_number, Pair<String, Define *> p_define_pair, String &r_expanded) { + String result = p_line; + + const String &key = p_define_pair.first; + const Define *define = p_define_pair.second; + + int index_start = 0; + int index = 0; + while (find_match(result, key, index, index_start)) { + String body = define->body; + if (define->arguments.size() > 0) { + // Complex macro with arguments. + int args_start = index + key.length(); + int args_end = p_line.find(")", args_start); + if (args_start == -1 || args_end == -1) { + set_error(RTR("Missing macro argument parenthesis."), p_line_number); + return FAILED; + } + + String values = result.substr(args_start + 1, args_end - (args_start + 1)); + Vector<String> args = values.split(","); + if (args.size() != define->arguments.size()) { + set_error(RTR("Invalid macro argument count."), p_line_number); + return FAILED; + } + + // Insert macro arguments into the body. + for (int i = 0; i < args.size(); i++) { + String arg_name = define->arguments[i]; + int arg_index_start = 0; + int arg_index = 0; + while (find_match(body, arg_name, arg_index, arg_index_start)) { + body = body.substr(0, arg_index) + args[i] + body.substr(arg_index + arg_name.length(), body.length() - (arg_index + arg_name.length())); + // Manually reset arg_index_start to where the arg value of the define finishes. + // This ensures we don't skip the other args of this macro in the string. + arg_index_start = arg_index + args[i].length() + 1; + } + } + + result = result.substr(0, index) + " " + body + " " + result.substr(args_end + 1, result.length()); + } else { + result = result.substr(0, index) + body + result.substr(index + key.length(), result.length() - (index + key.length())); + // Manually reset index_start to where the body value of the define finishes. + // This ensures we don't skip another instance of this macro in the string. + index_start = index + body.length() + 1; + break; + } + } + r_expanded = result; + return OK; +} + +bool ShaderPreprocessor::find_match(const String &p_string, const String &p_value, int &r_index, int &r_index_start) { + // Looks for value in string and then determines if the boundaries + // are non-word characters. This method semi-emulates \b in regex. + r_index = p_string.find(p_value, r_index_start); + while (r_index > -1) { + if (r_index > 0) { + if (is_char_word(p_string[r_index - 1])) { + r_index_start = r_index + 1; + r_index = p_string.find(p_value, r_index_start); + continue; + } + } + + if (r_index + p_value.length() < p_string.length()) { + if (is_char_word(p_string[r_index + p_value.length()])) { + r_index_start = r_index + p_value.length() + 1; + r_index = p_string.find(p_value, r_index_start); + continue; + } + } + + // Return and shift index start automatically for next call. + r_index_start = r_index + p_value.length() + 1; + return true; + } + + return false; +} + +String ShaderPreprocessor::next_directive(Tokenizer *p_tokenizer, const Vector<String> &p_directives) { + const int line = p_tokenizer->get_line(); + int nesting = 0; + + while (true) { + p_tokenizer->advance('#'); + + String id = p_tokenizer->peek_identifier(); + if (id.is_empty()) { + break; + } + + if (nesting == 0) { + for (int i = 0; i < p_directives.size(); i++) { + if (p_directives[i] == id) { + p_tokenizer->backtrack('#'); + return id; + } + } + } + + if (id == "ifdef" || id == "ifndef" || id == "if") { + nesting++; + } else if (id == "endif") { + nesting--; + } + } + + set_error(RTR("Can't find matching branch directive."), line); + return ""; +} + +void ShaderPreprocessor::add_to_output(const String &p_str) { + for (int i = 0; i < p_str.length(); i++) { + output.push_back(p_str[i]); + } +} + +void ShaderPreprocessor::set_error(const String &p_error, int p_line) { + if (state->error.is_empty()) { + state->error = p_error; + FilePosition fp; + fp.line = p_line + 1; + state->include_positions.push_back(fp); + } +} + +bool ShaderPreprocessor::check_directive_before_type(Tokenizer *p_tokenizer, const String &p_directive) { + if (p_tokenizer->get_index() < state->shader_type_pos) { + set_error(vformat(RTR("`#%s` may not be defined before `shader_type`."), p_directive), p_tokenizer->get_line()); + return false; + } + return true; +} + +ShaderPreprocessor::Define *ShaderPreprocessor::create_define(const String &p_body) { + ShaderPreprocessor::Define *define = memnew(Define); + define->body = p_body; + return define; +} + +void ShaderPreprocessor::clear() { + if (state_owner && state != nullptr) { + for (const RBMap<String, Define *>::Element *E = state->defines.front(); E; E = E->next()) { + memdelete(E->get()); + } + + for (const RBMap<String, Vector<SkippedCondition *>>::Element *E = state->skipped_conditions.front(); E; E = E->next()) { + for (SkippedCondition *condition : E->get()) { + memdelete(condition); + } + } + + memdelete(state); + } + state_owner = false; + state = nullptr; +} + +Error ShaderPreprocessor::preprocess(State *p_state, const String &p_code, String &r_result) { + clear(); + + output.clear(); + + state = p_state; + + CommentRemover remover(p_code); + String stripped = remover.strip(); + String error = remover.get_error(); + if (!error.is_empty()) { + set_error(error, remover.get_error_line()); + return FAILED; + } + + // Track code hashes to prevent cyclic include. + uint64_t code_hash = p_code.hash64(); + state->cyclic_include_hashes.push_back(code_hash); + + Tokenizer p_tokenizer(stripped); + int last_size = 0; + bool has_symbols_before_directive = false; + + while (true) { + const Token &t = p_tokenizer.get_token(); + + if (t.text == 0) { + break; + } + + if (state->disabled) { + // Preprocessor was disabled. + // Read the rest of the file into the output. + output.push_back(t.text); + continue; + } else { + // Add autogenerated tokens. + Vector<Token> generated; + p_tokenizer.get_and_clear_generated(&generated); + for (int i = 0; i < generated.size(); i++) { + output.push_back(generated[i].text); + } + } + + if (t.text == '#') { + if (has_symbols_before_directive) { + set_error(RTR("Invalid symbols placed before directive."), p_tokenizer.get_line()); + state->cyclic_include_hashes.erase(code_hash); // Remove this hash. + return FAILED; + } + process_directive(&p_tokenizer); + } else { + if (is_char_end(t.text)) { + expand_output_macros(last_size, p_tokenizer.get_line()); + last_size = output.size(); + has_symbols_before_directive = false; + } else if (!is_char_space(t.text)) { + has_symbols_before_directive = true; + } + output.push_back(t.text); + } + + if (!state->error.is_empty()) { + state->cyclic_include_hashes.erase(code_hash); // Remove this hash. + return FAILED; + } + } + state->cyclic_include_hashes.erase(code_hash); // Remove this hash. + + if (!state->disabled) { + if (state->condition_depth != 0) { + set_error(RTR("Unmatched conditional statement."), p_tokenizer.line); + return FAILED; + } + + expand_output_macros(last_size, p_tokenizer.get_line()); + } + + r_result = vector_to_string(output); + + return OK; +} + +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); + + 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) { + r_keywords->push_back("define"); + if (p_include_shader_keywords) { + r_keywords->push_back("else"); + } + r_keywords->push_back("endif"); + if (p_include_shader_keywords) { + r_keywords->push_back("if"); + } + r_keywords->push_back("ifdef"); + r_keywords->push_back("ifndef"); + r_keywords->push_back("include"); + r_keywords->push_back("pragma"); + r_keywords->push_back("undef"); +} + +void ShaderPreprocessor::get_pragma_list(List<String> *r_pragmas) { + r_pragmas->push_back("disable_preprocessor"); +} + +ShaderPreprocessor::ShaderPreprocessor() { +} + +ShaderPreprocessor::~ShaderPreprocessor() { + clear(); +} diff --git a/servers/rendering/shader_preprocessor.h b/servers/rendering/shader_preprocessor.h new file mode 100644 index 0000000000..9bac706049 --- /dev/null +++ b/servers/rendering/shader_preprocessor.h @@ -0,0 +1,202 @@ +/*************************************************************************/ +/* shader_preprocessor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_PREPROCESSOR_H +#define SHADER_PREPROCESSOR_H + +#include "core/string/ustring.h" +#include "core/templates/list.h" +#include "core/templates/local_vector.h" +#include "core/templates/rb_map.h" +#include "core/templates/rb_set.h" +#include "core/typedefs.h" + +#include "core/io/resource_loader.h" +#include "core/os/os.h" +#include "scene/resources/shader.h" +#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; + int line; + + Token(); + Token(char32_t p_text, int p_line); + }; + + // The real preprocessor that understands basic shader and preprocessor language syntax. + class Tokenizer { + public: + String code; + int line; + int index; + int size; + Vector<Token> generated; + + private: + void add_generated(const Token &p_t); + char32_t next(); + + public: + int get_line() const; + int get_index() const; + char32_t peek(); + + void get_and_clear_generated(Vector<Token> *r_out); + void backtrack(char32_t p_what); + LocalVector<Token> advance(char32_t p_what); + void skip_whitespace(); + String get_identifier(bool *r_is_cursor = nullptr, bool p_started = false); + String peek_identifier(); + Token get_token(); + + Tokenizer(const String &p_code); + }; + + class CommentRemover { + private: + LocalVector<char32_t> stripped; + String code; + int index; + int line; + int comment_line_open; + int comments_open; + int strings_open; + + public: + String get_error() const; + int get_error_line() const; + char32_t peek() const; + + bool advance(char32_t p_what); + String strip(); + + CommentRemover(const String &p_code); + }; + + struct Define { + Vector<String> arguments; + String body; + }; + + struct SkippedCondition { + int start_line = -1; + int end_line = -1; + }; + + struct State { + RBMap<String, Define *> defines; + Vector<bool> skip_stack_else; + int condition_depth = 0; + RBSet<String> includes; + List<uint64_t> cyclic_include_hashes; // Holds code hash of includes. + int include_depth = 0; + String current_include; + String current_shader_type; + int shader_type_pos = -1; + String error; + 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: + LocalVector<char32_t> output; + State *state = nullptr; + bool state_owner = false; + +private: + static bool is_char_word(char32_t p_char); + static bool is_char_space(char32_t p_char); + static bool is_char_end(char32_t p_char); + static String vector_to_string(const LocalVector<char32_t> &p_v, int p_start = 0, int p_end = -1); + static String tokens_to_string(const LocalVector<Token> &p_tokens); + + void process_directive(Tokenizer *p_tokenizer); + void process_define(Tokenizer *p_tokenizer); + void process_else(Tokenizer *p_tokenizer); + void process_endif(Tokenizer *p_tokenizer); + void process_if(Tokenizer *p_tokenizer); + void process_ifdef(Tokenizer *p_tokenizer); + void process_ifndef(Tokenizer *p_tokenizer); + void process_include(Tokenizer *p_tokenizer); + void process_pragma(Tokenizer *p_tokenizer); + void process_undef(Tokenizer *p_tokenizer); + + void start_branch_condition(Tokenizer *p_tokenizer, bool p_success); + + void expand_output_macros(int p_start, int p_line); + Error expand_macros(const String &p_string, int p_line, String &r_result); + Error expand_macros(const String &p_string, int p_line, Vector<Pair<String, Define *>> p_defines, String &r_result); + Error expand_macros_once(const String &p_line, int p_line_number, Pair<String, Define *> p_define_pair, String &r_expanded); + bool find_match(const String &p_string, const String &p_value, int &r_index, int &r_index_start); + + String next_directive(Tokenizer *p_tokenizer, const Vector<String> &p_directives); + void add_to_output(const String &p_str); + void set_error(const String &p_error, int p_line); + bool check_directive_before_type(Tokenizer *p_tokenizer, const String &p_directive); + + static Define *create_define(const String &p_body); + + void clear(); + + Error preprocess(State *p_state, const String &p_code, String &r_result); + +public: + typedef void (*IncludeCompletionFunction)(List<ScriptLanguage::CodeCompletionOption> *); + + 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); + static void get_pragma_list(List<String> *r_pragmas); + + ShaderPreprocessor(); + ~ShaderPreprocessor(); +}; + +#endif // SHADER_PREPROCESSOR_H diff --git a/servers/rendering/storage/material_storage.h b/servers/rendering/storage/material_storage.h index 00790106af..4189e792db 100644 --- a/servers/rendering/storage/material_storage.h +++ b/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; |