// Copyright 2018 The Amber Authors. // // 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 "src/shader_compiler.h" #include #include #include #include #include #if AMBER_ENABLE_SPIRV_TOOLS #include "spirv-tools/libspirv.hpp" #include "spirv-tools/linker.hpp" #include "spirv-tools/optimizer.hpp" #endif // AMBER_ENABLE_SPIRV_TOOLS #if AMBER_ENABLE_SHADERC #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wold-style-cast" #pragma clang diagnostic ignored "-Wshadow-uncaptured-local" #pragma clang diagnostic ignored "-Wweak-vtables" #include "shaderc/shaderc.hpp" #pragma clang diagnostic pop #endif // AMBER_ENABLE_SHADERC #if AMBER_ENABLE_DXC #include "src/dxc_helper.h" #endif // AMBER_ENABLE_DXC #ifdef AMBER_ENABLE_CLSPV #include "src/clspv_helper.h" #endif // AMBER_ENABLE_CLSPV namespace amber { ShaderCompiler::ShaderCompiler() = default; ShaderCompiler::ShaderCompiler(const std::string& env, bool disable_spirv_validation) : spv_env_(env), disable_spirv_validation_(disable_spirv_validation) {} ShaderCompiler::~ShaderCompiler() = default; std::pair> ShaderCompiler::Compile( Pipeline::ShaderInfo* shader_info, const ShaderMap& shader_map) const { const auto shader = shader_info->GetShader(); auto it = shader_map.find(shader->GetName()); if (it != shader_map.end()) { #if AMBER_ENABLE_CLSPV if (shader->GetFormat() == kShaderFormatOpenCLC) { return {Result("OPENCL-C shaders do not support pre-compiled shaders"), {}}; } #endif // AMBER_ENABLE_CLSPV return {{}, it->second}; } #if AMBER_ENABLE_SPIRV_TOOLS std::string spv_errors; spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0; if (!spv_env_.empty()) { if (!spvParseTargetEnv(spv_env_.c_str(), &target_env)) return {Result("Unable to parse SPIR-V target environment"), {}}; } auto msg_consumer = [&spv_errors](spv_message_level_t level, const char*, const spv_position_t& position, const char* message) { switch (level) { case SPV_MSG_FATAL: case SPV_MSG_INTERNAL_ERROR: case SPV_MSG_ERROR: spv_errors += "error: line " + std::to_string(position.index) + ": " + message + "\n"; break; case SPV_MSG_WARNING: spv_errors += "warning: line " + std::to_string(position.index) + ": " + message + "\n"; break; case SPV_MSG_INFO: spv_errors += "info: line " + std::to_string(position.index) + ": " + message + "\n"; break; case SPV_MSG_DEBUG: break; } }; spvtools::SpirvTools tools(target_env); tools.SetMessageConsumer(msg_consumer); #endif // AMBER_ENABLE_SPIRV_TOOLS std::vector results; if (shader->GetFormat() == kShaderFormatSpirvHex) { Result r = ParseHex(shader->GetData(), &results); if (!r.IsSuccess()) return {Result("Unable to parse shader hex."), {}}; #if AMBER_ENABLE_SHADERC } else if (shader->GetFormat() == kShaderFormatGlsl) { Result r = CompileGlsl(shader, &results); if (!r.IsSuccess()) return {r, {}}; #endif // AMBER_ENABLE_SHADERC #if AMBER_ENABLE_DXC } else if (shader->GetFormat() == kShaderFormatHlsl) { Result r = CompileHlsl(shader, &results); if (!r.IsSuccess()) return {r, {}}; #endif // AMBER_ENABLE_DXC #if AMBER_ENABLE_SPIRV_TOOLS } else if (shader->GetFormat() == kShaderFormatSpirvAsm) { if (!tools.Assemble(shader->GetData(), &results, spvtools::SpirvTools::kDefaultAssembleOption)) { return {Result("Shader assembly failed: " + spv_errors), {}}; } #endif // AMBER_ENABLE_SPIRV_TOOLS #if AMBER_ENABLE_CLSPV } else if (shader->GetFormat() == kShaderFormatOpenCLC) { Result r = CompileOpenCLC(shader_info, &results); if (!r.IsSuccess()) return {r, {}}; #endif // AMBER_ENABLE_CLSPV } else { return {Result("Invalid shader format"), results}; } // Validate the shader, but have an option to disable that. // Always use the data member, to avoid an unused-variable warning // when not using SPIRV-Tools support. if (!disable_spirv_validation_) { #if AMBER_ENABLE_SPIRV_TOOLS spvtools::ValidatorOptions options; if (!tools.Validate(results.data(), results.size(), options)) return {Result("Invalid shader: " + spv_errors), {}}; #endif // AMBER_ENABLE_SPIRV_TOOLS } #if AMBER_ENABLE_SPIRV_TOOLS // Optimize the shader if any optimizations were specified. if (!shader_info->GetShaderOptimizations().empty()) { spvtools::Optimizer optimizer(target_env); optimizer.SetMessageConsumer(msg_consumer); if (!optimizer.RegisterPassesFromFlags( shader_info->GetShaderOptimizations())) { return {Result("Invalid optimizations: " + spv_errors), {}}; } if (!optimizer.Run(results.data(), results.size(), &results)) return {Result("Optimizations failed: " + spv_errors), {}}; } #endif // AMBER_ENABLE_SPIRV_TOOLS return {{}, results}; } Result ShaderCompiler::ParseHex(const std::string& data, std::vector* result) const { size_t used = 0; const char* str = data.c_str(); uint8_t converted = 0; uint32_t tmp = 0; while (used < data.length()) { char* new_pos = nullptr; uint64_t v = static_cast(std::strtol(str, &new_pos, 16)); ++converted; // TODO(dsinclair): Is this actually right? tmp = tmp | (static_cast(v) << (8 * (converted - 1))); if (converted == 4) { result->push_back(tmp); tmp = 0; converted = 0; } used += static_cast(new_pos - str); str = new_pos; } return {}; } #if AMBER_ENABLE_SHADERC Result ShaderCompiler::CompileGlsl(const Shader* shader, std::vector* result) const { shaderc::Compiler compiler; shaderc::CompileOptions options; uint32_t env = 0u; uint32_t env_version = 0u; uint32_t spirv_version = 0u; auto r = ParseSpvEnv(spv_env_, &env, &env_version, &spirv_version); if (!r.IsSuccess()) return r; options.SetTargetEnvironment(static_cast(env), env_version); options.SetTargetSpirv(static_cast(spirv_version)); shaderc_shader_kind kind; if (shader->GetType() == kShaderTypeCompute) kind = shaderc_compute_shader; else if (shader->GetType() == kShaderTypeFragment) kind = shaderc_fragment_shader; else if (shader->GetType() == kShaderTypeGeometry) kind = shaderc_geometry_shader; else if (shader->GetType() == kShaderTypeVertex) kind = shaderc_vertex_shader; else if (shader->GetType() == kShaderTypeTessellationControl) kind = shaderc_tess_control_shader; else if (shader->GetType() == kShaderTypeTessellationEvaluation) kind = shaderc_tess_evaluation_shader; else return Result("Unknown shader type"); shaderc::SpvCompilationResult module = compiler.CompileGlslToSpv(shader->GetData(), kind, "-", options); if (module.GetCompilationStatus() != shaderc_compilation_status_success) return Result(module.GetErrorMessage()); std::copy(module.cbegin(), module.cend(), std::back_inserter(*result)); return {}; } #else Result ShaderCompiler::CompileGlsl(const Shader*, std::vector*) const { return {}; } #endif // AMBER_ENABLE_SHADERC #if AMBER_ENABLE_DXC Result ShaderCompiler::CompileHlsl(const Shader* shader, std::vector* result) const { std::string target; if (shader->GetType() == kShaderTypeCompute) target = "cs_6_2"; else if (shader->GetType() == kShaderTypeFragment) target = "ps_6_2"; else if (shader->GetType() == kShaderTypeGeometry) target = "gs_6_2"; else if (shader->GetType() == kShaderTypeVertex) target = "vs_6_2"; else return Result("Unknown shader type"); return dxchelper::Compile(shader->GetData(), "main", target, spv_env_, result); } #else Result ShaderCompiler::CompileHlsl(const Shader*, std::vector*) const { return {}; } #endif // AMBER_ENABLE_DXC #if AMBER_ENABLE_CLSPV Result ShaderCompiler::CompileOpenCLC(Pipeline::ShaderInfo* shader_info, std::vector* result) const { return clspvhelper::Compile(shader_info, result); } #else Result ShaderCompiler::CompileOpenCLC(Pipeline::ShaderInfo*, std::vector*) const { return {}; } #endif // AMBER_ENABLE_CLSPV namespace { // Value for the Vulkan API, used in the Shaderc API const uint32_t kVulkan = 0; // Values for versions of the Vulkan API, used in the Shaderc API const uint32_t kVulkan_1_0 = (uint32_t(1) << 22); const uint32_t kVulkan_1_1 = (uint32_t(1) << 22) | (1 << 12); // Values for SPIR-V versions, used in the Shaderc API const uint32_t kSpv_1_0 = uint32_t(0x10000); const uint32_t kSpv_1_1 = uint32_t(0x10100); const uint32_t kSpv_1_2 = uint32_t(0x10200); const uint32_t kSpv_1_3 = uint32_t(0x10300); const uint32_t kSpv_1_4 = uint32_t(0x10400); #if AMBER_ENABLE_SHADERC // Check that we have the right values, from the original definitions // in the Shaderc API. static_assert(kVulkan == shaderc_target_env_vulkan, "enum vulkan* value mismatch"); static_assert(kVulkan_1_0 == shaderc_env_version_vulkan_1_0, "enum vulkan1.0 value mismatch"); static_assert(kVulkan_1_1 == shaderc_env_version_vulkan_1_1, "enum vulkan1.1 value mismatch"); static_assert(kSpv_1_0 == shaderc_spirv_version_1_0, "enum spv1.0 value mismatch"); static_assert(kSpv_1_1 == shaderc_spirv_version_1_1, "enum spv1.1 value mismatch"); static_assert(kSpv_1_2 == shaderc_spirv_version_1_2, "enum spv1.2 value mismatch"); static_assert(kSpv_1_3 == shaderc_spirv_version_1_3, "enum spv1.3 value mismatch"); static_assert(kSpv_1_4 == shaderc_spirv_version_1_4, "enum spv1.4 value mismatch"); #endif } // namespace Result ParseSpvEnv(const std::string& spv_env, uint32_t* target_env, uint32_t* target_env_version, uint32_t* spirv_version) { if (!target_env || !target_env_version || !spirv_version) return Result("ParseSpvEnv: null pointer parameter"); // Use the same values as in Shaderc's shaderc/env.h struct Values { uint32_t env; uint32_t env_version; uint32_t spirv_version; }; Values values{kVulkan, kVulkan_1_0, kSpv_1_0}; if (spv_env == "" || spv_env == "spv1.0") { values = {kVulkan, kVulkan_1_0, kSpv_1_0}; } else if (spv_env == "spv1.1") { values = {kVulkan, kVulkan_1_1, kSpv_1_1}; } else if (spv_env == "spv1.2") { values = {kVulkan, kVulkan_1_1, kSpv_1_2}; } else if (spv_env == "spv1.3") { values = {kVulkan, kVulkan_1_1, kSpv_1_3}; } else if (spv_env == "spv1.4") { values = {kVulkan, kVulkan_1_1, kSpv_1_4}; } else if (spv_env == "vulkan1.0") { values = {kVulkan, kVulkan_1_0, kSpv_1_0}; } else if (spv_env == "vulkan1.1") { // Vulkan 1.1 requires support for SPIR-V 1.3. values = {kVulkan, kVulkan_1_1, kSpv_1_3}; } else if (spv_env == "vulkan1.1spv1.4") { values = {kVulkan, kVulkan_1_1, kSpv_1_4}; } else { return Result(std::string("Unrecognized environment ") + spv_env); } *target_env = values.env; *target_env_version = values.env_version; *spirv_version = values.spirv_version; return {}; } } // namespace amber