Revision control

++ // Copyright (C) 2020 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++
++ #include <QCoreApplication>
++
++ #include <QXmlStreamWriter>
++ #include <qlist.h>
++
++ #include <QCommandLineOption>
++ #include <QCommandLineParser>
++
++ #include <QtCore/qfile.h>
++ #include <QtCore/qdir.h>
++
++ #include <QtQuick3DUtils/private/qqsbcollection_p.h>
++
++ #include "genshaders.h"
++
++ #include "parser.h"
++
++ constexpr int DEAFULT_SEARCH_DEPTH = 0x10;
++
++ static int generateShaders(QVector<QString> &qsbcFiles,
++ const QVector<QString> &filePaths,
++ const QDir &sourceDir,
++ const QDir &outDir,
++ bool multilight,
++ bool verboseOutput,
++ bool dryRun)
++ {
++ MaterialParser::SceneData sceneData;
++ if (MaterialParser::parseQmlFiles(filePaths, sourceDir, sceneData, verboseOutput) == 0) {
++ if (sceneData.hasData()) {
++ GenShaders genShaders;
++ if (!genShaders.process(sceneData, qsbcFiles, outDir, multilight, dryRun))
++ return -1;
++ } else if (verboseOutput) {
++ if (!sceneData.viewport)
++ qWarning() << "No View3D item found";
++ }
++ }
++ return 0;
++ }
++
++ static int writeResourceFile(const QString &resourceFile,
++ const QVector<QString> &qsbcFiles,
++ const QDir &outDir)
++ {
++ if (qsbcFiles.isEmpty())
++ return -1;
++
++ const QString outFilename = outDir.canonicalPath() + QDir::separator() + resourceFile;
++ QFile outFile(outFilename);
++ if (!outFile.open(QFile::WriteOnly | QFile::Text | QFile::Truncate)) {
++ qWarning() << "Unable to create output file " << outFilename;
++ return -1;
++ }
++
++ QXmlStreamWriter writer(&outFile);
++ writer.setAutoFormatting(true);
++ writer.writeStartElement("RCC");
++ writer.writeStartElement("qresource");
++ writer.writeAttribute("prefix", "/");
++ for (const auto &f : qsbcFiles)
++ writer.writeTextElement("file", f);
++ writer.writeEndElement();
++ writer.writeEndElement();
++ outFile.close();
++
++ return 0;
++ }
++
++ struct SearchDepthGuard
++ {
++ explicit SearchDepthGuard(int m) : max(m) {}
++ int value = 0;
++ const int max = DEAFULT_SEARCH_DEPTH;
++ };
++
++ static void collectQmlFiles(const QList<QString> &pathArgs, QSet<QString> &filePaths, SearchDepthGuard &depth)
++ {
++ QFileInfo fi;
++ QDir dir;
++ for (const auto &arg : pathArgs) {
++ fi.setFile(arg);
++ if (fi.isFile()) {
++ if (fi.suffix() == QLatin1String("qml"))
++ filePaths.insert(fi.canonicalFilePath());
++ } else if (fi.isDir() && depth.value <= depth.max) {
++ dir.setPath(fi.filePath());
++ const auto entries = dir.entryList(QDir::Filter::Dirs | QDir::Filter::Files | QDir::Filter::NoDotAndDotDot);
++ const QString currentPath = QDir::currentPath();
++ QDir::setCurrent(dir.path());
++ ++depth.value;
++ collectQmlFiles(entries, filePaths, depth);
++ --depth.value;
++ QDir::setCurrent(currentPath);
++ }
++ }
++ }
++
++ int main(int argc, char *argv[])
++ {
++ QCoreApplication a(argc, argv);
++
++ QCommandLineParser cmdLineparser;
++ cmdLineparser.setApplicationDescription("Pre-generates material shaders for Qt Quick 3D");
++ cmdLineparser.addHelpOption();
++ // File options
++ QCommandLineOption changeDirOption({QChar(u'C'), QLatin1String("directory")},
++ QLatin1String("Change the working directory"),
++ QLatin1String("dir"));
++ cmdLineparser.addOption(changeDirOption);
++
++ // Debug options
++ QCommandLineOption verboseOutputOption({QChar(u'v'), QLatin1String("verbose")}, QLatin1String("Turn on verbose output."));
++ cmdLineparser.addOption(verboseOutputOption);
++
++ // Generator options
++ QCommandLineOption dryRunOption({QChar(u'n'), QLatin1String("dry-run")}, QLatin1String("Runs as normal, but no files are created."));
++ cmdLineparser.addOption(dryRunOption);
++
++ QCommandLineOption outputDirOption({QChar(u'o'), QLatin1String("output-dir")}, QLatin1String("Output directory for generated files."), QLatin1String("file"));
++ cmdLineparser.addOption(outputDirOption);
++
++ QCommandLineOption resourceFileOption({QChar(u'r'), QLatin1String("resource-file")}, QLatin1String("Name of generated resource file."), QLatin1String("file"));
++ cmdLineparser.addOption(resourceFileOption);
++
++ QCommandLineOption dumpQsbcFileOption({QChar(u'l'), QLatin1String("list-qsbc")}, QLatin1String("Lists qsbc file content."));
++ cmdLineparser.addOption(dumpQsbcFileOption);
++
++ QCommandLineOption extractQsbFileOption({QChar(u'e'), QLatin1String("extract-qsb")}, QLatin1String("Extract qsb from collection."), QLatin1String("key:[desc|vert|frag]"));
++ cmdLineparser.addOption(extractQsbFileOption);
++
++ QCommandLineOption dirDepthOption(QLatin1String("depth"), QLatin1String("Override default max depth (16) value when traversing the filesystem."), QLatin1String("number"));
++ cmdLineparser.addOption(dirDepthOption);
++
++ cmdLineparser.process(a);
++
++ if (cmdLineparser.isSet(changeDirOption)) {
++ const auto value = cmdLineparser.value(changeDirOption);
++ QFileInfo fi(value);
++ if (!fi.isDir()) {
++ qWarning("%s : %s - Not a directory", qPrintable(a.applicationName()), qPrintable(value));
++ return -1;
++ }
++ QDir::setCurrent(value);
++ }
++
++ QSet<QString> filePaths;
++ auto args = cmdLineparser.positionalArguments();
++
++ const bool collectQmlFilesMode = !(cmdLineparser.isSet(dumpQsbcFileOption) || cmdLineparser.isSet(extractQsbFileOption));
++ if (collectQmlFilesMode) {
++ if (args.isEmpty())
++ args.push_back(QDir::currentPath());
++
++ int searchDepth = DEAFULT_SEARCH_DEPTH;
++ if (cmdLineparser.isSet(dirDepthOption)) {
++ bool ok = false;
++ const int v = cmdLineparser.value(dirDepthOption).toInt(&ok);
++ if (ok)
++ searchDepth = v;
++ }
++
++ SearchDepthGuard depth(searchDepth);
++ collectQmlFiles(args, filePaths, depth);
++ } else if (!args.isEmpty()) {
++ filePaths.insert(args.first());
++ }
++
++ if (filePaths.isEmpty()) {
++ qWarning("No input file(s) found!");
++ a.exit(-1);
++ return -1;
++ }
++
++ if (cmdLineparser.isSet(dumpQsbcFileOption)) {
++ const auto &f = *filePaths.cbegin();
++ if (!f.isEmpty()) {
++ QQsbIODeviceCollection::dumpInfo(f);
++ a.exit(0);
++ return 0;
++ }
++ }
++
++ static const auto printBytes = [](const QByteArray &ba) {
++ for (const auto &b : ba)
++ printf("%c", b);
++ };
++
++ if (cmdLineparser.isSet(extractQsbFileOption)) {
++ const auto &f = *filePaths.cbegin();
++ const auto k = cmdLineparser.value(extractQsbFileOption);
++ const auto kl = QStringView(k).split(u':');
++
++ const auto &keyView = kl.at(0);
++ const QByteArray key = keyView.toLatin1();
++ enum ExtractWhat : quint8 { Desc = 0x1, Vert = 0x2, Frag = 0x4 };
++ quint8 what = 0;
++ if (kl.size() > 1) {
++ const auto &rest = kl.at(1);
++ const auto &options = rest.split(u'|');
++ for (const auto &o : options) {
++ if (o == QLatin1String("desc"))
++ what |= ExtractWhat::Desc;
++ if (o == QLatin1String("vert"))
++ what |= ExtractWhat::Vert;
++ if (o == QLatin1String("frag"))
++ what |= ExtractWhat::Frag;
++ }
++ }
++ QQsbIODeviceCollection qsbc(f);
++ if (qsbc.map(QQsbIODeviceCollection::Read)) {
++ const auto entries = qsbc.availableEntries();
++ const auto foundIt = entries.constFind(QQsbCollection::Entry(key));
++ if (foundIt != entries.cend()) {
++ QQsbCollection::EntryDesc ed;
++ qsbc.extractEntry(*foundIt, ed);
++ if (what == 0)
++ qDebug("Entry with key %s found.", key.constData());
++ if (what & ExtractWhat::Desc)
++ printBytes(ed.materialKey);
++ if (what & ExtractWhat::Vert)
++ printBytes(qUncompress(ed.vertShader.serialized()));
++ if (what & ExtractWhat::Frag)
++ printBytes(qUncompress(ed.fragShader.serialized()));
++ } else {
++ qWarning("Entry with key %s could not be found.", key.constData());
++ }
++ qsbc.unmap();
++ }
++ a.exit(0);
++ return 0;
++
++ qWarning("Command %s failed with input: %s and %s.", qPrintable(extractQsbFileOption.valueName()), qPrintable(f), qPrintable(k));
++ a.exit(-1);
++ return -1;
++ }
++
++ QString resourceFile = cmdLineparser.value(resourceFileOption);
++ if (resourceFile.isEmpty())
++ resourceFile = QStringLiteral("genshaders.qrc");
++
++ const bool dryRun = cmdLineparser.isSet(dryRunOption);
++ const QString &outputPath = cmdLineparser.isSet(outputDirOption) ? cmdLineparser.value(outputDirOption) : QDir::currentPath();
++ QDir outDir;
++ if (!outputPath.isEmpty() && !dryRun) {
++ outDir.setPath(outputPath);
++ if (outDir.exists(outputPath) || (!outDir.exists(outputPath) && outDir.mkpath(outputPath))) {
++ outDir.setPath(outputPath);
++ qDebug("Writing files to %s", qPrintable(outDir.canonicalPath()));
++ } else {
++ qDebug("Unable to change or create output folder %s", qPrintable(outputPath));
++ return -1;
++ }
++ }
++
++ const bool verboseOutput = cmdLineparser.isSet(verboseOutputOption);
++ const bool multilight = false;
++
++ QVector<QString> qsbcFiles;
++
++ int ret = 0;
++ if (filePaths.size())
++ ret = generateShaders(qsbcFiles, filePaths.values(), QDir::currentPath(), outDir, multilight, verboseOutput, dryRun);
++
++ if (ret == 0 && !dryRun)
++ writeResourceFile(resourceFile, qsbcFiles, outDir);
++
++ a.exit(ret);
++ return ret;
++ }