1 // Copyright 2015 The Shaderc Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "libshaderc_util/compiler.h" 16 17 #include <cstdint> 18 #include <tuple> 19 20 #include "libshaderc_util/format.h" 21 #include "libshaderc_util/io.h" 22 #include "libshaderc_util/message.h" 23 #include "libshaderc_util/resources.h" 24 #include "libshaderc_util/shader_stage.h" 25 #include "libshaderc_util/spirv_tools_wrapper.h" 26 #include "libshaderc_util/string_piece.h" 27 #include "libshaderc_util/version_profile.h" 28 29 #include "SPIRV/GlslangToSpv.h" 30 31 namespace { 32 using shaderc_util::string_piece; 33 34 // For use with glslang parsing calls. 35 const bool kNotForwardCompatible = false; 36 37 // Returns true if #line directive sets the line number for the next line in the 38 // given version and profile. 39 inline bool LineDirectiveIsForNextLine(int version, EProfile profile) { 40 return profile == EEsProfile || version >= 330; 41 } 42 43 // Returns a #line directive whose arguments are line and filename. 44 inline std::string GetLineDirective(int line, const string_piece& filename) { 45 return "#line " + std::to_string(line) + " \"" + filename.str() + "\"\n"; 46 } 47 48 // Given a canonicalized #line directive (starting exactly with "#line", using 49 // single spaces to separate different components, and having an optional 50 // newline at the end), returns the line number and string name/number. If no 51 // string name/number is provided, the second element in the returned pair is an 52 // empty string_piece. Behavior is undefined if the directive parameter is not a 53 // canonicalized #line directive. 54 std::pair<int, string_piece> DecodeLineDirective(string_piece directive) { 55 const string_piece kLineDirective = "#line "; 56 assert(directive.starts_with(kLineDirective)); 57 directive = directive.substr(kLineDirective.size()); 58 59 const int line = std::atoi(directive.data()); 60 const size_t space_loc = directive.find_first_of(' '); 61 if (space_loc == string_piece::npos) return std::make_pair(line, ""); 62 63 directive = directive.substr(space_loc); 64 directive = directive.strip("\" \n"); 65 return std::make_pair(line, directive); 66 } 67 } // anonymous namespace 68 69 namespace shaderc_util { 70 std::tuple<bool, std::vector<uint32_t>, size_t> Compiler::Compile( 71 const string_piece& input_source_string, EShLanguage forced_shader_stage, 72 const std::string& error_tag, 73 const std::function<EShLanguage(std::ostream* error_stream, 74 const string_piece& error_tag)>& 75 stage_callback, 76 CountingIncluder& includer, OutputType output_type, 77 std::ostream* error_stream, size_t* total_warnings, size_t* total_errors, 78 GlslInitializer* initializer) const { 79 // Compilation results to be returned: 80 // Initialize the result tuple as a failed compilation. In error cases, we 81 // should return result_tuple directly without setting its members. 82 auto result_tuple = 83 std::make_tuple(false, std::vector<uint32_t>(), (size_t)0u); 84 // Get the reference of the members of the result tuple. We should set their 85 // values for succeeded compilation before returning the result tuple. 86 bool& succeeded = std::get<0>(result_tuple); 87 std::vector<uint32_t>& compilation_output_data = std::get<1>(result_tuple); 88 size_t& compilation_output_data_size_in_bytes = std::get<2>(result_tuple); 89 90 auto token = initializer->Acquire(); 91 EShLanguage used_shader_stage = forced_shader_stage; 92 const std::string macro_definitions = 93 shaderc_util::format(predefined_macros_, "#define ", " ", "\n"); 94 const std::string pound_extension = 95 "#extension GL_GOOGLE_include_directive : enable\n"; 96 const std::string preamble = macro_definitions + pound_extension; 97 98 std::string preprocessed_shader; 99 100 // If only preprocessing, we definitely need to preprocess. Otherwise, if 101 // we don't know the stage until now, we need the preprocessed shader to 102 // deduce the shader stage. 103 if (output_type == OutputType::PreprocessedText || 104 used_shader_stage == EShLangCount) { 105 bool success; 106 std::string glslang_errors; 107 std::tie(success, preprocessed_shader, glslang_errors) = 108 PreprocessShader(error_tag, input_source_string, preamble, includer); 109 110 success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_, 111 /* suppress_warnings = */ true, 112 glslang_errors.c_str(), total_warnings, 113 total_errors); 114 if (!success) return result_tuple; 115 // Because of the behavior change of the #line directive, the #line 116 // directive introducing each file's content must use the syntax for the 117 // specified version. So we need to probe this shader's version and 118 // profile. 119 int version; 120 EProfile profile; 121 std::tie(version, profile) = DeduceVersionProfile(preprocessed_shader); 122 const bool is_for_next_line = LineDirectiveIsForNextLine(version, profile); 123 124 preprocessed_shader = 125 CleanupPreamble(preprocessed_shader, error_tag, pound_extension, 126 includer.num_include_directives(), is_for_next_line); 127 128 if (output_type == OutputType::PreprocessedText) { 129 // Set the values of the result tuple. 130 succeeded = true; 131 compilation_output_data = ConvertStringToVector(preprocessed_shader); 132 compilation_output_data_size_in_bytes = preprocessed_shader.size(); 133 return result_tuple; 134 } else if (used_shader_stage == EShLangCount) { 135 std::string errors; 136 std::tie(used_shader_stage, errors) = 137 GetShaderStageFromSourceCode(error_tag, preprocessed_shader); 138 if (!errors.empty()) { 139 *error_stream << errors; 140 return result_tuple; 141 } 142 if (used_shader_stage == EShLangCount) { 143 if ((used_shader_stage = stage_callback(error_stream, error_tag)) == 144 EShLangCount) { 145 return result_tuple; 146 } 147 } 148 } 149 } 150 151 // Parsing requires its own Glslang symbol tables. 152 glslang::TShader shader(used_shader_stage); 153 const char* shader_strings = input_source_string.data(); 154 const int shader_lengths = static_cast<int>(input_source_string.size()); 155 const char* string_names = error_tag.c_str(); 156 shader.setStringsWithLengthsAndNames(&shader_strings, &shader_lengths, 157 &string_names, 1); 158 shader.setPreamble(preamble.c_str()); 159 160 // TODO(dneto): Generate source-level debug info if requested. 161 bool success = 162 shader.parse(&shaderc_util::kDefaultTBuiltInResource, default_version_, 163 default_profile_, force_version_profile_, 164 kNotForwardCompatible, message_rules_, includer); 165 166 success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_, 167 suppress_warnings_, shader.getInfoLog(), 168 total_warnings, total_errors); 169 if (!success) return result_tuple; 170 171 glslang::TProgram program; 172 program.addShader(&shader); 173 success = program.link(EShMsgDefault); 174 success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_, 175 suppress_warnings_, program.getInfoLog(), 176 total_warnings, total_errors); 177 if (!success) return result_tuple; 178 179 // 'spirv' is an alias for the compilation_output_data. This alias is added 180 // to 181 // serve as an input for the call to DissassemblyBinary. 182 std::vector<uint32_t>& spirv = compilation_output_data; 183 // Note the call to GlslangToSpv also populates compilation_output_data. 184 glslang::GlslangToSpv(*program.getIntermediate(used_shader_stage), spirv); 185 if (output_type == OutputType::SpirvAssemblyText) { 186 std::string text_or_error; 187 if (!SpirvToolsDisassemble(spirv, &text_or_error)) { 188 *error_stream << "shaderc: internal error: compilation succeeded but " 189 "failed to disassemble: " 190 << text_or_error << "\n"; 191 return result_tuple; 192 } 193 succeeded = true; 194 compilation_output_data = ConvertStringToVector(text_or_error); 195 compilation_output_data_size_in_bytes = text_or_error.size(); 196 return result_tuple; 197 } else { 198 succeeded = true; 199 // Note compilation_output_data is already populated in GlslangToSpv(). 200 compilation_output_data_size_in_bytes = spirv.size() * sizeof(spirv[0]); 201 return result_tuple; 202 } 203 } 204 205 void Compiler::AddMacroDefinition(const char* macro, size_t macro_length, 206 const char* definition, 207 size_t definition_length) { 208 predefined_macros_[std::string(macro, macro_length)] = 209 definition ? std::string(definition, definition_length) : ""; 210 } 211 212 void Compiler::SetMessageRules(EShMessages rules) { message_rules_ = rules; } 213 214 EShMessages Compiler::GetMessageRules() const { return message_rules_; } 215 216 void Compiler::SetForcedVersionProfile(int version, EProfile profile) { 217 default_version_ = version; 218 default_profile_ = profile; 219 force_version_profile_ = true; 220 } 221 222 void Compiler::SetWarningsAsErrors() { warnings_as_errors_ = true; } 223 224 void Compiler::SetGenerateDebugInfo() { generate_debug_info_ = true; } 225 226 void Compiler::SetSuppressWarnings() { suppress_warnings_ = true; } 227 228 std::tuple<bool, std::string, std::string> Compiler::PreprocessShader( 229 const std::string& error_tag, const string_piece& shader_source, 230 const string_piece& shader_preamble, CountingIncluder& includer) const { 231 // The stage does not matter for preprocessing. 232 glslang::TShader shader(EShLangVertex); 233 const char* shader_strings = shader_source.data(); 234 const int shader_lengths = static_cast<int>(shader_source.size()); 235 const char* string_names = error_tag.c_str(); 236 shader.setStringsWithLengthsAndNames(&shader_strings, &shader_lengths, 237 &string_names, 1); 238 shader.setPreamble(shader_preamble.data()); 239 240 // The preprocessor might be sensitive to the target environment. 241 // So combine the existing rules with the just-give-me-preprocessor-output 242 // flag. 243 const auto rules = 244 static_cast<EShMessages>(EShMsgOnlyPreprocessor | message_rules_); 245 246 std::string preprocessed_shader; 247 const bool success = shader.preprocess( 248 &shaderc_util::kDefaultTBuiltInResource, default_version_, 249 default_profile_, force_version_profile_, kNotForwardCompatible, rules, 250 &preprocessed_shader, includer); 251 252 if (success) { 253 return std::make_tuple(true, preprocessed_shader, shader.getInfoLog()); 254 } 255 return std::make_tuple(false, "", shader.getInfoLog()); 256 } 257 258 std::string Compiler::CleanupPreamble(const string_piece& preprocessed_shader, 259 const string_piece& error_tag, 260 const string_piece& pound_extension, 261 int num_include_directives, 262 bool is_for_next_line) const { 263 // Those #define directives in preamble will become empty lines after 264 // preprocessing. We also injected an #extension directive to turn on #include 265 // directive support. In the original preprocessing output from glslang, it 266 // appears before the user source string. We need to do proper adjustment: 267 // * Remove empty lines generated from #define directives in preamble. 268 // * If there is no #include directive in the source code, we do not need to 269 // output the injected #extension directive. Otherwise, 270 // * If there exists a #version directive in the source code, it should be 271 // placed at the first line. Its original line will be filled with an empty 272 // line as placeholder to maintain the code structure. 273 274 const std::vector<string_piece> lines = 275 preprocessed_shader.get_fields('\n', /* keep_delimiter = */ true); 276 277 std::ostringstream output_stream; 278 279 size_t pound_extension_index = lines.size(); 280 size_t pound_version_index = lines.size(); 281 for (size_t i = 0; i < lines.size(); ++i) { 282 if (lines[i] == pound_extension) { 283 pound_extension_index = std::min(i, pound_extension_index); 284 } else if (lines[i].starts_with("#version")) { 285 // In a preprocessed shader, directives are in a canonical format, so we 286 // can confidently compare to '#version' verbatim, without worrying about 287 // whitespace. 288 pound_version_index = i; 289 if (num_include_directives > 0) output_stream << lines[i]; 290 break; 291 } 292 } 293 // We know that #extension directive exists and appears before #version 294 // directive (if any). 295 assert(pound_extension_index < lines.size()); 296 297 for (size_t i = 0; i < pound_extension_index; ++i) { 298 // All empty lines before the #line directive we injected are generated by 299 // preprocessing preamble. Do not output them. 300 if (lines[i].strip_whitespace().empty()) continue; 301 output_stream << lines[i]; 302 } 303 304 if (num_include_directives > 0) { 305 output_stream << pound_extension; 306 // Also output a #line directive for the main file. 307 output_stream << GetLineDirective(is_for_next_line, error_tag); 308 } 309 310 for (size_t i = pound_extension_index + 1; i < lines.size(); ++i) { 311 if (i == pound_version_index) { 312 if (num_include_directives > 0) { 313 output_stream << "\n"; 314 } else { 315 output_stream << lines[i]; 316 } 317 } else { 318 output_stream << lines[i]; 319 } 320 } 321 322 return output_stream.str(); 323 } 324 325 std::pair<EShLanguage, std::string> Compiler::GetShaderStageFromSourceCode( 326 string_piece filename, const std::string& preprocessed_shader) const { 327 const string_piece kPragmaShaderStageDirective = "#pragma shader_stage"; 328 const string_piece kLineDirective = "#line"; 329 330 int version; 331 EProfile profile; 332 std::tie(version, profile) = DeduceVersionProfile(preprocessed_shader); 333 const bool is_for_next_line = LineDirectiveIsForNextLine(version, profile); 334 335 std::vector<string_piece> lines = 336 string_piece(preprocessed_shader).get_fields('\n'); 337 // The filename, logical line number (which starts from 1 and is sensitive to 338 // #line directives), and stage value for #pragma shader_stage() directives. 339 std::vector<std::tuple<string_piece, size_t, string_piece>> stages; 340 // The physical line numbers of the first #pragma shader_stage() line and 341 // first non-preprocessing line in the preprocessed shader text. 342 size_t first_pragma_physical_line = lines.size() + 1; 343 size_t first_non_pp_line = lines.size() + 1; 344 345 for (size_t i = 0, logical_line_no = 1; i < lines.size(); ++i) { 346 const string_piece current_line = lines[i].strip_whitespace(); 347 if (current_line.starts_with(kPragmaShaderStageDirective)) { 348 const string_piece stage_value = 349 current_line.substr(kPragmaShaderStageDirective.size()).strip("()"); 350 stages.emplace_back(filename, logical_line_no, stage_value); 351 first_pragma_physical_line = std::min(first_pragma_physical_line, i + 1); 352 } else if (!current_line.empty() && !current_line.starts_with("#")) { 353 first_non_pp_line = std::min(first_non_pp_line, i + 1); 354 } 355 356 // Update logical line number for the next line. 357 if (current_line.starts_with(kLineDirective)) { 358 string_piece name; 359 std::tie(logical_line_no, name) = DecodeLineDirective(current_line); 360 if (!name.empty()) filename = name; 361 // Note that for core profile, the meaning of #line changed since version 362 // 330. The line number given by #line used to mean the logical line 363 // number of the #line line. Now it means the logical line number of the 364 // next line after the #line line. 365 if (!is_for_next_line) ++logical_line_no; 366 } else { 367 ++logical_line_no; 368 } 369 } 370 if (stages.empty()) return std::make_pair(EShLangCount, ""); 371 372 std::string error_message; 373 374 const string_piece& first_pragma_filename = std::get<0>(stages[0]); 375 const std::string first_pragma_line = std::to_string(std::get<1>(stages[0])); 376 const string_piece& first_pragma_stage = std::get<2>(stages[0]); 377 378 if (first_pragma_physical_line > first_non_pp_line) { 379 error_message += first_pragma_filename.str() + ":" + first_pragma_line + 380 ": error: '#pragma': the first 'shader_stage' #pragma " 381 "must appear before any non-preprocessing code\n"; 382 } 383 384 EShLanguage stage = MapStageNameToLanguage(first_pragma_stage); 385 if (stage == EShLangCount) { 386 error_message += 387 first_pragma_filename.str() + ":" + first_pragma_line + 388 ": error: '#pragma': invalid stage for 'shader_stage' #pragma: '" + 389 first_pragma_stage.str() + "'\n"; 390 } 391 392 for (size_t i = 1; i < stages.size(); ++i) { 393 const string_piece& current_stage = std::get<2>(stages[i]); 394 if (current_stage != first_pragma_stage) { 395 const string_piece& current_filename = std::get<0>(stages[i]); 396 const std::string current_line = std::to_string(std::get<1>(stages[i])); 397 error_message += current_filename.str() + ":" + current_line + 398 ": error: '#pragma': conflicting stages for " 399 "'shader_stage' #pragma: '" + 400 current_stage.str() + "' (was '" + 401 first_pragma_stage.str() + "' at " + 402 first_pragma_filename.str() + ":" + first_pragma_line + 403 ")\n"; 404 } 405 } 406 407 return std::make_pair(error_message.empty() ? stage : EShLangCount, 408 error_message); 409 } 410 411 std::pair<int, EProfile> Compiler::DeduceVersionProfile( 412 const std::string& preprocessed_shader) const { 413 int version = default_version_; 414 EProfile profile = default_profile_; 415 if (!force_version_profile_) { 416 std::tie(version, profile) = 417 GetVersionProfileFromSourceCode(preprocessed_shader); 418 if (version == 0 && profile == ENoProfile) { 419 version = default_version_; 420 profile = default_profile_; 421 } 422 } 423 return std::make_pair(version, profile); 424 } 425 426 std::pair<int, EProfile> Compiler::GetVersionProfileFromSourceCode( 427 const std::string& preprocessed_shader) const { 428 string_piece pound_version = preprocessed_shader; 429 const size_t pound_version_loc = pound_version.find("#version"); 430 if (pound_version_loc == string_piece::npos) { 431 return std::make_pair(0, ENoProfile); 432 } 433 pound_version = 434 pound_version.substr(pound_version_loc + std::strlen("#version")); 435 pound_version = pound_version.substr(0, pound_version.find_first_of("\n")); 436 437 std::string version_profile; 438 for (const auto character : pound_version) { 439 if (character != ' ') version_profile += character; 440 } 441 442 int version; 443 EProfile profile; 444 if (!ParseVersionProfile(version_profile, &version, &profile)) { 445 return std::make_pair(0, ENoProfile); 446 } 447 return std::make_pair(version, profile); 448 } 449 450 // Converts a string to a vector of uint32_t by copying the content of a given 451 // string to a vector<uint32_t> and returns it. Appends '\0' at the end if extra 452 // bytes are required to complete the last element. 453 std::vector<uint32_t> ConvertStringToVector(const std::string& str) { 454 size_t num_bytes_str = str.size() + 1u; 455 size_t vector_length = 456 (num_bytes_str + sizeof(uint32_t) - 1) / sizeof(uint32_t); 457 std::vector<uint32_t> result_vec(vector_length, 0); 458 std::strncpy(reinterpret_cast<char*>(result_vec.data()), str.c_str(), 459 str.size()); 460 return result_vec; 461 } 462 463 } // namesapce shaderc_util 464