diff options
Diffstat (limited to 'src/tools/qvkgen/qvkgen.cpp')
-rw-r--r-- | src/tools/qvkgen/qvkgen.cpp | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/src/tools/qvkgen/qvkgen.cpp b/src/tools/qvkgen/qvkgen.cpp new file mode 100644 index 0000000000..059f9413cb --- /dev/null +++ b/src/tools/qvkgen/qvkgen.cpp @@ -0,0 +1,530 @@ +/**************************************************************************** +** +** 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/qvector.h> +#include <QtCore/qfile.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qxmlstream.h> + +class VkSpecParser +{ +public: + bool parse(); + + struct TypedName { + QString name; + QString type; + QString typeSuffix; + }; + + struct Command { + TypedName cmd; + QVector<TypedName> args; + bool deviceLevel; + }; + + QVector<Command> commands() const { return m_commands; } + + void setFileName(const QString &fn) { m_fn = fn; } + +private: + void skip(); + void parseCommands(); + Command parseCommand(); + TypedName parseParamOrProto(const QString &tag); + QString parseName(); + + QFile m_file; + QXmlStreamReader m_reader; + QVector<Command> m_commands; + QString m_fn; +}; + +bool VkSpecParser::parse() +{ + m_file.setFileName(m_fn); + if (!m_file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning("Failed to open %s", qPrintable(m_file.fileName())); + return false; + } + + m_reader.setDevice(&m_file); + while (!m_reader.atEnd()) { + m_reader.readNext(); + if (m_reader.isStartElement()) { + if (m_reader.name() == QStringLiteral("commands")) + parseCommands(); + } + } + + return true; +} + +void VkSpecParser::skip() +{ + QString tag = m_reader.name().toString(); + while (!m_reader.atEnd()) { + m_reader.readNext(); + if (m_reader.isEndElement() && m_reader.name() == tag) + break; + } +} + +void VkSpecParser::parseCommands() +{ + m_commands.clear(); + + while (!m_reader.atEnd()) { + m_reader.readNext(); + if (m_reader.isEndElement() && m_reader.name() == QStringLiteral("commands")) + return; + if (m_reader.isStartElement() && m_reader.name() == "command") + m_commands.append(parseCommand()); + } +} + +VkSpecParser::Command VkSpecParser::parseCommand() +{ + Command c; + + while (!m_reader.atEnd()) { + m_reader.readNext(); + if (m_reader.isEndElement() && m_reader.name() == QStringLiteral("command")) + break; + if (m_reader.isStartElement()) { + const QString protoStr = QStringLiteral("proto"); + const QString paramStr = QStringLiteral("param"); + if (m_reader.name() == protoStr) { + c.cmd = parseParamOrProto(protoStr); + } else if (m_reader.name() == paramStr) { + c.args.append(parseParamOrProto(paramStr)); + } else { + skip(); + } + } + } + + c.deviceLevel = false; + if (!c.args.isEmpty()) { + QStringList dispatchableDeviceAndChildTypes { + QStringLiteral("VkDevice"), + QStringLiteral("VkQueue"), + QStringLiteral("VkCommandBuffer") + }; + if (dispatchableDeviceAndChildTypes.contains(c.args[0].type) + && c.cmd.name != QStringLiteral("vkGetDeviceProcAddr")) + { + c.deviceLevel = true; + } + } + + return c; +} + +VkSpecParser::TypedName VkSpecParser::parseParamOrProto(const QString &tag) +{ + TypedName t; + + while (!m_reader.atEnd()) { + m_reader.readNext(); + if (m_reader.isEndElement() && m_reader.name() == tag) + break; + if (m_reader.isStartElement()) { + if (m_reader.name() == QStringLiteral("name")) { + t.name = parseName(); + } else if (m_reader.name() != QStringLiteral("type")) { + skip(); + } + } else { + QStringRef text = m_reader.text().trimmed(); + if (!text.isEmpty()) { + if (text.startsWith(QLatin1Char('['))) { + t.typeSuffix += text; + } else { + if (!t.type.isEmpty()) + t.type += QLatin1Char(' '); + t.type += text; + } + } + } + } + + return t; +} + +QString VkSpecParser::parseName() +{ + QString name; + while (!m_reader.atEnd()) { + m_reader.readNext(); + if (m_reader.isEndElement() && m_reader.name() == QStringLiteral("name")) + break; + name += m_reader.text(); + } + return name.trimmed(); +} + +QString funcSig(const VkSpecParser::Command &c, const char *className = nullptr) +{ + QString s; + s.sprintf("%s %s%s%s", qPrintable(c.cmd.type), + (className ? className : ""), (className ? "::" : ""), + qPrintable(c.cmd.name)); + if (!c.args.isEmpty()) { + s += QLatin1Char('('); + bool first = true; + for (const VkSpecParser::TypedName &a : c.args) { + QString argStr; + argStr.sprintf("%s%s%s%s", qPrintable(a.type), (a.type.endsWith(QLatin1Char('*')) ? "" : " "), + qPrintable(a.name), qPrintable(a.typeSuffix)); + if (!first) + s += QStringLiteral(", "); + else + first = false; + s += argStr; + } + s += QLatin1Char(')'); + } + return s; +} + +QString funcCall(const VkSpecParser::Command &c, int idx) +{ + QString s; + // template: + // [return] reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(d_ptr->m_funcs[0])(instance, pPhysicalDeviceCount, pPhysicalDevices); + s.sprintf("%sreinterpret_cast<PFN_%s>(d_ptr->m_funcs[%d])", + (c.cmd.type == QStringLiteral("void") ? "" : "return "), + qPrintable(c.cmd.name), + idx); + if (!c.args.isEmpty()) { + s += QLatin1Char('('); + bool first = true; + for (const VkSpecParser::TypedName &a : c.args) { + if (!first) + s += QStringLiteral(", "); + else + first = false; + s += a.name; + } + s += QLatin1Char(')'); + } + return s; +} + +class Preamble +{ +public: + QByteArray get(const QString &fn); + +private: + QByteArray m_str; +} preamble; + +QByteArray Preamble::get(const QString &fn) +{ + if (!m_str.isEmpty()) + return m_str; + + QFile f(fn); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning("Failed to open %s", qPrintable(fn)); + return m_str; + } + + m_str = f.readAll(); + m_str.replace("FOO", "QtGui"); + m_str += "\n// This file is automatically generated by qvkgen. Do not edit.\n"; + + return m_str; +} + +bool genVulkanFunctionsH(const QVector<VkSpecParser::Command> &commands, const QString &licHeaderFn, const QString &outputBase) +{ + QFile f(outputBase + QStringLiteral(".h")); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { + qWarning("Failed to write %s", qPrintable(f.fileName())); + return false; + } + + static const char *s = +"%s\n" +"#ifndef QVULKANFUNCTIONS_H\n" +"#define QVULKANFUNCTIONS_H\n" +"\n" +"#include <QtGui/qtguiglobal.h>\n" +"\n" +"#if QT_CONFIG(vulkan) || defined(Q_CLANG_QDOC)\n" +"\n" +"#ifndef VK_NO_PROTOTYPES\n" +"#define VK_NO_PROTOTYPES\n" +"#endif\n" +"#include <vulkan/vulkan.h>\n" +"\n" +"#include <QtCore/qscopedpointer.h>\n" +"\n" +"QT_BEGIN_NAMESPACE\n" +"\n" +"class QVulkanInstance;\n" +"class QVulkanFunctionsPrivate;\n" +"class QVulkanDeviceFunctionsPrivate;\n" +"\n" +"class Q_GUI_EXPORT QVulkanFunctions\n" +"{\n" +"public:\n" +" ~QVulkanFunctions();\n" +"\n" +"%s\n" +"private:\n" +" Q_DISABLE_COPY(QVulkanFunctions)\n" +" QVulkanFunctions(QVulkanInstance *inst);\n" +"\n" +" QScopedPointer<QVulkanFunctionsPrivate> d_ptr;\n" +" friend class QVulkanInstance;\n" +"};\n" +"\n" +"class Q_GUI_EXPORT QVulkanDeviceFunctions\n" +"{\n" +"public:\n" +" ~QVulkanDeviceFunctions();\n" +"\n" +"%s\n" +"private:\n" +" Q_DISABLE_COPY(QVulkanDeviceFunctions)\n" +" QVulkanDeviceFunctions(QVulkanInstance *inst, VkDevice device);\n" +"\n" +" QScopedPointer<QVulkanDeviceFunctionsPrivate> d_ptr;\n" +" friend class QVulkanInstance;\n" +"};\n" +"\n" +"QT_END_NAMESPACE\n" +"\n" +"#endif // QT_CONFIG(vulkan) || defined(Q_CLANG_QDOC)\n" +"\n" +"#endif // QVULKANFUNCTIONS_H\n"; + + QString instCmdStr; + QString devCmdStr; + for (const VkSpecParser::Command &c : commands) { + QString *dst = c.deviceLevel ? &devCmdStr : &instCmdStr; + *dst += QStringLiteral(" "); + *dst += funcSig(c); + *dst += QStringLiteral(";\n"); + } + + QString str; + str.sprintf(s, preamble.get(licHeaderFn).constData(), instCmdStr.toUtf8().constData(), devCmdStr.toUtf8().constData()); + + f.write(str.toUtf8()); + + return true; +} + +bool genVulkanFunctionsPH(const QVector<VkSpecParser::Command> &commands, const QString &licHeaderFn, const QString &outputBase) +{ + QFile f(outputBase + QStringLiteral("_p.h")); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { + qWarning("Failed to write %s", qPrintable(f.fileName())); + return false; + } + + static const char *s = +"%s\n" +"#ifndef QVULKANFUNCTIONS_P_H\n" +"#define QVULKANFUNCTIONS_P_H\n" +"\n" +"//\n" +"// W A R N I N G\n" +"// -------------\n" +"//\n" +"// This file is not part of the Qt API. It exists purely as an\n" +"// implementation detail. This header file may change from version to\n" +"// version without notice, or even be removed.\n" +"//\n" +"// We mean it.\n" +"//\n" +"\n" +"#include \"qvulkanfunctions.h\"\n" +"\n" +"QT_BEGIN_NAMESPACE\n" +"\n" +"class QVulkanInstance;\n" +"\n" +"class QVulkanFunctionsPrivate\n" +"{\n" +"public:\n" +" QVulkanFunctionsPrivate(QVulkanInstance *inst);\n" +"\n" +" PFN_vkVoidFunction m_funcs[%d];\n" +"};\n" +"\n" +"class QVulkanDeviceFunctionsPrivate\n" +"{\n" +"public:\n" +" QVulkanDeviceFunctionsPrivate(QVulkanInstance *inst, VkDevice device);\n" +"\n" +" PFN_vkVoidFunction m_funcs[%d];\n" +"};\n" +"\n" +"QT_END_NAMESPACE\n" +"\n" +"#endif // QVULKANFUNCTIONS_P_H\n"; + + const int devLevelCount = std::count_if(commands.cbegin(), commands.cend(), + [](const VkSpecParser::Command &c) { return c.deviceLevel; }); + const int instLevelCount = commands.count() - devLevelCount; + + QString str; + str.sprintf(s, preamble.get(licHeaderFn).constData(), instLevelCount, devLevelCount); + + f.write(str.toUtf8()); + + return true; +} + +bool genVulkanFunctionsPC(const QVector<VkSpecParser::Command> &commands, const QString &licHeaderFn, const QString &outputBase) +{ + QFile f(outputBase + QStringLiteral("_p.cpp")); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { + qWarning("Failed to write %s", qPrintable(f.fileName())); + return false; + } + + static const char *s = +"%s\n" +"#include \"qvulkanfunctions_p.h\"\n" +"#include \"qvulkaninstance.h\"\n" +"\n" +"QT_BEGIN_NAMESPACE\n" +"\n%s" +"QVulkanFunctionsPrivate::QVulkanFunctionsPrivate(QVulkanInstance *inst)\n" +"{\n" +" static const char *funcNames[] = {\n" +"%s\n" +" };\n" +" for (int i = 0; i < %d; ++i) {\n" +" m_funcs[i] = inst->getInstanceProcAddr(funcNames[i]);\n" +" if (!m_funcs[i])\n" +" qWarning(\"QVulkanFunctions: Failed to resolve %%s\", funcNames[i]);\n" +" }\n" +"}\n" +"\n%s" +"QVulkanDeviceFunctionsPrivate::QVulkanDeviceFunctionsPrivate(QVulkanInstance *inst, VkDevice device)\n" +"{\n" +" QVulkanFunctions *f = inst->functions();\n" +" Q_ASSERT(f);\n\n" +" static const char *funcNames[] = {\n" +"%s\n" +" };\n" +" for (int i = 0; i < %d; ++i) {\n" +" m_funcs[i] = f->vkGetDeviceProcAddr(device, funcNames[i]);\n" +" if (!m_funcs[i])\n" +" qWarning(\"QVulkanDeviceFunctions: Failed to resolve %%s\", funcNames[i]);\n" +" }\n" +"}\n" +"\n" +"QT_END_NAMESPACE\n"; + + QString devCmdWrapperStr; + QString instCmdWrapperStr; + int devIdx = 0; + int instIdx = 0; + QString devCmdNamesStr; + QString instCmdNamesStr; + + for (int i = 0; i < commands.count(); ++i) { + QString *dst = commands[i].deviceLevel ? &devCmdWrapperStr : &instCmdWrapperStr; + int *idx = commands[i].deviceLevel ? &devIdx : &instIdx; + *dst += funcSig(commands[i], commands[i].deviceLevel ? "QVulkanDeviceFunctions" : "QVulkanFunctions"); + *dst += QString(QStringLiteral("\n{\n Q_ASSERT(d_ptr->m_funcs[%1]);\n ")).arg(*idx); + *dst += funcCall(commands[i], *idx); + *dst += QStringLiteral(";\n}\n\n"); + ++*idx; + + dst = commands[i].deviceLevel ? &devCmdNamesStr : &instCmdNamesStr; + *dst += QStringLiteral(" \""); + *dst += commands[i].cmd.name; + *dst += QStringLiteral("\",\n"); + } + + if (devCmdNamesStr.count() > 2) + devCmdNamesStr = devCmdNamesStr.left(devCmdNamesStr.count() - 2); + if (instCmdNamesStr.count() > 2) + instCmdNamesStr = instCmdNamesStr.left(instCmdNamesStr.count() - 2); + + QString str; + str.sprintf(s, preamble.get(licHeaderFn).constData(), + instCmdWrapperStr.toUtf8().constData(), instCmdNamesStr.toUtf8().constData(), instIdx, + devCmdWrapperStr.toUtf8().constData(), devCmdNamesStr.toUtf8().constData(), commands.count() - instIdx); + + f.write(str.toUtf8()); + + return true; +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + VkSpecParser parser; + + if (argc < 4) { + qWarning("Usage: qvkgen input_vk_xml input_license_header output_base\n" + " For example: qvkgen vulkan/vk.xml vulkan/qvulkanfunctions.header vulkan/qvulkanfunctions"); + return 1; + } + + parser.setFileName(QString::fromUtf8(argv[1])); + + if (!parser.parse()) + return 1; + + QVector<VkSpecParser::Command> commands = parser.commands(); + QStringList ignoredFuncs { + QStringLiteral("vkCreateInstance"), + QStringLiteral("vkDestroyInstance"), + QStringLiteral("vkGetInstanceProcAddr") + }; + + // Filter out extensions and unwanted functions. + // The check for the former is rather simplistic for now: skip if the last letter is uppercase... + for (int i = 0; i < commands.count(); ++i) { + QString name = commands[i].cmd.name; + QChar c = name[name.count() - 1]; + if (c.isUpper() || ignoredFuncs.contains(name)) + commands.remove(i--); + } + + QString licenseHeaderFileName = QString::fromUtf8(argv[2]); + QString outputBase = QString::fromUtf8(argv[3]); + genVulkanFunctionsH(commands, licenseHeaderFileName, outputBase); + genVulkanFunctionsPH(commands, licenseHeaderFileName, outputBase); + genVulkanFunctionsPC(commands, licenseHeaderFileName, outputBase); + + return 0; +} |