// Copyright 2015 The Shaderc Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "libshaderc_util/compiler.h" #include #include #include #include #include #include "SPIRV/GlslangToSpv.h" #include "libshaderc_util/format.h" #include "libshaderc_util/io_shaderc.h" #include "libshaderc_util/message.h" #include "libshaderc_util/resources.h" #include "libshaderc_util/shader_stage.h" #include "libshaderc_util/spirv_tools_wrapper.h" #include "libshaderc_util/string_piece.h" #include "libshaderc_util/version_profile.h" namespace { using shaderc_util::string_piece; // For use with glslang parsing calls. const bool kNotForwardCompatible = false; // Returns true if #line directive sets the line number for the next line in the // given version and profile. inline bool LineDirectiveIsForNextLine(int version, EProfile profile) { return profile == EEsProfile || version >= 330; } // Returns a #line directive whose arguments are line and filename. inline std::string GetLineDirective(int line, const string_piece& filename) { return "#line " + std::to_string(line) + " \"" + filename.str() + "\"\n"; } // Given a canonicalized #line directive (starting exactly with "#line", using // single spaces to separate different components, and having an optional // newline at the end), returns the line number and string name/number. If no // string name/number is provided, the second element in the returned pair is an // empty string_piece. Behavior is undefined if the directive parameter is not a // canonicalized #line directive. std::pair DecodeLineDirective(string_piece directive) { const string_piece kLineDirective = "#line "; assert(directive.starts_with(kLineDirective)); directive = directive.substr(kLineDirective.size()); const int line = std::atoi(directive.data()); const size_t space_loc = directive.find_first_of(' '); if (space_loc == string_piece::npos) return std::make_pair(line, ""); directive = directive.substr(space_loc); directive = directive.strip("\" \n"); return std::make_pair(line, directive); } // Returns the Glslang message rules for the given target environment, // source language, and whether we want HLSL offset rules. We assume // only valid combinations are used. EShMessages GetMessageRules(shaderc_util::Compiler::TargetEnv env, shaderc_util::Compiler::SourceLanguage lang, bool hlsl_offsets, bool hlsl_16bit_types, bool debug_info) { using shaderc_util::Compiler; EShMessages result = EShMsgCascadingErrors; if (lang == Compiler::SourceLanguage::HLSL) { result = static_cast(result | EShMsgReadHlsl); } switch (env) { case Compiler::TargetEnv::OpenGLCompat: // The compiler will have already errored out before now. // But we need to handle this enum. break; case Compiler::TargetEnv::OpenGL: result = static_cast(result | EShMsgSpvRules); break; case Compiler::TargetEnv::Vulkan: result = static_cast(result | EShMsgSpvRules | EShMsgVulkanRules); break; } if (hlsl_offsets) { result = static_cast(result | EShMsgHlslOffsets); } if (hlsl_16bit_types) { result = static_cast(result | EShMsgHlslEnable16BitTypes); } if (debug_info) { result = static_cast(result | EShMsgDebugInfo); } return result; } } // anonymous namespace namespace shaderc_util { unsigned int GlslangInitializer::initialize_count_ = 0; std::mutex* GlslangInitializer::glslang_mutex_ = nullptr; GlslangInitializer::GlslangInitializer() { static std::mutex first_call_mutex; // If this is the first call, glslang_mutex_ needs to be created, but in // thread safe manner. { const std::lock_guard first_call_lock(first_call_mutex); if (glslang_mutex_ == nullptr) { glslang_mutex_ = new std::mutex(); } } const std::lock_guard glslang_lock(*glslang_mutex_); if (initialize_count_ == 0) { glslang::InitializeProcess(); } initialize_count_++; } GlslangInitializer::~GlslangInitializer() { const std::lock_guard glslang_lock(*glslang_mutex_); initialize_count_--; if (initialize_count_ == 0) { glslang::FinalizeProcess(); // There is no delete for glslang_mutex_ here, because we cannot guarantee // there isn't a caller waiting for glslang_mutex_ in GlslangInitializer(). // // This means that this class does leak one std::mutex worth of memory after // the final instance is destroyed, but this allows us to defer allocating // and constructing until we are sure we need to. } } void Compiler::SetLimit(Compiler::Limit limit, int value) { switch (limit) { #define RESOURCE(NAME, FIELD, CNAME) \ case Limit::NAME: \ limits_.FIELD = value; \ break; #include "libshaderc_util/resources.inc" #undef RESOURCE } } int Compiler::GetLimit(Compiler::Limit limit) const { switch (limit) { #define RESOURCE(NAME, FIELD, CNAME) \ case Limit::NAME: \ return limits_.FIELD; #include "libshaderc_util/resources.inc" #undef RESOURCE } return 0; // Unreachable } std::tuple, size_t> Compiler::Compile( const string_piece& input_source_string, EShLanguage forced_shader_stage, const std::string& error_tag, const char* entry_point_name, const std::function& stage_callback, CountingIncluder& includer, OutputType output_type, std::ostream* error_stream, size_t* total_warnings, size_t* total_errors) const { // Compilation results to be returned: // Initialize the result tuple as a failed compilation. In error cases, we // should return result_tuple directly without setting its members. auto result_tuple = std::make_tuple(false, std::vector(), (size_t)0u); // Get the reference of the members of the result tuple. We should set their // values for succeeded compilation before returning the result tuple. bool& succeeded = std::get<0>(result_tuple); std::vector& compilation_output_data = std::get<1>(result_tuple); size_t& compilation_output_data_size_in_bytes = std::get<2>(result_tuple); // Check target environment. const auto target_client_info = GetGlslangClientInfo( error_tag, target_env_, target_env_version_, target_spirv_version_, target_spirv_version_is_forced_); if (!target_client_info.error.empty()) { *error_stream << target_client_info.error; *total_warnings = 0; *total_errors = 1; return result_tuple; } EShLanguage used_shader_stage = forced_shader_stage; const std::string macro_definitions = shaderc_util::format(predefined_macros_, "#define ", " ", "\n"); const std::string pound_extension = "#extension GL_GOOGLE_include_directive : enable\n"; const std::string preamble = macro_definitions + pound_extension; std::string preprocessed_shader; // If only preprocessing, we definitely need to preprocess. Otherwise, if // we don't know the stage until now, we need the preprocessed shader to // deduce the shader stage. if (output_type == OutputType::PreprocessedText || used_shader_stage == EShLangCount) { bool success; std::string glslang_errors; std::tie(success, preprocessed_shader, glslang_errors) = PreprocessShader(error_tag, input_source_string, preamble, includer); success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_, /* suppress_warnings = */ true, glslang_errors.c_str(), total_warnings, total_errors); if (!success) return result_tuple; // Because of the behavior change of the #line directive, the #line // directive introducing each file's content must use the syntax for the // specified version. So we need to probe this shader's version and // profile. int version; EProfile profile; std::tie(version, profile) = DeduceVersionProfile(preprocessed_shader); const bool is_for_next_line = LineDirectiveIsForNextLine(version, profile); preprocessed_shader = CleanupPreamble(preprocessed_shader, error_tag, pound_extension, includer.num_include_directives(), is_for_next_line); if (output_type == OutputType::PreprocessedText) { // Set the values of the result tuple. succeeded = true; compilation_output_data = ConvertStringToVector(preprocessed_shader); compilation_output_data_size_in_bytes = preprocessed_shader.size(); return result_tuple; } else if (used_shader_stage == EShLangCount) { std::string errors; std::tie(used_shader_stage, errors) = GetShaderStageFromSourceCode(error_tag, preprocessed_shader); if (!errors.empty()) { *error_stream << errors; return result_tuple; } if (used_shader_stage == EShLangCount) { if ((used_shader_stage = stage_callback(error_stream, error_tag)) == EShLangCount) { return result_tuple; } } } } // Parsing requires its own Glslang symbol tables. glslang::TShader shader(used_shader_stage); const char* shader_strings = input_source_string.data(); const int shader_lengths = static_cast(input_source_string.size()); const char* string_names = error_tag.c_str(); shader.setStringsWithLengthsAndNames(&shader_strings, &shader_lengths, &string_names, 1); shader.setPreamble(preamble.c_str()); shader.setEntryPoint(entry_point_name); shader.setAutoMapBindings(auto_bind_uniforms_); if (auto_combined_image_sampler_) { shader.setTextureSamplerTransformMode(EShTexSampTransUpgradeTextureRemoveSampler); } shader.setAutoMapLocations(auto_map_locations_); const auto& bases = auto_binding_base_[static_cast(used_shader_stage)]; shader.setShiftImageBinding(bases[static_cast(UniformKind::Image)]); shader.setShiftSamplerBinding(bases[static_cast(UniformKind::Sampler)]); shader.setShiftTextureBinding(bases[static_cast(UniformKind::Texture)]); shader.setShiftUboBinding(bases[static_cast(UniformKind::Buffer)]); shader.setShiftSsboBinding( bases[static_cast(UniformKind::StorageBuffer)]); shader.setShiftUavBinding( bases[static_cast(UniformKind::UnorderedAccessView)]); shader.setHlslIoMapping(hlsl_iomap_); shader.setResourceSetBinding( hlsl_explicit_bindings_[static_cast(used_shader_stage)]); shader.setEnvClient(target_client_info.client, target_client_info.client_version); shader.setEnvTarget(target_client_info.target_language, target_client_info.target_language_version); if (hlsl_functionality1_enabled_) { shader.setEnvTargetHlslFunctionality1(); } shader.setInvertY(invert_y_enabled_); shader.setNanMinMaxClamp(nan_clamp_); const EShMessages rules = GetMessageRules(target_env_, source_language_, hlsl_offsets_, hlsl_16bit_types_enabled_, generate_debug_info_); bool success = shader.parse(&limits_, default_version_, default_profile_, force_version_profile_, kNotForwardCompatible, rules, includer); success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_, suppress_warnings_, shader.getInfoLog(), total_warnings, total_errors); if (!success) return result_tuple; glslang::TProgram program; program.addShader(&shader); success = program.link(EShMsgDefault) && program.mapIO(); success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_, suppress_warnings_, program.getInfoLog(), total_warnings, total_errors); if (!success) return result_tuple; // 'spirv' is an alias for the compilation_output_data. This alias is added // to serve as an input for the call to DissassemblyBinary. std::vector& spirv = compilation_output_data; glslang::SpvOptions options; options.generateDebugInfo = generate_debug_info_; options.disableOptimizer = true; options.optimizeSize = false; // Note the call to GlslangToSpv also populates compilation_output_data. glslang::GlslangToSpv(*program.getIntermediate(used_shader_stage), spirv, &options); // Set the tool field (the top 16-bits) in the generator word to // 'Shaderc over Glslang'. const uint32_t shaderc_generator_word = 13; // From SPIR-V XML Registry const uint32_t generator_word_index = 2; // SPIR-V 2.3: Physical layout assert(spirv.size() > generator_word_index); spirv[generator_word_index] = (spirv[generator_word_index] & 0xffff) | (shaderc_generator_word << 16); std::vector opt_passes; if (hlsl_legalization_enabled_ && source_language_ == SourceLanguage::HLSL) { // If from HLSL, run this passes to "legalize" the SPIR-V for Vulkan // eg. forward and remove memory writes of opaque types. opt_passes.push_back(PassId::kLegalizationPasses); } opt_passes.insert(opt_passes.end(), enabled_opt_passes_.begin(), enabled_opt_passes_.end()); if (!opt_passes.empty()) { std::string opt_errors; if (!SpirvToolsOptimize(target_env_, target_env_version_, opt_passes, &spirv, &opt_errors)) { *error_stream << "shaderc: internal error: compilation succeeded but " "failed to optimize: " << opt_errors << "\n"; return result_tuple; } } if (output_type == OutputType::SpirvAssemblyText) { std::string text_or_error; if (!SpirvToolsDisassemble(target_env_, target_env_version_, spirv, &text_or_error)) { *error_stream << "shaderc: internal error: compilation succeeded but " "failed to disassemble: " << text_or_error << "\n"; return result_tuple; } succeeded = true; compilation_output_data = ConvertStringToVector(text_or_error); compilation_output_data_size_in_bytes = text_or_error.size(); return result_tuple; } else { succeeded = true; // Note compilation_output_data is already populated in GlslangToSpv(). compilation_output_data_size_in_bytes = spirv.size() * sizeof(spirv[0]); return result_tuple; } } void Compiler::AddMacroDefinition(const char* macro, size_t macro_length, const char* definition, size_t definition_length) { predefined_macros_[std::string(macro, macro_length)] = definition ? std::string(definition, definition_length) : ""; } void Compiler::SetTargetEnv(Compiler::TargetEnv env, Compiler::TargetEnvVersion version) { target_env_ = env; target_env_version_ = version; } void Compiler::SetTargetSpirv(Compiler::SpirvVersion version) { target_spirv_version_ = version; target_spirv_version_is_forced_ = true; } void Compiler::SetSourceLanguage(Compiler::SourceLanguage lang) { source_language_ = lang; } void Compiler::SetForcedVersionProfile(int version, EProfile profile) { default_version_ = version; default_profile_ = profile; force_version_profile_ = true; } void Compiler::SetWarningsAsErrors() { warnings_as_errors_ = true; } void Compiler::SetGenerateDebugInfo() { generate_debug_info_ = true; for (size_t i = 0; i < enabled_opt_passes_.size(); ++i) { if (enabled_opt_passes_[i] == PassId::kStripDebugInfo) { enabled_opt_passes_[i] = PassId::kNullPass; } } } void Compiler::SetOptimizationLevel(Compiler::OptimizationLevel level) { // Clear previous settings first. enabled_opt_passes_.clear(); switch (level) { case OptimizationLevel::Size: if (!generate_debug_info_) { enabled_opt_passes_.push_back(PassId::kStripDebugInfo); } enabled_opt_passes_.push_back(PassId::kSizePasses); break; case OptimizationLevel::Performance: if (!generate_debug_info_) { enabled_opt_passes_.push_back(PassId::kStripDebugInfo); } enabled_opt_passes_.push_back(PassId::kPerformancePasses); break; default: break; } } void Compiler::EnableHlslLegalization(bool hlsl_legalization_enabled) { hlsl_legalization_enabled_ = hlsl_legalization_enabled; } void Compiler::EnableHlslFunctionality1(bool enable) { hlsl_functionality1_enabled_ = enable; } void Compiler::EnableHlsl16BitTypes(bool enable) { hlsl_16bit_types_enabled_ = enable; } void Compiler::EnableInvertY(bool enable) { invert_y_enabled_ = enable; } void Compiler::SetNanClamp(bool enable) { nan_clamp_ = enable; } void Compiler::SetSuppressWarnings() { suppress_warnings_ = true; } std::tuple Compiler::PreprocessShader( const std::string& error_tag, const string_piece& shader_source, const string_piece& shader_preamble, CountingIncluder& includer) const { // The stage does not matter for preprocessing. glslang::TShader shader(EShLangVertex); const char* shader_strings = shader_source.data(); const int shader_lengths = static_cast(shader_source.size()); const char* string_names = error_tag.c_str(); shader.setStringsWithLengthsAndNames(&shader_strings, &shader_lengths, &string_names, 1); shader.setPreamble(shader_preamble.data()); auto target_client_info = GetGlslangClientInfo( error_tag, target_env_, target_env_version_, target_spirv_version_, target_spirv_version_is_forced_); if (!target_client_info.error.empty()) { return std::make_tuple(false, "", target_client_info.error); } shader.setEnvClient(target_client_info.client, target_client_info.client_version); if (hlsl_functionality1_enabled_) { shader.setEnvTargetHlslFunctionality1(); } shader.setInvertY(invert_y_enabled_); shader.setNanMinMaxClamp(nan_clamp_); // The preprocessor might be sensitive to the target environment. // So combine the existing rules with the just-give-me-preprocessor-output // flag. const auto rules = static_cast( EShMsgOnlyPreprocessor | GetMessageRules(target_env_, source_language_, hlsl_offsets_, hlsl_16bit_types_enabled_, false)); std::string preprocessed_shader; const bool success = shader.preprocess( &limits_, default_version_, default_profile_, force_version_profile_, kNotForwardCompatible, rules, &preprocessed_shader, includer); if (success) { return std::make_tuple(true, preprocessed_shader, shader.getInfoLog()); } return std::make_tuple(false, "", shader.getInfoLog()); } std::string Compiler::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) const { // Those #define directives in preamble will become empty lines after // preprocessing. We also injected an #extension directive to turn on #include // directive support. In the original preprocessing output from glslang, it // appears before the user source string. We need to do proper adjustment: // * Remove empty lines generated from #define directives in preamble. // * If there is no #include directive in the source code, we do not need to // output the injected #extension directive. Otherwise, // * If there exists a #version directive in the source code, it should be // placed at the first line. Its original line will be filled with an empty // line as placeholder to maintain the code structure. const std::vector lines = preprocessed_shader.get_fields('\n', /* keep_delimiter = */ true); std::ostringstream output_stream; size_t pound_extension_index = lines.size(); size_t pound_version_index = lines.size(); for (size_t i = 0; i < lines.size(); ++i) { if (lines[i] == pound_extension) { pound_extension_index = std::min(i, pound_extension_index); } else if (lines[i].starts_with("#version")) { // In a preprocessed shader, directives are in a canonical format, so we // can confidently compare to '#version' verbatim, without worrying about // whitespace. pound_version_index = i; if (num_include_directives > 0) output_stream << lines[i]; break; } } // We know that #extension directive exists and appears before #version // directive (if any). assert(pound_extension_index < lines.size()); for (size_t i = 0; i < pound_extension_index; ++i) { // All empty lines before the #line directive we injected are generated by // preprocessing preamble. Do not output them. if (lines[i].strip_whitespace().empty()) continue; output_stream << lines[i]; } if (num_include_directives > 0) { output_stream << pound_extension; // Also output a #line directive for the main file. output_stream << GetLineDirective(is_for_next_line, error_tag); } for (size_t i = pound_extension_index + 1; i < lines.size(); ++i) { if (i == pound_version_index) { if (num_include_directives > 0) { output_stream << "\n"; } else { output_stream << lines[i]; } } else { output_stream << lines[i]; } } return output_stream.str(); } std::pair Compiler::GetShaderStageFromSourceCode( string_piece filename, const std::string& preprocessed_shader) const { const string_piece kPragmaShaderStageDirective = "#pragma shader_stage"; const string_piece kLineDirective = "#line"; int version; EProfile profile; std::tie(version, profile) = DeduceVersionProfile(preprocessed_shader); const bool is_for_next_line = LineDirectiveIsForNextLine(version, profile); std::vector lines = string_piece(preprocessed_shader).get_fields('\n'); // The filename, logical line number (which starts from 1 and is sensitive to // #line directives), and stage value for #pragma shader_stage() directives. std::vector> stages; // The physical line numbers of the first #pragma shader_stage() line and // first non-preprocessing line in the preprocessed shader text. size_t first_pragma_physical_line = lines.size() + 1; size_t first_non_pp_line = lines.size() + 1; for (size_t i = 0, logical_line_no = 1; i < lines.size(); ++i) { const string_piece current_line = lines[i].strip_whitespace(); if (current_line.starts_with(kPragmaShaderStageDirective)) { const string_piece stage_value = current_line.substr(kPragmaShaderStageDirective.size()).strip("()"); stages.emplace_back(filename, logical_line_no, stage_value); first_pragma_physical_line = std::min(first_pragma_physical_line, i + 1); } else if (!current_line.empty() && !current_line.starts_with("#")) { first_non_pp_line = std::min(first_non_pp_line, i + 1); } // Update logical line number for the next line. if (current_line.starts_with(kLineDirective)) { string_piece name; std::tie(logical_line_no, name) = DecodeLineDirective(current_line); if (!name.empty()) filename = name; // Note that for core profile, the meaning of #line changed since version // 330. The line number given by #line used to mean the logical line // number of the #line line. Now it means the logical line number of the // next line after the #line line. if (!is_for_next_line) ++logical_line_no; } else { ++logical_line_no; } } if (stages.empty()) return std::make_pair(EShLangCount, ""); std::string error_message; const string_piece& first_pragma_filename = std::get<0>(stages[0]); const std::string first_pragma_line = std::to_string(std::get<1>(stages[0])); const string_piece& first_pragma_stage = std::get<2>(stages[0]); if (first_pragma_physical_line > first_non_pp_line) { error_message += first_pragma_filename.str() + ":" + first_pragma_line + ": error: '#pragma': the first 'shader_stage' #pragma " "must appear before any non-preprocessing code\n"; } EShLanguage stage = MapStageNameToLanguage(first_pragma_stage); if (stage == EShLangCount) { error_message += first_pragma_filename.str() + ":" + first_pragma_line + ": error: '#pragma': invalid stage for 'shader_stage' #pragma: '" + first_pragma_stage.str() + "'\n"; } for (size_t i = 1; i < stages.size(); ++i) { const string_piece& current_stage = std::get<2>(stages[i]); if (current_stage != first_pragma_stage) { const string_piece& current_filename = std::get<0>(stages[i]); const std::string current_line = std::to_string(std::get<1>(stages[i])); error_message += current_filename.str() + ":" + current_line + ": error: '#pragma': conflicting stages for " "'shader_stage' #pragma: '" + current_stage.str() + "' (was '" + first_pragma_stage.str() + "' at " + first_pragma_filename.str() + ":" + first_pragma_line + ")\n"; } } return std::make_pair(error_message.empty() ? stage : EShLangCount, error_message); } std::pair Compiler::DeduceVersionProfile( const std::string& preprocessed_shader) const { int version = default_version_; EProfile profile = default_profile_; if (!force_version_profile_) { std::tie(version, profile) = GetVersionProfileFromSourceCode(preprocessed_shader); if (version == 0 && profile == ENoProfile) { version = default_version_; profile = default_profile_; } } return std::make_pair(version, profile); } std::pair Compiler::GetVersionProfileFromSourceCode( const std::string& preprocessed_shader) const { string_piece pound_version = preprocessed_shader; const size_t pound_version_loc = pound_version.find("#version"); if (pound_version_loc == string_piece::npos) { return std::make_pair(0, ENoProfile); } pound_version = pound_version.substr(pound_version_loc + std::strlen("#version")); pound_version = pound_version.substr(0, pound_version.find_first_of("\n")); std::string version_profile; for (const auto character : pound_version) { if (character != ' ') version_profile += character; } int version; EProfile profile; if (!ParseVersionProfile(version_profile, &version, &profile)) { return std::make_pair(0, ENoProfile); } return std::make_pair(version, profile); } // Converts a string to a vector of uint32_t by copying the content of a given // string to a vector and returns it. Appends '\0' at the end if extra // bytes are required to complete the last element. std::vector ConvertStringToVector(const std::string& str) { size_t num_bytes_str = str.size() + 1u; size_t vector_length = (num_bytes_str + sizeof(uint32_t) - 1) / sizeof(uint32_t); std::vector result_vec(vector_length, 0); std::strncpy(reinterpret_cast(result_vec.data()), str.c_str(), str.size()); return result_vec; } GlslangClientInfo 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) { GlslangClientInfo result; std::ostringstream errs; using shaderc_util::Compiler; switch (env) { case Compiler::TargetEnv::Vulkan: result.client = glslang::EShClientVulkan; if (env_version == Compiler::TargetEnvVersion::Default || env_version == Compiler::TargetEnvVersion::Vulkan_1_0) { result.client_version = glslang::EShTargetVulkan_1_0; } else if (env_version == Compiler::TargetEnvVersion::Vulkan_1_1) { result.client_version = glslang::EShTargetVulkan_1_1; result.target_language_version = glslang::EShTargetSpv_1_3; } else if (env_version == Compiler::TargetEnvVersion::Vulkan_1_2) { result.client_version = glslang::EShTargetVulkan_1_2; result.target_language_version = glslang::EShTargetSpv_1_5; } else if (env_version == Compiler::TargetEnvVersion::Vulkan_1_3) { result.client_version = glslang::EShTargetVulkan_1_3; result.target_language_version = glslang::EShTargetSpv_1_6; } else { errs << "error:" << error_tag << ": Invalid target client version " << static_cast(env_version) << " for Vulkan environment " << int(env); } break; case Compiler::TargetEnv::OpenGLCompat: errs << "error: OpenGL compatibility profile is not supported"; break; case Compiler::TargetEnv::OpenGL: result.client = glslang::EShClientOpenGL; if (env_version == Compiler::TargetEnvVersion::Default || env_version == Compiler::TargetEnvVersion::OpenGL_4_5) { result.client_version = glslang::EShTargetOpenGL_450; } else { errs << "error:" << error_tag << ": Invalid target client version " << static_cast(env_version) << " for OpenGL environment " << int(env); } break; default: errs << "error:" << error_tag << ": Invalid target client environment " << int(env); break; } if (spv_version_is_forced && errs.str().empty()) { switch (spv_version) { case Compiler::SpirvVersion::v1_0: result.target_language_version = glslang::EShTargetSpv_1_0; break; case Compiler::SpirvVersion::v1_1: result.target_language_version = glslang::EShTargetSpv_1_1; break; case Compiler::SpirvVersion::v1_2: result.target_language_version = glslang::EShTargetSpv_1_2; break; case Compiler::SpirvVersion::v1_3: result.target_language_version = glslang::EShTargetSpv_1_3; break; case Compiler::SpirvVersion::v1_4: result.target_language_version = glslang::EShTargetSpv_1_4; break; case Compiler::SpirvVersion::v1_5: result.target_language_version = glslang::EShTargetSpv_1_5; break; case Compiler::SpirvVersion::v1_6: result.target_language_version = glslang::EShTargetSpv_1_6; break; default: errs << "error:" << error_tag << ": Unknown SPIR-V version " << std::hex << uint32_t(spv_version); break; } } result.error = errs.str(); return result; } } // namespace shaderc_util