#include "file_compiler.h"
#include <cassert>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#if SHADERC_ENABLE_WGSL_OUTPUT == 1
#include "tint/tint.h"
#endif
#include "file.h"
#include "file_includer.h"
#include "shader_stage.h"
#include "libshaderc_util/io_shaderc.h"
#include "libshaderc_util/message.h"
namespace {
using shaderc_util::string_piece;
template <typename CompilationResultType>
bool EmitSpirvBinaryAsCommaSeparatedNumbers(const CompilationResultType& result,
std::ostream* out) {
if (!std::is_same<CompilationResultType,
shaderc::SpvCompilationResult>::value)
return false;
if (result.cbegin() == result.cend()) return false;
std::ios::fmtflags output_stream_flag_cache(out->flags());
*out << std::hex << std::setfill('0');
auto RI = result.cbegin();
*out << "0x" << std::setw(8) << *RI++;
for (size_t counter = 1; RI != result.cend(); RI++, counter++) {
*out << ",";
if (counter % 4 == 0) {
*out << std::endl;
}
*out << "0x" << std::setw(8) << *RI;
}
out->flags(output_stream_flag_cache);
return true;
}
}
namespace glslc {
bool FileCompiler::CompileShaderFile(const InputFileSpec& input_file) {
std::vector<char> input_data;
std::string path = input_file.name;
if (!shaderc_util::ReadFile(path, &input_data)) {
return false;
}
std::string output_file_name = GetOutputFileName(input_file.name);
string_piece error_file_name = input_file.name;
if (error_file_name == "-") {
error_file_name = "<stdin>";
}
string_piece source_string = "";
if (!input_data.empty()) {
source_string = {&input_data.front(),
&input_data.front() + input_data.size()};
}
std::unique_ptr<FileIncluder> includer(
new FileIncluder(&include_file_finder_));
const auto& used_source_files = includer->file_path_trace();
options_.SetIncluder(std::move(includer));
if (input_file.stage == shaderc_spirv_assembly) {
if (output_type_ == OutputType::SpirvBinary) {
const auto result =
compiler_.AssembleToSpv(source_string.data(), source_string.size());
return EmitCompiledResult(result, input_file.name, output_file_name,
error_file_name, used_source_files);
} else {
return true;
}
}
options_.SetSourceLanguage(input_file.language);
switch (output_type_) {
case OutputType::SpirvBinary: {
const auto result = compiler_.CompileGlslToSpv(
source_string.data(), source_string.size(), input_file.stage,
error_file_name.data(), input_file.entry_point_name.c_str(),
options_);
return EmitCompiledResult(result, input_file.name, output_file_name,
error_file_name, used_source_files);
}
case OutputType::SpirvAssemblyText: {
const auto result = compiler_.CompileGlslToSpvAssembly(
source_string.data(), source_string.size(), input_file.stage,
error_file_name.data(), input_file.entry_point_name.c_str(),
options_);
return EmitCompiledResult(result, input_file.name, output_file_name,
error_file_name, used_source_files);
}
case OutputType::PreprocessedText: {
const auto result = compiler_.PreprocessGlsl(
source_string.data(), source_string.size(), input_file.stage,
error_file_name.data(), options_);
return EmitCompiledResult(result, input_file.name, output_file_name,
error_file_name, used_source_files);
}
}
return false;
}
template <typename CompilationResultType>
bool FileCompiler::EmitCompiledResult(
const CompilationResultType& result, const std::string& input_file,
const std::string& output_file_name, string_piece error_file_name,
const std::unordered_set<std::string>& used_source_files) {
total_errors_ += result.GetNumErrors();
total_warnings_ += result.GetNumWarnings();
bool compilation_success =
result.GetCompilationStatus() == shaderc_compilation_status_success;
if (result.GetCompilationStatus() ==
shaderc_compilation_status_invalid_stage) {
auto glsl_or_hlsl_extension = GetGlslOrHlslExtension(error_file_name);
if (glsl_or_hlsl_extension != "") {
std::cerr << "glslc: error: "
<< "'" << error_file_name << "': "
<< "." << glsl_or_hlsl_extension
<< " file encountered but no -fshader-stage specified ahead";
} else if (error_file_name == "<stdin>") {
std::cerr
<< "glslc: error: '-': -fshader-stage required when input is from "
"standard "
"input \"-\"";
} else {
std::cerr << "glslc: error: "
<< "'" << error_file_name << "': "
<< "file not recognized: File format not recognized";
}
std::cerr << "\n";
return false;
}
string_piece compilation_output(
reinterpret_cast<const char*>(result.cbegin()),
reinterpret_cast<const char*>(result.cend()));
std::string potential_dependency_info_output;
if (dependency_info_dumping_handler_) {
if (!dependency_info_dumping_handler_->DumpDependencyInfo(
GetCandidateOutputFileName(input_file), error_file_name.data(),
&potential_dependency_info_output, used_source_files)) {
return false;
}
if (!potential_dependency_info_output.empty()) {
compilation_output = potential_dependency_info_output;
}
}
std::ostream* out = nullptr;
std::ofstream potential_file_stream;
if (compilation_success) {
out = shaderc_util::GetOutputStream(output_file_name,
&potential_file_stream, &std::cerr);
if (!out || out->fail()) {
return false;
}
switch (binary_emission_format_) {
case SpirvBinaryEmissionFormat::Unspecified:
case SpirvBinaryEmissionFormat::Binary:
if (out == &std::cout) shaderc_util::FlushAndSetBinaryModeOnStdout();
out->write(compilation_output.data(), compilation_output.size());
if (out == &std::cout) shaderc_util::FlushAndSetTextModeOnStdout();
break;
case SpirvBinaryEmissionFormat::Numbers:
assert(output_type_ == OutputType::SpirvBinary);
if (EmitSpirvBinaryAsCommaSeparatedNumbers(result, out)) {
*out << std::endl;
}
break;
case SpirvBinaryEmissionFormat::CInitList:
assert(output_type_ == OutputType::SpirvBinary);
if (result.begin() != result.end()) {
*out << "{";
}
if (EmitSpirvBinaryAsCommaSeparatedNumbers(result, out)) {
*out << "}" << std::endl;
}
break;
case SpirvBinaryEmissionFormat::WGSL: {
#if SHADERC_ENABLE_WGSL_OUTPUT == 1
tint::Context ctx;
tint::reader::spirv::Parser spv_reader(
&ctx, std::vector<uint32_t>(result.begin(), result.end()));
if (!spv_reader.Parse()) {
std::cout << "error: failed to convert SPIR-V binary to WGSL: "
<< spv_reader.error() << std::endl;
return false;
}
tint::writer::wgsl::Generator wgsl_writer(spv_reader.module());
if (!wgsl_writer.Generate()) {
std::cout << "error: failed to convert to WGSL: "
<< wgsl_writer.error() << std::endl;
return false;
}
*out << wgsl_writer.result();
#endif
break;
}
}
}
std::cerr << result.GetErrorMessage();
if (out && out->fail()) {
if (out == &std::cout) {
std::cerr << "glslc: error: error writing to standard output"
<< std::endl;
} else {
std::cerr << "glslc: error: error writing to output file: '"
<< output_file_name_ << "'" << std::endl;
}
return false;
}
return compilation_success;
}
void FileCompiler::AddIncludeDirectory(const std::string& path) {
include_file_finder_.search_path().push_back(path);
}
void FileCompiler::SetIndividualCompilationFlag() {
if (output_type_ != OutputType::SpirvAssemblyText) {
needs_linking_ = false;
file_extension_ = ".spv";
}
}
void FileCompiler::SetDisassemblyFlag() {
if (!PreprocessingOnly()) {
output_type_ = OutputType::SpirvAssemblyText;
needs_linking_ = false;
file_extension_ = ".spvasm";
}
}
void FileCompiler::SetPreprocessingOnlyFlag() {
output_type_ = OutputType::PreprocessedText;
needs_linking_ = false;
if (output_file_name_.empty()) {
output_file_name_ = "-";
}
}
bool FileCompiler::ValidateOptions(size_t num_files) {
if (num_files == 0) {
std::cerr << "glslc: error: no input files" << std::endl;
return false;
}
if (num_files > 1 && needs_linking_) {
std::cerr << "glslc: error: linking multiple files is not supported yet. "
"Use -c to compile files individually."
<< std::endl;
return false;
}
if (num_files > 1 && ((!PreprocessingOnly() && !needs_linking_ &&
!output_file_name_.empty()) ||
(PreprocessingOnly() && output_file_name_ != "-"))) {
std::cerr << "glslc: error: cannot specify -o when generating multiple"
" output files"
<< std::endl;
return false;
}
if (dependency_info_dumping_handler_) {
std::string dependency_info_dumping_hander_error_msg;
if (!dependency_info_dumping_handler_->IsValid(
&dependency_info_dumping_hander_error_msg, num_files)) {
std::cerr << "glslc: error: " << dependency_info_dumping_hander_error_msg
<< std::endl;
return false;
}
}
if (binary_emission_format_ != SpirvBinaryEmissionFormat::Unspecified) {
if (output_type_ != OutputType::SpirvBinary) {
std::cerr << "glslc: error: cannot emit output as a ";
switch (binary_emission_format_) {
case SpirvBinaryEmissionFormat::Binary:
std::cerr << "binary";
break;
case SpirvBinaryEmissionFormat::Numbers:
std::cerr << "list of hex numbers";
break;
case SpirvBinaryEmissionFormat::CInitList:
std::cerr << "C-style initializer list";
break;
case SpirvBinaryEmissionFormat::WGSL:
std::cerr << "WGSL source program";
break;
case SpirvBinaryEmissionFormat::Unspecified:
break;
}
std::cerr << " when only preprocessing the source" << std::endl;
return false;
}
if (dependency_info_dumping_handler_ &&
dependency_info_dumping_handler_->DumpingAsCompilationOutput()) {
std::cerr << "glslc: error: cannot dump dependency info when specifying "
"any binary output format"
<< std::endl;
return false;
}
}
if (binary_emission_format_ == SpirvBinaryEmissionFormat::WGSL) {
#if SHADERC_ENABLE_WGSL_OUTPUT != 1
std::cerr << "glslc: error: can't output WGSL: glslc was built without "
"WGSL output support"
<< std::endl;
return false;
#endif
}
return true;
}
void FileCompiler::OutputMessages() {
shaderc_util::OutputMessages(&std::cerr, total_warnings_, total_errors_);
}
std::string FileCompiler::GetOutputFileName(std::string input_filename) {
if (output_file_name_.empty()) {
return needs_linking_ ? std::string("a.spv")
: GetCandidateOutputFileName(input_filename);
} else {
return output_file_name_.str();
}
}
std::string FileCompiler::GetCandidateOutputFileName(
std::string input_filename) {
if (!output_file_name_.empty() && !PreprocessingOnly()) {
return output_file_name_.str();
}
std::string extension = file_extension_;
if (PreprocessingOnly() || needs_linking_) {
extension = ".spv";
}
std::string candidate_output_file_name =
IsStageFile(input_filename)
? shaderc_util::GetBaseFileName(input_filename) + extension
: shaderc_util::GetBaseFileName(
input_filename.substr(0, input_filename.find_last_of('.')) +
extension);
return candidate_output_file_name;
}
}