blob: 69ca3e4befa5df43deb44ff647b8325f47528adb [file] [log] [blame]
// Copyright 2019 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 "spvc_private.h"
#include "spirv-tools/optimizer.hpp"
#include "spvc_log.h"
#include "spvcir_pass.h"
// Originally from libshaderc_utils/exceptions.h, copied here to avoid
// needing to depend on libshaderc_utils and pull in its dependency on
// glslang.
#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 spvc_private {
spv_target_env get_spv_target_env(shaderc_target_env env,
shaderc_env_version version) {
switch (env) {
case shaderc_target_env_opengl:
case shaderc_target_env_opengl_compat:
switch (version) {
case shaderc_env_version_opengl_4_5:
return SPV_ENV_OPENGL_4_5;
default:
break;
}
break;
case shaderc_target_env_vulkan:
switch (version) {
case shaderc_env_version_vulkan_1_0:
return SPV_ENV_VULKAN_1_0;
case shaderc_env_version_vulkan_1_1:
return SPV_ENV_VULKAN_1_1;
default:
break;
}
break;
case shaderc_target_env_webgpu:
return SPV_ENV_WEBGPU_0;
default:
break;
}
return SPV_ENV_VULKAN_1_0;
}
void consume_spirv_tools_message(shaderc_spvc_context* context,
spv_message_level_t level, const char* src,
const spv_position_t& pos,
const char* message) {
switch (level) {
case SPV_MSG_FATAL:
shaderc_spvc::ErrorLog(context) << message;
break;
case SPV_MSG_INTERNAL_ERROR:
shaderc_spvc::ErrorLog(context) << message;
break;
case SPV_MSG_ERROR:
shaderc_spvc::ErrorLog(context) << message;
break;
case SPV_MSG_WARNING:
shaderc_spvc::WarningLog(context) << message;
break;
case SPV_MSG_INFO:
shaderc_spvc::InfoLog(context) << message;
break;
case SPV_MSG_DEBUG:
shaderc_spvc::DebugLog(context) << message;
break;
}
}
shaderc_spvc_status validate_spirv(shaderc_spvc_context* context,
spv_target_env env, const uint32_t* source,
size_t source_len) {
spvtools::SpirvTools tools(env);
if (!tools.IsValid()) {
shaderc_spvc::ErrorLog(context) << "Could not initialize SPIRV-Tools.";
return shaderc_spvc_status_internal_error;
}
tools.SetMessageConsumer(std::bind(
consume_spirv_tools_message, context, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
if (!tools.Validate(source, source_len, spvtools::ValidatorOptions())) {
shaderc_spvc::ErrorLog(context) << "Validation of shader failed.";
return shaderc_spvc_status_validation_error;
}
return shaderc_spvc_status_success;
}
shaderc_spvc_status translate_spirv(shaderc_spvc_context* context,
spv_target_env source_env,
spv_target_env target_env,
const uint32_t* source, size_t source_len,
shaderc_spvc_compile_options_t options,
std::vector<uint32_t>* target) {
if (!target) {
shaderc_spvc::ErrorLog(context)
<< "null provided for translation destination.";
return shaderc_spvc_status_transformation_error;
}
if (source_env == target_env) {
target->resize(source_len);
memcpy(target->data(), source, source_len * sizeof(uint32_t));
return shaderc_spvc_status_success;
}
spvtools::Optimizer opt(source_env);
opt.SetMessageConsumer(std::bind(
consume_spirv_tools_message, context, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
if (source_env == SPV_ENV_WEBGPU_0 && target_env == SPV_ENV_VULKAN_1_1) {
opt.RegisterWebGPUToVulkanPasses();
} else if (source_env == SPV_ENV_VULKAN_1_1 &&
target_env == SPV_ENV_WEBGPU_0) {
opt.RegisterVulkanToWebGPUPasses();
} else {
shaderc_spvc::ErrorLog(context)
<< "No defined transformation between source and target execution "
"environments.";
return shaderc_spvc_status_transformation_error;
}
if (options->robust_buffer_access_pass) {
opt.RegisterPass(spvtools::CreateGraphicsRobustAccessPass());
}
if (!opt.Run(source, source_len, target)) {
shaderc_spvc::ErrorLog(context) << "Transformations between source and "
"target execution environments failed.";
return shaderc_spvc_status_transformation_error;
}
return shaderc_spvc_status_success;
}
shaderc_spvc_status validate_and_translate_spirv(
shaderc_spvc_context* context, const uint32_t* source, size_t source_len,
shaderc_spvc_compile_options_t options, std::vector<uint32_t>* target) {
shaderc_spvc_status status;
if (options->validate) {
status = validate_spirv(context, options->source_env, source, source_len);
if (status != shaderc_spvc_status_success) {
shaderc_spvc::ErrorLog(context) << "Validation of input source failed.";
return status;
}
}
status = translate_spirv(context, options->source_env, options->target_env,
source, source_len, options, target);
if (status != shaderc_spvc_status_success) return status;
if (options->validate && (options->source_env != options->target_env)) {
// Re-run validation on input if actually transformed.
status = validate_spirv(context, options->target_env, target->data(),
target->size());
if (status != shaderc_spvc_status_success) {
shaderc_spvc::ErrorLog(context)
<< "Validation of transformed source failed.";
return status;
}
}
return status;
}
shaderc_spvc_status generate_shader(spirv_cross::Compiler* compiler,
shaderc_spvc_compilation_result_t result) {
TRY_IF_EXCEPTIONS_ENABLED {
result->string_output = compiler->compile();
// An exception during compiling would crash (if exceptions off) or jump to
// the catch block (if exceptions on) so if we're here we know the compile
// worked.
return shaderc_spvc_status_success;
}
CATCH_IF_EXCEPTIONS_ENABLED(...) {
return shaderc_spvc_status_compilation_error;
}
}
shaderc_spvc_status generate_glsl_compiler(
const shaderc_spvc_context_t context, const uint32_t* source,
size_t source_len, shaderc_spvc_compile_options_t options) {
spirv_cross::CompilerGLSL* cross_compiler;
// spvc IR generation is under development, for now run spirv-cross
// compiler, unless explicitly requested.
// TODO (sarahM0): change the default to spvc IR generation when it's done
if (context->use_spvc_parser) {
shaderc_spvc_status status;
spirv_cross::ParsedIR ir;
status = generate_spvcir(context, &ir, source, source_len, options);
if (status != shaderc_spvc_status_success) {
shaderc_spvc::ErrorLog(context)
<< "Transformations between source and target execution environments "
"failed (spvc-ir-pass).";
return status;
} else {
cross_compiler = new (std::nothrow) spirv_cross::CompilerGLSL(ir);
}
} else {
cross_compiler =
new (std::nothrow) spirv_cross::CompilerGLSL(source, source_len);
}
if (!cross_compiler) {
shaderc_spvc::ErrorLog(context)
<< "Unable to initialize SPIRV-Cross GLSL compiler.";
return shaderc_spvc_status_compilation_error;
}
context->cross_compiler.reset(cross_compiler);
if (options->glsl.version == 0) {
// no version requested, was one detected in source?
options->glsl.version = cross_compiler->get_common_options().version;
if (options->glsl.version == 0) {
// no version detected in source, use default
options->glsl.version = DEFAULT_GLSL_VERSION;
} else {
// version detected implies ES also detected
options->glsl.es = cross_compiler->get_common_options().es;
}
}
// Override detected setting, if any.
if (options->force_es) options->glsl.es = options->forced_es_setting;
auto entry_points = cross_compiler->get_entry_points_and_stages();
spv::ExecutionModel model = spv::ExecutionModelMax;
if (!options->entry_point.empty()) {
// Make sure there is just one entry point with this name, or the stage is
// ambiguous.
uint32_t stage_count = 0;
for (auto& e : entry_points) {
if (e.name == options->entry_point) {
stage_count++;
model = e.execution_model;
}
}
if (stage_count != 1) {
context->cross_compiler.reset();
if (stage_count == 0) {
shaderc_spvc::ErrorLog(context)
<< "There is no entry point with name: " << options->entry_point;
} else {
shaderc_spvc::ErrorLog(context)
<< "There is more than one entry point with name: "
<< options->entry_point << ". Use --stage.";
}
return shaderc_spvc_status_compilation_error;
}
}
if (!options->entry_point.empty()) {
cross_compiler->set_entry_point(options->entry_point, model);
}
if (!options->glsl.vulkan_semantics) {
uint32_t sampler =
cross_compiler->build_dummy_sampler_for_combined_images();
if (sampler) {
// Set some defaults to make validation happy.
cross_compiler->set_decoration(sampler, spv::DecorationDescriptorSet, 0);
cross_compiler->set_decoration(sampler, spv::DecorationBinding, 0);
}
}
spirv_cross::ShaderResources res;
if (options->remove_unused_variables) {
auto active = cross_compiler->get_active_interface_variables();
res = cross_compiler->get_shader_resources(active);
cross_compiler->set_enabled_interface_variables(move(active));
} else {
res = cross_compiler->get_shader_resources();
}
if (options->flatten_ubo) {
for (auto& ubo : res.uniform_buffers)
cross_compiler->flatten_buffer_block(ubo.id);
for (auto& ubo : res.push_constant_buffers)
cross_compiler->flatten_buffer_block(ubo.id);
}
if (!options->glsl.vulkan_semantics) {
cross_compiler->build_combined_image_samplers();
// if (args.combined_samplers_inherit_bindings)
// spirv_cross_util::inherit_combined_sampler_bindings(*compiler);
// Give the remapped combined samplers new names.
for (auto& remap : cross_compiler->get_combined_image_samplers()) {
cross_compiler->set_name(
remap.combined_id,
spirv_cross::join("SPIRV_Cross_Combined",
cross_compiler->get_name(remap.image_id),
cross_compiler->get_name(remap.sampler_id)));
}
}
cross_compiler->set_common_options(options->glsl);
return shaderc_spvc_status_success;
}
shaderc_spvc_status generate_hlsl_compiler(
const shaderc_spvc_context_t context, const uint32_t* source,
size_t source_len, shaderc_spvc_compile_options_t options) {
spirv_cross::CompilerHLSL* cross_compiler;
// spvc IR generation is under development, for now run spirv-cross
// compiler, unless explicitly requested.
// TODO (sarahM0): change the default to spvc IR generation when it's done
if (context->use_spvc_parser) {
shaderc_spvc_status status;
spirv_cross::ParsedIR ir;
status = generate_spvcir(context, &ir, source, source_len, options);
if (status != shaderc_spvc_status_success) {
shaderc_spvc::ErrorLog(context)
<< "Transformations between source and target execution environments "
"failed (spvc-ir-pass).";
return status;
} else {
cross_compiler = new (std::nothrow) spirv_cross::CompilerHLSL(ir);
}
} else {
cross_compiler =
new (std::nothrow) spirv_cross::CompilerHLSL(source, source_len);
}
if (!cross_compiler) {
shaderc_spvc::ErrorLog(context)
<< "Unable to initialize SPIRV-Cross HLSL compiler.";
return shaderc_spvc_status_compilation_error;
}
context->cross_compiler.reset(cross_compiler);
cross_compiler->set_common_options(options->glsl);
cross_compiler->set_hlsl_options(options->hlsl);
return shaderc_spvc_status_success;
}
shaderc_spvc_status generate_msl_compiler(
const shaderc_spvc_context_t context, const uint32_t* source,
size_t source_len, shaderc_spvc_compile_options_t options) {
spirv_cross::CompilerMSL* cross_compiler;
// spvc IR generation is under development, for now run spirv-cross
// compiler, unless explicitly requested.
// TODO (sarahM0): change the default to spvc IR generation when it's done
if (context->use_spvc_parser) {
shaderc_spvc_status status;
spirv_cross::ParsedIR ir;
status = generate_spvcir(context, &ir, source, source_len, options);
if (status != shaderc_spvc_status_success) {
shaderc_spvc::ErrorLog(context)
<< "Transformations between source and target execution environments "
"failed (spvc-ir-pass).";
return status;
} else {
cross_compiler = new (std::nothrow) spirv_cross::CompilerMSL(ir);
}
} else {
cross_compiler =
new (std::nothrow) spirv_cross::CompilerMSL(source, source_len);
}
if (!cross_compiler) {
shaderc_spvc::ErrorLog(context)
<< "Unable to initialize SPIRV-Cross MSL compiler.";
return shaderc_spvc_status_compilation_error;
}
context->cross_compiler.reset(cross_compiler);
cross_compiler->set_common_options(options->glsl);
cross_compiler->set_msl_options(options->msl);
for (auto i : options->msl_discrete_descriptor_sets)
cross_compiler->add_discrete_descriptor_set(i);
return shaderc_spvc_status_success;
}
shaderc_spvc_status generate_vulkan_compiler(
const shaderc_spvc_context_t context, const uint32_t* source,
size_t source_len, shaderc_spvc_compile_options_t options) {
spirv_cross::CompilerReflection* cross_compiler;
// spvc IR generation is under development, for now run spirv-cross
// compiler, unless explicitly requested.
// TODO (sarahM0): change the default to spvc IR generation when it's done
if (context->use_spvc_parser) {
spirv_cross::ParsedIR ir;
shaderc_spvc_status status =
generate_spvcir(context, &ir, source, source_len, options);
if (status != shaderc_spvc_status_success) {
shaderc_spvc::ErrorLog(context)
<< "Transformations between source and target execution environments "
"failed (spvc-ir-pass).";
return status;
} else {
cross_compiler = new (std::nothrow) spirv_cross::CompilerReflection(ir);
}
} else {
cross_compiler =
new (std::nothrow) spirv_cross::CompilerReflection(source, source_len);
}
if (!cross_compiler) {
shaderc_spvc::ErrorLog(context)
<< "Unable to initialize SPIRV-Cross reflection compiler.";
return shaderc_spvc_status_compilation_error;
}
context->cross_compiler.reset(cross_compiler);
return shaderc_spvc_status_success;
}
shaderc_spvc_status generate_spvcir(const shaderc_spvc_context_t context,
spirv_cross::ParsedIR* ir,
const uint32_t* source, size_t source_len,
shaderc_spvc_compile_options_t options) {
if (context->use_spvc_parser) {
std::vector<uint32_t> binary_output;
spvtools::Optimizer opt(options->source_env);
opt.SetMessageConsumer(std::bind(
consume_spirv_tools_message, context, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
ir->spirv = std::vector<uint32_t>(source, source + source_len);
opt.RegisterPass(
spvtools::Optimizer::PassToken(std::unique_ptr<spvtools::opt::Pass>(
reinterpret_cast<spvtools::opt::Pass*>(
new spvtools::opt::SpvcIrPass(ir)))));
if (!opt.Run(source, source_len, &binary_output)) {
return shaderc_spvc_status_transformation_error;
}
}
return shaderc_spvc_status_success;
}
shaderc_spvc_status shaderc_spvc_decoration_to_spirv_cross_decoration(
const shaderc_spvc_decoration decoration,
spv::Decoration* spirv_cross_decoration_ptr) {
if (!spirv_cross_decoration_ptr) return shaderc_spvc_status_internal_error;
shaderc_spvc_status status = shaderc_spvc_status_success;
switch (decoration) {
case shaderc_spvc_decoration_specid:
*spirv_cross_decoration_ptr = spv::DecorationSpecId;
break;
case shaderc_spvc_decoration_block:
*spirv_cross_decoration_ptr = spv::DecorationBlock;
break;
case shaderc_spvc_decoration_rowmajor:
*spirv_cross_decoration_ptr = spv::DecorationRowMajor;
break;
case shaderc_spvc_decoration_colmajor:
*spirv_cross_decoration_ptr = spv::DecorationColMajor;
break;
case shaderc_spvc_decoration_arraystride:
*spirv_cross_decoration_ptr = spv::DecorationArrayStride;
break;
case shaderc_spvc_decoration_matrixstride:
*spirv_cross_decoration_ptr = spv::DecorationMatrixStride;
break;
case shaderc_spvc_decoration_builtin:
*spirv_cross_decoration_ptr = spv::DecorationBuiltIn;
break;
case shaderc_spvc_decoration_noperspective:
*spirv_cross_decoration_ptr = spv::DecorationNoPerspective;
break;
case shaderc_spvc_decoration_flat:
*spirv_cross_decoration_ptr = spv::DecorationFlat;
break;
case shaderc_spvc_decoration_centroid:
*spirv_cross_decoration_ptr = spv::DecorationCentroid;
break;
case shaderc_spvc_decoration_restrict:
*spirv_cross_decoration_ptr = spv::DecorationRestrict;
break;
case shaderc_spvc_decoration_aliased:
*spirv_cross_decoration_ptr = spv::DecorationAliased;
break;
case shaderc_spvc_decoration_nonwritable:
*spirv_cross_decoration_ptr = spv::DecorationNonWritable;
break;
case shaderc_spvc_decoration_nonreadable:
*spirv_cross_decoration_ptr = spv::DecorationNonReadable;
break;
case shaderc_spvc_decoration_uniform:
*spirv_cross_decoration_ptr = spv::DecorationUniform;
break;
case shaderc_spvc_decoration_location:
*spirv_cross_decoration_ptr = spv::DecorationLocation;
break;
case shaderc_spvc_decoration_component:
*spirv_cross_decoration_ptr = spv::DecorationComponent;
break;
case shaderc_spvc_decoration_index:
*spirv_cross_decoration_ptr = spv::DecorationIndex;
break;
case shaderc_spvc_decoration_binding:
*spirv_cross_decoration_ptr = spv::DecorationBinding;
break;
case shaderc_spvc_decoration_descriptorset:
*spirv_cross_decoration_ptr = spv::DecorationDescriptorSet;
break;
case shaderc_spvc_decoration_offset:
*spirv_cross_decoration_ptr = spv::DecorationOffset;
break;
case shaderc_spvc_decoration_nocontraction:
*spirv_cross_decoration_ptr = spv::DecorationNoContraction;
break;
default:
*spirv_cross_decoration_ptr = spv::DecorationMax;
status = shaderc_spvc_status_internal_error;
break;
}
return status;
}
} // namespace spvc_private