blob: 0fd4cf302b06a188c5f30cccb5c0d6c84f1b3550 [file] [log] [blame]
// 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 <algorithm>
#include <cstdlib>
#include <iterator>
#include <string>
#include <utility>
#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<Result, std::vector<uint32_t>> 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<uint32_t> 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<uint32_t>* 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<uint64_t>(std::strtol(str, &new_pos, 16));
++converted;
// TODO(dsinclair): Is this actually right?
tmp = tmp | (static_cast<uint32_t>(v) << (8 * (converted - 1)));
if (converted == 4) {
result->push_back(tmp);
tmp = 0;
converted = 0;
}
used += static_cast<size_t>(new_pos - str);
str = new_pos;
}
return {};
}
#if AMBER_ENABLE_SHADERC
Result ShaderCompiler::CompileGlsl(const Shader* shader,
std::vector<uint32_t>* 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<shaderc_target_env>(env),
env_version);
options.SetTargetSpirv(static_cast<shaderc_spirv_version>(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<uint32_t>*) const {
return {};
}
#endif // AMBER_ENABLE_SHADERC
#if AMBER_ENABLE_DXC
Result ShaderCompiler::CompileHlsl(const Shader* shader,
std::vector<uint32_t>* 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<uint32_t>*) const {
return {};
}
#endif // AMBER_ENABLE_DXC
#if AMBER_ENABLE_CLSPV
Result ShaderCompiler::CompileOpenCLC(Pipeline::ShaderInfo* shader_info,
std::vector<uint32_t>* result) const {
return clspvhelper::Compile(shader_info, result);
}
#else
Result ShaderCompiler::CompileOpenCLC(Pipeline::ShaderInfo*,
std::vector<uint32_t>*) 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