blob: 111b18b5ad7033f7cc46d1f11e0a1d33cd864697 [file] [log] [blame]
// 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 "file_compiler.h"
#include <cassert>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#if SHADERC_ENABLE_WGSL_OUTPUT == 1
#include "tint/tint.h"
#endif // SHADERC_ENABLE_WGSL_OUTPUT==1
#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;
// A helper function to emit SPIR-V binary code as a list of hex numbers in
// text form. Returns true if a non-empty compilation result is emitted
// successfully. Return false if nothing should be emitted, either because the
// compilation result is empty, or the compilation output is not SPIR-V binary
// code.
template <typename CompilationResultType>
bool EmitSpirvBinaryAsCommaSeparatedNumbers(const CompilationResultType& result,
std::ostream* out) {
// Return early if the compilation output is not in SPIR-V binary code form.
if (!std::is_same<CompilationResultType,
shaderc::SpvCompilationResult>::value)
return false;
// Return early if the compilation result is empty.
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 << ",";
// Break line for every four words.
if (counter % 4 == 0) {
*out << std::endl;
}
*out << "0x" << std::setw(8) << *RI;
}
out->flags(output_stream_flag_cache);
return true;
}
} // anonymous namespace
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 == "-") {
// If the input file was stdin, we want to output errors as <stdin>.
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_));
// Get a reference to the dependency trace before we pass the ownership to
// shaderc::CompileOptions.
const auto& used_source_files = includer->file_path_trace();
options_.SetIncluder(std::move(includer));
if (input_file.stage == shaderc_spirv_assembly) {
// Only act if the requested target is SPIR-V binary.
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;
}
}
// Set the language. Since we only use the options object in this
// method, then it's ok to always set it without resetting it after
// compilation. A subsequent compilation will set it again anyway.
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;
// Handle the error message for failing to deduce the shader kind.
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;
}
// Get a string_piece which refers to the normal compilation output for now.
// This string_piece might be redirected to the dependency info to be dumped
// later, if the handler is instantiated to dump as normal compilation output,
// and the original compilation output should be blocked. Otherwise it won't
// be touched. The main output stream dumps this string_piece later.
string_piece compilation_output(
reinterpret_cast<const char*>(result.cbegin()),
reinterpret_cast<const char*>(result.cend()));
// If we have dependency info dumping handler instantiated, we should dump
// dependency info first. This may redirect the compilation output
// string_piece to dependency info.
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()) {
// If the potential_dependency_info_output string is not empty, it means
// we should dump dependency info as normal compilation output. Redirect
// the compilation output string_piece to the dependency info stored in
// potential_dependency_info_output to make it happen.
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()) {
// An error message has already been emitted to the stderr stream.
return false;
}
// Write compilation output to output file. If an output format for SPIR-V
// binary code is specified, it is handled here.
switch (binary_emission_format_) {
case SpirvBinaryEmissionFormat::Unspecified:
case SpirvBinaryEmissionFormat::Binary:
// The output format is unspecified or specified as binary output.
// On Windows, the output stream must be set to binary mode. By
// default the standard output stream is set to text mode, which
// translates newlines (\n) to carriage-return newline pairs
// (\r\n).
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:
// The output format is specified to be a list of hex numbers, the
// compilation output must be in SPIR-V binary code form.
assert(output_type_ == OutputType::SpirvBinary);
if (EmitSpirvBinaryAsCommaSeparatedNumbers(result, out)) {
// Only emits the end-of-line character when the emitted compilation
// result is not empty.
*out << std::endl;
}
break;
case SpirvBinaryEmissionFormat::CInitList:
// The output format is specified to be a C-style initializer list, the
// compilation output must be in SPIR-V binary code form.
assert(output_type_ == OutputType::SpirvBinary);
if (result.begin() != result.end()) {
// Only emits the '{' when the compilation result is not empty.
*out << "{";
}
if (EmitSpirvBinaryAsCommaSeparatedNumbers(result, out)) {
// Only emits the end-of-line character when the emitted compilation
// result is not empty.
*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 // SHADERC_ENABLE_WGSL_OUTPUT==1
break;
}
}
}
// Write error message to std::cerr.
std::cerr << result.GetErrorMessage();
if (out && out->fail()) {
// Something wrong happened on output.
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 we are outputting many object files, we cannot specify -o. Also
// if we are preprocessing multiple files they must be to stdout.
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 we have dependency info dumping handler instantiated, we should check
// its validity.
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 the output format is specified to be a binary, a list of hex numbers or
// a C-style initializer list, the output must be in SPIR-V binary code form.
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:
// The compiler should never be here at runtime. This case is added to
// complete the switch cases.
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;
}
} // namesapce glslc