| 1 | /**************************************************************************/ |
| 2 | /* rendering_device_binds.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "rendering_device_binds.h" |
| 32 | |
| 33 | Error RDShaderFile::parse_versions_from_text(const String &p_text, const String p_defines, OpenIncludeFunction p_include_func, void *p_include_func_userdata) { |
| 34 | ERR_FAIL_NULL_V(RenderingDevice::get_singleton(), ERR_UNAVAILABLE); |
| 35 | |
| 36 | Vector<String> lines = p_text.split("\n" ); |
| 37 | |
| 38 | bool reading_versions = false; |
| 39 | bool stage_found[RD::SHADER_STAGE_MAX] = { false, false, false, false, false }; |
| 40 | RD::ShaderStage stage = RD::SHADER_STAGE_MAX; |
| 41 | static const char *stage_str[RD::SHADER_STAGE_MAX] = { |
| 42 | "vertex" , |
| 43 | "fragment" , |
| 44 | "tesselation_control" , |
| 45 | "tesselation_evaluation" , |
| 46 | "compute" , |
| 47 | }; |
| 48 | String stage_code[RD::SHADER_STAGE_MAX]; |
| 49 | int stages_found = 0; |
| 50 | HashMap<StringName, String> version_texts; |
| 51 | |
| 52 | versions.clear(); |
| 53 | base_error = "" ; |
| 54 | |
| 55 | for (int lidx = 0; lidx < lines.size(); lidx++) { |
| 56 | String line = lines[lidx]; |
| 57 | |
| 58 | { |
| 59 | String ls = line.strip_edges(); |
| 60 | if (ls.begins_with("#[" ) && ls.ends_with("]" )) { |
| 61 | String section = ls.substr(2, ls.length() - 3).strip_edges(); |
| 62 | if (section == "versions" ) { |
| 63 | if (stages_found) { |
| 64 | base_error = "Invalid shader file, #[versions] must be the first section found." ; |
| 65 | break; |
| 66 | } |
| 67 | reading_versions = true; |
| 68 | } else { |
| 69 | for (int i = 0; i < RD::SHADER_STAGE_MAX; i++) { |
| 70 | if (section == stage_str[i]) { |
| 71 | if (stage_found[i]) { |
| 72 | base_error = "Invalid shader file, stage appears twice: " + section; |
| 73 | break; |
| 74 | } |
| 75 | |
| 76 | stage_found[i] = true; |
| 77 | stages_found++; |
| 78 | |
| 79 | stage = RD::ShaderStage(i); |
| 80 | reading_versions = false; |
| 81 | break; |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | if (!base_error.is_empty()) { |
| 86 | break; |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | continue; |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | if (stage == RD::SHADER_STAGE_MAX && !line.strip_edges().is_empty()) { |
| 95 | line = line.strip_edges(); |
| 96 | if (line.begins_with("//" ) || line.begins_with("/*" )) { |
| 97 | continue; //assuming comment (single line) |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | if (reading_versions) { |
| 102 | String l = line.strip_edges(); |
| 103 | if (!l.is_empty()) { |
| 104 | if (!l.contains("=" )) { |
| 105 | base_error = "Missing `=` in '" + l + "'. Version syntax is `version = \"<defines with C escaping>\";`." ; |
| 106 | break; |
| 107 | } |
| 108 | if (!l.contains(";" )) { |
| 109 | // We don't require a semicolon per se, but it's needed for clang-format to handle things properly. |
| 110 | base_error = "Missing `;` in '" + l + "'. Version syntax is `version = \"<defines with C escaping>\";`." ; |
| 111 | break; |
| 112 | } |
| 113 | Vector<String> slices = l.get_slice(";" , 0).split("=" ); |
| 114 | String version = slices[0].strip_edges(); |
| 115 | if (!version.is_valid_identifier()) { |
| 116 | base_error = "Version names must be valid identifiers, found '" + version + "' instead." ; |
| 117 | break; |
| 118 | } |
| 119 | String define = slices[1].strip_edges(); |
| 120 | if (!define.begins_with("\"" ) || !define.ends_with("\"" )) { |
| 121 | base_error = "Version text must be quoted using \"\", instead found '" + define + "'." ; |
| 122 | break; |
| 123 | } |
| 124 | define = "\n" + define.substr(1, define.length() - 2).c_unescape() + "\n" ; // Add newline before and after just in case. |
| 125 | |
| 126 | version_texts[version] = define + "\n" + p_defines; |
| 127 | } |
| 128 | } else { |
| 129 | if (stage == RD::SHADER_STAGE_MAX && !line.strip_edges().is_empty()) { |
| 130 | base_error = "Text was found that does not belong to a valid section: " + line; |
| 131 | break; |
| 132 | } |
| 133 | |
| 134 | if (stage != RD::SHADER_STAGE_MAX) { |
| 135 | if (line.strip_edges().begins_with("#include" )) { |
| 136 | if (p_include_func) { |
| 137 | //process include |
| 138 | String include = line.replace("#include" , "" ).strip_edges(); |
| 139 | if (!include.begins_with("\"" ) || !include.ends_with("\"" )) { |
| 140 | base_error = "Malformed #include syntax, expected #include \"<path>\", found instead: " + include; |
| 141 | break; |
| 142 | } |
| 143 | include = include.substr(1, include.length() - 2).strip_edges(); |
| 144 | String include_text = p_include_func(include, p_include_func_userdata); |
| 145 | if (!include_text.is_empty()) { |
| 146 | stage_code[stage] += "\n" + include_text + "\n" ; |
| 147 | } else { |
| 148 | base_error = "#include failed for file '" + include + "'" ; |
| 149 | } |
| 150 | } else { |
| 151 | base_error = "#include used, but no include function provided." ; |
| 152 | } |
| 153 | } else { |
| 154 | stage_code[stage] += line + "\n" ; |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | Ref<RDShaderFile> shader_file; |
| 161 | shader_file.instantiate(); |
| 162 | |
| 163 | if (base_error.is_empty()) { |
| 164 | if (stage_found[RD::SHADER_STAGE_COMPUTE] && stages_found > 1) { |
| 165 | ERR_FAIL_V_MSG(ERR_PARSE_ERROR, "When writing compute shaders, [compute] mustbe the only stage present." ); |
| 166 | } |
| 167 | |
| 168 | if (version_texts.is_empty()) { |
| 169 | version_texts["" ] = "" ; //make sure a default version exists |
| 170 | } |
| 171 | |
| 172 | bool errors_found = false; |
| 173 | |
| 174 | /* STEP 2, Compile the versions, add to shader file */ |
| 175 | |
| 176 | for (const KeyValue<StringName, String> &E : version_texts) { |
| 177 | Ref<RDShaderSPIRV> bytecode; |
| 178 | bytecode.instantiate(); |
| 179 | |
| 180 | for (int i = 0; i < RD::SHADER_STAGE_MAX; i++) { |
| 181 | String code = stage_code[i]; |
| 182 | if (code.is_empty()) { |
| 183 | continue; |
| 184 | } |
| 185 | code = code.replace("VERSION_DEFINES" , E.value); |
| 186 | String error; |
| 187 | Vector<uint8_t> spirv = RenderingDevice::get_singleton()->shader_compile_spirv_from_source(RD::ShaderStage(i), code, RD::SHADER_LANGUAGE_GLSL, &error, false); |
| 188 | bytecode->set_stage_bytecode(RD::ShaderStage(i), spirv); |
| 189 | if (!error.is_empty()) { |
| 190 | error += String() + "\n\nStage '" + stage_str[i] + "' source code: \n\n" ; |
| 191 | Vector<String> sclines = code.split("\n" ); |
| 192 | for (int j = 0; j < sclines.size(); j++) { |
| 193 | error += itos(j + 1) + "\t\t" + sclines[j] + "\n" ; |
| 194 | } |
| 195 | errors_found = true; |
| 196 | } |
| 197 | bytecode->set_stage_compile_error(RD::ShaderStage(i), error); |
| 198 | } |
| 199 | |
| 200 | set_bytecode(bytecode, E.key); |
| 201 | } |
| 202 | |
| 203 | return errors_found ? ERR_PARSE_ERROR : OK; |
| 204 | } else { |
| 205 | return ERR_PARSE_ERROR; |
| 206 | } |
| 207 | } |
| 208 | |