skia2/tools/sksl-precompile/SkSLPrecompile.cpp
John Stiles 846eca6c72 Add support for dependent modules in sksl-precompile.
For now, the hierarchy of module dependencies is hard-coded into
dehydrate_sksl.py. (It is already hardcoded into SkSLCompiler.cpp, but
not in a way that is easily accessible to sksl-precompile.)

sksl-precompile now takes one output and an arbitrary number of inputs.
The inputs are processed from right to left, layering their symbol
tables as we go. e.g., sksl_frag is compiled like this:

  sksl-precompile sksl_frag.dehydrated.sksl sksl_frag.sksl sksl_gpu.sksl

At present this doesn't change anything, because every module is
written in a standalone fashion (since nothing else was allowed). I've
demonstrated that these changes actually work as intended in a
followup example CL (not meant to be submitted).

Change-Id: Ifac638537f77b4a9c78b8cd94a6c4efd4bad01cc
Bug: skia:13164
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/532197
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
2022-04-20 23:04:21 +00:00

145 lines
4.8 KiB
C++

/*
* Copyright 2022 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#define SK_OPTS_NS sksl_precompile_standalone
#include "include/core/SkGraphics.h"
#include "include/core/SkStream.h"
#include "include/private/SkStringView.h"
#include "src/core/SkCpu.h"
#include "src/core/SkOpts.h"
#include "src/opts/SkChecksum_opts.h"
#include "src/opts/SkVM_opts.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLDehydrator.h"
#include "src/sksl/SkSLFileOutputStream.h"
#include "src/sksl/SkSLStringStream.h"
#include "src/sksl/SkSLUtil.h"
#include "src/sksl/ir/SkSLUnresolvedFunction.h"
#include "src/sksl/ir/SkSLVarDeclarations.h"
#include "src/utils/SkOSPath.h"
#include "src/utils/SkShaderUtils.h"
#include <fstream>
#include <limits.h>
#include <list>
#include <optional>
#include <stdarg.h>
#include <stdio.h>
void SkDebugf(const char format[], ...) {
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
namespace SkOpts {
decltype(hash_fn) hash_fn = sksl_precompile_standalone::hash_fn;
decltype(interpret_skvm) interpret_skvm;
}
enum class ResultCode {
kSuccess = 0,
kCompileError = 1,
kInputError = 2,
kOutputError = 3,
};
/**
* Displays a usage banner; used when the command line arguments don't make sense.
*/
static void show_usage() {
printf("usage: sksl-precompile <output> <input>\n");
}
/**
* Handle a single input.
*/
ResultCode processCommand(const std::vector<std::string>& paths) {
if (paths.size() < 2) {
show_usage();
return ResultCode::kInputError;
}
SkSL::Program::Settings settings;
auto standaloneCaps = SkSL::ShaderCapsFactory::Standalone();
const SkSL::ShaderCaps* caps = standaloneCaps.get();
// 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;
// Load in each input as a module, from right to left.
// Each module inherits the symbols from its parent module.
SkSL::Compiler compiler(caps);
std::list<SkSL::LoadedModule> modules;
std::shared_ptr<SkSL::SymbolTable> inheritedSymbols = nullptr;
for (int inputIdx = paths.size() - 1; inputIdx >= 1; --inputIdx) {
const std::string& modulePath = paths[inputIdx];
std::ifstream in(modulePath);
std::string text((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
if (in.rdstate()) {
printf("error reading '%s'\n", modulePath.c_str());
return ResultCode::kInputError;
}
modules.push_front(compiler.loadModule(SkSL::ProgramKind::kFragment,
SkSL::Compiler::MakeModulePath(modulePath.c_str()),
/*base=*/inheritedSymbols,
/*dehydrate=*/inheritedSymbols == nullptr));
inheritedSymbols = modules.front().fSymbols;
}
// Dehydrate the leftmost input file into a buffer.
const std::string& inputPath = paths[1];
SkSL::LoadedModule& module = modules.front();
SkSL::Dehydrator dehydrator;
dehydrator.write(*module.fSymbols);
dehydrator.write(module.fElements);
SkString baseName = SkOSPath::Basename(inputPath.c_str());
if (int extension = baseName.findLastOf('.'); extension > 0) {
baseName.resize(extension);
}
SkSL::StringStream buffer;
dehydrator.finish(buffer);
const std::string& data = buffer.str();
// Emit the dehydrated data into our output file.
const std::string& outputPath = paths[0];
SkSL::FileOutputStream out(outputPath.c_str());
if (!out.isValid()) {
printf("error writing '%s'\n", outputPath.c_str());
return ResultCode::kOutputError;
}
out.printf("static uint8_t SKSL_INCLUDE_%s[] = {", baseName.c_str());
for (size_t i = 0; i < data.length(); ++i) {
out.printf("%s%d,", dehydrator.prefixAtOffset(i), uint8_t(data[i]));
}
out.printf("};\n");
out.printf("static constexpr size_t SKSL_INCLUDE_%s_LENGTH = sizeof(SKSL_INCLUDE_%s);\n",
baseName.c_str(), baseName.c_str());
if (!out.close()) {
printf("error writing '%s'\n", outputPath.c_str());
return ResultCode::kOutputError;
}
return ResultCode::kSuccess;
}
int main(int argc, const char** argv) {
std::vector<std::string> args;
for (int index=1; index<argc; ++index) {
args.push_back(argv[index]);
}
return (int)processCommand(args);
}