// 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 "shaderc_private.h"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <sstream>
#include <vector>
#include "SPIRV/spirv.hpp"
#include "libshaderc_util/compiler.h"
#include "libshaderc_util/counting_includer.h"
#include "libshaderc_util/resources.h"
#include "libshaderc_util/spirv_tools_wrapper.h"
#include "libshaderc_util/version_profile.h"
#if (defined(_MSC_VER) && !defined(_CPPUNWIND)) || !defined(__EXCEPTIONS)
#define TRY_IF_EXCEPTIONS_ENABLED
#define CATCH_IF_EXCEPTIONS_ENABLED(X) if (0)
#else
#define TRY_IF_EXCEPTIONS_ENABLED try
#define CATCH_IF_EXCEPTIONS_ENABLED(X) catch (X)
#endif
namespace {
// Returns shader stage (ie: vertex, fragment, etc.) in response to forced
// shader kinds. If the shader kind is not a forced kind, returns EshLangCount
// to let #pragma annotation or shader stage deducer determine the stage to
// use.
EShLanguage GetForcedStage(shaderc_shader_kind kind) {
switch (kind) {
case shaderc_glsl_vertex_shader:
return EShLangVertex;
case shaderc_glsl_fragment_shader:
return EShLangFragment;
case shaderc_glsl_compute_shader:
return EShLangCompute;
case shaderc_glsl_geometry_shader:
return EShLangGeometry;
case shaderc_glsl_tess_control_shader:
return EShLangTessControl;
case shaderc_glsl_tess_evaluation_shader:
return EShLangTessEvaluation;
case shaderc_glsl_infer_from_source:
case shaderc_glsl_default_vertex_shader:
case shaderc_glsl_default_fragment_shader:
case shaderc_glsl_default_compute_shader:
case shaderc_glsl_default_geometry_shader:
case shaderc_glsl_default_tess_control_shader:
case shaderc_glsl_default_tess_evaluation_shader:
case shaderc_spirv_assembly:
return EShLangCount;
}
assert(0 && "Unhandled shaderc_shader_kind");
return EShLangCount;
}
// Converts shaderc_target_env to EShMessages
EShMessages GetMessageRules(shaderc_target_env target) {
switch (target) {
case shaderc_target_env_opengl_compat:
break;
case shaderc_target_env_opengl:
return static_cast<EShMessages>(EShMsgSpvRules | EShMsgCascadingErrors);
case shaderc_target_env_vulkan:
return static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules |
EShMsgCascadingErrors);
}
return EShMsgCascadingErrors;
}
// A wrapper functor class to be used as stage deducer for libshaderc_util
// Compile() interface. When the given shader kind is one of the default shader
// kinds, this functor will be called if #pragma is not found in the source
// code. And it returns the corresponding shader stage. When the shader kind is
// a forced shader kind, this functor won't be called and it simply returns
// EShLangCount to make the syntax correct. When the shader kind is set to
// shaderc_glsl_deduce_from_pragma, this functor also returns EShLangCount, but
// the compiler should emit error if #pragma annotation is not found in this
// case.
class StageDeducer {
public:
explicit StageDeducer(
shaderc_shader_kind kind = shaderc_glsl_infer_from_source)
: kind_(kind), error_(false){};
// The method that underlying glslang will call to determine the shader stage
// to be used in current compilation. It is called only when there is neither
// forced shader kind (or say stage, in the view of glslang), nor #pragma
// annotation in the source code. This method transforms an user defined
// 'default' shader kind to the corresponding shader stage. As this is the
// last trial to determine the shader stage, failing to find the corresponding
// shader stage will record an error.
// Note that calling this method more than once during one compilation will
// have the error recorded for the previous call been overwriten by the next
// call.
EShLanguage operator()(std::ostream* /*error_stream*/,
const shaderc_util::string_piece& /*error_tag*/) {
EShLanguage stage = GetDefaultStage(kind_);
if (stage == EShLangCount) {
error_ = true;
} else {
error_ = false;
}
return stage;
};
// Returns true if there is error during shader stage deduction.
bool error() const { return error_; }
private:
// Gets the corresponding shader stage for a given 'default' shader kind. All
// other kinds are mapped to EShLangCount which should not be used.
EShLanguage GetDefaultStage(shaderc_shader_kind kind) const {
switch (kind) {
case shaderc_glsl_vertex_shader:
case shaderc_glsl_fragment_shader:
case shaderc_glsl_compute_shader:
case shaderc_glsl_geometry_shader:
case shaderc_glsl_tess_control_shader:
case shaderc_glsl_tess_evaluation_shader:
case shaderc_glsl_infer_from_source:
return EShLangCount;
case shaderc_glsl_default_vertex_shader:
return EShLangVertex;
case shaderc_glsl_default_fragment_shader:
return EShLangFragment;
case shaderc_glsl_default_compute_shader:
return EShLangCompute;
case shaderc_glsl_default_geometry_shader:
return EShLangGeometry;
case shaderc_glsl_default_tess_control_shader:
return EShLangTessControl;
case shaderc_glsl_default_tess_evaluation_shader:
return EShLangTessEvaluation;
case shaderc_spirv_assembly:
return EShLangCount;
}
assert(0 && "Unhandled shaderc_shader_kind");
return EShLangCount;
}
shaderc_shader_kind kind_;
bool error_;
};
// A bridge between the libshaderc includer and libshaderc_util includer.
class InternalFileIncluder : public shaderc_util::CountingIncluder {
public:
InternalFileIncluder(const shaderc_include_resolve_fn resolver,
const shaderc_include_result_release_fn result_releaser,
void* user_data)
: resolver_(resolver),
result_releaser_(result_releaser),
user_data_(user_data){};
InternalFileIncluder()
: resolver_(nullptr), result_releaser_(nullptr), user_data_(nullptr){};
private:
// Check the validity of the callbacks.
bool AreValidCallbacks() const {
return resolver_ != nullptr && result_releaser_ != nullptr;
}
// Maps a shaderc_include_type to the correpsonding Glslang include type.
shaderc_include_type GetIncludeType(
glslang::TShader::Includer::IncludeType type) {
switch (type) {
case glslang::TShader::Includer::EIncludeRelative:
return shaderc_include_type_relative;
case glslang::TShader::Includer::EIncludeStandard:
return shaderc_include_type_standard;
default:
break;
}
assert(0 && "Unhandled shaderc_include_type");
return shaderc_include_type_relative;
}
// Resolves an include request for the requested source of the given
// type in the context of the specified requesting source. On success,
// returns a newly allocated IncludeResponse containing the fully resolved
// name of the requested source and the contents of that source.
// On failure, returns a newly allocated IncludeResponse where the
// resolved name member is an empty string, and the contents members
// contains error details.
virtual glslang::TShader::Includer::IncludeResult* include_delegate(
const char* requested_source,
glslang::TShader::Includer::IncludeType type,
const char* requesting_source, size_t include_depth) override {
if (!AreValidCallbacks()) {
const char kUnexpectedIncludeError[] =
"#error unexpected include directive";
return new glslang::TShader::Includer::IncludeResult{
"", kUnexpectedIncludeError, strlen(kUnexpectedIncludeError),
nullptr};
}
shaderc_include_result* include_result =
resolver_(user_data_, requested_source, GetIncludeType(type),
requesting_source, include_depth);
// Make a glslang IncludeResult from a shaderc_include_result. The
// user_data member of the IncludeResult is a pointer to the
// shaderc_include_result object, so we can later release the latter.
return new glslang::TShader::Includer::IncludeResult{
std::string(include_result->source_name,
include_result->source_name_length),
include_result->content, include_result->content_length,
include_result};
}
// Releases the given IncludeResult.
virtual void release_delegate(
glslang::TShader::Includer::IncludeResult* result) override {
if (result && result_releaser_) {
result_releaser_(user_data_,
static_cast<shaderc_include_result*>(result->user_data));
}
delete result;
}
const shaderc_include_resolve_fn resolver_;
const shaderc_include_result_release_fn result_releaser_;
void* user_data_;
};
} // anonymous namespace
struct shaderc_compile_options {
shaderc_compile_options() : include_user_data(nullptr) {}
shaderc_util::Compiler compiler;
shaderc_include_resolve_fn include_resolver;
shaderc_include_result_release_fn include_result_releaser;
void* include_user_data;
};
shaderc_compile_options_t shaderc_compile_options_initialize() {
return new (std::nothrow) shaderc_compile_options;
}
shaderc_compile_options_t shaderc_compile_options_clone(
const shaderc_compile_options_t options) {
if (!options) {
return shaderc_compile_options_initialize();
}
return new (std::nothrow) shaderc_compile_options(*options);
}
void shaderc_compile_options_release(shaderc_compile_options_t options) {
delete options;
}
void shaderc_compile_options_add_macro_definition(
shaderc_compile_options_t options, const char* name, size_t name_length,
const char* value, size_t value_length) {
options->compiler.AddMacroDefinition(name, name_length, value, value_length);
}
void shaderc_compile_options_set_generate_debug_info(
shaderc_compile_options_t options) {
options->compiler.SetGenerateDebugInfo();
}
void shaderc_compile_options_set_forced_version_profile(
shaderc_compile_options_t options, int version, shaderc_profile profile) {
// Transfer the profile parameter from public enum type to glslang internal
// enum type. No default case here so that compiler will complain if new enum
// member is added later but not handled here.
switch (profile) {
case shaderc_profile_none:
options->compiler.SetForcedVersionProfile(version, ENoProfile);
break;
case shaderc_profile_core:
options->compiler.SetForcedVersionProfile(version, ECoreProfile);
break;
case shaderc_profile_compatibility:
options->compiler.SetForcedVersionProfile(version, ECompatibilityProfile);
break;
case shaderc_profile_es:
options->compiler.SetForcedVersionProfile(version, EEsProfile);
break;
}
}
void shaderc_compile_options_set_include_callbacks(
shaderc_compile_options_t options, shaderc_include_resolve_fn resolver,
shaderc_include_result_release_fn result_releaser, void* user_data) {
options->include_resolver = resolver;
options->include_result_releaser = result_releaser;
options->include_user_data = user_data;
}
void shaderc_compile_options_set_suppress_warnings(
shaderc_compile_options_t options) {
options->compiler.SetSuppressWarnings();
}
void shaderc_compile_options_set_target_env(shaderc_compile_options_t options,
shaderc_target_env target,
uint32_t version) {
// "version" reserved for future use, intended to distinguish between
// different versions of a target environment
options->compiler.SetMessageRules(GetMessageRules(target));
}
void shaderc_compile_options_set_warnings_as_errors(
shaderc_compile_options_t options) {
options->compiler.SetWarningsAsErrors();
}
shaderc_compiler_t shaderc_compiler_initialize() {
static shaderc_util::GlslInitializer* initializer =
new shaderc_util::GlslInitializer;
shaderc_compiler_t compiler = new (std::nothrow) shaderc_compiler;
compiler->initializer = initializer;
return compiler;
}
void shaderc_compiler_release(shaderc_compiler_t compiler) { delete compiler; }
namespace {
shaderc_compilation_result_t CompileToSpecifiedOutputType(
const shaderc_compiler_t compiler, const char* source_text,
size_t source_text_size, shaderc_shader_kind shader_kind,
const char* input_file_name, const char* entry_point_name,
const shaderc_compile_options_t additional_options,
shaderc_util::Compiler::OutputType output_type) {
auto* result = new (std::nothrow) shaderc_compilation_result_vector;
if (!result) return nullptr;
if (!input_file_name) {
result->messages = "Input file name string was null.";
result->num_errors = 1;
result->compilation_status = shaderc_compilation_status_compilation_error;
return result;
}
result->compilation_status = shaderc_compilation_status_invalid_stage;
bool compilation_succeeded = false; // In case we exit early.
std::vector<uint32_t> compilation_output_data;
size_t compilation_output_data_size_in_bytes = 0u;
if (!compiler->initializer) return result;
TRY_IF_EXCEPTIONS_ENABLED {
std::stringstream errors;
size_t total_warnings = 0;
size_t total_errors = 0;
std::string input_file_name_str(input_file_name);
EShLanguage forced_stage = GetForcedStage(shader_kind);
shaderc_util::string_piece source_string =
shaderc_util::string_piece(source_text, source_text + source_text_size);
StageDeducer stage_deducer(shader_kind);
if (additional_options) {
InternalFileIncluder includer(additional_options->include_resolver,
additional_options->include_result_releaser,
additional_options->include_user_data);
// Depends on return value optimization to avoid extra copy.
std::tie(compilation_succeeded, compilation_output_data,
compilation_output_data_size_in_bytes) =
additional_options->compiler.Compile(
source_string, forced_stage, input_file_name_str,
// stage_deducer has a flag: error_, which we need to check later.
// We need to make this a reference wrapper, so that std::function
// won't make a copy for this callable object.
std::ref(stage_deducer), includer, output_type, &errors,
&total_warnings, &total_errors, compiler->initializer);
} else {
// Compile with default options.
InternalFileIncluder includer;
std::tie(compilation_succeeded, compilation_output_data,
compilation_output_data_size_in_bytes) =
shaderc_util::Compiler().Compile(
source_string, forced_stage, input_file_name_str,
std::ref(stage_deducer), includer, output_type, &errors,
&total_warnings, &total_errors, compiler->initializer);
}
result->messages = errors.str();
result->SetOutputData(std::move(compilation_output_data));
result->output_data_size = compilation_output_data_size_in_bytes;
result->num_warnings = total_warnings;
result->num_errors = total_errors;
if (compilation_succeeded) {
result->compilation_status = shaderc_compilation_status_success;
} else {
// Check whether the error is caused by failing to deduce the shader
// stage. If it is the case, set the error type to shader kind error.
// Otherwise, set it to compilation error.
result->compilation_status =
stage_deducer.error() ? shaderc_compilation_status_invalid_stage
: shaderc_compilation_status_compilation_error;
}
}
CATCH_IF_EXCEPTIONS_ENABLED(...) {
result->compilation_status = shaderc_compilation_status_internal_error;
}
return result;
}
} // anonymous namespace
shaderc_compilation_result_t shaderc_compile_into_spv(
const shaderc_compiler_t compiler, const char* source_text,
size_t source_text_size, shaderc_shader_kind shader_kind,
const char* input_file_name, const char* entry_point_name,
const shaderc_compile_options_t additional_options) {
return CompileToSpecifiedOutputType(
compiler, source_text, source_text_size, shader_kind, input_file_name,
entry_point_name, additional_options,
shaderc_util::Compiler::OutputType::SpirvBinary);
}
shaderc_compilation_result_t shaderc_compile_into_spv_assembly(
const shaderc_compiler_t compiler, const char* source_text,
size_t source_text_size, shaderc_shader_kind shader_kind,
const char* input_file_name, const char* entry_point_name,
const shaderc_compile_options_t additional_options) {
return CompileToSpecifiedOutputType(
compiler, source_text, source_text_size, shader_kind, input_file_name,
entry_point_name, additional_options,
shaderc_util::Compiler::OutputType::SpirvAssemblyText);
}
shaderc_compilation_result_t shaderc_compile_into_preprocessed_text(
const shaderc_compiler_t compiler, const char* source_text,
size_t source_text_size, shaderc_shader_kind shader_kind,
const char* input_file_name, const char* entry_point_name,
const shaderc_compile_options_t additional_options) {
return CompileToSpecifiedOutputType(
compiler, source_text, source_text_size, shader_kind, input_file_name,
entry_point_name, additional_options,
shaderc_util::Compiler::OutputType::PreprocessedText);
}
shaderc_compilation_result_t shaderc_assemble_into_spv(
const shaderc_compiler_t compiler, const char* source_assembly,
size_t source_assembly_size) {
auto* result = new (std::nothrow) shaderc_compilation_result_spv_binary;
if (!result) return nullptr;
result->compilation_status = shaderc_compilation_status_invalid_assembly;
if (!compiler->initializer) return result;
if (source_assembly == nullptr) return result;
TRY_IF_EXCEPTIONS_ENABLED {
spv_binary assembling_output_data = nullptr;
std::string errors;
const bool assembling_succeeded = shaderc_util::SpirvToolsAssemble(
{source_assembly, source_assembly + source_assembly_size},
&assembling_output_data, &errors);
result->num_errors = !assembling_succeeded;
if (assembling_succeeded) {
result->SetOutputData(assembling_output_data);
result->output_data_size =
assembling_output_data->wordCount * sizeof(uint32_t);
result->compilation_status = shaderc_compilation_status_success;
} else {
result->messages = std::move(errors);
result->compilation_status = shaderc_compilation_status_invalid_assembly;
}
}
CATCH_IF_EXCEPTIONS_ENABLED(...) {
result->compilation_status = shaderc_compilation_status_internal_error;
}
return result;
}
size_t shaderc_result_get_length(const shaderc_compilation_result_t result) {
return result->output_data_size;
}
size_t shaderc_result_get_num_warnings(
const shaderc_compilation_result_t result) {
return result->num_warnings;
}
size_t shaderc_result_get_num_errors(
const shaderc_compilation_result_t result) {
return result->num_errors;
}
const char* shaderc_result_get_bytes(
const shaderc_compilation_result_t result) {
return result->GetBytes();
}
void shaderc_result_release(shaderc_compilation_result_t result) {
delete result;
}
const char* shaderc_result_get_error_message(
const shaderc_compilation_result_t result) {
return result->messages.c_str();
}
shaderc_compilation_status shaderc_result_get_compilation_status(
const shaderc_compilation_result_t result) {
return result->compilation_status;
}
void shaderc_get_spv_version(unsigned int* version, unsigned int* revision) {
*version = spv::Version;
*revision = spv::Revision;
}
bool shaderc_parse_version_profile(const char* str, int* version,
shaderc_profile* profile) {
EProfile glslang_profile;
bool success = shaderc_util::ParseVersionProfile(
std::string(str, strlen(str)), version, &glslang_profile);
if (!success) return false;
switch (glslang_profile) {
case EEsProfile:
*profile = shaderc_profile_es;
return true;
case ECoreProfile:
*profile = shaderc_profile_core;
return true;
case ECompatibilityProfile:
*profile = shaderc_profile_compatibility;
return true;
case ENoProfile:
*profile = shaderc_profile_none;
return true;
case EBadProfile:
return false;
}
// Shouldn't reach here, all profile enum should be handled above.
// Be strict to return false.
return false;
}