Files
filament/tools/resgen/src/main.cpp

363 lines
12 KiB
C++

/*
* Copyright (C) 2018 The Android Open Source Project
*
* 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 <utils/Path.h>
#include <getopt/getopt.h>
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
using namespace utils;
static const char* g_packageName = "resources";
static const char* g_deployDir = ".";
static bool g_keepExtension = false;
static bool g_appendNull = false;
static bool g_generateC = false;
static const char* USAGE = R"TXT(
RESGEN aggregates a sequence of binary blobs, each of which becomes a "resource" whose id
is the basename of the input file. It produces the following set of files:
resources.h ......... declares sizes and offsets for each resource
resources.S ......... small assembly file with incbin directive and rodata section
resources.apple.S ... ditto but with different rodata name and underscore prefixes
resources.bin ....... the aggregated binary blob that the incbin refers to
Usage:
RESGEN [options] <input_file_0> <input_file_1> ...
Options:
--help, -h
Print this message
--license, -L
Print copyright and license information
--package=string, -p string
Name of the resource package (defaults to "resources")
This is used to generate filenames and symbol prefixes
--deploy=dir, -x dir (defaults to ".")
Generate everything needed for deployment into <dir>
--keep, -k
Keep file extensions when generating symbols
--text, -t
Append a null terminator to each data blob
--cfile, -c
Generate xxd-style C file (useful for WebAssembly)
Examples:
RESGEN -cp textures jungle.png beach.png
> Generated files: textures.h, textures.S, textures.apple.S, textures.bin, textures.c
> Generated symbols: TEXTURES_JUNGLE_DATA, TEXTURES_JUNGLE_SIZE,
TEXTURES_BEACH_DATA, TEXTURES_BEACH_SIZE
)TXT";
static const char* APPLE_ASM_TEMPLATE = R"ASM(
.global _{RESOURCES}PACKAGE
.section __TEXT,__const
_{RESOURCES}PACKAGE:
.incbin "{resources}.bin"
)ASM";
static const char* ASM_TEMPLATE = R"ASM(
.global {RESOURCES}PACKAGE
.section .rodata
{RESOURCES}PACKAGE:
.incbin "{resources}.bin"
)ASM";
static void printUsage(const char* name) {
std::string execName(Path(name).getName());
const std::string from("RESGEN");
std::string usage(USAGE);
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
usage.replace(pos, from.length(), execName);
}
puts(usage.c_str());
}
static void license() {
static const char *license[] = {
#include "licenses/licenses.inc"
nullptr
};
const char **p = &license[0];
while (*p)
std::cout << *p++ << std::endl;
}
static int handleArguments(int argc, char* argv[]) {
static constexpr const char* OPTSTR = "hLp:x:ktc";
static const struct option OPTIONS[] = {
{ "help", no_argument, 0, 'h' },
{ "license", no_argument, 0, 'L' },
{ "package", required_argument, 0, 'p' },
{ "deploy", required_argument, 0, 'x' },
{ "keep", no_argument, 0, 'k' },
{ "text", no_argument, 0, 't' },
{ "cfile", no_argument, 0, 'c' },
{ 0, 0, 0, 0 } // termination of the option list
};
int opt;
int optionIndex = 0;
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &optionIndex)) >= 0) {
std::string arg(optarg ? optarg : "");
switch (opt) {
default:
case 'h':
printUsage(argv[0]);
exit(0);
case 'L':
license();
exit(0);
case 'p':
g_packageName = optarg;
break;
case 'x':
g_deployDir = optarg;
break;
case 'k':
g_keepExtension = true;
break;
case 't':
g_appendNull = true;
break;
case 'c':
g_generateC = true;
break;
}
}
return optind;
}
int main(int argc, char* argv[]) {
const int optionIndex = handleArguments(argc, argv);
const int numArgs = argc - optionIndex;
if (numArgs < 1) {
printUsage(argv[0]);
return 1;
}
vector<Path> inputPaths;
for (int argIndex = optionIndex; argIndex < argc; ++argIndex) {
inputPaths.emplace_back(argv[argIndex]);
}
std::string packageFile = g_packageName;
std::string packagePrefix = std::string(g_packageName) + "_";
transform(packageFile.begin(), packageFile.end(), packageFile.begin(), ::tolower);
transform(packagePrefix.begin(), packagePrefix.end(), packagePrefix.begin(), ::toupper);
std::string package = packagePrefix + "PACKAGE";
const Path deployDir(g_deployDir);
if (!deployDir.exists()) {
deployDir.mkdirRecursive();
}
const Path appleAsmPath(deployDir + (packageFile + ".apple.S"));
const Path asmPath(deployDir + (packageFile + ".S"));
const Path binPath(deployDir + (packageFile + ".bin"));
const Path headerPath(deployDir + (packageFile + ".h"));
const Path xxdPath(deployDir + (packageFile + ".c"));
// In the assembly language templates, replace {RESOURCES} with packagePrefix and replace
// {resources} with packageFile.
const std::string k1("{RESOURCES}");
const std::string k2("{resources}");
std::string aasmstr(APPLE_ASM_TEMPLATE);
std::string asmstr(ASM_TEMPLATE);
for (size_t pos = aasmstr.find(k1); pos != std::string::npos; pos = aasmstr.find(k1, pos))
aasmstr.replace(pos, k1.length(), packagePrefix);
for (size_t pos = aasmstr.find(k2); pos != std::string::npos; pos = aasmstr.find(k2, pos))
aasmstr.replace(pos, k2.length(), packageFile);
for (size_t pos = asmstr.find(k1); pos != std::string::npos; pos = asmstr.find(k1, pos))
asmstr.replace(pos, k1.length(), packagePrefix);
for (size_t pos = asmstr.find(k2); pos != std::string::npos; pos = asmstr.find(k2, pos))
asmstr.replace(pos, k2.length(), packageFile);
// Generate the Apple-friendly assembly language file.
ofstream appleAsmStream(appleAsmPath.getPath());
if (!appleAsmStream) {
cerr << "Unable to open " << appleAsmPath << endl;
exit(1);
}
// Generate the non-Apple assembly language file.
ofstream asmStream(asmPath.getPath());
if (!asmStream) {
cerr << "Unable to open " << asmPath << endl;
exit(1);
}
// Open the bin file for writing.
ofstream binStream(binPath.getPath(), ios::binary);
if (!binStream) {
cerr << "Unable to open " << binPath << endl;
exit(1);
}
// Open the header file stream for writing.
ostringstream headerStream;
headerStream << "#ifndef " << packagePrefix << "H_" << endl
<< "#define " << packagePrefix << "H_" << endl << endl
<< "#include <stdint.h>" << endl << endl
<< "extern \"C\" {" << endl
<< " extern const uint8_t " << package << "[];" << endl;
ostringstream headerMacros;
ostringstream xxdDefinitions;
ostringstream appleDataAsmStream;
ostringstream dataAsmStream;
// Open the generated C file for writing.
ofstream xxdStream;
if (g_generateC) {
xxdStream = ofstream(xxdPath.getPath());
if (!xxdStream) {
cerr << "Unable to open " << xxdPath << endl;
exit(1);
}
xxdStream << "#include <stdint.h>\n\nconst uint8_t " << package << "[] = {\n";
}
// Iterate through each input file and consume its contents.
size_t offset = 0;
for (const auto& inPath : inputPaths) {
ifstream inStream(inPath.getPath(), ios::binary);
if (!inStream) {
cerr << "Unable to open " << inPath << endl;
exit(1);
}
vector<uint8_t> content((istreambuf_iterator<char>(inStream)), {});
if (g_appendNull) {
content.push_back(0);
}
// Formulate the resource name and the prefixed resource name.
std::string rname = g_keepExtension ? inPath.getName() : inPath.getNameWithoutExtension();
replace(rname.begin(), rname.end(), '.', '_');
transform(rname.begin(), rname.end(), rname.begin(), ::toupper);
const std::string prname = packagePrefix + rname;
// Write the binary blob into the bin file.
binStream.write((const char*) content.data(), content.size());
// Write the offsets and sizes.
headerMacros
<< "#define " << prname << "_DATA (" << package << " + " << prname << "_OFFSET)\n";
headerStream
<< " extern int " << prname << "_OFFSET;\n"
<< " extern int " << prname << "_SIZE;\n";
dataAsmStream
<< prname << "_OFFSET:\n"
<< " .int " << offset << "\n"
<< prname << "_SIZE:\n"
<< " .int " << content.size() << "\n";
asmStream
<< " .global " << prname << "_OFFSET;\n"
<< " .global " << prname << "_SIZE;\n";
appleDataAsmStream
<< "_" << prname << "_OFFSET:\n"
<< " .int " << offset << "\n"
<< "_" << prname << "_SIZE:\n"
<< " .int " << content.size() << "\n";
appleAsmStream
<< " .global _" << prname << "_OFFSET;\n"
<< " .global _" << prname << "_SIZE;\n";
// Write the xxd-style ASCII array, followed by a blank line.
if (g_generateC) {
xxdDefinitions
<< "int " << prname << "_OFFSET = " << offset << ";\n"
<< "int " << prname << "_SIZE = " << content.size() << ";\n";
xxdStream << "// " << rname << "\n";
xxdStream << setfill('0') << hex;
size_t i = 0;
for (; i < content.size(); i++) {
if (i > 0 && i % 20 == 0) {
xxdStream << "\n";
}
xxdStream << "0x" << setw(2) << (int) content[i] << ", ";
}
if (i % 20 != 0) xxdStream << "\n";
xxdStream << "\n";
}
offset += content.size();
}
headerStream << "}\n" << headerMacros.str();
headerStream << "\n#endif\n";
// To optimize builds, avoid overwriting the header file if nothing has changed.
bool headerIsDirty = true;
ifstream headerInStream(headerPath.getPath(), std::ifstream::ate);
string headerContents = headerStream.str();
if (headerInStream) {
long fileSize = static_cast<long>(headerInStream.tellg());
if (fileSize == headerContents.size()) {
vector<char> previous(fileSize);
headerInStream.seekg(0);
headerInStream.read(previous.data(), fileSize);
headerIsDirty = 0 != memcmp(previous.data(), headerContents.c_str(), fileSize);
}
}
if (headerIsDirty) {
ofstream headerOutStream(headerPath.getPath());
if (!headerOutStream) {
cerr << "Unable to open " << headerPath << endl;
exit(1);
}
headerOutStream << headerContents;
}
asmStream << asmstr << dataAsmStream.str() << endl;
asmStream.close();
appleAsmStream << aasmstr << appleDataAsmStream.str() << endl;
appleAsmStream.close();
cout << "Generated files: "
<< headerPath << " "
<< asmPath << " "
<< appleAsmPath << " "
<< binPath;
if (g_generateC) {
xxdStream << "};\n\n" << xxdDefinitions.str();
cout << " " << xxdPath;
}
cout << endl;
}