1/* 2 * Copyright 2016 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#define SK_OPTS_NS skslc_standalone 9#include "src/core/SkOpts.h" 10#include "src/opts/SkChecksum_opts.h" 11#include "src/opts/SkVM_opts.h" 12 13#include "src/gpu/GrShaderUtils.h" 14#include "src/sksl/SkSLCompiler.h" 15#include "src/sksl/SkSLDehydrator.h" 16#include "src/sksl/SkSLFileOutputStream.h" 17#include "src/sksl/SkSLStringStream.h" 18#include "src/sksl/SkSLUtil.h" 19#include "src/sksl/codegen/SkSLPipelineStageCodeGenerator.h" 20#include "src/sksl/codegen/SkSLVMCodeGenerator.h" 21#include "src/sksl/ir/SkSLUnresolvedFunction.h" 22#include "src/sksl/ir/SkSLVarDeclarations.h" 23 24#include "spirv-tools/libspirv.hpp" 25 26#include <fstream> 27#include <limits.h> 28#include <stdarg.h> 29#include <stdio.h> 30 31void SkDebugf(const char format[], ...) { 32 va_list args; 33 va_start(args, format); 34 vfprintf(stderr, format, args); 35 va_end(args); 36} 37 38namespace SkOpts { 39 decltype(hash_fn) hash_fn = skslc_standalone::hash_fn; 40 decltype(interpret_skvm) interpret_skvm = skslc_standalone::interpret_skvm; 41} 42 43enum class ResultCode { 44 kSuccess = 0, 45 kCompileError = 1, 46 kInputError = 2, 47 kOutputError = 3, 48 kConfigurationError = 4, 49}; 50 51static std::unique_ptr<SkWStream> as_SkWStream(SkSL::OutputStream& s) { 52 struct Adapter : public SkWStream { 53 public: 54 Adapter(SkSL::OutputStream& out) : fOut(out), fBytesWritten(0) {} 55 56 bool write(const void* buffer, size_t size) override { 57 fOut.write(buffer, size); 58 fBytesWritten += size; 59 return true; 60 } 61 void flush() override {} 62 size_t bytesWritten() const override { return fBytesWritten; } 63 64 private: 65 SkSL::OutputStream& fOut; 66 size_t fBytesWritten; 67 }; 68 69 return std::make_unique<Adapter>(s); 70} 71 72// Given the path to a file (e.g. src/gpu/effects/GrFooFragmentProcessor.fp) and the expected 73// filename prefix and suffix (e.g. "Gr" and ".fp"), returns the "base name" of the 74// file (in this case, 'FooFragmentProcessor'). If no match, returns the empty string. 75static SkSL::String base_name(const SkSL::String& fpPath, const char* prefix, const char* suffix) { 76 SkSL::String result; 77 const char* end = &*fpPath.end(); 78 const char* fileName = end; 79 // back up until we find a slash 80 while (fileName != fpPath && '/' != *(fileName - 1) && '\\' != *(fileName - 1)) { 81 --fileName; 82 } 83 if (!strncmp(fileName, prefix, strlen(prefix)) && 84 !strncmp(end - strlen(suffix), suffix, strlen(suffix))) { 85 result.append(fileName + strlen(prefix), end - fileName - strlen(prefix) - strlen(suffix)); 86 } 87 return result; 88} 89 90// Given a string containing an SkSL program, searches for a #pragma settings comment, like so: 91// /*#pragma settings Default Sharpen*/ 92// The passed-in Settings object will be updated accordingly. Any number of options can be provided. 93static bool detect_shader_settings(const SkSL::String& text, 94 SkSL::Program::Settings* settings, 95 const SkSL::ShaderCapsClass** caps, 96 std::unique_ptr<SkSL::SkVMDebugInfo>* debugInfo) { 97 using Factory = SkSL::ShaderCapsFactory; 98 99 // Find a matching comment and isolate the name portion. 100 static constexpr char kPragmaSettings[] = "/*#pragma settings "; 101 const char* settingsPtr = strstr(text.c_str(), kPragmaSettings); 102 if (settingsPtr != nullptr) { 103 // Subtract one here in order to preserve the leading space, which is necessary to allow 104 // consumeSuffix to find the first item. 105 settingsPtr += strlen(kPragmaSettings) - 1; 106 107 const char* settingsEnd = strstr(settingsPtr, "*/"); 108 if (settingsEnd != nullptr) { 109 SkSL::String settingsText{settingsPtr, size_t(settingsEnd - settingsPtr)}; 110 111 // Apply settings as requested. Since they can come in any order, repeat until we've 112 // consumed them all. 113 for (;;) { 114 const size_t startingLength = settingsText.length(); 115 116 if (settingsText.consumeSuffix(" AddAndTrueToLoopCondition")) { 117 static auto s_addAndTrueCaps = Factory::AddAndTrueToLoopCondition(); 118 *caps = s_addAndTrueCaps.get(); 119 } 120 if (settingsText.consumeSuffix(" CannotUseFractForNegativeValues")) { 121 static auto s_negativeFractCaps = Factory::CannotUseFractForNegativeValues(); 122 *caps = s_negativeFractCaps.get(); 123 } 124 if (settingsText.consumeSuffix(" CannotUseFragCoord")) { 125 static auto s_noFragCoordCaps = Factory::CannotUseFragCoord(); 126 *caps = s_noFragCoordCaps.get(); 127 } 128 if (settingsText.consumeSuffix(" CannotUseMinAndAbsTogether")) { 129 static auto s_minAbsCaps = Factory::CannotUseMinAndAbsTogether(); 130 *caps = s_minAbsCaps.get(); 131 } 132 if (settingsText.consumeSuffix(" Default")) { 133 static auto s_defaultCaps = Factory::Default(); 134 *caps = s_defaultCaps.get(); 135 } 136 if (settingsText.consumeSuffix(" EmulateAbsIntFunction")) { 137 static auto s_emulateAbsIntCaps = Factory::EmulateAbsIntFunction(); 138 *caps = s_emulateAbsIntCaps.get(); 139 } 140 if (settingsText.consumeSuffix(" FramebufferFetchSupport")) { 141 static auto s_fbFetchSupport = Factory::FramebufferFetchSupport(); 142 *caps = s_fbFetchSupport.get(); 143 } 144 if (settingsText.consumeSuffix(" IncompleteShortIntPrecision")) { 145 static auto s_incompleteShortIntCaps = Factory::IncompleteShortIntPrecision(); 146 *caps = s_incompleteShortIntCaps.get(); 147 } 148 if (settingsText.consumeSuffix(" MustGuardDivisionEvenAfterExplicitZeroCheck")) { 149 static auto s_div0Caps = Factory::MustGuardDivisionEvenAfterExplicitZeroCheck(); 150 *caps = s_div0Caps.get(); 151 } 152 if (settingsText.consumeSuffix(" MustForceNegatedAtanParamToFloat")) { 153 static auto s_negativeAtanCaps = Factory::MustForceNegatedAtanParamToFloat(); 154 *caps = s_negativeAtanCaps.get(); 155 } 156 if (settingsText.consumeSuffix(" MustForceNegatedLdexpParamToMultiply")) { 157 static auto s_negativeLdexpCaps = 158 Factory::MustForceNegatedLdexpParamToMultiply(); 159 *caps = s_negativeLdexpCaps.get(); 160 } 161 if (settingsText.consumeSuffix(" RemovePowWithConstantExponent")) { 162 static auto s_powCaps = Factory::RemovePowWithConstantExponent(); 163 *caps = s_powCaps.get(); 164 } 165 if (settingsText.consumeSuffix(" RewriteDoWhileLoops")) { 166 static auto s_rewriteLoopCaps = Factory::RewriteDoWhileLoops(); 167 *caps = s_rewriteLoopCaps.get(); 168 } 169 if (settingsText.consumeSuffix(" RewriteSwitchStatements")) { 170 static auto s_rewriteSwitchCaps = Factory::RewriteSwitchStatements(); 171 *caps = s_rewriteSwitchCaps.get(); 172 } 173 if (settingsText.consumeSuffix(" RewriteMatrixVectorMultiply")) { 174 static auto s_rewriteMatVecMulCaps = Factory::RewriteMatrixVectorMultiply(); 175 *caps = s_rewriteMatVecMulCaps.get(); 176 } 177 if (settingsText.consumeSuffix(" RewriteMatrixComparisons")) { 178 static auto s_rewriteMatrixComparisons = Factory::RewriteMatrixComparisons(); 179 *caps = s_rewriteMatrixComparisons.get(); 180 } 181 if (settingsText.consumeSuffix(" ShaderDerivativeExtensionString")) { 182 static auto s_derivativeCaps = Factory::ShaderDerivativeExtensionString(); 183 *caps = s_derivativeCaps.get(); 184 } 185 if (settingsText.consumeSuffix(" UnfoldShortCircuitAsTernary")) { 186 static auto s_ternaryCaps = Factory::UnfoldShortCircuitAsTernary(); 187 *caps = s_ternaryCaps.get(); 188 } 189 if (settingsText.consumeSuffix(" UsesPrecisionModifiers")) { 190 static auto s_precisionCaps = Factory::UsesPrecisionModifiers(); 191 *caps = s_precisionCaps.get(); 192 } 193 if (settingsText.consumeSuffix(" Version110")) { 194 static auto s_version110Caps = Factory::Version110(); 195 *caps = s_version110Caps.get(); 196 } 197 if (settingsText.consumeSuffix(" Version450Core")) { 198 static auto s_version450CoreCaps = Factory::Version450Core(); 199 *caps = s_version450CoreCaps.get(); 200 } 201 if (settingsText.consumeSuffix(" AllowNarrowingConversions")) { 202 settings->fAllowNarrowingConversions = true; 203 } 204 if (settingsText.consumeSuffix(" ForceHighPrecision")) { 205 settings->fForceHighPrecision = true; 206 } 207 if (settingsText.consumeSuffix(" NoES2Restrictions")) { 208 settings->fEnforceES2Restrictions = false; 209 } 210 if (settingsText.consumeSuffix(" NoInline")) { 211 settings->fInlineThreshold = 0; 212 } 213 if (settingsText.consumeSuffix(" InlineThresholdMax")) { 214 settings->fInlineThreshold = INT_MAX; 215 } 216 if (settingsText.consumeSuffix(" Sharpen")) { 217 settings->fSharpenTextures = true; 218 } 219 if (settingsText.consumeSuffix(" SkVMDebugTrace")) { 220 settings->fOptimize = false; 221 *debugInfo = std::make_unique<SkSL::SkVMDebugInfo>(); 222 } 223 224 if (settingsText.empty()) { 225 break; 226 } 227 if (settingsText.length() == startingLength) { 228 printf("Unrecognized #pragma settings: %s\n", settingsText.c_str()); 229 return false; 230 } 231 } 232 } 233 } 234 235 return true; 236} 237 238/** 239 * Displays a usage banner; used when the command line arguments don't make sense. 240 */ 241static void show_usage() { 242 printf("usage: skslc <input> <output> <flags>\n" 243 " skslc <worklist>\n" 244 "\n" 245 "Allowed flags:\n" 246 "--settings: honor embedded /*#pragma settings*/ comments.\n" 247 "--nosettings: ignore /*#pragma settings*/ comments\n"); 248} 249 250/** 251 * Handle a single input. 252 */ 253ResultCode processCommand(std::vector<SkSL::String>& args) { 254 bool honorSettings = true; 255 if (args.size() == 4) { 256 // Handle four-argument case: `skslc in.sksl out.glsl --settings` 257 const SkSL::String& settingsArg = args[3]; 258 if (settingsArg == "--settings") { 259 honorSettings = true; 260 } else if (settingsArg == "--nosettings") { 261 honorSettings = false; 262 } else { 263 printf("unrecognized flag: %s\n\n", settingsArg.c_str()); 264 show_usage(); 265 return ResultCode::kInputError; 266 } 267 } else if (args.size() != 3) { 268 show_usage(); 269 return ResultCode::kInputError; 270 } 271 272 SkSL::ProgramKind kind; 273 const SkSL::String& inputPath = args[1]; 274 if (inputPath.ends_with(".vert")) { 275 kind = SkSL::ProgramKind::kVertex; 276 } else if (inputPath.ends_with(".frag") || inputPath.ends_with(".sksl")) { 277 kind = SkSL::ProgramKind::kFragment; 278 } else if (inputPath.ends_with(".rtb")) { 279 kind = SkSL::ProgramKind::kRuntimeBlender; 280 } else if (inputPath.ends_with(".rtcf")) { 281 kind = SkSL::ProgramKind::kRuntimeColorFilter; 282 } else if (inputPath.ends_with(".rts")) { 283 kind = SkSL::ProgramKind::kRuntimeShader; 284 } else { 285 printf("input filename must end in '.vert', '.frag', '.rtb', '.rtcf', " 286 "'.rts', or '.sksl'\n"); 287 return ResultCode::kInputError; 288 } 289 290 std::ifstream in(inputPath); 291 SkSL::String text((std::istreambuf_iterator<char>(in)), 292 std::istreambuf_iterator<char>()); 293 if (in.rdstate()) { 294 printf("error reading '%s'\n", inputPath.c_str()); 295 return ResultCode::kInputError; 296 } 297 298 SkSL::Program::Settings settings; 299 SkSL::StandaloneShaderCaps standaloneCaps; 300 const SkSL::ShaderCapsClass* caps = &standaloneCaps; 301 std::unique_ptr<SkSL::SkVMDebugInfo> debugInfo; 302 if (honorSettings) { 303 if (!detect_shader_settings(text, &settings, &caps, &debugInfo)) { 304 return ResultCode::kInputError; 305 } 306 } 307 308 // This tells the compiler where the rt-flip uniform will live should it be required. For 309 // testing purposes we don't care where that is, but the compiler will report an error if we 310 // leave them at their default invalid values, or if the offset overlaps another uniform. 311 settings.fRTFlipOffset = 16384; 312 settings.fRTFlipSet = 0; 313 settings.fRTFlipBinding = 0; 314 315 const SkSL::String& outputPath = args[2]; 316 auto emitCompileError = [&](SkSL::FileOutputStream& out, const char* errorText) { 317 // Overwrite the compiler output, if any, with an error message. 318 out.close(); 319 SkSL::FileOutputStream errorStream(outputPath); 320 errorStream.writeText("### Compilation failed:\n\n"); 321 errorStream.writeText(errorText); 322 errorStream.close(); 323 // Also emit the error directly to stdout. 324 puts(errorText); 325 }; 326 327 auto compileProgram = [&](const auto& writeFn) -> ResultCode { 328 SkSL::FileOutputStream out(outputPath); 329 SkSL::Compiler compiler(caps); 330 if (!out.isValid()) { 331 printf("error writing '%s'\n", outputPath.c_str()); 332 return ResultCode::kOutputError; 333 } 334 std::unique_ptr<SkSL::Program> program = compiler.convertProgram(kind, text, settings); 335 if (!program || !writeFn(compiler, *program, out)) { 336 emitCompileError(out, compiler.errorText().c_str()); 337 return ResultCode::kCompileError; 338 } 339 if (!out.close()) { 340 printf("error writing '%s'\n", outputPath.c_str()); 341 return ResultCode::kOutputError; 342 } 343 return ResultCode::kSuccess; 344 }; 345 346 if (outputPath.ends_with(".spirv")) { 347 return compileProgram( 348 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) { 349 return compiler.toSPIRV(program, out); 350 }); 351 } else if (outputPath.ends_with(".asm.frag") || outputPath.ends_with(".asm.vert")) { 352 return compileProgram( 353 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) { 354 // Compile program to SPIR-V assembly in a string-stream. 355 SkSL::StringStream assembly; 356 if (!compiler.toSPIRV(program, assembly)) { 357 return false; 358 } 359 // Convert the string-stream to a SPIR-V disassembly. 360 spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_0); 361 const SkSL::String& spirv(assembly.str()); 362 std::string disassembly; 363 if (!tools.Disassemble((const uint32_t*)spirv.data(), 364 spirv.size() / 4, &disassembly)) { 365 return false; 366 } 367 // Finally, write the disassembly to our output stream. 368 out.write(disassembly.data(), disassembly.size()); 369 return true; 370 }); 371 } else if (outputPath.ends_with(".glsl")) { 372 return compileProgram( 373 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) { 374 return compiler.toGLSL(program, out); 375 }); 376 } else if (outputPath.ends_with(".metal")) { 377 return compileProgram( 378 [](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) { 379 return compiler.toMetal(program, out); 380 }); 381 } else if (outputPath.ends_with(".skvm")) { 382 return compileProgram( 383 [&](SkSL::Compiler&, SkSL::Program& program, SkSL::OutputStream& out) { 384 skvm::Builder builder{skvm::Features{}}; 385 if (!SkSL::testingOnly_ProgramToSkVMShader(program, &builder, 386 debugInfo.get())) { 387 return false; 388 } 389 390 std::unique_ptr<SkWStream> redirect = as_SkWStream(out); 391 if (debugInfo) { 392 debugInfo->dump(redirect.get()); 393 } 394 builder.done().dump(redirect.get()); 395 return true; 396 }); 397 } else if (outputPath.ends_with(".stage")) { 398 return compileProgram( 399 [](SkSL::Compiler&, SkSL::Program& program, SkSL::OutputStream& out) { 400 class Callbacks : public SkSL::PipelineStage::Callbacks { 401 public: 402 using String = SkSL::String; 403 404 String getMangledName(const char* name) override { 405 return String(name) + "_0"; 406 } 407 408 String declareUniform(const SkSL::VarDeclaration* decl) override { 409 fOutput += decl->description(); 410 return String(decl->var().name()); 411 } 412 413 void defineFunction(const char* decl, 414 const char* body, 415 bool /*isMain*/) override { 416 fOutput += String(decl) + "{" + body + "}"; 417 } 418 419 void declareFunction(const char* decl) override { 420 fOutput += String(decl) + ";"; 421 } 422 423 void defineStruct(const char* definition) override { 424 fOutput += definition; 425 } 426 427 void declareGlobal(const char* declaration) override { 428 fOutput += declaration; 429 } 430 431 String sampleShader(int index, String coords) override { 432 return "child_" + SkSL::to_string(index) + ".eval(" + coords + ")"; 433 } 434 435 String sampleColorFilter(int index, String color) override { 436 return "child_" + SkSL::to_string(index) + ".eval(" + color + ")"; 437 } 438 439 String sampleBlender(int index, String src, String dst) override { 440 return "child_" + SkSL::to_string(index) + ".eval(" + src + ", " + 441 dst + ")"; 442 } 443 444 String fOutput; 445 }; 446 // The .stage output looks almost like valid SkSL, but not quite. 447 // The PipelineStageGenerator bridges the gap between the SkSL in `program`, 448 // and the C++ FP builder API (see GrSkSLFP). In that API, children don't need 449 // to be declared (so they don't emit declarations here). Children are sampled 450 // by index, not name - so all children here are just "child_N". 451 // The input color and coords have names in the original SkSL (as parameters to 452 // main), but those are ignored here. References to those variables become 453 // "_coords" and "_inColor". At runtime, those variable names are irrelevant 454 // when the new SkSL is emitted inside the FP - references to those variables 455 // are replaced with strings from EmitArgs, and might be varyings or differently 456 // named parameters. 457 Callbacks callbacks; 458 SkSL::PipelineStage::ConvertProgram(program, "_coords", "_inColor", 459 "_canvasColor", &callbacks); 460 out.writeString(GrShaderUtils::PrettyPrint(callbacks.fOutput)); 461 return true; 462 }); 463 } else if (outputPath.ends_with(".dehydrated.sksl")) { 464 SkSL::FileOutputStream out(outputPath); 465 SkSL::Compiler compiler(caps); 466 if (!out.isValid()) { 467 printf("error writing '%s'\n", outputPath.c_str()); 468 return ResultCode::kOutputError; 469 } 470 SkSL::LoadedModule module = 471 compiler.loadModule(kind, SkSL::Compiler::MakeModulePath(inputPath.c_str()), 472 /*base=*/nullptr, /*dehydrate=*/true); 473 SkSL::Dehydrator dehydrator; 474 dehydrator.write(*module.fSymbols); 475 dehydrator.write(module.fElements); 476 SkSL::String baseName = base_name(inputPath, "", ".sksl"); 477 SkSL::StringStream buffer; 478 dehydrator.finish(buffer); 479 const SkSL::String& data = buffer.str(); 480 out.printf("static uint8_t SKSL_INCLUDE_%s[] = {", baseName.c_str()); 481 for (size_t i = 0; i < data.length(); ++i) { 482 out.printf("%s%d,", dehydrator.prefixAtOffset(i), uint8_t(data[i])); 483 } 484 out.printf("};\n"); 485 out.printf("static constexpr size_t SKSL_INCLUDE_%s_LENGTH = sizeof(SKSL_INCLUDE_%s);\n", 486 baseName.c_str(), baseName.c_str()); 487 if (!out.close()) { 488 printf("error writing '%s'\n", outputPath.c_str()); 489 return ResultCode::kOutputError; 490 } 491 } else { 492 printf("expected output path to end with one of: .glsl, .metal, .spirv, .asm.frag, .skvm, " 493 ".stage, .asm.vert (got '%s')\n", outputPath.c_str()); 494 return ResultCode::kConfigurationError; 495 } 496 return ResultCode::kSuccess; 497} 498 499/** 500 * Processes multiple inputs in a single invocation of skslc. 501 */ 502ResultCode processWorklist(const char* worklistPath) { 503 SkSL::String inputPath(worklistPath); 504 if (!inputPath.ends_with(".worklist")) { 505 printf("expected .worklist file, found: %s\n\n", worklistPath); 506 show_usage(); 507 return ResultCode::kConfigurationError; 508 } 509 510 // The worklist contains one line per argument to pass to skslc. When a blank line is reached, 511 // those arguments will be passed to `processCommand`. 512 auto resultCode = ResultCode::kSuccess; 513 std::vector<SkSL::String> args = {"skslc"}; 514 std::ifstream in(worklistPath); 515 for (SkSL::String line; std::getline(in, line); ) { 516 if (in.rdstate()) { 517 printf("error reading '%s'\n", worklistPath); 518 return ResultCode::kInputError; 519 } 520 521 if (!line.empty()) { 522 // We found an argument. Remember it. 523 args.push_back(std::move(line)); 524 } else { 525 // We found a blank line. If we have any arguments stored up, process them as a command. 526 if (!args.empty()) { 527 ResultCode outcome = processCommand(args); 528 resultCode = std::max(resultCode, outcome); 529 530 // Clear every argument except the first ("skslc"). 531 args.resize(1); 532 } 533 } 534 } 535 536 // If the worklist ended with a list of arguments but no blank line, process those now. 537 if (args.size() > 1) { 538 ResultCode outcome = processCommand(args); 539 resultCode = std::max(resultCode, outcome); 540 } 541 542 // Return the "worst" status we encountered. For our purposes, compilation errors are the least 543 // serious, because they are expected to occur in unit tests. Other types of errors are not 544 // expected at all during a build. 545 return resultCode; 546} 547 548int main(int argc, const char** argv) { 549 if (argc == 2) { 550 // Worklists are the only two-argument case for skslc, and we don't intend to support 551 // nested worklists, so we can process them here. 552 return (int)processWorklist(argv[1]); 553 } else { 554 // Process non-worklist inputs. 555 std::vector<SkSL::String> args; 556 for (int index=0; index<argc; ++index) { 557 args.push_back(argv[index]); 558 } 559 560 return (int)processCommand(args); 561 } 562} 563