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 | |