summaryrefslogtreecommitdiffstats
path: root/tools/qsb/qsb.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/qsb/qsb.cpp')
-rw-r--r--tools/qsb/qsb.cpp490
1 files changed, 490 insertions, 0 deletions
diff --git a/tools/qsb/qsb.cpp b/tools/qsb/qsb.cpp
new file mode 100644
index 0000000..66435c5
--- /dev/null
+++ b/tools/qsb/qsb.cpp
@@ -0,0 +1,490 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qcommandlineparser.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qtemporarydir.h>
+#include <QtCore/qprocess.h>
+#include <QtCore/qdebug.h>
+#include <QtShaderTools/qshaderbaker.h>
+
+static bool writeToFile(const QByteArray &buf, const QString &filename, bool text = false)
+{
+ QFile f(filename);
+ QIODevice::OpenMode flags = QIODevice::WriteOnly;
+ if (text)
+ flags |= QIODevice::Text;
+ if (!f.open(flags)) {
+ qWarning("Failed to open %s for writing", qPrintable(filename));
+ return false;
+ }
+ f.write(buf);
+ return true;
+}
+
+static QByteArray readFile(const QString &filename, bool text = false)
+{
+ QFile f(filename);
+ QIODevice::OpenMode flags = QIODevice::ReadOnly;
+ if (text)
+ flags |= QIODevice::Text;
+ if (!f.open(flags)) {
+ qWarning("Failed to open %s", qPrintable(filename));
+ return QByteArray();
+ }
+ return f.readAll();
+}
+
+static bool runProcess(const QString &cmd, QByteArray *output, QByteArray *errorOutput)
+{
+ QProcess p;
+ p.start(cmd);
+ if (!p.waitForFinished()) {
+ qWarning("Failed to run %s", qPrintable(cmd));
+ return false;
+ }
+
+ if (p.exitStatus() == QProcess::CrashExit) {
+ qWarning("%s crashed", qPrintable(cmd));
+ return false;
+ }
+
+ *output = p.readAllStandardOutput();
+ *errorOutput = p.readAllStandardError();
+
+ if (p.exitCode() != 0) {
+ qWarning("%s returned non-zero error code %d", qPrintable(cmd), p.exitCode());
+ return false;
+ }
+
+ return true;
+}
+
+static QString stageStr(QRhiShader::ShaderStage stage)
+{
+ switch (stage) {
+ case QRhiShader::VertexStage:
+ return QStringLiteral("Vertex");
+ case QRhiShader::TessControlStage:
+ return QStringLiteral("TessControl");
+ case QRhiShader::TessEvaluationStage:
+ return QStringLiteral("TessEval");
+ case QRhiShader::GeometryStage:
+ return QStringLiteral("Geometry");
+ case QRhiShader::FragmentStage:
+ return QStringLiteral("Fragment");
+ case QRhiShader::ComputeStage:
+ return QStringLiteral("Compute");
+ default:
+ Q_UNREACHABLE();
+ }
+}
+
+static QString sourceStr(QRhiShaderKey::ShaderSource source)
+{
+ switch (source) {
+ case QRhiShaderKey::SpirvShader:
+ return QStringLiteral("SPIR-V");
+ case QRhiShaderKey::GlslShader:
+ return QStringLiteral("GLSL");
+ case QRhiShaderKey::HlslShader:
+ return QStringLiteral("HLSL");
+ case QRhiShaderKey::DxbcShader:
+ return QStringLiteral("DXBC");
+ case QRhiShaderKey::MslShader:
+ return QStringLiteral("MSL");
+ case QRhiShaderKey::DxilShader:
+ return QStringLiteral("DXIL");
+ case QRhiShaderKey::MetalLibShader:
+ return QStringLiteral("metallib");
+ default:
+ Q_UNREACHABLE();
+ }
+}
+
+static QString sourceVersionStr(const QRhiShaderVersion &v)
+{
+ QString s = v.version() ? QString::number(v.version()) : QString();
+ if (v.flags().testFlag(QRhiShaderVersion::GlslEs))
+ s += QLatin1String(" es");
+
+ return s;
+}
+
+static QString sourceVariantStr(const QRhiShaderKey::ShaderVariant &v)
+{
+ switch (v) {
+ case QRhiShaderKey::StandardShader:
+ return QLatin1String("Standard");
+ case QRhiShaderKey::BatchableVertexShader:
+ return QLatin1String("Batchable");
+ default:
+ Q_UNREACHABLE();
+ }
+}
+
+static void dump(const QRhiShader &bs)
+{
+ QTextStream ts(stdout);
+ ts << "Stage: " << stageStr(bs.stage()) << "\n\n";
+ QList<QRhiShaderKey> s = bs.availableShaders();
+ ts << "Has " << s.count() << " shaders: (unordered list)\n";
+ for (int i = 0; i < s.count(); ++i) {
+ ts << " Shader " << i << ": " << sourceStr(s[i].source())
+ << " " << sourceVersionStr(s[i].sourceVersion())
+ << " [" << sourceVariantStr(s[i].sourceVariant()) << "]\n";
+ }
+ ts << "\n";
+ ts << "Reflection info: " << bs.description().toJson() << "\n\n";
+ for (int i = 0; i < s.count(); ++i) {
+ ts << "Shader " << i << ": " << sourceStr(s[i].source())
+ << " " << sourceVersionStr(s[i].sourceVersion())
+ << " [" << sourceVariantStr(s[i].sourceVariant()) << "]\n";
+ QRhiShaderCode shader = bs.shader(s[i]);
+ if (!shader.entryPoint().isEmpty())
+ ts << "Entry point: " << shader.entryPoint() << "\n";
+ ts << "Contents:\n";
+ switch (s[i].source()) {
+ case QRhiShaderKey::SpirvShader:
+ Q_FALLTHROUGH();
+ case QRhiShaderKey::DxbcShader:
+ Q_FALLTHROUGH();
+ case QRhiShaderKey::DxilShader:
+ Q_FALLTHROUGH();
+ case QRhiShaderKey::MetalLibShader:
+ ts << "Binary of " << shader.shader().size() << " bytes\n\n";
+ break;
+ default:
+ ts << shader.shader() << "\n";
+ break;
+ }
+ ts << "\n************************************\n\n";
+ }
+}
+
+static QByteArray fxcProfile(const QRhiShader &bs, const QRhiShaderKey &k)
+{
+ QByteArray t;
+
+ switch (bs.stage()) {
+ case QRhiShader::VertexStage:
+ t += QByteArrayLiteral("vs_");
+ break;
+ case QRhiShader::TessControlStage:
+ t += QByteArrayLiteral("hs_");
+ break;
+ case QRhiShader::TessEvaluationStage:
+ t += QByteArrayLiteral("ds_");
+ break;
+ case QRhiShader::GeometryStage:
+ t += QByteArrayLiteral("gs_");
+ break;
+ case QRhiShader::FragmentStage:
+ t += QByteArrayLiteral("ps_");
+ break;
+ case QRhiShader::ComputeStage:
+ t += QByteArrayLiteral("cs_");
+ break;
+ default:
+ break;
+ }
+
+ const int major = k.sourceVersion().version() / 10;
+ const int minor = k.sourceVersion().version() % 10;
+ t += QByteArray::number(major);
+ t += '_';
+ t += QByteArray::number(minor);
+
+ return t;
+}
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+
+ QCommandLineParser cmdLineParser;
+ cmdLineParser.setApplicationDescription(QObject::tr("Qt Shader Baker"));
+ cmdLineParser.addHelpOption();
+ cmdLineParser.addPositionalArgument(QLatin1String("file"), QObject::tr("Vulkan GLSL source file to compile"), QObject::tr("file"));
+ QCommandLineOption batchableOption({ "b", "batchable" }, QObject::tr("Also generates rewritten vertex shader for Qt Quick scene graph batching."));
+ cmdLineParser.addOption(batchableOption);
+ QCommandLineOption glslOption({ "g", "glsl" },
+ QObject::tr("Comma separated list of GLSL versions to generate. (for example, \"100 es,120,330\")"),
+ QObject::tr("glsl"));
+ cmdLineParser.addOption(glslOption);
+ QCommandLineOption hlslOption({ "l", "hlsl" },
+ QObject::tr("Comma separated list of HLSL (Shader Model) versions to generate. F.ex. 50 is 5.0, 51 is 5.1."),
+ QObject::tr("hlsl"));
+ cmdLineParser.addOption(hlslOption);
+ QCommandLineOption mslOption({ "m", "msl" },
+ QObject::tr("Comma separated list of Metal Shading Language versions to generate. F.ex. 12 is 1.2, 20 is 2.0."),
+ QObject::tr("msl"));
+ cmdLineParser.addOption(mslOption);
+ QCommandLineOption outputOption({ "o", "output" },
+ QObject::tr("Output file for the baked shader pack."),
+ QObject::tr("output"));
+ cmdLineParser.addOption(outputOption);
+ QCommandLineOption fxcOption({ "c", "fxc" }, QObject::tr("In combination with --hlsl invokes fxc to store DXBC instead of HLSL."));
+ cmdLineParser.addOption(fxcOption);
+ QCommandLineOption mtllibOption({ "t", "metallib" },
+ QObject::tr("In combination with --msl builds a Metal library with xcrun metal(lib) and stores that instead of the source."));
+ cmdLineParser.addOption(mtllibOption);
+ QCommandLineOption dumpOption({ "d", "dump" }, QObject::tr("Switches to dump mode. Input file is expected to be a baked shader pack."));
+ cmdLineParser.addOption(dumpOption);
+
+ cmdLineParser.process(app);
+
+ if (cmdLineParser.positionalArguments().isEmpty()) {
+ cmdLineParser.showHelp();
+ return 0;
+ }
+
+ QShaderBaker baker;
+ for (const QString &fn : cmdLineParser.positionalArguments()) {
+ if (cmdLineParser.isSet(dumpOption)) {
+ QByteArray buf = readFile(fn);
+ if (!buf.isEmpty()) {
+ QRhiShader bs = QRhiShader::fromSerialized(buf);
+ if (bs.isValid())
+ dump(bs);
+ else
+ qWarning("Failed to deserialize %s", qPrintable(fn));
+ }
+ continue;
+ }
+
+ baker.setSourceFileName(fn);
+
+ QVector<QRhiShaderKey::ShaderVariant> variants;
+ variants << QRhiShaderKey::StandardShader;
+ if (cmdLineParser.isSet(batchableOption))
+ variants << QRhiShaderKey::BatchableVertexShader;
+
+ baker.setGeneratedShaderVariants(variants);
+
+ QVector<QShaderBaker::GeneratedShader> genShaders;
+
+ genShaders << qMakePair(QRhiShaderKey::SpirvShader, QRhiShaderVersion(100));
+
+ if (cmdLineParser.isSet(glslOption)) {
+ const QStringList versions = cmdLineParser.value(glslOption).trimmed().split(',');
+ for (QString version : versions) {
+ QRhiShaderVersion::Flags flags = 0;
+ if (version.endsWith(QLatin1String(" es"))) {
+ version = version.left(version.count() - 3);
+ flags |= QRhiShaderVersion::GlslEs;
+ } else if (version.endsWith(QLatin1String("es"))) {
+ version = version.left(version.count() - 2);
+ flags |= QRhiShaderVersion::GlslEs;
+ }
+ bool ok = false;
+ int v = version.toInt(&ok);
+ if (ok)
+ genShaders << qMakePair(QRhiShaderKey::GlslShader, QRhiShaderVersion(v, flags));
+ else
+ qWarning("Ignoring invalid GLSL version %s", qPrintable(version));
+ }
+ }
+
+ if (cmdLineParser.isSet(hlslOption)) {
+ const QStringList versions = cmdLineParser.value(hlslOption).trimmed().split(',');
+ for (QString version : versions) {
+ bool ok = false;
+ int v = version.toInt(&ok);
+ if (ok)
+ genShaders << qMakePair(QRhiShaderKey::HlslShader, QRhiShaderVersion(v));
+ else
+ qWarning("Ignoring invalid HLSL (Shader Model) version %s", qPrintable(version));
+ }
+ }
+
+ if (cmdLineParser.isSet(mslOption)) {
+ const QStringList versions = cmdLineParser.value(mslOption).trimmed().split(',');
+ for (QString version : versions) {
+ bool ok = false;
+ int v = version.toInt(&ok);
+ if (ok)
+ genShaders << qMakePair(QRhiShaderKey::MslShader, QRhiShaderVersion(v));
+ else
+ qWarning("Ignoring invalid MSL version %s", qPrintable(version));
+ }
+ }
+
+ baker.setGeneratedShaders(genShaders);
+
+ QRhiShader bs = baker.bake();
+ if (!bs.isValid()) {
+ qWarning("Shader baking failed: %s", qPrintable(baker.errorMessage()));
+ return 1;
+ }
+
+ if (cmdLineParser.isSet(fxcOption)) {
+ QTemporaryDir tempDir;
+ if (!tempDir.isValid()) {
+ qWarning("Failed to create temporary directory");
+ return 1;
+ }
+ auto skeys = bs.availableShaders();
+ for (QRhiShaderKey &k : skeys) {
+ if (k.source() == QRhiShaderKey::HlslShader) {
+ QRhiShaderCode s = bs.shader(k);
+
+ const QString tmpIn = tempDir.path() + QLatin1String("/qsb_hlsl_temp");
+ const QString tmpOut = tempDir.path() + QLatin1String("/qsb_hlsl_temp_out");
+ QFile f(tmpIn);
+ if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
+ qWarning("Failed to create temporary file");
+ return 1;
+ }
+ f.write(s.shader());
+ f.close();
+
+ const QByteArray tempOutFileName = QDir::toNativeSeparators(tmpOut).toUtf8();
+ const QByteArray inFileName = QDir::toNativeSeparators(tmpIn).toUtf8();
+ const QByteArray typeArg = fxcProfile(bs, k);
+ const QByteArray entryPoint = s.entryPoint();
+ const QString cmd = QString::asprintf("fxc /nologo /E %s /T %s /Fo %s %s",
+ entryPoint.constData(),
+ typeArg.constData(),
+ tempOutFileName.constData(),
+ inFileName.constData());
+ qDebug("%s", qPrintable(cmd));
+ QByteArray output;
+ QByteArray errorOutput;
+ bool success = runProcess(cmd, &output, &errorOutput);
+ if (!success) {
+ if (!output.isEmpty() || !errorOutput.isEmpty()) {
+ qDebug("%s\n%s",
+ qPrintable(output.constData()),
+ qPrintable(errorOutput.constData()));
+ }
+ return 1;
+ }
+ f.setFileName(tmpOut);
+ if (!f.open(QIODevice::ReadOnly)) {
+ qWarning("Failed to open fxc output %s", qPrintable(tmpOut));
+ return 1;
+ }
+ const QByteArray bytecode = f.readAll();
+ f.close();
+
+ QRhiShaderKey dxbcKey = k;
+ dxbcKey.setSource(QRhiShaderKey::DxbcShader);
+ QRhiShaderCode dxbcShader(bytecode, s.entryPoint());
+ bs.setShader(dxbcKey, dxbcShader);
+ bs.removeShader(k);
+ }
+ }
+ }
+
+ if (cmdLineParser.isSet(mtllibOption)) {
+ QTemporaryDir tempDir;
+ if (!tempDir.isValid()) {
+ qWarning("Failed to create temporary directory");
+ return 1;
+ }
+ auto skeys = bs.availableShaders();
+ for (const QRhiShaderKey &k : skeys) {
+ if (k.source() == QRhiShaderKey::MslShader) {
+ QRhiShaderCode s = bs.shader(k);
+
+ const QString tmpIn = tempDir.path() + QLatin1String("/qsb_msl_temp.metal");
+ const QString tmpInterm = tempDir.path() + QLatin1String("/qsb_msl_temp_air");
+ const QString tmpOut = tempDir.path() + QLatin1String("/qsb_msl_temp_out");
+ QFile f(tmpIn);
+ if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
+ qWarning("Failed to create temporary file");
+ return 1;
+ }
+ f.write(s.shader());
+ f.close();
+
+ const QByteArray inFileName = QDir::toNativeSeparators(tmpIn).toUtf8();
+ const QByteArray tempIntermediateFileName = QDir::toNativeSeparators(tmpInterm).toUtf8();
+ qDebug("About to invoke xcrun with metal and metallib.\n"
+ " qsb is set up for XCode 10. For earlier versions the -c argument may need to be removed.\n"
+ " If getting unable to find utility \"metal\", do xcode-select --switch /Applications/Xcode.app/Contents/Developer");
+ QString cmd = QString::asprintf("xcrun -sdk macosx metal -c %s -o %s",
+ inFileName.constData(),
+ tempIntermediateFileName.constData());
+ qDebug("%s", qPrintable(cmd));
+ QByteArray output;
+ QByteArray errorOutput;
+ bool success = runProcess(cmd, &output, &errorOutput);
+ if (!success) {
+ if (!output.isEmpty() || !errorOutput.isEmpty()) {
+ qDebug("%s\n%s",
+ qPrintable(output.constData()),
+ qPrintable(errorOutput.constData()));
+ }
+ return 1;
+ }
+
+ const QByteArray tempOutFileName = QDir::toNativeSeparators(tmpOut).toUtf8();
+ cmd = QString::asprintf("xcrun -sdk macosx metallib %s -o %s",
+ tempIntermediateFileName.constData(),
+ tempOutFileName.constData());
+ qDebug("%s", qPrintable(cmd));
+ output.clear();
+ errorOutput.clear();
+ success = runProcess(cmd, &output, &errorOutput);
+ if (!success) {
+ if (!output.isEmpty() || !errorOutput.isEmpty()) {
+ qDebug("%s\n%s",
+ qPrintable(output.constData()),
+ qPrintable(errorOutput.constData()));
+ }
+ return 1;
+ }
+
+ f.setFileName(tmpOut);
+ if (!f.open(QIODevice::ReadOnly)) {
+ qWarning("Failed to open xcrun metallib output %s", qPrintable(tmpOut));
+ return 1;
+ }
+ const QByteArray bytecode = f.readAll();
+ f.close();
+
+ QRhiShaderKey mtlKey = k;
+ mtlKey.setSource(QRhiShaderKey::MetalLibShader);
+ QRhiShaderCode mtlShader(bytecode, s.entryPoint());
+ bs.setShader(mtlKey, mtlShader);
+ bs.removeShader(k);
+ }
+ }
+ }
+
+ if (cmdLineParser.isSet(outputOption))
+ writeToFile(bs.serialized(), cmdLineParser.value(outputOption));
+ }
+
+ return 0;
+}