in tools/skslc/Main.cpp [515:801]
static ResultCode process_command(SkSpan<std::string> args) {
std::optional<bool> honorSettings;
std::vector<std::string> paths;
for (size_t i = 1; i < args.size(); ++i) {
const std::string& arg = args[i];
if (arg == "--settings") {
if (!set_flag(&honorSettings, "settings", true)) {
return ResultCode::kInputError;
}
} else if (arg == "--nosettings") {
if (!set_flag(&honorSettings, "settings", false)) {
return ResultCode::kInputError;
}
} else if (!skstd::starts_with(arg, "--")) {
paths.push_back(arg);
} else {
show_usage();
return ResultCode::kInputError;
}
}
if (paths.size() != 2) {
show_usage();
return ResultCode::kInputError;
}
if (!honorSettings.has_value()) {
honorSettings = true;
}
const std::string& inputPath = paths[0];
const std::string& outputPath = paths[1];
SkSL::ProgramKind kind;
if (skstd::ends_with(inputPath, ".vert")) {
kind = SkSL::ProgramKind::kVertex;
} else if (skstd::ends_with(inputPath, ".frag") || skstd::ends_with(inputPath, ".sksl")) {
kind = SkSL::ProgramKind::kFragment;
} else if (skstd::ends_with(inputPath, ".mvert")) {
kind = SkSL::ProgramKind::kMeshVertex;
} else if (skstd::ends_with(inputPath, ".mfrag")) {
kind = SkSL::ProgramKind::kMeshFragment;
} else if (skstd::ends_with(inputPath, ".compute")) {
kind = SkSL::ProgramKind::kCompute;
} else if (skstd::ends_with(inputPath, ".rtb")) {
kind = SkSL::ProgramKind::kRuntimeBlender;
} else if (skstd::ends_with(inputPath, ".rtcf")) {
kind = SkSL::ProgramKind::kRuntimeColorFilter;
} else if (skstd::ends_with(inputPath, ".rts")) {
kind = SkSL::ProgramKind::kRuntimeShader;
} else if (skstd::ends_with(inputPath, ".privrts")) {
kind = SkSL::ProgramKind::kPrivateRuntimeShader;
} else {
printf("input filename must end in '.vert', '.frag', '.mvert', '.mfrag', '.compute', "
"'.rtb', '.rtcf', '.rts', '.privrts', or '.sksl'\n");
return ResultCode::kInputError;
}
std::ifstream in(inputPath);
std::string text((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
if (in.rdstate()) {
printf("error reading '%s'\n", inputPath.c_str());
return ResultCode::kInputError;
}
SkSL::ProgramSettings settings;
const SkSL::ShaderCaps* caps = SkSL::ShaderCapsFactory::Standalone();
std::unique_ptr<SkSL::DebugTracePriv> debugTrace;
if (*honorSettings) {
if (!detect_shader_settings(text, &settings, &caps, &debugTrace)) {
return ResultCode::kInputError;
}
}
// This tells the compiler where the rt-flip uniform will live should it be required. For
// testing purposes we don't care where that is, but the compiler will report an error if we
// leave them at their default invalid values, or if the offset overlaps another uniform.
settings.fRTFlipOffset = 16384;
settings.fRTFlipSet = 0;
settings.fRTFlipBinding = 0;
auto emitCompileError = [&](const char* errorText) {
// Overwrite the compiler output, if any, with an error message.
SkSL::FileOutputStream errorStream(outputPath.c_str());
errorStream.writeText("### Compilation failed:\n\n");
errorStream.writeText(errorText);
errorStream.close();
// Also emit the error directly to stdout.
puts(errorText);
};
auto compileProgram = [&](const auto& writeFn) -> ResultCode {
SkSL::FileOutputStream out(outputPath.c_str());
SkSL::Compiler compiler;
if (!out.isValid()) {
printf("error writing '%s'\n", outputPath.c_str());
return ResultCode::kOutputError;
}
std::unique_ptr<SkSL::Program> program = compiler.convertProgram(kind, text, settings);
if (!program || !writeFn(compiler, caps, *program, out)) {
out.close();
emitCompileError(compiler.errorText().c_str());
return ResultCode::kCompileError;
}
if (!out.close()) {
printf("error writing '%s'\n", outputPath.c_str());
return ResultCode::kOutputError;
}
return ResultCode::kSuccess;
};
auto compileProgramAsRuntimeShader = [&](const auto& writeFn) -> ResultCode {
if (kind == SkSL::ProgramKind::kVertex) {
emitCompileError("Runtime shaders do not support vertex programs\n");
return ResultCode::kCompileError;
}
if (kind == SkSL::ProgramKind::kFragment) {
// Handle .sksl and .frag programs as runtime shaders.
kind = SkSL::ProgramKind::kPrivateRuntimeShader;
}
return compileProgram(writeFn);
};
if (skstd::ends_with(outputPath, ".spirv")) {
return compileProgram([](SkSL::Compiler& compiler,
const SkSL::ShaderCaps* shaderCaps,
SkSL::Program& program,
SkSL::OutputStream& out) {
return SkSL::ToSPIRV(program, shaderCaps, out, SkSL::ValidateSPIRVAndDissassemble);
});
} else if (skstd::ends_with(outputPath, ".asm.frag") ||
skstd::ends_with(outputPath, ".asm.vert") ||
skstd::ends_with(outputPath, ".asm.comp")) {
return compileProgram(
[](SkSL::Compiler& compiler,
const SkSL::ShaderCaps* shaderCaps,
SkSL::Program& program,
SkSL::OutputStream& out) {
// Compile program to SPIR-V assembly in a string-stream.
SkSL::StringStream assembly;
if (!SkSL::ToSPIRV(program,
shaderCaps,
assembly,
SkSL::ValidateSPIRVAndDissassemble)) {
return false;
}
// Convert the string-stream to a SPIR-V disassembly.
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_0);
const std::string& spirv(assembly.str());
std::string disassembly;
uint32_t options = spvtools::SpirvTools::kDefaultDisassembleOption;
options |= SPV_BINARY_TO_TEXT_OPTION_INDENT;
if (!tools.Disassemble((const uint32_t*)spirv.data(),
spirv.size() / 4,
&disassembly,
options)) {
return false;
}
// Finally, write the disassembly to our output stream.
out.write(disassembly.data(), disassembly.size());
return true;
});
} else if (skstd::ends_with(outputPath, ".glsl")) {
return compileProgram([](SkSL::Compiler& compiler,
const SkSL::ShaderCaps* shaderCaps,
SkSL::Program& program,
SkSL::OutputStream& out) {
return SkSL::ToGLSL(program, shaderCaps, out, SkSL::PrettyPrint::kYes);
});
} else if (skstd::ends_with(outputPath, ".metal")) {
return compileProgram([](SkSL::Compiler& compiler,
const SkSL::ShaderCaps* shaderCaps,
SkSL::Program& program,
SkSL::OutputStream& out) {
return SkSL::ToMetal(program, shaderCaps, out, SkSL::PrettyPrint::kYes);
});
} else if (skstd::ends_with(outputPath, ".hlsl")) {
return compileProgram([](SkSL::Compiler& compiler,
const SkSL::ShaderCaps* shaderCaps,
SkSL::Program& program,
SkSL::OutputStream& out) {
return SkSL::ToHLSL(program, shaderCaps, out, SkSL::ValidateSPIRVAndDissassemble);
});
} else if (skstd::ends_with(outputPath, ".wgsl")) {
return compileProgram([](SkSL::Compiler& compiler,
const SkSL::ShaderCaps* shaderCaps,
SkSL::Program& program,
SkSL::OutputStream& out) {
return SkSL::ToWGSL(program,
shaderCaps,
out,
SkSL::PrettyPrint::kYes,
SkSL::IncludeSyntheticCode::kYes,
SkSL::ValidateWGSL);
});
} else if (skstd::ends_with(outputPath, ".skrp")) {
settings.fMaxVersionAllowed = SkSL::Version::k300;
return compileProgramAsRuntimeShader([&](SkSL::Compiler& compiler,
const SkSL::ShaderCaps* shaderCaps,
SkSL::Program& program,
SkSL::OutputStream& out) {
SkSL::DebugTracePriv skrpDebugTrace;
const SkSL::FunctionDeclaration* main = program.getFunction("main");
if (!main) {
compiler.errorReporter().error({}, "code has no entrypoint");
return false;
}
bool wantTraceOps = (debugTrace != nullptr);
std::unique_ptr<SkSL::RP::Program> rasterProg = SkSL::MakeRasterPipelineProgram(
program, *main->definition(), &skrpDebugTrace, wantTraceOps);
if (!rasterProg) {
compiler.errorReporter().error({}, "code is not supported");
return false;
}
rasterProg->dump(as_SkWStream(out).get(), /*writeInstructionCount=*/true);
return true;
});
} else if (skstd::ends_with(outputPath, ".stage")) {
return compileProgram([](SkSL::Compiler&,
const SkSL::ShaderCaps* shaderCaps,
SkSL::Program& program,
SkSL::OutputStream& out) {
class Callbacks : public SkSL::PipelineStage::Callbacks {
public:
std::string getMangledName(const char* name) override {
return std::string(name) + "_0";
}
std::string declareUniform(const SkSL::VarDeclaration* decl) override {
fOutput += decl->description();
return std::string(decl->var()->name());
}
void defineFunction(const char* decl, const char* body, bool /*isMain*/) override {
fOutput += std::string(decl) + '{' + body + '}';
}
void declareFunction(const char* decl) override { fOutput += decl; }
void defineStruct(const char* definition) override { fOutput += definition; }
void declareGlobal(const char* declaration) override { fOutput += declaration; }
std::string sampleShader(int index, std::string coords) override {
return "child_" + std::to_string(index) + ".eval(" + coords + ')';
}
std::string sampleColorFilter(int index, std::string color) override {
return "child_" + std::to_string(index) + ".eval(" + color + ')';
}
std::string sampleBlender(int index, std::string src, std::string dst) override {
return "child_" + std::to_string(index) + ".eval(" + src + ", " + dst + ')';
}
std::string toLinearSrgb(std::string color) override {
return "toLinearSrgb(" + color + ')';
}
std::string fromLinearSrgb(std::string color) override {
return "fromLinearSrgb(" + color + ')';
}
std::string fOutput;
};
// The .stage output looks almost like valid SkSL, but not quite.
// The PipelineStageGenerator bridges the gap between the SkSL in `program`,
// and the C++ FP builder API (see GrSkSLFP). In that API, children don't need
// to be declared (so they don't emit declarations here). Children are sampled
// by index, not name - so all children here are just "child_N".
// The input color and coords have names in the original SkSL (as parameters to
// main), but those are ignored here. References to those variables become
// "_coords" and "_inColor". At runtime, those variable names are irrelevant
// when the new SkSL is emitted inside the FP - references to those variables
// are replaced with strings from EmitArgs, and might be varyings or differently
// named parameters.
Callbacks callbacks;
SkSL::PipelineStage::ConvertProgram(program, "_coords", "_inColor",
"_canvasColor", &callbacks);
out.writeString(SkShaderUtils::PrettyPrint(callbacks.fOutput));
return true;
});
} else {
printf("expected output path to end with one of: .glsl, .html, .metal, .hlsl, .wgsl, "
".spirv, .asm.vert, .asm.frag, .asm.comp, .skrp, .stage (got '%s')\n",
outputPath.c_str());
return ResultCode::kConfigurationError;
}
return ResultCode::kSuccess;
}