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 <iomanip>
19 #include <sstream>
20 #include <thread>
21 #include <tuple>
22 
23 #include "SPIRV/GlslangToSpv.h"
24 #include "libshaderc_util/format.h"
25 #include "libshaderc_util/io_shaderc.h"
26 #include "libshaderc_util/message.h"
27 #include "libshaderc_util/resources.h"
28 #include "libshaderc_util/shader_stage.h"
29 #include "libshaderc_util/spirv_tools_wrapper.h"
30 #include "libshaderc_util/string_piece.h"
31 #include "libshaderc_util/version_profile.h"
32 
33 namespace {
34 using shaderc_util::string_piece;
35 
36 // For use with glslang parsing calls.
37 const bool kNotForwardCompatible = false;
38 
39 // Returns true if #line directive sets the line number for the next line in the
40 // given version and profile.
LineDirectiveIsForNextLine(int version,EProfile profile)41 inline bool LineDirectiveIsForNextLine(int version, EProfile profile) {
42   return profile == EEsProfile || version >= 330;
43 }
44 
45 // Returns a #line directive whose arguments are line and filename.
GetLineDirective(int line,const string_piece & filename)46 inline std::string GetLineDirective(int line, const string_piece& filename) {
47   return "#line " + std::to_string(line) + " \"" + filename.str() + "\"\n";
48 }
49 
50 // Given a canonicalized #line directive (starting exactly with "#line", using
51 // single spaces to separate different components, and having an optional
52 // newline at the end), returns the line number and string name/number. If no
53 // string name/number is provided, the second element in the returned pair is an
54 // empty string_piece. Behavior is undefined if the directive parameter is not a
55 // canonicalized #line directive.
DecodeLineDirective(string_piece directive)56 std::pair<int, string_piece> DecodeLineDirective(string_piece directive) {
57   const string_piece kLineDirective = "#line ";
58   assert(directive.starts_with(kLineDirective));
59   directive = directive.substr(kLineDirective.size());
60 
61   const int line = std::atoi(directive.data());
62   const size_t space_loc = directive.find_first_of(' ');
63   if (space_loc == string_piece::npos) return std::make_pair(line, "");
64 
65   directive = directive.substr(space_loc);
66   directive = directive.strip("\" \n");
67   return std::make_pair(line, directive);
68 }
69 
70 // Returns the Glslang message rules for the given target environment,
71 // source language, and whether we want HLSL offset rules.  We assume
72 // only valid combinations are used.
GetMessageRules(shaderc_util::Compiler::TargetEnv env,shaderc_util::Compiler::SourceLanguage lang,bool hlsl_offsets,bool debug_info)73 EShMessages GetMessageRules(shaderc_util::Compiler::TargetEnv env,
74                             shaderc_util::Compiler::SourceLanguage lang,
75                             bool hlsl_offsets, bool debug_info) {
76   using shaderc_util::Compiler;
77   EShMessages result = EShMsgCascadingErrors;
78   if (lang == Compiler::SourceLanguage::HLSL) {
79     result = static_cast<EShMessages>(result | EShMsgReadHlsl);
80   }
81   switch (env) {
82     case Compiler::TargetEnv::OpenGLCompat:
83       // The compiler will have already errored out before now.
84       // But we need to handle this enum.
85       break;
86     case Compiler::TargetEnv::OpenGL:
87       result = static_cast<EShMessages>(result | EShMsgSpvRules);
88       break;
89     case Compiler::TargetEnv::Vulkan:
90       result =
91           static_cast<EShMessages>(result | EShMsgSpvRules | EShMsgVulkanRules);
92       break;
93   }
94   if (hlsl_offsets) {
95     result = static_cast<EShMessages>(result | EShMsgHlslOffsets);
96   }
97   if (debug_info) {
98     result = static_cast<EShMessages>(result | EShMsgDebugInfo);
99   }
100   return result;
101 }
102 
103 }  // anonymous namespace
104 
105 namespace shaderc_util {
106 
107 unsigned int GlslangInitializer::initialize_count_ = 0;
108 std::mutex* GlslangInitializer::glslang_mutex_ = nullptr;
109 
GlslangInitializer()110 GlslangInitializer::GlslangInitializer() {
111   static std::mutex first_call_mutex;
112 
113   // If this is the first call, glslang_mutex_ needs to be created, but in
114   // thread safe manner.
115   {
116     const std::lock_guard<std::mutex> first_call_lock(first_call_mutex);
117     if (glslang_mutex_ == nullptr) {
118       glslang_mutex_ = new std::mutex();
119     }
120   }
121 
122   const std::lock_guard<std::mutex> glslang_lock(*glslang_mutex_);
123 
124   if (initialize_count_ == 0) {
125     glslang::InitializeProcess();
126   }
127 
128   initialize_count_++;
129 }
130 
~GlslangInitializer()131 GlslangInitializer::~GlslangInitializer() {
132   const std::lock_guard<std::mutex> glslang_lock(*glslang_mutex_);
133 
134   initialize_count_--;
135 
136   if (initialize_count_ == 0) {
137     glslang::FinalizeProcess();
138     // There is no delete for glslang_mutex_ here, because we cannot guarantee
139     // there isn't a caller waiting for glslang_mutex_ in GlslangInitializer().
140     //
141     // This means that this class does leak one std::mutex worth of memory after
142     // the final instance is destroyed, but this allows us to defer allocating
143     // and constructing until we are sure we need to.
144   }
145 }
146 
SetLimit(Compiler::Limit limit,int value)147 void Compiler::SetLimit(Compiler::Limit limit, int value) {
148   switch (limit) {
149 #define RESOURCE(NAME, FIELD, CNAME) \
150   case Limit::NAME:                  \
151     limits_.FIELD = value;           \
152     break;
153 #include "libshaderc_util/resources.inc"
154 #undef RESOURCE
155   }
156 }
157 
GetLimit(Compiler::Limit limit) const158 int Compiler::GetLimit(Compiler::Limit limit) const {
159   switch (limit) {
160 #define RESOURCE(NAME, FIELD, CNAME) \
161   case Limit::NAME:                  \
162     return limits_.FIELD;
163 #include "libshaderc_util/resources.inc"
164 #undef RESOURCE
165   }
166   return 0;  // Unreachable
167 }
168 
Compile(const string_piece & input_source_string,EShLanguage forced_shader_stage,const std::string & error_tag,const char * entry_point_name,const std::function<EShLanguage (std::ostream * error_stream,const string_piece & error_tag)> & stage_callback,CountingIncluder & includer,OutputType output_type,std::ostream * error_stream,size_t * total_warnings,size_t * total_errors) const169 std::tuple<bool, std::vector<uint32_t>, size_t> Compiler::Compile(
170     const string_piece& input_source_string, EShLanguage forced_shader_stage,
171     const std::string& error_tag, const char* entry_point_name,
172     const std::function<EShLanguage(std::ostream* error_stream,
173                                     const string_piece& error_tag)>&
174         stage_callback,
175     CountingIncluder& includer, OutputType output_type,
176     std::ostream* error_stream, size_t* total_warnings, size_t* total_errors) const {
177   // Compilation results to be returned:
178   // Initialize the result tuple as a failed compilation. In error cases, we
179   // should return result_tuple directly without setting its members.
180   auto result_tuple =
181       std::make_tuple(false, std::vector<uint32_t>(), (size_t)0u);
182   // Get the reference of the members of the result tuple. We should set their
183   // values for succeeded compilation before returning the result tuple.
184   bool& succeeded = std::get<0>(result_tuple);
185   std::vector<uint32_t>& compilation_output_data = std::get<1>(result_tuple);
186   size_t& compilation_output_data_size_in_bytes = std::get<2>(result_tuple);
187 
188   // Check target environment.
189   const auto target_client_info = GetGlslangClientInfo(
190       error_tag, target_env_, target_env_version_,
191       target_spirv_version_, target_spirv_version_is_forced_);
192   if (!target_client_info.error.empty()) {
193     *error_stream << target_client_info.error;
194     *total_warnings = 0;
195     *total_errors = 1;
196     return result_tuple;
197   }
198 
199   EShLanguage used_shader_stage = forced_shader_stage;
200   const std::string macro_definitions =
201       shaderc_util::format(predefined_macros_, "#define ", " ", "\n");
202   const std::string pound_extension =
203       "#extension GL_GOOGLE_include_directive : enable\n";
204   const std::string preamble = macro_definitions + pound_extension;
205 
206   std::string preprocessed_shader;
207 
208   // If only preprocessing, we definitely need to preprocess. Otherwise, if
209   // we don't know the stage until now, we need the preprocessed shader to
210   // deduce the shader stage.
211   if (output_type == OutputType::PreprocessedText ||
212       used_shader_stage == EShLangCount) {
213     bool success;
214     std::string glslang_errors;
215     std::tie(success, preprocessed_shader, glslang_errors) =
216         PreprocessShader(error_tag, input_source_string, preamble, includer);
217 
218     success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_,
219                                    /* suppress_warnings = */ true,
220                                    glslang_errors.c_str(), total_warnings,
221                                    total_errors);
222     if (!success) return result_tuple;
223     // Because of the behavior change of the #line directive, the #line
224     // directive introducing each file's content must use the syntax for the
225     // specified version. So we need to probe this shader's version and
226     // profile.
227     int version;
228     EProfile profile;
229     std::tie(version, profile) = DeduceVersionProfile(preprocessed_shader);
230     const bool is_for_next_line = LineDirectiveIsForNextLine(version, profile);
231 
232     preprocessed_shader =
233         CleanupPreamble(preprocessed_shader, error_tag, pound_extension,
234                         includer.num_include_directives(), is_for_next_line);
235 
236     if (output_type == OutputType::PreprocessedText) {
237       // Set the values of the result tuple.
238       succeeded = true;
239       compilation_output_data = ConvertStringToVector(preprocessed_shader);
240       compilation_output_data_size_in_bytes = preprocessed_shader.size();
241       return result_tuple;
242     } else if (used_shader_stage == EShLangCount) {
243       std::string errors;
244       std::tie(used_shader_stage, errors) =
245           GetShaderStageFromSourceCode(error_tag, preprocessed_shader);
246       if (!errors.empty()) {
247         *error_stream << errors;
248         return result_tuple;
249       }
250       if (used_shader_stage == EShLangCount) {
251         if ((used_shader_stage = stage_callback(error_stream, error_tag)) ==
252             EShLangCount) {
253           return result_tuple;
254         }
255       }
256     }
257   }
258 
259   // Parsing requires its own Glslang symbol tables.
260   glslang::TShader shader(used_shader_stage);
261   const char* shader_strings = input_source_string.data();
262   const int shader_lengths = static_cast<int>(input_source_string.size());
263   const char* string_names = error_tag.c_str();
264   shader.setStringsWithLengthsAndNames(&shader_strings, &shader_lengths,
265                                        &string_names, 1);
266   shader.setPreamble(preamble.c_str());
267   shader.setEntryPoint(entry_point_name);
268   shader.setAutoMapBindings(auto_bind_uniforms_);
269   if (auto_combined_image_sampler_) {
270     shader.setTextureSamplerTransformMode(EShTexSampTransUpgradeTextureRemoveSampler);
271   }
272   shader.setAutoMapLocations(auto_map_locations_);
273   const auto& bases = auto_binding_base_[static_cast<int>(used_shader_stage)];
274   shader.setShiftImageBinding(bases[static_cast<int>(UniformKind::Image)]);
275   shader.setShiftSamplerBinding(bases[static_cast<int>(UniformKind::Sampler)]);
276   shader.setShiftTextureBinding(bases[static_cast<int>(UniformKind::Texture)]);
277   shader.setShiftUboBinding(bases[static_cast<int>(UniformKind::Buffer)]);
278   shader.setShiftSsboBinding(
279       bases[static_cast<int>(UniformKind::StorageBuffer)]);
280   shader.setShiftUavBinding(
281       bases[static_cast<int>(UniformKind::UnorderedAccessView)]);
282   shader.setHlslIoMapping(hlsl_iomap_);
283   shader.setResourceSetBinding(
284       hlsl_explicit_bindings_[static_cast<int>(used_shader_stage)]);
285   shader.setEnvClient(target_client_info.client,
286                       target_client_info.client_version);
287   shader.setEnvTarget(target_client_info.target_language,
288                       target_client_info.target_language_version);
289   if (hlsl_functionality1_enabled_) {
290     shader.setEnvTargetHlslFunctionality1();
291   }
292   shader.setInvertY(invert_y_enabled_);
293   shader.setNanMinMaxClamp(nan_clamp_);
294 
295   const EShMessages rules =
296       GetMessageRules(target_env_, source_language_, hlsl_offsets_,
297                       generate_debug_info_);
298 
299   bool success = shader.parse(&limits_, default_version_, default_profile_,
300                               force_version_profile_, kNotForwardCompatible,
301                               rules, includer);
302 
303   success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_,
304                                  suppress_warnings_, shader.getInfoLog(),
305                                  total_warnings, total_errors);
306   if (!success) return result_tuple;
307 
308   glslang::TProgram program;
309   program.addShader(&shader);
310   success = program.link(EShMsgDefault) && program.mapIO();
311   success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_,
312                                  suppress_warnings_, program.getInfoLog(),
313                                  total_warnings, total_errors);
314   if (!success) return result_tuple;
315 
316   // 'spirv' is an alias for the compilation_output_data. This alias is added
317   // to serve as an input for the call to DissassemblyBinary.
318   std::vector<uint32_t>& spirv = compilation_output_data;
319   glslang::SpvOptions options;
320   options.generateDebugInfo = generate_debug_info_;
321   options.disableOptimizer = true;
322   options.optimizeSize = false;
323   // Note the call to GlslangToSpv also populates compilation_output_data.
324   glslang::GlslangToSpv(*program.getIntermediate(used_shader_stage), spirv,
325                         &options);
326 
327   // Set the tool field (the top 16-bits) in the generator word to
328   // 'Shaderc over Glslang'.
329   const uint32_t shaderc_generator_word = 13;  // From SPIR-V XML Registry
330   const uint32_t generator_word_index = 2;     // SPIR-V 2.3: Physical layout
331   assert(spirv.size() > generator_word_index);
332   spirv[generator_word_index] =
333       (spirv[generator_word_index] & 0xffff) | (shaderc_generator_word << 16);
334 
335   std::vector<PassId> opt_passes;
336 
337   if (hlsl_legalization_enabled_ && source_language_ == SourceLanguage::HLSL) {
338     // If from HLSL, run this passes to "legalize" the SPIR-V for Vulkan
339     // eg. forward and remove memory writes of opaque types.
340     opt_passes.push_back(PassId::kLegalizationPasses);
341   }
342 
343   opt_passes.insert(opt_passes.end(), enabled_opt_passes_.begin(),
344                     enabled_opt_passes_.end());
345 
346   if (!opt_passes.empty()) {
347     std::string opt_errors;
348     if (!SpirvToolsOptimize(target_env_, target_env_version_,
349                             opt_passes, &spirv, &opt_errors)) {
350       *error_stream << "shaderc: internal error: compilation succeeded but "
351                        "failed to optimize: "
352                     << opt_errors << "\n";
353       return result_tuple;
354     }
355   }
356 
357   if (output_type == OutputType::SpirvAssemblyText) {
358     std::string text_or_error;
359     if (!SpirvToolsDisassemble(target_env_, target_env_version_, spirv,
360                                &text_or_error)) {
361       *error_stream << "shaderc: internal error: compilation succeeded but "
362                        "failed to disassemble: "
363                     << text_or_error << "\n";
364       return result_tuple;
365     }
366     succeeded = true;
367     compilation_output_data = ConvertStringToVector(text_or_error);
368     compilation_output_data_size_in_bytes = text_or_error.size();
369     return result_tuple;
370   } else {
371     succeeded = true;
372     // Note compilation_output_data is already populated in GlslangToSpv().
373     compilation_output_data_size_in_bytes = spirv.size() * sizeof(spirv[0]);
374     return result_tuple;
375   }
376 }
377 
AddMacroDefinition(const char * macro,size_t macro_length,const char * definition,size_t definition_length)378 void Compiler::AddMacroDefinition(const char* macro, size_t macro_length,
379                                   const char* definition,
380                                   size_t definition_length) {
381   predefined_macros_[std::string(macro, macro_length)] =
382       definition ? std::string(definition, definition_length) : "";
383 }
384 
SetTargetEnv(Compiler::TargetEnv env,Compiler::TargetEnvVersion version)385 void Compiler::SetTargetEnv(Compiler::TargetEnv env,
386                             Compiler::TargetEnvVersion version) {
387   target_env_ = env;
388   target_env_version_ = version;
389 }
390 
SetTargetSpirv(Compiler::SpirvVersion version)391 void Compiler::SetTargetSpirv(Compiler::SpirvVersion version) {
392   target_spirv_version_ = version;
393   target_spirv_version_is_forced_ = true;
394 }
395 
SetSourceLanguage(Compiler::SourceLanguage lang)396 void Compiler::SetSourceLanguage(Compiler::SourceLanguage lang) {
397   source_language_ = lang;
398 }
399 
SetForcedVersionProfile(int version,EProfile profile)400 void Compiler::SetForcedVersionProfile(int version, EProfile profile) {
401   default_version_ = version;
402   default_profile_ = profile;
403   force_version_profile_ = true;
404 }
405 
SetWarningsAsErrors()406 void Compiler::SetWarningsAsErrors() { warnings_as_errors_ = true; }
407 
SetGenerateDebugInfo()408 void Compiler::SetGenerateDebugInfo() {
409   generate_debug_info_ = true;
410   for (size_t i = 0; i < enabled_opt_passes_.size(); ++i) {
411     if (enabled_opt_passes_[i] == PassId::kStripDebugInfo) {
412       enabled_opt_passes_[i] = PassId::kNullPass;
413     }
414   }
415 }
416 
SetOptimizationLevel(Compiler::OptimizationLevel level)417 void Compiler::SetOptimizationLevel(Compiler::OptimizationLevel level) {
418   // Clear previous settings first.
419   enabled_opt_passes_.clear();
420 
421   switch (level) {
422     case OptimizationLevel::Size:
423       if (!generate_debug_info_) {
424         enabled_opt_passes_.push_back(PassId::kStripDebugInfo);
425       }
426       enabled_opt_passes_.push_back(PassId::kSizePasses);
427       break;
428     case OptimizationLevel::Performance:
429       if (!generate_debug_info_) {
430         enabled_opt_passes_.push_back(PassId::kStripDebugInfo);
431       }
432       enabled_opt_passes_.push_back(PassId::kPerformancePasses);
433       break;
434     default:
435       break;
436   }
437 }
438 
EnableHlslLegalization(bool hlsl_legalization_enabled)439 void Compiler::EnableHlslLegalization(bool hlsl_legalization_enabled) {
440   hlsl_legalization_enabled_ = hlsl_legalization_enabled;
441 }
442 
EnableHlslFunctionality1(bool enable)443 void Compiler::EnableHlslFunctionality1(bool enable) {
444   hlsl_functionality1_enabled_ = enable;
445 }
446 
EnableInvertY(bool enable)447 void Compiler::EnableInvertY(bool enable) { invert_y_enabled_ = enable; }
448 
SetNanClamp(bool enable)449 void Compiler::SetNanClamp(bool enable) { nan_clamp_ = enable; }
450 
SetSuppressWarnings()451 void Compiler::SetSuppressWarnings() { suppress_warnings_ = true; }
452 
PreprocessShader(const std::string & error_tag,const string_piece & shader_source,const string_piece & shader_preamble,CountingIncluder & includer) const453 std::tuple<bool, std::string, std::string> Compiler::PreprocessShader(
454     const std::string& error_tag, const string_piece& shader_source,
455     const string_piece& shader_preamble, CountingIncluder& includer) const {
456   // The stage does not matter for preprocessing.
457   glslang::TShader shader(EShLangVertex);
458   const char* shader_strings = shader_source.data();
459   const int shader_lengths = static_cast<int>(shader_source.size());
460   const char* string_names = error_tag.c_str();
461   shader.setStringsWithLengthsAndNames(&shader_strings, &shader_lengths,
462                                        &string_names, 1);
463   shader.setPreamble(shader_preamble.data());
464   auto target_client_info = GetGlslangClientInfo(
465       error_tag, target_env_, target_env_version_,
466       target_spirv_version_, target_spirv_version_is_forced_);
467   if (!target_client_info.error.empty()) {
468     return std::make_tuple(false, "", target_client_info.error);
469   }
470   shader.setEnvClient(target_client_info.client,
471                       target_client_info.client_version);
472   if (hlsl_functionality1_enabled_) {
473     shader.setEnvTargetHlslFunctionality1();
474   }
475   shader.setInvertY(invert_y_enabled_);
476   shader.setNanMinMaxClamp(nan_clamp_);
477 
478   // The preprocessor might be sensitive to the target environment.
479   // So combine the existing rules with the just-give-me-preprocessor-output
480   // flag.
481   const auto rules = static_cast<EShMessages>(
482       EShMsgOnlyPreprocessor |
483       GetMessageRules(target_env_, source_language_, hlsl_offsets_,
484                       false));
485 
486   std::string preprocessed_shader;
487   const bool success = shader.preprocess(
488       &limits_, default_version_, default_profile_, force_version_profile_,
489       kNotForwardCompatible, rules, &preprocessed_shader, includer);
490 
491   if (success) {
492     return std::make_tuple(true, preprocessed_shader, shader.getInfoLog());
493   }
494   return std::make_tuple(false, "", shader.getInfoLog());
495 }
496 
CleanupPreamble(const string_piece & preprocessed_shader,const string_piece & error_tag,const string_piece & pound_extension,int num_include_directives,bool is_for_next_line) const497 std::string Compiler::CleanupPreamble(const string_piece& preprocessed_shader,
498                                       const string_piece& error_tag,
499                                       const string_piece& pound_extension,
500                                       int num_include_directives,
501                                       bool is_for_next_line) const {
502   // Those #define directives in preamble will become empty lines after
503   // preprocessing. We also injected an #extension directive to turn on #include
504   // directive support. In the original preprocessing output from glslang, it
505   // appears before the user source string. We need to do proper adjustment:
506   // * Remove empty lines generated from #define directives in preamble.
507   // * If there is no #include directive in the source code, we do not need to
508   //   output the injected #extension directive. Otherwise,
509   // * If there exists a #version directive in the source code, it should be
510   //   placed at the first line. Its original line will be filled with an empty
511   //   line as placeholder to maintain the code structure.
512 
513   const std::vector<string_piece> lines =
514       preprocessed_shader.get_fields('\n', /* keep_delimiter = */ true);
515 
516   std::ostringstream output_stream;
517 
518   size_t pound_extension_index = lines.size();
519   size_t pound_version_index = lines.size();
520   for (size_t i = 0; i < lines.size(); ++i) {
521     if (lines[i] == pound_extension) {
522       pound_extension_index = std::min(i, pound_extension_index);
523     } else if (lines[i].starts_with("#version")) {
524       // In a preprocessed shader, directives are in a canonical format, so we
525       // can confidently compare to '#version' verbatim, without worrying about
526       // whitespace.
527       pound_version_index = i;
528       if (num_include_directives > 0) output_stream << lines[i];
529       break;
530     }
531   }
532   // We know that #extension directive exists and appears before #version
533   // directive (if any).
534   assert(pound_extension_index < lines.size());
535 
536   for (size_t i = 0; i < pound_extension_index; ++i) {
537     // All empty lines before the #line directive we injected are generated by
538     // preprocessing preamble. Do not output them.
539     if (lines[i].strip_whitespace().empty()) continue;
540     output_stream << lines[i];
541   }
542 
543   if (num_include_directives > 0) {
544     output_stream << pound_extension;
545     // Also output a #line directive for the main file.
546     output_stream << GetLineDirective(is_for_next_line, error_tag);
547   }
548 
549   for (size_t i = pound_extension_index + 1; i < lines.size(); ++i) {
550     if (i == pound_version_index) {
551       if (num_include_directives > 0) {
552         output_stream << "\n";
553       } else {
554         output_stream << lines[i];
555       }
556     } else {
557       output_stream << lines[i];
558     }
559   }
560 
561   return output_stream.str();
562 }
563 
GetShaderStageFromSourceCode(string_piece filename,const std::string & preprocessed_shader) const564 std::pair<EShLanguage, std::string> Compiler::GetShaderStageFromSourceCode(
565     string_piece filename, const std::string& preprocessed_shader) const {
566   const string_piece kPragmaShaderStageDirective = "#pragma shader_stage";
567   const string_piece kLineDirective = "#line";
568 
569   int version;
570   EProfile profile;
571   std::tie(version, profile) = DeduceVersionProfile(preprocessed_shader);
572   const bool is_for_next_line = LineDirectiveIsForNextLine(version, profile);
573 
574   std::vector<string_piece> lines =
575       string_piece(preprocessed_shader).get_fields('\n');
576   // The filename, logical line number (which starts from 1 and is sensitive to
577   // #line directives), and stage value for #pragma shader_stage() directives.
578   std::vector<std::tuple<string_piece, size_t, string_piece>> stages;
579   // The physical line numbers of the first #pragma shader_stage() line and
580   // first non-preprocessing line in the preprocessed shader text.
581   size_t first_pragma_physical_line = lines.size() + 1;
582   size_t first_non_pp_line = lines.size() + 1;
583 
584   for (size_t i = 0, logical_line_no = 1; i < lines.size(); ++i) {
585     const string_piece current_line = lines[i].strip_whitespace();
586     if (current_line.starts_with(kPragmaShaderStageDirective)) {
587       const string_piece stage_value =
588           current_line.substr(kPragmaShaderStageDirective.size()).strip("()");
589       stages.emplace_back(filename, logical_line_no, stage_value);
590       first_pragma_physical_line = std::min(first_pragma_physical_line, i + 1);
591     } else if (!current_line.empty() && !current_line.starts_with("#")) {
592       first_non_pp_line = std::min(first_non_pp_line, i + 1);
593     }
594 
595     // Update logical line number for the next line.
596     if (current_line.starts_with(kLineDirective)) {
597       string_piece name;
598       std::tie(logical_line_no, name) = DecodeLineDirective(current_line);
599       if (!name.empty()) filename = name;
600       // Note that for core profile, the meaning of #line changed since version
601       // 330. The line number given by #line used to mean the logical line
602       // number of the #line line. Now it means the logical line number of the
603       // next line after the #line line.
604       if (!is_for_next_line) ++logical_line_no;
605     } else {
606       ++logical_line_no;
607     }
608   }
609   if (stages.empty()) return std::make_pair(EShLangCount, "");
610 
611   std::string error_message;
612 
613   const string_piece& first_pragma_filename = std::get<0>(stages[0]);
614   const std::string first_pragma_line = std::to_string(std::get<1>(stages[0]));
615   const string_piece& first_pragma_stage = std::get<2>(stages[0]);
616 
617   if (first_pragma_physical_line > first_non_pp_line) {
618     error_message += first_pragma_filename.str() + ":" + first_pragma_line +
619                      ": error: '#pragma': the first 'shader_stage' #pragma "
620                      "must appear before any non-preprocessing code\n";
621   }
622 
623   EShLanguage stage = MapStageNameToLanguage(first_pragma_stage);
624   if (stage == EShLangCount) {
625     error_message +=
626         first_pragma_filename.str() + ":" + first_pragma_line +
627         ": error: '#pragma': invalid stage for 'shader_stage' #pragma: '" +
628         first_pragma_stage.str() + "'\n";
629   }
630 
631   for (size_t i = 1; i < stages.size(); ++i) {
632     const string_piece& current_stage = std::get<2>(stages[i]);
633     if (current_stage != first_pragma_stage) {
634       const string_piece& current_filename = std::get<0>(stages[i]);
635       const std::string current_line = std::to_string(std::get<1>(stages[i]));
636       error_message += current_filename.str() + ":" + current_line +
637                        ": error: '#pragma': conflicting stages for "
638                        "'shader_stage' #pragma: '" +
639                        current_stage.str() + "' (was '" +
640                        first_pragma_stage.str() + "' at " +
641                        first_pragma_filename.str() + ":" + first_pragma_line +
642                        ")\n";
643     }
644   }
645 
646   return std::make_pair(error_message.empty() ? stage : EShLangCount,
647                         error_message);
648 }
649 
DeduceVersionProfile(const std::string & preprocessed_shader) const650 std::pair<int, EProfile> Compiler::DeduceVersionProfile(
651     const std::string& preprocessed_shader) const {
652   int version = default_version_;
653   EProfile profile = default_profile_;
654   if (!force_version_profile_) {
655     std::tie(version, profile) =
656         GetVersionProfileFromSourceCode(preprocessed_shader);
657     if (version == 0 && profile == ENoProfile) {
658       version = default_version_;
659       profile = default_profile_;
660     }
661   }
662   return std::make_pair(version, profile);
663 }
664 
GetVersionProfileFromSourceCode(const std::string & preprocessed_shader) const665 std::pair<int, EProfile> Compiler::GetVersionProfileFromSourceCode(
666     const std::string& preprocessed_shader) const {
667   string_piece pound_version = preprocessed_shader;
668   const size_t pound_version_loc = pound_version.find("#version");
669   if (pound_version_loc == string_piece::npos) {
670     return std::make_pair(0, ENoProfile);
671   }
672   pound_version =
673       pound_version.substr(pound_version_loc + std::strlen("#version"));
674   pound_version = pound_version.substr(0, pound_version.find_first_of("\n"));
675 
676   std::string version_profile;
677   for (const auto character : pound_version) {
678     if (character != ' ') version_profile += character;
679   }
680 
681   int version;
682   EProfile profile;
683   if (!ParseVersionProfile(version_profile, &version, &profile)) {
684     return std::make_pair(0, ENoProfile);
685   }
686   return std::make_pair(version, profile);
687 }
688 
689 // Converts a string to a vector of uint32_t by copying the content of a given
690 // string to a vector<uint32_t> and returns it. Appends '\0' at the end if extra
691 // bytes are required to complete the last element.
ConvertStringToVector(const std::string & str)692 std::vector<uint32_t> ConvertStringToVector(const std::string& str) {
693   size_t num_bytes_str = str.size() + 1u;
694   size_t vector_length =
695       (num_bytes_str + sizeof(uint32_t) - 1) / sizeof(uint32_t);
696   std::vector<uint32_t> result_vec(vector_length, 0);
697   std::strncpy(reinterpret_cast<char*>(result_vec.data()), str.c_str(),
698                str.size());
699   return result_vec;
700 }
701 
GetGlslangClientInfo(const std::string & error_tag,shaderc_util::Compiler::TargetEnv env,shaderc_util::Compiler::TargetEnvVersion env_version,shaderc_util::Compiler::SpirvVersion spv_version,bool spv_version_is_forced)702 GlslangClientInfo GetGlslangClientInfo(
703     const std::string& error_tag, shaderc_util::Compiler::TargetEnv env,
704     shaderc_util::Compiler::TargetEnvVersion env_version,
705     shaderc_util::Compiler::SpirvVersion spv_version,
706     bool spv_version_is_forced) {
707   GlslangClientInfo result;
708   std::ostringstream errs;
709 
710   using shaderc_util::Compiler;
711   switch (env) {
712     case Compiler::TargetEnv::Vulkan:
713       result.client = glslang::EShClientVulkan;
714       if (env_version == Compiler::TargetEnvVersion::Default ||
715           env_version == Compiler::TargetEnvVersion::Vulkan_1_0) {
716         result.client_version = glslang::EShTargetVulkan_1_0;
717       } else if (env_version == Compiler::TargetEnvVersion::Vulkan_1_1) {
718         result.client_version = glslang::EShTargetVulkan_1_1;
719         result.target_language_version = glslang::EShTargetSpv_1_3;
720       } else if (env_version == Compiler::TargetEnvVersion::Vulkan_1_2) {
721         result.client_version = glslang::EShTargetVulkan_1_2;
722         result.target_language_version = glslang::EShTargetSpv_1_5;
723       } else {
724         errs << "error:" << error_tag << ": Invalid target client version "
725              << static_cast<uint32_t>(env_version) << " for Vulkan environment "
726              << int(env);
727       }
728       break;
729     case Compiler::TargetEnv::OpenGLCompat:
730       errs << "error: OpenGL compatibility profile is not supported";
731       break;
732     case Compiler::TargetEnv::OpenGL:
733       result.client = glslang::EShClientOpenGL;
734       if (env_version == Compiler::TargetEnvVersion::Default ||
735           env_version == Compiler::TargetEnvVersion::OpenGL_4_5) {
736         result.client_version = glslang::EShTargetOpenGL_450;
737       } else {
738         errs << "error:" << error_tag << ": Invalid target client version "
739              << static_cast<uint32_t>(env_version) << " for OpenGL environment "
740              << int(env);
741       }
742       break;
743     default:
744       errs << "error:" << error_tag << ": Invalid target client environment "
745            << int(env);
746       break;
747   }
748 
749   if (spv_version_is_forced && errs.str().empty()) {
750     switch (spv_version) {
751       case Compiler::SpirvVersion::v1_0:
752         result.target_language_version = glslang::EShTargetSpv_1_0;
753         break;
754       case Compiler::SpirvVersion::v1_1:
755         result.target_language_version = glslang::EShTargetSpv_1_1;
756         break;
757       case Compiler::SpirvVersion::v1_2:
758         result.target_language_version = glslang::EShTargetSpv_1_2;
759         break;
760       case Compiler::SpirvVersion::v1_3:
761         result.target_language_version = glslang::EShTargetSpv_1_3;
762         break;
763       case Compiler::SpirvVersion::v1_4:
764         result.target_language_version = glslang::EShTargetSpv_1_4;
765         break;
766       case Compiler::SpirvVersion::v1_5:
767         result.target_language_version = glslang::EShTargetSpv_1_5;
768         break;
769       default:
770         errs << "error:" << error_tag << ": Unknown SPIR-V version " << std::hex
771              << uint32_t(spv_version);
772         break;
773     }
774   }
775   result.error = errs.str();
776   return result;
777 }
778 
779 }  // namespace shaderc_util
780